using Godot;
using System;
using System.Text;
///
/// Tracks eye positions in VR and sends them via UDP.
///
public partial class EyeTrackingSystem : Node
{
[Export]
public Camera3D VrCamera { get; set; } // Your main VR camera (set via the Inspector)
[Export]
public XROrigin3D XrOrigin { get; set; }
[Export]
public Node3D Target { get; set; } // The object to track
[Export]
public string ReceiverIp { get; set; } = "127.0.0.1";
[Export]
public int Port { get; set; } = 12345;
private PacketPeerUdp _udp;
///
/// Called when the node enters the scene tree for the first time.
///
public override void _Ready()
{
_udp = new PacketPeerUdp();
Error err = _udp.ConnectToHost(ReceiverIp, Port);
if (err != Error.Ok)
{
GD.PrintErr($"Failed to connect to host: {ReceiverIp}");
}
}
///
/// Called every frame.
///
public override void _Process(double delta)
{
if (VrCamera == null || Target == null)
{
return;
}
if (Input.IsKeyPressed(Key.Space))
{
var xrInterface = XRServer.FindInterface("OpenXR");
if (xrInterface == null)
{
GD.PrintErr("OpenXR interface not found");
return;
}
// Create new transforms for each eye with the same orientation as the main camera
Transform3D leftEyeTransform = xrInterface.GetTransformForView(0, XrOrigin.GlobalTransform);
Transform3D rightEyeTransform = xrInterface.GetTransformForView(1, XrOrigin.GlobalTransform);
// Use the main camera's projection matrix
Vector2 viewport = xrInterface.GetRenderTargetSize();
float aspectRatio = viewport.X / viewport.Y;
Projection leftProjection = xrInterface.GetProjectionForView(0, aspectRatio, VrCamera.Near, VrCamera.Far);
Projection rightProjection = xrInterface.GetProjectionForView(1, aspectRatio, VrCamera.Near, VrCamera.Far);
// Project the target's world position for each eye
Vector2 leftViewportPos = ProjectPointWithTransform(Target.GlobalTransform.Origin, leftEyeTransform, leftProjection);
Vector2 rightViewportPos = ProjectPointWithTransform(Target.GlobalTransform.Origin, rightEyeTransform, rightProjection);
// Format the message as "leftX,leftY;rightX,rightY" and send it via UDP
string message = string.Format("{0:0.0000},{1:0.0000};{2:0.0000},{3:0.0000}",
leftViewportPos.X, leftViewportPos.Y, rightViewportPos.X, rightViewportPos.Y);
_udp.PutPacket(Encoding.UTF8.GetBytes(message));
}
}
///
/// Projects a world point using a custom camera transform and projection matrix.
///
/// The point in world space to project.
/// The camera transform.
/// The projection matrix.
/// The projected point in viewport coordinates (range [0,1]).
private Vector2 ProjectPointWithTransform(Vector3 worldPoint, Transform3D cameraTransform, Projection projection)
{
// Get the view matrix (inverse of camera transform)
Transform3D viewMatrix = cameraTransform.Inverse();
// Transform the world point to view space
Vector3 pointInView = viewMatrix * worldPoint;
// Create a Vector4 for the projection
Vector4 vec = new Vector4(pointInView.X, pointInView.Y, pointInView.Z, 1.0f);
// Transform to clip space
Vector4 clipSpace = projection * vec;
// Convert to normalized device coordinates (NDC)
Vector3 ndc;
if (clipSpace.W != 0)
{
ndc = new Vector3(clipSpace.X, clipSpace.Y, clipSpace.Z) / clipSpace.W;
}
else
{
ndc = new Vector3(clipSpace.X, clipSpace.Y, clipSpace.Z);
}
// Convert from NDC (range [-1,1]) to viewport coordinates (range [0,1])
float viewportX = (ndc.X * 0.5f) + 0.5f;
float viewportY = (ndc.Y * 0.5f) + 0.5f;
GD.Print($"{viewportX} {viewportY}");
return new Vector2(viewportX, viewportY);
}
///
/// Called when the node is about to be removed from the scene.
///
public override void _ExitTree()
{
_udp = null;
}
}