init
This commit is contained in:
235
addons/godot-xr-tools/objects/grab_points/grab.gd
Normal file
235
addons/godot-xr-tools/objects/grab_points/grab.gd
Normal file
@@ -0,0 +1,235 @@
|
||||
class_name Grab
|
||||
extends Grabber
|
||||
|
||||
|
||||
## Grab Class
|
||||
##
|
||||
## This class encodes information about an active grab. Additionally it applies
|
||||
## hand poses and collision exceptions as appropriate.
|
||||
|
||||
|
||||
## Priority for grip poses
|
||||
const GRIP_POSE_PRIORITY := 100
|
||||
|
||||
## Priority for grip targeting
|
||||
const GRIP_TARGET_PRIORITY := 100
|
||||
|
||||
|
||||
## Grab target
|
||||
var what : XRToolsPickable
|
||||
|
||||
## Grab point information
|
||||
var point : XRToolsGrabPoint
|
||||
|
||||
## Hand grab point information
|
||||
var hand_point : XRToolsGrabPointHand
|
||||
|
||||
## Grab transform
|
||||
var transform : Transform3D
|
||||
|
||||
## Position drive strength
|
||||
var drive_position : float = 1.0
|
||||
|
||||
## Angle drive strength
|
||||
var drive_angle : float = 1.0
|
||||
|
||||
## Aim drive strength
|
||||
var drive_aim : float = 0.0
|
||||
|
||||
## Has target arrived at grab point
|
||||
var _arrived : bool = false
|
||||
|
||||
## Collision exceptions we manage
|
||||
var _collision_exceptions : Array[RID]
|
||||
|
||||
|
||||
## Initialize the grab
|
||||
func _init(
|
||||
p_grabber : Grabber,
|
||||
p_what : XRToolsPickable,
|
||||
p_point : XRToolsGrabPoint,
|
||||
p_precise : bool) -> void:
|
||||
|
||||
# Copy the grabber information
|
||||
by = p_grabber.by
|
||||
pickup = p_grabber.pickup
|
||||
controller = p_grabber.controller
|
||||
hand = p_grabber.hand
|
||||
collision_hand = p_grabber.collision_hand
|
||||
|
||||
# Set the point
|
||||
what = p_what
|
||||
point = p_point
|
||||
hand_point = p_point as XRToolsGrabPointHand
|
||||
|
||||
# Calculate the grab transform
|
||||
if hand_point:
|
||||
# Get our adjusted grab point (palm position)
|
||||
transform = hand_point.get_palm_transform()
|
||||
elif point:
|
||||
transform = point.transform
|
||||
elif p_precise:
|
||||
transform = p_what.global_transform.affine_inverse() * by.global_transform
|
||||
else:
|
||||
transform = Transform3D.IDENTITY
|
||||
|
||||
# Set the drive parameters
|
||||
if hand_point:
|
||||
drive_position = hand_point.drive_position
|
||||
drive_angle = hand_point.drive_angle
|
||||
drive_aim = hand_point.drive_aim
|
||||
|
||||
# Apply collision exceptions
|
||||
if collision_hand:
|
||||
collision_hand.max_distance_reached.connect(_on_max_distance_reached)
|
||||
_add_collision_exceptions(what)
|
||||
|
||||
|
||||
## Set the target as arrived at the grab-point
|
||||
func set_arrived() -> void:
|
||||
# Ignore if already arrived
|
||||
if _arrived:
|
||||
return
|
||||
|
||||
# Set arrived and apply any hand pose
|
||||
print_verbose("%s> arrived at %s" % [what.name, point])
|
||||
_arrived = true
|
||||
_set_hand_pose()
|
||||
|
||||
# Report the grab
|
||||
print_verbose("%s> grabbed by %s", [what.name, by.name])
|
||||
what.grabbed.emit(what, by)
|
||||
|
||||
|
||||
## Set the grab point
|
||||
func set_grab_point(p_point : XRToolsGrabPoint) -> void:
|
||||
# Skip if no change
|
||||
if p_point == point:
|
||||
return
|
||||
|
||||
# Remove any current pose override
|
||||
_clear_hand_pose()
|
||||
|
||||
# Update the grab point
|
||||
point = p_point
|
||||
hand_point = point as XRToolsGrabPointHand
|
||||
|
||||
# Update the transform
|
||||
if point:
|
||||
# Get our adjusted grab point (palm position)
|
||||
transform = p_point.get_palm_transform()
|
||||
|
||||
# Apply the new hand grab-point settings
|
||||
if hand_point:
|
||||
drive_position = hand_point.drive_position
|
||||
drive_angle = hand_point.drive_angle
|
||||
drive_aim = hand_point.drive_aim
|
||||
|
||||
# Apply any pose overrides
|
||||
if _arrived:
|
||||
_set_hand_pose()
|
||||
|
||||
# Report switch
|
||||
print_verbose("%s> switched grab point to %s", [what.name, point.name])
|
||||
what.released.emit(what, by)
|
||||
what.grabbed.emit(what, by)
|
||||
|
||||
|
||||
## Release the grip
|
||||
func release() -> void:
|
||||
# Clear any hand pose
|
||||
_clear_hand_pose()
|
||||
|
||||
# Remove collision exceptions with a small delay
|
||||
if is_instance_valid(collision_hand) and not _collision_exceptions.is_empty():
|
||||
# Use RIDs instead of the objects directly in case they get freed while
|
||||
# we are waiting for the object to fall away
|
||||
var copy : Array[RID] = _collision_exceptions.duplicate()
|
||||
_collision_exceptions.clear()
|
||||
|
||||
# Delay removing our exceptions to give the object time to fall away
|
||||
collision_hand.get_tree().create_timer(0.5).timeout \
|
||||
.connect(_remove_collision_exceptions \
|
||||
.bind(copy) \
|
||||
.bind(collision_hand.get_rid()))
|
||||
|
||||
# Report the release
|
||||
print_verbose("%s> released by %s", [what.name, by.name])
|
||||
what.released.emit(what, by)
|
||||
|
||||
|
||||
# Hand has moved too far away from object, can no longer hold on to it.
|
||||
func _on_max_distance_reached() -> void:
|
||||
pickup.drop_object()
|
||||
|
||||
|
||||
# Set hand-pose overrides
|
||||
func _set_hand_pose() -> void:
|
||||
# Skip if not hand
|
||||
if not is_instance_valid(hand) or not is_instance_valid(hand_point):
|
||||
return
|
||||
|
||||
# Apply the hand-pose
|
||||
if hand_point.hand_pose:
|
||||
hand.add_pose_override(hand_point, GRIP_POSE_PRIORITY, hand_point.hand_pose)
|
||||
|
||||
# Apply hand snapping
|
||||
if hand_point.snap_hand:
|
||||
hand.add_target_override(hand_point, GRIP_TARGET_PRIORITY)
|
||||
|
||||
|
||||
# Clear any hand-pose overrides
|
||||
func _clear_hand_pose() -> void:
|
||||
# Skip if not hand
|
||||
if not is_instance_valid(hand) or not is_instance_valid(hand_point):
|
||||
return
|
||||
|
||||
# Remove hand-pose
|
||||
hand.remove_pose_override(hand_point)
|
||||
|
||||
# Remove hand snapping
|
||||
hand.remove_target_override(hand_point)
|
||||
|
||||
|
||||
# Add collision exceptions for the grabbed object and any of its children
|
||||
func _add_collision_exceptions(from : Node):
|
||||
if not is_instance_valid(collision_hand):
|
||||
return
|
||||
|
||||
if not is_instance_valid(from):
|
||||
return
|
||||
|
||||
# If this is a physics body, add an exception
|
||||
if from is PhysicsBody3D:
|
||||
# Make sure we don't collide with what we're holding
|
||||
_collision_exceptions.push_back(from.get_rid())
|
||||
PhysicsServer3D.body_add_collision_exception(collision_hand.get_rid(), from.get_rid())
|
||||
PhysicsServer3D.body_add_collision_exception(from.get_rid(), collision_hand.get_rid())
|
||||
|
||||
# Check all children
|
||||
for child in from.get_children():
|
||||
_add_collision_exceptions(child)
|
||||
|
||||
|
||||
# Remove the exceptions in our passed array. We call this with a small delay
|
||||
# to give an object a chance to drop away from the hand before it starts
|
||||
# colliding.
|
||||
# It is possible that another object is picked up in the meanwhile
|
||||
# and we thus fill _collision_exceptions with new content.
|
||||
# Hence using a copy of this list at the time of dropping the object.
|
||||
#
|
||||
# Note, this is static because our grab object gets destroyed before this code gets run.
|
||||
static func _remove_collision_exceptions( \
|
||||
on_collision_hand : RID, \
|
||||
exceptions : Array[RID]):
|
||||
|
||||
# This can be improved by checking if we're still colliding and only
|
||||
# removing those objects from our exception list that are not.
|
||||
# If any are left, we can restart a new timer.
|
||||
# This will also allow us to use a much smaller timer interval
|
||||
|
||||
# For now we'll remove all.
|
||||
|
||||
for body : RID in exceptions:
|
||||
PhysicsServer3D.body_remove_collision_exception(on_collision_hand, body)
|
||||
PhysicsServer3D.body_remove_collision_exception(body, on_collision_hand)
|
||||
1
addons/godot-xr-tools/objects/grab_points/grab.gd.uid
Normal file
1
addons/godot-xr-tools/objects/grab_points/grab.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c6pkfifsm2fy1
|
||||
239
addons/godot-xr-tools/objects/grab_points/grab_driver.gd
Normal file
239
addons/godot-xr-tools/objects/grab_points/grab_driver.gd
Normal file
@@ -0,0 +1,239 @@
|
||||
class_name XRToolsGrabDriver
|
||||
extends RemoteTransform3D
|
||||
|
||||
|
||||
## Grab state
|
||||
enum GrabState {
|
||||
LERP,
|
||||
SNAP,
|
||||
}
|
||||
|
||||
|
||||
## Drive state
|
||||
var state : GrabState = GrabState.SNAP
|
||||
|
||||
## Target pickable
|
||||
var target : XRToolsPickable
|
||||
|
||||
## Primary grab information
|
||||
var primary : Grab = null
|
||||
|
||||
## Secondary grab information
|
||||
var secondary : Grab = null
|
||||
|
||||
## Lerp start position
|
||||
var lerp_start : Transform3D
|
||||
|
||||
## Lerp total duration
|
||||
var lerp_duration : float = 1.0
|
||||
|
||||
## Lerp time
|
||||
var lerp_time : float = 0.0
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _physics_process(delta : float) -> void:
|
||||
# Skip if no primary node
|
||||
if not is_instance_valid(primary):
|
||||
return
|
||||
|
||||
# Set destination from primary grab
|
||||
var destination := primary.by.global_transform * primary.transform.affine_inverse()
|
||||
|
||||
# If present, apply secondary-node contributions
|
||||
if is_instance_valid(secondary):
|
||||
# Calculate lerp coefficients based on drive strengths
|
||||
var position_lerp := _vote(primary.drive_position, secondary.drive_position)
|
||||
var angle_lerp := _vote(primary.drive_angle, secondary.drive_angle)
|
||||
|
||||
# Calculate the transform from secondary grab
|
||||
var x1 := destination
|
||||
var x2 := secondary.by.global_transform * secondary.transform.affine_inverse()
|
||||
|
||||
# Independently lerp the angle and position
|
||||
destination = Transform3D(
|
||||
x1.basis.slerp(x2.basis, angle_lerp),
|
||||
x1.origin.lerp(x2.origin, position_lerp))
|
||||
|
||||
# Test if we need to apply aiming
|
||||
if secondary.drive_aim > 0.0:
|
||||
# Convert destination from global to primary-local
|
||||
destination = primary.by.global_transform.affine_inverse() * destination
|
||||
|
||||
# Calculate the from and to vectors in primary-local space
|
||||
var secondary_from := destination * secondary.transform.origin
|
||||
var secondary_to := primary.by.to_local(secondary.by.global_position)
|
||||
|
||||
# Build shortest arc
|
||||
secondary_from = secondary_from.normalized()
|
||||
secondary_to = secondary_to.normalized()
|
||||
var spherical := Quaternion(secondary_from, secondary_to)
|
||||
|
||||
# Build aim-rotation
|
||||
var rotate := Basis.IDENTITY.slerp(Basis(spherical), secondary.drive_aim)
|
||||
destination = Transform3D(rotate, Vector3.ZERO) * destination
|
||||
|
||||
# Convert destination from primary-local to global
|
||||
destination = primary.by.global_transform * destination
|
||||
|
||||
# Handle update
|
||||
match state:
|
||||
GrabState.LERP:
|
||||
# Progress the lerp
|
||||
lerp_time += delta
|
||||
if lerp_time < lerp_duration:
|
||||
# Interpolate from lerp_start to destination
|
||||
destination = lerp_start.interpolate_with(
|
||||
destination,
|
||||
lerp_time / lerp_duration)
|
||||
else:
|
||||
# Lerp completed
|
||||
state = GrabState.SNAP
|
||||
_update_weight()
|
||||
if primary: primary.set_arrived()
|
||||
if secondary: secondary.set_arrived()
|
||||
|
||||
if global_transform.is_equal_approx(destination):
|
||||
return
|
||||
|
||||
# Apply the destination transform
|
||||
global_transform = destination
|
||||
force_update_transform()
|
||||
if is_instance_valid(target):
|
||||
target.force_update_transform()
|
||||
|
||||
|
||||
## Set the secondary grab point
|
||||
func add_grab(p_grab : Grab) -> void:
|
||||
# Set the secondary grab
|
||||
if p_grab.hand_point and p_grab.hand_point.mode == XRToolsGrabPointHand.Mode.PRIMARY:
|
||||
print_verbose("%s> new primary grab %s" % [target.name, p_grab.by.name])
|
||||
secondary = primary
|
||||
primary = p_grab
|
||||
else:
|
||||
print_verbose("%s> new secondary grab %s" % [target.name, p_grab.by.name])
|
||||
secondary = p_grab
|
||||
|
||||
# If snapped then report arrived at the new grab
|
||||
if state == GrabState.SNAP:
|
||||
_update_weight()
|
||||
p_grab.set_arrived()
|
||||
|
||||
|
||||
## Get the grab information for the grab node
|
||||
func get_grab(by : Node3D) -> Grab:
|
||||
if primary and primary.by == by:
|
||||
return primary
|
||||
|
||||
if secondary and secondary.by == by:
|
||||
return secondary
|
||||
|
||||
return null
|
||||
|
||||
|
||||
func remove_grab(p_grab : Grab) -> void:
|
||||
# Remove the appropriate grab
|
||||
if p_grab == primary:
|
||||
# Remove primary (secondary promoted)
|
||||
print_verbose("%s> %s (primary) released" % [target.name, p_grab.by.name])
|
||||
primary = secondary
|
||||
secondary = null
|
||||
elif p_grab == secondary:
|
||||
# Remove secondary
|
||||
print_verbose("%s> %s (secondary) released" % [target.name, p_grab.by.name])
|
||||
secondary = null
|
||||
|
||||
if state == GrabState.SNAP:
|
||||
_update_weight()
|
||||
|
||||
|
||||
# Discard the driver
|
||||
func discard():
|
||||
remote_path = NodePath()
|
||||
queue_free()
|
||||
|
||||
|
||||
# Create the driver to lerp the target from its current location to the
|
||||
# primary grab-point.
|
||||
static func create_lerp(
|
||||
p_target : Node3D,
|
||||
p_grab : Grab,
|
||||
p_lerp_speed : float) -> XRToolsGrabDriver:
|
||||
|
||||
print_verbose("%s> lerping %s" % [p_target.name, p_grab.by.name])
|
||||
|
||||
# Construct the driver lerping from the current position
|
||||
var driver := XRToolsGrabDriver.new()
|
||||
driver.name = p_target.name + "_driver"
|
||||
driver.top_level = true
|
||||
driver.process_physics_priority = -80
|
||||
driver.state = GrabState.LERP
|
||||
driver.target = p_target
|
||||
driver.primary = p_grab
|
||||
driver.global_transform = p_target.global_transform
|
||||
|
||||
# Calculate the start and duration
|
||||
var end := p_grab.by.global_transform * p_grab.transform
|
||||
var delta := end.origin - p_target.global_position
|
||||
driver.lerp_start = p_target.global_transform
|
||||
driver.lerp_duration = delta.length() / p_lerp_speed
|
||||
|
||||
# Add the driver as a neighbor of the target as RemoteTransform3D nodes
|
||||
# cannot be descendands of the targets they drive.
|
||||
p_target.get_parent().add_child(driver)
|
||||
driver.remote_path = driver.get_path_to(p_target)
|
||||
|
||||
# Return the driver
|
||||
return driver
|
||||
|
||||
|
||||
# Create the driver to instantly snap to the primary grab-point.
|
||||
static func create_snap(
|
||||
p_target : Node3D,
|
||||
p_grab : Grab) -> XRToolsGrabDriver:
|
||||
|
||||
print_verbose("%s> snapping to %s" % [p_target.name, p_grab.by.name])
|
||||
|
||||
# Construct the driver snapped to the held position
|
||||
var driver := XRToolsGrabDriver.new()
|
||||
driver.name = p_target.name + "_driver"
|
||||
driver.top_level = true
|
||||
driver.process_physics_priority = -80
|
||||
driver.state = GrabState.SNAP
|
||||
driver.target = p_target
|
||||
driver.primary = p_grab
|
||||
driver.global_transform = p_grab.by.global_transform * p_grab.transform.affine_inverse()
|
||||
|
||||
# Snapped to grab-point so report arrived
|
||||
p_grab.set_arrived()
|
||||
|
||||
# Add the driver as a neighbor of the target as RemoteTransform3D nodes
|
||||
# cannot be descendands of the targets they drive.
|
||||
p_target.get_parent().add_child(driver)
|
||||
driver.remote_path = driver.get_path_to(p_target)
|
||||
|
||||
driver._update_weight()
|
||||
|
||||
# Return the driver
|
||||
return driver
|
||||
|
||||
|
||||
# Calculate the lerp voting from a to b
|
||||
static func _vote(a : float, b : float) -> float:
|
||||
if a == 0.0 and b == 0.0:
|
||||
return 0.0
|
||||
|
||||
return b / (a + b)
|
||||
|
||||
|
||||
# Update the weight on collision hands
|
||||
func _update_weight():
|
||||
if primary:
|
||||
var weight : float = target.mass
|
||||
if secondary:
|
||||
# Each hand carries half the weight
|
||||
weight = weight / 2.0
|
||||
if secondary.collision_hand:
|
||||
secondary.collision_hand.set_held_weight(weight)
|
||||
|
||||
if primary.collision_hand:
|
||||
primary.collision_hand.set_held_weight(weight)
|
||||
@@ -0,0 +1 @@
|
||||
uid://blacqnxi11y3x
|
||||
47
addons/godot-xr-tools/objects/grab_points/grab_point.gd
Normal file
47
addons/godot-xr-tools/objects/grab_points/grab_point.gd
Normal file
@@ -0,0 +1,47 @@
|
||||
class_name XRToolsGrabPoint
|
||||
extends Marker3D
|
||||
|
||||
|
||||
## XR Tools Grab Point Base Script
|
||||
##
|
||||
## This script is the base for all grab points. Pickable object extending from
|
||||
## [XRToolsPickable] can have numerous grab points to control where the object
|
||||
## is grabbed from.
|
||||
|
||||
|
||||
# Signal emitted when the user presses the action button while holding this grab point
|
||||
signal action_pressed(pickable, grab_point)
|
||||
|
||||
# Signal emitted when the user releases the action button while holding this grab point
|
||||
signal action_released(pickable, grab_point)
|
||||
|
||||
|
||||
## If true, the grab point is enabled for grabbing
|
||||
@export var enabled : bool = true
|
||||
|
||||
|
||||
## Evaluate fitness of the proposed grab, with 0.0 for not allowed.
|
||||
func can_grab(grabber : Node3D, _current : XRToolsGrabPoint) -> float:
|
||||
if not enabled:
|
||||
return 0.0
|
||||
|
||||
# Return the distance-weighted fitness
|
||||
return _weight(grabber)
|
||||
|
||||
|
||||
# Return a distance-weighted fitness weight in the range (0.0 - max]
|
||||
func _weight(grabber : Node3D, max : float = 1.0) -> float:
|
||||
var distance := global_position.distance_to(grabber.global_position)
|
||||
return max / (1.0 + distance)
|
||||
|
||||
|
||||
# action is called when user presses the action button while holding this grab point
|
||||
func action(pickable : XRToolsPickable):
|
||||
# let interested parties know
|
||||
action_pressed.emit(pickable, self)
|
||||
|
||||
|
||||
# action_release is called when user releases the action button while holding this grab point
|
||||
func action_release(pickable : XRToolsPickable):
|
||||
# let interested parties know
|
||||
action_released.emit(pickable, self)
|
||||
@@ -0,0 +1 @@
|
||||
uid://b8uwrf51jd5xm
|
||||
236
addons/godot-xr-tools/objects/grab_points/grab_point_hand.gd
Normal file
236
addons/godot-xr-tools/objects/grab_points/grab_point_hand.gd
Normal file
@@ -0,0 +1,236 @@
|
||||
@tool
|
||||
class_name XRToolsGrabPointHand
|
||||
extends XRToolsGrabPoint
|
||||
|
||||
|
||||
## XR Tools Grab Point Hand Script
|
||||
##
|
||||
## This script allows specifying a grab point for a specific hand. Additionally
|
||||
## the grab point can be used to control the pose of the hand, and to allow the
|
||||
## grab point position to be fine-tuned in the editor.
|
||||
|
||||
|
||||
## Hand for this grab point
|
||||
enum Hand {
|
||||
LEFT, ## Left hand
|
||||
RIGHT, ## Right hand
|
||||
}
|
||||
|
||||
## Grab mode for this grab point
|
||||
enum Mode {
|
||||
GENERAL, ## General grab point
|
||||
PRIMARY, ## Primary-hand grab point
|
||||
SECONDARY ## Secondary-hand grab point
|
||||
}
|
||||
|
||||
## Hand preview option
|
||||
enum PreviewMode {
|
||||
CLOSED, ## Preview hand closed
|
||||
OPEN, ## Preview hand open
|
||||
}
|
||||
|
||||
|
||||
## Left hand scene path (for editor preview)
|
||||
const LEFT_HAND_PATH := "res://addons/godot-xr-tools/hands/scenes/lowpoly/left_hand_low.tscn"
|
||||
|
||||
## Right hand scene path (for editor preview)
|
||||
const RIGHT_HAND_PATH := "res://addons/godot-xr-tools/hands/scenes/lowpoly/right_hand_low.tscn"
|
||||
|
||||
|
||||
## Grab-point handle
|
||||
@export var handle : String
|
||||
|
||||
## Which hand this grab point is for
|
||||
@export var hand : Hand: set = _set_hand
|
||||
|
||||
## Hand grab mode
|
||||
@export var mode : Mode = Mode.GENERAL
|
||||
|
||||
## Snap the hand mesh to the grab-point
|
||||
@export var snap_hand : bool = true
|
||||
|
||||
## Hand pose
|
||||
@export var hand_pose : XRToolsHandPoseSettings: set = _set_hand_pose
|
||||
|
||||
## If true, the hand is shown in the editor
|
||||
@export var editor_preview_mode : PreviewMode = PreviewMode.CLOSED: set = _set_editor_preview_mode
|
||||
|
||||
## How much this grab-point drives the position
|
||||
@export var drive_position : float = 1.0
|
||||
|
||||
## How much this grab-point drives the angle
|
||||
@export var drive_angle : float = 1.0
|
||||
|
||||
## How much this grab-point drives the aim
|
||||
@export var drive_aim : float = 0.0
|
||||
|
||||
## Hand to use for editor preview
|
||||
var _editor_preview_hand : XRToolsHand
|
||||
|
||||
# Adjust the grab point from our old aim positioning, to our palm positioning.
|
||||
func get_palm_transform(global : bool = false) -> Transform3D:
|
||||
var adj_transform : Transform3D = global_transform if global else transform
|
||||
|
||||
# Historically our hands have always been positioned based on our aim,
|
||||
# So we apply our old hardcoded offset, but adjusted for our palm center.
|
||||
var aim_offset := Transform3D()
|
||||
aim_offset.origin = Vector3(-0.02 if hand == Hand.LEFT else 0.02, -0.05, 0.10)
|
||||
adj_transform = adj_transform * aim_offset
|
||||
|
||||
return adj_transform
|
||||
|
||||
|
||||
## Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
# If in the editor then update the preview
|
||||
if Engine.is_editor_hint():
|
||||
_update_editor_preview()
|
||||
|
||||
|
||||
## Test if a grabber can grab by this grab-point
|
||||
func can_grab(grabber : Node3D, current : XRToolsGrabPoint) -> float:
|
||||
# Skip if not enabled
|
||||
if not enabled:
|
||||
return 0.0
|
||||
|
||||
# Verify the hand matches
|
||||
if not _is_correct_hand(grabber):
|
||||
return 0.0
|
||||
|
||||
# Fail if the hand grab is not permitted
|
||||
if not _is_valid_hand_grab(current):
|
||||
return 0.0
|
||||
|
||||
# Get the distance-weighted fitness in the range (0.0 - 0.5], but boost
|
||||
# to [0.5 - 1.0] for valid "specific" grabs.
|
||||
var fitness := _weight(grabber, 0.5)
|
||||
if mode != Mode.GENERAL:
|
||||
fitness += 0.5
|
||||
|
||||
# Return the grab fitness
|
||||
return fitness
|
||||
|
||||
|
||||
func _set_hand(new_value : Hand) -> void:
|
||||
hand = new_value
|
||||
if Engine.is_editor_hint():
|
||||
_update_editor_preview()
|
||||
|
||||
|
||||
func _set_hand_pose(new_value : XRToolsHandPoseSettings) -> void:
|
||||
# Unsubscribe from the old hand-pose changed signal
|
||||
if Engine.is_editor_hint() and hand_pose:
|
||||
hand_pose.changed.disconnect(_update_editor_preview)
|
||||
|
||||
# Save the hand pose
|
||||
hand_pose = new_value
|
||||
|
||||
# Update the editor preview
|
||||
if Engine.is_editor_hint() and hand_pose:
|
||||
hand_pose.changed.connect(_update_editor_preview)
|
||||
_update_editor_preview()
|
||||
|
||||
|
||||
func _set_editor_preview_mode(new_value : PreviewMode) -> void:
|
||||
editor_preview_mode = new_value
|
||||
if Engine.is_editor_hint():
|
||||
_update_editor_preview()
|
||||
|
||||
|
||||
func _update_editor_preview() -> void:
|
||||
# Discard any existing hand model
|
||||
if _editor_preview_hand:
|
||||
remove_child(_editor_preview_hand)
|
||||
_editor_preview_hand.queue_free()
|
||||
_editor_preview_hand = null
|
||||
|
||||
# Pick the hand scene
|
||||
var hand_path := LEFT_HAND_PATH if hand == Hand.LEFT else RIGHT_HAND_PATH
|
||||
var hand_scene : PackedScene = load(hand_path)
|
||||
if !hand_scene:
|
||||
return
|
||||
|
||||
# Construct the model
|
||||
_editor_preview_hand = hand_scene.instantiate()
|
||||
_editor_preview_hand.hand_offset_mode = 4 # Disabled
|
||||
|
||||
# Set the pose
|
||||
if hand_pose:
|
||||
_editor_preview_hand.add_pose_override(self, 0.0, hand_pose)
|
||||
|
||||
# Set the grip override
|
||||
if editor_preview_mode == PreviewMode.CLOSED:
|
||||
_editor_preview_hand.force_grip_trigger(1.0, 1.0)
|
||||
else:
|
||||
_editor_preview_hand.force_grip_trigger(0.0, 0.0)
|
||||
|
||||
# Add the editor-preview hand as a child
|
||||
add_child(_editor_preview_hand, false, Node.INTERNAL_MODE_BACK)
|
||||
|
||||
# Keep this backwards compatible,
|
||||
# position the hand according to the original aim logic
|
||||
var hand_node : Node3D = _editor_preview_hand.get_child(0)
|
||||
if hand_node:
|
||||
var custom_offset := Transform3D()
|
||||
custom_offset.origin = Vector3(-0.03 if hand == Hand.LEFT else 0.03, -0.05, 0.15)
|
||||
hand_node.transform = custom_offset
|
||||
|
||||
|
||||
# Is the grabber for the correct hand
|
||||
func _is_correct_hand(grabber : Node3D) -> bool:
|
||||
# Find the controller
|
||||
var controller := _get_grabber_controller(grabber)
|
||||
if not controller:
|
||||
return false
|
||||
|
||||
# Get the positional tracker
|
||||
var tracker := XRServer.get_tracker(controller.tracker) as XRPositionalTracker
|
||||
|
||||
# If left hand then verify left controller
|
||||
if hand == Hand.LEFT and tracker.hand != XRPositionalTracker.TRACKER_HAND_LEFT:
|
||||
return false
|
||||
|
||||
# If right hand then verify right controller
|
||||
if hand == Hand.RIGHT and tracker.hand != XRPositionalTracker.TRACKER_HAND_RIGHT:
|
||||
return false
|
||||
|
||||
# Controller matches hand
|
||||
return true
|
||||
|
||||
|
||||
# Test if hand grab is permitted
|
||||
func _is_valid_hand_grab(current : XRToolsGrabPoint) -> bool:
|
||||
# Not a valid hand grab if currently held by something other than a hand
|
||||
var current_hand := current as XRToolsGrabPointHand
|
||||
if current and not current_hand:
|
||||
return false
|
||||
|
||||
# Not valid if grabbing the same named handle
|
||||
if handle and current_hand and handle == current_hand.handle:
|
||||
return false
|
||||
|
||||
# Not valid if attempting PRIMARY grab while current is PRIMARY
|
||||
if mode == Mode.PRIMARY and current_hand and current_hand.mode == Mode.PRIMARY:
|
||||
return false
|
||||
|
||||
# Not valid if attempting SECONDARY grab while no current
|
||||
if mode == Mode.SECONDARY and not current_hand:
|
||||
return false
|
||||
|
||||
# Hand is allowed to grab
|
||||
return true
|
||||
|
||||
|
||||
# Get the controller associated with a grabber
|
||||
static func _get_grabber_controller(grabber : Node3D) -> XRController3D:
|
||||
# Ensure the grabber is valid
|
||||
if not is_instance_valid(grabber):
|
||||
return null
|
||||
|
||||
# Ensure the pickup is a function pickup for a controller
|
||||
var pickup := grabber as XRToolsFunctionPickup
|
||||
if not pickup:
|
||||
return null
|
||||
|
||||
# Get the controller associated with the pickup
|
||||
return pickup.get_controller()
|
||||
@@ -0,0 +1 @@
|
||||
uid://dx3swqg2owe4p
|
||||
@@ -0,0 +1,8 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://c25yxb0vt53vc"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dx3swqg2owe4p" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand.gd" id="1"]
|
||||
|
||||
[node name="GrabPointHandLeft" type="Marker3D"]
|
||||
visible = false
|
||||
script = ExtResource("1")
|
||||
hand_offset_mode = null
|
||||
@@ -0,0 +1,8 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://ctw7nbntd5pcj"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dx3swqg2owe4p" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand.gd" id="1"]
|
||||
|
||||
[node name="GrabPointHandRight" type="Marker3D"]
|
||||
visible = false
|
||||
script = ExtResource("1")
|
||||
hand = 1
|
||||
@@ -0,0 +1,17 @@
|
||||
@tool
|
||||
class_name XRToolsGrabPointRedirect
|
||||
extends XRToolsGrabPoint
|
||||
|
||||
|
||||
## Grab point to redirect grabbing to
|
||||
@export var target : XRToolsGrabPoint
|
||||
|
||||
|
||||
## Evaluate fitness of the proposed grab, with 0.0 for not allowed.
|
||||
func can_grab(grabber : Node3D, current : XRToolsGrabPoint) -> float:
|
||||
# Fail if no target
|
||||
if not is_instance_valid(target):
|
||||
return 0.0
|
||||
|
||||
# Consult the target
|
||||
return target.can_grab(grabber, current)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cdrjykm7xjl0n
|
||||
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://ca3daqmpo0tua"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cdrjykm7xjl0n" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_redirect.gd" id="1_vyuch"]
|
||||
|
||||
[node name="GrabPointRedirect" type="Marker3D"]
|
||||
script = ExtResource("1_vyuch")
|
||||
49
addons/godot-xr-tools/objects/grab_points/grab_point_snap.gd
Normal file
49
addons/godot-xr-tools/objects/grab_points/grab_point_snap.gd
Normal file
@@ -0,0 +1,49 @@
|
||||
@tool
|
||||
class_name XRToolsGrabPointSnap
|
||||
extends XRToolsGrabPoint
|
||||
|
||||
|
||||
## XR Tools Grab Point Snap Script
|
||||
##
|
||||
## This script allows specifying a grab point for snap zones. It supports
|
||||
## group-filters if different points are required for different snap zones.
|
||||
|
||||
|
||||
## Require grab-by to be in the specified group
|
||||
@export var require_group : String = ""
|
||||
|
||||
## Deny grab-by if in the specified group
|
||||
@export var exclude_group : String = ""
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
# Add a Position3D child to help editor visibility
|
||||
if Engine.is_editor_hint():
|
||||
add_child(Marker3D.new())
|
||||
|
||||
|
||||
## Evaluate fitness of the proposed grab, with 0.0 for not allowed.
|
||||
func can_grab(grabber : Node3D, current : XRToolsGrabPoint) -> float:
|
||||
# Skip if not enabled or current grab
|
||||
if not enabled or current:
|
||||
return 0.0
|
||||
|
||||
# Ensure the pickup is valid
|
||||
if not is_instance_valid(grabber):
|
||||
return 0.0
|
||||
|
||||
# Ensure the grabber is a snap-zone
|
||||
if not grabber is XRToolsSnapZone:
|
||||
return 0.0
|
||||
|
||||
# Refuse if the grabber is not in the required group
|
||||
if not require_group.is_empty() and not grabber.is_in_group(require_group):
|
||||
return 0.0
|
||||
|
||||
# Refuse if the grabber is in the excluded group
|
||||
if not exclude_group.is_empty() and grabber.is_in_group(exclude_group):
|
||||
return 0.0
|
||||
|
||||
# Return the distance-weighted fitness
|
||||
return _weight(grabber)
|
||||
@@ -0,0 +1 @@
|
||||
uid://dn7grmvmgjiqv
|
||||
@@ -0,0 +1,7 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://dh8grd7s3n8kg"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dn7grmvmgjiqv" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_snap.gd" id="1"]
|
||||
|
||||
[node name="GrabPointSnap" type="Marker3D"]
|
||||
visible = false
|
||||
script = ExtResource("1")
|
||||
32
addons/godot-xr-tools/objects/grab_points/grabber.gd
Normal file
32
addons/godot-xr-tools/objects/grab_points/grabber.gd
Normal file
@@ -0,0 +1,32 @@
|
||||
class_name Grabber
|
||||
|
||||
|
||||
## Grabber Class
|
||||
##
|
||||
## This class contains relevant information for a grabber including any
|
||||
## assocated pickup, controller, and hand nodes.
|
||||
|
||||
|
||||
## Grabber node
|
||||
var by : Node3D
|
||||
|
||||
## Pickup associated with the grabber
|
||||
var pickup : XRToolsFunctionPickup
|
||||
|
||||
## Controller associated with the grabber
|
||||
var controller : XRController3D
|
||||
|
||||
## Hand associated with the grabber
|
||||
var hand : XRToolsHand
|
||||
|
||||
## Collision hand associated with the grabber
|
||||
var collision_hand : XRToolsCollisionHand
|
||||
|
||||
## Initialize the grabber
|
||||
func _init(p_by : Node3D) -> void:
|
||||
by = p_by
|
||||
pickup = p_by as XRToolsFunctionPickup
|
||||
controller = pickup.get_controller() if pickup else null
|
||||
if controller:
|
||||
hand = XRToolsHand.find_instance(controller)
|
||||
collision_hand = XRToolsCollisionHand.find_instance(controller)
|
||||
1
addons/godot-xr-tools/objects/grab_points/grabber.gd.uid
Normal file
1
addons/godot-xr-tools/objects/grab_points/grabber.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bu86qn5rnylas
|
||||
Reference in New Issue
Block a user