// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using FlaxEditor.Tools.Terrain.Brushes;
using FlaxEngine;
namespace FlaxEditor.Tools.Terrain.Paint
{
///
/// The base class for terran paint tool modes.
///
[HideInEditor]
public abstract class Mode
{
///
/// The options container for the terrain editing apply.
///
public struct Options
{
///
/// If checked, modification apply method should be inverted.
///
public bool Invert;
///
/// The master strength parameter to apply when editing the terrain.
///
public float Strength;
///
/// The delta time (in seconds) for the terrain modification apply. Used to scale the strength. Adjusted to handle low-FPS.
///
public float DeltaTime;
}
///
/// The tool strength (normalized to range 0-1). Defines the intensity of the paint operation to make it stronger or more subtle.
///
[EditorOrder(0), Limit(0, 10, 0.01f), Tooltip("The tool strength (normalized to range 0-1). Defines the intensity of the paint operation to make it stronger or more subtle.")]
public float Strength = 1.0f;
///
/// Gets a value indicating whether this mode supports negative apply for terrain modification.
///
public virtual bool SupportsNegativeApply => false;
///
/// Gets the index of the active splatmap texture to modify by the tool. It must be equal or higher than zero bu less than .
///
public abstract int ActiveSplatmapIndex { get; }
///
/// Applies the modification to the terrain.
///
/// The brush.
/// The options.
/// The gizmo.
/// The terrain.
public unsafe void Apply(Brush brush, ref Options options, PaintTerrainGizmoMode gizmo, FlaxEngine.Terrain terrain)
{
// Combine final apply strength
float strength = Strength * options.Strength * options.DeltaTime * 10.0f;
if (strength <= 0.0f)
return;
if (options.Invert && SupportsNegativeApply)
strength *= -1;
// Prepare
var splatmapIndex = ActiveSplatmapIndex;
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 unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex;
ApplyParams p = new ApplyParams
{
Terrain = terrain,
Brush = brush,
Gizmo = gizmo,
Options = options,
Strength = strength,
SplatmapIndex = splatmapIndex,
HeightmapSize = heightmapSize,
TempBuffer = tempBuffer,
};
// Get brush bounds in terrain local space
var brushBounds = gizmo.CursorBrushBounds;
terrain.GetLocalToWorldMatrix(out p.TerrainWorld);
terrain.GetWorldToLocalMatrix(out var terrainInvWorld);
BoundingBox.Transform(ref brushBounds, ref terrainInvWorld, out var brushBoundsLocal);
// TODO: try caching brush weights before apply to reduce complexity and batch brush sampling
// Process all the patches under the cursor
for (int patchIndex = 0; patchIndex < gizmo.PatchesUnderCursor.Count; patchIndex++)
{
var patch = gizmo.PatchesUnderCursor[patchIndex];
var patchPositionLocal = new Vector3(patch.PatchCoord.X * patchSize, 0, patch.PatchCoord.Y * patchSize);
// Transform brush bounds from local terrain space into local patch vertex space
var brushBoundsPatchLocalMin = (brushBoundsLocal.Minimum - patchPositionLocal) * unitsPerVertexInv;
var brushBoundsPatchLocalMax = (brushBoundsLocal.Maximum - patchPositionLocal) * unitsPerVertexInv;
// Calculate patch heightmap area to modify by brush
var brushPatchMin = new Int2((int)Math.Floor(brushBoundsPatchLocalMin.X), (int)Math.Floor(brushBoundsPatchLocalMin.Z));
var brushPatchMax = new Int2((int)Math.Ceiling(brushBoundsPatchLocalMax.X), (int)Math.Ceiling(brushBoundsPatchLocalMax.Z));
var modifiedOffset = brushPatchMin;
var modifiedSize = brushPatchMax - brushPatchMin;
// Clamp to prevent overflows
if (modifiedOffset.X < 0)
{
modifiedSize.X += modifiedOffset.X;
modifiedOffset.X = 0;
}
if (modifiedOffset.Y < 0)
{
modifiedSize.Y += modifiedOffset.Y;
modifiedOffset.Y = 0;
}
modifiedSize.X = Mathf.Min(modifiedSize.X, heightmapSize - modifiedOffset.X);
modifiedSize.Y = Mathf.Min(modifiedSize.Y, heightmapSize - modifiedOffset.Y);
// Skip patch won't be modified at all
if (modifiedSize.X <= 0 || modifiedSize.Y <= 0)
continue;
// Get the patch data (cached internally by the c++ core in editor)
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.");
// Record patch data before editing it
if (!gizmo.CurrentEditUndoAction.HashPatch(ref patch.PatchCoord))
{
gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord, splatmapIndex);
}
// Apply modification
p.ModifiedOffset = modifiedOffset;
p.ModifiedSize = modifiedSize;
p.PatchCoord = patch.PatchCoord;
p.PatchPositionLocal = patchPositionLocal;
p.SourceData = sourceData;
Apply(ref p);
}
}
///
/// The mode apply parameters.
///
public unsafe struct ApplyParams
{
///
/// The brush.
///
public Brush Brush;
///
/// The options.
///
public Options Options;
///
/// The gizmo.
///
public PaintTerrainGizmoMode Gizmo;
///
/// The terrain.
///
public FlaxEngine.Terrain Terrain;
///
/// The patch coordinates.
///
public Int2 PatchCoord;
///
/// The modified offset.
///
public Int2 ModifiedOffset;
///
/// The modified size.
///
public Int2 ModifiedSize;
///
/// The final calculated strength of the effect to apply (can be negative for inverted terrain modification if is set).
///
public float Strength;
///
/// The splatmap texture index.
///
public int SplatmapIndex;
///
/// The temporary data buffer (for modified data).
///
public Color32* TempBuffer;
///
/// The source data buffer.
///
public Color32* SourceData;
///
/// The heightmap size (edge).
///
public int HeightmapSize;
///
/// The patch position in terrain local-space.
///
public Vector3 PatchPositionLocal;
///
/// The terrain local-to-world matrix.
///
public Matrix TerrainWorld;
}
///
/// Applies the modification to the terrain.
///
/// The parameters to use.
public abstract void Apply(ref ApplyParams p);
}
}