@ -10,7 +10,9 @@ enum Phase { CONFIG, AR }
@ onready var teleop_client : Node = $ TeleopClient
@ onready var xr_origin : XROrigin3D = $ XROrigin3D
@ onready var xr_camera : XRCamera3D = $ XROrigin3D / XRCamera3D
@ onready var webcam_quad : MeshInstance3D = $ XROrigin3D / WebcamQuad
@ onready var webcam_quad_head : MeshInstance3D = $ XROrigin3D / WebcamQuadHead
@ onready var webcam_quad_left : MeshInstance3D = $ XROrigin3D / WebcamQuadLeft
@ onready var webcam_quad_right : MeshInstance3D = $ XROrigin3D / WebcamQuadRight
@ onready var start_screen : Node3D = $ XROrigin3D / StartScreen
@ onready var left_controller : XRController3D = $ XROrigin3D / LeftController
@ onready var right_controller : XRController3D = $ XROrigin3D / RightController
@ -52,8 +54,10 @@ const GAZE_BALL_BASE_COLORS: Array = [
func _ready ( ) - > void :
# Hide webcam quad and start screen until positioned
webcam_quad . visible = false
# Hide webcam quads and start screen until positioned
webcam_quad_head . visible = false
webcam_quad_left . visible = false
webcam_quad_right . visible = false
start_screen . visible = false
# Initialize OpenXR interface
@ -92,8 +96,10 @@ func _ready() -> void:
# Connect teleop client connection state to start screen
teleop_client . connection_state_changed . connect ( _on_connection_state_changed )
# Wire webcam frames (can happen anytime we're connected)
teleop_client . webcam_frame_received . connect ( webcam_quad . _on_webcam_frame )
# Wire webcam frames — 3 cameras routed to 3 quads
teleop_client . webcam_frame_head . connect ( webcam_quad_head . _on_webcam_frame )
teleop_client . webcam_frame_left . connect ( webcam_quad_left . _on_webcam_frame )
teleop_client . webcam_frame_right . connect ( webcam_quad_right . _on_webcam_frame )
# Setup VR pointer with references to controllers and XR origin
vr_pointer . setup ( xr_origin , xr_camera , left_controller , right_controller )
@ -219,7 +225,7 @@ func _on_pose_recentered() -> void:
if current_phase == Phase . CONFIG :
_position_panel_in_front_of_user ( hmd )
elif current_phase == Phase . AR :
_position_quad_in_front_of_user ( webcam_quad , hmd , 1.2 , - 0.3 )
_position_camera_row ( hmd )
_create_gaze_balls ( hmd )
@ -258,10 +264,12 @@ func _on_launch_ar_requested() -> void:
if not body_tracker . tracking_data_ready . is_connected ( teleop_client . _on_tracking_data ) :
body_tracker . tracking_data_ready . connect ( teleop_client . _on_tracking_data )
# Position webcam quad in front of user (stationary)
# Position webcam quads in front of user — row: left | head | right
var hmd : = XRServer . get_hmd_transform ( )
_position_quad_in_front_of_user ( webcam_quad , hmd , 1.2 , - 0.3 )
webcam_quad . visible = true
_position_camera_row ( hmd )
webcam_quad_head . visible = true
webcam_quad_left . visible = true
webcam_quad_right . visible = true
# Hide start screen and G1 models
start_screen . hide_screen ( )
@ -318,6 +326,32 @@ func _position_quad_in_front_of_user(quad: Node3D, hmd: Transform3D, distance: f
quad . rotate_y ( PI )
func _position_camera_row ( hmd : Transform3D ) - > void :
## Position 3 camera quads in a row: left | head | right
var forward : = - hmd . basis . z
forward . y = 0
if forward . length ( ) < 0.01 :
forward = Vector3 ( 0 , 0 , - 1 )
forward = forward . normalized ( )
var right_dir : = forward . cross ( Vector3 . UP ) . normalized ( )
var center : = hmd . origin + forward * 1.2
center . y = hmd . origin . y - 0.3
var spacing : = 0.85 # distance between quad centers
for quad_data in [
[ webcam_quad_left , - spacing ] ,
[ webcam_quad_head , 0.0 ] ,
[ webcam_quad_right , spacing ] ,
] :
var quad : Node3D = quad_data [ 0 ]
var offset : float = quad_data [ 1 ]
quad . global_position = center + right_dir * offset
quad . look_at ( hmd . origin , Vector3 . UP )
quad . rotate_y ( PI )
func _create_gaze_balls ( hmd : Transform3D ) - > void :
var forward : = - hmd . basis . z
forward . y = 0
@ -471,7 +505,9 @@ func _exit_ar_mode() -> void:
_gaze_timers [ i ] = 0.0
if _gaze_laser :
_gaze_laser . visible = false
webcam_quad . visible = false
webcam_quad_head . visible = false
webcam_quad_left . visible = false
webcam_quad_right . visible = false
# Show start screen again, reposition in front of user
var hmd : = XRServer . get_hmd_transform ( )