Single Entity with Multiple Submeshes in Entities Graphics 1.4.16

Hello!

I’m using Unity 2022.3.62f3 with Entities 1.4.3 and Entities Graphics 1.4.16 , and I’m struggling to get multiple submeshes with different materials to render as a single Entity — both at runtime and via baking.

According to the changelog entry [1.1.0-exp.1] - 2023-09-18 :

“Add support for multiple submeshes per entity. Stop creating one entity per submesh unless skinning is used.”
Changelog | Entities Graphics | 1.4.16

This strongly suggests it’s possible — and indeed preferred — to have one Entity for multi-submesh meshes. However, I have been unable to find any working code examples or documentation covering this (the runtime-entity-creation page only shows a single-material case).

What I’ve tried

1. Runtime creation

Example code
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Rendering;
using Unity.Transforms;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;

[AddComponentMenu("Entities/Graphics/Multi Material Entity Spawner")]
public class MultiMaterialEntitySpawner : MonoBehaviour
{
    [Header("Mesh Configuration")]
    public Mesh sourceMesh; // Mesh with multiple submeshes
    public Material[] materials; // Array of materials (one per submesh)

    [Header("Rendering Settings")]
    public ShadowCastingMode shadowCastingMode = ShadowCastingMode.On;
    public bool receiveShadows = true;

    [Header("Entity Settings")]
    public int spawnCount = 10;
    public float spawnRadius = 5.0f;

    private EntityManager _entityManager;
    private Entity _prototypeEntity;

    private void Start()
    {
        if (sourceMesh == null || materials == null || materials.Length == 0)
        {
            Debug.LogError("Please assign a mesh and at least one material in the inspector.");
            return;
        }

        int subMeshCount = sourceMesh.subMeshCount;
        if (materials.Length < subMeshCount)
        {
            Debug.LogWarning($"Mesh has {subMeshCount} submeshes but only {materials.Length} materials provided. " +
                "Some submeshes may not render correctly.");
        }

        _entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;

        // Create prototype with multiple materials
        CreateMultiMaterialPrototype();

        // Spawn entities
        SpawnEntities();

        // Destroy prototype after use
        if (_prototypeEntity != Entity.Null)
        {
            _entityManager.DestroyEntity(_prototypeEntity);
        }
    }

    private void CreateMultiMaterialPrototype()
    {
        // Check number of submeshes
        int subMeshCount = sourceMesh.subMeshCount;

        // Create RenderMeshArray with multiple materials
        // For a mesh with multiple submeshes, materials must match the number of submeshes
        Material[] actualMaterials = new Material[subMeshCount];
        for (int i = 0; i < subMeshCount; i++)
        {
            if (i < materials.Length)
            {
                actualMaterials[i] = materials[i];
            }
            else
            {
                actualMaterials[i] = materials[0]; // Use first material as fallback
                Debug.LogWarning($"No material provided for submesh {i}, using first material as fallback");
            }
        }

        var renderMeshArray = new RenderMeshArray(actualMaterials, new Mesh[] { sourceMesh });

        // Correct creation of RenderMeshDescription for version 1.4.x
        // In version 1.4.x, RenderMeshDescription accepts a Renderer, not RenderFilterSettings
        var renderer = gameObject.GetComponent<Renderer>();
        if (renderer == null)
        {
            renderer = gameObject.AddComponent<MeshRenderer>();
        }

        // Configure renderer for correct rendering settings
        renderer.shadowCastingMode = shadowCastingMode;
        renderer.receiveShadows = receiveShadows;

        // Create RenderMeshDescription from renderer
        var renderMeshDescription = new RenderMeshDescription(renderer);

        // Create prototype
        _prototypeEntity = _entityManager.CreateEntity();

        // Correct AddComponents call for version 1.4.x
        // MaterialMeshInfo.FromRenderMeshArrayIndices accepts only two parameters: materialIndex and meshIndex
        RenderMeshUtility.AddComponents(
            _prototypeEntity,
            _entityManager,
            renderMeshDescription,
            renderMeshArray,
            MaterialMeshInfo.FromRenderMeshArrayIndices(0, 0) // materialIndex=0, meshIndex=0
        );

        // Add transform components
        _entityManager.AddComponentData(_prototypeEntity, new LocalTransform
        {
            Position = float3.zero,
            Rotation = quaternion.identity,
            Scale = 1.0f
        });

        _entityManager.AddComponentData(_prototypeEntity, new LocalToWorld());

        Debug.Log($"Created prototype entity with {subMeshCount} submeshes and {actualMaterials.Length} materials");
    }

    private void SpawnEntities()
    {
        using (var ecb = new EntityCommandBuffer(Allocator.TempJob))
        {
            var spawnJob = new SpawnJob
            {
                Prototype = _prototypeEntity,
                SpawnCount = spawnCount,
                SpawnRadius = spawnRadius,
                Ecb = ecb.AsParallelWriter()
            };

            var spawnHandle = spawnJob.Schedule(spawnCount, 64);
            spawnHandle.Complete();

            ecb.Playback(_entityManager);
            ecb.Dispose();
        }
    }

