diff --git a/Source/Editor/Tools/Terrain/Sculpt/FlattenMode.cs b/Source/Editor/Tools/Terrain/Sculpt/FlattenMode.cs
index 610a336b7..bf80eb34d 100644
--- a/Source/Editor/Tools/Terrain/Sculpt/FlattenMode.cs
+++ b/Source/Editor/Tools/Terrain/Sculpt/FlattenMode.cs
@@ -18,7 +18,7 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
public float TargetHeight = 0.0f;
///
- public override unsafe void Apply(ref ApplyParams p)
+ public override unsafe void ApplyBrushToPatch(ref ApplyParams p)
{
// If used with invert mode pick the target height level
if (p.Options.Invert)
diff --git a/Source/Editor/Tools/Terrain/Sculpt/HolesMode.cs b/Source/Editor/Tools/Terrain/Sculpt/HolesMode.cs
index d4c10f00e..3d75a56f2 100644
--- a/Source/Editor/Tools/Terrain/Sculpt/HolesMode.cs
+++ b/Source/Editor/Tools/Terrain/Sculpt/HolesMode.cs
@@ -26,7 +26,7 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
}
///
- public override unsafe void Apply(ref ApplyParams p)
+ public override unsafe void ApplyBrushToPatch(ref ApplyParams p)
{
var strength = p.Strength * -10.0f;
var brushPosition = p.Gizmo.CursorPosition;
diff --git a/Source/Editor/Tools/Terrain/Sculpt/Mode.cs b/Source/Editor/Tools/Terrain/Sculpt/Mode.cs
index 9a42d2ae8..a92b899e1 100644
--- a/Source/Editor/Tools/Terrain/Sculpt/Mode.cs
+++ b/Source/Editor/Tools/Terrain/Sculpt/Mode.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
+using System.Collections.Generic;
using FlaxEditor.Tools.Terrain.Brushes;
using FlaxEngine;
@@ -50,18 +51,20 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
public virtual bool EditHoles => false;
///
- /// Applies the modification to the terrain.
+ /// Gets all patches that will be affected by the brush
///
- /// The brush.
- /// The options.
- /// The gizmo.
- /// The terrain.
- public unsafe void Apply(Brush brush, ref Options options, SculptTerrainGizmoMode gizmo, FlaxEngine.Terrain terrain)
+ ///
+ ///
+ ///
+ ///
+ public unsafe virtual List GetAffectedPatches(Brush brush, ref Options options, SculptTerrainGizmoMode gizmo, FlaxEngine.Terrain terrain)
{
+ List affectedPatches = new();
+
// Combine final apply strength
float strength = Strength * options.Strength * options.DeltaTime;
if (strength <= 0.0f)
- return;
+ return affectedPatches;
if (options.Invert && SupportsNegativeApply)
strength *= -1;
@@ -72,20 +75,10 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount;
var tempBuffer = (float*)gizmo.GetHeightmapTempBuffer(heightmapLength * sizeof(float)).ToPointer();
var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex;
- ApplyParams p = new ApplyParams
- {
- Terrain = terrain,
- Brush = brush,
- Gizmo = gizmo,
- Options = options,
- Strength = strength,
- HeightmapSize = heightmapSize,
- TempBuffer = tempBuffer,
- };
// Get brush bounds in terrain local space
var brushBounds = gizmo.CursorBrushBounds;
- terrain.GetLocalToWorldMatrix(out p.TerrainWorld);
+ terrain.GetLocalToWorldMatrix(out var terrainWorld);
terrain.GetWorldToLocalMatrix(out var terrainInvWorld);
BoundingBox.Transform(ref brushBounds, ref terrainInvWorld, out var brushBoundsLocal);
@@ -131,26 +124,78 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
if (sourceHeights == null && sourceHoles == null)
throw new Exception("Cannot modify terrain. Loading heightmap failed. See log for more info.");
- // Record patch data before editing it
- if (!gizmo.CurrentEditUndoAction.HashPatch(ref patch.PatchCoord))
+ ApplyParams p = new ApplyParams
{
- gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord);
- }
+ Terrain = terrain,
+ TerrainWorld = terrainWorld,
+ Brush = brush,
+ Gizmo = gizmo,
+ Options = options,
+ Strength = strength,
+ HeightmapSize = heightmapSize,
+ TempBuffer = tempBuffer,
+ ModifiedOffset = modifiedOffset,
+ ModifiedSize = modifiedSize,
+ PatchCoord = patch.PatchCoord,
+ PatchPositionLocal = patchPositionLocal,
+ SourceHeightMap = sourceHeights,
+ SourceHolesMask = sourceHoles,
+ };
- // Apply modification
- p.ModifiedOffset = modifiedOffset;
- p.ModifiedSize = modifiedSize;
- p.PatchCoord = patch.PatchCoord;
- p.PatchPositionLocal = patchPositionLocal;
- p.SourceHeightMap = sourceHeights;
- p.SourceHolesMask = sourceHoles;
- Apply(ref p);
+ affectedPatches.Add(p);
}
+ return affectedPatches;
+ }
+
+ ///
+ /// Applies the modification to the terrain.
+ ///
+ /// The brush.
+ /// The options.
+ /// The gizmo.
+ /// The terrain.
+ public unsafe void Apply(Brush brush, ref Options options, SculptTerrainGizmoMode gizmo, FlaxEngine.Terrain terrain)
+ {
+ var affectedPatches = GetAffectedPatches(brush, ref options, gizmo, terrain);
+
+ if (affectedPatches.Count == 0)
+ {
+ return;
+ }
+
+ ApplyBrush(gizmo, affectedPatches);
+
// Auto NavMesh rebuild
+ var brushBounds = gizmo.CursorBrushBounds;
gizmo.CurrentEditUndoAction.AddDirtyBounds(ref brushBounds);
}
+ ///
+ /// Applies the brush to all affected patches
+ ///
+ ///
+ ///
+ public unsafe virtual void ApplyBrush(SculptTerrainGizmoMode gizmo, List affectedPatches)
+ {
+ for (int i = 0; i < affectedPatches.Count; i++)
+ {
+ ApplyParams patchApplyParams = affectedPatches[i];
+
+ // Record patch data before editing it
+ if (!gizmo.CurrentEditUndoAction.HashPatch(ref patchApplyParams.PatchCoord))
+ {
+ gizmo.CurrentEditUndoAction.AddPatch(ref patchApplyParams.PatchCoord);
+ }
+
+ ApplyBrushToPatch(ref patchApplyParams);
+
+ // Auto NavMesh rebuild
+ var brushBounds = gizmo.CursorBrushBounds;
+ gizmo.CurrentEditUndoAction.AddDirtyBounds(ref brushBounds);
+ }
+ }
+
///
/// The mode apply parameters.
///
@@ -231,6 +276,6 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
/// Applies the modification to the terrain.
///
/// The parameters to use.
- public abstract void Apply(ref ApplyParams p);
+ public abstract void ApplyBrushToPatch(ref ApplyParams p);
}
}
diff --git a/Source/Editor/Tools/Terrain/Sculpt/NoiseMode.cs b/Source/Editor/Tools/Terrain/Sculpt/NoiseMode.cs
index 32d02bb3f..7fcb09288 100644
--- a/Source/Editor/Tools/Terrain/Sculpt/NoiseMode.cs
+++ b/Source/Editor/Tools/Terrain/Sculpt/NoiseMode.cs
@@ -29,7 +29,7 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
public override bool SupportsNegativeApply => true;
///
- public override unsafe void Apply(ref ApplyParams p)
+ public override unsafe void ApplyBrushToPatch(ref ApplyParams p)
{
// Prepare
var brushPosition = p.Gizmo.CursorPosition;
diff --git a/Source/Editor/Tools/Terrain/Sculpt/SculptMode.cs b/Source/Editor/Tools/Terrain/Sculpt/SculptMode.cs
index c3409f30c..381b39ebd 100644
--- a/Source/Editor/Tools/Terrain/Sculpt/SculptMode.cs
+++ b/Source/Editor/Tools/Terrain/Sculpt/SculptMode.cs
@@ -15,7 +15,7 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
public override bool SupportsNegativeApply => true;
///
- public override unsafe void Apply(ref ApplyParams p)
+ public override unsafe void ApplyBrushToPatch(ref ApplyParams p)
{
var strength = p.Strength * 1000.0f;
var brushPosition = p.Gizmo.CursorPosition;
diff --git a/Source/Editor/Tools/Terrain/Sculpt/SmoothMode.cs b/Source/Editor/Tools/Terrain/Sculpt/SmoothMode.cs
index d23eae8dd..49ccfe0de 100644
--- a/Source/Editor/Tools/Terrain/Sculpt/SmoothMode.cs
+++ b/Source/Editor/Tools/Terrain/Sculpt/SmoothMode.cs
@@ -1,7 +1,9 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
-using System;
+using System.Collections.Generic;
using FlaxEngine;
+using FlaxEditor.Tools.Terrain.Brushes;
+using System;
namespace FlaxEditor.Tools.Terrain.Sculpt
{
@@ -19,43 +21,135 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
public float FilterRadius = 0.4f;
///
- public override unsafe void Apply(ref ApplyParams p)
+ public override unsafe void ApplyBrush(SculptTerrainGizmoMode gizmo, List affectedPatches)
{
- // Prepare
- var brushPosition = p.Gizmo.CursorPosition;
- var radius = Mathf.Max(Mathf.CeilToInt(FilterRadius * 0.01f * p.Brush.Size), 2);
- var max = p.HeightmapSize - 1;
- var strength = Mathf.Saturate(p.Strength);
-
- // Apply brush modification
Profiler.BeginEvent("Apply Brush");
- for (int z = 0; z < p.ModifiedSize.Y; z++)
+
+ // TODO: don't need these on each patch; just need them once
+ var heightmapSize = affectedPatches[0].HeightmapSize;
+ var radius = Mathf.Max(Mathf.CeilToInt(FilterRadius * 0.01f * affectedPatches[0].Brush.Size), 2);
+
+
+ /////
+ /// Calculate bounding coordinates of the total affected area
+ ///
+
+
+ Int2 modifieedAreaMinCoord = Int2.Maximum;
+ Int2 modifiedAreaMaxCoord = Int2.Minimum;
+
+ for (int i = 0; i < affectedPatches.Count; i++)
{
- var zz = z + p.ModifiedOffset.Y;
- for (int x = 0; x < p.ModifiedSize.X; x++)
+ var patch = affectedPatches[i];
+
+ var tl = (patch.PatchCoord * (heightmapSize - 1)) + patch.ModifiedOffset;
+ var br = tl + patch.ModifiedSize;
+
+ if (tl.X <= modifieedAreaMinCoord.X && tl.Y <= modifieedAreaMinCoord.Y)
{
- var xx = x + p.ModifiedOffset.X;
- var sourceHeight = p.SourceHeightMap[zz * p.HeightmapSize + xx];
+ modifieedAreaMinCoord = tl;
+ }
- var samplePositionLocal = p.PatchPositionLocal + new Vector3(xx * FlaxEngine.Terrain.UnitsPerVertex, sourceHeight, zz * FlaxEngine.Terrain.UnitsPerVertex);
- Vector3.Transform(ref samplePositionLocal, ref p.TerrainWorld, out Vector3 samplePositionWorld);
+ if (br.X >= modifiedAreaMaxCoord.X && br.Y >= modifiedAreaMaxCoord.Y)
+ {
+ modifiedAreaMaxCoord = br;
+ }
+ }
- var paintAmount = p.Brush.Sample(ref brushPosition, ref samplePositionWorld) * strength;
- if (paintAmount > 0)
+ var totalModifiedSize = modifiedAreaMaxCoord - modifieedAreaMinCoord;
+
+
+ /////
+ /// Build map of heights in affected area
+ ///
+
+
+ var modifiedHeights = new float[totalModifiedSize.X * totalModifiedSize.Y];
+
+ for (int i = 0; i < affectedPatches.Count; i++)
+ {
+ var patch = affectedPatches[i];
+
+ for (int z = 0; z < patch.ModifiedSize.Y; z++)
+ {
+ for (int x = 0; x < patch.ModifiedSize.X; x++)
{
+ // read height from current patch
+ var localCoordX = (x + patch.ModifiedOffset.X);
+ var localCoordY = (z + patch.ModifiedOffset.Y);
+ var coordHeight = patch.SourceHeightMap[(localCoordY * heightmapSize) + localCoordX];
+
+ // calculate the absolute coordinate of the terrain point
+ var absoluteCurrentPointCoord = patch.PatchCoord * (heightmapSize - 1) + patch.ModifiedOffset + new Int2(x, z);
+ var currentPointCoordRelativeToModifiedArea = absoluteCurrentPointCoord - modifieedAreaMinCoord;
+
+ // store height
+ var index = (currentPointCoordRelativeToModifiedArea.Y * totalModifiedSize.X) + currentPointCoordRelativeToModifiedArea.X;
+ modifiedHeights[index] = coordHeight;
+ }
+ }
+ }
+
+
+ /////
+ /// Iterate through modified points and smooth now that we have height information for all necessary points
+ ///
+
+
+ for (int i = 0; i < affectedPatches.Count; i++)
+ {
+ var patch = affectedPatches[i];
+
+ var brushPosition = patch.Gizmo.CursorPosition;
+ var strength = Mathf.Saturate(patch.Strength);
+
+ for (int z = 0; z < patch.ModifiedSize.Y; z++)
+ {
+ for (int x = 0; x < patch.ModifiedSize.X; x++)
+ {
+ // read height from current patch
+ var localCoordX = (x + patch.ModifiedOffset.X);
+ var localCoordY = (z + patch.ModifiedOffset.Y);
+ var coordHeight = patch.SourceHeightMap[(localCoordY * heightmapSize) + localCoordX];
+
+ // calculate the absolute coordinate of the terrain point
+ var absoluteCurrentPointCoord = patch.PatchCoord * (heightmapSize - 1) + patch.ModifiedOffset + new Int2(x, z);
+ var currentPointCoordRelativeToModifiedArea = absoluteCurrentPointCoord - modifieedAreaMinCoord;
+
+ // calculate brush influence at the current position
+ var samplePositionLocal = patch.PatchPositionLocal + new Vector3(localCoordX * FlaxEngine.Terrain.UnitsPerVertex, coordHeight, localCoordY * FlaxEngine.Terrain.UnitsPerVertex);
+ Vector3.Transform(ref samplePositionLocal, ref patch.TerrainWorld, out Vector3 samplePositionWorld);
+ var paintAmount = patch.Brush.Sample(ref brushPosition, ref samplePositionWorld) * strength;
+
+ if (paintAmount == 0)
+ {
+ patch.TempBuffer[z * patch.ModifiedSize.X + x] = coordHeight;
+ continue;
+ }
+
+ // Record patch data before editing it
+ if (!gizmo.CurrentEditUndoAction.HashPatch(ref patch.PatchCoord))
+ {
+ gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord);
+ }
+
// Sum the nearby values
float smoothValue = 0;
int smoothValueSamples = 0;
- int minX = Math.Max(x - radius + p.ModifiedOffset.X, 0);
- int minZ = Math.Max(z - radius + p.ModifiedOffset.Y, 0);
- int maxX = Math.Min(x + radius + p.ModifiedOffset.X, max);
- int maxZ = Math.Min(z + radius + p.ModifiedOffset.Y, max);
+
+ var minX = Math.Max(0, currentPointCoordRelativeToModifiedArea.X - radius);
+ var maxX = Math.Min(totalModifiedSize.X - 1, currentPointCoordRelativeToModifiedArea.X + radius);
+ var minZ = Math.Max(0, currentPointCoordRelativeToModifiedArea.Y - radius);
+ var maxZ = Math.Min(totalModifiedSize.Y - 1, currentPointCoordRelativeToModifiedArea.Y + radius);
+
for (int dz = minZ; dz <= maxZ; dz++)
{
for (int dx = minX; dx <= maxX; dx++)
{
- var height = p.SourceHeightMap[dz * p.HeightmapSize + dx];
+ var coordIndex = (dz * totalModifiedSize.X) + dx;
+ var height = modifiedHeights[coordIndex];
+
smoothValue += height;
smoothValueSamples++;
}
@@ -65,18 +159,26 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
smoothValue /= smoothValueSamples;
// Blend between the height and smooth value
- p.TempBuffer[z * p.ModifiedSize.X + x] = Mathf.Lerp(sourceHeight, smoothValue, paintAmount);
- }
- else
- {
- p.TempBuffer[z * p.ModifiedSize.X + x] = sourceHeight;
+ var newHeight = Mathf.Lerp(coordHeight, smoothValue, paintAmount);
+ patch.TempBuffer[z * patch.ModifiedSize.X + x] = newHeight;
}
}
- }
- Profiler.EndEvent();
- // Update terrain patch
- TerrainTools.ModifyHeightMap(p.Terrain, ref p.PatchCoord, p.TempBuffer, ref p.ModifiedOffset, ref p.ModifiedSize);
+ // Update terrain patch
+ TerrainTools.ModifyHeightMap(patch.Terrain, ref patch.PatchCoord, patch.TempBuffer, ref patch.ModifiedOffset, ref patch.ModifiedSize);
+ }
+
+ // Auto NavMesh rebuild
+ var brushBounds = gizmo.CursorBrushBounds;
+ gizmo.CurrentEditUndoAction.AddDirtyBounds(ref brushBounds);
+
+ Profiler.EndEvent();
+ }
+
+ ///
+ public override unsafe void ApplyBrushToPatch(ref ApplyParams p)
+ {
+ // noop; unused
}
}
}