vrp-gruppe-sechs/space-room/addons/godot-xr-tools/xr_tools.gd

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