[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_Name:
|
||||||
m_EditorClassIdentifier: VoxelWorld.Runtime::InfiniteWorld.VoxelWorld.VoxelWorldGenerator
|
m_EditorClassIdentifier: VoxelWorld.Runtime::InfiniteWorld.VoxelWorld.VoxelWorldGenerator
|
||||||
streamTarget: {fileID: 1331065949}
|
streamTarget: {fileID: 1331065949}
|
||||||
chunkSize: 16
|
config: {fileID: 11400000, guid: b8cf28a5522134b479c23f017234070c, type: 2}
|
||||||
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
|
|
||||||
--- !u!4 &1842209028
|
--- !u!4 &1842209028
|
||||||
Transform:
|
Transform:
|
||||||
m_ObjectHideFlags: 0
|
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