A game where you get to play as a slime, made with Godot.
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.
 
 

258 lines
8.7 KiB

public partial class TerrainEditingControls
{
Terrain _currentTerrain = null;
Material _editToolMaterial;
public override void _EnterTree()
{
_editToolMaterial = new StandardMaterial3D {
AlbedoColor = Colors.Blue,
ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded,
DepthDrawMode = BaseMaterial3D.DepthDrawModeEnum.Disabled,
NoDepthTest = true,
};
}
public override void _ExitTree()
=> ClearEditToolMesh();
public override void _Input(InputEvent ev)
{
var viewport = !Engine.IsEditorHint() ? GetViewport()
: EditorInterface.Singleton.GetEditorViewport3D();
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;
}
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);
}
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);
}
UpdateEditToolMesh(terrain, ToolShape switch {
// TODO: Edit corner, not full tile.
ToolShape.Corner => [tile],
ToolShape.Circle => GetTilesInRadius(),
ToolShape.Square => GetTilesInSquare(),
_ => throw new InvalidOperationException(),
});
} else {
ClearEditToolMesh();
}
}
// 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);
}
void UpdateEditToolMesh(Terrain terrain, IEnumerable<TilePos> tiles)
{
if (terrain != _currentTerrain) ClearEditToolMesh();
_currentTerrain = terrain;
var mesh = terrain.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 bottomRight, Vector3 bottomLeft) {
AddLine(topLeft , topRight );
AddLine(topRight , bottomRight);
AddLine(bottomRight, bottomLeft );
AddLine(bottomLeft , topLeft );
}
foreach (var tile in tiles) {
var (topLeft, topRight, bottomRight, bottomLeft)
= terrain.GetTileCornerPositions(tile);
AddQuad(topLeft, topRight, bottomRight, bottomLeft);
}
mesh.SurfaceEnd();
mesh.SurfaceSetMaterial(0, _editToolMaterial);
}
void ClearEditToolMesh()
=> _currentTerrain?.GetNodeOrNull("EditToolMesh")?.QueueFree();
(Terrain Terrain, Vector3 Position)? RayCastTerrain(InputEventMouse ev)
{
// 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);
var result = space.IntersectRay(query);
var collider = (GodotObject)result.GetValueOrDefault("collider");
return (collider is Terrain terrain)
? (terrain, (Vector3)result["position"])
: null;
}
static (TilePos, Corner) FindClosestTile(Terrain terrain, Vector3 position)
{
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(),
};
return (tile, corner);
}
}