|
|
@ -1,5 +1,10 @@ |
|
|
|
|
|
|
|
using System.IO; |
|
|
|
|
|
|
|
|
|
|
|
public partial class TerrainEditingControls |
|
|
|
public partial class TerrainEditingControls |
|
|
|
{ |
|
|
|
{ |
|
|
|
|
|
|
|
// Set by the terrain editing plugin. |
|
|
|
|
|
|
|
public EditorUndoRedoManager EditorUndoRedo { get; set; } |
|
|
|
|
|
|
|
|
|
|
|
Terrain _currentTerrain = null; |
|
|
|
Terrain _currentTerrain = null; |
|
|
|
|
|
|
|
|
|
|
|
Material _editToolMaterial; |
|
|
|
Material _editToolMaterial; |
|
|
@ -76,7 +81,6 @@ public partial class TerrainEditingControls |
|
|
|
|
|
|
|
|
|
|
|
// TODO: Handle different tool modes, such as flatten and painting. |
|
|
|
// TODO: Handle different tool modes, such as flatten and painting. |
|
|
|
// 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: Support undo and redo. |
|
|
|
|
|
|
|
// 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. |
|
|
|
|
|
|
|
|
|
|
@ -84,13 +88,16 @@ public partial class TerrainEditingControls |
|
|
|
if (mouse is InputEventMouseButton { ButtonIndex: MouseButton.Left, Pressed: true }) { |
|
|
|
if (mouse is InputEventMouseButton { ButtonIndex: MouseButton.Left, Pressed: true }) { |
|
|
|
GetViewport().SetInputAsHandled(); |
|
|
|
GetViewport().SetInputAsHandled(); |
|
|
|
|
|
|
|
|
|
|
|
const float AdjustHeight = 0.5f; |
|
|
|
var cornersToChange = new HashSet<(TilePos Position, Corner Corner)>(); |
|
|
|
var amount = UpDownToggle.ButtonPressed ? AdjustHeight : -AdjustHeight; |
|
|
|
|
|
|
|
|
|
|
|
// Raise selected tiles themselves. |
|
|
|
|
|
|
|
foreach (var pos in tiles) |
|
|
|
|
|
|
|
foreach (var corner2 in Enum.GetValues<Corner>()) |
|
|
|
|
|
|
|
cornersToChange.Add((pos, corner2)); |
|
|
|
|
|
|
|
|
|
|
|
// 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 (ConnectedToggle.ButtonPressed) { |
|
|
|
if (ConnectedToggle.ButtonPressed) { |
|
|
|
var corners = new HashSet<(TilePos Position, Corner Corner)>(); |
|
|
|
|
|
|
|
foreach (var pos in tiles) { |
|
|
|
foreach (var pos in tiles) { |
|
|
|
var tile2 = terrain.GetTile(pos); |
|
|
|
var tile2 = terrain.GetTile(pos); |
|
|
|
foreach (var corner2 in Enum.GetValues<Corner>()) { |
|
|
|
foreach (var corner2 in Enum.GetValues<Corner>()) { |
|
|
@ -98,30 +105,47 @@ public partial class TerrainEditingControls |
|
|
|
foreach (var (neighborPos, neighborCorner) in GetNeighbors(pos, corner2)) { |
|
|
|
foreach (var (neighborPos, neighborCorner) in GetNeighbors(pos, corner2)) { |
|
|
|
if (tiles.Contains(neighborPos)) continue; |
|
|
|
if (tiles.Contains(neighborPos)) continue; |
|
|
|
var neighborHeight = terrain.GetTile(neighborPos).Height[neighborCorner]; |
|
|
|
var neighborHeight = terrain.GetTile(neighborPos).Height[neighborCorner]; |
|
|
|
if (neighborHeight == height) corners.Add((neighborPos, neighborCorner)); |
|
|
|
if (neighborHeight == height) cornersToChange.Add((neighborPos, neighborCorner)); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Raise connected corners. |
|
|
|
|
|
|
|
foreach (var group in corners.GroupBy(e => e.Position, e => e.Corner)) { |
|
|
|
|
|
|
|
var pos = group.Key; |
|
|
|
|
|
|
|
var tile2 = terrain.GetTile(pos); |
|
|
|
|
|
|
|
foreach (var corner2 in group) |
|
|
|
|
|
|
|
tile2.Height[corner2] += amount; |
|
|
|
|
|
|
|
terrain.SetTile(pos, tile2); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Raise selected tiles themselves. |
|
|
|
const float AdjustHeight = 0.5f; |
|
|
|
foreach (var pos in tiles) { |
|
|
|
var flatten = ToolMode == ToolMode.Flatten; |
|
|
|
|
|
|
|
var amount = flatten ? terrain.GetTile(tile).Height[corner] |
|
|
|
|
|
|
|
: UpDownToggle.ButtonPressed ? +AdjustHeight |
|
|
|
|
|
|
|
: -AdjustHeight; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var tilesPrevious = new List<(TilePos, Corners<float>)>(); |
|
|
|
|
|
|
|
var tilesChanged = new List<(TilePos, Corners<float>)>(); |
|
|
|
|
|
|
|
foreach (var group in cornersToChange.GroupBy(c => c.Position, c => c.Corner)) { |
|
|
|
|
|
|
|
var pos = group.Key; |
|
|
|
var tile2 = terrain.GetTile(pos); |
|
|
|
var tile2 = terrain.GetTile(pos); |
|
|
|
tile2.Height.Adjust(amount); |
|
|
|
tilesPrevious.Add((pos, tile2.Height)); |
|
|
|
terrain.SetTile(pos, tile2); |
|
|
|
|
|
|
|
|
|
|
|
var newHeight = tile2.Height; |
|
|
|
|
|
|
|
foreach (var corner2 in group) { |
|
|
|
|
|
|
|
if (flatten) newHeight[corner2] = amount; |
|
|
|
|
|
|
|
else newHeight[corner2] += amount; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
tilesChanged.Add((pos, newHeight)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
terrain.UpdateMeshAndShape(); |
|
|
|
if (EditorUndoRedo is EditorUndoRedoManager undo) { |
|
|
|
terrain.NotifyPropertyListChanged(); |
|
|
|
var name = "Modify terrain"; // TODO: Change name depending on tool mode. |
|
|
|
|
|
|
|
undo.CreateAction(name, customContext: terrain, backwardUndoOps: false); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
undo.AddDoMethod(this, nameof(TerrainModifyHeight), terrain, Pack(tilesChanged)); |
|
|
|
|
|
|
|
undo.AddDoMethod(terrain, GodotObject.MethodName.NotifyPropertyListChanged); |
|
|
|
|
|
|
|
undo.AddDoMethod(terrain, nameof(Terrain.UpdateMeshAndShape)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
undo.AddUndoMethod(this, nameof(TerrainModifyHeight), terrain, Pack(tilesPrevious)); |
|
|
|
|
|
|
|
undo.AddUndoMethod(terrain, GodotObject.MethodName.NotifyPropertyListChanged); |
|
|
|
|
|
|
|
undo.AddUndoMethod(terrain, nameof(Terrain.UpdateMeshAndShape)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
undo.CommitAction(true); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
UpdateEditToolMesh(terrain, tiles); |
|
|
|
UpdateEditToolMesh(terrain, tiles); |
|
|
@ -131,6 +155,43 @@ public partial class TerrainEditingControls |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void TerrainModifyHeight(Terrain terrain, byte[] data) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
foreach (var (pos, corners) in Unpack(data)) { |
|
|
|
|
|
|
|
var tile = terrain.GetTile(pos); |
|
|
|
|
|
|
|
tile.Height = corners; |
|
|
|
|
|
|
|
terrain.SetTile(pos, tile); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static byte[] Pack(IEnumerable<(TilePos Position, Corners<float> Corners)> data) { |
|
|
|
|
|
|
|
using var stream = new MemoryStream(); |
|
|
|
|
|
|
|
using var writer = new BinaryWriter(stream); |
|
|
|
|
|
|
|
foreach (var (pos, corners) in data) { |
|
|
|
|
|
|
|
writer.Write(pos.X); |
|
|
|
|
|
|
|
writer.Write(pos.Y); |
|
|
|
|
|
|
|
writer.Write(corners.TopLeft); |
|
|
|
|
|
|
|
writer.Write(corners.TopRight); |
|
|
|
|
|
|
|
writer.Write(corners.BottomRight); |
|
|
|
|
|
|
|
writer.Write(corners.BottomLeft); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return stream.ToArray(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static IEnumerable<(TilePos Position, Corners<float> Corners)> Unpack(byte[] data) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
using var stream = new MemoryStream(data); |
|
|
|
|
|
|
|
using var reader = new BinaryReader(stream); |
|
|
|
|
|
|
|
while (stream.Position < stream.Length) { |
|
|
|
|
|
|
|
var x = reader.ReadInt32(); |
|
|
|
|
|
|
|
var y = reader.ReadInt32(); |
|
|
|
|
|
|
|
var corners = new Corners<float>( |
|
|
|
|
|
|
|
reader.ReadSingle(), reader.ReadSingle(), |
|
|
|
|
|
|
|
reader.ReadSingle(), reader.ReadSingle()); |
|
|
|
|
|
|
|
yield return (new(x, y), corners); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void UpdateEditToolMesh(Terrain terrain, IEnumerable<TilePos> tiles) |
|
|
|
void UpdateEditToolMesh(Terrain terrain, IEnumerable<TilePos> tiles) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (terrain != _currentTerrain) ClearEditToolMesh(); |
|
|
|
if (terrain != _currentTerrain) ClearEditToolMesh(); |
|
|
|