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

Rework interactables #692

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
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
26 changes: 22 additions & 4 deletions addons/godot-xr-tools/interactables/interactable_handle_driven.gd
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,38 @@ signal released(interactable)
var grabbed_handles := Array()


# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsInteractableHandleDriven"
## Transform3D that ignores driven behavior
var _private_transform : Transform3D
var _is_driven_change := false


func _enter_tree() -> void:
set_notify_local_transform(true)


# Called when the node enters the scene tree for the first time.
func _ready():
_private_transform = transform

# Hook picked_up and dropped signals from all child handles
_hook_child_handles(self)

# Turn off processing until a handle is grabbed
set_process(false)


func _notification(what: int) -> void:
if what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED:
# If change NOT from driven behavior
if !_is_driven_change:
_private_transform = transform
_is_driven_change = false


# Add support for is_xr_class on XRTools classes
func is_xr_class(name : String) -> bool:
return name == "XRToolsInteractableHandleDriven"


# Called when a handle is picked up
func _on_handle_picked_up(handle: XRToolsInteractableHandle) -> void:
# Append to the list of grabbed handles
Expand Down
138 changes: 57 additions & 81 deletions addons/godot-xr-tools/interactables/interactable_hinge.gd
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +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

## 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 @@ -56,12 +83,6 @@ func _ready():
# In Godot 4 we must now manually call our super class ready function
super()

# Set the initial position to match the initial hinge position value
transform = Transform3D(
Basis.from_euler(Vector3(_hinge_position_rad, 0, 0)),
Vector3.ZERO
)

# Connect signals
if released.connect(_on_hinge_released):
push_error("Cannot connect hinge released signal")
Expand All @@ -71,13 +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
var to_handle: Vector3 = handle.global_transform.origin * global_transform
var to_handle_origin: Vector3 = handle.handle_origin.global_transform.origin * global_transform
to_handle.x = 0.0
to_handle_origin.x = 0.0
offset_sum += to_handle_origin.signed_angle_to(to_handle, Vector3.RIGHT)
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 @@ -87,70 +109,24 @@ func _process(_delta: float) -> void:


# 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
emit_signal("hinge_moved", 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:
var position := deg_to_rad(value)
position = _do_move_hinge(position)
hinge_position = rad_to_deg(position)
_hinge_position_rad = position


# 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(position: float) -> float:
# Apply hinge step-quantization
if _hinge_steps_rad:
position = round(position / _hinge_steps_rad) * _hinge_steps_rad

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

# Move if necessary
if position != _hinge_position_rad:
transform.basis = Basis.from_euler(Vector3(position, 0.0, 0.0))

# Return the updated position
return position
Loading
Loading