    [GenerateTestsForBurstCompatibility]
    private struct SpawnJob : IJobParallelFor
    {
        public Entity Prototype;
        public int SpawnCount;
        public float SpawnRadius;
        public EntityCommandBuffer.ParallelWriter Ecb;

        public void Execute(int index)
        {
            Entity entity = Ecb.Instantiate(index, Prototype);

            // Calculate position on a circle
            float angle = (index / (float)SpawnCount) * 360f;
            float x = Mathf.Cos(angle * Mathf.Deg2Rad) * SpawnRadius;
            float z = Mathf.Sin(angle * Mathf.Deg2Rad) * SpawnRadius;

            Ecb.SetComponent(index, entity, new LocalTransform
            {
                Position = new float3(x, 0, z),
                Rotation = quaternion.Euler(0, angle, 0),
                Scale = 1.0f
            });
        }
    }

    // For visual debugging in the editor
    private void OnDrawGizmosSelected()
    {
        if (Application.isPlaying || sourceMesh == null)
            return;

        Gizmos.color = Color.yellow;
        Gizmos.DrawWireSphere(transform.position, spawnRadius);

        // Display number of submeshes
        Handles.Label(transform.position + Vector3.up * 2,
            $"Submeshes: {(sourceMesh != null ? sourceMesh.subMeshCount.ToString() : "0")}\n" +
            $"Materials: {(materials != null ? materials.Length.ToString() : "0")}");
    }

    [ContextMenu("Validate Mesh Submeshes")]
    private void ValidateMeshSubmeshes()
    {
        if (sourceMesh == null)
        {
            Debug.LogWarning("No mesh assigned.");
            return;
        }

        Debug.Log($"Mesh '{sourceMesh.name}' has {sourceMesh.subMeshCount} submeshes.");

        for (int i = 0; i < sourceMesh.subMeshCount; i++)
        {
            var subMesh = sourceMesh.GetSubMesh(i);
            Debug.Log($"Submesh {i}: {subMesh.indexCount} triangles, topology: {subMesh.topology}");
        }
    }
}

→ Only the first submesh renders (with first material).

2. Baking via SubScene

I placed a Prefab to a Subscene. Entity Debugger shows 2 separate Entities were created (one per submesh).
This contradicts the changelog — unless I’m misunderstanding something (e.g., perhaps ENABLE_MESH_RENDERER_SUBMESH_DATA_SHARING define or import settings are required?).

Core questions

  1. Is it actually possible today (in 1.4.16) to get a single Entity with multiple submeshes — both for static meshes and animated ones?
  2. If yes:
  • What is the exact API usage? (e.g., special MaterialMeshInfo , RenderMeshArray construction, flags?)
  • Are there hidden requirements (e.g., mesh import settings, defines, SRP compatibility)?
  1. For animated characters with 4–6 materials (e.g. skin, cloth, metal, faction-colored decal):
  • Must we really manage 4–6 Entities per character?
  • How do people handle runtime material changes (e.g. faction recoloring) across such a group efficiently?

I’m hoping to avoid the “one entity per submesh” fallback.
Any working example would be hugely appreciated!

Best regards.

For static entities, you will need the scripting define you identified to enable baking entities with submesh sharing. At runtime, you’ll need to explicitly set up a RenderMeshArray yourself to make use of MaterialMeshIndex. A MaterialMeshInfo has a mode that allows it to reference a span of these.

For animated characters, you will need a third-party package to do this. All the scalable third-party animation solutions replace Entities Graphics skinned mesh rendering with a custom solution, because the Entities Graphics implementation is experimental and quite poor for scalability. I’m the author of one of these packages, by the way. Latios-Framework-Documentation/Kinemation Animation and Rendering/README.md at main · Dreaming381/Latios-Framework-Documentation · GitHub

Thank you for the reply!

I can confirm that with ENABLE_MESH_RENDERER_SUBMESH_DATA_SHARING defined, I’m able to create a single Entity with multiple Submeshes when using Prefabs through Subscenes. However, I’m still unable to achieve the same result at runtime.

Here’s my current implementation that’s not working as expected (only renders the first submesh):

Example code
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Rendering;
using Unity.Transforms;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;

[AddComponentMenu("Entities/Graphics/Multi Submesh Entity")]
public class MultiSubmeshEntity : MonoBehaviour
{
    public Mesh sourceMesh; // Mesh with multiple submeshes
    public Material[] materials; // Materials for each submesh

    [Header("Debug Settings")]
    public bool logDetailedInfo = true;
    public bool validateSetupOnStart = true;

