Building procedrual mesh per SCD Nighmare

Trying to build a mesh per SCD value and it proving to be a big mess. My Ideal solution would be to build the separate meshes on two different Jobs and only running whenPlanetBuildComponent changed but the mesh is a reference type. This is my best effort below if anyone has any advice please chip in. It works but it clumsy and Im very unhappy with it

Thanks

protected override JobHandle OnUpdate(JobHandle inputDeps)
{
    JobHandle sequentialDeps = inputDeps;
    
    Entities
        .WithAll<PlanetBuildComponent>()
        .WithChangeFilter<PlanetBuildComponent>()
        .ForEach((int entityInQueryIndex, in Entity e) => 
        {
           Debug.Log(
               "<b> <size=13> <color=#EA70A3>Info : PlanetCreateBiomsSystem : GetPlanetIDs.</color> </size> </b>");
           
           _planetId = new List<PlanetIdComponent>();
           EntityManager.GetAllUniqueSharedComponentData<PlanetIdComponent>(_planetId);
       })
        .WithName("GetPlanetIDs")
        .WithoutBurst()
        .Run();
    
    //
    //
    foreach (PlanetIdComponent id in _planetId)
    {

        Entities
            .WithChangeFilter<PlanetBuildComponent>()
            .WithSharedComponentFilter(id)
            .WithReadOnly(id)
            .ForEach((int entityInQueryIndex, in Entity e, in PlanetGraphComponent g, in RenderMesh m, in PlanetBuildComponent b ) =>
            {
                Debug.Log(
                    "<b> <size=13> <color=#EA70A3>Info : PlanetCreateBiomsSystem : PlanetBuildMesh 1.</color> </size> </b>");
                
                Mesh newPlanetMesh = new Mesh();
                int numVertices = b.VertexCount;
                int numTriangles = numVertices * 2 - 4;
                var vertexDataArray = new NativeArray<PlanetVertexData>(numTriangles * 3, Allocator.TempJob);
                var vertexIndexDataArray = new NativeArray<PlanetVertexIndexData>(numTriangles * 3, Allocator.TempJob);
                
                //
                // Get mesh data 
                for (var i = 0; i < g.TileGraph.Value.Triangles.Length; i++)
                {
                    //
                    // GetMeshTrianglePositions
                    var posVertA =
                        new float3(
                            g.TileGraph.Value.Vertices[g.TileGraph.Value.Triangles[i].VertA].PositionX,
                            g.TileGraph.Value.Vertices[g.TileGraph.Value.Triangles[i].VertA].PositionY,
                            g.TileGraph.Value.Vertices[g.TileGraph.Value.Triangles[i].VertA].PositionZ);
                    var posVertB =
                        new float3(
                            g.TileGraph.Value.Vertices[g.TileGraph.Value.Triangles[i].VertB].PositionX,
                            g.TileGraph.Value.Vertices[g.TileGraph.Value.Triangles[i].VertB].PositionY,
                            g.TileGraph.Value.Vertices[g.TileGraph.Value.Triangles[i].VertB].PositionZ);
                    var posVertC =
                        new float3(
                            g.TileGraph.Value.Vertices[g.TileGraph.Value.Triangles[i].VertC].PositionX,
                            g.TileGraph.Value.Vertices[g.TileGraph.Value.Triangles[i].VertC].PositionY,
                            g.TileGraph.Value.Vertices[g.TileGraph.Value.Triangles[i].VertC].PositionZ);
                    
                    //
                    // GetMeshTriangleNormals
                    float3 norVertA =
                        math.normalize(
                            new float3(
                                g.TileGraph.Value.Vertices[g.TileGraph.Value.Triangles[i].VertA].PositionX,
                                g.TileGraph.Value.Vertices[g.TileGraph.Value.Triangles[i].VertA].PositionY,
                                g.TileGraph.Value.Vertices[g.TileGraph.Value.Triangles[i].VertA].PositionZ
                            )
                        );
                    float3 norVertB =
                        math.normalize(
                            new float3(
                                g.TileGraph.Value.Vertices[g.TileGraph.Value.Triangles[i].VertB].PositionX,
                                g.TileGraph.Value.Vertices[g.TileGraph.Value.Triangles[i].VertB].PositionY,
                                g.TileGraph.Value.Vertices[g.TileGraph.Value.Triangles[i].VertB].PositionZ
                            )
                        );
                    float3 norVertC =
                        math.normalize(
                            new float3(
                                g.TileGraph.Value.Vertices[g.TileGraph.Value.Triangles[i].VertC].PositionX,
                                g.TileGraph.Value.Vertices[g.TileGraph.Value.Triangles[i].VertC].PositionY,
                                g.TileGraph.Value.Vertices[g.TileGraph.Value.Triangles[i].VertC].PositionZ
                            )
                        );
                    
                    //
                    // GetMeshTriangleTangents
                    var tanVertA = new float4(-norVertA.z, 0, norVertA.x, -1);
                    var tanVertB = new float4(-norVertB.z, 0, norVertB.x, -1);
                    var tanVertC = new float4(-norVertC.z, 0, norVertC.x, -1);
                
                    //
                    // GetMeshTriangleUv
                    var uvVertA = new
                        float2(
                            math.atan2(norVertA.x, norVertA.z) / (-2f * math.PI),
                            math.asin(norVertA.y) / math.PI + 0.5f
                        );
                    if (uvVertA.x < 0f) uvVertA.x += 1f;
                
                    var uvVertB = new
                        float2(
                            math.atan2(norVertB.x, norVertB.z) / (-2f * math.PI),
                            math.asin(norVertB.y) / math.PI + 0.5f
                        );
                    if (uvVertB.x < 0f) uvVertB.x += 1f;
                
                    var uvVertC = new
                        float2(
                            math.atan2(norVertC.x, norVertC.z) / (-2f * math.PI),
                            math.asin(norVertC.y) / math.PI + 0.5f
                        );
                    if (uvVertC.x < 0f) uvVertC.x += 1f;
                
                    //
                    // TODO : GetMeshTriangleColo 
                
                    //
                    // Set VertexDataArray
                    var vertexDataA = new PlanetVertexData
                    {
                        Position = posVertA,
                        Normal = norVertA,
                        Tangent = tanVertA,
                        UVs = uvVertA,
                        Color = new int4(1, 1, 1, 1)
                    };
                    vertexDataArray[3 * i + 0] = vertexDataA;
                
                    var vertexDataB = new PlanetVertexData
                    {
                        Position = posVertB,
                        Normal = norVertB,
                        Tangent = tanVertB,
                        UVs = uvVertB,
                        Color = new int4(1, 1, 1, 1)
                    };
                    vertexDataArray[3 * i + 1] = vertexDataB;
                
                    var vertexDataC = new PlanetVertexData
                    {
                        Position = posVertC,
                        Normal = norVertC,
                        Tangent = tanVertC,
                        UVs = uvVertC,
                        Color = new int4(1, 1, 1, 1)
                    };
                    vertexDataArray[3 * i + 2] = vertexDataC;
                
                    //
                    // GetMeshTriangleIndices
                    vertexIndexDataArray[3 * i + 0] = 3 * i + 0;
                    vertexIndexDataArray[3 * i + 1] = 3 * i + 1;
                    vertexIndexDataArray[3 * i + 2] = 3 * i + 2;
                }
        
                //
                // This descriptor must match VertexData structure.
                var vertexAttrDescriptor = new[]
                {
                    new VertexAttributeDescriptor(VertexAttribute.Position),
                    new VertexAttributeDescriptor(VertexAttribute.Normal),
                    new VertexAttributeDescriptor(VertexAttribute.Tangent, VertexAttributeFormat.Float32, 4),
                    new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.UInt32, 4),
                    new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2)
                };
                
                //
                // Tell the mesh what format the params will be sent. Note the format must match VertexIndexData struct
                newPlanetMesh.SetVertexBufferParams(vertexDataArray.Length, vertexAttrDescriptor);
                newPlanetMesh.SetVertexBufferData(vertexDataArray, 0, 0, vertexDataArray.Length);
                newPlanetMesh.SetIndexBufferParams(vertexIndexDataArray.Length, IndexFormat.UInt32);
                newPlanetMesh.SetIndexBufferData(vertexIndexDataArray, 0, 0, vertexIndexDataArray.Length);
                newPlanetMesh.subMeshCount = 1;
                
                //
                // Set SubMeshDescriptor
                var descr = new SubMeshDescriptor
                {
                    baseVertex = 0,
                    bounds = default,
                    indexCount = vertexDataArray.Length,
                    indexStart = 0,
                    topology = MeshTopology.Triangles
                };
                newPlanetMesh.SetSubMesh(0, descr);
                
                //
                // Bounds
                newPlanetMesh.RecalculateBounds();
        
                //
                // Set SCD
                EntityManager.SetSharedComponentData(e, new RenderMesh
                {
                    material = m.material,
                    mesh = newPlanetMesh,
                    castShadows = m.castShadows,
                    receiveShadows = m.receiveShadows
                });
                
                //
                // Cleanup
                vertexDataArray.Dispose();
                vertexIndexDataArray.Dispose();
                    
            })
            .WithStructuralChanges()
            .WithoutBurst()
            .Run();
    }
    
    //
    // Cleanup
    _planetId.Clear();
    return sequentialDeps;
}

