[Tool] public partial class Terrain { bool _unhandledMouseMotion = false; // Used to detect when mouse moves off the terrain. Vector2I? _tileHover = null; // Position of currently hovered tile. bool _isSelecting = false; // Whether left mouse is held down to select tiles. (Vector2I Start, Vector2I End)? _selection = null; public override void _Input(InputEvent ev) { if (!IsEditing()) return; if (_isSelecting && ev is InputEventMouseButton { ButtonIndex: MouseButton.Left, Pressed: false }) { _isSelecting = false; GetViewport().SetInputAsHandled(); } if ((ev is InputEventMouseButton { ButtonIndex: var wheel, ShiftPressed: true }) && (wheel is MouseButton.WheelUp or MouseButton.WheelDown)) { const float AdjustHeight = 0.5f; var value = (wheel == MouseButton.WheelUp) ? 1.0f : -1.0f; foreach (var tile in GetSelectedTiles()) AdjustTileHeight(tile, value * AdjustHeight); if (_selection != null) UpdateMeshAndShape(); NotifyPropertyListChanged(); GetViewport().SetInputAsHandled(); } if (ev is InputEventMouseMotion) _unhandledMouseMotion = true; } public override void _InputEvent(Camera3D camera, InputEvent ev, Vector3 position, Vector3 normal, int shapeIdx) { if (!IsEditing()) return; var localPos = ToLocal(position); var tilePos = ToTilePos(localPos); if (ev is InputEventMouseButton { ButtonIndex: MouseButton.Left, Pressed: true }) { _selection = (tilePos, tilePos); _isSelecting = true; GetViewport().SetInputAsHandled(); } if (ev is InputEventMouseMotion) { _unhandledMouseMotion = false; _tileHover = tilePos; if (_isSelecting) _selection = _selection.Value with { End = tilePos }; } } public override void _Process(double delta) { if (!IsEditing()) { _tileHover = null; _selection = null; _isSelecting = false; } if (_unhandledMouseMotion) _tileHover = null; if ((_tileHover != null) || (_selection != null)) { var mesh = GetOrCreateMesh("EditToolMesh"); mesh.ClearSurfaces(); mesh.SurfaceBegin(Mesh.PrimitiveType.Lines); void AddLine(Vector3 start, Vector3 end) { mesh.SurfaceAddVertex(start); mesh.SurfaceAddVertex(end); } void AddQuad(Vector3 topLeft, Vector3 topRight, Vector3 bottomLeft, Vector3 bottomRight) { AddLine(topLeft , topRight ); AddLine(topRight , bottomRight); AddLine(bottomRight, bottomLeft ); AddLine(bottomLeft , topLeft ); } if (_tileHover is Vector2I hover) { var corners = GetGridCorners(hover); var margin = 0.1f; mesh.SurfaceSetColor(Colors.Black); AddQuad( corners.TopLeft + new Vector3(-margin, 0, -margin), corners.TopRight + new Vector3(+margin, 0, -margin), corners.BottomLeft + new Vector3(-margin, 0, +margin), corners.BottomRight + new Vector3(+margin, 0, +margin) ); } mesh.SurfaceSetColor(Colors.Blue); foreach (var tilePos in GetSelectedTiles()) { var corners = GetGridCorners(tilePos); AddQuad(corners.TopLeft, corners.TopRight, corners.BottomLeft, corners.BottomRight); } mesh.SurfaceEnd(); mesh.SurfaceSetMaterial(0, _editToolMaterial); } else { var meshInstance = (MeshInstance3D)GetNodeOrNull("EditToolMesh"); var mesh = (ImmediateMesh)meshInstance?.Mesh; mesh?.ClearSurfaces(); } } bool IsEditing() { if (Engine.IsEditorHint()) { var selection = EditorInterface.Singleton.GetSelection(); return selection.GetSelectedNodes().Contains(this); } else { return false; } } IEnumerable GetSelectedTiles() { if (_selection is not (Vector2I start, Vector2I end)) yield break; // Ensure start.X/Y is smaller than end.X/Y. (start, end) = (new(Min(start.X, end.X), Min(start.Y, end.Y)), new(Max(start.X, end.X), Max(start.Y, end.Y))); // Go over all tiles in the range and yield each one. for (var x = start.X; x <= end.X; x++) for (var y = start.Y; y <= end.Y; y++) yield return new(x, y); } }