Skip to content

Commit

Permalink
Cleanup hinge code
Browse files Browse the repository at this point in the history
- Calculate derived variables instead of setting
- Remove unused variable
- Fix the 'get_final_axis()' to just be the local 'hinge_axis' direction set by user
- Remove 'get_final_axis()'
- Remove extra check for change before setting 'hinge_position'
  • Loading branch information
squidt committed Nov 28, 2024
1 parent 4c7e466 commit 3423e2c
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 90 deletions.
142 changes: 54 additions & 88 deletions addons/godot-xr-tools/interactables/interactable_hinge.gd
Original file line number Diff line number Diff line change
Expand Up @@ -20,34 +20,57 @@ signal hinge_moved(angle)


## Hinge minimum limit
@export var hinge_limit_min : float = -45.0: set = _set_hinge_limit_min
@export var hinge_limit_min : float = -45.0

## Hinge maximum limit
@export var hinge_limit_max : float = 45.0: set = _set_hinge_limit_max
@export var hinge_limit_max : float = 45.0

## Hinge step size (zero for no steps)
@export var hinge_steps : float = 0.0: set = _set_hinge_steps
@export var hinge_steps : float = 0.0


## Hinge position
@export var hinge_position : float = 0.0: set = _set_hinge_position
@export var hinge_position : float = 0.0:
set(v):
# Apply constraints
var radians = _apply_hinge_constraints(deg_to_rad(v))

# No change
if is_equal_approx(radians, _hinge_position_rad):
return

# Set, Emit
_is_driven_change = true
transform = _private_transform.rotated_local(hinge_axis, radians)
hinge_position = rad_to_deg(radians)
hinge_moved.emit(hinge_position)


## Default position
@export var default_position : float = 0.0: set = _set_default_position
@export var default_position : float = 0.0

## Allow hinge to wrap between min and max limits
@export var hinge_wrapping : bool = false

## If true, the hinge moves to the default position when releases
@export var default_on_release : bool = false

## Set to rotate about this local axis
## Define a local axis to rotate about
@export var hinge_axis := Vector3.RIGHT:
set(v):
hinge_axis = v.normalized()

# Hinge values in radians
@onready var _hinge_limit_min_rad : float = deg_to_rad(hinge_limit_min)
@onready var _hinge_limit_max_rad : float = deg_to_rad(hinge_limit_max)
@onready var _hinge_steps_rad : float = deg_to_rad(hinge_steps)
@onready var _hinge_position_rad : float = deg_to_rad(hinge_position)
@onready var _default_position_rad : float = deg_to_rad(default_position)
@onready var _hinge_limit_min_rad : float:
get: return deg_to_rad(hinge_limit_min)
@onready var _hinge_limit_max_rad : float:
get: return deg_to_rad(hinge_limit_max)
@onready var _hinge_steps_rad : float:
get: return deg_to_rad(hinge_steps)
@onready var _hinge_position_rad : float:
get: return deg_to_rad(hinge_position)
@onready var _default_position_rad : float:
get: return deg_to_rad(default_position)


# Add support for is_xr_class on XRTools classes
Expand All @@ -69,17 +92,14 @@ func _ready():
func _process(_delta: float) -> void:
# Get the total handle angular offsets
var offset_sum := 0.0
for item in grabbed_handles:
var handle := item as XRToolsInteractableHandle
# global handle + handle_origin position
var axis := get_final_axis()
var to_handle : Vector3 = handle.global_transform.origin * global_transform
var to_handle_origin : Vector3 = handle.handle_origin.global_transform.origin * global_transform
var a_old = to_handle_origin.signed_angle_to(to_handle, axis)

# project 'to_handle' and 'to_handle_origin' on 'axis'
# then measure the angle
offset_sum += atan2(to_handle_origin.cross(to_handle).dot(axis), to_handle.dot(to_handle_origin))
for handle : XRToolsInteractableHandle in grabbed_handles:
# Move to local space
var to_handle := handle.global_transform.origin * global_transform
var to_origin := handle.handle_origin.global_transform.origin * global_transform

