You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
259 lines
8.7 KiB
259 lines
8.7 KiB
1 month ago
|
public partial class TerrainEditingControls
|
||
2 months ago
|
{
|
||
1 month ago
|
Terrain _currentTerrain = null;
|
||
2 months ago
|
|
||
1 month ago
|
Material _editToolMaterial;
|
||
1 month ago
|
public override void _EnterTree()
|
||
1 month ago
|
{
|
||
|
_editToolMaterial = new StandardMaterial3D {
|
||
1 month ago
|
AlbedoColor = Colors.Blue,
|
||
1 month ago
|
ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded,
|
||
|
DepthDrawMode = BaseMaterial3D.DepthDrawModeEnum.Disabled,
|
||
|
NoDepthTest = true,
|
||
|
};
|
||
|
}
|
||
|
|
||
1 month ago
|
public override void _ExitTree()
|
||
|
=> ClearEditToolMesh();
|
||
|
|
||
2 months ago
|
public override void _Input(InputEvent ev)
|
||
|
{
|
||
1 month ago
|
var viewport = !Engine.IsEditorHint() ? GetViewport()
|
||
|
: EditorInterface.Singleton.GetEditorViewport3D();
|
||
1 month ago
|
|
||
|
if (Engine.IsEditorHint()) {
|
||
|
// Make sure to transform the input event to the 3D scene's viewport.
|
||
|
var container = viewport.GetParent<SubViewportContainer>();
|
||
|
ev = ev.XformedBy(container.GetGlobalTransform().AffineInverse());
|
||
|
if (ev is InputEventMouse m) m.GlobalPosition = m.Position;
|
||
|
}
|
||
2 months ago
|
|
||
1 month ago
|
if (ev is InputEventMouse mouse) {
|
||
|
if (viewport.GetVisibleRect().HasPoint(mouse.Position)
|
||
|
&& (RayCastTerrain(mouse) is var (terrain, position))) {
|
||
|
var (tile, corner) = FindClosestTile(terrain, position);
|
||
|
|
||
|
var drawSize = (ToolShape == ToolShape.Corner) ? 1 : DrawSize;
|
||
|
var isEven = (drawSize % 2) == 0;
|
||
|
var radius = FloorToInt(drawSize / 2.0f);
|
||
|
|
||
|
// Offset tile position by corner.
|
||
|
if (isEven) tile = corner switch {
|
||
|
Corner.TopLeft => new(tile.X + 0, tile.Y + 0),
|
||
|
Corner.TopRight => new(tile.X + 1, tile.Y + 0),
|
||
|
Corner.BottomRight => new(tile.X + 1, tile.Y + 1),
|
||
|
Corner.BottomLeft => new(tile.X + 0, tile.Y + 1),
|
||
|
_ => throw new InvalidOperationException(),
|
||
|
};
|
||
|
|
||
|
IEnumerable<TilePos> GetTilesInSquare() {
|
||
|
var minX = tile.X - radius;
|
||
|
var minY = tile.Y - radius;
|
||
|
var maxX = tile.X + radius - (isEven ? 1 : 0);
|
||
|
var maxY = tile.Y + radius - (isEven ? 1 : 0);
|
||
|
for (var x = minX; x <= maxX; x++)
|
||
|
for (var y = minY; y <= maxY; y++)
|
||
|
yield return new(x, y);
|
||
2 months ago
|
}
|
||
2 months ago
|
|
||
1 month ago
|
IEnumerable<TilePos> GetTilesInRadius() {
|
||
|
var center = isEven
|
||
|
? new Vector2(tile.X , tile.Y )
|
||
|
: new Vector2(tile.X + 0.5f, tile.Y + 0.5f);
|
||
|
var distanceSqr = Pow(isEven ? radius - 0.25f : radius + 0.25f, 2);
|
||
|
return GetTilesInSquare()
|
||
|
.Where(tile => center.DistanceSquaredTo(
|
||
|
new Vector2(tile.X + 0.5f, tile.Y + 0.5f)) < distanceSqr);
|
||
2 months ago
|
}
|
||
|
|
||
1 month ago
|
UpdateEditToolMesh(terrain, ToolShape switch {
|
||
|
// TODO: Edit corner, not full tile.
|
||
|
ToolShape.Corner => [tile],
|
||
|
ToolShape.Circle => GetTilesInRadius(),
|
||
|
ToolShape.Square => GetTilesInSquare(),
|
||
|
_ => throw new InvalidOperationException(),
|
||
|
});
|
||
|
} else {
|
||
|
ClearEditToolMesh();
|
||
2 months ago
|
}
|
||
2 months ago
|
}
|
||
|
|
||
1 month ago
|
// if (_isSelecting && (ev is InputEventMouseButton { ButtonIndex: MouseButton.Left, Pressed: false })) {
|
||
|
// _isSelecting = false;
|
||
|
// GetViewport().SetInputAsHandled();
|
||
|
// return;
|
||
|
// }
|
||
|
|
||
|
// if ((ev is InputEventMouseButton { ButtonIndex: var wheel, Pressed: var pressed, ShiftPressed: true })
|
||
|
// && (wheel is MouseButton.WheelUp or MouseButton.WheelDown) && (_selection != null))
|
||
|
// {
|
||
|
// // NOTE: Potential bug in the Godot editor?
|
||
|
// // Does it zoom both when mouse wheel is "pressed" and "released"?
|
||
|
// // Because just cancelling one of them still causes zooming to occur.
|
||
|
// GetViewport().SetInputAsHandled();
|
||
|
// if (!pressed) return;
|
||
|
|
||
|
// const float AdjustHeight = 0.5f;
|
||
|
// var amount = (wheel == MouseButton.WheelUp)
|
||
|
// ? AdjustHeight : -AdjustHeight;
|
||
|
|
||
|
// var selection = TileRegion.From(_selection.Value);
|
||
|
|
||
|
// // Raise connected corners.
|
||
|
// foreach (var innerCorner in Enum.GetValues<Corner>()) {
|
||
|
// var outerCorner = innerCorner.GetOpposite();
|
||
|
|
||
|
// var innerPos = selection.GetTileFor(innerCorner);
|
||
|
// var outerPos = innerPos.GetNeighbor(innerCorner);
|
||
|
|
||
|
// var outerTile = terrain.GetTile(outerPos);
|
||
|
// var innerHeight = terrain.GetTile(innerPos).Height[innerCorner];
|
||
|
// var outerHeight = outerTile.Height[outerCorner];
|
||
|
|
||
|
// if (IsEqualApprox(outerHeight, innerHeight)) {
|
||
|
// outerTile.Height[outerCorner] = innerHeight + amount;
|
||
|
// terrain.SetTile(outerPos, outerTile);
|
||
|
// }
|
||
|
// }
|
||
|
|
||
|
// // Raise connected sides.
|
||
|
// foreach (var side in Enum.GetValues<Side>()) {
|
||
|
// foreach (var innerPos in selection.GetTilesFor(side)) {
|
||
|
// var outerPos = innerPos.GetNeighbor(side);
|
||
|
|
||
|
// var innerTile = terrain.GetTile(innerPos);
|
||
|
// var outerTile = terrain.GetTile(outerPos);
|
||
|
|
||
|
// var (innerCorner1, innerCorner2) = side.GetCorners();
|
||
|
// var (outerCorner1, outerCorner2) = side.GetOpposite().GetCorners();
|
||
|
|
||
|
// var changed = false;
|
||
|
// var matchingCorners = new[]{ (innerCorner1, outerCorner1), (innerCorner2, outerCorner2) };
|
||
|
// foreach (var (innerCorner, outerCorner) in matchingCorners) {
|
||
|
// var innerHeight = innerTile.Height[innerCorner];
|
||
|
// var outerHeight = outerTile.Height[outerCorner];
|
||
|
|
||
|
// if (IsEqualApprox(outerHeight, innerHeight)) {
|
||
|
// outerTile.Height[outerCorner] = innerHeight + amount;
|
||
|
// changed = true;
|
||
|
// }
|
||
|
// }
|
||
|
// if (changed) terrain.SetTile(outerPos, outerTile);
|
||
|
// }
|
||
|
// }
|
||
|
|
||
|
// // Raise selected tiles themselves.
|
||
|
// foreach (var pos in selection.GetAllTiles()) {
|
||
|
// var tile = terrain.GetTile(pos);
|
||
|
// tile.Height.Adjust(amount);
|
||
|
// terrain.SetTile(pos, tile);
|
||
|
// }
|
||
|
|
||
|
// terrain.UpdateMeshAndShape();
|
||
|
// terrain.NotifyPropertyListChanged();
|
||
|
// return;
|
||
|
// }
|
||
|
|
||
|
// if ((ev is InputEventMouseButton { ButtonIndex: var wheel2, Pressed: var pressed2, CtrlPressed: true })
|
||
|
// && (wheel2 is MouseButton.WheelUp or MouseButton.WheelDown) && (_selection != null))
|
||
|
// {
|
||
|
// GetViewport().SetInputAsHandled();
|
||
|
// if (!pressed2) return;
|
||
|
|
||
|
// var amount = (wheel2 == MouseButton.WheelUp) ? 1 : -1;
|
||
|
// var selection = TileRegion.From(_selection.Value);
|
||
|
|
||
|
// foreach (var pos in selection.GetAllTiles()) {
|
||
|
// var tile = terrain.GetTile(pos);
|
||
|
// tile.TexturePrimary = PosMod(tile.TexturePrimary + amount, 4);
|
||
|
// terrain.SetTile(pos, tile);
|
||
|
// }
|
||
|
|
||
|
// terrain.UpdateMeshAndShape();
|
||
|
// terrain.NotifyPropertyListChanged();
|
||
|
// return;
|
||
|
// }
|
||
|
|
||
|
// if (ev is InputEventMouseMotion)
|
||
|
// _unhandledMotion = true;
|
||
|
|
||
|
// if ((ev is InputEventMouse mouse) && viewport.GetVisibleRect().HasPoint(mouse.Position))
|
||
|
// OnInputRayCastTerrain(terrain, mouse);
|
||
|
}
|
||
2 months ago
|
|
||
1 month ago
|
void UpdateEditToolMesh(Terrain terrain, IEnumerable<TilePos> tiles)
|
||
|
{
|
||
|
if (terrain != _currentTerrain) ClearEditToolMesh();
|
||
|
_currentTerrain = terrain;
|
||
2 months ago
|
|
||
1 month ago
|
var mesh = terrain.GetOrCreateMesh("EditToolMesh");
|
||
|
mesh.ClearSurfaces();
|
||
|
mesh.SurfaceBegin(Mesh.PrimitiveType.Lines);
|
||
|
|
||
|
void AddLine(Vector3 start, Vector3 end) {
|
||
|
mesh.SurfaceAddVertex(start);
|
||
|
mesh.SurfaceAddVertex(end);
|
||
|
}
|
||
2 months ago
|
|
||
1 month ago
|
void AddQuad(Vector3 topLeft , Vector3 topRight,
|
||
|
Vector3 bottomRight, Vector3 bottomLeft) {
|
||
|
AddLine(topLeft , topRight );
|
||
|
AddLine(topRight , bottomRight);
|
||
|
AddLine(bottomRight, bottomLeft );
|
||
|
AddLine(bottomLeft , topLeft );
|
||
2 months ago
|
}
|
||
|
|
||
1 month ago
|
foreach (var tile in tiles) {
|
||
|
var (topLeft, topRight, bottomRight, bottomLeft)
|
||
|
= terrain.GetTileCornerPositions(tile);
|
||
|
AddQuad(topLeft, topRight, bottomRight, bottomLeft);
|
||
|
}
|
||
1 month ago
|
|
||
1 month ago
|
mesh.SurfaceEnd();
|
||
|
mesh.SurfaceSetMaterial(0, _editToolMaterial);
|
||
1 month ago
|
}
|
||
|
|
||
1 month ago
|
void ClearEditToolMesh()
|
||
|
=> _currentTerrain?.GetNodeOrNull("EditToolMesh")?.QueueFree();
|
||
|
|
||
|
(Terrain Terrain, Vector3 Position)? RayCastTerrain(InputEventMouse ev)
|
||
1 month ago
|
{
|
||
|
// Ray is cast from the editor camera's view.
|
||
|
var camera = EditorInterface.Singleton.GetEditorViewport3D().GetCamera3D();
|
||
|
var from = camera.ProjectRayOrigin(ev.Position);
|
||
|
var to = from + camera.ProjectRayNormal(ev.Position) * camera.Far;
|
||
|
|
||
|
// Actual collision is done in the edited scene though.
|
||
|
var root = (Node3D)EditorInterface.Singleton.GetEditedSceneRoot();
|
||
|
var space = root.GetWorld3D().DirectSpaceState;
|
||
|
var query = PhysicsRayQueryParameters3D.Create(from, to);
|
||
|
|
||
1 month ago
|
var result = space.IntersectRay(query);
|
||
1 month ago
|
var collider = (GodotObject)result.GetValueOrDefault("collider");
|
||
1 month ago
|
return (collider is Terrain terrain)
|
||
|
? (terrain, (Vector3)result["position"])
|
||
|
: null;
|
||
2 months ago
|
}
|
||
|
|
||
1 month ago
|
static (TilePos, Corner) FindClosestTile(Terrain terrain, Vector3 position)
|
||
2 months ago
|
{
|
||
1 month ago
|
var local = terrain.ToLocal(position);
|
||
|
|
||
|
var tileX = local.X / terrain.TileSize + 0.5 + terrain.Size.X / 2;
|
||
|
var tileY = local.Z / terrain.TileSize + 0.5 + terrain.Size.Y / 2;
|
||
|
var tile = new TilePos(FloorToInt(tileX), FloorToInt(tileY));
|
||
|
|
||
|
var cornerX = RoundToInt(PosMod(tileX, 1));
|
||
|
var cornerY = RoundToInt(PosMod(tileY, 1));
|
||
|
var corner = (cornerX, cornerY) switch {
|
||
|
(0, 0) => Corner.TopLeft,
|
||
|
(1, 0) => Corner.TopRight,
|
||
|
(1, 1) => Corner.BottomRight,
|
||
|
(0, 1) => Corner.BottomLeft,
|
||
|
_ => throw new InvalidOperationException(),
|
||
|
};
|
||
2 months ago
|
|
||
1 month ago
|
return (tile, corner);
|
||
2 months ago
|
}
|
||
|
}
|