diff --git a/assets/shaders/ReplacePalette.cs b/assets/shaders/ReplacePalette.cs
index f182cc7..d133c40 100644
--- a/assets/shaders/ReplacePalette.cs
+++ b/assets/shaders/ReplacePalette.cs
@@ -1,3 +1,4 @@
+// FIXME: Unable to cast object of type 'ReplacePalette' to type 'Godot.CollisionObject3D'.
public partial class ReplacePalette : Node3D
{
readonly struct TextureInfo(float min, float max)
diff --git a/objects/Interactable.cs b/objects/Interactable.cs
new file mode 100644
index 0000000..b568f57
--- /dev/null
+++ b/objects/Interactable.cs
@@ -0,0 +1,110 @@
+[Tool]
+public partial class Interactable : RigidBody3D
+{
+ PackedScene _modelScene;
+ [Export] public PackedScene ModelScene {
+ get => _modelScene;
+ set => _modelScene = OnModelSceneChanged(value);
+ }
+
+ Vector3I _gridSize;
+ /// Get the size of this item in grid units.
+ [Export] public Vector3I GridSize {
+ get => _gridSize;
+ set => _gridSize = OnGridSizeChanged(value);
+ }
+
+ public override void _Ready()
+ {
+ }
+
+ public override void _Process(double delta)
+ {
+ }
+
+ // FIXME: Only change this if ModelScene is actually changed, not when loaded.
+ PackedScene OnModelSceneChanged(PackedScene value)
+ {
+ if (GetNodeOrNull("_Model") is Node oldModel) {
+ RemoveChild(oldModel);
+ oldModel.QueueFree();
+ }
+
+ // Remove any previously added `CollisionShape3D` nodes.
+ foreach (var child in GetChildren(true).OfType()) {
+ RemoveChild(child);
+ child.QueueFree();
+ }
+
+ if (value is PackedScene scene) {
+ var model = scene.Instantiate();
+ model.Name = "_Model";
+
+ var numShapes = 0;
+ var min = Vector3.Zero;
+ var max = Vector3.Zero;
+
+ // Find all the `StaticBody3D` nodes in the model and parent
+ // their `CollisionShape3D` children to the this `RigidBody3D`.
+ // Required because shapes must be immediate children of the body.
+ // See: https://github.com/godotengine/godot-proposals/issues/535
+ // https://github.com/godotengine/godot/pull/77937
+ foreach (var body in model.FindChildren("*", "StaticBody3D").Cast()) {
+ body.GetParent().RemoveChild(body);
+ body.QueueFree();
+
+ foreach (var shape in body.GetChildren().OfType()) {
+ // Not unsetting the owner results in this warning:
+ // "Adding 'CollisionShape3D' as child to 'Interactable' will make owner '...' inconsistent."
+ shape.Owner = null;
+
+ body.RemoveChild(shape);
+ shape.Name = $"_{nameof(CollisionShape3D)}_{numShapes + 1}";
+ AddChild(shape, false, InternalMode.Front);
+ // shape.Owner = this;
+ numShapes++;
+
+ // Finds the axis-aligned boundary of all collision shapes.
+ // This assumes that the shape has an identity transformation.
+ var vertices = (shape.Shape as ConvexPolygonShape3D)?.Points
+ ?? (shape.Shape as ConcavePolygonShape3D)?.Data
+ ?? throw new Exception("Shape must be either convex or concave");
+ foreach (var vert in vertices) {
+ min = new(Min(min.X, vert.X), Min(min.Y, vert.Y), Min(min.Z, vert.Z));
+ max = new(Max(max.X, vert.X), Max(max.Y, vert.Y), Max(max.Z, vert.Z));
+ }
+ }
+ }
+
+ AddChild(model, false, InternalMode.Front);
+
+ // Set the grid size based on the boundary of all collision shapes.
+ GridSize = (Vector3I)((max - min).Snapped(Epsilon) / Grid.StepSize).Ceil();
+ } else {
+ GridSize = Vector3I.Zero;
+ }
+
+ return value;
+ }
+
+ Vector3I OnGridSizeChanged(Vector3I value)
+ {
+ if (GetNodeOrNull("_GridArea") is Node oldGridArea) {
+ RemoveChild(oldGridArea);
+ oldGridArea.QueueFree();
+ }
+
+ if (value.X > 0 && value.Y > 0 && value.Z > 0) {
+ var gridArea = new Area3D();
+ gridArea.Name = "_GridArea";
+
+ var shape = new CollisionShape3D();
+ shape.Shape = new BoxShape3D { Size = (Vector3)value * Grid.StepSize };
+ gridArea.AddChild(shape);
+
+ AddChild(gridArea, false, InternalMode.Front);
+ }
+
+ return value;
+ }
+}
diff --git a/objects/Item.cs b/objects/Item.cs
index f5a4280..aba23b9 100644
--- a/objects/Item.cs
+++ b/objects/Item.cs
@@ -1,5 +1,3 @@
-using Godot.NativeInterop;
-
public partial class Item : RigidBody3D
{
public MultiplayerSynchronizer Sync { get; internal set; }
@@ -18,8 +16,11 @@ public partial class Item : RigidBody3D
public override void _Ready()
{
// Set the collision properties here so we don't have to specify them in each item scene separately.
- CollisionLayer = (uint)(PhysicsLayer.Item | PhysicsLayer.Pickup);
- CollisionMask = (uint)(PhysicsLayer.Static | PhysicsLayer.Dynamic | PhysicsLayer.Player | PhysicsLayer.Item);
+ CollisionLayer = (uint)(PhysicsLayer.Small | PhysicsLayer.Interact | PhysicsLayer.Placable);
+ CollisionMask = (uint)(PhysicsLayer.Static | PhysicsLayer.Dynamic | PhysicsLayer.Small | PhysicsLayer.Body);
+
+ // TODO: Create a separate area for .Placable.
+ // Though gotta keep in mind that for some objects it needs to be same as physics collider, like small box.
// TODO: Find a better way to better import models with colliders.
// TODO: Import items dynamically at runtime?
diff --git a/objects/interactable.tscn b/objects/interactable.tscn
new file mode 100644
index 0000000..e2a4e0b
--- /dev/null
+++ b/objects/interactable.tscn
@@ -0,0 +1,10 @@
+[gd_scene load_steps=3 format=3 uid="uid://d4l5bguk01qqr"]
+
+[ext_resource type="Script" path="res://objects/Interactable.cs" id="1_vnawe"]
+[ext_resource type="PackedScene" uid="uid://dh18opmr8mjet" path="res://assets/models/plank.blend" id="2_cvuqp"]
+
+[node name="Interactable" type="RigidBody3D"]
+script = ExtResource("1_vnawe")
+ModelScene = ExtResource("2_cvuqp")
+GridSize = Vector3i(16, 1, 3)
+metadata/_edit_group_ = true
diff --git a/player/PickupController.cs b/player/PickupController.cs
index 84e5c6e..cc2631d 100644
--- a/player/PickupController.cs
+++ b/player/PickupController.cs
@@ -73,7 +73,7 @@ public partial class PickupController : Node3D
_targetedGrid = null;
}
} else {
- var interactable = RayToMouseCursor(PhysicsLayer.Pickup)?.Collider;
+ var interactable = RayToMouseCursor(PhysicsLayer.Interact)?.Collider;
// Remove the outline from the previously looked-at item.
if (TargetedItem != null) SetMeshLayerOutline(TargetedItem.Model, OutlineMode.Disable);
@@ -122,22 +122,16 @@ public partial class PickupController : Node3D
(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;
+ // This ray will be blocked by static and dynamic objects.
+ const PhysicsLayer MASK = PhysicsLayer.Static
+ | PhysicsLayer.Dynamic
+ | PhysicsLayer.Placable;
+ 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.
diff --git a/player/player.tscn b/player/player.tscn
index 5757b7b..639d433 100644
--- a/player/player.tscn
+++ b/player/player.tscn
@@ -103,7 +103,7 @@ node_connections = [&"is_holding", 0, &"walk_state", &"is_holding", 1, &"hold",
[node name="Player" type="CharacterBody3D"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.75, 0)
-collision_layer = 4
+collision_layer = 8
collision_mask = 3
script = ExtResource("1_a0mas")
@@ -112,15 +112,15 @@ shape = SubResource("CapsuleShape3D_h1mfd")
[node name="WorkshopTracker" type="Area3D" parent="."]
collision_layer = 0
-collision_mask = 1024
+collision_mask = 256
monitorable = false
[node name="CollisionShape3D" type="CollisionShape3D" parent="WorkshopTracker"]
shape = SubResource("BoxShape3D_gtuvx")
[node name="PushbackArea" type="Area3D" parent="."]
-collision_layer = 4
-collision_mask = 4
+collision_layer = 8
+collision_mask = 8
script = ExtResource("2_almik")
[node name="CollisionShape3D" type="CollisionShape3D" parent="PushbackArea"]
diff --git a/project.godot b/project.godot
index fcab4a2..2fb2b56 100644
--- a/project.godot
+++ b/project.godot
@@ -85,11 +85,11 @@ interact_place={
3d_render/layer_2="Outline"
3d_physics/layer_1="Static"
3d_physics/layer_2="Dynamic"
-3d_physics/layer_3="Player"
-3d_physics/layer_4="Item"
-3d_physics/layer_9="Pickup"
-3d_physics/layer_10="Place"
-3d_physics/layer_11="Areas"
+3d_physics/layer_3="Small"
+3d_physics/layer_4="Body"
+3d_physics/layer_9="Zone"
+3d_physics/layer_10="Interact"
+3d_physics/layer_11="Placable"
[rendering]
diff --git a/scenes/workshop.tscn b/scenes/workshop.tscn
index 13c9f80..9423d32 100644
--- a/scenes/workshop.tscn
+++ b/scenes/workshop.tscn
@@ -81,7 +81,6 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4, 0.5, -4)
[node name="Table" type="StaticBody3D" parent="Objects"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -4)
-collision_layer = 2
collision_mask = 0
[node name="Grid" parent="Objects/Table" instance=ExtResource("6_okibm")]
@@ -151,7 +150,6 @@ skeleton = NodePath("../MeshInstance3D")
[node name="Table2" type="StaticBody3D" parent="Objects"]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 1.3, -4.6)
-collision_layer = 2
collision_mask = 0
[node name="Grid" parent="Objects/Table2" instance=ExtResource("6_okibm")]
diff --git a/scripts/globals/PhysicsLayer.cs b/scripts/globals/PhysicsLayer.cs
index 9bf583e..f980a9e 100644
--- a/scripts/globals/PhysicsLayer.cs
+++ b/scripts/globals/PhysicsLayer.cs
@@ -1,20 +1,19 @@
[Flags]
public enum PhysicsLayer : uint
{
- /// Objects that are part of the scene, and never move.
- Static = 1 << 0,
- /// Objects players collide with, but may move.
- Dynamic = 1 << 1,
- /// Any players, both local and remote.
- Player = 1 << 2,
- /// Small objects players don't collide with, but collide with players.
- Item = 1 << 3,
+ /// Physical objects that are part of the scene and won't move.
+ Static = 0b0000_0000_0000_0001,
+ /// Physical objects that may move. Player collide with these.
+ Dynamic = 0b0000_0000_0000_0010,
+ /// Small physical objects that may move. Players don't collide with these.
+ Small = 0b0000_0000_0000_0100,
+ /// Physical bodies such as those of players.
+ Body = 0b0000_0000_0000_1000,
- /// Objects that may be picked up.
- Pickup = 1 << 8,
- /// Objects that other objects may be placed against.
- Place = 1 << 9,
-
- /// Various areas, such as workshops.
- Areas = 1 << 10,
+ /// Areas in the game that can be entered or left by players.
+ Zone = 0b0000_0001_0000_0000,
+ /// Objects that may be ray-traced to interact with them.
+ Interact = 0b0000_0010_0000_0000,
+ /// Objects that others may be placed again, grid-aligned bounding box.
+ Placable = 0b0000_0100_0000_0000,
}