Need help refactoring a simple mesh heightfield generator w/ proper use of DOTS stack

Howdy.

I am having issues wrapping my head around how to structure my code utilizing entities, jobs and burst. I feel like it’s possible to do the following:

  • Given a user authored value for how big a heightfield should be

  • Off of the main thread

  • Utilize burst so that each vertex in the field gets it’s height value calculated in parallel w/ Unity’s math.noise function

  • Utilize burst to generate the triangle indices for a regular triangle strip in parallel

  • After the previous two jobs have finished, copy the data out of a dynamics buffer (or native array) and into a RenderMesh

  • Only execute the above once, I will eventually want to dynamically add in a Tiling concept where I will want to add/remove this type of mesh on the fly as the character moves around

I’m really just after the most efficient way to generate a regular triangle network heightfield, so it’s possible my line of thinking is off above.

Here is my naive approach that forces me to use WithoutBurst & Run accordingly and outputs the mesh as expected. I’ve failed to break this code into what I am guessing is the correct flow I outlined previously.

    using UnityEngine;
    using Unity.Entities;
    using Unity.Mathematics;
    using System;

    [AddComponentMenu("OH/NoiseSeed")]
    public class NoiseSeedAuthoring : MonoBehaviour, IConvertGameObjectToEntity
    {
        public int Seed = 1337; // todo actually use this value
        public int Size = 10;
        public Material Material;

        public void Convert(Entity entity, EntityManager entityManager, GameObjectConversionSystem conversionSystem)
        {
            entityManager.AddComponentData(entity, new NoiseSeed
            {
                Seed = Seed,
                Size = Size,
                Loaded = false
            });
            entityManager.AddSharedComponentData(entity, new SimpleMeshRenderer
            { 
                Mesh = new Mesh(),
                Material = Material
            });
        }
    }


    public struct NoiseSeed : IComponentData
    {
        public int Seed;
        public int Size;
        public bool Loaded; // refactor out
    }

    public struct SimpleMeshRenderer : ISharedComponentData, IEquatable<SimpleMeshRenderer>
    {
        public Mesh Mesh;
        public Material Material;

        // todo
        public bool Equals(SimpleMeshRenderer other)
        {
            return false;
        }

        // todo https://docs.microsoft.com/en-us/dotnet/api/system.object.gethashcode?view=net-5.0
        public override int GetHashCode()
        {
            return 1;
        }
    }

    public class NoiseBuilderSystem : SystemBase
    {
        private EntityQuery _query;

        protected override void OnCreate()
        {
            base.OnCreate();
            var queryDescription = new EntityQueryDesc
            {
                All = new ComponentType[]
                { 
                    ComponentType.ReadWrite<NoiseSeed>(),
                    ComponentType.ReadWrite<SimpleMeshRenderer>(),
                }
            };
            _query = GetEntityQuery(queryDescription);
        }

        protected override void OnUpdate()
        {
            Entities
                .WithStoreEntityQueryInField(ref _query)
                .WithoutBurst() // todo refactor to WithBurst
                .ForEach((ref NoiseSeed seed, in SimpleMeshRenderer meshRenderer) =>
                {
                    if(!seed.Loaded)
                    {
                        LoadSeed(ref seed, in meshRenderer);
                    }
                   
                    Graphics.DrawMesh(meshRenderer.Mesh, new Vector3(), Quaternion.identity, meshRenderer.Material, 0);
                })
                .Run(); // todo refactor to ScheduleParallel()?
        }

        private static void LoadSeed(ref NoiseSeed noiseSeed, in SimpleMeshRenderer meshRenderer)
        {
            // verts, I expect this can be burst'd
            var rowSize = noiseSeed.Size;
            var colSize = noiseSeed.Size;
            var vertices = new Vector3[noiseSeed.Size * noiseSeed.Size];
            for (int row = 0, index = 0; row < rowSize; row++) 
            {
                for (int col = 0; col < noiseSeed.Size; col++, index++) 
                {
                    var heightPoint = new float2(row, col);
                    float height = noise.snoise(heightPoint);
                    vertices[index] = new Vector3(row, height, col);
                }
            }

            // tris, I expect this can be burst'd
            var quadRowSize = rowSize - 1;
            var quadColSize = colSize - 1;
            var triangles = new int[(quadRowSize * quadColSize) * (2*3)];
            for (int quadRow = 0, triangleIndex = 0; quadRow < quadRowSize; quadRow++) 
            {
                for (int quadCol = 0; quadCol < quadColSize; quadCol++, triangleIndex += 6) 
                {
                    var topLeft = (quadRow * rowSize) + quadCol;
                    var topRight = (quadRow * rowSize) + (quadCol + 1);
                    var bottomLeft = ((quadRow + 1) * rowSize) + quadCol;
                    var bottomRight = ((quadRow + 1) * rowSize) + (quadCol + 1);

                    triangles[triangleIndex] = topLeft; // 1st triangle
                    triangles[triangleIndex + 1] = topRight;
                    triangles[triangleIndex + 2] = bottomLeft;
                    triangles[triangleIndex + 3] = topRight; // 2nd triangle
                    triangles[triangleIndex + 4] = bottomRight;
                    triangles[triangleIndex + 5] = bottomLeft;
                }
            }

            // mesh, I presume this is where I would wait for the previous two handles to finish and perform a copy. I'm also confused on how to translate from my entity's DynamicBuffer<float3> to the Mesh's verticies Vector3[]
            meshRenderer.Mesh.vertices = vertices;
            meshRenderer.Mesh.triangles = triangles;

            
            meshRenderer.Mesh.RecalculateBounds(); // I believe this can just be set directly with a constant extent {.5, 1 .5}
            meshRenderer.Mesh.RecalculateNormals(); // I eventually plan on calculating the Normals in a similar manner / at the same time as the vertex's
            meshRenderer.Mesh.RecalculateTangents();

            // cache
            noiseSeed.Loaded = true;
        }
    }
}

