Skip to content

Commit 47742e8

Browse files
felixtrzmeta-codesync[bot]
authored andcommitted
fix(environment): compensate IBL rotation on XR recenter
Summary: ### Core fix: `environment-system.ts` When a user recenters in XR (e.g., long-pressing the Oculus button), the reference space resets with a new origin/orientation. Previously, `environmentRotation` and `backgroundRotation` were not adjusted, causing the IBL lighting and skybox to visually shift relative to the real world. The fix: - Listens for `sessionstart`/`sessionend` on `xrManager` to track XR lifecycle - Attaches a `reset` listener on the XR reference space (with deferred attachment via `needsResetListener` flag in the update loop if the ref space isn't available at session start) - Extracts the yaw component from the reset event's quaternion transform - Accumulates a `recenterYawOffset` that's applied to the Y rotation of both `scene.environmentRotation` (IBL) and `scene.backgroundRotation` (skybox) in `updateBackgroundProps`, `updateIBLProps`, and the immediate `applyRecenterCompensation` path - Cleans up all listeners on session end and system disposal ### Test fixture: `scene-understanding` example - Added a Venice sunset EXR texture to the asset manifest and `public/textures/` - Added `IBLTexture` and `DomeTexture` components to the level root so the scene-understanding example has a directional HDR environment for visually verifying the recenter fix Reviewed By: zjm-meta Differential Revision: D94147476 fbshipit-source-id: 6ddf2d5f15aa9d83a11a8b28b4aa74ec1ca03f0f
1 parent 8df424d commit 47742e8

File tree

3 files changed

+112
-3
lines changed

3 files changed

+112
-3
lines changed
Binary file not shown.

