From 585768e48e6bd5eb899676f35a475203589c7ebf Mon Sep 17 00:00:00 2001 From: copygirl Date: Tue, 26 Dec 2023 18:10:28 +0100 Subject: [PATCH] Add placement preview --- objects/Item.cs | 4 +-- objects/crate.tscn | 2 +- player/PickupController.cs | 62 +++++++++++++++++++++++++++++--------- player/player.tscn | 2 +- scenes/workshop.tscn | 10 ++++-- 5 files changed, 60 insertions(+), 20 deletions(-) diff --git a/objects/Item.cs b/objects/Item.cs index 0541461..0a755a9 100644 --- a/objects/Item.cs +++ b/objects/Item.cs @@ -2,6 +2,6 @@ public partial class Item : StaticBody3D { [Export] public Vector3I Size { get; set; } - public virtual MeshInstance3D Mesh - => GetNode("MeshInstance3D"); + public virtual Node3D Model + => GetNode("Model"); } diff --git a/objects/crate.tscn b/objects/crate.tscn index 80e4ff4..1d0871b 100644 --- a/objects/crate.tscn +++ b/objects/crate.tscn @@ -15,5 +15,5 @@ Palette = ExtResource("2_n415g") transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.125, 0) shape = SubResource("BoxShape3D_to3fn") -[node name="blockbench_export" parent="." instance=ExtResource("3_tncd6")] +[node name="Model" parent="." instance=ExtResource("3_tncd6")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.5, 0) diff --git a/player/PickupController.cs b/player/PickupController.cs index dfc2023..e16844a 100644 --- a/player/PickupController.cs +++ b/player/PickupController.cs @@ -2,7 +2,7 @@ public partial class PickupController : Node3D { public Item CurrentItem { get; private set; } public bool HasItemsHeld => GetChildCount() > 0; - MeshInstance3D _placementPreview; + Node3D _placementPreview; [Export] public Camera3D Camera { get; set; } [Export] public float PickupDistance { get; set; } = 2.0f; @@ -24,11 +24,19 @@ public partial class PickupController : Node3D if (@event.IsActionPressed("interact_pickup")) { if (!HasItemsHeld) { + // Create clone of the item's model, use it as placement preview. + _placementPreview = (Node3D)CurrentItem.Model.Duplicate(0); + _placementPreview.Name = "PlacementPreview"; + _placementPreview.TopLevel = true; + _placementPreview.Visible = false; + SetMeshLayerOutline(_placementPreview, OutlineMode.Exclusive); + AddChild(_placementPreview); + // Parent item to the `PickupController`. var prevRot = CurrentItem.GlobalRotation; CurrentItem.GetParent().RemoveChild(CurrentItem); AddChild(CurrentItem); - CurrentItem.Mesh.Layers &= (uint)~RenderLayer.Outline; + SetMeshLayerOutline(CurrentItem.Model, OutlineMode.Disable); CurrentItem.Position = Vector3.Zero; CurrentItem.GlobalRotation = prevRot; CurrentItem.CollisionLayer &= (uint)~PhysicsLayer.Static; @@ -44,7 +52,14 @@ public partial class PickupController : Node3D // CurrentItem.Freeze = false; RemoveChild(CurrentItem); _world.AddChild(CurrentItem); - CurrentItem.GlobalTransform = prevTransform; + + CurrentItem.GlobalTransform = _placementPreview.Visible + ? _placementPreview.GlobalTransform + : prevTransform; + + RemoveChild(_placementPreview); + _placementPreview.QueueFree(); + _placementPreview = null; GetViewport().SetInputAsHandled(); } @@ -59,28 +74,31 @@ public partial class PickupController : Node3D if (HasItemsHeld) { if ((RayToMouseCursor() is RayResult ray) && (ray.Collider is Grid)) { // Snao rotation to nearest axis. - // FIXME: This needs to snap to the - // var globalRot = CurrentItem.GlobalRotation; - // globalRot.X = Snapped(globalRot.X, Tau / 4); - // globalRot.Y = Snapped(globalRot.Y, Tau / 4); - // globalRot.Z = Snapped(globalRot.Z, Tau / 4); - // CurrentItem.GlobalRotation = globalRot; + // FIXME: This needs to snap relative to the grid rotation. + var globalRot = CurrentItem.GlobalRotation; + globalRot.X = Snapped(globalRot.X, Tau / 4); + globalRot.Y = Snapped(globalRot.Y, Tau / 4); + globalRot.Z = Snapped(globalRot.Z, Tau / 4); + _placementPreview.GlobalRotation = globalRot; // Snap the position to the grid. var halfSize = (Vector3)CurrentItem.Size * Grid.StepSize / 2; - var pos = ray.Position + halfSize * (ray.Normal * CurrentItem.GlobalTransform.Basis); + var pos = ray.Position + halfSize * (ray.Normal * _placementPreview.GlobalTransform.Basis); pos = pos.Snapped(Grid.StepSize * Vector3.One); // FIXME: This does global snapping only - CurrentItem.GlobalPosition = pos; + _placementPreview.GlobalPosition = pos; + _placementPreview.Visible = true; + } else { + _placementPreview.Visible = false; } } else { var interactable = RayToMouseCursor()?.Collider; // Remove the outline from the previously looked-at item. - if (CurrentItem != null) CurrentItem.Mesh.Layers &= (uint)~RenderLayer.Outline; + if (CurrentItem != null) SetMeshLayerOutline(CurrentItem.Model, OutlineMode.Disable); // If the ray hits anything and the object hit is an item, set it as current. CurrentItem = interactable as Item; // Add the outline to the currently looked-at item. - if (CurrentItem != null) CurrentItem.Mesh.Layers |= (uint)RenderLayer.Outline; + if (CurrentItem != null) SetMeshLayerOutline(CurrentItem.Model, OutlineMode.Enable); } } @@ -89,7 +107,11 @@ public partial class PickupController : Node3D if (CurrentItem == null) return; if (!IsInstanceValid(CurrentItem)) { CurrentItem = null; - _placementPreview?.QueueFree(); + if (_placementPreview != null) { + RemoveChild(_placementPreview); + _placementPreview.QueueFree(); + _placementPreview = null; + } } } @@ -115,4 +137,16 @@ public partial class PickupController : Node3D public Vector3 Position { get; } = (Vector3)dict["position"]; public Vector3 Normal { get; } = (Vector3)dict["normal"]; } + + enum OutlineMode { Disable, Enable, Exclusive } + static void SetMeshLayerOutline(Node3D parent, OutlineMode mode) + { + var children = parent.FindChildren("*", "MeshInstance3D", owned: false); + foreach (var mesh in children.Cast()) + switch (mode) { + case OutlineMode.Disable: mesh.Layers &= ~(uint)RenderLayer.Outline; break; + case OutlineMode.Enable: mesh.Layers |= (uint)RenderLayer.Outline; break; + case OutlineMode.Exclusive: mesh.Layers = (uint)RenderLayer.Outline; break; + } + } } diff --git a/player/player.tscn b/player/player.tscn index 357147b..2d9622b 100644 --- a/player/player.tscn +++ b/player/player.tscn @@ -1115,6 +1115,6 @@ use_external_skeleton = true external_skeleton = NodePath("../../../../../../../Model/Skeleton/Skeleton3D") [node name="PickupController" type="Node3D" parent="." node_paths=PackedStringArray("Camera")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.35, -0.45) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.4, -0.45) script = ExtResource("2_ns2pe") Camera = NodePath("../AnimationController/Root/LowerBody/UpperBody/Neck/Head/Camera") diff --git a/scenes/workshop.tscn b/scenes/workshop.tscn index f731224..27a446a 100644 --- a/scenes/workshop.tscn +++ b/scenes/workshop.tscn @@ -72,8 +72,11 @@ Size = Vector3i(2, 2, 4) [node name="CollisionShape3D" type="CollisionShape3D" parent="Table/SmallBox"] shape = SubResource("BoxShape3D_hkc0l") -[node name="MeshInstance3D" type="MeshInstance3D" parent="Table/SmallBox"] +[node name="Model" type="Node3D" parent="Table/SmallBox"] + +[node name="MeshInstance3D" type="MeshInstance3D" parent="Table/SmallBox/Model"] mesh = SubResource("BoxMesh_3qwx3") +skeleton = NodePath("../..") [node name="SmallBox2" type="StaticBody3D" parent="Table"] transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 0.251316, 1.06737, 0.127615) @@ -85,8 +88,11 @@ Size = Vector3i(2, 2, 4) [node name="CollisionShape3D" type="CollisionShape3D" parent="Table/SmallBox2"] shape = SubResource("BoxShape3D_hkc0l") -[node name="MeshInstance3D" type="MeshInstance3D" parent="Table/SmallBox2"] +[node name="Model" type="Node3D" parent="Table/SmallBox2"] + +[node name="MeshInstance3D" type="MeshInstance3D" parent="Table/SmallBox2/Model"] mesh = SubResource("BoxMesh_3qwx3") +skeleton = NodePath("../..") [node name="Grid" type="Area3D" parent="Table"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)