@relu-interactives/spatial-ecs 0.4.5 → 0.5.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-ln4Q7Eot.d.cts → System-V_mv07le.d.cts} +2 -2
- package/dist/{System-ln4Q7Eot.d.ts → System-V_mv07le.d.ts} +2 -2
- package/dist/{XRHandPose-CxPv-LBo.d.cts → XRHandPose-CQTLGxFC.d.cts} +218 -2
- package/dist/{XRHandPose-BImIa4ZS.d.ts → XRHandPose-CU3vNFuS.d.ts} +218 -2
- package/dist/{chunk-4AK4I3QF.js → chunk-BUEKOSR5.js} +102 -3
- package/dist/chunk-BUEKOSR5.js.map +1 -0
- package/dist/{chunk-QIIWFP3V.js → chunk-Q52ADLF7.js} +1255 -18
- package/dist/chunk-Q52ADLF7.js.map +1 -0
- package/dist/{chunk-SGADE55G.js → chunk-RXBLOO5B.js} +249 -90
- package/dist/chunk-RXBLOO5B.js.map +1 -0
- package/dist/{chunk-4DWZK4X4.js → chunk-WJD2VKEH.js} +3 -3
- package/dist/chunk-WJD2VKEH.js.map +1 -0
- package/dist/components/index.cjs +99 -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/spatial-ui.md +165 -0
- package/dist/factories/index.cjs +372 -114
- package/dist/factories/index.cjs.map +1 -1
- package/dist/factories/index.d.cts +13 -3
- package/dist/factories/index.d.ts +13 -3
- package/dist/factories/index.js +3 -3
- package/dist/{index-CuGqxuk-.d.ts → index-C3ik3sA1.d.ts} +16 -3
- package/dist/{index-BphKHaV-.d.ts → index-D2Jt_JzH.d.ts} +156 -3
- package/dist/{index-CH7qMsQT.d.cts → index-DaG03vyN.d.cts} +156 -3
- package/dist/{index-CQsHUez3.d.cts → index-Dlr-7-4m.d.cts} +16 -3
- package/dist/index.cjs +1864 -360
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +42 -9
- package/dist/index.d.ts +42 -9
- package/dist/index.js +10 -8
- package/dist/index.js.map +1 -1
- package/dist/systems/index.cjs +1346 -13
- 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-4AK4I3QF.js.map +0 -1
- package/dist/chunk-4DWZK4X4.js.map +0 -1
- package/dist/chunk-QIIWFP3V.js.map +0 -1
- package/dist/chunk-SGADE55G.js.map +0 -1
package/dist/systems/index.cjs
CHANGED
|
@@ -2187,6 +2187,100 @@ var _XRAnchorComponent = class _XRAnchorComponent extends Component {
|
|
|
2187
2187
|
__name(_XRAnchorComponent, "XRAnchorComponent");
|
|
2188
2188
|
var XRAnchorComponent = _XRAnchorComponent;
|
|
2189
2189
|
|
|
2190
|
+
// core/components/UIPanel.ts
|
|
2191
|
+
var _UIPanelComponent = class _UIPanelComponent extends Component {
|
|
2192
|
+
constructor(options) {
|
|
2193
|
+
super();
|
|
2194
|
+
this.width = options?.width ?? 0.6;
|
|
2195
|
+
this.height = options?.height ?? 0.4;
|
|
2196
|
+
this.backgroundColor = options?.backgroundColor ?? "#1c1c1c";
|
|
2197
|
+
this.backgroundOpacity = options?.backgroundOpacity ?? 0.92;
|
|
2198
|
+
this.cornerRadius = options?.cornerRadius ?? 0.02;
|
|
2199
|
+
this.borderWidth = options?.borderWidth ?? 0;
|
|
2200
|
+
this.borderColor = options?.borderColor ?? "#333333";
|
|
2201
|
+
this.pixelDensity = options?.pixelDensity ?? 1024;
|
|
2202
|
+
this.follow = options?.follow ?? "world";
|
|
2203
|
+
}
|
|
2204
|
+
};
|
|
2205
|
+
__name(_UIPanelComponent, "UIPanelComponent");
|
|
2206
|
+
var UIPanelComponent = _UIPanelComponent;
|
|
2207
|
+
|
|
2208
|
+
// core/components/UIText.ts
|
|
2209
|
+
var _UITextComponent = class _UITextComponent extends Component {
|
|
2210
|
+
constructor(options) {
|
|
2211
|
+
super();
|
|
2212
|
+
this.text = options?.text ?? "Text";
|
|
2213
|
+
this.width = options?.width ?? 0.5;
|
|
2214
|
+
this.height = options?.height ?? 0.12;
|
|
2215
|
+
this.fontSize = options?.fontSize ?? 0.035;
|
|
2216
|
+
this.fontWeight = options?.fontWeight ?? 500;
|
|
2217
|
+
this.color = options?.color ?? "#ffffff";
|
|
2218
|
+
this.align = options?.align ?? "center";
|
|
2219
|
+
this.valign = options?.valign ?? "middle";
|
|
2220
|
+
this.lineHeight = options?.lineHeight ?? 1.25;
|
|
2221
|
+
this.padding = options?.padding ?? 0.01;
|
|
2222
|
+
}
|
|
2223
|
+
};
|
|
2224
|
+
__name(_UITextComponent, "UITextComponent");
|
|
2225
|
+
var UITextComponent = _UITextComponent;
|
|
2226
|
+
|
|
2227
|
+
// core/components/UIButton.ts
|
|
2228
|
+
var _UIButtonComponent = class _UIButtonComponent extends Component {
|
|
2229
|
+
constructor(options) {
|
|
2230
|
+
super();
|
|
2231
|
+
this.label = options?.label ?? "Button";
|
|
2232
|
+
this.actionId = options?.actionId ?? "";
|
|
2233
|
+
this.width = options?.width ?? 0.3;
|
|
2234
|
+
this.height = options?.height ?? 0.1;
|
|
2235
|
+
this.fontSize = options?.fontSize ?? 0.03;
|
|
2236
|
+
this.fontWeight = options?.fontWeight ?? 600;
|
|
2237
|
+
this.textColor = options?.textColor ?? "#ffffff";
|
|
2238
|
+
this.backgroundColor = options?.backgroundColor ?? "#2a2a2a";
|
|
2239
|
+
this.backgroundOpacity = options?.backgroundOpacity ?? 1;
|
|
2240
|
+
this.cornerRadius = options?.cornerRadius ?? 0.02;
|
|
2241
|
+
this.borderWidth = options?.borderWidth ?? 2e-3;
|
|
2242
|
+
this.borderColor = options?.borderColor ?? "#3a3a3a";
|
|
2243
|
+
}
|
|
2244
|
+
};
|
|
2245
|
+
__name(_UIButtonComponent, "UIButtonComponent");
|
|
2246
|
+
var UIButtonComponent = _UIButtonComponent;
|
|
2247
|
+
|
|
2248
|
+
// core/components/UIToggle.ts
|
|
2249
|
+
var _UIToggleComponent = class _UIToggleComponent extends Component {
|
|
2250
|
+
constructor(options) {
|
|
2251
|
+
super();
|
|
2252
|
+
this.value = options?.value ?? false;
|
|
2253
|
+
this.actionId = options?.actionId ?? "";
|
|
2254
|
+
this.width = options?.width ?? 0.14;
|
|
2255
|
+
this.height = options?.height ?? 0.07;
|
|
2256
|
+
this.trackColor = options?.trackColor ?? "#2a2a2a";
|
|
2257
|
+
this.accentColor = options?.accentColor ?? "#ff6b35";
|
|
2258
|
+
this.knobColor = options?.knobColor ?? "#ffffff";
|
|
2259
|
+
}
|
|
2260
|
+
};
|
|
2261
|
+
__name(_UIToggleComponent, "UIToggleComponent");
|
|
2262
|
+
var UIToggleComponent = _UIToggleComponent;
|
|
2263
|
+
|
|
2264
|
+
// core/components/UISlider.ts
|
|
2265
|
+
var _UISliderComponent = class _UISliderComponent extends Component {
|
|
2266
|
+
constructor(options) {
|
|
2267
|
+
super();
|
|
2268
|
+
this.min = options?.min ?? 0;
|
|
2269
|
+
this.max = options?.max ?? Math.max(1, this.min + 1);
|
|
2270
|
+
if (this.max <= this.min) this.max = this.min + 1;
|
|
2271
|
+
const v = options?.value ?? (this.min + this.max) / 2;
|
|
2272
|
+
this.value = Math.min(this.max, Math.max(this.min, v));
|
|
2273
|
+
this.actionId = options?.actionId ?? "";
|
|
2274
|
+
this.width = options?.width ?? 0.4;
|
|
2275
|
+
this.height = options?.height ?? 0.06;
|
|
2276
|
+
this.trackColor = options?.trackColor ?? "#2a2a2a";
|
|
2277
|
+
this.accentColor = options?.accentColor ?? "#ff6b35";
|
|
2278
|
+
this.knobColor = options?.knobColor ?? "#ffffff";
|
|
2279
|
+
}
|
|
2280
|
+
};
|
|
2281
|
+
__name(_UISliderComponent, "UISliderComponent");
|
|
2282
|
+
var UISliderComponent = _UISliderComponent;
|
|
2283
|
+
|
|
2190
2284
|
// core/components/XRInteractor.ts
|
|
2191
2285
|
var _XRInteractorComponent = class _XRInteractorComponent extends Component {
|
|
2192
2286
|
constructor(options) {
|
|
@@ -5288,6 +5382,11 @@ function buildComponentValueMaps(snapshot) {
|
|
|
5288
5382
|
"XRHandPoseComponent"
|
|
5289
5383
|
),
|
|
5290
5384
|
xrAnchorValues: readByComponent(snapshot.components, "XRAnchorComponent"),
|
|
5385
|
+
uiPanelValues: readByComponent(snapshot.components, "UIPanelComponent"),
|
|
5386
|
+
uiTextValues: readByComponent(snapshot.components, "UITextComponent"),
|
|
5387
|
+
uiButtonValues: readByComponent(snapshot.components, "UIButtonComponent"),
|
|
5388
|
+
uiToggleValues: readByComponent(snapshot.components, "UIToggleComponent"),
|
|
5389
|
+
uiSliderValues: readByComponent(snapshot.components, "UISliderComponent"),
|
|
5291
5390
|
xrInteractorSideValues: readByComponent(
|
|
5292
5391
|
snapshot.components,
|
|
5293
5392
|
"XRInteractorSideComponent"
|
|
@@ -5609,6 +5708,49 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
|
|
|
5609
5708
|
}
|
|
5610
5709
|
}
|
|
5611
5710
|
}
|
|
5711
|
+
{
|
|
5712
|
+
const restoreUi = /* @__PURE__ */ __name((map, Ctor, existing) => {
|
|
5713
|
+
const source = asRecord3(map.get(sourceEntityId));
|
|
5714
|
+
if (!source) return;
|
|
5715
|
+
let component = existing;
|
|
5716
|
+
if (!component) {
|
|
5717
|
+
component = new Ctor();
|
|
5718
|
+
world.addComponent(targetEntityId, component);
|
|
5719
|
+
}
|
|
5720
|
+
const target = component;
|
|
5721
|
+
for (const [key, value] of Object.entries(source)) {
|
|
5722
|
+
const current = target[key];
|
|
5723
|
+
if ((typeof value === "string" || typeof value === "number" || typeof value === "boolean") && typeof current === typeof value) {
|
|
5724
|
+
target[key] = value;
|
|
5725
|
+
}
|
|
5726
|
+
}
|
|
5727
|
+
}, "restoreUi");
|
|
5728
|
+
restoreUi(
|
|
5729
|
+
values.uiPanelValues,
|
|
5730
|
+
UIPanelComponent,
|
|
5731
|
+
world.getComponent(targetEntityId, UIPanelComponent)
|
|
5732
|
+
);
|
|
5733
|
+
restoreUi(
|
|
5734
|
+
values.uiTextValues,
|
|
5735
|
+
UITextComponent,
|
|
5736
|
+
world.getComponent(targetEntityId, UITextComponent)
|
|
5737
|
+
);
|
|
5738
|
+
restoreUi(
|
|
5739
|
+
values.uiButtonValues,
|
|
5740
|
+
UIButtonComponent,
|
|
5741
|
+
world.getComponent(targetEntityId, UIButtonComponent)
|
|
5742
|
+
);
|
|
5743
|
+
restoreUi(
|
|
5744
|
+
values.uiToggleValues,
|
|
5745
|
+
UIToggleComponent,
|
|
5746
|
+
world.getComponent(targetEntityId, UIToggleComponent)
|
|
5747
|
+
);
|
|
5748
|
+
restoreUi(
|
|
5749
|
+
values.uiSliderValues,
|
|
5750
|
+
UISliderComponent,
|
|
5751
|
+
world.getComponent(targetEntityId, UISliderComponent)
|
|
5752
|
+
);
|
|
5753
|
+
}
|
|
5612
5754
|
{
|
|
5613
5755
|
const sourceInteractor = asRecord3(
|
|
5614
5756
|
values.xrInteractorValues.get(sourceEntityId)
|
|
@@ -7031,6 +7173,23 @@ var _Behaviour = class _Behaviour {
|
|
|
7031
7173
|
/** Called when this entity is released. Optional XR hook. */
|
|
7032
7174
|
onRelease(input) {
|
|
7033
7175
|
}
|
|
7176
|
+
/** A UI button on this entity (or a descendant) was clicked. Clicks fire on
|
|
7177
|
+
* RELEASE while still pointing at the widget — sliding off cancels. */
|
|
7178
|
+
onUiPress(_event) {
|
|
7179
|
+
}
|
|
7180
|
+
/** A pointer began hovering a UI widget on this entity (or a descendant). */
|
|
7181
|
+
onUiHoverStart(_event) {
|
|
7182
|
+
}
|
|
7183
|
+
/** The pointer left the widget. */
|
|
7184
|
+
onUiHoverEnd(_event) {
|
|
7185
|
+
}
|
|
7186
|
+
/** A UI toggle flipped; `event.value` is the new boolean. */
|
|
7187
|
+
onUiToggle(_event) {
|
|
7188
|
+
}
|
|
7189
|
+
/** A UI slider moved; `event.value` is the new number. Fires on real value
|
|
7190
|
+
* changes during the drag and once more on release. */
|
|
7191
|
+
onUiChange(_event) {
|
|
7192
|
+
}
|
|
7034
7193
|
};
|
|
7035
7194
|
__name(_Behaviour, "Behaviour");
|
|
7036
7195
|
/**
|
|
@@ -8561,6 +8720,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
8561
8720
|
this.grabs = /* @__PURE__ */ new Map();
|
|
8562
8721
|
this.hoveredEntityId = null;
|
|
8563
8722
|
/** Per-hand hover, with the hit distance so the ray can stop at the surface. */
|
|
8723
|
+
this.busyContributors = [];
|
|
8564
8724
|
this.hovers = /* @__PURE__ */ new Map();
|
|
8565
8725
|
/** Monotonic seconds, accumulated from deltaTime for velocity sampling. */
|
|
8566
8726
|
this.elapsed = 0;
|
|
@@ -8569,6 +8729,8 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
8569
8729
|
update(world, deltaTime) {
|
|
8570
8730
|
if (!world.isXrPresenting()) {
|
|
8571
8731
|
this.releaseAll(world);
|
|
8732
|
+
this.hovers.clear();
|
|
8733
|
+
this.hoveredEntityId = null;
|
|
8572
8734
|
return;
|
|
8573
8735
|
}
|
|
8574
8736
|
this.elapsed += Math.max(0, deltaTime);
|
|
@@ -9191,13 +9353,30 @@ var _XRInteractionSystem = class _XRInteractionSystem {
|
|
|
9191
9353
|
*/
|
|
9192
9354
|
isSourceBusy(world, input) {
|
|
9193
9355
|
if (this.grabs.has(input)) return true;
|
|
9356
|
+
for (const contributor of this.busyContributors) {
|
|
9357
|
+
try {
|
|
9358
|
+
if (contributor(world, input)) return true;
|
|
9359
|
+
} catch {
|
|
9360
|
+
}
|
|
9361
|
+
}
|
|
9194
9362
|
const hover = this.hovers.get(input);
|
|
9195
9363
|
if (!hover) return false;
|
|
9196
9364
|
const interactable = world.getComponent(
|
|
9197
9365
|
hover.entityId,
|
|
9198
9366
|
XRInteractableComponent
|
|
9199
9367
|
);
|
|
9200
|
-
return Boolean(
|
|
9368
|
+
return Boolean(
|
|
9369
|
+
interactable?.enabled && (interactable.grabbable || interactable.pointable)
|
|
9370
|
+
);
|
|
9371
|
+
}
|
|
9372
|
+
/** OR-ed into isSourceBusy; see the comment there. */
|
|
9373
|
+
registerBusyContributor(contributor) {
|
|
9374
|
+
this.busyContributors.push(contributor);
|
|
9375
|
+
}
|
|
9376
|
+
/** Hover for a specific input source (handedness-free — Vision Pro's
|
|
9377
|
+
* transient pointers all report handedness "none"). */
|
|
9378
|
+
getHoverForInput(input) {
|
|
9379
|
+
return this.hovers.get(input) ?? null;
|
|
9201
9380
|
}
|
|
9202
9381
|
/** True when THIS hand is holding something. */
|
|
9203
9382
|
isGrabbingWith(handedness) {
|
|
@@ -9819,6 +9998,88 @@ function decomposeXrPose(matrixElements, target) {
|
|
|
9819
9998
|
}
|
|
9820
9999
|
__name(decomposeXrPose, "decomposeXrPose");
|
|
9821
10000
|
|
|
10001
|
+
// core/xr/poseStability.ts
|
|
10002
|
+
var GLUED_REF_SPEED = 0.12;
|
|
10003
|
+
var STILL_SPEED = 0.05;
|
|
10004
|
+
var GLUED_FRAMES_TO_FREEZE = 3;
|
|
10005
|
+
var STATIC_FRAMES_TO_RECOVER = 10;
|
|
10006
|
+
var SNAP_SPEED = 3;
|
|
10007
|
+
var dist = /* @__PURE__ */ __name((a, b) => Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z), "dist");
|
|
10008
|
+
var _SurfacePoseGate = class _SurfacePoseGate {
|
|
10009
|
+
constructor() {
|
|
10010
|
+
this.prevRef = null;
|
|
10011
|
+
this.prevView = null;
|
|
10012
|
+
this.gluedStreak = 0;
|
|
10013
|
+
this.staticStreak = 0;
|
|
10014
|
+
this.suspect = false;
|
|
10015
|
+
this.deltas = 0;
|
|
10016
|
+
}
|
|
10017
|
+
/** True while poses are being rejected as head-glued. */
|
|
10018
|
+
get frozen() {
|
|
10019
|
+
return this.suspect;
|
|
10020
|
+
}
|
|
10021
|
+
/**
|
|
10022
|
+
* True once enough motion has been observed to classify the pose at all.
|
|
10023
|
+
* Surface CREATION waits for this: an entity spawned from a glued pose
|
|
10024
|
+
* materializes wherever the head happens to be — a rotated ghost copy of
|
|
10025
|
+
* the room — and freezing it there is barely better than letting it follow.
|
|
10026
|
+
*/
|
|
10027
|
+
get warmedUp() {
|
|
10028
|
+
return this.deltas >= GLUED_FRAMES_TO_FREEZE;
|
|
10029
|
+
}
|
|
10030
|
+
/**
|
|
10031
|
+
* Feed this frame's surface position in reference space and in view
|
|
10032
|
+
* (viewer-relative) space. Returns true when the pose should be APPLIED,
|
|
10033
|
+
* false when it should be frozen out.
|
|
10034
|
+
*
|
|
10035
|
+
* `viewPos` may be null when the viewer pose is unavailable (older
|
|
10036
|
+
* runtimes) — the gate then passes everything through, degrading to the
|
|
10037
|
+
* flag-only behavior.
|
|
10038
|
+
*/
|
|
10039
|
+
update(refPos, viewPos, deltaTime) {
|
|
10040
|
+
if (!viewPos || deltaTime <= 0) {
|
|
10041
|
+
this.prevRef = { ...refPos };
|
|
10042
|
+
this.prevView = viewPos ? { ...viewPos } : null;
|
|
10043
|
+
return !this.suspect;
|
|
10044
|
+
}
|
|
10045
|
+
const prevRef = this.prevRef;
|
|
10046
|
+
const prevView = this.prevView;
|
|
10047
|
+
this.prevRef = { ...refPos };
|
|
10048
|
+
this.prevView = { ...viewPos };
|
|
10049
|
+
if (!prevRef || !prevView) return !this.suspect;
|
|
10050
|
+
this.deltas += 1;
|
|
10051
|
+
const refSpeed = dist(refPos, prevRef) / deltaTime;
|
|
10052
|
+
const viewSpeed = dist(viewPos, prevView) / deltaTime;
|
|
10053
|
+
const glued = refSpeed > GLUED_REF_SPEED && viewSpeed < STILL_SPEED;
|
|
10054
|
+
const worldStatic = refSpeed < STILL_SPEED && viewSpeed > GLUED_REF_SPEED;
|
|
10055
|
+
if (this.suspect) {
|
|
10056
|
+
if (refSpeed > SNAP_SPEED && viewSpeed > SNAP_SPEED) {
|
|
10057
|
+
this.suspect = false;
|
|
10058
|
+
this.gluedStreak = 0;
|
|
10059
|
+
this.staticStreak = 0;
|
|
10060
|
+
return true;
|
|
10061
|
+
}
|
|
10062
|
+
this.staticStreak = worldStatic ? this.staticStreak + 1 : 0;
|
|
10063
|
+
if (this.staticStreak >= STATIC_FRAMES_TO_RECOVER) {
|
|
10064
|
+
this.suspect = false;
|
|
10065
|
+
this.gluedStreak = 0;
|
|
10066
|
+
this.staticStreak = 0;
|
|
10067
|
+
return true;
|
|
10068
|
+
}
|
|
10069
|
+
return false;
|
|
10070
|
+
}
|
|
10071
|
+
this.gluedStreak = glued ? this.gluedStreak + 1 : 0;
|
|
10072
|
+
if (this.gluedStreak >= GLUED_FRAMES_TO_FREEZE) {
|
|
10073
|
+
this.suspect = true;
|
|
10074
|
+
this.staticStreak = 0;
|
|
10075
|
+
return false;
|
|
10076
|
+
}
|
|
10077
|
+
return true;
|
|
10078
|
+
}
|
|
10079
|
+
};
|
|
10080
|
+
__name(_SurfacePoseGate, "SurfacePoseGate");
|
|
10081
|
+
var SurfacePoseGate = _SurfacePoseGate;
|
|
10082
|
+
|
|
9822
10083
|
// core/systems/XREnvironmentSystem.ts
|
|
9823
10084
|
var DEBUG_COLOR = 5088255;
|
|
9824
10085
|
function debugColorFor(kind, semanticLabel) {
|
|
@@ -9842,6 +10103,11 @@ function debugColorFor(kind, semanticLabel) {
|
|
|
9842
10103
|
}
|
|
9843
10104
|
__name(debugColorFor, "debugColorFor");
|
|
9844
10105
|
var GLOBAL_MESH_DEBUG_OPACITY = 0.35;
|
|
10106
|
+
var GLOBAL_MESH_SWITCH_MARGIN = 1.5;
|
|
10107
|
+
function isGlobalMeshSurface(mesh) {
|
|
10108
|
+
return (mesh.semanticLabel ?? null) === "global mesh";
|
|
10109
|
+
}
|
|
10110
|
+
__name(isGlobalMeshSurface, "isGlobalMeshSurface");
|
|
9845
10111
|
var DEBUG_RENDER_ORDER = 900;
|
|
9846
10112
|
var ROOM_CAPTURE_DELAY_SECONDS = 3;
|
|
9847
10113
|
var SYNTHETIC_FLOOR_EXTENT = 50;
|
|
@@ -9862,12 +10128,30 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
9862
10128
|
/** Keyed by the XRPlane/XRMesh object itself — the runtime's own identity. */
|
|
9863
10129
|
this.planes = /* @__PURE__ */ new Map();
|
|
9864
10130
|
this.meshes = /* @__PURE__ */ new Map();
|
|
10131
|
+
/**
|
|
10132
|
+
* Surfaces observed but not yet SPAWNED — their gate is still proving the
|
|
10133
|
+
* pose is world-anchored. Creating from an unflagged head-glued pose (seen
|
|
10134
|
+
* on-device after a suspend/resume regenerated the scan identities) plants
|
|
10135
|
+
* a rotated ghost copy of the room wherever the head happens to be, which
|
|
10136
|
+
* the apply-gate then freezes there — wrong forever. So admission itself
|
|
10137
|
+
* is gated: a few frames of world-anchored behavior first.
|
|
10138
|
+
*/
|
|
10139
|
+
this.pendingPlanes = /* @__PURE__ */ new Map();
|
|
10140
|
+
this.pendingMeshes = /* @__PURE__ */ new Map();
|
|
9865
10141
|
this.presenting = false;
|
|
9866
10142
|
this.debugVisible = false;
|
|
9867
10143
|
/** Seconds presenting, used to decide the room has genuinely nothing in it. */
|
|
9868
10144
|
this.elapsed = 0;
|
|
9869
10145
|
/** `initiateRoomCapture` may be called only ONCE per session. */
|
|
9870
10146
|
this.roomCaptureRequested = false;
|
|
10147
|
+
/** This frame's inverse viewer matrix, or null when unavailable. */
|
|
10148
|
+
this.viewerInverse = null;
|
|
10149
|
+
/** This frame's viewer position in reference space, for room election. */
|
|
10150
|
+
this.viewerPos = null;
|
|
10151
|
+
/** The ONE global room mesh currently allowed to spawn. */
|
|
10152
|
+
this.electedGlobalMesh = null;
|
|
10153
|
+
this.tmpViewerMatrix = new THREE26__namespace.Matrix4();
|
|
10154
|
+
this.tmpViewPos = new THREE26__namespace.Vector3();
|
|
9871
10155
|
}
|
|
9872
10156
|
update(world, deltaTime = 0) {
|
|
9873
10157
|
const presenting = world.isXrPresenting();
|
|
@@ -9905,13 +10189,22 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
9905
10189
|
const meshesAvailable = Boolean(frame.detectedMeshes);
|
|
9906
10190
|
const useMesh = (mode === "mesh" || mode === "auto") && meshesAvailable;
|
|
9907
10191
|
const usePlanes = mode === "planes" || mode === "auto" && !useMesh;
|
|
10192
|
+
const viewer = frame.getViewerPose?.(referenceSpace);
|
|
10193
|
+
if (viewer) {
|
|
10194
|
+
const vm = viewer.transform.matrix;
|
|
10195
|
+
this.viewerPos = { x: vm[12], y: vm[13], z: vm[14] };
|
|
10196
|
+
this.viewerInverse = this.tmpViewerMatrix.fromArray(vm).invert();
|
|
10197
|
+
} else {
|
|
10198
|
+
this.viewerPos = null;
|
|
10199
|
+
this.viewerInverse = null;
|
|
10200
|
+
}
|
|
9908
10201
|
if (usePlanes) {
|
|
9909
|
-
this.syncPlanes(world, frame, referenceSpace);
|
|
10202
|
+
this.syncPlanes(world, frame, referenceSpace, deltaTime);
|
|
9910
10203
|
} else if (this.planes.size > 0) {
|
|
9911
10204
|
this.clearMap(world, this.planes);
|
|
9912
10205
|
}
|
|
9913
10206
|
if (useMesh) {
|
|
9914
|
-
this.syncMeshes(world, frame, referenceSpace);
|
|
10207
|
+
this.syncMeshes(world, frame, referenceSpace, deltaTime);
|
|
9915
10208
|
} else if (this.meshes.size > 0) {
|
|
9916
10209
|
this.clearMap(world, this.meshes);
|
|
9917
10210
|
}
|
|
@@ -9998,7 +10291,7 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
9998
10291
|
if (ids.length === 0) return null;
|
|
9999
10292
|
return world.getComponent(ids[0], XRRigComponent) ?? null;
|
|
10000
10293
|
}
|
|
10001
|
-
syncPlanes(world, frame, referenceSpace) {
|
|
10294
|
+
syncPlanes(world, frame, referenceSpace, deltaTime) {
|
|
10002
10295
|
const detected = frame.detectedPlanes;
|
|
10003
10296
|
if (!detected) return;
|
|
10004
10297
|
for (const [plane, entry] of this.planes) {
|
|
@@ -10007,6 +10300,9 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
10007
10300
|
this.planes.delete(plane);
|
|
10008
10301
|
}
|
|
10009
10302
|
}
|
|
10303
|
+
for (const plane of this.pendingPlanes.keys()) {
|
|
10304
|
+
if (!detected.has(plane)) this.pendingPlanes.delete(plane);
|
|
10305
|
+
}
|
|
10010
10306
|
for (const plane of detected) {
|
|
10011
10307
|
const pose = frame.getPose(plane.planeSpace, referenceSpace);
|
|
10012
10308
|
if (!pose) continue;
|
|
@@ -10015,8 +10311,11 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
10015
10311
|
const changed = plane.lastChangedTime ?? 0;
|
|
10016
10312
|
if (!entry) {
|
|
10017
10313
|
if (emulated) continue;
|
|
10314
|
+
const gate = this.admit(this.pendingPlanes, plane, pose, deltaTime);
|
|
10315
|
+
if (!gate) continue;
|
|
10018
10316
|
const created = this.createPlaneEntity(world, plane);
|
|
10019
10317
|
if (!created) continue;
|
|
10318
|
+
created.gate = gate;
|
|
10020
10319
|
entry = created;
|
|
10021
10320
|
this.planes.set(plane, entry);
|
|
10022
10321
|
} else if (changed !== entry.lastChangedTime) {
|
|
@@ -10036,11 +10335,12 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
10036
10335
|
}
|
|
10037
10336
|
}
|
|
10038
10337
|
}
|
|
10039
|
-
entry.
|
|
10040
|
-
|
|
10338
|
+
const allowed = !emulated && this.gateAllows(entry.gate, pose, deltaTime);
|
|
10339
|
+
entry.emulated = emulated || entry.gate.frozen;
|
|
10340
|
+
if (allowed) this.applyPose(world, entry.entityId, pose);
|
|
10041
10341
|
}
|
|
10042
10342
|
}
|
|
10043
|
-
syncMeshes(world, frame, referenceSpace) {
|
|
10343
|
+
syncMeshes(world, frame, referenceSpace, deltaTime) {
|
|
10044
10344
|
const detected = frame.detectedMeshes;
|
|
10045
10345
|
if (!detected) return;
|
|
10046
10346
|
for (const [mesh, entry] of this.meshes) {
|
|
@@ -10049,7 +10349,20 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
10049
10349
|
this.meshes.delete(mesh);
|
|
10050
10350
|
}
|
|
10051
10351
|
}
|
|
10352
|
+
for (const mesh of this.pendingMeshes.keys()) {
|
|
10353
|
+
if (!detected.has(mesh)) this.pendingMeshes.delete(mesh);
|
|
10354
|
+
}
|
|
10355
|
+
const electedGlobal = this.electGlobalMesh(frame, detected, referenceSpace);
|
|
10052
10356
|
for (const mesh of detected) {
|
|
10357
|
+
if (isGlobalMeshSurface(mesh) && mesh !== electedGlobal) {
|
|
10358
|
+
const stale = this.meshes.get(mesh);
|
|
10359
|
+
if (stale) {
|
|
10360
|
+
this.destroyEntry(world, stale);
|
|
10361
|
+
this.meshes.delete(mesh);
|
|
10362
|
+
}
|
|
10363
|
+
this.pendingMeshes.delete(mesh);
|
|
10364
|
+
continue;
|
|
10365
|
+
}
|
|
10053
10366
|
const pose = frame.getPose(mesh.meshSpace, referenceSpace);
|
|
10054
10367
|
if (!pose) continue;
|
|
10055
10368
|
const emulated = pose.emulatedPosition === true;
|
|
@@ -10057,17 +10370,95 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
10057
10370
|
const changed = mesh.lastChangedTime ?? 0;
|
|
10058
10371
|
if (!entry) {
|
|
10059
10372
|
if (emulated) continue;
|
|
10373
|
+
const gate = this.admit(this.pendingMeshes, mesh, pose, deltaTime);
|
|
10374
|
+
if (!gate) continue;
|
|
10060
10375
|
const created = this.createMeshEntity(world, mesh);
|
|
10061
10376
|
if (!created) continue;
|
|
10377
|
+
created.gate = gate;
|
|
10062
10378
|
entry = created;
|
|
10063
10379
|
this.meshes.set(mesh, entry);
|
|
10064
10380
|
} else if (changed !== entry.lastChangedTime) {
|
|
10065
10381
|
entry.lastChangedTime = changed;
|
|
10066
10382
|
writeXrMeshGeometry(mesh.vertices, mesh.indices, entry.geometry);
|
|
10067
10383
|
}
|
|
10068
|
-
entry.
|
|
10069
|
-
|
|
10384
|
+
const allowed = !emulated && this.gateAllows(entry.gate, pose, deltaTime);
|
|
10385
|
+
entry.emulated = emulated || entry.gate.frozen;
|
|
10386
|
+
if (allowed) this.applyPose(world, entry.entityId, pose);
|
|
10387
|
+
}
|
|
10388
|
+
}
|
|
10389
|
+
/**
|
|
10390
|
+
* Pick THE room's global mesh: the one whose scan origin is nearest the
|
|
10391
|
+
* viewer, sticky to the current choice within a margin so origin jitter
|
|
10392
|
+
* can't flap the room. Without a viewer pose (or with a single candidate)
|
|
10393
|
+
* the first candidate stands; ties are impossible to break anyway.
|
|
10394
|
+
*/
|
|
10395
|
+
electGlobalMesh(frame, detected, referenceSpace) {
|
|
10396
|
+
let nearest = null;
|
|
10397
|
+
let nearestDist = Number.POSITIVE_INFINITY;
|
|
10398
|
+
let currentDist = Number.POSITIVE_INFINITY;
|
|
10399
|
+
let currentStillDetected = false;
|
|
10400
|
+
for (const mesh of detected) {
|
|
10401
|
+
if (!isGlobalMeshSurface(mesh)) continue;
|
|
10402
|
+
if (!this.viewerPos) {
|
|
10403
|
+
return this.electedGlobalMesh && detected.has(this.electedGlobalMesh) ? this.electedGlobalMesh : mesh;
|
|
10404
|
+
}
|
|
10405
|
+
const pose = frame.getPose(mesh.meshSpace, referenceSpace);
|
|
10406
|
+
if (!pose) continue;
|
|
10407
|
+
const m = pose.transform.matrix;
|
|
10408
|
+
const d = Math.hypot(
|
|
10409
|
+
m[12] - this.viewerPos.x,
|
|
10410
|
+
m[13] - this.viewerPos.y,
|
|
10411
|
+
m[14] - this.viewerPos.z
|
|
10412
|
+
);
|
|
10413
|
+
if (mesh === this.electedGlobalMesh) {
|
|
10414
|
+
currentDist = d;
|
|
10415
|
+
currentStillDetected = true;
|
|
10416
|
+
}
|
|
10417
|
+
if (d < nearestDist) {
|
|
10418
|
+
nearestDist = d;
|
|
10419
|
+
nearest = mesh;
|
|
10420
|
+
}
|
|
10421
|
+
}
|
|
10422
|
+
if (currentStillDetected && currentDist < nearestDist + GLOBAL_MESH_SWITCH_MARGIN) {
|
|
10423
|
+
return this.electedGlobalMesh;
|
|
10424
|
+
}
|
|
10425
|
+
this.electedGlobalMesh = nearest;
|
|
10426
|
+
return nearest;
|
|
10427
|
+
}
|
|
10428
|
+
/**
|
|
10429
|
+
* Admission control for a NOT-yet-spawned surface. With a viewer pose
|
|
10430
|
+
* available, the surface must behave world-anchored for a few frames before
|
|
10431
|
+
* an entity is created for it; without one (older runtimes, tests) it is
|
|
10432
|
+
* admitted immediately — the gate can't classify anyway. Returns the
|
|
10433
|
+
* warmed-up gate (carried into the entry so its motion history survives),
|
|
10434
|
+
* or null while the surface must keep waiting.
|
|
10435
|
+
*/
|
|
10436
|
+
admit(pending, key, pose, deltaTime) {
|
|
10437
|
+
if (!this.viewerInverse) return new SurfacePoseGate();
|
|
10438
|
+
let gate = pending.get(key);
|
|
10439
|
+
if (!gate) {
|
|
10440
|
+
gate = new SurfacePoseGate();
|
|
10441
|
+
pending.set(key, gate);
|
|
10442
|
+
}
|
|
10443
|
+
const allowed = this.gateAllows(gate, pose, deltaTime);
|
|
10444
|
+
if (!allowed || !gate.warmedUp) return null;
|
|
10445
|
+
pending.delete(key);
|
|
10446
|
+
return gate;
|
|
10447
|
+
}
|
|
10448
|
+
/**
|
|
10449
|
+
* Run a surface's pose through its head-glue gate: position in reference
|
|
10450
|
+
* space vs position relative to the viewer. Without a viewer pose the gate
|
|
10451
|
+
* passes everything, degrading to the flag-only behavior.
|
|
10452
|
+
*/
|
|
10453
|
+
gateAllows(gate, pose, deltaTime) {
|
|
10454
|
+
const m = pose.transform.matrix;
|
|
10455
|
+
const refPos = { x: m[12], y: m[13], z: m[14] };
|
|
10456
|
+
let viewPos = null;
|
|
10457
|
+
if (this.viewerInverse) {
|
|
10458
|
+
const v = this.tmpViewPos.set(refPos.x, refPos.y, refPos.z).applyMatrix4(this.viewerInverse);
|
|
10459
|
+
viewPos = { x: v.x, y: v.y, z: v.z };
|
|
10070
10460
|
}
|
|
10461
|
+
return gate.update(refPos, viewPos, deltaTime);
|
|
10071
10462
|
}
|
|
10072
10463
|
createPlaneEntity(world, plane) {
|
|
10073
10464
|
const collider = planeColliderFromPolygon(plane.polygon);
|
|
@@ -10087,7 +10478,8 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
10087
10478
|
lastChangedTime: plane.lastChangedTime ?? 0,
|
|
10088
10479
|
geometry: null,
|
|
10089
10480
|
material: null,
|
|
10090
|
-
emulated: false
|
|
10481
|
+
emulated: false,
|
|
10482
|
+
gate: new SurfacePoseGate()
|
|
10091
10483
|
};
|
|
10092
10484
|
if (this.debugVisible) {
|
|
10093
10485
|
this.attachPlaneDebug(world, entry, collider, plane.semanticLabel ?? null);
|
|
@@ -10160,7 +10552,8 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
10160
10552
|
lastChangedTime: mesh.lastChangedTime ?? 0,
|
|
10161
10553
|
geometry,
|
|
10162
10554
|
material,
|
|
10163
|
-
emulated: false
|
|
10555
|
+
emulated: false,
|
|
10556
|
+
gate: new SurfacePoseGate()
|
|
10164
10557
|
};
|
|
10165
10558
|
}
|
|
10166
10559
|
/** A thin box so a detected plane can be SEEN, not just collided with. */
|
|
@@ -10241,6 +10634,9 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
|
|
|
10241
10634
|
clearAll(world) {
|
|
10242
10635
|
this.clearMap(world, this.planes);
|
|
10243
10636
|
this.clearMap(world, this.meshes);
|
|
10637
|
+
this.pendingPlanes.clear();
|
|
10638
|
+
this.pendingMeshes.clear();
|
|
10639
|
+
this.electedGlobalMesh = null;
|
|
10244
10640
|
}
|
|
10245
10641
|
dispose(world) {
|
|
10246
10642
|
this.clearAll(world);
|
|
@@ -10252,6 +10648,918 @@ function normalizeOrientation(value) {
|
|
|
10252
10648
|
return value === "horizontal" || value === "vertical" ? value : null;
|
|
10253
10649
|
}
|
|
10254
10650
|
__name(normalizeOrientation, "normalizeOrientation");
|
|
10651
|
+
var MOUSE_CLICK_TRAVEL_PX = 6;
|
|
10652
|
+
var PRESS_TINT_MIN_SECONDS = 0.1;
|
|
10653
|
+
var PRESS_TINT = 0.72;
|
|
10654
|
+
var HOVER_TINT = 0.9;
|
|
10655
|
+
var _UIInteractionSystem = class _UIInteractionSystem {
|
|
10656
|
+
/**
|
|
10657
|
+
* @param interaction The XR interaction system, or null in a world with no
|
|
10658
|
+
* XR (flat 3D experiences) — the mouse path still works via
|
|
10659
|
+
* `options.inputSystem`.
|
|
10660
|
+
*/
|
|
10661
|
+
constructor(interaction, options) {
|
|
10662
|
+
this.xrPresses = /* @__PURE__ */ new Map();
|
|
10663
|
+
this.mousePress = null;
|
|
10664
|
+
this.mouseHover = null;
|
|
10665
|
+
/** entityId → seconds of pressed-tint left to display. */
|
|
10666
|
+
this.tintFloors = /* @__PURE__ */ new Map();
|
|
10667
|
+
this.tinted = /* @__PURE__ */ new Set();
|
|
10668
|
+
this.raycaster = new THREE26__namespace.Raycaster();
|
|
10669
|
+
this.interaction = interaction;
|
|
10670
|
+
this.inputSystem = options?.inputSystem ?? null;
|
|
10671
|
+
interaction?.registerBusyContributor(
|
|
10672
|
+
(_world, input) => this.xrPresses.has(input)
|
|
10673
|
+
);
|
|
10674
|
+
}
|
|
10675
|
+
update(world, deltaTime = 0) {
|
|
10676
|
+
if (world.isEditorMode()) return;
|
|
10677
|
+
if (world.isXrPresenting()) {
|
|
10678
|
+
this.updateXr(world);
|
|
10679
|
+
} else {
|
|
10680
|
+
this.updateMouse(world);
|
|
10681
|
+
}
|
|
10682
|
+
this.updateTints(world, deltaTime);
|
|
10683
|
+
}
|
|
10684
|
+
// ── XR path ────────────────────────────────────────────────────────────────
|
|
10685
|
+
updateXr(world) {
|
|
10686
|
+
const interaction = this.interaction;
|
|
10687
|
+
if (!interaction) return;
|
|
10688
|
+
const xrInput = interaction.inputSystem;
|
|
10689
|
+
const inputs = xrInput.getInputs();
|
|
10690
|
+
for (const input of inputs) {
|
|
10691
|
+
const hover = interaction.getHoverForInput(input);
|
|
10692
|
+
const hovered = hover ? this.widgetKind(world, hover.entityId) : null;
|
|
10693
|
+
if (input.selectStart && hover && hovered) {
|
|
10694
|
+
this.xrPresses.set(input, {
|
|
10695
|
+
entityId: hover.entityId,
|
|
10696
|
+
kind: hovered,
|
|
10697
|
+
travelPx: 0
|
|
10698
|
+
});
|
|
10699
|
+
this.beginPressTint(world, hover.entityId);
|
|
10700
|
+
}
|
|
10701
|
+
const press = this.xrPresses.get(input);
|
|
10702
|
+
if (press && press.kind === "slider") {
|
|
10703
|
+
this.dragSlider(world, press.entityId, input);
|
|
10704
|
+
}
|
|
10705
|
+
if (input.selectEnd || !input.connected) {
|
|
10706
|
+
if (press) {
|
|
10707
|
+
this.xrPresses.delete(input);
|
|
10708
|
+
const stillOn = hover?.entityId === press.entityId || // Vision Pro: the transient source is gone from the hover map on
|
|
10709
|
+
// its release frame — confirm against the last latched ray.
|
|
10710
|
+
this.rayHitsWidget(world, press.entityId, input);
|
|
10711
|
+
if (stillOn || press.kind === "slider") {
|
|
10712
|
+
this.fire(world, press, input);
|
|
10713
|
+
}
|
|
10714
|
+
this.endPressTint(press.entityId);
|
|
10715
|
+
}
|
|
10716
|
+
}
|
|
10717
|
+
}
|
|
10718
|
+
for (const [input] of this.xrPresses) {
|
|
10719
|
+
if (!inputs.includes(input)) this.xrPresses.delete(input);
|
|
10720
|
+
}
|
|
10721
|
+
}
|
|
10722
|
+
// ── Mouse path (flat preview) ──────────────────────────────────────────────
|
|
10723
|
+
updateMouse(world) {
|
|
10724
|
+
const input = this.inputSystem;
|
|
10725
|
+
if (!input) return;
|
|
10726
|
+
const hit = this.raycastMouse(world);
|
|
10727
|
+
const hovered = hit && this.widgetKind(world, hit) ? hit : null;
|
|
10728
|
+
if (hovered !== this.mouseHover) {
|
|
10729
|
+
if (this.mouseHover !== null) {
|
|
10730
|
+
this.setTint(world, this.mouseHover, 1);
|
|
10731
|
+
this.dispatchBubbling(
|
|
10732
|
+
world,
|
|
10733
|
+
this.mouseHover,
|
|
10734
|
+
(b, e) => b.onUiHoverEnd(e)
|
|
10735
|
+
);
|
|
10736
|
+
}
|
|
10737
|
+
if (hovered !== null) {
|
|
10738
|
+
this.setTint(world, hovered, HOVER_TINT);
|
|
10739
|
+
this.dispatchBubbling(world, hovered, (b, e) => b.onUiHoverStart(e));
|
|
10740
|
+
}
|
|
10741
|
+
this.mouseHover = hovered;
|
|
10742
|
+
}
|
|
10743
|
+
if (input.getMouseDown("left") && hovered) {
|
|
10744
|
+
const kind = this.widgetKind(world, hovered);
|
|
10745
|
+
if (kind) {
|
|
10746
|
+
this.mousePress = { entityId: hovered, kind, travelPx: 0 };
|
|
10747
|
+
this.beginPressTint(world, hovered);
|
|
10748
|
+
}
|
|
10749
|
+
}
|
|
10750
|
+
if (this.mousePress) {
|
|
10751
|
+
const delta = input.getMouseDelta();
|
|
10752
|
+
this.mousePress.travelPx += Math.hypot(delta.x, delta.y);
|
|
10753
|
+
if (this.mousePress.kind === "slider") {
|
|
10754
|
+
this.dragSliderMouse(world, this.mousePress.entityId);
|
|
10755
|
+
}
|
|
10756
|
+
}
|
|
10757
|
+
if (input.getMouseUp("left") && this.mousePress) {
|
|
10758
|
+
const press = this.mousePress;
|
|
10759
|
+
this.mousePress = null;
|
|
10760
|
+
const stillOn = hovered === press.entityId;
|
|
10761
|
+
const withinTravel = press.travelPx <= MOUSE_CLICK_TRAVEL_PX;
|
|
10762
|
+
if (press.kind === "slider" || stillOn && withinTravel) {
|
|
10763
|
+
this.fire(world, press, null);
|
|
10764
|
+
}
|
|
10765
|
+
this.endPressTint(press.entityId);
|
|
10766
|
+
}
|
|
10767
|
+
}
|
|
10768
|
+
raycastMouse(world) {
|
|
10769
|
+
const input = this.inputSystem;
|
|
10770
|
+
if (!input) return null;
|
|
10771
|
+
const dom = world.getRenderer().domElement;
|
|
10772
|
+
const rect = dom.getBoundingClientRect();
|
|
10773
|
+
let ndcX = 0;
|
|
10774
|
+
let ndcY = 0;
|
|
10775
|
+
if (input.isMousePointerLocked()) {
|
|
10776
|
+
ndcX = 0;
|
|
10777
|
+
ndcY = 0;
|
|
10778
|
+
} else {
|
|
10779
|
+
const pos = input.getMousePosition();
|
|
10780
|
+
ndcX = (pos.x - rect.left) / Math.max(1, rect.width) * 2 - 1;
|
|
10781
|
+
ndcY = -((pos.y - rect.top) / Math.max(1, rect.height) * 2 - 1);
|
|
10782
|
+
if (ndcX < -1 || ndcX > 1 || ndcY < -1 || ndcY > 1) return null;
|
|
10783
|
+
}
|
|
10784
|
+
this.raycaster.setFromCamera(new THREE26__namespace.Vector2(ndcX, ndcY), world.getCamera());
|
|
10785
|
+
const hits = world.raycast(
|
|
10786
|
+
this.raycaster.ray.origin,
|
|
10787
|
+
this.raycaster.ray.direction
|
|
10788
|
+
);
|
|
10789
|
+
for (const hit of hits) {
|
|
10790
|
+
if (typeof hit.entityId !== "number") continue;
|
|
10791
|
+
if (this.widgetKind(world, hit.entityId)) return hit.entityId;
|
|
10792
|
+
return null;
|
|
10793
|
+
}
|
|
10794
|
+
return null;
|
|
10795
|
+
}
|
|
10796
|
+
// ── Shared machinery ───────────────────────────────────────────────────────
|
|
10797
|
+
widgetKind(world, entityId) {
|
|
10798
|
+
if (world.getComponent(entityId, UIButtonComponent)) return "button";
|
|
10799
|
+
if (world.getComponent(entityId, UIToggleComponent)) return "toggle";
|
|
10800
|
+
if (world.getComponent(entityId, UISliderComponent)) return "slider";
|
|
10801
|
+
return null;
|
|
10802
|
+
}
|
|
10803
|
+
fire(world, press, input) {
|
|
10804
|
+
if (press.kind === "button") {
|
|
10805
|
+
this.dispatchBubbling(world, press.entityId, (b, e) => b.onUiPress(e), input);
|
|
10806
|
+
return;
|
|
10807
|
+
}
|
|
10808
|
+
if (press.kind === "toggle") {
|
|
10809
|
+
const toggle = world.getComponent(press.entityId, UIToggleComponent);
|
|
10810
|
+
if (!toggle) return;
|
|
10811
|
+
toggle.value = !toggle.value;
|
|
10812
|
+
this.dispatchBubbling(
|
|
10813
|
+
world,
|
|
10814
|
+
press.entityId,
|
|
10815
|
+
(b, e) => b.onUiToggle(e),
|
|
10816
|
+
input,
|
|
10817
|
+
toggle.value
|
|
10818
|
+
);
|
|
10819
|
+
return;
|
|
10820
|
+
}
|
|
10821
|
+
const slider = world.getComponent(press.entityId, UISliderComponent);
|
|
10822
|
+
if (!slider) return;
|
|
10823
|
+
this.dispatchBubbling(
|
|
10824
|
+
world,
|
|
10825
|
+
press.entityId,
|
|
10826
|
+
(b, e) => b.onUiChange(e),
|
|
10827
|
+
input,
|
|
10828
|
+
slider.value
|
|
10829
|
+
);
|
|
10830
|
+
}
|
|
10831
|
+
dragSlider(world, entityId, input) {
|
|
10832
|
+
const point = this.rayPointOnWidget(world, entityId, input);
|
|
10833
|
+
if (point) this.applySliderPoint(world, entityId, point, input);
|
|
10834
|
+
}
|
|
10835
|
+
dragSliderMouse(world, entityId) {
|
|
10836
|
+
const object = this.widgetObject(world, entityId);
|
|
10837
|
+
if (!object) return;
|
|
10838
|
+
const hits = this.raycaster.intersectObject(object, true);
|
|
10839
|
+
if (hits.length > 0) this.applySliderPoint(world, entityId, hits[0].point, null);
|
|
10840
|
+
}
|
|
10841
|
+
applySliderPoint(world, entityId, worldPoint, input) {
|
|
10842
|
+
const slider = world.getComponent(entityId, UISliderComponent);
|
|
10843
|
+
const object = this.widgetObject(world, entityId);
|
|
10844
|
+
if (!slider || !object) return;
|
|
10845
|
+
const local = object.worldToLocal(worldPoint.clone());
|
|
10846
|
+
const inset = slider.height / 2;
|
|
10847
|
+
const usable = Math.max(1e-3, slider.width - inset * 2);
|
|
10848
|
+
const t = Math.min(1, Math.max(0, local.x / usable + 0.5));
|
|
10849
|
+
const next = slider.min + t * (slider.max - slider.min);
|
|
10850
|
+
if (Math.abs(next - slider.value) < 1e-4) return;
|
|
10851
|
+
slider.value = next;
|
|
10852
|
+
this.dispatchBubbling(
|
|
10853
|
+
world,
|
|
10854
|
+
entityId,
|
|
10855
|
+
(b, e) => b.onUiChange(e),
|
|
10856
|
+
input,
|
|
10857
|
+
next
|
|
10858
|
+
);
|
|
10859
|
+
}
|
|
10860
|
+
widgetObject(world, entityId) {
|
|
10861
|
+
return world.getComponent(entityId, Object3DRef)?.object;
|
|
10862
|
+
}
|
|
10863
|
+
rayHitsWidget(world, entityId, input) {
|
|
10864
|
+
const object = this.widgetObject(world, entityId);
|
|
10865
|
+
if (!object) return false;
|
|
10866
|
+
this.raycaster.set(
|
|
10867
|
+
input.rayOrigin.clone(),
|
|
10868
|
+
input.rayDirection.clone().normalize()
|
|
10869
|
+
);
|
|
10870
|
+
return this.raycaster.intersectObject(object, true).length > 0;
|
|
10871
|
+
}
|
|
10872
|
+
rayPointOnWidget(world, entityId, input) {
|
|
10873
|
+
const object = this.widgetObject(world, entityId);
|
|
10874
|
+
if (!object) return null;
|
|
10875
|
+
this.raycaster.set(
|
|
10876
|
+
input.rayOrigin.clone(),
|
|
10877
|
+
input.rayDirection.clone().normalize()
|
|
10878
|
+
);
|
|
10879
|
+
const hits = this.raycaster.intersectObject(object, true);
|
|
10880
|
+
return hits.length > 0 ? hits[0].point : null;
|
|
10881
|
+
}
|
|
10882
|
+
/**
|
|
10883
|
+
* Dispatch to the widget's Behaviours, then BUBBLE up the parent chain so a
|
|
10884
|
+
* panel script hears its children. Bubbling (not a world broadcast) keeps
|
|
10885
|
+
* two spawned copies of the same menu prefab independent.
|
|
10886
|
+
*/
|
|
10887
|
+
dispatchBubbling(world, entityId, visit, input = null, value) {
|
|
10888
|
+
const event = {
|
|
10889
|
+
targetEntityId: entityId,
|
|
10890
|
+
actionId: this.actionIdOf(world, entityId),
|
|
10891
|
+
input,
|
|
10892
|
+
...value === void 0 ? {} : { value }
|
|
10893
|
+
};
|
|
10894
|
+
const visited = /* @__PURE__ */ new Set();
|
|
10895
|
+
let current = entityId;
|
|
10896
|
+
for (; ; ) {
|
|
10897
|
+
if (visited.has(current)) break;
|
|
10898
|
+
visited.add(current);
|
|
10899
|
+
const script = world.getComponent(current, ScriptComponent);
|
|
10900
|
+
const instances = script?.scriptInstances;
|
|
10901
|
+
if (instances) {
|
|
10902
|
+
for (const instance of instances.values()) {
|
|
10903
|
+
if (!(instance instanceof Behaviour)) continue;
|
|
10904
|
+
try {
|
|
10905
|
+
visit(instance, event);
|
|
10906
|
+
} catch (error) {
|
|
10907
|
+
console.error("[UIInteractionSystem] hook failed:", error);
|
|
10908
|
+
}
|
|
10909
|
+
}
|
|
10910
|
+
}
|
|
10911
|
+
const parentId = world.getComponent(current, ParentId)?.value;
|
|
10912
|
+
if (typeof parentId !== "number") break;
|
|
10913
|
+
current = parentId;
|
|
10914
|
+
}
|
|
10915
|
+
}
|
|
10916
|
+
actionIdOf(world, entityId) {
|
|
10917
|
+
const button = world.getComponent(entityId, UIButtonComponent);
|
|
10918
|
+
if (button?.actionId) return button.actionId;
|
|
10919
|
+
const toggle = world.getComponent(entityId, UIToggleComponent);
|
|
10920
|
+
if (toggle?.actionId) return toggle.actionId;
|
|
10921
|
+
const slider = world.getComponent(entityId, UISliderComponent);
|
|
10922
|
+
if (slider?.actionId) return slider.actionId;
|
|
10923
|
+
const name = world.getComponent(entityId, Name)?.value;
|
|
10924
|
+
return typeof name === "string" ? name : String(entityId);
|
|
10925
|
+
}
|
|
10926
|
+
// ── Pressed/hover tint ─────────────────────────────────────────────────────
|
|
10927
|
+
beginPressTint(world, entityId) {
|
|
10928
|
+
this.setTint(world, entityId, PRESS_TINT);
|
|
10929
|
+
this.tintFloors.set(entityId, PRESS_TINT_MIN_SECONDS);
|
|
10930
|
+
}
|
|
10931
|
+
endPressTint(entityId) {
|
|
10932
|
+
if (!this.tintFloors.has(entityId)) {
|
|
10933
|
+
this.tintFloors.set(entityId, 0);
|
|
10934
|
+
}
|
|
10935
|
+
}
|
|
10936
|
+
updateTints(world, deltaTime) {
|
|
10937
|
+
for (const [entityId, remaining] of this.tintFloors) {
|
|
10938
|
+
const next = remaining - deltaTime;
|
|
10939
|
+
const stillHeld = [...this.xrPresses.values()].some((p) => p.entityId === entityId) || this.mousePress?.entityId === entityId;
|
|
10940
|
+
if (stillHeld) continue;
|
|
10941
|
+
if (next <= 0) {
|
|
10942
|
+
this.tintFloors.delete(entityId);
|
|
10943
|
+
this.setTint(
|
|
10944
|
+
world,
|
|
10945
|
+
entityId,
|
|
10946
|
+
this.mouseHover === entityId ? HOVER_TINT : 1
|
|
10947
|
+
);
|
|
10948
|
+
} else {
|
|
10949
|
+
this.tintFloors.set(entityId, next);
|
|
10950
|
+
}
|
|
10951
|
+
}
|
|
10952
|
+
}
|
|
10953
|
+
setTint(world, entityId, scalar) {
|
|
10954
|
+
const object = this.widgetObject(world, entityId);
|
|
10955
|
+
if (!object) return;
|
|
10956
|
+
if (scalar === 1) this.tinted.delete(entityId);
|
|
10957
|
+
else this.tinted.add(entityId);
|
|
10958
|
+
object.traverse((child) => {
|
|
10959
|
+
const material = child.material;
|
|
10960
|
+
if (material?.color) material.color.setScalar(scalar);
|
|
10961
|
+
});
|
|
10962
|
+
}
|
|
10963
|
+
};
|
|
10964
|
+
__name(_UIInteractionSystem, "UIInteractionSystem");
|
|
10965
|
+
var UIInteractionSystem = _UIInteractionSystem;
|
|
10966
|
+
|
|
10967
|
+
// core/ui/uiCanvas.ts
|
|
10968
|
+
var DEFAULT_PIXELS_PER_METER = 1024;
|
|
10969
|
+
var MIN_PIXELS_PER_METER = 256;
|
|
10970
|
+
var MAX_PIXELS_PER_METER = 2048;
|
|
10971
|
+
var MAX_CANVAS_SIDE = 2048;
|
|
10972
|
+
function resolveCanvasSize(widthMeters, heightMeters, pixelDensity = DEFAULT_PIXELS_PER_METER) {
|
|
10973
|
+
const w = Math.max(0.01, widthMeters);
|
|
10974
|
+
const h = Math.max(0.01, heightMeters);
|
|
10975
|
+
const authored = Math.min(
|
|
10976
|
+
MAX_PIXELS_PER_METER,
|
|
10977
|
+
Math.max(MIN_PIXELS_PER_METER, pixelDensity)
|
|
10978
|
+
);
|
|
10979
|
+
const density = Math.min(authored, MAX_CANVAS_SIDE / Math.max(w, h));
|
|
10980
|
+
const even = /* @__PURE__ */ __name((v) => Math.max(2, 2 * Math.round(v * density / 2)), "even");
|
|
10981
|
+
return { pxWidth: even(w), pxHeight: even(h), density };
|
|
10982
|
+
}
|
|
10983
|
+
__name(resolveCanvasSize, "resolveCanvasSize");
|
|
10984
|
+
function wrapText(measure, text, maxWidthPx) {
|
|
10985
|
+
const lines = [];
|
|
10986
|
+
for (const paragraph of text.split("\n")) {
|
|
10987
|
+
const words = paragraph.split(/\s+/).filter((w) => w.length > 0);
|
|
10988
|
+
if (words.length === 0) {
|
|
10989
|
+
lines.push("");
|
|
10990
|
+
continue;
|
|
10991
|
+
}
|
|
10992
|
+
let line = "";
|
|
10993
|
+
for (const word of words) {
|
|
10994
|
+
const candidate = line ? `${line} ${word}` : word;
|
|
10995
|
+
if (measure(candidate) <= maxWidthPx) {
|
|
10996
|
+
line = candidate;
|
|
10997
|
+
continue;
|
|
10998
|
+
}
|
|
10999
|
+
if (line) {
|
|
11000
|
+
lines.push(line);
|
|
11001
|
+
line = "";
|
|
11002
|
+
}
|
|
11003
|
+
if (measure(word) <= maxWidthPx) {
|
|
11004
|
+
line = word;
|
|
11005
|
+
continue;
|
|
11006
|
+
}
|
|
11007
|
+
let chunk = "";
|
|
11008
|
+
for (const char of word) {
|
|
11009
|
+
const next = chunk + char;
|
|
11010
|
+
if (measure(next) <= maxWidthPx || chunk.length === 0) {
|
|
11011
|
+
chunk = next;
|
|
11012
|
+
} else {
|
|
11013
|
+
lines.push(chunk);
|
|
11014
|
+
chunk = char;
|
|
11015
|
+
}
|
|
11016
|
+
}
|
|
11017
|
+
line = chunk;
|
|
11018
|
+
}
|
|
11019
|
+
lines.push(line);
|
|
11020
|
+
}
|
|
11021
|
+
return lines;
|
|
11022
|
+
}
|
|
11023
|
+
__name(wrapText, "wrapText");
|
|
11024
|
+
var ELLIPSIS = "\u2026";
|
|
11025
|
+
function clampLines(measure, lines, maxLines, maxWidthPx) {
|
|
11026
|
+
if (maxLines <= 0) return [];
|
|
11027
|
+
if (lines.length <= maxLines) return lines;
|
|
11028
|
+
const kept = lines.slice(0, maxLines);
|
|
11029
|
+
let last = kept[maxLines - 1];
|
|
11030
|
+
while (last.length > 0 && measure(last + ELLIPSIS) > maxWidthPx) {
|
|
11031
|
+
last = last.slice(0, -1);
|
|
11032
|
+
}
|
|
11033
|
+
kept[maxLines - 1] = last + ELLIPSIS;
|
|
11034
|
+
return kept;
|
|
11035
|
+
}
|
|
11036
|
+
__name(clampLines, "clampLines");
|
|
11037
|
+
var UI_FONT_STACK = '-apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
|
|
11038
|
+
function tracePath(ctx, x, y, w, h, r) {
|
|
11039
|
+
const radius = Math.max(0, Math.min(r, w / 2, h / 2));
|
|
11040
|
+
ctx.beginPath();
|
|
11041
|
+
if (typeof ctx.roundRect === "function") {
|
|
11042
|
+
ctx.roundRect(x, y, w, h, radius);
|
|
11043
|
+
return;
|
|
11044
|
+
}
|
|
11045
|
+
ctx.moveTo(x + radius, y);
|
|
11046
|
+
ctx.arcTo(x + w, y, x + w, y + h, radius);
|
|
11047
|
+
ctx.arcTo(x + w, y + h, x, y + h, radius);
|
|
11048
|
+
ctx.arcTo(x, y + h, x, y, radius);
|
|
11049
|
+
ctx.arcTo(x, y, x + w, y, radius);
|
|
11050
|
+
ctx.closePath();
|
|
11051
|
+
}
|
|
11052
|
+
__name(tracePath, "tracePath");
|
|
11053
|
+
function withOpacity(ctx, color, opacity) {
|
|
11054
|
+
if (opacity >= 1) return color;
|
|
11055
|
+
ctx.save();
|
|
11056
|
+
ctx.fillStyle = color;
|
|
11057
|
+
const parsed = ctx.fillStyle;
|
|
11058
|
+
ctx.restore();
|
|
11059
|
+
if (parsed.startsWith("#") && (parsed.length === 7 || parsed.length === 4)) {
|
|
11060
|
+
const hex = parsed.length === 4 ? `#${parsed[1]}${parsed[1]}${parsed[2]}${parsed[2]}${parsed[3]}${parsed[3]}` : parsed;
|
|
11061
|
+
const r = Number.parseInt(hex.slice(1, 3), 16);
|
|
11062
|
+
const g = Number.parseInt(hex.slice(3, 5), 16);
|
|
11063
|
+
const b = Number.parseInt(hex.slice(5, 7), 16);
|
|
11064
|
+
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
|
11065
|
+
}
|
|
11066
|
+
return parsed;
|
|
11067
|
+
}
|
|
11068
|
+
__name(withOpacity, "withOpacity");
|
|
11069
|
+
function drawChrome(ctx, pxWidth, pxHeight, density, style) {
|
|
11070
|
+
ctx.clearRect(0, 0, pxWidth, pxHeight);
|
|
11071
|
+
const border = Math.max(0, style.borderWidthMeters * density);
|
|
11072
|
+
const radius = style.cornerRadiusMeters * density;
|
|
11073
|
+
tracePath(ctx, 0, 0, pxWidth, pxHeight, radius);
|
|
11074
|
+
ctx.fillStyle = withOpacity(
|
|
11075
|
+
ctx,
|
|
11076
|
+
style.backgroundColor,
|
|
11077
|
+
Math.max(0, Math.min(1, style.backgroundOpacity))
|
|
11078
|
+
);
|
|
11079
|
+
ctx.fill();
|
|
11080
|
+
if (border > 0) {
|
|
11081
|
+
tracePath(
|
|
11082
|
+
ctx,
|
|
11083
|
+
border / 2,
|
|
11084
|
+
border / 2,
|
|
11085
|
+
pxWidth - border,
|
|
11086
|
+
pxHeight - border,
|
|
11087
|
+
Math.max(0, radius - border / 2)
|
|
11088
|
+
);
|
|
11089
|
+
ctx.strokeStyle = style.borderColor;
|
|
11090
|
+
ctx.lineWidth = border;
|
|
11091
|
+
ctx.stroke();
|
|
11092
|
+
}
|
|
11093
|
+
}
|
|
11094
|
+
__name(drawChrome, "drawChrome");
|
|
11095
|
+
function drawTextBlock(ctx, pxWidth, pxHeight, density, text, style) {
|
|
11096
|
+
const fontPx = Math.max(4, style.fontSizeMeters * density);
|
|
11097
|
+
const paddingPx = style.paddingMeters * density;
|
|
11098
|
+
const contentW = Math.max(1, pxWidth - paddingPx * 2);
|
|
11099
|
+
const contentH = Math.max(1, pxHeight - paddingPx * 2);
|
|
11100
|
+
const lineHeightPx = fontPx * style.lineHeight;
|
|
11101
|
+
ctx.font = `${style.fontWeight} ${fontPx}px ${UI_FONT_STACK}`;
|
|
11102
|
+
const measure = /* @__PURE__ */ __name((s) => ctx.measureText(s).width, "measure");
|
|
11103
|
+
const maxLines = Math.max(1, Math.floor(contentH / lineHeightPx));
|
|
11104
|
+
const lines = clampLines(
|
|
11105
|
+
measure,
|
|
11106
|
+
wrapText(measure, text, contentW),
|
|
11107
|
+
maxLines,
|
|
11108
|
+
contentW
|
|
11109
|
+
);
|
|
11110
|
+
const blockH = lines.length * lineHeightPx;
|
|
11111
|
+
const top = style.valign === "top" ? paddingPx : style.valign === "bottom" ? pxHeight - paddingPx - blockH : (pxHeight - blockH) / 2;
|
|
11112
|
+
const anchorX = style.align === "left" ? paddingPx : style.align === "right" ? pxWidth - paddingPx : pxWidth / 2;
|
|
11113
|
+
ctx.save();
|
|
11114
|
+
tracePath(ctx, 0, 0, pxWidth, pxHeight, 0);
|
|
11115
|
+
ctx.clip();
|
|
11116
|
+
ctx.fillStyle = style.color;
|
|
11117
|
+
ctx.textAlign = style.align;
|
|
11118
|
+
ctx.textBaseline = "middle";
|
|
11119
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
11120
|
+
ctx.fillText(lines[i], anchorX, top + (i + 0.5) * lineHeightPx);
|
|
11121
|
+
}
|
|
11122
|
+
ctx.restore();
|
|
11123
|
+
}
|
|
11124
|
+
__name(drawTextBlock, "drawTextBlock");
|
|
11125
|
+
function panelSignature(widthM, heightM, size, style) {
|
|
11126
|
+
return JSON.stringify([
|
|
11127
|
+
widthM,
|
|
11128
|
+
heightM,
|
|
11129
|
+
size.pxWidth,
|
|
11130
|
+
size.pxHeight,
|
|
11131
|
+
style.backgroundColor,
|
|
11132
|
+
style.backgroundOpacity,
|
|
11133
|
+
style.cornerRadiusMeters,
|
|
11134
|
+
style.borderWidthMeters,
|
|
11135
|
+
style.borderColor
|
|
11136
|
+
]);
|
|
11137
|
+
}
|
|
11138
|
+
__name(panelSignature, "panelSignature");
|
|
11139
|
+
function textSignature(widthM, heightM, size, text, style) {
|
|
11140
|
+
return JSON.stringify([
|
|
11141
|
+
widthM,
|
|
11142
|
+
heightM,
|
|
11143
|
+
size.pxWidth,
|
|
11144
|
+
size.pxHeight,
|
|
11145
|
+
text,
|
|
11146
|
+
style.fontSizeMeters,
|
|
11147
|
+
style.fontWeight,
|
|
11148
|
+
style.color,
|
|
11149
|
+
style.align,
|
|
11150
|
+
style.valign,
|
|
11151
|
+
style.lineHeight,
|
|
11152
|
+
style.paddingMeters
|
|
11153
|
+
]);
|
|
11154
|
+
}
|
|
11155
|
+
__name(textSignature, "textSignature");
|
|
11156
|
+
function buttonSignature(widthM, heightM, size, panel, label, text) {
|
|
11157
|
+
return JSON.stringify([
|
|
11158
|
+
panelSignature(widthM, heightM, size, panel),
|
|
11159
|
+
label,
|
|
11160
|
+
text.fontSizeMeters,
|
|
11161
|
+
text.fontWeight,
|
|
11162
|
+
text.color
|
|
11163
|
+
]);
|
|
11164
|
+
}
|
|
11165
|
+
__name(buttonSignature, "buttonSignature");
|
|
11166
|
+
function trackSignature(widthM, heightM, size, panel, accentColor, knobColor) {
|
|
11167
|
+
return JSON.stringify([
|
|
11168
|
+
panelSignature(widthM, heightM, size, panel),
|
|
11169
|
+
accentColor,
|
|
11170
|
+
knobColor
|
|
11171
|
+
]);
|
|
11172
|
+
}
|
|
11173
|
+
__name(trackSignature, "trackSignature");
|
|
11174
|
+
function drawRoundedBar(ctx, pxX, pxY, pxW, pxH, color) {
|
|
11175
|
+
tracePath(ctx, pxX, pxY, pxW, pxH, pxH / 2);
|
|
11176
|
+
ctx.fillStyle = color;
|
|
11177
|
+
ctx.fill();
|
|
11178
|
+
}
|
|
11179
|
+
__name(drawRoundedBar, "drawRoundedBar");
|
|
11180
|
+
|
|
11181
|
+
// core/systems/UIPanelSystem.ts
|
|
11182
|
+
var FACE_RENDER_ORDER = 10;
|
|
11183
|
+
var CHILD_Z = 2e-3;
|
|
11184
|
+
var _UIPanelSystem = class _UIPanelSystem {
|
|
11185
|
+
constructor(options) {
|
|
11186
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
11187
|
+
this.createCanvas = options?.createCanvas ?? (() => document.createElement("canvas"));
|
|
11188
|
+
}
|
|
11189
|
+
update(world, deltaTime = 0) {
|
|
11190
|
+
const live = /* @__PURE__ */ new Set();
|
|
11191
|
+
for (const entityId of world.query(UIPanelComponent, Object3DRef)) {
|
|
11192
|
+
const panel = world.getComponent(entityId, UIPanelComponent);
|
|
11193
|
+
if (!panel) continue;
|
|
11194
|
+
live.add(entityId);
|
|
11195
|
+
this.syncPanel(world, entityId, panel);
|
|
11196
|
+
this.applyFollow(world, entityId, panel);
|
|
11197
|
+
}
|
|
11198
|
+
for (const entityId of world.query(UITextComponent, Object3DRef)) {
|
|
11199
|
+
const text = world.getComponent(entityId, UITextComponent);
|
|
11200
|
+
if (!text) continue;
|
|
11201
|
+
live.add(entityId);
|
|
11202
|
+
this.syncText(world, entityId, text);
|
|
11203
|
+
}
|
|
11204
|
+
for (const entityId of world.query(UIButtonComponent, Object3DRef)) {
|
|
11205
|
+
const button = world.getComponent(entityId, UIButtonComponent);
|
|
11206
|
+
if (!button) continue;
|
|
11207
|
+
live.add(entityId);
|
|
11208
|
+
this.syncButton(world, entityId, button);
|
|
11209
|
+
}
|
|
11210
|
+
for (const entityId of world.query(UIToggleComponent, Object3DRef)) {
|
|
11211
|
+
const toggle = world.getComponent(entityId, UIToggleComponent);
|
|
11212
|
+
if (!toggle) continue;
|
|
11213
|
+
live.add(entityId);
|
|
11214
|
+
this.syncToggle(world, entityId, toggle, deltaTime);
|
|
11215
|
+
}
|
|
11216
|
+
for (const entityId of world.query(UISliderComponent, Object3DRef)) {
|
|
11217
|
+
const slider = world.getComponent(entityId, UISliderComponent);
|
|
11218
|
+
if (!slider) continue;
|
|
11219
|
+
live.add(entityId);
|
|
11220
|
+
this.syncSlider(world, entityId, slider);
|
|
11221
|
+
}
|
|
11222
|
+
for (const [entityId, entry] of this.entries) {
|
|
11223
|
+
if (!live.has(entityId)) {
|
|
11224
|
+
this.destroyEntry(entry);
|
|
11225
|
+
this.entries.delete(entityId);
|
|
11226
|
+
}
|
|
11227
|
+
}
|
|
11228
|
+
}
|
|
11229
|
+
/** All GPU/canvas resources; call on world teardown. */
|
|
11230
|
+
dispose() {
|
|
11231
|
+
for (const entry of this.entries.values()) this.destroyEntry(entry);
|
|
11232
|
+
this.entries.clear();
|
|
11233
|
+
}
|
|
11234
|
+
// ── Widgets ────────────────────────────────────────────────────────────────
|
|
11235
|
+
syncPanel(world, entityId, panel) {
|
|
11236
|
+
const size = resolveCanvasSize(panel.width, panel.height, panel.pixelDensity);
|
|
11237
|
+
const style = {
|
|
11238
|
+
backgroundColor: panel.backgroundColor,
|
|
11239
|
+
backgroundOpacity: panel.backgroundOpacity,
|
|
11240
|
+
cornerRadiusMeters: panel.cornerRadius,
|
|
11241
|
+
borderWidthMeters: panel.borderWidth,
|
|
11242
|
+
borderColor: panel.borderColor
|
|
11243
|
+
};
|
|
11244
|
+
const signature = panelSignature(panel.width, panel.height, size, style);
|
|
11245
|
+
this.rebuildIfChanged(world, entityId, signature, panel.width, panel.height, size, (ctx) => {
|
|
11246
|
+
drawChrome(ctx, size.pxWidth, size.pxHeight, size.density, style);
|
|
11247
|
+
});
|
|
11248
|
+
}
|
|
11249
|
+
syncText(world, entityId, text) {
|
|
11250
|
+
const size = resolveCanvasSize(text.width, text.height);
|
|
11251
|
+
const style = {
|
|
11252
|
+
fontSizeMeters: text.fontSize,
|
|
11253
|
+
fontWeight: text.fontWeight,
|
|
11254
|
+
color: text.color,
|
|
11255
|
+
align: text.align,
|
|
11256
|
+
valign: text.valign,
|
|
11257
|
+
lineHeight: text.lineHeight,
|
|
11258
|
+
paddingMeters: text.padding
|
|
11259
|
+
};
|
|
11260
|
+
const signature = textSignature(text.width, text.height, size, text.text, style);
|
|
11261
|
+
this.rebuildIfChanged(world, entityId, signature, text.width, text.height, size, (ctx) => {
|
|
11262
|
+
ctx.clearRect(0, 0, size.pxWidth, size.pxHeight);
|
|
11263
|
+
drawTextBlock(ctx, size.pxWidth, size.pxHeight, size.density, text.text, style);
|
|
11264
|
+
});
|
|
11265
|
+
}
|
|
11266
|
+
syncButton(world, entityId, button) {
|
|
11267
|
+
const size = resolveCanvasSize(button.width, button.height);
|
|
11268
|
+
const chrome = {
|
|
11269
|
+
backgroundColor: button.backgroundColor,
|
|
11270
|
+
backgroundOpacity: button.backgroundOpacity,
|
|
11271
|
+
cornerRadiusMeters: button.cornerRadius,
|
|
11272
|
+
borderWidthMeters: button.borderWidth,
|
|
11273
|
+
borderColor: button.borderColor
|
|
11274
|
+
};
|
|
11275
|
+
const label = {
|
|
11276
|
+
fontSizeMeters: button.fontSize,
|
|
11277
|
+
fontWeight: button.fontWeight,
|
|
11278
|
+
color: button.textColor,
|
|
11279
|
+
align: "center",
|
|
11280
|
+
valign: "middle",
|
|
11281
|
+
lineHeight: 1.2,
|
|
11282
|
+
paddingMeters: 0.01
|
|
11283
|
+
};
|
|
11284
|
+
const signature = buttonSignature(
|
|
11285
|
+
button.width,
|
|
11286
|
+
button.height,
|
|
11287
|
+
size,
|
|
11288
|
+
chrome,
|
|
11289
|
+
button.label,
|
|
11290
|
+
label
|
|
11291
|
+
);
|
|
11292
|
+
this.rebuildIfChanged(world, entityId, signature, button.width, button.height, size, (ctx) => {
|
|
11293
|
+
drawChrome(ctx, size.pxWidth, size.pxHeight, size.density, chrome);
|
|
11294
|
+
drawTextBlock(ctx, size.pxWidth, size.pxHeight, size.density, button.label, label);
|
|
11295
|
+
});
|
|
11296
|
+
}
|
|
11297
|
+
syncToggle(world, entityId, toggle, deltaTime) {
|
|
11298
|
+
const size = resolveCanvasSize(toggle.width, toggle.height);
|
|
11299
|
+
const chrome = {
|
|
11300
|
+
backgroundColor: toggle.value ? toggle.accentColor : toggle.trackColor,
|
|
11301
|
+
backgroundOpacity: 1,
|
|
11302
|
+
cornerRadiusMeters: toggle.height / 2,
|
|
11303
|
+
borderWidthMeters: 0,
|
|
11304
|
+
borderColor: "#000000"
|
|
11305
|
+
};
|
|
11306
|
+
const signature = JSON.stringify([
|
|
11307
|
+
trackSignature(
|
|
11308
|
+
toggle.width,
|
|
11309
|
+
toggle.height,
|
|
11310
|
+
size,
|
|
11311
|
+
chrome,
|
|
11312
|
+
toggle.accentColor,
|
|
11313
|
+
toggle.knobColor
|
|
11314
|
+
),
|
|
11315
|
+
toggle.value
|
|
11316
|
+
]);
|
|
11317
|
+
const entry = this.rebuildIfChanged(
|
|
11318
|
+
world,
|
|
11319
|
+
entityId,
|
|
11320
|
+
signature,
|
|
11321
|
+
toggle.width,
|
|
11322
|
+
toggle.height,
|
|
11323
|
+
size,
|
|
11324
|
+
(ctx) => {
|
|
11325
|
+
drawChrome(ctx, size.pxWidth, size.pxHeight, size.density, chrome);
|
|
11326
|
+
}
|
|
11327
|
+
);
|
|
11328
|
+
if (!entry) return;
|
|
11329
|
+
if (!entry.knob) {
|
|
11330
|
+
entry.knob = this.makeDisc(
|
|
11331
|
+
toggle.height * 0.4,
|
|
11332
|
+
toggle.knobColor,
|
|
11333
|
+
entry.face.parent,
|
|
11334
|
+
CHILD_Z
|
|
11335
|
+
);
|
|
11336
|
+
}
|
|
11337
|
+
const inset = toggle.height / 2;
|
|
11338
|
+
const target = toggle.value ? toggle.width / 2 - inset : -(toggle.width / 2 - inset);
|
|
11339
|
+
const knob = entry.knob;
|
|
11340
|
+
const ease = Math.min(1, deltaTime * 14);
|
|
11341
|
+
knob.position.x += (target - knob.position.x) * (deltaTime > 0 ? ease : 1);
|
|
11342
|
+
knob.position.z = CHILD_Z;
|
|
11343
|
+
}
|
|
11344
|
+
syncSlider(world, entityId, slider) {
|
|
11345
|
+
const size = resolveCanvasSize(slider.width, slider.height);
|
|
11346
|
+
const trackThickness = slider.height * 0.32;
|
|
11347
|
+
const knobRadius = slider.height * 0.45;
|
|
11348
|
+
const usable = Math.max(1e-3, slider.width - slider.height);
|
|
11349
|
+
const chrome = {
|
|
11350
|
+
backgroundColor: slider.trackColor,
|
|
11351
|
+
backgroundOpacity: 1,
|
|
11352
|
+
cornerRadiusMeters: trackThickness / 2,
|
|
11353
|
+
borderWidthMeters: 0,
|
|
11354
|
+
borderColor: "#000000"
|
|
11355
|
+
};
|
|
11356
|
+
const signature = trackSignature(
|
|
11357
|
+
slider.width,
|
|
11358
|
+
slider.height,
|
|
11359
|
+
size,
|
|
11360
|
+
chrome,
|
|
11361
|
+
slider.accentColor,
|
|
11362
|
+
slider.knobColor
|
|
11363
|
+
);
|
|
11364
|
+
const entry = this.rebuildIfChanged(
|
|
11365
|
+
world,
|
|
11366
|
+
entityId,
|
|
11367
|
+
signature,
|
|
11368
|
+
slider.width,
|
|
11369
|
+
slider.height,
|
|
11370
|
+
size,
|
|
11371
|
+
(ctx) => {
|
|
11372
|
+
ctx.clearRect(0, 0, size.pxWidth, size.pxHeight);
|
|
11373
|
+
const trackWidthM = usable + trackThickness;
|
|
11374
|
+
drawRoundedBar(
|
|
11375
|
+
ctx,
|
|
11376
|
+
(slider.width - trackWidthM) / 2 * size.density,
|
|
11377
|
+
(slider.height - trackThickness) / 2 * size.density,
|
|
11378
|
+
trackWidthM * size.density,
|
|
11379
|
+
trackThickness * size.density,
|
|
11380
|
+
slider.trackColor
|
|
11381
|
+
);
|
|
11382
|
+
}
|
|
11383
|
+
);
|
|
11384
|
+
if (!entry) return;
|
|
11385
|
+
if (!entry.fill) {
|
|
11386
|
+
entry.fill = this.makeQuad(
|
|
11387
|
+
1,
|
|
11388
|
+
trackThickness,
|
|
11389
|
+
slider.accentColor,
|
|
11390
|
+
entry.face.parent,
|
|
11391
|
+
CHILD_Z / 2
|
|
11392
|
+
);
|
|
11393
|
+
entry.cap = this.makeDisc(
|
|
11394
|
+
trackThickness / 2,
|
|
11395
|
+
slider.accentColor,
|
|
11396
|
+
entry.face.parent,
|
|
11397
|
+
CHILD_Z / 2,
|
|
11398
|
+
Math.PI / 2,
|
|
11399
|
+
Math.PI
|
|
11400
|
+
);
|
|
11401
|
+
entry.cap.position.x = -usable / 2;
|
|
11402
|
+
}
|
|
11403
|
+
if (!entry.knob) {
|
|
11404
|
+
entry.knob = this.makeDisc(
|
|
11405
|
+
knobRadius,
|
|
11406
|
+
slider.knobColor,
|
|
11407
|
+
entry.face.parent,
|
|
11408
|
+
CHILD_Z
|
|
11409
|
+
);
|
|
11410
|
+
}
|
|
11411
|
+
const range = Math.max(1e-6, slider.max - slider.min);
|
|
11412
|
+
const t = Math.min(1, Math.max(0, (slider.value - slider.min) / range));
|
|
11413
|
+
entry.knob.position.x = -usable / 2 + t * usable;
|
|
11414
|
+
entry.knob.position.z = CHILD_Z;
|
|
11415
|
+
entry.fill.scale.x = Math.max(1e-4, t * usable);
|
|
11416
|
+
entry.fill.position.x = -usable / 2 + t * usable / 2;
|
|
11417
|
+
entry.fill.position.z = CHILD_Z / 2;
|
|
11418
|
+
}
|
|
11419
|
+
// ── Machinery ──────────────────────────────────────────────────────────────
|
|
11420
|
+
rebuildIfChanged(world, entityId, signature, widthM, heightM, size, draw) {
|
|
11421
|
+
const root = world.getComponent(entityId, Object3DRef)?.object;
|
|
11422
|
+
if (!root) return null;
|
|
11423
|
+
let entry = this.entries.get(entityId);
|
|
11424
|
+
if (entry && entry.signature === signature) return entry;
|
|
11425
|
+
if (entry) this.disposeMovingParts(entry);
|
|
11426
|
+
if (!entry) {
|
|
11427
|
+
const canvas2 = this.createCanvas();
|
|
11428
|
+
const texture = new THREE26__namespace.CanvasTexture(
|
|
11429
|
+
canvas2
|
|
11430
|
+
);
|
|
11431
|
+
this.configureTexture(world, texture);
|
|
11432
|
+
const material = new THREE26__namespace.MeshBasicMaterial({
|
|
11433
|
+
map: texture,
|
|
11434
|
+
transparent: true,
|
|
11435
|
+
depthWrite: false,
|
|
11436
|
+
// UI colours are authored absolutes; scene tone mapping would shift
|
|
11437
|
+
// them (the classic "why is my white UI beige" bug).
|
|
11438
|
+
toneMapped: false,
|
|
11439
|
+
side: THREE26__namespace.DoubleSide,
|
|
11440
|
+
premultipliedAlpha: true
|
|
11441
|
+
});
|
|
11442
|
+
const face = new THREE26__namespace.Mesh(
|
|
11443
|
+
new THREE26__namespace.PlaneGeometry(widthM, heightM),
|
|
11444
|
+
material
|
|
11445
|
+
);
|
|
11446
|
+
face.renderOrder = FACE_RENDER_ORDER;
|
|
11447
|
+
root.add(face);
|
|
11448
|
+
entry = {
|
|
11449
|
+
signature: "",
|
|
11450
|
+
face,
|
|
11451
|
+
texture,
|
|
11452
|
+
material,
|
|
11453
|
+
canvas: canvas2,
|
|
11454
|
+
knob: null,
|
|
11455
|
+
fill: null,
|
|
11456
|
+
cap: null
|
|
11457
|
+
};
|
|
11458
|
+
this.entries.set(entityId, entry);
|
|
11459
|
+
}
|
|
11460
|
+
const { canvas } = entry;
|
|
11461
|
+
if (canvas.width !== size.pxWidth || canvas.height !== size.pxHeight) {
|
|
11462
|
+
canvas.width = size.pxWidth;
|
|
11463
|
+
canvas.height = size.pxHeight;
|
|
11464
|
+
}
|
|
11465
|
+
const params = entry.face.geometry.parameters;
|
|
11466
|
+
if (params.width !== widthM || params.height !== heightM) {
|
|
11467
|
+
entry.face.geometry.dispose();
|
|
11468
|
+
entry.face.geometry = new THREE26__namespace.PlaneGeometry(widthM, heightM);
|
|
11469
|
+
}
|
|
11470
|
+
const ctx = canvas.getContext("2d");
|
|
11471
|
+
if (ctx) draw(ctx);
|
|
11472
|
+
entry.texture.needsUpdate = true;
|
|
11473
|
+
entry.signature = signature;
|
|
11474
|
+
return entry;
|
|
11475
|
+
}
|
|
11476
|
+
configureTexture(world, texture) {
|
|
11477
|
+
texture.colorSpace = THREE26__namespace.SRGBColorSpace;
|
|
11478
|
+
texture.generateMipmaps = true;
|
|
11479
|
+
texture.minFilter = THREE26__namespace.LinearMipmapLinearFilter;
|
|
11480
|
+
texture.magFilter = THREE26__namespace.LinearFilter;
|
|
11481
|
+
texture.premultiplyAlpha = true;
|
|
11482
|
+
try {
|
|
11483
|
+
const max = world.getRenderer().capabilities?.getMaxAnisotropy?.();
|
|
11484
|
+
if (typeof max === "number") texture.anisotropy = Math.min(8, max);
|
|
11485
|
+
} catch {
|
|
11486
|
+
}
|
|
11487
|
+
}
|
|
11488
|
+
makeQuad(widthM, heightM, color, parent, z) {
|
|
11489
|
+
return this.addPart(
|
|
11490
|
+
new THREE26__namespace.PlaneGeometry(widthM, heightM),
|
|
11491
|
+
color,
|
|
11492
|
+
parent,
|
|
11493
|
+
z
|
|
11494
|
+
);
|
|
11495
|
+
}
|
|
11496
|
+
/** A filled disc (or arc segment, for the fill's rounded cap). */
|
|
11497
|
+
makeDisc(radiusM, color, parent, z, thetaStart = 0, thetaLength = Math.PI * 2) {
|
|
11498
|
+
return this.addPart(
|
|
11499
|
+
new THREE26__namespace.CircleGeometry(radiusM, 32, thetaStart, thetaLength),
|
|
11500
|
+
color,
|
|
11501
|
+
parent,
|
|
11502
|
+
z
|
|
11503
|
+
);
|
|
11504
|
+
}
|
|
11505
|
+
addPart(geometry, color, parent, z) {
|
|
11506
|
+
const mesh = new THREE26__namespace.Mesh(
|
|
11507
|
+
geometry,
|
|
11508
|
+
new THREE26__namespace.MeshBasicMaterial({
|
|
11509
|
+
color,
|
|
11510
|
+
transparent: true,
|
|
11511
|
+
depthWrite: false,
|
|
11512
|
+
toneMapped: false,
|
|
11513
|
+
side: THREE26__namespace.DoubleSide
|
|
11514
|
+
})
|
|
11515
|
+
);
|
|
11516
|
+
mesh.renderOrder = FACE_RENDER_ORDER + 1;
|
|
11517
|
+
mesh.position.z = z;
|
|
11518
|
+
mesh.raycast = () => {
|
|
11519
|
+
};
|
|
11520
|
+
parent?.add(mesh);
|
|
11521
|
+
return mesh;
|
|
11522
|
+
}
|
|
11523
|
+
/** Remove + dispose knob/fill/cap so a sync pass rebuilds them fresh. */
|
|
11524
|
+
disposeMovingParts(entry) {
|
|
11525
|
+
for (const key of ["knob", "fill", "cap"]) {
|
|
11526
|
+
const mesh = entry[key];
|
|
11527
|
+
if (!mesh) continue;
|
|
11528
|
+
mesh.removeFromParent();
|
|
11529
|
+
mesh.geometry.dispose();
|
|
11530
|
+
mesh.material.dispose();
|
|
11531
|
+
entry[key] = null;
|
|
11532
|
+
}
|
|
11533
|
+
}
|
|
11534
|
+
/**
|
|
11535
|
+
* Billboard: yaw the ENTITY TRANSFORM toward the camera. Writing the
|
|
11536
|
+
* Object3D directly would be overwritten by `TransformSyncSystem` (which
|
|
11537
|
+
* runs after this system, before render) — the same reason locomotion
|
|
11538
|
+
* writes the rig transform. Skipped in the editor so the gizmo and the
|
|
11539
|
+
* authored rotation stay in charge while authoring.
|
|
11540
|
+
*/
|
|
11541
|
+
applyFollow(world, entityId, panel) {
|
|
11542
|
+
if (panel.follow !== "billboard" || world.isEditorMode()) return;
|
|
11543
|
+
const transform = world.getComponent(entityId, TransformComponent);
|
|
11544
|
+
const object = world.getComponent(entityId, Object3DRef)?.object;
|
|
11545
|
+
if (!transform || !object) return;
|
|
11546
|
+
const camera = world.getCamera();
|
|
11547
|
+
const cam = new THREE26__namespace.Vector3();
|
|
11548
|
+
const self = new THREE26__namespace.Vector3();
|
|
11549
|
+
camera.getWorldPosition(cam);
|
|
11550
|
+
object.getWorldPosition(self);
|
|
11551
|
+
transform.rotation.y = Math.atan2(cam.x - self.x, cam.z - self.z);
|
|
11552
|
+
}
|
|
11553
|
+
destroyEntry(entry) {
|
|
11554
|
+
this.disposeMovingParts(entry);
|
|
11555
|
+
entry.face.removeFromParent();
|
|
11556
|
+
entry.face.geometry.dispose();
|
|
11557
|
+
entry.face.material.dispose();
|
|
11558
|
+
entry.texture.dispose();
|
|
11559
|
+
}
|
|
11560
|
+
};
|
|
11561
|
+
__name(_UIPanelSystem, "UIPanelSystem");
|
|
11562
|
+
var UIPanelSystem = _UIPanelSystem;
|
|
10255
11563
|
var _XRHitTestSystem = class _XRHitTestSystem {
|
|
10256
11564
|
constructor() {
|
|
10257
11565
|
this.sources = /* @__PURE__ */ new Map();
|
|
@@ -10364,9 +11672,13 @@ var _XRAnchorSystem = class _XRAnchorSystem {
|
|
|
10364
11672
|
this.anchors = /* @__PURE__ */ new Map();
|
|
10365
11673
|
/** In-flight creations, so one entity is not anchored twice. */
|
|
10366
11674
|
this.pending = /* @__PURE__ */ new Set();
|
|
11675
|
+
/** Per-anchor head-glue gates — see XREnvironmentSystem's TrackedEntry. */
|
|
11676
|
+
this.gates = /* @__PURE__ */ new Map();
|
|
10367
11677
|
this.presenting = false;
|
|
11678
|
+
this.tmpViewerMatrix = new THREE26__namespace.Matrix4();
|
|
11679
|
+
this.tmpViewPos = new THREE26__namespace.Vector3();
|
|
10368
11680
|
}
|
|
10369
|
-
update(world) {
|
|
11681
|
+
update(world, deltaTime = 0) {
|
|
10370
11682
|
const presenting = world.isXrPresenting();
|
|
10371
11683
|
if (!presenting) {
|
|
10372
11684
|
if (this.presenting) {
|
|
@@ -10386,8 +11698,11 @@ var _XRAnchorSystem = class _XRAnchorSystem {
|
|
|
10386
11698
|
if (!live.has(entityId)) {
|
|
10387
11699
|
anchor.delete?.();
|
|
10388
11700
|
this.anchors.delete(entityId);
|
|
11701
|
+
this.gates.delete(entityId);
|
|
10389
11702
|
}
|
|
10390
11703
|
}
|
|
11704
|
+
const viewer = frame.getViewerPose?.(referenceSpace);
|
|
11705
|
+
const viewerInverse = viewer ? this.tmpViewerMatrix.fromArray(viewer.transform.matrix).invert() : null;
|
|
10391
11706
|
for (const entityId of entityIds) {
|
|
10392
11707
|
const component = world.getComponent(entityId, XRAnchorComponent);
|
|
10393
11708
|
const transform = world.getComponent(entityId, TransformComponent);
|
|
@@ -10397,6 +11712,7 @@ var _XRAnchorSystem = class _XRAnchorSystem {
|
|
|
10397
11712
|
if (existing) {
|
|
10398
11713
|
existing.delete?.();
|
|
10399
11714
|
this.anchors.delete(entityId);
|
|
11715
|
+
this.gates.delete(entityId);
|
|
10400
11716
|
}
|
|
10401
11717
|
continue;
|
|
10402
11718
|
}
|
|
@@ -10407,7 +11723,21 @@ var _XRAnchorSystem = class _XRAnchorSystem {
|
|
|
10407
11723
|
}
|
|
10408
11724
|
const pose = frame.getPose(anchor.anchorSpace, referenceSpace);
|
|
10409
11725
|
if (pose && pose.emulatedPosition !== true) {
|
|
10410
|
-
|
|
11726
|
+
let gate = this.gates.get(entityId);
|
|
11727
|
+
if (!gate) {
|
|
11728
|
+
gate = new SurfacePoseGate();
|
|
11729
|
+
this.gates.set(entityId, gate);
|
|
11730
|
+
}
|
|
11731
|
+
const m = pose.transform.matrix;
|
|
11732
|
+
const refPos = { x: m[12], y: m[13], z: m[14] };
|
|
11733
|
+
let viewPos = null;
|
|
11734
|
+
if (viewerInverse) {
|
|
11735
|
+
const v = this.tmpViewPos.set(refPos.x, refPos.y, refPos.z).applyMatrix4(viewerInverse);
|
|
11736
|
+
viewPos = { x: v.x, y: v.y, z: v.z };
|
|
11737
|
+
}
|
|
11738
|
+
if (gate.update(refPos, viewPos, deltaTime)) {
|
|
11739
|
+
decomposeXrPose(pose.transform.matrix, transform);
|
|
11740
|
+
}
|
|
10411
11741
|
}
|
|
10412
11742
|
}
|
|
10413
11743
|
}
|
|
@@ -10452,6 +11782,7 @@ var _XRAnchorSystem = class _XRAnchorSystem {
|
|
|
10452
11782
|
}).finally(() => this.pending.delete(entityId));
|
|
10453
11783
|
}
|
|
10454
11784
|
releaseAll() {
|
|
11785
|
+
this.gates.clear();
|
|
10455
11786
|
for (const anchor of this.anchors.values()) anchor.delete?.();
|
|
10456
11787
|
this.anchors.clear();
|
|
10457
11788
|
this.pending.clear();
|
|
@@ -11716,6 +13047,8 @@ exports.SCRIPT_OBJECT_ROOT_FLAG = SCRIPT_OBJECT_ROOT_FLAG;
|
|
|
11716
13047
|
exports.ScriptBehaviourSystem = ScriptBehaviourSystem;
|
|
11717
13048
|
exports.ScriptObjectSystem = ScriptObjectSystem;
|
|
11718
13049
|
exports.TransformSyncSystem = TransformSyncSystem;
|
|
13050
|
+
exports.UIInteractionSystem = UIInteractionSystem;
|
|
13051
|
+
exports.UIPanelSystem = UIPanelSystem;
|
|
11719
13052
|
exports.VRTeleportAreaSystem = VRTeleportAreaSystem;
|
|
11720
13053
|
exports.VideoSystem = VideoSystem;
|
|
11721
13054
|
exports.XRAnchorSystem = XRAnchorSystem;
|