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
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 85e740c52ab3e0a488b0109bd39c9e86
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8028fe033c76d4b4e82877336273ad43
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
namespace Project.Tasks.Editor
{
internal static class TaskBoardConstants
{
public const int MinutesPerHour = 60;
public const int HoursPerDay = 8;
public const int MinutesPerDay = HoursPerDay * MinutesPerHour;
public const int WorkDaysPerWeek = 5;
public const int MinutesPerWeek = WorkDaysPerWeek * MinutesPerDay;
public static readonly string[] Statuses = { "BackLog", "ToDo", "InProgress", "Review", "Done" };
public static readonly string[] Priorities = { "Lowest", "Low", "Medium", "High", "Highest" };
}
[Serializable]
internal sealed class TaskRecord
{
public string Id;
public string Title;
public string Status;
public string Priority;
public string Area;
public string Owner;
public string Created;
public string Updated;
public string ExecutionTime;
public string RelativeFilePath;
public string AbsoluteFilePath;
public string IndexSummary;
public string TaskSummary;
public int EstimatedMinutes = -1;
public string Header;
public string Why;
public string ExpectedOutcome;
public string CurrentContext;
public string AcceptanceCriteria;
public string Verification;
public string Risks;
public string HumanDecisions;
public string DecisionLog;
public string HandoffNotes;
public bool FileExists;
public bool DetailsLoaded;
public int IndexLineNumber = -1;
public readonly List<string> ValidationMessages = new List<string>();
}
[Serializable]
internal sealed class TaskBoardData
{
public string ProjectRoot;
public string TasksDirectory;
public string IndexPath;
public string OwnersConfigPath;
public readonly List<TaskRecord> Tasks = new List<TaskRecord>();
public readonly List<string> Warnings = new List<string>();
public readonly List<string> OwnerPresets = new List<string>();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4716897eb71185742ad590f2a59df8e3
@@ -0,0 +1,20 @@
%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: acfa62607cc78b8499e371559154647f, type: 3}
m_Name: TaskBoardOwners
m_EditorClassIdentifier: Assembly-CSharp-Editor::Project.Tasks.Editor.TaskBoardOwnersConfig
owners:
- unassigned
- pretty_kotik
- gitenax
- abysscion
- Firinor
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 84c93775036fa9242b5e5e4397f96d04
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,10 @@
using System.Collections.Generic;
using UnityEngine;
namespace Project.Tasks.Editor
{
internal sealed class TaskBoardOwnersConfig : ScriptableObject
{
public List<string> owners = new List<string> { "unassigned" };
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: acfa62607cc78b8499e371559154647f
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: da90adbe6fa0acb429453d3550e18961
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5203f967846df5f43baaa0a3ba2e2d5b
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c58482c592be1ac41ad4ad194e9175c0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6f0ac81b911bd5e4eb8659dbd0edd665
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,18 @@
%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: 0}
m_Name: VoxelBiomeProfile 1
m_EditorClassIdentifier: VoxelWorld.Runtime:InfiniteWorld.VoxelWorld:VoxelBiomeProfile
cliffTopSprite: {fileID: 3392429205621607710, guid: 48c8cb6b6e758e74e852e27073d0e263, type: 3}
cliffSideSprite: {fileID: -5744119150125800539, guid: f4c696ca947bbaf4ba5a27cc0f6a2cb8, type: 3}
dirtSprite: {fileID: -4783801984881171517, guid: 841a59b6af0505448b39ad06ddbc067f, type: 3}
walkableSurfaceSprite: {fileID: 4360390788417139489, guid: 5f3d8f393d5c64b489163ff940369b2d, type: 3}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: eac8d825dd62e1c439235d273a4ca613
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,18 @@
%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: 0}
m_Name: VoxelBiomeProfile 2
m_EditorClassIdentifier: VoxelWorld.Runtime:InfiniteWorld.VoxelWorld:VoxelBiomeProfile
cliffTopSprite: {fileID: 904681902854485019, guid: 3d70b2fe747c8134c8a38cc0e089d139, type: 3}
cliffSideSprite: {fileID: -1499911416505331838, guid: 0891183432730d34c8a41445947b6e85, type: 3}
dirtSprite: {fileID: -1865540202983660084, guid: 152cabc665e467d448f550899fcd56e8, type: 3}
walkableSurfaceSprite: {fileID: 5634026908348037240, guid: e5bbac025bbdb444ab5b16d34a3f38de, type: 3}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6d0dbe510ed048440a3925ef40aeeb5b
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,18 @@
%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: 0}
m_Name: VoxelBiomeProfile
m_EditorClassIdentifier: VoxelWorld.Runtime:InfiniteWorld.VoxelWorld:VoxelBiomeProfile
cliffTopSprite: {fileID: 5725184139431673129, guid: d8a251553db3e5d43ac029f301167578, type: 3}
cliffSideSprite: {fileID: 8591976868863569928, guid: b44be66700acb8d4ba360bdeb7c0bf2f, type: 3}
dirtSprite: {fileID: 4285751004304663802, guid: 89b3d97a04bee1e4d85e5d28c13de1f8, type: 3}
walkableSurfaceSprite: {fileID: 5613978133511580594, guid: 5702a47030ece9f42b0b513238c05bf4, type: 3}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ae25f699f7f7d4144bbd148907fda668
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -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: 6
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: 6
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:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a6692637cf5eb1b41a9d619e7557b2f0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
{
"name": "VoxelWorld.Runtime",
"rootNamespace": "InfiniteWorld.VoxelWorld",
"references": [
"UniTask"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f0a085d6765d01448bb424934e24ed9c
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,375 @@
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));
}
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 981858f28439459429fa291e1f6cb935
@@ -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
@@ -0,0 +1,889 @@
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);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 62cd563780165844caddc098f92ff23f
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 109e9bdd0fcc649c5bc59085c35bdc83
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,438 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!29 &1
OcclusionCullingSettings:
m_ObjectHideFlags: 0
serializedVersion: 2
m_OcclusionBakeSettings:
smallestOccluder: 5
smallestHole: 0.25
backfaceThreshold: 100
m_SceneGUID: 00000000000000000000000000000000
m_OcclusionCullingData: {fileID: 0}
--- !u!104 &2
RenderSettings:
m_ObjectHideFlags: 0
serializedVersion: 10
m_Fog: 0
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
m_FogMode: 3
m_FogDensity: 0.01
m_LinearFogStart: 0
m_LinearFogEnd: 300
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
m_AmbientIntensity: 1
m_AmbientMode: 3
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
m_SkyboxMaterial: {fileID: 0}
m_HaloStrength: 0.5
m_FlareStrength: 1
m_FlareFadeSpeed: 3
m_HaloTexture: {fileID: 0}
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
m_DefaultReflectionMode: 0
m_DefaultReflectionResolution: 128
m_ReflectionBounces: 1
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
m_ObjectHideFlags: 0
serializedVersion: 13
m_BakeOnSceneLoad: 0
m_GISettings:
serializedVersion: 2
m_BounceScale: 1
m_IndirectOutputScale: 1
m_AlbedoBoost: 1
m_EnvironmentLightingMode: 0
m_EnableBakedLightmaps: 0
m_EnableRealtimeLightmaps: 0
m_LightmapEditorSettings:
serializedVersion: 12
m_Resolution: 2
m_BakeResolution: 40
m_AtlasSize: 1024
m_AO: 0
m_AOMaxDistance: 1
m_CompAOExponent: 1
m_CompAOExponentDirect: 0
m_ExtractAmbientOcclusion: 0
m_Padding: 2
m_LightmapParameters: {fileID: 0}
m_LightmapsBakeMode: 1
m_TextureCompression: 1
m_ReflectionCompression: 2
m_MixedBakeMode: 2
m_BakeBackend: 2
m_PVRSampling: 1
m_PVRDirectSampleCount: 32
m_PVRSampleCount: 512
m_PVRBounces: 2
m_PVREnvironmentSampleCount: 256
m_PVREnvironmentReferencePointCount: 2048
m_PVRFilteringMode: 1
m_PVRDenoiserTypeDirect: 1
m_PVRDenoiserTypeIndirect: 1
m_PVRDenoiserTypeAO: 1
m_PVRFilterTypeDirect: 0
m_PVRFilterTypeIndirect: 0
m_PVRFilterTypeAO: 0
m_PVREnvironmentMIS: 1
m_PVRCulling: 1
m_PVRFilteringGaussRadiusDirect: 1
m_PVRFilteringGaussRadiusIndirect: 1
m_PVRFilteringGaussRadiusAO: 1
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
m_PVRFilteringAtrousPositionSigmaIndirect: 2
m_PVRFilteringAtrousPositionSigmaAO: 1
m_ExportTrainingData: 0
m_TrainingDataDestination: TrainingData
m_LightProbeSampleCountMultiplier: 4
m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
m_LightingSettings: {fileID: 0}
--- !u!196 &4
NavMeshSettings:
serializedVersion: 2
m_ObjectHideFlags: 0
m_BuildSettings:
serializedVersion: 3
agentTypeID: 0
agentRadius: 0.5
agentHeight: 2
agentSlope: 45
agentClimb: 0.4
ledgeDropHeight: 0
maxJumpAcrossDistance: 0
minRegionArea: 2
manualCellSize: 0
cellSize: 0.16666667
manualTileSize: 0
tileSize: 256
buildHeightMesh: 0
maxJobWorkers: 0
preserveTilesOutsideBounds: 0
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
--- !u!1 &47249969
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 47249972}
- component: {fileID: 47249971}
- component: {fileID: 47249970}
m_Layer: 0
m_Name: Directional Light
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &47249970
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 47249969}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalLightData
m_UsePipelineSettings: 1
m_AdditionalLightsShadowResolutionTier: 2
m_CustomShadowLayers: 0
m_LightCookieSize: {x: 1, y: 1}
m_LightCookieOffset: {x: 0, y: 0}
m_SoftShadowQuality: 0
m_RenderingLayersMask:
serializedVersion: 0
m_Bits: 1
m_ShadowRenderingLayersMask:
serializedVersion: 0
m_Bits: 1
m_Version: 4
m_LightLayerMask: 1
m_ShadowLayerMask: 1
m_RenderingLayers: 1
m_ShadowRenderingLayers: 1
--- !u!108 &47249971
Light:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 47249969}
m_Enabled: 1
serializedVersion: 12
m_Type: 1
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_Intensity: 1
m_Range: 10
m_SpotAngle: 30
m_InnerSpotAngle: 21.80208
m_CookieSize2D: {x: 0.5, y: 0.5}
m_Shadows:
m_Type: 0
m_Resolution: -1
m_CustomResolution: -1
m_Strength: 1
m_Bias: 0.05
m_NormalBias: 0.4
m_NearPlane: 0.2
m_CullingMatrixOverride:
e00: 1
e01: 0
e02: 0
e03: 0
e10: 0
e11: 1
e12: 0
e13: 0
e20: 0
e21: 0
e22: 1
e23: 0
e30: 0
e31: 0
e32: 0
e33: 1
m_UseCullingMatrixOverride: 0
m_Cookie: {fileID: 0}
m_DrawHalo: 0
m_Flare: {fileID: 0}
m_RenderMode: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingLayerMask: 1
m_Lightmapping: 4
m_LightShadowCasterMode: 0
m_AreaSize: {x: 1, y: 1}
m_BounceIntensity: 1
m_ColorTemperature: 6570
m_UseColorTemperature: 0
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
m_UseBoundingSphereOverride: 0
m_UseViewFrustumForShadowCasterCull: 1
m_ForceVisible: 0
m_ShadowRadius: 0
m_ShadowAngle: 0
m_LightUnit: 1
m_LuxAtDistance: 1
m_EnableSpotReflector: 1
--- !u!4 &47249972
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 47249969}
serializedVersion: 2
m_LocalRotation: {x: 0.40821794, y: -0.23456973, z: 0.10938166, w: 0.8754261}
m_LocalPosition: {x: 0, y: 8, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1331065945
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1331065949}
- component: {fileID: 1331065948}
- component: {fileID: 1331065947}
- component: {fileID: 1331065946}
m_Layer: 0
m_Name: Main Camera
m_TagString: MainCamera
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1331065946
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1331065945}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalCameraData
m_RenderShadows: 1
m_RequiresDepthTextureOption: 2
m_RequiresOpaqueTextureOption: 2
m_CameraType: 0
m_Cameras: []
m_RendererIndex: -1
m_VolumeLayerMask:
serializedVersion: 2
m_Bits: 1
m_VolumeTrigger: {fileID: 0}
m_VolumeFrameworkUpdateModeOption: 2
m_RenderPostProcessing: 0
m_Antialiasing: 0
m_AntialiasingQuality: 2
m_StopNaN: 0
m_Dithering: 0
m_ClearDepth: 1
m_AllowXRRendering: 1
m_AllowHDROutput: 1
m_UseScreenCoordOverride: 0
m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0}
m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0}
m_RequiresDepthTexture: 0
m_RequiresColorTexture: 0
m_TaaSettings:
m_Quality: 3
m_FrameInfluence: 0.1
m_JitterScale: 1
m_MipBias: 0
m_VarianceClampScale: 0.9
m_ContrastAdaptiveSharpening: 0
m_Version: 2
--- !u!81 &1331065947
AudioListener:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1331065945}
m_Enabled: 1
--- !u!20 &1331065948
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1331065945}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
m_projectionMatrixMode: 1
m_GateFitMode: 2
m_FOVAxisMode: 0
m_Iso: 200
m_ShutterSpeed: 0.005
m_Aperture: 16
m_FocusDistance: 10
m_FocalLength: 50
m_BladeCount: 5
m_Curvature: {x: 2, y: 11}
m_BarrelClipping: 0.25
m_Anamorphism: 0
m_SensorSize: {x: 36, y: 24}
m_LensShift: {x: 0, y: 0}
m_NormalizedViewPortRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: 1
near clip plane: 0.3
far clip plane: 300
field of view: 60
orthographic: 0
orthographic size: 5
m_Depth: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingPath: -1
m_TargetTexture: {fileID: 0}
m_TargetDisplay: 0
m_TargetEye: 3
m_HDR: 1
m_AllowMSAA: 1
m_AllowDynamicResolution: 0
m_ForceIntoRT: 0
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
--- !u!4 &1331065949
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1331065945}
serializedVersion: 2
m_LocalRotation: {x: 0.2778159, y: 0.3649717, z: -0.115075134, w: 0.88111955}
m_LocalPosition: {x: 8, y: 12, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1842209026
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1842209028}
- component: {fileID: 1842209027}
m_Layer: 0
m_Name: VoxelWorld
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1842209027
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1842209026}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 62cd563780165844caddc098f92ff23f, type: 3}
m_Name:
m_EditorClassIdentifier: VoxelWorld.Runtime::InfiniteWorld.VoxelWorld.VoxelWorldGenerator
streamTarget: {fileID: 1331065949}
config: {fileID: 11400000, guid: b8cf28a5522134b479c23f017234070c, type: 2}
--- !u!4 &1842209028
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1842209026}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
m_Roots:
- {fileID: 1331065949}
- {fileID: 47249972}
- {fileID: 1842209028}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: a0ae584ed532105449545cd0c38404e5
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1d3a1e4bd82462b4790722c7ebbf2e17
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,70 @@
Shader "Infinite World/VoxelWorld/TextureArrayUnlit"
{
Properties
{
_TextureArray("Texture Array", 2DArray) = "white" {}
_BaseColor("Base Color", Color) = (1, 1, 1, 1)
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry" "RenderPipeline"="UniversalPipeline" }
LOD 100
Pass
{
Name "Forward"
Tags { "LightMode"="SRPDefaultUnlit" }
Cull Back
ZWrite On
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.5
#pragma require 2darray
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
TEXTURE2D_ARRAY(_TextureArray);
SAMPLER(sampler_TextureArray);
CBUFFER_START(UnityPerMaterial)
half4 _BaseColor;
CBUFFER_END
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float2 uv : TEXCOORD0;
float2 textureData : TEXCOORD1;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float2 uv : TEXCOORD0;
float textureLayer : TEXCOORD1;
};
Varyings vert(Attributes input)
{
Varyings output;
VertexPositionInputs positionInputs = GetVertexPositionInputs(input.positionOS.xyz);
output.positionHCS = positionInputs.positionCS;
output.uv = input.uv;
output.textureLayer = input.textureData.x;
return output;
}
half4 frag(Varyings input) : SV_Target
{
float2 tiledUv = frac(input.uv);
half4 albedo = SAMPLE_TEXTURE2D_ARRAY(_TextureArray, sampler_TextureArray, tiledUv, input.textureLayer);
return albedo * _BaseColor;
}
ENDHLSL
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: ec80aebd8cb61f44cbfa6b7d5f087211
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant: