@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/systems/index.cjs
CHANGED
|
@@ -2154,11 +2154,39 @@ var _XRRigComponent = class _XRRigComponent extends Component {
|
|
|
2154
2154
|
this.teleportArcColor = options?.teleportArcColor ?? "#4da3ff";
|
|
2155
2155
|
this.teleportArcInvalidColor = options?.teleportArcInvalidColor ?? "#ff5a5a";
|
|
2156
2156
|
this.teleportArcThickness = options?.teleportArcThickness ?? 0.012;
|
|
2157
|
+
this.passthrough = options?.passthrough ?? false;
|
|
2158
|
+
this.depthOcclusion = options?.depthOcclusion ?? false;
|
|
2159
|
+
this.environmentPhysics = options?.environmentPhysics ?? "none";
|
|
2160
|
+
this.environmentDebug = options?.environmentDebug ?? false;
|
|
2161
|
+
this.hitTest = options?.hitTest ?? false;
|
|
2162
|
+
this.anchors = options?.anchors ?? false;
|
|
2157
2163
|
}
|
|
2158
2164
|
};
|
|
2159
2165
|
__name(_XRRigComponent, "XRRigComponent");
|
|
2160
2166
|
var XRRigComponent = _XRRigComponent;
|
|
2161
2167
|
|
|
2168
|
+
// core/components/XREnvironment.ts
|
|
2169
|
+
var _XREnvironmentComponent = class _XREnvironmentComponent extends Component {
|
|
2170
|
+
constructor(kind, semanticLabel = null, orientation = null) {
|
|
2171
|
+
super();
|
|
2172
|
+
this.kind = kind;
|
|
2173
|
+
this.semanticLabel = semanticLabel;
|
|
2174
|
+
this.orientation = orientation;
|
|
2175
|
+
}
|
|
2176
|
+
};
|
|
2177
|
+
__name(_XREnvironmentComponent, "XREnvironmentComponent");
|
|
2178
|
+
var XREnvironmentComponent = _XREnvironmentComponent;
|
|
2179
|
+
|
|
2180
|
+
// core/components/XRAnchor.ts
|
|
2181
|
+
var _XRAnchorComponent = class _XRAnchorComponent extends Component {
|
|
2182
|
+
constructor(options) {
|
|
2183
|
+
super();
|
|
2184
|
+
this.enabled = options?.enabled ?? true;
|
|
2185
|
+
}
|
|
2186
|
+
};
|
|
2187
|
+
__name(_XRAnchorComponent, "XRAnchorComponent");
|
|
2188
|
+
var XRAnchorComponent = _XRAnchorComponent;
|
|
2189
|
+
|
|
2162
2190
|
// core/components/XRInteractor.ts
|
|
2163
2191
|
var _XRInteractorComponent = class _XRInteractorComponent extends Component {
|
|
2164
2192
|
constructor(options) {
|
|
@@ -3045,6 +3073,8 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
|
|
|
3045
3073
|
this.signature = null;
|
|
3046
3074
|
this.activeTexture = null;
|
|
3047
3075
|
this.pendingImageUrl = null;
|
|
3076
|
+
/** Whether the skybox is currently withheld because passthrough is on. */
|
|
3077
|
+
this.passthroughSuppressed = false;
|
|
3048
3078
|
this.scene = scene;
|
|
3049
3079
|
}
|
|
3050
3080
|
disposeActiveTexture() {
|
|
@@ -3129,6 +3159,18 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
|
|
|
3129
3159
|
this.scene.fog = new THREE26__namespace.FogExp2(fogColor, density);
|
|
3130
3160
|
}
|
|
3131
3161
|
update(world) {
|
|
3162
|
+
const passthrough = world.isXrPassthrough();
|
|
3163
|
+
if (passthrough !== this.passthroughSuppressed) {
|
|
3164
|
+
this.passthroughSuppressed = passthrough;
|
|
3165
|
+
if (passthrough) {
|
|
3166
|
+
this.scene.background = null;
|
|
3167
|
+
} else {
|
|
3168
|
+
this.signature = null;
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
if (passthrough) {
|
|
3172
|
+
return;
|
|
3173
|
+
}
|
|
3132
3174
|
const entityIds = world.query(Environment);
|
|
3133
3175
|
if (entityIds.length === 0) {
|
|
3134
3176
|
if (this.signature !== null) {
|
|
@@ -5398,6 +5440,24 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
|
|
|
5398
5440
|
if (typeof sourceXrRig.fallbackEyeHeight === "number") {
|
|
5399
5441
|
rig.fallbackEyeHeight = sourceXrRig.fallbackEyeHeight;
|
|
5400
5442
|
}
|
|
5443
|
+
if (typeof sourceXrRig.passthrough === "boolean") {
|
|
5444
|
+
rig.passthrough = sourceXrRig.passthrough;
|
|
5445
|
+
}
|
|
5446
|
+
if (typeof sourceXrRig.depthOcclusion === "boolean") {
|
|
5447
|
+
rig.depthOcclusion = sourceXrRig.depthOcclusion;
|
|
5448
|
+
}
|
|
5449
|
+
if (typeof sourceXrRig.environmentPhysics === "string") {
|
|
5450
|
+
rig.environmentPhysics = sourceXrRig.environmentPhysics;
|
|
5451
|
+
}
|
|
5452
|
+
if (typeof sourceXrRig.environmentDebug === "boolean") {
|
|
5453
|
+
rig.environmentDebug = sourceXrRig.environmentDebug;
|
|
5454
|
+
}
|
|
5455
|
+
if (typeof sourceXrRig.hitTest === "boolean") {
|
|
5456
|
+
rig.hitTest = sourceXrRig.hitTest;
|
|
5457
|
+
}
|
|
5458
|
+
if (typeof sourceXrRig.anchors === "boolean") {
|
|
5459
|
+
rig.anchors = sourceXrRig.anchors;
|
|
5460
|
+
}
|
|
5401
5461
|
}
|
|
5402
5462
|
}
|
|
5403
5463
|
{
|
|
@@ -9650,6 +9710,509 @@ var _XRSessionSystem = class _XRSessionSystem {
|
|
|
9650
9710
|
};
|
|
9651
9711
|
__name(_XRSessionSystem, "XRSessionSystem");
|
|
9652
9712
|
var XRSessionSystem = _XRSessionSystem;
|
|
9713
|
+
var PLANE_COLLIDER_THICKNESS = 0.02;
|
|
9714
|
+
var MIN_PLANE_EXTENT = 0.01;
|
|
9715
|
+
function planeColliderFromPolygon(polygon) {
|
|
9716
|
+
if (!polygon || polygon.length < 3) return null;
|
|
9717
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
9718
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
9719
|
+
let minZ = Number.POSITIVE_INFINITY;
|
|
9720
|
+
let maxZ = Number.NEGATIVE_INFINITY;
|
|
9721
|
+
for (const point of polygon) {
|
|
9722
|
+
if (!Number.isFinite(point.x) || !Number.isFinite(point.z)) return null;
|
|
9723
|
+
minX = Math.min(minX, point.x);
|
|
9724
|
+
maxX = Math.max(maxX, point.x);
|
|
9725
|
+
minZ = Math.min(minZ, point.z);
|
|
9726
|
+
maxZ = Math.max(maxZ, point.z);
|
|
9727
|
+
}
|
|
9728
|
+
const width = maxX - minX;
|
|
9729
|
+
const depth = maxZ - minZ;
|
|
9730
|
+
if (width < MIN_PLANE_EXTENT || depth < MIN_PLANE_EXTENT) return null;
|
|
9731
|
+
return {
|
|
9732
|
+
// Thin on Y: that is the plane's normal in plane space.
|
|
9733
|
+
size: { x: width, y: PLANE_COLLIDER_THICKNESS, z: depth },
|
|
9734
|
+
// The polygon is not necessarily centred on its own origin, so the box has
|
|
9735
|
+
// to be offset to sit over it. Pushed DOWN half its thickness so the
|
|
9736
|
+
// collider's top face lines up with the real surface — otherwise objects
|
|
9737
|
+
// rest a centimetre above the floor, or sink a centimetre into it.
|
|
9738
|
+
offset: {
|
|
9739
|
+
x: (minX + maxX) / 2,
|
|
9740
|
+
y: -PLANE_COLLIDER_THICKNESS / 2,
|
|
9741
|
+
z: (minZ + maxZ) / 2
|
|
9742
|
+
}
|
|
9743
|
+
};
|
|
9744
|
+
}
|
|
9745
|
+
__name(planeColliderFromPolygon, "planeColliderFromPolygon");
|
|
9746
|
+
function writeXrMeshGeometry(vertices, indices, existing) {
|
|
9747
|
+
const geometry = existing ?? new THREE26__namespace.BufferGeometry();
|
|
9748
|
+
geometry.setAttribute("position", new THREE26__namespace.BufferAttribute(vertices, 3));
|
|
9749
|
+
geometry.setIndex(new THREE26__namespace.BufferAttribute(indices, 1));
|
|
9750
|
+
geometry.computeBoundingSphere();
|
|
9751
|
+
geometry.computeVertexNormals();
|
|
9752
|
+
return geometry;
|
|
9753
|
+
}
|
|
9754
|
+
__name(writeXrMeshGeometry, "writeXrMeshGeometry");
|
|
9755
|
+
function decomposeXrPose(matrixElements, target) {
|
|
9756
|
+
const matrix = new THREE26__namespace.Matrix4().fromArray(
|
|
9757
|
+
matrixElements
|
|
9758
|
+
);
|
|
9759
|
+
const position = new THREE26__namespace.Vector3();
|
|
9760
|
+
const quaternion = new THREE26__namespace.Quaternion();
|
|
9761
|
+
const scale = new THREE26__namespace.Vector3();
|
|
9762
|
+
matrix.decompose(position, quaternion, scale);
|
|
9763
|
+
const euler = new THREE26__namespace.Euler().setFromQuaternion(quaternion, "XYZ");
|
|
9764
|
+
target.position.x = position.x;
|
|
9765
|
+
target.position.y = position.y;
|
|
9766
|
+
target.position.z = position.z;
|
|
9767
|
+
target.rotation.x = euler.x;
|
|
9768
|
+
target.rotation.y = euler.y;
|
|
9769
|
+
target.rotation.z = euler.z;
|
|
9770
|
+
}
|
|
9771
|
+
__name(decomposeXrPose, "decomposeXrPose");
|
|
9772
|
+
|
|
9773
|
+
// core/systems/XREnvironmentSystem.ts
|
|
9774
|
+
var DEBUG_COLOR = 5088255;
|
|
9775
|
+
var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
9776
|
+
constructor() {
|
|
9777
|
+
/** Keyed by the XRPlane/XRMesh object itself — the runtime's own identity. */
|
|
9778
|
+
this.planes = /* @__PURE__ */ new Map();
|
|
9779
|
+
this.meshes = /* @__PURE__ */ new Map();
|
|
9780
|
+
this.presenting = false;
|
|
9781
|
+
this.debugVisible = false;
|
|
9782
|
+
}
|
|
9783
|
+
update(world) {
|
|
9784
|
+
const presenting = world.isXrPresenting();
|
|
9785
|
+
if (!presenting) {
|
|
9786
|
+
if (this.presenting) {
|
|
9787
|
+
this.presenting = false;
|
|
9788
|
+
this.clearAll(world);
|
|
9789
|
+
}
|
|
9790
|
+
return;
|
|
9791
|
+
}
|
|
9792
|
+
this.presenting = true;
|
|
9793
|
+
const rig = this.findRig(world);
|
|
9794
|
+
const mode = rig?.environmentPhysics ?? "none";
|
|
9795
|
+
if (mode === "none") {
|
|
9796
|
+
if (this.planes.size > 0 || this.meshes.size > 0) this.clearAll(world);
|
|
9797
|
+
return;
|
|
9798
|
+
}
|
|
9799
|
+
const debug = rig?.environmentDebug === true;
|
|
9800
|
+
if (debug !== this.debugVisible) {
|
|
9801
|
+
this.debugVisible = debug;
|
|
9802
|
+
this.applyDebugVisibility(world);
|
|
9803
|
+
}
|
|
9804
|
+
const xr = world.getRenderer().xr;
|
|
9805
|
+
const frame = xr.getFrame?.();
|
|
9806
|
+
const referenceSpace = xr.getReferenceSpace();
|
|
9807
|
+
if (!frame || !referenceSpace) return;
|
|
9808
|
+
const meshesAvailable = Boolean(frame.detectedMeshes);
|
|
9809
|
+
const useMesh = (mode === "mesh" || mode === "auto") && meshesAvailable;
|
|
9810
|
+
const usePlanes = mode === "planes" || mode === "auto" && !useMesh;
|
|
9811
|
+
if (usePlanes) {
|
|
9812
|
+
this.syncPlanes(world, frame, referenceSpace);
|
|
9813
|
+
} else if (this.planes.size > 0) {
|
|
9814
|
+
this.clearMap(world, this.planes);
|
|
9815
|
+
}
|
|
9816
|
+
if (useMesh) {
|
|
9817
|
+
this.syncMeshes(world, frame, referenceSpace);
|
|
9818
|
+
} else if (this.meshes.size > 0) {
|
|
9819
|
+
this.clearMap(world, this.meshes);
|
|
9820
|
+
}
|
|
9821
|
+
}
|
|
9822
|
+
/** How many pieces of real geometry are currently colliding. Script-facing. */
|
|
9823
|
+
getDetectedCount() {
|
|
9824
|
+
return { planes: this.planes.size, meshes: this.meshes.size };
|
|
9825
|
+
}
|
|
9826
|
+
findRig(world) {
|
|
9827
|
+
const ids = world.query(XRRigComponent);
|
|
9828
|
+
if (ids.length === 0) return null;
|
|
9829
|
+
return world.getComponent(ids[0], XRRigComponent) ?? null;
|
|
9830
|
+
}
|
|
9831
|
+
syncPlanes(world, frame, referenceSpace) {
|
|
9832
|
+
const detected = frame.detectedPlanes;
|
|
9833
|
+
if (!detected) return;
|
|
9834
|
+
for (const [plane, entry] of this.planes) {
|
|
9835
|
+
if (!detected.has(plane)) {
|
|
9836
|
+
this.destroyEntry(world, entry);
|
|
9837
|
+
this.planes.delete(plane);
|
|
9838
|
+
}
|
|
9839
|
+
}
|
|
9840
|
+
for (const plane of detected) {
|
|
9841
|
+
const pose = frame.getPose(plane.planeSpace, referenceSpace);
|
|
9842
|
+
if (!pose) continue;
|
|
9843
|
+
let entry = this.planes.get(plane);
|
|
9844
|
+
const changed = plane.lastChangedTime ?? 0;
|
|
9845
|
+
if (!entry) {
|
|
9846
|
+
const created = this.createPlaneEntity(world, plane);
|
|
9847
|
+
if (!created) continue;
|
|
9848
|
+
entry = created;
|
|
9849
|
+
this.planes.set(plane, entry);
|
|
9850
|
+
} else if (changed !== entry.lastChangedTime) {
|
|
9851
|
+
entry.lastChangedTime = changed;
|
|
9852
|
+
this.applyPlaneCollider(world, entry.entityId, plane);
|
|
9853
|
+
}
|
|
9854
|
+
this.applyPose(world, entry.entityId, pose);
|
|
9855
|
+
}
|
|
9856
|
+
}
|
|
9857
|
+
syncMeshes(world, frame, referenceSpace) {
|
|
9858
|
+
const detected = frame.detectedMeshes;
|
|
9859
|
+
if (!detected) return;
|
|
9860
|
+
for (const [mesh, entry] of this.meshes) {
|
|
9861
|
+
if (!detected.has(mesh)) {
|
|
9862
|
+
this.destroyEntry(world, entry);
|
|
9863
|
+
this.meshes.delete(mesh);
|
|
9864
|
+
}
|
|
9865
|
+
}
|
|
9866
|
+
for (const mesh of detected) {
|
|
9867
|
+
const pose = frame.getPose(mesh.meshSpace, referenceSpace);
|
|
9868
|
+
if (!pose) continue;
|
|
9869
|
+
let entry = this.meshes.get(mesh);
|
|
9870
|
+
const changed = mesh.lastChangedTime ?? 0;
|
|
9871
|
+
if (!entry) {
|
|
9872
|
+
const created = this.createMeshEntity(world, mesh);
|
|
9873
|
+
if (!created) continue;
|
|
9874
|
+
entry = created;
|
|
9875
|
+
this.meshes.set(mesh, entry);
|
|
9876
|
+
} else if (changed !== entry.lastChangedTime) {
|
|
9877
|
+
entry.lastChangedTime = changed;
|
|
9878
|
+
writeXrMeshGeometry(mesh.vertices, mesh.indices, entry.geometry);
|
|
9879
|
+
}
|
|
9880
|
+
this.applyPose(world, entry.entityId, pose);
|
|
9881
|
+
}
|
|
9882
|
+
}
|
|
9883
|
+
createPlaneEntity(world, plane) {
|
|
9884
|
+
const collider = planeColliderFromPolygon(plane.polygon);
|
|
9885
|
+
if (!collider) return null;
|
|
9886
|
+
const entityId = world.createEntity();
|
|
9887
|
+
world.addComponents(entityId, [
|
|
9888
|
+
new TransformComponent(),
|
|
9889
|
+
new XREnvironmentComponent(
|
|
9890
|
+
"plane",
|
|
9891
|
+
plane.semanticLabel ?? null,
|
|
9892
|
+
normalizeOrientation(plane.orientation)
|
|
9893
|
+
)
|
|
9894
|
+
]);
|
|
9895
|
+
this.applyPlaneCollider(world, entityId, plane);
|
|
9896
|
+
const entry = {
|
|
9897
|
+
entityId,
|
|
9898
|
+
lastChangedTime: plane.lastChangedTime ?? 0,
|
|
9899
|
+
geometry: null,
|
|
9900
|
+
material: null
|
|
9901
|
+
};
|
|
9902
|
+
if (this.debugVisible) this.attachPlaneDebug(world, entry, collider);
|
|
9903
|
+
return entry;
|
|
9904
|
+
}
|
|
9905
|
+
applyPlaneCollider(world, entityId, plane) {
|
|
9906
|
+
const collider = planeColliderFromPolygon(plane.polygon);
|
|
9907
|
+
if (!collider) return;
|
|
9908
|
+
const existing = world.getComponent(entityId, PhysicsComponent);
|
|
9909
|
+
const values = {
|
|
9910
|
+
// Fixed: the real room does not move because something hit it.
|
|
9911
|
+
rigidbody: { type: "fixed" },
|
|
9912
|
+
collider: {
|
|
9913
|
+
shape: "cuboid",
|
|
9914
|
+
size: collider.size,
|
|
9915
|
+
offsetPosition: collider.offset
|
|
9916
|
+
}
|
|
9917
|
+
};
|
|
9918
|
+
if (existing) {
|
|
9919
|
+
existing.setValues(values);
|
|
9920
|
+
} else {
|
|
9921
|
+
world.addComponent(entityId, new PhysicsComponent(values));
|
|
9922
|
+
}
|
|
9923
|
+
}
|
|
9924
|
+
createMeshEntity(world, mesh) {
|
|
9925
|
+
if (!mesh.vertices?.length || !mesh.indices?.length) return null;
|
|
9926
|
+
const geometry = writeXrMeshGeometry(mesh.vertices, mesh.indices);
|
|
9927
|
+
const material = new THREE26__namespace.MeshBasicMaterial({
|
|
9928
|
+
color: DEBUG_COLOR,
|
|
9929
|
+
wireframe: true
|
|
9930
|
+
});
|
|
9931
|
+
const root = new THREE26__namespace.Group();
|
|
9932
|
+
const child = new THREE26__namespace.Mesh(geometry, material);
|
|
9933
|
+
child.visible = this.debugVisible;
|
|
9934
|
+
root.add(child);
|
|
9935
|
+
const entityId = world.createEntity();
|
|
9936
|
+
world.addComponents(entityId, [
|
|
9937
|
+
new TransformComponent(),
|
|
9938
|
+
new Object3DRef(root),
|
|
9939
|
+
new XREnvironmentComponent("mesh", mesh.semanticLabel ?? null, null),
|
|
9940
|
+
new PhysicsComponent({
|
|
9941
|
+
rigidbody: { type: "fixed" },
|
|
9942
|
+
collider: { shape: "trimesh" }
|
|
9943
|
+
})
|
|
9944
|
+
]);
|
|
9945
|
+
return {
|
|
9946
|
+
entityId,
|
|
9947
|
+
lastChangedTime: mesh.lastChangedTime ?? 0,
|
|
9948
|
+
geometry,
|
|
9949
|
+
material
|
|
9950
|
+
};
|
|
9951
|
+
}
|
|
9952
|
+
/** A thin box so a detected plane can be SEEN, not just collided with. */
|
|
9953
|
+
attachPlaneDebug(world, entry, collider) {
|
|
9954
|
+
const geometry = new THREE26__namespace.BoxGeometry(
|
|
9955
|
+
collider.size.x,
|
|
9956
|
+
collider.size.y,
|
|
9957
|
+
collider.size.z
|
|
9958
|
+
);
|
|
9959
|
+
const material = new THREE26__namespace.MeshBasicMaterial({
|
|
9960
|
+
color: DEBUG_COLOR,
|
|
9961
|
+
wireframe: true
|
|
9962
|
+
});
|
|
9963
|
+
const root = new THREE26__namespace.Group();
|
|
9964
|
+
root.add(new THREE26__namespace.Mesh(geometry, material));
|
|
9965
|
+
entry.geometry = geometry;
|
|
9966
|
+
entry.material = material;
|
|
9967
|
+
world.addComponent(entry.entityId, new Object3DRef(root));
|
|
9968
|
+
}
|
|
9969
|
+
applyPose(world, entityId, pose) {
|
|
9970
|
+
const transform = world.getComponent(entityId, TransformComponent);
|
|
9971
|
+
if (!transform) return;
|
|
9972
|
+
decomposeXrPose(pose.transform.matrix, transform);
|
|
9973
|
+
}
|
|
9974
|
+
applyDebugVisibility(world) {
|
|
9975
|
+
for (const map of [this.planes, this.meshes]) {
|
|
9976
|
+
for (const entry of map.values()) {
|
|
9977
|
+
const ref = world.getComponent(entry.entityId, Object3DRef);
|
|
9978
|
+
ref?.object.traverse((child) => {
|
|
9979
|
+
if (child.isMesh) child.visible = this.debugVisible;
|
|
9980
|
+
});
|
|
9981
|
+
}
|
|
9982
|
+
}
|
|
9983
|
+
}
|
|
9984
|
+
destroyEntry(world, entry) {
|
|
9985
|
+
entry.geometry?.dispose();
|
|
9986
|
+
entry.material?.dispose();
|
|
9987
|
+
world.destroyEntity(entry.entityId);
|
|
9988
|
+
}
|
|
9989
|
+
clearMap(world, map) {
|
|
9990
|
+
for (const entry of map.values()) this.destroyEntry(world, entry);
|
|
9991
|
+
map.clear();
|
|
9992
|
+
}
|
|
9993
|
+
clearAll(world) {
|
|
9994
|
+
this.clearMap(world, this.planes);
|
|
9995
|
+
this.clearMap(world, this.meshes);
|
|
9996
|
+
}
|
|
9997
|
+
dispose(world) {
|
|
9998
|
+
this.clearAll(world);
|
|
9999
|
+
}
|
|
10000
|
+
};
|
|
10001
|
+
__name(_XREnvironmentSystem, "XREnvironmentSystem");
|
|
10002
|
+
var XREnvironmentSystem = _XREnvironmentSystem;
|
|
10003
|
+
function normalizeOrientation(value) {
|
|
10004
|
+
return value === "horizontal" || value === "vertical" ? value : null;
|
|
10005
|
+
}
|
|
10006
|
+
__name(normalizeOrientation, "normalizeOrientation");
|
|
10007
|
+
var _XRHitTestSystem = class _XRHitTestSystem {
|
|
10008
|
+
constructor() {
|
|
10009
|
+
this.sources = /* @__PURE__ */ new Map();
|
|
10010
|
+
/** Targets whose async request is in flight, so it is not fired twice. */
|
|
10011
|
+
this.pending = /* @__PURE__ */ new Set();
|
|
10012
|
+
this.results = /* @__PURE__ */ new Map();
|
|
10013
|
+
this.presenting = false;
|
|
10014
|
+
this.enabled = false;
|
|
10015
|
+
}
|
|
10016
|
+
update(world) {
|
|
10017
|
+
const presenting = world.isXrPresenting();
|
|
10018
|
+
if (!presenting) {
|
|
10019
|
+
if (this.presenting) {
|
|
10020
|
+
this.presenting = false;
|
|
10021
|
+
this.teardown();
|
|
10022
|
+
}
|
|
10023
|
+
return;
|
|
10024
|
+
}
|
|
10025
|
+
if (!this.presenting) {
|
|
10026
|
+
this.presenting = true;
|
|
10027
|
+
this.enabled = this.rigWantsHitTest(world);
|
|
10028
|
+
}
|
|
10029
|
+
if (!this.enabled) return;
|
|
10030
|
+
const xr = world.getRenderer().xr;
|
|
10031
|
+
const session = xr.getSession();
|
|
10032
|
+
const referenceSpace = xr.getReferenceSpace();
|
|
10033
|
+
const frame = xr.getFrame?.();
|
|
10034
|
+
if (!session || !referenceSpace || !frame) return;
|
|
10035
|
+
this.ensureViewerSource(session);
|
|
10036
|
+
this.ensureInputSources(session);
|
|
10037
|
+
this.results.clear();
|
|
10038
|
+
for (const [key, source] of this.sources) {
|
|
10039
|
+
const hits = frame.getHitTestResults(source);
|
|
10040
|
+
if (!hits || hits.length === 0) continue;
|
|
10041
|
+
const pose = hits[0].getPose(referenceSpace);
|
|
10042
|
+
if (!pose) continue;
|
|
10043
|
+
const matrix = new THREE26__namespace.Matrix4().fromArray(
|
|
10044
|
+
pose.transform.matrix
|
|
10045
|
+
);
|
|
10046
|
+
const position = new THREE26__namespace.Vector3();
|
|
10047
|
+
const quaternion = new THREE26__namespace.Quaternion();
|
|
10048
|
+
matrix.decompose(position, quaternion, new THREE26__namespace.Vector3());
|
|
10049
|
+
this.results.set(key, { position, quaternion });
|
|
10050
|
+
}
|
|
10051
|
+
}
|
|
10052
|
+
/**
|
|
10053
|
+
* Where the given ray meets the real world this frame, or null.
|
|
10054
|
+
*
|
|
10055
|
+
* Null is the normal case, not an error: the source may still be being
|
|
10056
|
+
* requested, the ray may point at nothing recognised, or the runtime may not
|
|
10057
|
+
* have granted `hit-test` at all.
|
|
10058
|
+
*/
|
|
10059
|
+
getHitResult(target = "viewer") {
|
|
10060
|
+
return this.results.get(target) ?? null;
|
|
10061
|
+
}
|
|
10062
|
+
rigWantsHitTest(world) {
|
|
10063
|
+
const ids = world.query(XRRigComponent);
|
|
10064
|
+
if (ids.length === 0) return false;
|
|
10065
|
+
return world.getComponent(ids[0], XRRigComponent)?.hitTest === true;
|
|
10066
|
+
}
|
|
10067
|
+
ensureViewerSource(session) {
|
|
10068
|
+
if (this.sources.has("viewer") || this.pending.has("viewer")) return;
|
|
10069
|
+
if (typeof session.requestHitTestSource !== "function") return;
|
|
10070
|
+
this.pending.add("viewer");
|
|
10071
|
+
void session.requestReferenceSpace("viewer").then((space) => session.requestHitTestSource?.({ space })).then((source) => {
|
|
10072
|
+
if (source && this.presenting) this.sources.set("viewer", source);
|
|
10073
|
+
else source?.cancel?.();
|
|
10074
|
+
}).catch(() => {
|
|
10075
|
+
}).finally(() => this.pending.delete("viewer"));
|
|
10076
|
+
}
|
|
10077
|
+
ensureInputSources(session) {
|
|
10078
|
+
if (typeof session.requestHitTestSource !== "function") return;
|
|
10079
|
+
if (!session.inputSources) return;
|
|
10080
|
+
for (const input of session.inputSources) {
|
|
10081
|
+
const handedness = input.handedness;
|
|
10082
|
+
if (!handedness || handedness === "none") continue;
|
|
10083
|
+
if (!input.targetRaySpace) continue;
|
|
10084
|
+
if (this.sources.has(handedness) || this.pending.has(handedness)) continue;
|
|
10085
|
+
this.pending.add(handedness);
|
|
10086
|
+
const request = session.requestHitTestSource({
|
|
10087
|
+
space: input.targetRaySpace
|
|
10088
|
+
});
|
|
10089
|
+
if (!request) {
|
|
10090
|
+
this.pending.delete(handedness);
|
|
10091
|
+
continue;
|
|
10092
|
+
}
|
|
10093
|
+
void request.then((source) => {
|
|
10094
|
+
if (source && this.presenting) this.sources.set(handedness, source);
|
|
10095
|
+
else source?.cancel?.();
|
|
10096
|
+
}).catch(() => {
|
|
10097
|
+
}).finally(() => this.pending.delete(handedness));
|
|
10098
|
+
}
|
|
10099
|
+
}
|
|
10100
|
+
teardown() {
|
|
10101
|
+
for (const source of this.sources.values()) source.cancel?.();
|
|
10102
|
+
this.sources.clear();
|
|
10103
|
+
this.results.clear();
|
|
10104
|
+
this.pending.clear();
|
|
10105
|
+
this.enabled = false;
|
|
10106
|
+
}
|
|
10107
|
+
dispose() {
|
|
10108
|
+
this.presenting = false;
|
|
10109
|
+
this.teardown();
|
|
10110
|
+
}
|
|
10111
|
+
};
|
|
10112
|
+
__name(_XRHitTestSystem, "XRHitTestSystem");
|
|
10113
|
+
var XRHitTestSystem = _XRHitTestSystem;
|
|
10114
|
+
var _XRAnchorSystem = class _XRAnchorSystem {
|
|
10115
|
+
constructor() {
|
|
10116
|
+
this.anchors = /* @__PURE__ */ new Map();
|
|
10117
|
+
/** In-flight creations, so one entity is not anchored twice. */
|
|
10118
|
+
this.pending = /* @__PURE__ */ new Set();
|
|
10119
|
+
this.presenting = false;
|
|
10120
|
+
}
|
|
10121
|
+
update(world) {
|
|
10122
|
+
const presenting = world.isXrPresenting();
|
|
10123
|
+
if (!presenting) {
|
|
10124
|
+
if (this.presenting) {
|
|
10125
|
+
this.presenting = false;
|
|
10126
|
+
this.releaseAll();
|
|
10127
|
+
}
|
|
10128
|
+
return;
|
|
10129
|
+
}
|
|
10130
|
+
this.presenting = true;
|
|
10131
|
+
const xr = world.getRenderer().xr;
|
|
10132
|
+
const referenceSpace = xr.getReferenceSpace();
|
|
10133
|
+
const frame = xr.getFrame?.();
|
|
10134
|
+
if (!frame || !referenceSpace) return;
|
|
10135
|
+
const entityIds = world.query(XRAnchorComponent, TransformComponent);
|
|
10136
|
+
const live = new Set(entityIds);
|
|
10137
|
+
for (const [entityId, anchor] of this.anchors) {
|
|
10138
|
+
if (!live.has(entityId)) {
|
|
10139
|
+
anchor.delete?.();
|
|
10140
|
+
this.anchors.delete(entityId);
|
|
10141
|
+
}
|
|
10142
|
+
}
|
|
10143
|
+
for (const entityId of entityIds) {
|
|
10144
|
+
const component = world.getComponent(entityId, XRAnchorComponent);
|
|
10145
|
+
const transform = world.getComponent(entityId, TransformComponent);
|
|
10146
|
+
if (!component || !transform) continue;
|
|
10147
|
+
if (!component.enabled) {
|
|
10148
|
+
const existing = this.anchors.get(entityId);
|
|
10149
|
+
if (existing) {
|
|
10150
|
+
existing.delete?.();
|
|
10151
|
+
this.anchors.delete(entityId);
|
|
10152
|
+
}
|
|
10153
|
+
continue;
|
|
10154
|
+
}
|
|
10155
|
+
const anchor = this.anchors.get(entityId);
|
|
10156
|
+
if (!anchor) {
|
|
10157
|
+
this.requestAnchor(entityId, transform, frame, referenceSpace);
|
|
10158
|
+
continue;
|
|
10159
|
+
}
|
|
10160
|
+
const pose = frame.getPose(anchor.anchorSpace, referenceSpace);
|
|
10161
|
+
if (pose) decomposeXrPose(pose.transform.matrix, transform);
|
|
10162
|
+
}
|
|
10163
|
+
}
|
|
10164
|
+
/** Whether this entity's pose is currently being driven by an anchor. */
|
|
10165
|
+
isAnchored(entityId) {
|
|
10166
|
+
return this.anchors.has(entityId);
|
|
10167
|
+
}
|
|
10168
|
+
requestAnchor(entityId, transform, frame, referenceSpace) {
|
|
10169
|
+
if (this.pending.has(entityId)) return;
|
|
10170
|
+
if (typeof frame.createAnchor !== "function") return;
|
|
10171
|
+
const RigidTransform = globalThis.XRRigidTransform;
|
|
10172
|
+
if (!RigidTransform) return;
|
|
10173
|
+
const quaternion = new THREE26__namespace.Quaternion().setFromEuler(
|
|
10174
|
+
new THREE26__namespace.Euler(
|
|
10175
|
+
transform.rotation.x,
|
|
10176
|
+
transform.rotation.y,
|
|
10177
|
+
transform.rotation.z,
|
|
10178
|
+
"XYZ"
|
|
10179
|
+
)
|
|
10180
|
+
);
|
|
10181
|
+
const pose = new RigidTransform(
|
|
10182
|
+
{
|
|
10183
|
+
x: transform.position.x,
|
|
10184
|
+
y: transform.position.y,
|
|
10185
|
+
z: transform.position.z,
|
|
10186
|
+
w: 1
|
|
10187
|
+
},
|
|
10188
|
+
{
|
|
10189
|
+
x: quaternion.x,
|
|
10190
|
+
y: quaternion.y,
|
|
10191
|
+
z: quaternion.z,
|
|
10192
|
+
w: quaternion.w
|
|
10193
|
+
}
|
|
10194
|
+
);
|
|
10195
|
+
const request = frame.createAnchor(pose, referenceSpace);
|
|
10196
|
+
if (!request) return;
|
|
10197
|
+
this.pending.add(entityId);
|
|
10198
|
+
void request.then((anchor) => {
|
|
10199
|
+
if (anchor && this.presenting) this.anchors.set(entityId, anchor);
|
|
10200
|
+
else anchor?.delete?.();
|
|
10201
|
+
}).catch(() => {
|
|
10202
|
+
}).finally(() => this.pending.delete(entityId));
|
|
10203
|
+
}
|
|
10204
|
+
releaseAll() {
|
|
10205
|
+
for (const anchor of this.anchors.values()) anchor.delete?.();
|
|
10206
|
+
this.anchors.clear();
|
|
10207
|
+
this.pending.clear();
|
|
10208
|
+
}
|
|
10209
|
+
dispose() {
|
|
10210
|
+
this.presenting = false;
|
|
10211
|
+
this.releaseAll();
|
|
10212
|
+
}
|
|
10213
|
+
};
|
|
10214
|
+
__name(_XRAnchorSystem, "XRAnchorSystem");
|
|
10215
|
+
var XRAnchorSystem = _XRAnchorSystem;
|
|
9653
10216
|
function buildGeometry2(area) {
|
|
9654
10217
|
if (area.shape === "circle") {
|
|
9655
10218
|
const radius = Math.max(5e-3, area.width / 2);
|
|
@@ -10905,8 +11468,11 @@ exports.ScriptObjectSystem = ScriptObjectSystem;
|
|
|
10905
11468
|
exports.TransformSyncSystem = TransformSyncSystem;
|
|
10906
11469
|
exports.VRTeleportAreaSystem = VRTeleportAreaSystem;
|
|
10907
11470
|
exports.VideoSystem = VideoSystem;
|
|
11471
|
+
exports.XRAnchorSystem = XRAnchorSystem;
|
|
10908
11472
|
exports.XRControllerVisualSystem = XRControllerVisualSystem;
|
|
11473
|
+
exports.XREnvironmentSystem = XREnvironmentSystem;
|
|
10909
11474
|
exports.XRHandPoseSystem = XRHandPoseSystem;
|
|
11475
|
+
exports.XRHitTestSystem = XRHitTestSystem;
|
|
10910
11476
|
exports.XRHoverVisualSystem = XRHoverVisualSystem;
|
|
10911
11477
|
exports.XRInputSystem = XRInputSystem;
|
|
10912
11478
|
exports.XRInteractionSystem = XRInteractionSystem;
|