diff --git a/app/config/config.go b/app/config/config.go index 1222abf..5938650 100644 --- a/app/config/config.go +++ b/app/config/config.go @@ -6,10 +6,12 @@ import _ "embed" var InitialConfig []byte type DisplayConfig struct { - Angle *int `yaml:"angle"` - FOV *int `yaml:"fov"` - Spacing *float32 `yaml:"spacing"` - Count *int `yaml:"count"` + Angle *int `yaml:"angle"` + FOV *int `yaml:"fov"` + Spacing *float32 `yaml:"spacing"` + RadiusMultiplier *float32 `yaml:"circle_radius_multiplier"` + UseCircularSpacing *bool `yaml:"use_circular_spacing"` + Count *int `yaml:"count"` } type AppOverrides struct { @@ -38,10 +40,12 @@ func getPtrToBool(bool bool) *bool { var DefaultConfig = &Config{ DisplayConfig: DisplayConfig{ - Angle: getPtrToInt(45), - FOV: getPtrToInt(45), - Spacing: getPtrToFloat32(0.5), - Count: getPtrToInt(3), + Angle: getPtrToInt(45), + FOV: getPtrToInt(45), + Spacing: getPtrToFloat32(0.5), + RadiusMultiplier: getPtrToFloat32(2), + UseCircularSpacing: getPtrToBool(true), + Count: getPtrToInt(3), }, Overrides: AppOverrides{ AllowUnsupportedDevices: getPtrToBool(false), diff --git a/app/config/default_config.yml b/app/config/default_config.yml index 4938843..3daeee3 100644 --- a/app/config/default_config.yml +++ b/app/config/default_config.yml @@ -9,7 +9,9 @@ display: angle: 45 # Angle of the virtual displays 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 overrides: allow_unsupported_devices: false # If true, allows unsupported devices to be used as long as they're a compatible vendor (Xreal) diff --git a/app/main.go b/app/main.go index 239c49e..f84df7a 100644 --- a/app/main.go +++ b/app/main.go @@ -118,7 +118,7 @@ func mainEntrypoint(context.Context, *cli.Command) error { bufio.NewReader(os.Stdin).ReadBytes('\n') // Wait for Enter key press before continuing 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") atexit.Register(func() { diff --git a/app/renderer/renderer.go b/app/renderer/renderer.go index 8e2f3e3..79cda44 100644 --- a/app/renderer/renderer.go +++ b/app/renderer/renderer.go @@ -35,6 +35,15 @@ func findOptimalHorizontalRes(verticalDisplayRes float32, horizontalDisplayRes f 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) { log.Info("Initializing AR driver") headset, err := ardriver.GetDevice() @@ -99,7 +108,9 @@ func EnterRenderLoop(config *libconfig.Config, displayMetadata *edidtools.Displa 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) camera := rl.NewCamera3D( @@ -125,6 +136,21 @@ func EnterRenderLoop(config *libconfig.Config, displayMetadata *edidtools.Displa horizontalSize := findOptimalHorizontalRes(float32(displayMetadata.MaxHeight), float32(displayMetadata.MaxWidth), verticalSize) 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{ X: 0.0, Y: 0.0, @@ -171,6 +197,17 @@ func EnterRenderLoop(config *libconfig.Config, displayMetadata *edidtools.Displa texture := rl.LoadTextureFromImage(image) 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) rects[i] = &TextureModelPair{ @@ -235,20 +272,35 @@ func EnterRenderLoop(config *libconfig.Config, displayMetadata *edidtools.Displa 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( rect.Model, - rl.Vector3{ - X: rect.CurrentDisplaySpacing, - Y: verticalSize / 2, - Z: 0, - }, + worldPos, // rotate around X to make it vertical rl.Vector3{ - X: 1, + X: 0, Y: 0, Z: 0, }, - 90, + 0, rl.Vector3{ X: 1, Y: 1, diff --git a/unrealxr b/unrealxr index 512cfa2..5c13589 100755 --- a/unrealxr +++ b/unrealxr @@ -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