# Find angle
# Project 'to_handle' and 'to_handle_origin' on 'hinge_axis'
offset_sum += atan2(to_origin.cross(to_handle).dot(hinge_axis), to_handle.dot(to_origin))

# Average the angular offsets
var offset := offset_sum / grabbed_handles.size()
Expand All @@ -88,79 +108,25 @@ func _process(_delta: float) -> void:
move_hinge(_hinge_position_rad + offset)


## Return a unit vector of the final rotation axis
func get_final_axis() -> Vector3:
return (hinge_axis * _private_transform.basis.inverse()).normalized()


# Move the hinge to the specified position
func move_hinge(position: float) -> void:
func move_hinge(radians: float) -> void:
# Do the hinge move
position = _do_move_hinge(position)
if position == _hinge_position_rad:
return
hinge_position = rad_to_deg(radians)

# Update the current positon
_hinge_position_rad = position
hinge_position = rad_to_deg(position)

# Emit the moved signal
hinge_moved.emit(hinge_position)
# Returns 'radians' with step-quantization and min/max limits applied
func _apply_hinge_constraints(radians: float) -> float:
# Apply hinge step-quantization
if !is_zero_approx(_hinge_steps_rad):
radians = roundf(radians / _hinge_steps_rad) * _hinge_steps_rad

# Apply hinge limits
if hinge_wrapping:
return wrapf(radians, _hinge_limit_min_rad, _hinge_limit_max_rad)
return clampf(radians, _hinge_limit_min_rad, _hinge_limit_max_rad)


# Handle release of hinge
func _on_hinge_released(_interactable: XRToolsInteractableHinge):
if default_on_release:
move_hinge(_default_position_rad)


# Called when hinge_limit_min is set externally
func _set_hinge_limit_min(value: float) -> void:
hinge_limit_min = value
_hinge_limit_min_rad = deg_to_rad(value)


# Called when hinge_limit_max is set externally
func _set_hinge_limit_max(value: float) -> void:
hinge_limit_max = value
_hinge_limit_max_rad = deg_to_rad(value)


# Called when hinge_steps is set externally
func _set_hinge_steps(value: float) -> void:
hinge_steps = value
_hinge_steps_rad = deg_to_rad(value)


# Called when hinge_position is set externally
func _set_hinge_position(value: float) -> void:

_is_driven_change = true
var rads := deg_to_rad(value)
rads = _do_move_hinge(rads)
hinge_position = rad_to_deg(rads)
_hinge_position_rad = rads


# Called when default_position is set externally
func _set_default_position(value: float) -> void:
default_position = value
_default_position_rad = deg_to_rad(value)


# Do the hinge move
func _do_move_hinge(_angle_radians: float) -> float:
# Apply hinge step-quantization
if _hinge_steps_rad:
_angle_radians = round(_angle_radians / _hinge_steps_rad) * _hinge_steps_rad

# Apply hinge limits
_angle_radians = clamp(_angle_radians, _hinge_limit_min_rad, _hinge_limit_max_rad)

# Move if necessary
if _angle_radians != _hinge_position_rad:
_is_driven_change = true
transform = _private_transform.rotated_local(get_final_axis(), _angle_radians)

# Return the updated _angle_radians
return _angle_radians
2 changes: 0 additions & 2 deletions assets/meshes/interactables/wheel_smooth.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,9 @@ size = Vector3(0.02, 0.08, 0.1)
[node name="WheelSmooth" type="Node3D"]

[node name="InteractableHinge" type="Node3D" parent="."]
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 0, 0, 0)
script = ExtResource("2")
hinge_limit_min = -360.0
hinge_limit_max = 360.0
hinge_axis = Vector3(0, 0, 1)

[node name="HandPoseArea" parent="InteractableHinge" instance=ExtResource("4_2vgo6")]
left_pose = ExtResource("5_gt8jj")
Expand Down

0 comments on commit 3423e2c

Please sign in to comment.