And here is where I’m currently left off scratching my head in the refactor.

  public struct VertexBufferElement : IBufferElementData
    {
        public float3 Value;
    }

    public struct TriangleIndexBufferElement : IBufferElementData
    {
        public int Value;
    }


// .. system update function:


var commandBuffer = _entityCommandBufferSystem
                .CreateCommandBuffer()
                .AsParallelWriter();

            var spawnTileJobHandle = Entities
                .WithName("SpawnTile")
                .WithBurst(FloatMode.Default, FloatPrecision.Standard, true)
                .ForEach((Entity entity, int entityInQueryIndex, in NoiseSeed noiseSeed) =>
                {
                    var vertexBuffer = commandBuffer.AddBuffer<VertexBufferElement>(entityInQueryIndex, entity);
                    vertexBuffer.Length = noiseSeed.Size * noiseSeed.Size;

                    var rowSize = noiseSeed.Size;
                    var colSize = noiseSeed.Size;
                    for (int row = 0, index = 0; row < rowSize; row++)
                    {
                        for (int col = 0; col < noiseSeed.Size; col++, index++)
                        {
                            var heightPoint = new float2(row, col);
                            float height = noise.snoise(heightPoint);
                            vertexBuffer[index] = new VertexBufferElement
                            {
                                Value = new float3 (row, height, col)
                            };
                        }
                    }
                }).Schedule(Dependency);

            spawnTileJobHandle.Complete();

            Entities
                .ForEach((DynamicBuffer<VertexBufferElement> vertexBuffer, ref SimpleMeshRenderer meshRenderer) =>
                {
                    meshRenderer.Mesh = new Mesh
                    {
                        vertices = new Vector3[vertexBuffer.Length]
                    };
                    for(int i = 0; i < vertexBuffer.Length; i++)
                    {
                        meshRenderer.Mesh.vertices[i] = new Vector3(vertexBuffer[i].Value.x, vertexBuffer[i].Value.y, vertexBuffer[i].Value.z);
                    }
                });

Second Script
Line 50.
Make sure you use floats / float3 not Vector3 with jobs and burst.

Line 54.

Don’t do vertices assignment.
Use NativeArray , then use

myMesh.SetVertices ( myNativeArrayVertices ) ;

Should be much more performant and you can burst the job.

In order to use mesh.SetVeriticies() how do I go from the DynamicBuffer to a NativeArray? Am I wrong in using the DynamicBuffer?

edit: I don’t see an interface method that accepts a NativeArray for .setVerticies, or anywhere that uses float3 in Mesh.cs

You can just do a Reinterpret()

Like this? Doesn’t seem right… I noticed from previous research that you handle lists differently? https://github.com/tertle/MeshDemo/blob/master/Assets/Scripts/Common/Native/ListExtensions.cs

