@relu-interactives/spatial-ecs 0.4.3 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-6UZC6M7V.js → chunk-OY2OONRR.js} +52 -12
- package/dist/chunk-OY2OONRR.js.map +1 -0
- package/dist/docs/guide/mixed-reality.md +23 -0
- package/dist/{index-CBdnIyI8.d.cts → index-7YCk4cmQ.d.cts} +23 -1
- package/dist/{index-n7SeH119.d.ts → index-B2oHfqkj.d.ts} +23 -1
- package/dist/index.cjs +50 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/systems/index.cjs +50 -10
- package/dist/systems/index.cjs.map +1 -1
- package/dist/systems/index.d.cts +1 -1
- package/dist/systems/index.d.ts +1 -1
- package/dist/systems/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-6UZC6M7V.js.map +0 -1
|
@@ -41,6 +41,29 @@ Each detected surface becomes an ordinary entity, so it goes through the same
|
|
|
41
41
|
physics as everything else: a dynamic rigidbody dropped in a mixed-reality scene
|
|
42
42
|
lands on the real floor with no extra code.
|
|
43
43
|
|
|
44
|
+
### The room has to have been scanned first
|
|
45
|
+
|
|
46
|
+
This is the single most common reason a mixed-reality scene comes up empty, and
|
|
47
|
+
it is not a bug in the experience.
|
|
48
|
+
|
|
49
|
+
**Plane and mesh detection do not scan anything.** They replay a scan the user
|
|
50
|
+
captured earlier through the headset's own room setup — on Quest, *Settings >
|
|
51
|
+
Guardian > Mixed Reality* (Space Setup). If the user has never run it,
|
|
52
|
+
`detectedPlanes` and `detectedMeshes` simply stay empty, with no prompt and no
|
|
53
|
+
error. If they ran it in a **different room**, you get that room's furniture
|
|
54
|
+
overlaid on this one, which looks like the tracking is broken.
|
|
55
|
+
|
|
56
|
+
`XREnvironmentSystem` handles this: when `environmentPhysics` is on and nothing
|
|
57
|
+
at all has been detected three seconds into the session, it calls
|
|
58
|
+
`XRSession.initiateRoomCapture()`, which launches room setup from inside the
|
|
59
|
+
session. It is a Meta extension, so it is feature-detected and silently skipped
|
|
60
|
+
elsewhere, and — per Meta's contract — it is only ever called **once per
|
|
61
|
+
session**.
|
|
62
|
+
|
|
63
|
+
If a user reports "it never asked to scan my room" and the geometry does not
|
|
64
|
+
match, they are almost certainly running against a stale scan from another
|
|
65
|
+
space; re-running Space Setup for the current room fixes it.
|
|
66
|
+
|
|
44
67
|
## Finding the real room from a script
|
|
45
68
|
|
|
46
69
|
Detected surfaces carry `XREnvironmentComponent`, so they are queryable like any
|
|
@@ -647,6 +647,8 @@ declare class XRSessionSystem implements EcsSystem {
|
|
|
647
647
|
private readonly inputSystem;
|
|
648
648
|
/** Y compensation applied when a floor-relative space was requested but not granted. */
|
|
649
649
|
private appliedOriginOffset;
|
|
650
|
+
/** The live session, so an async floor probe can tell if it has been replaced. */
|
|
651
|
+
private session;
|
|
650
652
|
constructor(inputSystem?: XRInputSystem);
|
|
651
653
|
update(world: World): void;
|
|
652
654
|
private handleSessionStart;
|
|
@@ -690,7 +692,27 @@ declare class XREnvironmentSystem implements EcsSystem {
|
|
|
690
692
|
private readonly meshes;
|
|
691
693
|
private presenting;
|
|
692
694
|
private debugVisible;
|
|
693
|
-
|
|
695
|
+
/** Seconds presenting, used to decide the room has genuinely nothing in it. */
|
|
696
|
+
private elapsed;
|
|
697
|
+
/** `initiateRoomCapture` may be called only ONCE per session. */
|
|
698
|
+
private roomCaptureRequested;
|
|
699
|
+
update(world: World, deltaTime?: number): void;
|
|
700
|
+
/**
|
|
701
|
+
* Ask the runtime to scan the room when it turns out we have no room.
|
|
702
|
+
*
|
|
703
|
+
* Plane and mesh detection do NOT scan anything — they replay a scan the user
|
|
704
|
+
* captured earlier through the system's own room setup. Most people have
|
|
705
|
+
* never run it, and those who have may have run it in a DIFFERENT room, so a
|
|
706
|
+
* mixed-reality experience opens against either nothing at all or someone
|
|
707
|
+
* else's furniture, with no prompt and no error.
|
|
708
|
+
*
|
|
709
|
+
* `XRSession.initiateRoomCapture()` (Meta's extension) launches that setup
|
|
710
|
+
* from inside the session, and exists precisely for this case. Contract, from
|
|
711
|
+
* Meta's docs: callable ONCE per session, and only worth calling once we are
|
|
712
|
+
* confident the room really is empty — the arrays populate a beat after the
|
|
713
|
+
* session starts, so a check on frame one would fire for everyone.
|
|
714
|
+
*/
|
|
715
|
+
private maybeCaptureRoom;
|
|
694
716
|
/** How many pieces of real geometry are currently colliding. Script-facing. */
|
|
695
717
|
getDetectedCount(): {
|
|
696
718
|
planes: number;
|
|
@@ -647,6 +647,8 @@ declare class XRSessionSystem implements EcsSystem {
|
|
|
647
647
|
private readonly inputSystem;
|
|
648
648
|
/** Y compensation applied when a floor-relative space was requested but not granted. */
|
|
649
649
|
private appliedOriginOffset;
|
|
650
|
+
/** The live session, so an async floor probe can tell if it has been replaced. */
|
|
651
|
+
private session;
|
|
650
652
|
constructor(inputSystem?: XRInputSystem);
|
|
651
653
|
update(world: World): void;
|
|
652
654
|
private handleSessionStart;
|
|
@@ -690,7 +692,27 @@ declare class XREnvironmentSystem implements EcsSystem {
|
|
|
690
692
|
private readonly meshes;
|
|
691
693
|
private presenting;
|
|
692
694
|
private debugVisible;
|
|
693
|
-
|
|
695
|
+
/** Seconds presenting, used to decide the room has genuinely nothing in it. */
|
|
696
|
+
private elapsed;
|
|
697
|
+
/** `initiateRoomCapture` may be called only ONCE per session. */
|
|
698
|
+
private roomCaptureRequested;
|
|
699
|
+
update(world: World, deltaTime?: number): void;
|
|
700
|
+
/**
|
|
701
|
+
* Ask the runtime to scan the room when it turns out we have no room.
|
|
702
|
+
*
|
|
703
|
+
* Plane and mesh detection do NOT scan anything — they replay a scan the user
|
|
704
|
+
* captured earlier through the system's own room setup. Most people have
|
|
705
|
+
* never run it, and those who have may have run it in a DIFFERENT room, so a
|
|
706
|
+
* mixed-reality experience opens against either nothing at all or someone
|
|
707
|
+
* else's furniture, with no prompt and no error.
|
|
708
|
+
*
|
|
709
|
+
* `XRSession.initiateRoomCapture()` (Meta's extension) launches that setup
|
|
710
|
+
* from inside the session, and exists precisely for this case. Contract, from
|
|
711
|
+
* Meta's docs: callable ONCE per session, and only worth calling once we are
|
|
712
|
+
* confident the room really is empty — the arrays populate a beat after the
|
|
713
|
+
* session starts, so a check on frame one would fire for everyone.
|
|
714
|
+
*/
|
|
715
|
+
private maybeCaptureRoom;
|
|
694
716
|
/** How many pieces of real geometry are currently colliding. Script-facing. */
|
|
695
717
|
getDetectedCount(): {
|
|
696
718
|
planes: number;
|
package/dist/index.cjs
CHANGED
|
@@ -12305,6 +12305,8 @@ var _XRSessionSystem = class _XRSessionSystem {
|
|
|
12305
12305
|
this.capabilities = NO_XR_CAPABILITIES;
|
|
12306
12306
|
/** Y compensation applied when a floor-relative space was requested but not granted. */
|
|
12307
12307
|
this.appliedOriginOffset = 0;
|
|
12308
|
+
/** The live session, so an async floor probe can tell if it has been replaced. */
|
|
12309
|
+
this.session = null;
|
|
12308
12310
|
this.inputSystem = inputSystem ?? null;
|
|
12309
12311
|
}
|
|
12310
12312
|
update(world) {
|
|
@@ -12323,6 +12325,7 @@ var _XRSessionSystem = class _XRSessionSystem {
|
|
|
12323
12325
|
}
|
|
12324
12326
|
handleSessionStart(world) {
|
|
12325
12327
|
const session = world.getRenderer().xr.getSession();
|
|
12328
|
+
this.session = session ?? null;
|
|
12326
12329
|
const mode = session && session.environmentBlendMode !== "opaque" ? "immersive-ar" : "immersive-vr";
|
|
12327
12330
|
this.capabilities = readXrCapabilities(session ?? null, mode);
|
|
12328
12331
|
if (session && this.inputSystem) {
|
|
@@ -12361,16 +12364,16 @@ var _XRSessionSystem = class _XRSessionSystem {
|
|
|
12361
12364
|
renderer.xr.setReferenceSpaceType(wantsFloor ? "local-floor" : "local");
|
|
12362
12365
|
} catch {
|
|
12363
12366
|
}
|
|
12364
|
-
|
|
12365
|
-
|
|
12366
|
-
|
|
12367
|
-
|
|
12368
|
-
|
|
12369
|
-
|
|
12370
|
-
|
|
12371
|
-
|
|
12367
|
+
this.appliedOriginOffset = 0;
|
|
12368
|
+
if (!wantsFloor || !session) return;
|
|
12369
|
+
const sessionAtRequest = session;
|
|
12370
|
+
session.requestReferenceSpace("local-floor").then(() => {
|
|
12371
|
+
}).catch(() => {
|
|
12372
|
+
if (this.session !== sessionAtRequest) return;
|
|
12373
|
+
if (this.appliedOriginOffset !== 0) return;
|
|
12374
|
+
this.appliedOriginOffset = rig.fallbackEyeHeight;
|
|
12372
12375
|
transform.position.y -= this.appliedOriginOffset;
|
|
12373
|
-
}
|
|
12376
|
+
});
|
|
12374
12377
|
}
|
|
12375
12378
|
handleSessionEnd(world) {
|
|
12376
12379
|
if (this.appliedOriginOffset !== 0) {
|
|
@@ -12381,6 +12384,7 @@ var _XRSessionSystem = class _XRSessionSystem {
|
|
|
12381
12384
|
}
|
|
12382
12385
|
this.appliedOriginOffset = 0;
|
|
12383
12386
|
}
|
|
12387
|
+
this.session = null;
|
|
12384
12388
|
this.capabilities = NO_XR_CAPABILITIES;
|
|
12385
12389
|
this.inputSystem?.detachSession();
|
|
12386
12390
|
this.forEachBehaviour(world, (behaviour) => {
|
|
@@ -12497,6 +12501,7 @@ __name(decomposeXrPose, "decomposeXrPose");
|
|
|
12497
12501
|
|
|
12498
12502
|
// core/systems/XREnvironmentSystem.ts
|
|
12499
12503
|
var DEBUG_COLOR = 5088255;
|
|
12504
|
+
var ROOM_CAPTURE_DELAY_SECONDS = 3;
|
|
12500
12505
|
var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
12501
12506
|
constructor() {
|
|
12502
12507
|
/** Keyed by the XRPlane/XRMesh object itself — the runtime's own identity. */
|
|
@@ -12504,17 +12509,24 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
12504
12509
|
this.meshes = /* @__PURE__ */ new Map();
|
|
12505
12510
|
this.presenting = false;
|
|
12506
12511
|
this.debugVisible = false;
|
|
12512
|
+
/** Seconds presenting, used to decide the room has genuinely nothing in it. */
|
|
12513
|
+
this.elapsed = 0;
|
|
12514
|
+
/** `initiateRoomCapture` may be called only ONCE per session. */
|
|
12515
|
+
this.roomCaptureRequested = false;
|
|
12507
12516
|
}
|
|
12508
|
-
update(world) {
|
|
12517
|
+
update(world, deltaTime = 0) {
|
|
12509
12518
|
const presenting = world.isXrPresenting();
|
|
12510
12519
|
if (!presenting) {
|
|
12511
12520
|
if (this.presenting) {
|
|
12512
12521
|
this.presenting = false;
|
|
12522
|
+
this.elapsed = 0;
|
|
12523
|
+
this.roomCaptureRequested = false;
|
|
12513
12524
|
this.clearAll(world);
|
|
12514
12525
|
}
|
|
12515
12526
|
return;
|
|
12516
12527
|
}
|
|
12517
12528
|
this.presenting = true;
|
|
12529
|
+
this.elapsed += deltaTime;
|
|
12518
12530
|
const rig = this.findRig(world);
|
|
12519
12531
|
const mode = rig?.environmentPhysics ?? "none";
|
|
12520
12532
|
if (mode === "none") {
|
|
@@ -12543,6 +12555,34 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
12543
12555
|
} else if (this.meshes.size > 0) {
|
|
12544
12556
|
this.clearMap(world, this.meshes);
|
|
12545
12557
|
}
|
|
12558
|
+
this.maybeCaptureRoom(world, frame);
|
|
12559
|
+
}
|
|
12560
|
+
/**
|
|
12561
|
+
* Ask the runtime to scan the room when it turns out we have no room.
|
|
12562
|
+
*
|
|
12563
|
+
* Plane and mesh detection do NOT scan anything — they replay a scan the user
|
|
12564
|
+
* captured earlier through the system's own room setup. Most people have
|
|
12565
|
+
* never run it, and those who have may have run it in a DIFFERENT room, so a
|
|
12566
|
+
* mixed-reality experience opens against either nothing at all or someone
|
|
12567
|
+
* else's furniture, with no prompt and no error.
|
|
12568
|
+
*
|
|
12569
|
+
* `XRSession.initiateRoomCapture()` (Meta's extension) launches that setup
|
|
12570
|
+
* from inside the session, and exists precisely for this case. Contract, from
|
|
12571
|
+
* Meta's docs: callable ONCE per session, and only worth calling once we are
|
|
12572
|
+
* confident the room really is empty — the arrays populate a beat after the
|
|
12573
|
+
* session starts, so a check on frame one would fire for everyone.
|
|
12574
|
+
*/
|
|
12575
|
+
maybeCaptureRoom(world, frame) {
|
|
12576
|
+
if (this.roomCaptureRequested) return;
|
|
12577
|
+
if (this.elapsed < ROOM_CAPTURE_DELAY_SECONDS) return;
|
|
12578
|
+
if (this.planes.size > 0 || this.meshes.size > 0) return;
|
|
12579
|
+
if ((frame.detectedPlanes?.size ?? 0) > 0) return;
|
|
12580
|
+
if ((frame.detectedMeshes?.size ?? 0) > 0) return;
|
|
12581
|
+
const session = world.getRenderer().xr.getSession?.();
|
|
12582
|
+
if (typeof session?.initiateRoomCapture !== "function") return;
|
|
12583
|
+
this.roomCaptureRequested = true;
|
|
12584
|
+
session.initiateRoomCapture()?.catch(() => {
|
|
12585
|
+
});
|
|
12546
12586
|
}
|
|
12547
12587
|
/** How many pieces of real geometry are currently colliding. Script-facing. */
|
|
12548
12588
|
getDetectedCount() {
|