Job-safe mesh operations are around the corner.

instead of on chunk change, could try using an event in some form: an event entity, event tag, NativeQueue, etc.

The job MeshData api is available right now in the latest 2020 version if you want to switch to that.

If you want to run main thread code after a job it’s up to you to manually force the job to complete first. You can schedule your jobs to operate on your mesh data early in the frame and assign the jobhandle to a publicly accessible variable.

Something like:

// Run early in the frame.
[UpdateInGroup(typeof(InitializationSystemGroup))]
class UpdateMeshDataSystem : SystemBase
{
    List<PlanetIdComponent> _ids = new List();
 
    public JobHandle FinalJobHandle => Dependency;
 
    protected override Jobhandle OnUpdate()
    {
        _ids.Clear();
        EntityManager.GetAllUniqueSharedComponentData(_ids);
     
        foreach(var id in _ids)
        {
            // Schedule your jobs. Only operate on mesh data, don't touch the mesh.
        }
    }
}

// Run later in the frame but before rendering
[UpdateInGroup(typeof(LateSimulationSystemGroup))]
class UploadMeshData : SystemBase
{
    protected override Jobhandle OnUpdate()
    {
        World.GetOrCreateSystem<UpdateMeshDataSystem>().FinalJobHandle.Complete();
        // Or check (if FinalJobHandle.IsComplete) first if you want to avoid blocking until the job is finished.
     
        // Query for the updated mesh data, update mesh on main thread.
    }
}
1 Like

Thank you will give this a try