Compare commits

..

2 Commits

  1. 1
      addons/terrain-editing/terrain_editing_plugin.gd
  2. 0
      assets/textures/terrain/editing/height_lower.png
  3. 6
      assets/textures/terrain/editing/height_lower.png.import
  4. 0
      assets/textures/terrain/editing/height_raise.png
  5. 6
      assets/textures/terrain/editing/height_raise.png.import
  6. 3
      terrain/Tile.cs
  7. 102
      terrain/editing/TerrainEditingControls+Editing.cs
  8. 14
      terrain/editing/TerrainEditingControls.cs
  9. 6
      terrain/editing/TerrainEditingControls.tscn

@ -20,6 +20,7 @@ func _make_visible(visible: bool) -> void:
if not controls: if not controls:
var controls_scene = load("res://terrain/editing/TerrainEditingControls.tscn") var controls_scene = load("res://terrain/editing/TerrainEditingControls.tscn")
controls = controls_scene.instantiate() controls = controls_scene.instantiate()
controls.EditorUndoRedo = get_undo_redo()
add_control_to_container(CONTAINER, controls) add_control_to_container(CONTAINER, controls)
elif controls and controls.get_parent(): elif controls and controls.get_parent():
remove_control_from_container(CONTAINER, controls) remove_control_from_container(CONTAINER, controls)

Before

Width:  |  Height:  |  Size: 135 B

After

Width:  |  Height:  |  Size: 135 B

@ -3,15 +3,15 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://covyafauwthij" uid="uid://covyafauwthij"
path="res://.godot/imported/height_down.png-47ae6eeb5feb0ddf7bd952befb4351fe.ctex" path="res://.godot/imported/height_lower.png-d00d3c7062c0a355f5c8381ee7e664da.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://assets/textures/terrain/editing/height_down.png" source_file="res://assets/textures/terrain/editing/height_lower.png"
dest_files=["res://.godot/imported/height_down.png-47ae6eeb5feb0ddf7bd952befb4351fe.ctex"] dest_files=["res://.godot/imported/height_lower.png-d00d3c7062c0a355f5c8381ee7e664da.ctex"]
[params] [params]

Before

Width:  |  Height:  |  Size: 149 B

After

Width:  |  Height:  |  Size: 149 B

@ -3,15 +3,15 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://dxbfohim13ti1" uid="uid://dxbfohim13ti1"
path="res://.godot/imported/height_up.png-d84180c0cff17a8285e5e5cb8f54e819.ctex" path="res://.godot/imported/height_raise.png-a5718ceb2e90ce17597f6300a0a9a715.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://assets/textures/terrain/editing/height_up.png" source_file="res://assets/textures/terrain/editing/height_raise.png"
dest_files=["res://.godot/imported/height_up.png-d84180c0cff17a8285e5e5cb8f54e819.ctex"] dest_files=["res://.godot/imported/height_raise.png-a5718ceb2e90ce17597f6300a0a9a715.ctex"]
[params] [params]

