lunchboxjs 0.1.4017 → 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.
Files changed (41) hide show
  1. package/dist/lunchboxjs.js +1666 -1659
  2. package/dist/lunchboxjs.min.js +1 -1
  3. package/dist/lunchboxjs.module.js +1620 -1659
  4. package/extras/OrbitControlsWrapper.vue +12 -4
  5. package/package.json +12 -4
  6. package/src/components/LunchboxEventHandlers.tsx +239 -0
  7. package/src/components/LunchboxWrapper/LunchboxScene.tsx +8 -0
  8. package/src/components/LunchboxWrapper/LunchboxWrapper.tsx +300 -0
  9. package/src/components/LunchboxWrapper/prepCanvas.ts +27 -21
  10. package/src/components/LunchboxWrapper/resizeCanvas.ts +13 -12
  11. package/src/components/autoGeneratedComponents.ts +1 -1
  12. package/src/components/index.ts +2 -4
  13. package/src/core/createNode.ts +2 -18
  14. package/src/core/ensure.ts +9 -196
  15. package/src/core/extend.ts +1 -1
  16. package/src/core/index.ts +0 -2
  17. package/src/core/instantiateThreeObject/index.ts +7 -2
  18. package/src/core/instantiateThreeObject/processProps.ts +1 -1
  19. package/src/core/interaction.ts +55 -0
  20. package/src/core/minidom.ts +5 -9
  21. package/src/core/update.ts +74 -51
  22. package/src/core/updateObjectProp.ts +5 -14
  23. package/src/index.ts +248 -76
  24. package/src/keys.ts +25 -0
  25. package/src/nodeOps/createElement.ts +2 -5
  26. package/src/nodeOps/index.ts +70 -57
  27. package/src/nodeOps/insert.ts +11 -32
  28. package/src/nodeOps/remove.ts +1 -17
  29. package/src/types.ts +34 -10
  30. package/src/utils/index.ts +1 -4
  31. package/dist/.DS_Store +0 -0
  32. package/src/.DS_Store +0 -0
  33. package/src/components/LunchboxWrapper/LunchboxWrapper.ts +0 -312
  34. package/src/components/catalogue.ts +0 -3
  35. package/src/core/.DS_Store +0 -0
  36. package/src/core/allNodes.ts +0 -4
  37. package/src/core/interaction/index.ts +0 -102
  38. package/src/core/interaction/input.ts +0 -4
  39. package/src/core/interaction/interactables.ts +0 -14
  40. package/src/core/interaction/setupAutoRaycaster.ts +0 -224
  41. package/src/core/start.ts +0 -11
@@ -12,7 +12,14 @@
12
12
 
13
13
  <script lang="ts" setup>
14
14
  import { computed, ref, watch } from 'vue'
15
- import { onBeforeRender, globals, Lunch, camera, renderer } from '../src'
15
+ import {
16
+ onBeforeRender,
17
+ globals,
18
+ Lunch,
19
+ // camera,
20
+ useRenderer,
21
+ useCamera,
22
+ } from '../src'
16
23
 
17
24
  // props
18
25
  const props = defineProps<{
@@ -20,14 +27,15 @@ const props = defineProps<{
20
27
  }>()
21
28
 
22
29
  // computed
23
- // const camera = globals.camera
24
- // const renderer = globals.renderer
25
30
  const ready = computed(() => {
26
- return camera.value !== null && renderer.value?.domElement
31
+ return camera.value !== null && renderer.value.domElement
27
32
  })
33
+ const camera = useCamera()
34
+ const renderer = useRenderer()
28
35
  const orbitArgs = computed(() => [camera.value, renderer.value?.domElement])
29
36
  // watch(() => orbitArgs.value, console.log, { immediate: true })
30
37
  // console.log(renderer)
38
+ watch(camera, console.log)
31
39
 
32
40
  // update
33
41
  const controls = ref<Lunch.LunchboxComponent>()
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "lunchboxjs",
3
- "version": "0.1.4017",
3
+ "version": "0.2.1001-beta.1",
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",
7
7
  "build:tsc": "tsc --project ./utils/tsconfig.lib.json",
8
8
  "build:rollup": "rollup -c ./utils/lib-rollup.ts",
9
9
  "build:dts": "cp utils/lib-dts.d.ts dist/lunchboxjs.es.d.ts && cp utils/lib-dts.d.ts dist/lunchboxjs.umd.d.ts",
10
- "build:lib": "npm run build:tsc && npm run build:rollup && npm run build:dts",
10
+ "build:lib": "rimraf js && npm run build:tsc && npm run build:rollup && npm run build:dts",
11
11
  "prepare": "npm run build:lib",
12
12
  "docs:dev": "vitepress dev docs",
13
13
  "docs:build": "vitepress build docs",
@@ -19,25 +19,33 @@
19
19
  "vue": "^3.2.16"
20
20
  },
