@relu-interactives/spatial-ecs 0.3.21 → 0.4.1
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-ln4Q7Eot.d.cts} +15 -1
- package/dist/{System-BhnLw0fZ.d.ts → System-ln4Q7Eot.d.ts} +15 -1
- package/dist/{XRHandPose-CrzI57JA.d.ts → XRHandPose-BImIa4ZS.d.ts} +82 -2
- package/dist/{XRHandPose-CLl4MiHx.d.cts → XRHandPose-CxPv-LBo.d.cts} +82 -2
- package/dist/{chunk-H6DQ7JRE.js → chunk-4AK4I3QF.js} +33 -3
- package/dist/chunk-4AK4I3QF.js.map +1 -0
- package/dist/{chunk-D6KXWNYT.js → chunk-4DWZK4X4.js} +3 -3
- package/dist/{chunk-D6KXWNYT.js.map → chunk-4DWZK4X4.js.map} +1 -1
- package/dist/{chunk-CDSE7476.js → chunk-LAOFCFEI.js} +554 -5
- package/dist/chunk-LAOFCFEI.js.map +1 -0
- package/dist/{chunk-P42OE3RU.js → chunk-SGADE55G.js} +14 -5
- package/dist/chunk-SGADE55G.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/mixed-reality.md +120 -0
- package/dist/factories/index.cjs +26 -0
- package/dist/factories/index.cjs.map +1 -1
- package/dist/factories/index.d.cts +5 -3
- package/dist/factories/index.d.ts +5 -3
- package/dist/factories/index.js +3 -3
- package/dist/{index-C2I5PxCI.d.cts → index-CBdnIyI8.d.cts} +94 -2
- package/dist/{index-g777AmFv.d.cts → index-CQsHUez3.d.cts} +42 -4
- package/dist/{index-JRcoel9F.d.ts → index-CuGqxuk-.d.ts} +42 -4
- package/dist/{index-6l4hn2Sh.d.ts → index-n7SeH119.d.ts} +94 -2
- package/dist/index.cjs +610 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -8
- package/dist/index.d.ts +9 -8
- package/dist/index.js +26 -8
- package/dist/index.js.map +1 -1
- package/dist/systems/index.cjs +580 -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 +1 -1
- package/dist/chunk-CDSE7476.js.map +0 -1
- package/dist/chunk-H6DQ7JRE.js.map +0 -1
- package/dist/chunk-P42OE3RU.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");
|
|
@@ -2855,6 +2861,24 @@ function createXRHandPoseComponent(world, entityId, ...args) {
|
|
|
2855
2861
|
}
|
|
2856
2862
|
__name(createXRHandPoseComponent, "createXRHandPoseComponent");
|
|
2857
2863
|
|
|
2864
|
+
// core/components/XRAnchor.ts
|
|
2865
|
+
var _XRAnchorComponent = class _XRAnchorComponent extends Component {
|
|
2866
|
+
constructor(options) {
|
|
2867
|
+
super();
|
|
2868
|
+
this.enabled = options?.enabled ?? true;
|
|
2869
|
+
}
|
|
2870
|
+
};
|
|
2871
|
+
__name(_XRAnchorComponent, "XRAnchorComponent");
|
|
2872
|
+
var XRAnchorComponent = _XRAnchorComponent;
|
|
2873
|
+
|
|
2874
|
+
// core/factories/components/XRAnchorFactory.ts
|
|
2875
|
+
function createXRAnchorComponent(world, entityId, ...args) {
|
|
2876
|
+
const component = new XRAnchorComponent(...args);
|
|
2877
|
+
world.addComponent(entityId, component);
|
|
2878
|
+
return component;
|
|
2879
|
+
}
|
|
2880
|
+
__name(createXRAnchorComponent, "createXRAnchorComponent");
|
|
2881
|
+
|
|
2858
2882
|
// core/components/XRInteractable.ts
|
|
2859
2883
|
var _XRInteractableComponent = class _XRInteractableComponent extends Component {
|
|
2860
2884
|
constructor(options) {
|
|
@@ -2966,6 +2990,7 @@ function registerDefaultComponentFactories(objectManagementSystem) {
|
|
|
2966
2990
|
objectManagementSystem.registerComponentFactory("xrRig", createXRRigComponent);
|
|
2967
2991
|
objectManagementSystem.registerComponentFactory("vrTeleportArea", createVRTeleportAreaComponent);
|
|
2968
2992
|
objectManagementSystem.registerComponentFactory("xrHandPose", createXRHandPoseComponent);
|
|
2993
|
+
objectManagementSystem.registerComponentFactory("xrAnchor", createXRAnchorComponent);
|
|
2969
2994
|
objectManagementSystem.registerComponentFactory("xrInteractable", createXRInteractableComponent);
|
|
2970
2995
|
objectManagementSystem.registerComponentFactory("xrInteractor", createXRInteractorComponent);
|
|
2971
2996
|
objectManagementSystem.registerComponentFactory("xrInteractorSide", createXRInteractorSideComponent);
|
|
@@ -4019,6 +4044,8 @@ __export(components_exports, {
|
|
|
4019
4044
|
TransformComponent: () => TransformComponent,
|
|
4020
4045
|
VRTeleportAreaComponent: () => VRTeleportAreaComponent,
|
|
4021
4046
|
VideoComponent: () => VideoComponent,
|
|
4047
|
+
XRAnchorComponent: () => XRAnchorComponent,
|
|
4048
|
+
XREnvironmentComponent: () => XREnvironmentComponent,
|
|
4022
4049
|
XRHandPoseComponent: () => XRHandPoseComponent,
|
|
4023
4050
|
XRInteractableComponent: () => XRInteractableComponent,
|
|
4024
4051
|
XRInteractorComponent: () => XRInteractorComponent,
|
|
@@ -4061,6 +4088,18 @@ var _PrefabNode = class _PrefabNode extends Component {
|
|
|
4061
4088
|
__name(_PrefabNode, "PrefabNode");
|
|
4062
4089
|
var PrefabNode = _PrefabNode;
|
|
4063
4090
|
|
|
4091
|
+
// core/components/XREnvironment.ts
|
|
4092
|
+
var _XREnvironmentComponent = class _XREnvironmentComponent extends Component {
|
|
4093
|
+
constructor(kind, semanticLabel = null, orientation = null) {
|
|
4094
|
+
super();
|
|
4095
|
+
this.kind = kind;
|
|
4096
|
+
this.semanticLabel = semanticLabel;
|
|
4097
|
+
this.orientation = orientation;
|
|
4098
|
+
}
|
|
4099
|
+
};
|
|
4100
|
+
__name(_XREnvironmentComponent, "XREnvironmentComponent");
|
|
4101
|
+
var XREnvironmentComponent = _XREnvironmentComponent;
|
|
4102
|
+
|
|
4064
4103
|
// core/factories/objects/ModelFactory.ts
|
|
4065
4104
|
function getModelFileName(path) {
|
|
4066
4105
|
const segments = path.split(/[\\/]/).filter(Boolean);
|
|
@@ -4723,6 +4762,7 @@ function buildComponentValueMaps(snapshot) {
|
|
|
4723
4762
|
snapshot.components,
|
|
4724
4763
|
"XRHandPoseComponent"
|
|
4725
4764
|
),
|
|
4765
|
+
xrAnchorValues: readByComponent(snapshot.components, "XRAnchorComponent"),
|
|
4726
4766
|
xrInteractorSideValues: readByComponent(
|
|
4727
4767
|
snapshot.components,
|
|
4728
4768
|
"XRInteractorSideComponent"
|
|
@@ -4876,6 +4916,24 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
|
|
|
4876
4916
|
if (typeof sourceXrRig.fallbackEyeHeight === "number") {
|
|
4877
4917
|
rig.fallbackEyeHeight = sourceXrRig.fallbackEyeHeight;
|
|
4878
4918
|
}
|
|
4919
|
+
if (typeof sourceXrRig.passthrough === "boolean") {
|
|
4920
|
+
rig.passthrough = sourceXrRig.passthrough;
|
|
4921
|
+
}
|
|
4922
|
+
if (typeof sourceXrRig.depthOcclusion === "boolean") {
|
|
4923
|
+
rig.depthOcclusion = sourceXrRig.depthOcclusion;
|
|
4924
|
+
}
|
|
4925
|
+
if (typeof sourceXrRig.environmentPhysics === "string") {
|
|
4926
|
+
rig.environmentPhysics = sourceXrRig.environmentPhysics;
|
|
4927
|
+
}
|
|
4928
|
+
if (typeof sourceXrRig.environmentDebug === "boolean") {
|
|
4929
|
+
rig.environmentDebug = sourceXrRig.environmentDebug;
|
|
4930
|
+
}
|
|
4931
|
+
if (typeof sourceXrRig.hitTest === "boolean") {
|
|
4932
|
+
rig.hitTest = sourceXrRig.hitTest;
|
|
4933
|
+
}
|
|
4934
|
+
if (typeof sourceXrRig.anchors === "boolean") {
|
|
4935
|
+
rig.anchors = sourceXrRig.anchors;
|
|
4936
|
+
}
|
|
4879
4937
|
}
|
|
4880
4938
|
}
|
|
4881
4939
|
{
|
|
@@ -5013,6 +5071,19 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
|
|
|
5013
5071
|
}
|
|
5014
5072
|
}
|
|
5015
5073
|
}
|
|
5074
|
+
{
|
|
5075
|
+
const sourceAnchor = asRecord3(values.xrAnchorValues.get(sourceEntityId));
|
|
5076
|
+
let anchor = world.getComponent(targetEntityId, XRAnchorComponent);
|
|
5077
|
+
if (sourceAnchor && !anchor) {
|
|
5078
|
+
anchor = new XRAnchorComponent();
|
|
5079
|
+
world.addComponent(targetEntityId, anchor);
|
|
5080
|
+
}
|
|
5081
|
+
if (sourceAnchor && anchor) {
|
|
5082
|
+
if (typeof sourceAnchor.enabled === "boolean") {
|
|
5083
|
+
anchor.enabled = sourceAnchor.enabled;
|
|
5084
|
+
}
|
|
5085
|
+
}
|
|
5086
|
+
}
|
|
5016
5087
|
{
|
|
5017
5088
|
const sourceInteractor = asRecord3(
|
|
5018
5089
|
values.xrInteractorValues.get(sourceEntityId)
|
|
@@ -7243,6 +7314,8 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
|
|
|
7243
7314
|
this.signature = null;
|
|
7244
7315
|
this.activeTexture = null;
|
|
7245
7316
|
this.pendingImageUrl = null;
|
|
7317
|
+
/** Whether the skybox is currently withheld because passthrough is on. */
|
|
7318
|
+
this.passthroughSuppressed = false;
|
|
7246
7319
|
this.scene = scene;
|
|
7247
7320
|
}
|
|
7248
7321
|
disposeActiveTexture() {
|
|
@@ -7327,6 +7400,18 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
|
|
|
7327
7400
|
this.scene.fog = new THREE51__namespace.FogExp2(fogColor, density);
|
|
7328
7401
|
}
|
|
7329
7402
|
update(world) {
|
|
7403
|
+
const passthrough = world.isXrPassthrough();
|
|
7404
|
+
if (passthrough !== this.passthroughSuppressed) {
|
|
7405
|
+
this.passthroughSuppressed = passthrough;
|
|
7406
|
+
if (passthrough) {
|
|
7407
|
+
this.scene.background = null;
|
|
7408
|
+
} else {
|
|
7409
|
+
this.signature = null;
|
|
7410
|
+
}
|
|
7411
|
+
}
|
|
7412
|
+
if (passthrough) {
|
|
7413
|
+
return;
|
|
7414
|
+
}
|
|
7330
7415
|
const entityIds = world.query(Environment);
|
|
7331
7416
|
if (entityIds.length === 0) {
|
|
7332
7417
|
if (this.signature !== null) {
|
|
@@ -12350,6 +12435,509 @@ var _XRSessionSystem = class _XRSessionSystem {
|
|
|
12350
12435
|
};
|
|
12351
12436
|
__name(_XRSessionSystem, "XRSessionSystem");
|
|
12352
12437
|
var XRSessionSystem = _XRSessionSystem;
|
|
12438
|
+
var PLANE_COLLIDER_THICKNESS = 0.02;
|
|
12439
|
+
var MIN_PLANE_EXTENT = 0.01;
|
|
12440
|
+
function planeColliderFromPolygon(polygon) {
|
|
12441
|
+
if (!polygon || polygon.length < 3) return null;
|
|
12442
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
12443
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
12444
|
+
let minZ = Number.POSITIVE_INFINITY;
|
|
12445
|
+
let maxZ = Number.NEGATIVE_INFINITY;
|
|
12446
|
+
for (const point of polygon) {
|
|
12447
|
+
if (!Number.isFinite(point.x) || !Number.isFinite(point.z)) return null;
|
|
12448
|
+
minX = Math.min(minX, point.x);
|
|
12449
|
+
maxX = Math.max(maxX, point.x);
|
|
12450
|
+
minZ = Math.min(minZ, point.z);
|
|
12451
|
+
maxZ = Math.max(maxZ, point.z);
|
|
12452
|
+
}
|
|
12453
|
+
const width = maxX - minX;
|
|
12454
|
+
const depth = maxZ - minZ;
|
|
12455
|
+
if (width < MIN_PLANE_EXTENT || depth < MIN_PLANE_EXTENT) return null;
|
|
12456
|
+
return {
|
|
12457
|
+
// Thin on Y: that is the plane's normal in plane space.
|
|
12458
|
+
size: { x: width, y: PLANE_COLLIDER_THICKNESS, z: depth },
|
|
12459
|
+
// The polygon is not necessarily centred on its own origin, so the box has
|
|
12460
|
+
// to be offset to sit over it. Pushed DOWN half its thickness so the
|
|
12461
|
+
// collider's top face lines up with the real surface — otherwise objects
|
|
12462
|
+
// rest a centimetre above the floor, or sink a centimetre into it.
|
|
12463
|
+
offset: {
|
|
12464
|
+
x: (minX + maxX) / 2,
|
|
12465
|
+
y: -PLANE_COLLIDER_THICKNESS / 2,
|
|
12466
|
+
z: (minZ + maxZ) / 2
|
|
12467
|
+
}
|
|
12468
|
+
};
|
|
12469
|
+
}
|
|
12470
|
+
__name(planeColliderFromPolygon, "planeColliderFromPolygon");
|
|
12471
|
+
function writeXrMeshGeometry(vertices, indices, existing) {
|
|
12472
|
+
const geometry = existing ?? new THREE51__namespace.BufferGeometry();
|
|
12473
|
+
geometry.setAttribute("position", new THREE51__namespace.BufferAttribute(vertices, 3));
|
|
12474
|
+
geometry.setIndex(new THREE51__namespace.BufferAttribute(indices, 1));
|
|
12475
|
+
geometry.computeBoundingSphere();
|
|
12476
|
+
geometry.computeVertexNormals();
|
|
12477
|
+
return geometry;
|
|
12478
|
+
}
|
|
12479
|
+
__name(writeXrMeshGeometry, "writeXrMeshGeometry");
|
|
12480
|
+
function decomposeXrPose(matrixElements, target) {
|
|
12481
|
+
const matrix = new THREE51__namespace.Matrix4().fromArray(
|
|
12482
|
+
matrixElements
|
|
12483
|
+
);
|
|
12484
|
+
const position = new THREE51__namespace.Vector3();
|
|
12485
|
+
const quaternion = new THREE51__namespace.Quaternion();
|
|
12486
|
+
const scale = new THREE51__namespace.Vector3();
|
|
12487
|
+
matrix.decompose(position, quaternion, scale);
|
|
12488
|
+
const euler = new THREE51__namespace.Euler().setFromQuaternion(quaternion, "XYZ");
|
|
12489
|
+
target.position.x = position.x;
|
|
12490
|
+
target.position.y = position.y;
|
|
12491
|
+
target.position.z = position.z;
|
|
12492
|
+
target.rotation.x = euler.x;
|
|
12493
|
+
target.rotation.y = euler.y;
|
|
12494
|
+
target.rotation.z = euler.z;
|
|
12495
|
+
}
|
|
12496
|
+
__name(decomposeXrPose, "decomposeXrPose");
|
|
12497
|
+
|
|
12498
|
+
// core/systems/XREnvironmentSystem.ts
|
|
12499
|
+
var DEBUG_COLOR = 5088255;
|
|
12500
|
+
var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
12501
|
+
constructor() {
|
|
12502
|
+
/** Keyed by the XRPlane/XRMesh object itself — the runtime's own identity. */
|
|
12503
|
+
this.planes = /* @__PURE__ */ new Map();
|
|
12504
|
+
this.meshes = /* @__PURE__ */ new Map();
|
|
12505
|
+
this.presenting = false;
|
|
12506
|
+
this.debugVisible = false;
|
|
12507
|
+
}
|
|
12508
|
+
update(world) {
|
|
12509
|
+
const presenting = world.isXrPresenting();
|
|
12510
|
+
if (!presenting) {
|
|
12511
|
+
if (this.presenting) {
|
|
12512
|
+
this.presenting = false;
|
|
12513
|
+
this.clearAll(world);
|
|
12514
|
+
}
|
|
12515
|
+
return;
|
|
12516
|
+
}
|
|
12517
|
+
this.presenting = true;
|
|
12518
|
+
const rig = this.findRig(world);
|
|
12519
|
+
const mode = rig?.environmentPhysics ?? "none";
|
|
12520
|
+
if (mode === "none") {
|
|
12521
|
+
if (this.planes.size > 0 || this.meshes.size > 0) this.clearAll(world);
|
|
12522
|
+
return;
|
|
12523
|
+
}
|
|
12524
|
+
const debug = rig?.environmentDebug === true;
|
|
12525
|
+
if (debug !== this.debugVisible) {
|
|
12526
|
+
this.debugVisible = debug;
|
|
12527
|
+
this.applyDebugVisibility(world);
|
|
12528
|
+
}
|
|
12529
|
+
const xr = world.getRenderer().xr;
|
|
12530
|
+
const frame = xr.getFrame?.();
|
|
12531
|
+
const referenceSpace = xr.getReferenceSpace();
|
|
12532
|
+
if (!frame || !referenceSpace) return;
|
|
12533
|
+
const meshesAvailable = Boolean(frame.detectedMeshes);
|
|
12534
|
+
const useMesh = (mode === "mesh" || mode === "auto") && meshesAvailable;
|
|
12535
|
+
const usePlanes = mode === "planes" || mode === "auto" && !useMesh;
|
|
12536
|
+
if (usePlanes) {
|
|
12537
|
+
this.syncPlanes(world, frame, referenceSpace);
|
|
12538
|
+
} else if (this.planes.size > 0) {
|
|
12539
|
+
this.clearMap(world, this.planes);
|
|
12540
|
+
}
|
|
12541
|
+
if (useMesh) {
|
|
12542
|
+
this.syncMeshes(world, frame, referenceSpace);
|
|
12543
|
+
} else if (this.meshes.size > 0) {
|
|
12544
|
+
this.clearMap(world, this.meshes);
|
|
12545
|
+
}
|
|
12546
|
+
}
|
|
12547
|
+
/** How many pieces of real geometry are currently colliding. Script-facing. */
|
|
12548
|
+
getDetectedCount() {
|
|
12549
|
+
return { planes: this.planes.size, meshes: this.meshes.size };
|
|
12550
|
+
}
|
|
12551
|
+
findRig(world) {
|
|
12552
|
+
const ids = world.query(XRRigComponent);
|
|
12553
|
+
if (ids.length === 0) return null;
|
|
12554
|
+
return world.getComponent(ids[0], XRRigComponent) ?? null;
|
|
12555
|
+
}
|
|
12556
|
+
syncPlanes(world, frame, referenceSpace) {
|
|
12557
|
+
const detected = frame.detectedPlanes;
|
|
12558
|
+
if (!detected) return;
|
|
12559
|
+
for (const [plane, entry] of this.planes) {
|
|
12560
|
+
if (!detected.has(plane)) {
|
|
12561
|
+
this.destroyEntry(world, entry);
|
|
12562
|
+
this.planes.delete(plane);
|
|
12563
|
+
}
|
|
12564
|
+
}
|
|
12565
|
+
for (const plane of detected) {
|
|
12566
|
+
const pose = frame.getPose(plane.planeSpace, referenceSpace);
|
|
12567
|
+
if (!pose) continue;
|
|
12568
|
+
let entry = this.planes.get(plane);
|
|
12569
|
+
const changed = plane.lastChangedTime ?? 0;
|
|
12570
|
+
if (!entry) {
|
|
12571
|
+
const created = this.createPlaneEntity(world, plane);
|
|
12572
|
+
if (!created) continue;
|
|
12573
|
+
entry = created;
|
|
12574
|
+
this.planes.set(plane, entry);
|
|
12575
|
+
} else if (changed !== entry.lastChangedTime) {
|
|
12576
|
+
entry.lastChangedTime = changed;
|
|
12577
|
+
this.applyPlaneCollider(world, entry.entityId, plane);
|
|
12578
|
+
}
|
|
12579
|
+
this.applyPose(world, entry.entityId, pose);
|
|
12580
|
+
}
|
|
12581
|
+
}
|
|
12582
|
+
syncMeshes(world, frame, referenceSpace) {
|
|
12583
|
+
const detected = frame.detectedMeshes;
|
|
12584
|
+
if (!detected) return;
|
|
12585
|
+
for (const [mesh, entry] of this.meshes) {
|
|
12586
|
+
if (!detected.has(mesh)) {
|
|
12587
|
+
this.destroyEntry(world, entry);
|
|
12588
|
+
this.meshes.delete(mesh);
|
|
12589
|
+
}
|
|
12590
|
+
}
|
|
12591
|
+
for (const mesh of detected) {
|
|
12592
|
+
const pose = frame.getPose(mesh.meshSpace, referenceSpace);
|
|
12593
|
+
if (!pose) continue;
|
|
12594
|
+
let entry = this.meshes.get(mesh);
|
|
12595
|
+
const changed = mesh.lastChangedTime ?? 0;
|
|
12596
|
+
if (!entry) {
|
|
12597
|
+
const created = this.createMeshEntity(world, mesh);
|
|
12598
|
+
if (!created) continue;
|
|
12599
|
+
entry = created;
|
|
12600
|
+
this.meshes.set(mesh, entry);
|
|
12601
|
+
} else if (changed !== entry.lastChangedTime) {
|
|
12602
|
+
entry.lastChangedTime = changed;
|
|
12603
|
+
writeXrMeshGeometry(mesh.vertices, mesh.indices, entry.geometry);
|
|
12604
|
+
}
|
|
12605
|
+
this.applyPose(world, entry.entityId, pose);
|
|
12606
|
+
}
|
|
12607
|
+
}
|
|
12608
|
+
createPlaneEntity(world, plane) {
|
|
12609
|
+
const collider = planeColliderFromPolygon(plane.polygon);
|
|
12610
|
+
if (!collider) return null;
|
|
12611
|
+
const entityId = world.createEntity();
|
|
12612
|
+
world.addComponents(entityId, [
|
|
12613
|
+
new TransformComponent(),
|
|
12614
|
+
new XREnvironmentComponent(
|
|
12615
|
+
"plane",
|
|
12616
|
+
plane.semanticLabel ?? null,
|
|
12617
|
+
normalizeOrientation(plane.orientation)
|
|
12618
|
+
)
|
|
12619
|
+
]);
|
|
12620
|
+
this.applyPlaneCollider(world, entityId, plane);
|
|
12621
|
+
const entry = {
|
|
12622
|
+
entityId,
|
|
12623
|
+
lastChangedTime: plane.lastChangedTime ?? 0,
|
|
12624
|
+
geometry: null,
|
|
12625
|
+
material: null
|
|
12626
|
+
};
|
|
12627
|
+
if (this.debugVisible) this.attachPlaneDebug(world, entry, collider);
|
|
12628
|
+
return entry;
|
|
12629
|
+
}
|
|
12630
|
+
applyPlaneCollider(world, entityId, plane) {
|
|
12631
|
+
const collider = planeColliderFromPolygon(plane.polygon);
|
|
12632
|
+
if (!collider) return;
|
|
12633
|
+
const existing = world.getComponent(entityId, PhysicsComponent);
|
|
12634
|
+
const values = {
|
|
12635
|
+
// Fixed: the real room does not move because something hit it.
|
|
12636
|
+
rigidbody: { type: "fixed" },
|
|
12637
|
+
collider: {
|
|
12638
|
+
shape: "cuboid",
|
|
12639
|
+
size: collider.size,
|
|
12640
|
+
offsetPosition: collider.offset
|
|
12641
|
+
}
|
|
12642
|
+
};
|
|
12643
|
+
if (existing) {
|
|
12644
|
+
existing.setValues(values);
|
|
12645
|
+
} else {
|
|
12646
|
+
world.addComponent(entityId, new PhysicsComponent(values));
|
|
12647
|
+
}
|
|
12648
|
+
}
|
|
12649
|
+
createMeshEntity(world, mesh) {
|
|
12650
|
+
if (!mesh.vertices?.length || !mesh.indices?.length) return null;
|
|
12651
|
+
const geometry = writeXrMeshGeometry(mesh.vertices, mesh.indices);
|
|
12652
|
+
const material = new THREE51__namespace.MeshBasicMaterial({
|
|
12653
|
+
color: DEBUG_COLOR,
|
|
12654
|
+
wireframe: true
|
|
12655
|
+
});
|
|
12656
|
+
const root = new THREE51__namespace.Group();
|
|
12657
|
+
const child = new THREE51__namespace.Mesh(geometry, material);
|
|
12658
|
+
child.visible = this.debugVisible;
|
|
12659
|
+
root.add(child);
|
|
12660
|
+
const entityId = world.createEntity();
|
|
12661
|
+
world.addComponents(entityId, [
|
|
12662
|
+
new TransformComponent(),
|
|
12663
|
+
new Object3DRef(root),
|
|
12664
|
+
new XREnvironmentComponent("mesh", mesh.semanticLabel ?? null, null),
|
|
12665
|
+
new PhysicsComponent({
|
|
12666
|
+
rigidbody: { type: "fixed" },
|
|
12667
|
+
collider: { shape: "trimesh" }
|
|
12668
|
+
})
|
|
12669
|
+
]);
|
|
12670
|
+
return {
|
|
12671
|
+
entityId,
|
|
12672
|
+
lastChangedTime: mesh.lastChangedTime ?? 0,
|
|
12673
|
+
geometry,
|
|
12674
|
+
material
|
|
12675
|
+
};
|
|
12676
|
+
}
|
|
12677
|
+
/** A thin box so a detected plane can be SEEN, not just collided with. */
|
|
12678
|
+
attachPlaneDebug(world, entry, collider) {
|
|
12679
|
+
const geometry = new THREE51__namespace.BoxGeometry(
|
|
12680
|
+
collider.size.x,
|
|
12681
|
+
collider.size.y,
|
|
12682
|
+
collider.size.z
|
|
12683
|
+
);
|
|
12684
|
+
const material = new THREE51__namespace.MeshBasicMaterial({
|
|
12685
|
+
color: DEBUG_COLOR,
|
|
12686
|
+
wireframe: true
|
|
12687
|
+
});
|
|
12688
|
+
const root = new THREE51__namespace.Group();
|
|
12689
|
+
root.add(new THREE51__namespace.Mesh(geometry, material));
|
|
12690
|
+
entry.geometry = geometry;
|
|
12691
|
+
entry.material = material;
|
|
12692
|
+
world.addComponent(entry.entityId, new Object3DRef(root));
|
|
12693
|
+
}
|
|
12694
|
+
applyPose(world, entityId, pose) {
|
|
12695
|
+
const transform = world.getComponent(entityId, TransformComponent);
|
|
12696
|
+
if (!transform) return;
|
|
12697
|
+
decomposeXrPose(pose.transform.matrix, transform);
|
|
12698
|
+
}
|
|
12699
|
+
applyDebugVisibility(world) {
|
|
12700
|
+
for (const map of [this.planes, this.meshes]) {
|
|
12701
|
+
for (const entry of map.values()) {
|
|
12702
|
+
const ref = world.getComponent(entry.entityId, Object3DRef);
|
|
12703
|
+
ref?.object.traverse((child) => {
|
|
12704
|
+
if (child.isMesh) child.visible = this.debugVisible;
|
|
12705
|
+
});
|
|
12706
|
+
}
|
|
12707
|
+
}
|
|
12708
|
+
}
|
|
12709
|
+
destroyEntry(world, entry) {
|
|
12710
|
+
entry.geometry?.dispose();
|
|
12711
|
+
entry.material?.dispose();
|
|
12712
|
+
world.destroyEntity(entry.entityId);
|
|
12713
|
+
}
|
|
12714
|
+
clearMap(world, map) {
|
|
12715
|
+
for (const entry of map.values()) this.destroyEntry(world, entry);
|
|
12716
|
+
map.clear();
|
|
12717
|
+
}
|
|
12718
|
+
clearAll(world) {
|
|
12719
|
+
this.clearMap(world, this.planes);
|
|
12720
|
+
this.clearMap(world, this.meshes);
|
|
12721
|
+
}
|
|
12722
|
+
dispose(world) {
|
|
12723
|
+
this.clearAll(world);
|
|
12724
|
+
}
|
|
12725
|
+
};
|
|
12726
|
+
__name(_XREnvironmentSystem, "XREnvironmentSystem");
|
|
12727
|
+
var XREnvironmentSystem = _XREnvironmentSystem;
|
|
12728
|
+
function normalizeOrientation(value) {
|
|
12729
|
+
return value === "horizontal" || value === "vertical" ? value : null;
|
|
12730
|
+
}
|
|
12731
|
+
__name(normalizeOrientation, "normalizeOrientation");
|
|
12732
|
+
var _XRHitTestSystem = class _XRHitTestSystem {
|
|
12733
|
+
constructor() {
|
|
12734
|
+
this.sources = /* @__PURE__ */ new Map();
|
|
12735
|
+
/** Targets whose async request is in flight, so it is not fired twice. */
|
|
12736
|
+
this.pending = /* @__PURE__ */ new Set();
|
|
12737
|
+
this.results = /* @__PURE__ */ new Map();
|
|
12738
|
+
this.presenting = false;
|
|
12739
|
+
this.enabled = false;
|
|
12740
|
+
}
|
|
12741
|
+
update(world) {
|
|
12742
|
+
const presenting = world.isXrPresenting();
|
|
12743
|
+
if (!presenting) {
|
|
12744
|
+
if (this.presenting) {
|
|
12745
|
+
this.presenting = false;
|
|
12746
|
+
this.teardown();
|
|
12747
|
+
}
|
|
12748
|
+
return;
|
|
12749
|
+
}
|
|
12750
|
+
if (!this.presenting) {
|
|
12751
|
+
this.presenting = true;
|
|
12752
|
+
this.enabled = this.rigWantsHitTest(world);
|
|
12753
|
+
}
|
|
12754
|
+
if (!this.enabled) return;
|
|
12755
|
+
const xr = world.getRenderer().xr;
|
|
12756
|
+
const session = xr.getSession();
|
|
12757
|
+
const referenceSpace = xr.getReferenceSpace();
|
|
12758
|
+
const frame = xr.getFrame?.();
|
|
12759
|
+
if (!session || !referenceSpace || !frame) return;
|
|
12760
|
+
this.ensureViewerSource(session);
|
|
12761
|
+
this.ensureInputSources(session);
|
|
12762
|
+
this.results.clear();
|
|
12763
|
+
for (const [key, source] of this.sources) {
|
|
12764
|
+
const hits = frame.getHitTestResults(source);
|
|
12765
|
+
if (!hits || hits.length === 0) continue;
|
|
12766
|
+
const pose = hits[0].getPose(referenceSpace);
|
|
12767
|
+
if (!pose) continue;
|
|
12768
|
+
const matrix = new THREE51__namespace.Matrix4().fromArray(
|
|
12769
|
+
pose.transform.matrix
|
|
12770
|
+
);
|
|
12771
|
+
const position = new THREE51__namespace.Vector3();
|
|
12772
|
+
const quaternion = new THREE51__namespace.Quaternion();
|
|
12773
|
+
matrix.decompose(position, quaternion, new THREE51__namespace.Vector3());
|
|
12774
|
+
this.results.set(key, { position, quaternion });
|
|
12775
|
+
}
|
|
12776
|
+
}
|
|
12777
|
+
/**
|
|
12778
|
+
* Where the given ray meets the real world this frame, or null.
|
|
12779
|
+
*
|
|
12780
|
+
* Null is the normal case, not an error: the source may still be being
|
|
12781
|
+
* requested, the ray may point at nothing recognised, or the runtime may not
|
|
12782
|
+
* have granted `hit-test` at all.
|
|
12783
|
+
*/
|
|
12784
|
+
getHitResult(target = "viewer") {
|
|
12785
|
+
return this.results.get(target) ?? null;
|
|
12786
|
+
}
|
|
12787
|
+
rigWantsHitTest(world) {
|
|
12788
|
+
const ids = world.query(XRRigComponent);
|
|
12789
|
+
if (ids.length === 0) return false;
|
|
12790
|
+
return world.getComponent(ids[0], XRRigComponent)?.hitTest === true;
|
|
12791
|
+
}
|
|
12792
|
+
ensureViewerSource(session) {
|
|
12793
|
+
if (this.sources.has("viewer") || this.pending.has("viewer")) return;
|
|
12794
|
+
if (typeof session.requestHitTestSource !== "function") return;
|
|
12795
|
+
this.pending.add("viewer");
|
|
12796
|
+
void session.requestReferenceSpace("viewer").then((space) => session.requestHitTestSource?.({ space })).then((source) => {
|
|
12797
|
+
if (source && this.presenting) this.sources.set("viewer", source);
|
|
12798
|
+
else source?.cancel?.();
|
|
12799
|
+
}).catch(() => {
|
|
12800
|
+
}).finally(() => this.pending.delete("viewer"));
|
|
12801
|
+
}
|
|
12802
|
+
ensureInputSources(session) {
|
|
12803
|
+
if (typeof session.requestHitTestSource !== "function") return;
|
|
12804
|
+
if (!session.inputSources) return;
|
|
12805
|
+
for (const input of session.inputSources) {
|
|
12806
|
+
const handedness = input.handedness;
|
|
12807
|
+
if (!handedness || handedness === "none") continue;
|
|
12808
|
+
if (!input.targetRaySpace) continue;
|
|
12809
|
+
if (this.sources.has(handedness) || this.pending.has(handedness)) continue;
|
|
12810
|
+
this.pending.add(handedness);
|
|
12811
|
+
const request = session.requestHitTestSource({
|
|
12812
|
+
space: input.targetRaySpace
|
|
12813
|
+
});
|
|
12814
|
+
if (!request) {
|
|
12815
|
+
this.pending.delete(handedness);
|
|
12816
|
+
continue;
|
|
12817
|
+
}
|
|
12818
|
+
void request.then((source) => {
|
|
12819
|
+
if (source && this.presenting) this.sources.set(handedness, source);
|
|
12820
|
+
else source?.cancel?.();
|
|
12821
|
+
}).catch(() => {
|
|
12822
|
+
}).finally(() => this.pending.delete(handedness));
|
|
12823
|
+
}
|
|
12824
|
+
}
|
|
12825
|
+
teardown() {
|
|
12826
|
+
for (const source of this.sources.values()) source.cancel?.();
|
|
12827
|
+
this.sources.clear();
|
|
12828
|
+
this.results.clear();
|
|
12829
|
+
this.pending.clear();
|
|
12830
|
+
this.enabled = false;
|
|
12831
|
+
}
|
|
12832
|
+
dispose() {
|
|
12833
|
+
this.presenting = false;
|
|
12834
|
+
this.teardown();
|
|
12835
|
+
}
|
|
12836
|
+
};
|
|
12837
|
+
__name(_XRHitTestSystem, "XRHitTestSystem");
|
|
12838
|
+
var XRHitTestSystem = _XRHitTestSystem;
|
|
12839
|
+
var _XRAnchorSystem = class _XRAnchorSystem {
|
|
12840
|
+
constructor() {
|
|
12841
|
+
this.anchors = /* @__PURE__ */ new Map();
|
|
12842
|
+
/** In-flight creations, so one entity is not anchored twice. */
|
|
12843
|
+
this.pending = /* @__PURE__ */ new Set();
|
|
12844
|
+
this.presenting = false;
|
|
12845
|
+
}
|
|
12846
|
+
update(world) {
|
|
12847
|
+
const presenting = world.isXrPresenting();
|
|
12848
|
+
if (!presenting) {
|
|
12849
|
+
if (this.presenting) {
|
|
12850
|
+
this.presenting = false;
|
|
12851
|
+
this.releaseAll();
|
|
12852
|
+
}
|
|
12853
|
+
return;
|
|
12854
|
+
}
|
|
12855
|
+
this.presenting = true;
|
|
12856
|
+
const xr = world.getRenderer().xr;
|
|
12857
|
+
const referenceSpace = xr.getReferenceSpace();
|
|
12858
|
+
const frame = xr.getFrame?.();
|
|
12859
|
+
if (!frame || !referenceSpace) return;
|
|
12860
|
+
const entityIds = world.query(XRAnchorComponent, TransformComponent);
|
|
12861
|
+
const live = new Set(entityIds);
|
|
12862
|
+
for (const [entityId, anchor] of this.anchors) {
|
|
12863
|
+
if (!live.has(entityId)) {
|
|
12864
|
+
anchor.delete?.();
|
|
12865
|
+
this.anchors.delete(entityId);
|
|
12866
|
+
}
|
|
12867
|
+
}
|
|
12868
|
+
for (const entityId of entityIds) {
|
|
12869
|
+
const component = world.getComponent(entityId, XRAnchorComponent);
|
|
12870
|
+
const transform = world.getComponent(entityId, TransformComponent);
|
|
12871
|
+
if (!component || !transform) continue;
|
|
12872
|
+
if (!component.enabled) {
|
|
12873
|
+
const existing = this.anchors.get(entityId);
|
|
12874
|
+
if (existing) {
|
|
12875
|
+
existing.delete?.();
|
|
12876
|
+
this.anchors.delete(entityId);
|
|
12877
|
+
}
|
|
12878
|
+
continue;
|
|
12879
|
+
}
|
|
12880
|
+
const anchor = this.anchors.get(entityId);
|
|
12881
|
+
if (!anchor) {
|
|
12882
|
+
this.requestAnchor(entityId, transform, frame, referenceSpace);
|
|
12883
|
+
continue;
|
|
12884
|
+
}
|
|
12885
|
+
const pose = frame.getPose(anchor.anchorSpace, referenceSpace);
|
|
12886
|
+
if (pose) decomposeXrPose(pose.transform.matrix, transform);
|
|
12887
|
+
}
|
|
12888
|
+
}
|
|
12889
|
+
/** Whether this entity's pose is currently being driven by an anchor. */
|
|
12890
|
+
isAnchored(entityId) {
|
|
12891
|
+
return this.anchors.has(entityId);
|
|
12892
|
+
}
|
|
12893
|
+
requestAnchor(entityId, transform, frame, referenceSpace) {
|
|
12894
|
+
if (this.pending.has(entityId)) return;
|
|
12895
|
+
if (typeof frame.createAnchor !== "function") return;
|
|
12896
|
+
const RigidTransform = globalThis.XRRigidTransform;
|
|
12897
|
+
if (!RigidTransform) return;
|
|
12898
|
+
const quaternion = new THREE51__namespace.Quaternion().setFromEuler(
|
|
12899
|
+
new THREE51__namespace.Euler(
|
|
12900
|
+
transform.rotation.x,
|
|
12901
|
+
transform.rotation.y,
|
|
12902
|
+
transform.rotation.z,
|
|
12903
|
+
"XYZ"
|
|
12904
|
+
)
|
|
12905
|
+
);
|
|
12906
|
+
const pose = new RigidTransform(
|
|
12907
|
+
{
|
|
12908
|
+
x: transform.position.x,
|
|
12909
|
+
y: transform.position.y,
|
|
12910
|
+
z: transform.position.z,
|
|
12911
|
+
w: 1
|
|
12912
|
+
},
|
|
12913
|
+
{
|
|
12914
|
+
x: quaternion.x,
|
|
12915
|
+
y: quaternion.y,
|
|
12916
|
+
z: quaternion.z,
|
|
12917
|
+
w: quaternion.w
|
|
12918
|
+
}
|
|
12919
|
+
);
|
|
12920
|
+
const request = frame.createAnchor(pose, referenceSpace);
|
|
12921
|
+
if (!request) return;
|
|
12922
|
+
this.pending.add(entityId);
|
|
12923
|
+
void request.then((anchor) => {
|
|
12924
|
+
if (anchor && this.presenting) this.anchors.set(entityId, anchor);
|
|
12925
|
+
else anchor?.delete?.();
|
|
12926
|
+
}).catch(() => {
|
|
12927
|
+
}).finally(() => this.pending.delete(entityId));
|
|
12928
|
+
}
|
|
12929
|
+
releaseAll() {
|
|
12930
|
+
for (const anchor of this.anchors.values()) anchor.delete?.();
|
|
12931
|
+
this.anchors.clear();
|
|
12932
|
+
this.pending.clear();
|
|
12933
|
+
}
|
|
12934
|
+
dispose() {
|
|
12935
|
+
this.presenting = false;
|
|
12936
|
+
this.releaseAll();
|
|
12937
|
+
}
|
|
12938
|
+
};
|
|
12939
|
+
__name(_XRAnchorSystem, "XRAnchorSystem");
|
|
12940
|
+
var XRAnchorSystem = _XRAnchorSystem;
|
|
12353
12941
|
function buildGeometry2(area) {
|
|
12354
12942
|
if (area.shape === "circle") {
|
|
12355
12943
|
const radius = Math.max(5e-3, area.width / 2);
|
|
@@ -14006,6 +14594,24 @@ var _World = class _World {
|
|
|
14006
14594
|
isXrPresenting() {
|
|
14007
14595
|
return this.renderer.xr?.isPresenting === true;
|
|
14008
14596
|
}
|
|
14597
|
+
/**
|
|
14598
|
+
* `true` while the session is compositing over the real world (passthrough).
|
|
14599
|
+
*
|
|
14600
|
+
* Read from the session's `environmentBlendMode` rather than from what the app
|
|
14601
|
+
* ASKED for: a request for `immersive-ar` can be granted as a plain opaque
|
|
14602
|
+
* session, and the blend mode is the only thing that says what the user is
|
|
14603
|
+
* actually looking at. `additive` (optical see-through) and `alpha-blend`
|
|
14604
|
+
* (video passthrough) both mean the real world is visible behind the render.
|
|
14605
|
+
*
|
|
14606
|
+
* The load-bearing consequence is that nothing may paint an opaque background:
|
|
14607
|
+
* a skybox drawn over the whole framebuffer hides the room completely, which
|
|
14608
|
+
* looks exactly like passthrough having failed.
|
|
14609
|
+
*/
|
|
14610
|
+
isXrPassthrough() {
|
|
14611
|
+
if (!this.isXrPresenting()) return false;
|
|
14612
|
+
const blendMode = this.renderer.xr?.getEnvironmentBlendMode?.();
|
|
14613
|
+
return blendMode === "additive" || blendMode === "alpha-blend";
|
|
14614
|
+
}
|
|
14009
14615
|
/**
|
|
14010
14616
|
* Pauses world simulation. While paused, `update()` skips all system ticks so
|
|
14011
14617
|
* physics, animation, scripts, and rendering stop advancing. The last rendered
|
|
@@ -15015,8 +15621,11 @@ exports.VELOCITY_SAMPLES = VELOCITY_SAMPLES;
|
|
|
15015
15621
|
exports.VRTeleportAreaSystem = VRTeleportAreaSystem;
|
|
15016
15622
|
exports.VideoSystem = VideoSystem;
|
|
15017
15623
|
exports.World = World;
|
|
15624
|
+
exports.XRAnchorSystem = XRAnchorSystem;
|
|
15018
15625
|
exports.XRControllerVisualSystem = XRControllerVisualSystem;
|
|
15626
|
+
exports.XREnvironmentSystem = XREnvironmentSystem;
|
|
15019
15627
|
exports.XRHandPoseSystem = XRHandPoseSystem;
|
|
15628
|
+
exports.XRHitTestSystem = XRHitTestSystem;
|
|
15020
15629
|
exports.XRHoverVisualSystem = XRHoverVisualSystem;
|
|
15021
15630
|
exports.XRInputSystem = XRInputSystem;
|
|
15022
15631
|
exports.XRInteractionSystem = XRInteractionSystem;
|
|
@@ -15076,6 +15685,7 @@ exports.createSpriteComponent = createSpriteComponent;
|
|
|
15076
15685
|
exports.createTransformComponent = createTransformComponent;
|
|
15077
15686
|
exports.createVRTeleportAreaComponent = createVRTeleportAreaComponent;
|
|
15078
15687
|
exports.createVideoComponent = createVideoComponent;
|
|
15688
|
+
exports.createXRAnchorComponent = createXRAnchorComponent;
|
|
15079
15689
|
exports.createXRHandPoseComponent = createXRHandPoseComponent;
|
|
15080
15690
|
exports.createXRInteractableComponent = createXRInteractableComponent;
|
|
15081
15691
|
exports.createXRInteractorComponent = createXRInteractorComponent;
|