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,228 @@
using System;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using Object = UnityEngine.Object;
namespace DanieleMarotta.RiversongCodeShowcase
{
[RequiresWorldReadyForUpdate]
public class TerrainShaderParametersSystem : GameSystem, IInitializable, IDisposable, IUpdatable, IOnWorldGenerationCompletedCallback
{
private static readonly int BlockedTilesMaskID = Shader.PropertyToID("_Blocked_Tiles_Mask");
private static readonly int CliffsMaskID = Shader.PropertyToID("_Cliffs_Mask");
private static readonly int WindMapID = Shader.PropertyToID("_Wind_Map");
private static readonly int WindMapScaleID = Shader.PropertyToID("_Wind_Map_Scale");
private static readonly int WindMapSpeedID = Shader.PropertyToID("_Wind_Map_Speed");
private static readonly int WindBendID = Shader.PropertyToID("_Wind_Bend");
[InjectService]
private GameConfig _config;
[InjectService]
private EditingState _editingState;
[InjectService]
private World _world;
[InjectService]
private ISignalBus _signalBus;
private Texture2D _blockedTilesMask;
private Texture2D _windMap;
private Texture2D _cliffsMask;
public TerrainShaderParametersSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public UniTask InitializeAsync()
{
_signalBus.Subscribe<WorldGenerationCompletedSignal>(OnWorldGenerationCompleted);
return UniTask.CompletedTask;
}
public void Dispose()
{
_signalBus.Unsubscribe<WorldGenerationCompletedSignal>(OnWorldGenerationCompleted);
if (_blockedTilesMask)
{
Object.Destroy(_blockedTilesMask);
_blockedTilesMask = null;
}
if (_cliffsMask)
{
Object.Destroy(_cliffsMask);
_cliffsMask = null;
}
}
private void OnWorldGenerationCompleted(WorldGenerationCompletedSignal signal)
{
signal.Callbacks.Add(this);
}
public async UniTask OnWorldGenerationCompletedAsync(World world)
{
InitializedBlockedTilesMask();
await InitializeCliffsMaskAsync();
await InitializeWindAsync();
InitializeDebugGUI();
}
private void InitializedBlockedTilesMask()
{
_blockedTilesMask = new Texture2D(_world.Size.x, _world.Size.y, TextureFormat.RGBA32, false);
Shader.SetGlobalTexture(BlockedTilesMaskID, _blockedTilesMask);
var black = new NativeArray<Color32>(_world.Size.x * _world.Size.y, Allocator.Temp);
_blockedTilesMask.SetPixelData(black, 0);
}
private async UniTask InitializeCliffsMaskAsync()
{
var size = _world.Size;
_cliffsMask = new Texture2D(size.x + 1, size.y + 1, TextureFormat.RGBA32, false);
Shader.SetGlobalTexture(CliffsMaskID, _cliffsMask);
var pixelData = new NativeArray<Color32>((size.x + 1) * (size.y + 1), Allocator.Persistent);
var heightmap = _world.Heightmap.GetNativeArray();
Parallel.ForEach(
TileRange.From(0, size),
tile =>
{
var color = default(Color32);
var tileIndex = math.mad(tile.y, size.x, tile.x);
var tr = tile.x < size.x && tile.y < size.y ? heightmap[tileIndex] : 0;
var tl = tile.x > 0 && tile.y < size.y ? heightmap[tileIndex - 1] : 0;
var br = tile.x < size.x && tile.y > 0 ? heightmap[tileIndex - size.x] : 0;
var bl = tile is { x: > 0, y: > 0 } ? heightmap[tileIndex - size.x - 1] : 0;
if (tr != tl || br != bl) color.r = 255;
if (tr != br || tl != bl) color.g = 255;
var pixelIndex = math.mad(tile.y, size.x + 1, tile.x);
// ReSharper disable once AccessToDisposedClosure
pixelData[pixelIndex] = color;
});
await UniTask.NextFrame();
_cliffsMask.SetPixelData(pixelData, 0);
_cliffsMask.Apply();
pixelData.Dispose();
}
private async UniTask InitializeWindAsync()
{
_windMap = await _config.Terrain.Wind.Map.LoadAssetAsync<Texture2D>();
var config = _config.Terrain.Wind;
Shader.SetGlobalTexture(WindMapID, _windMap);
Shader.SetGlobalFloat(WindMapScaleID, config.MapScale);
Shader.SetGlobalFloat(WindMapSpeedID, config.Speed);
Shader.SetGlobalVector(WindBendID, config.BendDirection);
}
private void InitializeDebugGUI()
{
#if DEBUG
var gui = TerrainShaderDebugGUI.Create();
gui.BlockedTilesMaskTexture = _blockedTilesMask;
gui.CliffsMaskTexture = _cliffsMask;
#endif
}
public void Update()
{
UpdateBlockedTiles();
}
private void UpdateBlockedTiles()
{
var pixelData = _blockedTilesMask.GetPixelData<Color32>(0);
new BlockMapJob
{
BlockMap = _world.BlockMap.GetNativeArray(),
Fertility = _world.Fertility.GetNativeArray(),
BlockTexture = pixelData,
GrowthQ8_8 = (int)math.round(_config.Terrain.GrassGrowthRate * Time.deltaTime * byte.MaxValue * 256)
}.Schedule(pixelData.Length, _blockedTilesMask.width)
.Complete();
if (_editingState.ActiveTool != null)
foreach (var (tile, _) in _editingState.ActiveTool.AffectedTiles)
{
if (_world.BlockMap.IsBlocked(tile, BlockReason.InvalidElevation)) continue;
var i = tile.x + tile.y * _blockedTilesMask.width;
var color = pixelData[i];
color.g = byte.MaxValue;
pixelData[i] = color;
}
_blockedTilesMask.Apply();
}
[BurstCompile]
private struct BlockMapJob : IJobParallelFor
{
[ReadOnly]
public NativeArray<BlockReason> BlockMap;
[ReadOnly]
public NativeArray<FertilityMapValue> Fertility;
public NativeArray<Color32> BlockTexture;
public int GrowthQ8_8;
[BurstCompile]
public void Execute(int index)
{
var color = BlockTexture[index];
var (currentFertility, maxFertility) = Fertility[index];
var clear = (currentFertility > 0 && currentFertility < maxFertility) || (BlockMap[index] & BlockReason.ClearGrassMask) != 0;
if (clear)
{
color.r = 255;
color.b = 0;
}
else
{
var q = (color.r << 8) | color.b;
q = math.max(0, q - GrowthQ8_8);
color.r = (byte)(q >> 8);
color.b = (byte)(q & 0xFF);
}
color.g = 0;
BlockTexture[index] = color;
}
}
}
}