21
21
  "devDependencies": {
22
+ "@babel/plugin-transform-runtime": "7.18.10",
23
+ "@rollup/plugin-babel": "5.3.1",
22
24
  "@rollup/plugin-node-resolve": "13.0.5",
23
25
  "@rollup/plugin-typescript": "8.3.0",
24
26
  "@types/lodash": "4.14.175",
27
+ "@types/node": "18.6.3",
25
28
  "@types/three": "0.141.0",
26
29
  "@types/uuid": "8.3.1",
27
- "@vitejs/plugin-vue": "^1.9.3",
30
+ "@vitejs/plugin-vue": "3.0.1",
31
+ "@vitejs/plugin-vue-jsx": "2.0.0",
32
+ "@vue/babel-plugin-jsx": "1.1.1",
28
33
  "chroma-js": "2.1.2",
29
34
  "kolorist": "1.5.1",
30
35
  "lodash": "4.17.21",
31
36
  "nice-color-palettes": "3.0.0",
32
37
  "prompt": "1.3.0",
33
38
  "prompts": "2.4.2",
39
+ "rimraf": "3.0.2",
34
40
  "rollup-plugin-delete": "2.0.0",
41
+ "rollup-plugin-jsx": "1.0.3",
35
42
  "rollup-plugin-terser": "7.0.2",
36
43
  "three": "0.141.0",
37
44
  "typescript": "^4.4.3",
38
- "vite": "^2.6.4",
45
+ "vite": "3.0.4",
39
46
  "vite-plugin-glsl": "0.0.5",
40
47
  "vitepress": "0.20.1",
48
+ "vue-jsx-factory": "0.3.0",
41
49
  "vue-tsc": "^0.3.0"
42
50
  },
