[Add] Batch optimization
This commit is contained in:
@@ -42,21 +42,31 @@ namespace InfiniteWorld.VoxelWorld
|
|||||||
[Header("Runtime")]
|
[Header("Runtime")]
|
||||||
[SerializeField, Min(1)] private int maxAsyncChunkJobs = 2;
|
[SerializeField, Min(1)] private int maxAsyncChunkJobs = 2;
|
||||||
[SerializeField, Min(1)] private int maxChunkBuildsPerFrame = 1;
|
[SerializeField, Min(1)] private int maxChunkBuildsPerFrame = 1;
|
||||||
|
[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, ChunkRuntime> chunks = new Dictionary<Vector2Int, ChunkRuntime>();
|
||||||
|
private readonly Dictionary<Vector2Int, RegionRuntime> regions = new Dictionary<Vector2Int, RegionRuntime>();
|
||||||
private readonly Queue<ChunkBuildResult> completedBuilds = new Queue<ChunkBuildResult>();
|
private readonly Queue<ChunkBuildResult> completedBuilds = new Queue<ChunkBuildResult>();
|
||||||
|
private readonly Queue<Vector2Int> dirtyRegions = new Queue<Vector2Int>();
|
||||||
|
private readonly HashSet<Vector2Int> queuedRegions = new HashSet<Vector2Int>();
|
||||||
private readonly object generationLock = new object();
|
private readonly object generationLock = new object();
|
||||||
|
|
||||||
private Transform chunkRoot;
|
private Transform chunkRoot;
|
||||||
|
private Transform regionRoot;
|
||||||
private Vector2Int lastGeneratedCenter = new Vector2Int(int.MinValue, int.MinValue);
|
private Vector2Int lastGeneratedCenter = new Vector2Int(int.MinValue, int.MinValue);
|
||||||
private int activeGenerationJobs;
|
private int activeGenerationJobs;
|
||||||
|
private int generationSession;
|
||||||
private VoxelWorldAtlas atlas;
|
private VoxelWorldAtlas atlas;
|
||||||
private int atlasBiomeCount;
|
private int atlasBiomeCount;
|
||||||
|
private bool regionRebuildLoopRunning;
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
|
generationSession++;
|
||||||
EnsureRuntimeData();
|
EnsureRuntimeData();
|
||||||
EnsureChunkRoot();
|
EnsureChunkRoot();
|
||||||
|
EnsureRegionRoot();
|
||||||
TryResolveStreamTarget();
|
TryResolveStreamTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +74,7 @@ namespace InfiniteWorld.VoxelWorld
|
|||||||
{
|
{
|
||||||
EnsureRuntimeData();
|
EnsureRuntimeData();
|
||||||
EnsureChunkRoot();
|
EnsureChunkRoot();
|
||||||
|
EnsureRegionRoot();
|
||||||
if (!TryResolveStreamTarget())
|
if (!TryResolveStreamTarget())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -83,7 +94,14 @@ namespace InfiniteWorld.VoxelWorld
|
|||||||
|
|
||||||
private void OnDisable()
|
private void OnDisable()
|
||||||
{
|
{
|
||||||
|
generationSession++;
|
||||||
|
lock (generationLock)
|
||||||
|
{
|
||||||
|
completedBuilds.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
CleanupChunks();
|
CleanupChunks();
|
||||||
|
CleanupRegions();
|
||||||
atlas?.Dispose();
|
atlas?.Dispose();
|
||||||
atlas = null;
|
atlas = null;
|
||||||
}
|
}
|
||||||
@@ -92,6 +110,8 @@ namespace InfiniteWorld.VoxelWorld
|
|||||||
{
|
{
|
||||||
maxMountainHeight = Mathf.Max(1, maxMountainHeight);
|
maxMountainHeight = Mathf.Max(1, maxMountainHeight);
|
||||||
chunkSize = Mathf.Max(8, chunkSize);
|
chunkSize = Mathf.Max(8, chunkSize);
|
||||||
|
renderRegionSizeInChunks = Mathf.Max(1, renderRegionSizeInChunks);
|
||||||
|
maxRegionBuildsPerFrame = Mathf.Max(1, maxRegionBuildsPerFrame);
|
||||||
int configuredBiomeCount = CountConfiguredBiomes();
|
int configuredBiomeCount = CountConfiguredBiomes();
|
||||||
|
|
||||||
if (atlas != null && atlasBiomeCount == configuredBiomeCount)
|
if (atlas != null && atlasBiomeCount == configuredBiomeCount)
|
||||||
@@ -102,6 +122,7 @@ namespace InfiniteWorld.VoxelWorld
|
|||||||
atlas?.Dispose();
|
atlas?.Dispose();
|
||||||
atlas = VoxelWorldAtlas.CreateRuntimeAtlas(biomeProfiles);
|
atlas = VoxelWorldAtlas.CreateRuntimeAtlas(biomeProfiles);
|
||||||
atlasBiomeCount = atlas.BiomeCount;
|
atlasBiomeCount = atlas.BiomeCount;
|
||||||
|
RefreshRegionMaterials();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureChunkRoot()
|
private void EnsureChunkRoot()
|
||||||
@@ -123,6 +144,41 @@ namespace InfiniteWorld.VoxelWorld
|
|||||||
chunkRoot = root.transform;
|
chunkRoot = root.transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void EnsureRegionRoot()
|
||||||
|
{
|
||||||
|
if (regionRoot != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Transform existing = transform.Find("VoxelRenderRegions");
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
regionRoot = existing;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameObject root = new GameObject("VoxelRenderRegions");
|
||||||
|
root.transform.SetParent(transform, false);
|
||||||
|
regionRoot = root.transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshRegionMaterials()
|
||||||
|
{
|
||||||
|
if (atlas == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (KeyValuePair<Vector2Int, RegionRuntime> pair in regions)
|
||||||
|
{
|
||||||
|
if (pair.Value.Renderer != null)
|
||||||
|
{
|
||||||
|
pair.Value.Renderer.sharedMaterial = atlas.Material;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool TryResolveStreamTarget()
|
private bool TryResolveStreamTarget()
|
||||||
{
|
{
|
||||||
if (streamTarget != null)
|
if (streamTarget != null)
|
||||||
@@ -164,7 +220,7 @@ namespace InfiniteWorld.VoxelWorld
|
|||||||
|
|
||||||
runtime.State = ChunkState.Generating;
|
runtime.State = ChunkState.Generating;
|
||||||
runtime.Version++;
|
runtime.Version++;
|
||||||
GenerateChunkDataAsync(coord, runtime.Version).Forget();
|
GenerateChunkDataAsync(coord, runtime.Version, generationSession).Forget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +243,7 @@ namespace InfiniteWorld.VoxelWorld
|
|||||||
|
|
||||||
runtime.Version++;
|
runtime.Version++;
|
||||||
runtime.State = ChunkState.SyncBuilding;
|
runtime.State = ChunkState.SyncBuilding;
|
||||||
ApplyBuildResult(GenerateChunkData(coord, runtime.Version));
|
ApplyBuildResult(GenerateChunkData(coord, runtime.Version, generationSession));
|
||||||
RenderChunk(coord);
|
RenderChunk(coord);
|
||||||
RefreshNeighborBorders(coord);
|
RefreshNeighborBorders(coord);
|
||||||
}
|
}
|
||||||
@@ -214,13 +270,14 @@ namespace InfiniteWorld.VoxelWorld
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MarkRegionDirty(coord);
|
||||||
chunks.Remove(coord);
|
chunks.Remove(coord);
|
||||||
runtime.Dispose();
|
runtime.Dispose();
|
||||||
RefreshNeighborBorders(coord);
|
RefreshNeighborBorders(coord);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ChunkBuildResult GenerateChunkData(Vector2Int coord, int version)
|
private ChunkBuildResult GenerateChunkData(Vector2Int coord, int version, int session)
|
||||||
{
|
{
|
||||||
int margin = Mathf.Max(2, smoothingPasses + 1);
|
int margin = Mathf.Max(2, smoothingPasses + 1);
|
||||||
int sampleSize = chunkSize + margin * 2;
|
int sampleSize = chunkSize + margin * 2;
|
||||||
@@ -260,7 +317,7 @@ namespace InfiniteWorld.VoxelWorld
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ChunkBuildResult(coord, heights, biomeIndices, version);
|
return new ChunkBuildResult(coord, heights, biomeIndices, version, session);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool[,] SmoothSampledMask(bool[,] source)
|
private bool[,] SmoothSampledMask(bool[,] source)
|
||||||
@@ -380,10 +437,11 @@ namespace InfiniteWorld.VoxelWorld
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
runtime.EnsureCreated(coord, chunkRoot, chunkSize, atlas.Material);
|
runtime.EnsureCreated(coord, chunkRoot, chunkSize);
|
||||||
ChunkMeshBuild meshBuild = BuildChunkMesh(coord, runtime.Heights, runtime.BiomeIndices);
|
ChunkMeshBuild meshBuild = BuildChunkMesh(coord, runtime.Heights, runtime.BiomeIndices);
|
||||||
runtime.ApplyMeshes(meshBuild, chunkSize);
|
runtime.ApplyMeshes(meshBuild, chunkSize);
|
||||||
runtime.State = ChunkState.Rendered;
|
runtime.State = ChunkState.Rendered;
|
||||||
|
MarkRegionDirty(coord);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ChunkMeshBuild BuildChunkMesh(Vector2Int coord, int[] heights, byte[] biomeIndices)
|
private ChunkMeshBuild BuildChunkMesh(Vector2Int coord, int[] heights, byte[] biomeIndices)
|
||||||
@@ -826,11 +884,11 @@ namespace InfiniteWorld.VoxelWorld
|
|||||||
return new Vector2Int(coord.x * chunkSize + localX, coord.y * chunkSize + localZ);
|
return new Vector2Int(coord.x * chunkSize + localX, coord.y * chunkSize + localZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async UniTaskVoid GenerateChunkDataAsync(Vector2Int coord, int version)
|
private async UniTaskVoid GenerateChunkDataAsync(Vector2Int coord, int version, int session)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ChunkBuildResult result = await UniTask.RunOnThreadPool(() => GenerateChunkData(coord, version));
|
ChunkBuildResult result = await UniTask.RunOnThreadPool(() => GenerateChunkData(coord, version, session));
|
||||||
lock (generationLock)
|
lock (generationLock)
|
||||||
{
|
{
|
||||||
completedBuilds.Enqueue(result);
|
completedBuilds.Enqueue(result);
|
||||||
@@ -893,8 +951,139 @@ namespace InfiniteWorld.VoxelWorld
|
|||||||
RenderChunk(coord);
|
RenderChunk(coord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void MarkRegionDirty(Vector2Int chunkCoord)
|
||||||
|
{
|
||||||
|
Vector2Int regionCoord = ChunkToRegion(chunkCoord);
|
||||||
|
if (!queuedRegions.Add(regionCoord))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dirtyRegions.Enqueue(regionCoord);
|
||||||
|
EnsureRegionRebuildLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureRegionRebuildLoop()
|
||||||
|
{
|
||||||
|
if (regionRebuildLoopRunning)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
regionRebuildLoopRunning = true;
|
||||||
|
ProcessDirtyRegionsAsync().Forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UniTaskVoid ProcessDirtyRegionsAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (dirtyRegions.Count > 0)
|
||||||
|
{
|
||||||
|
if (!this || !isActiveAndEnabled)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int buildsThisFrame = 0;
|
||||||
|
while (buildsThisFrame < maxRegionBuildsPerFrame && dirtyRegions.Count > 0)
|
||||||
|
{
|
||||||
|
Vector2Int regionCoord = dirtyRegions.Dequeue();
|
||||||
|
queuedRegions.Remove(regionCoord);
|
||||||
|
RebuildRegion(regionCoord);
|
||||||
|
buildsThisFrame++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dirtyRegions.Count > 0)
|
||||||
|
{
|
||||||
|
await UniTask.Yield(PlayerLoopTiming.Update);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
regionRebuildLoopRunning = false;
|
||||||
|
if (dirtyRegions.Count > 0)
|
||||||
|
{
|
||||||
|
EnsureRegionRebuildLoop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RebuildRegion(Vector2Int regionCoord)
|
||||||
|
{
|
||||||
|
if (atlas == null || regionRoot == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2Int baseChunk = new Vector2Int(regionCoord.x * renderRegionSizeInChunks, regionCoord.y * renderRegionSizeInChunks);
|
||||||
|
List<CombineInstance> combineInstances = new List<CombineInstance>(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)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
combineInstances.Add(new CombineInstance
|
||||||
|
{
|
||||||
|
mesh = runtime.RenderMesh,
|
||||||
|
transform = Matrix4x4.Translate(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2Int ChunkToRegion(Vector2Int chunkCoord)
|
||||||
|
{
|
||||||
|
return new Vector2Int(
|
||||||
|
Mathf.FloorToInt(chunkCoord.x / (float)renderRegionSizeInChunks),
|
||||||
|
Mathf.FloorToInt(chunkCoord.y / (float)renderRegionSizeInChunks));
|
||||||
|
}
|
||||||
|
|
||||||
|
private RegionRuntime GetOrCreateRegionRuntime(Vector2Int regionCoord)
|
||||||
|
{
|
||||||
|
if (!regions.TryGetValue(regionCoord, out RegionRuntime region))
|
||||||
|
{
|
||||||
|
region = new RegionRuntime();
|
||||||
|
regions.Add(regionCoord, region);
|
||||||
|
}
|
||||||
|
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
|
||||||
private bool ApplyBuildResult(ChunkBuildResult result)
|
private bool ApplyBuildResult(ChunkBuildResult result)
|
||||||
{
|
{
|
||||||
|
if (result.Session != generationSession)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!chunks.TryGetValue(result.Coord, out ChunkRuntime runtime))
|
if (!chunks.TryGetValue(result.Coord, out ChunkRuntime runtime))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -999,6 +1188,19 @@ namespace InfiniteWorld.VoxelWorld
|
|||||||
chunks.Clear();
|
chunks.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CleanupRegions()
|
||||||
|
{
|
||||||
|
foreach (KeyValuePair<Vector2Int, RegionRuntime> pair in regions)
|
||||||
|
{
|
||||||
|
pair.Value.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
regions.Clear();
|
||||||
|
dirtyRegions.Clear();
|
||||||
|
queuedRegions.Clear();
|
||||||
|
regionRebuildLoopRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
private int CountConfiguredBiomes()
|
private int CountConfiguredBiomes()
|
||||||
{
|
{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
@@ -1015,18 +1217,20 @@ namespace InfiniteWorld.VoxelWorld
|
|||||||
|
|
||||||
private readonly struct ChunkBuildResult
|
private readonly struct ChunkBuildResult
|
||||||
{
|
{
|
||||||
public ChunkBuildResult(Vector2Int coord, int[] heights, byte[] biomeIndices, int version)
|
public ChunkBuildResult(Vector2Int coord, int[] heights, byte[] biomeIndices, int version, int session)
|
||||||
{
|
{
|
||||||
Coord = coord;
|
Coord = coord;
|
||||||
Heights = heights;
|
Heights = heights;
|
||||||
BiomeIndices = biomeIndices;
|
BiomeIndices = biomeIndices;
|
||||||
Version = version;
|
Version = version;
|
||||||
|
Session = session;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector2Int Coord { get; }
|
public Vector2Int Coord { get; }
|
||||||
public int[] Heights { get; }
|
public int[] Heights { get; }
|
||||||
public byte[] BiomeIndices { get; }
|
public byte[] BiomeIndices { get; }
|
||||||
public int Version { get; }
|
public int Version { get; }
|
||||||
|
public int Session { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly struct ChunkMeshBuild
|
private readonly struct ChunkMeshBuild
|
||||||
@@ -1080,8 +1284,6 @@ namespace InfiniteWorld.VoxelWorld
|
|||||||
private sealed class ChunkRuntime
|
private sealed class ChunkRuntime
|
||||||
{
|
{
|
||||||
public Transform Root;
|
public Transform Root;
|
||||||
public MeshFilter Filter;
|
|
||||||
public MeshRenderer Renderer;
|
|
||||||
public MeshCollider MountainCollider;
|
public MeshCollider MountainCollider;
|
||||||
public BoxCollider GroundCollider;
|
public BoxCollider GroundCollider;
|
||||||
public Mesh RenderMesh;
|
public Mesh RenderMesh;
|
||||||
@@ -1094,21 +1296,18 @@ namespace InfiniteWorld.VoxelWorld
|
|||||||
public bool HasData => Heights != null && BiomeIndices != null;
|
public bool HasData => Heights != null && BiomeIndices != null;
|
||||||
public bool IsRendered => State == ChunkState.Rendered && Root != null;
|
public bool IsRendered => State == ChunkState.Rendered && Root != null;
|
||||||
|
|
||||||
public void EnsureCreated(Vector2Int coord, Transform parent, int chunkSize, Material material)
|
public void EnsureCreated(Vector2Int coord, Transform parent, int chunkSize)
|
||||||
{
|
{
|
||||||
if (Root == null)
|
if (Root == null)
|
||||||
{
|
{
|
||||||
GameObject chunkObject = new GameObject($"VoxelChunk_{coord.x}_{coord.y}");
|
GameObject chunkObject = new GameObject($"VoxelChunk_{coord.x}_{coord.y}");
|
||||||
chunkObject.transform.SetParent(parent, false);
|
chunkObject.transform.SetParent(parent, false);
|
||||||
Root = chunkObject.transform;
|
Root = chunkObject.transform;
|
||||||
Filter = chunkObject.AddComponent<MeshFilter>();
|
|
||||||
Renderer = chunkObject.AddComponent<MeshRenderer>();
|
|
||||||
MountainCollider = chunkObject.AddComponent<MeshCollider>();
|
MountainCollider = chunkObject.AddComponent<MeshCollider>();
|
||||||
GroundCollider = chunkObject.AddComponent<BoxCollider>();
|
GroundCollider = chunkObject.AddComponent<BoxCollider>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Root.localPosition = new Vector3(coord.x * chunkSize, 0f, coord.y * chunkSize);
|
Root.localPosition = new Vector3(coord.x * chunkSize, 0f, coord.y * chunkSize);
|
||||||
Renderer.sharedMaterial = material;
|
|
||||||
GroundCollider.size = new Vector3(chunkSize, 0.2f, chunkSize);
|
GroundCollider.size = new Vector3(chunkSize, 0.2f, chunkSize);
|
||||||
GroundCollider.center = new Vector3(chunkSize * 0.5f, -0.1f, chunkSize * 0.5f);
|
GroundCollider.center = new Vector3(chunkSize * 0.5f, -0.1f, chunkSize * 0.5f);
|
||||||
}
|
}
|
||||||
@@ -1127,7 +1326,6 @@ namespace InfiniteWorld.VoxelWorld
|
|||||||
|
|
||||||
RenderMesh = build.RenderMesh;
|
RenderMesh = build.RenderMesh;
|
||||||
ColliderMesh = build.ColliderMesh;
|
ColliderMesh = build.ColliderMesh;
|
||||||
Filter.sharedMesh = RenderMesh;
|
|
||||||
MountainCollider.sharedMesh = null;
|
MountainCollider.sharedMesh = null;
|
||||||
MountainCollider.sharedMesh = ColliderMesh != null && ColliderMesh.vertexCount > 0 ? ColliderMesh : null;
|
MountainCollider.sharedMesh = ColliderMesh != null && ColliderMesh.vertexCount > 0 ? ColliderMesh : null;
|
||||||
}
|
}
|
||||||
@@ -1168,6 +1366,70 @@ namespace InfiniteWorld.VoxelWorld
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class RegionRuntime
|
||||||
|
{
|
||||||
|
public Transform Root;
|
||||||
|
public MeshFilter Filter;
|
||||||
|
public MeshRenderer Renderer;
|
||||||
|
public Mesh RenderMesh;
|
||||||
|
|
||||||
|
public void EnsureCreated(Vector2Int regionCoord, Transform parent, int chunkSize, int regionSizeInChunks, Material material)
|
||||||
|
{
|
||||||
|
if (Root == null)
|
||||||
|
{
|
||||||
|
GameObject regionObject = new GameObject($"VoxelRegion_{regionCoord.x}_{regionCoord.y}");
|
||||||
|
regionObject.transform.SetParent(parent, false);
|
||||||
|
Root = regionObject.transform;
|
||||||
|
Filter = regionObject.AddComponent<MeshFilter>();
|
||||||
|
Renderer = regionObject.AddComponent<MeshRenderer>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Root.localPosition = new Vector3(regionCoord.x * regionSizeInChunks * chunkSize, 0f, regionCoord.y * regionSizeInChunks * chunkSize);
|
||||||
|
Renderer.sharedMaterial = material;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyMesh(Mesh mesh)
|
||||||
|
{
|
||||||
|
DestroyMesh(RenderMesh);
|
||||||
|
RenderMesh = mesh;
|
||||||
|
Filter.sharedMesh = RenderMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (Root != null)
|
||||||
|
{
|
||||||
|
if (Application.isPlaying)
|
||||||
|
{
|
||||||
|
UnityEngine.Object.Destroy(Root.gameObject);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UnityEngine.Object.DestroyImmediate(Root.gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DestroyMesh(RenderMesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DestroyMesh(Mesh mesh)
|
||||||
|
{
|
||||||
|
if (mesh == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Application.isPlaying)
|
||||||
|
{
|
||||||
|
UnityEngine.Object.Destroy(mesh);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UnityEngine.Object.DestroyImmediate(mesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private enum ChunkState
|
private enum ChunkState
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
|
|||||||
Reference in New Issue
Block a user