Rework XR positional trackers

This commit is contained in:
Bastiaan Olij 2021-08-29 16:05:11 +10:00
parent c2a616f3ec
commit 5d1ea92daf
29 changed files with 1359 additions and 802 deletions

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="XRAnchor3D" inherits="Node3D" version="4.0">
<class name="XRAnchor3D" inherits="XRNode3D" version="4.0">
<brief_description>
An anchor point in AR space.
</brief_description>
@ -11,24 +11,6 @@
<tutorials>
</tutorials>
<methods>
<method name="get_anchor_name" qualifiers="const">
<return type="String" />
<description>
Returns the name given to this anchor.
</description>
</method>
<method name="get_is_active" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if the anchor is being tracked and [code]false[/code] if no anchor with this ID is currently known.
</description>
</method>
<method name="get_mesh" qualifiers="const">
<return type="Mesh" />
<description>
If provided by the [XRInterface], this returns a mesh object for the anchor. For an anchor, this can be a shape related to the object being tracked or it can be a mesh that provides topology related to the anchor and can be used to create shadows/reflections on surfaces or for generating collision shapes.
</description>
</method>
<method name="get_plane" qualifiers="const">
<return type="Plane" />
<description>
@ -42,17 +24,4 @@
</description>
</method>
</methods>
<members>
<member name="anchor_id" type="int" setter="set_anchor_id" getter="get_anchor_id" default="1">
The anchor's ID. You can set this before the anchor itself exists. The first anchor gets an ID of [code]1[/code], the second an ID of [code]2[/code], etc. When anchors get removed, the engine can then assign the corresponding ID to new anchors. The most common situation where anchors "disappear" is when the AR server identifies that two anchors represent different parts of the same plane and merges them.
</member>
</members>
<signals>
<signal name="mesh_updated">
<argument index="0" name="mesh" type="Mesh" />
<description>
Emitted when the mesh associated with the anchor changes or when one becomes available. This is especially important for topology that is constantly being [code]mesh_updated[/code].
</description>
</signal>
</signals>
</class>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="XRController3D" inherits="Node3D" version="4.0">
<class name="XRController3D" inherits="XRNode3D" version="4.0">
<brief_description>
A spatial node representing a spatially-tracked controller.
</brief_description>
@ -7,40 +7,17 @@
This is a helper spatial node that is linked to the tracking of controllers. It also offers several handy passthroughs to the state of buttons and such on the controllers.
Controllers are linked by their ID. You can create controller nodes before the controllers are available. If your game always uses two controllers (one for each hand), you can predefine the controllers with ID 1 and 2; they will become active as soon as the controllers are identified. If you expect additional controllers to be used, you should react to the signals and add XRController3D nodes to your scene.
The position of the controller node is automatically updated by the [XRServer]. This makes this node ideal to add child nodes to visualize the controller.
As many XR runtimes now use a configurable action map all inputs are named.
</description>
<tutorials>
<link title="VR documentation index">https://docs.godotengine.org/en/latest/tutorials/vr/index.html</link>
</tutorials>
<methods>
<method name="get_controller_name" qualifiers="const">
<return type="String" />
<method name="get_axis" qualifiers="const">
<return type="Vector2" />
<argument index="0" name="name" type="StringName" />
<description>
If active, returns the name of the associated controller if provided by the AR/VR SDK used.
</description>
</method>
<method name="get_is_active" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if the bound controller is active. XR systems attempt to track active controllers.
</description>
</method>
<method name="get_joystick_axis" qualifiers="const">
<return type="float" />
<argument index="0" name="axis" type="int" />
<description>
Returns the value of the given axis for things like triggers, touchpads, etc. that are embedded into the controller.
</description>
</method>
<method name="get_joystick_id" qualifiers="const">
<return type="int" />
<description>
Returns the ID of the joystick object bound to this. Every controller tracked by the [XRServer] that has buttons and axis will also be registered as a joystick within Godot. This means that all the normal joystick tracking and input mapping will work for buttons and axis found on the AR/VR controllers. This ID is purely offered as information so you can link up the controller with its joystick entry.
</description>
</method>
<method name="get_mesh" qualifiers="const">
<return type="Mesh" />
<description>
If provided by the [XRInterface], this returns a mesh associated with the controller. This can be used to visualize the controller.
Returns a [Vector2] for the input with the given [code]name[/code]. This is used for thumbsticks and thumbpads found on many controllers.
</description>
</method>
<method name="get_tracker_hand" qualifiers="const">
@ -49,21 +26,22 @@
Returns the hand holding this controller, if known. See [enum XRPositionalTracker.TrackerHand].
</description>
</method>
<method name="get_value" qualifiers="const">
<return type="float" />
<argument index="0" name="name" type="StringName" />
<description>
Returns a numeric value for the input with the given [code]name[/code]. This is used for triggers and grip sensors.
</description>
</method>
<method name="is_button_pressed" qualifiers="const">
<return type="bool" />
<argument index="0" name="button" type="int" />
<argument index="0" name="name" type="StringName" />
<description>
Returns [code]true[/code] if the button at index [code]button[/code] is pressed. See [enum JoyButton].
Returns [code]true[/code] if the button with the given [code]name[/code] is pressed.
</description>
</method>
</methods>
<members>
<member name="controller_id" type="int" setter="set_controller_id" getter="get_controller_id" default="1">
The controller's ID.
A controller ID of 0 is unbound and will always result in an inactive node. Controller ID 1 is reserved for the first controller that identifies itself as the left-hand controller and ID 2 is reserved for the first controller that identifies itself as the right-hand controller.
For any other controller that the [XRServer] detects, we continue with controller ID 3.
When a controller is turned off, its slot is freed. This ensures controllers will keep the same ID even when controllers with lower IDs are turned off.
</member>
<member name="rumble" type="float" setter="set_rumble" getter="get_rumble" default="0.0">
The degree to which the controller vibrates. Ranges from [code]0.0[/code] to [code]1.0[/code] with precision [code].01[/code]. If changed, updates [member XRPositionalTracker.rumble] accordingly.
This is a useful property to animate if you want the controller to vibrate for a limited duration.
@ -71,21 +49,29 @@
</members>
<signals>
<signal name="button_pressed">
<argument index="0" name="button" type="int" />
<argument index="0" name="name" type="String" />
<description>
Emitted when a button on this controller is pressed.
</description>
</signal>
<signal name="button_released">
<argument index="0" name="button" type="int" />
<argument index="0" name="name" type="String" />
<description>
Emitted when a button on this controller is released.
</description>
</signal>
<signal name="mesh_updated">
<argument index="0" name="mesh" type="Mesh" />
<signal name="input_axis_changed">
<argument index="0" name="name" type="String" />
<argument index="1" name="value" type="Vector2" />
<description>
Emitted when the mesh associated with the controller changes or when one becomes available. Generally speaking this will be a static mesh after becoming available.
Emitted when a thumbstick or thumbpad on this controller is moved.
</description>
</signal>
<signal name="input_value_changed">
<argument index="0" name="name" type="String" />
<argument index="1" name="value" type="float" />
<description>
Emitted when a trigger or similar input on this controller changes value.
</description>
</signal>
</signals>

View File

@ -63,6 +63,20 @@
Is [code]true[/code] if this interface has been initialised.
</description>
</method>
<method name="trigger_haptic_pulse">
<return type="void" />
<argument index="0" name="action_name" type="String" />
<argument index="1" name="tracker_name" type="StringName" />
<argument index="2" name="frequency" type="float" />
<argument index="3" name="amplitude" type="float" />
<argument index="4" name="duration_sec" type="float" />
<argument index="5" name="delay_sec" type="float" />
<description>
Triggers a haptic pulse on a device associated with this interface.
[code]action_name[/code] is the name of the action for this pulse.
[code]tracker_name[/code] is optional and can be used to direct the pulse to a specific device provided that device is bound to this haptic.
</description>
</method>
<method name="uninitialize">
<return type="void" />
<description>
@ -88,21 +102,18 @@
<constant name="XR_STEREO" value="2" enum="Capabilities">
This interface supports stereoscopic rendering.
</constant>
<constant name="XR_AR" value="4" enum="Capabilities">
<constant name="XR_QUAD" value="4" enum="Capabilities">
This interface supports quad rendering (not yet supported by Godot).
</constant>
<constant name="XR_VR" value="8" enum="Capabilities">
this interface supports VR.
</constant>
<constant name="XR_AR" value="16" enum="Capabilities">
This interface supports AR (video background and real world tracking).
</constant>
<constant name="XR_EXTERNAL" value="8" enum="Capabilities">
<constant name="XR_EXTERNAL" value="32" enum="Capabilities">
This interface outputs to an external device. If the main viewport is used, the on screen output is an unmodified buffer of either the left or right eye (stretched if the viewport size is not changed to the same aspect ratio of [method get_render_target_size]). Using a separate viewport node frees up the main viewport for other purposes.
</constant>
<constant name="EYE_MONO" value="0" enum="Eyes">
Mono output, this is mostly used internally when retrieving positioning information for our camera node or when stereo scopic rendering is not supported.
</constant>
<constant name="EYE_LEFT" value="1" enum="Eyes">
Left eye output, this is mostly used internally when rendering the image for the left eye and obtaining positioning and projection information.
</constant>
<constant name="EYE_RIGHT" value="2" enum="Eyes">
Right eye output, this is mostly used internally when rendering the image for the right eye and obtaining positioning and projection information.
</constant>
<constant name="XR_NORMAL_TRACKING" value="0" enum="TrackingStatus">
Tracking is behaving as expected.
</constant>

View File

@ -55,6 +55,17 @@
<description>
</description>
</method>
<method name="_get_suggested_pose_names" qualifiers="virtual const">
<return type="PackedStringArray" />
<argument index="0" name="tracker_name" type="StringName" />
<description>
</description>
</method>
<method name="_get_suggested_tracker_names" qualifiers="virtual const">
<return type="PackedStringArray" />
<description>
</description>
</method>
<method name="_get_tracking_status" qualifiers="virtual const">
<return type="int" />
<description>
@ -99,6 +110,17 @@
<description>
</description>
</method>
<method name="_trigger_haptic_pulse" qualifiers="virtual">
<return type="void" />
<argument index="0" name="action_name" type="String" />
<argument index="1" name="tracker_name" type="StringName" />
<argument index="2" name="frequency" type="float" />
<argument index="3" name="amplitude" type="float" />
<argument index="4" name="duration_sec" type="float" />
<argument index="5" name="delay_sec" type="float" />
<description>
</description>
</method>
<method name="_uninitialize" qualifiers="virtual">
<return type="void" />
<description>

53
doc/classes/XRNode3D.xml Normal file
View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="XRNode3D" inherits="Node3D" version="4.0">
<brief_description>
A spatial node that has its position automatically updated by the [XRServer].
</brief_description>
<description>
This node can be bound to a specific pose of a [XRPositionalTracker] and will automatically have its [member Node3D.transform] updated by the [XRServer]. Nodes of this type must be added as children of the [XROrigin3D] node.
</description>
<tutorials>
</tutorials>
<methods>
<method name="get_has_tracking_data" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if the [member tracker] has current tracking data for the [member pose] being tracked.
</description>
</method>
<method name="get_is_active" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if the [member tracker] has been registered and the [member pose] is being tracked.
</description>
</method>
<method name="get_pose">
<return type="XRPose" />
<description>
Returns the [XRPose] containing the current state of the pose being tracked. This gives access to additional properties of this pose.
</description>
</method>
<method name="trigger_haptic_pulse">
<return type="void" />
<argument index="0" name="action_name" type="String" />
<argument index="1" name="frequency" type="float" />
<argument index="2" name="amplitude" type="float" />
<argument index="3" name="duration_sec" type="float" />
<argument index="4" name="delay_sec" type="float" />
<description>
Triggers a haptic pulse on a device associated with this interface.
[code]action_name[/code] is the name of the action for this pulse.
</description>
</method>
</methods>
<members>
<member name="pose" type="StringName" setter="set_pose_name" getter="get_pose_name" default="&amp;&quot;default&quot;">
The name of the pose we're bound to. Which poses a tracker supports is not known during design time.
Godot defines number of standard pose names such as [code]aim[/code] and [code]grip[/code] but other may be configured within a given [XRInterface].
</member>
<member name="tracker" type="StringName" setter="set_tracker" getter="get_tracker" default="&amp;&quot;&quot;">
The name of the tracker we're bound to. Which trackers are available is not known during design time.
Godot defines a number of standard trackers such as [code]left_hand[/code] and [code]right_hand[/code] but others may be configured within a given [XRInterface].
</member>
</members>
</class>

