@iwsdk/core 0.5.2 → 1.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -1
- package/dist/asset/asset-manager.d.ts.map +1 -1
- package/dist/asset/asset-manager.js +3 -2
- package/dist/asset/asset-manager.js.map +1 -1
- package/dist/camera/camera-system.d.ts +7 -3
- package/dist/camera/camera-system.d.ts.map +1 -1
- package/dist/camera/camera-system.js +81 -50
- package/dist/camera/camera-system.js.map +1 -1
- package/dist/camera/camera-utils.d.ts +5 -0
- package/dist/camera/camera-utils.d.ts.map +1 -1
- package/dist/camera/camera-utils.js +8 -1
- package/dist/camera/camera-utils.js.map +1 -1
- package/dist/depth/depth-sensing-system.d.ts +3 -0
- package/dist/depth/depth-sensing-system.d.ts.map +1 -1
- package/dist/depth/depth-sensing-system.js +55 -1
- package/dist/depth/depth-sensing-system.js.map +1 -1
- package/dist/ecs/world.d.ts +2 -0
- package/dist/ecs/world.d.ts.map +1 -1
- package/dist/ecs/world.js +4 -0
- package/dist/ecs/world.js.map +1 -1
- package/dist/grab/distance-grabbable.d.ts +5 -0
- package/dist/grab/distance-grabbable.d.ts.map +1 -1
- package/dist/grab/distance-grabbable.js +5 -0
- package/dist/grab/distance-grabbable.js.map +1 -1
- package/dist/grab/handles.d.ts +6 -0
- package/dist/grab/handles.d.ts.map +1 -1
- package/dist/grab/handles.js +46 -12
- package/dist/grab/handles.js.map +1 -1
- package/dist/grab/one-hand-grabbable.d.ts +5 -0
- package/dist/grab/one-hand-grabbable.d.ts.map +1 -1
- package/dist/grab/one-hand-grabbable.js +5 -0
- package/dist/grab/one-hand-grabbable.js.map +1 -1
- package/dist/grab/two-hands-grabbable.d.ts +5 -0
- package/dist/grab/two-hands-grabbable.d.ts.map +1 -1
- package/dist/grab/two-hands-grabbable.js +5 -0
- package/dist/grab/two-hands-grabbable.js.map +1 -1
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/init/index.d.ts +2 -2
- package/dist/init/index.d.ts.map +1 -1
- package/dist/init/session-grant.d.ts +34 -0
- package/dist/init/session-grant.d.ts.map +1 -0
- package/dist/init/session-grant.js +89 -0
- package/dist/init/session-grant.js.map +1 -0
- package/dist/init/world-initializer.d.ts +87 -4
- package/dist/init/world-initializer.d.ts.map +1 -1
- package/dist/init/world-initializer.js +48 -43
- package/dist/init/world-initializer.js.map +1 -1
- package/dist/init/xr.d.ts +19 -0
- package/dist/init/xr.d.ts.map +1 -1
- package/dist/init/xr.js +136 -41
- package/dist/init/xr.js.map +1 -1
- package/dist/input/canvas-pointer-system.d.ts +2 -0
- package/dist/input/canvas-pointer-system.d.ts.map +1 -1
- package/dist/input/canvas-pointer-system.js +33 -13
- package/dist/input/canvas-pointer-system.js.map +1 -1
- package/dist/input/input-system.d.ts.map +1 -1
- package/dist/input/input-system.js +5 -2
- package/dist/input/input-system.js.map +1 -1
- package/dist/layers/xr-cylinder-layer.d.ts +2 -1
- package/dist/layers/xr-cylinder-layer.d.ts.map +1 -1
- package/dist/layers/xr-cylinder-layer.js +2 -1
- package/dist/layers/xr-cylinder-layer.js.map +1 -1
- package/dist/layers/xr-layer-init.d.ts +19 -0
- package/dist/layers/xr-layer-init.d.ts.map +1 -0
- package/dist/layers/xr-layer-init.js +20 -0
- package/dist/layers/xr-layer-init.js.map +1 -0
- package/dist/layers/xr-layer-system.d.ts +41 -1
- package/dist/layers/xr-layer-system.d.ts.map +1 -1
- package/dist/layers/xr-layer-system.js +506 -132
- package/dist/layers/xr-layer-system.js.map +1 -1
- package/dist/layers/xr-layer-transform.d.ts +18 -0
- package/dist/layers/xr-layer-transform.d.ts.map +1 -0
- package/dist/layers/xr-layer-transform.js +115 -0
- package/dist/layers/xr-layer-transform.js.map +1 -0
- package/dist/layers/xr-quad-layer.d.ts +2 -1
- package/dist/layers/xr-quad-layer.d.ts.map +1 -1
- package/dist/layers/xr-quad-layer.js +2 -1
- package/dist/layers/xr-quad-layer.js.map +1 -1
- package/dist/level/level-component-applier.d.ts +2 -0
- package/dist/level/level-component-applier.d.ts.map +1 -1
- package/dist/level/level-component-applier.js +24 -5
- package/dist/level/level-component-applier.js.map +1 -1
- package/dist/mcp/ecs-debug-tools.d.ts +4 -1
- package/dist/mcp/ecs-debug-tools.d.ts.map +1 -1
- package/dist/mcp/ecs-debug-tools.js +113 -10
- package/dist/mcp/ecs-debug-tools.js.map +1 -1
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/mcp-runtime.d.ts +2 -0
- package/dist/mcp/mcp-runtime.d.ts.map +1 -1
- package/dist/mcp/mcp-runtime.js +41 -0
- package/dist/mcp/mcp-runtime.js.map +1 -1
- package/dist/mcp/ui-debug-tools.d.ts +56 -0
- package/dist/mcp/ui-debug-tools.d.ts.map +1 -0
- package/dist/mcp/ui-debug-tools.js +412 -0
- package/dist/mcp/ui-debug-tools.js.map +1 -0
- package/dist/physics/index.js +1 -1
- package/dist/physics/physics-runtime.d.ts +22 -0
- package/dist/physics/physics-runtime.d.ts.map +1 -0
- package/dist/physics/physics-runtime.js +423 -0
- package/dist/physics/physics-runtime.js.map +1 -0
- package/dist/physics/physics-system.d.ts +84 -108
- package/dist/physics/physics-system.d.ts.map +1 -1
- package/dist/physics/physics-system.js +686 -555
- package/dist/physics/physics-system.js.map +1 -1
- package/dist/physics/physics-transport.d.ts +20 -0
- package/dist/physics/physics-transport.d.ts.map +1 -0
- package/dist/physics/physics-transport.js +111 -0
- package/dist/physics/physics-transport.js.map +1 -0
- package/dist/physics/physics-worker-protocol.d.ts +144 -0
- package/dist/physics/physics-worker-protocol.d.ts.map +1 -0
- package/dist/physics/physics-worker-protocol.js +41 -0
- package/dist/physics/physics-worker-protocol.js.map +1 -0
- package/dist/physics/physics-worker-utils.d.ts +8 -0
- package/dist/physics/physics-worker-utils.d.ts.map +1 -0
- package/dist/physics/physics-worker-utils.js +12 -0
- package/dist/physics/physics-worker-utils.js.map +1 -0
- package/dist/physics/physics-worker.d.ts +8 -0
- package/dist/physics/physics-worker.d.ts.map +1 -0
- package/dist/physics/physics-worker.js +66 -0
- package/dist/physics/physics-worker.js.map +1 -0
- package/dist/project/normalize.js +7 -1
- package/dist/project/normalize.js.map +1 -1
- package/dist/project/types.d.ts +7 -1
- package/dist/project/types.d.ts.map +1 -1
- package/dist/project/validation.js +36 -2
- package/dist/project/validation.js.map +1 -1
- package/dist/scene-understanding/scene-understanding-system.d.ts +42 -6
- package/dist/scene-understanding/scene-understanding-system.d.ts.map +1 -1
- package/dist/scene-understanding/scene-understanding-system.js +391 -106
- package/dist/scene-understanding/scene-understanding-system.js.map +1 -1
- package/dist/schemas/iwsdk-project.v1.schema.json +20 -1
- package/dist/ui/follow.d.ts.map +1 -1
- package/dist/ui/follow.js +0 -1
- package/dist/ui/follow.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -5
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { createSystem } from '../ecs/system.js';
|
|
2
2
|
import '../runtime/three.js';
|
|
3
3
|
import { XRCylinderLayer } from './xr-cylinder-layer.js';
|
|
4
|
+
import { createQuadLayerInit } from './xr-layer-init.js';
|
|
4
5
|
import { XRLayerState } from './xr-layer-state.js';
|
|
6
|
+
import { resolveLayerReferencePose, resolveQuadLayerDimensions, resolveCylinderLayerDimensions, isValidCylinderLayerAngle } from './xr-layer-transform.js';
|
|
5
7
|
import { XRQuadLayer } from './xr-quad-layer.js';
|
|
6
|
-
import { Vector3, Quaternion,
|
|
8
|
+
import { Vector3, Quaternion, Matrix4, Color, MeshBasicMaterial, FrontSide, BackSide, Mesh, CustomBlending, AddEquation, ZeroFactor, SRGBColorSpace, PlaneGeometry, CylinderGeometry, WebGLRenderTarget, DepthTexture, UnsignedByteType, RGBAFormat, UnsignedInt248Type, UnsignedIntType, DepthStencilFormat, DepthFormat } from 'three';
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
@@ -13,17 +15,37 @@ import { Vector3, Quaternion, Color, PlaneGeometry, CylinderGeometry, MeshBasicM
|
|
|
13
15
|
*/
|
|
14
16
|
const _position = new Vector3();
|
|
15
17
|
const _quaternion = new Quaternion();
|
|
18
|
+
const _scale = new Vector3();
|
|
19
|
+
const _configuration = new Vector3();
|
|
20
|
+
const _relativeMatrix = new Matrix4();
|
|
16
21
|
const _clearColor = new Color();
|
|
17
22
|
function toRigidTransform(pos, quat) {
|
|
18
23
|
return new XRRigidTransform({ x: pos.x, y: pos.y, z: pos.z }, { x: quat.x, y: quat.y, z: quat.z, w: quat.w });
|
|
19
24
|
}
|
|
25
|
+
function isObjectTreeVisible(object) {
|
|
26
|
+
for (let current = object; current; current = current.parent) {
|
|
27
|
+
if (!current.visible) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
function sameNumber(first, second) {
|
|
34
|
+
return first === second || (Number.isNaN(first) && Number.isNaN(second));
|
|
35
|
+
}
|
|
36
|
+
function sameVector(first, second) {
|
|
37
|
+
return (sameNumber(first.x, second.x) &&
|
|
38
|
+
sameNumber(first.y, second.y) &&
|
|
39
|
+
sameNumber(first.z, second.z));
|
|
40
|
+
}
|
|
20
41
|
/**
|
|
21
42
|
* XRLayerSystem manages WebXR quad and cylinder composition layers.
|
|
22
43
|
*
|
|
23
44
|
* For each entity with an {@link XRQuadLayer} or {@link XRCylinderLayer}
|
|
24
45
|
* component, the system:
|
|
25
46
|
* - Creates a mesh (plane or cylinder) as a child of the entity's object3D
|
|
26
|
-
* - Renders layer content
|
|
47
|
+
* - Renders layer content via the component's `renderCallback` while its
|
|
48
|
+
* render surface is visible and active
|
|
27
49
|
* - In XR sessions with layer support, creates native XR composition layers
|
|
28
50
|
* and switches the mesh to a punch-through material that makes the
|
|
29
51
|
* projection layer transparent where the content layer appears
|
|
@@ -39,6 +61,12 @@ class XRLayerSystem extends createSystem({
|
|
|
39
61
|
constructor() {
|
|
40
62
|
super(...arguments);
|
|
41
63
|
this.sessionUsesLayers = false;
|
|
64
|
+
this.sessionLayerInitializationPending = false;
|
|
65
|
+
this.unsupportedLayerStates = new Map();
|
|
66
|
+
this.pendingNativeLayerRemovals = new Set();
|
|
67
|
+
this.retiredLayers = [];
|
|
68
|
+
this.layerVisibility = new Map();
|
|
69
|
+
this.layerConfigurations = new Map();
|
|
42
70
|
}
|
|
43
71
|
init() {
|
|
44
72
|
const onSessionStart = () => this.handleSessionStart();
|
|
@@ -49,9 +77,25 @@ class XRLayerSystem extends createSystem({
|
|
|
49
77
|
// Register the query subscriptions on cleanupFuncs so they're released on
|
|
50
78
|
// system teardown (matching the xr listeners above).
|
|
51
79
|
this.cleanupFuncs.push(this.queries.quadLayers.subscribe('qualify', (entity) => this.setupLayer(entity, 'quad')), this.queries.quadLayers.subscribe('disqualify', (entity) => this.teardownLayer(entity)), this.queries.cylinderLayers.subscribe('qualify', (entity) => this.setupLayer(entity, 'cylinder')), this.queries.cylinderLayers.subscribe('disqualify', (entity) => this.teardownLayer(entity)));
|
|
80
|
+
// Query disqualify does not run merely because the system itself is being
|
|
81
|
+
// destroyed. Release every live compositor and GPU resource explicitly.
|
|
82
|
+
this.cleanupFuncs.push(() => {
|
|
83
|
+
for (const entity of [...this.queries.activeLayers.entities]) {
|
|
84
|
+
this.teardownLayer(entity);
|
|
85
|
+
}
|
|
86
|
+
this.disposeRetiredLayers();
|
|
87
|
+
this.unsupportedLayerStates.clear();
|
|
88
|
+
this.pendingNativeLayerRemovals.clear();
|
|
89
|
+
this.layerVisibility.clear();
|
|
90
|
+
this.layerConfigurations.clear();
|
|
91
|
+
});
|
|
52
92
|
}
|
|
53
93
|
update() {
|
|
54
|
-
if (this.
|
|
94
|
+
if (this.sessionLayerInitializationPending) {
|
|
95
|
+
this.tryInitializeSessionLayers();
|
|
96
|
+
}
|
|
97
|
+
if (this.queries.activeLayers.entities.size === 0 &&
|
|
98
|
+
this.retiredLayers.length === 0) {
|
|
55
99
|
return;
|
|
56
100
|
}
|
|
57
101
|
this.renderLayers();
|
|
@@ -66,21 +110,8 @@ class XRLayerSystem extends createSystem({
|
|
|
66
110
|
const pixelWidth = entity.getValue(component, 'pixelWidth');
|
|
67
111
|
const pixelHeight = entity.getValue(component, 'pixelHeight');
|
|
68
112
|
const stencil = entity.getValue(component, 'stencil');
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (isQuad) {
|
|
72
|
-
const width = entity.getValue(XRQuadLayer, 'width');
|
|
73
|
-
const height = entity.getValue(XRQuadLayer, 'height');
|
|
74
|
-
geometry = new PlaneGeometry(width, height);
|
|
75
|
-
side = FrontSide;
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
const radius = entity.getValue(XRCylinderLayer, 'radius');
|
|
79
|
-
const centralAngle = entity.getValue(XRCylinderLayer, 'centralAngle');
|
|
80
|
-
const aspectRatio = entity.getValue(XRCylinderLayer, 'aspectRatio');
|
|
81
|
-
geometry = new CylinderGeometry(radius, radius, (radius * centralAngle) / aspectRatio, 64, 64, true, Math.PI - centralAngle / 2, centralAngle);
|
|
82
|
-
side = BackSide;
|
|
83
|
-
}
|
|
113
|
+
const geometry = this.createFallbackGeometry(entity, isQuad);
|
|
114
|
+
const side = isQuad ? FrontSide : BackSide;
|
|
84
115
|
const renderTarget = this.createRenderTarget(pixelWidth, pixelHeight, stencil);
|
|
85
116
|
const fallbackMaterial = new MeshBasicMaterial({
|
|
86
117
|
color: 0xffffff,
|
|
@@ -101,57 +132,90 @@ class XRLayerSystem extends createSystem({
|
|
|
101
132
|
pixelHeight,
|
|
102
133
|
stencil,
|
|
103
134
|
});
|
|
135
|
+
this.readLayerConfiguration(entity, isQuad, _configuration);
|
|
136
|
+
this.layerConfigurations.set(entity.index, _configuration.clone());
|
|
104
137
|
if (this.sessionUsesLayers) {
|
|
105
138
|
this.activateNativeLayer(entity);
|
|
106
139
|
this.updateSessionLayers();
|
|
107
140
|
}
|
|
108
141
|
}
|
|
109
142
|
teardownLayer(entity) {
|
|
143
|
+
var _a, _b;
|
|
110
144
|
const mesh = XRLayerState.data.mesh[entity.index];
|
|
111
145
|
if (!mesh) {
|
|
112
146
|
return;
|
|
113
147
|
}
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
entity.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (mesh.material !== fallbackMaterial) {
|
|
124
|
-
mesh.material.dispose();
|
|
148
|
+
const resources = {
|
|
149
|
+
xrLayer: XRLayerState.data.xrLayer[entity.index],
|
|
150
|
+
mesh,
|
|
151
|
+
fallbackMaterial: XRLayerState.data.fallbackMaterial[entity.index],
|
|
152
|
+
renderTarget: XRLayerState.data.renderTarget[entity.index],
|
|
153
|
+
};
|
|
154
|
+
const nativeLayerIsActive = (_b = (_a = this.xrManager.getSession()) === null || _a === void 0 ? void 0 : _a.renderState.layers) === null || _b === void 0 ? void 0 : _b.includes(resources.xrLayer);
|
|
155
|
+
if (resources.xrLayer && this.sessionUsesLayers) {
|
|
156
|
+
this.updateSessionLayers(entity);
|
|
125
157
|
}
|
|
126
|
-
renderTarget.dispose();
|
|
127
158
|
mesh.removeFromParent();
|
|
128
|
-
entity.
|
|
159
|
+
if (entity.active) {
|
|
160
|
+
entity.removeComponent(XRLayerState);
|
|
161
|
+
}
|
|
162
|
+
this.unsupportedLayerStates.delete(entity.index);
|
|
163
|
+
this.pendingNativeLayerRemovals.delete(entity.index);
|
|
164
|
+
this.layerVisibility.delete(entity.index);
|
|
165
|
+
this.layerConfigurations.delete(entity.index);
|
|
166
|
+
if (resources.xrLayer && this.sessionUsesLayers && nativeLayerIsActive) {
|
|
167
|
+
this.retiredLayers.push(resources);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
this.disposeLayerResources(resources);
|
|
171
|
+
}
|
|
129
172
|
}
|
|
130
173
|
// ---------------------------------------------------------------------------
|
|
131
174
|
// XR session lifecycle
|
|
132
175
|
// ---------------------------------------------------------------------------
|
|
133
176
|
handleSessionStart() {
|
|
177
|
+
this.sessionUsesLayers = false;
|
|
178
|
+
this.sessionLayerInitializationPending = true;
|
|
179
|
+
this.tryInitializeSessionLayers();
|
|
180
|
+
}
|
|
181
|
+
handleSessionEnd() {
|
|
182
|
+
for (const entity of this.queries.activeLayers.entities) {
|
|
183
|
+
this.deactivateNativeLayer(entity);
|
|
184
|
+
}
|
|
185
|
+
this.disposeRetiredLayers();
|
|
186
|
+
this.unsupportedLayerStates.clear();
|
|
187
|
+
this.pendingNativeLayerRemovals.clear();
|
|
188
|
+
this.layerVisibility.clear();
|
|
189
|
+
this.sessionUsesLayers = false;
|
|
190
|
+
this.sessionLayerInitializationPending = false;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Enable composition layers once the projection layer is active.
|
|
194
|
+
*
|
|
195
|
+
* super-three queues its projection-layer render state before dispatching
|
|
196
|
+
* `sessionstart`, so `renderState.layers` can legitimately be empty during
|
|
197
|
+
* that event. Keep rendering the mesh fallback and retry on subsequent
|
|
198
|
+
* frames instead of permanently classifying the session as unsupported.
|
|
199
|
+
*/
|
|
200
|
+
tryInitializeSessionLayers() {
|
|
134
201
|
var _a;
|
|
135
202
|
const session = this.xrManager.getSession();
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
this.sessionUsesLayers = false;
|
|
142
|
-
return;
|
|
203
|
+
const binding = this.xrManager.getBinding();
|
|
204
|
+
const renderState = session === null || session === void 0 ? void 0 : session.renderState;
|
|
205
|
+
if (!session || !binding || !renderState || renderState.baseLayer) {
|
|
206
|
+
this.sessionLayerInitializationPending = false;
|
|
207
|
+
return false;
|
|
143
208
|
}
|
|
209
|
+
if (!((_a = renderState.layers) === null || _a === void 0 ? void 0 : _a[0])) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
this.sessionLayerInitializationPending = false;
|
|
144
213
|
this.sessionUsesLayers = true;
|
|
145
214
|
for (const entity of this.queries.activeLayers.entities) {
|
|
146
215
|
this.activateNativeLayer(entity);
|
|
147
216
|
}
|
|
148
217
|
this.updateSessionLayers();
|
|
149
|
-
|
|
150
|
-
handleSessionEnd() {
|
|
151
|
-
for (const entity of this.queries.activeLayers.entities) {
|
|
152
|
-
this.deactivateNativeLayer(entity);
|
|
153
|
-
}
|
|
154
|
-
this.sessionUsesLayers = false;
|
|
218
|
+
return true;
|
|
155
219
|
}
|
|
156
220
|
// ---------------------------------------------------------------------------
|
|
157
221
|
// Native layer management
|
|
@@ -160,105 +224,208 @@ class XRLayerSystem extends createSystem({
|
|
|
160
224
|
const binding = this.xrManager.getBinding();
|
|
161
225
|
const session = this.xrManager.getSession();
|
|
162
226
|
if (!binding || !session) {
|
|
163
|
-
return;
|
|
227
|
+
return false;
|
|
164
228
|
}
|
|
165
229
|
const isQuad = !!XRLayerState.data.isQuad[entity.index];
|
|
166
230
|
const mesh = XRLayerState.data.mesh[entity.index];
|
|
167
231
|
const pixelWidth = XRLayerState.data.pixelWidth[entity.index];
|
|
168
232
|
const pixelHeight = XRLayerState.data.pixelHeight[entity.index];
|
|
169
|
-
const renderTarget = XRLayerState.data.renderTarget[entity.index];
|
|
170
233
|
const refSpace = this.xrManager.getReferenceSpace();
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
234
|
+
if (!refSpace) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
this.readLayerConfiguration(entity, isQuad, _configuration);
|
|
238
|
+
const hasSupportedTransform = resolveLayerReferencePose(mesh, this.player, _position, _quaternion, _scale, _relativeMatrix);
|
|
175
239
|
let xrLayer;
|
|
176
240
|
if (isQuad) {
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
241
|
+
const dimensions = hasSupportedTransform
|
|
242
|
+
? resolveQuadLayerDimensions(_configuration.x, _configuration.y, _scale)
|
|
243
|
+
: null;
|
|
244
|
+
if (!dimensions) {
|
|
245
|
+
this.rememberUnsupportedLayer(entity, 'quad');
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
xrLayer = binding.createQuadLayer(createQuadLayerInit({
|
|
249
|
+
transform: toRigidTransform(_position, _quaternion),
|
|
250
|
+
width: dimensions.width,
|
|
251
|
+
height: dimensions.height,
|
|
183
252
|
space: refSpace,
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
});
|
|
253
|
+
pixelWidth,
|
|
254
|
+
pixelHeight,
|
|
255
|
+
}));
|
|
188
256
|
}
|
|
189
257
|
else {
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
258
|
+
const dimensions = hasSupportedTransform
|
|
259
|
+
? resolveCylinderLayerDimensions(_configuration.x, _configuration.y, _scale)
|
|
260
|
+
: null;
|
|
261
|
+
if (!dimensions || !isValidCylinderLayerAngle(_configuration.z)) {
|
|
262
|
+
this.rememberUnsupportedLayer(entity, 'cylinder');
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
193
265
|
xrLayer = binding.createCylinderLayer({
|
|
194
|
-
transform,
|
|
195
|
-
radius,
|
|
196
|
-
centralAngle,
|
|
197
|
-
aspectRatio,
|
|
266
|
+
transform: toRigidTransform(_position, _quaternion),
|
|
267
|
+
radius: dimensions.radius,
|
|
268
|
+
centralAngle: _configuration.z,
|
|
269
|
+
aspectRatio: dimensions.aspectRatio,
|
|
198
270
|
space: refSpace,
|
|
199
271
|
viewPixelWidth: pixelWidth,
|
|
200
272
|
viewPixelHeight: pixelHeight,
|
|
201
273
|
clearOnAccess: false,
|
|
202
274
|
});
|
|
203
275
|
}
|
|
276
|
+
this.unsupportedLayerStates.delete(entity.index);
|
|
204
277
|
entity.setValue(XRLayerState, 'xrLayer', xrLayer);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
278
|
+
entity.setValue(XRLayerState, 'cachedTransform', {
|
|
279
|
+
position: _position.clone(),
|
|
280
|
+
quaternion: _quaternion.clone(),
|
|
281
|
+
scale: _scale.clone(),
|
|
282
|
+
configuration: _configuration.clone(),
|
|
283
|
+
});
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
deactivateNativeLayer(entity) {
|
|
287
|
+
var _a;
|
|
288
|
+
const mesh = XRLayerState.data.mesh[entity.index];
|
|
289
|
+
const xrLayer = XRLayerState.data.xrLayer[entity.index];
|
|
290
|
+
if (!xrLayer) {
|
|
291
|
+
entity.setValue(XRLayerState, 'cachedTransform', null);
|
|
292
|
+
this.pendingNativeLayerRemovals.delete(entity.index);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
(_a = xrLayer.destroy) === null || _a === void 0 ? void 0 : _a.call(xrLayer);
|
|
296
|
+
this.useFallbackSurface(entity, mesh);
|
|
297
|
+
entity.setValue(XRLayerState, 'xrLayer', null);
|
|
298
|
+
entity.setValue(XRLayerState, 'cachedTransform', null);
|
|
299
|
+
this.pendingNativeLayerRemovals.delete(entity.index);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Switch from the internal fallback texture only after the compositor layer
|
|
303
|
+
* is active. This avoids punching a transparent hole while
|
|
304
|
+
* updateRenderState() is still pending.
|
|
305
|
+
*/
|
|
306
|
+
useNativeSurface(entity, mesh) {
|
|
307
|
+
const fallbackMaterial = XRLayerState.data.fallbackMaterial[entity.index];
|
|
308
|
+
if (mesh.material !== fallbackMaterial) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const side = XRLayerState.data.isQuad[entity.index] ? FrontSide : BackSide;
|
|
208
312
|
const punchMaterial = new MeshBasicMaterial({ color: 0xffffff, side });
|
|
209
313
|
punchMaterial.blending = CustomBlending;
|
|
210
314
|
punchMaterial.blendEquation = AddEquation;
|
|
211
315
|
punchMaterial.blendSrc = ZeroFactor;
|
|
212
316
|
punchMaterial.blendDst = ZeroFactor;
|
|
213
317
|
mesh.material = punchMaterial;
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
318
|
+
const xrLayer = XRLayerState.data.xrLayer[entity.index];
|
|
319
|
+
xrLayer.blendTextureSourceAlpha = true;
|
|
320
|
+
const renderTarget = XRLayerState.data.renderTarget[entity.index];
|
|
217
321
|
renderTarget.isXRRenderTarget = true;
|
|
218
322
|
renderTarget.texture.colorSpace = SRGBColorSpace;
|
|
219
|
-
entity.setValue(XRLayerState, 'cachedTransform', {
|
|
220
|
-
position: _position.clone(),
|
|
221
|
-
quaternion: _quaternion.clone(),
|
|
222
|
-
});
|
|
223
323
|
}
|
|
224
|
-
|
|
225
|
-
|
|
324
|
+
/** Restore a fresh internal target after an external XR texture was bound. */
|
|
325
|
+
useFallbackSurface(entity, mesh) {
|
|
226
326
|
const fallbackMaterial = XRLayerState.data.fallbackMaterial[entity.index];
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const stencil = !!XRLayerState.data.stencil[entity.index];
|
|
230
|
-
// Dispose punch-through material
|
|
231
|
-
if (mesh.material !== fallbackMaterial) {
|
|
232
|
-
mesh.material.dispose();
|
|
327
|
+
if (mesh.material === fallbackMaterial) {
|
|
328
|
+
return;
|
|
233
329
|
}
|
|
234
|
-
|
|
330
|
+
mesh.material.dispose();
|
|
235
331
|
const oldRenderTarget = XRLayerState.data.renderTarget[entity.index];
|
|
236
332
|
oldRenderTarget.dispose();
|
|
237
|
-
const newRenderTarget = this.createRenderTarget(pixelWidth, pixelHeight, stencil);
|
|
333
|
+
const newRenderTarget = this.createRenderTarget(XRLayerState.data.pixelWidth[entity.index], XRLayerState.data.pixelHeight[entity.index], !!XRLayerState.data.stencil[entity.index]);
|
|
238
334
|
entity.setValue(XRLayerState, 'renderTarget', newRenderTarget);
|
|
239
|
-
// Restore fallback material with the fresh render target texture
|
|
240
335
|
fallbackMaterial.map = newRenderTarget.texture;
|
|
241
336
|
fallbackMaterial.map.offset.y = 1;
|
|
242
337
|
fallbackMaterial.map.repeat.y = -1;
|
|
243
338
|
mesh.material = fallbackMaterial;
|
|
244
|
-
|
|
245
|
-
|
|
339
|
+
}
|
|
340
|
+
disposeLayerResources(resources) {
|
|
341
|
+
var _a, _b;
|
|
342
|
+
(_b = (_a = resources.xrLayer) === null || _a === void 0 ? void 0 : _a.destroy) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
343
|
+
resources.mesh.geometry.dispose();
|
|
344
|
+
resources.fallbackMaterial.dispose();
|
|
345
|
+
if (resources.mesh.material !== resources.fallbackMaterial) {
|
|
346
|
+
resources.mesh.material.dispose();
|
|
347
|
+
}
|
|
348
|
+
resources.renderTarget.dispose();
|
|
349
|
+
}
|
|
350
|
+
disposeRetiredLayers() {
|
|
351
|
+
for (const resources of this.retiredLayers) {
|
|
352
|
+
this.disposeLayerResources(resources);
|
|
353
|
+
}
|
|
354
|
+
this.retiredLayers.length = 0;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Keep removed layers alive until the queued render state no longer
|
|
358
|
+
* references them. While removal is pending, clear their native textures so
|
|
359
|
+
* the compositor cannot present stale content from the detached entity.
|
|
360
|
+
*/
|
|
361
|
+
renderRetiredLayers(renderer) {
|
|
362
|
+
var _a;
|
|
363
|
+
if (this.retiredLayers.length === 0) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const activeLayers = (_a = this.xrManager.getSession()) === null || _a === void 0 ? void 0 : _a.renderState.layers;
|
|
367
|
+
if (!activeLayers) {
|
|
368
|
+
this.disposeRetiredLayers();
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const retained = [];
|
|
372
|
+
const released = [];
|
|
373
|
+
for (const resources of this.retiredLayers) {
|
|
374
|
+
if (activeLayers.includes(resources.xrLayer)) {
|
|
375
|
+
retained.push(resources);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
released.push(resources);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// Commit the classification before disposal or compositor access. If a
|
|
382
|
+
// later getSubImage() throws, already-released entries cannot be disposed
|
|
383
|
+
// again, while every still-active entry remains tracked for retry.
|
|
384
|
+
this.retiredLayers = retained;
|
|
385
|
+
for (const resources of released) {
|
|
386
|
+
this.disposeLayerResources(resources);
|
|
387
|
+
}
|
|
388
|
+
const binding = this.xrManager.getBinding();
|
|
389
|
+
const frame = this.xrFrame;
|
|
390
|
+
for (const resources of retained) {
|
|
391
|
+
if (!binding || !frame) {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
resources.xrLayer.blendTextureSourceAlpha = true;
|
|
395
|
+
resources.renderTarget.isXRRenderTarget = true;
|
|
396
|
+
resources.renderTarget.texture.colorSpace = SRGBColorSpace;
|
|
397
|
+
const subImage = binding.getSubImage(resources.xrLayer, frame);
|
|
398
|
+
renderer.setRenderTargetTextures(resources.renderTarget, subImage.colorTexture, undefined);
|
|
399
|
+
renderer.setRenderTarget(resources.renderTarget);
|
|
400
|
+
renderer.clear(true, true, true);
|
|
401
|
+
}
|
|
246
402
|
}
|
|
247
403
|
/**
|
|
248
404
|
* Rebuild the session's render state layers array.
|
|
249
405
|
* Keeps the projection layer at index 0, then appends content layers.
|
|
250
406
|
*/
|
|
251
|
-
updateSessionLayers() {
|
|
407
|
+
updateSessionLayers(excludedEntity) {
|
|
252
408
|
const session = this.xrManager.getSession();
|
|
253
409
|
if (!(session === null || session === void 0 ? void 0 : session.renderState.layers)) {
|
|
254
410
|
return;
|
|
255
411
|
}
|
|
256
412
|
// Projection layer is always the first entry
|
|
257
413
|
const projLayer = session.renderState.layers[0];
|
|
258
|
-
|
|
414
|
+
if (!projLayer) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
const layers = [projLayer];
|
|
259
418
|
for (const entity of this.queries.activeLayers.entities) {
|
|
419
|
+
if (entity === excludedEntity) {
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
260
422
|
const xrLayer = XRLayerState.data.xrLayer[entity.index];
|
|
261
|
-
|
|
423
|
+
const mesh = XRLayerState.data.mesh[entity.index];
|
|
424
|
+
const visible = mesh != null && isObjectTreeVisible(mesh);
|
|
425
|
+
this.layerVisibility.set(entity.index, visible);
|
|
426
|
+
if (xrLayer &&
|
|
427
|
+
visible &&
|
|
428
|
+
!this.pendingNativeLayerRemovals.has(entity.index)) {
|
|
262
429
|
layers.push(xrLayer);
|
|
263
430
|
}
|
|
264
431
|
}
|
|
@@ -268,48 +435,98 @@ class XRLayerSystem extends createSystem({
|
|
|
268
435
|
// Per-frame rendering
|
|
269
436
|
// ---------------------------------------------------------------------------
|
|
270
437
|
renderLayers() {
|
|
438
|
+
var _a, _b;
|
|
439
|
+
if (this.sessionLayerInitializationPending) {
|
|
440
|
+
this.tryInitializeSessionLayers();
|
|
441
|
+
}
|
|
271
442
|
const renderer = this.renderer;
|
|
272
443
|
const savedRenderTarget = renderer.getRenderTarget();
|
|
273
|
-
// Disable XR so renderer.render() uses the layer's own camera
|
|
274
|
-
// instead of the headset-tracked XR cameras.
|
|
275
444
|
const savedXrEnabled = renderer.xr.enabled;
|
|
276
|
-
|
|
277
|
-
// Save clear color so we can clear layers to transparent
|
|
445
|
+
// Save clear color so we can clear layers to transparent.
|
|
278
446
|
renderer.getClearColor(_clearColor);
|
|
279
447
|
const savedClearAlpha = renderer.getClearAlpha();
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
448
|
+
try {
|
|
449
|
+
// Disable XR so renderer.render() uses the layer-specific camera instead of
|
|
450
|
+
// the headset-tracked XR cameras.
|
|
451
|
+
renderer.xr.enabled = false;
|
|
452
|
+
renderer.setClearColor(0x000000, 0);
|
|
453
|
+
this.renderRetiredLayers(renderer);
|
|
454
|
+
for (const entity of this.queries.activeLayers.entities) {
|
|
455
|
+
const isQuad = !!XRLayerState.data.isQuad[entity.index];
|
|
456
|
+
const component = isQuad ? XRQuadLayer : XRCylinderLayer;
|
|
457
|
+
let xrLayer = XRLayerState.data.xrLayer[entity.index];
|
|
458
|
+
const mesh = XRLayerState.data.mesh[entity.index];
|
|
459
|
+
this.syncLayerGeometry(entity, mesh, isQuad);
|
|
460
|
+
const visible = isObjectTreeVisible(mesh);
|
|
461
|
+
if (this.layerVisibility.get(entity.index) !== visible) {
|
|
462
|
+
this.layerVisibility.set(entity.index, visible);
|
|
463
|
+
if (this.sessionUsesLayers && xrLayer) {
|
|
464
|
+
this.updateSessionLayers();
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (this.sessionUsesLayers && xrLayer) {
|
|
468
|
+
// An unsupported live transform queues removal. Keep the old native
|
|
469
|
+
// surface valid until that pending render state becomes active.
|
|
470
|
+
this.syncLayerTransform(xrLayer, mesh, entity);
|
|
471
|
+
}
|
|
472
|
+
if (this.sessionUsesLayers &&
|
|
473
|
+
!xrLayer &&
|
|
474
|
+
this.shouldRetryNativeLayer(entity, mesh, isQuad) &&
|
|
475
|
+
this.activateNativeLayer(entity)) {
|
|
476
|
+
xrLayer = XRLayerState.data.xrLayer[entity.index];
|
|
477
|
+
this.updateSessionLayers();
|
|
478
|
+
}
|
|
479
|
+
let nativeActive = this.sessionUsesLayers &&
|
|
480
|
+
xrLayer != null &&
|
|
481
|
+
((_b = (_a = this.xrManager.getSession()) === null || _a === void 0 ? void 0 : _a.renderState.layers) === null || _b === void 0 ? void 0 : _b.includes(xrLayer));
|
|
482
|
+
if (xrLayer &&
|
|
483
|
+
this.pendingNativeLayerRemovals.has(entity.index) &&
|
|
484
|
+
!nativeActive) {
|
|
485
|
+
this.deactivateNativeLayer(entity);
|
|
486
|
+
xrLayer = null;
|
|
487
|
+
nativeActive = false;
|
|
488
|
+
}
|
|
489
|
+
if (nativeActive && xrLayer) {
|
|
490
|
+
this.useNativeSurface(entity, mesh);
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
this.useFallbackSurface(entity, mesh);
|
|
494
|
+
}
|
|
495
|
+
const callback = entity.getValue(component, 'renderCallback');
|
|
496
|
+
if (!visible && !nativeActive) {
|
|
296
497
|
continue;
|
|
297
498
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
499
|
+
const renderTarget = XRLayerState.data.renderTarget[entity.index];
|
|
500
|
+
if (nativeActive && xrLayer) {
|
|
501
|
+
const binding = this.xrManager.getBinding();
|
|
502
|
+
const frame = this.xrFrame;
|
|
503
|
+
if (!binding || !frame) {
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
const subImage = binding.getSubImage(xrLayer, frame);
|
|
507
|
+
renderer.setRenderTargetTextures(renderTarget, subImage.colorTexture, undefined);
|
|
508
|
+
renderer.setRenderTarget(renderTarget);
|
|
509
|
+
if (!visible) {
|
|
510
|
+
// A hidden layer remains in the current render state until the next
|
|
511
|
+
// XR frame. Clear it now so that pending removal cannot expose the
|
|
512
|
+
// previous frame's stale content.
|
|
513
|
+
renderer.clear(true, true, true);
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
renderer.setRenderTarget(renderTarget);
|
|
519
|
+
}
|
|
520
|
+
callback === null || callback === void 0 ? void 0 : callback();
|
|
306
521
|
}
|
|
307
|
-
callback();
|
|
308
522
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
523
|
+
finally {
|
|
524
|
+
// Never leak layer-rendering state into the application render, even
|
|
525
|
+
// when a binding or user render callback throws.
|
|
526
|
+
renderer.setClearColor(_clearColor, savedClearAlpha);
|
|
527
|
+
renderer.xr.enabled = savedXrEnabled;
|
|
528
|
+
renderer.setRenderTarget(savedRenderTarget);
|
|
529
|
+
}
|
|
313
530
|
}
|
|
314
531
|
// ---------------------------------------------------------------------------
|
|
315
532
|
// Helpers
|
|
@@ -319,25 +536,182 @@ class XRLayerSystem extends createSystem({
|
|
|
319
536
|
* avoiding per-frame XRRigidTransform allocations for static layers.
|
|
320
537
|
*/
|
|
321
538
|
syncLayerTransform(xrLayer, mesh, entity) {
|
|
322
|
-
mesh.
|
|
323
|
-
|
|
324
|
-
|
|
539
|
+
const hasSupportedTransform = resolveLayerReferencePose(mesh, this.player, _position, _quaternion, _scale, _relativeMatrix);
|
|
540
|
+
const isQuad = !!XRLayerState.data.isQuad[entity.index];
|
|
541
|
+
this.readLayerConfiguration(entity, isQuad, _configuration);
|
|
325
542
|
const cached = XRLayerState.data.cachedTransform[entity.index];
|
|
326
|
-
if (
|
|
327
|
-
cached.
|
|
328
|
-
|
|
543
|
+
if (hasSupportedTransform &&
|
|
544
|
+
(cached === null || cached === void 0 ? void 0 : cached.position.equals(_position)) &&
|
|
545
|
+
cached.quaternion.equals(_quaternion) &&
|
|
546
|
+
cached.scale.equals(_scale) &&
|
|
547
|
+
sameVector(cached.configuration, _configuration)) {
|
|
548
|
+
this.restorePendingNativeLayer(entity);
|
|
549
|
+
return true;
|
|
329
550
|
}
|
|
551
|
+
if (isQuad) {
|
|
552
|
+
const dimensions = hasSupportedTransform
|
|
553
|
+
? resolveQuadLayerDimensions(_configuration.x, _configuration.y, _scale)
|
|
554
|
+
: null;
|
|
555
|
+
if (!dimensions) {
|
|
556
|
+
this.rememberUnsupportedLayer(entity, 'quad');
|
|
557
|
+
this.requestNativeLayerRemoval(entity);
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
xrLayer.width = dimensions.width;
|
|
561
|
+
xrLayer.height = dimensions.height;
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
const dimensions = hasSupportedTransform
|
|
565
|
+
? resolveCylinderLayerDimensions(_configuration.x, _configuration.y, _scale)
|
|
566
|
+
: null;
|
|
567
|
+
if (!dimensions || !isValidCylinderLayerAngle(_configuration.z)) {
|
|
568
|
+
this.rememberUnsupportedLayer(entity, 'cylinder');
|
|
569
|
+
this.requestNativeLayerRemoval(entity);
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
xrLayer.radius = dimensions.radius;
|
|
573
|
+
xrLayer.aspectRatio = dimensions.aspectRatio;
|
|
574
|
+
xrLayer.centralAngle = _configuration.z;
|
|
575
|
+
}
|
|
576
|
+
this.restorePendingNativeLayer(entity);
|
|
330
577
|
xrLayer.transform = toRigidTransform(_position, _quaternion);
|
|
331
578
|
if (cached) {
|
|
332
579
|
cached.position.copy(_position);
|
|
333
580
|
cached.quaternion.copy(_quaternion);
|
|
581
|
+
cached.scale.copy(_scale);
|
|
582
|
+
cached.configuration.copy(_configuration);
|
|
334
583
|
}
|
|
335
584
|
else {
|
|
336
585
|
entity.setValue(XRLayerState, 'cachedTransform', {
|
|
337
586
|
position: _position.clone(),
|
|
338
587
|
quaternion: _quaternion.clone(),
|
|
588
|
+
scale: _scale.clone(),
|
|
589
|
+
configuration: _configuration.clone(),
|
|
339
590
|
});
|
|
340
591
|
}
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
requestNativeLayerRemoval(entity) {
|
|
595
|
+
if (this.pendingNativeLayerRemovals.has(entity.index)) {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
this.pendingNativeLayerRemovals.add(entity.index);
|
|
599
|
+
this.updateSessionLayers();
|
|
600
|
+
}
|
|
601
|
+
restorePendingNativeLayer(entity) {
|
|
602
|
+
this.unsupportedLayerStates.delete(entity.index);
|
|
603
|
+
if (!this.pendingNativeLayerRemovals.delete(entity.index)) {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
this.updateSessionLayers();
|
|
607
|
+
}
|
|
608
|
+
readLayerConfiguration(entity, isQuad, target) {
|
|
609
|
+
if (isQuad) {
|
|
610
|
+
target.set(entity.getValue(XRQuadLayer, 'width'), entity.getValue(XRQuadLayer, 'height'), 0);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
target.set(entity.getValue(XRCylinderLayer, 'radius'), entity.getValue(XRCylinderLayer, 'aspectRatio'), entity.getValue(XRCylinderLayer, 'centralAngle'));
|
|
614
|
+
}
|
|
615
|
+
syncLayerGeometry(entity, mesh, isQuad) {
|
|
616
|
+
this.readLayerConfiguration(entity, isQuad, _configuration);
|
|
617
|
+
const cached = this.layerConfigurations.get(entity.index);
|
|
618
|
+
if (cached && sameVector(cached, _configuration)) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
this.layerConfigurations.set(entity.index, _configuration.clone());
|
|
622
|
+
// The fallback must reflect every live configuration, including values
|
|
623
|
+
// that a native composition layer cannot represent. Collapse non-finite
|
|
624
|
+
// dimensions to a safe degenerate geometry instead of leaving the last
|
|
625
|
+
// valid (and therefore misleading) shape on screen.
|
|
626
|
+
const geometry = this.createFallbackGeometry(entity, isQuad);
|
|
627
|
+
const oldGeometry = mesh.geometry;
|
|
628
|
+
mesh.geometry = geometry;
|
|
629
|
+
oldGeometry.dispose();
|
|
630
|
+
}
|
|
631
|
+
createFallbackGeometry(entity, isQuad) {
|
|
632
|
+
this.readLayerConfiguration(entity, isQuad, _configuration);
|
|
633
|
+
if (isQuad) {
|
|
634
|
+
const width = Number.isFinite(_configuration.x) ? _configuration.x : 0;
|
|
635
|
+
const height = Number.isFinite(_configuration.y) ? _configuration.y : 0;
|
|
636
|
+
return new PlaneGeometry(width, height);
|
|
637
|
+
}
|
|
638
|
+
const radius = Number.isFinite(_configuration.x) ? _configuration.x : 0;
|
|
639
|
+
const centralAngle = Number.isFinite(_configuration.z)
|
|
640
|
+
? _configuration.z
|
|
641
|
+
: 0;
|
|
642
|
+
const rawHeight = (_configuration.x * _configuration.z) / _configuration.y;
|
|
643
|
+
const height = Number.isFinite(rawHeight) ? rawHeight : 0;
|
|
644
|
+
return new CylinderGeometry(radius, radius, height, 64, 64, true, Math.PI - centralAngle / 2, centralAngle);
|
|
645
|
+
}
|
|
646
|
+
shouldRetryNativeLayer(entity, mesh, isQuad) {
|
|
647
|
+
const unsupported = this.unsupportedLayerStates.get(entity.index);
|
|
648
|
+
if (!unsupported) {
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
651
|
+
const hasSupportedTransform = resolveLayerReferencePose(mesh, this.player, _position, _quaternion, _scale, _relativeMatrix);
|
|
652
|
+
this.readLayerConfiguration(entity, isQuad, _configuration);
|
|
653
|
+
const hasSupportedConfiguration = isQuad
|
|
654
|
+
? resolveQuadLayerDimensions(_configuration.x, _configuration.y, _scale) != null
|
|
655
|
+
: resolveCylinderLayerDimensions(_configuration.x, _configuration.y, _scale) != null && isValidCylinderLayerAngle(_configuration.z);
|
|
656
|
+
// Approximate cache equality suppresses repeated work while a layer stays
|
|
657
|
+
// unsupported. Never let that tolerance hide a boundary crossing back to
|
|
658
|
+
// a representable native layer.
|
|
659
|
+
if (hasSupportedTransform && hasSupportedConfiguration) {
|
|
660
|
+
return true;
|
|
661
|
+
}
|
|
662
|
+
return (!sameVector(unsupported.configuration, _configuration) ||
|
|
663
|
+
this.hasLayerShapeChanged(unsupported.relativeMatrix, _relativeMatrix));
|
|
664
|
+
}
|
|
665
|
+
hasLayerShapeChanged(previous, current) {
|
|
666
|
+
const previousElements = previous.elements;
|
|
667
|
+
const currentElements = current.elements;
|
|
668
|
+
const previousFinite = previousElements.every(Number.isFinite);
|
|
669
|
+
const currentFinite = currentElements.every(Number.isFinite);
|
|
670
|
+
if (!previousFinite || !currentFinite) {
|
|
671
|
+
return previousElements.some((value, index) => !sameNumber(value, currentElements[index]));
|
|
672
|
+
}
|
|
673
|
+
const gram = (elements) => {
|
|
674
|
+
const xx = elements[0] ** 2 + elements[1] ** 2 + elements[2] ** 2;
|
|
675
|
+
const yy = elements[4] ** 2 + elements[5] ** 2 + elements[6] ** 2;
|
|
676
|
+
const zz = elements[8] ** 2 + elements[9] ** 2 + elements[10] ** 2;
|
|
677
|
+
const normalizeDot = (dot, first, second) => {
|
|
678
|
+
const denominator = Math.sqrt(first * second);
|
|
679
|
+
return denominator === 0 ? dot : dot / denominator;
|
|
680
|
+
};
|
|
681
|
+
return [
|
|
682
|
+
xx,
|
|
683
|
+
yy,
|
|
684
|
+
zz,
|
|
685
|
+
normalizeDot(elements[0] * elements[4] +
|
|
686
|
+
elements[1] * elements[5] +
|
|
687
|
+
elements[2] * elements[6], xx, yy),
|
|
688
|
+
normalizeDot(elements[0] * elements[8] +
|
|
689
|
+
elements[1] * elements[9] +
|
|
690
|
+
elements[2] * elements[10], xx, zz),
|
|
691
|
+
normalizeDot(elements[4] * elements[8] +
|
|
692
|
+
elements[5] * elements[9] +
|
|
693
|
+
elements[6] * elements[10], yy, zz),
|
|
694
|
+
];
|
|
695
|
+
};
|
|
696
|
+
const previousGram = gram(previousElements);
|
|
697
|
+
const currentGram = gram(currentElements);
|
|
698
|
+
const nearlyEqual = (a, b, index) => {
|
|
699
|
+
const tolerance = index < 3
|
|
700
|
+
? 1e-10 * Math.max(Number.EPSILON, Math.abs(a), Math.abs(b))
|
|
701
|
+
: 1e-10;
|
|
702
|
+
return Math.abs(a - b) <= tolerance;
|
|
703
|
+
};
|
|
704
|
+
return (previousGram.some((value, index) => !nearlyEqual(value, currentGram[index], index)) ||
|
|
705
|
+
Math.sign(previous.determinant()) !== Math.sign(current.determinant()));
|
|
706
|
+
}
|
|
707
|
+
rememberUnsupportedLayer(entity, type) {
|
|
708
|
+
if (!this.unsupportedLayerStates.has(entity.index)) {
|
|
709
|
+
console.warn(`[XRLayerSystem] ${type} layer ${entity.index} has unsupported dimensions or a non-finite, reflected, or sheared transform; using projection fallback.`);
|
|
710
|
+
}
|
|
711
|
+
this.unsupportedLayerStates.set(entity.index, {
|
|
712
|
+
relativeMatrix: _relativeMatrix.clone(),
|
|
713
|
+
configuration: _configuration.clone(),
|
|
714
|
+
});
|
|
341
715
|
}
|
|
342
716
|
createRenderTarget(width, height, stencil) {
|
|
343
717
|
return new WebGLRenderTarget(width, height, {
|