Redo some of the terrain editing logic

- Introduce a tileToChange variable
  Holds all of the tiles and which corners to modify
  Is used to calculate tilesPrevious & tilesChanged
- Support editing a single corner
- Edit tool mesh shows edited corners
main
copygirl 3 weeks ago
parent c1ec14c41d
commit 5c1b66d8a6
  1. 121
      terrain/Terrain+Editing.cs

@ -1,4 +1,5 @@
using System.IO; using System.IO;
using System.Runtime.InteropServices;
public partial class Terrain public partial class Terrain
{ {
@ -8,11 +9,15 @@ public partial class Terrain
// Set by the terrain editing plugin. // Set by the terrain editing plugin.
public EditorUndoRedoManager EditorUndoRedo { get; set; } public EditorUndoRedoManager EditorUndoRedo { get; set; }
// Dummy value to satisfy the overly careful compiler.
static bool _dummy = false;
Material _editToolMaterial; Material _editToolMaterial;
public override void _EnterTree() public override void _EnterTree()
{ {
_editToolMaterial = new StandardMaterial3D { _editToolMaterial = new StandardMaterial3D {
AlbedoColor = Colors.Blue, VertexColorUseAsAlbedo = true,
BlendMode = BaseMaterial3D.BlendModeEnum.Mix,
ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded, ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded,
DepthDrawMode = BaseMaterial3D.DepthDrawModeEnum.Disabled, DepthDrawMode = BaseMaterial3D.DepthDrawModeEnum.Disabled,
NoDepthTest = true, NoDepthTest = true,
@ -64,46 +69,60 @@ public partial class Terrain
center.DistanceSquaredTo(tile.ToCenter()) < distanceSqr); center.DistanceSquaredTo(tile.ToCenter()) < distanceSqr);
} }
var tiles = (toolShape switch {
ToolShape.Corner => [ hover.Position ],
ToolShape.Circle => GetTilesInRadius(),
ToolShape.Square => GetTilesInSquare(),
_ => throw new InvalidOperationException(),
}).ToHashSet();
// TODO: Handle different tool modes, such as painting. // TODO: Handle different tool modes, such as painting.
// TODO: Finally allow editing single corners.
// TODO: Allow click-dragging which doesn't affect already changed tiles / corners. // TODO: Allow click-dragging which doesn't affect already changed tiles / corners.
// TODO: Make mesh generation generate vertical walls between disconnected corners. // TODO: Make mesh generation generate vertical walls between disconnected corners.
// TODO: Use ArrayMesh instead of ImmediateMesh. // TODO: Use ArrayMesh instead of ImmediateMesh.
// TODO: Dynamically expand terrain instead of having it be a set size. // TODO: Dynamically expand terrain instead of having it be a set size.
// Raise / lower the terrain when left mouse button is pressed. // Holds onto all the tiles and which of their corners corners will be affected by this edit operation.
if (ev is InputEventMouseButton { ButtonIndex: MouseButton.Left, Pressed: true }) { var tilesToChange = new Dictionary<TilePos, Corners<bool>>();
prevent_default = true; ref Corners<bool> GetTileToChange(TilePos position)
// Don't look at this black magic. The Dictionary type should have this by default I swear!
=> ref CollectionsMarshal.GetValueRefOrAddDefault(tilesToChange, position, out _dummy);
var cornersToChange = new HashSet<(TilePos Position, Corner Corner)>(); if (toolShape == ToolShape.Corner) {
// Modify selected corner itself.
GetTileToChange(hover.Position)[hover.Corner] = true;
// Raise selected tiles themselves. if (isConnected) {
var height = GetTile(hover.Position).Height[hover.Corner];
foreach (var neighbor in GetNeighbors(hover.Position, hover.Corner)) {
var neighborHeight = GetTile(neighbor.Position).Height[neighbor.Corner];
if (neighborHeight != height) continue;
GetTileToChange(neighbor.Position)[neighbor.Corner] = true;
}
}
} else {
var tiles = (toolShape switch {
ToolShape.Circle => GetTilesInRadius(),
ToolShape.Square => GetTilesInSquare(),
_ => throw new InvalidOperationException(),
}).ToHashSet();
// Modify selected tiles themselves.
foreach (var pos in tiles) foreach (var pos in tiles)
foreach (var corner2 in Enum.GetValues<Corner>()) GetTileToChange(pos) = new(true);
cornersToChange.Add((pos, corner2));
if (isConnected) { // If the 'connected_toggle' button is active, move "connected" corners.
// If the 'connected_toggle' button is active, move "connected" corners. // Connected corners are the ones that are at the same height as ones already being moved.
// Connected corners are the ones that are at the same height as ones already being moved. if (isConnected) foreach (var pos in tiles) {
foreach (var pos in tiles) { var tile = GetTile(pos);
var tile = GetTile(pos); foreach (var corner in Enum.GetValues<Corner>()) {
foreach (var corner in Enum.GetValues<Corner>()) { var height = tile.Height[corner];
var height = tile.Height[corner]; foreach (var neighbor in GetNeighbors(pos, corner)) {
foreach (var (neighborPos, neighborCorner) in GetNeighbors(pos, corner)) { if (tiles.Contains(neighbor.Position)) continue;
if (tiles.Contains(neighborPos)) continue; var neighborHeight = GetTile(neighbor.Position).Height[neighbor.Corner];
var neighborHeight = GetTile(neighborPos).Height[neighborCorner]; if (neighborHeight != height) continue;
if (neighborHeight == height) cornersToChange.Add((neighborPos, neighborCorner)); GetTileToChange(neighbor.Position)[neighbor.Corner] = true;
}
} }
} }
} }
}
// Raise / lower the terrain when left mouse button is pressed.
if (ev is InputEventMouseButton { ButtonIndex: MouseButton.Left, Pressed: true }) {
prevent_default = true;
const float AdjustHeight = 0.5f; const float AdjustHeight = 0.5f;
var amount = isFlatten ? GetTile(hover.Position).Height[hover.Corner] var amount = isFlatten ? GetTile(hover.Position).Height[hover.Corner]
@ -111,16 +130,15 @@ public partial class Terrain
var tilesPrevious = new List<(TilePos, Corners<float>)>(); var tilesPrevious = new List<(TilePos, Corners<float>)>();
var tilesChanged = new List<(TilePos, Corners<float>)>(); var tilesChanged = new List<(TilePos, Corners<float>)>();
foreach (var group in cornersToChange.GroupBy(c => c.Position, c => c.Corner)) { foreach (var (pos, corners) in tilesToChange) {
var pos = group.Key;
var tile = GetTile(pos); var tile = GetTile(pos);
tilesPrevious.Add((pos, tile.Height)); tilesPrevious.Add((pos, tile.Height));
var newHeight = tile.Height; var newHeight = tile.Height;
foreach (var corner in group) { if (corners.TopLeft ) newHeight.TopLeft = isFlatten ? amount : newHeight.TopLeft + amount;
if (isFlatten) newHeight[corner] = amount; if (corners.TopRight ) newHeight.TopRight = isFlatten ? amount : newHeight.TopRight + amount;
else newHeight[corner] += amount; if (corners.BottomRight) newHeight.BottomRight = isFlatten ? amount : newHeight.BottomRight + amount;
} if (corners.BottomLeft ) newHeight.BottomLeft = isFlatten ? amount : newHeight.BottomLeft + amount;
tilesChanged.Add((pos, newHeight)); tilesChanged.Add((pos, newHeight));
} }
@ -140,7 +158,7 @@ public partial class Terrain
} }
} }
UpdateEditToolMesh(tiles); UpdateEditToolMesh(tilesToChange);
return prevent_default; return prevent_default;
} }
@ -157,29 +175,28 @@ public partial class Terrain
} }
void UpdateEditToolMesh(IEnumerable<TilePos> tiles) void UpdateEditToolMesh(Dictionary<TilePos, Corners<bool>> tiles)
{ {
var mesh = GetOrCreateMesh("EditToolMesh"); var mesh = GetOrCreateMesh("EditToolMesh");
mesh.ClearSurfaces(); mesh.ClearSurfaces();
mesh.SurfaceBegin(Mesh.PrimitiveType.Lines); mesh.SurfaceBegin(Mesh.PrimitiveType.Lines);
void AddLine(Vector3 start, Vector3 end) { void AddLine((Vector3 Position, bool Visible) start,
mesh.SurfaceAddVertex(start); (Vector3 Position, bool Visible) end) {
mesh.SurfaceAddVertex(end); mesh.SurfaceSetColor(start.Visible ? Colors.Blue : Colors.Transparent);
mesh.SurfaceAddVertex(start.Position);
mesh.SurfaceSetColor(end.Visible ? Colors.Blue : Colors.Transparent);
mesh.SurfaceAddVertex(end.Position);
} }
void AddQuad(Vector3 topLeft , Vector3 topRight , foreach (var (tile, visible) in tiles) {
Vector3 bottomRight, Vector3 bottomLeft) { var positions = GetTileCornerPositions(tile);
AddLine(topLeft , topRight ); foreach (var side in Enum.GetValues<Side>()) {
AddLine(topRight , bottomRight); var (corner1, corner2) = side.GetCorners();
AddLine(bottomRight, bottomLeft ); if (!visible[corner1] && !visible[corner2]) continue;
AddLine(bottomLeft , topLeft ); AddLine((positions[corner1], visible[corner1]),
} (positions[corner2], visible[corner2]));
}
foreach (var tile in tiles) {
var (topLeft, topRight, bottomRight, bottomLeft)
= GetTileCornerPositions(tile);
AddQuad(topLeft, topRight, bottomRight, bottomLeft);
} }
mesh.SurfaceEnd(); mesh.SurfaceEnd();
@ -211,7 +228,7 @@ public partial class Terrain
[Corner.BottomRight] = [(+1, +1, Corner.TopLeft ), (+1, 0, Corner.BottomLeft ), (0, +1, Corner.TopRight )], [Corner.BottomRight] = [(+1, +1, Corner.TopLeft ), (+1, 0, Corner.BottomLeft ), (0, +1, Corner.TopRight )],
[Corner.BottomLeft ] = [(-1, +1, Corner.TopRight ), (-1, 0, Corner.BottomRight), (0, +1, Corner.TopLeft )], [Corner.BottomLeft ] = [(-1, +1, Corner.TopRight ), (-1, 0, Corner.BottomRight), (0, +1, Corner.TopLeft )],
}; };
static IEnumerable<(TilePos, Corner)> GetNeighbors(TilePos pos, Corner corner) static IEnumerable<(TilePos Position, Corner Corner)> GetNeighbors(TilePos pos, Corner corner)
=> _offsetLookup[corner].Select(e => (new TilePos(pos.X + e.X, pos.Y + e.Y), e.Opposite)); => _offsetLookup[corner].Select(e => (new TilePos(pos.X + e.X, pos.Y + e.Y), e.Opposite));
static byte[] Pack(IEnumerable<(TilePos Position, Corners<float> Corners)> data) static byte[] Pack(IEnumerable<(TilePos Position, Corners<float> Corners)> data)

Loading…
Cancel
Save