lunchboxjs 0.1.4018 → 0.2.1001-beta.2
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/lunchboxjs.js +1739 -1670
- package/dist/lunchboxjs.min.js +1 -1
- package/dist/lunchboxjs.module.js +1694 -1667
- package/extras/OrbitControlsWrapper.vue +5 -7
- package/package.json +15 -4
- package/src/components/LunchboxEventHandlers.tsx +237 -0
- package/src/components/LunchboxWrapper/LunchboxScene.tsx +8 -0
- package/src/components/LunchboxWrapper/LunchboxWrapper.tsx +341 -0
- package/src/components/LunchboxWrapper/prepCanvas.ts +27 -21
- package/src/components/LunchboxWrapper/resizeCanvas.ts +13 -12
- package/src/components/autoGeneratedComponents.ts +1 -1
- package/src/components/index.ts +2 -4
- package/src/core/createNode.ts +2 -18
- package/src/core/extend.ts +1 -1
- package/src/core/index.ts +0 -3
- package/src/core/instantiateThreeObject/index.ts +7 -2
- package/src/core/instantiateThreeObject/processProps.ts +1 -1
- package/src/core/interaction.ts +55 -0
- package/src/core/minidom.ts +5 -9
- package/src/core/update.ts +92 -53
- package/src/core/updateObjectProp.ts +5 -14
- package/src/index.ts +270 -76
- package/src/keys.ts +25 -0
- package/src/nodeOps/createElement.ts +2 -5
- package/src/nodeOps/index.ts +70 -57
- package/src/nodeOps/insert.ts +11 -32
- package/src/nodeOps/remove.ts +1 -17
- package/src/types.ts +34 -10
- package/src/utils/index.ts +11 -4
- package/dist/.DS_Store +0 -0
- package/src/.DS_Store +0 -0
- package/src/components/LunchboxWrapper/LunchboxWrapper.ts +0 -312
- package/src/components/catalogue.ts +0 -3
- package/src/core/.DS_Store +0 -0
- package/src/core/allNodes.ts +0 -4
- package/src/core/ensure.ts +0 -203
- package/src/core/interaction/index.ts +0 -102
- package/src/core/interaction/input.ts +0 -4
- package/src/core/interaction/interactables.ts +0 -14
- package/src/core/interaction/setupAutoRaycaster.ts +0 -224
- package/src/core/start.ts +0 -11
package/dist/lunchboxjs.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
2
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vue'), require('three'), require('lodash')) :
|
|
3
3
|
typeof define === 'function' && define.amd ? define(['exports', 'vue', 'three', 'lodash'], factory) :
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Lunchbox = {}, global.vue, global.three, global.lodash));
|
|
5
5
|
})(this, (function (exports, vue, THREE, lodash) { 'use strict';
|
|
6
6
|
|
|
7
7
|
function _interopNamespace(e) {
|
|
@@ -24,1063 +24,819 @@
|
|
|
24
24
|
|
|
25
25
|
var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
function find(target) {
|
|
28
|
+
target = vue.isRef(target) ? target.value : target; // handle standard lunchbox node
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const camera = ensuredCamera.value;
|
|
34
|
-
// ignore if no element
|
|
35
|
-
if (!renderer?.domElement || !scene || !camera)
|
|
36
|
-
return;
|
|
37
|
-
width = width ?? window.innerWidth;
|
|
38
|
-
height = height ?? window.innerHeight;
|
|
39
|
-
// update camera
|
|
40
|
-
const aspect = width / height;
|
|
41
|
-
if (camera.type?.toLowerCase() === 'perspectivecamera') {
|
|
42
|
-
const perspectiveCamera = camera.instance;
|
|
43
|
-
perspectiveCamera.aspect = aspect;
|
|
44
|
-
perspectiveCamera.updateProjectionMatrix();
|
|
45
|
-
}
|
|
46
|
-
else if (camera.type?.toLowerCase() === 'orthographiccamera') {
|
|
47
|
-
// console.log('TODO: ortho camera update')
|
|
48
|
-
const orthoCamera = camera.instance;
|
|
49
|
-
const heightInTermsOfWidth = height / width;
|
|
50
|
-
orthoCamera.top = heightInTermsOfWidth * 10;
|
|
51
|
-
orthoCamera.bottom = -heightInTermsOfWidth * 10;
|
|
52
|
-
orthoCamera.right = 10;
|
|
53
|
-
orthoCamera.left = -10;
|
|
54
|
-
orthoCamera.updateProjectionMatrix();
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
console.log('TODO: non-ortho or perspective camera');
|
|
58
|
-
}
|
|
59
|
-
// update canvas
|
|
60
|
-
renderer.setSize(width, height);
|
|
61
|
-
// render immediately so there's no flicker
|
|
62
|
-
if (scene && camera.instance) {
|
|
63
|
-
renderer.render(vue.toRaw(scene), vue.toRaw(camera.instance));
|
|
64
|
-
}
|
|
65
|
-
};
|
|
30
|
+
if (isLunchboxStandardNode(target)) {
|
|
31
|
+
return target?.instance;
|
|
32
|
+
} // handle component
|
|
66
33
|
|
|
67
|
-
const getInnerDimensions = (node) => {
|
|
68
|
-
const computedStyle = getComputedStyle(node);
|
|
69
|
-
const width = node.clientWidth - parseFloat(computedStyle.paddingLeft) - parseFloat(computedStyle.paddingRight);
|
|
70
|
-
const height = node.clientHeight - parseFloat(computedStyle.paddingTop) - parseFloat(computedStyle.paddingBottom);
|
|
71
|
-
return { width, height };
|
|
72
|
-
};
|
|
73
|
-
const prepCanvas = (container, canvasElement, onBeforeUnmount, sizePolicy) => {
|
|
74
|
-
const containerElement = container.value?.domElement;
|
|
75
|
-
if (!containerElement)
|
|
76
|
-
throw new Error('missing container');
|
|
77
|
-
// save...
|
|
78
|
-
// ...and size element
|
|
79
|
-
const resizeCanvasByPolicy = () => {
|
|
80
|
-
if (sizePolicy === "container") {
|
|
81
|
-
const dims = getInnerDimensions(containerElement);
|
|
82
|
-
resizeCanvas(dims.width, dims.height);
|
|
83
|
-
}
|
|
84
|
-
else
|
|
85
|
-
resizeCanvas();
|
|
86
|
-
};
|
|
87
|
-
resizeCanvasByPolicy();
|
|
88
|
-
// attach listeners
|
|
89
|
-
const observer = new ResizeObserver(([canvas]) => {
|
|
90
|
-
resizeCanvasByPolicy();
|
|
91
|
-
});
|
|
92
|
-
// window.addEventListener('resize', resizeCanvas)
|
|
93
|
-
if (containerElement) {
|
|
94
|
-
observer.observe(containerElement);
|
|
95
|
-
}
|
|
96
|
-
// cleanup
|
|
97
|
-
onBeforeUnmount(() => {
|
|
98
|
-
if (canvasElement) {
|
|
99
|
-
observer.unobserve(canvasElement);
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
// TODO:
|
|
105
|
-
// Continue r3f prop - what else (besides camera fov) makes r3f look good?
|
|
106
|
-
/** fixed & fill styling for container */
|
|
107
|
-
const fillStyle = (position) => {
|
|
108
|
-
return {
|
|
109
|
-
position,
|
|
110
|
-
top: 0,
|
|
111
|
-
right: 0,
|
|
112
|
-
bottom: 0,
|
|
113
|
-
left: 0,
|
|
114
|
-
width: '100%',
|
|
115
|
-
height: '100%',
|
|
116
|
-
display: 'block',
|
|
117
|
-
};
|
|
118
|
-
};
|
|
119
|
-
const LunchboxWrapper = {
|
|
120
|
-
name: 'Lunchbox',
|
|
121
|
-
props: {
|
|
122
|
-
// These should match the Lunchbox.WrapperProps interface
|
|
123
|
-
background: String,
|
|
124
|
-
cameraArgs: Array,
|
|
125
|
-
cameraLook: Array,
|
|
126
|
-
cameraLookAt: Array,
|
|
127
|
-
cameraPosition: Array,
|
|
128
|
-
dpr: Number,
|
|
129
|
-
ortho: Boolean,
|
|
130
|
-
orthographic: Boolean,
|
|
131
|
-
r3f: Boolean,
|
|
132
|
-
rendererArguments: Object,
|
|
133
|
-
rendererProperties: Object,
|
|
134
|
-
sizePolicy: String,
|
|
135
|
-
shadow: [Boolean, Object],
|
|
136
|
-
transparent: Boolean,
|
|
137
|
-
zoom: Number,
|
|
138
|
-
updateSource: Object,
|
|
139
|
-
},
|
|
140
|
-
setup(props, context) {
|
|
141
|
-
const canvas = vue.ref();
|
|
142
|
-
const useFallbackRenderer = vue.ref(true);
|
|
143
|
-
const dpr = vue.ref(props.dpr ?? -1);
|
|
144
|
-
const container = vue.ref();
|
|
145
|
-
let renderer;
|
|
146
|
-
let camera;
|
|
147
|
-
let scene;
|
|
148
|
-
// https://threejs.org/docs/index.html#manual/en/introduction/Color-management
|
|
149
|
-
if (props.r3f && THREE__namespace?.ColorManagement) {
|
|
150
|
-
THREE__namespace.ColorManagement.legacyMode = false;
|
|
151
|
-
}
|
|
152
|
-
// MOUNT
|
|
153
|
-
// ====================
|
|
154
|
-
vue.onMounted(() => {
|
|
155
|
-
// canvas needs to exist
|
|
156
|
-
if (!canvas.value)
|
|
157
|
-
throw new Error('missing canvas');
|
|
158
|
-
// RENDERER
|
|
159
|
-
// ====================
|
|
160
|
-
// is there already a renderer?
|
|
161
|
-
// TODO: allow other renderer types
|
|
162
|
-
renderer = tryGetNodeWithInstanceType([
|
|
163
|
-
'WebGLRenderer',
|
|
164
|
-
]);
|
|
165
|
-
// if renderer is missing, initialize with options
|
|
166
|
-
if (!renderer) {
|
|
167
|
-
// build renderer args
|
|
168
|
-
const rendererArgs = {
|
|
169
|
-
alpha: props.transparent,
|
|
170
|
-
antialias: true,
|
|
171
|
-
canvas: canvas.value.domElement,
|
|
172
|
-
powerPreference: !!props.r3f
|
|
173
|
-
? 'high-performance'
|
|
174
|
-
: 'default',
|
|
175
|
-
...(props.rendererArguments ?? {}),
|
|
176
|
-
};
|
|
177
|
-
// create new renderer
|
|
178
|
-
ensureRenderer.value = createNode({
|
|
179
|
-
type: 'WebGLRenderer',
|
|
180
|
-
uuid: fallbackRendererUuid,
|
|
181
|
-
props: {
|
|
182
|
-
args: [rendererArgs],
|
|
183
|
-
},
|
|
184
|
-
});
|
|
185
|
-
// we've initialized the renderer, so anything depending on it can execute now
|
|
186
|
-
rendererReady.value = true;
|
|
187
|
-
const rendererAsWebGlRenderer = ensureRenderer;
|
|
188
|
-
// apply r3f settings if desired
|
|
189
|
-
if (props.r3f) {
|
|
190
|
-
if (rendererAsWebGlRenderer.value.instance) {
|
|
191
|
-
rendererAsWebGlRenderer.value.instance.outputEncoding =
|
|
192
|
-
THREE__namespace.sRGBEncoding;
|
|
193
|
-
rendererAsWebGlRenderer.value.instance.toneMapping =
|
|
194
|
-
THREE__namespace.ACESFilmicToneMapping;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
// update render sugar
|
|
198
|
-
const sugar = {
|
|
199
|
-
shadow: props.shadow,
|
|
200
|
-
};
|
|
201
|
-
if (rendererAsWebGlRenderer.value.instance && sugar?.shadow) {
|
|
202
|
-
rendererAsWebGlRenderer.value.instance.shadowMap.enabled =
|
|
203
|
-
true;
|
|
204
|
-
if (typeof sugar.shadow === 'object') {
|
|
205
|
-
rendererAsWebGlRenderer.value.instance.shadowMap.type =
|
|
206
|
-
sugar.shadow.type;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
// set renderer props if needed
|
|
210
|
-
if (props.rendererProperties) {
|
|
211
|
-
Object.keys(props.rendererProperties).forEach((key) => {
|
|
212
|
-
lodash.set(rendererAsWebGlRenderer.value, key, props.rendererProperties[key]);
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
// update using created renderer
|
|
216
|
-
renderer = rendererAsWebGlRenderer.value;
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
useFallbackRenderer.value = false;
|
|
220
|
-
// the user has initialized the renderer, so anything depending
|
|
221
|
-
// on the renderer can execute
|
|
222
|
-
rendererReady.value = true;
|
|
223
|
-
}
|
|
224
|
-
// CAMERA
|
|
225
|
-
// ====================
|
|
226
|
-
// is there already a camera?
|
|
227
|
-
camera = tryGetNodeWithInstanceType([
|
|
228
|
-
'PerspectiveCamera',
|
|
229
|
-
'OrthographicCamera',
|
|
230
|
-
]);
|
|
231
|
-
// if not, let's create one
|
|
232
|
-
if (!camera) {
|
|
233
|
-
// create ortho camera
|
|
234
|
-
if (props.ortho || props.orthographic) {
|
|
235
|
-
ensuredCamera.value = createNode({
|
|
236
|
-
props: { args: props.cameraArgs ?? [] },
|
|
237
|
-
type: 'OrthographicCamera',
|
|
238
|
-
uuid: fallbackCameraUuid,
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
ensuredCamera.value = createNode({
|
|
243
|
-
props: {
|
|
244
|
-
args: props.cameraArgs ?? [
|
|
245
|
-
props.r3f ? 75 : 45,
|
|
246
|
-
0.5625,
|
|
247
|
-
1,
|
|
248
|
-
1000,
|
|
249
|
-
],
|
|
250
|
-
},
|
|
251
|
-
type: 'PerspectiveCamera',
|
|
252
|
-
uuid: fallbackCameraUuid,
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
cameraReady.value = true;
|
|
256
|
-
camera = ensuredCamera.value;
|
|
257
|
-
}
|
|
258
|
-
else {
|
|
259
|
-
cameraReady.value = true;
|
|
260
|
-
}
|
|
261
|
-
if (!camera.instance) {
|
|
262
|
-
throw new Error('Error creating camera.');
|
|
263
|
-
}
|
|
264
|
-
// move camera if needed
|
|
265
|
-
if (camera && props.cameraPosition) {
|
|
266
|
-
camera.instance.position.set(...props.cameraPosition);
|
|
267
|
-
}
|
|
268
|
-
// angle camera if needed
|
|
269
|
-
if (camera && (props.cameraLookAt || props.cameraLook)) {
|
|
270
|
-
const source = (props.cameraLookAt || props.cameraLook);
|
|
271
|
-
camera.instance.lookAt(...source);
|
|
272
|
-
}
|
|
273
|
-
// zoom camera if needed
|
|
274
|
-
if (camera && props.zoom !== undefined) {
|
|
275
|
-
camera.instance.zoom = props.zoom;
|
|
276
|
-
}
|
|
277
|
-
// SCENE
|
|
278
|
-
// ====================
|
|
279
|
-
scene = ensuredScene.value;
|
|
280
|
-
// set background color
|
|
281
|
-
if (scene && scene.instance && props.background) {
|
|
282
|
-
scene.instance.background = new THREE__namespace.Color(props.background);
|
|
283
|
-
}
|
|
284
|
-
// MISC PROPERTIES
|
|
285
|
-
// ====================
|
|
286
|
-
if (dpr.value === -1) {
|
|
287
|
-
dpr.value = window.devicePixelRatio;
|
|
288
|
-
}
|
|
289
|
-
if (renderer?.instance) {
|
|
290
|
-
renderer.instance.setPixelRatio(dpr.value);
|
|
291
|
-
globals.dpr.value = dpr.value;
|
|
292
|
-
// prep canvas (sizing, observe, unmount, etc)
|
|
293
|
-
prepCanvas(container, renderer.instance.domElement, vue.onBeforeUnmount, props.sizePolicy);
|
|
294
|
-
}
|
|
295
|
-
else {
|
|
296
|
-
throw new Error('missing renderer');
|
|
297
|
-
}
|
|
298
|
-
// CALLBACK PREP
|
|
299
|
-
// ====================
|
|
300
|
-
const app = vue.getCurrentInstance().appContext.app;
|
|
301
|
-
// START
|
|
302
|
-
// ====================
|
|
303
|
-
for (let startCallback of startCallbacks) {
|
|
304
|
-
startCallback({
|
|
305
|
-
app,
|
|
306
|
-
camera: camera.instance,
|
|
307
|
-
renderer: renderer.instance,
|
|
308
|
-
scene: scene.instance,
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
// KICK UPDATE
|
|
312
|
-
// ====================
|
|
313
|
-
update({
|
|
314
|
-
app,
|
|
315
|
-
camera: camera.instance,
|
|
316
|
-
renderer: renderer.instance,
|
|
317
|
-
scene: scene.instance,
|
|
318
|
-
updateSource: props.updateSource,
|
|
319
|
-
});
|
|
320
|
-
});
|
|
321
|
-
// UNMOUNT
|
|
322
|
-
// ====================
|
|
323
|
-
vue.onBeforeUnmount(() => {
|
|
324
|
-
cancelUpdate();
|
|
325
|
-
cancelUpdateSource();
|
|
326
|
-
});
|
|
327
|
-
// RENDER FUNCTION
|
|
328
|
-
// ====================
|
|
329
|
-
const containerFillStyle = props.sizePolicy === 'container' ? 'static' : 'absolute';
|
|
330
|
-
const canvasFillStyle = props.sizePolicy === 'container' ? 'static' : 'fixed';
|
|
331
|
-
return () => [
|
|
332
|
-
context.slots.default?.() ?? null,
|
|
333
|
-
vue.h('div', {
|
|
334
|
-
style: fillStyle(containerFillStyle),
|
|
335
|
-
ref: container,
|
|
336
|
-
}, [
|
|
337
|
-
useFallbackRenderer.value
|
|
338
|
-
? vue.h('canvas', {
|
|
339
|
-
style: fillStyle(canvasFillStyle),
|
|
340
|
-
class: 'lunchbox-canvas',
|
|
341
|
-
ref: canvas,
|
|
342
|
-
})
|
|
343
|
-
: null,
|
|
344
|
-
]),
|
|
345
|
-
];
|
|
346
|
-
},
|
|
347
|
-
};
|
|
348
34
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
'mesh',
|
|
353
|
-
'instancedMesh',
|
|
354
|
-
'scene',
|
|
355
|
-
'sprite',
|
|
356
|
-
'object3D',
|
|
357
|
-
// geometry
|
|
358
|
-
'instancedBufferGeometry',
|
|
359
|
-
'bufferGeometry',
|
|
360
|
-
'boxBufferGeometry',
|
|
361
|
-
'circleBufferGeometry',
|
|
362
|
-
'coneBufferGeometry',
|
|
363
|
-
'cylinderBufferGeometry',
|
|
364
|
-
'dodecahedronBufferGeometry',
|
|
365
|
-
'extrudeBufferGeometry',
|
|
366
|
-
'icosahedronBufferGeometry',
|
|
367
|
-
'latheBufferGeometry',
|
|
368
|
-
'octahedronBufferGeometry',
|
|
369
|
-
'parametricBufferGeometry',
|
|
370
|
-
'planeBufferGeometry',
|
|
371
|
-
'polyhedronBufferGeometry',
|
|
372
|
-
'ringBufferGeometry',
|
|
373
|
-
'shapeBufferGeometry',
|
|
374
|
-
'sphereBufferGeometry',
|
|
375
|
-
'tetrahedronBufferGeometry',
|
|
376
|
-
'textBufferGeometry',
|
|
377
|
-
'torusBufferGeometry',
|
|
378
|
-
'torusKnotBufferGeometry',
|
|
379
|
-
'tubeBufferGeometry',
|
|
380
|
-
'wireframeGeometry',
|
|
381
|
-
'parametricGeometry',
|
|
382
|
-
'tetrahedronGeometry',
|
|
383
|
-
'octahedronGeometry',
|
|
384
|
-
'icosahedronGeometry',
|
|
385
|
-
'dodecahedronGeometry',
|
|
386
|
-
'polyhedronGeometry',
|
|
387
|
-
'tubeGeometry',
|
|
388
|
-
'torusKnotGeometry',
|
|
389
|
-
'torusGeometry',
|
|
390
|
-
// textgeometry has been moved to /examples/jsm/geometries/TextGeometry
|
|
391
|
-
// 'textGeometry',
|
|
392
|
-
'sphereGeometry',
|
|
393
|
-
'ringGeometry',
|
|
394
|
-
'planeGeometry',
|
|
395
|
-
'latheGeometry',
|
|
396
|
-
'shapeGeometry',
|
|
397
|
-
'extrudeGeometry',
|
|
398
|
-
'edgesGeometry',
|
|
399
|
-
'coneGeometry',
|
|
400
|
-
'cylinderGeometry',
|
|
401
|
-
'circleGeometry',
|
|
402
|
-
'boxGeometry',
|
|
403
|
-
// materials
|
|
404
|
-
'material',
|
|
405
|
-
'shadowMaterial',
|
|
406
|
-
'spriteMaterial',
|
|
407
|
-
'rawShaderMaterial',
|
|
408
|
-
'shaderMaterial',
|
|
409
|
-
'pointsMaterial',
|
|
410
|
-
'meshPhysicalMaterial',
|
|
411
|
-
'meshStandardMaterial',
|
|
412
|
-
'meshPhongMaterial',
|
|
413
|
-
'meshToonMaterial',
|
|
414
|
-
'meshNormalMaterial',
|
|
415
|
-
'meshLambertMaterial',
|
|
416
|
-
'meshDepthMaterial',
|
|
417
|
-
'meshDistanceMaterial',
|
|
418
|
-
'meshBasicMaterial',
|
|
419
|
-
'meshMatcapMaterial',
|
|
420
|
-
'lineDashedMaterial',
|
|
421
|
-
'lineBasicMaterial',
|
|
422
|
-
// lights
|
|
423
|
-
'light',
|
|
424
|
-
'spotLightShadow',
|
|
425
|
-
'spotLight',
|
|
426
|
-
'pointLight',
|
|
427
|
-
'rectAreaLight',
|
|
428
|
-
'hemisphereLight',
|
|
429
|
-
'directionalLightShadow',
|
|
430
|
-
'directionalLight',
|
|
431
|
-
'ambientLight',
|
|
432
|
-
'lightShadow',
|
|
433
|
-
'ambientLightProbe',
|
|
434
|
-
'hemisphereLightProbe',
|
|
435
|
-
'lightProbe',
|
|
436
|
-
// textures
|
|
437
|
-
'texture',
|
|
438
|
-
'videoTexture',
|
|
439
|
-
'dataTexture',
|
|
440
|
-
'dataTexture3D',
|
|
441
|
-
'compressedTexture',
|
|
442
|
-
'cubeTexture',
|
|
443
|
-
'canvasTexture',
|
|
444
|
-
'depthTexture',
|
|
445
|
-
// Texture loaders
|
|
446
|
-
'textureLoader',
|
|
447
|
-
// misc
|
|
448
|
-
'group',
|
|
449
|
-
'catmullRomCurve3',
|
|
450
|
-
'points',
|
|
451
|
-
// helpers
|
|
452
|
-
'cameraHelper',
|
|
453
|
-
// cameras
|
|
454
|
-
'camera',
|
|
455
|
-
'perspectiveCamera',
|
|
456
|
-
'orthographicCamera',
|
|
457
|
-
'cubeCamera',
|
|
458
|
-
'arrayCamera',
|
|
459
|
-
// renderers
|
|
460
|
-
'webGLRenderer',
|
|
461
|
-
/*
|
|
462
|
-
// List copied from r3f:
|
|
463
|
-
// https://github.com/pmndrs/react-three-fiber/blob/master/packages/fiber/src/three-types.ts
|
|
464
|
-
|
|
465
|
-
// NOT IMPLEMENTED (can be added via Extend - docs.lunchboxjs.com/components/extend/):
|
|
466
|
-
audioListener: AudioListenerProps
|
|
467
|
-
positionalAudio: PositionalAudioProps
|
|
468
|
-
|
|
469
|
-
lOD: LODProps
|
|
470
|
-
skinnedMesh: SkinnedMeshProps
|
|
471
|
-
skeleton: SkeletonProps
|
|
472
|
-
bone: BoneProps
|
|
473
|
-
lineSegments: LineSegmentsProps
|
|
474
|
-
lineLoop: LineLoopProps
|
|
475
|
-
// see `audio`
|
|
476
|
-
// line: LineProps
|
|
477
|
-
immediateRenderObject: ImmediateRenderObjectProps
|
|
478
|
-
|
|
479
|
-
// primitive
|
|
480
|
-
primitive: PrimitiveProps
|
|
481
|
-
|
|
482
|
-
// helpers
|
|
483
|
-
spotLightHelper: SpotLightHelperProps
|
|
484
|
-
skeletonHelper: SkeletonHelperProps
|
|
485
|
-
pointLightHelper: PointLightHelperProps
|
|
486
|
-
hemisphereLightHelper: HemisphereLightHelperProps
|
|
487
|
-
gridHelper: GridHelperProps
|
|
488
|
-
polarGridHelper: PolarGridHelperProps
|
|
489
|
-
directionalLightHelper: DirectionalLightHelperProps
|
|
490
|
-
boxHelper: BoxHelperProps
|
|
491
|
-
box3Helper: Box3HelperProps
|
|
492
|
-
planeHelper: PlaneHelperProps
|
|
493
|
-
arrowHelper: ArrowHelperProps
|
|
494
|
-
axesHelper: AxesHelperProps
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
// misc
|
|
498
|
-
raycaster: RaycasterProps
|
|
499
|
-
vector2: Vector2Props
|
|
500
|
-
vector3: Vector3Props
|
|
501
|
-
vector4: Vector4Props
|
|
502
|
-
euler: EulerProps
|
|
503
|
-
matrix3: Matrix3Props
|
|
504
|
-
matrix4: Matrix4Props
|
|
505
|
-
quaternion: QuaternionProps
|
|
506
|
-
bufferAttribute: BufferAttributeProps
|
|
507
|
-
instancedBufferAttribute: InstancedBufferAttributeProps
|
|
508
|
-
color: ColorProps
|
|
509
|
-
fog: FogProps
|
|
510
|
-
fogExp2: FogExp2Props
|
|
511
|
-
shape: ShapeProps
|
|
512
|
-
*/
|
|
513
|
-
];
|
|
35
|
+
if (isLunchboxComponent(target)) {
|
|
36
|
+
return target?.$el?.instance;
|
|
37
|
+
} // handle vnode
|
|
514
38
|
|
|
515
|
-
const catalogue = {};
|
|
516
39
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
inheritAttrs: false,
|
|
521
|
-
name: tag,
|
|
522
|
-
setup(props, context) {
|
|
523
|
-
return () => {
|
|
524
|
-
return vue.h(tag, context.attrs, context.slots?.default?.() || []);
|
|
525
|
-
};
|
|
526
|
-
},
|
|
527
|
-
});
|
|
528
|
-
// turn components into registered map
|
|
529
|
-
const processed = autoGeneratedComponents
|
|
530
|
-
.map(createComponent$1)
|
|
531
|
-
.reduce((acc, curr) => {
|
|
532
|
-
acc[curr.name] = curr;
|
|
533
|
-
return acc;
|
|
534
|
-
}, {});
|
|
535
|
-
const components = {
|
|
536
|
-
...processed,
|
|
537
|
-
Lunchbox: LunchboxWrapper,
|
|
538
|
-
};
|
|
40
|
+
if (vue.isVNode(target)) {
|
|
41
|
+
return target.el?.instance;
|
|
42
|
+
}
|
|
539
43
|
|
|
540
|
-
|
|
541
|
-
target = vue.isRef(target) ? target.value : target;
|
|
542
|
-
// handle standard lunchbox node
|
|
543
|
-
if (isLunchboxStandardNode(target)) {
|
|
544
|
-
return target?.instance;
|
|
545
|
-
}
|
|
546
|
-
// handle component
|
|
547
|
-
if (isLunchboxComponent(target)) {
|
|
548
|
-
return target?.$el?.instance;
|
|
549
|
-
}
|
|
550
|
-
// handle vnode
|
|
551
|
-
if (vue.isVNode(target)) {
|
|
552
|
-
return target.el?.instance;
|
|
553
|
-
}
|
|
554
|
-
return null;
|
|
44
|
+
return null;
|
|
555
45
|
}
|
|
556
46
|
|
|
557
|
-
// MAKE SURE THESE MATCH VALUES IN types.EventKey
|
|
558
47
|
/** Type check on whether target is a Lunchbox.EventKey */
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
'onPointerUp',
|
|
565
|
-
'onPointerDown',
|
|
566
|
-
'onPointerOver',
|
|
567
|
-
'onPointerOut',
|
|
568
|
-
'onPointerEnter',
|
|
569
|
-
'onPointerLeave',
|
|
570
|
-
'onPointerMove',
|
|
571
|
-
// 'onPointerMissed',
|
|
572
|
-
// 'onUpdate',
|
|
573
|
-
'onWheel',
|
|
574
|
-
].includes(target);
|
|
48
|
+
|
|
49
|
+
const isEventKey = target => {
|
|
50
|
+
return ['onClick', 'onContextMenu', 'onDoubleClick', 'onPointerUp', 'onPointerDown', 'onPointerOver', 'onPointerOut', 'onPointerEnter', 'onPointerLeave', 'onPointerMove', // 'onPointerMissed',
|
|
51
|
+
// 'onUpdate',
|
|
52
|
+
'onWheel'].includes(target);
|
|
575
53
|
};
|
|
576
|
-
const isLunchboxComponent =
|
|
577
|
-
|
|
54
|
+
const isLunchboxComponent = node => {
|
|
55
|
+
return node?.$el && node?.$el?.hasOwnProperty?.('instance');
|
|
578
56
|
};
|
|
579
|
-
const isLunchboxDomComponent =
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
const typeToCheck = typeof node === 'string' ? node : node?.type;
|
|
583
|
-
return lunchboxDomComponentNames.includes(typeToCheck ?? '');
|
|
57
|
+
const isLunchboxDomComponent = node => {
|
|
58
|
+
if (node?.metaType === 'domMeta') return true;
|
|
59
|
+
return node?.props?.['data-lunchbox'];
|
|
584
60
|
};
|
|
585
|
-
const isLunchboxStandardNode =
|
|
586
|
-
|
|
61
|
+
const isLunchboxStandardNode = node => {
|
|
62
|
+
return node?.metaType === 'standardMeta';
|
|
587
63
|
};
|
|
588
|
-
const isLunchboxRootNode =
|
|
589
|
-
|
|
64
|
+
const isLunchboxRootNode = node => {
|
|
65
|
+
return node.isLunchboxRootNode;
|
|
590
66
|
};
|
|
591
67
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
68
|
+
/** Create a new Lunchbox comment node. */
|
|
69
|
+
|
|
70
|
+
function createCommentNode(options = {}) {
|
|
71
|
+
const defaults = {
|
|
72
|
+
text: options.text ?? ''
|
|
73
|
+
};
|
|
74
|
+
return new exports.MiniDom.RendererCommentNode({ ...defaults,
|
|
75
|
+
...options,
|
|
76
|
+
metaType: 'commentMeta'
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/** Create a new DOM node. */
|
|
602
80
|
|
|
603
|
-
|
|
604
|
-
|
|
81
|
+
function createDomNode(options = {}) {
|
|
82
|
+
const domElement = document.createElement(options.type ?? '');
|
|
83
|
+
const defaults = {
|
|
84
|
+
domElement
|
|
85
|
+
};
|
|
86
|
+
const node = new exports.MiniDom.RendererDomNode({ ...defaults,
|
|
87
|
+
...options,
|
|
88
|
+
metaType: 'domMeta'
|
|
89
|
+
});
|
|
90
|
+
return node;
|
|
91
|
+
}
|
|
92
|
+
/** Create a new Lunchbox text node. */
|
|
93
|
+
|
|
94
|
+
function createTextNode(options = {}) {
|
|
95
|
+
const defaults = {
|
|
96
|
+
text: options.text ?? ''
|
|
97
|
+
};
|
|
98
|
+
return new exports.MiniDom.RendererTextNode({ ...options,
|
|
99
|
+
...defaults,
|
|
100
|
+
metaType: 'textMeta'
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/** Create a new Lunchbox standard node. */
|
|
104
|
+
|
|
105
|
+
function createNode(options = {}, props = {}) {
|
|
106
|
+
const defaults = {
|
|
107
|
+
attached: options.attached ?? [],
|
|
108
|
+
attachedArray: options.attachedArray ?? {},
|
|
109
|
+
instance: options.instance ?? null
|
|
110
|
+
};
|
|
111
|
+
const node = new exports.MiniDom.RendererStandardNode({ ...options,
|
|
112
|
+
...defaults,
|
|
113
|
+
metaType: 'standardMeta'
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (node.type && !isLunchboxRootNode(node) && !node.instance) {
|
|
117
|
+
node.instance = instantiateThreeObject({ ...node,
|
|
118
|
+
props: { ...node.props,
|
|
119
|
+
...props
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return node;
|
|
125
|
+
}
|
|
605
126
|
|
|
606
127
|
/** Add an event listener to the given node. Also creates the event teardown function and any necessary raycaster/interaction dictionary updates. */
|
|
607
|
-
function addEventListener({
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
128
|
+
function addEventListener({
|
|
129
|
+
node,
|
|
130
|
+
key,
|
|
131
|
+
interactables,
|
|
132
|
+
value
|
|
133
|
+
}) {
|
|
134
|
+
// create new records for this key if needed
|
|
135
|
+
if (!node.eventListeners[key]) {
|
|
136
|
+
node.eventListeners[key] = [];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!node.eventListenerRemoveFunctions[key]) {
|
|
140
|
+
node.eventListenerRemoveFunctions[key] = [];
|
|
141
|
+
} // add event listener
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
node.eventListeners[key].push(value); // if we need it, let's get/create the main raycaster
|
|
145
|
+
|
|
146
|
+
if (interactionsRequiringRaycaster.includes(key)) {
|
|
147
|
+
if (node.instance && !interactables.value.includes(node)) {
|
|
148
|
+
// add to interactables
|
|
149
|
+
interactables.value.push(node);
|
|
150
|
+
node.eventListenerRemoveFunctions[key].push(() => {
|
|
151
|
+
// remove from interactables
|
|
152
|
+
const idx = interactables.value.indexOf(node);
|
|
153
|
+
|
|
154
|
+
if (idx !== -1) {
|
|
155
|
+
interactables.value.splice(idx, 1);
|
|
625
156
|
}
|
|
157
|
+
});
|
|
626
158
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
const idx = currentIntersections
|
|
631
|
-
.map((v) => v.element)
|
|
632
|
-
.findIndex((v) => v.instance &&
|
|
633
|
-
v.instance.uuid === node.instance?.uuid);
|
|
634
|
-
if (idx !== -1) {
|
|
635
|
-
if (isDown &&
|
|
636
|
-
(key === 'onClick' || key === 'onPointerDown')) {
|
|
637
|
-
node.eventListeners[key].forEach((func) => {
|
|
638
|
-
func({
|
|
639
|
-
intersection: currentIntersections[idx].intersection,
|
|
640
|
-
});
|
|
641
|
-
});
|
|
642
|
-
}
|
|
643
|
-
else if (!isDown && key === 'onPointerUp') {
|
|
644
|
-
node.eventListeners[key].forEach((func) => {
|
|
645
|
-
func({
|
|
646
|
-
intersection: currentIntersections[idx].intersection,
|
|
647
|
-
});
|
|
648
|
-
});
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
});
|
|
652
|
-
node.eventListenerRemoveFunctions[key].push(stop);
|
|
653
|
-
}
|
|
654
|
-
return node;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return node;
|
|
655
162
|
}
|
|
656
|
-
const interactionsRequiringRaycaster = [
|
|
657
|
-
'onClick',
|
|
658
|
-
'onPointerUp',
|
|
659
|
-
'onPointerDown',
|
|
660
|
-
'onPointerOver',
|
|
661
|
-
'onPointerOut',
|
|
662
|
-
'onPointerEnter',
|
|
663
|
-
'onPointerLeave',
|
|
664
|
-
'onPointerMove',
|
|
665
|
-
// 'onPointerMissed',
|
|
163
|
+
const interactionsRequiringRaycaster = ['onClick', 'onPointerUp', 'onPointerDown', 'onPointerOver', 'onPointerOut', 'onPointerEnter', 'onPointerLeave', 'onPointerMove' // 'onPointerMissed',
|
|
666
164
|
];
|
|
667
165
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
166
|
+
const resizeCanvas = (camera, renderer, scene, width, height) => {
|
|
167
|
+
// ignore if no element
|
|
168
|
+
if (!renderer?.domElement || !scene || !camera) return;
|
|
169
|
+
width = width ?? window.innerWidth;
|
|
170
|
+
height = height ?? window.innerHeight; // update camera
|
|
171
|
+
|
|
172
|
+
const aspect = width / height;
|
|
173
|
+
|
|
174
|
+
if (camera.type?.toLowerCase() === 'perspectivecamera') {
|
|
175
|
+
const perspectiveCamera = camera;
|
|
176
|
+
perspectiveCamera.aspect = aspect;
|
|
177
|
+
perspectiveCamera.updateProjectionMatrix();
|
|
178
|
+
} else if (camera.type?.toLowerCase() === 'orthographiccamera') {
|
|
179
|
+
// TODO: ortho camera update
|
|
180
|
+
const orthoCamera = camera;
|
|
181
|
+
const heightInTermsOfWidth = height / width;
|
|
182
|
+
orthoCamera.top = heightInTermsOfWidth * 10;
|
|
183
|
+
orthoCamera.bottom = -heightInTermsOfWidth * 10;
|
|
184
|
+
orthoCamera.right = 10;
|
|
185
|
+
orthoCamera.left = -10;
|
|
186
|
+
orthoCamera.updateProjectionMatrix();
|
|
187
|
+
} else ; // update canvas
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
renderer.setSize(width, height); // render immediately so there's no flicker
|
|
191
|
+
|
|
192
|
+
if (scene && camera) {
|
|
193
|
+
renderer.render(vue.toRaw(scene), vue.toRaw(camera));
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const getInnerDimensions = node => {
|
|
198
|
+
const computedStyle = getComputedStyle(node);
|
|
199
|
+
const width = node.clientWidth - parseFloat(computedStyle.paddingLeft) - parseFloat(computedStyle.paddingRight);
|
|
200
|
+
const height = node.clientHeight - parseFloat(computedStyle.paddingTop) - parseFloat(computedStyle.paddingBottom);
|
|
201
|
+
return {
|
|
202
|
+
width,
|
|
203
|
+
height
|
|
204
|
+
};
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const prepCanvas = (container, camera, renderer, scene, sizePolicy) => {
|
|
208
|
+
const containerElement = container.value?.domElement;
|
|
209
|
+
if (!containerElement) throw new Error('missing container'); // save and size element
|
|
210
|
+
|
|
211
|
+
const resizeCanvasByPolicy = () => {
|
|
212
|
+
if (sizePolicy === 'container') {
|
|
213
|
+
const dims = getInnerDimensions(containerElement);
|
|
214
|
+
resizeCanvas(camera, renderer, scene, dims.width, dims.height);
|
|
215
|
+
} else resizeCanvas(camera, renderer, scene);
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
resizeCanvasByPolicy(); // attach listeners
|
|
219
|
+
|
|
220
|
+
let observer = new ResizeObserver(() => {
|
|
221
|
+
resizeCanvasByPolicy();
|
|
222
|
+
}); // window.addEventListener('resize', resizeCanvas)
|
|
223
|
+
|
|
224
|
+
if (containerElement) {
|
|
225
|
+
observer.observe(containerElement);
|
|
226
|
+
} // cleanup
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
dispose() {
|
|
231
|
+
if (containerElement) {
|
|
232
|
+
observer.unobserve(containerElement);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const LunchboxScene = vue.defineComponent({
|
|
240
|
+
name: 'LunchboxScene',
|
|
241
|
+
|
|
242
|
+
setup(props, {
|
|
243
|
+
slots
|
|
244
|
+
}) {
|
|
245
|
+
return () => vue.createVNode(vue.resolveComponent("scene"), null, {
|
|
246
|
+
default: () => [slots.default?.()]
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const LunchboxEventHandlers = vue.defineComponent({
|
|
253
|
+
name: 'LunchboxEventHandlers',
|
|
254
|
+
|
|
255
|
+
setup() {
|
|
256
|
+
const interactables = useLunchboxInteractables();
|
|
257
|
+
const globals = useGlobals();
|
|
258
|
+
const mousePos = vue.ref({
|
|
259
|
+
x: Infinity,
|
|
260
|
+
y: Infinity
|
|
261
|
+
});
|
|
262
|
+
const inputActive = vue.ref(false);
|
|
263
|
+
let currentIntersections = [];
|
|
264
|
+
const raycaster = new THREE__namespace.Raycaster(new THREE__namespace.Vector3(), new THREE__namespace.Vector3(0, 0, -1));
|
|
265
|
+
|
|
266
|
+
const fireEventsFromIntersections = ({
|
|
267
|
+
element,
|
|
268
|
+
eventKeys,
|
|
269
|
+
intersection
|
|
270
|
+
}) => {
|
|
271
|
+
if (!element) return;
|
|
272
|
+
eventKeys.forEach(eventKey => {
|
|
273
|
+
if (element.eventListeners[eventKey]) {
|
|
274
|
+
element.eventListeners[eventKey].forEach(cb => {
|
|
275
|
+
cb({
|
|
276
|
+
intersection
|
|
712
277
|
});
|
|
713
|
-
|
|
714
|
-
// mark complete
|
|
715
|
-
autoRaycasterEventsInitialized = true;
|
|
716
|
-
// cancel setup watcher
|
|
717
|
-
if (stopWatcher) {
|
|
718
|
-
stopWatcher();
|
|
278
|
+
});
|
|
719
279
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
280
|
+
});
|
|
281
|
+
}; // add mouse listener to renderer DOM element when the element is ready
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
onRendererReady(v => {
|
|
285
|
+
if (!v?.domElement) return; // we have a DOM element, so let's add mouse listeners
|
|
286
|
+
|
|
287
|
+
const {
|
|
288
|
+
domElement
|
|
289
|
+
} = v;
|
|
290
|
+
|
|
291
|
+
const mouseMoveListener = evt => {
|
|
292
|
+
const screenWidth = (domElement.width ?? 1) / globals.dpr;
|
|
293
|
+
const screenHeight = (domElement.height ?? 1) / globals.dpr;
|
|
294
|
+
mousePos.value.x = evt.offsetX / screenWidth * 2 - 1;
|
|
295
|
+
mousePos.value.y = -(evt.offsetY / screenHeight) * 2 + 1;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const mouseDownListener = () => inputActive.value = true;
|
|
299
|
+
|
|
300
|
+
const mouseUpListener = () => inputActive.value = false; // add mouse events
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
domElement.addEventListener('pointermove', mouseMoveListener);
|
|
304
|
+
domElement.addEventListener('pointerdown', mouseDownListener);
|
|
305
|
+
domElement.addEventListener('pointerup', mouseUpListener);
|
|
306
|
+
});
|
|
307
|
+
const camera = useCamera();
|
|
308
|
+
|
|
309
|
+
const update = () => {
|
|
310
|
+
const c = camera.value;
|
|
311
|
+
if (!c) return; // console.log(camera.value)
|
|
312
|
+
|
|
313
|
+
raycaster.setFromCamera(mousePos.value, c);
|
|
314
|
+
const intersections = raycaster.intersectObjects(interactables?.value.map(v => v.instance) ?? []);
|
|
315
|
+
let leaveValues = [],
|
|
316
|
+
entering = [],
|
|
317
|
+
staying = []; // intersection arrays
|
|
318
|
+
|
|
319
|
+
leaveValues = currentIntersections.map(v => v.intersection); // element arrays
|
|
320
|
+
|
|
321
|
+
intersections?.forEach(intersection => {
|
|
322
|
+
const currentIdx = currentIntersections.findIndex(v => v.intersection.object === intersection.object);
|
|
323
|
+
|
|
739
324
|
if (currentIdx === -1) {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
325
|
+
const found = interactables?.value.find(v => v.instance?.uuid === intersection.object.uuid);
|
|
326
|
+
|
|
327
|
+
if (found) {
|
|
328
|
+
entering.push({
|
|
329
|
+
element: found,
|
|
330
|
+
intersection
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
const found = interactables?.value.find(v => v.instance?.uuid === intersection.object.uuid);
|
|
335
|
+
|
|
336
|
+
if (found) {
|
|
337
|
+
staying.push({
|
|
338
|
+
element: found,
|
|
339
|
+
intersection
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
} // this is a current intersection, so it won't be in our `leave` array
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
const leaveIdx = leaveValues.findIndex(v => v.object.uuid === intersection.object.uuid);
|
|
346
|
+
|
|
753
347
|
if (leaveIdx !== -1) {
|
|
754
|
-
|
|
348
|
+
leaveValues.splice(leaveIdx, 1);
|
|
755
349
|
}
|
|
756
|
-
|
|
757
|
-
|
|
350
|
+
});
|
|
351
|
+
const leaving = leaveValues.map(intersection => {
|
|
758
352
|
return {
|
|
759
|
-
|
|
760
|
-
|
|
353
|
+
element: interactables?.value.find(interactable => interactable.instance?.uuid === intersection.object.uuid),
|
|
354
|
+
intersection
|
|
761
355
|
};
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
356
|
+
}); // new interactions
|
|
357
|
+
|
|
358
|
+
entering.forEach(({
|
|
359
|
+
element,
|
|
360
|
+
intersection
|
|
361
|
+
}) => {
|
|
765
362
|
fireEventsFromIntersections({
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
363
|
+
element,
|
|
364
|
+
eventKeys: ['onPointerEnter'],
|
|
365
|
+
intersection
|
|
769
366
|
});
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
];
|
|
777
|
-
fireEventsFromIntersections({
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
367
|
+
}); // unchanged interactions
|
|
368
|
+
|
|
369
|
+
staying.forEach(({
|
|
370
|
+
element,
|
|
371
|
+
intersection
|
|
372
|
+
}) => {
|
|
373
|
+
const eventKeys = ['onPointerOver', 'onPointerMove'];
|
|
374
|
+
fireEventsFromIntersections({
|
|
375
|
+
element,
|
|
376
|
+
eventKeys,
|
|
377
|
+
intersection
|
|
378
|
+
});
|
|
379
|
+
}); // exited interactions
|
|
380
|
+
|
|
381
|
+
leaving.forEach(({
|
|
382
|
+
element,
|
|
383
|
+
intersection
|
|
384
|
+
}) => {
|
|
385
|
+
const eventKeys = ['onPointerLeave', 'onPointerOut'];
|
|
386
|
+
fireEventsFromIntersections({
|
|
387
|
+
element,
|
|
388
|
+
eventKeys,
|
|
389
|
+
intersection
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
currentIntersections = [].concat(entering, staying);
|
|
393
|
+
}; // update function
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
onBeforeRender(update);
|
|
397
|
+
|
|
398
|
+
const teardown = () => offBeforeRender(update);
|
|
399
|
+
|
|
400
|
+
vue.onBeforeUnmount(teardown);
|
|
401
|
+
const clickEventKeys = ['onClick', 'onPointerDown', 'onPointerUp'];
|
|
402
|
+
vue.watch(inputActive, isDown => {
|
|
403
|
+
// run raycaster on click (necessary when `update` is not automatically called,
|
|
404
|
+
// for example in `updateSource` functions)
|
|
405
|
+
update(); // meshes with multiple intersections receive multiple callbacks by default -
|
|
406
|
+
// let's make it so they only receive one callback of each type per frame.
|
|
407
|
+
// (ie usually when you click on a mesh, you expect only one click event to fire, even
|
|
408
|
+
// if there are technically multiple intersections with that mesh)
|
|
409
|
+
|
|
410
|
+
const uuidsInteractedWithThisFrame = [];
|
|
411
|
+
currentIntersections.forEach(v => {
|
|
412
|
+
clickEventKeys.forEach(key => {
|
|
413
|
+
const id = v.element.uuid + key;
|
|
414
|
+
|
|
415
|
+
if (isDown && (key === 'onClick' || key === 'onPointerDown')) {
|
|
416
|
+
if (!uuidsInteractedWithThisFrame.includes(id)) {
|
|
417
|
+
v.element.eventListeners[key]?.forEach(cb => cb({
|
|
418
|
+
intersection: v.intersection
|
|
419
|
+
}));
|
|
420
|
+
uuidsInteractedWithThisFrame.push(id);
|
|
421
|
+
}
|
|
422
|
+
} else if (!isDown && key === 'onPointerUp') {
|
|
423
|
+
if (!uuidsInteractedWithThisFrame.includes(id)) {
|
|
424
|
+
v.element.eventListeners[key]?.forEach(cb => cb({
|
|
425
|
+
intersection: v.intersection
|
|
426
|
+
}));
|
|
427
|
+
uuidsInteractedWithThisFrame.push(id);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
}); // return arbitrary object to ensure instantiation
|
|
433
|
+
// TODO: why can't we return a <raycaster/> here?
|
|
434
|
+
|
|
435
|
+
return () => vue.createVNode(vue.resolveComponent("object3D"), null, null);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
/** fixed & fill styling for container */
|
|
441
|
+
|
|
442
|
+
const fillStyle = position => {
|
|
443
|
+
return {
|
|
444
|
+
position,
|
|
445
|
+
top: 0,
|
|
446
|
+
right: 0,
|
|
447
|
+
bottom: 0,
|
|
448
|
+
left: 0,
|
|
449
|
+
width: '100%',
|
|
450
|
+
height: '100%',
|
|
451
|
+
display: 'block'
|
|
452
|
+
};
|
|
800
453
|
};
|
|
801
454
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
455
|
+
const LunchboxWrapper = vue.defineComponent({
|
|
456
|
+
name: 'Lunchbox',
|
|
457
|
+
props: {
|
|
458
|
+
// These should match the Lunchbox.WrapperProps interface
|
|
459
|
+
background: String,
|
|
460
|
+
cameraArgs: Array,
|
|
461
|
+
cameraLook: Array,
|
|
462
|
+
cameraLookAt: Array,
|
|
463
|
+
cameraPosition: Array,
|
|
464
|
+
dpr: Number,
|
|
465
|
+
ortho: Boolean,
|
|
466
|
+
orthographic: Boolean,
|
|
467
|
+
r3f: Boolean,
|
|
468
|
+
rendererArguments: Object,
|
|
469
|
+
rendererProperties: Object,
|
|
470
|
+
sizePolicy: String,
|
|
471
|
+
shadow: [Boolean, Object],
|
|
472
|
+
transparent: Boolean,
|
|
473
|
+
zoom: Number,
|
|
474
|
+
updateSource: Object
|
|
475
|
+
},
|
|
476
|
+
|
|
477
|
+
setup(props, context) {
|
|
478
|
+
const canvas = vue.ref();
|
|
479
|
+
let dpr = props.dpr ?? -1;
|
|
480
|
+
const container = vue.ref();
|
|
481
|
+
const renderer = vue.ref();
|
|
482
|
+
const camera = vue.ref();
|
|
483
|
+
const scene = vue.ref();
|
|
484
|
+
const globals = useGlobals();
|
|
485
|
+
const updateGlobals = useUpdateGlobals();
|
|
486
|
+
const app = useApp();
|
|
487
|
+
const consolidatedCameraProperties = vue.reactive({});
|
|
488
|
+
const startCallbacks = useStartCallbacks(); // https://threejs.org/docs/index.html#manual/en/introduction/Color-management
|
|
489
|
+
|
|
490
|
+
if (props.r3f && THREE__namespace?.ColorManagement) {
|
|
491
|
+
THREE__namespace.ColorManagement.legacyMode = false;
|
|
822
492
|
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
493
|
+
|
|
494
|
+
const interactables = useLunchboxInteractables(); // MOUNT
|
|
495
|
+
// ====================
|
|
496
|
+
|
|
497
|
+
vue.onMounted(async () => {
|
|
498
|
+
// canvas needs to exist (or user needs to handle it on their own)
|
|
499
|
+
if (!canvas.value && !context.slots?.renderer?.()?.length) throw new Error('missing canvas'); // no camera provided, so let's create one
|
|
500
|
+
|
|
501
|
+
if (!context.slots?.camera?.()?.length) {
|
|
502
|
+
if (props.cameraPosition) {
|
|
503
|
+
consolidatedCameraProperties.position = props.cameraPosition;
|
|
833
504
|
}
|
|
834
|
-
|
|
835
|
-
if (
|
|
836
|
-
|
|
837
|
-
autoCreated[singleType] = createdAsNode;
|
|
838
|
-
return createdAsNode;
|
|
505
|
+
|
|
506
|
+
if (props.cameraLook || props.cameraLookAt) {
|
|
507
|
+
consolidatedCameraProperties.lookAt = props.cameraLook || props.cameraLookAt;
|
|
839
508
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
// GENERIC ENSURE FUNCTION
|
|
844
|
-
// ====================
|
|
845
|
-
// Problem:
|
|
846
|
-
// I want to make sure an object of type Xyz exists in my Lunchbox app.
|
|
847
|
-
// If it doesn't exist, I want to create it and add it to the root node.
|
|
848
|
-
//
|
|
849
|
-
// Solution:
|
|
850
|
-
// export const ensuredXyz = buildEnsured<Xyz>('Xyz', 'FALLBACK_XYZ')
|
|
851
|
-
//
|
|
852
|
-
// Now in other components, you can do both:
|
|
853
|
-
// import { ensuredXyz }
|
|
854
|
-
// ensuredXyz.value (...)
|
|
855
|
-
// and:
|
|
856
|
-
// ensuredXyz.value = ...
|
|
857
|
-
const autoCreated = vue.reactive({});
|
|
858
|
-
const overrides = vue.reactive({});
|
|
859
|
-
/**
|
|
860
|
-
* Build a computed ensured value with a getter and setter.
|
|
861
|
-
* @param pascalCaseTypes List of types this can be. Will autocreate first type if array provided.
|
|
862
|
-
* @param fallbackUuid Fallback UUID to use.
|
|
863
|
-
* @param props Props to pass to autocreated element
|
|
864
|
-
* @returns Computed getter/setter for ensured object.
|
|
865
|
-
*/
|
|
866
|
-
function buildEnsured(pascalCaseTypes, fallbackUuid, props = {}, callback = null) {
|
|
867
|
-
// make sure we've got an array
|
|
868
|
-
if (!Array.isArray(pascalCaseTypes)) {
|
|
869
|
-
pascalCaseTypes = [pascalCaseTypes];
|
|
870
|
-
}
|
|
871
|
-
// add type for autoCreated and overrides
|
|
872
|
-
for (let singleType of pascalCaseTypes) {
|
|
873
|
-
if (!autoCreated[singleType]) {
|
|
874
|
-
autoCreated[singleType] = null;
|
|
509
|
+
|
|
510
|
+
if (props.zoom !== undefined) {
|
|
511
|
+
consolidatedCameraProperties.zoom = props.zoom;
|
|
875
512
|
}
|
|
876
|
-
|
|
877
|
-
|
|
513
|
+
} // SCENE
|
|
514
|
+
// ====================
|
|
515
|
+
// set background color
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
if (scene.value?.$el?.instance && props.background) {
|
|
519
|
+
scene.value.$el.instance.background = new THREE__namespace.Color(props.background);
|
|
520
|
+
} // MISC PROPERTIES
|
|
521
|
+
// ====================
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
if (dpr === -1) {
|
|
525
|
+
dpr = window.devicePixelRatio;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
updateGlobals?.({
|
|
529
|
+
dpr
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
while (!renderer.value?.$el?.instance && // TODO: remove `as any`
|
|
533
|
+
!renderer.value?.component?.ctx.$el?.instance) {
|
|
534
|
+
await new Promise(r => requestAnimationFrame(r));
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
while (!scene.value?.$el?.instance && // TODO: remove `as any`
|
|
538
|
+
!scene.value?.component?.ctx.$el?.instance) {
|
|
539
|
+
await new Promise(r => requestAnimationFrame(r));
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const normalizedRenderer = renderer.value?.$el?.instance ?? renderer.value?.component?.ctx.$el?.instance;
|
|
543
|
+
normalizedRenderer.setPixelRatio(globals.dpr);
|
|
544
|
+
const normalizedScene = scene.value?.$el?.instance ?? scene.value?.component?.ctx.$el?.instance;
|
|
545
|
+
const normalizedCamera = camera.value?.$el?.instance ?? camera.value?.component?.ctx.$el?.instance; // TODO: update DPR on monitor switch
|
|
546
|
+
// prep canvas (sizing, observe, unmount, etc)
|
|
547
|
+
// (only run if no custom renderer)
|
|
548
|
+
|
|
549
|
+
if (!context.slots?.renderer?.()?.length) {
|
|
550
|
+
// TODO: use dispose
|
|
551
|
+
prepCanvas(container, normalizedCamera, normalizedRenderer, normalizedScene, props.sizePolicy);
|
|
552
|
+
|
|
553
|
+
if (props.r3f) {
|
|
554
|
+
normalizedRenderer.outputEncoding = THREE__namespace.sRGBEncoding;
|
|
555
|
+
normalizedRenderer.toneMapping = THREE__namespace.ACESFilmicToneMapping;
|
|
556
|
+
} // update render sugar
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
const sugar = {
|
|
560
|
+
shadow: props.shadow
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
if (sugar?.shadow) {
|
|
564
|
+
normalizedRenderer.shadowMap.enabled = true;
|
|
565
|
+
|
|
566
|
+
if (typeof sugar.shadow === 'object') {
|
|
567
|
+
normalizedRenderer.shadowMap.type = sugar.shadow.type;
|
|
568
|
+
}
|
|
878
569
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
const
|
|
941
|
-
|
|
942
|
-
|
|
570
|
+
} // START
|
|
571
|
+
// ====================
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
if (!app) {
|
|
575
|
+
throw new Error('error creating app');
|
|
576
|
+
} // save renderer, scene, camera
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
app.config.globalProperties.lunchbox.camera = normalizedCamera;
|
|
580
|
+
app.config.globalProperties.lunchbox.renderer = normalizedRenderer;
|
|
581
|
+
app.config.globalProperties.lunchbox.scene = normalizedScene;
|
|
582
|
+
|
|
583
|
+
for (let startCallback of startCallbacks ?? []) {
|
|
584
|
+
startCallback({
|
|
585
|
+
app,
|
|
586
|
+
camera: normalizedCamera,
|
|
587
|
+
renderer: normalizedRenderer,
|
|
588
|
+
scene: normalizedScene
|
|
589
|
+
});
|
|
590
|
+
} // KICK UPDATE
|
|
591
|
+
// ====================
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
update({
|
|
595
|
+
app,
|
|
596
|
+
camera: normalizedCamera,
|
|
597
|
+
renderer: normalizedRenderer,
|
|
598
|
+
scene: normalizedScene,
|
|
599
|
+
updateSource: props.updateSource
|
|
600
|
+
});
|
|
601
|
+
}); // UNMOUNT
|
|
602
|
+
// ====================
|
|
603
|
+
|
|
604
|
+
vue.onBeforeUnmount(() => {
|
|
605
|
+
cancelUpdate();
|
|
606
|
+
cancelUpdateSource();
|
|
607
|
+
}); // RENDER FUNCTION
|
|
608
|
+
// ====================
|
|
609
|
+
|
|
610
|
+
const containerFillStyle = props.sizePolicy === 'container' ? 'static' : 'absolute';
|
|
611
|
+
const canvasFillStyle = props.sizePolicy === 'container' ? 'static' : 'fixed'; // REACTIVE CUSTOM CAMERAS
|
|
612
|
+
// ====================
|
|
613
|
+
// find first camera with `type.name` property
|
|
614
|
+
// (which indicates a Lunch.Node)
|
|
615
|
+
|
|
616
|
+
const activeCamera = vue.computed(() => {
|
|
617
|
+
const output = context.slots?.camera?.().find(c => c.type?.name);
|
|
618
|
+
|
|
619
|
+
if (output) {
|
|
620
|
+
return output;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
return output;
|
|
624
|
+
}); // TODO: make custom cameras reactive
|
|
625
|
+
|
|
626
|
+
vue.watch(activeCamera, async (newVal, oldVal) => {
|
|
627
|
+
// console.log('got camera', newVal)
|
|
628
|
+
if (newVal && newVal?.props?.key !== oldVal?.props?.key) {
|
|
629
|
+
// TODO: remove cast
|
|
630
|
+
camera.value = newVal; // TODO: why isn't this updating app camera?
|
|
631
|
+
// const el = await waitFor(() => newVal.el)
|
|
632
|
+
// console.log(el)
|
|
633
|
+
// camera.value = el
|
|
634
|
+
// console.log(newVal.uuid)
|
|
635
|
+
// updateGlobals?.({ camera: el })
|
|
636
|
+
}
|
|
637
|
+
}, {
|
|
638
|
+
immediate: true
|
|
639
|
+
}); // RENDER FUNCTION
|
|
640
|
+
// ====================
|
|
641
|
+
|
|
642
|
+
return () => vue.createVNode(vue.Fragment, null, [context.slots?.renderer?.()?.length ? // TODO: remove `as any` cast
|
|
643
|
+
renderer.value = context.slots?.renderer?.()[0] : // ...otherwise, add canvas...
|
|
644
|
+
vue.createVNode(vue.Fragment, null, [vue.createVNode("div", {
|
|
645
|
+
"class": "lunchbox-container",
|
|
646
|
+
"style": fillStyle(containerFillStyle),
|
|
647
|
+
"ref": container,
|
|
648
|
+
"data-lunchbox": "true"
|
|
649
|
+
}, [vue.createVNode("canvas", {
|
|
650
|
+
"ref": canvas,
|
|
651
|
+
"class": "lunchbox-canvas",
|
|
652
|
+
"style": fillStyle(canvasFillStyle),
|
|
653
|
+
"data-lunchbox": "true"
|
|
654
|
+
}, null)]), canvas.value?.domElement && vue.createVNode(vue.resolveComponent("webGLRenderer"), vue.mergeProps(props.rendererProperties ?? {}, {
|
|
655
|
+
"ref": renderer,
|
|
656
|
+
"args": [{
|
|
657
|
+
alpha: props.transparent,
|
|
658
|
+
antialias: true,
|
|
659
|
+
canvas: canvas.value?.domElement,
|
|
660
|
+
powerPreference: !!props.r3f ? 'high-performance' : 'default',
|
|
661
|
+
...(props.rendererArguments ?? {})
|
|
662
|
+
}]
|
|
663
|
+
}), null)]), context.slots?.scene?.()?.length ? // TODO: remove `as any` cast
|
|
664
|
+
scene.value = context.slots?.scene?.()[0] : // ...otherwise, add default scene
|
|
665
|
+
// TODO: why does this need to be a separate component? <scene> throws an error
|
|
666
|
+
vue.createVNode(LunchboxScene, {
|
|
667
|
+
"ref": scene
|
|
668
|
+
}, {
|
|
669
|
+
default: () => [context.slots?.default?.()]
|
|
670
|
+
}), context.slots?.camera?.()?.length ? // TODO: remove `any` cast
|
|
671
|
+
camera.value : props.ortho || props.orthographic ? vue.createVNode(vue.resolveComponent("orthographicCamera"), vue.mergeProps({
|
|
672
|
+
"ref": camera,
|
|
673
|
+
"args": props.cameraArgs ?? []
|
|
674
|
+
}, consolidatedCameraProperties), null) : vue.createVNode(vue.resolveComponent("perspectiveCamera"), vue.mergeProps({
|
|
675
|
+
"ref": camera,
|
|
676
|
+
"args": props.cameraArgs ?? [props.r3f ? 75 : 45, 0.5625, 1, 1000]
|
|
677
|
+
}, consolidatedCameraProperties), null), interactables?.value.length && vue.createVNode(LunchboxEventHandlers, null, null)]);
|
|
678
|
+
}
|
|
679
|
+
|
|
943
680
|
});
|
|
944
|
-
// ENSURE SCENE
|
|
945
|
-
// ====================
|
|
946
|
-
const fallbackSceneUuid = 'FALLBACK_SCENE';
|
|
947
|
-
const ensuredScene = buildEnsured('Scene', fallbackSceneUuid);
|
|
948
|
-
// ENSURE AUTO-RAYCASTER
|
|
949
|
-
const autoRaycasterUuid = 'AUTO_RAYCASTER';
|
|
950
|
-
// `unknown` is intentional here - we need to typecast the node since Raycaster isn't an Object3D
|
|
951
|
-
const ensuredRaycaster = buildEnsured('Raycaster', autoRaycasterUuid, {}, (node) => setupAutoRaycaster(node));
|
|
952
681
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
682
|
+
// list of all components to register out of the box
|
|
683
|
+
const autoGeneratedComponents = [// ThreeJS basics
|
|
684
|
+
'mesh', 'instancedMesh', 'scene', 'sprite', 'object3D', // geometry
|
|
685
|
+
'instancedBufferGeometry', 'bufferGeometry', 'boxBufferGeometry', 'circleBufferGeometry', 'coneBufferGeometry', 'cylinderBufferGeometry', 'dodecahedronBufferGeometry', 'extrudeBufferGeometry', 'icosahedronBufferGeometry', 'latheBufferGeometry', 'octahedronBufferGeometry', 'parametricBufferGeometry', 'planeBufferGeometry', 'polyhedronBufferGeometry', 'ringBufferGeometry', 'shapeBufferGeometry', 'sphereBufferGeometry', 'tetrahedronBufferGeometry', 'textBufferGeometry', 'torusBufferGeometry', 'torusKnotBufferGeometry', 'tubeBufferGeometry', 'wireframeGeometry', 'parametricGeometry', 'tetrahedronGeometry', 'octahedronGeometry', 'icosahedronGeometry', 'dodecahedronGeometry', 'polyhedronGeometry', 'tubeGeometry', 'torusKnotGeometry', 'torusGeometry', // textgeometry has been moved to /examples/jsm/geometries/TextGeometry
|
|
686
|
+
// 'textGeometry',
|
|
687
|
+
'sphereGeometry', 'ringGeometry', 'planeGeometry', 'latheGeometry', 'shapeGeometry', 'extrudeGeometry', 'edgesGeometry', 'coneGeometry', 'cylinderGeometry', 'circleGeometry', 'boxGeometry', // materials
|
|
688
|
+
'material', 'shadowMaterial', 'spriteMaterial', 'rawShaderMaterial', 'shaderMaterial', 'pointsMaterial', 'meshPhysicalMaterial', 'meshStandardMaterial', 'meshPhongMaterial', 'meshToonMaterial', 'meshNormalMaterial', 'meshLambertMaterial', 'meshDepthMaterial', 'meshDistanceMaterial', 'meshBasicMaterial', 'meshMatcapMaterial', 'lineDashedMaterial', 'lineBasicMaterial', // lights
|
|
689
|
+
'light', 'spotLightShadow', 'spotLight', 'pointLight', 'rectAreaLight', 'hemisphereLight', 'directionalLightShadow', 'directionalLight', 'ambientLight', 'lightShadow', 'ambientLightProbe', 'hemisphereLightProbe', 'lightProbe', // textures
|
|
690
|
+
'texture', 'videoTexture', 'dataTexture', 'dataTexture3D', 'compressedTexture', 'cubeTexture', 'canvasTexture', 'depthTexture', // Texture loaders
|
|
691
|
+
'textureLoader', // misc
|
|
692
|
+
'group', 'catmullRomCurve3', 'points', 'raycaster', // helpers
|
|
693
|
+
'cameraHelper', // cameras
|
|
694
|
+
'camera', 'perspectiveCamera', 'orthographicCamera', 'cubeCamera', 'arrayCamera', // renderers
|
|
695
|
+
'webGLRenderer'
|
|
696
|
+
/*
|
|
697
|
+
// List copied from r3f:
|
|
698
|
+
// https://github.com/pmndrs/react-three-fiber/blob/master/packages/fiber/src/three-types.ts
|
|
699
|
+
// NOT IMPLEMENTED (can be added via Extend - docs.lunchboxjs.com/components/extend/):
|
|
700
|
+
audioListener: AudioListenerProps
|
|
701
|
+
positionalAudio: PositionalAudioProps
|
|
702
|
+
lOD: LODProps
|
|
703
|
+
skinnedMesh: SkinnedMeshProps
|
|
704
|
+
skeleton: SkeletonProps
|
|
705
|
+
bone: BoneProps
|
|
706
|
+
lineSegments: LineSegmentsProps
|
|
707
|
+
lineLoop: LineLoopProps
|
|
708
|
+
// see `audio`
|
|
709
|
+
// line: LineProps
|
|
710
|
+
immediateRenderObject: ImmediateRenderObjectProps
|
|
711
|
+
// primitive
|
|
712
|
+
primitive: PrimitiveProps
|
|
713
|
+
// helpers
|
|
714
|
+
spotLightHelper: SpotLightHelperProps
|
|
715
|
+
skeletonHelper: SkeletonHelperProps
|
|
716
|
+
pointLightHelper: PointLightHelperProps
|
|
717
|
+
hemisphereLightHelper: HemisphereLightHelperProps
|
|
718
|
+
gridHelper: GridHelperProps
|
|
719
|
+
polarGridHelper: PolarGridHelperProps
|
|
720
|
+
directionalLightHelper: DirectionalLightHelperProps
|
|
721
|
+
boxHelper: BoxHelperProps
|
|
722
|
+
box3Helper: Box3HelperProps
|
|
723
|
+
planeHelper: PlaneHelperProps
|
|
724
|
+
arrowHelper: ArrowHelperProps
|
|
725
|
+
axesHelper: AxesHelperProps
|
|
726
|
+
// misc
|
|
727
|
+
vector2: Vector2Props
|
|
728
|
+
vector3: Vector3Props
|
|
729
|
+
vector4: Vector4Props
|
|
730
|
+
euler: EulerProps
|
|
731
|
+
matrix3: Matrix3Props
|
|
732
|
+
matrix4: Matrix4Props
|
|
733
|
+
quaternion: QuaternionProps
|
|
734
|
+
bufferAttribute: BufferAttributeProps
|
|
735
|
+
instancedBufferAttribute: InstancedBufferAttributeProps
|
|
736
|
+
color: ColorProps
|
|
737
|
+
fog: FogProps
|
|
738
|
+
fogExp2: FogExp2Props
|
|
739
|
+
shape: ShapeProps
|
|
740
|
+
*/
|
|
741
|
+
];
|
|
742
|
+
|
|
743
|
+
const catalogue = {}; // component creation utility
|
|
744
|
+
|
|
745
|
+
const createComponent$1 = tag => vue.defineComponent({
|
|
746
|
+
inheritAttrs: false,
|
|
747
|
+
name: tag,
|
|
748
|
+
|
|
749
|
+
setup(props, context) {
|
|
750
|
+
return () => {
|
|
751
|
+
return vue.h(tag, context.attrs, context.slots?.default?.() || []);
|
|
994
752
|
};
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
}
|
|
1016
|
-
// TODO: these manual overrides are a bit brittle - replace?
|
|
1017
|
-
if (node.type?.toLowerCase() === 'scene') {
|
|
1018
|
-
// manually set scene override
|
|
1019
|
-
ensuredScene.value = node;
|
|
1020
|
-
}
|
|
1021
|
-
else if (node.type?.toLowerCase().endsWith('camera')) {
|
|
1022
|
-
ensuredCamera.value = node;
|
|
1023
|
-
}
|
|
1024
|
-
return node;
|
|
1025
|
-
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
}); // turn components into registered map
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
const processed = autoGeneratedComponents.map(createComponent$1).reduce((acc, curr) => {
|
|
759
|
+
acc[curr.name] = curr;
|
|
760
|
+
return acc;
|
|
761
|
+
}, {});
|
|
762
|
+
const components = { ...processed,
|
|
763
|
+
Lunchbox: LunchboxWrapper
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
const createComponent = tag => vue.defineComponent({
|
|
767
|
+
inheritAttrs: false,
|
|
768
|
+
name: tag,
|
|
769
|
+
|
|
770
|
+
render() {
|
|
771
|
+
return vue.h(tag, this.$attrs, this.$slots?.default?.() || []);
|
|
772
|
+
}
|
|
1026
773
|
|
|
1027
|
-
const createComponent = (tag) => vue.defineComponent({
|
|
1028
|
-
inheritAttrs: false,
|
|
1029
|
-
name: tag,
|
|
1030
|
-
render() {
|
|
1031
|
-
return vue.h(tag, this.$attrs, this.$slots?.default?.() || []);
|
|
1032
|
-
},
|
|
1033
774
|
});
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
775
|
+
|
|
776
|
+
const extend = ({
|
|
777
|
+
app,
|
|
778
|
+
...targets
|
|
779
|
+
}) => {
|
|
780
|
+
Object.keys(targets).forEach(key => {
|
|
781
|
+
app.component(key, createComponent(key));
|
|
782
|
+
catalogue[key] = targets[key];
|
|
783
|
+
});
|
|
1039
784
|
};
|
|
1040
785
|
|
|
1041
786
|
/** Process props into either themselves or the $attached value */
|
|
1042
|
-
function processProp({
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
787
|
+
function processProp({
|
|
788
|
+
node,
|
|
789
|
+
prop
|
|
790
|
+
}) {
|
|
791
|
+
// return $attachedArray value if needed
|
|
792
|
+
if (typeof prop === 'string' && prop.startsWith('$attachedArray')) {
|
|
793
|
+
return node.attachedArray[prop.replace('$attachedArray.', '')];
|
|
794
|
+
} // return $attached value if needed
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
if (typeof prop === 'string' && prop.startsWith('$attached')) {
|
|
798
|
+
return node.attached[prop.replace('$attached.', '')];
|
|
799
|
+
} // otherwise, return plain value
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
return prop;
|
|
1053
803
|
}
|
|
1054
|
-
function processPropAsArray({
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
804
|
+
function processPropAsArray({
|
|
805
|
+
node,
|
|
806
|
+
prop
|
|
807
|
+
}) {
|
|
808
|
+
const isAttachedArray = typeof prop === 'string' && prop.startsWith('$attachedArray');
|
|
809
|
+
const output = processProp({
|
|
810
|
+
node,
|
|
811
|
+
prop
|
|
812
|
+
});
|
|
813
|
+
return Array.isArray(output) && isAttachedArray ? output : [output];
|
|
1060
814
|
}
|
|
1061
815
|
|
|
1062
816
|
function instantiateThreeObject(node) {
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
let processedArgs = [];
|
|
1079
|
-
argsWrappedInArrays.forEach((arr) => {
|
|
1080
|
-
processedArgs = processedArgs.concat(arr);
|
|
817
|
+
if (!node.type) return null; // what class will we be instantiating?
|
|
818
|
+
|
|
819
|
+
const uppercaseType = node.type[0].toUpperCase() + node.type.slice(1);
|
|
820
|
+
const translatedType = uppercaseType.replace(/Lunchbox$/, '');
|
|
821
|
+
const targetClass = catalogue[node.type] || THREE__namespace[uppercaseType] || catalogue[translatedType] || THREE__namespace[translatedType];
|
|
822
|
+
if (!targetClass) throw `${uppercaseType} is not part of the THREE namespace! Did you forget to extend? import {extend} from 'lunchbox'; extend({app, YourComponent, ...})`; // what args have we been provided?
|
|
823
|
+
|
|
824
|
+
const args = node.props.args ?? []; // replace $attached values with their instances
|
|
825
|
+
// we need to guarantee everything comes back as an array so we can spread $attachedArrays,
|
|
826
|
+
// so we'll use processPropAsArray
|
|
827
|
+
|
|
828
|
+
const argsWrappedInArrays = args.map(arg => {
|
|
829
|
+
return processPropAsArray({
|
|
830
|
+
node,
|
|
831
|
+
prop: arg
|
|
1081
832
|
});
|
|
1082
|
-
|
|
1083
|
-
|
|
833
|
+
});
|
|
834
|
+
let processedArgs = [];
|
|
835
|
+
argsWrappedInArrays.forEach(arr => {
|
|
836
|
+
processedArgs = processedArgs.concat(arr);
|
|
837
|
+
});
|
|
838
|
+
const instance = new targetClass(...processedArgs);
|
|
839
|
+
return instance;
|
|
1084
840
|
}
|
|
1085
841
|
|
|
1086
842
|
// Unique ID creation requires a high quality random # generator. In the browser we therefore
|
|
@@ -1121,9 +877,9 @@
|
|
|
1121
877
|
}
|
|
1122
878
|
|
|
1123
879
|
function stringify(arr) {
|
|
1124
|
-
var offset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
|
|
1125
|
-
// Note: Be careful editing this code! It's been tuned for performance
|
|
880
|
+
var offset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; // Note: Be careful editing this code! It's been tuned for performance
|
|
1126
881
|
// and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434
|
|
882
|
+
|
|
1127
883
|
var uuid = (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + '-' + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + '-' + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + '-' + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + '-' + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); // Consistency check for valid UUID. If this throws, it's likely due to one
|
|
1128
884
|
// of the following:
|
|
1129
885
|
// - One or more input array values don't map to a hex octet (leading to
|
|
@@ -1157,775 +913,1088 @@
|
|
|
1157
913
|
return stringify(rnds);
|
|
1158
914
|
}
|
|
1159
915
|
|
|
1160
|
-
// MiniDom recreates DOM node properties and methods.
|
|
1161
916
|
// Since Vue 3 is a DOM-first framework, many of its nodeOps depend on
|
|
1162
917
|
// properties and methods the DOM naturally contains. MiniDom recreates
|
|
1163
918
|
// those properties (as well as a few from the tree-model npm package)
|
|
1164
919
|
// to make a DOM-like but otherwise agnostic hierarchy structure.
|
|
1165
|
-
|
|
920
|
+
|
|
921
|
+
exports.MiniDom = void 0;
|
|
922
|
+
|
|
1166
923
|
(function (MiniDom) {
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
allNodes.push(this);
|
|
1173
|
-
}
|
|
1174
|
-
uuid;
|
|
1175
|
-
// DOM FEATURES
|
|
1176
|
-
// ====================
|
|
1177
|
-
parentNode;
|
|
1178
|
-
get nextSibling() {
|
|
1179
|
-
if (!this.parentNode)
|
|
1180
|
-
return null;
|
|
1181
|
-
const idx = this.parentNode.children.findIndex((n) => n.uuid === this.uuid);
|
|
1182
|
-
// return next sibling if we're present and not the last child of the parent
|
|
1183
|
-
if (idx !== -1 && idx < this.parentNode.children.length - 1) {
|
|
1184
|
-
return this.parentNode.children[idx + 1];
|
|
1185
|
-
}
|
|
1186
|
-
return null;
|
|
1187
|
-
}
|
|
1188
|
-
insertBefore(child, anchor) {
|
|
1189
|
-
child.removeAsChildFromAnyParents();
|
|
1190
|
-
child.parentNode = this;
|
|
1191
|
-
const anchorIdx = this.children.findIndex((n) => n.uuid === anchor?.uuid);
|
|
1192
|
-
if (anchorIdx !== -1) {
|
|
1193
|
-
this.children.splice(anchorIdx, 0, child);
|
|
1194
|
-
}
|
|
1195
|
-
else {
|
|
1196
|
-
this.children.push(child);
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
removeChild(child) {
|
|
1200
|
-
const idx = this.children.findIndex((n) => n?.uuid === child?.uuid);
|
|
1201
|
-
if (idx !== -1) {
|
|
1202
|
-
this.children.splice(idx, 1);
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
// TREE FEATURES
|
|
1206
|
-
// ====================
|
|
1207
|
-
children = [];
|
|
1208
|
-
addChild(child) {
|
|
1209
|
-
if (child) {
|
|
1210
|
-
// remove child from any other parents
|
|
1211
|
-
child.removeAsChildFromAnyParents();
|
|
1212
|
-
// add to this node
|
|
1213
|
-
child.parentNode = this;
|
|
1214
|
-
this.insertBefore(child, null);
|
|
1215
|
-
}
|
|
1216
|
-
return this;
|
|
1217
|
-
}
|
|
1218
|
-
/** Get the array of Nodes representing the path from the root to this Node (inclusive). */
|
|
1219
|
-
getPath() {
|
|
1220
|
-
const output = [];
|
|
1221
|
-
let current = this;
|
|
1222
|
-
while (current) {
|
|
1223
|
-
output.unshift(current);
|
|
1224
|
-
current = current.parentNode;
|
|
1225
|
-
}
|
|
1226
|
-
return output;
|
|
1227
|
-
}
|
|
1228
|
-
/** Drop this node. Removes parent's knowledge of this node
|
|
1229
|
-
* and resets this node's internal parent. */
|
|
1230
|
-
drop() {
|
|
1231
|
-
// remove parent
|
|
1232
|
-
this.parentNode = null;
|
|
1233
|
-
// remove as child
|
|
1234
|
-
this.removeAsChildFromAnyParents();
|
|
1235
|
-
}
|
|
1236
|
-
/** Walk over the entire subtree. Return falsey value in callback to end early. */
|
|
1237
|
-
// TODO: depth-first vs breadth-first
|
|
1238
|
-
walk(callback) {
|
|
1239
|
-
const queue = [this, ...this.children];
|
|
1240
|
-
const traversed = [];
|
|
1241
|
-
let canContinue = true;
|
|
1242
|
-
while (queue.length && canContinue) {
|
|
1243
|
-
const current = queue.shift();
|
|
1244
|
-
if (current) {
|
|
1245
|
-
if (traversed.includes(current))
|
|
1246
|
-
continue;
|
|
1247
|
-
traversed.push(current);
|
|
1248
|
-
queue.push(...current.children.filter((child) => !traversed.includes(child)));
|
|
1249
|
-
canContinue = callback(current);
|
|
1250
|
-
}
|
|
1251
|
-
else {
|
|
1252
|
-
canContinue = false;
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
// INTERNAL FEATURES
|
|
1257
|
-
// ====================
|
|
1258
|
-
minidomType;
|
|
1259
|
-
removeAsChildFromAnyParents() {
|
|
1260
|
-
allNodes.forEach((node) => node.removeChild(this));
|
|
1261
|
-
}
|
|
924
|
+
class BaseNode {
|
|
925
|
+
constructor(options = {}, parent) {
|
|
926
|
+
this.parentNode = options?.parentNode ?? parent ?? null;
|
|
927
|
+
this.minidomType = 'MinidomBaseNode';
|
|
928
|
+
this.uuid = options?.uuid ?? v4(); // allNodes.push(this)
|
|
1262
929
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
constructor(options = {}, parent) {
|
|
1266
|
-
super(options, parent);
|
|
1267
|
-
this.minidomType = 'RendererNode';
|
|
1268
|
-
this.eventListeners = {};
|
|
1269
|
-
this.eventListenerRemoveFunctions = {};
|
|
1270
|
-
this.name = options.name ?? '';
|
|
1271
|
-
this.metaType = options.metaType ?? 'standardMeta';
|
|
1272
|
-
this.props = options.props ?? [];
|
|
1273
|
-
this.type = options.type ?? '';
|
|
1274
|
-
}
|
|
1275
|
-
eventListeners;
|
|
1276
|
-
eventListenerRemoveFunctions;
|
|
1277
|
-
name;
|
|
1278
|
-
metaType;
|
|
1279
|
-
props;
|
|
1280
|
-
type;
|
|
1281
|
-
drop() {
|
|
1282
|
-
super.drop();
|
|
1283
|
-
// handle remove functions
|
|
1284
|
-
Object.keys(this.eventListenerRemoveFunctions).forEach((key) => {
|
|
1285
|
-
this.eventListenerRemoveFunctions[key].forEach((func) => func());
|
|
1286
|
-
});
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
MiniDom.RendererBaseNode = RendererBaseNode;
|
|
930
|
+
|
|
931
|
+
uuid; // DOM FEATURES
|
|
1290
932
|
// ====================
|
|
1291
|
-
|
|
933
|
+
|
|
934
|
+
parentNode;
|
|
935
|
+
|
|
936
|
+
get nextSibling() {
|
|
937
|
+
if (!this.parentNode) return null;
|
|
938
|
+
const idx = this.parentNode.children.findIndex(n => n.uuid === this.uuid); // return next sibling if we're present and not the last child of the parent
|
|
939
|
+
|
|
940
|
+
if (idx !== -1 && idx < this.parentNode.children.length - 1) {
|
|
941
|
+
return this.parentNode.children[idx + 1];
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
return null;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
insertBefore(child, anchor) {
|
|
948
|
+
child.removeAsChildFromAnyParents();
|
|
949
|
+
child.parentNode = this;
|
|
950
|
+
const anchorIdx = this.children.findIndex(n => n.uuid === anchor?.uuid);
|
|
951
|
+
|
|
952
|
+
if (anchorIdx !== -1) {
|
|
953
|
+
this.children.splice(anchorIdx, 0, child);
|
|
954
|
+
} else {
|
|
955
|
+
this.children.push(child);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
removeChild(child) {
|
|
960
|
+
const idx = this.children.findIndex(n => n?.uuid === child?.uuid);
|
|
961
|
+
|
|
962
|
+
if (idx !== -1) {
|
|
963
|
+
this.children.splice(idx, 1);
|
|
964
|
+
}
|
|
965
|
+
} // TREE FEATURES
|
|
1292
966
|
// ====================
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
children = [];
|
|
970
|
+
|
|
971
|
+
addChild(child) {
|
|
972
|
+
if (child) {
|
|
973
|
+
// remove child from any other parents
|
|
974
|
+
child.removeAsChildFromAnyParents(); // add to this node
|
|
975
|
+
|
|
976
|
+
child.parentNode = this;
|
|
977
|
+
this.insertBefore(child, null);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
return this;
|
|
1301
981
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
982
|
+
/** Get the array of Nodes representing the path from the root to this Node (inclusive). */
|
|
983
|
+
|
|
984
|
+
|
|
985
|
+
getPath() {
|
|
986
|
+
const output = [];
|
|
987
|
+
let current = this;
|
|
988
|
+
|
|
989
|
+
while (current) {
|
|
990
|
+
output.unshift(current);
|
|
991
|
+
current = current.parentNode;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
return output;
|
|
1309
995
|
}
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
996
|
+
/** Drop this node. Removes parent's knowledge of this node
|
|
997
|
+
* and resets this node's internal parent. */
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
drop() {
|
|
1001
|
+
// remove as child
|
|
1002
|
+
this.removeAsChildFromAnyParents(); // remove parent
|
|
1003
|
+
|
|
1004
|
+
this.parentNode = null;
|
|
1318
1005
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1006
|
+
/** Walk over the entire subtree. Return falsey value in callback to end early. */
|
|
1007
|
+
// TODO: depth-first vs breadth-first
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
walk(callback) {
|
|
1011
|
+
const queue = [this, ...this.children];
|
|
1012
|
+
const traversed = [];
|
|
1013
|
+
let canContinue = true;
|
|
1014
|
+
|
|
1015
|
+
while (queue.length && canContinue) {
|
|
1016
|
+
const current = queue.shift();
|
|
1017
|
+
|
|
1018
|
+
if (current) {
|
|
1019
|
+
if (traversed.includes(current)) continue;
|
|
1020
|
+
traversed.push(current);
|
|
1021
|
+
queue.push(...current.children.filter(child => !traversed.includes(child)));
|
|
1022
|
+
canContinue = callback(current);
|
|
1023
|
+
} else {
|
|
1024
|
+
canContinue = false;
|
|
1324
1025
|
}
|
|
1325
|
-
|
|
1026
|
+
}
|
|
1027
|
+
} // INTERNAL FEATURES
|
|
1028
|
+
// ====================
|
|
1029
|
+
|
|
1030
|
+
|
|
1031
|
+
minidomType;
|
|
1032
|
+
|
|
1033
|
+
removeAsChildFromAnyParents() {
|
|
1034
|
+
this.parentNode?.removeChild(this);
|
|
1326
1035
|
}
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1036
|
+
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
MiniDom.BaseNode = BaseNode;
|
|
1040
|
+
|
|
1041
|
+
class RendererBaseNode extends MiniDom.BaseNode {
|
|
1042
|
+
constructor(options = {}, parent) {
|
|
1043
|
+
super(options, parent);
|
|
1044
|
+
this.minidomType = 'RendererNode';
|
|
1045
|
+
this.eventListeners = {};
|
|
1046
|
+
this.eventListenerRemoveFunctions = {};
|
|
1047
|
+
this.name = options.name ?? '';
|
|
1048
|
+
this.metaType = options.metaType ?? 'standardMeta';
|
|
1049
|
+
this.props = options.props ?? [];
|
|
1050
|
+
this.type = options.type ?? '';
|
|
1338
1051
|
}
|
|
1339
|
-
MiniDom.RendererStandardNode = RendererStandardNode;
|
|
1340
|
-
})(MiniDom || (MiniDom = {}));
|
|
1341
|
-
function isMinidomNode(item) {
|
|
1342
|
-
return item?.minidomType === 'RendererNode';
|
|
1343
|
-
}
|
|
1344
|
-
const rootNode = new MiniDom.RendererRootNode();
|
|
1345
|
-
rootNode.minidomType = 'RootNode';
|
|
1346
1052
|
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1053
|
+
eventListeners;
|
|
1054
|
+
eventListenerRemoveFunctions;
|
|
1055
|
+
name;
|
|
1056
|
+
metaType;
|
|
1057
|
+
props;
|
|
1058
|
+
type;
|
|
1059
|
+
|
|
1060
|
+
drop() {
|
|
1061
|
+
super.drop(); // handle remove functions
|
|
1062
|
+
|
|
1063
|
+
Object.keys(this.eventListenerRemoveFunctions).forEach(key => {
|
|
1064
|
+
this.eventListenerRemoveFunctions[key].forEach(func => func());
|
|
1065
|
+
});
|
|
1351
1066
|
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1067
|
+
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
MiniDom.RendererBaseNode = RendererBaseNode; // ====================
|
|
1071
|
+
// SPECIFIC RENDERER NODES BELOW
|
|
1072
|
+
// ====================
|
|
1073
|
+
|
|
1074
|
+
class RendererRootNode extends MiniDom.RendererBaseNode {
|
|
1075
|
+
constructor(options = {}, parent) {
|
|
1076
|
+
super(options, parent);
|
|
1077
|
+
this.domElement = options.domElement ?? document.createElement('div');
|
|
1354
1078
|
}
|
|
1355
|
-
};
|
|
1356
1079
|
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
camera: ensuredCamera.value?.instance,
|
|
1368
|
-
updateSource: opts.updateSource,
|
|
1369
|
-
}));
|
|
1370
|
-
};
|
|
1371
|
-
const update = (opts) => {
|
|
1372
|
-
if (opts.updateSource) {
|
|
1373
|
-
if (!watchStopHandle) {
|
|
1374
|
-
// request next frame only when state changes
|
|
1375
|
-
watchStopHandle = vue.watch(opts.updateSource, () => {
|
|
1376
|
-
requestUpdate(opts);
|
|
1377
|
-
}, {
|
|
1378
|
-
deep: true,
|
|
1379
|
-
});
|
|
1380
|
-
}
|
|
1080
|
+
domElement;
|
|
1081
|
+
isLunchboxRootNode = true;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
MiniDom.RendererRootNode = RendererRootNode;
|
|
1085
|
+
|
|
1086
|
+
class RendererCommentNode extends MiniDom.RendererBaseNode {
|
|
1087
|
+
constructor(options = {}, parent) {
|
|
1088
|
+
super(options, parent);
|
|
1089
|
+
this.text = options.text ?? '';
|
|
1381
1090
|
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1091
|
+
|
|
1092
|
+
text;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
MiniDom.RendererCommentNode = RendererCommentNode;
|
|
1096
|
+
|
|
1097
|
+
class RendererDomNode extends MiniDom.RendererBaseNode {
|
|
1098
|
+
constructor(options = {}, parent) {
|
|
1099
|
+
super(options, parent);
|
|
1100
|
+
this.domElement = options.domElement ?? document.createElement('div');
|
|
1385
1101
|
}
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
})
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
if (app.customRender) {
|
|
1397
|
-
app.customRender(opts);
|
|
1398
|
-
}
|
|
1399
|
-
else {
|
|
1400
|
-
renderer.render(vue.toRaw(scene), vue.toRaw(camera));
|
|
1401
|
-
}
|
|
1102
|
+
|
|
1103
|
+
domElement;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
MiniDom.RendererDomNode = RendererDomNode;
|
|
1107
|
+
|
|
1108
|
+
class RendererTextNode extends MiniDom.RendererBaseNode {
|
|
1109
|
+
constructor(options = {}, parent) {
|
|
1110
|
+
super(options, parent);
|
|
1111
|
+
this.text = options.text ?? '';
|
|
1402
1112
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1113
|
+
|
|
1114
|
+
text;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
MiniDom.RendererTextNode = RendererTextNode;
|
|
1118
|
+
|
|
1119
|
+
class RendererStandardNode extends MiniDom.RendererBaseNode {
|
|
1120
|
+
constructor(options = {}, parent) {
|
|
1121
|
+
super(options, parent);
|
|
1122
|
+
this.attached = options.attached ?? [];
|
|
1123
|
+
this.attachedArray = options.attachedArray ?? {};
|
|
1124
|
+
this.instance = options.instance ?? null;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
attached;
|
|
1128
|
+
attachedArray;
|
|
1129
|
+
instance;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
MiniDom.RendererStandardNode = RendererStandardNode;
|
|
1133
|
+
})(exports.MiniDom || (exports.MiniDom = {}));
|
|
1134
|
+
|
|
1135
|
+
function isMinidomNode(item) {
|
|
1136
|
+
return item?.minidomType === 'RendererNode';
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
const globalsInjectionKey = Symbol();
|
|
1140
|
+
const updateGlobalsInjectionKey = Symbol();
|
|
1141
|
+
const setCustomRenderKey = Symbol();
|
|
1142
|
+
const clearCustomRenderKey = Symbol();
|
|
1143
|
+
const beforeRenderKey = Symbol();
|
|
1144
|
+
const onBeforeRenderKey = Symbol();
|
|
1145
|
+
const offBeforeRenderKey = Symbol();
|
|
1146
|
+
const afterRenderKey = Symbol();
|
|
1147
|
+
const onAfterRenderKey = Symbol();
|
|
1148
|
+
const offAfterRenderKey = Symbol();
|
|
1149
|
+
const frameIdKey = Symbol();
|
|
1150
|
+
const watchStopHandleKey = Symbol();
|
|
1151
|
+
const appRootNodeKey = Symbol();
|
|
1152
|
+
const appKey = Symbol();
|
|
1153
|
+
const appRenderersKey = Symbol();
|
|
1154
|
+
const appSceneKey = Symbol();
|
|
1155
|
+
const appCameraKey = Symbol();
|
|
1156
|
+
const lunchboxInteractables = Symbol();
|
|
1157
|
+
const startCallbackKey = Symbol();
|
|
1158
|
+
|
|
1159
|
+
const requestUpdate = opts => {
|
|
1160
|
+
if (typeof opts.app.config.globalProperties.lunchbox.frameId === 'number') {
|
|
1161
|
+
cancelAnimationFrame(opts.app.config.globalProperties.lunchbox.frameId);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
opts.app.config.globalProperties.lunchbox.frameId = requestAnimationFrame(() => update({
|
|
1165
|
+
app: opts.app,
|
|
1166
|
+
renderer: opts.renderer,
|
|
1167
|
+
scene: opts.scene,
|
|
1168
|
+
camera: opts.camera,
|
|
1169
|
+
updateSource: opts.updateSource
|
|
1170
|
+
}));
|
|
1171
|
+
};
|
|
1172
|
+
|
|
1173
|
+
const update = opts => {
|
|
1174
|
+
if (opts.updateSource) {
|
|
1175
|
+
if (!opts.app.config.globalProperties.lunchbox.watchStopHandle) {
|
|
1176
|
+
// request next frame only when state changes
|
|
1177
|
+
opts.app.config.globalProperties.lunchbox.watchStopHandle = vue.watch(opts.updateSource, () => {
|
|
1178
|
+
requestUpdate(opts);
|
|
1179
|
+
}, {
|
|
1180
|
+
deep: true
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
} else {
|
|
1184
|
+
// request next frame on a continuous loop
|
|
1185
|
+
requestUpdate(opts);
|
|
1186
|
+
} // prep options
|
|
1187
|
+
|
|
1188
|
+
|
|
1189
|
+
const {
|
|
1190
|
+
app,
|
|
1191
|
+
renderer,
|
|
1192
|
+
scene
|
|
1193
|
+
} = opts; // BEFORE RENDER
|
|
1194
|
+
|
|
1195
|
+
app.config.globalProperties.lunchbox.beforeRender.forEach(cb => {
|
|
1196
|
+
cb?.(opts);
|
|
1197
|
+
}); // RENDER
|
|
1198
|
+
|
|
1199
|
+
if (renderer && scene && opts.app.config.globalProperties.lunchbox.camera) {
|
|
1200
|
+
if (app.customRender) {
|
|
1201
|
+
app.customRender(opts);
|
|
1202
|
+
} else {
|
|
1203
|
+
renderer.render(vue.toRaw(scene), opts.app.config.globalProperties.lunchbox.camera // toRaw(camera)
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
} // AFTER RENDER
|
|
1207
|
+
|
|
1208
|
+
|
|
1209
|
+
app.config.globalProperties.lunchbox.afterRender.forEach(cb => {
|
|
1210
|
+
cb?.(opts);
|
|
1211
|
+
});
|
|
1212
|
+
}; // before render
|
|
1213
|
+
// ====================
|
|
1214
|
+
|
|
1215
|
+
/** Obtain callback methods for `onBeforeRender` and `offBeforeRender`. Usually used internally by Lunchbox. */
|
|
1216
|
+
|
|
1217
|
+
const useBeforeRender = () => {
|
|
1218
|
+
return {
|
|
1219
|
+
onBeforeRender: vue.inject(onBeforeRenderKey),
|
|
1220
|
+
offBeforeRender: vue.inject(offBeforeRenderKey)
|
|
1221
|
+
};
|
|
1409
1222
|
};
|
|
1223
|
+
/** Run a function before every render.
|
|
1224
|
+
*
|
|
1225
|
+
* Note that if `updateSource` is set in the Lunchbox wrapper component, this will **only** run
|
|
1226
|
+
* before a render triggered by that `updateSource`. Normally, the function should run every frame.
|
|
1227
|
+
*/
|
|
1228
|
+
|
|
1410
1229
|
const onBeforeRender = (cb, index = Infinity) => {
|
|
1411
|
-
|
|
1412
|
-
beforeRender.push(cb);
|
|
1413
|
-
}
|
|
1414
|
-
else {
|
|
1415
|
-
beforeRender.splice(index, 0, cb);
|
|
1416
|
-
}
|
|
1230
|
+
useBeforeRender().onBeforeRender?.(cb, index);
|
|
1417
1231
|
};
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1232
|
+
/** Remove a function from the `beforeRender` callback list. Useful for tearing down functions added
|
|
1233
|
+
* by `onBeforeRender`.
|
|
1234
|
+
*/
|
|
1235
|
+
|
|
1236
|
+
const offBeforeRender = cb => {
|
|
1237
|
+
useBeforeRender().offBeforeRender?.(cb);
|
|
1238
|
+
}; // after render
|
|
1239
|
+
// ====================
|
|
1240
|
+
|
|
1241
|
+
/** Obtain callback methods for `onAfterRender` and `offAfterRender`. Usually used internally by Lunchbox. */
|
|
1242
|
+
|
|
1243
|
+
const useAfterRender = () => {
|
|
1244
|
+
return {
|
|
1245
|
+
onAfterRender: vue.inject(onBeforeRenderKey),
|
|
1246
|
+
offAfterRender: vue.inject(offBeforeRenderKey)
|
|
1247
|
+
};
|
|
1426
1248
|
};
|
|
1249
|
+
/** Run a function after every render.
|
|
1250
|
+
*
|
|
1251
|
+
* Note that if `updateSource` is set in the Lunchbox wrapper component, this will **only** run
|
|
1252
|
+
* after a render triggered by that `updateSource`. Normally, the function should run every frame.
|
|
1253
|
+
*/
|
|
1254
|
+
|
|
1427
1255
|
const onAfterRender = (cb, index = Infinity) => {
|
|
1428
|
-
|
|
1429
|
-
afterRender.push(cb);
|
|
1430
|
-
}
|
|
1431
|
-
else {
|
|
1432
|
-
afterRender.splice(index, 0, cb);
|
|
1433
|
-
}
|
|
1256
|
+
useBeforeRender().onBeforeRender?.(cb, index);
|
|
1434
1257
|
};
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
afterRender.splice(idx, 1);
|
|
1442
|
-
}
|
|
1258
|
+
/** Remove a function from the `afterRender` callback list. Useful for tearing down functions added
|
|
1259
|
+
* by `onAfterRender`.
|
|
1260
|
+
*/
|
|
1261
|
+
|
|
1262
|
+
const offAfterRender = cb => {
|
|
1263
|
+
useBeforeRender().offBeforeRender?.(cb);
|
|
1443
1264
|
};
|
|
1265
|
+
/** Obtain a function used to cancel the current update frame. Use `cancelUpdate` if you wish
|
|
1266
|
+
* to immediately invoke the cancellation function. Usually used internally by Lunchbox.
|
|
1267
|
+
*/
|
|
1268
|
+
|
|
1269
|
+
const useCancelUpdate = () => {
|
|
1270
|
+
const frameId = vue.inject(frameIdKey);
|
|
1271
|
+
return () => {
|
|
1272
|
+
if (frameId !== undefined) cancelAnimationFrame(frameId);
|
|
1273
|
+
};
|
|
1274
|
+
};
|
|
1275
|
+
/** Cancel the current update frame. Usually used internally by Lunchbox. */
|
|
1276
|
+
|
|
1444
1277
|
const cancelUpdate = () => {
|
|
1445
|
-
|
|
1446
|
-
cancelAnimationFrame(frameID);
|
|
1278
|
+
useCancelUpdate()?.();
|
|
1447
1279
|
};
|
|
1280
|
+
/** Obtain a function used to cancel an update source. Use `cancelUpdateSource` if you wish to
|
|
1281
|
+
* immediately invoke the cancellation function. Usually used internally by Lunchbox.
|
|
1282
|
+
*/
|
|
1283
|
+
|
|
1284
|
+
const useCancelUpdateSource = () => {
|
|
1285
|
+
const cancel = vue.inject(watchStopHandleKey);
|
|
1286
|
+
return () => cancel?.();
|
|
1287
|
+
};
|
|
1288
|
+
/** Cancel an update source. Usually used internally by Lunchbox. */
|
|
1289
|
+
|
|
1448
1290
|
const cancelUpdateSource = () => {
|
|
1449
|
-
|
|
1450
|
-
watchStopHandle();
|
|
1291
|
+
useCancelUpdateSource()?.();
|
|
1451
1292
|
};
|
|
1452
1293
|
|
|
1453
1294
|
/** Update a single prop on a given node. */
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
if
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1295
|
+
|
|
1296
|
+
function updateObjectProp({
|
|
1297
|
+
node,
|
|
1298
|
+
key,
|
|
1299
|
+
interactables,
|
|
1300
|
+
value
|
|
1301
|
+
}) {
|
|
1302
|
+
// handle and return early if prop is an event
|
|
1303
|
+
// (event list from react-three-fiber)
|
|
1304
|
+
if (isEventKey(key)) {
|
|
1305
|
+
return addEventListener({
|
|
1306
|
+
node,
|
|
1307
|
+
key,
|
|
1308
|
+
interactables,
|
|
1309
|
+
value
|
|
1310
|
+
});
|
|
1311
|
+
} // update THREE property
|
|
1312
|
+
// get final key
|
|
1313
|
+
|
|
1314
|
+
|
|
1315
|
+
const camelKey = key.replace(/-/g, '.');
|
|
1316
|
+
const finalKey = propertyShortcuts[camelKey] || camelKey; // handle and return early if prop is specific to Vue/Lunchbox
|
|
1317
|
+
|
|
1318
|
+
if (internalLunchboxVueKeys.includes(key) || internalLunchboxVueKeys.includes(finalKey)) return node; // everything else should be Three-specific, so let's cancel if this isn't a standard node
|
|
1319
|
+
|
|
1320
|
+
if (!isLunchboxStandardNode(node)) return node; // parse $attached values
|
|
1321
|
+
|
|
1322
|
+
if (typeof value === 'string' && value.startsWith('$attached')) {
|
|
1323
|
+
const attachedName = value.replace('$attached.', '');
|
|
1324
|
+
value = lodash.get(node.attached, attachedName, null);
|
|
1325
|
+
} // save instance
|
|
1326
|
+
|
|
1327
|
+
|
|
1328
|
+
const target = node.instance; // cancel if no target
|
|
1329
|
+
|
|
1330
|
+
if (!target) return node; // burrow down until we get property to change
|
|
1331
|
+
|
|
1332
|
+
let liveProperty;
|
|
1333
|
+
|
|
1334
|
+
for (let i = 0; i < nestedPropertiesToCheck.length && !liveProperty; i++) {
|
|
1335
|
+
const nestedProperty = nestedPropertiesToCheck[i];
|
|
1336
|
+
const fullPath = [nestedProperty, finalKey].filter(Boolean).join('.');
|
|
1337
|
+
liveProperty = liveProperty = lodash.get(target, fullPath);
|
|
1338
|
+
} // change property
|
|
1339
|
+
// first, save as array in case we need to spread it
|
|
1340
|
+
|
|
1341
|
+
|
|
1342
|
+
if (liveProperty && lodash.isNumber(value) && liveProperty.setScalar) {
|
|
1343
|
+
// if value is a number and the property has a `setScalar` method, use that
|
|
1344
|
+
liveProperty.setScalar(value);
|
|
1345
|
+
} else if (liveProperty && liveProperty.set) {
|
|
1346
|
+
// if property has `set` method, use that (https://github.com/pmndrs/react-three-fiber/blob/master/markdown/api.md#shortcuts)
|
|
1347
|
+
const nextValueAsArray = Array.isArray(value) ? value : [value];
|
|
1348
|
+
target[finalKey].set(...nextValueAsArray);
|
|
1349
|
+
} else if (typeof liveProperty === 'function') {
|
|
1350
|
+
// some function properties are set rather than called, so let's handle them
|
|
1351
|
+
if (finalKey.toLowerCase() === 'onbeforerender' || finalKey.toLowerCase() === 'onafterrender') {
|
|
1352
|
+
target[finalKey] = value;
|
|
1353
|
+
} else {
|
|
1354
|
+
if (!Array.isArray(value)) {
|
|
1355
|
+
throw new Error('Arguments on a declarative method must be wrapped in an array.\nWorks:\n<example :methodCall="[256]" />\nDoesn\'t work:\n<example :methodCall="256" />');
|
|
1356
|
+
} // if property is a function, let's try calling it
|
|
1357
|
+
|
|
1358
|
+
|
|
1359
|
+
liveProperty.bind(node.instance)(...value);
|
|
1360
|
+
} // pass the result to the parent
|
|
1361
|
+
// const parent = node.parentNode
|
|
1362
|
+
// if (parent) {
|
|
1363
|
+
// const parentAsLunchboxNode = parent as Lunchbox.Node
|
|
1364
|
+
// parentAsLunchboxNode.attached[finalKey] = result
|
|
1365
|
+
// ; (parentAsLunchboxNode.instance as any)[finalKey] = result
|
|
1366
|
+
// }
|
|
1367
|
+
|
|
1368
|
+
} else if (lodash.get(target, finalKey, undefined) !== undefined) {
|
|
1369
|
+
// blank strings evaluate to `true`
|
|
1370
|
+
// <mesh castShadow receiveShadow /> will work the same as
|
|
1371
|
+
// <mesh :castShadow="true" :receiveShadow="true" />
|
|
1372
|
+
lodash.set(target, finalKey, value === '' ? true : value);
|
|
1373
|
+
} else {
|
|
1374
|
+
// if you see this error in production, you might need to add `finalKey`
|
|
1375
|
+
// to `internalLunchboxVueKeys` below
|
|
1376
|
+
console.log(`No property ${finalKey} found on ${target}`);
|
|
1377
|
+
} // mark that we need to update if needed
|
|
1378
|
+
|
|
1379
|
+
|
|
1380
|
+
const targetTypeRaw = target?.texture?.type || target?.type;
|
|
1381
|
+
|
|
1382
|
+
if (typeof targetTypeRaw === 'string') {
|
|
1383
|
+
const targetType = targetTypeRaw.toLowerCase();
|
|
1384
|
+
|
|
1385
|
+
switch (true) {
|
|
1386
|
+
case targetType.includes('material'):
|
|
1387
|
+
target.needsUpdate = true;
|
|
1388
|
+
break;
|
|
1389
|
+
|
|
1390
|
+
case targetType.includes('camera') && target.updateProjectionMatrix:
|
|
1391
|
+
target.updateProjectionMatrix();
|
|
1392
|
+
break;
|
|
1544
1393
|
}
|
|
1545
|
-
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
return node;
|
|
1546
1397
|
}
|
|
1547
1398
|
const propertyShortcuts = {
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1399
|
+
x: 'position.x',
|
|
1400
|
+
y: 'position.y',
|
|
1401
|
+
z: 'position.z'
|
|
1551
1402
|
};
|
|
1552
1403
|
const nestedPropertiesToCheck = ['', 'parameters'];
|
|
1553
1404
|
/** props that Lunchbox intercepts and prevents passing to created instances */
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
'attachArray',
|
|
1558
|
-
'is.default',
|
|
1559
|
-
'isDefault',
|
|
1560
|
-
'key',
|
|
1561
|
-
'onAdded',
|
|
1562
|
-
// 'onReady',
|
|
1563
|
-
'ref',
|
|
1564
|
-
'src',
|
|
1565
|
-
];
|
|
1405
|
+
|
|
1406
|
+
const internalLunchboxVueKeys = ['args', 'attach', 'attachArray', 'is.default', 'isDefault', 'key', 'onAdded', // 'onReady',
|
|
1407
|
+
'ref', 'src'];
|
|
1566
1408
|
|
|
1567
1409
|
const autoAttach = ['geometry', 'material'];
|
|
1568
1410
|
const createElement = (type, isSVG, isCustomizedBuiltin, vnodeProps) => {
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
}
|
|
1579
|
-
// handle standard node
|
|
1580
|
-
const node = createNode(options);
|
|
1581
|
-
// autoattach
|
|
1582
|
-
autoAttach.forEach((key) => {
|
|
1583
|
-
if (type.toLowerCase().endsWith(key)) {
|
|
1584
|
-
node.props.attach = key;
|
|
1585
|
-
}
|
|
1586
|
-
});
|
|
1587
|
-
// TODO: array autoattach
|
|
1411
|
+
const options = {
|
|
1412
|
+
type,
|
|
1413
|
+
props: vnodeProps
|
|
1414
|
+
}; // handle dom node
|
|
1415
|
+
|
|
1416
|
+
const isDomNode = isLunchboxDomComponent(options);
|
|
1417
|
+
|
|
1418
|
+
if (isDomNode) {
|
|
1419
|
+
const node = createDomNode(options);
|
|
1588
1420
|
return node;
|
|
1421
|
+
} // handle standard node
|
|
1422
|
+
|
|
1423
|
+
|
|
1424
|
+
const node = createNode(options); // autoattach
|
|
1425
|
+
|
|
1426
|
+
autoAttach.forEach(key => {
|
|
1427
|
+
if (type.toLowerCase().endsWith(key)) {
|
|
1428
|
+
node.props.attach = key;
|
|
1429
|
+
}
|
|
1430
|
+
}); // TODO: array autoattach
|
|
1431
|
+
|
|
1432
|
+
return node;
|
|
1589
1433
|
};
|
|
1590
1434
|
|
|
1591
1435
|
const insert = (child, parent, anchor) => {
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1436
|
+
if (!parent) {
|
|
1437
|
+
throw new Error('missing parent');
|
|
1438
|
+
} // add to parent tree node if we have one
|
|
1439
|
+
// let effectiveParent = parent ?? ensureRootNode()
|
|
1440
|
+
|
|
1441
|
+
|
|
1442
|
+
parent.insertBefore(child, anchor); // handle comment & text nodes
|
|
1443
|
+
|
|
1444
|
+
if (child.metaType === 'commentMeta' || child.metaType === 'textMeta') {
|
|
1445
|
+
return;
|
|
1446
|
+
} // handle dom element
|
|
1447
|
+
|
|
1448
|
+
|
|
1449
|
+
if (isLunchboxDomComponent(child)) {
|
|
1450
|
+
if (isLunchboxDomComponent(parent) || isLunchboxRootNode(parent)) {
|
|
1451
|
+
parent.domElement.appendChild(child.domElement);
|
|
1598
1452
|
}
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1453
|
+
} // handle standard nodes
|
|
1454
|
+
|
|
1455
|
+
|
|
1456
|
+
if (isLunchboxStandardNode(child)) {
|
|
1457
|
+
// let effectiveParent = parent
|
|
1458
|
+
let effectiveParentNodeType = parent.metaType;
|
|
1459
|
+
|
|
1460
|
+
if (effectiveParentNodeType === 'textMeta' || effectiveParentNodeType === 'commentMeta') {
|
|
1461
|
+
const path = parent.getPath();
|
|
1462
|
+
|
|
1463
|
+
for (let i = path.length - 1; i >= 0; i--) {
|
|
1464
|
+
if (path[i].metaType !== 'textMeta' && path[i].metaType !== 'commentMeta') {
|
|
1465
|
+
parent = path[i];
|
|
1466
|
+
break;
|
|
1603
1467
|
}
|
|
1468
|
+
}
|
|
1604
1469
|
}
|
|
1605
|
-
|
|
1606
|
-
if (isLunchboxStandardNode(child)) {
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
if (child.instance &&
|
|
1630
|
-
child.instance.isObject3D &&
|
|
1631
|
-
sceneNode.instance) {
|
|
1632
|
-
if (sceneNode !== child) {
|
|
1633
|
-
sceneNode.instance.add(child.instance);
|
|
1634
|
-
}
|
|
1635
|
-
}
|
|
1636
|
-
}
|
|
1637
|
-
// add to hierarchy otherwise
|
|
1638
|
-
else if (isLunchboxStandardNode(child) &&
|
|
1639
|
-
child.instance?.isObject3D &&
|
|
1640
|
-
isLunchboxStandardNode(effectiveParent) &&
|
|
1641
|
-
effectiveParent.instance?.isObject3D) {
|
|
1642
|
-
effectiveParent.instance?.add?.(child.instance);
|
|
1643
|
-
}
|
|
1644
|
-
// add attached props
|
|
1645
|
-
if (child?.props?.attach &&
|
|
1646
|
-
isLunchboxStandardNode(parent) &&
|
|
1647
|
-
parent?.instance) {
|
|
1648
|
-
// if this element is a loader and the `src` attribute is being used,
|
|
1649
|
-
// let's assume we want to create the loader and run `load`
|
|
1650
|
-
const isUsingLoaderSugar = child.type?.toLowerCase().endsWith('loader') &&
|
|
1651
|
-
child.props.src &&
|
|
1652
|
-
(child.props.attach || child.props.attachArray);
|
|
1653
|
-
// run special loader behavior
|
|
1654
|
-
if (isUsingLoaderSugar) {
|
|
1655
|
-
runLoader(child, parent);
|
|
1656
|
-
}
|
|
1657
|
-
else {
|
|
1658
|
-
// update attached normally
|
|
1659
|
-
attachToParentInstance(child, parent, child.props.attach);
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
// fire onAdded event
|
|
1663
|
-
if (child.props?.onAdded) {
|
|
1664
|
-
child.props.onAdded({
|
|
1665
|
-
instance: child.instance,
|
|
1666
|
-
});
|
|
1667
|
-
}
|
|
1470
|
+
|
|
1471
|
+
if (isLunchboxStandardNode(child) && child.instance?.isObject3D && isLunchboxStandardNode(parent) && parent.instance?.isObject3D) {
|
|
1472
|
+
parent.instance?.add?.(child.instance);
|
|
1473
|
+
} // add attached props
|
|
1474
|
+
|
|
1475
|
+
|
|
1476
|
+
if (child?.props?.attach && isLunchboxStandardNode(parent) && parent?.instance) {
|
|
1477
|
+
// if this element is a loader and the `src` attribute is being used,
|
|
1478
|
+
// let's assume we want to create the loader and run `load`
|
|
1479
|
+
const isUsingLoaderSugar = child.type?.toLowerCase().endsWith('loader') && child.props.src && (child.props.attach || child.props.attachArray); // run special loader behavior
|
|
1480
|
+
|
|
1481
|
+
if (isUsingLoaderSugar) {
|
|
1482
|
+
runLoader(child, parent);
|
|
1483
|
+
} else {
|
|
1484
|
+
// update attached normally
|
|
1485
|
+
attachToParentInstance(child, parent, child.props.attach);
|
|
1486
|
+
}
|
|
1487
|
+
} // fire onAdded event
|
|
1488
|
+
|
|
1489
|
+
|
|
1490
|
+
if (child.props?.onAdded) {
|
|
1491
|
+
child.props.onAdded({
|
|
1492
|
+
instance: child.instance
|
|
1493
|
+
});
|
|
1668
1494
|
}
|
|
1495
|
+
}
|
|
1669
1496
|
};
|
|
1497
|
+
|
|
1670
1498
|
function runLoader(child, parent) {
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
}
|
|
1499
|
+
const loader = child.instance; // ensure parent has attached spaces ready
|
|
1500
|
+
|
|
1501
|
+
parent.attached = parent.attached || {};
|
|
1502
|
+
parent.attachedArray = parent.attachedArray || {}; // this should never be true, but just in case
|
|
1503
|
+
|
|
1504
|
+
if (!child.props.attach) return;
|
|
1505
|
+
|
|
1506
|
+
if (child.type?.toLowerCase() === 'textureloader') {
|
|
1507
|
+
// if this is a texture loader, immediately pass
|
|
1508
|
+
// load function to parent attachment
|
|
1509
|
+
const textureLoader = loader;
|
|
1510
|
+
const inProgressTexture = textureLoader.load(child.props.src);
|
|
1511
|
+
attachToParentInstance(child, parent, child.props.attach, inProgressTexture);
|
|
1512
|
+
} else {
|
|
1513
|
+
// use a standard callback-based loader
|
|
1514
|
+
loader.load(child.props.src, loadedData => {
|
|
1515
|
+
attachToParentInstance(child, parent, child.props.attach, loadedData);
|
|
1516
|
+
}, null, err => {
|
|
1517
|
+
throw new Error(err);
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1693
1520
|
}
|
|
1521
|
+
|
|
1694
1522
|
function attachToParentInstance(child, parent, key, value) {
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
// TODO: implement auto-attaching array
|
|
1710
|
-
parentInstanceAsAny[key] = [parentInstanceAsAny[key]];
|
|
1523
|
+
const finalValueToAttach = value ?? child.instance;
|
|
1524
|
+
const parentInstanceAsAny = parent.instance;
|
|
1525
|
+
|
|
1526
|
+
if (child.props.attach === key) {
|
|
1527
|
+
parent.attached = {
|
|
1528
|
+
[key]: finalValueToAttach,
|
|
1529
|
+
...(parent.attached || {})
|
|
1530
|
+
};
|
|
1531
|
+
parentInstanceAsAny[key] = value ?? child.instance;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
if (child.props.attachArray === key) {
|
|
1535
|
+
if (!parent.attachedArray[child.props.attachArray]) {
|
|
1536
|
+
parent.attachedArray[child.props.attachArray] = [];
|
|
1711
1537
|
}
|
|
1538
|
+
|
|
1539
|
+
parent.attachedArray[child.props.attachArray].push(finalValueToAttach); // TODO: implement auto-attaching array
|
|
1540
|
+
|
|
1541
|
+
parentInstanceAsAny[key] = [parentInstanceAsAny[key]];
|
|
1542
|
+
}
|
|
1712
1543
|
}
|
|
1713
1544
|
|
|
1714
|
-
const remove =
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
n.type !== 'scene' &&
|
|
1739
|
-
n.instance?.dispose;
|
|
1740
|
-
if (dispose)
|
|
1741
|
-
dispose.bind(n.instance)();
|
|
1742
|
-
n.instance = null;
|
|
1743
|
-
}
|
|
1744
|
-
// drop tree node
|
|
1745
|
-
n.drop();
|
|
1746
|
-
// remove Lunchbox node from main list
|
|
1747
|
-
const idx = allNodes.findIndex((v) => v.uuid === n.uuid);
|
|
1748
|
-
if (idx !== -1) {
|
|
1749
|
-
allNodes.splice(idx, 1);
|
|
1750
|
-
}
|
|
1751
|
-
});
|
|
1545
|
+
const remove = node => {
|
|
1546
|
+
if (!node) return; // prep subtree
|
|
1547
|
+
|
|
1548
|
+
const subtree = [];
|
|
1549
|
+
node.walk(descendant => {
|
|
1550
|
+
subtree.push(descendant);
|
|
1551
|
+
return true;
|
|
1552
|
+
}); // clean up subtree
|
|
1553
|
+
|
|
1554
|
+
subtree.forEach(n => {
|
|
1555
|
+
if (isLunchboxStandardNode(n)) {
|
|
1556
|
+
// try to remove three object
|
|
1557
|
+
n.instance?.removeFromParent?.(); // try to dispose three object
|
|
1558
|
+
|
|
1559
|
+
const dispose = // calling `dispose` on a scene triggers an error,
|
|
1560
|
+
// so let's ignore if this node is a scene
|
|
1561
|
+
n.type !== 'scene' && n.instance?.dispose;
|
|
1562
|
+
if (dispose) dispose.bind(n.instance)();
|
|
1563
|
+
n.instance = null;
|
|
1564
|
+
} // drop tree node
|
|
1565
|
+
|
|
1566
|
+
|
|
1567
|
+
n.drop();
|
|
1568
|
+
});
|
|
1752
1569
|
};
|
|
1753
1570
|
|
|
1754
1571
|
/*
|
|
1755
1572
|
Elements are `create`d from the outside in, then `insert`ed from the inside out.
|
|
1756
1573
|
*/
|
|
1757
|
-
|
|
1574
|
+
|
|
1575
|
+
const createNodeOps = () => {
|
|
1576
|
+
// APP-LEVEL GLOBALS
|
|
1577
|
+
// ====================
|
|
1578
|
+
// These need to exist at the app level in a place where the node ops can access them.
|
|
1579
|
+
// It'd be better to set these via `app.provide` at app creation, but the node ops need access
|
|
1580
|
+
// to these values before the app is instantiated, so this is the next-best place for them to exist.
|
|
1581
|
+
const interactables = vue.ref([]); // NODE OPS
|
|
1582
|
+
// ====================
|
|
1583
|
+
|
|
1584
|
+
const nodeOps = {
|
|
1758
1585
|
createElement,
|
|
1586
|
+
|
|
1759
1587
|
createText(text) {
|
|
1760
|
-
|
|
1588
|
+
return createTextNode({
|
|
1589
|
+
text
|
|
1590
|
+
});
|
|
1761
1591
|
},
|
|
1592
|
+
|
|
1762
1593
|
createComment(text) {
|
|
1763
|
-
|
|
1594
|
+
return createCommentNode({
|
|
1595
|
+
text
|
|
1596
|
+
});
|
|
1764
1597
|
},
|
|
1598
|
+
|
|
1765
1599
|
insert,
|
|
1600
|
+
|
|
1766
1601
|
nextSibling(node) {
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
return null;
|
|
1771
|
-
return result;
|
|
1602
|
+
const result = node.nextSibling;
|
|
1603
|
+
if (!result) return null;
|
|
1604
|
+
return result;
|
|
1772
1605
|
},
|
|
1606
|
+
|
|
1773
1607
|
parentNode(node) {
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
return result;
|
|
1608
|
+
const result = node.parentNode;
|
|
1609
|
+
if (!result) return null;
|
|
1610
|
+
return result;
|
|
1778
1611
|
},
|
|
1612
|
+
|
|
1779
1613
|
patchProp(node, key, prevValue, nextValue) {
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
node.domElement.setAttribute(key, nextValue);
|
|
1790
|
-
}
|
|
1791
|
-
return;
|
|
1614
|
+
if (isLunchboxDomComponent(node)) {
|
|
1615
|
+
// handle DOM node
|
|
1616
|
+
if (key === 'style') {
|
|
1617
|
+
// special handling for style
|
|
1618
|
+
Object.keys(nextValue).forEach(k => {
|
|
1619
|
+
node.domElement.style[k] = nextValue[k];
|
|
1620
|
+
});
|
|
1621
|
+
} else {
|
|
1622
|
+
node.domElement.setAttribute(key, nextValue);
|
|
1792
1623
|
}
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1624
|
+
|
|
1625
|
+
return;
|
|
1626
|
+
} // ignore if root node, or Lunchbox internal prop
|
|
1627
|
+
|
|
1628
|
+
|
|
1629
|
+
if (isLunchboxRootNode(node) || key.startsWith('$')) {
|
|
1630
|
+
return;
|
|
1631
|
+
} // otherwise, update prop
|
|
1632
|
+
|
|
1633
|
+
|
|
1634
|
+
updateObjectProp({
|
|
1635
|
+
node: node,
|
|
1636
|
+
key,
|
|
1637
|
+
interactables,
|
|
1638
|
+
value: nextValue
|
|
1639
|
+
});
|
|
1799
1640
|
},
|
|
1641
|
+
|
|
1800
1642
|
remove,
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
},
|
|
1804
|
-
setText() {
|
|
1805
|
-
// noop
|
|
1643
|
+
|
|
1644
|
+
setElementText() {// noop
|
|
1806
1645
|
},
|
|
1807
|
-
};
|
|
1808
1646
|
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1647
|
+
setText() {// noop
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
};
|
|
1651
|
+
return {
|
|
1652
|
+
nodeOps,
|
|
1653
|
+
interactables
|
|
1654
|
+
};
|
|
1814
1655
|
};
|
|
1815
|
-
|
|
1816
|
-
|
|
1656
|
+
|
|
1657
|
+
/** The current camera as a computed value. */
|
|
1658
|
+
|
|
1659
|
+
const useCamera = () => vue.inject(appCameraKey);
|
|
1817
1660
|
/** Run a function using the current camera when it's present. */
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1661
|
+
|
|
1662
|
+
const onCameraReady = cb => {
|
|
1663
|
+
const stopWatch = vue.watch(useCamera(), newVal => {
|
|
1664
|
+
if (newVal) {
|
|
1665
|
+
cb(newVal);
|
|
1666
|
+
stopWatch();
|
|
1667
|
+
}
|
|
1668
|
+
}, {
|
|
1669
|
+
immediate: true
|
|
1670
|
+
});
|
|
1671
|
+
};
|
|
1672
|
+
/** The current renderer as a computed value. */
|
|
1673
|
+
|
|
1674
|
+
const useRenderer = () => vue.inject(appRenderersKey);
|
|
1827
1675
|
/** Run a function using the current renderer when it's present. */
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1676
|
+
|
|
1677
|
+
const onRendererReady = cb => {
|
|
1678
|
+
const stopWatch = vue.watch(useRenderer(), newVal => {
|
|
1679
|
+
if (newVal) {
|
|
1680
|
+
cb(newVal);
|
|
1681
|
+
stopWatch();
|
|
1682
|
+
}
|
|
1683
|
+
}, {
|
|
1684
|
+
immediate: true
|
|
1685
|
+
});
|
|
1686
|
+
};
|
|
1687
|
+
/** The current scene as a computed value. */
|
|
1688
|
+
|
|
1689
|
+
const useScene = () => vue.inject(appSceneKey);
|
|
1837
1690
|
/** Run a function using the current scene when it's present. */
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1691
|
+
|
|
1692
|
+
const onSceneReady = cb => {
|
|
1693
|
+
const stopWatch = vue.watch(useScene(), newVal => {
|
|
1694
|
+
if (newVal) {
|
|
1695
|
+
cb(newVal);
|
|
1696
|
+
stopWatch();
|
|
1697
|
+
}
|
|
1698
|
+
}, {
|
|
1699
|
+
immediate: true
|
|
1700
|
+
});
|
|
1701
|
+
}; // CUSTOM RENDER SUPPORT
|
|
1846
1702
|
// ====================
|
|
1847
|
-
|
|
1848
|
-
let queuedCustomRenderFunction = null;
|
|
1703
|
+
|
|
1849
1704
|
/** Set a custom render function, overriding the Lunchbox app's default render function.
|
|
1850
1705
|
* Changing this requires the user to manually render their scene.
|
|
1706
|
+
*
|
|
1707
|
+
* Invokes immediately - use `useCustomRender().setCustomRender`
|
|
1708
|
+
* if you need to call somewhere outside of `setup`.
|
|
1851
1709
|
*/
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
else
|
|
1856
|
-
queuedCustomRenderFunction = render;
|
|
1710
|
+
|
|
1711
|
+
const setCustomRender = render => {
|
|
1712
|
+
useCustomRender()?.setCustomRender?.(render);
|
|
1857
1713
|
};
|
|
1858
|
-
/** Clear the active app's custom render function.
|
|
1714
|
+
/** Clear the active app's custom render function.
|
|
1715
|
+
*
|
|
1716
|
+
* Invokes immediately - use `useCustomRender().clearCustomRender`
|
|
1717
|
+
* if you need to call somewhere outside of `setup`.
|
|
1718
|
+
*/
|
|
1719
|
+
|
|
1859
1720
|
const clearCustomRender = () => {
|
|
1860
|
-
|
|
1861
|
-
app.clearCustomRender();
|
|
1862
|
-
else
|
|
1863
|
-
queuedCustomRenderFunction = null;
|
|
1721
|
+
useCustomRender()?.clearCustomRender?.();
|
|
1864
1722
|
};
|
|
1865
|
-
|
|
1723
|
+
/** Provides `setCustomRender` and `clearCustomRender` functions to be called in a non-`setup` context. */
|
|
1724
|
+
|
|
1725
|
+
const useCustomRender = () => {
|
|
1726
|
+
return {
|
|
1727
|
+
/** Set a custom render function, overriding the Lunchbox app's default render function.
|
|
1728
|
+
* Changing this requires the user to manually render their scene. */
|
|
1729
|
+
setCustomRender: vue.inject(setCustomRenderKey),
|
|
1730
|
+
|
|
1731
|
+
/** Clear the active app's custom render function. */
|
|
1732
|
+
clearCustomRender: vue.inject(clearCustomRenderKey)
|
|
1733
|
+
};
|
|
1734
|
+
};
|
|
1735
|
+
/** Use app-level globals. */
|
|
1736
|
+
|
|
1737
|
+
const useGlobals = () => vue.inject(globalsInjectionKey);
|
|
1738
|
+
/** Construct a function to update your app-level globals.
|
|
1739
|
+
*
|
|
1740
|
+
* ```js
|
|
1741
|
+
* // in setup():
|
|
1742
|
+
* const updateGlobals = useUpdateGlobals()
|
|
1743
|
+
*
|
|
1744
|
+
* // ...later, to update the device pixel resolution...
|
|
1745
|
+
* updateGlobals({ dpr: 2 })
|
|
1746
|
+
* ```
|
|
1747
|
+
*/
|
|
1748
|
+
|
|
1749
|
+
const useUpdateGlobals = () => vue.inject(updateGlobalsInjectionKey);
|
|
1750
|
+
/** Update app-level globals.
|
|
1751
|
+
*
|
|
1752
|
+
* Invokes immediately - use `useUpdateGlobals`
|
|
1753
|
+
* if you need to call somewhere outside of `setup`.
|
|
1754
|
+
*/
|
|
1755
|
+
|
|
1756
|
+
const updateGlobals = newValue => {
|
|
1757
|
+
useUpdateGlobals()?.(newValue);
|
|
1758
|
+
};
|
|
1759
|
+
/** Use the current Lunchbox app. Usually used internally by Lunchbox. */
|
|
1760
|
+
|
|
1761
|
+
const useApp = () => vue.inject(appKey);
|
|
1762
|
+
/** Obtain a list of the start callback functions. Usually used internally by Lunchbox. */
|
|
1763
|
+
|
|
1764
|
+
const useStartCallbacks = () => vue.inject(startCallbackKey);
|
|
1765
|
+
/** Run a given callback once when the Lunchbox app starts. Include an index to
|
|
1766
|
+
* splice the callback at that index in the callback queue. */
|
|
1767
|
+
|
|
1768
|
+
const onStart = (cb, index = Infinity) => {
|
|
1769
|
+
const callbacks = useStartCallbacks();
|
|
1770
|
+
|
|
1771
|
+
if (index === Infinity) {
|
|
1772
|
+
callbacks?.push(cb);
|
|
1773
|
+
} else {
|
|
1774
|
+
callbacks?.splice(index, 0, cb);
|
|
1775
|
+
}
|
|
1776
|
+
};
|
|
1777
|
+
/** Obtain a list of interactable objects (registered via onClick, onHover, etc events). Usually used internally by Lunchbox. */
|
|
1778
|
+
|
|
1779
|
+
const useLunchboxInteractables = () => vue.inject(lunchboxInteractables); // CREATE APP
|
|
1866
1780
|
// ====================
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
};
|
|
1900
|
-
// add queued custom render if we have one
|
|
1901
|
-
if (queuedCustomRenderFunction) {
|
|
1902
|
-
app.setCustomRender(queuedCustomRenderFunction);
|
|
1903
|
-
queuedCustomRenderFunction = null;
|
|
1781
|
+
|
|
1782
|
+
const createApp = root => {
|
|
1783
|
+
const {
|
|
1784
|
+
nodeOps,
|
|
1785
|
+
interactables
|
|
1786
|
+
} = createNodeOps();
|
|
1787
|
+
const app = vue.createRenderer(nodeOps).createApp(root); // provide Lunchbox interaction handlers flag (modified when user references events via
|
|
1788
|
+
// @click, etc)
|
|
1789
|
+
|
|
1790
|
+
app.provide(lunchboxInteractables, interactables); // register all components
|
|
1791
|
+
// ====================
|
|
1792
|
+
|
|
1793
|
+
Object.keys(components).forEach(key => {
|
|
1794
|
+
app?.component(key, components[key]);
|
|
1795
|
+
}); // provide custom renderer functions
|
|
1796
|
+
// ====================
|
|
1797
|
+
|
|
1798
|
+
app.provide(setCustomRenderKey, render => {
|
|
1799
|
+
app.setCustomRender(render);
|
|
1800
|
+
});
|
|
1801
|
+
app.provide(clearCustomRenderKey, () => {
|
|
1802
|
+
app.clearCustomRender();
|
|
1803
|
+
}); // before render
|
|
1804
|
+
// ====================
|
|
1805
|
+
|
|
1806
|
+
const beforeRender = [];
|
|
1807
|
+
app.provide(beforeRenderKey, beforeRender);
|
|
1808
|
+
app.provide(onBeforeRenderKey, (cb, index = Infinity) => {
|
|
1809
|
+
if (index === Infinity) {
|
|
1810
|
+
beforeRender.push(cb);
|
|
1811
|
+
} else {
|
|
1812
|
+
beforeRender.splice(index, 0, cb);
|
|
1904
1813
|
}
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1814
|
+
});
|
|
1815
|
+
app.provide(offBeforeRenderKey, cb => {
|
|
1816
|
+
if (isFinite(cb)) {
|
|
1817
|
+
beforeRender.splice(cb, 1);
|
|
1818
|
+
} else {
|
|
1819
|
+
const idx = beforeRender.findIndex(v => v == cb);
|
|
1820
|
+
|
|
1821
|
+
if (idx !== -1) {
|
|
1822
|
+
beforeRender.splice(idx, 1);
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
}); // after render
|
|
1826
|
+
// ====================
|
|
1827
|
+
|
|
1828
|
+
const afterRender = [];
|
|
1829
|
+
app.provide(afterRenderKey, afterRender);
|
|
1830
|
+
app.provide(onAfterRenderKey, (cb, index = Infinity) => {
|
|
1831
|
+
if (index === Infinity) {
|
|
1832
|
+
afterRender.push(cb);
|
|
1833
|
+
} else {
|
|
1834
|
+
afterRender.splice(index, 0, cb);
|
|
1835
|
+
}
|
|
1836
|
+
});
|
|
1837
|
+
app.provide(offAfterRenderKey, cb => {
|
|
1838
|
+
if (isFinite(cb)) {
|
|
1839
|
+
afterRender.splice(cb, 1);
|
|
1840
|
+
} else {
|
|
1841
|
+
const idx = afterRender.findIndex(v => v == cb);
|
|
1842
|
+
|
|
1843
|
+
if (idx !== -1) {
|
|
1844
|
+
afterRender.splice(idx, 1);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
}); // save app-level components
|
|
1848
|
+
// ====================
|
|
1849
|
+
|
|
1850
|
+
app.config.globalProperties.lunchbox = vue.reactive({
|
|
1851
|
+
afterRender,
|
|
1852
|
+
beforeRender,
|
|
1853
|
+
camera: null,
|
|
1854
|
+
dpr: 1,
|
|
1855
|
+
frameId: -1,
|
|
1856
|
+
renderer: null,
|
|
1857
|
+
scene: null,
|
|
1858
|
+
watchStopHandle: null // TODO: inputActive, mousePos
|
|
1859
|
+
|
|
1860
|
+
}); // provide app-level globals & globals update method
|
|
1861
|
+
// ====================
|
|
1862
|
+
|
|
1863
|
+
app.provide(globalsInjectionKey, app.config.globalProperties.lunchbox);
|
|
1864
|
+
app.provide(updateGlobalsInjectionKey, newGlobals => {
|
|
1865
|
+
Object.keys(newGlobals).forEach(key => {
|
|
1866
|
+
const typedKey = key; // TODO: fix
|
|
1867
|
+
|
|
1868
|
+
app.config.globalProperties.lunchbox[typedKey] = newGlobals[typedKey];
|
|
1869
|
+
});
|
|
1870
|
+
}); // frame ID (used for update functions)
|
|
1871
|
+
// ====================
|
|
1872
|
+
|
|
1873
|
+
app.provide(frameIdKey, app.config.globalProperties.lunchbox.frameId); // watch stop handler (used for conditional update loop)
|
|
1874
|
+
// ====================
|
|
1875
|
+
|
|
1876
|
+
app.provide(watchStopHandleKey, app.config.globalProperties.lunchbox.watchStopHandle); // update mount function to match Lunchbox.Node
|
|
1877
|
+
// ====================
|
|
1878
|
+
|
|
1879
|
+
const {
|
|
1880
|
+
mount
|
|
1881
|
+
} = app;
|
|
1882
|
+
|
|
1883
|
+
app.mount = (root, ...args) => {
|
|
1884
|
+
// find DOM element to use as app root
|
|
1885
|
+
const domElement = typeof root === 'string' ? document.querySelector(root) : root; // create or find root node
|
|
1886
|
+
|
|
1887
|
+
const rootNode = new exports.MiniDom.RendererRootNode({
|
|
1888
|
+
domElement,
|
|
1889
|
+
isLunchboxRootNode: true,
|
|
1890
|
+
name: 'root',
|
|
1891
|
+
metaType: 'rootMeta',
|
|
1892
|
+
type: 'root',
|
|
1893
|
+
uuid: 'LUNCHBOX_ROOT'
|
|
1894
|
+
});
|
|
1895
|
+
app.rootNode = rootNode;
|
|
1896
|
+
app.provide(appRootNodeKey, rootNode);
|
|
1897
|
+
const mounted = mount(rootNode, ...args);
|
|
1898
|
+
return mounted;
|
|
1899
|
+
}; // embed .extend function
|
|
1900
|
+
// ====================
|
|
1901
|
+
|
|
1902
|
+
|
|
1903
|
+
app.extend = targets => {
|
|
1904
|
+
extend({
|
|
1905
|
+
app: app,
|
|
1906
|
+
...targets
|
|
1907
|
+
});
|
|
1910
1908
|
return app;
|
|
1909
|
+
}; // start callback functions
|
|
1910
|
+
// ====================
|
|
1911
|
+
|
|
1912
|
+
|
|
1913
|
+
const startCallbacks = [];
|
|
1914
|
+
app.provide(startCallbackKey, startCallbacks); // prep for custom render support
|
|
1915
|
+
// ====================
|
|
1916
|
+
|
|
1917
|
+
app.setCustomRender = newRender => {
|
|
1918
|
+
if (app) {
|
|
1919
|
+
app.customRender = newRender;
|
|
1920
|
+
}
|
|
1921
|
+
}; // add custom render removal
|
|
1922
|
+
|
|
1923
|
+
|
|
1924
|
+
app.clearCustomRender = () => {
|
|
1925
|
+
if (app) {
|
|
1926
|
+
app.customRender = null;
|
|
1927
|
+
}
|
|
1928
|
+
}; // provide app
|
|
1929
|
+
// ====================
|
|
1930
|
+
|
|
1931
|
+
|
|
1932
|
+
app.provide(appKey, app);
|
|
1933
|
+
app.provide(appRenderersKey, vue.computed(() => app.config.globalProperties.lunchbox.renderer));
|
|
1934
|
+
app.provide(appSceneKey, vue.computed(() => app.config.globalProperties.lunchbox.scene));
|
|
1935
|
+
app.provide(appCameraKey, vue.computed(() => app.config.globalProperties.lunchbox.camera)); // done
|
|
1936
|
+
|
|
1937
|
+
return app;
|
|
1911
1938
|
};
|
|
1912
1939
|
|
|
1913
|
-
exports.
|
|
1940
|
+
exports.addEventListener = addEventListener;
|
|
1941
|
+
exports.afterRenderKey = afterRenderKey;
|
|
1942
|
+
exports.appCameraKey = appCameraKey;
|
|
1943
|
+
exports.appKey = appKey;
|
|
1944
|
+
exports.appRenderersKey = appRenderersKey;
|
|
1945
|
+
exports.appRootNodeKey = appRootNodeKey;
|
|
1946
|
+
exports.appSceneKey = appSceneKey;
|
|
1947
|
+
exports.beforeRenderKey = beforeRenderKey;
|
|
1948
|
+
exports.cancelUpdate = cancelUpdate;
|
|
1949
|
+
exports.cancelUpdateSource = cancelUpdateSource;
|
|
1914
1950
|
exports.clearCustomRender = clearCustomRender;
|
|
1951
|
+
exports.clearCustomRenderKey = clearCustomRenderKey;
|
|
1915
1952
|
exports.createApp = createApp;
|
|
1953
|
+
exports.createCommentNode = createCommentNode;
|
|
1954
|
+
exports.createDomNode = createDomNode;
|
|
1955
|
+
exports.createNode = createNode;
|
|
1956
|
+
exports.createTextNode = createTextNode;
|
|
1957
|
+
exports.extend = extend;
|
|
1916
1958
|
exports.find = find;
|
|
1917
|
-
exports.
|
|
1959
|
+
exports.frameIdKey = frameIdKey;
|
|
1960
|
+
exports.globalsInjectionKey = globalsInjectionKey;
|
|
1961
|
+
exports.instantiateThreeObject = instantiateThreeObject;
|
|
1962
|
+
exports.isMinidomNode = isMinidomNode;
|
|
1963
|
+
exports.lunchboxInteractables = lunchboxInteractables;
|
|
1964
|
+
exports.nestedPropertiesToCheck = nestedPropertiesToCheck;
|
|
1918
1965
|
exports.offAfterRender = offAfterRender;
|
|
1966
|
+
exports.offAfterRenderKey = offAfterRenderKey;
|
|
1919
1967
|
exports.offBeforeRender = offBeforeRender;
|
|
1968
|
+
exports.offBeforeRenderKey = offBeforeRenderKey;
|
|
1920
1969
|
exports.onAfterRender = onAfterRender;
|
|
1970
|
+
exports.onAfterRenderKey = onAfterRenderKey;
|
|
1921
1971
|
exports.onBeforeRender = onBeforeRender;
|
|
1972
|
+
exports.onBeforeRenderKey = onBeforeRenderKey;
|
|
1973
|
+
exports.onCameraReady = onCameraReady;
|
|
1974
|
+
exports.onRendererReady = onRendererReady;
|
|
1975
|
+
exports.onSceneReady = onSceneReady;
|
|
1922
1976
|
exports.onStart = onStart;
|
|
1923
|
-
exports.renderer = renderer;
|
|
1924
|
-
exports.scene = scene;
|
|
1925
1977
|
exports.setCustomRender = setCustomRender;
|
|
1978
|
+
exports.setCustomRenderKey = setCustomRenderKey;
|
|
1979
|
+
exports.startCallbackKey = startCallbackKey;
|
|
1980
|
+
exports.update = update;
|
|
1981
|
+
exports.updateGlobals = updateGlobals;
|
|
1982
|
+
exports.updateGlobalsInjectionKey = updateGlobalsInjectionKey;
|
|
1983
|
+
exports.updateObjectProp = updateObjectProp;
|
|
1984
|
+
exports.useAfterRender = useAfterRender;
|
|
1985
|
+
exports.useApp = useApp;
|
|
1986
|
+
exports.useBeforeRender = useBeforeRender;
|
|
1926
1987
|
exports.useCamera = useCamera;
|
|
1988
|
+
exports.useCancelUpdate = useCancelUpdate;
|
|
1989
|
+
exports.useCancelUpdateSource = useCancelUpdateSource;
|
|
1990
|
+
exports.useCustomRender = useCustomRender;
|
|
1991
|
+
exports.useGlobals = useGlobals;
|
|
1992
|
+
exports.useLunchboxInteractables = useLunchboxInteractables;
|
|
1927
1993
|
exports.useRenderer = useRenderer;
|
|
1928
1994
|
exports.useScene = useScene;
|
|
1995
|
+
exports.useStartCallbacks = useStartCallbacks;
|
|
1996
|
+
exports.useUpdateGlobals = useUpdateGlobals;
|
|
1997
|
+
exports.watchStopHandleKey = watchStopHandleKey;
|
|
1929
1998
|
|
|
1930
1999
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
1931
2000
|
|