Advertisement
JontePonte

less messy meshgen

Jan 9th, 2025
121
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 13.04 KB | None | 0 0
  1. using System.Collections.Generic;
  2. using Unity.Collections;
  3. using UnityEngine;
  4. using Unity.Mathematics;
  5. using Unity.Jobs;
  6. using UnityEngine.Rendering;
  7. using UnityEngine.UI;
  8. using UnityTemplateProjects;
  9.  
  10. public class ChunkGenerator : MonoBehaviour
  11. {
  12.     class JobData
  13.     {
  14.         public Mesh landMesh, waterMesh;
  15.         public Vector2Int noiseOffset;
  16.  
  17.         public JobHandle handle;
  18.  
  19.         public bool scheduled;
  20.         public bool instantCompletion;
  21.         public int frameCount;
  22.  
  23.         public NativeArray<int> customVoxelValues;
  24.  
  25.         public NativeList<float3> vertices;
  26.         public NativeList<int> triangles;
  27.         public NativeList<float2> uvs;
  28.  
  29.         public NativeList<float3> waterVertices;
  30.         public NativeList<int> waterTriangles;
  31.         public NativeList<float2> waterUvs;
  32.     }
  33.  
  34.     public Texture2D atlas;
  35.     public Material terrainMaterial;
  36.     public Material waterMaterial;
  37.     public Image loadingScreen;
  38.     public SimpleCameraController player;
  39.  
  40.     public Vector2Int chunkDimensions;
  41.     public static Vector2Int ChunkDimensions;
  42.  
  43.     [Header("Terrain Settings")]
  44.     public int surfaceLevel;
  45.     public int seaLevel;
  46.     public float sandDensity;
  47.     public float maxTreeHeight;
  48.  
  49.     [Header("Noise Settings")]
  50.     public float persistance;
  51.     public float frequency;
  52.     public float lacunarity;
  53.     public int numOctaves;
  54.     public Vector2Int offset;
  55.     public AnimationCurve heightCurve;
  56.     public float heightMultiplier;
  57.     public float noise3DContribution;
  58.     public int noiseSeed;
  59.  
  60.     [Header("Performance")]
  61.     public int maxProccessingFrames;
  62.     public int maxJobsPerFrame;
  63.  
  64.     Noise noise;
  65.  
  66.     NativeArray<int3> faceVertices;
  67.     NativeArray<int3> faceDirections;
  68.     NativeArray<float2> uvCoordinates;
  69.  
  70.     NativeArray<float> sampledHeightCurve;
  71.  
  72.     [HideInInspector]
  73.     public NativeHashMap<int2, NativeArray<int>> chunkDataMap;
  74.  
  75.     List<JobData> jobList;
  76.     List<JobData> jobQueue;
  77.  
  78.     bool loadedTerrain;
  79.  
  80.     public GameObject CreateChunkObject(Vector2Int offset)
  81.     {
  82.         GameObject chunk = new GameObject(transform.childCount.ToString());
  83.         var meshRenderer = chunk.AddComponent<MeshRenderer>();
  84.         meshRenderer.sharedMaterial = new Material(terrainMaterial)
  85.         {
  86.             mainTexture = atlas
  87.         };
  88.  
  89.         Mesh landMesh = new Mesh
  90.         {
  91.             indexFormat = IndexFormat.UInt32
  92.         };
  93.  
  94.         Mesh waterMesh = new Mesh
  95.         {
  96.             indexFormat = IndexFormat.UInt32
  97.         };
  98.  
  99.         CreateChunkMesh(landMesh, waterMesh, offset, false);
  100.  
  101.         chunk.AddComponent<MeshFilter>().mesh = landMesh;
  102.  
  103.         //Water is seperate object so it can have transparency
  104.         GameObject water = new GameObject("Water");
  105.         water.transform.parent = chunk.transform;
  106.         water.AddComponent<MeshRenderer>().sharedMaterial = waterMaterial;
  107.         water.AddComponent<MeshFilter>().mesh = waterMesh;
  108.  
  109.         return chunk;
  110.     }
  111.  
  112.     public void CreateChunkMesh(Mesh landMesh, Mesh waterMesh, Vector2Int offset, bool instantCompletion, NativeArray<int> customVoxelValues = default)
  113.     {
  114.         if (instantCompletion)
  115.         {
  116.             var data = new JobData
  117.             {
  118.                 landMesh = landMesh,
  119.                 waterMesh = waterMesh,
  120.                 noiseOffset = offset,
  121.                 instantCompletion = instantCompletion,
  122.                 customVoxelValues = customVoxelValues
  123.             };
  124.  
  125.             CreateAndScheduleJob(data);
  126.             CompleteJob(data);
  127.         }
  128.         else
  129.         {
  130.             jobQueue.Add(new JobData
  131.             {
  132.                 landMesh = landMesh,
  133.                 waterMesh = waterMesh,
  134.                 noiseOffset = offset,
  135.                 instantCompletion = instantCompletion,
  136.                 customVoxelValues = customVoxelValues
  137.             });
  138.         }
  139.     }
  140.  
  141.     void Start()
  142.     {
  143.         Initialize();
  144.         player.enabled = false;
  145.         loadingScreen.enabled = true;
  146.     }
  147.  
  148.     public void Initialize()
  149.     {
  150.         System.Random rand = new(noiseSeed);
  151.  
  152.         chunkDataMap = new NativeHashMap<int2, NativeArray<int>>(1024, Allocator.Persistent);
  153.         jobList = new();
  154.         jobQueue = new();
  155.  
  156.         sampledHeightCurve = SampleCurve(heightCurve, 256);
  157.  
  158.         ChunkDimensions = chunkDimensions;
  159.  
  160.         noise = new Noise(
  161.             frequency,
  162.             persistance,
  163.             lacunarity,
  164.             numOctaves,
  165.             heightMultiplier,
  166.             rand.Next() / 10000,
  167.             surfaceLevel,
  168.             seaLevel,
  169.             noise3DContribution,
  170.             sandDensity,
  171.             maxTreeHeight,
  172.             sampledHeightCurve
  173.         );
  174.  
  175.         faceVertices = new NativeArray<int3>(24, Allocator.Persistent);
  176.  
  177.         //Bottom face
  178.         faceVertices[0] = new int3(1, 0, 0);
  179.         faceVertices[1] = new int3(1, 0, 1);
  180.         faceVertices[2] = new int3(0, 0, 1);
  181.         faceVertices[3] = new int3(0, 0, 0);
  182.  
  183.         //Front face
  184.         faceVertices[4] = new int3(1, 0, 1);
  185.         faceVertices[5] = new int3(1, 1, 1);
  186.         faceVertices[6] = new int3(0, 1, 1);
  187.         faceVertices[7] = new int3(0, 0, 1);
  188.  
  189.         //Back face
  190.         faceVertices[8] = new int3(0, 0, 0);
  191.         faceVertices[9] = new int3(0, 1, 0);
  192.         faceVertices[10] = new int3(1, 1, 0);
  193.         faceVertices[11] = new int3(1, 0, 0);
  194.  
  195.         //Left face
  196.         faceVertices[12] = new int3(0, 0, 1);
  197.         faceVertices[13] = new int3(0, 1, 1);
  198.         faceVertices[14] = new int3(0, 1, 0);
  199.         faceVertices[15] = new int3(0, 0, 0);
  200.  
  201.         //Right face
  202.         faceVertices[16] = new int3(1, 0, 0);
  203.         faceVertices[17] = new int3(1, 1, 0);
  204.         faceVertices[18] = new int3(1, 1, 1);
  205.         faceVertices[19] = new int3(1, 0, 1);
  206.  
  207.         //Top face
  208.         faceVertices[20] = new int3(0, 1, 0);
  209.         faceVertices[21] = new int3(0, 1, 1);
  210.         faceVertices[22] = new int3(1, 1, 1);
  211.         faceVertices[23] = new int3(1, 1, 0);
  212.  
  213.         uvCoordinates = new NativeArray<float2>(6, Allocator.Persistent);
  214.  
  215.         uvCoordinates[0] = new float2(.5f, 0);
  216.         uvCoordinates[1] = new float2(.5f, .5f);
  217.         uvCoordinates[2] = new float2(.5f, .5f);
  218.         uvCoordinates[3] = new float2(.5f, .5f);
  219.         uvCoordinates[4] = new float2(.5f, .5f);
  220.         uvCoordinates[5] = new float2(0, 0);
  221.  
  222.         faceDirections = new NativeArray<int3>(6, Allocator.Persistent);
  223.  
  224.         faceDirections[0] = new int3(0, -1, 0); // Down
  225.         faceDirections[1] = new int3(0, 0, 1);  // Forward
  226.         faceDirections[2] = new int3(0, 0, -1); // Back
  227.         faceDirections[3] = new int3(-1, 0, 0); // Left
  228.         faceDirections[4] = new int3(1, 0, 0);  // Right
  229.         faceDirections[5] = new int3(0, 1, 0);  // Up
  230.     }
  231.  
  232.     void LateUpdate()
  233.     {
  234.         for (int i = 0; i < jobList.Count; i++)
  235.         {
  236.             JobData jobData = jobList[i];
  237.             if (jobData.scheduled && (jobData.handle.IsCompleted || jobData.frameCount == maxProccessingFrames || jobData.instantCompletion))
  238.             {
  239.                 CompleteJob(jobData);
  240.                 jobList.RemoveAt(i);
  241.             }
  242.             else
  243.             {
  244.                 CreateAndScheduleJob(jobData);
  245.             }
  246.         }
  247.  
  248.         if (jobList.Count < maxJobsPerFrame && jobQueue.Count > 0)
  249.         {
  250.             int length = Mathf.Min(maxJobsPerFrame, jobQueue.Count);
  251.  
  252.             for (int i = 0; i < length; i++)
  253.             {
  254.                 jobList.Add(jobQueue[i]);
  255.             }
  256.  
  257.             jobQueue.RemoveRange(0, length);
  258.         }
  259.  
  260.         if (!loadedTerrain && jobQueue.Count == 0)
  261.         {
  262.             player.enabled = true;
  263.             loadingScreen.enabled = false;
  264.             loadedTerrain = true;
  265.         }
  266.     }
  267.  
  268.     void CreateAndScheduleJob(JobData data)
  269.     {
  270.         NativeList<float3> vertices = new NativeList<float3>(Allocator.Persistent);
  271.         NativeList<int> triangles = new NativeList<int>(Allocator.Persistent);
  272.         NativeList<float2> uvs = new NativeList<float2>(Allocator.Persistent);
  273.  
  274.         NativeList<float3> waterVertices = new NativeList<float3>(Allocator.Persistent);
  275.         NativeList<int> waterTriangles = new NativeList<int>(Allocator.Persistent);
  276.         NativeList<float2> waterUvs = new NativeList<float2>(Allocator.Persistent);
  277.  
  278.         int2 intOffset = new int2(data.noiseOffset.x, data.noiseOffset.y);
  279.  
  280.         if (data.customVoxelValues == default)
  281.         {
  282.             if (!chunkDataMap.ContainsKey(intOffset))
  283.                 chunkDataMap.Add(intOffset, noise.GenerateVoxelValues(data.noiseOffset));
  284.         }
  285.         else
  286.         {
  287.             if (!chunkDataMap.ContainsKey(intOffset))
  288.                 chunkDataMap.Add(intOffset, data.customVoxelValues);
  289.         }
  290.  
  291.         AddNeighboringChunkData(intOffset, data.noiseOffset);
  292.  
  293.         ChunkJob job = new ChunkJob
  294.         {
  295.             vertices = vertices,
  296.             triangles = triangles,
  297.             uvs = uvs,
  298.  
  299.             waterVertices = waterVertices,
  300.             waterTriangles = waterTriangles,
  301.             waterUvs = waterUvs,
  302.  
  303.             thisChunkData = chunkDataMap[intOffset],
  304.             rightChunkData = chunkDataMap[intOffset + new int2(chunkDimensions.x, 0)],
  305.             leftChunkData = chunkDataMap[intOffset + new int2(-chunkDimensions.x, 0)],
  306.             frontChunkData = chunkDataMap[intOffset + new int2(0, chunkDimensions.x)],
  307.             backChunkData = chunkDataMap[intOffset + new int2(0, -chunkDimensions.x)],
  308.  
  309.             faceDirections = faceDirections,
  310.             uvCordinates = uvCoordinates,
  311.             faceVertices = faceVertices,
  312.             chunkDimensions = new int2(chunkDimensions.x, chunkDimensions.y)
  313.         };
  314.  
  315.         data.vertices = vertices;
  316.         data.triangles = triangles;
  317.         data.uvs = uvs;
  318.  
  319.         data.waterVertices = waterVertices;
  320.         data.waterTriangles = waterTriangles;
  321.         data.waterUvs = waterUvs;
  322.  
  323.         data.handle = job.Schedule();
  324.         data.scheduled = true;
  325.     }
  326.  
  327.     void CompleteJob(JobData data)
  328.     {
  329.         data.handle.Complete();
  330.  
  331.         NativeArray<Vector3> vertexArray = data.vertices.AsArray().Reinterpret<Vector3>();
  332.         NativeArray<int> triangleArray = data.triangles.AsArray();
  333.         NativeArray<Vector2> uvArray = data.uvs.AsArray().Reinterpret<Vector2>();
  334.  
  335.         // Assign data to mesh
  336.         data.landMesh.SetVertexBufferParams(vertexArray.Length, new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3));
  337.         data.landMesh.SetVertexBufferData(vertexArray, 0, 0, vertexArray.Length, 0, MeshUpdateFlags.Default);
  338.         data.landMesh.SetIndexBufferParams(triangleArray.Length, IndexFormat.UInt32);
  339.         data.landMesh.SetIndexBufferData(triangleArray, 0, 0, triangleArray.Length, MeshUpdateFlags.Default);
  340.         data.landMesh.SetSubMesh(0, new SubMeshDescriptor(0, triangleArray.Length), MeshUpdateFlags.Default);
  341.         data.landMesh.SetUVs(0, uvArray);
  342.  
  343.         data.landMesh.RecalculateBounds();
  344.         data.landMesh.RecalculateNormals();
  345.  
  346.         data.vertices.Dispose();
  347.         data.triangles.Dispose();
  348.         data.uvs.Dispose();
  349.  
  350.         vertexArray.Dispose();
  351.         triangleArray.Dispose();
  352.         uvArray.Dispose();
  353.  
  354.         if (data.waterVertices.Length > 0)
  355.         {
  356.             NativeArray<Vector3> wVertexArray = data.waterVertices.AsArray().Reinterpret<Vector3>();
  357.             NativeArray<int> wTriangleArray = data.waterTriangles.AsArray();
  358.             NativeArray<Vector2> wUvArray = data.waterUvs.AsArray().Reinterpret<Vector2>();
  359.  
  360.             // Assign data to mesh
  361.             data.waterMesh.SetVertexBufferParams(wVertexArray.Length, new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3));
  362.             data.waterMesh.SetVertexBufferData(wVertexArray, 0, 0, wVertexArray.Length, 0, MeshUpdateFlags.Default);
  363.             data.waterMesh.SetIndexBufferParams(wTriangleArray.Length, IndexFormat.UInt32);
  364.             data.waterMesh.SetIndexBufferData(wTriangleArray, 0, 0, wTriangleArray.Length, MeshUpdateFlags.Default);
  365.             data.waterMesh.SetSubMesh(0, new SubMeshDescriptor(0, wTriangleArray.Length), MeshUpdateFlags.Default);
  366.             data.waterMesh.SetUVs(0, wUvArray);
  367.  
  368.             data.waterMesh.RecalculateBounds();
  369.             data.waterMesh.RecalculateNormals();
  370.  
  371.             wVertexArray.Dispose();
  372.             wTriangleArray.Dispose();
  373.             wUvArray.Dispose();
  374.         }
  375.  
  376.         data.waterTriangles.Dispose();
  377.         data.waterVertices.Dispose();
  378.         data.waterUvs.Dispose();
  379.     }
  380.  
  381.     private void AddNeighboringChunkData(int2 intOffset, Vector2Int offset)
  382.     {
  383.         if (!chunkDataMap.ContainsKey(intOffset + new int2(chunkDimensions.x, 0)))
  384.             chunkDataMap.Add(intOffset + new int2(chunkDimensions.x, 0), noise.GenerateVoxelValues(offset + Vector2Int.right * chunkDimensions.x));
  385.  
  386.         if (!chunkDataMap.ContainsKey(intOffset + new int2(-chunkDimensions.x, 0)))
  387.             chunkDataMap.Add(intOffset + new int2(-chunkDimensions.x, 0), noise.GenerateVoxelValues(offset + Vector2Int.left * chunkDimensions.x));
  388.  
  389.         if (!chunkDataMap.ContainsKey(intOffset + new int2(0, chunkDimensions.x)))
  390.             chunkDataMap.Add(intOffset + new int2(0, chunkDimensions.x), noise.GenerateVoxelValues(offset + Vector2Int.up * chunkDimensions.x));
  391.  
  392.         if (!chunkDataMap.ContainsKey(intOffset + new int2(0, -chunkDimensions.x)))
  393.             chunkDataMap.Add(intOffset + new int2(0, -chunkDimensions.x), noise.GenerateVoxelValues(offset + Vector2Int.down * chunkDimensions.x));
  394.     }
  395.  
  396.     //Converts 3D index into 1D
  397.     public static int CalculateVoxelIndex(int x, int y, int z)
  398.     {
  399.         return x * ChunkDimensions.y * ChunkDimensions.x + y * ChunkDimensions.x + z;
  400.     }
  401.  
  402.     //Sampled curve that can be used in job
  403.     private NativeArray<float> SampleCurve(AnimationCurve curve, int resolution)
  404.     {
  405.         NativeArray<float> curveArr = new NativeArray<float>(resolution, Allocator.Persistent);
  406.  
  407.         for (int i = 0; i < resolution; i++)
  408.         {
  409.             curveArr[i] = curve.Evaluate((float)i / resolution);
  410.         }
  411.  
  412.         return curveArr;
  413.     }
  414.  
  415.     private void OnDestroy()
  416.     {
  417.         foreach (var chunk in chunkDataMap)
  418.         {
  419.             chunk.Value.Dispose();
  420.         }
  421.         chunkDataMap.Dispose();
  422.  
  423.         foreach (var job in jobList)
  424.         {
  425.             job.handle.Complete();
  426.             DisposeJobData(job);
  427.         }
  428.  
  429.         foreach (var queueJob in jobQueue)
  430.         {
  431.             DisposeJobData(queueJob);
  432.         }
  433.  
  434.         faceVertices.Dispose();
  435.         faceDirections.Dispose();
  436.         uvCoordinates.Dispose();
  437.         sampledHeightCurve.Dispose();
  438.         noise.Dispose();
  439.     }
  440.  
  441.     void DisposeJobData(JobData job)
  442.     {
  443.         if (job.vertices.IsCreated) job.vertices.Dispose();
  444.         if (job.triangles.IsCreated) job.triangles.Dispose();
  445.         if (job.uvs.IsCreated) job.uvs.Dispose();
  446.         if (job.waterVertices.IsCreated) job.waterVertices.Dispose();
  447.         if (job.waterTriangles.IsCreated) job.waterTriangles.Dispose();
  448.         if (job.waterUvs.IsCreated) job.waterUvs.Dispose();
  449.     }
  450. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement