riversong code showcase

This commit is contained in:
Daniele Marotta
2026-05-21 15:52:18 +02:00
commit 4c9eea1c02
462 changed files with 23406 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
using System.Collections.Generic;
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class ChunkGenerationJobState
{
public int Lod;
public List<TerrainChunk> Chunks = new();
public List<Mesh> Meshes = new();
public Mesh.MeshDataArray MeshDataArray;
public void Clear()
{
Lod = 0;
Chunks.Clear();
Meshes.Clear();
MeshDataArray = default;
}
}
}

View File

@@ -0,0 +1,124 @@
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
public abstract class ChunkMeshGenerator
{
private readonly List<ChunkGenerationJobState> _pendingJobs = new();
private JobHandle _dependency;
protected ChunkMeshGenerator(World world, GameConfig config)
{
World = world;
Config = config;
}
protected World World { get; }
protected GameConfig Config { get; }
public void InitializeChunk(TerrainChunk chunk)
{
for (var lod = 0; lod < GetLodCount(); lod++) InitializeChunk(chunk, lod);
}
private void InitializeChunk(TerrainChunk chunk, int lod)
{
var childName = GetGameObjectName(chunk.Root.name, chunk.Coords, lod);
var child = new GameObject(childName);
child.transform.SetParent(chunk.Root.transform);
var mesh = new Mesh { name = childName };
mesh.MarkDynamic();
var meshFilter = child.AddComponent<MeshFilter>();
meshFilter.sharedMesh = mesh;
var renderer = child.AddComponent<MeshRenderer>();
InitializeChunk(chunk, lod, child, mesh, renderer);
}
protected abstract int GetLodCount();
protected abstract string GetGameObjectName(string chunkName, int2 chunkCoords, int lod);
protected abstract void InitializeChunk(TerrainChunk chunk, int lod, GameObject gameObject, Mesh mesh, Renderer renderer);
public void BeginGeneratingChunks(List<TerrainChunk> chunks)
{
var chunkCoords = new NativeArray<int2>(chunks.Count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
for (var i = 0; i < chunks.Count; i++) chunkCoords[i] = chunks[i].Coords;
for (var lod = 0; lod < GetLodCount(); lod++)
{
var jobState = CreateJobState(lod, chunks);
_pendingJobs.Add(jobState);
var jobHandle = ScheduleJob(jobState, chunkCoords);
_dependency = JobHandle.CombineDependencies(_dependency, jobHandle);
}
chunkCoords.Dispose(_dependency);
}
private ChunkGenerationJobState CreateJobState(int lod, List<TerrainChunk> chunks)
{
var jobState = new ChunkGenerationJobState { Lod = lod };
jobState.Chunks.AddRange(chunks);
foreach (var chunk in chunks)
{
var mesh = GetMesh(chunk, lod);
jobState.Meshes.Add(mesh);
}
jobState.MeshDataArray = Mesh.AllocateWritableMeshData(jobState.Meshes);
return jobState;
}
protected abstract Mesh GetMesh(TerrainChunk chunk, int lod);
protected abstract JobHandle ScheduleJob(ChunkGenerationJobState jobState, NativeArray<int2> chunkCoords);
public async UniTask Wait()
{
while (!_dependency.IsCompleted) await UniTask.NextFrame();
_dependency.Complete();
_dependency = default;
}
public void EndGeneratingChunks()
{
foreach (var jobState in _pendingJobs) OnCompleted(jobState);
_pendingJobs.Clear();
}
private void OnCompleted(ChunkGenerationJobState jobState)
{
Mesh.ApplyAndDisposeWritableMeshData(jobState.MeshDataArray, jobState.Meshes);
foreach (var mesh in jobState.Meshes)
{
mesh.RecalculateBounds();
mesh.UploadMeshData(false);
}
OnCompleted(jobState.Lod, jobState.Chunks);
jobState.Clear();
}
protected abstract void OnCompleted(int lod, List<TerrainChunk> chunks);
}
}

View File

@@ -0,0 +1,70 @@
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using Random = Unity.Mathematics.Random;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class CropsChunkMeshGenerator : GrassChunkMeshGeneratorBase<CropsGrassTileMask>
{
public CropsChunkMeshGenerator(World world, GameConfig config) : base(world, config)
{
}
protected override int GetLodCount()
{
return Config.Terrain.CropsLODs.Length;
}
protected override string GetGameObjectName(string chunkName, int2 chunkCoords, int lod)
{
return $"{chunkName}_Crops_LOD{lod}";
}
protected override void InitializeChunk(TerrainChunk chunk, int lod, GameObject gameObject, Mesh mesh, Renderer renderer)
{
base.InitializeChunk(chunk, lod, gameObject, mesh, renderer);
chunk.CropsLODs[lod] = new TerrainChunk.RenderData(mesh, renderer);
}
protected override Mesh GetMesh(TerrainChunk chunk, int lod)
{
return chunk.CropsLODs[lod].Mesh;
}
protected override JobHandle ScheduleJob(ChunkGenerationJobState jobState, NativeArray<int2> chunkCoords, NativeArray<Random> randomArray)
{
var job = new GenerateGrassChunkJob<CropsGrassTileMask>();
PrepareJob(jobState, chunkCoords, randomArray, jobState.Lod, ref job);
return job.Schedule(chunkCoords.Length, Config.WorldGen.ChunksPerThread);
}
protected override Renderer GetRenderer(TerrainChunk chunk, int lod)
{
return chunk.CropsLODs[lod].Renderer;
}
protected override Material GetMaterial(int lod)
{
return Config.Terrain.CropsMaterial;
}
protected override GameConfig.WorldGenConfig.GrassConfig GetGrassGenerationConfig()
{
return Config.WorldGen.Crops;
}
protected override CropsGrassTileMask GetMask()
{
var fertility = World.Fertility.GetNativeArray();
return new CropsGrassTileMask { Fertility = fertility };
}
protected override int GetDensityForLOD(int lod)
{
return Config.Terrain.CropsLODs[lod].Density;
}
}
}

View File

@@ -0,0 +1,14 @@
using Unity.Collections;
namespace DanieleMarotta.RiversongCodeShowcase
{
public struct CropsGrassTileMask : IGrassTileMask
{
public NativeArray<FertilityMapValue> Fertility;
public bool CanGenerateAtTile(int tileIndex)
{
return Fertility[tileIndex].MaxFertility > 0;
}
}
}

View File

