# Question Help with Voxel engine, Jobs, Burst, Greedy Meshing algorithm problem [CLOSED]

Discussion in 'Scripting' started by HajiyevEl, May 18, 2024.

1. ### HajiyevEl

Joined:
Feb 19, 2020
Posts:
55
Hi, I'm trying to make a custom Voxel Engine for my game which is little similar to Minecraft (at least in cubic map). I watched several playlist about making Minecraft on youtube, but most of them do not implement Greedy Meshing, and use Tasks for multi-threading. There was one engine that i took fancy to, but it's too complex for my game, and as i understand from authors youtube videos, he has reduced usage of virtual methods to increase performance, but it made engine less flexible. At least it's not flexible enough for my needs.
https://github.com/BLaZeKiLL/VloxyEngine

Long story short - I started to make my own little engine. I took something from youtube tutorirals, something from other articles, and glued it all in ChatGPT. Yeah... As expected, i stumbled upon a problem when i wanted to implement Greedy Meshing algorithm from this article https://eddieabbondanz.io/post/voxel/greedy-mesh/

Simple generation via 'ChunkMeshDataGenerationJob' works fine.

Problem is - when generating mesh via 'ChunkGreedyMeshDataGenerationJob' - it does not generate properly.

Here is my code. There are only 5 scripts, so it's not so much. I will add ChatGPT explanations about Greedy meshing part.

Block.cs
Code (CSharp):
1.
2. using System;
3. using Unity.Collections;
4. using Unity.Mathematics;
5.
6. namespace HAELENT
7. {
8.     /// <summary>
9.     /// Enum representing the different types of blocks in the voxel engine.
10.     /// </summary>
11.     public enum BlockType
12.     {
13.         AIR,   // Represents an empty space
14.         STONE, // Represents a stone block
15.         DIRT,  // Represents a dirt block
16.         GRASS, // Represents a grass block
17.         WATER, // Represents a water block
18.         SAND,  // Represents a sand block
19.         WOOD,  // Represents a wood block
20.         LEAFS, // Represents leaves
21.         ERROR,  // Represents an error block (used for debugging or invalid states)
22.         HIDDEN  // Represents an hidden block (used for Greedy Meshing)
23.     }
24.
25.     /// <summary>
26.     /// Struct representing a single block in the voxel engine.
27.     /// </summary>
28.     public struct Block
29.     {
30.         /// <summary>
31.         /// The type of the block, defined by the BlockType enum.
32.         /// </summary>
33.         public BlockType type;
34.     }
35.
36.     public struct BlockData
37.     {
39.         public static readonly int3[] Vertices = new int3[8]
40.         {
41.             new int3(0, 0, 0),  //[0]
42.             new int3(1, 0, 0),  //[1]
43.             new int3(1, 1, 0),  //[2]
44.             new int3(0, 1, 0),  //[3]
45.             new int3(0, 0, 1),  //[4]
46.             new int3(1, 0, 1),  //[5]
47.             new int3(1, 1, 1),  //[6]
48.             new int3(0, 1, 1)   //[7]
49.         };
50.
52.         public static readonly int[] Triangles = new int[36] {
53.             0, 2, 1, 0, 3, 2, // Front
54.             5, 6, 4, 4, 6, 7, // Back
55.             4, 7, 0, 0, 7, 3, // Left
56.             1, 2, 5, 5, 2, 6, // Right
57.             3, 7, 2, 2, 7, 6, // Top
58.             4, 0, 5, 5, 0, 1  // Bottom
59.         };
60.     }
61. }

Chunk.cs
Code (CSharp):
1. using Unity.Collections;
2. using Unity.Mathematics;
3.
4. namespace HAELENT
5. {
6.     /// <summary>
7.     /// Represents a chunk of blocks in the voxel engine.
8.     /// </summary>
9.     public struct Chunk
10.     {
11.         /// <summary>
12.         /// The array of blocks within this chunk.
13.         /// </summary>
14.         public NativeArray<Block> blocks;
15.
16.         /// <summary>
17.         /// The size of the chunk in 3D space (width, height, depth).
18.         /// </summary>
19.         public int3 size;
20.
21.         /// <summary>
22.         /// Initializes a new chunk with the specified size.
23.         /// </summary>
24.         /// <param name="size">The size of the chunk.</param>
25.         public Chunk(int3 size)
26.         {
27.             this.size = size;
28.             blocks = new NativeArray<Block>(size.x * size.y * size.z, Allocator.Persistent);
29.         }
30.
31.         /// <summary>
32.         /// Disposes of the chunk, releasing its resources.
33.         /// </summary>
34.         public void Dispose()
35.         {
36.             if (blocks.IsCreated)
37.                 blocks.Dispose();
38.         }
39.
40.         /// <summary>
41.         /// Gets the block at the specified coordinates within the chunk.
42.         /// </summary>
43.         /// <param name="x">The x-coordinate.</param>
44.         /// <param name="y">The y-coordinate.</param>
45.         /// <param name="z">The z-coordinate.</param>
46.         /// <returns>The block at the specified coordinates.</returns>
47.         public Block GetBlock(int x, int y, int z)
48.         {
49.             int index = x + size.x * (y + size.y * z);
50.             return blocks[index];
51.         }
52.
53.         /// <summary>
54.         /// Sets the block at the specified coordinates within the chunk.
55.         /// </summary>
56.         /// <param name="x">The x-coordinate.</param>
57.         /// <param name="y">The y-coordinate.</param>
58.         /// <param name="z">The z-coordinate.</param>
59.         /// <param name="block">The block to set.</param>
60.         public void SetBlock(int x, int y, int z, Block block)
61.         {
62.             int index = x + size.x * (y + size.y * z);
63.             blocks[index] = block;
64.         }
65.     }
66. }

ChunkMeshDataGenerationJob.cs
Code (CSharp):
1. using Unity.Burst;
2. using Unity.Collections;
3. using Unity.Jobs;
4. using Unity.Mathematics;
5. using UnityEngine;
6.
7. namespace HAELENT
8. {
9.     /// <summary>
10.     /// Job to generate mesh data for a chunk of blocks.
11.     /// </summary>
12.     [BurstCompile]
13.     public struct ChunkMeshDataGenerationJob : IJob
14.     {
15.         /// <summary>
16.         /// The array of blocks to generate the mesh for.
17.         /// </summary>
19.
20.         /// <summary>
21.         /// The size of the chunk in 3D space (width, height, depth).
22.         /// </summary>
23.         public int3 chunkSize;
24.
25.         /// <summary>
26.         /// The list of vertices to be populated by the job.
27.         /// </summary>
28.         public NativeList<float3> vertices;
29.
30.         /// <summary>
31.         /// The list of triangles to be populated by the job.
32.         /// </summary>
33.         public NativeList<int> triangles;
34.
35.         /// <summary>
36.         /// Executes the job to generate the mesh data.
37.         /// </summary>
38.         public void Execute()
39.         {
40.             for (int x = 0; x < chunkSize.x; x++)
41.             {
42.                 for (int y = 0; y < chunkSize.y; y++)
43.                 {
44.                     for (int z = 0; z < chunkSize.z; z++)
45.                     {
46.                         int index = x + chunkSize.x * (y + chunkSize.y * z);
47.                         if (blocks[index].type != BlockType.AIR)
48.                         {
49.                             // Add cube mesh for solid block
51.                         }
52.                     }
53.                 }
54.             }
55.         }
56.
57.         /// <summary>
58.         /// Adds the vertices and triangles for a cube at the given position.
59.         /// </summary>
60.         /// <param name="position">The position of the cube.</param>
62.         {
63.             int vertexStart = vertices.Length;
64.
65.             // Add vertices (8 vertices for a cube)
74.
75.             for (int i = 0; i < BlockData.Triangles.Length; i++)
76.             {
78.             }
79.         }
80.     }
81. }