    private void Start()
    {
        LogDiagnostic($"=== Starting MultiSubmeshEntity ===");
        LogDiagnostic($"Unity Version: {Application.unityVersion}");
        LogDiagnostic($"Entities Package: 1.4.3");
        LogDiagnostic($"Entities Graphics Package: 1.4.16");

        CheckSubmeshDataSharingDefine();

        if (validateSetupOnStart)
        {
            if (!ValidateMeshAndMaterials())
            {
                LogError("Validation failed! Check console for details.");
                return;
            }
        }

        if (sourceMesh == null || materials == null || materials.Length == 0)
        {
            LogError("Please assign a mesh and materials in the inspector.");
            return;
        }

        var em = World.DefaultGameObjectInjectionWorld.EntityManager;
        Entity prototype = Entity.Null;

        try
        {
            // Create RenderMeshArray with MaterialMeshIndex[] - KEY FOR MULTI-SUBMESH
            var renderMeshArray = CreateRenderMeshArrayWithMaterialMeshIndices();

            // Create prototype entity
            prototype = em.CreateEntity();
            LogDiagnostic($"Prototype entity created: {prototype}");

            // Setup rendering components
            SetupRenderingComponents(em, prototype, renderMeshArray);

            // Add transform components
            SetupTransformComponents(em, prototype);

            LogSuccess($"✅ Entity created successfully with {sourceMesh.subMeshCount} submeshes");

            // Create copies for demonstration
            SpawnEntityCopies(em, prototype);

        }
        catch (System.Exception ex)
        {
            LogError($"❌ Critical error: {ex.Message}");
            LogError($"Stack trace: {ex.StackTrace}");
        }
        finally
        {
            // Clean up prototype
            if (prototype != Entity.Null && em.Exists(prototype))
            {
                LogDiagnostic($"Destroying prototype entity: {prototype}");
                em.DestroyEntity(prototype);
            }
        }
    }

    private RenderMeshArray CreateRenderMeshArrayWithMaterialMeshIndices()
    {
        int subMeshCount = sourceMesh.subMeshCount;
        LogDiagnostic($"Mesh has {subMeshCount} submeshes");
        LogDiagnostic($"Available materials: {materials.Length}");

        // Check material/submesh correspondence
        if (materials.Length < subMeshCount)
        {
            LogWarning($"⚠️ Warning: Only {materials.Length} materials provided for {subMeshCount} submeshes");
            LogWarning($"Additional submeshes will use the first material as fallback");
        }

        // 1. Create arrays of materials and meshes
        Material[] actualMaterials = new Material[subMeshCount];
        Mesh[] actualMeshes = new Mesh[1]; // All submeshes use the same mesh
        actualMeshes[0] = sourceMesh; // One mesh for all submeshes

        for (int i = 0; i < subMeshCount; i++)
        {
            // Assign materials
            if (i < materials.Length && materials[i] != null)
            {
                actualMaterials[i] = materials[i];
                LogDiagnostic($"✓ Submesh {i}: Material '{materials[i].name}' assigned");
            }
            else
            {
                actualMaterials[i] = materials[0]; // Fallback to first material
                LogWarning($"⚠️ Submesh {i}: Using fallback material '{materials[0].name}'");
            }
        }

        // 2. CREATE MaterialMeshIndex[] - KEY FOR MULTI-SUBMESH
        MaterialMeshIndex[] materialMeshIndices = new MaterialMeshIndex[subMeshCount];

        for (int i = 0; i < subMeshCount; i++)
        {
            materialMeshIndices[i] = new MaterialMeshIndex
            {
                MaterialIndex = i,    // Material index in actualMaterials
                MeshIndex = 0,        // Mesh index in actualMeshes (always 0 for single mesh)
                SubMeshIndex = i
            };

            LogDiagnostic($"✓ MaterialMeshIndex[{i}]: MaterialIndex={i}, MeshIndex=0");
        }

        // 3. CREATE RenderMeshArray WITH THIRD PARAMETER
        var renderMeshArray = new RenderMeshArray(actualMaterials, actualMeshes, materialMeshIndices);

        LogDiagnostic($"✓ RenderMeshArray created successfully");
        LogDiagnostic($"- Materials count: {actualMaterials.Length}");
        LogDiagnostic($"- Meshes count: {actualMeshes.Length}");
        LogDiagnostic($"- MaterialMeshIndices count: {materialMeshIndices.Length}");

        return renderMeshArray;
    }

