[Add] Optimization

This commit is contained in:
2026-03-31 12:33:11 +07:00
parent 7c2491c1ee
commit 5f35ed8a6e
@@ -42,21 +42,34 @@ namespace InfiniteWorld.VoxelWorld
[Header("Runtime")]
[SerializeField, Min(1)] private int maxAsyncChunkJobs = 2;
[SerializeField, Min(1)] private int maxChunkBuildsPerFrame = 1;
[SerializeField, Min(1)] private int maxChunkMeshBuildsPerFrame = 1;
[SerializeField, Min(1)] private int maxColliderAppliesPerFrame = 1;
[SerializeField, Min(0)] private int maxNeighborRefreshesPerFrame = 2;
[SerializeField, Min(1)] private int renderRegionSizeInChunks = 4;
[SerializeField, Min(1)] private int maxRegionBuildsPerFrame = 1;
private readonly Dictionary<Vector2Int, ChunkRuntime> chunks = new Dictionary<Vector2Int, ChunkRuntime>();
private readonly Dictionary<Vector2Int, RegionRuntime> regions = new Dictionary<Vector2Int, RegionRuntime>();
private readonly Dictionary<Vector2Int, int> regionVersions = new Dictionary<Vector2Int, int>();
private readonly Queue<ChunkBuildResult> completedBuilds = new Queue<ChunkBuildResult>();
private readonly Queue<RegionBuildResult> completedRegionBuilds = new Queue<RegionBuildResult>();
private readonly Queue<PendingColliderMeshApply> pendingColliderApplies = new Queue<PendingColliderMeshApply>();
private readonly Queue<Vector2Int> dirtyChunkMeshes = new Queue<Vector2Int>();
private readonly HashSet<Vector2Int> queuedChunkMeshes = new HashSet<Vector2Int>();
private readonly Queue<Vector2Int> dirtyRegions = new Queue<Vector2Int>();
private readonly HashSet<Vector2Int> queuedRegions = new HashSet<Vector2Int>();
private readonly Queue<Vector2Int> pendingNeighborRefreshes = new Queue<Vector2Int>();
private readonly HashSet<Vector2Int> queuedNeighborRefreshes = new HashSet<Vector2Int>();
private readonly object generationLock = new object();
private readonly object regionBuildLock = new object();
private Transform chunkRoot;
private Transform regionRoot;
private Vector2Int lastGeneratedCenter = new Vector2Int(int.MinValue, int.MinValue);
private int activeGenerationJobs;
private int nextChunkRuntimeId;
private int generationSession;
private int regionRebuildSession;
private VoxelWorldAtlas atlas;
private int atlasBiomeCount;
private bool regionRebuildLoopRunning;
@@ -64,6 +77,7 @@ namespace InfiniteWorld.VoxelWorld
private void Awake()
{
generationSession++;
regionRebuildSession++;
EnsureRuntimeData();
EnsureChunkRoot();
EnsureRegionRoot();
@@ -88,6 +102,10 @@ namespace InfiniteWorld.VoxelWorld
}
DrainCompletedBuilds(maxChunkBuildsPerFrame);
DrainDirtyChunkMeshes(maxChunkMeshBuildsPerFrame);
DrainPendingColliderApplies(maxColliderAppliesPerFrame);
DrainNeighborRefreshes(maxNeighborRefreshesPerFrame);
DrainCompletedRegionBuilds(maxRegionBuildsPerFrame);
ScheduleChunkGeneration(centerChunk);
EnsureBlockingChunksLoaded(centerChunk);
}
@@ -95,9 +113,15 @@ namespace InfiniteWorld.VoxelWorld
private void OnDisable()
{
generationSession++;
regionRebuildSession++;
lock (generationLock)
{
completedBuilds.Clear();
activeGenerationJobs = 0;
}
lock (regionBuildLock)
{
completedRegionBuilds.Clear();
}
CleanupChunks();
@@ -110,6 +134,9 @@ namespace InfiniteWorld.VoxelWorld
{
maxMountainHeight = Mathf.Max(1, maxMountainHeight);
chunkSize = Mathf.Max(8, chunkSize);
maxChunkMeshBuildsPerFrame = Mathf.Max(1, maxChunkMeshBuildsPerFrame);
maxColliderAppliesPerFrame = Mathf.Max(1, maxColliderAppliesPerFrame);
maxNeighborRefreshesPerFrame = Mathf.Max(0, maxNeighborRefreshesPerFrame);
renderRegionSizeInChunks = Mathf.Max(1, renderRegionSizeInChunks);
maxRegionBuildsPerFrame = Mathf.Max(1, maxRegionBuildsPerFrame);
int configuredBiomeCount = CountConfiguredBiomes();
@@ -220,7 +247,7 @@ namespace InfiniteWorld.VoxelWorld
runtime.State = ChunkState.Generating;
runtime.Version++;
GenerateChunkDataAsync(coord, runtime.Version, generationSession).Forget();
GenerateChunkDataAsync(coord, runtime.Version, generationSession, runtime.RuntimeId).Forget();
}
}
@@ -231,21 +258,18 @@ namespace InfiniteWorld.VoxelWorld
{
Vector2Int coord = requiredCoords[i];
ChunkRuntime runtime = GetOrCreateChunkRuntime(coord);
if (runtime.IsRendered)
{
continue;
}
if (runtime.State == ChunkState.Generating)
if (runtime.IsRendered || runtime.HasData || runtime.State == ChunkState.Generating || runtime.State == ChunkState.SyncBuilding)
{
continue;
}
runtime.Version++;
runtime.State = ChunkState.SyncBuilding;
ApplyBuildResult(GenerateChunkData(coord, runtime.Version, generationSession));
RenderChunk(coord);
RefreshNeighborBorders(coord);
if (ApplyBuildResult(GenerateChunkData(coord, runtime.Version, generationSession, runtime.RuntimeId)))
{
EnqueueChunkMeshBuild(coord);
QueueNeighborRefresh(coord);
}
}
}
@@ -273,11 +297,11 @@ namespace InfiniteWorld.VoxelWorld
MarkRegionDirty(coord);
chunks.Remove(coord);
runtime.Dispose();
RefreshNeighborBorders(coord);
QueueNeighborRefresh(coord);
}
}
private ChunkBuildResult GenerateChunkData(Vector2Int coord, int version, int session)
private ChunkBuildResult GenerateChunkData(Vector2Int coord, int version, int session, int runtimeId)
{
int margin = Mathf.Max(2, smoothingPasses + 1);
int sampleSize = chunkSize + margin * 2;
@@ -317,7 +341,7 @@ namespace InfiniteWorld.VoxelWorld
}
}
return new ChunkBuildResult(coord, heights, biomeIndices, version, session);
return new ChunkBuildResult(coord, heights, biomeIndices, version, session, runtimeId);
}
private bool[,] SmoothSampledMask(bool[,] source)
@@ -439,7 +463,8 @@ namespace InfiniteWorld.VoxelWorld
runtime.EnsureCreated(coord, chunkRoot, chunkSize);
ChunkMeshBuild meshBuild = BuildChunkMesh(coord, runtime.Heights, runtime.BiomeIndices);
runtime.ApplyMeshes(meshBuild, chunkSize);
runtime.ApplyRenderData(meshBuild.RenderSnapshot);
EnqueueColliderApply(coord, runtime.Version, runtime.RuntimeId, meshBuild.ColliderMesh);
runtime.State = ChunkState.Rendered;
MarkRegionDirty(coord);
}
@@ -453,7 +478,8 @@ namespace InfiniteWorld.VoxelWorld
BuildMountainTops(coord, heights, biomeIndices, renderBuffers, colliderBuffers);
BuildMountainSides(coord, heights, biomeIndices, renderBuffers, colliderBuffers);
return new ChunkMeshBuild(renderBuffers.ToMesh($"Render_{coord.x}_{coord.y}"), colliderBuffers.ToMesh($"Collider_{coord.x}_{coord.y}"));
ChunkRenderSnapshot renderSnapshot = renderBuffers.ToSnapshot();
return new ChunkMeshBuild(colliderBuffers.ToMesh($"Collider_{coord.x}_{coord.y}"), renderSnapshot);
}
private void BuildGroundSurface(int[] heights, byte[] biomeIndices, MeshBuffers renderBuffers)
@@ -884,22 +910,33 @@ namespace InfiniteWorld.VoxelWorld
return new Vector2Int(coord.x * chunkSize + localX, coord.y * chunkSize + localZ);
}
private async UniTaskVoid GenerateChunkDataAsync(Vector2Int coord, int version, int session)
private async UniTaskVoid GenerateChunkDataAsync(Vector2Int coord, int version, int session, int runtimeId)
{
try
{
ChunkBuildResult result = await UniTask.RunOnThreadPool(() => GenerateChunkData(coord, version, session));
ChunkBuildResult result = await UniTask.RunOnThreadPool(() => GenerateChunkData(coord, version, session, runtimeId));
lock (generationLock)
{
completedBuilds.Enqueue(result);
activeGenerationJobs = Mathf.Max(0, activeGenerationJobs - 1);
if (session == generationSession)
{
completedBuilds.Enqueue(result);
activeGenerationJobs = Mathf.Max(0, activeGenerationJobs - 1);
}
}
}
catch (Exception exception)
{
lock (generationLock)
{
activeGenerationJobs = Mathf.Max(0, activeGenerationJobs - 1);
if (session == generationSession)
{
activeGenerationJobs = Mathf.Max(0, activeGenerationJobs - 1);
}
}
if (session == generationSession && chunks.TryGetValue(coord, out ChunkRuntime runtime) && runtime.Version == version && runtime.RuntimeId == runtimeId)
{
runtime.State = ChunkState.None;
}
Debug.LogException(exception, this);
@@ -927,12 +964,100 @@ namespace InfiniteWorld.VoxelWorld
continue;
}
RenderChunk(result.Coord);
RefreshNeighborBorders(result.Coord);
EnqueueChunkMeshBuild(result.Coord);
QueueNeighborRefresh(result.Coord);
builds++;
}
}
private void DrainDirtyChunkMeshes(int maxBuilds)
{
int builds = 0;
while (builds < maxBuilds)
{
if (dirtyChunkMeshes.Count == 0)
{
break;
}
Vector2Int coord = dirtyChunkMeshes.Dequeue();
queuedChunkMeshes.Remove(coord);
if (!chunks.TryGetValue(coord, out ChunkRuntime runtime) || !runtime.HasData)
{
continue;
}
RenderChunk(coord);
builds++;
}
}
private void EnqueueChunkMeshBuild(Vector2Int coord)
{
if (!queuedChunkMeshes.Add(coord))
{
return;
}
dirtyChunkMeshes.Enqueue(coord);
}
private void EnqueueColliderApply(Vector2Int coord, int version, int runtimeId, Mesh colliderMesh)
{
pendingColliderApplies.Enqueue(new PendingColliderMeshApply(coord, version, runtimeId, colliderMesh));
}
private void DrainPendingColliderApplies(int maxApplies)
{
int applies = 0;
while (applies < maxApplies)
{
if (pendingColliderApplies.Count == 0)
{
break;
}
PendingColliderMeshApply pending = pendingColliderApplies.Dequeue();
if (!chunks.TryGetValue(pending.Coord, out ChunkRuntime runtime) || runtime.Version != pending.Version || runtime.RuntimeId != pending.RuntimeId)
{
DestroyMeshAsset(pending.ColliderMesh);
applies++;
continue;
}
runtime.ApplyColliderMesh(pending.ColliderMesh);
applies++;
}
}
private void QueueNeighborRefresh(Vector2Int coord)
{
if (!queuedNeighborRefreshes.Add(coord))
{
return;
}
pendingNeighborRefreshes.Enqueue(coord);
}
private void DrainNeighborRefreshes(int maxRefreshes)
{
int refreshes = 0;
while (refreshes < maxRefreshes)
{
if (pendingNeighborRefreshes.Count == 0)
{
break;
}
Vector2Int coord = pendingNeighborRefreshes.Dequeue();
queuedNeighborRefreshes.Remove(coord);
RefreshNeighborBorders(coord);
refreshes++;
}
}
private void RefreshNeighborBorders(Vector2Int coord)
{
TryRenderNeighbor(coord + Vector2Int.up);
@@ -948,12 +1073,15 @@ namespace InfiniteWorld.VoxelWorld
return;
}
RenderChunk(coord);
EnqueueChunkMeshBuild(coord);
}
private void MarkRegionDirty(Vector2Int chunkCoord)
{
Vector2Int regionCoord = ChunkToRegion(chunkCoord);
regionVersions.TryGetValue(regionCoord, out int version);
regionVersions[regionCoord] = version + 1;
if (!queuedRegions.Add(regionCoord))
{
return;
@@ -971,16 +1099,16 @@ namespace InfiniteWorld.VoxelWorld
}
regionRebuildLoopRunning = true;
ProcessDirtyRegionsAsync().Forget();
ProcessDirtyRegionsAsync(regionRebuildSession).Forget();
}
private async UniTaskVoid ProcessDirtyRegionsAsync()
private async UniTaskVoid ProcessDirtyRegionsAsync(int session)
{
try
{
while (dirtyRegions.Count > 0)
{
if (!this || !isActiveAndEnabled)
if (!this || !isActiveAndEnabled || session != regionRebuildSession)
{
break;
}
@@ -990,7 +1118,19 @@ namespace InfiniteWorld.VoxelWorld
{
Vector2Int regionCoord = dirtyRegions.Dequeue();
queuedRegions.Remove(regionCoord);
RebuildRegion(regionCoord);
if (!TryCaptureRegionBuildRequest(regionCoord, out RegionBuildRequest request))
{
buildsThisFrame++;
continue;
}
RegionBuildResult result = await UniTask.RunOnThreadPool(() => BuildRegionBuffers(request));
lock (regionBuildLock)
{
completedRegionBuilds.Enqueue(result);
}
buildsThisFrame++;
}
@@ -1002,61 +1142,45 @@ namespace InfiniteWorld.VoxelWorld
}
finally
{
regionRebuildLoopRunning = false;
if (dirtyRegions.Count > 0)
if (session == regionRebuildSession)
{
EnsureRegionRebuildLoop();
regionRebuildLoopRunning = false;
if (dirtyRegions.Count > 0)
{
EnsureRegionRebuildLoop();
}
}
}
}
private void RebuildRegion(Vector2Int regionCoord)
private bool TryCaptureRegionBuildRequest(Vector2Int regionCoord, out RegionBuildRequest request)
{
if (atlas == null || regionRoot == null)
{
return;
request = new RegionBuildRequest();
return false;
}
Vector2Int baseChunk = new Vector2Int(regionCoord.x * renderRegionSizeInChunks, regionCoord.y * renderRegionSizeInChunks);
List<CombineInstance> combineInstances = new List<CombineInstance>(renderRegionSizeInChunks * renderRegionSizeInChunks);
List<RegionChunkSnapshot> chunkSnapshots = new List<RegionChunkSnapshot>(renderRegionSizeInChunks * renderRegionSizeInChunks);
for (int z = 0; z < renderRegionSizeInChunks; z++)
{
for (int x = 0; x < renderRegionSizeInChunks; x++)
{
Vector2Int chunkCoord = new Vector2Int(baseChunk.x + x, baseChunk.y + z);
if (!chunks.TryGetValue(chunkCoord, out ChunkRuntime runtime) || runtime.RenderMesh == null || runtime.RenderMesh.vertexCount == 0)
if (!chunks.TryGetValue(chunkCoord, out ChunkRuntime runtime) || runtime.RenderSnapshot == null || runtime.RenderSnapshot.IsEmpty)
{
continue;
}
combineInstances.Add(new CombineInstance
{
mesh = runtime.RenderMesh,
transform = Matrix4x4.Translate(new Vector3(x * chunkSize, 0f, z * chunkSize))
});
chunkSnapshots.Add(new RegionChunkSnapshot(runtime.RenderSnapshot, new Vector3(x * chunkSize, 0f, z * chunkSize)));
}
}
if (combineInstances.Count == 0)
{
if (regions.TryGetValue(regionCoord, out RegionRuntime regionToRemove))
{
regionToRemove.Dispose();
regions.Remove(regionCoord);
}
return;
}
RegionRuntime region = GetOrCreateRegionRuntime(regionCoord);
region.EnsureCreated(regionCoord, regionRoot, chunkSize, renderRegionSizeInChunks, atlas.Material);
Mesh combinedMesh = new Mesh { name = $"Region_{regionCoord.x}_{regionCoord.y}" };
combinedMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
combinedMesh.CombineMeshes(combineInstances.ToArray(), true, true, false);
combinedMesh.RecalculateBounds();
region.ApplyMesh(combinedMesh);
regionVersions.TryGetValue(regionCoord, out int version);
request = new RegionBuildRequest(regionCoord, version, generationSession, chunkSnapshots.ToArray());
return true;
}
private Vector2Int ChunkToRegion(Vector2Int chunkCoord)
@@ -1077,6 +1201,131 @@ namespace InfiniteWorld.VoxelWorld
return region;
}
private static RegionBuildResult BuildRegionBuffers(RegionBuildRequest request)
{
if (request.Chunks == null || request.Chunks.Length == 0)
{
return RegionBuildResult.CreateEmpty(request.RegionCoord, request.Version, request.Session);
}
int totalVertexCount = 0;
int totalTriangleCount = 0;
for (int i = 0; i < request.Chunks.Length; i++)
{
ChunkRenderSnapshot snapshot = request.Chunks[i].Snapshot;
totalVertexCount += snapshot.Vertices.Length;
totalTriangleCount += snapshot.Triangles.Length;
}
if (totalVertexCount == 0 || totalTriangleCount == 0)
{
return RegionBuildResult.CreateEmpty(request.RegionCoord, request.Version, request.Session);
}
Vector3[] vertices = new Vector3[totalVertexCount];
Vector3[] normals = new Vector3[totalVertexCount];
Vector2[] uv0 = new Vector2[totalVertexCount];
Vector2[] uv1 = new Vector2[totalVertexCount];
int[] triangles = new int[totalTriangleCount];
int vertexOffset = 0;
int triangleOffset = 0;
Vector3 min = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
Vector3 max = new Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity);
for (int chunkIndex = 0; chunkIndex < request.Chunks.Length; chunkIndex++)
{
RegionChunkSnapshot chunk = request.Chunks[chunkIndex];
ChunkRenderSnapshot snapshot = chunk.Snapshot;
Vector3 offset = chunk.LocalOffset;
for (int vertexIndex = 0; vertexIndex < snapshot.Vertices.Length; vertexIndex++)
{
Vector3 vertex = snapshot.Vertices[vertexIndex] + offset;
int writeIndex = vertexOffset + vertexIndex;
vertices[writeIndex] = vertex;
normals[writeIndex] = snapshot.Normals[vertexIndex];
uv0[writeIndex] = snapshot.Uv0[vertexIndex];
uv1[writeIndex] = snapshot.Uv1[vertexIndex];
min = Vector3.Min(min, vertex);
max = Vector3.Max(max, vertex);
}
for (int triangleIndex = 0; triangleIndex < snapshot.Triangles.Length; triangleIndex++)
{
triangles[triangleOffset + triangleIndex] = snapshot.Triangles[triangleIndex] + vertexOffset;
}
vertexOffset += snapshot.Vertices.Length;
triangleOffset += snapshot.Triangles.Length;
}
Bounds bounds = new Bounds((min + max) * 0.5f, max - min);
return new RegionBuildResult(request.RegionCoord, request.Version, request.Session, vertices, normals, uv0, uv1, triangles, bounds);
}
private void DrainCompletedRegionBuilds(int maxBuilds)
{
int builds = 0;
while (builds < maxBuilds)
{
RegionBuildResult result;
lock (regionBuildLock)
{
if (completedRegionBuilds.Count == 0)
{
break;
}
result = completedRegionBuilds.Dequeue();
}
ApplyRegionBuildResult(result);
builds++;
}
}
private void ApplyRegionBuildResult(RegionBuildResult result)
{
if (result.Session != generationSession || atlas == null || regionRoot == null)
{
return;
}
regionVersions.TryGetValue(result.RegionCoord, out int currentVersion);
if (result.Version != currentVersion)
{
return;
}
if (result.IsEmpty)
{
if (regions.TryGetValue(result.RegionCoord, out RegionRuntime regionToRemove))
{
regionToRemove.Dispose();
regions.Remove(result.RegionCoord);
}
regionVersions.Remove(result.RegionCoord);
return;
}
RegionRuntime region = GetOrCreateRegionRuntime(result.RegionCoord);
region.EnsureCreated(result.RegionCoord, regionRoot, chunkSize, renderRegionSizeInChunks, atlas.Material);
Mesh mesh = new Mesh { name = $"Region_{result.RegionCoord.x}_{result.RegionCoord.y}" };
mesh.indexFormat = result.Vertices.Length > 65535 ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
mesh.vertices = result.Vertices;
mesh.normals = result.Normals;
mesh.uv = result.Uv0;
mesh.uv2 = result.Uv1;
mesh.triangles = result.Triangles;
mesh.bounds = result.Bounds;
region.ApplyMesh(mesh);
}
private bool ApplyBuildResult(ChunkBuildResult result)
{
if (result.Session != generationSession)
@@ -1094,6 +1343,11 @@ namespace InfiniteWorld.VoxelWorld
return false;
}
if (runtime.RuntimeId != result.RuntimeId)
{
return false;
}
runtime.Heights = result.Heights;
runtime.BiomeIndices = result.BiomeIndices;
if (!runtime.IsRendered)
@@ -1165,6 +1419,7 @@ namespace InfiniteWorld.VoxelWorld
if (!chunks.TryGetValue(coord, out ChunkRuntime runtime))
{
runtime = new ChunkRuntime();
runtime.RuntimeId = ++nextChunkRuntimeId;
chunks.Add(coord, runtime);
}
@@ -1186,6 +1441,31 @@ namespace InfiniteWorld.VoxelWorld
}
chunks.Clear();
dirtyChunkMeshes.Clear();
queuedChunkMeshes.Clear();
pendingNeighborRefreshes.Clear();
queuedNeighborRefreshes.Clear();
while (pendingColliderApplies.Count > 0)
{
DestroyMeshAsset(pendingColliderApplies.Dequeue().ColliderMesh);
}
}
private static void DestroyMeshAsset(Mesh mesh)
{
if (mesh == null)
{
return;
}
if (Application.isPlaying)
{
UnityEngine.Object.Destroy(mesh);
}
else
{
UnityEngine.Object.DestroyImmediate(mesh);
}
}
private void CleanupRegions()
@@ -1196,8 +1476,13 @@ namespace InfiniteWorld.VoxelWorld
}
regions.Clear();
regionVersions.Clear();
dirtyRegions.Clear();
queuedRegions.Clear();
lock (regionBuildLock)
{
completedRegionBuilds.Clear();
}
regionRebuildLoopRunning = false;
}
@@ -1215,15 +1500,16 @@ namespace InfiniteWorld.VoxelWorld
return Mathf.Max(1, count);
}
private readonly struct ChunkBuildResult
private struct ChunkBuildResult
{
public ChunkBuildResult(Vector2Int coord, int[] heights, byte[] biomeIndices, int version, int session)
public ChunkBuildResult(Vector2Int coord, int[] heights, byte[] biomeIndices, int version, int session, int runtimeId)
{
Coord = coord;
Heights = heights;
BiomeIndices = biomeIndices;
Version = version;
Session = session;
RuntimeId = runtimeId;
}
public Vector2Int Coord { get; }
@@ -1231,20 +1517,116 @@ namespace InfiniteWorld.VoxelWorld
public byte[] BiomeIndices { get; }
public int Version { get; }
public int Session { get; }
public int RuntimeId { get; }
}
private readonly struct ChunkMeshBuild
private struct ChunkMeshBuild
{
public ChunkMeshBuild(Mesh renderMesh, Mesh colliderMesh)
public ChunkMeshBuild(Mesh colliderMesh, ChunkRenderSnapshot renderSnapshot)
{
RenderMesh = renderMesh;
ColliderMesh = colliderMesh;
RenderSnapshot = renderSnapshot;
}
public Mesh ColliderMesh { get; }
public ChunkRenderSnapshot RenderSnapshot { get; }
}
private struct PendingColliderMeshApply
{
public PendingColliderMeshApply(Vector2Int coord, int version, int runtimeId, Mesh colliderMesh)
{
Coord = coord;
Version = version;
RuntimeId = runtimeId;
ColliderMesh = colliderMesh;
}
public Mesh RenderMesh { get; }
public Vector2Int Coord { get; }
public int Version { get; }
public int RuntimeId { get; }
public Mesh ColliderMesh { get; }
}
private sealed class ChunkRenderSnapshot
{
public ChunkRenderSnapshot(Vector3[] vertices, Vector3[] normals, Vector2[] uv0, Vector2[] uv1, int[] triangles)
{
Vertices = vertices;
Normals = normals;
Uv0 = uv0;
Uv1 = uv1;
Triangles = triangles;
}
public Vector3[] Vertices { get; }
public Vector3[] Normals { get; }
public Vector2[] Uv0 { get; }
public Vector2[] Uv1 { get; }
public int[] Triangles { get; }
public bool IsEmpty => Vertices == null || Vertices.Length == 0 || Triangles == null || Triangles.Length == 0;
}
private struct RegionChunkSnapshot
{
public RegionChunkSnapshot(ChunkRenderSnapshot snapshot, Vector3 localOffset)
{
Snapshot = snapshot;
LocalOffset = localOffset;
}
public ChunkRenderSnapshot Snapshot { get; }
public Vector3 LocalOffset { get; }
}
private struct RegionBuildRequest
{
public RegionBuildRequest(Vector2Int regionCoord, int version, int session, RegionChunkSnapshot[] chunks)
{
RegionCoord = regionCoord;
Version = version;
Session = session;
Chunks = chunks;
}
public Vector2Int RegionCoord { get; }
public int Version { get; }
public int Session { get; }
public RegionChunkSnapshot[] Chunks { get; }
}
private struct RegionBuildResult
{
public RegionBuildResult(Vector2Int regionCoord, int version, int session, Vector3[] vertices, Vector3[] normals, Vector2[] uv0, Vector2[] uv1, int[] triangles, Bounds bounds)
{
RegionCoord = regionCoord;
Version = version;
Session = session;
Vertices = vertices;
Normals = normals;
Uv0 = uv0;
Uv1 = uv1;
Triangles = triangles;
Bounds = bounds;
}
public Vector2Int RegionCoord { get; }
public int Version { get; }
public int Session { get; }
public Vector3[] Vertices { get; }
public Vector3[] Normals { get; }
public Vector2[] Uv0 { get; }
public Vector2[] Uv1 { get; }
public int[] Triangles { get; }
public Bounds Bounds { get; }
public bool IsEmpty => Vertices == null || Vertices.Length == 0 || Triangles == null || Triangles.Length == 0;
public static RegionBuildResult CreateEmpty(Vector2Int regionCoord, int version, int session)
{
return new RegionBuildResult(regionCoord, version, session, null, null, null, null, null, new Bounds());
}
}
private sealed class MeshBuffers
{
public readonly List<Vector3> Vertices = new List<Vector3>(512);
@@ -1279,6 +1661,16 @@ namespace InfiniteWorld.VoxelWorld
mesh.RecalculateBounds();
return mesh;
}
public ChunkRenderSnapshot ToSnapshot()
{
return new ChunkRenderSnapshot(
Vertices.ToArray(),
Normals.ToArray(),
Uvs.ToArray(),
TextureData.ToArray(),
Triangles.ToArray());
}
}
private sealed class ChunkRuntime
@@ -1286,12 +1678,13 @@ namespace InfiniteWorld.VoxelWorld
public Transform Root;
public MeshCollider MountainCollider;
public BoxCollider GroundCollider;
public Mesh RenderMesh;
public Mesh ColliderMesh;
public ChunkRenderSnapshot RenderSnapshot;
public int[] Heights;
public byte[] BiomeIndices;
public ChunkState State;
public int Version;
public int RuntimeId;
public bool HasData => Heights != null && BiomeIndices != null;
public bool IsRendered => State == ChunkState.Rendered && Root != null;
@@ -1312,20 +1705,19 @@ namespace InfiniteWorld.VoxelWorld
GroundCollider.center = new Vector3(chunkSize * 0.5f, -0.1f, chunkSize * 0.5f);
}
public void ApplyMeshes(ChunkMeshBuild build, int chunkSize)
public void ApplyRenderData(ChunkRenderSnapshot renderSnapshot)
{
if (RenderMesh != null)
{
DestroyMesh(RenderMesh);
}
RenderSnapshot = renderSnapshot;
}
public void ApplyColliderMesh(Mesh colliderMesh)
{
if (ColliderMesh != null)
{
DestroyMesh(ColliderMesh);
}
RenderMesh = build.RenderMesh;
ColliderMesh = build.ColliderMesh;
ColliderMesh = colliderMesh;
MountainCollider.sharedMesh = null;
MountainCollider.sharedMesh = ColliderMesh != null && ColliderMesh.vertexCount > 0 ? ColliderMesh : null;
}
@@ -1344,8 +1736,8 @@ namespace InfiniteWorld.VoxelWorld
}
}
DestroyMesh(RenderMesh);
DestroyMesh(ColliderMesh);
RenderSnapshot = null;
}
private static void DestroyMesh(Mesh mesh)