Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System.Collections.Generic;
- using Unity.Collections;
- using UnityEngine;
- using Unity.Mathematics;
- using Unity.Jobs;
- using UnityEngine.Rendering;
- public class ChunkGenerator : MonoBehaviour
- {
- public Texture2D atlas;
- public Material material;
- public Material waterMaterial;
- public Vector2Int chunkDimensions;
- public static Vector2Int ChunkDimensions;
- public int maxProccessingFrames;
- public int maxJobsPerFrame;
- [Header("Terrain Settings")]
- public int surfaceLevel;
- public int seaLevel;
- public float sandDensity;
- public float maxTreeHeight;
- [Header("Noise Settings")]
- public float persistance;
- public float frequency;
- public float lacunarity;
- public int numOfOctaves;
- public Vector2Int offset;
- public AnimationCurve heightCurve;
- public float heightMultiplier;
- public float noise3DContribution;
- public int noiseSeed;
- [Header(" ")]
- public bool autoUpdate;
- public string executionTime;
- Noise noise;
- NativeArray<int3> faceVertices;
- NativeArray<int3> faceDirections;
- NativeArray<float2> uvCoordinates;
- NativeArray<float> sampledCurve;
- [HideInInspector]
- public NativeHashMap<int2, NativeArray<int>> chunkDataMap;
- List<JobData> jobList;
- List<JobData> jobQueue;
- private void Start()
- {
- Initialize();
- executionTime = "N/A";
- }
- private void OnValidate()
- {
- if (autoUpdate)
- {
- GenerateChunk();
- }
- }
- public void GenerateChunk()
- {
- Initialize();
- if (GameObject.Find("Chunk") == null)
- {
- float startTime = Time.realtimeSinceStartup;
- CreateChunkObject(offset);
- CompleteJobs();
- executionTime = (Time.realtimeSinceStartup - startTime) * 1000f + " ms";
- }
- else
- {
- float startTime = Time.realtimeSinceStartup;
- Mesh landMesh = new Mesh
- {
- indexFormat = IndexFormat.UInt32
- };
- Mesh waterMesh = new Mesh
- {
- indexFormat = IndexFormat.UInt32
- };
- CreateChunkMesh(landMesh, waterMesh, offset, true);
- CompleteJobs();
- executionTime = (Time.realtimeSinceStartup - startTime) * 1000f + " ms";
- GameObject.Find("Chunk").GetComponent<MeshFilter>().mesh = landMesh;
- }
- faceVertices.Dispose();
- faceDirections.Dispose();
- uvCoordinates.Dispose();
- }
- public GameObject CreateChunkObject(Vector2Int offset)
- {
- GameObject chunk = new GameObject("Chunk");
- var meshRenderer = chunk.AddComponent<MeshRenderer>();
- meshRenderer.sharedMaterial = new Material(material)
- {
- mainTexture = atlas
- };
- Mesh landMesh = new Mesh
- {
- indexFormat = IndexFormat.UInt32
- };
- Mesh waterMesh = new Mesh
- {
- indexFormat = IndexFormat.UInt32
- };
- CreateChunkMesh(landMesh, waterMesh, offset, false);
- chunk.AddComponent<MeshFilter>().mesh = landMesh;
- //Water is seperate object so it can have transparency
- GameObject water = new GameObject("Water");
- water.transform.parent = chunk.transform;
- water.AddComponent<MeshRenderer>().sharedMaterial = waterMaterial;
- water.AddComponent<MeshFilter>().mesh = waterMesh;
- return chunk;
- }
- public void CreateChunkMesh(Mesh landMesh, Mesh waterMesh, Vector2Int offset, bool instantCompletion, NativeArray<int> customVoxelValues = default)
- {
- NativeList<float3> vertices = new NativeList<float3>(Allocator.Persistent);
- NativeList<int> triangles = new NativeList<int>(Allocator.Persistent);
- NativeList<float2> uvs = new NativeList<float2>(Allocator.Persistent);
- NativeList<float3> waterVertices = new NativeList<float3>(Allocator.Persistent);
- NativeList<int> waterTriangles = new NativeList<int>(Allocator.Persistent);
- NativeList<float2> waterUvs = new NativeList<float2>(Allocator.Persistent);
- int2 intOffset = new int2(offset.x, offset.y);
- if (customVoxelValues == default)
- {
- if (!chunkDataMap.ContainsKey(intOffset))
- chunkDataMap.Add(intOffset, noise.GenerateVoxelValues(offset));
- }
- else
- {
- if (!chunkDataMap.ContainsKey(intOffset))
- chunkDataMap.Add(intOffset, customVoxelValues);
- }
- //Neighbouring data is used to determine if faces should be rendered at the border of the chunk
- AddNeighboringChunkData(intOffset, offset);
- ChunkJob job = new ChunkJob
- {
- vertices = vertices,
- triangles = triangles,
- uvs = uvs,
- waterVertices = waterVertices,
- waterTriangles = waterTriangles,
- waterUvs = waterUvs,
- thisChunkData = chunkDataMap[intOffset],
- rightChunkData = chunkDataMap[intOffset + new int2(chunkDimensions.x, 0)],
- leftChunkData = chunkDataMap[intOffset + new int2(-chunkDimensions.x, 0)],
- frontChunkData = chunkDataMap[intOffset + new int2(0, chunkDimensions.x)],
- backChunkData = chunkDataMap[intOffset + new int2(0, -chunkDimensions.x)],
- faceDirections = faceDirections,
- uvCordinates = uvCoordinates,
- faceVertices = faceVertices,
- chunkDimensions = new int2(chunkDimensions.x, chunkDimensions.y)
- };
- JobHandle handle = job.Schedule();
- if (instantCompletion)
- {
- jobList.Add(new JobData
- {
- mesh = landMesh,
- waterMesh = waterMesh,
- handle = handle,
- vertices = vertices,
- triangles = triangles,
- uvs = uvs,
- waterVertices = waterVertices,
- waterTriangles = waterTriangles,
- waterUvs = waterUvs,
- instantCompletion = true
- });
- }
- else
- {
- jobQueue.Add(new JobData
- {
- mesh = landMesh,
- waterMesh = waterMesh,
- handle = handle,
- vertices = vertices,
- triangles = triangles,
- uvs = uvs,
- waterVertices = waterVertices,
- waterTriangles = waterTriangles,
- waterUvs = waterUvs,
- instantCompletion = false
- });
- }
- }
- private void AddNeighboringChunkData(int2 intOffset, Vector2Int offset)
- {
- if (!chunkDataMap.ContainsKey(intOffset + new int2(chunkDimensions.x, 0)))
- chunkDataMap.Add(intOffset + new int2(chunkDimensions.x, 0), noise.GenerateVoxelValues(offset + Vector2Int.right * chunkDimensions.x));
- if (!chunkDataMap.ContainsKey(intOffset + new int2(-chunkDimensions.x, 0)))
- chunkDataMap.Add(intOffset + new int2(-chunkDimensions.x, 0), noise.GenerateVoxelValues(offset + Vector2Int.left * chunkDimensions.x));
- if (!chunkDataMap.ContainsKey(intOffset + new int2(0, chunkDimensions.x)))
- chunkDataMap.Add(intOffset + new int2(0, chunkDimensions.x), noise.GenerateVoxelValues(offset + Vector2Int.up * chunkDimensions.x));
- if (!chunkDataMap.ContainsKey(intOffset + new int2(0, -chunkDimensions.x)))
- chunkDataMap.Add(intOffset + new int2(0, -chunkDimensions.x), noise.GenerateVoxelValues(offset + Vector2Int.down * chunkDimensions.x));
- }
- public static int CalculateVoxelIndex(int x, int y, int z)
- {
- //Converts 3D index into 1D
- return x * (ChunkDimensions.y * ChunkDimensions.x) + y * ChunkDimensions.x + z;
- }
- private void LateUpdate()
- {
- CompleteJobs();
- }
- void CompleteJobs()
- {
- for (int i = jobList.Count - 1; i >= 0; i--)
- {
- jobList[i].frameCount++;
- if (jobList[i].frameCount - 1 == maxProccessingFrames || jobList[i].handle.IsCompleted || jobList[i].instantCompletion)
- {
- jobList[i].handle.Complete();
- NativeArray<Vector3> vertexArray = jobList[i].vertices.AsArray().Reinterpret<Vector3>();
- NativeArray<int> triangleArray = jobList[i].triangles.AsArray();
- NativeArray<Vector2> uvArray = jobList[i].uvs.AsArray().Reinterpret<Vector2>();
- // Assign data to mesh
- jobList[i].mesh.SetVertexBufferParams(vertexArray.Length, new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3));
- jobList[i].mesh.SetVertexBufferData(vertexArray, 0, 0, vertexArray.Length, 0, MeshUpdateFlags.Default);
- jobList[i].mesh.SetIndexBufferParams(triangleArray.Length, IndexFormat.UInt32);
- jobList[i].mesh.SetIndexBufferData(triangleArray, 0, 0, triangleArray.Length, MeshUpdateFlags.Default);
- jobList[i].mesh.SetSubMesh(0, new SubMeshDescriptor(0, triangleArray.Length), MeshUpdateFlags.Default);
- jobList[i].mesh.SetUVs(0, uvArray);
- jobList[i].mesh.RecalculateBounds();
- jobList[i].mesh.RecalculateNormals();
- jobList[i].vertices.Dispose();
- jobList[i].triangles.Dispose();
- jobList[i].uvs.Dispose();
- vertexArray.Dispose();
- triangleArray.Dispose();
- uvArray.Dispose();
- if (jobList[i].waterVertices.Length > 0)
- {
- NativeArray<Vector3> wVertexArray = jobList[i].waterVertices.AsArray().Reinterpret<Vector3>();
- NativeArray<int> wTriangleArray = jobList[i].waterTriangles.AsArray();
- NativeArray<Vector2> wUvArray = jobList[i].waterUvs.AsArray().Reinterpret<Vector2>();
- // Assign data to mesh
- jobList[i].waterMesh.SetVertexBufferParams(wVertexArray.Length, new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3));
- jobList[i].waterMesh.SetVertexBufferData(wVertexArray, 0, 0, wVertexArray.Length, 0, MeshUpdateFlags.Default);
- jobList[i].waterMesh.SetIndexBufferParams(wTriangleArray.Length, IndexFormat.UInt32);
- jobList[i].waterMesh.SetIndexBufferData(wTriangleArray, 0, 0, wTriangleArray.Length, MeshUpdateFlags.Default);
- jobList[i].waterMesh.SetSubMesh(0, new SubMeshDescriptor(0, wTriangleArray.Length), MeshUpdateFlags.Default);
- jobList[i].waterMesh.SetUVs(0, wUvArray);
- jobList[i].waterMesh.RecalculateBounds();
- jobList[i].waterMesh.RecalculateNormals();
- jobList[i].waterVertices.Dispose();
- jobList[i].waterTriangles.Dispose();
- jobList[i].waterUvs.Dispose();
- wVertexArray.Dispose();
- wTriangleArray.Dispose();
- wUvArray.Dispose();
- }
- jobList.RemoveAt(i);
- }
- }
- if(jobList.Count < maxJobsPerFrame && jobQueue.Count > 0)
- {
- int length = Mathf.Min(maxJobsPerFrame, jobQueue.Count);
- for (int i = 0; i < length; i++)
- {
- jobList.Add(jobQueue[i]);
- }
- jobQueue.RemoveRange(0, length);
- }
- }
- class JobData
- {
- public JobHandle handle;
- public Mesh mesh;
- public Mesh waterMesh;
- public NativeList<float3> vertices;
- public NativeList<int> triangles;
- public NativeList<float2> uvs;
- public NativeList<float3> waterVertices;
- public NativeList<int> waterTriangles;
- public NativeList<float2> waterUvs;
- public int frameCount;
- public bool instantCompletion;
- }
- //Sampled curve that can be used in job
- private NativeArray<float> SampleCurve(AnimationCurve curve, int resolution)
- {
- NativeArray<float> curveArr = new NativeArray<float>(resolution, Allocator.Persistent);
- for (int i = 0; i < resolution; i++)
- {
- curveArr[i] = curve.Evaluate((float)i / resolution);
- }
- return curveArr;
- }
- private void Initialize()
- {
- System.Random rand = new System.Random(noiseSeed);
- chunkDataMap = new NativeHashMap<int2, NativeArray<int>>(1024, Allocator.Persistent);
- jobList = new();
- jobQueue = new();
- sampledCurve = SampleCurve(heightCurve, 256);
- ChunkDimensions = chunkDimensions;
- noise = new Noise(frequency, persistance, lacunarity, numOfOctaves, heightMultiplier, rand.Next() / 10000, surfaceLevel, seaLevel, noise3DContribution, sandDensity, maxTreeHeight, sampledCurve);
- faceVertices = new NativeArray<int3>(24, Allocator.Persistent);
- //Bottom face
- faceVertices[0] = new int3(1, 0, 0);
- faceVertices[1] = new int3(1, 0, 1);
- faceVertices[2] = new int3(0, 0, 1);
- faceVertices[3] = new int3(0, 0, 0);
- //Front face
- faceVertices[4] = new int3(1, 0, 1);
- faceVertices[5] = new int3(1, 1, 1);
- faceVertices[6] = new int3(0, 1, 1);
- faceVertices[7] = new int3(0, 0, 1);
- //Back face
- faceVertices[8] = new int3(0, 0, 0);
- faceVertices[9] = new int3(0, 1, 0);
- faceVertices[10] = new int3(1, 1, 0);
- faceVertices[11] = new int3(1, 0, 0);
- //Left face
- faceVertices[12] = new int3(0, 0, 1);
- faceVertices[13] = new int3(0, 1, 1);
- faceVertices[14] = new int3(0, 1, 0);
- faceVertices[15] = new int3(0, 0, 0);
- //Right face
- faceVertices[16] = new int3(1, 0, 0);
- faceVertices[17] = new int3(1, 1, 0);
- faceVertices[18] = new int3(1, 1, 1);
- faceVertices[19] = new int3(1, 0, 1);
- //Top face
- faceVertices[20] = new int3(0, 1, 0);
- faceVertices[21] = new int3(0, 1, 1);
- faceVertices[22] = new int3(1, 1, 1);
- faceVertices[23] = new int3(1, 1, 0);
- uvCoordinates = new NativeArray<float2>(6, Allocator.Persistent);
- uvCoordinates[0] = new float2(.5f, 0);
- uvCoordinates[1] = new float2(.5f, .5f);
- uvCoordinates[2] = new float2(.5f, .5f);
- uvCoordinates[3] = new float2(.5f, .5f);
- uvCoordinates[4] = new float2(.5f, .5f);
- uvCoordinates[5] = new float2(0, 0);
- faceDirections = new NativeArray<int3>(6, Allocator.Persistent);
- faceDirections[0] = new int3(0, -1, 0); // Down
- faceDirections[1] = new int3(0, 0, 1); // Forward
- faceDirections[2] = new int3(0, 0, -1); // Back
- faceDirections[3] = new int3(-1, 0, 0); // Left
- faceDirections[4] = new int3(1, 0, 0); // Right
- faceDirections[5] = new int3(0, 1, 0); // Up
- }
- private void OnDestroy()
- {
- faceVertices.Dispose();
- faceDirections.Dispose();
- uvCoordinates.Dispose();
- sampledCurve.Dispose();
- noise.Dispose();
- foreach (var chunk in chunkDataMap)
- {
- chunk.Value.Dispose();
- }
- chunkDataMap.Dispose();
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement