Merge branch 'feature/817-terrain-smoothing' of https://github.com/envision3d/FlaxEngine into envision3d-feature/817-terrain-smoothing
This commit is contained in:
@@ -18,7 +18,7 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
|
||||
public float TargetHeight = 0.0f;
|
||||
|
||||
/// <inheritdoc />
|
||||
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)
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Applies the modification to the terrain.
|
||||
/// Gets all patches that will be affected by the brush
|
||||
/// </summary>
|
||||
/// <param name="brush">The brush.</param>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <param name="gizmo">The gizmo.</param>
|
||||
/// <param name="terrain">The terrain.</param>
|
||||
public unsafe void Apply(Brush brush, ref Options options, SculptTerrainGizmoMode gizmo, FlaxEngine.Terrain terrain)
|
||||
/// <param name="brush"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="gizmo"></param>
|
||||
/// <param name="terrain"></param>
|
||||
public unsafe virtual List<ApplyParams> GetAffectedPatches(Brush brush, ref Options options, SculptTerrainGizmoMode gizmo, FlaxEngine.Terrain terrain)
|
||||
{
|
||||
List<ApplyParams> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the modification to the terrain.
|
||||
/// </summary>
|
||||
/// <param name="brush">The brush.</param>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <param name="gizmo">The gizmo.</param>
|
||||
/// <param name="terrain">The terrain.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the brush to all affected patches
|
||||
/// </summary>
|
||||
/// <param name="gizmo"></param>
|
||||
/// <param name="affectedPatches"></param>
|
||||
public unsafe virtual void ApplyBrush(SculptTerrainGizmoMode gizmo, List<ApplyParams> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The mode apply parameters.
|
||||
/// </summary>
|
||||
@@ -231,6 +276,6 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
|
||||
/// Applies the modification to the terrain.
|
||||
/// </summary>
|
||||
/// <param name="p">The parameters to use.</param>
|
||||
public abstract void Apply(ref ApplyParams p);
|
||||
public abstract void ApplyBrushToPatch(ref ApplyParams p);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
|
||||
public override bool SupportsNegativeApply => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override unsafe void Apply(ref ApplyParams p)
|
||||
public override unsafe void ApplyBrushToPatch(ref ApplyParams p)
|
||||
{
|
||||
// Prepare
|
||||
var brushPosition = p.Gizmo.CursorPosition;
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
|
||||
public override bool SupportsNegativeApply => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override unsafe void Apply(ref ApplyParams p)
|
||||
public override unsafe void ApplyBrush(SculptTerrainGizmoMode gizmo, List<ApplyParams> 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();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override unsafe void ApplyBrushToPatch(ref ApplyParams p)
|
||||
{
|
||||
// noop; unused
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user