ChunkGreedyMeshDataGenerationJob.cs
Code (CSharp):
1. using Unity.Burst;
2. using Unity.Collections;
3. using Unity.Jobs;
4. using Unity.Mathematics;
5.
6. namespace HAELENT
7. {
8.     /// <summary>
9.     /// Job to generate mesh data for a chunk using the greedy meshing algorithm.
10.     /// </summary>
11.     [BurstCompile]
12.     public struct ChunkGreedyMeshDataGenerationJob : IJob
13.     {
14.         // The array of blocks within the chunk.
16.
17.         // The size of the chunk in 3D space (width, height, depth).
18.         public int3 chunkSize;
19.
20.         // NativeList to store the vertices of the generated mesh.
21.         public NativeList<float3> vertices;
22.
23.         // NativeList to store the indices of the generated mesh.
24.         public NativeList<int> triangles;
25.
26.         /// <summary>
27.         /// The main execution method of the job.
28.         /// </summary>
29.         public void Execute()
30.         {
31.             GreedyMesh();
32.         }
33.
34.         /// <summary>
35.         /// Implements the greedy meshing algorithm to optimize mesh generation by reducing the number of polygons.
36.         /// </summary>
37.         private void GreedyMesh()
38.         {
39.             // Greedy meshing algorithm loops over each dimension.
40.             for (int d = 0; d < 3; d++)
41.             {
42.                 int i, j, k, l, w, h;
43.                 int u = (d + 1) % 3;
44.                 int v = (d + 2) % 3;
45.
46.                 NativeArray<int3> x = new NativeArray<int3>(3, Allocator.Temp);
47.                 NativeArray<int3> q = new NativeArray<int3>(3, Allocator.Temp);
48.                 NativeArray<Block> mask = new NativeArray<Block>(chunkSize[u] * chunkSize[v], Allocator.Temp);
49.
50.                 // Increment in the current dimension.
51.                 q[d] = 1;
52.
53.                 // Iterate over the current dimension.
54.                 for (x[d] = -1; math.all(x[d] < chunkSize[d]);)
55.                 {
56.                     int n = 0;
57.
58.                     // Create a mask for the current slice.
59.                     for (x[v] = 0; math.all(x[v] < chunkSize[v]); x[v]++)
60.                     {
61.                         for (x[u] = 0; math.all(x[u] < chunkSize[u]); x[u]++)
62.                         {
63.                             // Get blocks at the current and adjacent positions.
64.                             Block block1 = math.all((x[d] >= 0)) ? GetBlock(x[0].x, x[1].y, x[2].z) : new Block { type = BlockType.HIDDEN };
65.                             Block block2 = math.all((x[d] < chunkSize[d] - 1)) ? GetBlock(x[0].x + q[0].x, x[1].y + q[1].y, x[2].z + q[2].z) : new Block { type = BlockType.HIDDEN };
66.
67.                             // If blocks are different, add the current block to the mask.
68.                             if (block1.type != block2.type)
69.                             {
71.                             }
72.                             else
73.                             {
74.                                 mask[n++] = new Block { type = BlockType.HIDDEN };
75.                             }
76.                         }
77.                     }
78.
79.                     x[d]++;
80.
81.                     n = 0;
82.
84.                     for (j = 0; j < chunkSize[v]; j++)
85.                     {
86.                         for (i = 0; i < chunkSize[u];)
87.                         {
88.                             if (mask[n].type != BlockType.HIDDEN )
89.                             {
90.                                 // Determine the width of the quad.
91.                                 for (w = 1; i + w < chunkSize[u] && mask[n + w].type == mask[n].type; w++) { }
92.
93.                                 bool done = false;
94.
95.                                 // Determine the height of the quad.
96.                                 for (h = 1; j + h < chunkSize[v]; h++)
97.                                 {
98.                                     for (k = 0; k < w; k++)
99.                                     {
101.                                         {
102.                                             done = true;
103.                                             break;
104.                                         }
105.                                     }
106.
107.                                     if (done) break;
108.                                 }
109.
110.                                 x[u] = i;
111.                                 x[v] = j;
112.
113.                                 int3 du = new int3();
114.                                 du[u] = w;
115.
116.                                 int3 dv = new int3();
117.                                 dv[v] = h;
118.
121.                                 vertices.Add(new float3(x[0].x + du[0], x[1].y + du[1], x[2].z + du[2]));
122.                                 vertices.Add(new float3(x[0].x + du[0] + dv[0], x[1].y + du[1] + dv[1], x[2].z + du[2] + dv[2]));
123.                                 vertices.Add(new float3(x[0].x + dv[0], x[1].y + dv[1], x[2].z + dv[2]));
124.
125.                                 int vertCount = vertices.Length;
126.
134.
135.                                 // Clear the mask for the processed area.
136.                                 for (l = 0; l < h; l++)
137.                                 {
138.                                     for (k = 0; k < w; k++)
139.                                     {
140.                                         var index = n + k + l * chunkSize[u];
141.                                         // Get the block from the mask array
143.                                         tempBlock.type = BlockType.HIDDEN ;
145.                                     }
146.                                 }
147.
148.                                 i += w;
149.                                 n += w;
150.                             }
151.                             else
152.                             {
153.                                 i++;
154.                                 n++;
155.                             }
156.                         }
157.                     }
158.                 }
159.
161.                 x.Dispose();
162.                 q.Dispose();
163.             }
164.         }
165.
166.         /// <summary>
167.         /// Gets the block at the specified coordinates within the chunk.
168.         /// </summary>
169.         /// <param name="x">The x-coordinate.</param>
170.         /// <param name="y">The y-coordinate.</param>
171.         /// <param name="z">The z-coordinate.</param>
172.         /// <returns>The block at the specified coordinates.</returns>
173.         private Block GetBlock(int x, int y, int z)
174.         {
175.             int index = x + chunkSize.x * (y + chunkSize.y * z);
176.             return blocks[index];
177.         }
178.     }
179. }

