|
|
|
|
|
|
|
[Tool]
|
|
|
|
public partial class Grid : Node3D
|
|
|
|
{
|
|
|
|
public const float StepSize = 0.05f; // 5cm
|
|
|
|
|
|
|
|
const float Offset = 0.001f;
|
|
|
|
const int ThickLineEvery = 4;
|
|
|
|
const float ThickLineWidth = 0.006f;
|
|
|
|
const float ThinLineWidth = 0.004f;
|
|
|
|
static readonly Color ThickLineColor = new(0.25f, 0.25f, 0.25f);
|
|
|
|
static readonly Color ThinLineColor = new(0.35f, 0.35f, 0.35f);
|
|
|
|
|
|
|
|
// TODO: Make GridSize be three-dimensional?
|
|
|
|
Vector2I _gridSize = new(16, 16);
|
|
|
|
Vector3 _halfGridActualSize = new(8.0f, 0.0f, 8.0f);
|
|
|
|
[Export] public Vector2I GridSize {
|
|
|
|
get => _gridSize;
|
|
|
|
set {
|
|
|
|
_gridSize = value;
|
|
|
|
// Helper value for converting grid pos to local pos and back.
|
|
|
|
_halfGridActualSize = new Vector3(GridSize.X, 0, GridSize.Y) * (StepSize / 2.0f);
|
|
|
|
UpdateImmediateMesh();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override void _Ready()
|
|
|
|
=> UpdateImmediateMesh();
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary> Returns whether the specified item is contained in this or any nested grids. </summary>
|
|
|
|
public bool ContainsItem(Item item)
|
|
|
|
{
|
|
|
|
while (item?.GetParentOrNull<Node>() is Grid grid) {
|
|
|
|
if (grid == this) return true;
|
|
|
|
item = grid.GetParent() as Item;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Recursively adds all items included in this grid to the specified
|
|
|
|
/// collection. The collection must be of a valid super-type of Item.
|
|
|
|
/// </summary>
|
|
|
|
public void AddItemsRecursively<T>(ICollection<T> collection)
|
|
|
|
{
|
|
|
|
if (!typeof(T).IsAssignableFrom(typeof(Item)))
|
|
|
|
throw new ArgumentException($"Type '{typeof(T)}' is not a super-type of Item", nameof(T));
|
|
|
|
|
|
|
|
foreach (var child in GetChildren().OfType<Item>()) {
|
|
|
|
collection.Add((T)(object)child);
|
|
|
|
child.GetNodeOrNull<Grid>(nameof(Grid))?.AddItemsRecursively(collection);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Vector3I LocalToGrid(Vector3 pos)
|
|
|
|
=> (Vector3I)((pos + _halfGridActualSize) / StepSize);
|
|
|
|
public Vector3 GridToLocal(Vector3I pos)
|
|
|
|
=> (pos + Vector3.One * 0.5f) * StepSize - _halfGridActualSize;
|
|
|
|
|
|
|
|
public Vector3I GlobalToGrid(Vector3 pos)
|
|
|
|
=> LocalToGrid(ToLocal(pos));
|
|
|
|
public Vector3 GridToGlobal(Vector3I pos)
|
|
|
|
=> ToGlobal(GridToLocal(pos));
|
|
|
|
|
|
|
|
public static Aabb LocalItemTransformToLocalAabb(Transform3D transform, Item item)
|
|
|
|
{
|
|
|
|
var bounds = (transform.Basis * ((Vector3)item.Size * StepSize)).Abs();
|
|
|
|
return new(transform.Origin - bounds / 2, bounds);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Returns if the item could be considered for placement
|
|
|
|
/// at the specified local position and normal.
|
|
|
|
/// </summary>
|
|
|
|
public bool CanPlaceAgainst(Item item, Vector3 position, Vector3 normal)
|
|
|
|
{
|
|
|
|
return normal.IsEqualApprox(Vector3.Up);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Returns whether the item can be placed at the specified local transform.
|
|
|
|
/// The transform needs to be grid-aligned, such as by calling <see cref="Snap"/>.
|
|
|
|
/// </summary>
|
|
|
|
public bool CanPlaceAt(Item item, Transform3D transform)
|
|
|
|
{
|
|
|
|
var region = LocalItemTransformToLocalAabb(transform, item).Grow(-0.01f);
|
|
|
|
foreach (var other in GetChildren().OfType<Item>())
|
|
|
|
if (region.Intersects(LocalItemTransformToLocalAabb(other.Transform, other)))
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Snaps a local transform to line up with the grid.
|
|
|
|
/// The transform will be "pushed out" into the normal vector's
|
|
|
|
/// direction according to the current rotation and item's size.
|
|
|
|
/// </summary>
|
|
|
|
public Transform3D Snap(Transform3D transform, Vector3 normal, Item item)
|
|
|
|
{
|
|
|
|
// Snap rotation to nearest axis.
|
|
|
|
transform.Basis = Basis.FromEuler(transform.Basis.GetEuler().Snapped(Tau / 4));
|
|
|
|
// Offset / "push out" by half of the item's size.
|
|
|
|
var halfSize = (Vector3)item.Size * StepSize / 2;
|
|
|
|
var axis = (normal * transform.Basis).Abs().MaxAxisIndex();
|
|
|
|
transform.Origin += halfSize[(int)axis] * normal;
|
|
|
|
// Snap the position to the grid.
|
|
|
|
var halfOff = (transform.Basis * halfSize + _halfGridActualSize).PosMod(1.0f);
|
|
|
|
transform.Origin = halfOff + (transform.Origin - halfOff).Snapped(StepSize);
|
|
|
|
return transform;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static StandardMaterial3D _material;
|
|
|
|
static StandardMaterial3D GetOrCreateMaterial()
|
|
|
|
=> _material ??= new() { VertexColorUseAsAlbedo = true };
|
|
|
|
|
|
|
|
ImmediateMesh _mesh;
|
|
|
|
void UpdateImmediateMesh()
|
|
|
|
{
|
|
|
|
static bool IsThickLine(int line)
|
|
|
|
=> (line % ThickLineEvery) == 0;
|
|
|
|
|
|
|
|
static (float, Color) GetLineWidthAndColor(int line)
|
|
|
|
=> IsThickLine(line) ? (ThickLineWidth, ThickLineColor) : (ThinLineWidth, ThinLineColor);
|
|
|
|
|
|
|
|
static void FlatQuad(ImmediateMesh mesh, float x1, float x2, float y1, float y2) {
|
|
|
|
mesh.SurfaceAddVertex(new(x1, Offset, y1)); // 1--2
|
|
|
|
mesh.SurfaceAddVertex(new(x2, Offset, y1)); // | /
|
|
|
|
mesh.SurfaceAddVertex(new(x1, Offset, y2)); // 3'
|
|
|
|
mesh.SurfaceAddVertex(new(x2, Offset, y1)); // .1
|
|
|
|
mesh.SurfaceAddVertex(new(x2, Offset, y2)); // / |
|
|
|
|
mesh.SurfaceAddVertex(new(x1, Offset, y2)); // 3--2
|
|
|
|
}
|
|
|
|
|
|
|
|
_mesh = (ImmediateMesh)GetNodeOrNull<MeshInstance3D>("MeshInstance3D")?.Mesh;
|
|
|
|
if (_mesh == null) {
|
|
|
|
var meshInstance = new MeshInstance3D();
|
|
|
|
meshInstance.Mesh = _mesh = new();
|
|
|
|
AddChild(meshInstance, true);
|
|
|
|
} else
|
|
|
|
_mesh.ClearSurfaces();
|
|
|
|
|
|
|
|
_mesh.SurfaceBegin(Mesh.PrimitiveType.Triangles);
|
|
|
|
|
|
|
|
// Horizontal Lines
|
|
|
|
var half_width = GridSize.X * StepSize / 2;
|
|
|
|
for (var line = 0; line <= GridSize.Y; line++) {
|
|
|
|
var (line_width, line_color) = GetLineWidthAndColor(line);
|
|
|
|
var y = StepSize * (line - GridSize.Y / 2.0f);
|
|
|
|
_mesh.SurfaceSetColor(line_color);
|
|
|
|
FlatQuad(_mesh, -half_width, +half_width, y - line_width / 2, y + line_width / 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Vertical Lines
|
|
|
|
var half_depth = GridSize.Y * StepSize / 2;
|
|
|
|
for (var line = 0; line <= GridSize.X; line++) {
|
|
|
|
var (line_width, line_color) = GetLineWidthAndColor(line);
|
|
|
|
var x = StepSize * (line - GridSize.X / 2.0f);
|
|
|
|
_mesh.SurfaceSetColor(line_color);
|
|
|
|
FlatQuad(_mesh, x - line_width / 2, x + line_width / 2, -half_depth, +half_depth);
|
|
|
|
}
|
|
|
|
|
|
|
|
_mesh.SurfaceEnd();
|
|
|
|
_mesh.SurfaceSetMaterial(0, GetOrCreateMaterial());
|
|
|
|
}
|
|
|
|
}
|