@@ -0,0 +1,151 @@
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;
using Random = Unity.Mathematics.Random;
namespace DanieleMarotta.RiversongCodeShowcase
{
[BurstCompile]
public struct GenerateGrassChunkJob<T> : IJobParallelFor where T : unmanaged, IGrassTileMask
{
public int2 WorldSize;
[ReadOnly]
public NativeArray<int2> ChunkCoordsArray;
public int ChunkSize;
[ReadOnly]
public T Mask;
[ReadOnly]
public NativeArray<int> Heightmap;
[NativeDisableParallelForRestriction]
public Mesh.MeshDataArray MeshDataArray;
public NativeArray<Random> RandomArray;
public int Density;
public float NoiseScale;
public float DiscardThreshold;
public float BladeWidth;
public float2 BladeHeightRange;
public void Execute(int index)
{
var chunkCoords = ChunkCoordsArray[index];
var random = RandomArray[index];
var vertexAttributes = new NativeArray<VertexAttributeDescriptor>(3, Allocator.Temp);
vertexAttributes[0] = new VertexAttributeDescriptor(VertexAttribute.Position);
vertexAttributes[1] = new VertexAttributeDescriptor(VertexAttribute.Color);
vertexAttributes[2] = new VertexAttributeDescriptor(VertexAttribute.TexCoord0, dimension: 2);
var triangleCount = ChunkSize * ChunkSize * Density * Density;
var vertexTemp = new NativeList<Vertex>(3 * triangleCount, Allocator.Temp);
var indexTemp = new NativeList<int>(3 * triangleCount, Allocator.Temp);
var tileMin = chunkCoords * ChunkSize;
var tileMax = math.min(tileMin + ChunkSize, WorldSize);
var tile = int2.zero;
var spacing = 1f / Density;
var triangle = 0;
for (tile.y = tileMin.y; tile.y < tileMax.y; tile.y++)
for (tile.x = tileMin.x; tile.x < tileMax.x; tile.x++)
{
var tileIndex = math.mad(tile.y, WorldSize.x, tile.x);
if (!Mask.CanGenerateAtTile(tileIndex)) continue;
var worldY = Heightmap[tileIndex];
for (var stepZ = 0; stepZ < Density; stepZ++)
{
var worldZ = math.mad(0.5f + stepZ, spacing, tile.y);
for (var stepX = 0; stepX < Density; stepX++)
{
var worldX = math.mad(0.5f + stepX, spacing, tile.x);
var bladeOrigin = new float3(worldX, worldY, worldZ);
bladeOrigin += 0.25f * spacing * new float3(random.NextFloat(-1, 1), 0, random.NextFloat(-1, 1));
const float edgeDistance = 0.3f;
var d = math.frac(bladeOrigin.xz);
if ((d.x < edgeDistance && (tile.x == 0 || Heightmap[tileIndex - 1] != worldY)) ||
(d.x > 1 - edgeDistance && (tile.x == WorldSize.x - 1 || Heightmap[tileIndex + 1] != worldY)) ||
(d.y < edgeDistance && (tile.y == 0 || Heightmap[tileIndex - WorldSize.x] != worldY)) ||
(d.y > 1 - edgeDistance && (tile.y == WorldSize.y - 1 || Heightmap[tileIndex + WorldSize.x] != worldY)) ||
(d is { x: < edgeDistance, y: < edgeDistance } && Heightmap[tileIndex - 1 - WorldSize.x] != worldY) ||
(d is { x: > 1 - edgeDistance, y: < edgeDistance } && Heightmap[tileIndex + 1 - WorldSize.x] != worldY) ||
(d is { x: < edgeDistance, y: > 1 - edgeDistance } && Heightmap[tileIndex - 1 + WorldSize.x] != worldY) ||
(d is { x: > 1 - edgeDistance, y: > 1 - edgeDistance } && Heightmap[tileIndex + 1 + WorldSize.x] != worldY))
continue;
var n = noise.snoise(NoiseScale * bladeOrigin);
n = math.remap(-1, 1, 0, 1, n);
if (n < DiscardThreshold) continue;
var rotation = quaternion.Euler(0, random.NextFloat(math.PI2), 0);
var halfBladeWidth = 0.5f * BladeWidth;
var bladeHeight = math.lerp(BladeHeightRange.x, BladeHeightRange.y, n);
var side = math.mul(rotation, new float3(halfBladeWidth, 0, 0));
var v0 = bladeOrigin - side;
var v1 = bladeOrigin + bladeHeight * math.up();
var v2 = bladeOrigin + side;
vertexTemp.AddNoResize(new Vertex(v0, bladeOrigin, new float2(0, bladeHeight)));
vertexTemp.AddNoResize(new Vertex(v1, bladeOrigin, new float2(bladeHeight, bladeHeight)));
vertexTemp.AddNoResize(new Vertex(v2, bladeOrigin, new float2(0, bladeHeight)));
indexTemp.AddNoResize(triangle);
indexTemp.AddNoResize(triangle + 1);
indexTemp.AddNoResize(triangle + 2);
triangle += 3;
}
}
}
var mesh = MeshDataArray[index];
mesh.SetVertexBufferParams(vertexTemp.Length, vertexAttributes);
var chunkVertexData = mesh.GetVertexData<Vertex>();
chunkVertexData.CopyFrom(vertexTemp.AsArray());
mesh.SetIndexBufferParams(indexTemp.Length, IndexFormat.UInt32);
var chunkIndexData = mesh.GetIndexData<int>();
chunkIndexData.CopyFrom(indexTemp.AsArray());
mesh.subMeshCount = 1;
mesh.SetSubMesh(0, new SubMeshDescriptor(0, indexTemp.Length));
}
private struct Vertex
{
public float3 Position;
public float3 Color;
public float2 TexCoord0;
public Vertex(float3 position, float3 color, float2 texCoord0)
{
Position = position;
Color = color;
TexCoord0 = texCoord0;
}
}
}
}

View File