Entities
                .WithBurst()
                .ForEach((DynamicBuffer<VertexBufferElement> vertexBuffer, DynamicBuffer<TriangleIndexBufferElement> triangleBuffer,ref SimpleMeshRenderer meshRenderer) =>
                {
                    meshRenderer.Mesh.MarkDynamic();
                    meshRenderer.Mesh.SetVertices(vertexBuffer.Reinterpret<Vector3>().ToNativeArray(Allocator.Temp));
                    meshRenderer.Mesh.SetTriangles(triangleBuffer.Reinterpret<int>().ToNativeArray(Allocator.Temp).ToArray(), 0);
                }).ScheduleParallel();

This is very old code, they’ve since added overloads for NativeArrays so you don’t have to convert to array or anything like that

var vertexBuffer = new NativeArray();
mesh.SetVertices(vertexBuffer.Reinterpret());

Is all you’d need if you were using float3

Can you provide an example of converting from a DynamicBuffer? I don’t have a NativeArray and I I’m confused by when you say if I were using float3, am I not?

It’s possible I shouldn’t be using dynamic buffers in the first place and instead should be populating a NativeArray?

Here is an example of the code executing as I expected again still with the concerns with how the copying of data is occurring. I can’t imagine calling ToArray twice is ideal. Again looking for the best way to structure this use case :slight_smile:

edit: my code got flagged for moderation, I suppose you’ll have to wait and see it…

    public struct VertexBufferElement : IBufferElementData
    {
        public float3 Value;
    }

    public struct TriangleIndexBufferElement : IBufferElementData
    {
        public int Value;
    }
   
    public struct NoiseSeed : IComponentData
    {
        public int Seed;
        public int Size;
    }

    public struct SimpleMeshRenderer : ISharedComponentData, IEquatable<SimpleMeshRenderer>
    {
        public Mesh Mesh;
        public Material Material;

        // todo
        public bool Equals(SimpleMeshRenderer other)
        {
            return false;
            //return Mesh == other.Mesh && Material == other.Material;
        }

        // todo https://docs.microsoft.com/en-us/dotnet/api/system.object.gethashcode?view=net-5.0
        public override int GetHashCode()
        {
            return 1; // maybe use x,y as origin but that is currently not associated to this component
            // what even happens if this isn't implemented correctly and there are multiple objects?
        }
    }

    public class NoiseBuilderSystem : SystemBase
    {
        private BeginInitializationEntityCommandBufferSystem _entityCommandBufferSystem;

        protected override void OnCreate()
        {
            base.OnCreate();
            _entityCommandBufferSystem = World.GetOrCreateSystem<BeginInitializationEntityCommandBufferSystem>();
        }

        protected override void OnUpdate()
        {
            var commandBuffer = _entityCommandBufferSystem
                .CreateCommandBuffer()
                .AsParallelWriter();

            var spawnTileJobHandle = Entities
                .WithName("SpawnTile")
                .WithBurst(FloatMode.Default, FloatPrecision.Standard, true)
                .ForEach((Entity entity, int entityInQueryIndex, in NoiseSeed noiseSeed) =>
                {
                    var vertexBuffer = commandBuffer.AddBuffer<VertexBufferElement>(entityInQueryIndex, entity);
                    vertexBuffer.Length = noiseSeed.Size * noiseSeed.Size;
                    for (int row = 0, index = 0; row < noiseSeed.Size; row++)
                    {
                        for (int col = 0; col < noiseSeed.Size; col++, index++)
                        {
                            var heightPoint = new float2(row, col);
                            float height = noise.snoise(heightPoint);
                            vertexBuffer[index] = new VertexBufferElement
                            {
                                Value = new float3 (row, height, col)
                            };
                        }
                    }

                    var quadSize = noiseSeed.Size - 1;
                    var triangleBuffer = commandBuffer.AddBuffer<TriangleIndexBufferElement>(entityInQueryIndex, entity);
                    triangleBuffer.Length = (quadSize * quadSize) * (2*3);
                    for (int quadRow = 0, triangleIndex = 0; quadRow < quadSize; quadRow++)
                    {
                        for (int quadCol = 0; quadCol < quadSize; quadCol++, triangleIndex += 6)
                        {
                            var topLeft = (quadRow * noiseSeed.Size) + quadCol;
                            var topRight = (quadRow * noiseSeed.Size) + (quadCol + 1);
                            var bottomLeft = ((quadRow + 1) * noiseSeed.Size) + quadCol;
                            var bottomRight = ((quadRow + 1) * noiseSeed.Size) + (quadCol + 1);

                            triangleBuffer[triangleIndex] = new TriangleIndexBufferElement{ Value = topLeft }; // 1st triangle
                            triangleBuffer[triangleIndex + 1] = new TriangleIndexBufferElement{ Value = topRight };
                            triangleBuffer[triangleIndex + 2] = new TriangleIndexBufferElement{ Value = bottomLeft };
                            triangleBuffer[triangleIndex + 3] = new TriangleIndexBufferElement{ Value = topRight }; // 2nd triangle
                            triangleBuffer[triangleIndex + 4] = new TriangleIndexBufferElement{ Value = bottomRight };
                            triangleBuffer[triangleIndex + 5] = new TriangleIndexBufferElement{ Value = bottomLeft };
                        }
                    }

                    commandBuffer.RemoveComponent<NoiseSeed>(entityInQueryIndex, entity);

                }).ScheduleParallel(Dependency);

            spawnTileJobHandle.Complete();

            Entities
                .WithName("CopyMeshData")
                .WithoutBurst()
                .WithStructuralChanges()
                .ForEach((Entity entity, int entityInQueryIndex, DynamicBuffer<VertexBufferElement> vertexBuffer, DynamicBuffer<TriangleIndexBufferElement> triangleBuffer, in SimpleMeshRenderer meshRenderer) => 
                {
                    meshRenderer.Mesh.MarkDynamic();
                    meshRenderer.Mesh.SetVertices(vertexBuffer.Reinterpret<Vector3>().ToNativeArray(Allocator.Temp));
                    meshRenderer.Mesh.SetTriangles(triangleBuffer.Reinterpret<int>().ToNativeArray(Allocator.Temp).ToArray(), 0);
                    meshRenderer.Mesh.RecalculateBounds();
                    meshRenderer.Mesh.RecalculateNormals();
                    meshRenderer.Mesh.RecalculateTangents();

                    EntityManager.RemoveComponent<VertexBufferElement>(entity);
                    EntityManager.RemoveComponent<TriangleIndexBufferElement>(entity);
                }).Run();

            Entities
                .WithoutBurst()
                .ForEach((in SimpleMeshRenderer meshRenderer) =>
                {
                    Graphics.DrawMesh(meshRenderer.Mesh, Vector3.zero, Quaternion.identity, meshRenderer.Material, 0);
                })
                .Run();
        }

assuming your buffer looks like this

public struct Vertex : IBufferElement
{
    public float3 Index;
}

Then you could just do buffer.AsNativeArray().Reinterpret();
The memory lines up fine.

Performance wise it’s all just data reinterpretation so no copying or anything. If you edited the Vector3 array it would modify the buffer.

1 Like

There’s no need to bother with Vector3s. You can and should use float3 since it can potentially get more performance from burst. Mesh.SetVertices will accept native arrays of float3, it has generic overloads for exactly that purpose.

Seems like you have a solid grasp on what you’re doing, you’re just struggling with the weird ECS syntax. Here’s a simple example:

    public struct VertsBuffer : IBufferElementData
    {
        public float3 Value;
    }

    public struct IndexBuffer : IBufferElementData
    {
        public int Value;
    }

    protected override void OnUpdate()
    {
        Mesh mesh = new Mesh();
        Entities
            .WithoutBurst()
            .ForEach((
            in DynamicBuffer<IndexBuffer> indexBuffer,
            in DynamicBuffer<VertsBuffer> vertsBuffer) =>
            {
                var verts = vertsBuffer.Reinterpret<float3>().AsNativeArray();
                var indices = indexBuffer.Reinterpret<int>().AsNativeArray();
                mesh.SetVertices(verts);
                mesh.SetIndices(indices, MeshTopology.Triangles, 0);
            }).Run();
    }

Oh there is a nice generic version now. (Been nearly 2 years since I’ve looked at setting mesh data)

Yeah no need to use vector3 at all

@Sarkahn_1 thank you I was almost there with the syntax and SetIndicies was what I needed (and closer to what I’d expect from how the graphics pipeline expects).

My first dive into parallel programming so bear with me. Looking for a code review of sorts, here are my current open DOTS related questions:

  • is there a way to avoid the structural changes? Sync points | Entities | 0.16.0-preview.21 When I think about this data processing workflow I really am doing a series of steps to get my final product and only doing so only once for each “seed” then discarding all the processing data. Perhaps its better for me to use a blob (Struct BlobBuilder | Entities | 0.16.0-preview.21)? I would like to think it possible to perform a copy of the verts/tris off of the main thread, but right now I am forced to use Run() which is blocking and I believe I want to avoid this. It also seems like I’m still on the main thread when I execute .Complete(), so I feel like my thinking is a bit off here, like I know that the jobs will split off async and then join back up into the main thread but my brain is fizzling. It just seems off for me to be adding/removing components the way I am but perhaps there is no performance hit for doing so. For reference, I will eventually be moving to an irregular triangle network (for Delaunay Triangulation for instance) which has additional processing steps and memory requirements, so I’m just trying to learn as much as possible here.
  • is there a way to avoid using ISharedComponent to render a mesh each frame from verts/tris? According to the docs it seems like my current usage is not the proper use case. I will never have two of my meshes alike as they are simply tiled out heightfields. Interface ISharedComponentData | Entities | 0.16.0-preview.21 “Use shared components when many entities share the same data values and it is more efficient to process all the entities of a given value together.”. I suppose yet again my main motivation is to not use Run() and be able to draw it off of the main thread, but maybe this is not the proper way to think of it since I might not want to have the drawing performed async.
  • assuming I don’t use blobs and stick with buffers, or regardless really, is there a cleaner way to break this up into multiple systems? it’s obvious to split out the drawing of the mesh into a separate system, but it seems like the whole seed → mesh is one system.
  • what’s the difference between AsNativeArray() vs ToNativeArray(), is one sort of just using pointers and the other actually doing a straight up copy which is why it needs the allocator?

Not so DOTS’y question:

  • do I need to use mesh.MarkDynamic()? the docs (Unity - Scripting API: Mesh.MarkDynamic) make it seem like this is more intended if I were to be streaming in the verticies as opposed to the straight copy I am doing atm with SetVerticies.

At the end of the day, just trying to plant a seed, water it and once it’s done growing just continuously render it and forget about all that processing data. I will be loading in these seeds dynamically with the current model, so I want the processing to not effect the FPS as much while the user is interacting with the game.

It’s a pretty involved topic, hard to give general advice without going into a ton of detail. You can do all your mesh processing in jobs, but at the end of it you need to push your data into the mesh on the main thread. As far as I know that can’t be helped. You can alleviate your process jobs from blocking by not calling Complete on your processing jobs and instead storing the jobhandle and checking if(handle.IsCompleted) every frame, then push to the mesh only at that point.

There’s also the “MeshData” api which I did some experimenting with in my terminals library. It wasn’t the right fit for my specific scenario where I need to write to a single mesh often but rarely rebuilt the vertices. From what you’re describing it sounds like you could potentially benefit from using the MeshData API, but it’s pretty awkward to use and there’s not much documentation on it. The long and short of it is it lets you build some arbitrary number of meshes inside jobs by working directly with the mesh data. It should be especially great if you need to build a lot of different meshes at the same time in parallel. But again, even with that API, you still need to call Mesh.ApplyAndDisposeWritableMeshData on the main thread after processing.

I can’t really discern from your code how often they would be happening. Unless you’re doing it many many times per frame I wouldn’t worry about it.

AFAIK there’s no “drawing off the main thread” in Unity. You could try using the hybrid renderer, but it’s very very bad at handling this specific scenario right now, apparently that’s being worked on according to unity devs. I’ve found that using gameobjects with a meshfilter or doing what you’re doing now - looping over the meshes and calling DrawMesh - are the fastest options we have for rendering a lot of different procedurally generated meshes. Someone smarter than me could probably do some kind of shader magic to generate and render their meshes on the GPU. Beyond that the best thing you can do is some kind of minecraft style chunking where you load and unload meshes as needed.

Basically, yes. Don’t ever call ToArray/ToNativeArray unless you need a deep copy. AsNativeArray does no copying.

@Sarkahn_1 Thanks for the quick reply! Helpful information and example code. Looking at the MeshData and other job examples (i.e. GitHub - stella3d/job-system-cookbook: Unity Technologies management has fucked everything up. this is a guide to the job system circa 2019, Unity Job System and Burst Compiler: Getting Started | Kodeco, GitHub - keijiro/Firefly: Unity ECS example for special effects) is also helpful. I think the issue I’m having is I’m jamming my data into entities, when in reality that is best suited for just job constructs and then copy the data out when it is complete. This should remove the unneeded structural changes. For the graphics, that makes sense, I’ll just stick with DrawMesh until I start seeing a bottleneck, hopefully the data only gets sent once in the backend on the way to the gpu…

Added in the tiling without changing the structure really, it’s getting messy!

{
    using UnityEngine;
    using Unity.Burst;
    using Unity.Collections;
    using Unity.Entities;
    using Unity.Jobs;
    using Unity.Mathematics;
    using Unity.Transforms;
    using System;

    public struct VertexBufferElement : IBufferElementData
    {
        public float3 Value;
    }

    public struct TriangleIndexBufferElement : IBufferElementData
    {
        public int Value;
    }
   
    public struct NoiseSeed : IComponentData
    {
        public int Seed;
        public int Size;
        public float2 CellLocation;
    }

    public struct SimpleMeshRenderer : ISharedComponentData, IEquatable<SimpleMeshRenderer>
    {
        public Mesh Mesh;
        public Material Material;
        public float2 CellLocation;

        // todo
        public bool Equals(SimpleMeshRenderer other)
        {
            return false;
            //return Mesh == other.Mesh && Material == other.Material;
        }

        // todo https://docs.microsoft.com/en-us/dotnet/api/system.object.gethashcode?view=net-5.0
        public override int GetHashCode()
        {
            return 1; // maybe use x,y as origin but that is currently not associated to this component
            // what even happens if this isn't implemented correctly and there are multiple objects?
        }
    }

    public class NoiseBuilderSystem : SystemBase
    {
        private BeginInitializationEntityCommandBufferSystem _entityCommandBufferSystem;
        private NativeHashMap<float2, Entity> _cells;

        protected override void OnCreate()
        {
            base.OnCreate();
            _entityCommandBufferSystem = World.GetOrCreateSystem<BeginInitializationEntityCommandBufferSystem>();
            _cells = new NativeHashMap<float2, Entity>(100, Allocator.Persistent);
        }

        protected override void OnDestroy()
        {
            base.OnDestroy();
            _cells.Dispose();
        }

        protected override void OnUpdate()
        {
            Vector3 playerPosition = Vector3.zero;
            Entities
                .WithName("GetCameraPosition")
                .WithoutBurst()
                .ForEach((in Camera camera, in Transform transform) => {
                    playerPosition = transform.position;
                }).Run();

            if(playerPosition == Vector3.zero)
            {
                Debug.LogWarning("Camera not found!");
            }

            float2 cellLocation = float2.zero;
            int cellLength = 0;
            Entities
                .WithName("LookupCellLocation")
                .WithoutBurst()
                .ForEach((in GridSettings gridSettings) =>
                {
                    cellLocation = LookupCellLocation(new float2(playerPosition.x, playerPosition.z), gridSettings.TileSideLength);
                    cellLength = gridSettings.TileSideLength;
                }).Run();

            Entity cellEntity;
            if(!_cells.TryGetValue(cellLocation, out cellEntity))
            {
                cellEntity = EntityManager.CreateEntity();
                EntityManager.AddComponent<NoiseSeed>(cellEntity);
                var noiseSeed = new NoiseSeed();
                noiseSeed.Seed = 1337;
                noiseSeed.Size = cellLength;
                noiseSeed.CellLocation = cellLocation;
                EntityManager.SetComponentData<NoiseSeed>(cellEntity, noiseSeed);
                _cells.Add(cellLocation, cellEntity);
                //Debug.Log(string.Format("0: {0}, {1}", noiseSeed.CellLocation.x, noiseSeed.CellLocation.y));

                var smr = new SimpleMeshRenderer();
                smr.Mesh = new Mesh();
                //smr.Material = null;
                smr.CellLocation = new float2(noiseSeed.CellLocation.x * cellLength, noiseSeed.CellLocation.y * cellLength);
                EntityManager.AddSharedComponentData<SimpleMeshRenderer>(cellEntity, smr);
            }

            var commandBuffer = _entityCommandBufferSystem
                .CreateCommandBuffer()
                .AsParallelWriter();

            var spawnTileJobHandle = Entities
                .WithName("SpawnTile")
                .WithBurst(FloatMode.Default, FloatPrecision.Standard, true)
                .ForEach((Entity entity, int entityInQueryIndex, in NoiseSeed noiseSeed) =>
                {
                    var vertexBuffer = commandBuffer.AddBuffer<VertexBufferElement>(entityInQueryIndex, entity);
                    vertexBuffer.Length = noiseSeed.Size * noiseSeed.Size;
                    for (int row = 0, index = 0; row < noiseSeed.Size; row++)
                    {
                        for (int col = 0; col < noiseSeed.Size; col++, index++)
                        {
                            var heightPoint = new float2(row, col);
                            float height = noise.snoise(heightPoint);
                            vertexBuffer[index] = new VertexBufferElement
                            {
                                Value = new float3 (row, height, col)
                            };
                        }
                    }

                    var quadSize = noiseSeed.Size - 1;
                    var triangleBuffer = commandBuffer.AddBuffer<TriangleIndexBufferElement>(entityInQueryIndex, entity);
                    triangleBuffer.Length = (quadSize * quadSize) * (2*3);
                    for (int quadRow = 0, triangleIndex = 0; quadRow < quadSize; quadRow++)
                    {
                        for (int quadCol = 0; quadCol < quadSize; quadCol++, triangleIndex += 6)
                        {
                            var topLeft = (quadRow * noiseSeed.Size) + quadCol;
                            var topRight = (quadRow * noiseSeed.Size) + (quadCol + 1);
                            var bottomLeft = ((quadRow + 1) * noiseSeed.Size) + quadCol;
                            var bottomRight = ((quadRow + 1) * noiseSeed.Size) + (quadCol + 1);

                            triangleBuffer[triangleIndex] = new TriangleIndexBufferElement{ Value = topLeft }; // 1st triangle
                            triangleBuffer[triangleIndex + 1] = new TriangleIndexBufferElement{ Value = topRight };
                            triangleBuffer[triangleIndex + 2] = new TriangleIndexBufferElement{ Value = bottomLeft };
                            triangleBuffer[triangleIndex + 3] = new TriangleIndexBufferElement{ Value = topRight }; // 2nd triangle
                            triangleBuffer[triangleIndex + 4] = new TriangleIndexBufferElement{ Value = bottomRight };
                            triangleBuffer[triangleIndex + 5] = new TriangleIndexBufferElement{ Value = bottomLeft };
                        }
                    }

                }).ScheduleParallel(Dependency);

            spawnTileJobHandle.Complete();

            Entities
                .WithName("CopyMeshData")
                .WithoutBurst()
                .WithStructuralChanges()
                .ForEach((Entity entity, int entityInQueryIndex, DynamicBuffer<VertexBufferElement> vertexBuffer, DynamicBuffer<TriangleIndexBufferElement> triangleBuffer, in SimpleMeshRenderer meshRenderer, in NoiseSeed noiseSeed) =>
                {
                    meshRenderer.Mesh.SetVertices(vertexBuffer.Reinterpret<float3>().AsNativeArray());
                    meshRenderer.Mesh.SetIndices(triangleBuffer.Reinterpret<int>().AsNativeArray(), MeshTopology.Triangles, 0);
                    meshRenderer.Mesh.RecalculateBounds();
                    meshRenderer.Mesh.RecalculateNormals();

                    EntityManager.RemoveComponent<VertexBufferElement>(entity);
                    EntityManager.RemoveComponent<TriangleIndexBufferElement>(entity);
                    EntityManager.RemoveComponent<NoiseSeed>(entity);

                    //Debug.Log(string.Format("2: {0}, {1}", meshRenderer.CellLocation.x, meshRenderer.CellLocation.y));
                }).Run();

            Entities
                .WithoutBurst()
                .ForEach((in SimpleMeshRenderer meshRenderer) =>
                {
                    //Debug.Log(string.Format("3: {0}, {1}", meshRenderer.CellLocation.x, meshRenderer.CellLocation.y));
                    Graphics.DrawMesh(meshRenderer.Mesh, new Vector3(meshRenderer.CellLocation.x, 0, meshRenderer.CellLocation.y), Quaternion.identity, meshRenderer.Material, 0);
                })
                .Run();
        }

        public static float2 LookupCellLocation(float2 lookupLocation, int cellSize)
        {
            return math.floor(lookupLocation / cellSize);
        }

    }
}