Improve grid and placement code

- Grid is no longer a collision object,
  instead we just ray-cast against its parent
- Refactored some placement code into
  its own method 'GetValidPlacement'
main
copygirl 6 months ago
parent 8cc15ae68e
commit 32caf91234
  1. 50
      objects/Grid.cs
  2. 5
      objects/grid.tscn
  3. 82
      player/PickupController.cs
  4. 6
      scenes/workshop.tscn

@ -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();
/// <summary> Returns whether the specified item is contained in this or any nested grids. </summary>
@ -44,6 +39,21 @@ public partial class Grid : Area3D
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);
@ -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()
{

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

@ -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<CollisionObject3D> { HeldItem };
var heldItemGrid = HeldItem.GetNodeOrNull<Grid>(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<Grid>(nameof(Grid))
?? ray.Collider.GetParentOrNull<Grid>();
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<CollisionObject3D> 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<CollisionObject3D>(),
(Vector3)result["position"],
(Vector3)result["normal"]

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

Loading…
Cancel
Save