diff --git a/Source/Editor/Tools/Terrain/Paint/Mode.cs b/Source/Editor/Tools/Terrain/Paint/Mode.cs
index 3647691b3..03e4a4690 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,32 @@ 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 refersto 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 ec41b5286..5921f7d10 100644
--- a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs
+++ b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs
@@ -1,6 +1,5 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
-using System;
using FlaxEngine;
namespace FlaxEditor.Tools.Terrain.Paint
@@ -73,53 +72,53 @@ 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");
+ bool otherModified = false;
for (int z = 0; z < p.ModifiedSize.Y; z++)
{
var zz = z + p.ModifiedOffset.Y;
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.Saturate(p.Brush.Sample(ref brushPosition, ref samplePositionWorld));
- var paintAmount = p.Brush.Sample(ref brushPosition, ref samplePositionWorld) * strength;
+ var paintAmount = sample * strength;
+ if (paintAmount < 0.0f)
+ continue; // Skip when pixel won't be affected
- // Extract layer weight
- byte* srcPtr = &src.R;
- var srcWeight = *(srcPtr + layerComponent) / 255.0f;
-
- // Accumulate weight
- float dstWeight = srcWeight + paintAmount;
-
- // Check for solid layer case
- if (dstWeight >= 1.0f)
- {
- // Erase other layers
- // TODO: maybe erase only the higher layers?
- // TODO: need to erase also weights form the other splatmaps
- src = Color32.Transparent;
-
- // Use limit value
- dstWeight = 1.0f;
- }
-
- // Modify packed weight
- *(srcPtr + layerComponent) = (byte)(dstWeight * 255.0f);
-
- // Write back
+ // Paint on the active splatmap texture
+ src[c] = Mathf.Saturate(src[c] + paintAmount);
+ src[(c + 1) % 4] = Mathf.Saturate(src[(c + 1) % 4] - paintAmount);
+ src[(c + 2) % 4] = Mathf.Saturate(src[(c + 2) % 4] - paintAmount);
+ src[(c + 3) % 4] = Mathf.Saturate(src[(c + 3) % 4] - paintAmount);
p.TempBuffer[z * p.ModifiedSize.X + x] = src;
+
+ var other = (Color)p.SourceDataOther[zz * p.HeightmapSize + xx];
+ //if (other.ValuesSum > 0.0f) // Skip editing the other splatmap if it's empty
+ {
+ // Remove 'paint' from the other splatmap texture
+ other[c] = Mathf.Saturate(other[c] - paintAmount);
+ other[(c + 1) % 4] = Mathf.Saturate(other[(c + 1) % 4] - paintAmount);
+ other[(c + 2) % 4] = Mathf.Saturate(other[(c + 2) % 4] - paintAmount);
+ other[(c + 3) % 4] = Mathf.Saturate(other[(c + 3) % 4] - paintAmount);
+ p.TempBufferOther[z * p.ModifiedSize.X + x] = other;
+ otherModified = true;
+ }
}
}
Profiler.EndEvent();
// Update terrain patch
TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndex, p.TempBuffer, ref p.ModifiedOffset, ref p.ModifiedSize);
+ if (otherModified)
+ 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..4e7925dd9 100644
--- a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs
+++ b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs
@@ -36,9 +36,34 @@ namespace FlaxEditor.Tools.Terrain
"Layer 7",
};
- private IntPtr _cachedSplatmapData;
- private int _cachedSplatmapDataSize;
+ private struct SplatmapData
+ {
+ public IntPtr DataPtr;
+ public int 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 SplatmapData[] _cachedSplatmapData = new SplatmapData[2];
///
/// The terrain painting gizmo.
@@ -230,20 +255,13 @@ 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 != IntPtr.Zero)
- {
- Marshal.FreeHGlobal(_cachedSplatmapData);
- }
- _cachedSplatmapData = Marshal.AllocHGlobal(size);
- _cachedSplatmapDataSize = size;
- }
-
- return _cachedSplatmapData;
+ ref var splatmapData = ref _cachedSplatmapData[splatmapIndex];
+ splatmapData.EnsureCapacity(size);
+ return splatmapData.DataPtr;
}
///
@@ -276,12 +294,8 @@ namespace FlaxEditor.Tools.Terrain
base.OnDeactivated();
// Free temporary memory buffer
- if (_cachedSplatmapData != IntPtr.Zero)
- {
- Marshal.FreeHGlobal(_cachedSplatmapData);
- _cachedSplatmapData = IntPtr.Zero;
- _cachedSplatmapDataSize = 0;
- }
+ foreach (var splatmapData in _cachedSplatmapData)
+ splatmapData.Free();
}
///
diff --git a/Source/Engine/Core/Math/Color.cs b/Source/Engine/Core/Math/Color.cs
index d585177a0..7474b913d 100644
--- a/Source/Engine/Core/Math/Color.cs
+++ b/Source/Engine/Core/Math/Color.cs
@@ -92,6 +92,21 @@ namespace FlaxEngine
///
public float MaxColorComponent => Mathf.Max(Mathf.Max(R, G), B);
+ ///
+ /// Gets a minimum component value (max of r,g,b,a).
+ ///
+ public float MinValue => Math.Min(R, Math.Min(G, Math.Min(B, A)));
+
+ ///
+ /// Gets a maximum component value (min of r,g,b,a).
+ ///
+ public float MaxValue => Math.Max(R, Math.Max(G, Math.Max(B, A)));
+
+ ///
+ /// Gets a sum of the component values.
+ ///
+ public float ValuesSum => R + G + B + A;
+
///
/// Constructs a new Color with given r,g,b,a component.
///