Compare commits

...
Sign in to create a new pull request.

4 commits

Author SHA1 Message Date
102c83d5b9
fix: Adds look vector correction code
This is needed for the Xreal line of devices as the reverse engineered
driver works, but has... issues. One of them being normal sensor drift,
the other being ~2330 units in any direction for some reason.

This is a hack but I can't think of a normal person that can spin their
head around fast enough to trigger anything past 7 units/pull cycle,
especially considering our already high pulling rate (basically as
fast as we can)
2025-06-28 19:49:12 -04:00
0efaf19b40
feature: Implement display rotation 2025-06-27 22:08:51 -04:00
6911afb6e0
fix: Adds missing script headers on unrealxr startup script 2025-06-27 12:50:29 -04:00
30e90af276
feature: Attempt to decrease load by decreasing max frame rate of window
We don't really need to run at twice the speed of the display's refresh
rate as Raylib manages the target time between frames well.
2025-06-27 12:48:25 -04:00
5 changed files with 95 additions and 18 deletions

View file

@ -6,10 +6,12 @@ import _ "embed"
var InitialConfig []byte var InitialConfig []byte
type DisplayConfig struct { type DisplayConfig struct {
Angle *int `yaml:"angle"` Angle *int `yaml:"angle"`
FOV *int `yaml:"fov"` FOV *int `yaml:"fov"`
Spacing *float32 `yaml:"spacing"` Spacing *float32 `yaml:"spacing"`
Count *int `yaml:"count"` RadiusMultiplier *float32 `yaml:"circle_radius_multiplier"`
UseCircularSpacing *bool `yaml:"use_circular_spacing"`
Count *int `yaml:"count"`
} }
type AppOverrides struct { type AppOverrides struct {
@ -38,10 +40,12 @@ func getPtrToBool(bool bool) *bool {
var DefaultConfig = &Config{ var DefaultConfig = &Config{
DisplayConfig: DisplayConfig{ DisplayConfig: DisplayConfig{
Angle: getPtrToInt(45), Angle: getPtrToInt(45),
FOV: getPtrToInt(45), FOV: getPtrToInt(45),
Spacing: getPtrToFloat32(0.5), Spacing: getPtrToFloat32(0.5),
Count: getPtrToInt(3), RadiusMultiplier: getPtrToFloat32(2),
UseCircularSpacing: getPtrToBool(true),
Count: getPtrToInt(3),
}, },
Overrides: AppOverrides{ Overrides: AppOverrides{
AllowUnsupportedDevices: getPtrToBool(false), AllowUnsupportedDevices: getPtrToBool(false),

View file

@ -9,7 +9,9 @@
display: display:
angle: 45 # Angle of the virtual displays angle: 45 # Angle of the virtual displays
fov: 45 # FOV of the 3D camera fov: 45 # FOV of the 3D camera
spacing: 0.5 # Spacing between virtual displays spacing: 0.5 # Raw spacing between virtual displays. Does not use circles in the layout. Purely flat plane.
circle_radius_multiplier: 2 # Multiplier for the radius of the circle used to calculate the spacing between virtual displays. "Rounded" plane of sorts.
use_circular_spacing: true # If true, uses a circular layout for the virtual displays.
count: 3 # Count of virtual displays count: 3 # Count of virtual displays
overrides: overrides:
allow_unsupported_devices: false # If true, allows unsupported devices to be used as long as they're a compatible vendor (Xreal) allow_unsupported_devices: false # If true, allows unsupported devices to be used as long as they're a compatible vendor (Xreal)

View file

@ -118,7 +118,7 @@ func mainEntrypoint(context.Context, *cli.Command) error {
bufio.NewReader(os.Stdin).ReadBytes('\n') // Wait for Enter key press before continuing bufio.NewReader(os.Stdin).ReadBytes('\n') // Wait for Enter key press before continuing
log.Info("Initializing XR headset") log.Info("Initializing XR headset")
rl.SetTargetFPS(int32(displayMetadata.MaxRefreshRate * 2)) rl.SetTargetFPS(int32(displayMetadata.MaxRefreshRate))
rl.InitWindow(int32(displayMetadata.MaxWidth), int32(displayMetadata.MaxHeight), "UnrealXR") rl.InitWindow(int32(displayMetadata.MaxWidth), int32(displayMetadata.MaxHeight), "UnrealXR")
atexit.Register(func() { atexit.Register(func() {

View file

@ -35,6 +35,15 @@ func findOptimalHorizontalRes(verticalDisplayRes float32, horizontalDisplayRes f
return horizontalSize return horizontalSize
} }
func findHfovFromVfov(vfovDeg, w, h float64) float64 {
vfovRad := vfovDeg * math.Pi / 180
ar := w / h
hfovRad := 2 * math.Atan(math.Tan(vfovRad/2)*ar)
return hfovRad * 180 / math.Pi
}
func EnterRenderLoop(config *libconfig.Config, displayMetadata *edidtools.DisplayMetadata, evdiCards []*EvdiDisplayMetadata) { func EnterRenderLoop(config *libconfig.Config, displayMetadata *edidtools.DisplayMetadata, evdiCards []*EvdiDisplayMetadata) {
log.Info("Initializing AR driver") log.Info("Initializing AR driver")
headset, err := ardriver.GetDevice() headset, err := ardriver.GetDevice()
@ -99,7 +108,9 @@ func EnterRenderLoop(config *libconfig.Config, displayMetadata *edidtools.Displa
headset.RegisterEventListeners(arEventListner) headset.RegisterEventListeners(arEventListner)
fovY := float32(45.0) fovY := float32(*config.DisplayConfig.FOV)
fovX := findHfovFromVfov(float64(fovY), float64(displayMetadata.MaxWidth), float64(displayMetadata.MaxHeight))
verticalSize := findMaxVerticalSize(fovY, 5.0) verticalSize := findMaxVerticalSize(fovY, 5.0)
camera := rl.NewCamera3D( camera := rl.NewCamera3D(
@ -125,6 +136,21 @@ func EnterRenderLoop(config *libconfig.Config, displayMetadata *edidtools.Displa
horizontalSize := findOptimalHorizontalRes(float32(displayMetadata.MaxHeight), float32(displayMetadata.MaxWidth), verticalSize) horizontalSize := findOptimalHorizontalRes(float32(displayMetadata.MaxHeight), float32(displayMetadata.MaxWidth), verticalSize)
coreMesh := rl.GenMeshPlane(horizontalSize, verticalSize, 1, 1) coreMesh := rl.GenMeshPlane(horizontalSize, verticalSize, 1, 1)
var radius float32
if *config.DisplayConfig.UseCircularSpacing == true {
radiusX := (horizontalSize / 2) / float32(math.Tan((float64(fovX)*math.Pi/180.0)/2))
radiusY := (verticalSize / 2) / float32(math.Tan((float64(fovY)*math.Pi/180.0)/2))
if radiusY > radiusX {
radius = radiusY
} else {
radius = radiusX
}
radius *= *config.DisplayConfig.RadiusMultiplier
}
movementVector := rl.Vector3{ movementVector := rl.Vector3{
X: 0.0, X: 0.0,
Y: 0.0, Y: 0.0,
@ -171,6 +197,17 @@ func EnterRenderLoop(config *libconfig.Config, displayMetadata *edidtools.Displa
texture := rl.LoadTextureFromImage(image) texture := rl.LoadTextureFromImage(image)
model := rl.LoadModelFromMesh(coreMesh) model := rl.LoadModelFromMesh(coreMesh)
// spin up/down
pitchRad := float32(-90 * rl.Deg2rad)
// spin left/right
yawRad := currentAngle * rl.Deg2rad
rotX := rl.MatrixRotateX(pitchRad)
rotY := rl.MatrixRotateY(yawRad)
transform := rl.MatrixMultiply(rotX, rotY)
model.Transform = transform
rl.SetMaterialTexture(model.Materials, rl.MapAlbedo, texture) rl.SetMaterialTexture(model.Materials, rl.MapAlbedo, texture)
rects[i] = &TextureModelPair{ rects[i] = &TextureModelPair{
@ -198,6 +235,24 @@ func EnterRenderLoop(config *libconfig.Config, displayMetadata *edidtools.Displa
lookVector.Z = (currentRoll - previousRoll) * 6.5 lookVector.Z = (currentRoll - previousRoll) * 6.5
} }
// evil look hacks to not randomly explode
maxTrustedSize := float64(7)
if math.Abs(float64(lookVector.X)) > maxTrustedSize {
log.Errorf("WOAH. Ignoring extreme camera movement for vector X: %.02f", lookVector.X)
lookVector.X = 0
}
if math.Abs(float64(lookVector.Y)) > maxTrustedSize {
log.Errorf("WOAH. Ignoring extreme camera movement for vector Y: %.02f", lookVector.Y)
lookVector.Y = 0
}
if !hasZVectorDisabledQuirk && math.Abs(float64(lookVector.Z)) > maxTrustedSize {
log.Errorf("WOAH. Ignoring extreme camera movement for vector Z: %.02f", lookVector.Z)
lookVector.Z = 0
}
rl.UpdateCameraPro(&camera, movementVector, lookVector, 0) rl.UpdateCameraPro(&camera, movementVector, lookVector, 0)
} }
} else { } else {
@ -235,20 +290,35 @@ func EnterRenderLoop(config *libconfig.Config, displayMetadata *edidtools.Displa
card.EvdiNode.RequestUpdate(card.Buffer) card.EvdiNode.RequestUpdate(card.Buffer)
} }
worldPos := rl.Vector3{
X: 0,
Y: verticalSize / 2,
Z: 0,
}
if *config.DisplayConfig.UseCircularSpacing == true {
yawRad := float32(rl.Deg2rad * rect.CurrentAngle)
// WTF?
posX := float32(math.Sin(float64(yawRad))) * radius
posZ := -float32(math.Cos(float64(yawRad))) * radius
worldPos.X = posX
worldPos.Z = posZ + radius
} else {
worldPos.X = rect.CurrentDisplaySpacing
}
rl.DrawModelEx( rl.DrawModelEx(
rect.Model, rect.Model,
rl.Vector3{ worldPos,
X: rect.CurrentDisplaySpacing,
Y: verticalSize / 2,
Z: 0,
},
// rotate around X to make it vertical // rotate around X to make it vertical
rl.Vector3{ rl.Vector3{
X: 1, X: 0,
Y: 0, Y: 0,
Z: 0, Z: 0,
}, },
90, 0,
rl.Vector3{ rl.Vector3{
X: 1, X: 1,
Y: 1, Y: 1,

View file

@ -1 +1,2 @@
#!/usr/bin/env bash
sudo UNREALXR_LOG_LEVEL="$UNREALXR_LOG_LEVEL" LD_LIBRARY_PATH="$LD_LIBRARY_PATH" UNREALXR_CONFIG_PATH="$UNREALXR_CONFIG_PATH" UNREALXR_DATA_PATH="$UNREALXR_DATA_PATH" WAYLAND_DISPLAY="$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" XDG_RUNTIME_DIR="/user/run/0" ./uxr sudo UNREALXR_LOG_LEVEL="$UNREALXR_LOG_LEVEL" LD_LIBRARY_PATH="$LD_LIBRARY_PATH" UNREALXR_CONFIG_PATH="$UNREALXR_CONFIG_PATH" UNREALXR_DATA_PATH="$UNREALXR_DATA_PATH" WAYLAND_DISPLAY="$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" XDG_RUNTIME_DIR="/user/run/0" ./uxr