    private void SetupRenderingComponents(EntityManager em, Entity entity, RenderMeshArray renderMeshArray)
    {
        // Create RenderMeshDescription with settings
        var renderMeshDescription = new RenderMeshDescription(
            layer: gameObject.layer,
            shadowCastingMode: ShadowCastingMode.On,
            receiveShadows: true,
            lightProbeUsage: LightProbeUsage.BlendProbes
        );

        LogDiagnostic($"✓ RenderMeshDescription created");
        LogDiagnostic($"- Layer: {gameObject.layer}");
        LogDiagnostic($"- ShadowCastingMode: {ShadowCastingMode.On}");
        LogDiagnostic($"- ReceiveShadows: true");

        // 4. CREATE MaterialMeshInfo WITH PROPER INDICES
        // IMPORTANT: For runtime entities, MaterialMeshInfo.FromRenderMeshArrayIndices()
        // will create negative indices (-1, -1) which is CORRECT behavior
        // This indicates array indices rather than literal material/mesh IDs
        int subMeshCount = sourceMesh.subMeshCount;

        MaterialMeshInfo materialMeshInfo;

        if (subMeshCount > 1)
        {
            // For multiple submeshes, use submeshIndex parameter
            materialMeshInfo = MaterialMeshInfo.FromRenderMeshArrayIndices(
                materialIndexInRenderMeshArray: 0,
                meshIndexInRenderMeshArray: 0,
                submeshIndex: 0
            );

            LogDiagnostic($"✓ MaterialMeshInfo created for multi-submesh rendering");
            LogDiagnostic($"- Material index: 0, Mesh index: 0, Submesh index: 0");
            LogDiagnostic($"- Expected MaterialMeshInfo values: Material={materialMeshInfo.Material}, Mesh={materialMeshInfo.Mesh}, SubMesh={materialMeshInfo.SubMesh}");
            LogDiagnostic($"- Note: Negative values are CORRECT for runtime entities (indicates array indices)");
        }
        else
        {
            // For single submesh use standard method
            materialMeshInfo = MaterialMeshInfo.FromRenderMeshArrayIndices(0, 0);
            LogDiagnostic($"✓ MaterialMeshInfo created for single submesh: FromRenderMeshArrayIndices(0, 0)");
            LogDiagnostic($"- Expected MaterialMeshInfo values: Material={materialMeshInfo.Material}, Mesh={materialMeshInfo.Mesh}");
        }

        // 5. ADD RENDERING COMPONENTS
        RenderMeshUtility.AddComponents(
            entity,
            em,
            renderMeshDescription,
            renderMeshArray,
            materialMeshInfo
        );

        LogDiagnostic($"✓ RenderMeshUtility.AddComponents succeeded");

        // 6. SIMPLE COMPONENT CHECK
        if (logDetailedInfo)
        {
            CheckEntityComponents(em, entity);
        }
    }

    private void CheckEntityComponents(EntityManager em, Entity entity)
    {
        // Check main rendering components
        if (em.HasComponent<RenderBounds>(entity))
        {
            LogDiagnostic($"✓ Entity has RenderBounds component");
        }

        if (em.HasComponent<LocalToWorld>(entity))
        {
            LogDiagnostic($"✓ Entity has LocalToWorld component");
        }

        // Check MaterialMeshInfo values - THIS IS CRITICAL FOR DIAGNOSIS
        if (em.HasComponent<MaterialMeshInfo>(entity))
        {
            var mmi = em.GetComponentData<MaterialMeshInfo>(entity);
            LogDiagnostic($"✓ MaterialMeshInfo component exists");
            LogDiagnostic($"- Material: {mmi.Material}, Mesh: {mmi.Mesh}, SubMesh: {mmi.SubMesh}");

            // Explain the values
            if (mmi.Material < 0 && mmi.Mesh < 0)
            {
                LogInfo($"ℹ️ Negative Material/Mesh values are CORRECT for runtime entities");
                LogInfo($"ℹ️ These indicate array indices in RenderMeshArray (not literal IDs)");
                LogInfo($"ℹ️ Prefabs show 0,0 because they use baked literal IDs, not array indices");
            }
            else
            {
                LogInfo($"ℹ️ Positive Material/Mesh values indicate literal IDs (typical for baked Prefabs)");
            }

            LogDiagnostic($"- HasMaterialMeshIndexRange: {mmi.HasMaterialMeshIndexRange}");
            if (mmi.HasMaterialMeshIndexRange)
            {
                LogDiagnostic($"- MaterialMeshIndexRange: start={mmi.MaterialMeshIndexRange.start}, length={mmi.MaterialMeshIndexRange.length}");
            }
        }
    }

    private void SetupTransformComponents(EntityManager em, Entity entity)
    {
        // Proper Vector3 to float3 conversion
        float3 position = transform.position;
        quaternion rotation = transform.rotation;
        float scale = transform.localScale.x; // Use uniform scale

        em.AddComponentData(entity, new LocalTransform
        {
            Position = position,
            Rotation = rotation,
            Scale = scale
        });

        em.AddComponent<LocalToWorld>(entity);

        LogDiagnostic($"✓ Transform components added");
    }

    private void SpawnEntityCopies(EntityManager em, Entity prototype)
    {
        const int copyCount = 5;
        LogDiagnostic($"--- Spawning {copyCount} entity copies ---");

        using (var ecb = new EntityCommandBuffer(Allocator.TempJob))
        {
            for (int i = 0; i < copyCount; i++)
            {
                Entity copy = ecb.Instantiate(prototype);

                // Position copies in a circle - use only float3
                float angle = (i / (float)copyCount) * 360f;
                float radius = 2.5f;
                float3 offset = new float3(
                    math.cos(math.radians(angle)) * radius,
                    0,
                    math.sin(math.radians(angle)) * radius
                );

                // Proper position conversion
                float3 basePosition = transform.position;
                float3 finalPosition = basePosition + offset;

                ecb.SetComponent(copy, new LocalTransform
                {
                    Position = finalPosition,
                    Rotation = quaternion.Euler(0, angle, 0),
                    Scale = transform.localScale.x // Uniform scale
                });
            }

            ecb.Playback(em);
            LogSuccess($"✓ {copyCount} entity copies spawned successfully");
        }
    }

