Pre-position XR camera at cockpit before VR entry
All checks were successful
Build / build (push) Successful in 1m42s

Use setTransformationFromNonVRCamera to set XR reference space
to ship cockpit position before entering immersive mode. This
prevents camera jumping on Quest when the 2D preloader disappears.

Also includes:
- Ship physics tuning (reduced force multipliers and fuel consumption)
- Fix reverse thrust direction in shipPhysics.ts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2025-11-29 14:46:13 -06:00
parent 226ec5f51a
commit 5cdbf22e67
4 changed files with 20 additions and 13 deletions

View File

@ -6,10 +6,10 @@ import log from './logger';
const DEFAULT_SHIP_PHYSICS = { const DEFAULT_SHIP_PHYSICS = {
maxLinearVelocity: 200, maxLinearVelocity: 200,
maxAngularVelocity: 1.4, maxAngularVelocity: 1.4,
linearForceMultiplier: 500, linearForceMultiplier: 400,
angularForceMultiplier: .5, angularForceMultiplier: .4,
linearFuelConsumptionRate: 0.0002778, // 1 minute at full thrust (60 Hz) linearFuelConsumptionRate: 0.00002778, // 1 minute at full thrust (60 Hz)
angularFuelConsumptionRate: 0.0001389, // 2 minutes at full thrust (60 Hz) angularFuelConsumptionRate: 0.00001389, // 2 minutes at full thrust (60 Hz)
linearDamping: 0.2, linearDamping: 0.2,
angularDamping: 0.6, // Moderate damping for 2-3 second coast angularDamping: 0.6, // Moderate damping for 2-3 second coast
alwaysActive: true, // Prevent physics sleep (false may cause abrupt stops at zero velocity) alwaysActive: true, // Prevent physics sleep (false may cause abrupt stops at zero velocity)

View File

@ -1,4 +1,4 @@
import { AudioEngineV2, Engine, ParticleHelper } from "@babylonjs/core"; import { AudioEngineV2, Engine, FreeCamera, ParticleHelper, Vector3 } from "@babylonjs/core";
import { DefaultScene } from "../defaultScene"; import { DefaultScene } from "../defaultScene";
import { Level1 } from "../../levels/level1"; import { Level1 } from "../../levels/level1";
import Level from "../../levels/level"; import Level from "../../levels/level";
@ -87,6 +87,16 @@ export function createLevelSelectedHandler(context: LevelSelectedContext): (e: C
if (DefaultScene.XR) { if (DefaultScene.XR) {
try { try {
preloader.updateProgress(75, 'Entering VR...'); preloader.updateProgress(75, 'Entering VR...');
// Pre-position XR camera at ship cockpit before entering VR
// This prevents camera jump on Quest when immersive mode starts
const spawnPos = config.ship?.position || [0, 0, 0];
const cockpitPosition = new Vector3(spawnPos[0], spawnPos[1] + 1.2, spawnPos[2]);
const tempCamera = new FreeCamera("tempCockpit", cockpitPosition, DefaultScene.MainScene);
DefaultScene.XR.baseExperience.camera.setTransformationFromNonVRCamera(tempCamera, true);
tempCamera.dispose();
log.debug('[Main] XR camera pre-positioned at cockpit:', cockpitPosition.toString());
xrSession = await DefaultScene.XR.baseExperience.enterXRAsync('immersive-vr', 'local-floor'); xrSession = await DefaultScene.XR.baseExperience.enterXRAsync('immersive-vr', 'local-floor');
log.debug('XR session started successfully (render loop paused until camera is ready)'); log.debug('XR session started successfully (render loop paused until camera is ready)');
} catch (error) { } catch (error) {
@ -171,11 +181,9 @@ export function createLevelSelectedHandler(context: LevelSelectedContext): (e: C
}); });
} }
// Hide preloader // Hide preloader immediately - SceneFader handles visual transition
preloader.updateProgress(100, 'Ready!'); preloader.updateProgress(100, 'Ready!');
setTimeout(() => { preloader.hide();
preloader.hide();
}, 500);
// Hide UI (no longer remove from DOM - let Svelte routing handle it) // Hide UI (no longer remove from DOM - let Svelte routing handle it)
log.info('[Main] ========== HIDING UI FOR GAMEPLAY =========='); log.info('[Main] ========== HIDING UI FOR GAMEPLAY ==========');

View File

@ -106,7 +106,7 @@ export class Level1 implements Level {
xr.baseExperience.camera.position = new Vector3(0, 1.2, 0); xr.baseExperience.camera.position = new Vector3(0, 1.2, 0);
log.debug('[Level1] XR camera parented to cameraRig at position (0, 1.2, 0)'); log.debug('[Level1] XR camera parented to cameraRig at position (0, 1.2, 0)');
// Show the canvas now that camera is properly positioned in ship // Show the canvas now that camera is parented
const canvas = document.getElementById('gameCanvas'); const canvas = document.getElementById('gameCanvas');
if (canvas) { if (canvas) {
canvas.style.display = 'block'; canvas.style.display = 'block';

View File

@ -60,7 +60,6 @@ export class ShipPhysics {
const currentSpeed = currentLinearVelocity.length(); const currentSpeed = currentLinearVelocity.length();
let linearMagnitude = 0; let linearMagnitude = 0;
let angularMagnitude = 0;
// Apply linear force from left stick Y (forward/backward) // Apply linear force from left stick Y (forward/backward)
if (Math.abs(leftStick.y) > 0.15) { if (Math.abs(leftStick.y) > 0.15) {
@ -81,7 +80,7 @@ export class ShipPhysics {
); );
// Apply reverse thrust factor: forward at full power, reverse at reduced power // Apply reverse thrust factor: forward at full power, reverse at reduced power
const thrustMultiplier = thrustDirection < 0 const thrustMultiplier = thrustDirection > 0
? 1.0 // Forward thrust at full power ? 1.0 // Forward thrust at full power
: this._config.reverseThrustFactor; // Reverse thrust scaled down : this._config.reverseThrustFactor; // Reverse thrust scaled down
@ -109,7 +108,7 @@ export class ShipPhysics {
} }
// Calculate rotation magnitude for torque // Calculate rotation magnitude for torque
angularMagnitude = let angularMagnitude =
Math.abs(rightStick.y) + Math.abs(rightStick.y) +
Math.abs(rightStick.x) + Math.abs(rightStick.x) +
Math.abs(leftStick.x); Math.abs(leftStick.x);