From cf5f501ab7edca8ca4cc6ba541f2f1996a9d3db3 Mon Sep 17 00:00:00 2001 From: MineBill Date: Tue, 24 Oct 2023 14:31:54 +0300 Subject: [PATCH] Allow erasing layers/painting over layers from the other splatmap. Since Layers are hardcoded to 8, this commit also assumes that there will always be 2 splatmaps. --- Source/Editor/Tools/Terrain/Paint/Mode.cs | 30 +++++++++- .../Tools/Terrain/Paint/SingleLayerMode.cs | 30 +++++++--- .../Tools/Terrain/PaintTerrainGizmoMode.cs | 60 ++++++++++++++----- 3 files changed, 95 insertions(+), 25 deletions(-) diff --git a/Source/Editor/Tools/Terrain/Paint/Mode.cs b/Source/Editor/Tools/Terrain/Paint/Mode.cs index 3647691b3..bf9857bea 100644 --- a/Source/Editor/Tools/Terrain/Paint/Mode.cs +++ b/Source/Editor/Tools/Terrain/Paint/Mode.cs @@ -67,11 +67,13 @@ namespace FlaxEditor.Tools.Terrain.Paint // Prepare var splatmapIndex = ActiveSplatmapIndex; + var splatmapIndexOther = (splatmapIndex + 1) % 2; var chunkSize = terrain.ChunkSize; var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1; var heightmapLength = heightmapSize * heightmapSize; var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount; - var tempBuffer = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes).ToPointer(); + var tempBuffer = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, splatmapIndex).ToPointer(); + var tempBufferOther = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, (splatmapIndex + 1) % 2).ToPointer(); var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex; ApplyParams p = new ApplyParams { @@ -81,8 +83,10 @@ namespace FlaxEditor.Tools.Terrain.Paint Options = options, Strength = strength, SplatmapIndex = splatmapIndex, + SplatmapIndexOther = splatmapIndexOther, HeightmapSize = heightmapSize, TempBuffer = tempBuffer, + TempBufferOther = tempBufferOther, }; // Get brush bounds in terrain local space @@ -131,11 +135,16 @@ namespace FlaxEditor.Tools.Terrain.Paint var sourceData = TerrainTools.GetSplatMapData(terrain, ref patch.PatchCoord, splatmapIndex); if (sourceData == null) throw new Exception("Cannot modify terrain. Loading splatmap failed. See log for more info."); + + var sourceDataOther = TerrainTools.GetSplatMapData(terrain, ref patch.PatchCoord, splatmapIndexOther); + if (sourceDataOther == null) + throw new Exception("Cannot modify terrain. Loading splatmap failed. See log for more info."); // Record patch data before editing it if (!gizmo.CurrentEditUndoAction.HashPatch(ref patch.PatchCoord)) { gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord, splatmapIndex); + gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord, splatmapIndexOther); } // Apply modification @@ -144,6 +153,7 @@ namespace FlaxEditor.Tools.Terrain.Paint p.PatchCoord = patch.PatchCoord; p.PatchPositionLocal = patchPositionLocal; p.SourceData = sourceData; + p.SourceDataOther = sourceDataOther; Apply(ref p); } } @@ -197,16 +207,34 @@ namespace FlaxEditor.Tools.Terrain.Paint /// The splatmap texture index. /// public int SplatmapIndex; + + /// + /// The splatmap texture index. If is 0, this will be 1. + /// If is 1, this will be 0. + /// + public int SplatmapIndexOther; /// /// The temporary data buffer (for modified data). /// public Color32* TempBuffer; + + /// + /// The 'other" temporary data buffer (for modified data). If refers + /// to the splatmap with index 0, this one will refer to the one with index 1. + /// + public Color32* TempBufferOther; /// /// The source data buffer. /// public Color32* SourceData; + + /// + /// The 'other' source data buffer. If refers + /// to the splatmap with index 0, this one will refer to the one with index 1. + /// + public Color32* SourceDataOther; /// /// The heightmap size (edge). diff --git a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs index 3bb014e24..d0c76a830 100644 --- a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs +++ b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs @@ -72,7 +72,7 @@ namespace FlaxEditor.Tools.Terrain.Paint var strength = p.Strength; var layer = (int)Layer; var brushPosition = p.Gizmo.CursorPosition; - var layerComponent = layer % 4; + var c = layer % 4; // Apply brush modification Profiler.BeginEvent("Apply Brush"); @@ -82,26 +82,38 @@ namespace FlaxEditor.Tools.Terrain.Paint for (int x = 0; x < p.ModifiedSize.X; x++) { var xx = x + p.ModifiedOffset.X; - var src = p.SourceData[zz * p.HeightmapSize + xx]; + var src = (Color)p.SourceData[zz * p.HeightmapSize + xx]; var samplePositionLocal = p.PatchPositionLocal + new Vector3(xx * FlaxEngine.Terrain.UnitsPerVertex, 0, zz * FlaxEngine.Terrain.UnitsPerVertex); Vector3.Transform(ref samplePositionLocal, ref p.TerrainWorld, out Vector3 samplePositionWorld); - - var sample = Mathf.Clamp(p.Brush.Sample(ref brushPosition, ref samplePositionWorld), 0f, 1f); - var paintAmount = sample * strength * (1f - src[layerComponent] / 255f); - src[layerComponent] = (byte)Mathf.Clamp(src[layerComponent] + paintAmount * 255, 0, 255); - src[(layerComponent + 1) % 4] = (byte)Mathf.Clamp(src[(layerComponent + 1) % 4] - paintAmount * 255, 0, 255); - src[(layerComponent + 2) % 4] = (byte)Mathf.Clamp(src[(layerComponent + 2) % 4] - paintAmount * 255, 0, 255); - src[(layerComponent + 3) % 4] = (byte)Mathf.Clamp(src[(layerComponent + 3) % 4] - paintAmount * 255, 0, 255); + var sample = Mathf.Clamp(p.Brush.Sample(ref brushPosition, ref samplePositionWorld), 0f, 1f); + var paintAmount = sample * strength * (1f - src[c]); + // Paint on the active splatmap texture + src[c] = Mathf.Clamp(src[c] + paintAmount, 0, 1f); + src[(c + 1) % 4] = Mathf.Clamp(src[(c + 1) % 4] - paintAmount, 0, 1f); + src[(c + 2) % 4] = Mathf.Clamp(src[(c + 2) % 4] - paintAmount, 0, 1f); + src[(c + 3) % 4] = Mathf.Clamp(src[(c + 3) % 4] - paintAmount, 0, 1f); + p.TempBuffer[z * p.ModifiedSize.X + x] = src; + + // Remove 'paint' from the other splatmap texture + var other = (Color)p.SourceDataOther[zz * p.HeightmapSize + xx]; + + other[c] = Mathf.Clamp(other[c] - paintAmount, 0, 1f); + other[(c + 1) % 4] = Mathf.Clamp(other[(c + 1) % 4] - paintAmount, 0, 1f); + other[(c + 2) % 4] = Mathf.Clamp(other[(c + 2) % 4] - paintAmount, 0, 1f); + other[(c + 3) % 4] = Mathf.Clamp(other[(c + 3) % 4] - paintAmount, 0, 1f); + + p.TempBufferOther[z * p.ModifiedSize.X + x] = other; } } Profiler.EndEvent(); // Update terrain patch TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndex, p.TempBuffer, ref p.ModifiedOffset, ref p.ModifiedSize); + TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndexOther, p.TempBufferOther, ref p.ModifiedOffset, ref p.ModifiedSize); } } } diff --git a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs index 1d1bf87ca..05aaa48b5 100644 --- a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs +++ b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs @@ -36,9 +36,43 @@ namespace FlaxEditor.Tools.Terrain "Layer 7", }; - private IntPtr _cachedSplatmapData; - private int _cachedSplatmapDataSize; + private class SplatmapData + { + public IntPtr DataPtr { get; set; } = IntPtr.Zero; + public int Size { get; set; } = 0; + + public SplatmapData(int size) + { + EnsureCapacity(size); + } + + public void EnsureCapacity(int size) + { + if (Size < size) + { + if (DataPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(DataPtr); + } + DataPtr = Marshal.AllocHGlobal(size); + Size = size; + } + } + + /// + public void Free() + { + if (DataPtr == IntPtr.Zero) + return; + + Marshal.FreeHGlobal(DataPtr); + DataPtr = IntPtr.Zero; + Size = 0; + } + } + private EditTerrainMapAction _activeAction; + private List _cachedSplatmapData = new(); /// /// The terrain painting gizmo. @@ -230,20 +264,18 @@ namespace FlaxEditor.Tools.Terrain /// Gets the splatmap temporary scratch memory buffer used to modify terrain samples. Allocated memory is unmanaged by GC. /// /// The minimum buffer size (in bytes). + /// The splatmap index for which to return/create the temp buffer. /// The allocated memory using interface. - public IntPtr GetSplatmapTempBuffer(int size) + public IntPtr GetSplatmapTempBuffer(int size, int splatmapIndex) { - if (_cachedSplatmapDataSize < size) + if (_cachedSplatmapData.Count <= splatmapIndex) { - if (_cachedSplatmapData != IntPtr.Zero) - { - Marshal.FreeHGlobal(_cachedSplatmapData); - } - _cachedSplatmapData = Marshal.AllocHGlobal(size); - _cachedSplatmapDataSize = size; + _cachedSplatmapData.Add(new SplatmapData(size)); + return _cachedSplatmapData[splatmapIndex].DataPtr; } - return _cachedSplatmapData; + _cachedSplatmapData[splatmapIndex].EnsureCapacity(size); + return _cachedSplatmapData[splatmapIndex].DataPtr; } /// @@ -276,11 +308,9 @@ namespace FlaxEditor.Tools.Terrain base.OnDeactivated(); // Free temporary memory buffer - if (_cachedSplatmapData != IntPtr.Zero) + foreach (var splatmapData in _cachedSplatmapData) { - Marshal.FreeHGlobal(_cachedSplatmapData); - _cachedSplatmapData = IntPtr.Zero; - _cachedSplatmapDataSize = 0; + splatmapData.Free(); } }