ChunkMeshGenerator.cs
Code (CSharp):
1. using Unity.Collections;
2. using Unity.Jobs;
3. using Unity.Mathematics;
4. using UnityEngine;
5.
6. namespace HAELENT
7. {
8.     /// <summary>
9.     /// Generates the mesh for a chunk of blocks and assigns it to a MeshFilter.
10.     /// </summary>
11.     public class ChunkMeshGenerator : MonoBehaviour
12.     {
13.         /// <summary>
14.         /// The size of the chunk to generate.
15.         /// </summary>
16.         public int3 chunkSize = new int3(16, 16, 16);
17.
18.         /// <summary>
19.         /// If should use Greedy Meshing algorithm to generate mesh.
20.         /// </summary>
21.         public bool UseGreedyMeshing = true;
22.
23.         /// <summary>
24.         /// The chunk of blocks.
25.         /// </summary>
26.         private Chunk chunk;
27.
28.         /// <summary>
29.         /// The MeshFilter component used to display the generated mesh.
30.         /// </summary>
31.         private MeshFilter meshFilter;
32.
33.         /// <summary>
34.         /// Initializes the chunk and starts the mesh generation.
35.         /// </summary>
36.         void Start()
37.         {
38.             chunk = new Chunk(chunkSize);
39.             meshFilter = GetComponent<MeshFilter>();
40.
41.             if (meshFilter == null)
43.
44.             // Populate chunk with sample data
45.             for (int x = 0; x < chunkSize.x; x++)
46.             {
47.                 for (int y = 0; y < chunkSize.y; y++)
48.                 {
49.                     for (int z = 0; z < chunkSize.z; z++)
50.                     {
51.                         // Example data: create a ground layer of stone blocks at y = 0, air blocks elsewhere
52.                         Block block = new Block { type = y == 0 ? BlockType.STONE : BlockType.AIR };
53.                         chunk.SetBlock(x, y, z, block);
54.                     }
55.                 }
56.             }
57.
58.             if (UseGreedyMeshing)
59.                 GenerateGreedyMesh();
60.             else
61.                 GenerateMesh();
62.         }
63.
64.         /// <summary>
65.         /// Generates the mesh for the chunk and assigns it to the MeshFilter.
66.         /// </summary>
67.         void GenerateMesh()
68.         {
69.             NativeList<float3> vertices = new NativeList<float3>(Allocator.TempJob);
70.             NativeList<int> triangles = new NativeList<int>(Allocator.TempJob);
71.
72.             ChunkMeshDataGenerationJob job = new ChunkMeshDataGenerationJob
73.             {
74.                 blocks = chunk.blocks,
75.                 chunkSize = chunkSize,
76.                 vertices = vertices,
77.                 triangles = triangles
78.             };
79.
80.             JobHandle handle = job.Schedule();
81.             handle.Complete();
82.
83.             Vector3[] verticesAsVector3 = new Vector3[vertices.Length];
84.             for (int i = 0; i < vertices.Length; i++)
85.             {
86.                 verticesAsVector3[i] = vertices[i];
87.             }
88.
89.             Mesh mesh = new Mesh
90.             {
91.                 vertices = verticesAsVector3,
92.                 triangles = triangles.ToArray()
93.             };
94.             mesh.RecalculateNormals();
95.
96.             meshFilter.mesh = mesh;
97.
98.             vertices.Dispose();
99.             triangles.Dispose();
100.         }
101.
102.         /// <summary>
103.         /// Generates the greedy mesh for the chunk and assigns it to the MeshFilter.
104.         /// </summary>
105.         void GenerateGreedyMesh()
106.         {
107.             NativeList<float3> vertices = new NativeList<float3>(Allocator.TempJob);
108.             NativeList<int> triangles = new NativeList<int>(Allocator.TempJob);
109.
110.             ChunkGreedyMeshDataGenerationJob job = new ChunkGreedyMeshDataGenerationJob
111.             {
112.                 blocks = chunk.blocks,
113.                 chunkSize = chunkSize,
114.                 vertices = vertices,
115.                 triangles = triangles
116.             };
117.
118.             JobHandle handle = job.Schedule();
119.             handle.Complete();
120.
121.             Vector3[] verticesAsVector3 = new Vector3[vertices.Length];
122.             for (int i = 0; i < vertices.Length; i++)
123.             {
124.                 verticesAsVector3[i] = vertices[i];
125.             }
126.
127.             Mesh mesh = new Mesh
128.             {
129.                 vertices = verticesAsVector3,
130.                 triangles = triangles.ToArray()
131.             };
132.
133.             mesh.RecalculateNormals();
134.
135.             meshFilter.mesh = mesh;
136.
137.             Debug.Log(\$"Generated Greedy Mesh with {vertices.Length} vertices and {triangles.Length / 3} triangles");
138.
139.             vertices.Dispose();
140.             triangles.Dispose();
141.         }
142.
143.         /// <summary>
144.         /// Disposes of the chunk to release resources.
145.         /// </summary>
146.         void OnDestroy()
147.         {
148.             chunk.Dispose();
149.         }
150.     }
151. }

