lunchboxjs 0.1.4001 → 0.1.4005
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 +3 -0
- package/dist/lunchboxjs.js +407 -226
- package/dist/lunchboxjs.min.js +1 -1
- package/dist/lunchboxjs.module.js +405 -227
- package/extras/Gltf.vue +2 -2
- package/extras/OrbitControlsWrapper.vue +9 -10
- package/package.json +24 -3
- package/src/components/LunchboxWrapper/LunchboxWrapper.ts +125 -33
- package/src/components/LunchboxWrapper/resizeCanvas.ts +17 -7
- package/src/components/autoGeneratedComponents.ts +175 -0
- package/src/components/index.ts +5 -188
- package/src/core/createNode.ts +19 -9
- package/src/core/ensure.ts +166 -130
- package/src/core/instantiateThreeObject/index.ts +6 -4
- package/src/core/interaction/index.ts +4 -2
- package/src/core/interaction/setupAutoRaycaster.ts +6 -6
- package/src/core/minidom.ts +85 -27
- package/src/core/update.ts +36 -26
- package/src/index.ts +62 -22
- package/src/nodeOps/createElement.ts +3 -6
- package/src/nodeOps/insert.ts +6 -4
- package/src/nodeOps/remove.ts +19 -5
- package/src/types.ts +10 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ref, onMounted, getCurrentInstance, onBeforeUnmount, h, defineComponent, isRef, isVNode, watch, computed, createRenderer } from 'vue';
|
|
1
|
+
import { toRaw, ref, onMounted, getCurrentInstance, onBeforeUnmount, h, defineComponent, isRef, isVNode, watch, reactive, computed, createRenderer } from 'vue';
|
|
2
2
|
import * as THREE from 'three';
|
|
3
3
|
import { Color } from 'three';
|
|
4
4
|
import { set, get, isNumber } from 'lodash';
|
|
@@ -7,29 +7,39 @@ import { set, get, isNumber } from 'lodash';
|
|
|
7
7
|
const allNodes = [];
|
|
8
8
|
|
|
9
9
|
const resizeCanvas = (width, height) => {
|
|
10
|
-
const renderer = ensureRenderer
|
|
11
|
-
const scene =
|
|
10
|
+
const renderer = ensureRenderer.value?.instance;
|
|
11
|
+
const scene = ensuredScene.value.instance;
|
|
12
|
+
const camera = ensuredCamera.value;
|
|
12
13
|
// ignore if no element
|
|
13
|
-
if (!renderer?.domElement || !scene)
|
|
14
|
+
if (!renderer?.domElement || !scene || !camera)
|
|
14
15
|
return;
|
|
15
16
|
width = width ?? window.innerWidth;
|
|
16
17
|
height = height ?? window.innerHeight;
|
|
17
18
|
// update camera
|
|
18
19
|
const aspect = width / height;
|
|
19
|
-
const camera = ensureCamera();
|
|
20
20
|
if (camera.type?.toLowerCase() === 'perspectivecamera') {
|
|
21
21
|
const perspectiveCamera = camera.instance;
|
|
22
22
|
perspectiveCamera.aspect = aspect;
|
|
23
23
|
perspectiveCamera.updateProjectionMatrix();
|
|
24
24
|
}
|
|
25
|
+
else if (camera.type?.toLowerCase() === 'orthographiccamera') {
|
|
26
|
+
// console.log('TODO: ortho camera update')
|
|
27
|
+
const orthoCamera = camera.instance;
|
|
28
|
+
const heightInTermsOfWidth = height / width;
|
|
29
|
+
orthoCamera.top = heightInTermsOfWidth * 10;
|
|
30
|
+
orthoCamera.bottom = -heightInTermsOfWidth * 10;
|
|
31
|
+
orthoCamera.right = 10;
|
|
32
|
+
orthoCamera.left = -10;
|
|
33
|
+
orthoCamera.updateProjectionMatrix();
|
|
34
|
+
}
|
|
25
35
|
else {
|
|
26
|
-
console.log('TODO: ortho camera
|
|
36
|
+
console.log('TODO: non-ortho or perspective camera');
|
|
27
37
|
}
|
|
28
38
|
// update canvas
|
|
29
39
|
renderer.setSize(width, height);
|
|
30
40
|
// render immediately so there's no flicker
|
|
31
41
|
if (scene && camera.instance) {
|
|
32
|
-
renderer.render(scene, camera.instance);
|
|
42
|
+
renderer.render(toRaw(scene), toRaw(camera.instance));
|
|
33
43
|
}
|
|
34
44
|
};
|
|
35
45
|
|
|
@@ -73,8 +83,13 @@ const LunchboxWrapper = {
|
|
|
73
83
|
props: {
|
|
74
84
|
// These should match the Lunchbox.WrapperProps interface
|
|
75
85
|
background: String,
|
|
86
|
+
cameraArgs: Array,
|
|
87
|
+
cameraLook: Array,
|
|
88
|
+
cameraLookAt: Array,
|
|
76
89
|
cameraPosition: Array,
|
|
77
90
|
dpr: Number,
|
|
91
|
+
ortho: Boolean,
|
|
92
|
+
orthographic: Boolean,
|
|
78
93
|
rendererProperties: Object,
|
|
79
94
|
shadow: [Boolean, Object],
|
|
80
95
|
transparent: Boolean,
|
|
@@ -85,6 +100,7 @@ const LunchboxWrapper = {
|
|
|
85
100
|
const dpr = ref(props.dpr ?? -1);
|
|
86
101
|
const container = ref();
|
|
87
102
|
let renderer;
|
|
103
|
+
let camera;
|
|
88
104
|
let scene;
|
|
89
105
|
// MOUNT
|
|
90
106
|
// ====================
|
|
@@ -92,40 +108,106 @@ const LunchboxWrapper = {
|
|
|
92
108
|
// canvas needs to exist
|
|
93
109
|
if (!canvas.value)
|
|
94
110
|
throw new Error('missing canvas');
|
|
95
|
-
// ensure camera
|
|
96
|
-
const camera = ensureCamera().instance;
|
|
97
|
-
// move camera if needed
|
|
98
|
-
if (camera && props.cameraPosition) {
|
|
99
|
-
camera.position.set(...props.cameraPosition);
|
|
100
|
-
}
|
|
101
111
|
// RENDERER
|
|
102
112
|
// ====================
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
113
|
+
// is there already a renderer?
|
|
114
|
+
// TODO: allow other renderer types
|
|
115
|
+
renderer = tryGetNodeWithInstanceType([
|
|
116
|
+
'WebGLRenderer',
|
|
117
|
+
]);
|
|
118
|
+
// if renderer is missing, initialize with options
|
|
119
|
+
if (!renderer) {
|
|
120
|
+
// build renderer args
|
|
121
|
+
const rendererArgs = {
|
|
122
|
+
antialias: true,
|
|
123
|
+
canvas: canvas.value.domElement,
|
|
124
|
+
};
|
|
125
|
+
if (props.transparent) {
|
|
126
|
+
rendererArgs.alpha = true;
|
|
127
|
+
}
|
|
128
|
+
// create new renderer
|
|
129
|
+
ensureRenderer.value = createNode({
|
|
130
|
+
type: 'WebGLRenderer',
|
|
131
|
+
uuid: fallbackRendererUuid,
|
|
132
|
+
props: {
|
|
133
|
+
args: [rendererArgs],
|
|
134
|
+
},
|
|
120
135
|
});
|
|
136
|
+
// we've initialized the renderer, so anything depending on it can execute now
|
|
137
|
+
rendererReady.value = true;
|
|
138
|
+
const rendererAsWebGlRenderer = ensureRenderer;
|
|
139
|
+
// update render sugar
|
|
140
|
+
const sugar = {
|
|
141
|
+
shadow: props.shadow,
|
|
142
|
+
};
|
|
143
|
+
if (rendererAsWebGlRenderer.value.instance && sugar?.shadow) {
|
|
144
|
+
rendererAsWebGlRenderer.value.instance.shadowMap.enabled =
|
|
145
|
+
true;
|
|
146
|
+
if (typeof sugar.shadow === 'object') {
|
|
147
|
+
rendererAsWebGlRenderer.value.instance.shadowMap.type =
|
|
148
|
+
sugar.shadow.type;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// set renderer props if needed
|
|
152
|
+
if (props.rendererProperties) {
|
|
153
|
+
Object.keys(props.rendererProperties).forEach((key) => {
|
|
154
|
+
set(rendererAsWebGlRenderer.value, key, props.rendererProperties[key]);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
// update using created renderer
|
|
158
|
+
renderer = rendererAsWebGlRenderer.value;
|
|
121
159
|
}
|
|
122
|
-
|
|
160
|
+
else {
|
|
123
161
|
useFallbackRenderer.value = false;
|
|
162
|
+
// the user has initialized the renderer, so anything depending
|
|
163
|
+
// on the renderer can execute
|
|
164
|
+
rendererReady.value = true;
|
|
124
165
|
return;
|
|
125
166
|
}
|
|
167
|
+
// CAMERA
|
|
168
|
+
// ====================
|
|
169
|
+
// is there already a camera?
|
|
170
|
+
camera = tryGetNodeWithInstanceType([
|
|
171
|
+
'PerspectiveCamera',
|
|
172
|
+
'OrthographicCamera',
|
|
173
|
+
]);
|
|
174
|
+
// if not, let's create one
|
|
175
|
+
if (!camera) {
|
|
176
|
+
// create ortho camera
|
|
177
|
+
if (props.ortho || props.orthographic) {
|
|
178
|
+
ensuredCamera.value = createNode({
|
|
179
|
+
props: { args: props.cameraArgs ?? [] },
|
|
180
|
+
type: 'OrthographicCamera',
|
|
181
|
+
uuid: fallbackCameraUuid,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
ensuredCamera.value = createNode({
|
|
186
|
+
props: {
|
|
187
|
+
args: props.cameraArgs ?? [45, 0.5625, 1, 1000],
|
|
188
|
+
},
|
|
189
|
+
type: 'PerspectiveCamera',
|
|
190
|
+
uuid: fallbackCameraUuid,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
cameraReady.value = true;
|
|
194
|
+
camera = ensuredCamera.value;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
cameraReady.value = true;
|
|
198
|
+
}
|
|
199
|
+
// move camera if needed
|
|
200
|
+
if (camera && props.cameraPosition) {
|
|
201
|
+
camera.instance?.position.set(...props.cameraPosition);
|
|
202
|
+
}
|
|
203
|
+
// angle camera if needed
|
|
204
|
+
if (camera && (props.cameraLookAt || props.cameraLook)) {
|
|
205
|
+
const source = (props.cameraLookAt || props.cameraLook);
|
|
206
|
+
camera.instance?.lookAt(...source);
|
|
207
|
+
}
|
|
126
208
|
// SCENE
|
|
127
209
|
// ====================
|
|
128
|
-
scene =
|
|
210
|
+
scene = ensuredScene.value;
|
|
129
211
|
// set background color
|
|
130
212
|
if (scene && scene.instance && props.background) {
|
|
131
213
|
scene.instance.background = new Color(props.background);
|
|
@@ -135,7 +217,7 @@ const LunchboxWrapper = {
|
|
|
135
217
|
if (dpr.value === -1) {
|
|
136
218
|
dpr.value = window.devicePixelRatio;
|
|
137
219
|
}
|
|
138
|
-
if (renderer
|
|
220
|
+
if (renderer?.instance) {
|
|
139
221
|
renderer.instance.setPixelRatio(dpr.value);
|
|
140
222
|
globals.dpr.value = dpr.value;
|
|
141
223
|
// prep canvas (sizing, observe, unmount, etc)
|
|
@@ -146,9 +228,10 @@ const LunchboxWrapper = {
|
|
|
146
228
|
}
|
|
147
229
|
// KICK UPDATE
|
|
148
230
|
// ====================
|
|
231
|
+
// console.log(scene)
|
|
149
232
|
update({
|
|
150
233
|
app: getCurrentInstance().appContext.app,
|
|
151
|
-
camera,
|
|
234
|
+
camera: camera.instance,
|
|
152
235
|
renderer: renderer.instance,
|
|
153
236
|
scene: scene.instance,
|
|
154
237
|
});
|
|
@@ -178,21 +261,6 @@ const LunchboxWrapper = {
|
|
|
178
261
|
},
|
|
179
262
|
};
|
|
180
263
|
|
|
181
|
-
const catalogue = {};
|
|
182
|
-
|
|
183
|
-
const lunchboxDomComponentNames = [
|
|
184
|
-
'canvas',
|
|
185
|
-
'div',
|
|
186
|
-
'LunchboxWrapper',
|
|
187
|
-
];
|
|
188
|
-
// component creation utility
|
|
189
|
-
const createComponent$1 = (tag) => defineComponent({
|
|
190
|
-
inheritAttrs: false,
|
|
191
|
-
name: tag,
|
|
192
|
-
setup(props, context) {
|
|
193
|
-
return () => h(tag, context.attrs, context.slots?.default?.() || []);
|
|
194
|
-
},
|
|
195
|
-
});
|
|
196
264
|
// list of all components to register out of the box
|
|
197
265
|
const autoGeneratedComponents = [
|
|
198
266
|
// ThreeJS basics
|
|
@@ -305,21 +373,11 @@ const autoGeneratedComponents = [
|
|
|
305
373
|
'arrayCamera',
|
|
306
374
|
// renderers
|
|
307
375
|
'webGLRenderer',
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
});
|
|
312
|
-
const components = {
|
|
313
|
-
...autoGeneratedComponents,
|
|
314
|
-
'Lunchbox': LunchboxWrapper,
|
|
315
|
-
// Gltf,
|
|
316
|
-
};
|
|
317
|
-
// console.log(components, Gltf)
|
|
318
|
-
/*
|
|
319
|
-
// List copied from r3f
|
|
320
|
-
// https://github.com/pmndrs/react-three-fiber/blob/master/packages/fiber/src/three-types.ts
|
|
376
|
+
/*
|
|
377
|
+
// List copied from r3f:
|
|
378
|
+
// https://github.com/pmndrs/react-three-fiber/blob/master/packages/fiber/src/three-types.ts
|
|
321
379
|
|
|
322
|
-
// NOT IMPLEMENTED:
|
|
380
|
+
// NOT IMPLEMENTED (can be added via Extend - docs.lunchboxjs.com/components/extend/):
|
|
323
381
|
audioListener: AudioListenerProps
|
|
324
382
|
positionalAudio: PositionalAudioProps
|
|
325
383
|
|
|
@@ -367,6 +425,27 @@ const components = {
|
|
|
367
425
|
fogExp2: FogExp2Props
|
|
368
426
|
shape: ShapeProps
|
|
369
427
|
*/
|
|
428
|
+
];
|
|
429
|
+
|
|
430
|
+
const catalogue = {};
|
|
431
|
+
|
|
432
|
+
const lunchboxDomComponentNames = ['canvas', 'div', 'LunchboxWrapper'];
|
|
433
|
+
// component creation utility
|
|
434
|
+
const createComponent$1 = (tag) => defineComponent({
|
|
435
|
+
inheritAttrs: false,
|
|
436
|
+
name: tag,
|
|
437
|
+
setup(props, context) {
|
|
438
|
+
return () => h(tag, context.attrs, context.slots?.default?.() || []);
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
autoGeneratedComponents.map(createComponent$1).reduce((acc, curr) => {
|
|
442
|
+
acc[curr.name] = curr;
|
|
443
|
+
return acc;
|
|
444
|
+
});
|
|
445
|
+
const components = {
|
|
446
|
+
...autoGeneratedComponents,
|
|
447
|
+
Lunchbox: LunchboxWrapper,
|
|
448
|
+
};
|
|
370
449
|
|
|
371
450
|
function find(target) {
|
|
372
451
|
target = isRef(target) ? target.value : target;
|
|
@@ -423,7 +502,7 @@ const isLunchboxRootNode = (node) => {
|
|
|
423
502
|
/** Create a new Lunchbox comment node. */
|
|
424
503
|
function createCommentNode(options = {}) {
|
|
425
504
|
const defaults = {
|
|
426
|
-
text: options.text ?? ''
|
|
505
|
+
text: options.text ?? '',
|
|
427
506
|
};
|
|
428
507
|
return new MiniDom.RendererCommentNode({
|
|
429
508
|
...defaults,
|
|
@@ -447,7 +526,7 @@ function createDomNode(options = {}) {
|
|
|
447
526
|
/** Create a new Lunchbox text node. */
|
|
448
527
|
function createTextNode(options = {}) {
|
|
449
528
|
const defaults = {
|
|
450
|
-
text: options.text ?? ''
|
|
529
|
+
text: options.text ?? '',
|
|
451
530
|
};
|
|
452
531
|
return new MiniDom.RendererTextNode({
|
|
453
532
|
...options,
|
|
@@ -460,7 +539,7 @@ function createNode(options = {}, props = {}) {
|
|
|
460
539
|
const defaults = {
|
|
461
540
|
attached: options.attached ?? [],
|
|
462
541
|
attachedArray: options.attachedArray ?? {},
|
|
463
|
-
instance: options.instance ?? null
|
|
542
|
+
instance: options.instance ?? null,
|
|
464
543
|
};
|
|
465
544
|
const node = new MiniDom.RendererStandardNode({
|
|
466
545
|
...options,
|
|
@@ -468,14 +547,25 @@ function createNode(options = {}, props = {}) {
|
|
|
468
547
|
metaType: 'standardMeta',
|
|
469
548
|
});
|
|
470
549
|
if (node.type && !isLunchboxRootNode(node) && !node.instance) {
|
|
550
|
+
// if (node.type.includes('Camera')) {
|
|
551
|
+
// console.log(node.type, {
|
|
552
|
+
// ...node.props,
|
|
553
|
+
// ...props,
|
|
554
|
+
// })
|
|
555
|
+
// console.trace()
|
|
556
|
+
// }
|
|
471
557
|
node.instance = instantiateThreeObject({
|
|
472
558
|
...node,
|
|
473
559
|
props: {
|
|
474
560
|
...node.props,
|
|
475
561
|
...props,
|
|
476
|
-
}
|
|
562
|
+
},
|
|
477
563
|
});
|
|
478
564
|
}
|
|
565
|
+
if (node.type === 'scene') {
|
|
566
|
+
// manually set scene override
|
|
567
|
+
ensuredScene.value = node;
|
|
568
|
+
}
|
|
479
569
|
return node;
|
|
480
570
|
}
|
|
481
571
|
|
|
@@ -506,7 +596,9 @@ function addEventListener({ node, key, value, }) {
|
|
|
506
596
|
node.eventListeners[key].push(value);
|
|
507
597
|
// if we need it, let's get/create the main raycaster
|
|
508
598
|
if (interactionsRequiringRaycaster.includes(key)) {
|
|
509
|
-
|
|
599
|
+
// we're not using `v` here, we're just making sure the raycaster has been created
|
|
600
|
+
// TODO: is this necessary?
|
|
601
|
+
ensuredRaycaster.value;
|
|
510
602
|
if (node.instance && !interactables.includes(node)) {
|
|
511
603
|
addInteractable(node);
|
|
512
604
|
node.eventListenerRemoveFunctions[key].push(() => removeInteractable(node));
|
|
@@ -564,7 +656,7 @@ const setupAutoRaycaster = (node) => {
|
|
|
564
656
|
return;
|
|
565
657
|
// add mouse events once renderer is ready
|
|
566
658
|
let stopWatcher = null;
|
|
567
|
-
stopWatcher = watch(() =>
|
|
659
|
+
stopWatcher = watch(() => ensureRenderer.value, (renderer) => {
|
|
568
660
|
// make sure renderer exists
|
|
569
661
|
if (!renderer?.instance)
|
|
570
662
|
return;
|
|
@@ -605,8 +697,8 @@ const setupAutoRaycaster = (node) => {
|
|
|
605
697
|
let currentIntersections = [];
|
|
606
698
|
const autoRaycasterBeforeRender = () => {
|
|
607
699
|
// setup
|
|
608
|
-
const raycaster =
|
|
609
|
-
const camera =
|
|
700
|
+
const raycaster = ensuredRaycaster.value?.instance;
|
|
701
|
+
const camera = ensuredCamera.value?.instance;
|
|
610
702
|
if (!raycaster || !camera)
|
|
611
703
|
return;
|
|
612
704
|
raycaster.setFromCamera(globals.mousePos.value, camera);
|
|
@@ -690,119 +782,147 @@ function ensureRootNode(options = {}) {
|
|
|
690
782
|
}
|
|
691
783
|
return lunchboxRootNode;
|
|
692
784
|
}
|
|
693
|
-
//
|
|
785
|
+
// This is used in `buildEnsured` below and `LunchboxWrapper`
|
|
786
|
+
/** Search the overrides record and the node tree for a node in the given types */
|
|
787
|
+
function tryGetNodeWithInstanceType(pascalCaseTypes) {
|
|
788
|
+
if (!Array.isArray(pascalCaseTypes)) {
|
|
789
|
+
pascalCaseTypes = [pascalCaseTypes];
|
|
790
|
+
}
|
|
791
|
+
// default to override if we have one
|
|
792
|
+
for (let singleType of pascalCaseTypes) {
|
|
793
|
+
if (overrides[singleType])
|
|
794
|
+
return overrides[singleType];
|
|
795
|
+
}
|
|
796
|
+
// look for auto-created node
|
|
797
|
+
for (let singleType of pascalCaseTypes) {
|
|
798
|
+
const found = autoCreated[singleType] ||
|
|
799
|
+
allNodes.find((node) => node.type?.toLowerCase() ===
|
|
800
|
+
singleType.toLowerCase());
|
|
801
|
+
// if we have one, save and return
|
|
802
|
+
if (found) {
|
|
803
|
+
const createdAsNode = found;
|
|
804
|
+
autoCreated[singleType] = createdAsNode;
|
|
805
|
+
return createdAsNode;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
// GENERIC ENSURE FUNCTION
|
|
694
811
|
// ====================
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
812
|
+
// Problem:
|
|
813
|
+
// I want to make sure an object of type Xyz exists in my Lunchbox app.
|
|
814
|
+
// If it doesn't exist, I want to create it and add it to the root node.
|
|
815
|
+
//
|
|
816
|
+
// Solution:
|
|
817
|
+
// export const ensuredXyz = buildEnsured<Xyz>('Xyz', 'FALLBACK_XYZ')
|
|
818
|
+
//
|
|
819
|
+
// Now in other components, you can do both:
|
|
820
|
+
// import { ensuredXyz }
|
|
821
|
+
// ensuredXyz.value (...)
|
|
822
|
+
// and:
|
|
823
|
+
// ensuredXyz.value = ...
|
|
824
|
+
const autoCreated = reactive({});
|
|
825
|
+
const overrides = reactive({});
|
|
826
|
+
/**
|
|
827
|
+
* Build a computed ensured value with a getter and setter.
|
|
828
|
+
* @param pascalCaseTypes List of types this can be. Will autocreate first type if array provided.
|
|
829
|
+
* @param fallbackUuid Fallback UUID to use.
|
|
830
|
+
* @param props Props to pass to autocreated element
|
|
831
|
+
* @returns Computed getter/setter for ensured object.
|
|
832
|
+
*/
|
|
833
|
+
function buildEnsured(pascalCaseTypes, fallbackUuid, props = {}, callback = null) {
|
|
834
|
+
// make sure we've got an array
|
|
835
|
+
if (!Array.isArray(pascalCaseTypes)) {
|
|
836
|
+
pascalCaseTypes = [pascalCaseTypes];
|
|
708
837
|
}
|
|
709
|
-
//
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
838
|
+
// add type for autoCreated and overrides
|
|
839
|
+
for (let singleType of pascalCaseTypes) {
|
|
840
|
+
if (!autoCreated[singleType]) {
|
|
841
|
+
autoCreated[singleType] = null;
|
|
842
|
+
}
|
|
843
|
+
if (!overrides[singleType]) {
|
|
844
|
+
overrides[singleType] = null;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return computed({
|
|
848
|
+
get() {
|
|
849
|
+
// try to get existing type
|
|
850
|
+
const existing = tryGetNodeWithInstanceType(pascalCaseTypes);
|
|
851
|
+
if (existing)
|
|
852
|
+
return existing;
|
|
853
|
+
// otherwise, create a new node
|
|
854
|
+
const root = ensureRootNode();
|
|
855
|
+
const node = createNode({
|
|
856
|
+
type: pascalCaseTypes[0],
|
|
857
|
+
uuid: fallbackUuid,
|
|
858
|
+
props,
|
|
859
|
+
});
|
|
860
|
+
root.addChild(node);
|
|
861
|
+
autoCreated[pascalCaseTypes[0]] = node;
|
|
862
|
+
if (callback) {
|
|
863
|
+
callback(node);
|
|
864
|
+
}
|
|
865
|
+
return node;
|
|
866
|
+
},
|
|
867
|
+
set(val) {
|
|
868
|
+
const t = val.type ?? '';
|
|
869
|
+
const pascalType = t[0].toUpperCase() + t.slice(1);
|
|
870
|
+
overrides[pascalType] = val;
|
|
716
871
|
},
|
|
717
872
|
});
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
873
|
+
}
|
|
874
|
+
// ENSURE CAMERA
|
|
875
|
+
// ====================
|
|
876
|
+
const fallbackCameraUuid = 'FALLBACK_CAMERA';
|
|
877
|
+
const defaultCamera = buildEnsured(['PerspectiveCamera', 'OrthographicCamera'], fallbackCameraUuid, { args: [45, 0.5625, 1, 1000] });
|
|
878
|
+
/** Special value to be changed ONLY in `LunchboxWrapper`.
|
|
879
|
+
* Functions waiting for a Camera need to wait for this to be true. */
|
|
880
|
+
const cameraReady = ref(false);
|
|
881
|
+
const ensuredCamera = computed({
|
|
882
|
+
get() {
|
|
883
|
+
return (cameraReady.value ? defaultCamera.value : null);
|
|
884
|
+
},
|
|
885
|
+
set(val) {
|
|
886
|
+
const t = val.type ?? '';
|
|
887
|
+
const pascalType = t[0].toUpperCase() + t.slice(1);
|
|
888
|
+
overrides[pascalType] = val;
|
|
889
|
+
},
|
|
890
|
+
});
|
|
891
|
+
// export const ensuredCamera = buildEnsured<THREE.Camera>(
|
|
892
|
+
// ['PerspectiveCamera', 'OrthographicCamera'],
|
|
893
|
+
// fallbackCameraUuid,
|
|
894
|
+
// {
|
|
895
|
+
// args: [45, 0.5625, 1, 1000],
|
|
896
|
+
// }
|
|
897
|
+
// )
|
|
724
898
|
// ENSURE RENDERER
|
|
725
899
|
// ====================
|
|
726
900
|
const fallbackRendererUuid = 'FALLBACK_RENDERER';
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
uuid: fallbackRendererUuid,
|
|
745
|
-
}, { args: fallbackArgs });
|
|
746
|
-
// shadow sugar
|
|
747
|
-
if (sugar?.shadow) {
|
|
748
|
-
rendererNode.instance.shadowMap.enabled = true;
|
|
749
|
-
if (typeof sugar.shadow === 'object') {
|
|
750
|
-
rendererNode.instance.shadowMap.type = sugar.shadow.type;
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
root.addChild(rendererNode);
|
|
754
|
-
createdRenderer.value = rendererNode;
|
|
755
|
-
// return created node
|
|
756
|
-
return rendererNode;
|
|
757
|
-
};
|
|
901
|
+
const v = buildEnsured(
|
|
902
|
+
// TODO: ensure support for css/svg renderers
|
|
903
|
+
['WebGLRenderer'], //, 'CSS2DRenderer', 'CSS3DRenderer', 'SVGRenderer'],
|
|
904
|
+
fallbackRendererUuid, {});
|
|
905
|
+
/** Special value to be changed ONLY in `LunchboxWrapper`.
|
|
906
|
+
* Functions waiting for a Renderer need to wait for this to be true. */
|
|
907
|
+
const rendererReady = ref(false);
|
|
908
|
+
const ensureRenderer = computed({
|
|
909
|
+
get() {
|
|
910
|
+
return (rendererReady.value ? v.value : null);
|
|
911
|
+
},
|
|
912
|
+
set(val) {
|
|
913
|
+
const t = val.type ?? '';
|
|
914
|
+
const pascalType = t[0].toUpperCase() + t.slice(1);
|
|
915
|
+
overrides[pascalType] = val;
|
|
916
|
+
},
|
|
917
|
+
});
|
|
758
918
|
// ENSURE SCENE
|
|
759
919
|
// ====================
|
|
760
920
|
const fallbackSceneUuid = 'FALLBACK_SCENE';
|
|
761
|
-
const
|
|
762
|
-
const ensureScene = () => {
|
|
763
|
-
// look for scenes
|
|
764
|
-
const foundScene = allNodes.find((node) => node.type?.toLowerCase() === 'scene');
|
|
765
|
-
// if we have one, return
|
|
766
|
-
if (foundScene) {
|
|
767
|
-
const sceneAsLunchboxNode = foundScene;
|
|
768
|
-
createdScene.value = sceneAsLunchboxNode;
|
|
769
|
-
return sceneAsLunchboxNode;
|
|
770
|
-
}
|
|
771
|
-
// otherwise, create a new scene
|
|
772
|
-
const root = ensureRootNode();
|
|
773
|
-
const sceneNode = createNode({
|
|
774
|
-
type: 'Scene',
|
|
775
|
-
uuid: fallbackSceneUuid,
|
|
776
|
-
});
|
|
777
|
-
root.addChild(sceneNode);
|
|
778
|
-
createdScene.value = sceneNode;
|
|
779
|
-
return sceneNode;
|
|
780
|
-
};
|
|
921
|
+
const ensuredScene = buildEnsured('Scene', fallbackSceneUuid);
|
|
781
922
|
// ENSURE AUTO-RAYCASTER
|
|
782
923
|
const autoRaycasterUuid = 'AUTO_RAYCASTER';
|
|
783
|
-
|
|
784
|
-
const
|
|
785
|
-
// look for autoraycaster
|
|
786
|
-
const found = allNodes.find((node) => node.uuid === autoRaycasterUuid);
|
|
787
|
-
// if we have one, return
|
|
788
|
-
if (found) {
|
|
789
|
-
const foundAsNode = found;
|
|
790
|
-
createdRaycaster.value = foundAsNode;
|
|
791
|
-
return foundAsNode;
|
|
792
|
-
}
|
|
793
|
-
// otherwise, create raycaster
|
|
794
|
-
const root = ensureRootNode();
|
|
795
|
-
const raycasterNode = createNode({
|
|
796
|
-
type: 'Raycaster',
|
|
797
|
-
uuid: autoRaycasterUuid,
|
|
798
|
-
});
|
|
799
|
-
root.addChild(raycasterNode);
|
|
800
|
-
createdRaycaster.value = raycasterNode;
|
|
801
|
-
// finish auto-raycaster setup
|
|
802
|
-
setupAutoRaycaster(raycasterNode);
|
|
803
|
-
// done with raycaster
|
|
804
|
-
return raycasterNode;
|
|
805
|
-
};
|
|
924
|
+
// `unknown` is intentional here - we need to typecast the node since Raycaster isn't an Object3D
|
|
925
|
+
const ensuredRaycaster = buildEnsured('Raycaster', autoRaycasterUuid, {}, (node) => setupAutoRaycaster(node));
|
|
806
926
|
|
|
807
927
|
const createComponent = (tag) => defineComponent({
|
|
808
928
|
inheritAttrs: false,
|
|
@@ -850,9 +970,11 @@ function instantiateThreeObject(node) {
|
|
|
850
970
|
// replace $attached values with their instances
|
|
851
971
|
// we need to guarantee everything comes back as an array so we can spread $attachedArrays,
|
|
852
972
|
// so we'll use processPropAsArray
|
|
853
|
-
const argsWrappedInArrays = args.map((arg) => {
|
|
973
|
+
const argsWrappedInArrays = args.map((arg) => {
|
|
974
|
+
return processPropAsArray({ node, prop: arg });
|
|
975
|
+
});
|
|
854
976
|
let processedArgs = [];
|
|
855
|
-
argsWrappedInArrays.forEach(arr => {
|
|
977
|
+
argsWrappedInArrays.forEach((arr) => {
|
|
856
978
|
processedArgs = processedArgs.concat(arr);
|
|
857
979
|
});
|
|
858
980
|
const instance = new targetClass(...processedArgs);
|
|
@@ -954,7 +1076,7 @@ var MiniDom;
|
|
|
954
1076
|
get nextSibling() {
|
|
955
1077
|
if (!this.parentNode)
|
|
956
1078
|
return null;
|
|
957
|
-
const idx = this.parentNode.children.findIndex(n => n.uuid === this.uuid);
|
|
1079
|
+
const idx = this.parentNode.children.findIndex((n) => n.uuid === this.uuid);
|
|
958
1080
|
// return next sibling if we're present and not the last child of the parent
|
|
959
1081
|
if (idx !== -1 && idx < this.parentNode.children.length - 1) {
|
|
960
1082
|
return this.parentNode.children[idx + 1];
|
|
@@ -964,7 +1086,7 @@ var MiniDom;
|
|
|
964
1086
|
insertBefore(child, anchor) {
|
|
965
1087
|
child.removeAsChildFromAnyParents();
|
|
966
1088
|
child.parentNode = this;
|
|
967
|
-
const anchorIdx = this.children.findIndex(n => n.uuid === anchor?.uuid);
|
|
1089
|
+
const anchorIdx = this.children.findIndex((n) => n.uuid === anchor?.uuid);
|
|
968
1090
|
if (anchorIdx !== -1) {
|
|
969
1091
|
this.children.splice(anchorIdx, 0, child);
|
|
970
1092
|
}
|
|
@@ -973,7 +1095,7 @@ var MiniDom;
|
|
|
973
1095
|
}
|
|
974
1096
|
}
|
|
975
1097
|
removeChild(child) {
|
|
976
|
-
const idx = this.children.findIndex(n => n?.uuid === child?.uuid);
|
|
1098
|
+
const idx = this.children.findIndex((n) => n?.uuid === child?.uuid);
|
|
977
1099
|
if (idx !== -1) {
|
|
978
1100
|
this.children.splice(idx, 1);
|
|
979
1101
|
}
|
|
@@ -985,7 +1107,7 @@ var MiniDom;
|
|
|
985
1107
|
if (child) {
|
|
986
1108
|
// remove child from any other parents
|
|
987
1109
|
child.removeAsChildFromAnyParents();
|
|
988
|
-
// add to this node
|
|
1110
|
+
// add to this node
|
|
989
1111
|
child.parentNode = this;
|
|
990
1112
|
this.insertBefore(child, null);
|
|
991
1113
|
}
|
|
@@ -1013,11 +1135,15 @@ var MiniDom;
|
|
|
1013
1135
|
// TODO: depth-first vs breadth-first
|
|
1014
1136
|
walk(callback) {
|
|
1015
1137
|
const queue = [this, ...this.children];
|
|
1138
|
+
const traversed = [];
|
|
1016
1139
|
let canContinue = true;
|
|
1017
1140
|
while (queue.length && canContinue) {
|
|
1018
1141
|
const current = queue.shift();
|
|
1019
1142
|
if (current) {
|
|
1020
|
-
|
|
1143
|
+
if (traversed.includes(current))
|
|
1144
|
+
continue;
|
|
1145
|
+
traversed.push(current);
|
|
1146
|
+
queue.push(...current.children.filter((child) => !traversed.includes(child)));
|
|
1021
1147
|
canContinue = callback(current);
|
|
1022
1148
|
}
|
|
1023
1149
|
else {
|
|
@@ -1029,7 +1155,7 @@ var MiniDom;
|
|
|
1029
1155
|
// ====================
|
|
1030
1156
|
minidomType;
|
|
1031
1157
|
removeAsChildFromAnyParents() {
|
|
1032
|
-
allNodes.forEach(node => node.removeChild(this));
|
|
1158
|
+
allNodes.forEach((node) => node.removeChild(this));
|
|
1033
1159
|
}
|
|
1034
1160
|
}
|
|
1035
1161
|
MiniDom.BaseNode = BaseNode;
|
|
@@ -1053,8 +1179,8 @@ var MiniDom;
|
|
|
1053
1179
|
drop() {
|
|
1054
1180
|
super.drop();
|
|
1055
1181
|
// handle remove functions
|
|
1056
|
-
Object.keys(this.eventListenerRemoveFunctions).forEach(key => {
|
|
1057
|
-
this.eventListenerRemoveFunctions[key].forEach(func => func());
|
|
1182
|
+
Object.keys(this.eventListenerRemoveFunctions).forEach((key) => {
|
|
1183
|
+
this.eventListenerRemoveFunctions[key].forEach((func) => func());
|
|
1058
1184
|
});
|
|
1059
1185
|
}
|
|
1060
1186
|
}
|
|
@@ -1065,7 +1191,8 @@ var MiniDom;
|
|
|
1065
1191
|
class RendererRootNode extends MiniDom.RendererBaseNode {
|
|
1066
1192
|
constructor(options = {}, parent) {
|
|
1067
1193
|
super(options, parent);
|
|
1068
|
-
this.domElement =
|
|
1194
|
+
this.domElement =
|
|
1195
|
+
options.domElement ?? document.createElement('div');
|
|
1069
1196
|
}
|
|
1070
1197
|
domElement;
|
|
1071
1198
|
isLunchboxRootNode = true;
|
|
@@ -1082,7 +1209,8 @@ var MiniDom;
|
|
|
1082
1209
|
class RendererDomNode extends MiniDom.RendererBaseNode {
|
|
1083
1210
|
constructor(options = {}, parent) {
|
|
1084
1211
|
super(options, parent);
|
|
1085
|
-
this.domElement =
|
|
1212
|
+
this.domElement =
|
|
1213
|
+
options.domElement ?? document.createElement('div');
|
|
1086
1214
|
}
|
|
1087
1215
|
domElement;
|
|
1088
1216
|
}
|
|
@@ -1115,7 +1243,14 @@ let frameID;
|
|
|
1115
1243
|
const beforeRender = [];
|
|
1116
1244
|
const afterRender = [];
|
|
1117
1245
|
const update = (opts) => {
|
|
1118
|
-
|
|
1246
|
+
// request next frame
|
|
1247
|
+
frameID = requestAnimationFrame(() => update({
|
|
1248
|
+
app: opts.app,
|
|
1249
|
+
renderer: ensureRenderer.value?.instance,
|
|
1250
|
+
scene: ensuredScene.value.instance,
|
|
1251
|
+
camera: ensuredCamera.value?.instance,
|
|
1252
|
+
}));
|
|
1253
|
+
// prep options
|
|
1119
1254
|
const { app, renderer, scene, camera } = opts;
|
|
1120
1255
|
// BEFORE RENDER
|
|
1121
1256
|
beforeRender.forEach((cb) => {
|
|
@@ -1125,7 +1260,12 @@ const update = (opts) => {
|
|
|
1125
1260
|
});
|
|
1126
1261
|
// RENDER
|
|
1127
1262
|
if (renderer && scene && camera) {
|
|
1128
|
-
|
|
1263
|
+
if (app.customRender) {
|
|
1264
|
+
app.customRender(opts);
|
|
1265
|
+
}
|
|
1266
|
+
else {
|
|
1267
|
+
renderer.render(toRaw(scene), toRaw(camera));
|
|
1268
|
+
}
|
|
1129
1269
|
}
|
|
1130
1270
|
// AFTER RENDER
|
|
1131
1271
|
afterRender.forEach((cb) => {
|
|
@@ -1133,22 +1273,6 @@ const update = (opts) => {
|
|
|
1133
1273
|
cb(opts);
|
|
1134
1274
|
}
|
|
1135
1275
|
});
|
|
1136
|
-
/*
|
|
1137
|
-
frameID = requestAnimationFrame(() => update(renderer, scene, camera))
|
|
1138
|
-
|
|
1139
|
-
// Make sure we have all necessary components
|
|
1140
|
-
if (!renderer) {
|
|
1141
|
-
renderer = ensureRenderer().instance
|
|
1142
|
-
}
|
|
1143
|
-
if (!scene) {
|
|
1144
|
-
scene = ensureScene().instance
|
|
1145
|
-
}
|
|
1146
|
-
if (!camera) {
|
|
1147
|
-
camera = ensureCamera().instance
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
*/
|
|
1152
1276
|
};
|
|
1153
1277
|
const onBeforeRender = (cb, index = Infinity) => {
|
|
1154
1278
|
if (index === Infinity) {
|
|
@@ -1158,6 +1282,15 @@ const onBeforeRender = (cb, index = Infinity) => {
|
|
|
1158
1282
|
beforeRender.splice(index, 0, cb);
|
|
1159
1283
|
}
|
|
1160
1284
|
};
|
|
1285
|
+
const offBeforeRender = (cb) => {
|
|
1286
|
+
if (isFinite(cb)) {
|
|
1287
|
+
beforeRender.splice(cb, 1);
|
|
1288
|
+
}
|
|
1289
|
+
else {
|
|
1290
|
+
const idx = beforeRender.findIndex((v) => v == cb);
|
|
1291
|
+
beforeRender.splice(idx, 1);
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1161
1294
|
const onAfterRender = (cb, index = Infinity) => {
|
|
1162
1295
|
if (index === Infinity) {
|
|
1163
1296
|
afterRender.push(cb);
|
|
@@ -1166,6 +1299,15 @@ const onAfterRender = (cb, index = Infinity) => {
|
|
|
1166
1299
|
afterRender.splice(index, 0, cb);
|
|
1167
1300
|
}
|
|
1168
1301
|
};
|
|
1302
|
+
const offAfterRender = (cb) => {
|
|
1303
|
+
if (isFinite(cb)) {
|
|
1304
|
+
afterRender.splice(cb, 1);
|
|
1305
|
+
}
|
|
1306
|
+
else {
|
|
1307
|
+
const idx = afterRender.findIndex((v) => v == cb);
|
|
1308
|
+
afterRender.splice(idx, 1);
|
|
1309
|
+
}
|
|
1310
|
+
};
|
|
1169
1311
|
const cancelUpdate = () => {
|
|
1170
1312
|
if (frameID)
|
|
1171
1313
|
cancelAnimationFrame(frameID);
|
|
@@ -1271,10 +1413,7 @@ const internalLunchboxVueKeys = [
|
|
|
1271
1413
|
'src',
|
|
1272
1414
|
];
|
|
1273
1415
|
|
|
1274
|
-
const autoAttach = [
|
|
1275
|
-
'geometry',
|
|
1276
|
-
'material',
|
|
1277
|
-
];
|
|
1416
|
+
const autoAttach = ['geometry', 'material'];
|
|
1278
1417
|
const createElement = (type, isSVG, isCustomizedBuiltin, vnodeProps) => {
|
|
1279
1418
|
const options = { type };
|
|
1280
1419
|
if (vnodeProps) {
|
|
@@ -1289,7 +1428,7 @@ const createElement = (type, isSVG, isCustomizedBuiltin, vnodeProps) => {
|
|
|
1289
1428
|
// handle standard node
|
|
1290
1429
|
const node = createNode(options);
|
|
1291
1430
|
// autoattach
|
|
1292
|
-
autoAttach.forEach(key => {
|
|
1431
|
+
autoAttach.forEach((key) => {
|
|
1293
1432
|
if (type.toLowerCase().endsWith(key)) {
|
|
1294
1433
|
node.props.attach = key;
|
|
1295
1434
|
}
|
|
@@ -1329,17 +1468,19 @@ const insert = (child, parent, anchor) => {
|
|
|
1329
1468
|
}
|
|
1330
1469
|
// add to scene if parent is the wrapper node
|
|
1331
1470
|
if (child.metaType === 'standardMeta' &&
|
|
1332
|
-
child.type !== '
|
|
1471
|
+
child.type !== 'scene' &&
|
|
1333
1472
|
isLunchboxRootNode(effectiveParent)) {
|
|
1334
1473
|
// ensure scene exists
|
|
1335
|
-
const sceneNode =
|
|
1474
|
+
const sceneNode = ensuredScene.value;
|
|
1336
1475
|
if (sceneNode.instance && child) {
|
|
1337
1476
|
sceneNode.addChild(child);
|
|
1338
1477
|
}
|
|
1339
1478
|
if (child.instance &&
|
|
1340
1479
|
child.instance.isObject3D &&
|
|
1341
1480
|
sceneNode.instance) {
|
|
1342
|
-
sceneNode
|
|
1481
|
+
if (sceneNode !== child) {
|
|
1482
|
+
sceneNode.instance.add(child.instance);
|
|
1483
|
+
}
|
|
1343
1484
|
}
|
|
1344
1485
|
}
|
|
1345
1486
|
// add to hierarchy otherwise
|
|
@@ -1422,19 +1563,29 @@ function attachToParentInstance(child, parent, key, value) {
|
|
|
1422
1563
|
const remove = (node) => {
|
|
1423
1564
|
if (!node)
|
|
1424
1565
|
return;
|
|
1566
|
+
const overrideKeys = Object.keys(overrides);
|
|
1425
1567
|
// prep subtree
|
|
1426
1568
|
const subtree = [];
|
|
1427
|
-
node.walk(descendant => {
|
|
1569
|
+
node.walk((descendant) => {
|
|
1428
1570
|
subtree.push(descendant);
|
|
1429
1571
|
return true;
|
|
1430
1572
|
});
|
|
1431
1573
|
// clean up subtree
|
|
1432
1574
|
subtree.forEach((n) => {
|
|
1575
|
+
const overrideKey = overrideKeys.find((key) => overrides[key]?.uuid === n.uuid);
|
|
1576
|
+
// if this node is an override, remove it from the overrides list
|
|
1577
|
+
if (overrideKey) {
|
|
1578
|
+
overrides[overrideKey] = null;
|
|
1579
|
+
}
|
|
1433
1580
|
if (isLunchboxStandardNode(n)) {
|
|
1434
1581
|
// try to remove three object
|
|
1435
1582
|
n.instance?.removeFromParent?.();
|
|
1436
1583
|
// try to dispose three object
|
|
1437
|
-
const dispose =
|
|
1584
|
+
const dispose =
|
|
1585
|
+
// calling `dispose` on a scene triggers an error,
|
|
1586
|
+
// so let's ignore if this node is a scene
|
|
1587
|
+
n.type !== 'scene' &&
|
|
1588
|
+
n.instance?.dispose;
|
|
1438
1589
|
if (dispose)
|
|
1439
1590
|
dispose.bind(n.instance)();
|
|
1440
1591
|
n.instance = null;
|
|
@@ -1442,7 +1593,7 @@ const remove = (node) => {
|
|
|
1442
1593
|
// drop tree node
|
|
1443
1594
|
n.drop();
|
|
1444
1595
|
// remove Lunchbox node from main list
|
|
1445
|
-
const idx = allNodes.findIndex(v => v.uuid === n.uuid);
|
|
1596
|
+
const idx = allNodes.findIndex((v) => v.uuid === n.uuid);
|
|
1446
1597
|
if (idx !== -1) {
|
|
1447
1598
|
allNodes.splice(idx, 1);
|
|
1448
1599
|
}
|
|
@@ -1509,15 +1660,34 @@ const globals = {
|
|
|
1509
1660
|
dpr: ref(1),
|
|
1510
1661
|
inputActive,
|
|
1511
1662
|
mousePos,
|
|
1512
|
-
camera: createdCamera,
|
|
1513
|
-
renderer: createdRenderer,
|
|
1514
|
-
scene: createdScene,
|
|
1515
1663
|
};
|
|
1516
|
-
const camera = computed(() =>
|
|
1517
|
-
const renderer = computed(() =>
|
|
1518
|
-
const scene = computed(() =>
|
|
1664
|
+
const camera = computed(() => ensuredCamera.value?.instance ?? null);
|
|
1665
|
+
const renderer = computed(() => ensureRenderer.value?.instance ?? null);
|
|
1666
|
+
const scene = computed(() => ensuredScene.value.instance);
|
|
1667
|
+
// CUSTOM RENDER SUPPORT
|
|
1668
|
+
// ====================
|
|
1669
|
+
let app = null;
|
|
1670
|
+
let queuedCustomRenderFunction = null;
|
|
1671
|
+
/** Set a custom render function, overriding the Lunchbox app's default render function.
|
|
1672
|
+
* Changing this requires the user to manually render their scene.
|
|
1673
|
+
*/
|
|
1674
|
+
const setCustomRender = (render) => {
|
|
1675
|
+
if (app)
|
|
1676
|
+
app.setCustomRender(render);
|
|
1677
|
+
else
|
|
1678
|
+
queuedCustomRenderFunction = render;
|
|
1679
|
+
};
|
|
1680
|
+
/** Clear the active app's custom render function. */
|
|
1681
|
+
const clearCustomRender = () => {
|
|
1682
|
+
if (app)
|
|
1683
|
+
app.clearCustomRender();
|
|
1684
|
+
else
|
|
1685
|
+
queuedCustomRenderFunction = null;
|
|
1686
|
+
};
|
|
1687
|
+
// CREATE APP
|
|
1688
|
+
// ====================
|
|
1519
1689
|
const createApp = (root) => {
|
|
1520
|
-
|
|
1690
|
+
app = createRenderer(nodeOps).createApp(root);
|
|
1521
1691
|
// register all components
|
|
1522
1692
|
Object.keys(components).forEach((key) => {
|
|
1523
1693
|
app.component(key, components[key]);
|
|
@@ -1540,16 +1710,24 @@ const createApp = (root) => {
|
|
|
1540
1710
|
};
|
|
1541
1711
|
// embed .extend function
|
|
1542
1712
|
app.extend = (targets) => {
|
|
1543
|
-
extend({ app, ...targets });
|
|
1713
|
+
extend({ app: app, ...targets });
|
|
1544
1714
|
return app;
|
|
1545
1715
|
};
|
|
1546
|
-
//
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
//
|
|
1716
|
+
// prep for custom render support
|
|
1717
|
+
app.setCustomRender = (newRender) => {
|
|
1718
|
+
app.customRender = newRender;
|
|
1719
|
+
};
|
|
1720
|
+
// add queued custom render if we have one
|
|
1721
|
+
if (queuedCustomRenderFunction) {
|
|
1722
|
+
app.setCustomRender(queuedCustomRenderFunction);
|
|
1723
|
+
queuedCustomRenderFunction = null;
|
|
1724
|
+
}
|
|
1725
|
+
// add custom render removal
|
|
1726
|
+
app.clearCustomRender = () => {
|
|
1727
|
+
app.customRender = null;
|
|
1728
|
+
};
|
|
1551
1729
|
// done
|
|
1552
1730
|
return app;
|
|
1553
1731
|
};
|
|
1554
1732
|
|
|
1555
|
-
export { camera, createApp, find, globals, lunchboxRootNode as lunchboxTree, onAfterRender, onBeforeRender, renderer, scene,
|
|
1733
|
+
export { camera, clearCustomRender, createApp, find, globals, lunchboxRootNode as lunchboxTree, offAfterRender, offBeforeRender, onAfterRender, onBeforeRender, renderer, scene, setCustomRender };
|