lunchboxjs 2.0.0-beta.0 → 2.0.0-beta.1

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