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.
- package/dist/lunchboxjs.js +1666 -1659
- package/dist/lunchboxjs.min.js +1 -1
- package/dist/lunchboxjs.module.js +1620 -1659
- package/extras/OrbitControlsWrapper.vue +12 -4
- package/package.json +12 -4
- package/src/components/LunchboxEventHandlers.tsx +239 -0
- package/src/components/LunchboxWrapper/LunchboxScene.tsx +8 -0
- package/src/components/LunchboxWrapper/LunchboxWrapper.tsx +300 -0
- package/src/components/LunchboxWrapper/prepCanvas.ts +27 -21
- package/src/components/LunchboxWrapper/resizeCanvas.ts +13 -12
- package/src/components/autoGeneratedComponents.ts +1 -1
- package/src/components/index.ts +2 -4
- package/src/core/createNode.ts +2 -18
- package/src/core/ensure.ts +9 -196
- package/src/core/extend.ts +1 -1
- package/src/core/index.ts +0 -2
- package/src/core/instantiateThreeObject/index.ts +7 -2
- package/src/core/instantiateThreeObject/processProps.ts +1 -1
- package/src/core/interaction.ts +55 -0
- package/src/core/minidom.ts +5 -9
- package/src/core/update.ts +74 -51
- package/src/core/updateObjectProp.ts +5 -14
- package/src/index.ts +248 -76
- package/src/keys.ts +25 -0
- package/src/nodeOps/createElement.ts +2 -5
- package/src/nodeOps/index.ts +70 -57
- package/src/nodeOps/insert.ts +11 -32
- package/src/nodeOps/remove.ts +1 -17
- package/src/types.ts +34 -10
- package/src/utils/index.ts +1 -4
- package/dist/.DS_Store +0 -0
- package/src/.DS_Store +0 -0
- package/src/components/LunchboxWrapper/LunchboxWrapper.ts +0 -312
- package/src/components/catalogue.ts +0 -3
- package/src/core/.DS_Store +0 -0
- package/src/core/allNodes.ts +0 -4
- 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 -224
- 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 {
|
|
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
|
|
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
|
|
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": "
|
|
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": "
|
|
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,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
|
+
})
|