riversong code showcase
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user