@relu-interactives/spatial-ecs 0.3.20 → 0.4.0
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/{System-BhnLw0fZ.d.cts → System-QjjSc6LZ.d.cts} +14 -0
- package/dist/{System-BhnLw0fZ.d.ts → System-QjjSc6LZ.d.ts} +14 -0
- package/dist/{XRHandPose-CLl4MiHx.d.cts → XRHandPose-Capx-RTD.d.cts} +47 -2
- package/dist/{XRHandPose-CrzI57JA.d.ts → XRHandPose-Csd3QGkj.d.ts} +47 -2
- package/dist/{chunk-CDSE7476.js → chunk-24ZMSBR7.js} +540 -5
- package/dist/chunk-24ZMSBR7.js.map +1 -0
- package/dist/{chunk-D6KXWNYT.js → chunk-2GTFFLGG.js} +3 -3
- package/dist/{chunk-D6KXWNYT.js.map → chunk-2GTFFLGG.js.map} +1 -1
- package/dist/{chunk-P42OE3RU.js → chunk-OWQPGEBB.js} +4 -4
- package/dist/{chunk-P42OE3RU.js.map → chunk-OWQPGEBB.js.map} +1 -1
- package/dist/{chunk-H6DQ7JRE.js → chunk-ZWONFM2F.js} +33 -3
- package/dist/chunk-ZWONFM2F.js.map +1 -0
- package/dist/components/index.cjs +30 -0
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.d.cts +3 -3
- package/dist/components/index.d.ts +3 -3
- package/dist/components/index.js +1 -1
- package/dist/docs/guide/accessing-components.md +7 -6
- package/dist/docs/guide/behaviour-data-binding/component-input.md +10 -6
- package/dist/docs/guide/behaviour-data-binding/entity-input.md +13 -5
- package/dist/docs/guide/camera-rigs.md +2 -2
- package/dist/docs/guide/components/animation.md +12 -5
- package/dist/docs/guide/components/audio.md +13 -6
- package/dist/docs/guide/components/camera.md +10 -4
- package/dist/docs/guide/components/entity-type.md +13 -6
- package/dist/docs/guide/components/image-target-anchor.md +6 -2
- package/dist/docs/guide/components/image-target.md +10 -5
- package/dist/docs/guide/components/image.md +10 -4
- package/dist/docs/guide/components/instance-slot.md +7 -3
- package/dist/docs/guide/components/instanced-mesh.md +15 -7
- package/dist/docs/guide/components/light.md +10 -4
- package/dist/docs/guide/components/material.md +14 -8
- package/dist/docs/guide/components/mesh-geometry.md +10 -4
- package/dist/docs/guide/components/mesh.md +10 -4
- package/dist/docs/guide/components/model.md +4 -2
- package/dist/docs/guide/components/name.md +11 -6
- package/dist/docs/guide/components/object3d-ref.md +7 -3
- package/dist/docs/guide/components/parent-id.md +10 -4
- package/dist/docs/guide/components/physics.md +12 -6
- package/dist/docs/guide/components/script.md +10 -4
- package/dist/docs/guide/components/sprite.md +10 -4
- package/dist/docs/guide/components/transform.md +6 -2
- package/dist/docs/guide/components/video.md +10 -4
- package/dist/docs/guide/prefabs.md +2 -0
- package/dist/docs/guide/runtime-entity-management.md +1 -1
- package/dist/docs/guide/spawning/instanced-mesh.md +11 -6
- package/dist/docs/guide/systems/input-system.md +4 -1
- package/dist/docs/guide/systems/script-object-system.md +9 -1
- package/dist/docs/guide/vr-and-hand-poses.md +7 -3
- package/dist/docs/guide/world-api.md +23 -15
- package/dist/factories/index.cjs +6 -0
- package/dist/factories/index.cjs.map +1 -1
- package/dist/factories/index.d.cts +2 -2
- package/dist/factories/index.d.ts +2 -2
- package/dist/factories/index.js +3 -3
- package/dist/{index-6l4hn2Sh.d.ts → index-BAeeVJ6f.d.ts} +94 -2
- package/dist/{index-g777AmFv.d.cts → index-BRATaqTT.d.ts} +78 -4
- package/dist/{index-JRcoel9F.d.ts → index-C1rprGz_.d.cts} +78 -4
- package/dist/{index-C2I5PxCI.d.cts → index-SC9Mgjht.d.cts} +94 -2
- package/dist/index.cjs +586 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +26 -8
- package/dist/index.js.map +1 -1
- package/dist/systems/index.cjs +566 -0
- package/dist/systems/index.cjs.map +1 -1
- package/dist/systems/index.d.cts +2 -2
- package/dist/systems/index.d.ts +2 -2
- package/dist/systems/index.js +3 -3
- package/dist/types.d.cts +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/worldSnapshotUtils.d.cts +1 -1
- package/dist/worldSnapshotUtils.d.ts +1 -1
- package/package.json +4 -2
- package/dist/chunk-CDSE7476.js.map +0 -1
- package/dist/chunk-H6DQ7JRE.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -2579,6 +2579,12 @@ var _XRRigComponent = class _XRRigComponent extends Component {
|
|
|
2579
2579
|
this.teleportArcColor = options?.teleportArcColor ?? "#4da3ff";
|
|
2580
2580
|
this.teleportArcInvalidColor = options?.teleportArcInvalidColor ?? "#ff5a5a";
|
|
2581
2581
|
this.teleportArcThickness = options?.teleportArcThickness ?? 0.012;
|
|
2582
|
+
this.passthrough = options?.passthrough ?? false;
|
|
2583
|
+
this.depthOcclusion = options?.depthOcclusion ?? false;
|
|
2584
|
+
this.environmentPhysics = options?.environmentPhysics ?? "none";
|
|
2585
|
+
this.environmentDebug = options?.environmentDebug ?? false;
|
|
2586
|
+
this.hitTest = options?.hitTest ?? false;
|
|
2587
|
+
this.anchors = options?.anchors ?? false;
|
|
2582
2588
|
}
|
|
2583
2589
|
};
|
|
2584
2590
|
__name(_XRRigComponent, "XRRigComponent");
|
|
@@ -4019,6 +4025,8 @@ __export(components_exports, {
|
|
|
4019
4025
|
TransformComponent: () => TransformComponent,
|
|
4020
4026
|
VRTeleportAreaComponent: () => VRTeleportAreaComponent,
|
|
4021
4027
|
VideoComponent: () => VideoComponent,
|
|
4028
|
+
XRAnchorComponent: () => XRAnchorComponent,
|
|
4029
|
+
XREnvironmentComponent: () => XREnvironmentComponent,
|
|
4022
4030
|
XRHandPoseComponent: () => XRHandPoseComponent,
|
|
4023
4031
|
XRInteractableComponent: () => XRInteractableComponent,
|
|
4024
4032
|
XRInteractorComponent: () => XRInteractorComponent,
|
|
@@ -4061,6 +4069,28 @@ var _PrefabNode = class _PrefabNode extends Component {
|
|
|
4061
4069
|
__name(_PrefabNode, "PrefabNode");
|
|
4062
4070
|
var PrefabNode = _PrefabNode;
|
|
4063
4071
|
|
|
4072
|
+
// core/components/XREnvironment.ts
|
|
4073
|
+
var _XREnvironmentComponent = class _XREnvironmentComponent extends Component {
|
|
4074
|
+
constructor(kind, semanticLabel = null, orientation = null) {
|
|
4075
|
+
super();
|
|
4076
|
+
this.kind = kind;
|
|
4077
|
+
this.semanticLabel = semanticLabel;
|
|
4078
|
+
this.orientation = orientation;
|
|
4079
|
+
}
|
|
4080
|
+
};
|
|
4081
|
+
__name(_XREnvironmentComponent, "XREnvironmentComponent");
|
|
4082
|
+
var XREnvironmentComponent = _XREnvironmentComponent;
|
|
4083
|
+
|
|
4084
|
+
// core/components/XRAnchor.ts
|
|
4085
|
+
var _XRAnchorComponent = class _XRAnchorComponent extends Component {
|
|
4086
|
+
constructor(options) {
|
|
4087
|
+
super();
|
|
4088
|
+
this.enabled = options?.enabled ?? true;
|
|
4089
|
+
}
|
|
4090
|
+
};
|
|
4091
|
+
__name(_XRAnchorComponent, "XRAnchorComponent");
|
|
4092
|
+
var XRAnchorComponent = _XRAnchorComponent;
|
|
4093
|
+
|
|
4064
4094
|
// core/factories/objects/ModelFactory.ts
|
|
4065
4095
|
function getModelFileName(path) {
|
|
4066
4096
|
const segments = path.split(/[\\/]/).filter(Boolean);
|
|
@@ -4876,6 +4906,24 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
|
|
|
4876
4906
|
if (typeof sourceXrRig.fallbackEyeHeight === "number") {
|
|
4877
4907
|
rig.fallbackEyeHeight = sourceXrRig.fallbackEyeHeight;
|
|
4878
4908
|
}
|
|
4909
|
+
if (typeof sourceXrRig.passthrough === "boolean") {
|
|
4910
|
+
rig.passthrough = sourceXrRig.passthrough;
|
|
4911
|
+
}
|
|
4912
|
+
if (typeof sourceXrRig.depthOcclusion === "boolean") {
|
|
4913
|
+
rig.depthOcclusion = sourceXrRig.depthOcclusion;
|
|
4914
|
+
}
|
|
4915
|
+
if (typeof sourceXrRig.environmentPhysics === "string") {
|
|
4916
|
+
rig.environmentPhysics = sourceXrRig.environmentPhysics;
|
|
4917
|
+
}
|
|
4918
|
+
if (typeof sourceXrRig.environmentDebug === "boolean") {
|
|
4919
|
+
rig.environmentDebug = sourceXrRig.environmentDebug;
|
|
4920
|
+
}
|
|
4921
|
+
if (typeof sourceXrRig.hitTest === "boolean") {
|
|
4922
|
+
rig.hitTest = sourceXrRig.hitTest;
|
|
4923
|
+
}
|
|
4924
|
+
if (typeof sourceXrRig.anchors === "boolean") {
|
|
4925
|
+
rig.anchors = sourceXrRig.anchors;
|
|
4926
|
+
}
|
|
4879
4927
|
}
|
|
4880
4928
|
}
|
|
4881
4929
|
{
|
|
@@ -7243,6 +7291,8 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
|
|
|
7243
7291
|
this.signature = null;
|
|
7244
7292
|
this.activeTexture = null;
|
|
7245
7293
|
this.pendingImageUrl = null;
|
|
7294
|
+
/** Whether the skybox is currently withheld because passthrough is on. */
|
|
7295
|
+
this.passthroughSuppressed = false;
|
|
7246
7296
|
this.scene = scene;
|
|
7247
7297
|
}
|
|
7248
7298
|
disposeActiveTexture() {
|
|
@@ -7327,6 +7377,18 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
|
|
|
7327
7377
|
this.scene.fog = new THREE51__namespace.FogExp2(fogColor, density);
|
|
7328
7378
|
}
|
|
7329
7379
|
update(world) {
|
|
7380
|
+
const passthrough = world.isXrPassthrough();
|
|
7381
|
+
if (passthrough !== this.passthroughSuppressed) {
|
|
7382
|
+
this.passthroughSuppressed = passthrough;
|
|
7383
|
+
if (passthrough) {
|
|
7384
|
+
this.scene.background = null;
|
|
7385
|
+
} else {
|
|
7386
|
+
this.signature = null;
|
|
7387
|
+
}
|
|
7388
|
+
}
|
|
7389
|
+
if (passthrough) {
|
|
7390
|
+
return;
|
|
7391
|
+
}
|
|
7330
7392
|
const entityIds = world.query(Environment);
|
|
7331
7393
|
if (entityIds.length === 0) {
|
|
7332
7394
|
if (this.signature !== null) {
|
|
@@ -12350,6 +12412,509 @@ var _XRSessionSystem = class _XRSessionSystem {
|
|
|
12350
12412
|
};
|
|
12351
12413
|
__name(_XRSessionSystem, "XRSessionSystem");
|
|
12352
12414
|
var XRSessionSystem = _XRSessionSystem;
|
|
12415
|
+
var PLANE_COLLIDER_THICKNESS = 0.02;
|
|
12416
|
+
var MIN_PLANE_EXTENT = 0.01;
|
|
12417
|
+
function planeColliderFromPolygon(polygon) {
|
|
12418
|
+
if (!polygon || polygon.length < 3) return null;
|
|
12419
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
12420
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
12421
|
+
let minZ = Number.POSITIVE_INFINITY;
|
|
12422
|
+
let maxZ = Number.NEGATIVE_INFINITY;
|
|
12423
|
+
for (const point of polygon) {
|
|
12424
|
+
if (!Number.isFinite(point.x) || !Number.isFinite(point.z)) return null;
|
|
12425
|
+
minX = Math.min(minX, point.x);
|
|
12426
|
+
maxX = Math.max(maxX, point.x);
|
|
12427
|
+
minZ = Math.min(minZ, point.z);
|
|
12428
|
+
maxZ = Math.max(maxZ, point.z);
|
|
12429
|
+
}
|
|
12430
|
+
const width = maxX - minX;
|
|
12431
|
+
const depth = maxZ - minZ;
|
|
12432
|
+
if (width < MIN_PLANE_EXTENT || depth < MIN_PLANE_EXTENT) return null;
|
|
12433
|
+
return {
|
|
12434
|
+
// Thin on Y: that is the plane's normal in plane space.
|
|
12435
|
+
size: { x: width, y: PLANE_COLLIDER_THICKNESS, z: depth },
|
|
12436
|
+
// The polygon is not necessarily centred on its own origin, so the box has
|
|
12437
|
+
// to be offset to sit over it. Pushed DOWN half its thickness so the
|
|
12438
|
+
// collider's top face lines up with the real surface — otherwise objects
|
|
12439
|
+
// rest a centimetre above the floor, or sink a centimetre into it.
|
|
12440
|
+
offset: {
|
|
12441
|
+
x: (minX + maxX) / 2,
|
|
12442
|
+
y: -PLANE_COLLIDER_THICKNESS / 2,
|
|
12443
|
+
z: (minZ + maxZ) / 2
|
|
12444
|
+
}
|
|
12445
|
+
};
|
|
12446
|
+
}
|
|
12447
|
+
__name(planeColliderFromPolygon, "planeColliderFromPolygon");
|
|
12448
|
+
function writeXrMeshGeometry(vertices, indices, existing) {
|
|
12449
|
+
const geometry = existing ?? new THREE51__namespace.BufferGeometry();
|
|
12450
|
+
geometry.setAttribute("position", new THREE51__namespace.BufferAttribute(vertices, 3));
|
|
12451
|
+
geometry.setIndex(new THREE51__namespace.BufferAttribute(indices, 1));
|
|
12452
|
+
geometry.computeBoundingSphere();
|
|
12453
|
+
geometry.computeVertexNormals();
|
|
12454
|
+
return geometry;
|
|
12455
|
+
}
|
|
12456
|
+
__name(writeXrMeshGeometry, "writeXrMeshGeometry");
|
|
12457
|
+
function decomposeXrPose(matrixElements, target) {
|
|
12458
|
+
const matrix = new THREE51__namespace.Matrix4().fromArray(
|
|
12459
|
+
matrixElements
|
|
12460
|
+
);
|
|
12461
|
+
const position = new THREE51__namespace.Vector3();
|
|
12462
|
+
const quaternion = new THREE51__namespace.Quaternion();
|
|
12463
|
+
const scale = new THREE51__namespace.Vector3();
|
|
12464
|
+
matrix.decompose(position, quaternion, scale);
|
|
12465
|
+
const euler = new THREE51__namespace.Euler().setFromQuaternion(quaternion, "XYZ");
|
|
12466
|
+
target.position.x = position.x;
|
|
12467
|
+
target.position.y = position.y;
|
|
12468
|
+
target.position.z = position.z;
|
|
12469
|
+
target.rotation.x = euler.x;
|
|
12470
|
+
target.rotation.y = euler.y;
|
|
12471
|
+
target.rotation.z = euler.z;
|
|
12472
|
+
}
|
|
12473
|
+
__name(decomposeXrPose, "decomposeXrPose");
|
|
12474
|
+
|
|
12475
|
+
// core/systems/XREnvironmentSystem.ts
|
|
12476
|
+
var DEBUG_COLOR = 5088255;
|
|
12477
|
+
var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
12478
|
+
constructor() {
|
|
12479
|
+
/** Keyed by the XRPlane/XRMesh object itself — the runtime's own identity. */
|
|
12480
|
+
this.planes = /* @__PURE__ */ new Map();
|
|
12481
|
+
this.meshes = /* @__PURE__ */ new Map();
|
|
12482
|
+
this.presenting = false;
|
|
12483
|
+
this.debugVisible = false;
|
|
12484
|
+
}
|
|
12485
|
+
update(world) {
|
|
12486
|
+
const presenting = world.isXrPresenting();
|
|
12487
|
+
if (!presenting) {
|
|
12488
|
+
if (this.presenting) {
|
|
12489
|
+
this.presenting = false;
|
|
12490
|
+
this.clearAll(world);
|
|
12491
|
+
}
|
|
12492
|
+
return;
|
|
12493
|
+
}
|
|
12494
|
+
this.presenting = true;
|
|
12495
|
+
const rig = this.findRig(world);
|
|
12496
|
+
const mode = rig?.environmentPhysics ?? "none";
|
|
12497
|
+
if (mode === "none") {
|
|
12498
|
+
if (this.planes.size > 0 || this.meshes.size > 0) this.clearAll(world);
|
|
12499
|
+
return;
|
|
12500
|
+
}
|
|
12501
|
+
const debug = rig?.environmentDebug === true;
|
|
12502
|
+
if (debug !== this.debugVisible) {
|
|
12503
|
+
this.debugVisible = debug;
|
|
12504
|
+
this.applyDebugVisibility(world);
|
|
12505
|
+
}
|
|
12506
|
+
const xr = world.getRenderer().xr;
|
|
12507
|
+
const frame = xr.getFrame?.();
|
|
12508
|
+
const referenceSpace = xr.getReferenceSpace();
|
|
12509
|
+
if (!frame || !referenceSpace) return;
|
|
12510
|
+
const meshesAvailable = Boolean(frame.detectedMeshes);
|
|
12511
|
+
const useMesh = (mode === "mesh" || mode === "auto") && meshesAvailable;
|
|
12512
|
+
const usePlanes = mode === "planes" || mode === "auto" && !useMesh;
|
|
12513
|
+
if (usePlanes) {
|
|
12514
|
+
this.syncPlanes(world, frame, referenceSpace);
|
|
12515
|
+
} else if (this.planes.size > 0) {
|
|
12516
|
+
this.clearMap(world, this.planes);
|
|
12517
|
+
}
|
|
12518
|
+
if (useMesh) {
|
|
12519
|
+
this.syncMeshes(world, frame, referenceSpace);
|
|
12520
|
+
} else if (this.meshes.size > 0) {
|
|
12521
|
+
this.clearMap(world, this.meshes);
|
|
12522
|
+
}
|
|
12523
|
+
}
|
|
12524
|
+
/** How many pieces of real geometry are currently colliding. Script-facing. */
|
|
12525
|
+
getDetectedCount() {
|
|
12526
|
+
return { planes: this.planes.size, meshes: this.meshes.size };
|
|
12527
|
+
}
|
|
12528
|
+
findRig(world) {
|
|
12529
|
+
const ids = world.query(XRRigComponent);
|
|
12530
|
+
if (ids.length === 0) return null;
|
|
12531
|
+
return world.getComponent(ids[0], XRRigComponent) ?? null;
|
|
12532
|
+
}
|
|
12533
|
+
syncPlanes(world, frame, referenceSpace) {
|
|
12534
|
+
const detected = frame.detectedPlanes;
|
|
12535
|
+
if (!detected) return;
|
|
12536
|
+
for (const [plane, entry] of this.planes) {
|
|
12537
|
+
if (!detected.has(plane)) {
|
|
12538
|
+
this.destroyEntry(world, entry);
|
|
12539
|
+
this.planes.delete(plane);
|
|
12540
|
+
}
|
|
12541
|
+
}
|
|
12542
|
+
for (const plane of detected) {
|
|
12543
|
+
const pose = frame.getPose(plane.planeSpace, referenceSpace);
|
|
12544
|
+
if (!pose) continue;
|
|
12545
|
+
let entry = this.planes.get(plane);
|
|
12546
|
+
const changed = plane.lastChangedTime ?? 0;
|
|
12547
|
+
if (!entry) {
|
|
12548
|
+
const created = this.createPlaneEntity(world, plane);
|
|
12549
|
+
if (!created) continue;
|
|
12550
|
+
entry = created;
|
|
12551
|
+
this.planes.set(plane, entry);
|
|
12552
|
+
} else if (changed !== entry.lastChangedTime) {
|
|
12553
|
+
entry.lastChangedTime = changed;
|
|
12554
|
+
this.applyPlaneCollider(world, entry.entityId, plane);
|
|
12555
|
+
}
|
|
12556
|
+
this.applyPose(world, entry.entityId, pose);
|
|
12557
|
+
}
|
|
12558
|
+
}
|
|
12559
|
+
syncMeshes(world, frame, referenceSpace) {
|
|
12560
|
+
const detected = frame.detectedMeshes;
|
|
12561
|
+
if (!detected) return;
|
|
12562
|
+
for (const [mesh, entry] of this.meshes) {
|
|
12563
|
+
if (!detected.has(mesh)) {
|
|
12564
|
+
this.destroyEntry(world, entry);
|
|
12565
|
+
this.meshes.delete(mesh);
|
|
12566
|
+
}
|
|
12567
|
+
}
|
|
12568
|
+
for (const mesh of detected) {
|
|
12569
|
+
const pose = frame.getPose(mesh.meshSpace, referenceSpace);
|
|
12570
|
+
if (!pose) continue;
|
|
12571
|
+
let entry = this.meshes.get(mesh);
|
|
12572
|
+
const changed = mesh.lastChangedTime ?? 0;
|
|
12573
|
+
if (!entry) {
|
|
12574
|
+
const created = this.createMeshEntity(world, mesh);
|
|
12575
|
+
if (!created) continue;
|
|
12576
|
+
entry = created;
|
|
12577
|
+
this.meshes.set(mesh, entry);
|
|
12578
|
+
} else if (changed !== entry.lastChangedTime) {
|
|
12579
|
+
entry.lastChangedTime = changed;
|
|
12580
|
+
writeXrMeshGeometry(mesh.vertices, mesh.indices, entry.geometry);
|
|
12581
|
+
}
|
|
12582
|
+
this.applyPose(world, entry.entityId, pose);
|
|
12583
|
+
}
|
|
12584
|
+
}
|
|
12585
|
+
createPlaneEntity(world, plane) {
|
|
12586
|
+
const collider = planeColliderFromPolygon(plane.polygon);
|
|
12587
|
+
if (!collider) return null;
|
|
12588
|
+
const entityId = world.createEntity();
|
|
12589
|
+
world.addComponents(entityId, [
|
|
12590
|
+
new TransformComponent(),
|
|
12591
|
+
new XREnvironmentComponent(
|
|
12592
|
+
"plane",
|
|
12593
|
+
plane.semanticLabel ?? null,
|
|
12594
|
+
normalizeOrientation(plane.orientation)
|
|
12595
|
+
)
|
|
12596
|
+
]);
|
|
12597
|
+
this.applyPlaneCollider(world, entityId, plane);
|
|
12598
|
+
const entry = {
|
|
12599
|
+
entityId,
|
|
12600
|
+
lastChangedTime: plane.lastChangedTime ?? 0,
|
|
12601
|
+
geometry: null,
|
|
12602
|
+
material: null
|
|
12603
|
+
};
|
|
12604
|
+
if (this.debugVisible) this.attachPlaneDebug(world, entry, collider);
|
|
12605
|
+
return entry;
|
|
12606
|
+
}
|
|
12607
|
+
applyPlaneCollider(world, entityId, plane) {
|
|
12608
|
+
const collider = planeColliderFromPolygon(plane.polygon);
|
|
12609
|
+
if (!collider) return;
|
|
12610
|
+
const existing = world.getComponent(entityId, PhysicsComponent);
|
|
12611
|
+
const values = {
|
|
12612
|
+
// Fixed: the real room does not move because something hit it.
|
|
12613
|
+
rigidbody: { type: "fixed" },
|
|
12614
|
+
collider: {
|
|
12615
|
+
shape: "cuboid",
|
|
12616
|
+
size: collider.size,
|
|
12617
|
+
offsetPosition: collider.offset
|
|
12618
|
+
}
|
|
12619
|
+
};
|
|
12620
|
+
if (existing) {
|
|
12621
|
+
existing.setValues(values);
|
|
12622
|
+
} else {
|
|
12623
|
+
world.addComponent(entityId, new PhysicsComponent(values));
|
|
12624
|
+
}
|
|
12625
|
+
}
|
|
12626
|
+
createMeshEntity(world, mesh) {
|
|
12627
|
+
if (!mesh.vertices?.length || !mesh.indices?.length) return null;
|
|
12628
|
+
const geometry = writeXrMeshGeometry(mesh.vertices, mesh.indices);
|
|
12629
|
+
const material = new THREE51__namespace.MeshBasicMaterial({
|
|
12630
|
+
color: DEBUG_COLOR,
|
|
12631
|
+
wireframe: true
|
|
12632
|
+
});
|
|
12633
|
+
const root = new THREE51__namespace.Group();
|
|
12634
|
+
const child = new THREE51__namespace.Mesh(geometry, material);
|
|
12635
|
+
child.visible = this.debugVisible;
|
|
12636
|
+
root.add(child);
|
|
12637
|
+
const entityId = world.createEntity();
|
|
12638
|
+
world.addComponents(entityId, [
|
|
12639
|
+
new TransformComponent(),
|
|
12640
|
+
new Object3DRef(root),
|
|
12641
|
+
new XREnvironmentComponent("mesh", mesh.semanticLabel ?? null, null),
|
|
12642
|
+
new PhysicsComponent({
|
|
12643
|
+
rigidbody: { type: "fixed" },
|
|
12644
|
+
collider: { shape: "trimesh" }
|
|
12645
|
+
})
|
|
12646
|
+
]);
|
|
12647
|
+
return {
|
|
12648
|
+
entityId,
|
|
12649
|
+
lastChangedTime: mesh.lastChangedTime ?? 0,
|
|
12650
|
+
geometry,
|
|
12651
|
+
material
|
|
12652
|
+
};
|
|
12653
|
+
}
|
|
12654
|
+
/** A thin box so a detected plane can be SEEN, not just collided with. */
|
|
12655
|
+
attachPlaneDebug(world, entry, collider) {
|
|
12656
|
+
const geometry = new THREE51__namespace.BoxGeometry(
|
|
12657
|
+
collider.size.x,
|
|
12658
|
+
collider.size.y,
|
|
12659
|
+
collider.size.z
|
|
12660
|
+
);
|
|
12661
|
+
const material = new THREE51__namespace.MeshBasicMaterial({
|
|
12662
|
+
color: DEBUG_COLOR,
|
|
12663
|
+
wireframe: true
|
|
12664
|
+
});
|
|
12665
|
+
const root = new THREE51__namespace.Group();
|
|
12666
|
+
root.add(new THREE51__namespace.Mesh(geometry, material));
|
|
12667
|
+
entry.geometry = geometry;
|
|
12668
|
+
entry.material = material;
|
|
12669
|
+
world.addComponent(entry.entityId, new Object3DRef(root));
|
|
12670
|
+
}
|
|
12671
|
+
applyPose(world, entityId, pose) {
|
|
12672
|
+
const transform = world.getComponent(entityId, TransformComponent);
|
|
12673
|
+
if (!transform) return;
|
|
12674
|
+
decomposeXrPose(pose.transform.matrix, transform);
|
|
12675
|
+
}
|
|
12676
|
+
applyDebugVisibility(world) {
|
|
12677
|
+
for (const map of [this.planes, this.meshes]) {
|
|
12678
|
+
for (const entry of map.values()) {
|
|
12679
|
+
const ref = world.getComponent(entry.entityId, Object3DRef);
|
|
12680
|
+
ref?.object.traverse((child) => {
|
|
12681
|
+
if (child.isMesh) child.visible = this.debugVisible;
|
|
12682
|
+
});
|
|
12683
|
+
}
|
|
12684
|
+
}
|
|
12685
|
+
}
|
|
12686
|
+
destroyEntry(world, entry) {
|
|
12687
|
+
entry.geometry?.dispose();
|
|
12688
|
+
entry.material?.dispose();
|
|
12689
|
+
world.destroyEntity(entry.entityId);
|
|
12690
|
+
}
|
|
12691
|
+
clearMap(world, map) {
|
|
12692
|
+
for (const entry of map.values()) this.destroyEntry(world, entry);
|
|
12693
|
+
map.clear();
|
|
12694
|
+
}
|
|
12695
|
+
clearAll(world) {
|
|
12696
|
+
this.clearMap(world, this.planes);
|
|
12697
|
+
this.clearMap(world, this.meshes);
|
|
12698
|
+
}
|
|
12699
|
+
dispose(world) {
|
|
12700
|
+
this.clearAll(world);
|
|
12701
|
+
}
|
|
12702
|
+
};
|
|
12703
|
+
__name(_XREnvironmentSystem, "XREnvironmentSystem");
|
|
12704
|
+
var XREnvironmentSystem = _XREnvironmentSystem;
|
|
12705
|
+
function normalizeOrientation(value) {
|
|
12706
|
+
return value === "horizontal" || value === "vertical" ? value : null;
|
|
12707
|
+
}
|
|
12708
|
+
__name(normalizeOrientation, "normalizeOrientation");
|
|
12709
|
+
var _XRHitTestSystem = class _XRHitTestSystem {
|
|
12710
|
+
constructor() {
|
|
12711
|
+
this.sources = /* @__PURE__ */ new Map();
|
|
12712
|
+
/** Targets whose async request is in flight, so it is not fired twice. */
|
|
12713
|
+
this.pending = /* @__PURE__ */ new Set();
|
|
12714
|
+
this.results = /* @__PURE__ */ new Map();
|
|
12715
|
+
this.presenting = false;
|
|
12716
|
+
this.enabled = false;
|
|
12717
|
+
}
|
|
12718
|
+
update(world) {
|
|
12719
|
+
const presenting = world.isXrPresenting();
|
|
12720
|
+
if (!presenting) {
|
|
12721
|
+
if (this.presenting) {
|
|
12722
|
+
this.presenting = false;
|
|
12723
|
+
this.teardown();
|
|
12724
|
+
}
|
|
12725
|
+
return;
|
|
12726
|
+
}
|
|
12727
|
+
if (!this.presenting) {
|
|
12728
|
+
this.presenting = true;
|
|
12729
|
+
this.enabled = this.rigWantsHitTest(world);
|
|
12730
|
+
}
|
|
12731
|
+
if (!this.enabled) return;
|
|
12732
|
+
const xr = world.getRenderer().xr;
|
|
12733
|
+
const session = xr.getSession();
|
|
12734
|
+
const referenceSpace = xr.getReferenceSpace();
|
|
12735
|
+
const frame = xr.getFrame?.();
|
|
12736
|
+
if (!session || !referenceSpace || !frame) return;
|
|
12737
|
+
this.ensureViewerSource(session);
|
|
12738
|
+
this.ensureInputSources(session);
|
|
12739
|
+
this.results.clear();
|
|
12740
|
+
for (const [key, source] of this.sources) {
|
|
12741
|
+
const hits = frame.getHitTestResults(source);
|
|
12742
|
+
if (!hits || hits.length === 0) continue;
|
|
12743
|
+
const pose = hits[0].getPose(referenceSpace);
|
|
12744
|
+
if (!pose) continue;
|
|
12745
|
+
const matrix = new THREE51__namespace.Matrix4().fromArray(
|
|
12746
|
+
pose.transform.matrix
|
|
12747
|
+
);
|
|
12748
|
+
const position = new THREE51__namespace.Vector3();
|
|
12749
|
+
const quaternion = new THREE51__namespace.Quaternion();
|
|
12750
|
+
matrix.decompose(position, quaternion, new THREE51__namespace.Vector3());
|
|
12751
|
+
this.results.set(key, { position, quaternion });
|
|
12752
|
+
}
|
|
12753
|
+
}
|
|
12754
|
+
/**
|
|
12755
|
+
* Where the given ray meets the real world this frame, or null.
|
|
12756
|
+
*
|
|
12757
|
+
* Null is the normal case, not an error: the source may still be being
|
|
12758
|
+
* requested, the ray may point at nothing recognised, or the runtime may not
|
|
12759
|
+
* have granted `hit-test` at all.
|
|
12760
|
+
*/
|
|
12761
|
+
getHitResult(target = "viewer") {
|
|
12762
|
+
return this.results.get(target) ?? null;
|
|
12763
|
+
}
|
|
12764
|
+
rigWantsHitTest(world) {
|
|
12765
|
+
const ids = world.query(XRRigComponent);
|
|
12766
|
+
if (ids.length === 0) return false;
|
|
12767
|
+
return world.getComponent(ids[0], XRRigComponent)?.hitTest === true;
|
|
12768
|
+
}
|
|
12769
|
+
ensureViewerSource(session) {
|
|
12770
|
+
if (this.sources.has("viewer") || this.pending.has("viewer")) return;
|
|
12771
|
+
if (typeof session.requestHitTestSource !== "function") return;
|
|
12772
|
+
this.pending.add("viewer");
|
|
12773
|
+
void session.requestReferenceSpace("viewer").then((space) => session.requestHitTestSource?.({ space })).then((source) => {
|
|
12774
|
+
if (source && this.presenting) this.sources.set("viewer", source);
|
|
12775
|
+
else source?.cancel?.();
|
|
12776
|
+
}).catch(() => {
|
|
12777
|
+
}).finally(() => this.pending.delete("viewer"));
|
|
12778
|
+
}
|
|
12779
|
+
ensureInputSources(session) {
|
|
12780
|
+
if (typeof session.requestHitTestSource !== "function") return;
|
|
12781
|
+
if (!session.inputSources) return;
|
|
12782
|
+
for (const input of session.inputSources) {
|
|
12783
|
+
const handedness = input.handedness;
|
|
12784
|
+
if (!handedness || handedness === "none") continue;
|
|
12785
|
+
if (!input.targetRaySpace) continue;
|
|
12786
|
+
if (this.sources.has(handedness) || this.pending.has(handedness)) continue;
|
|
12787
|
+
this.pending.add(handedness);
|
|
12788
|
+
const request = session.requestHitTestSource({
|
|
12789
|
+
space: input.targetRaySpace
|
|
12790
|
+
});
|
|
12791
|
+
if (!request) {
|
|
12792
|
+
this.pending.delete(handedness);
|
|
12793
|
+
continue;
|
|
12794
|
+
}
|
|
12795
|
+
void request.then((source) => {
|
|
12796
|
+
if (source && this.presenting) this.sources.set(handedness, source);
|
|
12797
|
+
else source?.cancel?.();
|
|
12798
|
+
}).catch(() => {
|
|
12799
|
+
}).finally(() => this.pending.delete(handedness));
|
|
12800
|
+
}
|
|
12801
|
+
}
|
|
12802
|
+
teardown() {
|
|
12803
|
+
for (const source of this.sources.values()) source.cancel?.();
|
|
12804
|
+
this.sources.clear();
|
|
12805
|
+
this.results.clear();
|
|
12806
|
+
this.pending.clear();
|
|
12807
|
+
this.enabled = false;
|
|
12808
|
+
}
|
|
12809
|
+
dispose() {
|
|
12810
|
+
this.presenting = false;
|
|
12811
|
+
this.teardown();
|
|
12812
|
+
}
|
|
12813
|
+
};
|
|
12814
|
+
__name(_XRHitTestSystem, "XRHitTestSystem");
|
|
12815
|
+
var XRHitTestSystem = _XRHitTestSystem;
|
|
12816
|
+
var _XRAnchorSystem = class _XRAnchorSystem {
|
|
12817
|
+
constructor() {
|
|
12818
|
+
this.anchors = /* @__PURE__ */ new Map();
|
|
12819
|
+
/** In-flight creations, so one entity is not anchored twice. */
|
|
12820
|
+
this.pending = /* @__PURE__ */ new Set();
|
|
12821
|
+
this.presenting = false;
|
|
12822
|
+
}
|
|
12823
|
+
update(world) {
|
|
12824
|
+
const presenting = world.isXrPresenting();
|
|
12825
|
+
if (!presenting) {
|
|
12826
|
+
if (this.presenting) {
|
|
12827
|
+
this.presenting = false;
|
|
12828
|
+
this.releaseAll();
|
|
12829
|
+
}
|
|
12830
|
+
return;
|
|
12831
|
+
}
|
|
12832
|
+
this.presenting = true;
|
|
12833
|
+
const xr = world.getRenderer().xr;
|
|
12834
|
+
const referenceSpace = xr.getReferenceSpace();
|
|
12835
|
+
const frame = xr.getFrame?.();
|
|
12836
|
+
if (!frame || !referenceSpace) return;
|
|
12837
|
+
const entityIds = world.query(XRAnchorComponent, TransformComponent);
|
|
12838
|
+
const live = new Set(entityIds);
|
|
12839
|
+
for (const [entityId, anchor] of this.anchors) {
|
|
12840
|
+
if (!live.has(entityId)) {
|
|
12841
|
+
anchor.delete?.();
|
|
12842
|
+
this.anchors.delete(entityId);
|
|
12843
|
+
}
|
|
12844
|
+
}
|
|
12845
|
+
for (const entityId of entityIds) {
|
|
12846
|
+
const component = world.getComponent(entityId, XRAnchorComponent);
|
|
12847
|
+
const transform = world.getComponent(entityId, TransformComponent);
|
|
12848
|
+
if (!component || !transform) continue;
|
|
12849
|
+
if (!component.enabled) {
|
|
12850
|
+
const existing = this.anchors.get(entityId);
|
|
12851
|
+
if (existing) {
|
|
12852
|
+
existing.delete?.();
|
|
12853
|
+
this.anchors.delete(entityId);
|
|
12854
|
+
}
|
|
12855
|
+
continue;
|
|
12856
|
+
}
|
|
12857
|
+
const anchor = this.anchors.get(entityId);
|
|
12858
|
+
if (!anchor) {
|
|
12859
|
+
this.requestAnchor(entityId, transform, frame, referenceSpace);
|
|
12860
|
+
continue;
|
|
12861
|
+
}
|
|
12862
|
+
const pose = frame.getPose(anchor.anchorSpace, referenceSpace);
|
|
12863
|
+
if (pose) decomposeXrPose(pose.transform.matrix, transform);
|
|
12864
|
+
}
|
|
12865
|
+
}
|
|
12866
|
+
/** Whether this entity's pose is currently being driven by an anchor. */
|
|
12867
|
+
isAnchored(entityId) {
|
|
12868
|
+
return this.anchors.has(entityId);
|
|
12869
|
+
}
|
|
12870
|
+
requestAnchor(entityId, transform, frame, referenceSpace) {
|
|
12871
|
+
if (this.pending.has(entityId)) return;
|
|
12872
|
+
if (typeof frame.createAnchor !== "function") return;
|
|
12873
|
+
const RigidTransform = globalThis.XRRigidTransform;
|
|
12874
|
+
if (!RigidTransform) return;
|
|
12875
|
+
const quaternion = new THREE51__namespace.Quaternion().setFromEuler(
|
|
12876
|
+
new THREE51__namespace.Euler(
|
|
12877
|
+
transform.rotation.x,
|
|
12878
|
+
transform.rotation.y,
|
|
12879
|
+
transform.rotation.z,
|
|
12880
|
+
"XYZ"
|
|
12881
|
+
)
|
|
12882
|
+
);
|
|
12883
|
+
const pose = new RigidTransform(
|
|
12884
|
+
{
|
|
12885
|
+
x: transform.position.x,
|
|
12886
|
+
y: transform.position.y,
|
|
12887
|
+
z: transform.position.z,
|
|
12888
|
+
w: 1
|
|
12889
|
+
},
|
|
12890
|
+
{
|
|
12891
|
+
x: quaternion.x,
|
|
12892
|
+
y: quaternion.y,
|
|
12893
|
+
z: quaternion.z,
|
|
12894
|
+
w: quaternion.w
|
|
12895
|
+
}
|
|
12896
|
+
);
|
|
12897
|
+
const request = frame.createAnchor(pose, referenceSpace);
|
|
12898
|
+
if (!request) return;
|
|
12899
|
+
this.pending.add(entityId);
|
|
12900
|
+
void request.then((anchor) => {
|
|
12901
|
+
if (anchor && this.presenting) this.anchors.set(entityId, anchor);
|
|
12902
|
+
else anchor?.delete?.();
|
|
12903
|
+
}).catch(() => {
|
|
12904
|
+
}).finally(() => this.pending.delete(entityId));
|
|
12905
|
+
}
|
|
12906
|
+
releaseAll() {
|
|
12907
|
+
for (const anchor of this.anchors.values()) anchor.delete?.();
|
|
12908
|
+
this.anchors.clear();
|
|
12909
|
+
this.pending.clear();
|
|
12910
|
+
}
|
|
12911
|
+
dispose() {
|
|
12912
|
+
this.presenting = false;
|
|
12913
|
+
this.releaseAll();
|
|
12914
|
+
}
|
|
12915
|
+
};
|
|
12916
|
+
__name(_XRAnchorSystem, "XRAnchorSystem");
|
|
12917
|
+
var XRAnchorSystem = _XRAnchorSystem;
|
|
12353
12918
|
function buildGeometry2(area) {
|
|
12354
12919
|
if (area.shape === "circle") {
|
|
12355
12920
|
const radius = Math.max(5e-3, area.width / 2);
|
|
@@ -14006,6 +14571,24 @@ var _World = class _World {
|
|
|
14006
14571
|
isXrPresenting() {
|
|
14007
14572
|
return this.renderer.xr?.isPresenting === true;
|
|
14008
14573
|
}
|
|
14574
|
+
/**
|
|
14575
|
+
* `true` while the session is compositing over the real world (passthrough).
|
|
14576
|
+
*
|
|
14577
|
+
* Read from the session's `environmentBlendMode` rather than from what the app
|
|
14578
|
+
* ASKED for: a request for `immersive-ar` can be granted as a plain opaque
|
|
14579
|
+
* session, and the blend mode is the only thing that says what the user is
|
|
14580
|
+
* actually looking at. `additive` (optical see-through) and `alpha-blend`
|
|
14581
|
+
* (video passthrough) both mean the real world is visible behind the render.
|
|
14582
|
+
*
|
|
14583
|
+
* The load-bearing consequence is that nothing may paint an opaque background:
|
|
14584
|
+
* a skybox drawn over the whole framebuffer hides the room completely, which
|
|
14585
|
+
* looks exactly like passthrough having failed.
|
|
14586
|
+
*/
|
|
14587
|
+
isXrPassthrough() {
|
|
14588
|
+
if (!this.isXrPresenting()) return false;
|
|
14589
|
+
const blendMode = this.renderer.xr?.getEnvironmentBlendMode?.();
|
|
14590
|
+
return blendMode === "additive" || blendMode === "alpha-blend";
|
|
14591
|
+
}
|
|
14009
14592
|
/**
|
|
14010
14593
|
* Pauses world simulation. While paused, `update()` skips all system ticks so
|
|
14011
14594
|
* physics, animation, scripts, and rendering stop advancing. The last rendered
|
|
@@ -15015,8 +15598,11 @@ exports.VELOCITY_SAMPLES = VELOCITY_SAMPLES;
|
|
|
15015
15598
|
exports.VRTeleportAreaSystem = VRTeleportAreaSystem;
|
|
15016
15599
|
exports.VideoSystem = VideoSystem;
|
|
15017
15600
|
exports.World = World;
|
|
15601
|
+
exports.XRAnchorSystem = XRAnchorSystem;
|
|
15018
15602
|
exports.XRControllerVisualSystem = XRControllerVisualSystem;
|
|
15603
|
+
exports.XREnvironmentSystem = XREnvironmentSystem;
|
|
15019
15604
|
exports.XRHandPoseSystem = XRHandPoseSystem;
|
|
15605
|
+
exports.XRHitTestSystem = XRHitTestSystem;
|
|
15020
15606
|
exports.XRHoverVisualSystem = XRHoverVisualSystem;
|
|
15021
15607
|
exports.XRInputSystem = XRInputSystem;
|
|
15022
15608
|
exports.XRInteractionSystem = XRInteractionSystem;
|