-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a new snap path control that allows you to snap objects on a rail.
- Loading branch information
Showing
12 changed files
with
405 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
@tool | ||
class_name XRToolsSnapPath | ||
extends XRToolsSnapZone | ||
|
||
|
||
## An [XRToolsSnapZone] that allows [XRToolsPickable] to be placed along a | ||
## child [Path3D] node. They can either be placed along any point in the curve | ||
## or at discrete intervals by setting "snap_interval" above '0.0'. | ||
## | ||
## Note: Attached [XRToolsPickable]s will face the +Z axis. | ||
|
||
|
||
## Real world distance between intervals in Meters. | ||
## Enabled when not 0 | ||
@export var snap_interval := 0.0: | ||
set(v): snap_interval = absf(v) | ||
|
||
@onready var path : Path3D | ||
|
||
|
||
func _ready() -> void: | ||
super._ready() | ||
|
||
for c in get_children(): | ||
if c is Path3D: | ||
path = c | ||
break | ||
|
||
|
||
func has_snap_interval() -> bool: | ||
return !is_equal_approx(snap_interval, 0.0) | ||
|
||
|
||
func _get_configuration_warnings() -> PackedStringArray: | ||
# Check for Path3D child | ||
for c in get_children(): | ||
if c is Path3D: | ||
path = c | ||
return[] | ||
return["This node requires a Path3D child node to define its shape."] | ||
|
||
|
||
# Called when a target in our grab area is dropped | ||
func _on_target_dropped(target: Node3D) -> void: | ||
# Skip if invalid | ||
if !enabled or !path or !target.can_pick_up(self) or \ | ||
!is_instance_valid(target) or \ | ||
is_instance_valid(picked_up_object): | ||
return | ||
|
||
# Make a zone that will destruct once its object has left | ||
var zone = _make_temp_zone() | ||
var offset = _find_offset(path, target.global_position) | ||
|
||
# if snap guide | ||
if _has_snap_guide(target): | ||
# comply with guide | ||
offset = _find_closest_offset_with_length(path.curve, offset, _get_snap_guide(target).length) | ||
|
||
# too large to place on path | ||
if is_equal_approx(offset, -1.0): | ||
return | ||
|
||
# if snap_interval has been set, use it | ||
if has_snap_interval(): | ||
offset = snappedf(offset, snap_interval) | ||
|
||
# set position | ||
zone.position = path.curve.sample_baked(offset) | ||
|
||
# Add zone as a child | ||
path.add_child(zone) | ||
zone.owner = path | ||
|
||
# Connect self-destruct with lambda | ||
zone.has_dropped.connect(func(): zone.queue_free(), Object.ConnectFlags.CONNECT_ONE_SHOT) | ||
|
||
# Use Pickable's Shapes as our Shapes | ||
for c in target.get_children(): | ||
if c is CollisionShape3D: | ||
PhysicsServer3D.area_add_shape(zone.get_rid(), c.shape.get_rid(), c.transform) | ||
|
||
# Force pickup | ||
zone.pick_up_object(target) | ||
|
||
|
||
# Make a zone that dies on dropping objects | ||
func _make_temp_zone(): | ||
var zone = XRToolsSnapZone.new() | ||
|
||
# connect lambda to play stash sounds when temp zone picks up | ||
if has_node("AudioStreamPlayer3D"): | ||
zone.has_picked_up.connect(\ | ||
func(object):\ | ||
$AudioStreamPlayer3D.stream = stash_sound;\ | ||
$AudioStreamPlayer3D.play()\ | ||
) | ||
|
||
# XRToolsSnapZone manaul copy | ||
zone.enabled = true | ||
zone.stash_sound = stash_sound | ||
zone.grab_distance = grab_distance | ||
zone.snap_mode = snap_mode | ||
zone.snap_require = snap_require | ||
zone.snap_exclude = snap_exclude | ||
zone.grab_require = grab_require | ||
zone.grab_exclude = grab_exclude | ||
zone.initial_object = NodePath() | ||
|
||
# CollisionObject3D manual copy | ||
zone.disable_mode = disable_mode | ||
zone.collision_layer = collision_layer | ||
zone.collision_mask = collision_mask | ||
zone.collision_priority = collision_priority | ||
|
||
return zone | ||
|
||
|
||
func _has_snap_guide(target: Node3D) -> bool: | ||
for c in target.get_children(): | ||
if c is XRToolsSnapPathGuide: | ||
return true | ||
return false | ||
|
||
|
||
func _get_snap_guide(target: Node3D) -> Node3D: | ||
for c in target.get_children(): | ||
if c is XRToolsSnapPathGuide: | ||
return c | ||
return null | ||
|
||
|
||
# Returns -1 if invalid | ||
# _offset should be in _curve's local coordinates | ||
func _find_closest_offset_with_length(_curve: Curve3D, _offset: float, _length: float) -> float: | ||
# p1 and p2 are the object's start and end respectively | ||
var p1 = _offset | ||
var p2 = _offset - _length | ||
|
||
# a _curve's final point is its end, aka the furthest 'forward', which is why it is p1 | ||
# path_p1 and path_p2 are the curve's start and end respectively | ||
var path_p1 := _curve.get_closest_offset(_curve.get_point_position(_curve.point_count-1)) | ||
var path_p2 := _curve.get_closest_offset(_curve.get_point_position(0)) | ||
|
||
# if at front (or beyond) | ||
if is_equal_approx(p1, path_p1): | ||
# if too large | ||
if p2 < path_p2: | ||
return -1 | ||
# if too far back | ||
elif p2 < path_p2: | ||
# check if snapping will over-extend | ||
if has_snap_interval(): | ||
# snapping p1_new may move it further back, and out-of-bounds | ||
# larger snaps move the object further forward | ||
var p1_new = path_p2 + _length | ||
var ideal_snap = snappedf(p1_new, snap_interval) | ||
var more_snap = _snappedf_up(p1_new, snap_interval) | ||
# if ideal snap fits, take that | ||
if ideal_snap >= p1_new: | ||
return ideal_snap | ||
return more_snap | ||
return path_p2 + _length | ||
# otherwise: within bounds | ||
return p1 | ||
|
||
|
||
## Round 'x' upwards to the nearest 'step' | ||
func _snappedf_up(x, step) -> float: | ||
return step * ceilf(x / step) | ||
|
||
|
||
func _find_offset(_path: Path3D, _global_position: Vector3) -> float: | ||
# transform target pos to local space | ||
var local_pos: Vector3 = _global_position * _path.global_transform | ||
return _path.curve.get_closest_offset(local_pos) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
[gd_scene load_steps=4 format=3 uid="uid://dsstvanwd58r0"] | ||
|
||
[ext_resource type="Script" path="res://addons/godot-xr-tools/objects/snap_path.gd" id="1_m211o"] | ||
|
||
[sub_resource type="BoxShape3D" id="BoxShape3D_pik8g"] | ||
size = Vector3(0.1, 0.1, 1) | ||
|
||
[sub_resource type="Curve3D" id="Curve3D_w68am"] | ||
_data = { | ||
"points": PackedVector3Array(0, 0, 0, 0, 0, 0, 0, 0, -0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0.5), | ||
"tilts": PackedFloat32Array(0, 0) | ||
} | ||
point_count = 2 | ||
|
||
[node name="SnapPath" type="Area3D"] | ||
collision_layer = 4 | ||
collision_mask = 65540 | ||
script = ExtResource("1_m211o") | ||
|
||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."] | ||
shape = SubResource("BoxShape3D_pik8g") | ||
|
||
[node name="AudioStreamPlayer3D" type="AudioStreamPlayer3D" parent="."] | ||
unit_size = 3.0 | ||
max_db = 1.0 | ||
max_distance = 100.0 | ||
|
||
[node name="Path3D" type="Path3D" parent="."] | ||
curve = SubResource("Curve3D_w68am") | ||
|
||
[connection signal="body_entered" from="." to="." method="_on_snap_zone_body_entered"] | ||
[connection signal="body_exited" from="." to="." method="_on_snap_zone_body_exited"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
@tool | ||
class_name XRToolsSnapPathGuide | ||
extends Marker3D | ||
|
||
|
||
## XRToolsSnapRailGuide depicts a guide for [XRToolsSnapPath] to judge the | ||
## length of an [XRToolsPickable], helping place pickables within its bounds. | ||
## Add as a child node to any [XRToolsPickable], then move negatively along | ||
## the Z-Axis to define a length. | ||
|
||
|
||
var length : float: | ||
get: | ||
return abs(position.z) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,7 @@ | ||
@tool | ||
extends XRToolsPickable | ||
|
||
@export var player_camera : Camera3D: | ||
set(value): | ||
player_camera = value | ||
if is_inside_tree(): | ||
_update_player_camera() | ||
|
||
func _update_player_camera(): | ||
$sniper_rifle/ScopeDisplay.player_camera = player_camera | ||
|
||
# Called when the node enters the scene tree for the first time. | ||
func _ready(): | ||
super._ready() | ||
_update_player_camera() | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Oops, something went wrong.