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")]