lunchboxjs 0.2.1001-beta.1 → 0.2.1001-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { isRef, isVNode, inject, toRaw, defineComponent, createVNode, resolveComponent, ref, watch, onBeforeUnmount, reactive, onMounted, Fragment, mergeProps, h, createRenderer, computed } from 'vue';
1
+ import { isRef, isVNode, toRaw, defineComponent, createVNode, resolveComponent, ref, onBeforeUnmount, watch, reactive, onMounted, computed, Fragment, mergeProps, h, inject, createRenderer } from 'vue';
2
2
  import * as THREE from 'three';
3
3
  import { get, isNumber, set } from 'lodash';
4
4
 
@@ -102,34 +102,6 @@ function createNode(options = {}, props = {}) {
102
102
  return node;
103
103
  }
104
104
 
105
- const globalsInjectionKey = Symbol();
106
- const updateGlobalsInjectionKey = Symbol();
107
- const setCustomRenderKey = Symbol();
108
- const clearCustomRenderKey = Symbol();
109
- const beforeRenderKey = Symbol();
110
- const onBeforeRenderKey = Symbol();
111
- const offBeforeRenderKey = Symbol();
112
- const afterRenderKey = Symbol();
113
- const onAfterRenderKey = Symbol();
114
- const offAfterRenderKey = Symbol();
115
- const frameIdKey = Symbol();
116
- const watchStopHandleKey = Symbol();
117
- const appRootNodeKey = Symbol();
118
- const appKey = Symbol();
119
- const appRenderersKey = Symbol();
120
- const appSceneKey = Symbol();
121
- const appCameraKey = Symbol();
122
- const lunchboxInteractables = Symbol();
123
- const startCallbackKey = Symbol();
124
-
125
- const ensuredCamera = () => inject(appCameraKey); // ENSURE RENDERER
126
- // ====================
127
-
128
- const ensureRenderer = () => inject(appRenderersKey); // ENSURE SCENE
129
- // ====================
130
-
131
- const ensuredScene = () => inject(appSceneKey);
132
-
133
105
  /** Add an event listener to the given node. Also creates the event teardown function and any necessary raycaster/interaction dictionary updates. */
