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