43
51
  "peerDependencies": {
@@ -0,0 +1,239 @@
1
+ import { defineComponent, onBeforeUnmount, ref, watch } from 'vue'
2
+ import {
3
+ Lunch,
4
+ useCamera,
5
+ useGlobals,
6
+ useLunchboxInteractables,
7
+ useRenderer,
8
+ } from '..'
9
+ import * as THREE from 'three'
10
+ import { offBeforeRender, onBeforeRender } from '../core'
11
+
12
+ export const LunchboxEventHandlers = defineComponent({
13
+ name: 'LunchboxEventHandlers',
14
+ setup() {
15
+ const interactables = useLunchboxInteractables()
16
+ const camera = useCamera()
17
+ const renderer = useRenderer()
18
+ const globals = useGlobals()
19
+ const mousePos = ref({ x: Infinity, y: Infinity })
20
+ const inputActive = ref(false)
21
+
22
+ let currentIntersections: Array<{
23
+ element: Lunch.Node
24
+ intersection: THREE.Intersection<THREE.Object3D>
25
+ }> = []
26
+
27
+ const raycaster = new THREE.Raycaster(
28
+ new THREE.Vector3(),
29
+ new THREE.Vector3(0, 0, -1)
30
+ )
31
+
32
+ const fireEventsFromIntersections = ({
33
+ element,
34
+ eventKeys,
35
+ intersection,
36
+ }: {
37
+ element: Lunch.Node
38
+ eventKeys: Array<Lunch.EventKey>
39
+ intersection: THREE.Intersection<THREE.Object3D>
40
+ }) => {
41
+ if (!element) return
42
+ eventKeys.forEach((eventKey) => {
43
+ if (element.eventListeners[eventKey]) {
44
+ element.eventListeners[eventKey].forEach((cb) => {
45
+ cb({ intersection })
46
+ })
47
+ }
48
+ })
49
+ }
50
+
51
+ // 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
+ )
79
+
80
+ const update = () => {
81
+ const c = camera.value
82
+ if (!c) return
83
+
84
+ raycaster.setFromCamera(mousePos.value, c)
85
+ const intersections = raycaster.intersectObjects(
86
+ interactables?.value.map(
87
+ (v) => v.instance as any as THREE.Object3D
88
+ ) ?? []
89
+ )
90
+
91
+ let enterValues: Array<THREE.Intersection<THREE.Object3D>> = [],
92
+ sameValues: Array<THREE.Intersection<THREE.Object3D>> = [],
93
+ leaveValues: Array<THREE.Intersection<THREE.Object3D>> = [],
94
+ entering: Array<{
95
+ element: Lunch.Node
96
+ intersection: THREE.Intersection<THREE.Object3D>
97
+ }> = [],
98
+ staying: Array<{
99
+ element: Lunch.Node
100
+ intersection: THREE.Intersection<THREE.Object3D>
101
+ }> = []
102
+
103
+ // intersection arrays
104
+ leaveValues = currentIntersections.map((v) => v.intersection)
105
+
106
+ // element arrays
107
+ intersections?.forEach((intersection) => {
108
+ const currentIdx = currentIntersections.findIndex(
109
+ (v) => v.intersection.object === intersection.object
110
+ )
111
+ if (currentIdx === -1) {
112
+ // new intersection
113
+ enterValues.push(intersection)
114
+
115
+ const found = interactables?.value.find(
116
+ (v) => v.instance?.uuid === intersection.object.uuid
117
+ )
118
+ if (found) {
119
+ entering.push({ element: found, intersection })
120
+ }
121
+ } else {
122
+ // existing intersection
123
+ sameValues.push(intersection)
124
+
125
+ const found = interactables?.value.find(
126
+ (v) => v.instance?.uuid === intersection.object.uuid
127
+ )
128
+ if (found) {
129
+ staying.push({ element: found, intersection })
130
+ }
131
+ }
132
+ // this is a current intersection, so it won't be in our `leave` array
133
+ const leaveIdx = leaveValues.findIndex(
134
+ (v) => v.object.uuid === intersection.object.uuid
135
+ )
136
+ if (leaveIdx !== -1) {
137
+ leaveValues.splice(leaveIdx, 1)
138
+ }
139
+ })
140
+
141
+ const leaving: Array<{
142
+ element: Lunch.Node
143
+ intersection: THREE.Intersection<THREE.Object3D>
144
+ }> = leaveValues.map((intersection) => {
145
+ return {
146
+ element: interactables?.value.find(
147
+ (interactable) =>
148
+ interactable.instance?.uuid ===
149
+ intersection.object.uuid
150
+ ) as any as Lunch.Node,
151
+ intersection,
152
+ }
153
+ })
154
+
155
+ // new interactions
156
+ entering.forEach(({ element, intersection }) => {
157
+ fireEventsFromIntersections({
158
+ element,
159
+ eventKeys: ['onPointerEnter'],
160
+ intersection,
161
+ })
162
+ })
163
+
164
+ // unchanged interactions
165
+ staying.forEach(({ element, intersection }) => {
166
+ const eventKeys: Array<Lunch.EventKey> = [
167
+ 'onPointerOver',
168
+ 'onPointerMove',
169
+ ]
170
+ fireEventsFromIntersections({
171
+ element,
172
+ eventKeys,
173
+ intersection,
174
+ })
175
+ })
176
+
177
+ // exited interactions
178
+ leaving.forEach(({ element, intersection }) => {
179
+ const eventKeys: Array<Lunch.EventKey> = [
180
+ 'onPointerLeave',
181
+ 'onPointerOut',
182
+ ]
183
+ fireEventsFromIntersections({
184
+ element,
185
+ eventKeys,
186
+ intersection,
187
+ })
188
+ })
189
+
190
+ currentIntersections = ([] as any).concat(entering, staying)
191
+ }
192
+
193
+ // update function
194
+ onBeforeRender(update)
195
+
196
+ const teardown = () => offBeforeRender(update)
197
+ onBeforeUnmount(teardown)
198
+
199
+ const clickEventKeys: Lunch.EventKey[] = [
200
+ 'onClick',
201
+ 'onPointerDown',
202
+ 'onPointerUp',
203
+ ]
204
+ watch(inputActive, (isDown) => {
205
+ // meshes with multiple intersections receive multiple callbacks by default -
206
+ // let's make it so they only receive one callback of each type per frame.
207
+ // (ie usually when you click on a mesh, you expect only one click event to fire, even
208
+ // if there are technically multiple intersections with that mesh)
209
+ const uuidsInteractedWithThisFrame: string[] = []
210
+ currentIntersections.forEach((v) => {
211
+ clickEventKeys.forEach((key) => {
212
+ const id = v.element.uuid + key
213
+ if (
214
+ isDown &&
215
+ (key === 'onClick' || key === 'onPointerDown')
216
+ ) {
217
+ if (!uuidsInteractedWithThisFrame.includes(id)) {
218
+ v.element.eventListeners[key]?.forEach((cb) =>
219
+ cb({ intersection: v.intersection })
220
+ )
221
+ uuidsInteractedWithThisFrame.push(id)
222
+ }
223
+ } else if (!isDown && key === 'onPointerUp') {
224
+ if (!uuidsInteractedWithThisFrame.includes(id)) {
225
+ v.element.eventListeners[key]?.forEach((cb) =>
226
+ cb({ intersection: v.intersection })
227
+ )
228
+ uuidsInteractedWithThisFrame.push(id)
229
+ }
230
+ }
231
+ })
232
+ })
233
+ })
234
+
235
+ // return arbitrary object to ensure instantiation
236
+ // TODO: why can't we return a <raycaster/> here?
237
+ return () => <object3D />
238
+ },
239
+ })
@@ -0,0 +1,8 @@
1
+ import { defineComponent } from 'vue'
2
+
3
+ export const LunchboxScene = defineComponent({
4
+ name: 'LunchboxScene',
5
+ setup(props, { slots }) {
6
+ return () => <scene>{slots.default?.()}</scene>
7
+ },
8
+ })
@@ -0,0 +1,300 @@
1
+ import {
2
+ defineComponent,
3
+ onBeforeUnmount,
4
+ onMounted,
5
+ PropType,
6
+ reactive,
7
+ ref,
8
+ WatchSource,
9
+ } from 'vue'
10
+ import { cancelUpdate, cancelUpdateSource, MiniDom, update } from '../../core'
11
+ import { Lunch, useApp, useGlobals, useLunchboxInteractables } from '../..'
12
+ import * as THREE from 'three'
13
+ import { prepCanvas } from './prepCanvas'
14
+ import { useUpdateGlobals, useStartCallbacks } from '../..'
15
+ import { LunchboxScene } from './LunchboxScene'
16
+ import { LunchboxEventHandlers } from '../LunchboxEventHandlers'
17
+
18
+ /** fixed & fill styling for container */
19
+ const fillStyle = (position: string) => {
20
+ return {
21
+ position,
22
+ top: 0,
23
+ right: 0,
24
+ bottom: 0,
25
+ left: 0,
26
+ width: '100%',
27
+ height: '100%',
28
+ display: 'block',
29
+ }
30
+ }
31
+
32
+ export const LunchboxWrapper = defineComponent({
33
+ name: 'Lunchbox',
34
+ props: {
35
+ // These should match the Lunchbox.WrapperProps interface
36
+ background: String,
37
+ cameraArgs: Array,
38
+ cameraLook: Array as unknown as PropType<Lunch.Vector3AsArray>,
39
+ cameraLookAt: Array as unknown as PropType<Lunch.Vector3AsArray>,
40
+ cameraPosition: Array as unknown as PropType<Lunch.Vector3AsArray>,
41
+ dpr: Number,
42
+ ortho: Boolean,
43
+ orthographic: Boolean,
44
+ r3f: Boolean,
45
+ rendererArguments: Object,
46
+ rendererProperties: Object,
47
+ sizePolicy: String as PropType<Lunch.SizePolicy>,
48
+ shadow: [Boolean, Object],
49
+ transparent: Boolean,
50
+ zoom: Number,
51
+ updateSource: Object as PropType<WatchSource>,
52
+ },
53
+ setup(props, context) {
54
+ const canvas = ref<MiniDom.RendererDomNode>()
55
+ let dpr = props.dpr ?? -1
56
+ const container = ref<MiniDom.RendererDomNode>()
57
+ const renderer = ref<Lunch.LunchboxComponent<THREE.Renderer>>()
58
+ const camera = ref<Lunch.LunchboxComponent<THREE.Camera>>()
59
+ const scene = ref<Lunch.LunchboxComponent<THREE.Scene>>()
60
+ const globals = useGlobals()
61
+ const updateGlobals = useUpdateGlobals()
62
+ const app = useApp()
63
+ const consolidatedCameraProperties: Record<string, any> = reactive({})
64
+ const startCallbacks = useStartCallbacks()
65
+
66
+ // https://threejs.org/docs/index.html#manual/en/introduction/Color-management
67
+ if (props.r3f && (THREE as any)?.ColorManagement) {
68
+ ;(THREE as any).ColorManagement.legacyMode = false
69
+ }
70
+
71
+ const interactables = useLunchboxInteractables()
72
+
73
+ // MOUNT
74
+ // ====================
75
+ onMounted(async () => {
76
+ // canvas needs to exist (or user needs to handle it on their own)
77
+ if (!canvas.value && !context.slots?.renderer?.()?.length)
78
+ throw new Error('missing canvas')
79
+
80
+ // no camera provided, so let's create one
81
+ if (!context.slots?.camera?.()?.length) {
82
+ if (props.cameraPosition) {
83
+ consolidatedCameraProperties.position = props.cameraPosition
84
+ }
85
+ if (props.cameraLook || props.cameraLookAt) {
86
+ consolidatedCameraProperties.lookAt =
87
+ props.cameraLook || props.cameraLookAt
88
+ }
89
+ if (props.zoom !== undefined) {
90
+ consolidatedCameraProperties.zoom = props.zoom
91
+ }
92
+ }
93
+
94
+ // SCENE
95
+ // ====================
96
+ // set background color
97
+ if (scene.value?.$el?.instance && props.background) {
98
+ scene.value.$el.instance.background = new THREE.Color(
99
+ props.background
100
+ )
101
+ }
102
+
103
+ // MISC PROPERTIES
104
+ // ====================
105
+ if (dpr === -1) {
106
+ dpr = window.devicePixelRatio
107
+ }
108
+ updateGlobals?.({ dpr })
109
+
110
+ while (
111
+ !renderer.value?.$el?.instance &&
112
+ // TODO: remove `as any`
113
+ !(renderer.value as any)?.component?.ctx.$el?.instance
114
+ ) {
115
+ await new Promise((r) => requestAnimationFrame(r))
116
+ }
117
+
118
+ while (
119
+ !scene.value?.$el?.instance &&
120
+ // TODO: remove `as any`
121
+ !(scene.value as any)?.component?.ctx.$el?.instance
122
+ ) {
123
+ await new Promise((r) => requestAnimationFrame(r))
124
+ }
125
+
126
+ const normalizedRenderer = (renderer.value?.$el?.instance ??
127
+ (renderer.value as any)?.component?.ctx.$el
128
+ ?.instance) as THREE.WebGLRenderer
129
+
130
+ normalizedRenderer.setPixelRatio(globals.dpr)
131
+
132
+ const normalizedScene = (scene.value?.$el?.instance ??
133
+ (scene.value as any)?.component?.ctx.$el
134
+ ?.instance) as THREE.Scene
135
+
136
+ const normalizedCamera = (camera.value?.$el?.instance ??
137
+ (camera.value as any)?.component?.ctx.$el
138
+ ?.instance) as THREE.Camera
139
+
140
+ // TODO: update DPR on monitor switch
141
+ // prep canvas (sizing, observe, unmount, etc)
142
+ // (only run if no custom renderer)
143
+ if (!context.slots?.renderer?.()?.length) {
144
+ // TODO: use dispose
145
+ const { dispose } = prepCanvas(
146
+ container,
147
+ normalizedCamera,
148
+ normalizedRenderer,
149
+ normalizedScene,
150
+ props.sizePolicy
151
+ )
152
+
153
+ if (props.r3f) {
154
+ normalizedRenderer.outputEncoding = THREE.sRGBEncoding
155
+ normalizedRenderer.toneMapping = THREE.ACESFilmicToneMapping
156
+ }
157
+
158
+ // update render sugar
159
+ const sugar = {
160
+ shadow: props.shadow,
161
+ }
162
+ if (sugar?.shadow) {
163
+ normalizedRenderer.shadowMap.enabled = true
164
+ if (typeof sugar.shadow === 'object') {
165
+ normalizedRenderer.shadowMap.type = sugar.shadow.type
166
+ }
167
+ }
168
+ }
169
+
170
+ // START
171
+ // ====================
172
+ if (!app) {
173
+ throw new Error('error creating app')
174
+ }
175
+
176
+ // save renderer, scene, camera
177
+ app.config.globalProperties.lunchbox.camera = normalizedCamera
178
+ app.config.globalProperties.lunchbox.renderer = normalizedRenderer
179
+ app.config.globalProperties.lunchbox.scene = normalizedScene
180
+
181
+ for (let startCallback of startCallbacks ?? []) {
182
+ startCallback({
183
+ app,
184
+ camera: normalizedCamera,
185
+ renderer: normalizedRenderer,
186
+ scene: normalizedScene,
187
+ })
188
+ }
189
+
190
+ // KICK UPDATE
191
+ // ====================
192
+ update({
193
+ app,
194
+ camera: normalizedCamera,
195
+ renderer: normalizedRenderer,
196
+ scene: normalizedScene,
197
+ updateSource: props.updateSource,
198
+ })
199
+ })
200
+
201
+ // UNMOUNT
202
+ // ====================
203
+ onBeforeUnmount(() => {
204
+ cancelUpdate()
205
+ cancelUpdateSource()
206
+ })
207
+
208
+ // RENDER FUNCTION
209
+ // ====================
210
+ const containerFillStyle =
211
+ props.sizePolicy === 'container' ? 'static' : 'absolute'
212
+ const canvasFillStyle =
213
+ props.sizePolicy === 'container' ? 'static' : 'fixed'
214
+
215
+ return () => (
216
+ <>
217
+ {/* use renderer slot if provided... */}
218
+ {context.slots?.renderer?.()?.length ? (
219
+ // TODO: remove `as any` cast
220
+ (renderer.value = context.slots?.renderer?.()[0] as any)
221
+ ) : (
222
+ // ...otherwise, add canvas...
223
+ <>
224
+ <div
225
+ class="lunchbox-container"
226
+ style={fillStyle(containerFillStyle) as any}
227
+ ref={container}
228
+ data-lunchbox="true"
229
+ >
230
+ <canvas
231
+ ref={canvas}
232
+ class="lunchbox-canvas"
233
+ style={fillStyle(canvasFillStyle) as any}
234
+ data-lunchbox="true"
235
+ ></canvas>
236
+ </div>
237
+ {/* ...and create default renderer once canvas is present */}
238
+ {canvas.value?.domElement && (
239
+ <webGLRenderer
240
+ {...(props.rendererProperties ?? {})}
241
+ ref={renderer}
242
+ args={[
243
+ {
244
+ alpha: props.transparent,
245
+ antialias: true,
246
+ canvas: canvas.value?.domElement,
247
+ powerPreference: !!props.r3f
248
+ ? 'high-performance'
249
+ : 'default',
250
+ ...(props.rendererArguments ?? {}),
251
+ },
252
+ ]}
253
+ />
254
+ )}
255
+ </>
256
+ )}
257
+
258
+ {/* use scene slot if provided... */}
259
+ {context.slots?.scene?.()?.length ? (
260
+ // TODO: remove `as any` cast
261
+ (scene.value = context.slots?.scene?.()[0] as any)
262
+ ) : (
263
+ // ...otherwise, add default scene
264
+ // TODO: why does this need to be a separate component? <scene> throws an error
265
+ <LunchboxScene ref={scene}>
266
+ {context.slots?.default?.()}
267
+ </LunchboxScene>
268
+ )}
269
+
270
+ {/* use camera slot if provided... */}
271
+ {context.slots?.camera?.()?.length ? (
272
+ // TODO: remove `any` cast
273
+ (camera.value = context.slots?.camera?.()[0] as any)
274
+ ) : props.ortho || props.orthographic ? (
275
+ <orthographicCamera
276
+ ref={camera}
277
+ args={props.cameraArgs ?? []}
278
+ {...consolidatedCameraProperties}
279
+ />
280
+ ) : (
281
+ <perspectiveCamera
282
+ ref={camera}
283
+ args={
284
+ props.cameraArgs ?? [
285
+ props.r3f ? 75 : 45,
286
+ 0.5625,
287
+ 1,
288
+ 1000,
289
+ ]
290
+ }
291
+ {...consolidatedCameraProperties}
292
+ />
293
+ )}
294
+
295
+ {/* Lunchbox interaction handlers */}
296
+ {interactables?.value.length && <LunchboxEventHandlers />}
297
+ </>
298
+ )
299
+ },
300
+ })