Let's walk through the ChunkGreedyMeshDataGenerationJob using 3D text illustrations and its variables to illustrate the greedy meshing process.

3D Text Illustrations with Variables
1. Chunk Initialization
Consider a small chunk with dimensions 3x3x3, represented as a 3D grid of blocks. Assume the chunk size is chunkSize = new int3(3, 3, 3). Each block in the chunk can be of different types (e.g., air (A), solid (S)).

Chunk Grid (3x3x3):
Code (CSharp):
1. Level 0:
2. [S] [S] [A]
3. [S] [A] [A]
4. [A] [A] [A]
5.
6. Level 1:
7. [S] [S] [A]
8. [S] [A] [A]
9. [A] [A] [A]
10.
11. Level 2:
12. [S] [S] [A]
13. [S] [A] [A]
14. [A] [A] [A]
2. Iterate Over Dimensions
The algorithm iterates over three principal dimensions (X, Y, Z) using the outer loop: for (int d = 0; d < 3; d++).

3. Create Mask for Current Slice
For a slice in the X dimension (d=0), the slice at x=-1:

• q[d] = new int3(1, 0, 0), increment in the X dimension.
• Initialize x = new NativeArray<int3>(3, Allocator.Temp) to store coordinates.
• Initialize mask = new NativeArray<Block>(chunkSize.y * chunkSize.z, Allocator.Temp) to store the mask.
Code (CSharp):
2.        [A] [A] [A]
3.        [A] [A] [A]
4.        [A] [A] [A]
Fill the mask by comparing each block with its neighbor in the X direction (q[d]):

• x[d] = -1 (current slice at x=-1).
• Iterate through YZ plane.
Example slice at x=0:

• block1 = GetBlock(0, y, z) and block2 = GetBlock(1, y, z).
• If block1 and block2 are different, set mask value to block1.
Code (CSharp):
2.        [S] [S] [A]
3.        [S] [A] [A]
4.        [A] [A] [A]
Identify continuous areas of the same block type (e.g., solid blocks) and form quads:

• Initialize variables w (width) and h (height) for the quads.
For mask[0,0] = S, find the largest continuous area:
Code (CSharp):
2. [S] [S]
3. [S] [A]
4.
5. Width (w) = 2, Height (h) = 1

• x = 0, x[v] = 0, du = new int3(2, 0, 0), dv = new int3(0, 1, 0).

Vertices:
Code (CSharp):
1. V0 = [0, 0, 0]
2. V1 = [2, 0, 0]
3. V2 = [2, 1, 0]
4. V3 = [0, 1, 0]
Triangles:
Code (CSharp):
1. T0 = (V0, V1, V2)
2. T1 = (V0, V2, V3)
7. Clear Processed Area in Mask
Update the mask to indicate processed blocks (set to BlockType.HIDDEN).