examples/scene-understanding/src/index.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import {
99
XRAnchor,
10+
AssetType,
1011
Color,
1112
XRMesh,
1213
XRPlane,
@@ -15,6 +16,8 @@ import {
1516
createSystem,
1617
Interactable,
1718
DistanceGrabbable,
19+
IBLTexture,
20+
DomeTexture,
1821
Mesh,
1922
SphereGeometry,
2023
MeshStandardMaterial,
@@ -90,7 +93,13 @@ export class SceneShowSystem extends createSystem({
9093
}
9194

9295
World.create(document.getElementById('scene-container'), {
93-
undefined,
96+
assets: {
97+
veniceSunset: {
98+
url: './textures/venice_sunset_1k.exr',
99+
type: AssetType.HDRTexture,
100+
priority: 'critical',
101+
},
102+
},
94103
xr: {
95104
sessionMode: SessionMode.ImmersiveAR,
96105
referenceSpace: ReferenceSpaceType.Unbounded,
@@ -111,5 +120,9 @@ World.create(document.getElementById('scene-container'), {
111120

112121
scene.background = new Color(0x808080);
113122

123+
const root = world.activeLevel.value;
124+
root.addComponent(IBLTexture, { src: 'veniceSunset' });
125+
root.addComponent(DomeTexture, { src: 'veniceSunset' });
126+
114127
world.registerSystem(SceneShowSystem);
115128
});

packages/core/src/environment/environment-system.ts

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ export class EnvironmentSystem extends createSystem({
7777
private pmrem!: PMREMGenerator;
7878
private gradientGeometry?: SphereGeometry;
7979
private tmpColor: Color = new Color();
80+
private recenterYawOffset: number = 0;
81+
private resetListenerCleanup: (() => void) | null = null;
82+
private needsResetListener: boolean = false;
8083

8184
init(): void {
8285
this.pmrem = new PMREMGenerator(this.renderer);
@@ -93,9 +96,28 @@ export class EnvironmentSystem extends createSystem({
9396
this.cleanupFuncs.push(
9497
this.visibilityState.subscribe(() => this.updateBackgroundForXRMode()),
9598
);
99+
100+
// XR recenter compensation
101+
this.xrManager.addEventListener('sessionstart', this.onXRSessionStart);
102+
this.xrManager.addEventListener('sessionend', this.onXRSessionEnd);
103+
this.cleanupFuncs.push(() => {
104+
this.xrManager.removeEventListener('sessionstart', this.onXRSessionStart);
105+
this.xrManager.removeEventListener('sessionend', this.onXRSessionEnd);
106+
this.resetListenerCleanup?.();
107+
this.resetListenerCleanup = null;
108+
});
96109
}
97110

98111
update(): void {
112+
// Deferred reset listener attachment
113+
if (this.needsResetListener) {
114+
const refSpace = this.xrManager.getReferenceSpace();
115+
if (refSpace) {
116+
this.attachResetListener(refSpace);
117+
this.needsResetListener = false;
118+
}
119+
}
120+
99121
// Choose background source: DomeTexture > DomeGradient
100122
let backgroundEntity: Entity | undefined;
101123
for (const e of this.queries.domeTextures.entities) {
@@ -199,7 +221,11 @@ export class EnvironmentSystem extends createSystem({
199221
this.scene.backgroundBlurriness = blurriness;
200222
this.scene.backgroundIntensity = intensity;
201223
if (rot) {
202-
this.scene.backgroundRotation?.set(rot[0] || 0, rot[1] || 0, rot[2] || 0);
224+
this.scene.backgroundRotation?.set(
225+
rot[0] || 0,
226+
(rot[1] || 0) + this.recenterYawOffset,
227+
rot[2] || 0,
228+
);
203229
}
204230
}
205231

@@ -295,7 +321,7 @@ export class EnvironmentSystem extends createSystem({
295321
if (rot) {
296322
this.scene.environmentRotation?.set(
297323
rot[0] || 0,
298-
rot[1] || 0,
324+
(rot[1] || 0) + this.recenterYawOffset,
299325
rot[2] || 0,
300326
);
301327
}
@@ -359,6 +385,76 @@ export class EnvironmentSystem extends createSystem({
359385
}
360386
}
361387

388+
// XR recenter handling
389+
private onXRSessionStart = (): void => {
390+
this.recenterYawOffset = 0;
391+
const refSpace = this.xrManager.getReferenceSpace();
392+
if (refSpace) {
393+
this.attachResetListener(refSpace);
394+
} else {
395+
this.needsResetListener = true;
396+
}
397+
};
398+
399+
private attachResetListener(refSpace: XRReferenceSpace): void {
400+
const onReset = (event: XRReferenceSpaceEvent) => {
401+
this.onReferenceSpaceReset(event);
402+
};
403+
refSpace.addEventListener('reset', onReset);
404+
this.resetListenerCleanup = () => {
405+
refSpace.removeEventListener('reset', onReset);
406+
};
407+
}
408+
409+
private onXRSessionEnd = (): void => {
410+
this.resetListenerCleanup?.();
411+
this.resetListenerCleanup = null;
412+
this.recenterYawOffset = 0;
413+
this.needsResetListener = false;
414+
};
415+
416+
private onReferenceSpaceReset(event: XRReferenceSpaceEvent): void {
417+
const transform = event.transform;
418+
if (!transform) {
419+
return;
420+
}
421+
422+
const q = transform.orientation;
423+
const yaw = Math.atan2(
424+
2 * (q.w * q.y + q.x * q.z),
425+
1 - 2 * (q.y * q.y + q.z * q.z),
426+
);
427+
428+
this.recenterYawOffset += yaw;
429+
this.applyRecenterCompensation();
430+
}
431+
432+
private applyRecenterCompensation(): void {
433+
for (const entity of this.queries.iblTextures.entities) {
434+
const rot = entity.getVectorView(IBLTexture, 'rotation');
435+
if (rot) {
436+
this.scene.environmentRotation?.set(
437+
rot[0] || 0,
438+
(rot[1] || 0) + this.recenterYawOffset,
439+
rot[2] || 0,
440+
);
441+
}
442+
break;
443+
}
444+
445+
for (const entity of this.queries.domeTextures.entities) {
446+
const rot = entity.getVectorView(DomeTexture, 'rotation');
447+
if (rot) {
448+
this.scene.backgroundRotation?.set(
449+
rot[0] || 0,
450+
(rot[1] || 0) + this.recenterYawOffset,
451+
rot[2] || 0,
452+
);
453+
}
454+
break;
455+
}
456+
}
457+
362458
// XR background handling
363459
private updateBackgroundForXRMode(): void {
364460
const session = this.xrManager.getSession?.() ?? this.world.session;

0 commit comments

Comments
 (0)