Skip to content

Commit

Permalink
Add finger collisions and pickup collisions to collision hands
Browse files Browse the repository at this point in the history
  • Loading branch information
BastiaanOlij committed Oct 28, 2024
1 parent 6e3eedb commit 0aea6c4
Show file tree
Hide file tree
Showing 21 changed files with 395 additions and 289 deletions.
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,
# 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
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,27 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://dk8o82rjww802"
path="res://.godot/imported/african_realistic_baseColor.png-67cf0f3230115c16edbeaf7b76430f7c.ctex"
path.s3tc="res://.godot/imported/african_realistic_baseColor.png-67cf0f3230115c16edbeaf7b76430f7c.s3tc.ctex"
path.etc2="res://.godot/imported/african_realistic_baseColor.png-67cf0f3230115c16edbeaf7b76430f7c.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_realistic_baseColor.png"
dest_files=["res://.godot/imported/african_realistic_baseColor.png-67cf0f3230115c16edbeaf7b76430f7c.ctex"]
dest_files=["res://.godot/imported/african_realistic_baseColor.png-67cf0f3230115c16edbeaf7b76430f7c.s3tc.ctex", "res://.godot/imported/african_realistic_baseColor.png-67cf0f3230115c16edbeaf7b76430f7c.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

0 comments on commit 0aea6c4

Please sign in to comment.