Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add finger collisions and pickup collisions to collision hands #690

Merged
merged 1 commit into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions VERSIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
- Add visibility_changed notifications to Viewport2Din3D hosted scenes
- Invisible Viewport2Din3D now disable physics and viewport updates
- Add SnapPath
- Improvements to collision hands so collision shapes of picked up objects
are added and we no longer have hands collide with dropped objects

# 4.3.3
- Fix Viewport2Din3D property forwarding
Expand Down
59 changes: 58 additions & 1 deletion addons/godot-xr-tools/functions/function_pickup.gd
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ const DEFAULT_RANGE_MASK := 0b0000_0000_0000_0000_0000_0000_0000_0100
# Constant for worst-case grab distance
const MAX_GRAB_DISTANCE2: float = 1000000.0

# Class for storing copied collision data
class CopiedCollision extends RefCounted:
var collision_shape : CollisionShape3D
var org_transform : Transform3D

## Pickup enabled property
@export var enabled : bool = true
Expand Down Expand Up @@ -81,11 +85,14 @@ var _grab_area : Area3D
var _grab_collision : CollisionShape3D
var _ranged_area : Area3D
var _ranged_collision : CollisionShape3D

var _active_copied_collisions : Array[CopiedCollision]

## Controller
@onready var _controller := XRHelpers.get_xr_controller(self)

## Collision hand (if applicable)
@onready var _collision_hand : XRToolsCollisionHand = XRToolsCollisionHand.find_ancestor(self)

## Grip threshold (from configuration)
@onready var _grip_threshold : float = XRTools.get_grip_threshold()

Expand Down Expand Up @@ -172,6 +179,7 @@ func _process(delta):
# Average velocity of this pickup
_velocity_averager.add_transform(delta, global_transform)

_update_copied_collisions()
_update_closest_object()


Expand Down Expand Up @@ -371,6 +379,9 @@ func drop_object() -> void:
if not is_instance_valid(picked_up_object):
return

# Remove any copied collision objects
_remove_copied_collisions()