    private bool ValidateMeshAndMaterials()
    {
        bool isValid = true;

        if (sourceMesh == null)
        {
            LogError("❌ Source mesh is not assigned!");
            isValid = false;
        }
        else
        {
            LogInfo($"=== Mesh Validation ===");
            LogInfo($"Mesh name: {sourceMesh.name}");
            LogInfo($"Vertex count: {sourceMesh.vertexCount}");
            LogInfo($"Submesh count: {sourceMesh.subMeshCount}");

            if (sourceMesh.subMeshCount == 0)
            {
                LogError("❌ Mesh has no submeshes! Check mesh import settings.");
                isValid = false;
            }
            else if (sourceMesh.subMeshCount == 1)
            {
                LogWarning("⚠️ Mesh has only 1 submesh. For multi-material rendering, mesh should have multiple submeshes.");
            }

            // Check each submesh
            for (int i = 0; i < sourceMesh.subMeshCount; i++)
            {
                var submesh = sourceMesh.GetSubMesh(i);
                LogInfo($"Submesh {i}:");
                LogInfo($"- Topology: {submesh.topology}");
                LogInfo($"- Index count: {submesh.indexCount}");
                LogInfo($"- Triangle count: {submesh.indexCount / 3}");
                LogInfo($"- First vertex: {submesh.firstVertex}");
                LogInfo($"- Vertex count: {submesh.vertexCount}");
            }
        }

        if (materials == null || materials.Length == 0)
        {
            LogError("❌ No materials assigned!");
            isValid = false;
        }
        else
        {
            LogInfo($"=== Materials Validation ===");
            LogInfo($"Materials assigned: {materials.Length}");

            for (int i = 0; i < materials.Length; i++)
            {
                if (materials[i] == null)
                {
                    LogError($"❌ Material at index {i} is null!");
                    isValid = false;
                }
                else
                {
                    LogInfo($"Material {i}: {materials[i].name}");
                    LogInfo($"- Shader: {materials[i].shader.name}");

                    // Check DOTS compatibility
                    bool isCompatible = materials[i].shader.name.Contains("Lit") ||
                                       materials[i].shader.name.Contains("Unlit") ||
                                       materials[i].shader.name.Contains("HDRP") ||
                                       materials[i].shader.name.Contains("URP");

                    if (!isCompatible)
                    {
                        LogWarning($"⚠️ Material '{materials[i].name}' may not be compatible with Entities Graphics");
                        LogWarning($"Shader '{materials[i].shader.name}' should be HDRP/Lit or URP/Lit for best results");
                    }
                }
            }
        }

        return isValid;
    }

    private void CheckSubmeshDataSharingDefine()
    {
        bool isDefined = false;
#if ENABLE_MESH_RENDERER_SUBMESH_DATA_SHARING
        isDefined = true;
#endif

        if (isDefined)
        {
            LogSuccess("✅ ENABLE_MESH_RENDERER_SUBMESH_DATA_SHARING is DEFINED");
        }
        else
        {
            LogError("❌ ENABLE_MESH_RENDERER_SUBMESH_DATA_SHARING is NOT DEFINED!");
            LogError("This define symbol is REQUIRED for multi-submesh rendering in runtime.");
            LogError("To fix this:");
            LogError("1. Go to Edit → Project Settings → Player");
            LogError("2. Find 'Scripting Define Symbols' for your platform");
            LogError("3. Add: ENABLE_MESH_RENDERER_SUBMESH_DATA_SHARING");
            LogError("4. Restart Unity Editor completely");
        }
    }

    // Logging helper methods
    private void LogDiagnostic(string message)
    {
        if (logDetailedInfo) Debug.Log($"<b>[DIAGNOSTIC] {message}</b>");
    }

    private void LogInfo(string message) => Debug.Log($"<color=cyan>[INFO] {message}</color>");
    private void LogWarning(string message) => Debug.LogWarning($"<color=yellow>[WARNING] {message}</color>");
    private void LogError(string message) => Debug.LogError($"<color=red>[ERROR] {message}</color>");
    private void LogSuccess(string message) => Debug.Log($"<color=green>[SUCCESS] {message}</color>");

    [ContextMenu("Full Validation")]
    private void FullValidation()
    {
        bool isValid = ValidateMeshAndMaterials();
        CheckSubmeshDataSharingDefine();

        if (isValid)
        {
            LogSuccess("✅ All validations passed!");
        }
        else
        {
            LogError("❌ Validation failed! Fix the issues above.");
        }
    }
}

My immediate need is just to get static meshes rendering properly with multiple materials on a single entity. For animated characters, it’s concerning that ECS doesn’t seem to handle multi-submesh entities well, but I can think later about third-party packages as a fix.

Line 170, you are using FromRenderMeshArrayIndices where I believe you want to use FromMaterialMeshIndexRange.

It works, thank you!

Here’s a working code example in case anyone needs it:

Code example
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Rendering;
using Unity.Transforms;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;