@@ -0,0 +1,158 @@
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;
namespace DanieleMarotta.RiversongCodeShowcase
{
[BurstCompile]
public struct GenerateTerrainChunkJob : IJobParallelFor
{
public int2 WorldSize;
[ReadOnly]
public NativeArray<int2> ChunkCoordsArray;
public int ChunkSize;
[ReadOnly]
public NativeArray<int> Heightmap;
[NativeDisableParallelForRestriction]
public Mesh.MeshDataArray MeshDataArray;
public void Execute(int index)
{
var coords = ChunkCoordsArray[index];
var vertexAttributes = new NativeArray<VertexAttributeDescriptor>(3, Allocator.Temp);
vertexAttributes[0] = new VertexAttributeDescriptor(VertexAttribute.Position);
vertexAttributes[1] = new VertexAttributeDescriptor(VertexAttribute.Normal);
vertexAttributes[2] = new VertexAttributeDescriptor(VertexAttribute.TexCoord0, dimension: 2);
var tileCount = ChunkSize * ChunkSize;
var vertexTemp = new NativeList<Vertex>(20 * tileCount, Allocator.Temp);
var indexTemp = new NativeList<int>(30 * tileCount, Allocator.Temp);
var tileMin = coords * ChunkSize;
var tileMax = math.min(tileMin + ChunkSize, WorldSize);
var tile = int2.zero;
for (tile.y = tileMin.y; tile.y < tileMax.y; tile.y++)
for (tile.x = tileMin.x; tile.x < tileMax.x; tile.x++)
{
var h = Heightmap[math.mad(tile.y, WorldSize.x, tile.x)];
if (h == 0) continue;
var v0 = new Vertex(new float3(tile.x, h, tile.y), math.up(), float2.zero);
var v1 = new Vertex(new float3(tile.x, h, tile.y + 1), math.up(), float2.zero);
var v2 = new Vertex(new float3(tile.x + 1, h, tile.y + 1), math.up(), float2.zero);
var v3 = new Vertex(new float3(tile.x + 1, h, tile.y), math.up(), float2.zero);
WriteQuad(vertexTemp, indexTemp, v0, v1, v2, v3, false);
}
var cliffSubMeshIndexStart = indexTemp.Length;
for (tile.y = tileMin.y; tile.y < tileMax.y && tile.y < WorldSize.y - 1; tile.y++)
for (tile.x = tileMin.x; tile.x < tileMax.x && tile.x < WorldSize.x - 1; tile.x++)
{
var h = Heightmap[math.mad(tile.y, WorldSize.x, tile.x)];
var hx = Heightmap[math.mad(tile.y, WorldSize.x, tile.x + 1)];
if (h != hx)
{
var hMin = math.min(h, hx);
var hMax = math.max(h, hx);
for (var y = hMin; y < hMax; y++)
{
var bottomUV = math.unlerp(hMin, hMax, y);
var topUV = math.unlerp(hMin, hMax, y + 1);
var normal = h > hx ? math.right() : math.left();
var v0 = new Vertex(new float3(tile.x + 1, y, tile.y), normal, bottomUV);
var v1 = new Vertex(new float3(tile.x + 1, y + 1, tile.y), normal, topUV);
var v2 = new Vertex(new float3(tile.x + 1, y + 1, tile.y + 1), normal, topUV);
var v3 = new Vertex(new float3(tile.x + 1, y, tile.y + 1), normal, bottomUV);
WriteQuad(vertexTemp, indexTemp, v0, v1, v2, v3, h < hx);
}
}
var hz = Heightmap[math.mad(tile.y + 1, WorldSize.x, tile.x)];
if (h != hz)
{
var hMin = math.min(h, hz);
var hMax = math.max(h, hz);
for (var y = hMin; y < hMax; y++)
{
var bottomUV = math.unlerp(hMin, hMax, y);
var topUV = math.unlerp(hMin, hMax, y + 1);
var normal = h > hz ? math.forward() : math.back();
var v0 = new Vertex(new float3(tile.x + 1, y, tile.y + 1), normal, bottomUV);
var v1 = new Vertex(new float3(tile.x + 1, y + 1, tile.y + 1), normal, topUV);
var v2 = new Vertex(new float3(tile.x, y + 1, tile.y + 1), normal, topUV);
var v3 = new Vertex(new float3(tile.x, y, tile.y + 1), normal, bottomUV);
WriteQuad(vertexTemp, indexTemp, v0, v1, v2, v3, h < hz);
}
}
}
var mesh = MeshDataArray[index];
mesh.SetVertexBufferParams(vertexTemp.Length, vertexAttributes);
var chunkVertexData = mesh.GetVertexData<Vertex>();
chunkVertexData.CopyFrom(vertexTemp.AsArray());
mesh.SetIndexBufferParams(indexTemp.Length, IndexFormat.UInt32);
var chunkIndexData = mesh.GetIndexData<int>();
chunkIndexData.CopyFrom(indexTemp.AsArray());
mesh.subMeshCount = indexTemp.Length > cliffSubMeshIndexStart ? 2 : 1;
mesh.SetSubMesh(0, new SubMeshDescriptor(0, cliffSubMeshIndexStart));
if (mesh.subMeshCount > 1) mesh.SetSubMesh(1, new SubMeshDescriptor(cliffSubMeshIndexStart, chunkIndexData.Length - cliffSubMeshIndexStart));
}
private void WriteQuad(NativeList<Vertex> vertices, NativeList<int> indices, in Vertex v0, in Vertex v1, in Vertex v2, in Vertex v3, bool flip)
{
var v = vertices.Length;
vertices.AddNoResize(v0);
vertices.AddNoResize(v1);
vertices.AddNoResize(v2);
vertices.AddNoResize(v3);
var i0 = v + (flip ? 2 : 0);
var i1 = v + 1;
var i2 = v + (flip ? 0 : 2);
var i3 = v + 3;
indices.AddNoResize(i0);
indices.AddNoResize(i1);
indices.AddNoResize(i2);
indices.AddNoResize(i2);
indices.AddNoResize(i3);
indices.AddNoResize(i0);
}
private struct Vertex
{
public float3 Position;
public float3 Normal;
public float2 TexCoord0;
public Vertex(float3 position, float3 normal, float2 texCoord0)
{
Position = position;
Normal = normal;
TexCoord0 = texCoord0;
}
}
}
}

