Files
FlaxEngine/Source/Editor/Tools/Terrain/Paint/Mode.cs

260 lines
11 KiB
C#

// Copyright (c) Wojciech Figat. All rights reserved.
using System;
using FlaxEditor.Tools.Terrain.Brushes;
using FlaxEngine;
namespace FlaxEditor.Tools.Terrain.Paint
{
/// <summary>
/// The base class for terran paint tool modes.
/// </summary>
[HideInEditor]
public abstract class Mode
{
/// <summary>
/// The options container for the terrain editing apply.
/// </summary>
public struct Options
{
/// <summary>
/// If checked, modification apply method should be inverted.
/// </summary>
public bool Invert;
/// <summary>
/// The master strength parameter to apply when editing the terrain.
/// </summary>
public float Strength;
/// <summary>
/// The delta time (in seconds) for the terrain modification apply. Used to scale the strength. Adjusted to handle low-FPS.
/// </summary>
public float DeltaTime;
}
/// <summary>
/// The tool strength (normalized to range 0-1). Defines the intensity of the paint operation to make it stronger or more subtle.
/// </summary>
[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;
/// <summary>
/// Gets a value indicating whether this mode supports negative apply for terrain modification.
/// </summary>
public virtual bool SupportsNegativeApply => false;
/// <summary>
/// Gets the index of the active splatmap texture to modify by the tool. It must be equal or higher than zero bu less than <see cref="FlaxEngine.Terrain.MaxSplatmapsCount"/>.
/// </summary>
public abstract int ActiveSplatmapIndex { get; }
/// <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, 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 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, 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
{
Terrain = terrain,
Brush = brush,
Gizmo = gizmo,
Options = options,
Strength = strength,
SplatmapIndex = splatmapIndex,
SplatmapIndexOther = splatmapIndexOther,
HeightmapSize = heightmapSize,
TempBuffer = tempBuffer,
TempBufferOther = tempBufferOther,
};
// Get brush bounds in terrain local space
var brushBounds = gizmo.CursorBrushBounds;
terrain.GetLocalToWorldMatrix(out p.TerrainWorld);
terrain.GetWorldToLocalMatrix(out var terrainInvWorld);
BoundingBox.Transform(brushBounds, 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, 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, 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
p.ModifiedOffset = modifiedOffset;
p.ModifiedSize = modifiedSize;
p.PatchCoord = patch.PatchCoord;
p.PatchPositionLocal = patchPositionLocal;
p.SourceData = sourceData;
p.SourceDataOther = sourceDataOther;
Apply(ref p);
}
}
/// <summary>
/// The mode apply parameters.
/// </summary>
public unsafe struct ApplyParams
{
/// <summary>
/// The brush.
/// </summary>
public Brush Brush;
/// <summary>
/// The options.
/// </summary>
public Options Options;
/// <summary>
/// The gizmo.
/// </summary>
public PaintTerrainGizmoMode Gizmo;
/// <summary>
/// The terrain.
/// </summary>
public FlaxEngine.Terrain Terrain;
/// <summary>
/// The patch coordinates.
/// </summary>
public Int2 PatchCoord;
/// <summary>
/// The modified offset.
/// </summary>
public Int2 ModifiedOffset;
/// <summary>
/// The modified size.
/// </summary>
public Int2 ModifiedSize;
/// <summary>
/// The final calculated strength of the effect to apply (can be negative for inverted terrain modification if <see cref="SupportsNegativeApply"/> is set).
/// </summary>
public float Strength;
/// <summary>
/// The splatmap texture index.
/// </summary>
public int SplatmapIndex;
/// <summary>
/// The splatmap texture index. If <see cref="SplatmapIndex"/> is 0, this will be 1. If <see cref="SplatmapIndex"/> is 1, this will be 0.
/// </summary>
public int SplatmapIndexOther;
/// <summary>
/// The temporary data buffer (for modified data).
/// </summary>
public Color32* TempBuffer;
/// <summary>
/// The 'other' temporary data buffer (for modified data). If <see cref="TempBuffer"/> refersto the splatmap with index 0, this one will refer to the one with index 1.
/// </summary>
public Color32* TempBufferOther;
/// <summary>
/// The source data buffer.
/// </summary>
public Color32* SourceData;
/// <summary>
/// The 'other' source data buffer. If <see cref="SourceData"/> refers
/// to the splatmap with index 0, this one will refer to the one with index 1.
/// </summary>
public Color32* SourceDataOther;
/// <summary>
/// The heightmap size (edge).
/// </summary>
public int HeightmapSize;
/// <summary>
/// The patch position in terrain local-space.
/// </summary>
public Vector3 PatchPositionLocal;
/// <summary>
/// The terrain local-to-world matrix.
/// </summary>
public Matrix TerrainWorld;
}
/// <summary>
/// Applies the modification to the terrain.
/// </summary>
/// <param name="p">The parameters to use.</param>
public abstract void Apply(ref ApplyParams p);
}
}