434 lines
13 KiB
GDScript
434 lines
13 KiB
GDScript
class_name XRTools
|
|
extends Node
|
|
|
|
## Below are helper functions to obtain various project settings that drive
|
|
## the default behavior of XR Tools. The project settings themselves are
|
|
## registered in plugin.gd.
|
|
## Some of these settings can be overridden by the user through user settings.
|
|
|
|
## Offset modes
|
|
enum HandOffsetMode {
|
|
HAND_OFFSET_AUTO, # Determine based on using default poses
|
|
HAND_OFFSET_AIM, # Our pose is an aim pose
|
|
HAND_OFFSET_GRIP, # Our pose is a grip pose
|
|
HAND_OFFSET_PALM # Our pose is a palm pose
|
|
}
|
|
|
|
# Map interaction profiles to grip rotations.
|
|
# TODO We need to complete this with more controllers,
|
|
static var grip_rotations: Dictionary[String, float] = {
|
|
"/interaction_profiles/oculus/touch_controller": deg_to_rad(-60.0),
|
|
"/interaction_profiles/facebook/touch_controller_pro": deg_to_rad(-60.0),
|
|
"/interaction_profiles/meta/touch_controller_plus": deg_to_rad(-60.0),
|
|
"/interaction_profiles/bytedance/pico4_controller": deg_to_rad(-40.0),
|
|
"/interaction_profiles/bytedance/pico4s_controller": deg_to_rad(-40.0),
|
|
"/interaction_profiles/bytedance/pico_ultra_controller_bd": deg_to_rad(-40.0)
|
|
}
|
|
|
|
## Get our configured grip threshold.
|
|
static func get_grip_threshold() -> float:
|
|
# can return null which is not a float, so don't type this!
|
|
var threshold = 0.7
|
|
|
|
if ProjectSettings.has_setting("godot_xr_tools/input/grip_threshold"):
|
|
threshold = ProjectSettings.get_setting("godot_xr_tools/input/grip_threshold")
|
|
|
|
if !(threshold >= 0.2 and threshold <= 0.8):
|
|
# out of bounds? reset to default
|
|
threshold = 0.7
|
|
|
|
return threshold
|
|
|
|
|
|
## Set our configured grip threshold.
|
|
static func set_grip_threshold(p_threshold : float) -> void:
|
|
if !(p_threshold >= 0.2 and p_threshold <= 0.8):
|
|
print("Threshold out of bounds")
|
|
return
|
|
|
|
ProjectSettings.set_setting("godot_xr_tools/input/grip_threshold", p_threshold)
|
|
|
|
|
|
## Get our y-axis dead zone.
|
|
static func get_y_axis_dead_zone() -> float:
|
|
# can return null which is not a float, so don't type this!
|
|
var deadzone = 0.1
|
|
|
|
if ProjectSettings.has_setting("godot_xr_tools/input/y_axis_dead_zone"):
|
|
deadzone = ProjectSettings.get_setting("godot_xr_tools/input/y_axis_dead_zone")
|
|
|
|
if !(deadzone >= 0.0 and deadzone <= 0.5):
|
|
# out of bounds? reset to default
|
|
deadzone = 0.1
|
|
|
|
return deadzone
|
|
|
|
|
|
## Set our y-axis dead zone.
|
|
static func set_y_axis_dead_zone(p_deadzone : float) -> void:
|
|
if !(p_deadzone >= 0.0 and p_deadzone <= 0.5):
|
|
print("Deadzone out of bounds")
|
|
return
|
|
|
|
ProjectSettings.set_setting("godot_xr_tools/input/y_axis_dead_zone", p_deadzone)
|
|
|
|
|
|
## Get our x-axis dead zone.
|
|
static func get_x_axis_dead_zone() -> float:
|
|
# can return null which is not a float, so don't type this!
|
|
var deadzone = 0.2
|
|
|
|
if ProjectSettings.has_setting("godot_xr_tools/input/x_axis_dead_zone"):
|
|
deadzone = ProjectSettings.get_setting("godot_xr_tools/input/x_axis_dead_zone")
|
|
|
|
if !(deadzone >= 0.0 and deadzone <= 0.5):
|
|
# out of bounds? reset to default
|
|
deadzone = 0.2
|
|
|
|
return deadzone
|
|
|
|
|
|
## Set our x-axis dead zone.
|
|
static func set_x_axis_dead_zone(p_deadzone : float) -> void:
|
|
if !(p_deadzone >= 0.0 and p_deadzone <= 0.5):
|
|
print("Deadzone out of bounds")
|
|
return
|
|
|
|
ProjectSettings.set_setting("godot_xr_tools/input/x_axis_dead_zone", p_deadzone)
|
|
|
|
|
|
## Get our snap turning dead zone.
|
|
static func get_snap_turning_deadzone() -> float:
|
|
# can return null which is not a float, so don't type this!
|
|
var deadzone = 0.25
|
|
|
|
if ProjectSettings.has_setting("godot_xr_tools/input/snap_turning_deadzone"):
|
|
deadzone = ProjectSettings.get_setting("godot_xr_tools/input/snap_turning_deadzone")
|
|
|
|
if !(deadzone >= 0.0 and deadzone <= 0.5):
|
|
# out of bounds? reset to default
|
|
deadzone = 0.25
|
|
|
|
return deadzone
|
|
|
|
|
|
## Set our snap turning dead zone.
|
|
static func set_snap_turning_deadzone(p_deadzone : float) -> void:
|
|
if !(p_deadzone >= 0.0 and p_deadzone <= 0.5):
|
|
print("Deadzone out of bounds")
|
|
return
|
|
|
|
ProjectSettings.set_setting("godot_xr_tools/input/snap_turning_deadzone", p_deadzone)
|
|
|
|
|
|
## Get our default value for enabling snap turning.
|
|
static func get_default_snap_turning() -> bool:
|
|
var default = true
|
|
|
|
if ProjectSettings.has_setting("godot_xr_tools/input/default_snap_turning"):
|
|
default = ProjectSettings.get_setting("godot_xr_tools/input/default_snap_turning")
|
|
|
|
# default may not be bool, so JIC
|
|
return default == true
|
|
|
|
|
|
## Set our default value for enabling snap turning.
|
|
static func set_default_snap_turning(p_default : bool) -> void:
|
|
ProjectSettings.set_setting("godot_xr_tools/input/default_snap_turning", p_default)
|
|
|
|
|
|
## Get our player standard height.
|
|
static func get_player_standard_height() -> float:
|
|
var standard_height = 1.85
|
|
|
|
if ProjectSettings.has_setting("godot_xr_tools/player/standard_height"):
|
|
standard_height = ProjectSettings.get_setting("godot_xr_tools/player/standard_height")
|
|
|
|
if !(standard_height >= 1.0 and standard_height <= 2.5):
|
|
# out of bounds? reset to default
|
|
standard_height = 1.85
|
|
|
|
return standard_height
|
|
|
|
|
|
## Set our player standard height.
|
|
static func set_player_standard_height(p_height : float) -> void:
|
|
if !(p_height >= 1.0 and p_height <= 2.5):
|
|
print("Standard height out of bounds")
|
|
return
|
|
|
|
ProjectSettings.set_setting("godot_xr_tools/player/standard_height", p_height)
|
|
|
|
|
|
## Find all children of the specified node matching the given criteria
|
|
##
|
|
## This function returns an array containing all children of the specified
|
|
## node matching the given criteria. This function can be slow and find_child
|
|
## is faster if only one child is needed.
|
|
##
|
|
## The pattern argument specifies the match pattern to check against the
|
|
## node name. Use "*" to match anything.
|
|
##
|
|
## The type argument specifies the type of node to find. Use "" to match any
|
|
## type.
|
|
##
|
|
## The recursive argument specifies whether the search deeply though all child
|
|
## nodes, or whether to only check the immediate children.
|
|
##
|
|
## The owned argument specifies whether the node must be owned.
|
|
static func find_xr_children(
|
|
node : Node,
|
|
pattern : String,
|
|
type : String = "",
|
|
recursive : bool = true,
|
|
owned : bool = true) -> Array:
|
|
# Find the children
|
|
var found := []
|
|
if node:
|
|
_find_xr_children(found, node, pattern, type, recursive, owned)
|
|
return found
|
|
|
|
|
|
## Find a child of the specified node matching the given criteria
|
|
##
|
|
## This function finds the first child of the specified node matching the given
|
|
## criteria.
|
|
##
|
|
## The pattern argument specifies the match pattern to check against the
|
|
## node name. Use "*" to match anything.
|
|
##
|
|
## The type argument specifies the type of node to find. Use "" to match any
|
|
## type.
|
|
##
|
|
## The recursive argument specifies whether the search deeply though all child
|
|
## nodes, or whether to only check the immediate children.
|
|
##
|
|
## The owned argument specifies whether the node must be owned.
|
|
static func find_xr_child(
|
|
node : Node,
|
|
pattern : String,
|
|
type : String = "",
|
|
recursive : bool = true,
|
|
owned : bool = true) -> Node:
|
|
# Find the child
|
|
if node:
|
|
return _find_xr_child(node, pattern, type, recursive, owned)
|
|
|
|
# Invalid node
|
|
return null
|
|
|
|
|
|
## Find an ancestor of the specified node matching the given criteria
|
|
##
|
|
## This function finds the first ancestor of the specified node matching the
|
|
## given criteria.
|
|
##
|
|
## The pattern argument specifies the match pattern to check against the
|
|
## node name. Use "*" to match anything.
|
|
##
|
|
## The type argument specifies the type of node to find. Use "" to match any
|
|
## type.
|
|
static func find_xr_ancestor(
|
|
node : Node,
|
|
pattern : String,
|
|
type : String = "") -> Node:
|
|
# Loop finding ancestor
|
|
while node:
|
|
# If node matches filter then break
|
|
if (node.name.match(pattern) and
|
|
(type == "" or is_xr_class(node, type))):
|
|
break
|
|
|
|
# Advance to parent
|
|
node = node.get_parent()
|
|
|
|
# Return found node (or null)
|
|
return node
|
|
|
|
|
|
# Recursive helper function for find_children.
|
|
static func _find_xr_children(
|
|
found : Array,
|
|
node : Node,
|
|
pattern : String,
|
|
type : String,
|
|
recursive : bool,
|
|
owned : bool) -> void:
|
|
# Iterate over all children
|
|
for i in node.get_child_count():
|
|
# Get the child
|
|
var child := node.get_child(i)
|
|
|
|
# If child matches filter then add it to the array
|
|
if (child.name.match(pattern) and
|
|
(type == "" or is_xr_class(child, type)) and
|
|
(not owned or child.owner)):
|
|
found.push_back(child)
|
|
|
|
# If recursive is enabled then descend into children
|
|
if recursive:
|
|
_find_xr_children(found, child, pattern, type, recursive, owned)
|
|
|
|
|
|
# Recursive helper functiomn for find_child
|
|
static func _find_xr_child(
|
|
node : Node,
|
|
pattern : String,
|
|
type : String,
|
|
recursive : bool,
|
|
owned : bool) -> Node:
|
|
# Iterate over all children
|
|
for i in node.get_child_count():
|
|
# Get the child
|
|
var child := node.get_child(i)
|
|
|
|
# If child matches filter then return it
|
|
if (child.name.match(pattern) and
|
|
(type == "" or is_xr_class(child, type)) and
|
|
(not owned or child.owner)):
|
|
return child
|
|
|
|
# If recursive is enabled then descend into children
|
|
if recursive:
|
|
var found := _find_xr_child(child, pattern, type, recursive, owned)
|
|
if found:
|
|
return found
|
|
|
|
# Not found
|
|
return null
|
|
|
|
|
|
# Test if a given node is of the specified class
|
|
static func is_xr_class(node : Node, type : String) -> bool:
|
|
if node.has_method("is_xr_class"):
|
|
if node.is_xr_class(type):
|
|
return true
|
|
|
|
return node.is_class(type)
|
|
|
|
|
|
## Gets our grip rotation for various controller profiles.
|
|
## Note that this is a guestimate and that in theory rotations
|
|
## can vary between runtimes.
|
|
static func get_grip_rotation(profile : String) -> float:
|
|
# TODO add in a way for users to override this through a setting.
|
|
if grip_rotations.has(profile):
|
|
return grip_rotations[profile]
|
|
|
|
# We return 45 degrees as a default
|
|
return deg_to_rad(-45.0)
|
|
|
|
|
|
## Helper function to get a transform that offset the controller pose
|
|
## so we center on the palm
|
|
static func get_palm_offset(mode : HandOffsetMode, xr_controller : XRController3D) -> Transform3D:
|
|
var transform: Transform3D = Transform3D()
|
|
var is_left_hand: bool = true
|
|
var profile : String = ""
|
|
|
|
if xr_controller:
|
|
if xr_controller.tracker != "left_hand":
|
|
is_left_hand = false
|
|
|
|
var xr_tracker : XRControllerTracker = XRServer.get_tracker(xr_controller.tracker)
|
|
if xr_tracker:
|
|
profile = xr_tracker.profile
|
|
|
|
if mode != XRTools.HandOffsetMode.HAND_OFFSET_AUTO:
|
|
pass
|
|
elif xr_controller.pose == "aim":
|
|
mode = XRTools.HandOffsetMode.HAND_OFFSET_AIM
|
|
elif xr_controller.pose == "grip":
|
|
mode = XRTools.HandOffsetMode.HAND_OFFSET_GRIP
|
|
else:
|
|
# Assume we're using a palm pose
|
|
mode = XRTools.HandOffsetMode.HAND_OFFSET_PALM
|
|
|
|
match mode:
|
|
XRTools.HandOffsetMode.HAND_OFFSET_AUTO:
|
|
# No controller? keep identity transform
|
|
pass
|
|
XRTools.HandOffsetMode.HAND_OFFSET_AIM:
|
|
# These are our original aim offsets.
|
|
# They are fairly unreliable now for many headsets.
|
|
if is_left_hand:
|
|
transform.origin = Vector3(-0.02, -0.05, 0.10)
|
|
else:
|
|
transform.origin = Vector3(0.02, -0.05, 0.10)
|
|
XRTools.HandOffsetMode.HAND_OFFSET_GRIP:
|
|
# Grip, is rotated 45 degrees to be aligned with controller grip
|
|
# So we reverse the rotation
|
|
transform.basis = Basis(Vector3(1.0, 0.0, 0.0), get_grip_rotation(profile))
|
|
|
|
# Todo, we should offset.origin.x depending on the hand,
|
|
# as our grip pose is centered on the controller.
|
|
# But we need some sort of average, or possibly start maintaining
|
|
# a matrix of adjustments per interaction profile, which would suck.
|
|
XRTools.HandOffsetMode.HAND_OFFSET_PALM:
|
|
# Palm, identity transform does fine.
|
|
# Our palm pose should be in the correct location.
|
|
pass
|
|
_:
|
|
# Unsupported
|
|
pass
|
|
|
|
return transform
|
|
|
|
|
|
## Helper function to get a transform that offset the controller pose
|
|
## so we're at the aim position.
|
|
## Note that if the aim pose is used, we use that location as is,
|
|
## else we try and reproduce the original aim pose location,
|
|
## which may be different.
|
|
static func get_aim_offset(mode : HandOffsetMode, xr_controller : XRController3D) -> Transform3D:
|
|
var transform: Transform3D = Transform3D()
|
|
var is_left_hand: bool = true
|
|
var profile : String = ""
|
|
|
|
if xr_controller:
|
|
if xr_controller.tracker != "left_hand":
|
|
is_left_hand = false
|
|
|
|
var xr_tracker : XRControllerTracker = XRServer.get_tracker(xr_controller.tracker)
|
|
if xr_tracker:
|
|
profile = xr_tracker.profile
|
|
|
|
if mode != XRTools.HandOffsetMode.HAND_OFFSET_AUTO:
|
|
pass
|
|
elif xr_controller.pose == "aim":
|
|
mode = XRTools.HandOffsetMode.HAND_OFFSET_AIM
|
|
elif xr_controller.pose == "grip":
|
|
mode = XRTools.HandOffsetMode.HAND_OFFSET_GRIP
|
|
else:
|
|
# Assume we're using a palm pose
|
|
mode = XRTools.HandOffsetMode.HAND_OFFSET_PALM
|
|
|
|
match mode:
|
|
XRTools.HandOffsetMode.HAND_OFFSET_AUTO:
|
|
# No controller? keep identity transform
|
|
pass
|
|
XRTools.HandOffsetMode.HAND_OFFSET_AIM:
|
|
# Aim, identity transform is what we want.
|
|
pass
|
|
XRTools.HandOffsetMode.HAND_OFFSET_GRIP:
|
|
# Grip, is rotated 45 degrees to be aligned with controller grip
|
|
# So we reverse the rotation
|
|
transform.basis = Basis(Vector3(1.0, 0.0, 0.0), get_grip_rotation(profile))
|
|
|
|
# and offset
|
|
if is_left_hand:
|
|
transform.origin = transform.basis * Vector3(0.02, 0.05, -0.10)
|
|
else:
|
|
transform.origin = transform.basis * Vector3(-0.02, 0.05, -0.10)
|
|
XRTools.HandOffsetMode.HAND_OFFSET_PALM:
|
|
# Just offset
|
|
if is_left_hand:
|
|
transform.origin = Vector3(0.02, 0.05, -0.10)
|
|
else:
|
|
transform.origin = Vector3(-0.02, 0.05, -0.10)
|
|
_:
|
|
# Unsupported
|
|
pass
|
|
|
|
return transform
|