[Add] Ref
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 0cac3da4969c2f94985de9f5fb30a682, type: 3}
|
||||
m_Name: VoxelWorldConfig
|
||||
m_EditorClassIdentifier: VoxelWorld.Runtime::InfiniteWorld.VoxelWorld.VoxelWorldConfig
|
||||
chunkSize: 16
|
||||
generationRadius: 3
|
||||
blockingGenerationRadius: 0
|
||||
seed: 12345
|
||||
maxMountainHeight: 3
|
||||
macroNoiseScale: 0.05
|
||||
detailNoiseScale: 0.12
|
||||
ridgeNoiseScale: 0.18
|
||||
wallThreshold: 0.6
|
||||
rockBias: 0.04
|
||||
smoothingPasses: 2
|
||||
passNoiseScale: 0.018
|
||||
passDetailScale: 0.041
|
||||
passThreshold: 0.22
|
||||
passFeather: 0.12
|
||||
heightNoiseScale: 0.08
|
||||
terraceNoiseScale: 0.17
|
||||
heightBias: 0.05
|
||||
biomeProfiles:
|
||||
- {fileID: 11400000, guid: ae25f699f7f7d4144bbd148907fda668, type: 2}
|
||||
- {fileID: 11400000, guid: eac8d825dd62e1c439235d273a4ca613, type: 2}
|
||||
- {fileID: 11400000, guid: 6d0dbe510ed048440a3925ef40aeeb5b, type: 2}
|
||||
biomeNoiseScale: 0.02
|
||||
biomeSize: 48
|
||||
maxAsyncChunkJobs: 2
|
||||
maxChunkBuildsPerFrame: 1
|
||||
maxChunkMeshBuildsPerFrame: 1
|
||||
maxColliderAppliesPerFrame: 1
|
||||
maxNeighborRefreshesPerFrame: 2
|
||||
renderRegionSizeInChunks: 4
|
||||
maxRegionBuildsPerFrame: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b8cf28a5522134b479c23f017234070c
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -413,32 +413,7 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: VoxelWorld.Runtime::InfiniteWorld.VoxelWorld.VoxelWorldGenerator
|
||||
streamTarget: {fileID: 1331065949}
|
||||
chunkSize: 16
|
||||
generationRadius: 7
|
||||
blockingGenerationRadius: 1
|
||||
seed: 12345
|
||||
maxMountainHeight: 12
|
||||
macroNoiseScale: 0.05
|
||||
detailNoiseScale: 0.12
|
||||
ridgeNoiseScale: 0.18
|
||||
wallThreshold: 0.6
|
||||
rockBias: 0.04
|
||||
smoothingPasses: 2
|
||||
passNoiseScale: 0.018
|
||||
passDetailScale: 0.041
|
||||
passThreshold: 0.22
|
||||
passFeather: 0.12
|
||||
heightNoiseScale: 0.08
|
||||
terraceNoiseScale: 0.17
|
||||
heightBias: 0.05
|
||||
biomeProfiles:
|
||||
- {fileID: 11400000, guid: ae25f699f7f7d4144bbd148907fda668, type: 2}
|
||||
- {fileID: 11400000, guid: eac8d825dd62e1c439235d273a4ca613, type: 2}
|
||||
- {fileID: 11400000, guid: 6d0dbe510ed048440a3925ef40aeeb5b, type: 2}
|
||||
biomeNoiseScale: 0.02
|
||||
biomeSize: 4
|
||||
maxAsyncChunkJobs: 8
|
||||
maxChunkBuildsPerFrame: 2
|
||||
config: {fileID: 11400000, guid: b8cf28a5522134b479c23f017234070c, type: 2}
|
||||
--- !u!4 &1842209028
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfiniteWorld.VoxelWorld
|
||||
{
|
||||
[CreateAssetMenu(menuName = "Infinite World/Voxel World Config", fileName = "VoxelWorldConfig")]
|
||||
public sealed class VoxelWorldConfig : ScriptableObject
|
||||
{
|
||||
[Header("Streaming")]
|
||||
[Min(8)] public int chunkSize = 16;
|
||||
[Min(1)] public int generationRadius = 3;
|
||||
[Min(0)] public int blockingGenerationRadius;
|
||||
public int seed = 12345;
|
||||
[Min(1)] public int maxMountainHeight = 3;
|
||||
|
||||
[Header("Shape Noise")]
|
||||
public float macroNoiseScale = 0.05f;
|
||||
public float detailNoiseScale = 0.12f;
|
||||
public float ridgeNoiseScale = 0.18f;
|
||||
public float wallThreshold = 0.6f;
|
||||
public float rockBias = 0.04f;
|
||||
[Min(0)] public int smoothingPasses = 2;
|
||||
|
||||
[Header("Global Passes")]
|
||||
public float passNoiseScale = 0.018f;
|
||||
public float passDetailScale = 0.041f;
|
||||
public float passThreshold = 0.22f;
|
||||
public float passFeather = 0.12f;
|
||||
|
||||
[Header("Height")]
|
||||
public float heightNoiseScale = 0.08f;
|
||||
public float terraceNoiseScale = 0.17f;
|
||||
public float heightBias = 0.05f;
|
||||
|
||||
[Header("Biomes")]
|
||||
public List<VoxelBiomeProfile> biomeProfiles = new List<VoxelBiomeProfile>();
|
||||
public float biomeNoiseScale = 0.02f;
|
||||
[Min(1f)] public float biomeSize = 48f;
|
||||
|
||||
[Header("Runtime")]
|
||||
[Min(1)] public int maxAsyncChunkJobs = 2;
|
||||
[Min(1)] public int maxChunkBuildsPerFrame = 1;
|
||||
[Min(1)] public int maxChunkMeshBuildsPerFrame = 1;
|
||||
[Min(1)] public int maxColliderAppliesPerFrame = 1;
|
||||
[Min(0)] public int maxNeighborRefreshesPerFrame = 2;
|
||||
[Min(1)] public int renderRegionSizeInChunks = 4;
|
||||
[Min(1)] public int maxRegionBuildsPerFrame = 1;
|
||||
}
|
||||
|
||||
internal readonly struct VoxelWorldResolvedSettings
|
||||
{
|
||||
private static readonly IReadOnlyList<VoxelBiomeProfile> EmptyBiomes = System.Array.Empty<VoxelBiomeProfile>();
|
||||
|
||||
public static readonly VoxelWorldResolvedSettings Default = Resolve(null);
|
||||
|
||||
public VoxelWorldResolvedSettings(
|
||||
int chunkSize,
|
||||
int generationRadius,
|
||||
int blockingGenerationRadius,
|
||||
int seed,
|
||||
int maxMountainHeight,
|
||||
float macroNoiseScale,
|
||||
float detailNoiseScale,
|
||||
float ridgeNoiseScale,
|
||||
float wallThreshold,
|
||||
float rockBias,
|
||||
int smoothingPasses,
|
||||
float passNoiseScale,
|
||||
float passDetailScale,
|
||||
float passThreshold,
|
||||
float passFeather,
|
||||
float heightNoiseScale,
|
||||
float terraceNoiseScale,
|
||||
float heightBias,
|
||||
IReadOnlyList<VoxelBiomeProfile> biomeProfiles,
|
||||
float biomeNoiseScale,
|
||||
float biomeSize,
|
||||
int maxAsyncChunkJobs,
|
||||
int maxChunkBuildsPerFrame,
|
||||
int maxChunkMeshBuildsPerFrame,
|
||||
int maxColliderAppliesPerFrame,
|
||||
int maxNeighborRefreshesPerFrame,
|
||||
int renderRegionSizeInChunks,
|
||||
int maxRegionBuildsPerFrame)
|
||||
{
|
||||
ChunkSize = chunkSize;
|
||||
GenerationRadius = generationRadius;
|
||||
BlockingGenerationRadius = blockingGenerationRadius;
|
||||
Seed = seed;
|
||||
MaxMountainHeight = maxMountainHeight;
|
||||
MacroNoiseScale = macroNoiseScale;
|
||||
DetailNoiseScale = detailNoiseScale;
|
||||
RidgeNoiseScale = ridgeNoiseScale;
|
||||
WallThreshold = wallThreshold;
|
||||
RockBias = rockBias;
|
||||
SmoothingPasses = smoothingPasses;
|
||||
PassNoiseScale = passNoiseScale;
|
||||
PassDetailScale = passDetailScale;
|
||||
PassThreshold = passThreshold;
|
||||
PassFeather = passFeather;
|
||||
HeightNoiseScale = heightNoiseScale;
|
||||
TerraceNoiseScale = terraceNoiseScale;
|
||||
HeightBias = heightBias;
|
||||
BiomeProfiles = biomeProfiles;
|
||||
BiomeNoiseScale = biomeNoiseScale;
|
||||
BiomeSize = biomeSize;
|
||||
MaxAsyncChunkJobs = maxAsyncChunkJobs;
|
||||
MaxChunkBuildsPerFrame = maxChunkBuildsPerFrame;
|
||||
MaxChunkMeshBuildsPerFrame = maxChunkMeshBuildsPerFrame;
|
||||
MaxColliderAppliesPerFrame = maxColliderAppliesPerFrame;
|
||||
MaxNeighborRefreshesPerFrame = maxNeighborRefreshesPerFrame;
|
||||
RenderRegionSizeInChunks = renderRegionSizeInChunks;
|
||||
MaxRegionBuildsPerFrame = maxRegionBuildsPerFrame;
|
||||
}
|
||||
|
||||
public int ChunkSize { get; }
|
||||
public int GenerationRadius { get; }
|
||||
public int BlockingGenerationRadius { get; }
|
||||
public int Seed { get; }
|
||||
public int MaxMountainHeight { get; }
|
||||
public float MacroNoiseScale { get; }
|
||||
public float DetailNoiseScale { get; }
|
||||
public float RidgeNoiseScale { get; }
|
||||
public float WallThreshold { get; }
|
||||
public float RockBias { get; }
|
||||
public int SmoothingPasses { get; }
|
||||
public float PassNoiseScale { get; }
|
||||
public float PassDetailScale { get; }
|
||||
public float PassThreshold { get; }
|
||||
public float PassFeather { get; }
|
||||
public float HeightNoiseScale { get; }
|
||||
public float TerraceNoiseScale { get; }
|
||||
public float HeightBias { get; }
|
||||
public IReadOnlyList<VoxelBiomeProfile> BiomeProfiles { get; }
|
||||
public float BiomeNoiseScale { get; }
|
||||
public float BiomeSize { get; }
|
||||
public int MaxAsyncChunkJobs { get; }
|
||||
public int MaxChunkBuildsPerFrame { get; }
|
||||
public int MaxChunkMeshBuildsPerFrame { get; }
|
||||
public int MaxColliderAppliesPerFrame { get; }
|
||||
public int MaxNeighborRefreshesPerFrame { get; }
|
||||
public int RenderRegionSizeInChunks { get; }
|
||||
public int MaxRegionBuildsPerFrame { get; }
|
||||
|
||||
public static VoxelWorldResolvedSettings Resolve(VoxelWorldConfig config)
|
||||
{
|
||||
IReadOnlyList<VoxelBiomeProfile> biomes = config != null && config.biomeProfiles != null
|
||||
? config.biomeProfiles
|
||||
: EmptyBiomes;
|
||||
|
||||
return new VoxelWorldResolvedSettings(
|
||||
Mathf.Max(8, config != null ? config.chunkSize : 16),
|
||||
Mathf.Max(1, config != null ? config.generationRadius : 3),
|
||||
Mathf.Max(0, config != null ? config.blockingGenerationRadius : 0),
|
||||
config != null ? config.seed : 12345,
|
||||
Mathf.Max(1, config != null ? config.maxMountainHeight : 3),
|
||||
config != null ? config.macroNoiseScale : 0.05f,
|
||||
config != null ? config.detailNoiseScale : 0.12f,
|
||||
config != null ? config.ridgeNoiseScale : 0.18f,
|
||||
config != null ? config.wallThreshold : 0.6f,
|
||||
config != null ? config.rockBias : 0.04f,
|
||||
Mathf.Max(0, config != null ? config.smoothingPasses : 2),
|
||||
config != null ? config.passNoiseScale : 0.018f,
|
||||
config != null ? config.passDetailScale : 0.041f,
|
||||
config != null ? config.passThreshold : 0.22f,
|
||||
config != null ? config.passFeather : 0.12f,
|
||||
config != null ? config.heightNoiseScale : 0.08f,
|
||||
config != null ? config.terraceNoiseScale : 0.17f,
|
||||
config != null ? config.heightBias : 0.05f,
|
||||
biomes,
|
||||
config != null ? config.biomeNoiseScale : 0.02f,
|
||||
Mathf.Max(1f, config != null ? config.biomeSize : 48f),
|
||||
Mathf.Max(1, config != null ? config.maxAsyncChunkJobs : 2),
|
||||
Mathf.Max(1, config != null ? config.maxChunkBuildsPerFrame : 1),
|
||||
Mathf.Max(1, config != null ? config.maxChunkMeshBuildsPerFrame : 1),
|
||||
Mathf.Max(1, config != null ? config.maxColliderAppliesPerFrame : 1),
|
||||
Mathf.Max(0, config != null ? config.maxNeighborRefreshesPerFrame : 2),
|
||||
Mathf.Max(1, config != null ? config.renderRegionSizeInChunks : 4),
|
||||
Mathf.Max(1, config != null ? config.maxRegionBuildsPerFrame : 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0cac3da4969c2f94985de9f5fb30a682
|
||||
@@ -0,0 +1,165 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfiniteWorld.VoxelWorld
|
||||
{
|
||||
public sealed partial class VoxelWorldGenerator
|
||||
{
|
||||
private ChunkBuildResult GenerateChunkData(Vector2Int coord, int version, int session, int runtimeId)
|
||||
{
|
||||
int margin = Mathf.Max(2, smoothingPasses + 1);
|
||||
int sampleSize = chunkSize + margin * 2;
|
||||
bool[,] sampled = new bool[sampleSize, sampleSize];
|
||||
|
||||
for (int z = 0; z < sampleSize; z++)
|
||||
{
|
||||
for (int x = 0; x < sampleSize; x++)
|
||||
{
|
||||
int localX = x - margin;
|
||||
int localZ = z - margin;
|
||||
Vector2Int worldCell = ChunkToWorldCell(coord, localX, localZ);
|
||||
sampled[x, z] = SampleRock(worldCell);
|
||||
}
|
||||
}
|
||||
|
||||
for (int pass = 0; pass < smoothingPasses; pass++)
|
||||
{
|
||||
sampled = SmoothSampledMask(sampled);
|
||||
}
|
||||
|
||||
int[] heights = new int[chunkSize * chunkSize];
|
||||
byte[] biomeIndices = new byte[chunkSize * chunkSize];
|
||||
for (int z = 0; z < chunkSize; z++)
|
||||
{
|
||||
for (int x = 0; x < chunkSize; x++)
|
||||
{
|
||||
Vector2Int worldCell = ChunkToWorldCell(coord, x, z);
|
||||
biomeIndices[z * chunkSize + x] = SampleBiomeIndex(worldCell);
|
||||
bool hasMountain = sampled[x + margin, z + margin];
|
||||
if (!hasMountain)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
heights[z * chunkSize + x] = SampleHeight(worldCell);
|
||||
}
|
||||
}
|
||||
|
||||
return new ChunkBuildResult(coord, heights, biomeIndices, version, session, runtimeId);
|
||||
}
|
||||
|
||||
private bool[,] SmoothSampledMask(bool[,] source)
|
||||
{
|
||||
int width = source.GetLength(0);
|
||||
int height = source.GetLength(1);
|
||||
bool[,] result = new bool[width, height];
|
||||
|
||||
for (int z = 0; z < height; z++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
int solidNeighbors = CountSampledNeighbors(source, x, z);
|
||||
if (solidNeighbors >= 5)
|
||||
{
|
||||
result[x, z] = true;
|
||||
}
|
||||
else if (solidNeighbors <= 2)
|
||||
{
|
||||
result[x, z] = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
result[x, z] = source[x, z];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int CountSampledNeighbors(bool[,] sampled, int x, int z)
|
||||
{
|
||||
int width = sampled.GetLength(0);
|
||||
int height = sampled.GetLength(1);
|
||||
int count = 0;
|
||||
|
||||
for (int oz = -1; oz <= 1; oz++)
|
||||
{
|
||||
for (int ox = -1; ox <= 1; ox++)
|
||||
{
|
||||
if (ox == 0 && oz == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int nx = x + ox;
|
||||
int nz = z + oz;
|
||||
if (nx < 0 || nz < 0 || nx >= width || nz >= height)
|
||||
{
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sampled[nx, nz])
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private bool SampleRock(Vector2Int worldCell)
|
||||
{
|
||||
float macro = Mathf.PerlinNoise((worldCell.x + seed * 0.13f) * macroNoiseScale, (worldCell.y - seed * 0.17f) * macroNoiseScale);
|
||||
float detail = Mathf.PerlinNoise((worldCell.x - seed * 0.23f) * detailNoiseScale, (worldCell.y + seed * 0.19f) * detailNoiseScale);
|
||||
float ridge = 1f - Mathf.Abs(Mathf.PerlinNoise((worldCell.x + seed * 0.31f) * ridgeNoiseScale, (worldCell.y + seed * 0.29f) * ridgeNoiseScale) * 2f - 1f);
|
||||
|
||||
float rockValue = macro * 0.62f + detail * 0.18f + ridge * 0.20f + rockBias;
|
||||
if (IsInsideGlobalPass(worldCell))
|
||||
{
|
||||
rockValue -= 0.45f;
|
||||
}
|
||||
|
||||
return rockValue >= wallThreshold;
|
||||
}
|
||||
|
||||
private bool IsInsideGlobalPass(Vector2Int worldCell)
|
||||
{
|
||||
float primary = Mathf.PerlinNoise((worldCell.x + seed * 0.41f) * passNoiseScale, (worldCell.y - seed * 0.43f) * passNoiseScale);
|
||||
float detail = Mathf.PerlinNoise((worldCell.x - seed * 0.17f) * passDetailScale, (worldCell.y + seed * 0.23f) * passDetailScale);
|
||||
float ridged = Mathf.Abs(primary * 2f - 1f);
|
||||
float warped = Mathf.Lerp(ridged, Mathf.Abs(detail * 2f - 1f), 0.35f);
|
||||
return warped <= passThreshold + passFeather * detail;
|
||||
}
|
||||
|
||||
private int SampleHeight(Vector2Int worldCell)
|
||||
{
|
||||
float macro = Mathf.PerlinNoise((worldCell.x - seed * 0.47f) * heightNoiseScale, (worldCell.y + seed * 0.37f) * heightNoiseScale);
|
||||
float terrace = Mathf.PerlinNoise((worldCell.x + seed * 0.67f) * terraceNoiseScale, (worldCell.y - seed * 0.59f) * terraceNoiseScale);
|
||||
float ridge = 1f - Mathf.Abs(Mathf.PerlinNoise((worldCell.x + seed * 0.71f) * ridgeNoiseScale, (worldCell.y - seed * 0.73f) * ridgeNoiseScale) * 2f - 1f);
|
||||
float heightValue = macro * 0.55f + terrace * 0.2f + ridge * 0.25f + heightBias;
|
||||
int height = 1 + Mathf.FloorToInt(Mathf.Clamp01(heightValue) * maxMountainHeight);
|
||||
return Mathf.Clamp(height, 1, maxMountainHeight);
|
||||
}
|
||||
|
||||
private byte SampleBiomeIndex(Vector2Int worldCell)
|
||||
{
|
||||
int biomeCount = Mathf.Max(1, atlasBiomeCount > 0 ? atlasBiomeCount : CountConfiguredBiomes());
|
||||
if (biomeCount <= 1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
float effectiveScale = biomeNoiseScale / Mathf.Max(1f, biomeSize);
|
||||
float noise = Mathf.PerlinNoise((worldCell.x + seed * 0.83f) * effectiveScale, (worldCell.y - seed * 0.79f) * effectiveScale);
|
||||
int biomeIndex = Mathf.FloorToInt(Mathf.Clamp01(noise) * biomeCount);
|
||||
return (byte)Mathf.Clamp(biomeIndex, 0, biomeCount - 1);
|
||||
}
|
||||
|
||||
private Vector2Int ChunkToWorldCell(Vector2Int coord, int localX, int localZ)
|
||||
{
|
||||
return new Vector2Int(coord.x * chunkSize + localX, coord.y * chunkSize + localZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9da608950686fb345b172db0a56bced5
|
||||
@@ -0,0 +1,451 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfiniteWorld.VoxelWorld
|
||||
{
|
||||
public sealed partial class VoxelWorldGenerator
|
||||
{
|
||||
private void RenderChunk(Vector2Int coord)
|
||||
{
|
||||
if (!chunks.TryGetValue(coord, out ChunkRuntime runtime) || !runtime.HasData || atlas == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
runtime.EnsureCreated(coord, chunkRoot, chunkSize);
|
||||
ChunkMeshBuild meshBuild = BuildChunkMesh(coord, runtime.Heights, runtime.BiomeIndices);
|
||||
runtime.ApplyRenderData(meshBuild.RenderSnapshot);
|
||||
EnqueueColliderApply(coord, runtime.Version, runtime.RuntimeId, meshBuild.ColliderMesh);
|
||||
runtime.State = ChunkState.Rendered;
|
||||
MarkRegionDirty(coord);
|
||||
}
|
||||
|
||||
private ChunkMeshBuild BuildChunkMesh(Vector2Int coord, int[] heights, byte[] biomeIndices)
|
||||
{
|
||||
MeshBuffers renderBuffers = new MeshBuffers();
|
||||
MeshBuffers colliderBuffers = new MeshBuffers();
|
||||
|
||||
BuildGroundSurface(heights, biomeIndices, renderBuffers);
|
||||
BuildMountainTops(coord, heights, biomeIndices, renderBuffers, colliderBuffers);
|
||||
BuildMountainSides(coord, heights, biomeIndices, renderBuffers, colliderBuffers);
|
||||
|
||||
ChunkRenderSnapshot renderSnapshot = renderBuffers.ToSnapshot();
|
||||
return new ChunkMeshBuild(colliderBuffers.ToMesh($"Collider_{coord.x}_{coord.y}"), renderSnapshot);
|
||||
}
|
||||
|
||||
private void BuildGroundSurface(int[] heights, byte[] biomeIndices, MeshBuffers renderBuffers)
|
||||
{
|
||||
bool[,] visited = new bool[chunkSize, chunkSize];
|
||||
for (int z = 0; z < chunkSize; z++)
|
||||
{
|
||||
for (int x = 0; x < chunkSize; x++)
|
||||
{
|
||||
int index = z * chunkSize + x;
|
||||
if (visited[x, z] || heights[index] > 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
byte biomeIndex = biomeIndices[index];
|
||||
int width = 1;
|
||||
while (x + width < chunkSize && !visited[x + width, z] && heights[z * chunkSize + x + width] == 0 && biomeIndices[z * chunkSize + x + width] == biomeIndex)
|
||||
{
|
||||
width++;
|
||||
}
|
||||
|
||||
int depth = 1;
|
||||
bool canGrow = true;
|
||||
while (z + depth < chunkSize && canGrow)
|
||||
{
|
||||
for (int ix = 0; ix < width; ix++)
|
||||
{
|
||||
int candidateIndex = (z + depth) * chunkSize + x + ix;
|
||||
if (visited[x + ix, z + depth] || heights[candidateIndex] > 0 || biomeIndices[candidateIndex] != biomeIndex)
|
||||
{
|
||||
canGrow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (canGrow)
|
||||
{
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int dz = 0; dz < depth; dz++)
|
||||
{
|
||||
for (int dx = 0; dx < width; dx++)
|
||||
{
|
||||
visited[x + dx, z + dz] = true;
|
||||
}
|
||||
}
|
||||
|
||||
AddTopQuad(renderBuffers, x, z, width, depth, 0f, atlas.GetTextureLayer(biomeIndex, VoxelSurfaceType.WalkableSurface));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildMountainTops(Vector2Int coord, int[] heights, byte[] biomeIndices, MeshBuffers renderBuffers, MeshBuffers colliderBuffers)
|
||||
{
|
||||
bool[,] visited = new bool[chunkSize, chunkSize];
|
||||
for (int z = 0; z < chunkSize; z++)
|
||||
{
|
||||
for (int x = 0; x < chunkSize; x++)
|
||||
{
|
||||
int height = heights[z * chunkSize + x];
|
||||
if (height <= 0 || visited[x, z])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int index = z * chunkSize + x;
|
||||
VoxelSurfaceType surfaceType = IsCliffTop(ChunkToWorldCell(coord, x, z), height) ? VoxelSurfaceType.CliffTop : VoxelSurfaceType.WalkableSurface;
|
||||
byte biomeIndex = biomeIndices[index];
|
||||
|
||||
int width = 1;
|
||||
while (x + width < chunkSize && !visited[x + width, z])
|
||||
{
|
||||
int candidateIndex = z * chunkSize + x + width;
|
||||
if (heights[candidateIndex] != height || biomeIndices[candidateIndex] != biomeIndex)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
VoxelSurfaceType candidateSurface = IsCliffTop(ChunkToWorldCell(coord, x + width, z), height) ? VoxelSurfaceType.CliffTop : VoxelSurfaceType.WalkableSurface;
|
||||
if (candidateSurface != surfaceType)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
width++;
|
||||
}
|
||||
|
||||
int depth = 1;
|
||||
bool canGrow = true;
|
||||
while (z + depth < chunkSize && canGrow)
|
||||
{
|
||||
for (int ix = 0; ix < width; ix++)
|
||||
{
|
||||
if (visited[x + ix, z + depth])
|
||||
{
|
||||
canGrow = false;
|
||||
break;
|
||||
}
|
||||
|
||||
int candidateIndex = (z + depth) * chunkSize + x + ix;
|
||||
if (heights[candidateIndex] != height || biomeIndices[candidateIndex] != biomeIndex)
|
||||
{
|
||||
canGrow = false;
|
||||
break;
|
||||
}
|
||||
|
||||
VoxelSurfaceType candidateSurface = IsCliffTop(ChunkToWorldCell(coord, x + ix, z + depth), height) ? VoxelSurfaceType.CliffTop : VoxelSurfaceType.WalkableSurface;
|
||||
if (candidateSurface != surfaceType)
|
||||
{
|
||||
canGrow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (canGrow)
|
||||
{
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int dz = 0; dz < depth; dz++)
|
||||
{
|
||||
for (int dx = 0; dx < width; dx++)
|
||||
{
|
||||
visited[x + dx, z + dz] = true;
|
||||
}
|
||||
}
|
||||
|
||||
int textureLayer = atlas.GetTextureLayer(biomeIndex, surfaceType);
|
||||
AddTopQuad(renderBuffers, x, z, width, depth, height, textureLayer);
|
||||
AddTopQuad(colliderBuffers, x, z, width, depth, height, 0, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildMountainSides(Vector2Int coord, int[] heights, byte[] biomeIndices, MeshBuffers renderBuffers, MeshBuffers colliderBuffers)
|
||||
{
|
||||
for (int z = 0; z < chunkSize; z++)
|
||||
{
|
||||
BuildNorthSouthFaceSlice(coord, heights, biomeIndices, renderBuffers, colliderBuffers, z, true);
|
||||
BuildNorthSouthFaceSlice(coord, heights, biomeIndices, renderBuffers, colliderBuffers, z, false);
|
||||
}
|
||||
|
||||
for (int x = 0; x < chunkSize; x++)
|
||||
{
|
||||
BuildEastWestFaceSlice(coord, heights, biomeIndices, renderBuffers, colliderBuffers, x, true);
|
||||
BuildEastWestFaceSlice(coord, heights, biomeIndices, renderBuffers, colliderBuffers, x, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildNorthSouthFaceSlice(Vector2Int coord, int[] heights, byte[] biomeIndices, MeshBuffers renderBuffers, MeshBuffers colliderBuffers, int z, bool north)
|
||||
{
|
||||
bool[,] visited = new bool[chunkSize, maxMountainHeight];
|
||||
for (int x = 0; x < chunkSize; x++)
|
||||
{
|
||||
for (int y = 0; y < maxMountainHeight; y++)
|
||||
{
|
||||
if (visited[x, y] || !TryGetNorthSouthFace(coord, heights, biomeIndices, x, z, y, north, out byte biomeIndex, out VoxelSurfaceType surfaceType, out int textureLayer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int width = 1;
|
||||
while (x + width < chunkSize && !visited[x + width, y] && TryGetNorthSouthFace(coord, heights, biomeIndices, x + width, z, y, north, out byte candidateBiome, out VoxelSurfaceType candidateSurface, out int candidateTexture) && candidateBiome == biomeIndex && candidateSurface == surfaceType && candidateTexture == textureLayer)
|
||||
{
|
||||
width++;
|
||||
}
|
||||
|
||||
int height = 1;
|
||||
bool canGrow = true;
|
||||
while (y + height < maxMountainHeight && canGrow)
|
||||
{
|
||||
for (int dx = 0; dx < width; dx++)
|
||||
{
|
||||
if (visited[x + dx, y + height] || !TryGetNorthSouthFace(coord, heights, biomeIndices, x + dx, z, y + height, north, out byte candidateBiome, out VoxelSurfaceType candidateSurface, out int candidateTexture) || candidateBiome != biomeIndex || candidateSurface != surfaceType || candidateTexture != textureLayer)
|
||||
{
|
||||
canGrow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (canGrow)
|
||||
{
|
||||
height++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int dy = 0; dy < height; dy++)
|
||||
{
|
||||
for (int dx = 0; dx < width; dx++)
|
||||
{
|
||||
visited[x + dx, y + dy] = true;
|
||||
}
|
||||
}
|
||||
|
||||
float faceZ = north ? z + 1f : z;
|
||||
Vector3 bl = new Vector3(x, y, faceZ);
|
||||
Vector3 br = new Vector3(x + width, y, faceZ);
|
||||
Vector3 tr = new Vector3(x + width, y + height, faceZ);
|
||||
Vector3 tl = new Vector3(x, y + height, faceZ);
|
||||
|
||||
if (north)
|
||||
{
|
||||
AddVerticalQuad(renderBuffers, bl, br, tr, tl, width, height, textureLayer, Vector3.forward);
|
||||
AddVerticalQuad(colliderBuffers, bl, br, tr, tl, width, height, 0, Vector3.forward, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddVerticalQuad(renderBuffers, br, bl, tl, tr, width, height, textureLayer, Vector3.back);
|
||||
AddVerticalQuad(colliderBuffers, br, bl, tl, tr, width, height, 0, Vector3.back, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildEastWestFaceSlice(Vector2Int coord, int[] heights, byte[] biomeIndices, MeshBuffers renderBuffers, MeshBuffers colliderBuffers, int x, bool east)
|
||||
{
|
||||
bool[,] visited = new bool[chunkSize, maxMountainHeight];
|
||||
for (int z = 0; z < chunkSize; z++)
|
||||
{
|
||||
for (int y = 0; y < maxMountainHeight; y++)
|
||||
{
|
||||
if (visited[z, y] || !TryGetEastWestFace(coord, heights, biomeIndices, x, z, y, east, out byte biomeIndex, out VoxelSurfaceType surfaceType, out int textureLayer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int width = 1;
|
||||
while (z + width < chunkSize && !visited[z + width, y] && TryGetEastWestFace(coord, heights, biomeIndices, x, z + width, y, east, out byte candidateBiome, out VoxelSurfaceType candidateSurface, out int candidateTexture) && candidateBiome == biomeIndex && candidateSurface == surfaceType && candidateTexture == textureLayer)
|
||||
{
|
||||
width++;
|
||||
}
|
||||
|
||||
int height = 1;
|
||||
bool canGrow = true;
|
||||
while (y + height < maxMountainHeight && canGrow)
|
||||
{
|
||||
for (int dz = 0; dz < width; dz++)
|
||||
{
|
||||
if (visited[z + dz, y + height] || !TryGetEastWestFace(coord, heights, biomeIndices, x, z + dz, y + height, east, out byte candidateBiome, out VoxelSurfaceType candidateSurface, out int candidateTexture) || candidateBiome != biomeIndex || candidateSurface != surfaceType || candidateTexture != textureLayer)
|
||||
{
|
||||
canGrow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (canGrow)
|
||||
{
|
||||
height++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int dy = 0; dy < height; dy++)
|
||||
{
|
||||
for (int dz = 0; dz < width; dz++)
|
||||
{
|
||||
visited[z + dz, y + dy] = true;
|
||||
}
|
||||
}
|
||||
|
||||
float faceX = east ? x + 1f : x;
|
||||
Vector3 bl = new Vector3(faceX, y, z);
|
||||
Vector3 br = new Vector3(faceX, y, z + width);
|
||||
Vector3 tr = new Vector3(faceX, y + height, z + width);
|
||||
Vector3 tl = new Vector3(faceX, y + height, z);
|
||||
|
||||
if (east)
|
||||
{
|
||||
AddVerticalQuad(renderBuffers, br, bl, tl, tr, width, height, textureLayer, Vector3.right);
|
||||
AddVerticalQuad(colliderBuffers, br, bl, tl, tr, width, height, 0, Vector3.right, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddVerticalQuad(renderBuffers, bl, br, tr, tl, width, height, textureLayer, Vector3.left);
|
||||
AddVerticalQuad(colliderBuffers, bl, br, tr, tl, width, height, 0, Vector3.left, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetNorthSouthFace(Vector2Int coord, int[] heights, byte[] biomeIndices, int x, int z, int y, bool north, out byte biomeIndex, out VoxelSurfaceType surfaceType, out int textureLayer)
|
||||
{
|
||||
int current = heights[z * chunkSize + x];
|
||||
int worldX = coord.x * chunkSize + x;
|
||||
int worldZ = coord.y * chunkSize + z;
|
||||
int neighbor = GetHeightAtWorldCell(new Vector2Int(worldX, north ? worldZ + 1 : worldZ - 1));
|
||||
if (y >= current || y < neighbor)
|
||||
{
|
||||
biomeIndex = 0;
|
||||
surfaceType = VoxelSurfaceType.Dirt;
|
||||
textureLayer = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
biomeIndex = biomeIndices[z * chunkSize + x];
|
||||
surfaceType = y == current - 1 ? VoxelSurfaceType.CliffSide : VoxelSurfaceType.Dirt;
|
||||
textureLayer = atlas.GetTextureLayer(biomeIndex, surfaceType);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryGetEastWestFace(Vector2Int coord, int[] heights, byte[] biomeIndices, int x, int z, int y, bool east, out byte biomeIndex, out VoxelSurfaceType surfaceType, out int textureLayer)
|
||||
{
|
||||
int current = heights[z * chunkSize + x];
|
||||
int worldX = coord.x * chunkSize + x;
|
||||
int worldZ = coord.y * chunkSize + z;
|
||||
int neighbor = GetHeightAtWorldCell(new Vector2Int(east ? worldX + 1 : worldX - 1, worldZ));
|
||||
if (y >= current || y < neighbor)
|
||||
{
|
||||
biomeIndex = 0;
|
||||
surfaceType = VoxelSurfaceType.Dirt;
|
||||
textureLayer = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
biomeIndex = biomeIndices[z * chunkSize + x];
|
||||
surfaceType = y == current - 1 ? VoxelSurfaceType.CliffSide : VoxelSurfaceType.Dirt;
|
||||
textureLayer = atlas.GetTextureLayer(biomeIndex, surfaceType);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void AddTopQuad(MeshBuffers buffers, float x, float z, float width, float depth, float height, int textureLayer, bool withUv = true)
|
||||
{
|
||||
int baseIndex = buffers.Vertices.Count;
|
||||
buffers.Vertices.Add(new Vector3(x, height, z));
|
||||
buffers.Vertices.Add(new Vector3(x + width, height, z));
|
||||
buffers.Vertices.Add(new Vector3(x + width, height, z + depth));
|
||||
buffers.Vertices.Add(new Vector3(x, height, z + depth));
|
||||
buffers.Normals.Add(Vector3.up);
|
||||
buffers.Normals.Add(Vector3.up);
|
||||
buffers.Normals.Add(Vector3.up);
|
||||
buffers.Normals.Add(Vector3.up);
|
||||
|
||||
if (withUv)
|
||||
{
|
||||
buffers.Uvs.Add(new Vector2(0f, 0f));
|
||||
buffers.Uvs.Add(new Vector2(width, 0f));
|
||||
buffers.Uvs.Add(new Vector2(width, depth));
|
||||
buffers.Uvs.Add(new Vector2(0f, depth));
|
||||
Vector2 textureData = new Vector2(textureLayer, 0f);
|
||||
buffers.TextureData.Add(textureData);
|
||||
buffers.TextureData.Add(textureData);
|
||||
buffers.TextureData.Add(textureData);
|
||||
buffers.TextureData.Add(textureData);
|
||||
}
|
||||
|
||||
buffers.Triangles.Add(baseIndex);
|
||||
buffers.Triangles.Add(baseIndex + 2);
|
||||
buffers.Triangles.Add(baseIndex + 1);
|
||||
buffers.Triangles.Add(baseIndex);
|
||||
buffers.Triangles.Add(baseIndex + 3);
|
||||
buffers.Triangles.Add(baseIndex + 2);
|
||||
}
|
||||
|
||||
private void AddVerticalQuad(MeshBuffers buffers, Vector3 bottomLeft, Vector3 bottomRight, Vector3 topRight, Vector3 topLeft, float width, float height, int textureLayer, Vector3 normal, bool withUv = true)
|
||||
{
|
||||
int baseIndex = buffers.Vertices.Count;
|
||||
buffers.Vertices.Add(bottomLeft);
|
||||
buffers.Vertices.Add(bottomRight);
|
||||
buffers.Vertices.Add(topRight);
|
||||
buffers.Vertices.Add(topLeft);
|
||||
buffers.Normals.Add(normal);
|
||||
buffers.Normals.Add(normal);
|
||||
buffers.Normals.Add(normal);
|
||||
buffers.Normals.Add(normal);
|
||||
|
||||
if (withUv)
|
||||
{
|
||||
buffers.Uvs.Add(new Vector2(0f, 0f));
|
||||
buffers.Uvs.Add(new Vector2(width, 0f));
|
||||
buffers.Uvs.Add(new Vector2(width, height));
|
||||
buffers.Uvs.Add(new Vector2(0f, height));
|
||||
Vector2 textureData = new Vector2(textureLayer, 0f);
|
||||
buffers.TextureData.Add(textureData);
|
||||
buffers.TextureData.Add(textureData);
|
||||
buffers.TextureData.Add(textureData);
|
||||
buffers.TextureData.Add(textureData);
|
||||
}
|
||||
|
||||
buffers.Triangles.Add(baseIndex);
|
||||
buffers.Triangles.Add(baseIndex + 1);
|
||||
buffers.Triangles.Add(baseIndex + 2);
|
||||
buffers.Triangles.Add(baseIndex);
|
||||
buffers.Triangles.Add(baseIndex + 2);
|
||||
buffers.Triangles.Add(baseIndex + 3);
|
||||
}
|
||||
|
||||
private bool IsCliffTop(Vector2Int worldCell, int currentHeight)
|
||||
{
|
||||
return GetHeightAtWorldCell(worldCell + Vector2Int.up) < currentHeight ||
|
||||
GetHeightAtWorldCell(worldCell + Vector2Int.right) < currentHeight ||
|
||||
GetHeightAtWorldCell(worldCell + Vector2Int.down) < currentHeight ||
|
||||
GetHeightAtWorldCell(worldCell + Vector2Int.left) < currentHeight;
|
||||
}
|
||||
|
||||
private int GetHeightAtWorldCell(Vector2Int worldCell)
|
||||
{
|
||||
Vector2Int coord = new Vector2Int(
|
||||
Mathf.FloorToInt(worldCell.x / (float)chunkSize),
|
||||
Mathf.FloorToInt(worldCell.y / (float)chunkSize));
|
||||
|
||||
if (!chunks.TryGetValue(coord, out ChunkRuntime runtime) || !runtime.HasData)
|
||||
{
|
||||
return SampleRock(worldCell) ? SampleHeight(worldCell) : 0;
|
||||
}
|
||||
|
||||
int localX = worldCell.x - coord.x * chunkSize;
|
||||
int localZ = worldCell.y - coord.y * chunkSize;
|
||||
if (localX < 0 || localZ < 0 || localX >= chunkSize || localZ >= chunkSize)
|
||||
{
|
||||
return SampleRock(worldCell) ? SampleHeight(worldCell) : 0;
|
||||
}
|
||||
|
||||
return runtime.Heights[localZ * chunkSize + localX];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 811ff92b4e36193499cad8631c9443a7
|
||||
@@ -0,0 +1,339 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace InfiniteWorld.VoxelWorld
|
||||
{
|
||||
public sealed partial class VoxelWorldGenerator
|
||||
{
|
||||
private struct ChunkBuildResult
|
||||
{
|
||||
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; }
|
||||
public int[] Heights { get; }
|
||||
public byte[] BiomeIndices { get; }
|
||||
public int Version { get; }
|
||||
public int Session { get; }
|
||||
public int RuntimeId { get; }
|
||||
}
|
||||
|
||||
private struct ChunkMeshBuild
|
||||
{
|
||||
public ChunkMeshBuild(Mesh colliderMesh, ChunkRenderSnapshot renderSnapshot)
|
||||
{
|
||||
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 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);
|
||||
public readonly List<Vector3> Normals = new List<Vector3>(512);
|
||||
public readonly List<Vector2> Uvs = new List<Vector2>(512);
|
||||
public readonly List<Vector2> TextureData = new List<Vector2>(512);
|
||||
public readonly List<int> Triangles = new List<int>(1024);
|
||||
|
||||
public Mesh ToMesh(string meshName)
|
||||
{
|
||||
Mesh mesh = new Mesh { name = meshName };
|
||||
if (Vertices.Count == 0)
|
||||
{
|
||||
return mesh;
|
||||
}
|
||||
|
||||
mesh.indexFormat = Vertices.Count > 65535 ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
|
||||
mesh.SetVertices(Vertices);
|
||||
if (Normals.Count == Vertices.Count)
|
||||
{
|
||||
mesh.SetNormals(Normals);
|
||||
}
|
||||
if (Uvs.Count == Vertices.Count)
|
||||
{
|
||||
mesh.SetUVs(0, Uvs);
|
||||
}
|
||||
if (TextureData.Count == Vertices.Count)
|
||||
{
|
||||
mesh.SetUVs(1, TextureData);
|
||||
}
|
||||
mesh.SetTriangles(Triangles, 0, true);
|
||||
mesh.RecalculateBounds();
|
||||
return mesh;
|
||||
}
|
||||
|
||||
public ChunkRenderSnapshot ToSnapshot()
|
||||
{
|
||||
return new ChunkRenderSnapshot(
|
||||
Vertices.ToArray(),
|
||||
Normals.ToArray(),
|
||||
Uvs.ToArray(),
|
||||
TextureData.ToArray(),
|
||||
Triangles.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ChunkRuntime
|
||||
{
|
||||
public Transform Root;
|
||||
public MeshCollider MountainCollider;
|
||||
public BoxCollider GroundCollider;
|
||||
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;
|
||||
|
||||
public void EnsureCreated(Vector2Int coord, Transform parent, int chunkSize)
|
||||
{
|
||||
if (Root == null)
|
||||
{
|
||||
GameObject chunkObject = new GameObject($"VoxelChunk_{coord.x}_{coord.y}");
|
||||
chunkObject.transform.SetParent(parent, false);
|
||||
Root = chunkObject.transform;
|
||||
MountainCollider = chunkObject.AddComponent<MeshCollider>();
|
||||
GroundCollider = chunkObject.AddComponent<BoxCollider>();
|
||||
}
|
||||
|
||||
Root.localPosition = new Vector3(coord.x * chunkSize, 0f, coord.y * chunkSize);
|
||||
GroundCollider.size = new Vector3(chunkSize, 0.2f, chunkSize);
|
||||
GroundCollider.center = new Vector3(chunkSize * 0.5f, -0.1f, chunkSize * 0.5f);
|
||||
}
|
||||
|
||||
public void ApplyRenderData(ChunkRenderSnapshot renderSnapshot)
|
||||
{
|
||||
RenderSnapshot = renderSnapshot;
|
||||
}
|
||||
|
||||
public void ApplyColliderMesh(Mesh colliderMesh)
|
||||
{
|
||||
if (ColliderMesh != null)
|
||||
{
|
||||
DestroyMesh(ColliderMesh);
|
||||
}
|
||||
|
||||
ColliderMesh = colliderMesh;
|
||||
MountainCollider.sharedMesh = null;
|
||||
MountainCollider.sharedMesh = ColliderMesh != null && ColliderMesh.vertexCount > 0 ? ColliderMesh : null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Root != null)
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
Object.Destroy(Root.gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
Object.DestroyImmediate(Root.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
DestroyMesh(ColliderMesh);
|
||||
RenderSnapshot = null;
|
||||
}
|
||||
|
||||
private static void DestroyMesh(Mesh mesh)
|
||||
{
|
||||
if (mesh == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
Object.Destroy(mesh);
|
||||
}
|
||||
else
|
||||
{
|
||||
Object.DestroyImmediate(mesh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
Object.Destroy(Root.gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
Object.DestroyImmediate(Root.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
DestroyMesh(RenderMesh);
|
||||
}
|
||||
|
||||
private static void DestroyMesh(Mesh mesh)
|
||||
{
|
||||
if (mesh == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
Object.Destroy(mesh);
|
||||
}
|
||||
else
|
||||
{
|
||||
Object.DestroyImmediate(mesh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum ChunkState
|
||||
{
|
||||
None,
|
||||
Generating,
|
||||
SyncBuilding,
|
||||
ReadyToRender,
|
||||
Rendered
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 56e0b6ddd79410b40bdda73c4e20515d
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user