# let go of this object
picked_up_object.let_go(
self,
Expand Down Expand Up @@ -406,10 +417,56 @@ func _pick_up_object(target: Node3D) -> void:

# If object picked up then emit signal
if is_instance_valid(picked_up_object):
_copy_collisions()

picked_up_object.request_highlight(self, false)
emit_signal("has_picked_up", picked_up_object)


# Copy collision shapes on the held object to our collision hand (if applicable).
# If we're two handing an object, both collision hands will get copies.
func _copy_collisions():
if not is_instance_valid(_collision_hand):
return

if not is_instance_valid(picked_up_object) or not picked_up_object is RigidBody3D:
return

for child in picked_up_object.get_children():
if child is CollisionShape3D and not child.disabled:

var copied_collision : CopiedCollision = CopiedCollision.new()
copied_collision.collision_shape = CollisionShape3D.new()
copied_collision.collision_shape.shape = child.shape
copied_collision.org_transform = child.transform

_collision_hand.add_child(copied_collision.collision_shape, false, Node.INTERNAL_MODE_BACK)
copied_collision.collision_shape.global_transform = picked_up_object.global_transform * \
copied_collision.org_transform

_active_copied_collisions.push_back(copied_collision)


# Adjust positions of our collisions to match actual location of object
func _update_copied_collisions():
if is_instance_valid(_collision_hand) and is_instance_valid(picked_up_object):
for copied_collision : CopiedCollision in _active_copied_collisions:
if is_instance_valid(copied_collision.collision_shape):
copied_collision.collision_shape.global_transform = picked_up_object.global_transform * \
copied_collision.org_transform


# Remove copied collision shapes
func _remove_copied_collisions():
if is_instance_valid(_collision_hand):
for copied_collision : CopiedCollision in _active_copied_collisions:
if is_instance_valid(copied_collision.collision_shape):
_collision_hand.remove_child(copied_collision.collision_shape)
copied_collision.collision_shape.queue_free()

_active_copied_collisions.clear()


func _on_button_pressed(p_button) -> void:
if p_button == action_button_action and is_instance_valid(picked_up_object):
if picked_up_object.has_method("action"):
Expand Down
128 changes: 127 additions & 1 deletion addons/godot-xr-tools/hands/collision_hand.gd
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const DEFAULT_LAYER := 0b0000_0000_0000_0010_0000_0000_0000_0000
# - 3:pickable-objects
# - 4:wall-walking
# - 5:grappling-target
const DEFAULT_MASK := 0b0000_0000_0000_0000_1111_1111_1111_1111
const DEFAULT_MASK := 0b0000_0000_0000_0101_0000_0000_0001_1111

# How much displacement is required for the hand to start orienting to a surface
const ORIENT_DISPLACEMENT := 0.05
Expand All @@ -44,6 +44,30 @@ const TELEPORT_DISTANCE := 1.0
@export var mode : CollisionHandMode = CollisionHandMode.COLLIDE


## Links to skeleton that adds finger digits
@export var hand_skeleton : Skeleton3D:
set(value):
if hand_skeleton == value:
return

if hand_skeleton:
if hand_skeleton.has_signal("skeleton_updated"):
# Godot 4.3+
hand_skeleton.skeleton_updated.disconnect(_on_skeleton_updated)
else:
hand_skeleton.pose_updated.disconnect(_on_skeleton_updated)
for digit in _digit_collision_shapes:
var shape : CollisionShape3D = _digit_collision_shapes[digit]
remove_child(shape)
shape.queue_free()
_digit_collision_shapes.clear()

hand_skeleton = value
if hand_skeleton and is_inside_tree():
_update_hand_skeleton()

notify_property_list_changed()

# Controller to target (if no target overrides)
var _controller : XRController3D

Expand All @@ -53,6 +77,10 @@ var _target_overrides := []
# Current target (controller or override)
var _target : Node3D

# Skeleton collisions
var _palm_collision_shape : CollisionShape3D
var _digit_collision_shapes : Dictionary


## Target-override class
class TargetOverride:
Expand All @@ -73,8 +101,43 @@ func is_xr_class(name : String) -> bool:
return name == "XRToolsCollisionHand"


# Return warnings related to this node
func _get_configuration_warnings() -> PackedStringArray:
var warnings := PackedStringArray()

# Check palm node
if not _palm_collision_shape:
warnings.push_back("Collision hand scenes are deprecated, use collision node script directly.")

# Check if skeleton is a child
if hand_skeleton and not is_ancestor_of(hand_skeleton):
warnings.push_back("The hand skeleton node should be within the tree of this node.")

# Return warnings
return warnings


# Called when the node enters the scene tree for the first time.
func _ready():
var palm_collision : CollisionShape3D = get_node_or_null("CollisionShape3D")
if not palm_collision:
# We create our object even in editor to supress our warning.
# This allows us to just add an XRToolsCollisionHand node without
# using our scene.
_palm_collision_shape = CollisionShape3D.new()
_palm_collision_shape.name = "Palm"
_palm_collision_shape.shape = \
preload("res://addons/godot-xr-tools/hands/scenes/collision/hand_palm.shape")
_palm_collision_shape.transform.origin = Vector3(0.0, -0.05, 0.11)
add_child(_palm_collision_shape, false, Node.INTERNAL_MODE_BACK)
elif not Engine.is_editor_hint():
# Use our existing collision shape node but only in runtime.
# In editor we can check this to provide a deprecation warning.
palm_collision.name = "Palm"
_palm_collision_shape = palm_collision

_update_hand_skeleton()

# Do not initialise if in the editor
if Engine.is_editor_hint():
return
Expand All @@ -83,6 +146,7 @@ func _ready():
# and boost the physics priority above any grab-drivers or hands.
top_level = true
process_physics_priority = -90
sync_to_physics = false

# Populate nodes
_controller = XRTools.find_xr_ancestor(self, "*", "XRController3D")
Expand Down Expand Up @@ -135,6 +199,14 @@ static func find_instance(node : Node) -> XRToolsCollisionHand:
"*",
"XRToolsCollisionHand") as XRToolsCollisionHand

## This function searches an [XRToolsCollisionHand] that is an ancestor
## of the given node.
static func find_ancestor(node : Node) -> XRToolsCollisionHand:
return XRTools.find_xr_ancestor(
node,
"*",
"XRToolsCollisionHand") as XRToolsCollisionHand


## This function searches from the specified node for the left controller
## [XRToolsCollisionHand] assuming the node is a sibling of the [XROrigin3D].
Expand Down Expand Up @@ -229,3 +301,57 @@ func _update_target() -> void:
# Use first target override if specified
if _target_overrides.size():
_target = _target_overrides[0].target


# If a skeleton is set, update.
func _update_hand_skeleton():
if hand_skeleton:
if hand_skeleton.has_signal("skeleton_updated"):
# Godot 4.3+
hand_skeleton.skeleton_updated.connect(_on_skeleton_updated)
else:
hand_skeleton.pose_updated.connect(_on_skeleton_updated)

# Run atleast once to init
_on_skeleton_updated()


# Update our finger digits when our skeleton updates
func _on_skeleton_updated():
if not hand_skeleton:
return

var bone_count = hand_skeleton.get_bone_count()
for i in bone_count:
var collision_node : CollisionShape3D
var offset : Transform3D
offset.origin = Vector3(0.0, 0.015, 0.0) # move to side of joint