@ -90,6 +90,9 @@ public struct Corners<T>(T topLeft, T topRight, T bottomRight, T bottomLeft)
public T BottomRight = bottomRight; public T BottomRight = bottomRight;
public T BottomLeft = bottomLeft; public T BottomLeft = bottomLeft;
public Corners(T value)
: this(value, value, value, value) { }
public T this[Corner corner] { public T this[Corner corner] {
readonly get => corner switch { readonly get => corner switch {
Corner.TopLeft => TopLeft, Corner.TopLeft => TopLeft,

@ -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,48 @@ 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. var isFlatten = ToolMode == ToolMode.Flatten;
foreach (var pos in tiles) { var isRaise = RaiseLowerToggle.ButtonPressed;
const float AdjustHeight = 0.5f;
var amount = isFlatten ? terrain.GetTile(tile).Height[corner]
: isRaise ? 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 (isFlatten) 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 +156,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();

@ -7,8 +7,8 @@ public partial class TerrainEditingControls
public Slider DrawSizeSlider { get; private set; } public Slider DrawSizeSlider { get; private set; }
public Button[] PaintTextureButtons { get; private set; } public Button[] PaintTextureButtons { get; private set; }
public Button UpDownToggle { get; private set; } public Button RaiseLowerToggle { get; private set; }
public Button ConnectedToggle { get; private set; } public Button ConnectedToggle { get; private set; }
public ToolMode ToolMode { get => GetToolMode (); set => SetToolMode (value); } public ToolMode ToolMode { get => GetToolMode (); set => SetToolMode (value); }
public ToolShape ToolShape { get => GetToolShape(); set => SetToolShape(value); } public ToolShape ToolShape { get => GetToolShape(); set => SetToolShape(value); }
@ -44,12 +44,12 @@ public partial class TerrainEditingControls
foreach (var (i, button) in PaintTextureButtons.Select((b, i) => (i, b))) foreach (var (i, button) in PaintTextureButtons.Select((b, i) => (i, b)))
button.Pressed += () => SetTexture(i + 1); button.Pressed += () => SetTexture(i + 1);
var drawSizeLabel = GetNode<Label>("SizeLabel"); var drawSizeLabel = GetNode<Label> ("SizeLabel");
DrawSizeSlider = GetNode<Slider>("SizeSlider"); DrawSizeSlider = GetNode<Slider>("SizeSlider");
DrawSizeSlider.ValueChanged += (_) => drawSizeLabel.Text = $"{DrawSize}"; DrawSizeSlider.ValueChanged += (_) => drawSizeLabel.Text = $"{DrawSize}";
UpDownToggle = GetNode<Button>("UpDown"); RaiseLowerToggle = GetNode<Button>("RaiseLower");
ConnectedToggle = GetNode<Button>("Connected"); ConnectedToggle = GetNode<Button>("Connected");
} }
ToolMode GetToolMode() ToolMode GetToolMode()
@ -67,7 +67,7 @@ public partial class TerrainEditingControls
ToolShapeButtons[0].Item2.Icon = (value != ToolMode.Paint) ToolShapeButtons[0].Item2.Icon = (value != ToolMode.Paint)
? CornerTextureNormal : CornerTexturePaint; ? CornerTextureNormal : CornerTexturePaint;
UpDownToggle.Disabled = value is ToolMode.Flatten or ToolMode.Paint; RaiseLowerToggle.Disabled = value is ToolMode.Flatten or ToolMode.Paint;
ConnectedToggle.Disabled = value is ToolMode.Paint; ConnectedToggle.Disabled = value is ToolMode.Paint;
} }

@ -9,8 +9,8 @@
[ext_resource type="Texture2D" uid="uid://dc0q2xn2cgcjw" path="res://assets/textures/terrain/editing/corner_paint.png" id="3_e00xo"] [ext_resource type="Texture2D" uid="uid://dc0q2xn2cgcjw" path="res://assets/textures/terrain/editing/corner_paint.png" id="3_e00xo"]
[ext_resource type="Texture2D" uid="uid://2u1ldmh0osbx" path="res://assets/textures/terrain/editing/circle.png" id="2_yvc34"] [ext_resource type="Texture2D" uid="uid://2u1ldmh0osbx" path="res://assets/textures/terrain/editing/circle.png" id="2_yvc34"]
[ext_resource type="Texture2D" uid="uid://btjd1704xtdjv" path="res://assets/textures/terrain/editing/square.png" id="3_aaaoe"] [ext_resource type="Texture2D" uid="uid://btjd1704xtdjv" path="res://assets/textures/terrain/editing/square.png" id="3_aaaoe"]
[ext_resource type="Texture2D" uid="uid://dxbfohim13ti1" path="res://assets/textures/terrain/editing/height_up.png" id="9_u4loi"] [ext_resource type="Texture2D" uid="uid://dxbfohim13ti1" path="res://assets/textures/terrain/editing/height_raise.png" id="9_u4loi"]
[ext_resource type="Texture2D" uid="uid://covyafauwthij" path="res://assets/textures/terrain/editing/height_down.png" id="10_owj33"] [ext_resource type="Texture2D" uid="uid://covyafauwthij" path="res://assets/textures/terrain/editing/height_lower.png" id="10_owj33"]
[ext_resource type="Texture2D" uid="uid://dsbovbbrtuv8f" path="res://assets/textures/terrain/editing/connected_on.png" id="8_4qifu"] [ext_resource type="Texture2D" uid="uid://dsbovbbrtuv8f" path="res://assets/textures/terrain/editing/connected_on.png" id="8_4qifu"]
[ext_resource type="Texture2D" uid="uid://c5j5v62f7p6qt" path="res://assets/textures/terrain/editing/connected_off.png" id="12_5hs2d"] [ext_resource type="Texture2D" uid="uid://c5j5v62f7p6qt" path="res://assets/textures/terrain/editing/connected_off.png" id="12_5hs2d"]
[ext_resource type="Image" uid="uid://b0jp1dyxugbr7" path="res://assets/textures/terrain/grass.png" id="Image_d41co"] [ext_resource type="Image" uid="uid://b0jp1dyxugbr7" path="res://assets/textures/terrain/grass.png" id="Image_d41co"]
@ -91,7 +91,7 @@ value = -1.0
[node name="HSeparator2" type="HSeparator" parent="."] [node name="HSeparator2" type="HSeparator" parent="."]
layout_mode = 2 layout_mode = 2
[node name="UpDown" type="Button" parent="."] [node name="RaiseLower" type="Button" parent="."]
layout_mode = 2 layout_mode = 2
toggle_mode = true toggle_mode = true
button_pressed = true button_pressed = true

Loading…
Cancel
Save