lunchboxjs 0.2.1001-beta.0 → 0.2.1001-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.
- package/README.md +0 -47
- package/dist/lunchboxjs.js +292 -347
- package/dist/lunchboxjs.min.js +1 -1
- package/dist/lunchboxjs.module.js +292 -342
- package/package.json +3 -2
- package/src/components/LunchboxEventHandlers.tsx +239 -0
- package/src/components/LunchboxWrapper/LunchboxWrapper.tsx +7 -6
- package/src/components/autoGeneratedComponents.ts +1 -1
- package/src/components/index.ts +2 -2
- package/src/core/createNode.ts +1 -1
- package/src/core/extend.ts +1 -1
- package/src/core/instantiateThreeObject/index.ts +1 -1
- package/src/core/instantiateThreeObject/processProps.ts +1 -1
- package/src/core/interaction.ts +55 -0
- package/src/core/minidom.ts +1 -1
- package/src/core/update.ts +2 -30
- package/src/core/updateObjectProp.ts +5 -14
- package/src/index.ts +12 -3
- package/src/keys.ts +1 -0
- package/src/nodeOps/index.ts +70 -57
- package/src/types.ts +0 -8
- package/src/components/catalogue.ts +0 -3
- package/src/core/interaction/index.ts +0 -102
- package/src/core/interaction/input.ts +0 -4
- package/src/core/interaction/interactables.ts +0 -14
- package/src/core/interaction/setupAutoRaycaster.ts +0 -232
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isRef, isVNode, inject,
|
|
1
|
+
import { isRef, isVNode, inject, toRaw, defineComponent, createVNode, resolveComponent, ref, watch, onBeforeUnmount, reactive, onMounted, Fragment, mergeProps, h, createRenderer, computed } from 'vue';
|
|
2
2
|
import * as THREE from 'three';
|
|
3
3
|
import { get, isNumber, set } from 'lodash';
|
|
4
4
|
|
|
@@ -119,6 +119,7 @@ const appKey = Symbol();
|
|
|
119
119
|
const appRenderersKey = Symbol();
|
|
120
120
|
const appSceneKey = Symbol();
|
|
121
121
|
const appCameraKey = Symbol();
|
|
122
|
+
const lunchboxInteractables = Symbol();
|
|
122
123
|
const startCallbackKey = Symbol();
|
|
123
124
|
|
|
124
125
|
const ensuredCamera = () => inject(appCameraKey); // ENSURE RENDERER
|
|
@@ -129,215 +130,11 @@ const ensureRenderer = () => inject(appRenderersKey); // ENSURE SCENE
|
|
|
129
130
|
|
|
130
131
|
const ensuredScene = () => inject(appSceneKey);
|
|
131
132
|
|
|
132
|
-
const interactables = [];
|
|
133
|
-
const addInteractable = target => {
|
|
134
|
-
interactables.push(target);
|
|
135
|
-
};
|
|
136
|
-
const removeInteractable = target => {
|
|
137
|
-
const idx = interactables.indexOf(target);
|
|
138
|
-
|
|
139
|
-
if (idx !== -1) {
|
|
140
|
-
interactables.splice(idx, 1);
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
/** Mouse is down, touch is pressed, etc */
|
|
145
|
-
|
|
146
|
-
const inputActive = ref(false);
|
|
147
|
-
|
|
148
|
-
// let mouseDownListener: (event: MouseEvent) => void
|
|
149
|
-
// let mouseUpListener: (event: MouseEvent) => void
|
|
150
|
-
|
|
151
|
-
const mousePos = ref({
|
|
152
|
-
x: Infinity,
|
|
153
|
-
y: Infinity
|
|
154
|
-
}); // let autoRaycasterEventsInitialized = false
|
|
155
|
-
// let frameID: number
|
|
156
|
-
// export const setupAutoRaycaster = (node: Lunch.Node<THREE.Raycaster>) => {
|
|
157
|
-
// const instance = node.instance
|
|
158
|
-
// if (!instance) return
|
|
159
|
-
// // TODO: inject doesn't work here. replace this raycaster with a component so we can
|
|
160
|
-
// // `inject` in `setup`?
|
|
161
|
-
// const appLevelGlobals = { dpr: window.devicePixelRatio } //useGlobals()
|
|
162
|
-
// // add mouse events once renderer is ready
|
|
163
|
-
// let stopWatcher: WatchStopHandle | null = null
|
|
164
|
-
// stopWatcher = watch(
|
|
165
|
-
// () => ensureRenderer.value,
|
|
166
|
-
// (renderer) => {
|
|
167
|
-
// // make sure renderer exists
|
|
168
|
-
// if (!renderer?.instance) return
|
|
169
|
-
// // cancel early if autoraycaster exists
|
|
170
|
-
// if (autoRaycasterEventsInitialized) {
|
|
171
|
-
// if (stopWatcher) stopWatcher()
|
|
172
|
-
// return
|
|
173
|
-
// }
|
|
174
|
-
// // create mouse events
|
|
175
|
-
// mouseMoveListener = (evt) => {
|
|
176
|
-
// const screenWidth =
|
|
177
|
-
// (renderer.instance!.domElement.width ?? 1) /
|
|
178
|
-
// appLevelGlobals.dpr
|
|
179
|
-
// const screenHeight =
|
|
180
|
-
// (renderer.instance!.domElement.height ?? 1) /
|
|
181
|
-
// appLevelGlobals.dpr
|
|
182
|
-
// mousePos.value.x = (evt.offsetX / screenWidth) * 2 - 1
|
|
183
|
-
// mousePos.value.y = -(evt.offsetY / screenHeight) * 2 + 1
|
|
184
|
-
// }
|
|
185
|
-
// mouseDownListener = () => (inputActive.value = true)
|
|
186
|
-
// mouseUpListener = () => (inputActive.value = false)
|
|
187
|
-
// // add mouse events
|
|
188
|
-
// renderer.instance.domElement.addEventListener(
|
|
189
|
-
// 'mousemove',
|
|
190
|
-
// mouseMoveListener
|
|
191
|
-
// )
|
|
192
|
-
// renderer.instance.domElement.addEventListener(
|
|
193
|
-
// 'mousedown',
|
|
194
|
-
// mouseDownListener
|
|
195
|
-
// )
|
|
196
|
-
// renderer.instance.domElement.addEventListener(
|
|
197
|
-
// 'mouseup',
|
|
198
|
-
// mouseUpListener
|
|
199
|
-
// )
|
|
200
|
-
// // TODO: add touch events
|
|
201
|
-
// // process mouse events asynchronously, whenever the mouse state changes
|
|
202
|
-
// watch(
|
|
203
|
-
// () => [inputActive.value, mousePos.value.x, mousePos.value.y],
|
|
204
|
-
// () => {
|
|
205
|
-
// if (frameID) cancelAnimationFrame(frameID)
|
|
206
|
-
// frameID = requestAnimationFrame(() => {
|
|
207
|
-
// autoRaycasterBeforeRender()
|
|
208
|
-
// })
|
|
209
|
-
// }
|
|
210
|
-
// )
|
|
211
|
-
// // mark complete
|
|
212
|
-
// autoRaycasterEventsInitialized = true
|
|
213
|
-
// // cancel setup watcher
|
|
214
|
-
// if (stopWatcher) {
|
|
215
|
-
// stopWatcher()
|
|
216
|
-
// }
|
|
217
|
-
// },
|
|
218
|
-
// { immediate: true }
|
|
219
|
-
// )
|
|
220
|
-
// }
|
|
221
|
-
// AUTO-RAYCASTER CALLBACK
|
|
222
|
-
// ====================
|
|
223
|
-
|
|
224
|
-
let currentIntersections = []; // const autoRaycasterBeforeRender = () => {
|
|
225
|
-
// // setup
|
|
226
|
-
// const raycaster = ensuredRaycaster.value?.instance
|
|
227
|
-
// const camera = ensuredCamera.value?.instance
|
|
228
|
-
// if (!raycaster || !camera) return
|
|
229
|
-
// raycaster.setFromCamera(globals.mousePos.value, camera)
|
|
230
|
-
// const intersections = raycaster.intersectObjects(
|
|
231
|
-
// interactables.map((v) => v.instance as any as THREE.Object3D)
|
|
232
|
-
// )
|
|
233
|
-
// let enterValues: Array<Intersection<THREE.Object3D>> = [],
|
|
234
|
-
// sameValues: Array<Intersection<THREE.Object3D>> = [],
|
|
235
|
-
// leaveValues: Array<Intersection<THREE.Object3D>> = [],
|
|
236
|
-
// entering: Array<{
|
|
237
|
-
// element: Lunch.Node
|
|
238
|
-
// intersection: Intersection<THREE.Object3D>
|
|
239
|
-
// }> = [],
|
|
240
|
-
// staying: Array<{
|
|
241
|
-
// element: Lunch.Node
|
|
242
|
-
// intersection: Intersection<THREE.Object3D>
|
|
243
|
-
// }> = []
|
|
244
|
-
// // intersection arrays
|
|
245
|
-
// leaveValues = currentIntersections.map((v) => v.intersection)
|
|
246
|
-
// // element arrays
|
|
247
|
-
// intersections?.forEach((intersection) => {
|
|
248
|
-
// const currentIdx = currentIntersections.findIndex(
|
|
249
|
-
// (v) => v.intersection.object === intersection.object
|
|
250
|
-
// )
|
|
251
|
-
// if (currentIdx === -1) {
|
|
252
|
-
// // new intersection
|
|
253
|
-
// enterValues.push(intersection)
|
|
254
|
-
// const found = interactables.find(
|
|
255
|
-
// (v) => v.instance?.uuid === intersection.object.uuid
|
|
256
|
-
// )
|
|
257
|
-
// if (found) {
|
|
258
|
-
// entering.push({ element: found, intersection })
|
|
259
|
-
// }
|
|
260
|
-
// } else {
|
|
261
|
-
// // existing intersection
|
|
262
|
-
// sameValues.push(intersection)
|
|
263
|
-
// const found = interactables.find(
|
|
264
|
-
// (v) => v.instance?.uuid === intersection.object.uuid
|
|
265
|
-
// )
|
|
266
|
-
// if (found) {
|
|
267
|
-
// staying.push({ element: found, intersection })
|
|
268
|
-
// }
|
|
269
|
-
// }
|
|
270
|
-
// // this is a current intersection, so it won't be in our `leave` array
|
|
271
|
-
// const leaveIdx = leaveValues.findIndex(
|
|
272
|
-
// (v) => v.object.uuid === intersection.object.uuid
|
|
273
|
-
// )
|
|
274
|
-
// if (leaveIdx !== -1) {
|
|
275
|
-
// leaveValues.splice(leaveIdx, 1)
|
|
276
|
-
// }
|
|
277
|
-
// })
|
|
278
|
-
// const leaving: Array<{
|
|
279
|
-
// element: Lunch.Node
|
|
280
|
-
// intersection: Intersection<THREE.Object3D>
|
|
281
|
-
// }> = leaveValues.map((intersection) => {
|
|
282
|
-
// return {
|
|
283
|
-
// element: interactables.find(
|
|
284
|
-
// (interactable) =>
|
|
285
|
-
// interactable.instance?.uuid === intersection.object.uuid
|
|
286
|
-
// ) as any as Lunch.Node,
|
|
287
|
-
// intersection,
|
|
288
|
-
// }
|
|
289
|
-
// })
|
|
290
|
-
// // new interactions
|
|
291
|
-
// entering.forEach(({ element, intersection }) => {
|
|
292
|
-
// fireEventsFromIntersections({
|
|
293
|
-
// element,
|
|
294
|
-
// eventKeys: ['onPointerEnter'],
|
|
295
|
-
// intersection,
|
|
296
|
-
// })
|
|
297
|
-
// })
|
|
298
|
-
// // unchanged interactions
|
|
299
|
-
// staying.forEach(({ element, intersection }) => {
|
|
300
|
-
// const eventKeys: Array<Lunch.EventKey> = [
|
|
301
|
-
// 'onPointerOver',
|
|
302
|
-
// 'onPointerMove',
|
|
303
|
-
// ]
|
|
304
|
-
// fireEventsFromIntersections({ element, eventKeys, intersection })
|
|
305
|
-
// })
|
|
306
|
-
// // exited interactions
|
|
307
|
-
// leaving.forEach(({ element, intersection }) => {
|
|
308
|
-
// const eventKeys: Array<Lunch.EventKey> = [
|
|
309
|
-
// 'onPointerLeave',
|
|
310
|
-
// 'onPointerOut',
|
|
311
|
-
// ]
|
|
312
|
-
// fireEventsFromIntersections({ element, eventKeys, intersection })
|
|
313
|
-
// })
|
|
314
|
-
// currentIntersections = ([] as any).concat(entering, staying)
|
|
315
|
-
// }
|
|
316
|
-
// utility function for firing multiple callbacks and multiple events on a Lunchbox.Element
|
|
317
|
-
// const fireEventsFromIntersections = ({
|
|
318
|
-
// element,
|
|
319
|
-
// eventKeys,
|
|
320
|
-
// intersection,
|
|
321
|
-
// }: {
|
|
322
|
-
// element: Lunch.Node
|
|
323
|
-
// eventKeys: Array<Lunch.EventKey>
|
|
324
|
-
// intersection: Intersection<THREE.Object3D>
|
|
325
|
-
// }) => {
|
|
326
|
-
// if (!element) return
|
|
327
|
-
// eventKeys.forEach((eventKey) => {
|
|
328
|
-
// if (element.eventListeners[eventKey]) {
|
|
329
|
-
// element.eventListeners[eventKey].forEach((cb) => {
|
|
330
|
-
// cb({ intersection })
|
|
331
|
-
// })
|
|
332
|
-
// }
|
|
333
|
-
// })
|
|
334
|
-
// }
|
|
335
|
-
|
|
336
133
|
/** Add an event listener to the given node. Also creates the event teardown function and any necessary raycaster/interaction dictionary updates. */
|
|
337
|
-
|
|
338
134
|
function addEventListener({
|
|
339
135
|
node,
|
|
340
136
|
key,
|
|
137
|
+
interactables,
|
|
341
138
|
value
|
|
342
139
|
}) {
|
|
343
140
|
// create new records for this key if needed
|
|
@@ -353,37 +150,18 @@ function addEventListener({
|
|
|
353
150
|
node.eventListeners[key].push(value); // if we need it, let's get/create the main raycaster
|
|
354
151
|
|
|
355
152
|
if (interactionsRequiringRaycaster.includes(key)) {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
if (key === 'onClick' || key === 'onPointerDown' || key === 'onPointerUp') {
|
|
367
|
-
const stop = watch(() => inputActive.value, isDown => {
|
|
368
|
-
const idx = currentIntersections.map(v => v.element).findIndex(v => v.instance && v.instance.uuid === node.instance?.uuid);
|
|
369
|
-
|
|
370
|
-
if (idx !== -1) {
|
|
371
|
-
if (isDown && (key === 'onClick' || key === 'onPointerDown')) {
|
|
372
|
-
node.eventListeners[key].forEach(func => {
|
|
373
|
-
func({
|
|
374
|
-
intersection: currentIntersections[idx].intersection
|
|
375
|
-
});
|
|
376
|
-
});
|
|
377
|
-
} else if (!isDown && key === 'onPointerUp') {
|
|
378
|
-
node.eventListeners[key].forEach(func => {
|
|
379
|
-
func({
|
|
380
|
-
intersection: currentIntersections[idx].intersection
|
|
381
|
-
});
|
|
382
|
-
});
|
|
153
|
+
if (node.instance && !interactables.value.includes(node)) {
|
|
154
|
+
// add to interactables
|
|
155
|
+
interactables.value.push(node);
|
|
156
|
+
node.eventListenerRemoveFunctions[key].push(() => {
|
|
157
|
+
// remove from interactables
|
|
158
|
+
const idx = interactables.value.indexOf(node);
|
|
159
|
+
|
|
160
|
+
if (idx !== -1) {
|
|
161
|
+
interactables.value.splice(idx, 1);
|
|
383
162
|
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
node.eventListenerRemoveFunctions[key].push(stop);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
387
165
|
}
|
|
388
166
|
|
|
389
167
|
return node;
|
|
@@ -477,6 +255,195 @@ const LunchboxScene = defineComponent({
|
|
|
477
255
|
|
|
478
256
|
});
|
|
479
257
|
|
|
258
|
+
const LunchboxEventHandlers = defineComponent({
|
|
259
|
+
name: 'LunchboxEventHandlers',
|
|
260
|
+
|
|
261
|
+
setup() {
|
|
262
|
+
const interactables = useLunchboxInteractables();
|
|
263
|
+
const camera = useCamera();
|
|
264
|
+
const renderer = useRenderer();
|
|
265
|
+
const globals = useGlobals();
|
|
266
|
+
const mousePos = ref({
|
|
267
|
+
x: Infinity,
|
|
268
|
+
y: Infinity
|
|
269
|
+
});
|
|
270
|
+
const inputActive = ref(false);
|
|
271
|
+
let currentIntersections = [];
|
|
272
|
+
const raycaster = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3(0, 0, -1));
|
|
273
|
+
|
|
274
|
+
const fireEventsFromIntersections = ({
|
|
275
|
+
element,
|
|
276
|
+
eventKeys,
|
|
277
|
+
intersection
|
|
278
|
+
}) => {
|
|
279
|
+
if (!element) return;
|
|
280
|
+
eventKeys.forEach(eventKey => {
|
|
281
|
+
if (element.eventListeners[eventKey]) {
|
|
282
|
+
element.eventListeners[eventKey].forEach(cb => {
|
|
283
|
+
cb({
|
|
284
|
+
intersection
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
}; // add mouse listener to renderer DOM element when the element is ready
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
const stopWatch = watch(renderer, v => {
|
|
293
|
+
if (!v?.domElement) return; // we have a DOM element, so let's add mouse listeners
|
|
294
|
+
|
|
295
|
+
const {
|
|
296
|
+
domElement
|
|
297
|
+
} = v;
|
|
298
|
+
|
|
299
|
+
const mouseMoveListener = evt => {
|
|
300
|
+
const screenWidth = (domElement.width ?? 1) / globals.dpr;
|
|
301
|
+
const screenHeight = (domElement.height ?? 1) / globals.dpr;
|
|
302
|
+
mousePos.value.x = evt.offsetX / screenWidth * 2 - 1;
|
|
303
|
+
mousePos.value.y = -(evt.offsetY / screenHeight) * 2 + 1;
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const mouseDownListener = () => inputActive.value = true;
|
|
307
|
+
|
|
308
|
+
const mouseUpListener = () => inputActive.value = false; // add mouse events
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
domElement.addEventListener('pointermove', mouseMoveListener);
|
|
312
|
+
domElement.addEventListener('pointerdown', mouseDownListener);
|
|
313
|
+
domElement.addEventListener('pointerup', mouseUpListener); // stop the watcher
|
|
314
|
+
|
|
315
|
+
stopWatch();
|
|
316
|
+
}, {
|
|
317
|
+
immediate: true
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
const update = () => {
|
|
321
|
+
const c = camera.value;
|
|
322
|
+
if (!c) return;
|
|
323
|
+
raycaster.setFromCamera(mousePos.value, c);
|
|
324
|
+
const intersections = raycaster.intersectObjects(interactables?.value.map(v => v.instance) ?? []);
|
|
325
|
+
let leaveValues = [],
|
|
326
|
+
entering = [],
|
|
327
|
+
staying = []; // intersection arrays
|
|
328
|
+
|
|
329
|
+
leaveValues = currentIntersections.map(v => v.intersection); // element arrays
|
|
330
|
+
|
|
331
|
+
intersections?.forEach(intersection => {
|
|
332
|
+
const currentIdx = currentIntersections.findIndex(v => v.intersection.object === intersection.object);
|
|
333
|
+
|
|
334
|
+
if (currentIdx === -1) {
|
|
335
|
+
const found = interactables?.value.find(v => v.instance?.uuid === intersection.object.uuid);
|
|
336
|
+
|
|
337
|
+
if (found) {
|
|
338
|
+
entering.push({
|
|
339
|
+
element: found,
|
|
340
|
+
intersection
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
} else {
|
|
344
|
+
const found = interactables?.value.find(v => v.instance?.uuid === intersection.object.uuid);
|
|
345
|
+
|
|
346
|
+
if (found) {
|
|
347
|
+
staying.push({
|
|
348
|
+
element: found,
|
|
349
|
+
intersection
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
} // this is a current intersection, so it won't be in our `leave` array
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
const leaveIdx = leaveValues.findIndex(v => v.object.uuid === intersection.object.uuid);
|
|
356
|
+
|
|
357
|
+
if (leaveIdx !== -1) {
|
|
358
|
+
leaveValues.splice(leaveIdx, 1);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
const leaving = leaveValues.map(intersection => {
|
|
362
|
+
return {
|
|
363
|
+
element: interactables?.value.find(interactable => interactable.instance?.uuid === intersection.object.uuid),
|
|
364
|
+
intersection
|
|
365
|
+
};
|
|
366
|
+
}); // new interactions
|
|
367
|
+
|
|
368
|
+
entering.forEach(({
|
|
369
|
+
element,
|
|
370
|
+
intersection
|
|
371
|
+
}) => {
|
|
372
|
+
fireEventsFromIntersections({
|
|
373
|
+
element,
|
|
374
|
+
eventKeys: ['onPointerEnter'],
|
|
375
|
+
intersection
|
|
376
|
+
});
|
|
377
|
+
}); // unchanged interactions
|
|
378
|
+
|
|
379
|
+
staying.forEach(({
|
|
380
|
+
element,
|
|
381
|
+
intersection
|
|
382
|
+
}) => {
|
|
383
|
+
const eventKeys = ['onPointerOver', 'onPointerMove'];
|
|
384
|
+
fireEventsFromIntersections({
|
|
385
|
+
element,
|
|
386
|
+
eventKeys,
|
|
387
|
+
intersection
|
|
388
|
+
});
|
|
389
|
+
}); // exited interactions
|
|
390
|
+
|
|
391
|
+
leaving.forEach(({
|
|
392
|
+
element,
|
|
393
|
+
intersection
|
|
394
|
+
}) => {
|
|
395
|
+
const eventKeys = ['onPointerLeave', 'onPointerOut'];
|
|
396
|
+
fireEventsFromIntersections({
|
|
397
|
+
element,
|
|
398
|
+
eventKeys,
|
|
399
|
+
intersection
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
currentIntersections = [].concat(entering, staying);
|
|
403
|
+
}; // update function
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
onBeforeRender(update);
|
|
407
|
+
|
|
408
|
+
const teardown = () => offBeforeRender(update);
|
|
409
|
+
|
|
410
|
+
onBeforeUnmount(teardown);
|
|
411
|
+
const clickEventKeys = ['onClick', 'onPointerDown', 'onPointerUp'];
|
|
412
|
+
watch(inputActive, isDown => {
|
|
413
|
+
// meshes with multiple intersections receive multiple callbacks by default -
|
|
414
|
+
// let's make it so they only receive one callback of each type per frame.
|
|
415
|
+
// (ie usually when you click on a mesh, you expect only one click event to fire, even
|
|
416
|
+
// if there are technically multiple intersections with that mesh)
|
|
417
|
+
const uuidsInteractedWithThisFrame = [];
|
|
418
|
+
currentIntersections.forEach(v => {
|
|
419
|
+
clickEventKeys.forEach(key => {
|
|
420
|
+
const id = v.element.uuid + key;
|
|
421
|
+
|
|
422
|
+
if (isDown && (key === 'onClick' || key === 'onPointerDown')) {
|
|
423
|
+
if (!uuidsInteractedWithThisFrame.includes(id)) {
|
|
424
|
+
v.element.eventListeners[key]?.forEach(cb => cb({
|
|
425
|
+
intersection: v.intersection
|
|
426
|
+
}));
|
|
427
|
+
uuidsInteractedWithThisFrame.push(id);
|
|
428
|
+
}
|
|
429
|
+
} else if (!isDown && key === 'onPointerUp') {
|
|
430
|
+
if (!uuidsInteractedWithThisFrame.includes(id)) {
|
|
431
|
+
v.element.eventListeners[key]?.forEach(cb => cb({
|
|
432
|
+
intersection: v.intersection
|
|
433
|
+
}));
|
|
434
|
+
uuidsInteractedWithThisFrame.push(id);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
}); // return arbitrary object to ensure instantiation
|
|
440
|
+
// TODO: why can't we return a <raycaster/> here?
|
|
441
|
+
|
|
442
|
+
return () => createVNode(resolveComponent("object3D"), null, null);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
});
|
|
446
|
+
|
|
480
447
|
/** fixed & fill styling for container */
|
|
481
448
|
|
|
482
449
|
const fillStyle = position => {
|
|
@@ -529,9 +496,10 @@ const LunchboxWrapper = defineComponent({
|
|
|
529
496
|
|
|
530
497
|
if (props.r3f && THREE?.ColorManagement) {
|
|
531
498
|
THREE.ColorManagement.legacyMode = false;
|
|
532
|
-
}
|
|
533
|
-
// ====================
|
|
499
|
+
}
|
|
534
500
|
|
|
501
|
+
const interactables = useLunchboxInteractables(); // MOUNT
|
|
502
|
+
// ====================
|
|
535
503
|
|
|
536
504
|
onMounted(async () => {
|
|
537
505
|
// canvas needs to exist (or user needs to handle it on their own)
|
|
@@ -567,23 +535,17 @@ const LunchboxWrapper = defineComponent({
|
|
|
567
535
|
updateGlobals?.({
|
|
568
536
|
dpr
|
|
569
537
|
});
|
|
570
|
-
console.log(1);
|
|
571
538
|
|
|
572
539
|
while (!renderer.value?.$el?.instance && // TODO: remove `as any`
|
|
573
540
|
!renderer.value?.component?.ctx.$el?.instance) {
|
|
574
|
-
console.log(2);
|
|
575
541
|
await new Promise(r => requestAnimationFrame(r));
|
|
576
542
|
}
|
|
577
543
|
|
|
578
|
-
console.log(3);
|
|
579
|
-
|
|
580
544
|
while (!scene.value?.$el?.instance && // TODO: remove `as any`
|
|
581
545
|
!scene.value?.component?.ctx.$el?.instance) {
|
|
582
|
-
console.log(4);
|
|
583
546
|
await new Promise(r => requestAnimationFrame(r));
|
|
584
547
|
}
|
|
585
548
|
|
|
586
|
-
console.log(5);
|
|
587
549
|
const normalizedRenderer = renderer.value?.$el?.instance ?? renderer.value?.component?.ctx.$el?.instance;
|
|
588
550
|
normalizedRenderer.setPixelRatio(globals.dpr);
|
|
589
551
|
const normalizedScene = scene.value?.$el?.instance ?? scene.value?.component?.ctx.$el?.instance;
|
|
@@ -689,7 +651,7 @@ const LunchboxWrapper = defineComponent({
|
|
|
689
651
|
}, consolidatedCameraProperties), null) : createVNode(resolveComponent("perspectiveCamera"), mergeProps({
|
|
690
652
|
"ref": camera,
|
|
691
653
|
"args": props.cameraArgs ?? [props.r3f ? 75 : 45, 0.5625, 1, 1000]
|
|
692
|
-
}, consolidatedCameraProperties), null)]);
|
|
654
|
+
}, consolidatedCameraProperties), null), interactables?.value.length && createVNode(LunchboxEventHandlers, null, null)]);
|
|
693
655
|
}
|
|
694
656
|
|
|
695
657
|
});
|
|
@@ -704,7 +666,7 @@ const autoGeneratedComponents = [// ThreeJS basics
|
|
|
704
666
|
'light', 'spotLightShadow', 'spotLight', 'pointLight', 'rectAreaLight', 'hemisphereLight', 'directionalLightShadow', 'directionalLight', 'ambientLight', 'lightShadow', 'ambientLightProbe', 'hemisphereLightProbe', 'lightProbe', // textures
|
|
705
667
|
'texture', 'videoTexture', 'dataTexture', 'dataTexture3D', 'compressedTexture', 'cubeTexture', 'canvasTexture', 'depthTexture', // Texture loaders
|
|
706
668
|
'textureLoader', // misc
|
|
707
|
-
'group', 'catmullRomCurve3', 'points', // helpers
|
|
669
|
+
'group', 'catmullRomCurve3', 'points', 'raycaster', // helpers
|
|
708
670
|
'cameraHelper', // cameras
|
|
709
671
|
'camera', 'perspectiveCamera', 'orthographicCamera', 'cubeCamera', 'arrayCamera', // renderers
|
|
710
672
|
'webGLRenderer'
|
|
@@ -739,7 +701,6 @@ planeHelper: PlaneHelperProps
|
|
|
739
701
|
arrowHelper: ArrowHelperProps
|
|
740
702
|
axesHelper: AxesHelperProps
|
|
741
703
|
// misc
|
|
742
|
-
raycaster: RaycasterProps
|
|
743
704
|
vector2: Vector2Props
|
|
744
705
|
vector3: Vector3Props
|
|
745
706
|
vector4: Vector4Props
|
|
@@ -756,7 +717,7 @@ shape: ShapeProps
|
|
|
756
717
|
*/
|
|
757
718
|
];
|
|
758
719
|
|
|
759
|
-
const catalogue = {};
|
|
720
|
+
const catalogue = {}; // component creation utility
|
|
760
721
|
|
|
761
722
|
const createComponent$1 = tag => defineComponent({
|
|
762
723
|
inheritAttrs: false,
|
|
@@ -1152,10 +1113,6 @@ function isMinidomNode(item) {
|
|
|
1152
1113
|
return item?.minidomType === 'RendererNode';
|
|
1153
1114
|
}
|
|
1154
1115
|
|
|
1155
|
-
// let watchStopHandle: WatchStopHandle
|
|
1156
|
-
// export const beforeRender = [] as Lunch.UpdateCallback[]
|
|
1157
|
-
// export const afterRender = [] as Lunch.UpdateCallback[]
|
|
1158
|
-
|
|
1159
1116
|
const requestUpdate = opts => {
|
|
1160
1117
|
if (typeof opts.app.config.globalProperties.lunchbox.frameId === 'number') {
|
|
1161
1118
|
cancelAnimationFrame(opts.app.config.globalProperties.lunchbox.frameId);
|
|
@@ -1244,22 +1201,7 @@ const onAfterRender = (cb, index = Infinity) => {
|
|
|
1244
1201
|
|
|
1245
1202
|
const offAfterRender = cb => {
|
|
1246
1203
|
useBeforeRender().offBeforeRender?.(cb);
|
|
1247
|
-
}; //
|
|
1248
|
-
// if (index === Infinity) {
|
|
1249
|
-
// afterRender.push(cb)
|
|
1250
|
-
// } else {
|
|
1251
|
-
// afterRender.splice(index, 0, cb)
|
|
1252
|
-
// }
|
|
1253
|
-
// }
|
|
1254
|
-
// export const offAfterRender = (cb: Lunch.UpdateCallback | number) => {
|
|
1255
|
-
// if (isFinite(cb as number)) {
|
|
1256
|
-
// afterRender.splice(cb as number, 1)
|
|
1257
|
-
// } else {
|
|
1258
|
-
// const idx = afterRender.findIndex((v) => v == cb)
|
|
1259
|
-
// afterRender.splice(idx, 1)
|
|
1260
|
-
// }
|
|
1261
|
-
// }
|
|
1262
|
-
// TODO: document
|
|
1204
|
+
}; // TODO: document
|
|
1263
1205
|
|
|
1264
1206
|
const useCancelUpdate = () => {
|
|
1265
1207
|
const frameId = inject(frameIdKey);
|
|
@@ -1281,28 +1223,12 @@ const cancelUpdateSource = () => {
|
|
|
1281
1223
|
useCancelUpdateSource()?.();
|
|
1282
1224
|
};
|
|
1283
1225
|
|
|
1284
|
-
/** Update the given node so all of its props are current. */
|
|
1285
|
-
|
|
1286
|
-
function updateAllObjectProps({
|
|
1287
|
-
node
|
|
1288
|
-
}) {
|
|
1289
|
-
// set props
|
|
1290
|
-
const props = node.props || {};
|
|
1291
|
-
let output = node;
|
|
1292
|
-
Object.keys(props).forEach(key => {
|
|
1293
|
-
output = updateObjectProp({
|
|
1294
|
-
node,
|
|
1295
|
-
key,
|
|
1296
|
-
value: props[key]
|
|
1297
|
-
});
|
|
1298
|
-
});
|
|
1299
|
-
return output;
|
|
1300
|
-
}
|
|
1301
1226
|
/** Update a single prop on a given node. */
|
|
1302
1227
|
|
|
1303
1228
|
function updateObjectProp({
|
|
1304
1229
|
node,
|
|
1305
1230
|
key,
|
|
1231
|
+
interactables,
|
|
1306
1232
|
value
|
|
1307
1233
|
}) {
|
|
1308
1234
|
// handle and return early if prop is an event
|
|
@@ -1311,6 +1237,7 @@ function updateObjectProp({
|
|
|
1311
1237
|
return addEventListener({
|
|
1312
1238
|
node,
|
|
1313
1239
|
key,
|
|
1240
|
+
interactables,
|
|
1314
1241
|
value
|
|
1315
1242
|
});
|
|
1316
1243
|
} // update THREE property
|
|
@@ -1566,72 +1493,86 @@ const remove = node => {
|
|
|
1566
1493
|
Elements are `create`d from the outside in, then `insert`ed from the inside out.
|
|
1567
1494
|
*/
|
|
1568
1495
|
|
|
1569
|
-
const
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
createComment(text) {
|
|
1579
|
-
return createCommentNode({
|
|
1580
|
-
text
|
|
1581
|
-
});
|
|
1582
|
-
},
|
|
1583
|
-
|
|
1584
|
-
insert,
|
|
1585
|
-
|
|
1586
|
-
nextSibling(node) {
|
|
1587
|
-
const result = node.nextSibling; // console.log('found', result)
|
|
1496
|
+
const createNodeOps = () => {
|
|
1497
|
+
// APP-LEVEL GLOBALS
|
|
1498
|
+
// ====================
|
|
1499
|
+
// These need to exist at the app level in a place where the node ops can access them.
|
|
1500
|
+
// It'd be better to set these via `app.provide` at app creation, but the node ops need access
|
|
1501
|
+
// to these values before the app is instantiated, so this is the next-best place for them to exist.
|
|
1502
|
+
const interactables = ref([]); // NODE OPS
|
|
1503
|
+
// ====================
|
|
1588
1504
|
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
},
|
|
1505
|
+
const nodeOps = {
|
|
1506
|
+
createElement,
|
|
1592
1507
|
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1508
|
+
createText(text) {
|
|
1509
|
+
return createTextNode({
|
|
1510
|
+
text
|
|
1511
|
+
});
|
|
1512
|
+
},
|
|
1598
1513
|
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1514
|
+
createComment(text) {
|
|
1515
|
+
return createCommentNode({
|
|
1516
|
+
text
|
|
1517
|
+
});
|
|
1518
|
+
},
|
|
1519
|
+
|
|
1520
|
+
insert,
|
|
1521
|
+
|
|
1522
|
+
nextSibling(node) {
|
|
1523
|
+
const result = node.nextSibling;
|
|
1524
|
+
if (!result) return null;
|
|
1525
|
+
return result;
|
|
1526
|
+
},
|
|
1527
|
+
|
|
1528
|
+
parentNode(node) {
|
|
1529
|
+
const result = node.parentNode;
|
|
1530
|
+
if (!result) return null;
|
|
1531
|
+
return result;
|
|
1532
|
+
},
|
|
1533
|
+
|
|
1534
|
+
patchProp(node, key, prevValue, nextValue) {
|
|
1535
|
+
if (isLunchboxDomComponent(node)) {
|
|
1536
|
+
// handle DOM node
|
|
1537
|
+
if (key === 'style') {
|
|
1538
|
+
// special handling for style
|
|
1539
|
+
Object.keys(nextValue).forEach(k => {
|
|
1540
|
+
node.domElement.style[k] = nextValue[k];
|
|
1541
|
+
});
|
|
1542
|
+
} else {
|
|
1543
|
+
node.domElement.setAttribute(key, nextValue);
|
|
1544
|
+
}
|
|
1610
1545
|
|
|
1611
|
-
|
|
1612
|
-
|
|
1546
|
+
return;
|
|
1547
|
+
} // ignore if root node, or Lunchbox internal prop
|
|
1613
1548
|
|
|
1614
1549
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1550
|
+
if (isLunchboxRootNode(node) || key.startsWith('$')) {
|
|
1551
|
+
return;
|
|
1552
|
+
} // otherwise, update prop
|
|
1618
1553
|
|
|
1619
1554
|
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1555
|
+
updateObjectProp({
|
|
1556
|
+
node: node,
|
|
1557
|
+
key,
|
|
1558
|
+
interactables,
|
|
1559
|
+
value: nextValue
|
|
1560
|
+
});
|
|
1561
|
+
},
|
|
1626
1562
|
|
|
1627
|
-
|
|
1563
|
+
remove,
|
|
1628
1564
|
|
|
1629
|
-
|
|
1630
|
-
|
|
1565
|
+
setElementText() {// noop
|
|
1566
|
+
},
|
|
1631
1567
|
|
|
1632
|
-
|
|
1633
|
-
|
|
1568
|
+
setText() {// noop
|
|
1569
|
+
}
|
|
1634
1570
|
|
|
1571
|
+
};
|
|
1572
|
+
return {
|
|
1573
|
+
nodeOps,
|
|
1574
|
+
interactables
|
|
1575
|
+
};
|
|
1635
1576
|
};
|
|
1636
1577
|
|
|
1637
1578
|
/** The current camera. Often easier to use `useCamera` instead of this. */
|
|
@@ -1734,11 +1675,20 @@ const onStart = (cb, index = Infinity) => {
|
|
|
1734
1675
|
} else {
|
|
1735
1676
|
callbacks?.splice(index, 0, cb);
|
|
1736
1677
|
}
|
|
1737
|
-
}; //
|
|
1678
|
+
}; // TODO: document
|
|
1679
|
+
|
|
1680
|
+
const useLunchboxInteractables = () => inject(lunchboxInteractables); // CREATE APP
|
|
1738
1681
|
// ====================
|
|
1739
1682
|
|
|
1740
1683
|
const createApp = root => {
|
|
1741
|
-
const
|
|
1684
|
+
const {
|
|
1685
|
+
nodeOps,
|
|
1686
|
+
interactables
|
|
1687
|
+
} = createNodeOps();
|
|
1688
|
+
const app = createRenderer(nodeOps).createApp(root); // provide Lunchbox interaction handlers flag (modified when user references events via
|
|
1689
|
+
// @click, etc)
|
|
1690
|
+
|
|
1691
|
+
app.provide(lunchboxInteractables, interactables); // register all components
|
|
1742
1692
|
// ====================
|
|
1743
1693
|
|
|
1744
1694
|
Object.keys(components).forEach(key => {
|
|
@@ -1888,4 +1838,4 @@ const createApp = root => {
|
|
|
1888
1838
|
return app;
|
|
1889
1839
|
};
|
|
1890
1840
|
|
|
1891
|
-
export { MiniDom, addEventListener,
|
|
1841
|
+
export { MiniDom, addEventListener, afterRenderKey, appCameraKey, appKey, appRenderersKey, appRootNodeKey, appSceneKey, beforeRenderKey, camera, cancelUpdate, cancelUpdateSource, clearCustomRender, clearCustomRenderKey, createApp, createCommentNode, createDomNode, createNode, createTextNode, ensureRenderer, ensuredCamera, ensuredScene, extend, find, frameIdKey, globalsInjectionKey, instantiateThreeObject, isMinidomNode, lunchboxInteractables, nestedPropertiesToCheck, offAfterRender, offAfterRenderKey, offBeforeRender, offBeforeRenderKey, onAfterRender, onAfterRenderKey, onBeforeRender, onBeforeRenderKey, onStart, renderer, scene, setCustomRender, setCustomRenderKey, startCallbackKey, update, updateGlobals, updateGlobalsInjectionKey, updateObjectProp, useAfterRender, useApp, useBeforeRender, useCamera, useCancelUpdate, useCancelUpdateSource, useCustomRender, useGlobals, useLunchboxInteractables, useRenderer, useRootNode, useScene, useStartCallbacks, useUpdateGlobals, watchStopHandleKey };
|