Skip to content

Commit e76fd7c

Browse files
felixtrzmeta-codesync[bot]
authored andcommitted
fix(input): rebuild interactables every frame to handle dynamically added objects
Summary: - Fix timing issue where dynamically added Interactable objects weren't becoming interactive - Rebuild `interactableDescendants` every frame in XR mode instead of only when dirty - Remove expensive `isDescendantOf()` parent chain traversal check (no longer needed) - Reuse single `intersectables` array to avoid allocations - Set `scene.interactableDescendants = undefined` in 2D mode for proper mouse/touch events - Objects now become interactive on next frame after TransformSystem parents them to scene Reviewed By: zjm-meta Differential Revision: D87656625 Privacy Context Container: L1334777 fbshipit-source-id: 5113910601e2579924a2a0a2a1eb8ef0fcc045fc
1 parent 1924ad8 commit e76fd7c

1 file changed

Lines changed: 13 additions & 36 deletions

File tree

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

Lines changed: 13 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { Hovered, Interactable, Pressed } from './state-tags.js';
1717
*
1818
* @remarks
1919
* - Scheduled after player movement so pointers reflect updated transforms.
20-
* - Maintains a filtered list of interactable Object3D roots when XR visibility is `Visible`.
20+
* - Rebuilds the interactable list every frame when in XR mode.
2121
* - Adds transient `Hovered` / `Pressed` tags so other systems can react declaratively.
2222
*
2323
* @category Input
@@ -42,7 +42,6 @@ export class InputSystem extends createSystem(
4242
) {
4343
private intersectables: Object3D[] = [];
4444
private shouldSetIntersectables = false;
45-
private dirty = true;
4645
private listeners = new WeakMap<
4746
Object3D,
4847
{
@@ -55,47 +54,38 @@ export class InputSystem extends createSystem(
5554
private lastBVHUpdate = new WeakMap<Object3D, number>();
5655

5756
init(): void {
58-
// React to XR visibility for enabling scoped intersections
57+
// Track XR visibility for scoped intersections (XR mode only)
5958
this.cleanupFuncs.push(
6059
this.visibilityState.subscribe((value) => {
61-
const nextVisible = value === VisibilityState.Visible;
62-
if (this.shouldSetIntersectables !== nextVisible) {
63-
this.shouldSetIntersectables = nextVisible;
64-
this.dirty = true;
65-
}
60+
this.shouldSetIntersectables = value === VisibilityState.Visible;
6661
}),
6762
);
6863

69-
// Handle additions/removals of interactables
64+
// Setup/cleanup event listeners when entities qualify/disqualify
7065
this.queries.interactable.subscribe('qualify', (entity) => {
7166
this.setupEventListeners(entity);
72-
this.dirty = true;
7367
});
7468
this.queries.interactable.subscribe('disqualify', (entity) => {
7569
this.cleanupEventListeners(entity);
76-
this.dirty = true;
7770
});
7871
}
7972

8073
update(delta: number, time: number): void {
8174
// Update input sampling first
8275
this.input.update(this.xrManager, delta, time);
8376

84-
// Maintain the filtered list of interactables for pointer raycasting
85-
if (this.dirty) {
86-
this.dirty = false;
77+
// Rebuild interactables list every frame in XR mode
78+
if (this.shouldSetIntersectables) {
8779
this.intersectables.length = 0;
88-
if (this.shouldSetIntersectables) {
89-
for (const entity of this.queries.interactable.entities) {
90-
const obj = entity.object3D;
91-
if (isDescendantOf(obj, this.scene)) {
92-
this.intersectables.push(obj!);
93-
}
80+
for (const entity of this.queries.interactable.entities) {
81+
if (entity.object3D) {
82+
this.intersectables.push(entity.object3D);
9483
}
95-
(this.scene as any).interactableDescendants = this.intersectables;
96-
} else {
97-
(this.scene as any).interactableDescendants = undefined;
9884
}
85+
this.scene.interactableDescendants = this.intersectables;
86+
} else {
87+
// In 2D mode, clear the reference so pointer-events uses default behavior
88+
this.scene.interactableDescendants = undefined;
9989
}
10090
}
10191

@@ -194,16 +184,3 @@ export class InputSystem extends createSystem(
194184
}
195185
}
196186
}
197-
198-
function isDescendantOf(
199-
object: Object3D | null | undefined,
200-
parent: Object3D,
201-
): boolean {
202-
while (object) {
203-
if (object === parent) {
204-
return true;
205-
}
206-
object = object.parent as any;
207-
}
208-
return false;
209-
}

0 commit comments

Comments
 (0)