From 46cdd001f6c9b84568781f135d417640041e269a Mon Sep 17 00:00:00 2001 From: alvarosabu Date: Mon, 3 Apr 2023 21:53:48 +0200 Subject: [PATCH] feat: use tres provider and context --- playground/src/components/MultipleCanvas.vue | 63 ++++++- playground/src/pages/index.vue | 2 +- src/components/TresCanvas.ts | 116 +------------ src/components/TresScene.ts | 164 +++++++++++++++++++ src/composables/useRenderer/index.ts | 21 ++- src/composables/useTres/index.ts | 42 +++-- src/core/nodeOps.ts | 2 +- 7 files changed, 269 insertions(+), 141 deletions(-) create mode 100644 src/components/TresScene.ts diff --git a/playground/src/components/MultipleCanvas.vue b/playground/src/components/MultipleCanvas.vue index dd504773a..039d0578f 100644 --- a/playground/src/components/MultipleCanvas.vue +++ b/playground/src/components/MultipleCanvas.vue @@ -1,13 +1,66 @@ diff --git a/playground/src/pages/index.vue b/playground/src/pages/index.vue index cc86edd27..3f7daad70 100644 --- a/playground/src/pages/index.vue +++ b/playground/src/pages/index.vue @@ -1,4 +1,4 @@ diff --git a/src/components/TresCanvas.ts b/src/components/TresCanvas.ts index e1f41989f..b4b412ded 100644 --- a/src/components/TresCanvas.ts +++ b/src/components/TresCanvas.ts @@ -1,12 +1,8 @@ -import { App, defineComponent, h, onUnmounted, ref, watch, watchEffect } from 'vue' -import * as THREE from 'three' +import { TresScene } from './TresScene' +import { defineComponent, h } from 'vue' import { ShadowMapType, TextureEncoding, ToneMapping } from 'three' -import { createTres } from '/@/core/renderer' -import { useLogger } from '/@/composables' -import { useCamera, useRenderer, useRenderLoop, useRaycaster, useTres } from '/@/composables' -import { extend } from '/@/core/catalogue' +import { useTresProvider } from '/@/composables' import { RendererPresetsType } from '/@/composables/useRenderer/const' -import { TresObject } from '../types' export interface TresCanvasProps { shadows?: boolean @@ -27,8 +23,6 @@ export interface TresCanvasProps { * Vue component for rendering a Tres component. */ -const { logWarning } = useLogger() - export const TresCanvas = defineComponent({ name: 'TresCanvas', props: [ @@ -46,109 +40,11 @@ export const TresCanvas = defineComponent({ 'windowSize', 'preset', ] as unknown as undefined, - setup(props, { slots, expose }) { - if (props.physicallyCorrectLights === true) { - logWarning('physicallyCorrectLights is deprecated, useLegacyLights is now false by default') - } - - const container = ref() - const canvas = ref() - const scene = new THREE.Scene() - const { setState } = useTres() - - setState('scene', scene) - - onUnmounted(() => { - setState('renderer', null) - }) - - function initRenderer() { - const { renderer } = useRenderer(canvas, container, props) - - const { activeCamera } = useCamera() - - const { onLoop } = useRenderLoop() - - const { raycaster, pointer } = useRaycaster() - - watchEffect(() => { - if (activeCamera.value) raycaster.value.setFromCamera(pointer.value, activeCamera.value) - }) - - onLoop(() => { - if (activeCamera.value) renderer.value?.render(scene, activeCamera.value) - }) - } - - watch(canvas, initRenderer) - - let app: App - - function mountApp() { - app = createTres(slots) - app.provide('useTres', useTres()) - app.provide('extend', extend) - app.mount(scene as unknown as TresObject) - } - - mountApp() - expose({ - scene, - }) - - function dispose() { - scene.children = [] - app.unmount() - mountApp() - } - - if (import.meta.hot) { - import.meta.hot.on('vite:afterUpdate', dispose) - } + setup(props, { slots }) { + useTresProvider() return () => { - return h( - h( - 'div', - { - ref: container, - 'data-scene': scene.uuid, - key: scene.uuid, - style: { - position: 'relative', - width: '100%', - height: '100%', - pointerEvents: 'auto', - touchAction: 'none', - }, - }, - [ - h( - 'div', - { - style: { - width: '100%', - height: '100%', - }, - }, - [ - h('canvas', { - ref: canvas, - 'data-scene': scene.uuid, - style: { - display: 'block', - width: '100%', - height: '100%', - position: props.windowSize ? 'fixed' : 'absolute', - top: 0, - left: 0, - }, - }), - ], - ), - ], - ), - ) + return h(TresScene, props, slots) } }, }) diff --git a/src/components/TresScene.ts b/src/components/TresScene.ts new file mode 100644 index 000000000..9c57c3973 --- /dev/null +++ b/src/components/TresScene.ts @@ -0,0 +1,164 @@ +import { App, defineComponent, h, onMounted, onUnmounted, onUpdated, provide, ref, watch, watchEffect } from 'vue' +import * as THREE from 'three' +import { PerspectiveCamera, ShadowMapType, TextureEncoding, ToneMapping } from 'three' +import { createTres } from '/@/core/renderer' +import { useLogger } from '/@/composables' +import { useCamera, useRenderer, useRenderLoop, useRaycaster, useTres } from '/@/composables' +import { extend } from '/@/core/catalogue' +import { RendererPresetsType } from '/@/composables/useRenderer/const' +import { TresObject } from '../types' + +export interface TresSceneProps { + shadows?: boolean + shadowMapType?: ShadowMapType + physicallyCorrectLights?: boolean + useLegacyLights?: boolean + outputEncoding?: TextureEncoding + toneMapping?: ToneMapping + toneMappingExposure?: number + context?: WebGLRenderingContext + powerPreference?: 'high-performance' | 'low-power' | 'default' + preserveDrawingBuffer?: boolean + clearColor?: string + windowSize?: boolean + preset?: RendererPresetsType +} +/** + * Vue component for rendering a Tres component. + */ + +const { logWarning } = useLogger() + +export const TresScene = defineComponent({ + name: 'TresScene', + props: [ + 'shadows', + 'shadowMapType', + 'physicallyCorrectLights', + 'useLegacyLights', + 'outputEncoding', + 'toneMapping', + 'toneMappingExposure', + 'context', + 'powerPreference', + 'preserveDrawingBuffer', + 'clearColor', + 'windowSize', + 'preset', + ] as unknown as undefined, + setup(props, { slots, expose }) { + if (props.physicallyCorrectLights === true) { + logWarning('physicallyCorrectLights is deprecated, useLegacyLights is now false by default') + } + + const container = ref() + const canvas = ref() + const scene = new THREE.Scene() + const { state, setState } = useTres() + + setState('scene', scene) + setState('canvas', canvas) + setState('container', container) + + const { pushCamera } = useCamera() + pushCamera(new PerspectiveCamera()) + + onMounted(() => { + initRenderer() + }) + + onUnmounted(() => { + setState('renderer', null) + }) + + function initRenderer() { + const { renderer } = useRenderer(props) + + const { activeCamera } = useCamera() + + const { onLoop } = useRenderLoop() + + const { raycaster, pointer } = useRaycaster() + + watchEffect(() => { + if (activeCamera.value) raycaster.value.setFromCamera(pointer.value, activeCamera.value) + }) + + onLoop(() => { + if (activeCamera.value) renderer.value?.render(scene, activeCamera.value) + }) + } + + let app: App + + function mountApp() { + app = createTres(slots) + app.provide('useTres', useTres()) + app.provide('extend', extend) + app.mount(scene as unknown as TresObject) + } + + mountApp() + + expose({ + scene, + }) + + function dispose() { + scene.children = [] + app.unmount() + mountApp() + } + + if (import.meta.hot) { + import.meta.hot.on('vite:afterUpdate', dispose) + } + + return () => { + return h( + h( + 'div', + { + ref: container, + 'data-scene': scene.uuid, + key: scene.uuid, + style: { + position: 'relative', + width: '100%', + height: '100%', + pointerEvents: 'auto', + touchAction: 'none', + }, + }, + [ + h( + 'div', + { + style: { + width: '100%', + height: '100%', + }, + }, + [ + h('canvas', { + ref: canvas, + 'data-scene': scene.uuid, + style: { + display: 'block', + width: '100%', + height: '100%', + position: props.windowSize ? 'fixed' : 'absolute', + top: 0, + left: 0, + }, + }), + ], + ), + ], + ), + ) + } + }, +}) + +export default TresScene diff --git a/src/composables/useRenderer/index.ts b/src/composables/useRenderer/index.ts index b8bbee4a2..dad181d63 100644 --- a/src/composables/useRenderer/index.ts +++ b/src/composables/useRenderer/index.ts @@ -113,17 +113,16 @@ export interface UseRendererOptions extends WebGLRendererParameters { preset?: RendererPresetsType } -const renderer = shallowRef() -const isReady = ref(false) - /** * Reactive Three.js WebGLRenderer instance * * @param canvas - * @param container + * @param state.container * @param {UseRendererOptions} [options] */ -export function useRenderer(canvas: MaybeElementRef, container: MaybeElementRef, options: UseRendererOptions) { +export function useRenderer(options: UseRendererOptions) { + const renderer = shallowRef() + const isReady = ref(false) // Defaults const { alpha = true, @@ -149,15 +148,15 @@ export function useRenderer(canvas: MaybeElementRef, container: MaybeElementRef, preset = undefined, } = toRefs(options) - const { setState } = useTres() + const { state, setState } = useTres() - const { width, height } = resolveUnref(windowSize) ? useWindowSize() : useElementSize(container) + const { width, height } = resolveUnref(windowSize) ? useWindowSize() : useElementSize(state.container) const { logError, logWarning } = useLogger() const { pixelRatio } = useDevicePixelRatio() const { pause, resume } = useRenderLoop() const aspectRatio = computed(() => width.value / height.value) - if (!resolveUnref(windowSize) && container?.value?.offsetHeight === 0) { + if (!resolveUnref(windowSize) && state.container?.value?.offsetHeight === 0) { logWarning(`Oops... Seems like your canvas height is currently 0px, by default it takes the height of it's parent, so make sure it has some height with CSS. You could set windowSize=true to force the canvas to be the size of the window.`) } @@ -198,7 +197,7 @@ You could set windowSize=true to force the canvas to be the size of the window.` } const init = () => { - const _canvas = unrefElement(canvas) + const _canvas = unrefElement(state.canvas) if (!_canvas) { return @@ -249,9 +248,9 @@ You could set windowSize=true to force the canvas to be the size of the window.` ) watch( - () => [canvas, container], + () => [state.canvas, state.container], () => { - if (unrefElement(canvas) && unrefElement(container)) { + if (unrefElement(state.canvas) && unrefElement(state.container)) { init() } }, diff --git a/src/composables/useTres/index.ts b/src/composables/useTres/index.ts index 6aa856c44..db04fd2a5 100644 --- a/src/composables/useTres/index.ts +++ b/src/composables/useTres/index.ts @@ -1,6 +1,7 @@ import { Clock, EventDispatcher, Raycaster, Scene, Vector2, WebGLRenderer } from 'three' -import { computed, ComputedRef, shallowReactive, toRefs } from 'vue' -import { Camera } from '/@/composables' +import { generateUUID } from 'three/src/math/MathUtils' +import { computed, ComputedRef, inject, provide, shallowReactive, toRefs } from 'vue' +import { Camera, useLogger } from '/@/composables' export interface TresState { /** @@ -90,15 +91,8 @@ export interface TresState { [key: string]: any } -const INIT_STATE = { - camera: undefined, - cameras: [], - scene: undefined, - renderer: undefined, - aspectRatio: computed(() => window.innerWidth / window.innerHeight), -} -const state: TresState = shallowReactive(INIT_STATE) - +const TRES_CONTEXT_KEY = Symbol() +const { logError } = useLogger() /** * The Tres state. * @@ -107,7 +101,15 @@ const state: TresState = shallowReactive(INIT_STATE) * @export * @return {*} {TresState, getState, setState} */ -export function useTres() { +export function useTresProvider() { + const state: TresState = shallowReactive({ + uuid: generateUUID(), + camera: undefined, + cameras: [], + scene: undefined, + renderer: undefined, + aspectRatio: computed(() => window.innerWidth / window.innerHeight), + }) /** * Get a state value. * @@ -129,10 +131,24 @@ export function useTres() { state[key] = value } - return { + const toProvide = { state, ...toRefs(state), getState, setState, } + + provide(TRES_CONTEXT_KEY, toProvide) + + return toProvide +} + +export const useTres = () => { + const context = inject(TRES_CONTEXT_KEY) + + if (!context) return + + if (!context) logError('UseTres together with useTresProvider') + + return context } diff --git a/src/core/nodeOps.ts b/src/core/nodeOps.ts index cf5454b1e..94a394061 100644 --- a/src/core/nodeOps.ts +++ b/src/core/nodeOps.ts @@ -79,8 +79,8 @@ export const nodeOps: RendererOptions = { let prevInstance: TresEvent | null = null let currentInstance: TresEvent | null = null - const { raycaster } = useRaycaster() if (child && child instanceof Mesh && hasEvents(child)) { + const { raycaster } = useRaycaster() onLoop(() => { if (parent?.children && child && raycaster) { const intersects = raycaster.value.intersectObjects(parent.children)