var bone_name = hand_skeleton.get_bone_name(i)
if bone_name == "Palm_L":
offset.origin = Vector3(-0.02, 0.025, 0.0) # move to side of joint
collision_node = _palm_collision_shape
elif bone_name == "Palm_R":
offset.origin = Vector3(0.02, 0.025, 0.0) # move to side of joint
collision_node = _palm_collision_shape
elif bone_name.contains("Proximal") or bone_name.contains("Intermediate") or \
bone_name.contains("Distal"):
if _digit_collision_shapes.has(bone_name):
collision_node = _digit_collision_shapes[bone_name]
else:
collision_node = CollisionShape3D.new()
collision_node.name = bone_name
collision_node.shape = \
preload("res://addons/godot-xr-tools/hands/scenes/collision/hand_digit.shape")
add_child(collision_node, false, Node.INTERNAL_MODE_BACK)
_digit_collision_shapes[bone_name] = collision_node

if collision_node:
# TODO it would require a far more complex approach,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something for a future PR. Also this logic would likely be more suited to be a SkeletonModifier3D implementation so that the skeleton is also limited in movement, something even more powerful when combined with full hand tracking.

That might be an XRT2 thing though.

# but being able to check if our collision shapes can move to their new locations
# would be interesting.

collision_node.transform = global_transform.inverse() \
* hand_skeleton.global_transform \
* hand_skeleton.get_bone_global_pose(i) \
* offset
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[gd_scene load_steps=2 format=3 uid="uid://yrg5yt0yvc1q"]

[ext_resource type="Script" path="res://addons/godot-xr-tools/hands/collision_hand.gd" id="1_vdcct"]

[node name="XRToolsCollisionHand" type="AnimatableBody3D"]
collision_layer = 131072
collision_mask = 327711
sync_to_physics = false
script = ExtResource("1_vdcct")
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
[gd_scene load_steps=3 format=3 uid="uid://bkv43ec6chcf3"]

[ext_resource type="Script" path="res://addons/godot-xr-tools/hands/collision_hand.gd" id="1_t5acd"]

[sub_resource type="BoxShape3D" id="BoxShape3D_bv7in"]
size = Vector3(0.045, 0.075, 0.1)
[ext_resource type="Shape3D" uid="uid://uc7owi5j7ib0" path="res://addons/godot-xr-tools/hands/scenes/collision/hand_palm.shape" id="2_5wm8j"]

[node name="CollisionHandLeft" type="AnimatableBody3D"]
collision_layer = 131072
Expand All @@ -13,4 +11,4 @@ script = ExtResource("1_t5acd")

[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.03, -0.05, 0.11)
shape = SubResource("BoxShape3D_bv7in")
shape = ExtResource("2_5wm8j")
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
[gd_scene load_steps=3 format=3 uid="uid://c3uoohvnshach"]

[ext_resource type="Script" path="res://addons/godot-xr-tools/hands/collision_hand.gd" id="1_so3hf"]

[sub_resource type="BoxShape3D" id="BoxShape3D_fc2ij"]
size = Vector3(0.045, 0.075, 0.1)
[ext_resource type="Shape3D" uid="uid://uc7owi5j7ib0" path="res://addons/godot-xr-tools/hands/scenes/collision/hand_palm.shape" id="2_vvxfo"]

[node name="CollisionHandRight" type="AnimatableBody3D"]
collision_layer = 131072
Expand All @@ -13,4 +11,4 @@ script = ExtResource("1_so3hf")

[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.03, -0.05, 0.11)
shape = SubResource("BoxShape3D_fc2ij")
shape = ExtResource("2_vvxfo")
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,27 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://cs7m7m0k2506g"
path="res://.godot/imported/african_baseColor.png-c1a63b2c85973a5f7673482d994697e9.ctex"
path.s3tc="res://.godot/imported/african_baseColor.png-c1a63b2c85973a5f7673482d994697e9.s3tc.ctex"
path.etc2="res://.godot/imported/african_baseColor.png-c1a63b2c85973a5f7673482d994697e9.etc2.ctex"
metadata={
"vram_texture": false
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}

[deps]

source_file="res://addons/godot-xr-tools/hands/textures/african_baseColor.png"
dest_files=["res://.godot/imported/african_baseColor.png-c1a63b2c85973a5f7673482d994697e9.ctex"]
dest_files=["res://.godot/imported/african_baseColor.png-c1a63b2c85973a5f7673482d994697e9.s3tc.ctex", "res://.godot/imported/african_baseColor.png-c1a63b2c85973a5f7673482d994697e9.etc2.ctex"]

[params]

compress/mode=0
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
Expand All @@ -31,4 +33,4 @@ process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
detect_3d/compress_to=0
Loading
Loading