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
package/dist/lunchboxjs.js
CHANGED
|
@@ -141,6 +141,7 @@
|
|
|
141
141
|
const appRenderersKey = Symbol();
|
|
142
142
|
const appSceneKey = Symbol();
|
|
143
143
|
const appCameraKey = Symbol();
|
|
144
|
+
const lunchboxInteractables = Symbol();
|
|
144
145
|
const startCallbackKey = Symbol();
|
|
145
146
|
|
|
146
147
|
const ensuredCamera = () => vue.inject(appCameraKey); // ENSURE RENDERER
|
|
@@ -151,215 +152,11 @@
|
|
|
151
152
|
|
|
152
153
|
const ensuredScene = () => vue.inject(appSceneKey);
|
|
153
154
|
|
|
154
|
-
const interactables = [];
|
|
155
|
-
const addInteractable = target => {
|
|
156
|
-
interactables.push(target);
|
|
157
|
-
};
|
|
158
|
-
const removeInteractable = target => {
|
|
159
|
-
const idx = interactables.indexOf(target);
|
|
160
|
-
|
|
161
|
-
if (idx !== -1) {
|
|
162
|
-
interactables.splice(idx, 1);
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
/** Mouse is down, touch is pressed, etc */
|
|
167
|
-
|
|
168
|
-
const inputActive = vue.ref(false);
|
|
169
|
-
|
|
170
|
-
// let mouseDownListener: (event: MouseEvent) => void
|
|
171
|
-
// let mouseUpListener: (event: MouseEvent) => void
|
|
172
|
-
|
|
173
|
-
const mousePos = vue.ref({
|
|
174
|
-
x: Infinity,
|
|
175
|
-
y: Infinity
|
|
176
|
-
}); // let autoRaycasterEventsInitialized = false
|
|
177
|
-
// let frameID: number
|
|
178
|
-
// export const setupAutoRaycaster = (node: Lunch.Node<THREE.Raycaster>) => {
|
|
179
|
-
// const instance = node.instance
|
|
180
|
-
// if (!instance) return
|
|
181
|
-
// // TODO: inject doesn't work here. replace this raycaster with a component so we can
|
|
182
|
-
// // `inject` in `setup`?
|
|
183
|
-
// const appLevelGlobals = { dpr: window.devicePixelRatio } //useGlobals()
|
|
184
|
-
// // add mouse events once renderer is ready
|
|
185
|
-
// let stopWatcher: WatchStopHandle | null = null
|
|
186
|
-
// stopWatcher = watch(
|
|
187
|
-
// () => ensureRenderer.value,
|
|
188
|
-
// (renderer) => {
|
|
189
|
-
// // make sure renderer exists
|
|
190
|
-
// if (!renderer?.instance) return
|
|
191
|
-
// // cancel early if autoraycaster exists
|
|
192
|
-
// if (autoRaycasterEventsInitialized) {
|
|
193
|
-
// if (stopWatcher) stopWatcher()
|
|
194
|
-
// return
|
|
195
|
-
// }
|
|
196
|
-
// // create mouse events
|
|
197
|
-
// mouseMoveListener = (evt) => {
|
|
198
|
-
// const screenWidth =
|
|
199
|
-
// (renderer.instance!.domElement.width ?? 1) /
|
|
200
|
-
// appLevelGlobals.dpr
|
|
201
|
-
// const screenHeight =
|
|
202
|
-
// (renderer.instance!.domElement.height ?? 1) /
|
|
203
|
-
// appLevelGlobals.dpr
|
|
204
|
-
// mousePos.value.x = (evt.offsetX / screenWidth) * 2 - 1
|
|
205
|
-
// mousePos.value.y = -(evt.offsetY / screenHeight) * 2 + 1
|
|
206
|
-
// }
|
|
207
|
-
// mouseDownListener = () => (inputActive.value = true)
|
|
208
|
-
// mouseUpListener = () => (inputActive.value = false)
|
|
209
|
-
// // add mouse events
|
|
210
|
-
// renderer.instance.domElement.addEventListener(
|
|
211
|
-
// 'mousemove',
|
|
212
|
-
// mouseMoveListener
|
|
213
|
-
// )
|
|
214
|
-
// renderer.instance.domElement.addEventListener(
|
|
215
|
-
// 'mousedown',
|
|
216
|
-
// mouseDownListener
|
|
217
|
-
// )
|
|
218
|
-
// renderer.instance.domElement.addEventListener(
|
|
219
|
-
// 'mouseup',
|
|
220
|
-
// mouseUpListener
|
|
221
|
-
// )
|
|
222
|
-
// // TODO: add touch events
|
|
223
|
-
// // process mouse events asynchronously, whenever the mouse state changes
|
|
224
|
-
// watch(
|
|
225
|
-
// () => [inputActive.value, mousePos.value.x, mousePos.value.y],
|
|
226
|
-
// () => {
|
|
227
|
-
// if (frameID) cancelAnimationFrame(frameID)
|
|
228
|
-
// frameID = requestAnimationFrame(() => {
|
|
229
|
-
// autoRaycasterBeforeRender()
|
|
230
|
-
// })
|
|
231
|
-
// }
|
|
232
|
-
// )
|
|
233
|
-
// // mark complete
|
|
234
|
-
// autoRaycasterEventsInitialized = true
|
|
235
|
-
// // cancel setup watcher
|
|
236
|
-
// if (stopWatcher) {
|
|
237
|
-
// stopWatcher()
|
|
238
|
-
// }
|
|
239
|
-
// },
|
|
240
|
-
// { immediate: true }
|
|
241
|
-
// )
|
|
242
|
-
// }
|
|
243
|
-
// AUTO-RAYCASTER CALLBACK
|
|
244
|
-
// ====================
|
|
245
|
-
|
|
246
|
-
let currentIntersections = []; // const autoRaycasterBeforeRender = () => {
|
|
247
|
-
// // setup
|
|
248
|
-
// const raycaster = ensuredRaycaster.value?.instance
|
|
249
|
-
// const camera = ensuredCamera.value?.instance
|
|
250
|
-
// if (!raycaster || !camera) return
|
|
251
|
-
// raycaster.setFromCamera(globals.mousePos.value, camera)
|
|
252
|
-
// const intersections = raycaster.intersectObjects(
|
|
253
|
-
// interactables.map((v) => v.instance as any as THREE.Object3D)
|
|
254
|
-
// )
|
|
255
|
-
// let enterValues: Array<Intersection<THREE.Object3D>> = [],
|
|
256
|
-
// sameValues: Array<Intersection<THREE.Object3D>> = [],
|
|
257
|
-
// leaveValues: Array<Intersection<THREE.Object3D>> = [],
|
|
258
|
-
// entering: Array<{
|
|
259
|
-
// element: Lunch.Node
|
|
260
|
-
// intersection: Intersection<THREE.Object3D>
|
|
261
|
-
// }> = [],
|
|
262
|
-
// staying: Array<{
|
|
263
|
-
// element: Lunch.Node
|
|
264
|
-
// intersection: Intersection<THREE.Object3D>
|
|
265
|
-
// }> = []
|
|
266
|
-
// // intersection arrays
|
|
267
|
-
// leaveValues = currentIntersections.map((v) => v.intersection)
|
|
268
|
-
// // element arrays
|
|
269
|
-
// intersections?.forEach((intersection) => {
|
|
270
|
-
// const currentIdx = currentIntersections.findIndex(
|
|
271
|
-
// (v) => v.intersection.object === intersection.object
|
|
272
|
-
// )
|
|
273
|
-
// if (currentIdx === -1) {
|
|
274
|
-
// // new intersection
|
|
275
|
-
// enterValues.push(intersection)
|
|
276
|
-
// const found = interactables.find(
|
|
277
|
-
// (v) => v.instance?.uuid === intersection.object.uuid
|
|
278
|
-
// )
|
|
279
|
-
// if (found) {
|
|
280
|
-
// entering.push({ element: found, intersection })
|
|
281
|
-
// }
|
|
282
|
-
// } else {
|
|
283
|
-
// // existing intersection
|
|
284
|
-
// sameValues.push(intersection)
|
|
285
|
-
// const found = interactables.find(
|
|
286
|
-
// (v) => v.instance?.uuid === intersection.object.uuid
|
|
287
|
-
// )
|
|
288
|
-
// if (found) {
|
|
289
|
-
// staying.push({ element: found, intersection })
|
|
290
|
-
// }
|
|
291
|
-
// }
|
|
292
|
-
// // this is a current intersection, so it won't be in our `leave` array
|
|
293
|
-
// const leaveIdx = leaveValues.findIndex(
|
|
294
|
-
// (v) => v.object.uuid === intersection.object.uuid
|
|
295
|
-
// )
|
|
296
|
-
// if (leaveIdx !== -1) {
|
|
297
|
-
// leaveValues.splice(leaveIdx, 1)
|
|
298
|
-
// }
|
|
299
|
-
// })
|
|
300
|
-
// const leaving: Array<{
|
|
301
|
-
// element: Lunch.Node
|
|
302
|
-
// intersection: Intersection<THREE.Object3D>
|
|
303
|
-
// }> = leaveValues.map((intersection) => {
|
|
304
|
-
// return {
|
|
305
|
-
// element: interactables.find(
|
|
306
|
-
// (interactable) =>
|
|
307
|
-
// interactable.instance?.uuid === intersection.object.uuid
|
|
308
|
-
// ) as any as Lunch.Node,
|
|
309
|
-
// intersection,
|
|
310
|
-
// }
|
|
311
|
-
// })
|
|
312
|
-
// // new interactions
|
|
313
|
-
// entering.forEach(({ element, intersection }) => {
|
|
314
|
-
// fireEventsFromIntersections({
|
|
315
|
-
// element,
|
|
316
|
-
// eventKeys: ['onPointerEnter'],
|
|
317
|
-
// intersection,
|
|
318
|
-
// })
|
|
319
|
-
// })
|
|
320
|
-
// // unchanged interactions
|
|
321
|
-
// staying.forEach(({ element, intersection }) => {
|
|
322
|
-
// const eventKeys: Array<Lunch.EventKey> = [
|
|
323
|
-
// 'onPointerOver',
|
|
324
|
-
// 'onPointerMove',
|
|
325
|
-
// ]
|
|
326
|
-
// fireEventsFromIntersections({ element, eventKeys, intersection })
|
|
327
|
-
// })
|
|
328
|
-
// // exited interactions
|
|
329
|
-
// leaving.forEach(({ element, intersection }) => {
|
|
330
|
-
// const eventKeys: Array<Lunch.EventKey> = [
|
|
331
|
-
// 'onPointerLeave',
|
|
332
|
-
// 'onPointerOut',
|
|
333
|
-
// ]
|
|
334
|
-
// fireEventsFromIntersections({ element, eventKeys, intersection })
|
|
335
|
-
// })
|
|
336
|
-
// currentIntersections = ([] as any).concat(entering, staying)
|
|
337
|
-
// }
|
|
338
|
-
// utility function for firing multiple callbacks and multiple events on a Lunchbox.Element
|
|
339
|
-
// const fireEventsFromIntersections = ({
|
|
340
|
-
// element,
|
|
341
|
-
// eventKeys,
|
|
342
|
-
// intersection,
|
|
343
|
-
// }: {
|
|
344
|
-
// element: Lunch.Node
|
|
345
|
-
// eventKeys: Array<Lunch.EventKey>
|
|
346
|
-
// intersection: Intersection<THREE.Object3D>
|
|
347
|
-
// }) => {
|
|
348
|
-
// if (!element) return
|
|
349
|
-
// eventKeys.forEach((eventKey) => {
|
|
350
|
-
// if (element.eventListeners[eventKey]) {
|
|
351
|
-
// element.eventListeners[eventKey].forEach((cb) => {
|
|
352
|
-
// cb({ intersection })
|
|
353
|
-
// })
|
|
354
|
-
// }
|
|
355
|
-
// })
|
|
356
|
-
// }
|
|
357
|
-
|
|
358
155
|
/** Add an event listener to the given node. Also creates the event teardown function and any necessary raycaster/interaction dictionary updates. */
|
|
359
|
-
|
|
360
156
|
function addEventListener({
|
|
361
157
|
node,
|
|
362
158
|
key,
|
|
159
|
+
interactables,
|
|
363
160
|
value
|
|
364
161
|
}) {
|
|
365
162
|
// create new records for this key if needed
|
|
@@ -375,37 +172,18 @@
|
|
|
375
172
|
node.eventListeners[key].push(value); // if we need it, let's get/create the main raycaster
|
|
376
173
|
|
|
377
174
|
if (interactionsRequiringRaycaster.includes(key)) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
if (key === 'onClick' || key === 'onPointerDown' || key === 'onPointerUp') {
|
|
389
|
-
const stop = vue.watch(() => inputActive.value, isDown => {
|
|
390
|
-
const idx = currentIntersections.map(v => v.element).findIndex(v => v.instance && v.instance.uuid === node.instance?.uuid);
|
|
391
|
-
|
|
392
|
-
if (idx !== -1) {
|
|
393
|
-
if (isDown && (key === 'onClick' || key === 'onPointerDown')) {
|
|
394
|
-
node.eventListeners[key].forEach(func => {
|
|
395
|
-
func({
|
|
396
|
-
intersection: currentIntersections[idx].intersection
|
|
397
|
-
});
|
|
398
|
-
});
|
|
399
|
-
} else if (!isDown && key === 'onPointerUp') {
|
|
400
|
-
node.eventListeners[key].forEach(func => {
|
|
401
|
-
func({
|
|
402
|
-
intersection: currentIntersections[idx].intersection
|
|
403
|
-
});
|
|
404
|
-
});
|
|
175
|
+
if (node.instance && !interactables.value.includes(node)) {
|
|
176
|
+
// add to interactables
|
|
177
|
+
interactables.value.push(node);
|
|
178
|
+
node.eventListenerRemoveFunctions[key].push(() => {
|
|
179
|
+
// remove from interactables
|
|
180
|
+
const idx = interactables.value.indexOf(node);
|
|
181
|
+
|
|
182
|
+
if (idx !== -1) {
|
|
183
|
+
interactables.value.splice(idx, 1);
|
|
405
184
|
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
node.eventListenerRemoveFunctions[key].push(stop);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
409
187
|
}
|
|
410
188
|
|
|
411
189
|
return node;
|
|
@@ -499,6 +277,195 @@
|
|
|
499
277
|
|
|
500
278
|
});
|
|
501
279
|
|
|
280
|
+
const LunchboxEventHandlers = vue.defineComponent({
|
|
281
|
+
name: 'LunchboxEventHandlers',
|
|
282
|
+
|
|
283
|
+
setup() {
|
|
284
|
+
const interactables = useLunchboxInteractables();
|
|
285
|
+
const camera = useCamera();
|
|
286
|
+
const renderer = useRenderer();
|
|
287
|
+
const globals = useGlobals();
|
|
288
|
+
const mousePos = vue.ref({
|
|
289
|
+
x: Infinity,
|
|
290
|
+
y: Infinity
|
|
291
|
+
});
|
|
292
|
+
const inputActive = vue.ref(false);
|
|
293
|
+
let currentIntersections = [];
|
|
294
|
+
const raycaster = new THREE__namespace.Raycaster(new THREE__namespace.Vector3(), new THREE__namespace.Vector3(0, 0, -1));
|
|
295
|
+
|
|
296
|
+
const fireEventsFromIntersections = ({
|
|
297
|
+
element,
|
|
298
|
+
eventKeys,
|
|
299
|
+
intersection
|
|
300
|
+
}) => {
|
|
301
|
+
if (!element) return;
|
|
302
|
+
eventKeys.forEach(eventKey => {
|
|
303
|
+
if (element.eventListeners[eventKey]) {
|
|
304
|
+
element.eventListeners[eventKey].forEach(cb => {
|
|
305
|
+
cb({
|
|
306
|
+
intersection
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
}; // add mouse listener to renderer DOM element when the element is ready
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
const stopWatch = vue.watch(renderer, v => {
|
|
315
|
+
if (!v?.domElement) return; // we have a DOM element, so let's add mouse listeners
|
|
316
|
+
|
|
317
|
+
const {
|
|
318
|
+
domElement
|
|
319
|
+
} = v;
|
|
320
|
+
|
|
321
|
+
const mouseMoveListener = evt => {
|
|
322
|
+
const screenWidth = (domElement.width ?? 1) / globals.dpr;
|
|
323
|
+
const screenHeight = (domElement.height ?? 1) / globals.dpr;
|
|
324
|
+
mousePos.value.x = evt.offsetX / screenWidth * 2 - 1;
|
|
325
|
+
mousePos.value.y = -(evt.offsetY / screenHeight) * 2 + 1;
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const mouseDownListener = () => inputActive.value = true;
|
|
329
|
+
|
|
330
|
+
const mouseUpListener = () => inputActive.value = false; // add mouse events
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
domElement.addEventListener('pointermove', mouseMoveListener);
|
|
334
|
+
domElement.addEventListener('pointerdown', mouseDownListener);
|
|
335
|
+
domElement.addEventListener('pointerup', mouseUpListener); // stop the watcher
|
|
336
|
+
|
|
337
|
+
stopWatch();
|
|
338
|
+
}, {
|
|
339
|
+
immediate: true
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const update = () => {
|
|
343
|
+
const c = camera.value;
|
|
344
|
+
if (!c) return;
|
|
345
|
+
raycaster.setFromCamera(mousePos.value, c);
|
|
346
|
+
const intersections = raycaster.intersectObjects(interactables?.value.map(v => v.instance) ?? []);
|
|
347
|
+
let leaveValues = [],
|
|
348
|
+
entering = [],
|
|
349
|
+
staying = []; // intersection arrays
|
|
350
|
+
|
|
351
|
+
leaveValues = currentIntersections.map(v => v.intersection); // element arrays
|
|
352
|
+
|
|
353
|
+
intersections?.forEach(intersection => {
|
|
354
|
+
const currentIdx = currentIntersections.findIndex(v => v.intersection.object === intersection.object);
|
|
355
|
+
|
|
356
|
+
if (currentIdx === -1) {
|
|
357
|
+
const found = interactables?.value.find(v => v.instance?.uuid === intersection.object.uuid);
|
|
358
|
+
|
|
359
|
+
if (found) {
|
|
360
|
+
entering.push({
|
|
361
|
+
element: found,
|
|
362
|
+
intersection
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
} else {
|
|
366
|
+
const found = interactables?.value.find(v => v.instance?.uuid === intersection.object.uuid);
|
|
367
|
+
|
|
368
|
+
if (found) {
|
|
369
|
+
staying.push({
|
|
370
|
+
element: found,
|
|
371
|
+
intersection
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
} // this is a current intersection, so it won't be in our `leave` array
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
const leaveIdx = leaveValues.findIndex(v => v.object.uuid === intersection.object.uuid);
|
|
378
|
+
|
|
379
|
+
if (leaveIdx !== -1) {
|
|
380
|
+
leaveValues.splice(leaveIdx, 1);
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
const leaving = leaveValues.map(intersection => {
|
|
384
|
+
return {
|
|
385
|
+
element: interactables?.value.find(interactable => interactable.instance?.uuid === intersection.object.uuid),
|
|
386
|
+
intersection
|
|
387
|
+
};
|
|
388
|
+
}); // new interactions
|
|
389
|
+
|
|
390
|
+
entering.forEach(({
|
|
391
|
+
element,
|
|
392
|
+
intersection
|
|
393
|
+
}) => {
|
|
394
|
+
fireEventsFromIntersections({
|
|
395
|
+
element,
|
|
396
|
+
eventKeys: ['onPointerEnter'],
|
|
397
|
+
intersection
|
|
398
|
+
});
|
|
399
|
+
}); // unchanged interactions
|
|
400
|
+
|
|
401
|
+
staying.forEach(({
|
|
402
|
+
element,
|
|
403
|
+
intersection
|
|
404
|
+
}) => {
|
|
405
|
+
const eventKeys = ['onPointerOver', 'onPointerMove'];
|
|
406
|
+
fireEventsFromIntersections({
|
|
407
|
+
element,
|
|
408
|
+
eventKeys,
|
|
409
|
+
intersection
|
|
410
|
+
});
|
|
411
|
+
}); // exited interactions
|
|
412
|
+
|
|
413
|
+
leaving.forEach(({
|
|
414
|
+
element,
|
|
415
|
+
intersection
|
|
416
|
+
}) => {
|
|
417
|
+
const eventKeys = ['onPointerLeave', 'onPointerOut'];
|
|
418
|
+
fireEventsFromIntersections({
|
|
419
|
+
element,
|
|
420
|
+
eventKeys,
|
|
421
|
+
intersection
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
currentIntersections = [].concat(entering, staying);
|
|
425
|
+
}; // update function
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
onBeforeRender(update);
|
|
429
|
+
|
|
430
|
+
const teardown = () => offBeforeRender(update);
|
|
431
|
+
|
|
432
|
+
vue.onBeforeUnmount(teardown);
|
|
433
|
+
const clickEventKeys = ['onClick', 'onPointerDown', 'onPointerUp'];
|
|
434
|
+
vue.watch(inputActive, isDown => {
|
|
435
|
+
// meshes with multiple intersections receive multiple callbacks by default -
|
|
436
|
+
// let's make it so they only receive one callback of each type per frame.
|
|
437
|
+
// (ie usually when you click on a mesh, you expect only one click event to fire, even
|
|
438
|
+
// if there are technically multiple intersections with that mesh)
|
|
439
|
+
const uuidsInteractedWithThisFrame = [];
|
|
440
|
+
currentIntersections.forEach(v => {
|
|
441
|
+
clickEventKeys.forEach(key => {
|
|
442
|
+
const id = v.element.uuid + key;
|
|
443
|
+
|
|
444
|
+
if (isDown && (key === 'onClick' || key === 'onPointerDown')) {
|
|
445
|
+
if (!uuidsInteractedWithThisFrame.includes(id)) {
|
|
446
|
+
v.element.eventListeners[key]?.forEach(cb => cb({
|
|
447
|
+
intersection: v.intersection
|
|
448
|
+
}));
|
|
449
|
+
uuidsInteractedWithThisFrame.push(id);
|
|
450
|
+
}
|
|
451
|
+
} else if (!isDown && key === 'onPointerUp') {
|
|
452
|
+
if (!uuidsInteractedWithThisFrame.includes(id)) {
|
|
453
|
+
v.element.eventListeners[key]?.forEach(cb => cb({
|
|
454
|
+
intersection: v.intersection
|
|
455
|
+
}));
|
|
456
|
+
uuidsInteractedWithThisFrame.push(id);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
}); // return arbitrary object to ensure instantiation
|
|
462
|
+
// TODO: why can't we return a <raycaster/> here?
|
|
463
|
+
|
|
464
|
+
return () => vue.createVNode(vue.resolveComponent("object3D"), null, null);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
});
|
|
468
|
+
|
|
502
469
|
/** fixed & fill styling for container */
|
|
503
470
|
|
|
504
471
|
const fillStyle = position => {
|
|
@@ -551,9 +518,10 @@
|
|
|
551
518
|
|
|
552
519
|
if (props.r3f && THREE__namespace?.ColorManagement) {
|
|
553
520
|
THREE__namespace.ColorManagement.legacyMode = false;
|
|
554
|
-
}
|
|
555
|
-
// ====================
|
|
521
|
+
}
|
|
556
522
|
|
|
523
|
+
const interactables = useLunchboxInteractables(); // MOUNT
|
|
524
|
+
// ====================
|
|
557
525
|
|
|
558
526
|
vue.onMounted(async () => {
|
|
559
527
|
// canvas needs to exist (or user needs to handle it on their own)
|
|
@@ -589,23 +557,17 @@
|
|
|
589
557
|
updateGlobals?.({
|
|
590
558
|
dpr
|
|
591
559
|
});
|
|
592
|
-
console.log(1);
|
|
593
560
|
|
|
594
561
|
while (!renderer.value?.$el?.instance && // TODO: remove `as any`
|
|
595
562
|
!renderer.value?.component?.ctx.$el?.instance) {
|
|
596
|
-
console.log(2);
|
|
597
563
|
await new Promise(r => requestAnimationFrame(r));
|
|
598
564
|
}
|
|
599
565
|
|
|
600
|
-
console.log(3);
|
|
601
|
-
|
|
602
566
|
while (!scene.value?.$el?.instance && // TODO: remove `as any`
|
|
603
567
|
!scene.value?.component?.ctx.$el?.instance) {
|
|
604
|
-
console.log(4);
|
|
605
568
|
await new Promise(r => requestAnimationFrame(r));
|
|
606
569
|
}
|
|
607
570
|
|
|
608
|
-
console.log(5);
|
|
609
571
|
const normalizedRenderer = renderer.value?.$el?.instance ?? renderer.value?.component?.ctx.$el?.instance;
|
|
610
572
|
normalizedRenderer.setPixelRatio(globals.dpr);
|
|
611
573
|
const normalizedScene = scene.value?.$el?.instance ?? scene.value?.component?.ctx.$el?.instance;
|
|
@@ -711,7 +673,7 @@
|
|
|
711
673
|
}, consolidatedCameraProperties), null) : vue.createVNode(vue.resolveComponent("perspectiveCamera"), vue.mergeProps({
|
|
712
674
|
"ref": camera,
|
|
713
675
|
"args": props.cameraArgs ?? [props.r3f ? 75 : 45, 0.5625, 1, 1000]
|
|
714
|
-
}, consolidatedCameraProperties), null)]);
|
|
676
|
+
}, consolidatedCameraProperties), null), interactables?.value.length && vue.createVNode(LunchboxEventHandlers, null, null)]);
|
|
715
677
|
}
|
|
716
678
|
|
|
717
679
|
});
|
|
@@ -726,7 +688,7 @@
|
|
|
726
688
|
'light', 'spotLightShadow', 'spotLight', 'pointLight', 'rectAreaLight', 'hemisphereLight', 'directionalLightShadow', 'directionalLight', 'ambientLight', 'lightShadow', 'ambientLightProbe', 'hemisphereLightProbe', 'lightProbe', // textures
|
|
727
689
|
'texture', 'videoTexture', 'dataTexture', 'dataTexture3D', 'compressedTexture', 'cubeTexture', 'canvasTexture', 'depthTexture', // Texture loaders
|
|
728
690
|
'textureLoader', // misc
|
|
729
|
-
'group', 'catmullRomCurve3', 'points', // helpers
|
|
691
|
+
'group', 'catmullRomCurve3', 'points', 'raycaster', // helpers
|
|
730
692
|
'cameraHelper', // cameras
|
|
731
693
|
'camera', 'perspectiveCamera', 'orthographicCamera', 'cubeCamera', 'arrayCamera', // renderers
|
|
732
694
|
'webGLRenderer'
|
|
@@ -761,7 +723,6 @@
|
|
|
761
723
|
arrowHelper: ArrowHelperProps
|
|
762
724
|
axesHelper: AxesHelperProps
|
|
763
725
|
// misc
|
|
764
|
-
raycaster: RaycasterProps
|
|
765
726
|
vector2: Vector2Props
|
|
766
727
|
vector3: Vector3Props
|
|
767
728
|
vector4: Vector4Props
|
|
@@ -778,7 +739,7 @@
|
|
|
778
739
|
*/
|
|
779
740
|
];
|
|
780
741
|
|
|
781
|
-
const catalogue = {};
|
|
742
|
+
const catalogue = {}; // component creation utility
|
|
782
743
|
|
|
783
744
|
const createComponent$1 = tag => vue.defineComponent({
|
|
784
745
|
inheritAttrs: false,
|
|
@@ -1174,10 +1135,6 @@
|
|
|
1174
1135
|
return item?.minidomType === 'RendererNode';
|
|
1175
1136
|
}
|
|
1176
1137
|
|
|
1177
|
-
// let watchStopHandle: WatchStopHandle
|
|
1178
|
-
// export const beforeRender = [] as Lunch.UpdateCallback[]
|
|
1179
|
-
// export const afterRender = [] as Lunch.UpdateCallback[]
|
|
1180
|
-
|
|
1181
1138
|
const requestUpdate = opts => {
|
|
1182
1139
|
if (typeof opts.app.config.globalProperties.lunchbox.frameId === 'number') {
|
|
1183
1140
|
cancelAnimationFrame(opts.app.config.globalProperties.lunchbox.frameId);
|
|
@@ -1266,22 +1223,7 @@
|
|
|
1266
1223
|
|
|
1267
1224
|
const offAfterRender = cb => {
|
|
1268
1225
|
useBeforeRender().offBeforeRender?.(cb);
|
|
1269
|
-
}; //
|
|
1270
|
-
// if (index === Infinity) {
|
|
1271
|
-
// afterRender.push(cb)
|
|
1272
|
-
// } else {
|
|
1273
|
-
// afterRender.splice(index, 0, cb)
|
|
1274
|
-
// }
|
|
1275
|
-
// }
|
|
1276
|
-
// export const offAfterRender = (cb: Lunch.UpdateCallback | number) => {
|
|
1277
|
-
// if (isFinite(cb as number)) {
|
|
1278
|
-
// afterRender.splice(cb as number, 1)
|
|
1279
|
-
// } else {
|
|
1280
|
-
// const idx = afterRender.findIndex((v) => v == cb)
|
|
1281
|
-
// afterRender.splice(idx, 1)
|
|
1282
|
-
// }
|
|
1283
|
-
// }
|
|
1284
|
-
// TODO: document
|
|
1226
|
+
}; // TODO: document
|
|
1285
1227
|
|
|
1286
1228
|
const useCancelUpdate = () => {
|
|
1287
1229
|
const frameId = vue.inject(frameIdKey);
|
|
@@ -1303,28 +1245,12 @@
|
|
|
1303
1245
|
useCancelUpdateSource()?.();
|
|
1304
1246
|
};
|
|
1305
1247
|
|
|
1306
|
-
/** Update the given node so all of its props are current. */
|
|
1307
|
-
|
|
1308
|
-
function updateAllObjectProps({
|
|
1309
|
-
node
|
|
1310
|
-
}) {
|
|
1311
|
-
// set props
|
|
1312
|
-
const props = node.props || {};
|
|
1313
|
-
let output = node;
|
|
1314
|
-
Object.keys(props).forEach(key => {
|
|
1315
|
-
output = updateObjectProp({
|
|
1316
|
-
node,
|
|
1317
|
-
key,
|
|
1318
|
-
value: props[key]
|
|
1319
|
-
});
|
|
1320
|
-
});
|
|
1321
|
-
return output;
|
|
1322
|
-
}
|
|
1323
1248
|
/** Update a single prop on a given node. */
|
|
1324
1249
|
|
|
1325
1250
|
function updateObjectProp({
|
|
1326
1251
|
node,
|
|
1327
1252
|
key,
|
|
1253
|
+
interactables,
|
|
1328
1254
|
value
|
|
1329
1255
|
}) {
|
|
1330
1256
|
// handle and return early if prop is an event
|
|
@@ -1333,6 +1259,7 @@
|
|
|
1333
1259
|
return addEventListener({
|
|
1334
1260
|
node,
|
|
1335
1261
|
key,
|
|
1262
|
+
interactables,
|
|
1336
1263
|
value
|
|
1337
1264
|
});
|
|
1338
1265
|
} // update THREE property
|
|
@@ -1588,72 +1515,86 @@
|
|
|
1588
1515
|
Elements are `create`d from the outside in, then `insert`ed from the inside out.
|
|
1589
1516
|
*/
|
|
1590
1517
|
|
|
1591
|
-
const
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
createComment(text) {
|
|
1601
|
-
return createCommentNode({
|
|
1602
|
-
text
|
|
1603
|
-
});
|
|
1604
|
-
},
|
|
1605
|
-
|
|
1606
|
-
insert,
|
|
1607
|
-
|
|
1608
|
-
nextSibling(node) {
|
|
1609
|
-
const result = node.nextSibling; // console.log('found', result)
|
|
1518
|
+
const createNodeOps = () => {
|
|
1519
|
+
// APP-LEVEL GLOBALS
|
|
1520
|
+
// ====================
|
|
1521
|
+
// These need to exist at the app level in a place where the node ops can access them.
|
|
1522
|
+
// It'd be better to set these via `app.provide` at app creation, but the node ops need access
|
|
1523
|
+
// to these values before the app is instantiated, so this is the next-best place for them to exist.
|
|
1524
|
+
const interactables = vue.ref([]); // NODE OPS
|
|
1525
|
+
// ====================
|
|
1610
1526
|
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
},
|
|
1527
|
+
const nodeOps = {
|
|
1528
|
+
createElement,
|
|
1614
1529
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1530
|
+
createText(text) {
|
|
1531
|
+
return createTextNode({
|
|
1532
|
+
text
|
|
1533
|
+
});
|
|
1534
|
+
},
|
|
1620
1535
|
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1536
|
+
createComment(text) {
|
|
1537
|
+
return createCommentNode({
|
|
1538
|
+
text
|
|
1539
|
+
});
|
|
1540
|
+
},
|
|
1541
|
+
|
|
1542
|
+
insert,
|
|
1543
|
+
|
|
1544
|
+
nextSibling(node) {
|
|
1545
|
+
const result = node.nextSibling;
|
|
1546
|
+
if (!result) return null;
|
|
1547
|
+
return result;
|
|
1548
|
+
},
|
|
1549
|
+
|
|
1550
|
+
parentNode(node) {
|
|
1551
|
+
const result = node.parentNode;
|
|
1552
|
+
if (!result) return null;
|
|
1553
|
+
return result;
|
|
1554
|
+
},
|
|
1555
|
+
|
|
1556
|
+
patchProp(node, key, prevValue, nextValue) {
|
|
1557
|
+
if (isLunchboxDomComponent(node)) {
|
|
1558
|
+
// handle DOM node
|
|
1559
|
+
if (key === 'style') {
|
|
1560
|
+
// special handling for style
|
|
1561
|
+
Object.keys(nextValue).forEach(k => {
|
|
1562
|
+
node.domElement.style[k] = nextValue[k];
|
|
1563
|
+
});
|
|
1564
|
+
} else {
|
|
1565
|
+
node.domElement.setAttribute(key, nextValue);
|
|
1566
|
+
}
|
|
1632
1567
|
|
|
1633
|
-
|
|
1634
|
-
|
|
1568
|
+
return;
|
|
1569
|
+
} // ignore if root node, or Lunchbox internal prop
|
|
1635
1570
|
|
|
1636
1571
|
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1572
|
+
if (isLunchboxRootNode(node) || key.startsWith('$')) {
|
|
1573
|
+
return;
|
|
1574
|
+
} // otherwise, update prop
|
|
1640
1575
|
|
|
1641
1576
|
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1577
|
+
updateObjectProp({
|
|
1578
|
+
node: node,
|
|
1579
|
+
key,
|
|
1580
|
+
interactables,
|
|
1581
|
+
value: nextValue
|
|
1582
|
+
});
|
|
1583
|
+
},
|
|
1648
1584
|
|
|
1649
|
-
|
|
1585
|
+
remove,
|
|
1650
1586
|
|
|
1651
|
-
|
|
1652
|
-
|
|
1587
|
+
setElementText() {// noop
|
|
1588
|
+
},
|
|
1653
1589
|
|
|
1654
|
-
|
|
1655
|
-
|
|
1590
|
+
setText() {// noop
|
|
1591
|
+
}
|
|
1656
1592
|
|
|
1593
|
+
};
|
|
1594
|
+
return {
|
|
1595
|
+
nodeOps,
|
|
1596
|
+
interactables
|
|
1597
|
+
};
|
|
1657
1598
|
};
|
|
1658
1599
|
|
|
1659
1600
|
/** The current camera. Often easier to use `useCamera` instead of this. */
|
|
@@ -1756,11 +1697,20 @@
|
|
|
1756
1697
|
} else {
|
|
1757
1698
|
callbacks?.splice(index, 0, cb);
|
|
1758
1699
|
}
|
|
1759
|
-
}; //
|
|
1700
|
+
}; // TODO: document
|
|
1701
|
+
|
|
1702
|
+
const useLunchboxInteractables = () => vue.inject(lunchboxInteractables); // CREATE APP
|
|
1760
1703
|
// ====================
|
|
1761
1704
|
|
|
1762
1705
|
const createApp = root => {
|
|
1763
|
-
const
|
|
1706
|
+
const {
|
|
1707
|
+
nodeOps,
|
|
1708
|
+
interactables
|
|
1709
|
+
} = createNodeOps();
|
|
1710
|
+
const app = vue.createRenderer(nodeOps).createApp(root); // provide Lunchbox interaction handlers flag (modified when user references events via
|
|
1711
|
+
// @click, etc)
|
|
1712
|
+
|
|
1713
|
+
app.provide(lunchboxInteractables, interactables); // register all components
|
|
1764
1714
|
// ====================
|
|
1765
1715
|
|
|
1766
1716
|
Object.keys(components).forEach(key => {
|
|
@@ -1911,7 +1861,6 @@
|
|
|
1911
1861
|
};
|
|
1912
1862
|
|
|
1913
1863
|
exports.addEventListener = addEventListener;
|
|
1914
|
-
exports.addInteractable = addInteractable;
|
|
1915
1864
|
exports.afterRenderKey = afterRenderKey;
|
|
1916
1865
|
exports.appCameraKey = appCameraKey;
|
|
1917
1866
|
exports.appKey = appKey;
|
|
@@ -1929,7 +1878,6 @@
|
|
|
1929
1878
|
exports.createDomNode = createDomNode;
|
|
1930
1879
|
exports.createNode = createNode;
|
|
1931
1880
|
exports.createTextNode = createTextNode;
|
|
1932
|
-
exports.currentIntersections = currentIntersections;
|
|
1933
1881
|
exports.ensureRenderer = ensureRenderer;
|
|
1934
1882
|
exports.ensuredCamera = ensuredCamera;
|
|
1935
1883
|
exports.ensuredScene = ensuredScene;
|
|
@@ -1937,11 +1885,9 @@
|
|
|
1937
1885
|
exports.find = find;
|
|
1938
1886
|
exports.frameIdKey = frameIdKey;
|
|
1939
1887
|
exports.globalsInjectionKey = globalsInjectionKey;
|
|
1940
|
-
exports.inputActive = inputActive;
|
|
1941
1888
|
exports.instantiateThreeObject = instantiateThreeObject;
|
|
1942
|
-
exports.interactables = interactables;
|
|
1943
1889
|
exports.isMinidomNode = isMinidomNode;
|
|
1944
|
-
exports.
|
|
1890
|
+
exports.lunchboxInteractables = lunchboxInteractables;
|
|
1945
1891
|
exports.nestedPropertiesToCheck = nestedPropertiesToCheck;
|
|
1946
1892
|
exports.offAfterRender = offAfterRender;
|
|
1947
1893
|
exports.offAfterRenderKey = offAfterRenderKey;
|
|
@@ -1952,14 +1898,12 @@
|
|
|
1952
1898
|
exports.onBeforeRender = onBeforeRender;
|
|
1953
1899
|
exports.onBeforeRenderKey = onBeforeRenderKey;
|
|
1954
1900
|
exports.onStart = onStart;
|
|
1955
|
-
exports.removeInteractable = removeInteractable;
|
|
1956
1901
|
exports.renderer = renderer;
|
|
1957
1902
|
exports.scene = scene;
|
|
1958
1903
|
exports.setCustomRender = setCustomRender;
|
|
1959
1904
|
exports.setCustomRenderKey = setCustomRenderKey;
|
|
1960
1905
|
exports.startCallbackKey = startCallbackKey;
|
|
1961
1906
|
exports.update = update;
|
|
1962
|
-
exports.updateAllObjectProps = updateAllObjectProps;
|
|
1963
1907
|
exports.updateGlobals = updateGlobals;
|
|
1964
1908
|
exports.updateGlobalsInjectionKey = updateGlobalsInjectionKey;
|
|
1965
1909
|
exports.updateObjectProp = updateObjectProp;
|
|
@@ -1971,6 +1915,7 @@
|
|
|
1971
1915
|
exports.useCancelUpdateSource = useCancelUpdateSource;
|
|
1972
1916
|
exports.useCustomRender = useCustomRender;
|
|
1973
1917
|
exports.useGlobals = useGlobals;
|
|
1918
|
+
exports.useLunchboxInteractables = useLunchboxInteractables;
|
|
1974
1919
|
exports.useRenderer = useRenderer;
|
|
1975
1920
|
exports.useRootNode = useRootNode;
|
|
1976
1921
|
exports.useScene = useScene;
|