[AddComponentMenu("Entities/Graphics/Multi Submesh Entity")]
public class MultiSubmeshEntity : MonoBehaviour
{
    public Mesh sourceMesh; // Mesh with multiple submeshes
    public Material[] materials; // Materials for each submesh

    [Header("Debug Settings")]
    public bool logDetailedInfo = true;
    public bool validateSetupOnStart = true;

    private void Start()
    {
        LogDiagnostic($"=== Starting MultiSubmeshEntity ===");
        LogDiagnostic($"Unity Version: {Application.unityVersion}");
        LogDiagnostic($"Entities Package: 1.4.3");
        LogDiagnostic($"Entities Graphics Package: 1.4.16");

        CheckSubmeshDataSharingDefine();

        if (validateSetupOnStart)
        {
            if (!ValidateMeshAndMaterials())
            {
                LogError("Validation failed! Check console for details.");
                return;
            }
        }

        if (sourceMesh == null || materials == null || materials.Length == 0)
        {
            LogError("Please assign a mesh and materials in the inspector.");
            return;
        }

        var em = World.DefaultGameObjectInjectionWorld.EntityManager;
        Entity prototype = Entity.Null;

        try
        {
            // Create RenderMeshArray with MaterialMeshIndex[] - KEY FOR MULTI-SUBMESH
            var renderMeshArray = CreateRenderMeshArrayWithMaterialMeshIndices();

            // Create prototype entity
            prototype = em.CreateEntity();
            LogDiagnostic($"Prototype entity created: {prototype}");

            // Setup rendering components
            SetupRenderingComponents(em, prototype, renderMeshArray);

            // Add transform components
            SetupTransformComponents(em, prototype);

            LogSuccess($"✅ Entity created successfully with {sourceMesh.subMeshCount} submeshes");

            // Create copies for demonstration
            SpawnEntityCopies(em, prototype);

        }
        catch (System.Exception ex)
        {
            LogError($"❌ Critical error: {ex.Message}");
            LogError($"Stack trace: {ex.StackTrace}");
        }
        finally
        {
            // Clean up prototype
            if (prototype != Entity.Null && em.Exists(prototype))
            {
                LogDiagnostic($"Destroying prototype entity: {prototype}");
                em.DestroyEntity(prototype);
            }
        }
    }

    private RenderMeshArray CreateRenderMeshArrayWithMaterialMeshIndices()
    {
        int subMeshCount = sourceMesh.subMeshCount;
        LogDiagnostic($"Mesh has {subMeshCount} submeshes");
        LogDiagnostic($"Available materials: {materials.Length}");

        // Check material/submesh correspondence
        if (materials.Length < subMeshCount)
        {
            LogWarning($"⚠️ Warning: Only {materials.Length} materials provided for {subMeshCount} submeshes");
            LogWarning($"Additional submeshes will use the first material as fallback");
        }

        // 1. Create arrays of materials and meshes
        Material[] actualMaterials = new Material[subMeshCount];
        Mesh[] actualMeshes = new Mesh[1]; // All submeshes use the same mesh
        actualMeshes[0] = sourceMesh; // One mesh for all submeshes

        for (int i = 0; i < subMeshCount; i++)
        {
            // Assign materials
            if (i < materials.Length && materials[i] != null)
            {
                actualMaterials[i] = materials[i];
                LogDiagnostic($"✓ Submesh {i}: Material '{materials[i].name}' assigned");
            }
            else
            {
                actualMaterials[i] = materials[0]; // Fallback to first material
                LogWarning($"⚠️ Submesh {i}: Using fallback material '{materials[0].name}'");
            }
        }

        // 2. CREATE MaterialMeshIndex[] - KEY FOR MULTI-SUBMESH
        MaterialMeshIndex[] materialMeshIndices = new MaterialMeshIndex[subMeshCount];

        for (int i = 0; i < subMeshCount; i++)
        {
            materialMeshIndices[i] = new MaterialMeshIndex
            {
                MaterialIndex = i,    // Material index in actualMaterials
                MeshIndex = 0,        // Mesh index in actualMeshes (always 0 for single mesh)
                SubMeshIndex = i
            };

            LogDiagnostic($"✓ MaterialMeshIndex[{i}]: MaterialIndex={i}, MeshIndex=0");
        }

        // 3. CREATE RenderMeshArray WITH THIRD PARAMETER
        var renderMeshArray = new RenderMeshArray(actualMaterials, actualMeshes, materialMeshIndices);

        LogDiagnostic($"✓ RenderMeshArray created successfully");
        LogDiagnostic($"- Materials count: {actualMaterials.Length}");
        LogDiagnostic($"- Meshes count: {actualMeshes.Length}");
        LogDiagnostic($"- MaterialMeshIndices count: {materialMeshIndices.Length}");

        return renderMeshArray;
    }