41
doc/classes/XRPose.xml Normal file
View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="XRPose" inherits="RefCounted" version="4.0">
<brief_description>
This object contains all data related to a pose on a tracked object.
</brief_description>
<description>
XR runtimes often identify multiple locations on devices such as controllers that are spatially tracked.
Orientation, location, linear velocity and angular velocity are all provided for each pose by the XR runtime. This object contains this state of a pose.
</description>
<tutorials>
</tutorials>
<methods>
<method name="get_adjusted_transform" qualifiers="const">
<return type="Transform3D" />
<description>
Returns the [member transform] with world scale and our reference frame applied. This is the transform used to position [XRNode3D] objects.
</description>
</method>
</methods>
<members>
<member name="angular_velocity" type="Vector3" setter="set_angular_velocity" getter="get_angular_velocity" default="Vector3(0, 0, 0)">
The angular velocity for this pose.
</member>
<member name="has_tracking_data" type="bool" setter="set_has_tracking_data" getter="get_has_tracking_data" default="false">
If [code]true[/code] our tracking data is up to date. If [code]false[/code] we're no longer receiving new tracking data and our state is whatever that last valid state was.
</member>
<member name="linear_velocity" type="Vector3" setter="set_linear_velocity" getter="get_linear_velocity" default="Vector3(0, 0, 0)">
The linear velocity of this pose.
</member>
<member name="name" type="StringName" setter="set_name" getter="get_name" default="&amp;&quot;&quot;">
The name of this pose. Pose names are often driven by an action map setup by the user. Godot does suggest a number of pose names that it expects [XRInterface]s to implement:
- [code]root[/code] defines a root location, often used for tracked objects that do not have further nodes.
- [code]aim[/code] defines the tip of a controller with the orientation pointing outwards, for instance: add your raycasts to this.
- [code]grip[/code] defines the location where the user grips the controller
- [code]skeleton[/code] defines the root location a hand mesh should be placed when using hand tracking and the animated skeleton supplied by the XR runtime.
</member>
<member name="transform" type="Transform3D" setter="set_transform" getter="get_transform" default="Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)">
The transform containing the original and transform as reported by the XR runtime.
</member>
</members>
</class>

View File

@ -5,86 +5,113 @@
</brief_description>
<description>
An instance of this object represents a device that is tracked, such as a controller or anchor point. HMDs aren't represented here as they are handled internally.
As controllers are turned on and the AR/VR interface detects them, instances of this object are automatically added to this list of active tracking objects accessible through the [XRServer].
The [XRController3D] and [XRAnchor3D] both consume objects of this type and should be used in your project. The positional trackers are just under-the-hood objects that make this all work. These are mostly exposed so that GDNative-based interfaces can interact with them.
As controllers are turned on and the [XRInterface] detects them, instances of this object are automatically added to this list of active tracking objects accessible through the [XRServer].
The [XRController3D] and [XRAnchor3D] both consume objects of this type and should be used in your project. The positional trackers are just under-the-hood objects that make this all work. These are mostly exposed so that GDExtension-based interfaces can interact with them.
</description>
<tutorials>
<link title="VR documentation index">https://docs.godotengine.org/en/latest/tutorials/vr/index.html</link>
</tutorials>
<methods>
<method name="get_joy_id" qualifiers="const">
<return type="int" />
<method name="get_input" qualifiers="const">
<return type="Variant" />
<argument index="0" name="name" type="StringName" />
<description>
If this is a controller that is being tracked, the controller will also be represented by a joystick entry with this ID.
Returns an input for this tracker. It can return a boolean, float or [Vector2] value depending on whether the input is a button, trigger or thumbstick/thumbpad.
</description>
</method>
<method name="get_mesh" qualifiers="const">
<return type="Mesh" />
<method name="get_pose" qualifiers="const">
<return type="XRPose" />
<argument index="0" name="name" type="StringName" />
<description>
Returns the mesh related to a controller or anchor point if one is available.
Returns the current [XRPose] state object for the bound [code]pose[/code].
</description>
</method>
<method name="get_orientation" qualifiers="const">
<return type="Basis" />
<description>
Returns the controller's orientation matrix.
</description>
</method>
<method name="get_position" qualifiers="const">
<return type="Vector3" />
<description>
Returns the world-space controller position.
</description>
</method>
<method name="get_tracker_hand" qualifiers="const">
<return type="int" enum="XRPositionalTracker.TrackerHand" />
<description>
Returns the hand holding this tracker, if known. See [enum TrackerHand] constants.
</description>
</method>
<method name="get_tracker_id" qualifiers="const">
<return type="int" />
<description>
Returns the internal tracker ID. This uniquely identifies the tracker per tracker type and matches the ID you need to specify for nodes such as the [XRController3D] and [XRAnchor3D] nodes.
</description>
</method>
<method name="get_tracker_name" qualifiers="const">
<return type="StringName" />
<description>
Returns the controller or anchor point's name, if applicable.
</description>
</method>
<method name="get_tracker_type" qualifiers="const">
<return type="int" enum="XRServer.TrackerType" />
<description>
Returns the tracker's type, which will be one of the values from the [enum XRServer.TrackerType] enum.
</description>
</method>
<method name="get_transform" qualifiers="const">
<return type="Transform3D" />
<argument index="0" name="adjust_by_reference_frame" type="bool" />
<description>
Returns the transform combining this device's orientation and position.
</description>
</method>
<method name="is_tracking_orientation" qualifiers="const">
<method name="has_pose" qualifiers="const">
<return type="bool" />
<argument index="0" name="name" type="StringName" />
<description>
Returns [code]true[/code] if this device is tracking orientation.
Returns [code]true[/code] if the bound [code]tracker[/code] is available and is currently tracking the bound [code]pose[/code].
</description>
</method>
<method name="is_tracking_position" qualifiers="const">
<return type="bool" />
<method name="invalidate_pose">
<return type="void" />
<argument index="0" name="name" type="StringName" />
<description>
Returns [code]true[/code] if this device is tracking position.
Marks this pose as invalid, we don't clear the last reported state but it allows users to decide if trackers need to be hidden if we loose tracking or just remain at their last known position.
</description>
</method>
<method name="set_input">
<return type="void" />
<argument index="0" name="name" type="StringName" />
<argument index="1" name="value" type="Variant" />
<description>
Changes the value for the given input. This method is called by a [XRInterface] implementation and should not be used directly.
</description>
</method>
<method name="set_pose">
<return type="void" />
<argument index="0" name="name" type="StringName" />
<argument index="1" name="transform" type="Transform3D" />
<argument index="2" name="linear_velocity" type="Vector3" />
<argument index="3" name="angular_velocity" type="Vector3" />
<description>
Sets the transform, linear velocity and angular velocity for the given pose. This method is called by a [XRInterface] implementation and should not be used directly.
</description>
</method>
</methods>
<members>
<member name="description" type="String" setter="set_tracker_desc" getter="get_tracker_desc" default="&quot;&quot;">
The description of this tracker.
</member>
<member name="hand" type="int" setter="set_tracker_hand" getter="get_tracker_hand" enum="XRPositionalTracker.TrackerHand" default="0">
Defines which hand this tracker relates to.
</member>
<member name="name" type="StringName" setter="set_tracker_name" getter="get_tracker_name" default="&amp;&quot;Unknown&quot;">
The unique name of this tracker. The trackers that are available differ between various XR runtimes and can often be configured by the user. Godot maintains a number of reserved names that it expects the [XRInterface] to implement if applicable:
- [code]left_hand[/code] identifies the controller held in the players left hand
- [code]right_hand[/code] identifies the controller held in the players right hand
</member>
<member name="rumble" type="float" setter="set_rumble" getter="get_rumble" default="0.0">
The degree to which the tracker rumbles. Ranges from [code]0.0[/code] to [code]1.0[/code] with precision [code].01[/code].
</member>
<member name="type" type="int" setter="set_tracker_type" getter="get_tracker_type" enum="XRServer.TrackerType" default="128">
The type of tracker.
</member>
</members>
<signals>
<signal name="button_pressed">
<argument index="0" name="name" type="String" />
<description>
Emitted when a button on this tracker is pressed. Note that many XR runtimes allow other inputs to be mapped to buttons.
</description>
</signal>
<signal name="button_released">
<argument index="0" name="name" type="String" />
<description>
Emitted when a button on this tracker is released.
</description>
</signal>
<signal name="input_axis_changed">
<argument index="0" name="name" type="String" />
<argument index="1" name="vector" type="Vector2" />
<description>
Emitted when a thumbstick or thumbpad on this tracker moves.
</description>
</signal>
<signal name="input_value_changed">
<argument index="0" name="name" type="String" />
<argument index="1" name="value" type="float" />
<description>
Emitted when a trigger or similar input on this tracker changes value.
</description>
</signal>
<signal name="pose_changed">
<argument index="0" name="pose" type="XRPose" />
<description>
Emitted when the state of a pose tracked by this tracker changes.
</description>
</signal>
</signals>
<constants>
<constant name="TRACKER_HAND_UNKNOWN" value="0" enum="TrackerHand">
The hand this tracker is held in is unknown or not applicable.

View File

@ -90,20 +90,21 @@
<method name="get_reference_frame" qualifiers="const">
<return type="Transform3D" />
<description>
Returns the reference frame transform. Mostly used internally and exposed for GDNative build interfaces.
Returns the reference frame transform. Mostly used internally and exposed for GDExtension build interfaces.
</description>
</method>
<method name="get_tracker" qualifiers="const">
<return type="XRPositionalTracker" />
<argument index="0" name="idx" type="int" />
<argument index="0" name="tracker_name" type="StringName" />
<description>
Returns the positional tracker at the given ID.
Returns the positional tracker with this name.
</description>
</method>
<method name="get_tracker_count" qualifiers="const">
<return type="int" />
<method name="get_trackers">
<return type="Dictionary" />
<argument index="0" name="tracker_types" type="int" />
<description>
Returns the number of trackers currently registered.
Returns a dictionary of trackers for this type.
</description>
</method>
<method name="remove_interface">
@ -145,7 +146,6 @@
<signal name="tracker_added">
<argument index="0" name="tracker_name" type="StringName" />
<argument index="1" name="type" type="int" />
<argument index="2" name="id" type="int" />
<description>
Emitted when a new tracker has been added. If you don't use a fixed number of controllers or if you're using [XRAnchor3D]s for an AR solution, it is important to react to this signal to add the appropriate [XRController3D] or [XRAnchor3D] nodes related to this new tracker.
</description>
@ -153,20 +153,29 @@
<signal name="tracker_removed">
<argument index="0" name="tracker_name" type="StringName" />
<argument index="1" name="type" type="int" />
<argument index="2" name="id" type="int" />
<description>
Emitted when a tracker is removed. You should remove any [XRController3D] or [XRAnchor3D] points if applicable. This is not mandatory, the nodes simply become inactive and will be made active again when a new tracker becomes available (i.e. a new controller is switched on that takes the place of the previous one).
</description>
</signal>
<signal name="tracker_updated">
<argument index="0" name="tracker_name" type="StringName" />
<argument index="1" name="type" type="int" />
<description>
Emitted when an existing tracker has been updated. This can happen if the user switches controllers.
</description>
</signal>
</signals>
<constants>
<constant name="TRACKER_CONTROLLER" value="1" enum="TrackerType">
<constant name="TRACKER_HEAD" value="1" enum="TrackerType">
The tracker tracks the location of the players head. This is usually a location centered between the players eyes. Note that for handheld AR devices this can be the current location of the device.
</constant>
<constant name="TRACKER_CONTROLLER" value="2" enum="TrackerType">
The tracker tracks the location of a controller.
</constant>
<constant name="TRACKER_BASESTATION" value="2" enum="TrackerType">
<constant name="TRACKER_BASESTATION" value="4" enum="TrackerType">
The tracker tracks the location of a base station.
</constant>
<constant name="TRACKER_ANCHOR" value="4" enum="TrackerType">
<constant name="TRACKER_ANCHOR" value="8" enum="TrackerType">
The tracker tracks the location and size of an AR anchor.
</constant>
<constant name="TRACKER_ANY_KNOWN" value="127" enum="TrackerType">

View File

