Move Files, remove 2D world gen

This commit is contained in:
2026-04-07 03:10:03 +07:00
parent 55cea836ed
commit 9675b7b31d
121 changed files with 77 additions and 6922 deletions
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: a6692637cf5eb1b41a9d619e7557b2f0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,16 +0,0 @@
{
"name": "VoxelWorld.Runtime",
"rootNamespace": "InfiniteWorld.VoxelWorld",
"references": [
"UniTask"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: f0a085d6765d01448bb424934e24ed9c
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,375 +0,0 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace InfiniteWorld.VoxelWorld
{
public enum VoxelSurfaceType : byte
{
CliffTop,
CliffSide,
Dirt,
WalkableSurface
}
[CreateAssetMenu(menuName = "Infinite World/Voxel Biome Profile", fileName = "VoxelBiomeProfile")]
public sealed class VoxelBiomeProfile : ScriptableObject
{
public Sprite cliffTopSprite;
public Sprite cliffSideSprite;
public Sprite dirtSprite;
public Sprite walkableSurfaceSprite;
public Sprite GetSprite(VoxelSurfaceType surfaceType)
{
return surfaceType switch
{
VoxelSurfaceType.CliffTop => cliffTopSprite,
VoxelSurfaceType.CliffSide => cliffSideSprite,
VoxelSurfaceType.Dirt => dirtSprite,
VoxelSurfaceType.WalkableSurface => walkableSurfaceSprite,
_ => walkableSurfaceSprite
};
}
public bool HasAnyAssignedSprites()
{
return cliffTopSprite != null || cliffSideSprite != null || dirtSprite != null || walkableSurfaceSprite != null;
}
}
internal sealed class VoxelWorldAtlas : IDisposable
{
private const int FallbackTileSize = 32;
private const string ShaderName = "Infinite World/VoxelWorld/TextureArrayUnlit";
private static readonly VoxelSurfaceType[] SurfaceOrder =
{
VoxelSurfaceType.CliffTop,
VoxelSurfaceType.CliffSide,
VoxelSurfaceType.Dirt,
VoxelSurfaceType.WalkableSurface
};
private readonly Dictionary<int, Dictionary<VoxelSurfaceType, int>> layerLookup;
private VoxelWorldAtlas(Texture2DArray textureArray, Material material, Dictionary<int, Dictionary<VoxelSurfaceType, int>> layers)
{
TextureArray = textureArray;
Material = material;
layerLookup = layers;
}
public Texture2DArray TextureArray { get; }
public Material Material { get; }
public int BiomeCount => layerLookup.Count;
public int GetTextureLayer(int biomeIndex, VoxelSurfaceType surfaceType)
{
if (layerLookup.TryGetValue(biomeIndex, out Dictionary<VoxelSurfaceType, int> biomeLayers) && biomeLayers.TryGetValue(surfaceType, out int layer))
{
return layer;
}
return layerLookup[0][surfaceType];
}
public static VoxelWorldAtlas CreateRuntimeAtlas(IReadOnlyList<VoxelBiomeProfile> biomeProfiles)
{
List<VoxelBiomeProfile> sourceBiomes = new List<VoxelBiomeProfile>();
if (biomeProfiles != null)
{
for (int i = 0; i < biomeProfiles.Count; i++)
{
if (biomeProfiles[i] != null)
{
sourceBiomes.Add(biomeProfiles[i]);
}
}
}
if (sourceBiomes.Count == 0)
{
sourceBiomes.Add(null);
}
int tileSize = DetermineTileSize(sourceBiomes);
int layerCount = sourceBiomes.Count * SurfaceOrder.Length;
Texture2DArray textureArray = new Texture2DArray(tileSize, tileSize, layerCount, TextureFormat.RGBA32, false, false)
{
filterMode = FilterMode.Point,
wrapMode = TextureWrapMode.Repeat,
anisoLevel = 1,
name = "VoxelWorld_RuntimeTextureArray",
hideFlags = HideFlags.HideAndDontSave
};
Dictionary<int, Dictionary<VoxelSurfaceType, int>> layers = new Dictionary<int, Dictionary<VoxelSurfaceType, int>>(sourceBiomes.Count);
int layerIndex = 0;
for (int biomeIndex = 0; biomeIndex < sourceBiomes.Count; biomeIndex++)
{
Dictionary<VoxelSurfaceType, int> biomeLayers = new Dictionary<VoxelSurfaceType, int>(SurfaceOrder.Length);
layers[biomeIndex] = biomeLayers;
for (int surfaceIndex = 0; surfaceIndex < SurfaceOrder.Length; surfaceIndex++)
{
VoxelSurfaceType surfaceType = SurfaceOrder[surfaceIndex];
Texture2D tileTexture = BuildSurfaceTexture(sourceBiomes[biomeIndex], surfaceType, tileSize);
CopyTextureToArrayLayer(tileTexture, textureArray, layerIndex);
biomeLayers[surfaceType] = layerIndex;
layerIndex++;
if (Application.isPlaying)
{
UnityEngine.Object.Destroy(tileTexture);
}
else
{
UnityEngine.Object.DestroyImmediate(tileTexture);
}
}
}
textureArray.Apply(false, false);
Shader shader = Shader.Find(ShaderName);
if (shader == null)
{
shader = Shader.Find("Universal Render Pipeline/Unlit") ?? Shader.Find("Standard");
Debug.LogError($"Shader '{ShaderName}' was not found. Falling back to '{shader?.name ?? "<missing>"}', but voxel texture array sampling will not work until the shader asset is imported correctly.");
}
Material material = new Material(shader)
{
name = "VoxelWorld_RuntimeMaterial",
hideFlags = HideFlags.HideAndDontSave
};
if (material.HasProperty("_TextureArray"))
{
material.SetTexture("_TextureArray", textureArray);
}
if (material.HasProperty("_BaseColor"))
{
material.SetColor("_BaseColor", Color.white);
}
if (material.HasProperty("_Color"))
{
material.SetColor("_Color", Color.white);
}
return new VoxelWorldAtlas(textureArray, material, layers);
}
public void Dispose()
{
if (Application.isPlaying)
{
UnityEngine.Object.Destroy(TextureArray);
UnityEngine.Object.Destroy(Material);
}
else
{
UnityEngine.Object.DestroyImmediate(TextureArray);
UnityEngine.Object.DestroyImmediate(Material);
}
}
private static int DetermineTileSize(IReadOnlyList<VoxelBiomeProfile> biomeProfiles)
{
int tileSize = FallbackTileSize;
for (int biomeIndex = 0; biomeIndex < biomeProfiles.Count; biomeIndex++)
{
VoxelBiomeProfile biome = biomeProfiles[biomeIndex];
for (int surfaceIndex = 0; surfaceIndex < SurfaceOrder.Length; surfaceIndex++)
{
Sprite sprite = biome != null ? biome.GetSprite(SurfaceOrder[surfaceIndex]) : null;
if (sprite == null)
{
continue;
}
Rect rect = sprite.textureRect;
tileSize = Mathf.Max(tileSize, Mathf.CeilToInt(Mathf.Max(rect.width, rect.height)));
}
}
return Mathf.Max(FallbackTileSize, tileSize);
}
private static Texture2D BuildSurfaceTexture(VoxelBiomeProfile biome, VoxelSurfaceType surfaceType, int tileSize)
{
Texture2D texture = new Texture2D(tileSize, tileSize, TextureFormat.RGBA32, false)
{
filterMode = FilterMode.Point,
wrapMode = TextureWrapMode.Repeat,
hideFlags = HideFlags.HideAndDontSave
};
Fill(texture, new Color(1f, 0f, 1f, 1f));
Sprite sprite = biome != null ? biome.GetSprite(surfaceType) : null;
if (sprite == null || !TryBlitSprite(texture, sprite))
{
DrawFallbackSurface(texture, new RectInt(0, 0, tileSize, tileSize), surfaceType);
}
texture.Apply(false, false);
return texture;
}
private static void CopyTextureToArrayLayer(Texture2D source, Texture2DArray destination, int layerIndex)
{
bool canUseCopyTexture = SystemInfo.supports2DArrayTextures &&
SystemInfo.copyTextureSupport != UnityEngine.Rendering.CopyTextureSupport.None &&
source.width == destination.width &&
source.height == destination.height;
if (canUseCopyTexture)
{
try
{
Graphics.CopyTexture(source, 0, 0, destination, layerIndex, 0);
return;
}
catch (Exception)
{
// Fall back to CPU copy when runtime copy is unavailable for this platform/import setup.
}
}
destination.SetPixels(source.GetPixels(), layerIndex, 0);
}
private static bool TryBlitSprite(Texture2D target, Sprite sprite)
{
try
{
Texture2D source = sprite.texture;
Rect spriteRect = sprite.textureRect;
for (int y = 0; y < target.height; y++)
{
float sampleY = Mathf.Lerp(spriteRect.yMin, spriteRect.yMax - 1f, y / (float)Mathf.Max(1, target.height - 1));
int sourceY = Mathf.Clamp(Mathf.RoundToInt(sampleY), 0, source.height - 1);
for (int x = 0; x < target.width; x++)
{
float sampleX = Mathf.Lerp(spriteRect.xMin, spriteRect.xMax - 1f, x / (float)Mathf.Max(1, target.width - 1));
int sourceX = Mathf.Clamp(Mathf.RoundToInt(sampleX), 0, source.width - 1);
target.SetPixel(x, y, source.GetPixel(sourceX, sourceY));
}
}
return true;
}
catch (UnityException)
{
Debug.LogWarning($"Sprite '{sprite.name}' texture is not readable. Falling back to generated tile for voxel biome texture array.");
return false;
}
}
private static void DrawFallbackSurface(Texture2D texture, RectInt rect, VoxelSurfaceType surfaceType)
{
Color baseColor;
Color accentColor;
switch (surfaceType)
{
case VoxelSurfaceType.CliffTop:
baseColor = new Color(0.40f, 0.72f, 0.28f, 1f);
accentColor = new Color(0.18f, 0.35f, 0.13f, 1f);
break;
case VoxelSurfaceType.CliffSide:
baseColor = new Color(0.47f, 0.34f, 0.22f, 1f);
accentColor = new Color(0.25f, 0.52f, 0.18f, 1f);
break;
case VoxelSurfaceType.Dirt:
baseColor = new Color(0.44f, 0.31f, 0.20f, 1f);
accentColor = new Color(0.31f, 0.20f, 0.12f, 1f);
break;
default:
baseColor = new Color(0.28f, 0.58f, 0.25f, 1f);
accentColor = new Color(0.18f, 0.40f, 0.16f, 1f);
break;
}
FillRect(texture, rect, baseColor);
ApplyNoise(texture, rect, 0.06f, 101 + (int)surfaceType * 43);
if (surfaceType == VoxelSurfaceType.CliffSide)
{
FillRect(texture, new RectInt(rect.x, rect.yMax - Mathf.Max(4, rect.height / 6), rect.width, Mathf.Max(4, rect.height / 6)), accentColor);
}
else if (surfaceType == VoxelSurfaceType.CliffTop)
{
FillRect(texture, new RectInt(rect.x, rect.yMax - Mathf.Max(4, rect.height / 5), rect.width, Mathf.Max(4, rect.height / 5)), accentColor);
}
else
{
for (int y = rect.y + 4; y < rect.yMax - 4; y += Mathf.Max(6, rect.height / 4))
{
for (int x = rect.x + 4; x < rect.xMax - 4; x += Mathf.Max(6, rect.width / 4))
{
DrawDisc(texture, x, y, 2, accentColor);
}
}
}
}
private static void Fill(Texture2D texture, Color color)
{
Color[] pixels = new Color[texture.width * texture.height];
for (int i = 0; i < pixels.Length; i++)
{
pixels[i] = color;
}
texture.SetPixels(pixels);
}
private static void FillRect(Texture2D texture, RectInt rect, Color color)
{
for (int y = rect.yMin; y < rect.yMax; y++)
{
for (int x = rect.xMin; x < rect.xMax; x++)
{
texture.SetPixel(x, y, color);
}
}
}
private static void DrawDisc(Texture2D texture, int centerX, int centerY, int radius, Color color)
{
int sqrRadius = radius * radius;
for (int y = -radius; y <= radius; y++)
{
for (int x = -radius; x <= radius; x++)
{
if (x * x + y * y > sqrRadius)
{
continue;
}
texture.SetPixel(centerX + x, centerY + y, color);
}
}
}
private static void ApplyNoise(Texture2D texture, RectInt rect, float strength, int seed)
{
for (int y = rect.yMin; y < rect.yMax; y++)
{
for (int x = rect.xMin; x < rect.xMax; x++)
{
Color pixel = texture.GetPixel(x, y);
float noise = Mathf.PerlinNoise((x + seed) * 0.18f, (y - seed) * 0.18f) - 0.5f;
texture.SetPixel(x, y, pixel * (1f + noise * strength * 2f));
}
}
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 981858f28439459429fa291e1f6cb935
@@ -1,182 +0,0 @@
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));
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 0cac3da4969c2f94985de9f5fb30a682
@@ -1,165 +0,0 @@
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);
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 9da608950686fb345b172db0a56bced5
@@ -1,451 +0,0 @@
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];
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 811ff92b4e36193499cad8631c9443a7
@@ -1,339 +0,0 @@
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
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 56e0b6ddd79410b40bdda73c4e20515d
@@ -1,889 +0,0 @@
using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace InfiniteWorld.VoxelWorld
{
public sealed partial class VoxelWorldGenerator : MonoBehaviour
{
[Header("References")]
[SerializeField] private Transform streamTarget;
[SerializeField] private VoxelWorldConfig config;
private readonly Dictionary<Vector2Int, ChunkRuntime> chunks = new Dictionary<Vector2Int, ChunkRuntime>();
private readonly Dictionary<Vector2Int, RegionRuntime> regions = new Dictionary<Vector2Int, RegionRuntime>();
private readonly Dictionary<Vector2Int, int> regionVersions = new Dictionary<Vector2Int, int>();
private readonly Queue<ChunkBuildResult> completedBuilds = new Queue<ChunkBuildResult>();
private readonly Queue<RegionBuildResult> completedRegionBuilds = new Queue<RegionBuildResult>();
private readonly Queue<PendingColliderMeshApply> pendingColliderApplies = new Queue<PendingColliderMeshApply>();
private readonly Queue<Vector2Int> dirtyChunkMeshes = new Queue<Vector2Int>();
private readonly HashSet<Vector2Int> queuedChunkMeshes = new HashSet<Vector2Int>();
private readonly Queue<Vector2Int> dirtyRegions = new Queue<Vector2Int>();
private readonly HashSet<Vector2Int> queuedRegions = new HashSet<Vector2Int>();
private readonly Queue<Vector2Int> pendingNeighborRefreshes = new Queue<Vector2Int>();
private readonly HashSet<Vector2Int> queuedNeighborRefreshes = new HashSet<Vector2Int>();
private readonly object generationLock = new object();
private readonly object regionBuildLock = new object();
private Transform chunkRoot;
private Transform regionRoot;
private Vector2Int lastGeneratedCenter = new Vector2Int(int.MinValue, int.MinValue);
private int activeGenerationJobs;
private int nextChunkRuntimeId;
private int generationSession;
private int regionRebuildSession;
private VoxelWorldAtlas atlas;
private int atlasBiomeCount;
private bool regionRebuildLoopRunning;
private VoxelWorldResolvedSettings settings = VoxelWorldResolvedSettings.Default;
private int chunkSize => settings.ChunkSize;
private int generationRadius => settings.GenerationRadius;
private int blockingGenerationRadius => settings.BlockingGenerationRadius;
private int seed => settings.Seed;
private int maxMountainHeight => settings.MaxMountainHeight;
private float macroNoiseScale => settings.MacroNoiseScale;
private float detailNoiseScale => settings.DetailNoiseScale;
private float ridgeNoiseScale => settings.RidgeNoiseScale;
private float wallThreshold => settings.WallThreshold;
private float rockBias => settings.RockBias;
private int smoothingPasses => settings.SmoothingPasses;
private float passNoiseScale => settings.PassNoiseScale;
private float passDetailScale => settings.PassDetailScale;
private float passThreshold => settings.PassThreshold;
private float passFeather => settings.PassFeather;
private float heightNoiseScale => settings.HeightNoiseScale;
private float terraceNoiseScale => settings.TerraceNoiseScale;
private float heightBias => settings.HeightBias;
private IReadOnlyList<VoxelBiomeProfile> biomeProfiles => settings.BiomeProfiles;
private float biomeNoiseScale => settings.BiomeNoiseScale;
private float biomeSize => settings.BiomeSize;
private int maxAsyncChunkJobs => settings.MaxAsyncChunkJobs;
private int maxChunkBuildsPerFrame => settings.MaxChunkBuildsPerFrame;
private int maxChunkMeshBuildsPerFrame => settings.MaxChunkMeshBuildsPerFrame;
private int maxColliderAppliesPerFrame => settings.MaxColliderAppliesPerFrame;
private int maxNeighborRefreshesPerFrame => settings.MaxNeighborRefreshesPerFrame;
private int renderRegionSizeInChunks => settings.RenderRegionSizeInChunks;
private int maxRegionBuildsPerFrame => settings.MaxRegionBuildsPerFrame;
private void Awake()
{
generationSession++;
regionRebuildSession++;
EnsureRuntimeData();
EnsureChunkRoot();
EnsureRegionRoot();
TryResolveStreamTarget();
}
private void Update()
{
EnsureRuntimeData();
EnsureChunkRoot();
EnsureRegionRoot();
if (!TryResolveStreamTarget())
{
return;
}
Vector2Int centerChunk = WorldToChunk(streamTarget.position);
if (centerChunk != lastGeneratedCenter)
{
lastGeneratedCenter = centerChunk;
UnloadDistantChunks(centerChunk);
}
DrainCompletedBuilds(maxChunkBuildsPerFrame);
DrainDirtyChunkMeshes(maxChunkMeshBuildsPerFrame);
DrainPendingColliderApplies(maxColliderAppliesPerFrame);
DrainNeighborRefreshes(maxNeighborRefreshesPerFrame);
DrainCompletedRegionBuilds(maxRegionBuildsPerFrame);
ScheduleChunkGeneration(centerChunk);
EnsureBlockingChunksLoaded(centerChunk);
}
private void OnDisable()
{
generationSession++;
regionRebuildSession++;
lock (generationLock)
{
completedBuilds.Clear();
activeGenerationJobs = 0;
}
lock (regionBuildLock)
{
completedRegionBuilds.Clear();
}
CleanupChunks();
CleanupRegions();
atlas?.Dispose();
atlas = null;
}
private void EnsureRuntimeData()
{
settings = VoxelWorldResolvedSettings.Resolve(config);
int configuredBiomeCount = CountConfiguredBiomes();
if (atlas != null && atlasBiomeCount == configuredBiomeCount)
{
return;
}
atlas?.Dispose();
atlas = VoxelWorldAtlas.CreateRuntimeAtlas(biomeProfiles);
atlasBiomeCount = atlas.BiomeCount;
RefreshRegionMaterials();
}
private void EnsureChunkRoot()
{
if (chunkRoot != null)
{
return;
}
Transform existing = transform.Find("VoxelChunks");
if (existing != null)
{
chunkRoot = existing;
return;
}
GameObject root = new GameObject("VoxelChunks");
root.transform.SetParent(transform, false);
chunkRoot = root.transform;
}
private void EnsureRegionRoot()
{
if (regionRoot != null)
{
return;
}
Transform existing = transform.Find("VoxelRenderRegions");
if (existing != null)
{
regionRoot = existing;
return;
}
GameObject root = new GameObject("VoxelRenderRegions");
root.transform.SetParent(transform, false);
regionRoot = root.transform;
}
private void RefreshRegionMaterials()
{
if (atlas == null)
{
return;
}
foreach (KeyValuePair<Vector2Int, RegionRuntime> pair in regions)
{
if (pair.Value.Renderer != null)
{
pair.Value.Renderer.sharedMaterial = atlas.Material;
}
}
}
private bool TryResolveStreamTarget()
{
if (streamTarget != null)
{
return true;
}
Camera mainCamera = Camera.main;
if (mainCamera == null)
{
return false;
}
streamTarget = mainCamera.transform;
return true;
}
private void ScheduleChunkGeneration(Vector2Int centerChunk)
{
List<Vector2Int> coords = GetCoordsByPriority(centerChunk, generationRadius);
for (int i = 0; i < coords.Count; i++)
{
Vector2Int coord = coords[i];
ChunkRuntime runtime = GetOrCreateChunkRuntime(coord);
if (runtime.IsRendered || runtime.HasData || runtime.State == ChunkState.Generating)
{
continue;
}
if (IsWithinRadius(coord, centerChunk, blockingGenerationRadius))
{
continue;
}
if (!TryReserveGenerationSlot())
{
break;
}
runtime.State = ChunkState.Generating;
runtime.Version++;
GenerateChunkDataAsync(coord, runtime.Version, generationSession, runtime.RuntimeId).Forget();
}
}
private void EnsureBlockingChunksLoaded(Vector2Int centerChunk)
{
List<Vector2Int> requiredCoords = GetCoordsByPriority(centerChunk, blockingGenerationRadius);
for (int i = 0; i < requiredCoords.Count; i++)
{
Vector2Int coord = requiredCoords[i];
ChunkRuntime runtime = GetOrCreateChunkRuntime(coord);
if (runtime.IsRendered || runtime.HasData || runtime.State == ChunkState.Generating || runtime.State == ChunkState.SyncBuilding)
{
continue;
}
runtime.Version++;
runtime.State = ChunkState.SyncBuilding;
if (ApplyBuildResult(GenerateChunkData(coord, runtime.Version, generationSession, runtime.RuntimeId)))
{
EnqueueChunkMeshBuild(coord);
QueueNeighborRefresh(coord);
}
}
}
private void UnloadDistantChunks(Vector2Int centerChunk)
{
List<Vector2Int> coordsToRemove = new List<Vector2Int>();
foreach (KeyValuePair<Vector2Int, ChunkRuntime> pair in chunks)
{
if (IsWithinRadius(pair.Key, centerChunk, generationRadius))
{
continue;
}
coordsToRemove.Add(pair.Key);
}
for (int i = 0; i < coordsToRemove.Count; i++)
{
Vector2Int coord = coordsToRemove[i];
if (!chunks.TryGetValue(coord, out ChunkRuntime runtime))
{
continue;
}
MarkRegionDirty(coord);
chunks.Remove(coord);
runtime.Dispose();
QueueNeighborRefresh(coord);
}
}
private Vector2Int WorldToChunk(Vector3 position)
{
return new Vector2Int(
Mathf.FloorToInt(position.x / chunkSize),
Mathf.FloorToInt(position.z / chunkSize));
}
private async UniTaskVoid GenerateChunkDataAsync(Vector2Int coord, int version, int session, int runtimeId)
{
try
{
ChunkBuildResult result = await UniTask.RunOnThreadPool(() => GenerateChunkData(coord, version, session, runtimeId));
lock (generationLock)
{
if (session == generationSession)
{
completedBuilds.Enqueue(result);
activeGenerationJobs = Mathf.Max(0, activeGenerationJobs - 1);
}
}
}
catch (Exception exception)
{
lock (generationLock)
{
if (session == generationSession)
{
activeGenerationJobs = Mathf.Max(0, activeGenerationJobs - 1);
}
}
if (session == generationSession && chunks.TryGetValue(coord, out ChunkRuntime runtime) && runtime.Version == version && runtime.RuntimeId == runtimeId)
{
runtime.State = ChunkState.None;
}
Debug.LogException(exception, this);
}
}
private void DrainCompletedBuilds(int maxBuilds)
{
int builds = 0;
while (builds < maxBuilds)
{
ChunkBuildResult result;
lock (generationLock)
{
if (completedBuilds.Count == 0)
{
break;
}
result = completedBuilds.Dequeue();
}
if (!ApplyBuildResult(result))
{
continue;
}
EnqueueChunkMeshBuild(result.Coord);
QueueNeighborRefresh(result.Coord);
builds++;
}
}
private void DrainDirtyChunkMeshes(int maxBuilds)
{
int builds = 0;
while (builds < maxBuilds)
{
if (dirtyChunkMeshes.Count == 0)
{
break;
}
Vector2Int coord = dirtyChunkMeshes.Dequeue();
queuedChunkMeshes.Remove(coord);
if (!chunks.TryGetValue(coord, out ChunkRuntime runtime) || !runtime.HasData)
{
continue;
}
RenderChunk(coord);
builds++;
}
}
private void EnqueueChunkMeshBuild(Vector2Int coord)
{
if (!queuedChunkMeshes.Add(coord))
{
return;
}
dirtyChunkMeshes.Enqueue(coord);
}
private void EnqueueColliderApply(Vector2Int coord, int version, int runtimeId, Mesh colliderMesh)
{
pendingColliderApplies.Enqueue(new PendingColliderMeshApply(coord, version, runtimeId, colliderMesh));
}
private void DrainPendingColliderApplies(int maxApplies)
{
int applies = 0;
while (applies < maxApplies)
{
if (pendingColliderApplies.Count == 0)
{
break;
}
PendingColliderMeshApply pending = pendingColliderApplies.Dequeue();
if (!chunks.TryGetValue(pending.Coord, out ChunkRuntime runtime) || runtime.Version != pending.Version || runtime.RuntimeId != pending.RuntimeId)
{
DestroyMeshAsset(pending.ColliderMesh);
applies++;
continue;
}
runtime.ApplyColliderMesh(pending.ColliderMesh);
applies++;
}
}
private void QueueNeighborRefresh(Vector2Int coord)
{
if (!queuedNeighborRefreshes.Add(coord))
{
return;
}
pendingNeighborRefreshes.Enqueue(coord);
}
private void DrainNeighborRefreshes(int maxRefreshes)
{
int refreshes = 0;
while (refreshes < maxRefreshes)
{
if (pendingNeighborRefreshes.Count == 0)
{
break;
}
Vector2Int coord = pendingNeighborRefreshes.Dequeue();
queuedNeighborRefreshes.Remove(coord);
RefreshNeighborBorders(coord);
refreshes++;
}
}
private void RefreshNeighborBorders(Vector2Int coord)
{
TryRenderNeighbor(coord + Vector2Int.up);
TryRenderNeighbor(coord + Vector2Int.right);
TryRenderNeighbor(coord + Vector2Int.down);
TryRenderNeighbor(coord + Vector2Int.left);
}
private void TryRenderNeighbor(Vector2Int coord)
{
if (!chunks.TryGetValue(coord, out ChunkRuntime runtime) || !runtime.HasData)
{
return;
}
EnqueueChunkMeshBuild(coord);
}
private void MarkRegionDirty(Vector2Int chunkCoord)
{
Vector2Int regionCoord = ChunkToRegion(chunkCoord);
regionVersions.TryGetValue(regionCoord, out int version);
regionVersions[regionCoord] = version + 1;
if (!queuedRegions.Add(regionCoord))
{
return;
}
dirtyRegions.Enqueue(regionCoord);
EnsureRegionRebuildLoop();
}
private void EnsureRegionRebuildLoop()
{
if (regionRebuildLoopRunning)
{
return;
}
regionRebuildLoopRunning = true;
ProcessDirtyRegionsAsync(regionRebuildSession).Forget();
}
private async UniTaskVoid ProcessDirtyRegionsAsync(int session)
{
try
{
while (dirtyRegions.Count > 0)
{
if (!this || !isActiveAndEnabled || session != regionRebuildSession)
{
break;
}
int buildsThisFrame = 0;
while (buildsThisFrame < maxRegionBuildsPerFrame && dirtyRegions.Count > 0)
{
Vector2Int regionCoord = dirtyRegions.Dequeue();
queuedRegions.Remove(regionCoord);
if (!TryCaptureRegionBuildRequest(regionCoord, out RegionBuildRequest request))
{
buildsThisFrame++;
continue;
}
RegionBuildResult result = await UniTask.RunOnThreadPool(() => BuildRegionBuffers(request));
lock (regionBuildLock)
{
completedRegionBuilds.Enqueue(result);
}
buildsThisFrame++;
}
if (dirtyRegions.Count > 0)
{
await UniTask.Yield(PlayerLoopTiming.Update);
}
}
}
finally
{
if (session == regionRebuildSession)
{
regionRebuildLoopRunning = false;
if (dirtyRegions.Count > 0)
{
EnsureRegionRebuildLoop();
}
}
}
}
private bool TryCaptureRegionBuildRequest(Vector2Int regionCoord, out RegionBuildRequest request)
{
if (atlas == null || regionRoot == null)
{
request = new RegionBuildRequest();
return false;
}
Vector2Int baseChunk = new Vector2Int(regionCoord.x * renderRegionSizeInChunks, regionCoord.y * renderRegionSizeInChunks);
List<RegionChunkSnapshot> chunkSnapshots = new List<RegionChunkSnapshot>(renderRegionSizeInChunks * renderRegionSizeInChunks);
for (int z = 0; z < renderRegionSizeInChunks; z++)
{
for (int x = 0; x < renderRegionSizeInChunks; x++)
{
Vector2Int chunkCoord = new Vector2Int(baseChunk.x + x, baseChunk.y + z);
if (!chunks.TryGetValue(chunkCoord, out ChunkRuntime runtime) || runtime.RenderSnapshot == null || runtime.RenderSnapshot.IsEmpty)
{
continue;
}
chunkSnapshots.Add(new RegionChunkSnapshot(runtime.RenderSnapshot, new Vector3(x * chunkSize, 0f, z * chunkSize)));
}
}
regionVersions.TryGetValue(regionCoord, out int version);
request = new RegionBuildRequest(regionCoord, version, generationSession, chunkSnapshots.ToArray());
return true;
}
private Vector2Int ChunkToRegion(Vector2Int chunkCoord)
{
return new Vector2Int(
Mathf.FloorToInt(chunkCoord.x / (float)renderRegionSizeInChunks),
Mathf.FloorToInt(chunkCoord.y / (float)renderRegionSizeInChunks));
}
private RegionRuntime GetOrCreateRegionRuntime(Vector2Int regionCoord)
{
if (!regions.TryGetValue(regionCoord, out RegionRuntime region))
{
region = new RegionRuntime();
regions.Add(regionCoord, region);
}
return region;
}
private static RegionBuildResult BuildRegionBuffers(RegionBuildRequest request)
{
if (request.Chunks == null || request.Chunks.Length == 0)
{
return RegionBuildResult.CreateEmpty(request.RegionCoord, request.Version, request.Session);
}
int totalVertexCount = 0;
int totalTriangleCount = 0;
for (int i = 0; i < request.Chunks.Length; i++)
{
ChunkRenderSnapshot snapshot = request.Chunks[i].Snapshot;
totalVertexCount += snapshot.Vertices.Length;
totalTriangleCount += snapshot.Triangles.Length;
}
if (totalVertexCount == 0 || totalTriangleCount == 0)
{
return RegionBuildResult.CreateEmpty(request.RegionCoord, request.Version, request.Session);
}
Vector3[] vertices = new Vector3[totalVertexCount];
Vector3[] normals = new Vector3[totalVertexCount];
Vector2[] uv0 = new Vector2[totalVertexCount];
Vector2[] uv1 = new Vector2[totalVertexCount];
int[] triangles = new int[totalTriangleCount];
int vertexOffset = 0;
int triangleOffset = 0;
Vector3 min = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
Vector3 max = new Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity);
for (int chunkIndex = 0; chunkIndex < request.Chunks.Length; chunkIndex++)
{
RegionChunkSnapshot chunk = request.Chunks[chunkIndex];
ChunkRenderSnapshot snapshot = chunk.Snapshot;
Vector3 offset = chunk.LocalOffset;
for (int vertexIndex = 0; vertexIndex < snapshot.Vertices.Length; vertexIndex++)
{
Vector3 vertex = snapshot.Vertices[vertexIndex] + offset;
int writeIndex = vertexOffset + vertexIndex;
vertices[writeIndex] = vertex;
normals[writeIndex] = snapshot.Normals[vertexIndex];
uv0[writeIndex] = snapshot.Uv0[vertexIndex];
uv1[writeIndex] = snapshot.Uv1[vertexIndex];
min = Vector3.Min(min, vertex);
max = Vector3.Max(max, vertex);
}
for (int triangleIndex = 0; triangleIndex < snapshot.Triangles.Length; triangleIndex++)
{
triangles[triangleOffset + triangleIndex] = snapshot.Triangles[triangleIndex] + vertexOffset;
}
vertexOffset += snapshot.Vertices.Length;
triangleOffset += snapshot.Triangles.Length;
}
Bounds bounds = new Bounds((min + max) * 0.5f, max - min);
return new RegionBuildResult(request.RegionCoord, request.Version, request.Session, vertices, normals, uv0, uv1, triangles, bounds);
}
private void DrainCompletedRegionBuilds(int maxBuilds)
{
int builds = 0;
while (builds < maxBuilds)
{
RegionBuildResult result;
lock (regionBuildLock)
{
if (completedRegionBuilds.Count == 0)
{
break;
}
result = completedRegionBuilds.Dequeue();
}
ApplyRegionBuildResult(result);
builds++;
}
}
private void ApplyRegionBuildResult(RegionBuildResult result)
{
if (result.Session != generationSession || atlas == null || regionRoot == null)
{
return;
}
regionVersions.TryGetValue(result.RegionCoord, out int currentVersion);
if (result.Version != currentVersion)
{
return;
}
if (result.IsEmpty)
{
if (regions.TryGetValue(result.RegionCoord, out RegionRuntime regionToRemove))
{
regionToRemove.Dispose();
regions.Remove(result.RegionCoord);
}
regionVersions.Remove(result.RegionCoord);
return;
}
RegionRuntime region = GetOrCreateRegionRuntime(result.RegionCoord);
region.EnsureCreated(result.RegionCoord, regionRoot, chunkSize, renderRegionSizeInChunks, atlas.Material);
Mesh mesh = new Mesh { name = $"Region_{result.RegionCoord.x}_{result.RegionCoord.y}" };
mesh.indexFormat = result.Vertices.Length > 65535 ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
mesh.vertices = result.Vertices;
mesh.normals = result.Normals;
mesh.uv = result.Uv0;
mesh.uv2 = result.Uv1;
mesh.triangles = result.Triangles;
mesh.bounds = result.Bounds;
region.ApplyMesh(mesh);
}
private bool ApplyBuildResult(ChunkBuildResult result)
{
if (result.Session != generationSession)
{
return false;
}
if (!chunks.TryGetValue(result.Coord, out ChunkRuntime runtime))
{
return false;
}
if (runtime.Version != result.Version)
{
return false;
}
if (runtime.RuntimeId != result.RuntimeId)
{
return false;
}
runtime.Heights = result.Heights;
runtime.BiomeIndices = result.BiomeIndices;
if (!runtime.IsRendered)
{
runtime.State = ChunkState.ReadyToRender;
}
return true;
}
private bool TryReserveGenerationSlot()
{
lock (generationLock)
{
if (activeGenerationJobs >= maxAsyncChunkJobs)
{
return false;
}
activeGenerationJobs++;
return true;
}
}
private List<Vector2Int> GetCoordsByPriority(Vector2Int centerChunk, int radius)
{
List<Vector2Int> coords = new List<Vector2Int>((radius * 2 + 1) * (radius * 2 + 1));
for (int z = -radius; z <= radius; z++)
{
for (int x = -radius; x <= radius; x++)
{
coords.Add(new Vector2Int(centerChunk.x + x, centerChunk.y + z));
}
}
coords.Sort((left, right) => CompareChunkPriority(centerChunk, left, right));
return coords;
}
private static int CompareChunkPriority(Vector2Int centerChunk, Vector2Int left, Vector2Int right)
{
int leftDx = Mathf.Abs(left.x - centerChunk.x);
int leftDz = Mathf.Abs(left.y - centerChunk.y);
int rightDx = Mathf.Abs(right.x - centerChunk.x);
int rightDz = Mathf.Abs(right.y - centerChunk.y);
int leftChebyshev = Mathf.Max(leftDx, leftDz);
int rightChebyshev = Mathf.Max(rightDx, rightDz);
int chebyshevCompare = leftChebyshev.CompareTo(rightChebyshev);
if (chebyshevCompare != 0)
{
return chebyshevCompare;
}
int leftDistance = leftDx * leftDx + leftDz * leftDz;
int rightDistance = rightDx * rightDx + rightDz * rightDz;
int distanceCompare = leftDistance.CompareTo(rightDistance);
if (distanceCompare != 0)
{
return distanceCompare;
}
int zCompare = left.y.CompareTo(right.y);
return zCompare != 0 ? zCompare : left.x.CompareTo(right.x);
}
private ChunkRuntime GetOrCreateChunkRuntime(Vector2Int coord)
{
if (!chunks.TryGetValue(coord, out ChunkRuntime runtime))
{
runtime = new ChunkRuntime();
runtime.RuntimeId = ++nextChunkRuntimeId;
chunks.Add(coord, runtime);
}
return runtime;
}
private static bool IsWithinRadius(Vector2Int coord, Vector2Int centerChunk, int radius)
{
int dx = Mathf.Abs(coord.x - centerChunk.x);
int dz = Mathf.Abs(coord.y - centerChunk.y);
return dx <= radius && dz <= radius;
}
private void CleanupChunks()
{
foreach (KeyValuePair<Vector2Int, ChunkRuntime> pair in chunks)
{
pair.Value.Dispose();
}
chunks.Clear();
dirtyChunkMeshes.Clear();
queuedChunkMeshes.Clear();
pendingNeighborRefreshes.Clear();
queuedNeighborRefreshes.Clear();
while (pendingColliderApplies.Count > 0)
{
DestroyMeshAsset(pendingColliderApplies.Dequeue().ColliderMesh);
}
}
private static void DestroyMeshAsset(Mesh mesh)
{
if (mesh == null)
{
return;
}
if (Application.isPlaying)
{
UnityEngine.Object.Destroy(mesh);
}
else
{
UnityEngine.Object.DestroyImmediate(mesh);
}
}
private void CleanupRegions()
{
foreach (KeyValuePair<Vector2Int, RegionRuntime> pair in regions)
{
pair.Value.Dispose();
}
regions.Clear();
regionVersions.Clear();
dirtyRegions.Clear();
queuedRegions.Clear();
lock (regionBuildLock)
{
completedRegionBuilds.Clear();
}
regionRebuildLoopRunning = false;
}
private int CountConfiguredBiomes()
{
int count = 0;
for (int i = 0; i < biomeProfiles.Count; i++)
{
if (biomeProfiles[i] != null)
{
count++;
}
}
return Mathf.Max(1, count);
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 62cd563780165844caddc098f92ff23f