@relu-interactives/spatial-ecs 0.3.5 → 0.3.7
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-vrU46Vuw.d.cts → System-TmC2EijF.d.cts} +2 -2
- package/dist/{System-vrU46Vuw.d.ts → System-TmC2EijF.d.ts} +2 -2
- package/dist/{XRRig-2686IqLQ.d.cts → XRInteractor-CCUzSqg9.d.ts} +96 -2
- package/dist/{XRRig-D2QURXl1.d.ts → XRInteractor-I2q6TKtF.d.cts} +96 -2
- package/dist/{chunk-4BZYK3JP.js → chunk-FQTOC7HK.js} +135 -88
- package/dist/chunk-FQTOC7HK.js.map +1 -0
- package/dist/{chunk-2LVFGVWI.js → chunk-JRJ3CL5H.js} +3 -3
- package/dist/{chunk-2LVFGVWI.js.map → chunk-JRJ3CL5H.js.map} +1 -1
- package/dist/{chunk-6QC2PJLD.js → chunk-N4GKZ7IV.js} +29 -3
- package/dist/chunk-N4GKZ7IV.js.map +1 -0
- package/dist/{chunk-G53MZVKQ.js → chunk-O6FJA4XW.js} +367 -53
- package/dist/chunk-O6FJA4XW.js.map +1 -0
- package/dist/components/index.cjs +26 -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/factories/index.cjs +185 -112
- package/dist/factories/index.cjs.map +1 -1
- package/dist/factories/index.d.cts +7 -3
- package/dist/factories/index.d.ts +7 -3
- package/dist/factories/index.js +3 -3
- package/dist/{index-BvCmmuJ0.d.cts → index-B9nwRDf9.d.cts} +65 -14
- package/dist/{index-BtKOaHfY.d.ts → index-BltjPEhg.d.cts} +10 -3
- package/dist/{index-Xu238jLn.d.cts → index-W8pUajCW.d.ts} +10 -3
- package/dist/{index-B-CViRMx.d.ts → index-_q9TVDI5.d.ts} +65 -14
- package/dist/index.cjs +684 -293
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -9
- package/dist/index.d.ts +35 -9
- package/dist/index.js +8 -8
- package/dist/systems/index.cjs +386 -48
- 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-4BZYK3JP.js.map +0 -1
- package/dist/chunk-6QC2PJLD.js.map +0 -1
- package/dist/chunk-G53MZVKQ.js.map +0 -1
package/dist/systems/index.cjs
CHANGED
|
@@ -2109,6 +2109,7 @@ var XRInteractableComponent = _XRInteractableComponent;
|
|
|
2109
2109
|
var _XRRigComponent = class _XRRigComponent extends Component {
|
|
2110
2110
|
constructor(options) {
|
|
2111
2111
|
super();
|
|
2112
|
+
this.trackingOrigin = options?.trackingOrigin ?? "floor";
|
|
2112
2113
|
this.locomotion = options?.locomotion ?? "teleport";
|
|
2113
2114
|
this.moveSpeed = options?.moveSpeed ?? 2;
|
|
2114
2115
|
this.snapTurn = options?.snapTurn ?? true;
|
|
@@ -2119,6 +2120,29 @@ var _XRRigComponent = class _XRRigComponent extends Component {
|
|
|
2119
2120
|
__name(_XRRigComponent, "XRRigComponent");
|
|
2120
2121
|
var XRRigComponent = _XRRigComponent;
|
|
2121
2122
|
|
|
2123
|
+
// core/components/XRInteractor.ts
|
|
2124
|
+
var _XRInteractorComponent = class _XRInteractorComponent extends Component {
|
|
2125
|
+
constructor(options) {
|
|
2126
|
+
super();
|
|
2127
|
+
this.interactorType = options?.interactorType ?? "both";
|
|
2128
|
+
this.handPreset = options?.handPreset ?? "default";
|
|
2129
|
+
this.handColor = options?.handColor ?? "#e8b48a";
|
|
2130
|
+
this.handOpacity = options?.handOpacity ?? 1;
|
|
2131
|
+
this.handEmissiveIntensity = options?.handEmissiveIntensity ?? 0;
|
|
2132
|
+
this.showRay = options?.showRay ?? true;
|
|
2133
|
+
}
|
|
2134
|
+
};
|
|
2135
|
+
__name(_XRInteractorComponent, "XRInteractorComponent");
|
|
2136
|
+
var XRInteractorComponent = _XRInteractorComponent;
|
|
2137
|
+
var _XRInteractorSideComponent = class _XRInteractorSideComponent extends Component {
|
|
2138
|
+
constructor(options) {
|
|
2139
|
+
super();
|
|
2140
|
+
this.side = options?.side ?? "left";
|
|
2141
|
+
}
|
|
2142
|
+
};
|
|
2143
|
+
__name(_XRInteractorSideComponent, "XRInteractorSideComponent");
|
|
2144
|
+
var XRInteractorSideComponent = _XRInteractorSideComponent;
|
|
2145
|
+
|
|
2122
2146
|
// core/utils/dracoDecoderPath.ts
|
|
2123
2147
|
var LOCAL_DRACO_DECODER_RELATIVE_PATH = "./assets/draco/";
|
|
2124
2148
|
var DRACO_DECODER_CDN_PATH = "https://www.gstatic.com/draco/versioned/decoders/1.5.7/";
|
|
@@ -4913,6 +4937,14 @@ function buildComponentValueMaps(snapshot) {
|
|
|
4913
4937
|
snapshot.components,
|
|
4914
4938
|
"XRInteractableComponent"
|
|
4915
4939
|
),
|
|
4940
|
+
xrInteractorValues: readByComponent(
|
|
4941
|
+
snapshot.components,
|
|
4942
|
+
"XRInteractorComponent"
|
|
4943
|
+
),
|
|
4944
|
+
xrInteractorSideValues: readByComponent(
|
|
4945
|
+
snapshot.components,
|
|
4946
|
+
"XRInteractorSideComponent"
|
|
4947
|
+
),
|
|
4916
4948
|
instancedMeshValues: readByComponent(snapshot.components, "InstancedMeshComponent"),
|
|
4917
4949
|
instanceSlotValues: readByComponent(snapshot.components, "InstanceSlotComponent"),
|
|
4918
4950
|
prefabNodeValues: readByComponent(snapshot.components, "PrefabNode"),
|
|
@@ -5099,6 +5131,60 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
|
|
|
5099
5131
|
}
|
|
5100
5132
|
}
|
|
5101
5133
|
}
|
|
5134
|
+
{
|
|
5135
|
+
const sourceInteractor = asRecord3(
|
|
5136
|
+
values.xrInteractorValues.get(sourceEntityId)
|
|
5137
|
+
);
|
|
5138
|
+
const interactor = world.getComponent(targetEntityId, XRInteractorComponent);
|
|
5139
|
+
if (sourceInteractor && interactor) {
|
|
5140
|
+
if (typeof sourceInteractor.interactorType === "string") {
|
|
5141
|
+
interactor.interactorType = sourceInteractor.interactorType;
|
|
5142
|
+
}
|
|
5143
|
+
if (typeof sourceInteractor.handPreset === "string") {
|
|
5144
|
+
interactor.handPreset = sourceInteractor.handPreset;
|
|
5145
|
+
}
|
|
5146
|
+
if (typeof sourceInteractor.handColor === "string") {
|
|
5147
|
+
interactor.handColor = sourceInteractor.handColor;
|
|
5148
|
+
}
|
|
5149
|
+
if (typeof sourceInteractor.handOpacity === "number") {
|
|
5150
|
+
interactor.handOpacity = sourceInteractor.handOpacity;
|
|
5151
|
+
}
|
|
5152
|
+
if (typeof sourceInteractor.handEmissiveIntensity === "number") {
|
|
5153
|
+
interactor.handEmissiveIntensity = sourceInteractor.handEmissiveIntensity;
|
|
5154
|
+
}
|
|
5155
|
+
if (typeof sourceInteractor.showRay === "boolean") {
|
|
5156
|
+
interactor.showRay = sourceInteractor.showRay;
|
|
5157
|
+
}
|
|
5158
|
+
}
|
|
5159
|
+
}
|
|
5160
|
+
{
|
|
5161
|
+
const sourceSide = asRecord3(
|
|
5162
|
+
values.xrInteractorSideValues.get(sourceEntityId)
|
|
5163
|
+
);
|
|
5164
|
+
if (sourceSide && typeof sourceSide.side === "string") {
|
|
5165
|
+
const existing = world.getComponent(
|
|
5166
|
+
targetEntityId,
|
|
5167
|
+
XRInteractorSideComponent
|
|
5168
|
+
);
|
|
5169
|
+
if (existing) {
|
|
5170
|
+
existing.side = sourceSide.side;
|
|
5171
|
+
} else {
|
|
5172
|
+
world.addComponent(
|
|
5173
|
+
targetEntityId,
|
|
5174
|
+
new XRInteractorSideComponent({
|
|
5175
|
+
side: sourceSide.side
|
|
5176
|
+
})
|
|
5177
|
+
);
|
|
5178
|
+
}
|
|
5179
|
+
}
|
|
5180
|
+
}
|
|
5181
|
+
{
|
|
5182
|
+
const sourceRigOrigin = asRecord3(values.xrRigValues.get(sourceEntityId));
|
|
5183
|
+
const rigForOrigin = world.getComponent(targetEntityId, XRRigComponent);
|
|
5184
|
+
if (sourceRigOrigin && rigForOrigin && typeof sourceRigOrigin.trackingOrigin === "string") {
|
|
5185
|
+
rigForOrigin.trackingOrigin = sourceRigOrigin.trackingOrigin;
|
|
5186
|
+
}
|
|
5187
|
+
}
|
|
5102
5188
|
{
|
|
5103
5189
|
const imComponent = world.getComponent(targetEntityId, InstancedMeshComponent);
|
|
5104
5190
|
if (imComponent && imComponent.instanceEntityIds.length > 0) {
|
|
@@ -5688,6 +5774,7 @@ var HYDRATABLE_ENTITY_KINDS = /* @__PURE__ */ new Set([
|
|
|
5688
5774
|
"instanceSlot",
|
|
5689
5775
|
"scriptObject",
|
|
5690
5776
|
"xrRig",
|
|
5777
|
+
"xrInteractor",
|
|
5691
5778
|
"teleportArea"
|
|
5692
5779
|
]);
|
|
5693
5780
|
|
|
@@ -7698,6 +7785,9 @@ var _XRInputSystem = class _XRInputSystem {
|
|
|
7698
7785
|
state.hasAxes = false;
|
|
7699
7786
|
state.buttons = [];
|
|
7700
7787
|
}
|
|
7788
|
+
}
|
|
7789
|
+
for (const record of this.records.values()) {
|
|
7790
|
+
const state = record.state;
|
|
7701
7791
|
if (record.pendingSelectStart) {
|
|
7702
7792
|
state.select = true;
|
|
7703
7793
|
state.selectStart = true;
|
|
@@ -7933,6 +8023,10 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
|
|
|
7933
8023
|
/** Curve up to the landing point, for the visual system. Empty when not aiming. */
|
|
7934
8024
|
this.aimCurve = [];
|
|
7935
8025
|
this.landingPoint = null;
|
|
8026
|
+
/** True when the current aim lands somewhere the player may actually stand. */
|
|
8027
|
+
this.aimValid = false;
|
|
8028
|
+
/** Sources currently aiming via thumbstick (hysteresis, so it doesn't chatter). */
|
|
8029
|
+
this.stickAiming = /* @__PURE__ */ new WeakSet();
|
|
7936
8030
|
/** True once we've tried to self-heal camera parenting for this session. */
|
|
7937
8031
|
this.parentingChecked = false;
|
|
7938
8032
|
this.inputSystem = inputSystem;
|
|
@@ -7940,6 +8034,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
|
|
|
7940
8034
|
update(world, deltaTime) {
|
|
7941
8035
|
this.aimCurve = [];
|
|
7942
8036
|
this.landingPoint = null;
|
|
8037
|
+
this.aimValid = false;
|
|
7943
8038
|
if (!world.isXrPresenting()) {
|
|
7944
8039
|
this.parentingChecked = false;
|
|
7945
8040
|
return;
|
|
@@ -7947,6 +8042,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
|
|
|
7947
8042
|
if (!this.parentingChecked) {
|
|
7948
8043
|
this.parentingChecked = true;
|
|
7949
8044
|
this.ensureCameraUnderRig(world);
|
|
8045
|
+
this.warnOnIncompleteSetup(world);
|
|
7950
8046
|
}
|
|
7951
8047
|
const rigEntityId = world.query(XRRigComponent, TransformComponent)[0];
|
|
7952
8048
|
if (typeof rigEntityId !== "number") return;
|
|
@@ -7964,6 +8060,26 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
|
|
|
7964
8060
|
this.applySmoothMove(world, rig, rigTransform, inputs, deltaTime);
|
|
7965
8061
|
}
|
|
7966
8062
|
}
|
|
8063
|
+
/**
|
|
8064
|
+
* Name what is missing, once per session.
|
|
8065
|
+
*
|
|
8066
|
+
* Inside a headset there is no console and no inspector, so a scene that
|
|
8067
|
+
* simply cannot move gives the author nothing to go on. These messages are
|
|
8068
|
+
* visible over remote debugging and say exactly which entity to add.
|
|
8069
|
+
*/
|
|
8070
|
+
warnOnIncompleteSetup(world) {
|
|
8071
|
+
if (typeof world.query(XRRigComponent)[0] !== "number") {
|
|
8072
|
+
console.warn(
|
|
8073
|
+
"[XRLocomotion] No XR Rig in this scene \u2014 the player cannot move. Add one via Add Object \u2192 VR \u2192 XR Rig (the scene camera is parented to it automatically)."
|
|
8074
|
+
);
|
|
8075
|
+
return;
|
|
8076
|
+
}
|
|
8077
|
+
if (world.query(VRTeleportAreaComponent).length === 0) {
|
|
8078
|
+
console.warn(
|
|
8079
|
+
"[XRLocomotion] No Teleport Area in this scene \u2014 teleport has nowhere to land. Add one via Add Object \u2192 VR \u2192 Teleport Area, or add a Teleport Area component to an existing floor."
|
|
8080
|
+
);
|
|
8081
|
+
}
|
|
8082
|
+
}
|
|
7967
8083
|
/**
|
|
7968
8084
|
* Guarantee the render camera is a CHILD of the rig.
|
|
7969
8085
|
*
|
|
@@ -8031,29 +8147,60 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
|
|
|
8031
8147
|
}
|
|
8032
8148
|
applyTeleport(world, transform, inputs) {
|
|
8033
8149
|
const targets = this.collectTeleportTargets(world);
|
|
8034
|
-
if (targets.length === 0) return;
|
|
8035
8150
|
for (const input of inputs) {
|
|
8036
|
-
|
|
8151
|
+
const source = input;
|
|
8152
|
+
const stick = this.readStickAim(input, source);
|
|
8153
|
+
const aiming = input.select || stick.aiming;
|
|
8154
|
+
const commit = input.selectEnd || stick.released;
|
|
8155
|
+
if (!aiming && !commit) continue;
|
|
8037
8156
|
const points = sampleTeleportArc(
|
|
8038
8157
|
input.rayOrigin,
|
|
8039
8158
|
input.rayDirection,
|
|
8040
8159
|
this.arcPoints
|
|
8041
8160
|
);
|
|
8042
8161
|
const hit = this.raycastArc(points, targets);
|
|
8043
|
-
|
|
8044
|
-
this.landingPoint = hit.point;
|
|
8045
|
-
this.
|
|
8046
|
-
if (
|
|
8162
|
+
this.aimCurve = hit ? points.slice(0, hit.segmentIndex + 1).concat(hit.point) : points.slice();
|
|
8163
|
+
this.landingPoint = hit ? hit.point : null;
|
|
8164
|
+
this.aimValid = hit !== null;
|
|
8165
|
+
if (commit && hit) {
|
|
8047
8166
|
transform.position.x = hit.point.x;
|
|
8048
8167
|
transform.position.y = hit.point.y;
|
|
8049
8168
|
transform.position.z = hit.point.z;
|
|
8050
8169
|
this.aimCurve = [];
|
|
8051
8170
|
this.landingPoint = null;
|
|
8171
|
+
this.aimValid = false;
|
|
8172
|
+
} else if (commit) {
|
|
8173
|
+
this.aimCurve = [];
|
|
8174
|
+
this.aimValid = false;
|
|
8052
8175
|
}
|
|
8053
8176
|
}
|
|
8054
8177
|
}
|
|
8178
|
+
/**
|
|
8179
|
+
* Thumbstick-forward aiming with hysteresis.
|
|
8180
|
+
*
|
|
8181
|
+
* Engaging at 0.6 and releasing at 0.3 stops a stick resting near the
|
|
8182
|
+
* threshold from flickering the arc on and off — and the release edge is what
|
|
8183
|
+
* commits the teleport, so chatter there would teleport the player randomly.
|
|
8184
|
+
*/
|
|
8185
|
+
readStickAim(input, source) {
|
|
8186
|
+
if (!input.hasAxes || input.axes.length < 4) {
|
|
8187
|
+
return { aiming: false, released: false };
|
|
8188
|
+
}
|
|
8189
|
+
const forward = -(input.axes[3] ?? 0);
|
|
8190
|
+
const wasAiming = this.stickAiming.has(source);
|
|
8191
|
+
if (!wasAiming && forward > 0.6) {
|
|
8192
|
+
this.stickAiming.add(source);
|
|
8193
|
+
return { aiming: true, released: false };
|
|
8194
|
+
}
|
|
8195
|
+
if (wasAiming && forward < 0.3) {
|
|
8196
|
+
this.stickAiming.delete(source);
|
|
8197
|
+
return { aiming: false, released: true };
|
|
8198
|
+
}
|
|
8199
|
+
return { aiming: wasAiming, released: false };
|
|
8200
|
+
}
|
|
8055
8201
|
/** Walk the arc segment-by-segment, returning the first surface hit. */
|
|
8056
8202
|
raycastArc(points, targets) {
|
|
8203
|
+
if (targets.length === 0) return null;
|
|
8057
8204
|
for (let i = 1; i < points.length; i += 1) {
|
|
8058
8205
|
const from = points[i - 1];
|
|
8059
8206
|
const to = points[i];
|
|
@@ -8084,6 +8231,10 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
|
|
|
8084
8231
|
getAimCurve() {
|
|
8085
8232
|
return this.aimCurve;
|
|
8086
8233
|
}
|
|
8234
|
+
/** True when the aim lands on a valid teleport area. */
|
|
8235
|
+
isAimValid() {
|
|
8236
|
+
return this.aimValid;
|
|
8237
|
+
}
|
|
8087
8238
|
/** Where the arc currently lands, or null when not aiming at a valid area. */
|
|
8088
8239
|
getLandingPoint() {
|
|
8089
8240
|
return this.landingPoint;
|
|
@@ -8126,6 +8277,8 @@ var _XRSessionSystem = class _XRSessionSystem {
|
|
|
8126
8277
|
constructor(inputSystem) {
|
|
8127
8278
|
this.presenting = false;
|
|
8128
8279
|
this.capabilities = NO_XR_CAPABILITIES;
|
|
8280
|
+
/** Y compensation applied when a floor-relative space was requested but not granted. */
|
|
8281
|
+
this.appliedOriginOffset = 0;
|
|
8129
8282
|
this.inputSystem = inputSystem ?? null;
|
|
8130
8283
|
}
|
|
8131
8284
|
update(world) {
|
|
@@ -8149,11 +8302,59 @@ var _XRSessionSystem = class _XRSessionSystem {
|
|
|
8149
8302
|
if (session && this.inputSystem) {
|
|
8150
8303
|
this.inputSystem.attachSession(session);
|
|
8151
8304
|
}
|
|
8305
|
+
this.applyTrackingOrigin(world, session ?? null);
|
|
8152
8306
|
this.forEachBehaviour(world, (behaviour) => {
|
|
8153
8307
|
behaviour.onXRSessionStart(this.capabilities);
|
|
8154
8308
|
});
|
|
8155
8309
|
}
|
|
8310
|
+
/**
|
|
8311
|
+
* Apply the rig's authored tracking origin, and compensate when the device
|
|
8312
|
+
* cannot honour it.
|
|
8313
|
+
*
|
|
8314
|
+
* `floor` (`local-floor`) puts Y=0 on the user's real floor, which is what a
|
|
8315
|
+
* scene authored standing on the ground expects. `eye` (`local`) puts Y=0 at
|
|
8316
|
+
* the headset, for seated experiences.
|
|
8317
|
+
*
|
|
8318
|
+
* The compensation matters: `local-floor` is only ever REQUESTED as an
|
|
8319
|
+
* optional feature (requiring it would make `requestSession` reject outright
|
|
8320
|
+
* on devices that lack it). When it isn't granted, the runtime silently falls
|
|
8321
|
+
* back to `local` — the origin jumps to head height and a floor-authored
|
|
8322
|
+
* scene swallows the user, which is exactly the "I'm standing under the
|
|
8323
|
+
* building" symptom. Offsetting the rig down by the eye height restores the
|
|
8324
|
+
* intended floor relationship instead of leaving it broken.
|
|
8325
|
+
*/
|
|
8326
|
+
applyTrackingOrigin(world, session) {
|
|
8327
|
+
const rigEntityId = world.query(XRRigComponent, TransformComponent)[0];
|
|
8328
|
+
if (typeof rigEntityId !== "number") return;
|
|
8329
|
+
const rig = world.getComponent(rigEntityId, XRRigComponent);
|
|
8330
|
+
const transform = world.getComponent(rigEntityId, TransformComponent);
|
|
8331
|
+
if (!rig || !transform) return;
|
|
8332
|
+
const renderer = world.getRenderer();
|
|
8333
|
+
const wantsFloor = rig.trackingOrigin === "floor";
|
|
8334
|
+
try {
|
|
8335
|
+
renderer.xr.setReferenceSpaceType(wantsFloor ? "local-floor" : "local");
|
|
8336
|
+
} catch {
|
|
8337
|
+
}
|
|
8338
|
+
const granted = session?.enabledFeatures;
|
|
8339
|
+
const floorGranted = Array.isArray(granted) ? granted.includes("local-floor") : (
|
|
8340
|
+
// No report at all: trust the request, since a wrong offset is worse
|
|
8341
|
+
// than none on devices that did honour it.
|
|
8342
|
+
true
|
|
8343
|
+
);
|
|
8344
|
+
this.appliedOriginOffset = wantsFloor && !floorGranted ? rig.fallbackEyeHeight : 0;
|
|
8345
|
+
if (this.appliedOriginOffset !== 0) {
|
|
8346
|
+
transform.position.y -= this.appliedOriginOffset;
|
|
8347
|
+
}
|
|
8348
|
+
}
|
|
8156
8349
|
handleSessionEnd(world) {
|
|
8350
|
+
if (this.appliedOriginOffset !== 0) {
|
|
8351
|
+
const rigEntityId = world.query(XRRigComponent, TransformComponent)[0];
|
|
8352
|
+
if (typeof rigEntityId === "number") {
|
|
8353
|
+
const transform = world.getComponent(rigEntityId, TransformComponent);
|
|
8354
|
+
if (transform) transform.position.y += this.appliedOriginOffset;
|
|
8355
|
+
}
|
|
8356
|
+
this.appliedOriginOffset = 0;
|
|
8357
|
+
}
|
|
8157
8358
|
this.capabilities = NO_XR_CAPABILITIES;
|
|
8158
8359
|
this.inputSystem?.detachSession();
|
|
8159
8360
|
this.forEachBehaviour(world, (behaviour) => {
|
|
@@ -8247,17 +8448,74 @@ var _VRTeleportAreaSystem = class _VRTeleportAreaSystem {
|
|
|
8247
8448
|
};
|
|
8248
8449
|
__name(_VRTeleportAreaSystem, "VRTeleportAreaSystem");
|
|
8249
8450
|
var VRTeleportAreaSystem = _VRTeleportAreaSystem;
|
|
8451
|
+
function createHandMaterial(preset, options) {
|
|
8452
|
+
const color = new THREE14__namespace.Color(options.color);
|
|
8453
|
+
switch (preset) {
|
|
8454
|
+
case "ghost":
|
|
8455
|
+
return new THREE14__namespace.MeshBasicMaterial({
|
|
8456
|
+
color,
|
|
8457
|
+
transparent: true,
|
|
8458
|
+
opacity: Math.min(1, Math.max(0.05, options.opacity * 0.45)),
|
|
8459
|
+
depthWrite: false,
|
|
8460
|
+
side: THREE14__namespace.DoubleSide
|
|
8461
|
+
});
|
|
8462
|
+
case "neon":
|
|
8463
|
+
return new THREE14__namespace.MeshStandardMaterial({
|
|
8464
|
+
color,
|
|
8465
|
+
emissive: color,
|
|
8466
|
+
emissiveIntensity: Math.max(0.5, options.emissiveIntensity || 2),
|
|
8467
|
+
roughness: 0.35,
|
|
8468
|
+
metalness: 0,
|
|
8469
|
+
transparent: options.opacity < 1,
|
|
8470
|
+
opacity: options.opacity
|
|
8471
|
+
});
|
|
8472
|
+
case "skeleton":
|
|
8473
|
+
return new THREE14__namespace.MeshBasicMaterial({
|
|
8474
|
+
color,
|
|
8475
|
+
wireframe: true,
|
|
8476
|
+
transparent: true,
|
|
8477
|
+
opacity: options.opacity
|
|
8478
|
+
});
|
|
8479
|
+
case "custom":
|
|
8480
|
+
case "default":
|
|
8481
|
+
return new THREE14__namespace.MeshStandardMaterial({
|
|
8482
|
+
color,
|
|
8483
|
+
roughness: 0.85,
|
|
8484
|
+
metalness: 0,
|
|
8485
|
+
emissive: color,
|
|
8486
|
+
emissiveIntensity: Math.max(0, options.emissiveIntensity),
|
|
8487
|
+
transparent: options.opacity < 1,
|
|
8488
|
+
opacity: options.opacity,
|
|
8489
|
+
side: THREE14__namespace.DoubleSide
|
|
8490
|
+
});
|
|
8491
|
+
default: {
|
|
8492
|
+
return new THREE14__namespace.MeshStandardMaterial({ color });
|
|
8493
|
+
}
|
|
8494
|
+
}
|
|
8495
|
+
}
|
|
8496
|
+
__name(createHandMaterial, "createHandMaterial");
|
|
8497
|
+
function applyHandMaterial(root, material) {
|
|
8498
|
+
root.traverse((child) => {
|
|
8499
|
+
const mesh = child;
|
|
8500
|
+
if (!mesh.isMesh) return;
|
|
8501
|
+
mesh.material = material;
|
|
8502
|
+
});
|
|
8503
|
+
}
|
|
8504
|
+
__name(applyHandMaterial, "applyHandMaterial");
|
|
8505
|
+
var SLOTS = [
|
|
8506
|
+
{ index: 0, side: "left" },
|
|
8507
|
+
{ index: 1, side: "right" }
|
|
8508
|
+
];
|
|
8250
8509
|
var _XRControllerVisualSystem = class _XRControllerVisualSystem {
|
|
8251
8510
|
constructor(options) {
|
|
8252
8511
|
this.controllerModelFactory = new XRControllerModelFactory_js.XRControllerModelFactory();
|
|
8253
8512
|
this.handModelFactory = new XRHandModelFactory_js.XRHandModelFactory();
|
|
8254
8513
|
this.group = new THREE14__namespace.Group();
|
|
8255
|
-
this.
|
|
8514
|
+
this.slots = /* @__PURE__ */ new Map();
|
|
8256
8515
|
this.attachedTo = null;
|
|
8257
|
-
/**
|
|
8258
|
-
this.
|
|
8259
|
-
this.
|
|
8260
|
-
this.slots = options?.slots ?? 2;
|
|
8516
|
+
/** Last applied preset signature, so materials rebuild only on real change. */
|
|
8517
|
+
this.handStyleSignature = "";
|
|
8518
|
+
this.handMaterial = null;
|
|
8261
8519
|
this.interactionSystem = options?.interactionSystem ?? null;
|
|
8262
8520
|
this.group.name = "XRControllerVisuals";
|
|
8263
8521
|
this.group.userData.ecsPreviewHidden = true;
|
|
@@ -8266,39 +8524,111 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
|
|
|
8266
8524
|
const renderer = world.getRenderer();
|
|
8267
8525
|
if (!renderer.xr?.enabled) return;
|
|
8268
8526
|
this.ensureAttached(world);
|
|
8269
|
-
this.
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8527
|
+
this.ensureSlots(world);
|
|
8528
|
+
const interactor = this.resolveInteractor(world);
|
|
8529
|
+
const type = interactor?.interactorType ?? "both";
|
|
8530
|
+
const showControllers = type === "controllers" || type === "both";
|
|
8531
|
+
const showHands = type === "hands" || type === "both";
|
|
8532
|
+
this.syncHandMaterial(interactor);
|
|
8533
|
+
for (const { index, side } of SLOTS) {
|
|
8534
|
+
const visuals = this.slots.get(index);
|
|
8535
|
+
if (!visuals) continue;
|
|
8536
|
+
visuals.controllerModel.visible = showControllers;
|
|
8537
|
+
visuals.handModel.visible = showHands;
|
|
8538
|
+
visuals.ray.visible = this.shouldShowRay(interactor);
|
|
8539
|
+
if (showHands && this.handMaterial) {
|
|
8540
|
+
applyHandMaterial(visuals.handModel, this.handMaterial);
|
|
8541
|
+
}
|
|
8542
|
+
this.syncCustomContent(world, visuals, side);
|
|
8543
|
+
}
|
|
8544
|
+
}
|
|
8545
|
+
/** The scene's interactor config, if one was authored. */
|
|
8546
|
+
resolveInteractor(world) {
|
|
8547
|
+
const entityId = world.query(XRInteractorComponent)[0];
|
|
8548
|
+
if (typeof entityId !== "number") return null;
|
|
8549
|
+
return world.getComponent(entityId, XRInteractorComponent) ?? null;
|
|
8550
|
+
}
|
|
8551
|
+
/**
|
|
8552
|
+
* Re-parent the author's left/right interactor content onto the tracked grip
|
|
8553
|
+
* space so it follows the hand.
|
|
8554
|
+
*
|
|
8555
|
+
* `ObjectManagementSystem` reconciles ECS objects back under their ParentId
|
|
8556
|
+
* every frame, so this re-attach has to be re-asserted — the same coexistence
|
|
8557
|
+
* dance the AR anchor system performs.
|
|
8558
|
+
*/
|
|
8559
|
+
syncCustomContent(world, visuals, side) {
|
|
8560
|
+
let content = null;
|
|
8561
|
+
for (const entityId of world.query(
|
|
8562
|
+
XRInteractorSideComponent,
|
|
8563
|
+
Object3DRef
|
|
8564
|
+
)) {
|
|
8565
|
+
const slot = world.getComponent(entityId, XRInteractorSideComponent);
|
|
8566
|
+
if (slot?.side !== side) continue;
|
|
8567
|
+
content = world.getComponent(entityId, Object3DRef)?.object ?? null;
|
|
8568
|
+
break;
|
|
8569
|
+
}
|
|
8570
|
+
if (!content) {
|
|
8571
|
+
visuals.attachedCustom = null;
|
|
8572
|
+
return;
|
|
8573
|
+
}
|
|
8574
|
+
if (content.parent !== visuals.grip) {
|
|
8575
|
+
visuals.grip.add(content);
|
|
8576
|
+
}
|
|
8577
|
+
visuals.attachedCustom = content;
|
|
8578
|
+
}
|
|
8579
|
+
/** Rebuild the hand material only when the authored style actually changes. */
|
|
8580
|
+
syncHandMaterial(interactor) {
|
|
8581
|
+
const preset = interactor?.handPreset ?? "default";
|
|
8582
|
+
const color = interactor?.handColor ?? "#e8b48a";
|
|
8583
|
+
const opacity = interactor?.handOpacity ?? 1;
|
|
8584
|
+
const emissive = interactor?.handEmissiveIntensity ?? 0;
|
|
8585
|
+
const signature = `${preset}:${color}:${opacity}:${emissive}`;
|
|
8586
|
+
if (signature === this.handStyleSignature && this.handMaterial) return;
|
|
8587
|
+
this.handStyleSignature = signature;
|
|
8588
|
+
this.handMaterial?.dispose();
|
|
8589
|
+
this.handMaterial = createHandMaterial(preset, {
|
|
8590
|
+
color,
|
|
8591
|
+
opacity,
|
|
8592
|
+
emissiveIntensity: emissive
|
|
8593
|
+
});
|
|
8594
|
+
}
|
|
8595
|
+
/**
|
|
8596
|
+
* The ray is meaningful only when it points at something. Authors can turn it
|
|
8597
|
+
* off entirely; otherwise it appears on hover/grab so it isn't permanent
|
|
8598
|
+
* clutter implying the whole scene is clickable.
|
|
8599
|
+
*/
|
|
8600
|
+
shouldShowRay(interactor) {
|
|
8601
|
+
if (interactor && !interactor.showRay) return false;
|
|
8602
|
+
if (!this.interactionSystem) return true;
|
|
8603
|
+
return this.interactionSystem.getHoveredEntityId() !== null || this.interactionSystem.isGrabbing();
|
|
8604
|
+
}
|
|
8605
|
+
/** Lazily build per-slot visuals; the device reports profiles late. */
|
|
8606
|
+
ensureSlots(world) {
|
|
8607
|
+
const renderer = world.getRenderer();
|
|
8608
|
+
for (const { index } of SLOTS) {
|
|
8609
|
+
if (this.slots.has(index)) continue;
|
|
8273
8610
|
const controller = renderer.xr.getController(index);
|
|
8274
8611
|
const grip = renderer.xr.getControllerGrip(index);
|
|
8275
8612
|
const hand = renderer.xr.getHand(index);
|
|
8276
8613
|
if (!controller || !grip || !hand) continue;
|
|
8277
8614
|
const ray = this.createRay();
|
|
8278
|
-
this.rays.push(ray);
|
|
8279
8615
|
controller.add(ray);
|
|
8280
|
-
this.
|
|
8281
|
-
grip.add(
|
|
8282
|
-
this.
|
|
8283
|
-
hand.add(
|
|
8284
|
-
this.group.add(hand);
|
|
8285
|
-
this.
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8616
|
+
const controllerModel = this.controllerModelFactory.createControllerModel(grip);
|
|
8617
|
+
grip.add(controllerModel);
|
|
8618
|
+
const handModel = this.handModelFactory.createHandModel(hand, "mesh");
|
|
8619
|
+
hand.add(handModel);
|
|
8620
|
+
this.group.add(controller, grip, hand);
|
|
8621
|
+
this.slots.set(index, {
|
|
8622
|
+
controller,
|
|
8623
|
+
grip,
|
|
8624
|
+
hand,
|
|
8625
|
+
ray,
|
|
8626
|
+
controllerModel,
|
|
8627
|
+
handModel,
|
|
8628
|
+
attachedCustom: null
|
|
8629
|
+
});
|
|
8289
8630
|
}
|
|
8290
8631
|
}
|
|
8291
|
-
/**
|
|
8292
|
-
* Show the aim ray only when it is meaningful: hovering something
|
|
8293
|
-
* interactive, or mid-grab. A permanently-drawn ray is visual noise, and it
|
|
8294
|
-
* implies everything is clickable when most of the scene is not. Teleport
|
|
8295
|
-
* aiming has its own arc, so the straight ray stays hidden for that.
|
|
8296
|
-
*/
|
|
8297
|
-
updateRayVisibility() {
|
|
8298
|
-
if (!this.interactionSystem) return;
|
|
8299
|
-
const active = this.interactionSystem.getHoveredEntityId() !== null || this.interactionSystem.isGrabbing();
|
|
8300
|
-
for (const ray of this.rays) ray.visible = active;
|
|
8301
|
-
}
|
|
8302
8632
|
/**
|
|
8303
8633
|
* Parent the visuals to the XR rig when there is one, so locomotion moves the
|
|
8304
8634
|
* hands with the player. Falls back to the scene root.
|
|
@@ -8324,7 +8654,7 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
|
|
|
8324
8654
|
const line = new THREE14__namespace.Line(geometry, material);
|
|
8325
8655
|
line.name = "XRPointerRay";
|
|
8326
8656
|
line.renderOrder = 999;
|
|
8327
|
-
line.visible =
|
|
8657
|
+
line.visible = false;
|
|
8328
8658
|
return line;
|
|
8329
8659
|
}
|
|
8330
8660
|
dispose() {
|
|
@@ -8340,9 +8670,10 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
|
|
|
8340
8670
|
}
|
|
8341
8671
|
});
|
|
8342
8672
|
this.group.clear();
|
|
8343
|
-
this.
|
|
8344
|
-
this.
|
|
8345
|
-
this.
|
|
8673
|
+
this.slots.clear();
|
|
8674
|
+
this.handMaterial?.dispose();
|
|
8675
|
+
this.handMaterial = null;
|
|
8676
|
+
this.handStyleSignature = "";
|
|
8346
8677
|
this.attachedTo = null;
|
|
8347
8678
|
}
|
|
8348
8679
|
};
|
|
@@ -8350,6 +8681,7 @@ __name(_XRControllerVisualSystem, "XRControllerVisualSystem");
|
|
|
8350
8681
|
var XRControllerVisualSystem = _XRControllerVisualSystem;
|
|
8351
8682
|
var MARKER_RADIUS = 0.28;
|
|
8352
8683
|
var ACCENT = new THREE14__namespace.Color("#4da3ff");
|
|
8684
|
+
var BLOCKED = new THREE14__namespace.Color("#ff5a5a");
|
|
8353
8685
|
var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
|
|
8354
8686
|
constructor(locomotionSystem) {
|
|
8355
8687
|
this.group = new THREE14__namespace.Group();
|
|
@@ -8380,16 +8712,22 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
|
|
|
8380
8712
|
}
|
|
8381
8713
|
const curve = this.locomotionSystem.getAimCurve();
|
|
8382
8714
|
const landing = this.locomotionSystem.getLandingPoint();
|
|
8383
|
-
|
|
8715
|
+
const valid = this.locomotionSystem.isAimValid();
|
|
8716
|
+
if (curve.length < 2) {
|
|
8384
8717
|
this.setVisible(false);
|
|
8385
8718
|
return;
|
|
8386
8719
|
}
|
|
8387
|
-
this.updateArcGeometry(curve);
|
|
8388
|
-
|
|
8389
|
-
|
|
8720
|
+
this.updateArcGeometry(curve, valid ? ACCENT : BLOCKED);
|
|
8721
|
+
if (valid && landing) {
|
|
8722
|
+
this.marker.position.copy(landing);
|
|
8723
|
+
this.marker.position.y += 0.01;
|
|
8724
|
+
this.marker.visible = true;
|
|
8725
|
+
} else {
|
|
8726
|
+
this.marker.visible = false;
|
|
8727
|
+
}
|
|
8390
8728
|
this.setVisible(true);
|
|
8391
8729
|
}
|
|
8392
|
-
updateArcGeometry(curve) {
|
|
8730
|
+
updateArcGeometry(curve, tint) {
|
|
8393
8731
|
const count = curve.length;
|
|
8394
8732
|
const positions = new Float32Array(count * 3);
|
|
8395
8733
|
const colors = new Float32Array(count * 3);
|
|
@@ -8400,9 +8738,9 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
|
|
|
8400
8738
|
positions[i * 3 + 2] = point.z;
|
|
8401
8739
|
const t = count > 1 ? i / (count - 1) : 0;
|
|
8402
8740
|
const fade = 1 - t * 0.75;
|
|
8403
|
-
colors[i * 3] =
|
|
8404
|
-
colors[i * 3 + 1] =
|
|
8405
|
-
colors[i * 3 + 2] =
|
|
8741
|
+
colors[i * 3] = tint.r * fade;
|
|
8742
|
+
colors[i * 3 + 1] = tint.g * fade;
|
|
8743
|
+
colors[i * 3 + 2] = tint.b * fade;
|
|
8406
8744
|
}
|
|
8407
8745
|
this.arcGeometry.setAttribute(
|
|
8408
8746
|
"position",
|