View File

@@ -0,0 +1,78 @@
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;
using Random = Unity.Mathematics.Random;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class GrassChunkMeshGenerator : GrassChunkMeshGeneratorBase<GrassTileMask>
{
public GrassChunkMeshGenerator(World world, GameConfig config) : base(world, config)
{
}
protected override int GetLodCount()
{
return Config.Terrain.GrassLODs.Length;
}
protected override string GetGameObjectName(string chunkName, int2 chunkCoords, int lod)
{
return $"{chunkName}_Grass_LOD{lod}";
}
protected override void InitializeChunk(TerrainChunk chunk, int lod, GameObject gameObject, Mesh mesh, Renderer renderer)
{
base.InitializeChunk(chunk, lod, gameObject, mesh, renderer);
renderer.shadowCastingMode = ShadowCastingMode.Off;
chunk.GrassLODs[lod] = new TerrainChunk.RenderData(mesh, renderer);
}
protected override Mesh GetMesh(TerrainChunk chunk, int lod)
{
return chunk.GrassLODs[lod].Mesh;
}
protected override JobHandle ScheduleJob(ChunkGenerationJobState jobState, NativeArray<int2> chunkCoords, NativeArray<Random> randomArray)
{
var job = new GenerateGrassChunkJob<GrassTileMask>();
PrepareJob(jobState, chunkCoords, randomArray, jobState.Lod, ref job);
return job.Schedule(chunkCoords.Length, Config.WorldGen.ChunksPerThread);
}
protected override Renderer GetRenderer(TerrainChunk chunk, int lod)
{
return chunk.GrassLODs[lod].Renderer;
}
protected override Material GetMaterial(int lod)
{
return Config.Terrain.GrassMaterial;
}
protected override GameConfig.WorldGenConfig.GrassConfig GetGrassGenerationConfig()
{
return Config.WorldGen.Grass;
}
protected override GrassTileMask GetMask()
{
var heightmap = World.Heightmap.GetNativeArray();
var fertility = World.Fertility.GetNativeArray();
return new GrassTileMask
{
Heightmap = heightmap,
Fertility = fertility
};
}
protected override int GetDensityForLOD(int lod)
{
return Config.Terrain.GrassLODs[lod].Density;
}
}
}

View File

