Browse Source

feat: use tres provider and context

alvarosabu 2 years ago
parent
commit
46cdd001f6

+ 58 - 5
playground/src/components/MultipleCanvas.vue

@@ -1,13 +1,66 @@
 <script setup lang="ts">
-import { TresCanvas } from '/@/'
+import { BasicShadowMap, NoToneMapping, sRGBEncoding } from 'three'
+import { TresCanvas } from '/@/components/TresCanvas'
+import { GLTFModel, OrbitControls } from '@tresjs/cientos'
+
+const state = reactive({
+  clearColor: '#201919',
+  shadows: true,
+  alpha: false,
+  shadowMapType: BasicShadowMap,
+  outputEncoding: sRGBEncoding,
+  toneMapping: NoToneMapping,
+})
+
+const state2 = reactive({
+  clearColor: '#4f4f4f',
+  shadows: true,
+  alpha: false,
+
+  /*  shadowMapType: BasicShadowMap,
+  outputEncoding: sRGBEncoding,
+  toneMapping: NoToneMapping, */
+})
 </script>
 <template>
   <div class="flex">
     <div class="w-1/2 aspect-video">
-      <TresCanvas />
+      <TresCanvas v-bind="state">
+        <TresPerspectiveCamera :position="[5, 5, 5]" :fov="45" :near="0.1" :far="1000" :look-at="[0, 4, 0]" />
+        <OrbitControls />
+
+        <TresAmbientLight :intensity="0.5" />
+        <TresMesh :position="[0, 4, 0]">
+          <TresBoxGeometry :args="[1, 1, 1]" />
+          <TresMeshToonMaterial color="cyan" />
+        </TresMesh>
+
+        <Suspense>
+          <TestSphere />
+        </Suspense>
+        <TresDirectionalLight :position="[0, 2, 4]" :intensity="1" />
+      </TresCanvas>
+    </div>
+    <div class="w-1/2 aspect-video">
+      <TresCanvas v-bind="state2">
+        <TresPerspectiveCamera :position="[5, 5, 5]" :fov="45" :near="0.1" :far="1000" :look-at="[0, 4, 0]" />
+        <TresAmbientLight :intensity="0.5" />
+
+        <TresMesh :position="[0, 4, 0]" cast-shadow>
+          <TresSphereGeometry :args="[2, 32, 32]" />
+          <TresMeshToonMaterial color="yellow" />
+        </TresMesh>
+        <OrbitControls />
+
+        <Suspense>
+          <GLTFModel
+            path="https://raw.githubusercontent.com/Tresjs/assets/main/models/gltf/aku-aku/AkuAku.gltf"
+            draco
+          />
+        </Suspense>
+
+        <TresDirectionalLight :position="[0, 2, 4]" :intensity="1" cast-shadow />
+      </TresCanvas>
     </div>
-    <!-- <div class="w-1/2 aspect-video">
-      <TresCanvas />
-    </div> -->
   </div>
 </template>

+ 1 - 1
playground/src/pages/index.vue

@@ -1,4 +1,4 @@
 <script setup lang="ts"></script>
 <template>
-  <MultipleCanvas></MultipleCanvas>
+  <MultipleCanvas />
 </template>

+ 6 - 110
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<TresCanvasProps>({
   name: 'TresCanvas',
   props: [
@@ -46,109 +40,11 @@ export const TresCanvas = defineComponent<TresCanvasProps>({
     '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<HTMLElement>()
-    const canvas = ref<HTMLCanvasElement>()
-    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)
     }
   },
 })

+ 164 - 0
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<TresSceneProps>({
+  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<HTMLElement>()
+    const canvas = ref<HTMLElement>()
+    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

+ 10 - 11
src/composables/useRenderer/index.ts

@@ -113,17 +113,16 @@ export interface UseRendererOptions extends WebGLRendererParameters {
   preset?: RendererPresetsType
 }
 
-const renderer = shallowRef<WebGLRenderer>()
-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<WebGLRenderer>()
+  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()
       }
     },

+ 29 - 13
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
 }

+ 1 - 1
src/core/nodeOps.ts

@@ -79,8 +79,8 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
     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)