@ -2522,6 +2522,9 @@ bool Main::iteration() {
bool exit = false;
// process all our active interfaces
XRServer::get_singleton()->_process();
for (int iters = 0; iters < advance.physics_steps; ++iters) {
if (Input::get_singleton()->is_using_input_buffering() && agile_input_event_flushing) {
Input::get_singleton()->flush_buffered_events();

View File

@ -126,6 +126,8 @@ void MobileVRInterface::set_position_from_sensors() {
// 9dof is a misleading marketing term coming from 3 accelerometer axis + 3 gyro axis + 3 magnetometer axis = 9 axis
// but in reality this only offers 3 dof (yaw, pitch, roll) orientation
Basis orientation;
uint64_t ticks = OS::get_singleton()->get_ticks_usec();
uint64_t ticks_elapsed = ticks - last_ticks;
float delta_time = (double)ticks_elapsed / 1000000.0;
@ -207,8 +209,8 @@ void MobileVRInterface::set_position_from_sensors() {
};
};
// JIC
orientation.orthonormalize();
// and copy to our head transform
head_transform.basis = orientation.orthonormalized();
last_ticks = ticks;
};
@ -318,7 +320,7 @@ bool MobileVRInterface::initialize() {
ERR_FAIL_NULL_V(xr_server, false);
if (!initialized) {
// reset our sensor data and orientation
// reset our sensor data
mag_count = 0;
has_gyro = false;
sensor_first = true;
@ -326,9 +328,15 @@ bool MobileVRInterface::initialize() {
mag_next_max = Vector3(-10000, -10000, -10000);
mag_current_min = Vector3(0, 0, 0);
mag_current_max = Vector3(0, 0, 0);
head_transform.basis = Basis();
head_transform.origin = Vector3(0.0, eye_height, 0.0);
// reset our orientation
orientation = Basis();
// we must create a tracker for our head
head.instantiate();
head->set_tracker_type(XRServer::TRACKER_HEAD);
head->set_tracker_name("head");
head->set_tracker_desc("Players head");
xr_server->add_tracker(head);
// make this our primary interface
xr_server->set_primary_interface(this);
@ -343,10 +351,19 @@ bool MobileVRInterface::initialize() {
void MobileVRInterface::uninitialize() {
if (initialized) {
// do any cleanup here...
XRServer *xr_server = XRServer::get_singleton();
if (xr_server != nullptr && xr_server->get_primary_interface() == this) {
// no longer our primary interface
xr_server->set_primary_interface(nullptr);
if (xr_server != nullptr) {
if (head.is_valid()) {
xr_server->remove_tracker(head);
head.unref();
}
if (xr_server->get_primary_interface() == this) {
// no longer our primary interface
xr_server->set_primary_interface(nullptr);
}
}
initialized = false;
@ -377,11 +394,10 @@ Transform3D MobileVRInterface::get_camera_transform() {
float world_scale = xr_server->get_world_scale();
// just scale our origin point of our transform
Transform3D hmd_transform;
hmd_transform.basis = orientation;
hmd_transform.origin = Vector3(0.0, eye_height * world_scale, 0.0);
Transform3D _head_transform = head_transform;
_head_transform.origin *= world_scale;
transform_for_eye = (xr_server->get_reference_frame()) * hmd_transform;
transform_for_eye = (xr_server->get_reference_frame()) * _head_transform;
}
return transform_for_eye;
@ -409,11 +425,10 @@ Transform3D MobileVRInterface::get_transform_for_view(uint32_t p_view, const Tra
};
// just scale our origin point of our transform
Transform3D hmd_transform;
hmd_transform.basis = orientation;
hmd_transform.origin = Vector3(0.0, eye_height * world_scale, 0.0);
Transform3D _head_transform = head_transform;
_head_transform.origin *= world_scale;
transform_for_eye = p_cam_transform * (xr_server->get_reference_frame()) * hmd_transform * transform_for_eye;
transform_for_eye = p_cam_transform * (xr_server->get_reference_frame()) * _head_transform * transform_for_eye;
} else {
// huh? well just return what we got....
transform_for_eye = p_cam_transform;
@ -476,7 +491,16 @@ void MobileVRInterface::process() {
_THREAD_SAFE_METHOD_
if (initialized) {
// update our head transform orientation
set_position_from_sensors();
// update our head transform position (should be constant)
head_transform.origin = Vector3(0.0, eye_height, 0.0);
if (head.is_valid()) {
// Set our head position, note in real space, reference frame and world scale is applied later
head->set_pose("default", head_transform, Vector3(), Vector3());
}
};
};

View File

@ -53,7 +53,6 @@ class MobileVRInterface : public XRInterface {
private:
bool initialized = false;
XRInterface::TrackingStatus tracking_state;
Basis orientation;
// Just set some defaults for these. At some point we need to look at adding a lookup table for common device + headset combos and/or support reading cardboard QR codes
double eye_height = 1.85;
@ -68,6 +67,10 @@ private:
double k2 = 0.215;
double aspect = 1.0;
// at a minimum we need a tracker for our head
Ref<XRPositionalTracker> head;
Transform3D head_transform;
/*
logic for processing our sensor data, this was originally in our positional tracker logic but I think
that doesn't make sense in hindsight. It only makes marginally more sense to park it here for now,

View File

@ -87,7 +87,7 @@
There are several ways to handle "controller" input:
- Using [XRController3D] nodes and their [signal XRController3D.button_pressed] and [signal XRController3D.button_released] signals. This is how controllers are typically handled in AR/VR apps in Godot, however, this will only work with advanced VR controllers like the Oculus Touch or Index controllers, for example. The buttons codes are defined by [url=https://immersive-web.github.io/webxr-gamepads-module/#xr-standard-gamepad-mapping]Section 3.3 of the WebXR Gamepads Module[/url].
- Using [method Node._unhandled_input] and [InputEventJoypadButton] or [InputEventJoypadMotion]. This works the same as normal joypads, except the [member InputEvent.device] starts at 100, so the left controller is 100 and the right controller is 101, and the button codes are also defined by [url=https://immersive-web.github.io/webxr-gamepads-module/#xr-standard-gamepad-mapping]Section 3.3 of the WebXR Gamepads Module[/url].
- Using the [signal select], [signal squeeze] and related signals. This method will work for both advanced VR controllers, and non-traditional "controllers" like a tap on the screen, a spoken voice command or a button press on the device itself. The [code]controller_id[/code] passed to these signals is the same id as used in [member XRController3D.controller_id].
- Using the [signal select], [signal squeeze] and related signals. This method will work for both advanced VR controllers, and non-traditional "controllers" like a tap on the screen, a spoken voice command or a button press on the device itself.
You can use one or all of these methods to allow your game or app to support a wider or narrower set of devices and input methods, or to allow more advanced interactions with more advanced devices.
</description>
<tutorials>

View File

@ -163,9 +163,14 @@ String WebXRInterfaceJS::get_reference_space_type() const {
Ref<XRPositionalTracker> WebXRInterfaceJS::get_controller(int p_controller_id) const {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL_V(xr_server, nullptr);
ERR_FAIL_NULL_V(xr_server, Ref<XRPositionalTracker>());
return xr_server->find_by_type_and_id(XRServer::TRACKER_CONTROLLER, p_controller_id);
// TODO support more then two controllers
if (p_controller_id >= 0 && p_controller_id < 2) {
return controllers[p_controller_id];
};
return Ref<XRPositionalTracker>();
}
String WebXRInterfaceJS::get_visibility_state() const {
@ -224,6 +229,13 @@ bool WebXRInterfaceJS::initialize() {
return false;
}
// we must create a tracker for our head
head_tracker.instantiate();
head_tracker->set_tracker_type(XRServer::TRACKER_HEAD);
head_tracker->set_tracker_name("head");
head_tracker->set_tracker_desc("Players head");
xr_server->add_tracker(head_tracker);
// make this our primary interface
xr_server->set_primary_interface(this);
@ -254,9 +266,17 @@ bool WebXRInterfaceJS::initialize() {
void WebXRInterfaceJS::uninitialize() {
if (initialized) {
XRServer *xr_server = XRServer::get_singleton();
if (xr_server != nullptr && xr_server->get_primary_interface() == this) {
// no longer our primary interface
xr_server->set_primary_interface(nullptr);
if (xr_server != nullptr) {
if (head_tracker.is_valid()) {
xr_server->remove_tracker(head_tracker);
head_tracker.unref();
}
if (xr_server->get_primary_interface() == this) {
// no longer our primary interface
xr_server->set_primary_interface(nullptr);
}
}
godot_webxr_uninitialize();
@ -373,9 +393,9 @@ Vector<BlitToScreen> WebXRInterfaceJS::commit_views(RID p_render_target, const R
}
// @todo Refactor this to be based on "views" rather than "eyes".
godot_webxr_commit_for_eye(XRInterface::EYE_LEFT);
godot_webxr_commit_for_eye(1);
if (godot_webxr_get_view_count() > 1) {
godot_webxr_commit_for_eye(XRInterface::EYE_RIGHT);
godot_webxr_commit_for_eye(2);
}
return blit_to_screen;
@ -385,6 +405,11 @@ void WebXRInterfaceJS::process() {
if (initialized) {
godot_webxr_sample_controller_data();
if (head_tracker.is_valid()) {
// TODO set default pose to our head location (i.e. get_camera_transform without world scale and reference frame applied)
// head_tracker->set_pose("default", head_transform, Vector3(), Vector3());
}
int controller_count = godot_webxr_get_controller_count();
if (controller_count == 0) {
return;
@ -400,51 +425,70 @@ void WebXRInterfaceJS::_update_tracker(int p_controller_id) {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_CONTROLLER, p_controller_id + 1);
// need to support more then two controllers...
if (p_controller_id < 0 || p_controller_id > 1) {
return;
}
Ref<XRPositionalTracker> tracker = controllers[p_controller_id];
if (godot_webxr_is_controller_connected(p_controller_id)) {
if (tracker.is_null()) {
tracker.instantiate();
tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER);
// Controller id's 0 and 1 are always the left and right hands.
if (p_controller_id < 2) {
tracker->set_tracker_name(p_controller_id == 0 ? "Left" : "Right");
tracker->set_tracker_name(p_controller_id == 0 ? "left_hand" : "right_hand");
tracker->set_tracker_desc(p_controller_id == 0 ? "Left hand controller" : "Right hand controller");
tracker->set_tracker_hand(p_controller_id == 0 ? XRPositionalTracker::TRACKER_HAND_LEFT : XRPositionalTracker::TRACKER_HAND_RIGHT);
} else {
char name[1024];
sprintf(name, "tracker_%i", p_controller_id);
tracker->set_tracker_name(name);
tracker->set_tracker_desc(name);
}
// Use the ids we're giving to our "virtual" gamepads.
tracker->set_joy_id(p_controller_id + 100);
xr_server->add_tracker(tracker);
}
Input *input = Input::get_singleton();
float *tracker_matrix = godot_webxr_get_controller_transform(p_controller_id);
if (tracker_matrix) {
// Note, poses should NOT have world scale and our reference frame applied!
Transform3D transform = _js_matrix_to_transform(tracker_matrix);
tracker->set_position(transform.origin);
tracker->set_orientation(transform.basis);
tracker->set_pose("default", transform, Vector3(), Vector3());
free(tracker_matrix);
}
// TODO implement additional poses such as "aim" and "grip"
int *buttons = godot_webxr_get_controller_buttons(p_controller_id);
if (buttons) {
// TODO buttons should be named properly, this is just a temporary fix
for (int i = 0; i < buttons[0]; i++) {
input->joy_button(p_controller_id + 100, (JoyButton)i, *((float *)buttons + (i + 1)));
char name[1024];
sprintf(name, "button_%i", i);
float value = *((float *)buttons + (i + 1));
bool state = value > 0.0;
tracker->set_input(name, state);
}
free(buttons);
}
int *axes = godot_webxr_get_controller_axes(p_controller_id);
if (axes) {
// TODO again just a temporary fix, split these between proper float and vector2 inputs
for (int i = 0; i < axes[0]; i++) {
Input::JoyAxisValue joy_axis;
joy_axis.min = -1;
joy_axis.value = *((float *)axes + (i + 1));
input->joy_axis(p_controller_id + 100, (JoyAxis)i, joy_axis);
char name[1024];
sprintf(name, "axis_%i", i);
float value = *((float *)axes + (i + 1));
;
tracker->set_input(name, value);
}
free(axes);
}
} else if (tracker.is_valid()) {
xr_server->remove_tracker(tracker);
controllers[p_controller_id].unref();
}
}
@ -454,7 +498,7 @@ void WebXRInterfaceJS::_on_controller_changed() {
for (int i = 0; i < 2; i++) {
bool controller_connected = godot_webxr_is_controller_connected(i);
if (controllers_state[i] != controller_connected) {
Input::get_singleton()->joy_connection_changed(i + 100, controller_connected, i == 0 ? "Left" : "Right", "");
// Input::get_singleton()->joy_connection_changed(i + 100, controller_connected, i == 0 ? "Left" : "Right", "");
controllers_state[i] = controller_connected;
}
}

View File

@ -46,6 +46,7 @@ class WebXRInterfaceJS : public WebXRInterface {
private:
bool initialized;
Ref<XRPositionalTracker> head_tracker;
String session_mode;
String required_features;
@ -53,7 +54,9 @@ private:
String requested_reference_space_types;
String reference_space_type;
// TODO maybe turn into a vector to support more then 2 controllers...
bool controllers_state[2];
Ref<XRPositionalTracker> controllers[2];
Size2 render_targetsize;
Transform3D _js_matrix_to_transform(float *p_js_matrix);

View File

@ -47,13 +47,45 @@ void XRCamera3D::_notification(int p_what) {
case NOTIFICATION_EXIT_TREE: {
// need to find our XROrigin3D parent and let it know we're no longer its camera!
XROrigin3D *origin = Object::cast_to<XROrigin3D>(get_parent());
if (origin != nullptr) {
origin->clear_tracked_camera_if(this);
if (origin != nullptr && origin->get_tracked_camera() == this) {
origin->set_tracked_camera(nullptr);
}
}; break;
};
};
void XRCamera3D::_changed_tracker(const StringName p_tracker_name, int p_tracker_type) {
if (p_tracker_name == tracker_name) {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
tracker = xr_server->get_tracker(p_tracker_name);
if (tracker.is_valid()) {
tracker->connect("pose_changed", callable_mp(this, &XRCamera3D::_pose_changed));
Ref<XRPose> pose = tracker->get_pose(pose_name);
if (pose.is_valid()) {
set_transform(pose->get_adjusted_transform());
}
}
}
}
void XRCamera3D::_removed_tracker(const StringName p_tracker_name, int p_tracker_type) {
if (p_tracker_name == tracker_name) {
if (tracker.is_valid()) {
tracker->disconnect("pose_changed", callable_mp(this, &XRCamera3D::_pose_changed));
}
tracker.unref();
}
}
void XRCamera3D::_pose_changed(const Ref<XRPose> &p_pose) {
if (p_pose->get_name() == pose_name) {
set_transform(p_pose->get_adjusted_transform());
}
}
TypedArray<String> XRCamera3D::get_configuration_warnings() const {
TypedArray<String> warnings = Node::get_configuration_warnings();
@ -172,195 +204,215 @@ Vector<Plane> XRCamera3D::get_frustum() const {
return cm.get_projection_planes(get_camera_transform());
};
////////////////////////////////////////////////////////////////////////////////////////////////////
void XRController3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
set_process_internal(true);
}; break;
case NOTIFICATION_EXIT_TREE: {
set_process_internal(false);
}; break;
case NOTIFICATION_INTERNAL_PROCESS: {
// get our XRServer
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
// find the tracker for our controller
Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_CONTROLLER, controller_id);
if (!tracker.is_valid()) {
// this controller is currently turned off
is_active = false;
button_states = 0;
} else {
is_active = true;
set_transform(tracker->get_transform(true));
int joy_id = tracker->get_joy_id();
if (joy_id >= 0) {
int mask = 1;
// check button states
for (int i = 0; i < 16; i++) {
bool was_pressed = (button_states & mask) == mask;
bool is_pressed = Input::get_singleton()->is_joy_button_pressed(joy_id, (JoyButton)i);
if (!was_pressed && is_pressed) {
emit_signal(SNAME("button_pressed"), i);
button_states += mask;
} else if (was_pressed && !is_pressed) {
emit_signal(SNAME("button_released"), i);
button_states -= mask;
};
mask = mask << 1;
};
} else {
button_states = 0;
};
// check for an updated mesh
Ref<Mesh> trackerMesh = tracker->get_mesh();
if (mesh != trackerMesh) {
mesh = trackerMesh;
emit_signal(SNAME("mesh_updated"), mesh);
}
};
}; break;
default:
break;
};
};
void XRController3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_controller_id", "controller_id"), &XRController3D::set_controller_id);
ClassDB::bind_method(D_METHOD("get_controller_id"), &XRController3D::get_controller_id);
ADD_PROPERTY(PropertyInfo(Variant::INT, "controller_id", PROPERTY_HINT_RANGE, "0,32,1"), "set_controller_id", "get_controller_id");
ClassDB::bind_method(D_METHOD("get_controller_name"), &XRController3D::get_controller_name);
// passthroughs to information about our related joystick
ClassDB::bind_method(D_METHOD("get_joystick_id"), &XRController3D::get_joystick_id);
ClassDB::bind_method(D_METHOD("is_button_pressed", "button"), &XRController3D::is_button_pressed);
ClassDB::bind_method(D_METHOD("get_joystick_axis", "axis"), &XRController3D::get_joystick_axis);
ClassDB::bind_method(D_METHOD("get_is_active"), &XRController3D::get_is_active);
ClassDB::bind_method(D_METHOD("get_tracker_hand"), &XRController3D::get_tracker_hand);
ClassDB::bind_method(D_METHOD("get_rumble"), &XRController3D::get_rumble);
ClassDB::bind_method(D_METHOD("set_rumble", "rumble"), &XRController3D::set_rumble);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rumble", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_rumble", "get_rumble");
ADD_PROPERTY_DEFAULT("rumble", 0.0);
ClassDB::bind_method(D_METHOD("get_mesh"), &XRController3D::get_mesh);
ADD_SIGNAL(MethodInfo("button_pressed", PropertyInfo(Variant::INT, "button")));
ADD_SIGNAL(MethodInfo("button_released", PropertyInfo(Variant::INT, "button")));
ADD_SIGNAL(MethodInfo("mesh_updated", PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh")));
};
void XRController3D::set_controller_id(int p_controller_id) {
// We don't check any bounds here, this controller may not yet be active and just be a place holder until it is.
// Note that setting this to 0 means this node is not bound to a controller yet.
controller_id = p_controller_id;
update_configuration_warnings();
};
int XRController3D::get_controller_id() const {
return controller_id;
};
String XRController3D::get_controller_name() const {
// get our XRServer
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL_V(xr_server, String());
Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_CONTROLLER, controller_id);
if (!tracker.is_valid()) {
return String("Not connected");
};
return tracker->get_tracker_name();
};
int XRController3D::get_joystick_id() const {
// get our XRServer
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL_V(xr_server, 0);
Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_CONTROLLER, controller_id);
if (!tracker.is_valid()) {
// No tracker? no joystick id... (0 is our first joystick)
return -1;
};
return tracker->get_joy_id();
};
bool XRController3D::is_button_pressed(int p_button) const {
int joy_id = get_joystick_id();
if (joy_id == -1) {
return false;
};
return Input::get_singleton()->is_joy_button_pressed(joy_id, (JoyButton)p_button);
};
float XRController3D::get_joystick_axis(int p_axis) const {
int joy_id = get_joystick_id();
if (joy_id == -1) {
return 0.0;
};
return Input::get_singleton()->get_joy_axis(joy_id, (JoyAxis)p_axis);
};
real_t XRController3D::get_rumble() const {
// get our XRServer
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL_V(xr_server, 0.0);
Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_CONTROLLER, controller_id);
if (!tracker.is_valid()) {
return 0.0;
};
return tracker->get_rumble();
};
void XRController3D::set_rumble(real_t p_rumble) {
// get our XRServer
XRCamera3D::XRCamera3D() {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_CONTROLLER, controller_id);
if (tracker.is_valid()) {
tracker->set_rumble(p_rumble);
};
};
Ref<Mesh> XRController3D::get_mesh() const {
return mesh;
xr_server->connect("tracker_added", callable_mp(this, &XRCamera3D::_changed_tracker));
xr_server->connect("tracker_updated", callable_mp(this, &XRCamera3D::_changed_tracker));
xr_server->connect("tracker_removed", callable_mp(this, &XRCamera3D::_removed_tracker));
}
bool XRController3D::get_is_active() const {
return is_active;
};
XRPositionalTracker::TrackerHand XRController3D::get_tracker_hand() const {
// get our XRServer
XRCamera3D::~XRCamera3D() {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL_V(xr_server, XRPositionalTracker::TRACKER_HAND_UNKNOWN);
ERR_FAIL_NULL(xr_server);
Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_CONTROLLER, controller_id);
if (!tracker.is_valid()) {
return XRPositionalTracker::TRACKER_HAND_UNKNOWN;
};
xr_server->disconnect("tracker_added", callable_mp(this, &XRCamera3D::_changed_tracker));
xr_server->disconnect("tracker_updated", callable_mp(this, &XRCamera3D::_changed_tracker));
xr_server->disconnect("tracker_removed", callable_mp(this, &XRCamera3D::_removed_tracker));
}
return tracker->get_tracker_hand();
////////////////////////////////////////////////////////////////////////////////////////////////////
// XRNode3D is a node that has it's transform updated by an XRPositionalTracker.
// Note that trackers are only available in runtime and only after an XRInterface registers one.
// So we bind by name and as long as a tracker isn't available, our node remains inactive.
void XRNode3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_tracker", "tracker_name"), &XRNode3D::set_tracker);
ClassDB::bind_method(D_METHOD("get_tracker"), &XRNode3D::get_tracker);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "tracker", PROPERTY_HINT_ENUM_SUGGESTION), "set_tracker", "get_tracker");
ClassDB::bind_method(D_METHOD("set_pose_name", "pose"), &XRNode3D::set_pose_name);
ClassDB::bind_method(D_METHOD("get_pose_name"), &XRNode3D::get_pose_name);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "pose", PROPERTY_HINT_ENUM_SUGGESTION), "set_pose_name", "get_pose_name");
ClassDB::bind_method(D_METHOD("get_is_active"), &XRNode3D::get_is_active);
ClassDB::bind_method(D_METHOD("get_has_tracking_data"), &XRNode3D::get_has_tracking_data);
ClassDB::bind_method(D_METHOD("get_pose"), &XRNode3D::get_pose);
ClassDB::bind_method(D_METHOD("trigger_haptic_pulse", "action_name", "frequency", "amplitude", "duration_sec", "delay_sec"), &XRNode3D::trigger_haptic_pulse);
};
TypedArray<String> XRController3D::get_configuration_warnings() const {
void XRNode3D::_validate_property(PropertyInfo &property) const {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
if (property.name == "tracker") {
PackedStringArray names = xr_server->get_suggested_tracker_names();
String hint_string;
for (const String &name : names) {
hint_string += name + ",";
}
property.hint_string = hint_string;
} else if (property.name == "pose") {
PackedStringArray names = xr_server->get_suggested_pose_names(tracker_name);
String hint_string;
for (const String &name : names) {
hint_string += name + ",";
}
property.hint_string = hint_string;
}
}
void XRNode3D::set_tracker(const StringName p_tracker_name) {
if (tracker.is_valid() && tracker->get_tracker_name() == p_tracker_name) {
// didn't change
return;
}
// just in case
_unbind_tracker();
// copy the name
tracker_name = p_tracker_name;
pose_name = "default";
// see if it's already available
_bind_tracker();
update_configuration_warnings();
notify_property_list_changed();
}
StringName XRNode3D::get_tracker() const {
return tracker_name;
}
void XRNode3D::set_pose_name(const StringName p_pose_name) {
pose_name = p_pose_name;
// Update pose if we are bound to a tracker with a valid pose
Ref<XRPose> pose = get_pose();
if (pose.is_valid()) {
set_transform(pose->get_adjusted_transform());
}
}
StringName XRNode3D::get_pose_name() const {
return pose_name;
}
bool XRNode3D::get_is_active() const {
if (tracker.is_null()) {
return false;
} else if (!tracker->has_pose(pose_name)) {
return false;
} else {
return true;
}
}
bool XRNode3D::get_has_tracking_data() const {
if (tracker.is_null()) {
return false;
} else if (!tracker->has_pose(pose_name)) {
return false;
} else {
return tracker->get_pose(pose_name)->get_has_tracking_data();
}
}
void XRNode3D::trigger_haptic_pulse(const String &p_action_name, double p_frequency, double p_amplitude, double p_duration_sec, double p_delay_sec) {
// TODO need to link trackers to the interface that registered them so we can call this on the correct interface.
// For now this works fine as in 99% of the cases we only have our primary interface active
XRServer *xr_server = XRServer::get_singleton();
if (xr_server != nullptr) {
Ref<XRInterface> xr_interface = xr_server->get_primary_interface();
if (xr_interface.is_valid()) {
xr_interface->trigger_haptic_pulse(p_action_name, tracker_name, p_frequency, p_amplitude, p_duration_sec, p_delay_sec);
}
}
}
Ref<XRPose> XRNode3D::get_pose() {
if (tracker.is_valid()) {
return tracker->get_pose(pose_name);
} else {
return Ref<XRPose>();
}
}
void XRNode3D::_bind_tracker() {
ERR_FAIL_COND_MSG(tracker.is_valid(), "Unbind the current tracker first");
XRServer *xr_server = XRServer::get_singleton();
if (xr_server != nullptr) {
tracker = xr_server->get_tracker(tracker_name);
if (tracker.is_null()) {
// It is possible and valid if the tracker isn't available (yet), in this case we just exit
return;
}
tracker->connect("pose_changed", callable_mp(this, &XRNode3D::_pose_changed));
Ref<XRPose> pose = get_pose();
if (pose.is_valid()) {
set_transform(pose->get_adjusted_transform());
}
}
}
void XRNode3D::_unbind_tracker() {
if (tracker.is_valid()) {
tracker->disconnect("pose_changed", callable_mp(this, &XRNode3D::_pose_changed));
tracker.unref();
}
}
void XRNode3D::_changed_tracker(const StringName p_tracker_name, int p_tracker_type) {
if (p_tracker_name == p_tracker_name) {
// just in case unref our current tracker
_unbind_tracker();
// get our new tracker
_bind_tracker();
}
}
void XRNode3D::_removed_tracker(const StringName p_tracker_name, int p_tracker_type) {
if (p_tracker_name == p_tracker_name) {
// unref our tracker, it's no longer available
_unbind_tracker();
}
}
void XRNode3D::_pose_changed(const Ref<XRPose> &p_pose) {
if (p_pose.is_valid() && p_pose->get_name() == pose_name) {
set_transform(p_pose->get_adjusted_transform());
}
}
XRNode3D::XRNode3D() {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
xr_server->connect("tracker_added", callable_mp(this, &XRNode3D::_changed_tracker));
xr_server->connect("tracker_updated", callable_mp(this, &XRNode3D::_changed_tracker));
xr_server->connect("tracker_removed", callable_mp(this, &XRNode3D::_removed_tracker));
}
XRNode3D::~XRNode3D() {
_unbind_tracker();
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
xr_server->disconnect("tracker_added", callable_mp(this, &XRNode3D::_changed_tracker));
xr_server->disconnect("tracker_updated", callable_mp(this, &XRNode3D::_changed_tracker));
xr_server->disconnect("tracker_removed", callable_mp(this, &XRNode3D::_removed_tracker));
}
TypedArray<String> XRNode3D::get_configuration_warnings() const {
TypedArray<String> warnings = Node::get_configuration_warnings();
if (is_visible() && is_inside_tree()) {
@ -370,130 +422,171 @@ TypedArray<String> XRController3D::get_configuration_warnings() const {
warnings.push_back(TTR("XRController3D must have an XROrigin3D node as its parent."));
}
if (controller_id == 0) {
warnings.push_back(TTR("The controller ID must not be 0 or this controller won't be bound to an actual controller."));
if (tracker_name == "") {
warnings.push_back(TTR("No tracker name is set."));
}
if (pose_name == "") {
warnings.push_back(TTR("No pose is set."));
}
}
return warnings;
};
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void XRAnchor3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
set_process_internal(true);
}; break;
case NOTIFICATION_EXIT_TREE: {
set_process_internal(false);
}; break;
case NOTIFICATION_INTERNAL_PROCESS: {
// get our XRServer
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
void XRController3D::_bind_methods() {
// passthroughs to information about our related joystick
ClassDB::bind_method(D_METHOD("is_button_pressed", "name"), &XRController3D::is_button_pressed);
ClassDB::bind_method(D_METHOD("get_value", "name"), &XRController3D::get_value);
ClassDB::bind_method(D_METHOD("get_axis", "name"), &XRController3D::get_axis);
// find the tracker for our anchor
Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_ANCHOR, anchor_id);
if (!tracker.is_valid()) {
// this anchor is currently not available
is_active = false;
} else {
is_active = true;
Transform3D transform;
ClassDB::bind_method(D_METHOD("get_tracker_hand"), &XRController3D::get_tracker_hand);
// we'll need our world_scale
real_t world_scale = xr_server->get_world_scale();
ClassDB::bind_method(D_METHOD("get_rumble"), &XRController3D::get_rumble);
ClassDB::bind_method(D_METHOD("set_rumble", "rumble"), &XRController3D::set_rumble);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rumble", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_rumble", "get_rumble");
ADD_PROPERTY_DEFAULT("rumble", 0.0);
// get our info from our tracker
transform.basis = tracker->get_orientation();
transform.origin = tracker->get_position(); // <-- already adjusted to world scale
// our basis is scaled to the size of the plane the anchor is tracking
// extract the size from our basis and reset the scale
size = transform.basis.get_scale() * world_scale;
transform.basis.orthonormalize();
// apply our reference frame and set our transform
set_transform(xr_server->get_reference_frame() * transform);
// check for an updated mesh
Ref<Mesh> trackerMesh = tracker->get_mesh();
if (mesh != trackerMesh) {
mesh = trackerMesh;
emit_signal(SNAME("mesh_updated"), mesh);
}
};
}; break;
default:
break;
};
ADD_SIGNAL(MethodInfo("button_pressed", PropertyInfo(Variant::STRING, "name")));
ADD_SIGNAL(MethodInfo("button_released", PropertyInfo(Variant::STRING, "name")));
ADD_SIGNAL(MethodInfo("input_value_changed", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::FLOAT, "value")));
ADD_SIGNAL(MethodInfo("input_axis_changed", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::VECTOR2, "value")));
};
void XRController3D::_bind_tracker() {
XRNode3D::_bind_tracker();
if (tracker.is_valid()) {
// bind to input signals
tracker->connect("button_pressed", callable_mp(this, &XRController3D::_button_pressed));
tracker->connect("button_released", callable_mp(this, &XRController3D::_button_released));
tracker->connect("input_value_changed", callable_mp(this, &XRController3D::_input_value_changed));
tracker->connect("input_axis_changed", callable_mp(this, &XRController3D::_input_axis_changed));
}
}
void XRController3D::_unbind_tracker() {
if (tracker.is_valid()) {
// unbind input signals
tracker->disconnect("button_pressed", callable_mp(this, &XRController3D::_button_pressed));
tracker->disconnect("button_released", callable_mp(this, &XRController3D::_button_released));
tracker->disconnect("input_value_changed", callable_mp(this, &XRController3D::_input_value_changed));
tracker->disconnect("input_axis_changed", callable_mp(this, &XRController3D::_input_axis_changed));
}
XRNode3D::_unbind_tracker();
}
void XRController3D::_button_pressed(const String &p_name) {
// just pass it on...
emit_signal("button_pressed", p_name);
}
void XRController3D::_button_released(const String &p_name) {
// just pass it on...
emit_signal("button_released", p_name);
}
void XRController3D::_input_value_changed(const String &p_name, float p_value) {
// just pass it on...
emit_signal("input_value_changed", p_name, p_value);
}
void XRController3D::_input_axis_changed(const String &p_name, Vector2 p_value) {
// just pass it on...
emit_signal("input_axis_changed", p_name, p_value);
}
bool XRController3D::is_button_pressed(const StringName &p_name) const {
if (tracker.is_valid()) {
// Inputs should already be of the correct type, our XR runtime handles conversions between raw input and the desired type
bool pressed = tracker->get_input(p_name);
return pressed;
} else {
return false;
}
}
float XRController3D::get_value(const StringName &p_name) const {
if (tracker.is_valid()) {
// Inputs should already be of the correct type, our XR runtime handles conversions between raw input and the desired type, but just in case we convert
Variant input = tracker->get_input(p_name);
switch (input.get_type()) {
case Variant::BOOL: {
bool value = input;
return value ? 1.0 : 0.0;
} break;
case Variant::FLOAT: {
float value = input;
return value;
} break;
default:
return 0.0;
};
} else {
return 0.0;
}
}
Vector2 XRController3D::get_axis(const StringName &p_name) const {
if (tracker.is_valid()) {
// Inputs should already be of the correct type, our XR runtime handles conversions between raw input and the desired type, but just in case we convert
Variant input = tracker->get_input(p_name);
switch (input.get_type()) {
case Variant::BOOL: {
bool value = input;
return Vector2(value ? 1.0 : 0.0, 0.0);
} break;
case Variant::FLOAT: {
float value = input;
return Vector2(value, 0.0);
} break;
case Variant::VECTOR2: {
Vector2 axis = input;
return axis;
}
default:
return Vector2();
}
} else {
return Vector2();
}
}
real_t XRController3D::get_rumble() const {
if (!tracker.is_valid()) {
return 0.0;
}
return tracker->get_rumble();
}
void XRController3D::set_rumble(real_t p_rumble) {
if (tracker.is_valid()) {
tracker->set_rumble(p_rumble);
}
}
XRPositionalTracker::TrackerHand XRController3D::get_tracker_hand() const {
// get our XRServer
if (!tracker.is_valid()) {
return XRPositionalTracker::TRACKER_HAND_UNKNOWN;
}
return tracker->get_tracker_hand();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void XRAnchor3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_anchor_id", "anchor_id"), &XRAnchor3D::set_anchor_id);
ClassDB::bind_method(D_METHOD("get_anchor_id"), &XRAnchor3D::get_anchor_id);
ADD_PROPERTY(PropertyInfo(Variant::INT, "anchor_id", PROPERTY_HINT_RANGE, "0,32,1"), "set_anchor_id", "get_anchor_id");
ClassDB::bind_method(D_METHOD("get_anchor_name"), &XRAnchor3D::get_anchor_name);
ClassDB::bind_method(D_METHOD("get_is_active"), &XRAnchor3D::get_is_active);
ClassDB::bind_method(D_METHOD("get_size"), &XRAnchor3D::get_size);
ClassDB::bind_method(D_METHOD("get_plane"), &XRAnchor3D::get_plane);
ClassDB::bind_method(D_METHOD("get_mesh"), &XRAnchor3D::get_mesh);
ADD_SIGNAL(MethodInfo("mesh_updated", PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh")));
};
void XRAnchor3D::set_anchor_id(int p_anchor_id) {
// We don't check any bounds here, this anchor may not yet be active and just be a place holder until it is.
// Note that setting this to 0 means this node is not bound to an anchor yet.
anchor_id = p_anchor_id;
update_configuration_warnings();
};
int XRAnchor3D::get_anchor_id() const {
return anchor_id;
};
}
Vector3 XRAnchor3D::get_size() const {
return size;
};
String XRAnchor3D::get_anchor_name() const {
// get our XRServer
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL_V(xr_server, String());
Ref<XRPositionalTracker> tracker = xr_server->find_by_type_and_id(XRServer::TRACKER_ANCHOR, anchor_id);
if (!tracker.is_valid()) {
return String("Not connected");
};
return tracker->get_tracker_name();
};
bool XRAnchor3D::get_is_active() const {
return is_active;
};
TypedArray<String> XRAnchor3D::get_configuration_warnings() const {
TypedArray<String> warnings = Node::get_configuration_warnings();
if (is_visible() && is_inside_tree()) {
// must be child node of XROrigin3D!
XROrigin3D *origin = Object::cast_to<XROrigin3D>(get_parent());
if (origin == nullptr) {
warnings.push_back(TTR("XRAnchor3D must have an XROrigin3D node as its parent."));
}
if (anchor_id == 0) {
warnings.push_back(TTR("The anchor ID must not be 0 or this anchor won't be bound to an actual anchor."));
}
}
return warnings;
};
}
Plane XRAnchor3D::get_plane() const {
Vector3 location = get_position();
@ -502,10 +595,6 @@ Plane XRAnchor3D::get_plane() const {
Plane plane(orientation.get_axis(1).normalized(), location);
return plane;
};
Ref<Mesh> XRAnchor3D::get_mesh() const {
return mesh;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@ -525,23 +614,21 @@ TypedArray<String> XROrigin3D::get_configuration_warnings() const {
}
return warnings;
};
}
void XROrigin3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_world_scale", "world_scale"), &XROrigin3D::set_world_scale);
ClassDB::bind_method(D_METHOD("get_world_scale"), &XROrigin3D::get_world_scale);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "world_scale"), "set_world_scale", "get_world_scale");
};
}
void XROrigin3D::set_tracked_camera(XRCamera3D *p_tracked_camera) {
tracked_camera = p_tracked_camera;
};
}
void XROrigin3D::clear_tracked_camera_if(XRCamera3D *p_tracked_camera) {
if (tracked_camera == p_tracked_camera) {
tracked_camera = nullptr;
};
};
XRCamera3D *XROrigin3D::get_tracked_camera() const {
return tracked_camera;
}
real_t XROrigin3D::get_world_scale() const {
// get our XRServer
@ -549,7 +636,7 @@ real_t XROrigin3D::get_world_scale() const {
ERR_FAIL_NULL_V(xr_server, 1.0);
return xr_server->get_world_scale();
};
}
void XROrigin3D::set_world_scale(real_t p_world_scale) {
// get our XRServer
@ -557,7 +644,7 @@ void XROrigin3D::set_world_scale(real_t p_world_scale) {
ERR_FAIL_NULL(xr_server);
xr_server->set_world_scale(p_world_scale);
};
}
void XROrigin3D::_notification(int p_what) {
// get our XRServer
@ -596,4 +683,4 @@ void XROrigin3D::_notification(int p_what) {
interface->notification(p_what);
}
}
};
}

View File

@ -45,8 +45,18 @@ class XRCamera3D : public Camera3D {
GDCLASS(XRCamera3D, Camera3D);
protected:
// The name and pose for our HMD tracker is currently the only hardcoded bit.
// If we ever are able to support multiple HMDs we may need to make this settable.
StringName tracker_name = "head";
StringName pose_name = "default";
Ref<XRPositionalTracker> tracker;
void _notification(int p_what);
void _changed_tracker(const StringName p_tracker_name, int p_tracker_type);
void _removed_tracker(const StringName p_tracker_name, int p_tracker_type);
void _pose_changed(const Ref<XRPose> &p_pose);
public:
TypedArray<String> get_configuration_warnings() const override;
@ -55,8 +65,55 @@ public:
virtual Vector3 project_position(const Point2 &p_point, real_t p_z_depth) const override;
virtual Vector<Plane> get_frustum() const override;
XRCamera3D() {}
~XRCamera3D() {}
XRCamera3D();
~XRCamera3D();
};
/*
XRNode3D is a helper node that implements binding to a tracker.
It must be a child node of our XROrigin node
*/
class XRNode3D : public Node3D {
GDCLASS(XRNode3D, Node3D);
private:
StringName tracker_name;
StringName pose_name = "default";
bool is_active = true;
protected:
Ref<XRPositionalTracker> tracker;
static void _bind_methods();
virtual void _bind_tracker();
virtual void _unbind_tracker();
void _changed_tracker(const StringName p_tracker_name, int p_tracker_type);
void _removed_tracker(const StringName p_tracker_name, int p_tracker_type);
void _pose_changed(const Ref<XRPose> &p_pose);
public:
virtual void _validate_property(PropertyInfo &property) const override;
void set_tracker(const StringName p_tracker_name);
StringName get_tracker() const;
void set_pose_name(const StringName p_pose);
StringName get_pose_name() const;
bool get_is_active() const;
bool get_has_tracking_data() const;
void trigger_haptic_pulse(const String &p_action_name, double p_frequency, double p_amplitude, double p_duration_sec, double p_delay_sec = 0);
Ref<XRPose> get_pose();
TypedArray<String> get_configuration_warnings() const override;
XRNode3D();
~XRNode3D();
};
/*
@ -65,38 +122,31 @@ public:
It must be a child node of our XROrigin node
*/
class XRController3D : public Node3D {
GDCLASS(XRController3D, Node3D);
class XRController3D : public XRNode3D {
GDCLASS(XRController3D, XRNode3D);
private:
int controller_id = 1;
bool is_active = true;
int button_states = 0;
Ref<Mesh> mesh;
protected:
void _notification(int p_what);
static void _bind_methods();
public:
void set_controller_id(int p_controller_id);
int get_controller_id() const;
String get_controller_name() const;
virtual void _bind_tracker() override;
virtual void _unbind_tracker() override;
int get_joystick_id() const;
bool is_button_pressed(int p_button) const;
float get_joystick_axis(int p_axis) const;
void _button_pressed(const String &p_name);
void _button_released(const String &p_name);
void _input_value_changed(const String &p_name, float p_value);
void _input_axis_changed(const String &p_name, Vector2 p_value);
public:
bool is_button_pressed(const StringName &p_name) const;
float get_value(const StringName &p_name) const;
Vector2 get_axis(const StringName &p_name) const;
real_t get_rumble() const;
void set_rumble(real_t p_rumble);
bool get_is_active() const;
XRPositionalTracker::TrackerHand get_tracker_hand() const;
Ref<Mesh> get_mesh() const;
TypedArray<String> get_configuration_warnings() const override;
XRController3D() {}
~XRController3D() {}
};
@ -106,33 +156,19 @@ public:
It must be a child node of our XROrigin3D node
*/
class XRAnchor3D : public Node3D {
GDCLASS(XRAnchor3D, Node3D);
class XRAnchor3D : public XRNode3D {
GDCLASS(XRAnchor3D, XRNode3D);
private:
int anchor_id = 1;
bool is_active = true;
Vector3 size;
Ref<Mesh> mesh;
protected:
void _notification(int p_what);
static void _bind_methods();
public:
void set_anchor_id(int p_anchor_id);
int get_anchor_id() const;
String get_anchor_name() const;
bool get_is_active() const;
Vector3 get_size() const;
Plane get_plane() const;
Ref<Mesh> get_mesh() const;
TypedArray<String> get_configuration_warnings() const override;
XRAnchor3D() {}
~XRAnchor3D() {}
};
@ -159,7 +195,7 @@ public:
TypedArray<String> get_configuration_warnings() const override;
void set_tracked_camera(XRCamera3D *p_tracked_camera);
void clear_tracked_camera_if(XRCamera3D *p_tracked_camera);
XRCamera3D *get_tracked_camera() const;
real_t get_world_scale() const;
void set_world_scale(real_t p_world_scale);

View File

@ -450,6 +450,7 @@ void register_scene_types() {
GDREGISTER_CLASS(Camera3D);
GDREGISTER_CLASS(AudioListener3D);
GDREGISTER_CLASS(XRCamera3D);
GDREGISTER_VIRTUAL_CLASS(XRNode3D);
GDREGISTER_CLASS(XRController3D);
GDREGISTER_CLASS(XRAnchor3D);
GDREGISTER_CLASS(XROrigin3D);

View File

@ -133,6 +133,7 @@ void register_server_types() {
GDREGISTER_VIRTUAL_CLASS(XRInterface);
GDREGISTER_CLASS(XRInterfaceExtension); // can't register this as virtual because we need a creation function for our extensions.
GDREGISTER_CLASS(XRPose);
GDREGISTER_CLASS(XRPositionalTracker);
GDREGISTER_CLASS(AudioStream);

View File

@ -495,9 +495,6 @@ void RendererViewport::draw_viewports() {
if (XRServer::get_singleton() != nullptr) {
xr_interface = XRServer::get_singleton()->get_primary_interface();
// process all our active interfaces
XRServer::get_singleton()->_process();
}
if (Engine::get_singleton()->is_editor_hint()) {

View File

@ -47,6 +47,8 @@ void XRInterface::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_render_target_size"), &XRInterface::get_render_target_size);
ClassDB::bind_method(D_METHOD("get_view_count"), &XRInterface::get_view_count);
ClassDB::bind_method(D_METHOD("trigger_haptic_pulse", "action_name", "tracker_name", "frequency", "amplitude", "duration_sec", "delay_sec"), &XRInterface::trigger_haptic_pulse);
ADD_GROUP("Interface", "interface_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interface_is_primary"), "set_primary", "is_primary");
@ -63,13 +65,11 @@ void XRInterface::_bind_methods() {
BIND_ENUM_CONSTANT(XR_NONE);
BIND_ENUM_CONSTANT(XR_MONO);
BIND_ENUM_CONSTANT(XR_STEREO);
BIND_ENUM_CONSTANT(XR_QUAD);
BIND_ENUM_CONSTANT(XR_VR);
BIND_ENUM_CONSTANT(XR_AR);
BIND_ENUM_CONSTANT(XR_EXTERNAL);
BIND_ENUM_CONSTANT(EYE_MONO);
BIND_ENUM_CONSTANT(EYE_LEFT);
BIND_ENUM_CONSTANT(EYE_RIGHT);
BIND_ENUM_CONSTANT(XR_NORMAL_TRACKING);
BIND_ENUM_CONSTANT(XR_EXCESSIVE_MOTION);
BIND_ENUM_CONSTANT(XR_INSUFFICIENT_FEATURES);
@ -114,9 +114,24 @@ int XRInterface::get_camera_feed_id() {
}
/** these are optional, so we want dummies **/
PackedStringArray XRInterface::get_suggested_tracker_names() const {
PackedStringArray arr;
return arr;
}
PackedStringArray XRInterface::get_suggested_pose_names(const StringName &p_tracker_name) const {
PackedStringArray arr;
return arr;
}
XRInterface::TrackingStatus XRInterface::get_tracking_status() const {
return XR_UNKNOWN_TRACKING;
}
void XRInterface::notification(int p_what) {
}
void XRInterface::trigger_haptic_pulse(const String &p_action_name, const StringName &p_tracker_name, double p_frequency, double p_amplitude, double p_duration_sec, double p_delay_sec) {
}

View File

@ -58,14 +58,10 @@ public:
XR_NONE = 0, /* no capabilities */
XR_MONO = 1, /* can be used with mono output */
XR_STEREO = 2, /* can be used with stereo output */
XR_AR = 4, /* offers a camera feed for AR */
XR_EXTERNAL = 8 /* renders to external device */
};
enum Eyes {
EYE_MONO, /* my son says we should call this EYE_CYCLOPS */
EYE_LEFT,
EYE_RIGHT
XR_QUAD = 4, /* can be used with quad output (not currently supported) */
XR_VR = 8, /* offers VR support */
XR_AR = 16, /* offers AR support */
XR_EXTERNAL = 32 /* renders to external device */
};
enum TrackingStatus { /* tracking status currently based on AR but we can start doing more with this for VR as well */
@ -94,7 +90,12 @@ public:
virtual bool initialize() = 0; /* initialize this interface, if this has an HMD it becomes the primary interface */
virtual void uninitialize() = 0; /* deinitialize this interface */
/** input and output **/
virtual PackedStringArray get_suggested_tracker_names() const; /* return a list of likely/suggested tracker names */
virtual PackedStringArray get_suggested_pose_names(const StringName &p_tracker_name) const; /* return a list of likely/suggested action names for this tracker */
virtual TrackingStatus get_tracking_status() const; /* get the status of our current tracking */
virtual void trigger_haptic_pulse(const String &p_action_name, const StringName &p_tracker_name, double p_frequency, double p_amplitude, double p_duration_sec, double p_delay_sec = 0); /* trigger a haptic pulse */
/** specific to VR **/
// nothing yet
@ -124,7 +125,6 @@ public:
};
VARIANT_ENUM_CAST(XRInterface::Capabilities);
VARIANT_ENUM_CAST(XRInterface::Eyes);
VARIANT_ENUM_CAST(XRInterface::TrackingStatus);
#endif // !XR_INTERFACE_H

View File

@ -41,8 +41,6 @@ void XRInterfaceExtension::_bind_methods() {
GDVIRTUAL_BIND(_initialize);
GDVIRTUAL_BIND(_uninitialize);
GDVIRTUAL_BIND(_get_tracking_status);
GDVIRTUAL_BIND(_get_render_target_size);
GDVIRTUAL_BIND(_get_view_count);
GDVIRTUAL_BIND(_get_camera_transform);
@ -54,6 +52,13 @@ void XRInterfaceExtension::_bind_methods() {
GDVIRTUAL_BIND(_process);
GDVIRTUAL_BIND(_notification, "what");
/** input and output **/
GDVIRTUAL_BIND(_get_suggested_tracker_names);
GDVIRTUAL_BIND(_get_suggested_pose_names, "tracker_name");
GDVIRTUAL_BIND(_get_tracking_status);
GDVIRTUAL_BIND(_trigger_haptic_pulse, "action_name", "tracker_name", "frequency", "amplitude", "duration_sec", "delay_sec");
// we don't have any properties specific to VR yet....
// but we do have properties specific to AR....
@ -111,6 +116,22 @@ void XRInterfaceExtension::uninitialize() {
GDVIRTUAL_CALL(_uninitialize);
}
PackedStringArray XRInterfaceExtension::get_suggested_tracker_names() const {
PackedStringArray arr;
GDVIRTUAL_CALL(_get_suggested_tracker_names, arr);
return arr;
}
PackedStringArray XRInterfaceExtension::get_suggested_pose_names(const StringName &p_tracker_name) const {
PackedStringArray arr;
GDVIRTUAL_CALL(_get_suggested_pose_names, p_tracker_name, arr);
return arr;
}
XRInterface::TrackingStatus XRInterfaceExtension::get_tracking_status() const {
uint32_t status;
@ -121,6 +142,10 @@ XRInterface::TrackingStatus XRInterfaceExtension::get_tracking_status() const {
return XR_UNKNOWN_TRACKING;
}
void XRInterfaceExtension::trigger_haptic_pulse(const String &p_action_name, const StringName &p_tracker_name, double p_frequency, double p_amplitude, double p_duration_sec, double p_delay_sec) {
GDVIRTUAL_CALL(_trigger_haptic_pulse, p_action_name, p_tracker_name, p_frequency, p_amplitude, p_duration_sec, p_delay_sec);
}
/** these will only be implemented on AR interfaces, so we want dummies for VR **/
bool XRInterfaceExtension::get_anchor_detection_is_enabled() const {
bool enabled;

View File

@ -62,8 +62,17 @@ public:
GDVIRTUAL0R(bool, _initialize);
GDVIRTUAL0(_uninitialize);
/** input and output **/
virtual PackedStringArray get_suggested_tracker_names() const override; /* return a list of likely/suggested tracker names */
virtual PackedStringArray get_suggested_pose_names(const StringName &p_tracker_name) const override; /* return a list of likely/suggested action names for this tracker */
virtual TrackingStatus get_tracking_status() const override;
virtual void trigger_haptic_pulse(const String &p_action_name, const StringName &p_tracker_name, double p_frequency, double p_amplitude, double p_duration_sec, double p_delay_sec = 0) override;
GDVIRTUAL0RC(PackedStringArray, _get_suggested_tracker_names);
GDVIRTUAL1RC(PackedStringArray, _get_suggested_pose_names, const StringName &);
GDVIRTUAL0RC(uint32_t, _get_tracking_status);
GDVIRTUAL6(_trigger_haptic_pulse, const String &, const StringName &, double, double, double, double);
/** specific to VR **/
// nothing yet

110
servers/xr/xr_pose.cpp Normal file
View File

@ -0,0 +1,110 @@
/*************************************************************************/
/* xr_pose.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "xr_pose.h"
#include "servers/xr_server.h"
void XRPose::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_has_tracking_data", "has_tracking_data"), &XRPose::set_has_tracking_data);
ClassDB::bind_method(D_METHOD("get_has_tracking_data"), &XRPose::get_has_tracking_data);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "has_tracking_data"), "set_has_tracking_data", "get_has_tracking_data");
ClassDB::bind_method(D_METHOD("set_name", "name"), &XRPose::set_name);
ClassDB::bind_method(D_METHOD("get_name"), &XRPose::get_name);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "name"), "set_name", "get_name");
ClassDB::bind_method(D_METHOD("set_transform", "transform"), &XRPose::set_transform);
ClassDB::bind_method(D_METHOD("get_transform"), &XRPose::get_transform);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "transform"), "set_transform", "get_transform");
ClassDB::bind_method(D_METHOD("get_adjusted_transform"), &XRPose::get_adjusted_transform);
ClassDB::bind_method(D_METHOD("set_linear_velocity", "velocity"), &XRPose::set_linear_velocity);
ClassDB::bind_method(D_METHOD("get_linear_velocity"), &XRPose::get_linear_velocity);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "linear_velocity"), "set_linear_velocity", "get_linear_velocity");
ClassDB::bind_method(D_METHOD("set_angular_velocity", "velocity"), &XRPose::set_angular_velocity);
ClassDB::bind_method(D_METHOD("get_angular_velocity"), &XRPose::get_angular_velocity);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "angular_velocity"), "set_angular_velocity", "get_angular_velocity");
}
void XRPose::set_has_tracking_data(const bool p_has_tracking_data) {
has_tracking_data = p_has_tracking_data;
}
bool XRPose::get_has_tracking_data() const {
return has_tracking_data;
}
void XRPose::set_name(const StringName &p_name) {
name = p_name;
}
StringName XRPose::get_name() const {
return name;
}
void XRPose::set_transform(const Transform3D p_transform) {
transform = p_transform;
}
Transform3D XRPose::get_transform() const {
return transform;
}
Transform3D XRPose::get_adjusted_transform() const {
Transform3D adjusted_transform = transform;
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL_V(xr_server, transform);
// apply world scale
adjusted_transform.origin *= xr_server->get_world_scale();
// apply reference frame
adjusted_transform = xr_server->get_reference_frame() * adjusted_transform;
return adjusted_transform;
}
void XRPose::set_linear_velocity(const Vector3 p_velocity) {
linear_velocity = p_velocity;
}
Vector3 XRPose::get_linear_velocity() const {
return linear_velocity;
}
void XRPose::set_angular_velocity(const Vector3 p_velocity) {
angular_velocity = p_velocity;
}
Vector3 XRPose::get_angular_velocity() const {
return angular_velocity;
}

68
servers/xr/xr_pose.h Normal file
View File

@ -0,0 +1,68 @@
/*************************************************************************/
/* xr_pose.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef XR_POSE_H
#define XR_POSE_H
#include "core/object/ref_counted.h"
class XRPose : public RefCounted {
GDCLASS(XRPose, RefCounted);
public:
private:
bool has_tracking_data = false;
StringName name;
Transform3D transform;
Vector3 linear_velocity;
Vector3 angular_velocity;
protected:
static void _bind_methods();
public:
void set_has_tracking_data(const bool p_has_tracking_data);
bool get_has_tracking_data() const;
void set_name(const StringName &p_name);
StringName get_name() const;
void set_transform(const Transform3D p_transform);
Transform3D get_transform() const;
Transform3D get_adjusted_transform() const;
void set_linear_velocity(const Vector3 p_velocity);
Vector3 get_linear_velocity() const;
void set_angular_velocity(const Vector3 p_velocity);
Vector3 get_angular_velocity() const;
};
#endif

View File

@ -37,29 +37,37 @@ void XRPositionalTracker::_bind_methods() {
BIND_ENUM_CONSTANT(TRACKER_HAND_LEFT);
BIND_ENUM_CONSTANT(TRACKER_HAND_RIGHT);
// this class is read only from GDScript, so we only have access to getters..
ClassDB::bind_method(D_METHOD("get_tracker_type"), &XRPositionalTracker::get_tracker_type);
ClassDB::bind_method(D_METHOD("get_tracker_id"), &XRPositionalTracker::get_tracker_id);
ClassDB::bind_method(D_METHOD("get_tracker_name"), &XRPositionalTracker::get_tracker_name);
ClassDB::bind_method(D_METHOD("get_joy_id"), &XRPositionalTracker::get_joy_id);
ClassDB::bind_method(D_METHOD("is_tracking_orientation"), &XRPositionalTracker::is_tracking_orientation);
ClassDB::bind_method(D_METHOD("get_orientation"), &XRPositionalTracker::get_orientation);
ClassDB::bind_method(D_METHOD("is_tracking_position"), &XRPositionalTracker::is_tracking_position);
ClassDB::bind_method(D_METHOD("get_position"), &XRPositionalTracker::get_position);
ClassDB::bind_method(D_METHOD("get_tracker_hand"), &XRPositionalTracker::get_tracker_hand);
ClassDB::bind_method(D_METHOD("get_transform", "adjust_by_reference_frame"), &XRPositionalTracker::get_transform);
ClassDB::bind_method(D_METHOD("get_mesh"), &XRPositionalTracker::get_mesh);
ClassDB::bind_method(D_METHOD("set_tracker_type", "type"), &XRPositionalTracker::set_tracker_type);
ADD_PROPERTY(PropertyInfo(Variant::INT, "type"), "set_tracker_type", "get_tracker_type");
ClassDB::bind_method(D_METHOD("get_tracker_name"), &XRPositionalTracker::get_tracker_name);
ClassDB::bind_method(D_METHOD("set_tracker_name", "name"), &XRPositionalTracker::set_tracker_name);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "name"), "set_tracker_name", "get_tracker_name");
ClassDB::bind_method(D_METHOD("get_tracker_desc"), &XRPositionalTracker::get_tracker_desc);
ClassDB::bind_method(D_METHOD("set_tracker_desc", "description"), &XRPositionalTracker::set_tracker_desc);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "description"), "set_tracker_desc", "get_tracker_desc");
ClassDB::bind_method(D_METHOD("get_tracker_hand"), &XRPositionalTracker::get_tracker_hand);
ClassDB::bind_method(D_METHOD("set_tracker_hand", "hand"), &XRPositionalTracker::set_tracker_hand);
ADD_PROPERTY(PropertyInfo(Variant::INT, "hand", PROPERTY_HINT_ENUM, "Unknown,Left,Right"), "set_tracker_hand", "get_tracker_hand");
ClassDB::bind_method(D_METHOD("has_pose", "name"), &XRPositionalTracker::has_pose);
ClassDB::bind_method(D_METHOD("get_pose", "name"), &XRPositionalTracker::get_pose);
ClassDB::bind_method(D_METHOD("invalidate_pose", "name"), &XRPositionalTracker::invalidate_pose);
ClassDB::bind_method(D_METHOD("set_pose", "name", "transform", "linear_velocity", "angular_velocity"), &XRPositionalTracker::set_pose);
ADD_SIGNAL(MethodInfo("pose_changed", PropertyInfo(Variant::OBJECT, "pose", PROPERTY_HINT_RESOURCE_TYPE, "XRPose")));
ClassDB::bind_method(D_METHOD("get_input", "name"), &XRPositionalTracker::get_input);
ClassDB::bind_method(D_METHOD("set_input", "name", "value"), &XRPositionalTracker::set_input);
ADD_SIGNAL(MethodInfo("button_pressed", PropertyInfo(Variant::STRING, "name")));
ADD_SIGNAL(MethodInfo("button_released", PropertyInfo(Variant::STRING, "name")));
ADD_SIGNAL(MethodInfo("input_value_changed", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::FLOAT, "value")));
ADD_SIGNAL(MethodInfo("input_axis_changed", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::VECTOR2, "vector")));
// these functions we don't want to expose to normal users but do need to be callable from GDNative
ClassDB::bind_method(D_METHOD("_set_tracker_type", "type"), &XRPositionalTracker::set_tracker_type);
ClassDB::bind_method(D_METHOD("_set_tracker_name", "name"), &XRPositionalTracker::set_tracker_name);
ClassDB::bind_method(D_METHOD("_set_joy_id", "joy_id"), &XRPositionalTracker::set_joy_id);
ClassDB::bind_method(D_METHOD("_set_orientation", "orientation"), &XRPositionalTracker::set_orientation);
ClassDB::bind_method(D_METHOD("_set_rw_position", "rw_position"), &XRPositionalTracker::set_rw_position);
ClassDB::bind_method(D_METHOD("_set_mesh", "mesh"), &XRPositionalTracker::set_mesh);
ClassDB::bind_method(D_METHOD("get_rumble"), &XRPositionalTracker::get_rumble);
ClassDB::bind_method(D_METHOD("set_rumble", "rumble"), &XRPositionalTracker::set_rumble);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rumble"), "set_rumble", "get_rumble");
};
@ -67,13 +75,6 @@ void XRPositionalTracker::set_tracker_type(XRServer::TrackerType p_type) {
if (type != p_type) {
type = p_type;
hand = XRPositionalTracker::TRACKER_HAND_UNKNOWN;
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
// get a tracker id for our type
// note if this is a controller this will be 3 or higher but we may change it later.
tracker_id = xr_server->get_free_tracker_id_for_type(p_type);
};
};
@ -81,7 +82,8 @@ XRServer::TrackerType XRPositionalTracker::get_tracker_type() const {
return type;
};
void XRPositionalTracker::set_tracker_name(const String &p_name) {
void XRPositionalTracker::set_tracker_name(const StringName &p_name) {
// Note: this should not be changed after the tracker is registered with the XRServer!
name = p_name;
};
@ -89,85 +91,13 @@ StringName XRPositionalTracker::get_tracker_name() const {
return name;
};
int XRPositionalTracker::get_tracker_id() const {
return tracker_id;
};
void XRPositionalTracker::set_tracker_desc(const String &p_desc) {
description = p_desc;
}
void XRPositionalTracker::set_joy_id(int p_joy_id) {
joy_id = p_joy_id;
};
int XRPositionalTracker::get_joy_id() const {
return joy_id;
};
bool XRPositionalTracker::is_tracking_orientation() const {
return tracking_orientation;
};
void XRPositionalTracker::set_orientation(const Basis &p_orientation) {
_THREAD_SAFE_METHOD_
tracking_orientation = true; // obviously we have this
orientation = p_orientation;
};
Basis XRPositionalTracker::get_orientation() const {
_THREAD_SAFE_METHOD_
return orientation;
};
bool XRPositionalTracker::is_tracking_position() const {
return tracking_position;
};
void XRPositionalTracker::set_position(const Vector3 &p_position) {
_THREAD_SAFE_METHOD_
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
real_t world_scale = xr_server->get_world_scale();
ERR_FAIL_COND(world_scale == 0);
tracking_position = true; // obviously we have this
rw_position = p_position / world_scale;
};
Vector3 XRPositionalTracker::get_position() const {
_THREAD_SAFE_METHOD_
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL_V(xr_server, rw_position);
real_t world_scale = xr_server->get_world_scale();
return rw_position * world_scale;
};
void XRPositionalTracker::set_rw_position(const Vector3 &p_rw_position) {
_THREAD_SAFE_METHOD_
tracking_position = true; // obviously we have this
rw_position = p_rw_position;
};
Vector3 XRPositionalTracker::get_rw_position() const {
_THREAD_SAFE_METHOD_
return rw_position;
};
void XRPositionalTracker::set_mesh(const Ref<Mesh> &p_mesh) {
_THREAD_SAFE_METHOD_
mesh = p_mesh;
};
Ref<Mesh> XRPositionalTracker::get_mesh() const {
_THREAD_SAFE_METHOD_
return mesh;
};
String XRPositionalTracker::get_tracker_desc() const {
return description;
}
XRPositionalTracker::TrackerHand XRPositionalTracker::get_tracker_hand() const {
return hand;
@ -182,33 +112,98 @@ void XRPositionalTracker::set_tracker_hand(const XRPositionalTracker::TrackerHan
ERR_FAIL_COND((type != XRServer::TRACKER_CONTROLLER) && (p_hand != XRPositionalTracker::TRACKER_HAND_UNKNOWN));
hand = p_hand;
if (hand == XRPositionalTracker::TRACKER_HAND_LEFT) {
if (!xr_server->is_tracker_id_in_use_for_type(type, 1)) {
tracker_id = 1;
};
} else if (hand == XRPositionalTracker::TRACKER_HAND_RIGHT) {
if (!xr_server->is_tracker_id_in_use_for_type(type, 2)) {
tracker_id = 2;
};
};
};
};
Transform3D XRPositionalTracker::get_transform(bool p_adjust_by_reference_frame) const {
Transform3D new_transform;
bool XRPositionalTracker::has_pose(const StringName &p_action_name) const {
return poses.has(p_action_name);
}
new_transform.basis = get_orientation();
new_transform.origin = get_position();
Ref<XRPose> XRPositionalTracker::get_pose(const StringName &p_action_name) const {
Ref<XRPose> pose;
if (p_adjust_by_reference_frame) {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL_V(xr_server, new_transform);
if (poses.has(p_action_name)) {
pose = poses[p_action_name];
}
new_transform = xr_server->get_reference_frame() * new_transform;
};
return pose;
}
return new_transform;
};
void XRPositionalTracker::invalidate_pose(const StringName &p_action_name) {
// only update this if we were tracking this pose
if (poses.has(p_action_name)) {
// We just set tracking data as invalid, we leave our current transform and velocity data as is so controllers don't suddenly jump to origin.
poses[p_action_name]->set_has_tracking_data(false);
}
}
void XRPositionalTracker::set_pose(const StringName &p_action_name, const Transform3D &p_transform, const Vector3 &p_linear_velocity, const Vector3 &p_angular_velocity) {
Ref<XRPose> new_pose;
new_pose.instantiate();
new_pose->set_name(p_action_name);
new_pose->set_has_tracking_data(true);
new_pose->set_transform(p_transform);
new_pose->set_linear_velocity(p_linear_velocity);
new_pose->set_angular_velocity(p_angular_velocity);
poses[p_action_name] = new_pose;
emit_signal("pose_changed", new_pose);
// TODO discuss whether we also want to create and emit an InputEventXRPose event
}
Variant XRPositionalTracker::get_input(const StringName &p_action_name) const {
if (inputs.has(p_action_name)) {
return inputs[p_action_name];
} else {
return Variant();
}
}
void XRPositionalTracker::set_input(const StringName &p_action_name, const Variant &p_value) {
bool changed = false;
// XR inputs
if (inputs.has(p_action_name)) {
changed = inputs[p_action_name] != p_value;
} else {
changed = true;
}
if (changed) {
// store the new value
inputs[p_action_name] = p_value;
// emit signals to let the rest of the world know
switch (p_value.get_type()) {
case Variant::BOOL: {
bool pressed = p_value;
if (pressed) {
emit_signal("button_pressed", p_action_name);
} else {
emit_signal("button_released", p_action_name);
}
// TODO discuss whether we also want to create and emit an InputEventXRButton event
} break;
case Variant::FLOAT: {
emit_signal("input_value_changed", p_action_name, p_value);
// TODO discuss whether we also want to create and emit an InputEventXRValue event
} break;
case Variant::VECTOR2: {
emit_signal("input_axis_changed", p_action_name, p_value);
// TODO discuss whether we also want to create and emit an InputEventXRAxis event
} break;
default: {
// ???
} break;
}
}
}
real_t XRPositionalTracker::get_rumble() const {
return rumble;
@ -225,10 +220,6 @@ void XRPositionalTracker::set_rumble(real_t p_rumble) {
XRPositionalTracker::XRPositionalTracker() {
type = XRServer::TRACKER_UNKNOWN;
name = "Unknown";
joy_id = -1;
tracker_id = 0;
tracking_orientation = false;
tracking_position = false;
hand = TRACKER_HAND_UNKNOWN;
rumble = 0.0;
};

View File

@ -33,6 +33,7 @@
#include "core/os/thread_safe.h"
#include "scene/resources/mesh.h"
#include "servers/xr/xr_pose.h"
#include "servers/xr_server.h"
/**
@ -57,14 +58,14 @@ public:
private:
XRServer::TrackerType type; // type of tracker
StringName name; // (unique) name of the tracker
int tracker_id; // tracker index id that is unique per type
int joy_id; // if we also have a related joystick entity, the id of the joystick
bool tracking_orientation; // do we track orientation?
Basis orientation; // our orientation
bool tracking_position; // do we track position?
Vector3 rw_position; // our position "in the real world, so without world_scale applied"
Ref<Mesh> mesh; // when available, a mesh that can be used to render this tracker
String description; // description of the tracker, this is interface dependent, for OpenXR this will be the interaction profile bound for to the tracker
TrackerHand hand; // if known, the hand this tracker is held in
Map<StringName, Ref<XRPose>> poses;
Map<StringName, Variant> inputs;
int joy_id; // if we also have a related joystick entity, the id of the joystick
Ref<Mesh> mesh; // when available, a mesh that can be used to render this tracker
real_t rumble; // rumble strength, 0.0 is off, 1.0 is maximum, note that we only record here, xr_interface is responsible for execution
protected:
@ -73,27 +74,24 @@ protected:
public:
void set_tracker_type(XRServer::TrackerType p_type);
XRServer::TrackerType get_tracker_type() const;
void set_tracker_name(const String &p_name);
void set_tracker_name(const StringName &p_name);
StringName get_tracker_name() const;
int get_tracker_id() const;
void set_joy_id(int p_joy_id);
int get_joy_id() const;
bool is_tracking_orientation() const;
void set_orientation(const Basis &p_orientation);
Basis get_orientation() const;
bool is_tracking_position() const;
void set_position(const Vector3 &p_position); // set position with world_scale applied
Vector3 get_position() const; // get position with world_scale applied
void set_rw_position(const Vector3 &p_rw_position);
Vector3 get_rw_position() const;
void set_tracker_desc(const String &p_desc);
String get_tracker_desc() const;
XRPositionalTracker::TrackerHand get_tracker_hand() const;
void set_tracker_hand(const XRPositionalTracker::TrackerHand p_hand);
bool has_pose(const StringName &p_action_name) const;
Ref<XRPose> get_pose(const StringName &p_action_name) const;
void invalidate_pose(const StringName &p_action_name);
void set_pose(const StringName &p_action_name, const Transform3D &p_transform, const Vector3 &p_linear_velocity, const Vector3 &p_angular_velocity);
Variant get_input(const StringName &p_action_name) const;
void set_input(const StringName &p_action_name, const Variant &p_value);
// TODO replace by new implementation
real_t get_rumble() const;
void set_rumble(real_t p_rumble);
void set_mesh(const Ref<Mesh> &p_mesh);
Ref<Mesh> get_mesh() const;
Transform3D get_transform(bool p_adjust_by_reference_frame) const;
XRPositionalTracker();
~XRPositionalTracker() {}

View File

@ -54,10 +54,11 @@ void XRServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_interface", "idx"), &XRServer::get_interface);
ClassDB::bind_method(D_METHOD("get_interfaces"), &XRServer::get_interfaces);
ClassDB::bind_method(D_METHOD("find_interface", "name"), &XRServer::find_interface);
ClassDB::bind_method(D_METHOD("get_tracker_count"), &XRServer::get_tracker_count);
ClassDB::bind_method(D_METHOD("get_tracker", "idx"), &XRServer::get_tracker);
ClassDB::bind_method(D_METHOD("add_tracker", "tracker"), &XRServer::add_tracker);
ClassDB::bind_method(D_METHOD("remove_tracker", "tracker"), &XRServer::remove_tracker);
ClassDB::bind_method(D_METHOD("get_trackers", "tracker_types"), &XRServer::get_trackers);
ClassDB::bind_method(D_METHOD("get_tracker", "tracker_name"), &XRServer::get_tracker);
ClassDB::bind_method(D_METHOD("get_primary_interface"), &XRServer::get_primary_interface);
ClassDB::bind_method(D_METHOD("set_primary_interface", "interface"), &XRServer::set_primary_interface);
@ -68,6 +69,7 @@ void XRServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_last_commit_usec"), &XRServer::get_last_commit_usec);
ClassDB::bind_method(D_METHOD("get_last_frame_usec"), &XRServer::get_last_frame_usec);
BIND_ENUM_CONSTANT(TRACKER_HEAD);
BIND_ENUM_CONSTANT(TRACKER_CONTROLLER);
BIND_ENUM_CONSTANT(TRACKER_BASESTATION);
BIND_ENUM_CONSTANT(TRACKER_ANCHOR);
@ -82,8 +84,9 @@ void XRServer::_bind_methods() {
ADD_SIGNAL(MethodInfo("interface_added", PropertyInfo(Variant::STRING_NAME, "interface_name")));
ADD_SIGNAL(MethodInfo("interface_removed", PropertyInfo(Variant::STRING_NAME, "interface_name")));
ADD_SIGNAL(MethodInfo("tracker_added", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type"), PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("tracker_removed", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type"), PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("tracker_added", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type")));
ADD_SIGNAL(MethodInfo("tracker_updated", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type")));
ADD_SIGNAL(MethodInfo("tracker_removed", PropertyInfo(Variant::STRING_NAME, "tracker_name"), PropertyInfo(Variant::INT, "type")));
};
double XRServer::get_world_scale() const {
@ -224,92 +227,6 @@ Array XRServer::get_interfaces() const {
return ret;
};
/*
A little extra info on the tracker ids, these are unique per tracker type so we get some consistency in recognising our trackers, specifically controllers.
The first controller that is turned of will get ID 1, the second will get ID 2, etc.
The magic happens when one of the controllers is turned off, say controller 1 turns off, controller 2 will remain controller 2, controller 3 will remain controller 3.
If controller number 1 is turned on again it again gets ID 1 unless another new controller was turned on since.
The most likely scenario however is a controller that runs out of battery and another controller being used to replace it.
Because the controllers are often linked to physical objects, say you're holding a shield in controller 1, your left hand, and a gun in controller 2, your right hand, and controller 1 dies:
- using our tracker index would suddenly make the gun disappear and the shield jump into your right hand because controller 2 becomes controller 1.
- using this approach the shield disappears or is no longer tracked, but the gun stays firmly in your right hand because that is still controller 2, further more, if controller 1 is replaced the shield will return.
*/
bool XRServer::is_tracker_id_in_use_for_type(TrackerType p_tracker_type, int p_tracker_id) const {
for (int i = 0; i < trackers.size(); i++) {
if (trackers[i]->get_tracker_type() == p_tracker_type && trackers[i]->get_tracker_id() == p_tracker_id) {
return true;
};
};
// all good
return false;
};
int XRServer::get_free_tracker_id_for_type(TrackerType p_tracker_type) {
// We start checking at 1, 0 means that it's not a controller..
// Note that for controller we reserve:
// - 1 for the left hand controller and
// - 2 for the right hand controller
// so we start at 3 :)
int tracker_id = p_tracker_type == XRServer::TRACKER_CONTROLLER ? 3 : 1;
while (is_tracker_id_in_use_for_type(p_tracker_type, tracker_id)) {
// try the next one
tracker_id++;
};
return tracker_id;
};
void XRServer::add_tracker(Ref<XRPositionalTracker> p_tracker) {
ERR_FAIL_COND(p_tracker.is_null());
trackers.push_back(p_tracker);
emit_signal(SNAME("tracker_added"), p_tracker->get_tracker_name(), p_tracker->get_tracker_type(), p_tracker->get_tracker_id());
};
void XRServer::remove_tracker(Ref<XRPositionalTracker> p_tracker) {
ERR_FAIL_COND(p_tracker.is_null());
int idx = -1;
for (int i = 0; i < trackers.size(); i++) {
if (trackers[i] == p_tracker) {
idx = i;
break;
};
};
ERR_FAIL_COND(idx == -1);
emit_signal(SNAME("tracker_removed"), p_tracker->get_tracker_name(), p_tracker->get_tracker_type(), p_tracker->get_tracker_id());
trackers.remove(idx);
};
int XRServer::get_tracker_count() const {
return trackers.size();
};
Ref<XRPositionalTracker> XRServer::get_tracker(int p_index) const {
ERR_FAIL_INDEX_V(p_index, trackers.size(), Ref<XRPositionalTracker>());
return trackers[p_index];
};
Ref<XRPositionalTracker> XRServer::find_by_type_and_id(TrackerType p_tracker_type, int p_tracker_id) const {
ERR_FAIL_COND_V(p_tracker_id == 0, Ref<XRPositionalTracker>());
for (int i = 0; i < trackers.size(); i++) {
if (trackers[i]->get_tracker_type() == p_tracker_type && trackers[i]->get_tracker_id() == p_tracker_id) {
return trackers[i];
};
};
return Ref<XRPositionalTracker>();
};
Ref<XRInterface> XRServer::get_primary_interface() const {
return primary_interface;
};
@ -325,6 +242,107 @@ void XRServer::set_primary_interface(const Ref<XRInterface> &p_primary_interface
}
};
void XRServer::add_tracker(Ref<XRPositionalTracker> p_tracker) {
ERR_FAIL_COND(p_tracker.is_null());
StringName tracker_name = p_tracker->get_tracker_name();
if (trackers.has(tracker_name)) {
if (trackers[tracker_name] != p_tracker) {
// We already have a tracker with this name, we're going to replace it
trackers[tracker_name] = p_tracker;
emit_signal(SNAME("tracker_updated"), tracker_name, p_tracker->get_tracker_type());
}
} else {
trackers[tracker_name] = p_tracker;
emit_signal(SNAME("tracker_added"), tracker_name, p_tracker->get_tracker_type());
}
};
void XRServer::remove_tracker(Ref<XRPositionalTracker> p_tracker) {
ERR_FAIL_COND(p_tracker.is_null());
StringName tracker_name = p_tracker->get_tracker_name();
if (trackers.has(tracker_name)) {
// we send the signal right before removing it
emit_signal(SNAME("tracker_removed"), p_tracker->get_tracker_name(), p_tracker->get_tracker_type());
// and remove it
trackers.erase(tracker_name);
}
};
Dictionary XRServer::get_trackers(int p_tracker_types) {
Dictionary res;
for (int i = 0; i < trackers.size(); i++) {
Ref<XRPositionalTracker> tracker = trackers.get_value_at_index(i);
if (tracker.is_valid() && (tracker->get_tracker_type() & p_tracker_types) != 0) {
res[tracker->get_tracker_name()] = tracker;
}
}
return res;
}
Ref<XRPositionalTracker> XRServer::get_tracker(const StringName &p_name) const {
if (trackers.has(p_name)) {
return trackers[p_name];
} else {
// tracker hasn't been registered yet, which is fine, no need to spam the error log...
return Ref<XRPositionalTracker>();
}
};
PackedStringArray XRServer::get_suggested_tracker_names() const {
PackedStringArray arr;
for (int i = 0; i < interfaces.size(); i++) {
Ref<XRInterface> interface = interfaces[i];
PackedStringArray interface_arr = interface->get_suggested_tracker_names();
for (int a = 0; a < interface_arr.size(); a++) {
if (!arr.has(interface_arr[a])) {
arr.push_back(interface_arr[a]);
}
}
}
if (arr.size() == 0) {
// no suggestions from our tracker? include our defaults
arr.push_back(String("head"));
arr.push_back(String("left_hand"));
arr.push_back(String("right_hand"));
}
return arr;
}
PackedStringArray XRServer::get_suggested_pose_names(const StringName &p_tracker_name) const {
PackedStringArray arr;
for (int i = 0; i < interfaces.size(); i++) {
Ref<XRInterface> interface = interfaces[i];
PackedStringArray interface_arr = interface->get_suggested_pose_names(p_tracker_name);
for (int a = 0; a < interface_arr.size(); a++) {
if (!arr.has(interface_arr[a])) {
arr.push_back(interface_arr[a]);
}
}
}
if (arr.size() == 0) {
// no suggestions from our tracker? include our defaults
arr.push_back(String("default"));
if ((p_tracker_name == "left_hand") || (p_tracker_name == "right_hand")) {
arr.push_back(String("aim"));
arr.push_back(String("grip"));
arr.push_back(String("skeleton"));
}
}
return arr;
}
uint64_t XRServer::get_last_process_usec() {
return last_process_usec;
};
@ -373,8 +391,9 @@ XRServer::~XRServer() {
interfaces.remove(0);
}
// TODO pretty sure there is a clear function or something...
while (trackers.size() > 0) {
trackers.remove(0);
trackers.erase(trackers.get_key_at_index(0));
}
singleton = nullptr;

View File

@ -60,9 +60,10 @@ class XRServer : public Object {
public:
enum TrackerType {
TRACKER_CONTROLLER = 0x01, /* tracks a controller */
TRACKER_BASESTATION = 0x02, /* tracks location of a base station */
TRACKER_ANCHOR = 0x04, /* tracks an anchor point, used in AR to track a real live location */
TRACKER_HEAD = 0x01, /* tracks the position of the players head (or in case of handheld AR, location of the phone) */
TRACKER_CONTROLLER = 0x02, /* tracks a controller */
TRACKER_BASESTATION = 0x04, /* tracks location of a base station */
TRACKER_ANCHOR = 0x08, /* tracks an anchor point, used in AR to track a real live location */
TRACKER_UNKNOWN = 0x80, /* unknown tracker */
TRACKER_ANY_KNOWN = 0x7f, /* all except unknown */
@ -77,7 +78,7 @@ public:
private:
Vector<Ref<XRInterface>> interfaces;
Vector<Ref<XRPositionalTracker>> trackers;
Dictionary trackers;
Ref<XRInterface> primary_interface; /* we'll identify one interface as primary, this will be used by our viewports */
@ -164,13 +165,17 @@ public:
Our trackers are objects that expose the orientation and position of physical devices such as controller, anchor points, etc.
They are created and managed by our active AR/VR interfaces.
*/
bool is_tracker_id_in_use_for_type(TrackerType p_tracker_type, int p_tracker_id) const;
int get_free_tracker_id_for_type(TrackerType p_tracker_type);
void add_tracker(Ref<XRPositionalTracker> p_tracker);
void remove_tracker(Ref<XRPositionalTracker> p_tracker);
int get_tracker_count() const;
Ref<XRPositionalTracker> get_tracker(int p_index) const;
Ref<XRPositionalTracker> find_by_type_and_id(TrackerType p_tracker_type, int p_tracker_id) const;
Dictionary get_trackers(int p_tracker_types);
Ref<XRPositionalTracker> get_tracker(const StringName &p_name) const;
/*
We don't know which trackers and actions will existing during runtime but we can request suggested names from our interfaces to help our IDE UI.
*/
PackedStringArray get_suggested_tracker_names() const;
PackedStringArray get_suggested_pose_names(const StringName &p_tracker_name) const;
// Q: Should we add get_suggested_input_names and get_suggested_haptic_names even though we don't use them for the IDE?
uint64_t get_last_process_usec();
uint64_t get_last_commit_usec();