@@ -0,0 +1,72 @@
using System.Collections.Generic;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using Random = Unity.Mathematics.Random;
namespace DanieleMarotta.RiversongCodeShowcase
{
public abstract class GrassChunkMeshGeneratorBase<T> : ChunkMeshGenerator where T : unmanaged, IGrassTileMask
{
protected GrassChunkMeshGeneratorBase(World world, GameConfig config) : base(world, config)
{
}
protected override void InitializeChunk(TerrainChunk chunk, int lod, GameObject gameObject, Mesh mesh, Renderer renderer)
{
renderer.enabled = false;
}
protected override void OnCompleted(int lod, List<TerrainChunk> chunks)
{
foreach (var chunk in chunks)
{
var renderer = GetRenderer(chunk, lod);
renderer.sharedMaterial = GetMaterial(lod);
}
}
protected abstract Renderer GetRenderer(TerrainChunk chunk, int lod);
protected abstract Material GetMaterial(int lod);
protected override JobHandle ScheduleJob(ChunkGenerationJobState jobState, NativeArray<int2> chunkCoords)
{
var randomArray = new NativeArray<Random>(chunkCoords.Length, Allocator.Persistent);
for (var i = 0; i < randomArray.Length; i++) randomArray[i] = Random.CreateFromIndex((uint)(World.Seed + i));
var jobHandle = ScheduleJob(jobState, chunkCoords, randomArray);
randomArray.Dispose(jobHandle);
return jobHandle;
}
protected abstract JobHandle ScheduleJob(ChunkGenerationJobState jobState, NativeArray<int2> chunkCoords, NativeArray<Random> randomArray);
protected void PrepareJob(ChunkGenerationJobState jobState, NativeArray<int2> chunkCoords, NativeArray<Random> randomArray, int lod, ref GenerateGrassChunkJob<T> job)
{
var grassGenerationConfig = GetGrassGenerationConfig();
job.WorldSize = World.Size;
job.ChunkCoordsArray = chunkCoords;
job.ChunkSize = Config.WorldGen.ChunkSize;
job.Mask = GetMask();
job.Heightmap = World.Heightmap.GetNativeArray();
job.MeshDataArray = jobState.MeshDataArray;
job.RandomArray = randomArray;
job.Density = GetDensityForLOD(lod);
job.NoiseScale = grassGenerationConfig.NoiseScale;
job.DiscardThreshold = grassGenerationConfig.NoiseDiscardThreshold;
job.BladeWidth = grassGenerationConfig.BladeWidth;
job.BladeHeightRange = grassGenerationConfig.BladeHeightRange;
}
protected abstract GameConfig.WorldGenConfig.GrassConfig GetGrassGenerationConfig();
protected abstract T GetMask();
protected abstract int GetDensityForLOD(int lod);
}
}

View File

@@ -0,0 +1,16 @@
using Unity.Collections;
namespace DanieleMarotta.RiversongCodeShowcase
{
public struct GrassTileMask : IGrassTileMask
{
public NativeArray<int> Heightmap;
public NativeArray<FertilityMapValue> Fertility;
public bool CanGenerateAtTile(int tileIndex)
{
return Heightmap[tileIndex] > 0 && Fertility[tileIndex].MaxFertility == 0;
}
}
}

View File

@@ -0,0 +1,7 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IGrassTileMask
{
bool CanGenerateAtTile(int tileIndex);
}
}

View File

@@ -0,0 +1,68 @@
using System.Collections.Generic;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class TerrainChunkMeshGenerator : ChunkMeshGenerator
{
public TerrainChunkMeshGenerator(World world, GameConfig config) : base(world, config)
{
}
protected override int GetLodCount()
{
return 1;
}
protected override string GetGameObjectName(string chunkName, int2 chunkCoords, int lod)
{
return $"{chunkName}_Terrain";
}
protected override void InitializeChunk(TerrainChunk chunk, int lod, GameObject gameObject, Mesh mesh, Renderer renderer)
{
gameObject.layer = GameObjectLayers.Terrain;
chunk.Terrain = new TerrainChunk.RenderData(mesh, renderer);
}
protected override Mesh GetMesh(TerrainChunk chunk, int lod)
{
return chunk.Terrain.Mesh;
}
protected override JobHandle ScheduleJob(ChunkGenerationJobState jobState, NativeArray<int2> chunkCoords)
{
return new GenerateTerrainChunkJob
{
WorldSize = World.Size,
ChunkCoordsArray = chunkCoords,
ChunkSize = Config.WorldGen.ChunkSize,
Heightmap = World.Heightmap.GetNativeArray(),
MeshDataArray = jobState.MeshDataArray
}.Schedule(chunkCoords.Length, Config.WorldGen.ChunksPerThread);
}
protected override void OnCompleted(int lod, List<TerrainChunk> chunks)
{
foreach (var chunk in chunks)
{
var mesh = chunk.Terrain.Mesh;
var renderer = chunk.Terrain.Renderer;
var subMeshCount = mesh.subMeshCount;
var materials = new Material[subMeshCount];
materials[0] = Config.Terrain.GroundMaterial;
if (subMeshCount > 1) materials[1] = Config.Terrain.CliffMaterial;
renderer.sharedMaterials = materials;
renderer.gameObject.AddComponent<MeshCollider>();
}
}
}
}