diff --git a/objects/Grid.cs b/objects/Grid.cs
index 0b653f9..7a3e296 100644
--- a/objects/Grid.cs
+++ b/objects/Grid.cs
@@ -1,5 +1,6 @@
+
[Tool]
-public partial class Grid : Area3D
+public partial class Grid : Node3D
{
public const float StepSize = 0.05f; // 5cm
@@ -19,19 +20,13 @@ public partial class Grid : Area3D
_gridSize = value;
// Helper value for converting grid pos to local pos and back.
_halfGridActualSize = new Vector3(GridSize.X, 0, GridSize.Y) * (StepSize / 2.0f);
- Update();
+ UpdateImmediateMesh();
}
}
public override void _Ready()
- => Update();
-
- void Update()
- {
- UpdateCollisionShape();
- UpdateImmediateMesh();
- }
+ => UpdateImmediateMesh();
/// Returns whether the specified item is contained in this or any nested grids.
@@ -44,6 +39,21 @@ public partial class Grid : Area3D
return false;
}
+ ///
+ /// Recursively adds all items included in this grid to the specified
+ /// collection. The collection must be of a valid super-type of Item.
+ ///
+ public void AddItemsRecursively(ICollection 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- ()) {
+ collection.Add((T)(object)child);
+ child.GetNodeOrNull(nameof(Grid))?.AddItemsRecursively(collection);
+ }
+ }
+
public Vector3I LocalToGrid(Vector3 pos)
=> (Vector3I)((pos + _halfGridActualSize) / StepSize);
@@ -108,28 +118,6 @@ public partial class Grid : Area3D
static StandardMaterial3D GetOrCreateMaterial()
=> _material ??= new() { VertexColorUseAsAlbedo = true };
- ConvexPolygonShape3D _shape;
- void UpdateCollisionShape()
- {
- if (Engine.IsEditorHint()) return;
-
- if (_shape == null) {
- _shape = new ConvexPolygonShape3D();
- AddChild(new CollisionShape3D { Shape = _shape }, true);
- }
-
- const float Offset = 0.001f;
- var x = GridSize.X * StepSize / 2;
- var y = GridSize.Y * StepSize / 2;
-
- _shape.Points = [
- new(-x, Offset, -y),
- new( x, Offset, -y),
- new( x, Offset, y),
- new(-x, Offset, y),
- ];
- }
-
ImmediateMesh _mesh;
void UpdateImmediateMesh()
{
diff --git a/objects/grid.tscn b/objects/grid.tscn
index 637bf5a..d95ce42 100644
--- a/objects/grid.tscn
+++ b/objects/grid.tscn
@@ -2,8 +2,5 @@
[ext_resource type="Script" path="res://objects/Grid.cs" id="1_7yhbt"]
-[node name="Grid" type="Area3D"]
-collision_layer = 512
-collision_mask = 0
-monitoring = false
+[node name="Grid" type="Node3D"]
script = ExtResource("1_7yhbt")
diff --git a/player/PickupController.cs b/player/PickupController.cs
index 4c45b53..84e5c6e 100644
--- a/player/PickupController.cs
+++ b/player/PickupController.cs
@@ -61,39 +61,17 @@ public partial class PickupController : Node3D
if (!_player.IsLocal) return;
if (HasItemsHeld) {
- // This ray will be blocked by static and dynamic objects.
- const PhysicsLayer Mask = PhysicsLayer.Place | PhysicsLayer.Static | PhysicsLayer.Dynamic;
-
- if ((RayToMouseCursor(Mask) is RayResult ray) && (ray.Collider is Grid grid)
- // Not pointing at item's own grid, or one of its nested grids.
- && (grid.GetParent() != HeldItem) && !grid.ContainsItem(HeldItem))
- {
- var inverseTransform = grid.GlobalTransform.AffineInverse();
- var inverseBasis = grid.GlobalBasis.Inverse();
-
- var pos = inverseTransform * ray.Position;
- var normal = inverseBasis * ray.Normal;
-
- if (grid.CanPlaceAgainst(HeldItem, pos, normal)) {
- var transform = new Transform3D(inverseBasis * HeldItem.GlobalBasis, pos);
- transform = grid.Snap(transform, normal, HeldItem);
- var canPlace = grid.CanPlaceAt(HeldItem, transform);
-
- var outlineColor = canPlace ? OutlineYesPlace : OutlineNoPlace;
- _outlineMaterial.SetShaderParameter("line_color", outlineColor);
-
- _preview.GlobalTransform = grid.GlobalTransform * transform;
- _preview.Visible = true;
- _targetedGrid = canPlace ? grid : null;
- } else {
- _preview.Visible = false;
- _targetedGrid = null;
- }
+ if (GetValidPlacement(HeldItem) is var (grid, transform, isFree)) {
+ var outlineColor = isFree ? OutlineYesPlace : OutlineNoPlace;
+ _outlineMaterial.SetShaderParameter("line_color", outlineColor);
+
+ _preview.GlobalTransform = grid.GlobalTransform * transform;
+ _preview.Visible = true;
+ _targetedGrid = isFree ? grid : null;
} else {
_preview.Visible = false;
_targetedGrid = null;
}
-
} else {
var interactable = RayToMouseCursor(PhysicsLayer.Pickup)?.Collider;
@@ -142,8 +120,48 @@ public partial class PickupController : Node3D
return preview;
}
+ (Grid Grid, Transform3D Target, bool IsFree)? GetValidPlacement(Item itemToPlace)
+ {
+ // This ray will be blocked by static and dynamic objects.
+ const PhysicsLayer Mask = PhysicsLayer.Static | PhysicsLayer.Dynamic | PhysicsLayer.Item;
+ // FIXME: Remove .Place and .Pickup physics layers?
+ // TODO: We need a separate physics layers for:
+ // - The physical item collider used for physics calculations (simplified).
+ // - The placement collider which should match the item's appearance.
+ // - The general space / size an item takes up, as a cuboid.
+ // TODO: Probably just overhaul the physics layers altogether.
+ // It would be better to have a "collides with player" and "player collides with it" layer, etc.
+
+ var excludeSet = new HashSet { HeldItem };
+ var heldItemGrid = HeldItem.GetNodeOrNull(nameof(Grid));
+ heldItemGrid?.AddItemsRecursively(excludeSet);
+
+ // Cast a ray and make sure it hit something.
+ if (RayToMouseCursor(Mask, excludeSet) is not RayResult ray) return null;
+
+ // Find a grid to place against, which will be either the grid belonging
+ // to the item the ray intersected, or the grid said item is placed upon.
+ var grid = ray.Collider.GetNodeOrNull(nameof(Grid))
+ ?? ray.Collider.GetParentOrNull();
+ if (grid == null) return null; // No suitable grid found.
+
+ var inverseTransform = grid.GlobalTransform.AffineInverse();
+ var inverseBasis = grid.GlobalBasis.Inverse();
+
+ var pos = inverseTransform * ray.Position;
+ var normal = inverseBasis * ray.Normal;
+
+ if (!grid.CanPlaceAgainst(itemToPlace, pos, normal)) return null;
+
+ var transform = new Transform3D(inverseBasis * itemToPlace.GlobalBasis, pos);
+ transform = grid.Snap(transform, normal, itemToPlace);
+ var isFree = grid.CanPlaceAt(itemToPlace, transform);
+
+ return (grid, transform, isFree);
+ }
+
record class RayResult(CollisionObject3D Collider, Vector3 Position, Vector3 Normal);
- RayResult RayToMouseCursor(PhysicsLayer collisionMask)
+ RayResult RayToMouseCursor(PhysicsLayer collisionMask, IEnumerable excluded = null)
{
var camera = _player.Camera.Camera;
var mouse = GetViewport().GetMousePosition();
@@ -153,12 +171,10 @@ public partial class PickupController : Node3D
var query = PhysicsRayQueryParameters3D.Create(from, to);
query.CollisionMask = (uint)collisionMask;
query.CollideWithAreas = true;
- // Exclude the `CurrentItem` from collision checking if it's being held.
- query.Exclude = HasItemsHeld ? [ HeldItem.GetRid() ] : [];
+ query.Exclude = new((excluded ?? []).Select(obj => obj.GetRid()));
var result = GetWorld3D().DirectSpaceState.IntersectRay(query);
return (result.Count > 0) ? new(
- // FIXME: Unable to cast object of type 'ReplacePalette' to type 'Godot.CollisionObject3D'.
result["collider"].As(),
(Vector3)result["position"],
(Vector3)result["normal"]
diff --git a/scenes/workshop.tscn b/scenes/workshop.tscn
index 22720e8..13c9f80 100644
--- a/scenes/workshop.tscn
+++ b/scenes/workshop.tscn
@@ -109,13 +109,13 @@ transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 0.775,
[node name="Cutting Board" parent="Objects/Table/Grid" instance=ExtResource("5_hj6pf")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.025, 0.2)
-[node name="Bolt" parent="Objects/Table/Grid/Cutting Board/Grid" index="1" instance=ExtResource("5_r6ljd")]
+[node name="Bolt" parent="Objects/Table/Grid/Cutting Board/Grid" index="0" instance=ExtResource("5_r6ljd")]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.1, 0.025, -0.0250001)
-[node name="Bolt2" parent="Objects/Table/Grid/Cutting Board/Grid" index="2" instance=ExtResource("5_r6ljd")]
+[node name="Bolt2" parent="Objects/Table/Grid/Cutting Board/Grid" index="1" instance=ExtResource("5_r6ljd")]
transform = Transform3D(-1, 8.74228e-08, 3.82137e-15, 0, -4.37114e-08, 1, 8.74228e-08, 1, 4.37114e-08, 0.15, 0.025, -0.0250001)
-[node name="Bolt3" parent="Objects/Table/Grid/Cutting Board/Grid" index="3" instance=ExtResource("5_r6ljd")]
+[node name="Bolt3" parent="Objects/Table/Grid/Cutting Board/Grid" index="2" instance=ExtResource("5_r6ljd")]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.2, 0.025, -0.0250001)
[node name="Small Box" parent="Objects/Table/Grid" instance=ExtResource("10_kgm3d")]