134
106
  function addEventListener({
135
107
  node,
@@ -260,8 +232,6 @@ const LunchboxEventHandlers = defineComponent({
260
232
 
261
233
  setup() {
262
234
  const interactables = useLunchboxInteractables();
263
- const camera = useCamera();
264
- const renderer = useRenderer();
265
235
  const globals = useGlobals();
266
236
  const mousePos = ref({
267
237
  x: Infinity,
@@ -289,7 +259,7 @@ const LunchboxEventHandlers = defineComponent({
289
259
  }; // add mouse listener to renderer DOM element when the element is ready
290
260
 
291
261
 
292
- const stopWatch = watch(renderer, v => {
262
+ onRendererReady(v => {
293
263
  if (!v?.domElement) return; // we have a DOM element, so let's add mouse listeners
294
264
 
295
265
  const {
@@ -310,16 +280,14 @@ const LunchboxEventHandlers = defineComponent({
310
280
 
311
281
  domElement.addEventListener('pointermove', mouseMoveListener);
312
282
  domElement.addEventListener('pointerdown', mouseDownListener);
313
- domElement.addEventListener('pointerup', mouseUpListener); // stop the watcher
314
-
315
- stopWatch();
316
- }, {
317
- immediate: true
283
+ domElement.addEventListener('pointerup', mouseUpListener);
318
284
  });
285
+ const camera = useCamera();
319
286
 
320
287
  const update = () => {
321
288
  const c = camera.value;
322
- if (!c) return;
289
+ if (!c) return; // console.log(camera.value)
290
+
323
291
  raycaster.setFromCamera(mousePos.value, c);
324
292
  const intersections = raycaster.intersectObjects(interactables?.value.map(v => v.instance) ?? []);
325
293
  let leaveValues = [],
@@ -410,10 +378,13 @@ const LunchboxEventHandlers = defineComponent({
410
378
  onBeforeUnmount(teardown);
411
379
  const clickEventKeys = ['onClick', 'onPointerDown', 'onPointerUp'];
412
380
  watch(inputActive, isDown => {
413
- // meshes with multiple intersections receive multiple callbacks by default -
381
+ // run raycaster on click (necessary when `update` is not automatically called,
382
+ // for example in `updateSource` functions)
383
+ update(); // meshes with multiple intersections receive multiple callbacks by default -
414
384
  // let's make it so they only receive one callback of each type per frame.
415
385
  // (ie usually when you click on a mesh, you expect only one click event to fire, even
416
386
  // if there are technically multiple intersections with that mesh)
387
+
417
388
  const uuidsInteractedWithThisFrame = [];
418
389
  currentIntersections.forEach(v => {
419
390
  clickEventKeys.forEach(key => {
@@ -615,7 +586,37 @@ const LunchboxWrapper = defineComponent({
615
586
  // ====================
616
587
 
617
588
  const containerFillStyle = props.sizePolicy === 'container' ? 'static' : 'absolute';
618
- const canvasFillStyle = props.sizePolicy === 'container' ? 'static' : 'fixed';
589
+ const canvasFillStyle = props.sizePolicy === 'container' ? 'static' : 'fixed'; // REACTIVE CUSTOM CAMERAS
590
+ // ====================
591
+ // find first camera with `type.name` property
592
+ // (which indicates a Lunch.Node)
593
+
594
+ const activeCamera = computed(() => {
595
+ const output = context.slots?.camera?.().find(c => c.type?.name);
596
+
597
+ if (output) {
598
+ return output;
599
+ }
600
+
601
+ return output;
602
+ }); // TODO: make custom cameras reactive
603
+
604
+ watch(activeCamera, async (newVal, oldVal) => {
605
+ // console.log('got camera', newVal)
606
+ if (newVal && newVal?.props?.key !== oldVal?.props?.key) {
607
+ // TODO: remove cast
608
+ camera.value = newVal; // TODO: why isn't this updating app camera?
609
+ // const el = await waitFor(() => newVal.el)
610
+ // console.log(el)
611
+ // camera.value = el
612
+ // console.log(newVal.uuid)
613
+ // updateGlobals?.({ camera: el })
614
+ }
615
+ }, {
616
+ immediate: true
617
+ }); // RENDER FUNCTION
618
+ // ====================
619
+
619
620
  return () => createVNode(Fragment, null, [context.slots?.renderer?.()?.length ? // TODO: remove `as any` cast
620
621
  renderer.value = context.slots?.renderer?.()[0] : // ...otherwise, add canvas...
621
622
  createVNode(Fragment, null, [createVNode("div", {
@@ -645,7 +646,7 @@ const LunchboxWrapper = defineComponent({
645
646
  }, {
646
647
  default: () => [context.slots?.default?.()]
647
648
  }), context.slots?.camera?.()?.length ? // TODO: remove `any` cast
648
- camera.value = context.slots?.camera?.()[0] : props.ortho || props.orthographic ? createVNode(resolveComponent("orthographicCamera"), mergeProps({
649
+ camera.value : props.ortho || props.orthographic ? createVNode(resolveComponent("orthographicCamera"), mergeProps({
649
650
  "ref": camera,
650
651
  "args": props.cameraArgs ?? []
651
652
  }, consolidatedCameraProperties), null) : createVNode(resolveComponent("perspectiveCamera"), mergeProps({
@@ -1113,6 +1114,26 @@ function isMinidomNode(item) {
1113
1114
  return item?.minidomType === 'RendererNode';
1114
1115
  }
1115
1116
 
1117
+ const globalsInjectionKey = Symbol();
1118
+ const updateGlobalsInjectionKey = Symbol();
1119
+ const setCustomRenderKey = Symbol();
1120
+ const clearCustomRenderKey = Symbol();
1121
+ const beforeRenderKey = Symbol();
1122
+ const onBeforeRenderKey = Symbol();
1123
+ const offBeforeRenderKey = Symbol();
1124
+ const afterRenderKey = Symbol();
1125
+ const onAfterRenderKey = Symbol();
1126
+ const offAfterRenderKey = Symbol();
1127
+ const frameIdKey = Symbol();
1128
+ const watchStopHandleKey = Symbol();
1129
+ const appRootNodeKey = Symbol();
1130
+ const appKey = Symbol();
1131
+ const appRenderersKey = Symbol();
1132
+ const appSceneKey = Symbol();
1133
+ const appCameraKey = Symbol();
1134
+ const lunchboxInteractables = Symbol();
1135
+ const startCallbackKey = Symbol();
1136
+
1116
1137
  const requestUpdate = opts => {
1117
1138
  if (typeof opts.app.config.globalProperties.lunchbox.frameId === 'number') {
1118
1139
  cancelAnimationFrame(opts.app.config.globalProperties.lunchbox.frameId);
@@ -1146,20 +1167,19 @@ const update = opts => {
1146
1167
  const {
1147
1168
  app,
1148
1169
  renderer,
1149
- scene,
1150
- camera
1170
+ scene
1151
1171
  } = opts; // BEFORE RENDER
1152
1172
 
1153
1173
  app.config.globalProperties.lunchbox.beforeRender.forEach(cb => {
1154
1174
  cb?.(opts);
1155
1175
  }); // RENDER
1156
1176
 
1157
- if (renderer && scene && camera) {
1177
+ if (renderer && scene && opts.app.config.globalProperties.lunchbox.camera) {
1158
1178
  if (app.customRender) {
1159
1179
  app.customRender(opts);
1160
1180
  } else {
1161
- renderer.render(toRaw(scene), // opts.app.config.globalProperties.lunchbox.camera!
1162
- toRaw(camera));
1181
+ renderer.render(toRaw(scene), opts.app.config.globalProperties.lunchbox.camera // toRaw(camera)
1182
+ );
1163
1183
  }
1164
1184
  } // AFTER RENDER
1165
1185
 
@@ -1169,55 +1189,81 @@ const update = opts => {
1169
1189
  });
1170
1190
  }; // before render
1171
1191
  // ====================
1172
- // TODO: document
1192
+
1193
+ /** Obtain callback methods for `onBeforeRender` and `offBeforeRender`. Usually used internally by Lunchbox. */
1173
1194
 
1174
1195
  const useBeforeRender = () => {
1175
1196
  return {
1176
1197
  onBeforeRender: inject(onBeforeRenderKey),
1177
1198
  offBeforeRender: inject(offBeforeRenderKey)
1178
1199
  };
1179
- }; // TODO: document
1200
+ };
1201
+ /** Run a function before every render.
1202
+ *
1203
+ * Note that if `updateSource` is set in the Lunchbox wrapper component, this will **only** run
1204
+ * before a render triggered by that `updateSource`. Normally, the function should run every frame.
1205
+ */
1180
1206
 
1181
1207
  const onBeforeRender = (cb, index = Infinity) => {
1182
1208
  useBeforeRender().onBeforeRender?.(cb, index);
1183
- }; // TODO: document
1209
+ };
1210
+ /** Remove a function from the `beforeRender` callback list. Useful for tearing down functions added
1211
+ * by `onBeforeRender`.
1212
+ */
1184
1213
 
1185
1214
  const offBeforeRender = cb => {
1186
1215
  useBeforeRender().offBeforeRender?.(cb);
1187
1216
  }; // after render
1188
1217
  // ====================
1189
- // TODO: document
1218
+
1219
+ /** Obtain callback methods for `onAfterRender` and `offAfterRender`. Usually used internally by Lunchbox. */
1190
1220
 
1191
1221
  const useAfterRender = () => {
1192
1222
  return {
1193
1223
  onAfterRender: inject(onBeforeRenderKey),
1194
1224
  offAfterRender: inject(offBeforeRenderKey)
1195
1225
  };
1196
- }; // TODO: document
1226
+ };
1227
+ /** Run a function after every render.
1228
+ *
1229
+ * Note that if `updateSource` is set in the Lunchbox wrapper component, this will **only** run
1230
+ * after a render triggered by that `updateSource`. Normally, the function should run every frame.
1231
+ */
1197
1232
 
1198
1233
  const onAfterRender = (cb, index = Infinity) => {
1199
1234
  useBeforeRender().onBeforeRender?.(cb, index);
1200
- }; // TODO: document
1235
+ };
1236
+ /** Remove a function from the `afterRender` callback list. Useful for tearing down functions added
1237
+ * by `onAfterRender`.
1238
+ */
1201
1239
 
1202
1240
  const offAfterRender = cb => {
1203
1241
  useBeforeRender().offBeforeRender?.(cb);
1204
- }; // TODO: document
1242
+ };
1243
+ /** Obtain a function used to cancel the current update frame. Use `cancelUpdate` if you wish
1244
+ * to immediately invoke the cancellation function. Usually used internally by Lunchbox.
1245
+ */
1205
1246
 
1206
1247
  const useCancelUpdate = () => {
1207
1248
  const frameId = inject(frameIdKey);
1208
1249
  return () => {
1209
1250
  if (frameId !== undefined) cancelAnimationFrame(frameId);
1210
1251
  };
1211
- }; // TODO: document
1252
+ };
1253
+ /** Cancel the current update frame. Usually used internally by Lunchbox. */
1212
1254
 
1213
1255
  const cancelUpdate = () => {
1214
1256
  useCancelUpdate()?.();
1215
- }; // TODO: document
1257
+ };
1258
+ /** Obtain a function used to cancel an update source. Use `cancelUpdateSource` if you wish to
1259
+ * immediately invoke the cancellation function. Usually used internally by Lunchbox.
1260
+ */
1216
1261
 
1217
1262
  const useCancelUpdateSource = () => {
1218
1263
  const cancel = inject(watchStopHandleKey);
1219
1264
  return () => cancel?.();
1220
- }; // TODO: document
1265
+ };
1266
+ /** Cancel an update source. Usually used internally by Lunchbox. */
1221
1267
 
1222
1268
  const cancelUpdateSource = () => {
1223
1269
  useCancelUpdateSource()?.();
@@ -1268,6 +1314,7 @@ function updateObjectProp({
1268
1314
  const fullPath = [nestedProperty, finalKey].filter(Boolean).join('.');
1269
1315
  liveProperty = liveProperty = get(target, fullPath);
1270
1316
  } // change property
1317
+ // first, save as array in case we need to spread it
1271
1318
 
1272
1319
 
1273
1320
  if (liveProperty && isNumber(value) && liveProperty.setScalar) {
@@ -1278,14 +1325,24 @@ function updateObjectProp({
1278
1325
  const nextValueAsArray = Array.isArray(value) ? value : [value];
1279
1326
  target[finalKey].set(...nextValueAsArray);
1280
1327
  } else if (typeof liveProperty === 'function') {
1281
- // if property is a function, let's try calling it
1282
- liveProperty.bind(node.instance)(...value); // pass the result to the parent
1328
+ // some function properties are set rather than called, so let's handle them
1329
+ if (finalKey.toLowerCase() === 'onbeforerender' || finalKey.toLowerCase() === 'onafterrender') {
1330
+ target[finalKey] = value;
1331
+ } else {
1332
+ if (!Array.isArray(value)) {
1333
+ throw new Error('Arguments on a declarative method must be wrapped in an array.\nWorks:\n<example :methodCall="[256]" />\nDoesn\'t work:\n<example :methodCall="256" />');
1334
+ } // if property is a function, let's try calling it
1335
+
1336
+
1337
+ liveProperty.bind(node.instance)(...value);
1338
+ } // pass the result to the parent
1283
1339
  // const parent = node.parentNode
1284
1340
  // if (parent) {
1285
1341
  // const parentAsLunchboxNode = parent as Lunchbox.Node
1286
1342
  // parentAsLunchboxNode.attached[finalKey] = result
1287
1343
  // ; (parentAsLunchboxNode.instance as any)[finalKey] = result
1288
1344
  // }
1345
+
1289
1346
  } else if (get(target, finalKey, undefined) !== undefined) {
1290
1347
  // blank strings evaluate to `true`
1291
1348
  // <mesh castShadow receiveShadow /> will work the same as
@@ -1575,33 +1632,51 @@ const createNodeOps = () => {
1575
1632
  };
1576
1633
  };
1577
1634
 
1578
- /** The current camera. Often easier to use `useCamera` instead of this. */
1579
- // TODO: update docs
1635
+ /** The current camera as a computed value. */
1580
1636
 
1581
- const camera = ensuredCamera; // TODO: update docs
1637
+ const useCamera = () => inject(appCameraKey);
1638
+ /** Run a function using the current camera when it's present. */
1582
1639
 
1583
- const useCamera = () => ensuredCamera();
1584
- /** The current renderer as a computed value. Often easier to use `useRenderer` instead of this. */
1640
+ const onCameraReady = cb => {
1641
+ const stopWatch = watch(useCamera(), newVal => {
1642
+ if (newVal) {
1643
+ cb(newVal);
1644
+ stopWatch();
1645
+ }
1646
+ }, {
1647
+ immediate: true
1648
+ });
1649
+ };
1650
+ /** The current renderer as a computed value. */
1585
1651
 
1586
- const renderer = ensureRenderer;
1652
+ const useRenderer = () => inject(appRenderersKey);
1587
1653
  /** Run a function using the current renderer when it's present. */
1588
1654
 
1589
- const useRenderer = () => ensureRenderer();
1590
- /** The current scene. Often easier to use `useScene` instead of this. */
1591
- // TODO: update docs
1655
+ const onRendererReady = cb => {
1656
+ const stopWatch = watch(useRenderer(), newVal => {
1657
+ if (newVal) {
1658
+ cb(newVal);
1659
+ stopWatch();
1660
+ }
1661
+ }, {
1662
+ immediate: true
1663
+ });
1664
+ };
1665
+ /** The current scene as a computed value. */
1592
1666
 
1593
- const scene = ensuredScene;
1667
+ const useScene = () => inject(appSceneKey);
1594
1668
  /** Run a function using the current scene when it's present. */
1595
- // TODO: update docs
1596
1669
 
1597
- function useScene(callback) {
1598
- return watch(scene, newVal => {
1599
- if (!newVal) return;
1600
- callback(newVal.value);
1670
+ const onSceneReady = cb => {
1671
+ const stopWatch = watch(useScene(), newVal => {
1672
+ if (newVal) {
1673
+ cb(newVal);
1674
+ stopWatch();
1675
+ }
1601
1676
  }, {
1602
1677
  immediate: true
1603
1678
  });
1604
- } // CUSTOM RENDER SUPPORT
1679
+ }; // CUSTOM RENDER SUPPORT
1605
1680
  // ====================
1606
1681
 
1607
1682
  /** Set a custom render function, overriding the Lunchbox app's default render function.
@@ -1658,14 +1733,15 @@ const useUpdateGlobals = () => inject(updateGlobalsInjectionKey);
1658
1733
 
1659
1734
  const updateGlobals = newValue => {
1660
1735
  useUpdateGlobals()?.(newValue);
1661
- }; // TODO: document
1662
-
1663
- const useRootNode = () => inject(appRootNodeKey); // TODO: document
1736
+ };
1737
+ /** Use the current Lunchbox app. Usually used internally by Lunchbox. */
1664
1738
 
1665
- const useApp = () => inject(appKey); // TODO: document
1739
+ const useApp = () => inject(appKey);
1740
+ /** Obtain a list of the start callback functions. Usually used internally by Lunchbox. */
1666
1741
 
1667
- const useStartCallbacks = () => inject(startCallbackKey); //[] as Lunch.UpdateCallback[]
1668
- // TODO: document
1742
+ const useStartCallbacks = () => inject(startCallbackKey);
1743
+ /** Run a given callback once when the Lunchbox app starts. Include an index to
1744
+ * splice the callback at that index in the callback queue. */
1669
1745
 
1670
1746
  const onStart = (cb, index = Infinity) => {
1671
1747
  const callbacks = useStartCallbacks();
@@ -1675,7 +1751,8 @@ const onStart = (cb, index = Infinity) => {
1675
1751
  } else {
1676
1752
  callbacks?.splice(index, 0, cb);
1677
1753
  }
1678
- }; // TODO: document
1754
+ };
1755
+ /** Obtain a list of interactable objects (registered via onClick, onHover, etc events). Usually used internally by Lunchbox. */
1679
1756
 
1680
1757
  const useLunchboxInteractables = () => inject(lunchboxInteractables); // CREATE APP
1681
1758
  // ====================
@@ -1838,4 +1915,4 @@ const createApp = root => {
1838
1915
  return app;
1839
1916
  };
1840
1917
 
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 };
1918
+ export { MiniDom, addEventListener, afterRenderKey, appCameraKey, appKey, appRenderersKey, appRootNodeKey, appSceneKey, beforeRenderKey, cancelUpdate, cancelUpdateSource, clearCustomRender, clearCustomRenderKey, createApp, createCommentNode, createDomNode, createNode, createTextNode, extend, find, frameIdKey, globalsInjectionKey, instantiateThreeObject, isMinidomNode, lunchboxInteractables, nestedPropertiesToCheck, offAfterRender, offAfterRenderKey, offBeforeRender, offBeforeRenderKey, onAfterRender, onAfterRenderKey, onBeforeRender, onBeforeRenderKey, onCameraReady, onRendererReady, onSceneReady, onStart, setCustomRender, setCustomRenderKey, startCallbackKey, update, updateGlobals, updateGlobalsInjectionKey, updateObjectProp, useAfterRender, useApp, useBeforeRender, useCamera, useCancelUpdate, useCancelUpdateSource, useCustomRender, useGlobals, useLunchboxInteractables, useRenderer, useScene, useStartCallbacks, useUpdateGlobals, watchStopHandleKey };
@@ -11,15 +11,8 @@
11
11
  </template>
12
12
 
13
13
  <script lang="ts" setup>
14
- import { computed, ref, watch } from 'vue'
15
- import {
16
- onBeforeRender,
17
- globals,
18
- Lunch,
19
- // camera,
20
- useRenderer,
21
- useCamera,
22
- } from '../src'
14
+ import { computed, ref } from 'vue'
15
+ import { onBeforeRender, Lunch, useCamera, useRenderer } from '../src'
23
16
 
24
17
  // props
25
18
  const props = defineProps<{
@@ -33,9 +26,6 @@ const ready = computed(() => {
33
26
  const camera = useCamera()
34
27
  const renderer = useRenderer()
35
28
  const orbitArgs = computed(() => [camera.value, renderer.value?.domElement])
36
- // watch(() => orbitArgs.value, console.log, { immediate: true })
37
- // console.log(renderer)
38
- watch(camera, console.log)
39
29
 
40
30
  // update
41
31
  const controls = ref<Lunch.LunchboxComponent>()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lunchboxjs",
3
- "version": "0.2.1001-beta.1",
3
+ "version": "0.2.1001-beta.2",
4
4
  "scripts": {
5
5
  "dev": "vite -c utils/vite.config.ts",
6
6
  "build": "vue-tsc --noEmit && vite build -c utils/vite.config.ts",
@@ -14,6 +14,9 @@
14
14
  "docs:serve": "vitepress serve docs",
15
15
  "demo:create": "node utils/createExample"
16
16
  },
17
+ "engines": {
18
+ "npm": ">=16.0.0"
19
+ },
17
20
  "dependencies": {
18
21
  "uuid": "8.3.2",
19
22
  "vue": "^3.2.16"
@@ -1,10 +1,10 @@
1
1
  import { defineComponent, onBeforeUnmount, ref, watch } from 'vue'
2
2
  import {
3
- Lunch,
4
3
  useCamera,
4
+ Lunch,
5
5
  useGlobals,
6
6
  useLunchboxInteractables,
7
- useRenderer,
7
+ onRendererReady,
8
8
  } from '..'
9
9
  import * as THREE from 'three'
10
10
  import { offBeforeRender, onBeforeRender } from '../core'
@@ -13,8 +13,6 @@ export const LunchboxEventHandlers = defineComponent({
13
13
  name: 'LunchboxEventHandlers',
14
14
  setup() {
15
15
  const interactables = useLunchboxInteractables()
16
- const camera = useCamera()
17
- const renderer = useRenderer()
18
16
  const globals = useGlobals()
19
17
  const mousePos = ref({ x: Infinity, y: Infinity })
20
18
  const inputActive = ref(false)
@@ -49,38 +47,34 @@ export const LunchboxEventHandlers = defineComponent({
49
47
  }
50
48
 
51
49
  // add mouse listener to renderer DOM element when the element is ready
52
- const stopWatch = watch(
53
- renderer,
54
- (v) => {
55
- if (!v?.domElement) return
56
-
57
- // we have a DOM element, so let's add mouse listeners
58
- const { domElement } = v
59
-
60
- const mouseMoveListener = (evt: PointerEvent) => {
61
- const screenWidth = (domElement.width ?? 1) / globals.dpr
62
- const screenHeight = (domElement.height ?? 1) / globals.dpr
63
- mousePos.value.x = (evt.offsetX / screenWidth) * 2 - 1
64
- mousePos.value.y = -(evt.offsetY / screenHeight) * 2 + 1
65
- }
66
- const mouseDownListener = () => (inputActive.value = true)
67
- const mouseUpListener = () => (inputActive.value = false)
68
-
69
- // add mouse events
70
- domElement.addEventListener('pointermove', mouseMoveListener)
71
- domElement.addEventListener('pointerdown', mouseDownListener)
72
- domElement.addEventListener('pointerup', mouseUpListener)
73
-
74
- // stop the watcher
75
- stopWatch()
76
- },
77
- { immediate: true }
78
- )
50
+ onRendererReady((v) => {
51
+ if (!v?.domElement) return
52
+
53
+ // we have a DOM element, so let's add mouse listeners
54
+ const { domElement } = v
55
+
56
+ const mouseMoveListener = (evt: PointerEvent) => {
57
+ const screenWidth = (domElement.width ?? 1) / globals.dpr
58
+ const screenHeight = (domElement.height ?? 1) / globals.dpr
59
+ mousePos.value.x = (evt.offsetX / screenWidth) * 2 - 1
60
+ mousePos.value.y = -(evt.offsetY / screenHeight) * 2 + 1
61
+ }
62
+ const mouseDownListener = () => (inputActive.value = true)
63
+ const mouseUpListener = () => (inputActive.value = false)
64
+
65
+ // add mouse events
66
+ domElement.addEventListener('pointermove', mouseMoveListener)
67
+ domElement.addEventListener('pointerdown', mouseDownListener)
68
+ domElement.addEventListener('pointerup', mouseUpListener)
69
+ })
79
70
 
71
+ const camera = useCamera()
80
72
  const update = () => {
81
73
  const c = camera.value
82
74
  if (!c) return
83
75
 
76
+ // console.log(camera.value)
77
+
84
78
  raycaster.setFromCamera(mousePos.value, c)
85
79
  const intersections = raycaster.intersectObjects(
86
80
  interactables?.value.map(
@@ -202,6 +196,10 @@ export const LunchboxEventHandlers = defineComponent({
202
196
  'onPointerUp',
203
197
  ]
204
198
  watch(inputActive, (isDown) => {
199
+ // run raycaster on click (necessary when `update` is not automatically called,
200
+ // for example in `updateSource` functions)
201
+ update()
202
+
205
203
  // meshes with multiple intersections receive multiple callbacks by default -
206
204
  // let's make it so they only receive one callback of each type per frame.
207
205
  // (ie usually when you click on a mesh, you expect only one click event to fire, even
@@ -1,10 +1,12 @@
1
1
  import {
2
+ computed,
2
3
  defineComponent,
3
4
  onBeforeUnmount,
4
5
  onMounted,
5
6
  PropType,
6
7
  reactive,
7
8
  ref,
9
+ watch,
8
10
  WatchSource,
9
11
  } from 'vue'
10
12
  import { cancelUpdate, cancelUpdateSource, MiniDom, update } from '../../core'
@@ -14,6 +16,8 @@ import { prepCanvas } from './prepCanvas'
14
16
  import { useUpdateGlobals, useStartCallbacks } from '../..'
15
17
  import { LunchboxScene } from './LunchboxScene'
16
18
  import { LunchboxEventHandlers } from '../LunchboxEventHandlers'
19
+ import * as Keys from '../../keys'
20
+ import { waitFor } from '../../utils'
17
21
 
18
22
  /** fixed & fill styling for container */
19
23
  const fillStyle = (position: string) => {
@@ -212,6 +216,43 @@ export const LunchboxWrapper = defineComponent({
212
216
  const canvasFillStyle =
213
217
  props.sizePolicy === 'container' ? 'static' : 'fixed'
214
218
 
219
+ // REACTIVE CUSTOM CAMERAS
220
+ // ====================
221
+ // find first camera with `type.name` property
222
+ // (which indicates a Lunch.Node)
223
+ const activeCamera = computed(() => {
224
+ const output = context.slots
225
+ ?.camera?.()
226
+ .find((c) => (c.type as any)?.name)
227
+ if (output) {
228
+ return output
229
+ }
230
+
231
+ return output
232
+ })
233
+
234
+ // TODO: make custom cameras reactive
235
+ watch(
236
+ activeCamera,
237
+ async (newVal, oldVal) => {
238
+ // console.log('got camera', newVal)
239
+ if (newVal && newVal?.props?.key !== oldVal?.props?.key) {
240
+ // TODO: remove cast
241
+ camera.value = newVal as any
242
+
243
+ // TODO: why isn't this updating app camera?
244
+ // const el = await waitFor(() => newVal.el)
245
+ // console.log(el)
246
+ // camera.value = el
247
+ // console.log(newVal.uuid)
248
+ // updateGlobals?.({ camera: el })
249
+ }
250
+ },
251
+ { immediate: true }
252
+ )
253
+
254
+ // RENDER FUNCTION
255
+ // ====================
215
256
  return () => (
216
257
  <>
217
258
  {/* use renderer slot if provided... */}
@@ -270,7 +311,7 @@ export const LunchboxWrapper = defineComponent({
270
311
  {/* use camera slot if provided... */}
271
312
  {context.slots?.camera?.()?.length ? (
272
313
  // TODO: remove `any` cast
273
- (camera.value = context.slots?.camera?.()[0] as any)
314
+ camera.value
274
315
  ) : props.ortho || props.orthographic ? (
275
316
  <orthographicCamera
276
317
  ref={camera}
package/src/core/index.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  export * from './createNode'
2
- export * from './ensure'
3
2
  export * from './interaction'
4
3
  export * from './extend'
5
4
  export * from './instantiateThreeObject'