    private void SetupRenderingComponents(EntityManager em, Entity entity, RenderMeshArray renderMeshArray)
    {
        // Create RenderMeshDescription with settings
        var renderMeshDescription = new RenderMeshDescription(
            layer: gameObject.layer,
            shadowCastingMode: ShadowCastingMode.On,
            receiveShadows: true,
            lightProbeUsage: LightProbeUsage.BlendProbes
        );

        LogDiagnostic($"✓ RenderMeshDescription created");
        LogDiagnostic($"- Layer: {gameObject.layer}");
        LogDiagnostic($"- ShadowCastingMode: {ShadowCastingMode.On}");
        LogDiagnostic($"- ReceiveShadows: true");

        // 4. CREATE MaterialMeshInfo WITH PROPER INDICES
        // IMPORTANT: For runtime entities, MaterialMeshInfo.FromRenderMeshArrayIndices()
        // will create negative indices (-1, -1) which is CORRECT behavior
        // This indicates array indices rather than literal material/mesh IDs
        int subMeshCount = sourceMesh.subMeshCount;

        MaterialMeshInfo materialMeshInfo;

        if (subMeshCount > 1)
        {
            // For multiple submeshes
            materialMeshInfo = MaterialMeshInfo.FromMaterialMeshIndexRange(0, subMeshCount);
            LogDiagnostic($"✓ MaterialMeshInfo created for multi-submesh rendering");
            LogDiagnostic($"- Expected MaterialMeshInfo values: Material={materialMeshInfo.Material}, Mesh={materialMeshInfo.Mesh}, SubMesh={materialMeshInfo.SubMesh}");
        }
        else
        {
            // For single submesh use standard method
            materialMeshInfo = MaterialMeshInfo.FromRenderMeshArrayIndices(0, 0);
            LogDiagnostic($"✓ MaterialMeshInfo created for single submesh: FromRenderMeshArrayIndices(0, 0)");
            LogDiagnostic($"- Expected MaterialMeshInfo values: Material={materialMeshInfo.Material}, Mesh={materialMeshInfo.Mesh}");
        }

        // 5. ADD RENDERING COMPONENTS
        RenderMeshUtility.AddComponents(
            entity,
            em,
            renderMeshDescription,
            renderMeshArray,
            materialMeshInfo
        );

        LogDiagnostic($"✓ RenderMeshUtility.AddComponents succeeded");

        // 6. SIMPLE COMPONENT CHECK
        if (logDetailedInfo)
        {
            CheckEntityComponents(em, entity);
        }
    }

    private void CheckEntityComponents(EntityManager em, Entity entity)
    {
        // Check main rendering components
        if (em.HasComponent<RenderBounds>(entity))
        {
            LogDiagnostic($"✓ Entity has RenderBounds component");
        }

        if (em.HasComponent<LocalToWorld>(entity))
        {
            LogDiagnostic($"✓ Entity has LocalToWorld component");
        }

        // Check MaterialMeshInfo values - THIS IS CRITICAL FOR DIAGNOSIS
        if (em.HasComponent<MaterialMeshInfo>(entity))
        {
            var mmi = em.GetComponentData<MaterialMeshInfo>(entity);
            LogDiagnostic($"✓ MaterialMeshInfo component exists");
            LogDiagnostic($"- Material: {mmi.Material}, Mesh: {mmi.Mesh}, SubMesh: {mmi.SubMesh}");

            // Explain the values
            if (mmi.Material < 0 && mmi.Mesh < 0)
            {
                LogInfo($"ℹ️ Negative Material/Mesh values are CORRECT for runtime entities");
                LogInfo($"ℹ️ These indicate array indices in RenderMeshArray (not literal IDs)");
                LogInfo($"ℹ️ Prefabs show 0,0 because they use baked literal IDs, not array indices");
            }
            else
            {
                LogInfo($"ℹ️ Positive Material/Mesh values indicate literal IDs (typical for baked Prefabs)");
            }

            LogDiagnostic($"- HasMaterialMeshIndexRange: {mmi.HasMaterialMeshIndexRange}");
            if (mmi.HasMaterialMeshIndexRange)
            {
                LogDiagnostic($"- MaterialMeshIndexRange: start={mmi.MaterialMeshIndexRange.start}, length={mmi.MaterialMeshIndexRange.length}");
            }
        }
    }

    private void SetupTransformComponents(EntityManager em, Entity entity)
    {
        // Proper Vector3 to float3 conversion
        float3 position = transform.position;
        quaternion rotation = transform.rotation;
        float scale = transform.localScale.x; // Use uniform scale

        em.AddComponentData(entity, new LocalTransform
        {
            Position = position,
            Rotation = rotation,
            Scale = scale
        });

        em.AddComponent<LocalToWorld>(entity);

        LogDiagnostic($"✓ Transform components added");
    }