Code (CSharp):
2. [HIDDEN, HIDDEN, A]
3. [S, A, A]
4. [A, A, A]
Continue this process for the rest of the mask and other slices.

Detailed Variable Walkthrough
Variables:

• d: Current dimension (0 for X, 1 for Y, 2 for Z).
• i, j, k, l, w, h: Loop variables for iterating through the slice and determining quad size.
• u, v: Dimensions orthogonal to d (e.g., for d=0, u=1 (Y) and v=2 (Z)).
• x: Array of coordinates in the chunk.
• q: Array for dimension increment.
Greedy Mesh Process Illustrated in Detail:

• Initialize Variables:
Code (CSharp):
1. NativeArray<int3> x = new NativeArray<int3>(3, Allocator.Temp);
2. NativeArray<int3> q = new NativeArray<int3>(3, Allocator.Temp);
3. NativeArray<Block> mask = new NativeArray<Block>(chunkSize[u] * chunkSize[v], Allocator.Temp);
4. q[d] = new int3(0);
5. q[d][d] = 1;
2. Iterate Over Dimensions and Create Mask:
Code (CSharp):
1. for (int d = 0; d < 3; d++) {
2.     for (x[d] = -1; math.all(x[d] < chunkSize[d]); x[d]++) {
3.         int n = 0;
4.         for (x[v] = 0; math.all(x[v] < chunkSize[v]); x[v]++) {
5.             for (x[u] = 0; math.all(x[u] < chunkSize[u]); x[u]++) {
6.                 Block block1 = math.all(x[d] >= 0) ? GetBlock(x[0].x, x[1].y, x[2].z) : new Block { type = BlockType.HIDDEN };
7.                 Block block2 = math.all(x[d] < chunkSize[d] - 1) ? GetBlock(x[0].x + q[0].x, x[1].y + q[1].y, x[2].z + q[2].z) : new Block { type = BlockType.HIDDEN };
8.                 mask[n++] = block1.type != block2.type ? block1 : new Block { type = BlockType.HIDDEN };
9.             }
10.         }
11.     }
12. }
Code (CSharp):
1. for (int j = 0; j < chunkSize[v]; j++) {
2.     for (int i = 0; i < chunkSize[u];) {
3.         if (mask[n].type != BlockType.HIDDEN) {
4.             for (w = 1; i + w < chunkSize[u] && mask[n + w].type == mask[n].type; w++) {}
5.             bool done = false;
6.             for (h = 1; j + h < chunkSize[v]; h++) {
7.                 for (int k = 0; k < w; k++) {
8.                     if (mask[n + k + h * chunkSize[u]].type != mask[n].type) {
9.                         done = true;
10.                         break;
11.                     }
12.                 }
13.                 if (done) break;
14.             }
15.             x[u] = i;
16.             x[v] = j;
17.             int3 du = new int3(0); du[u] = w;
18.             int3 dv = new int3(0); dv[v] = h;
20.             vertices.Add(new float3(x[0].x + du[0], x[1].y + du[1], x[2].z + du[2]));
21.             vertices.Add(new float3(x[0].x + du[0] + dv[0], x[1].y + du[1] + dv[1], x[2].z + du[2] + dv[2]));
22.             vertices.Add(new float3(x[0].x + dv[0], x[1].y + dv[1], x[2].z + dv[2]));
23.             int vertCount = vertices.Length;
30.             for (l = 0; l < h; l++) {
31.                 for (k = 0; k < w; k++) {
32.                     mask[n + k + l * chunkSize[u]] = new Block { type = BlockType.HIDDEN };
33.                 }
34.             }
35.             i += w;
36.             n += w;
37.         } else {
38.             i++;
39.             n++;
40.         }
41.     }
42. }
4. Dispose Temporary Arrays:
Code (CSharp):
2. x.Dispose();
3. q.Dispose();

Last edited: May 18, 2024
2. ### CodeSmile

Joined:
Apr 10, 2014
Posts:
6,922
Don't expect anybody to help you debug big chunks of AI generated code.

When you create an "engine", you ought to be able to either fix these issues yourself or at least narrow down the issue to something specific that you don't understand.

Note that greedy meshing is not a top priority item to implement, since it's purely a performance optimization.

Bunny83 likes this.
3. ### HajiyevEl

Joined:
Feb 19, 2020
Posts:
55
Yeah, you are correct. I should learn basics about greedy meshing first and try to implement it on my own, even if it takes much more time. Thanks for reply! Closed.