    private void SpawnEntityCopies(EntityManager em, Entity prototype)
    {
        const int copyCount = 5;
        LogDiagnostic($"--- Spawning {copyCount} entity copies ---");

        using (var ecb = new EntityCommandBuffer(Allocator.TempJob))
        {
            for (int i = 0; i < copyCount; i++)
            {
                Entity copy = ecb.Instantiate(prototype);

                // Position copies in a circle - use only float3
                float angle = (i / (float)copyCount) * 360f;
                float radius = 2.5f;
                float3 offset = new float3(
                    math.cos(math.radians(angle)) * radius,
                    0,
                    math.sin(math.radians(angle)) * radius
                );

                // Proper position conversion
                float3 basePosition = transform.position;
                float3 finalPosition = basePosition + offset;

                ecb.SetComponent(copy, new LocalTransform
                {
                    Position = finalPosition,
                    Rotation = quaternion.Euler(0, angle, 0),
                    Scale = transform.localScale.x // Uniform scale
                });
            }

            ecb.Playback(em);
            LogSuccess($"✓ {copyCount} entity copies spawned successfully");
        }
    }

    private bool ValidateMeshAndMaterials()
    {
        bool isValid = true;

        if (sourceMesh == null)
        {
            LogError("❌ Source mesh is not assigned!");
            isValid = false;
        }
        else
        {
            LogInfo($"=== Mesh Validation ===");
            LogInfo($"Mesh name: {sourceMesh.name}");
            LogInfo($"Vertex count: {sourceMesh.vertexCount}");
            LogInfo($"Submesh count: {sourceMesh.subMeshCount}");

            if (sourceMesh.subMeshCount == 0)
            {
                LogError("❌ Mesh has no submeshes! Check mesh import settings.");
                isValid = false;
            }
            else if (sourceMesh.subMeshCount == 1)
            {
                LogWarning("⚠️ Mesh has only 1 submesh. For multi-material rendering, mesh should have multiple submeshes.");
            }

            // Check each submesh
            for (int i = 0; i < sourceMesh.subMeshCount; i++)
            {
                var submesh = sourceMesh.GetSubMesh(i);
                LogInfo($"Submesh {i}:");
                LogInfo($"- Topology: {submesh.topology}");
                LogInfo($"- Index count: {submesh.indexCount}");
                LogInfo($"- Triangle count: {submesh.indexCount / 3}");
                LogInfo($"- First vertex: {submesh.firstVertex}");
                LogInfo($"- Vertex count: {submesh.vertexCount}");
            }
        }

        if (materials == null || materials.Length == 0)
        {
            LogError("❌ No materials assigned!");
            isValid = false;
        }
        else
        {
            LogInfo($"=== Materials Validation ===");
            LogInfo($"Materials assigned: {materials.Length}");

            for (int i = 0; i < materials.Length; i++)
            {
                if (materials[i] == null)
                {
                    LogError($"❌ Material at index {i} is null!");
                    isValid = false;
                }
                else
                {
                    LogInfo($"Material {i}: {materials[i].name}");
                    LogInfo($"- Shader: {materials[i].shader.name}");

                    // Check DOTS compatibility
                    bool isCompatible = materials[i].shader.name.Contains("Lit") ||
                                       materials[i].shader.name.Contains("Unlit") ||
                                       materials[i].shader.name.Contains("HDRP") ||
                                       materials[i].shader.name.Contains("URP");

                    if (!isCompatible)
                    {
                        LogWarning($"⚠️ Material '{materials[i].name}' may not be compatible with Entities Graphics");
                        LogWarning($"Shader '{materials[i].shader.name}' should be HDRP/Lit or URP/Lit for best results");
                    }
                }
            }
        }

        return isValid;
    }

    private void CheckSubmeshDataSharingDefine()
    {
        bool isDefined = false;
#if ENABLE_MESH_RENDERER_SUBMESH_DATA_SHARING
        isDefined = true;
#endif

        if (isDefined)
        {
            LogSuccess("✅ ENABLE_MESH_RENDERER_SUBMESH_DATA_SHARING is DEFINED");
        }
        else
        {
            LogError("❌ ENABLE_MESH_RENDERER_SUBMESH_DATA_SHARING is NOT DEFINED!");
            LogError("This define symbol is REQUIRED for multi-submesh rendering in runtime.");
            LogError("To fix this:");
            LogError("1. Go to Edit → Project Settings → Player");
            LogError("2. Find 'Scripting Define Symbols' for your platform");
            LogError("3. Add: ENABLE_MESH_RENDERER_SUBMESH_DATA_SHARING");
            LogError("4. Restart Unity Editor completely");
        }
    }

    // Logging helper methods
    private void LogDiagnostic(string message)
    {
        if (logDetailedInfo) Debug.Log($"<b>[DIAGNOSTIC] {message}</b>");
    }

    private void LogInfo(string message) => Debug.Log($"<color=cyan>[INFO] {message}</color>");
    private void LogWarning(string message) => Debug.LogWarning($"<color=yellow>[WARNING] {message}</color>");
    private void LogError(string message) => Debug.LogError($"<color=red>[ERROR] {message}</color>");
    private void LogSuccess(string message) => Debug.Log($"<color=green>[SUCCESS] {message}</color>");

    [ContextMenu("Full Validation")]
    private void FullValidation()
    {
        bool isValid = ValidateMeshAndMaterials();
        CheckSubmeshDataSharingDefine();

        if (isValid)
        {
            LogSuccess("✅ All validations passed!");
        }
        else
        {
            LogError("❌ Validation failed! Fix the issues above.");
        }
    }
}