Explorar o código

Merge pull request #108 from Tresjs/feature/37-better-state-management-usetres

feat(core): better state management useTres
Alvaro Saburido %!s(int64=2) %!d(string=hai) anos
pai
achega
c206b6b3bb

+ 2 - 21
docs/api/composables.md

@@ -170,9 +170,9 @@ Then you can use the new component in your template. Notice that the new compone
 </template>
 ```
 
-# useTres <Badge type="warning" text="experimental" />
+# useTres <Badge type="warning" text="^1.7.0" />
 
-This composable aims to provide access to the state model which contains the default renderer, camera, scene, and other useful properties. It is still experimental and it is not recommended to use it in production because is highly like subject to change.
+This composable aims to provide access to the state model which contains the default renderer, camera, scene, and other useful properties.
 
 ```ts
 const { state } = useTres()
@@ -180,22 +180,3 @@ const { state } = useTres()
 console.log(state.camera) // THREE.PerspectiveCamera
 console.log(state.renderer) // THREE.WebGLRenderer
 ```
-
-Until this composable is stable, it is recommended to use the `provide/inject` API to acces the elements you need. These are the available keys:
-
-- `camera`: it returns the current active camera
-- `renderer`: it returns the current active renderer
-- `local-scene`: it returns the current active scene
-- `catalogue`: it returns the current catalogue of components
-- `extend` : it returns the `extend` function from the `useCatalogue` composable. Specially needed if you are a plugin author.
-- `aspect-ratio`: it returns the current aspect ratio of the canvas
-
-```ts
-import { provide, inject } from 'vue'
-
-const camera = inject<Ref<Camera>>('camera')
-const renderer = inject<Ref<WebGLRenderer>>('renderer')
-
-console.log(camera.value) // THREE.PerspectiveCamera
-console.log(renderer.value) // THREE.WebGLRenderer
-```

+ 5 - 9
packages/cientos/src/core/OrbitControls.vue

@@ -1,8 +1,7 @@
 <script lang="ts" setup>
-import { useTres } from '@tresjs/core'
-import { Camera, Vector3, WebGLRenderer } from 'three'
+import { Camera, Vector3 } from 'three'
 import { OrbitControls } from 'three-stdlib'
-import { inject, ref, watch, type Ref } from 'vue'
+import { ref, watch, watchEffect, unref, type Ref } from 'vue'
 
 import { useCientos } from './useCientos'
 
@@ -19,13 +18,10 @@ const props = withDefaults(
   },
 )
 
-const { setState } = useTres()
+const { state, setState, extend } = useCientos()
 
 const controls = ref(null)
-const camera = inject<Ref<Camera>>('camera')
-const renderer = inject<Ref<WebGLRenderer>>('renderer')
 
-const { extend } = useCientos()
 extend({ OrbitControls })
 
 watch(controls, value => {
@@ -39,9 +35,9 @@ watch(controls, value => {
 
 <template>
   <TresOrbitControls
-    v-if="camera && renderer"
+    v-if="state.camera && state.renderer"
     ref="controls"
-    :args="[camera, renderer?.domElement]"
+    :args="[unref(state.camera), state.renderer?.domElement]"
     :enabling-dampling="enableDamping"
   />
 </template>

+ 5 - 8
packages/cientos/src/core/TransformControls.vue

@@ -4,6 +4,7 @@ import { Camera, Object3D, Scene, WebGLRenderer, type Event } from 'three'
 import { TransformControls as TransformControlsImp } from 'three-stdlib'
 import { inject, computed, type Ref, unref, watch, shallowRef, ShallowRef, onUnmounted } from 'vue'
 import { pick, hasSetter } from '../utils'
+import { useCientos } from './useCientos'
 
 const props = withDefaults(
   defineProps<{
@@ -29,9 +30,7 @@ const emit = defineEmits(['dragging', 'change', 'mouseDown', 'mouseUp', 'objectC
 
 let controls: ShallowRef<TransformControlsImp | undefined> = shallowRef()
 
-const camera = inject<Ref<Camera>>('camera')
-const renderer = inject<Ref<WebGLRenderer>>('renderer')
-const scene = inject<Ref<Scene>>('local-scene')
+const { state } = useCientos()
 
 const transformProps = computed(() =>
   pick(props, [
@@ -48,8 +47,6 @@ const transformProps = computed(() =>
     'showZ',
   ]),
 )
-const { state } = useTres()
-
 const onChange = () => emit('change', controls.value)
 const onMouseDown = () => emit('mouseDown', controls.value)
 const onMouseUp = () => emit('mouseUp', controls.value)
@@ -71,11 +68,11 @@ function addEventListeners(controls: TransformControlsImp) {
 watch(
   () => props.object,
   () => {
-    if (camera?.value && renderer?.value && scene?.value && props.object) {
-      controls.value = new TransformControlsImp(camera.value, unref(renderer).domElement)
+    if (state.camera?.value && state.renderer && state.scene && props.object) {
+      controls.value = new TransformControlsImp(state.camera.value, unref(state.renderer).domElement)
 
       controls.value.attach(unref(props.object))
-      scene.value.add(unref(controls) as TransformControlsImp)
+      state.scene.add(unref(controls) as TransformControlsImp)
 
       addEventListeners(unref(controls) as TransformControlsImp)
     }

+ 5 - 0
packages/cientos/src/core/useCientos.ts

@@ -1,3 +1,4 @@
+import { useTres } from '@tresjs/core'
 import { inject } from 'vue'
 
 export function useCientos() {
@@ -6,7 +7,11 @@ export function useCientos() {
     (() => {
       console.warn('No extend function provided')
     })
+
+  const { state, setState } = inject('useTres', useTres())
   return {
+    state,
+    setState,
     extend,
   }
 }

+ 4 - 3
packages/cientos/src/core/useFBX/component.ts

@@ -1,6 +1,7 @@
 import { Object3D, Scene } from 'three'
 import { defineComponent, inject, Ref } from 'vue'
 import { useFBX } from '.'
+import { useCientos } from '../useCientos'
 
 export const FBXModel = defineComponent({
   name: 'FBXModel',
@@ -11,7 +12,7 @@ export const FBXModel = defineComponent({
     },
   },
   async setup(props, { expose }) {
-    const scene = inject<Ref<Scene>>('local-scene')
+    const { state } = useCientos()
     let model: Object3D | null = null
 
     function getModel() {
@@ -21,8 +22,8 @@ export const FBXModel = defineComponent({
 
     model = await useFBX(props.path as string)
 
-    if (scene?.value && model.isObject3D) {
-      scene.value.add(model)
+    if (state.scene && model.isObject3D) {
+      state.scene.add(model)
     }
     return () => {
       model

+ 5 - 5
packages/cientos/src/core/useGLTF/component.ts

@@ -1,6 +1,6 @@
-import { Scene } from 'three'
-import { defineComponent, inject, Ref } from 'vue'
+import { defineComponent } from 'vue'
 import { useGLTF } from '.'
+import { useCientos } from '../useCientos'
 
 export const GLTFModel = defineComponent({
   name: 'GLTFModel',
@@ -11,15 +11,15 @@ export const GLTFModel = defineComponent({
   },
 
   async setup(props, { expose }) {
-    const scene = inject<Ref<Scene>>('local-scene')
+    const { state } = useCientos()
 
     function getModel() {
       return model
     }
     expose({ getModel })
     const { scene: model } = await useGLTF(props.path as string, { draco: props.draco, decoderPath: props.decoderPath })
-    if (scene?.value) {
-      scene.value.add(model)
+    if (state.scene) {
+      state.scene.add(model)
     }
     return () => {
       model

+ 1 - 0
packages/tres/src/core/index.ts

@@ -7,3 +7,4 @@ export * from './useScene/'
 export * from './useLoader'
 export * from './useTexture'
 export * from './useTres'
+export * from './useRaycaster'

+ 19 - 21
packages/tres/src/core/useCamera/index.ts

@@ -1,7 +1,7 @@
 import { useTres } from '/@/core/'
 import { PerspectiveCamera, OrthographicCamera } from 'three'
 
-import { computed, ComputedRef, watch, inject, Ref } from 'vue'
+import { computed, ComputedRef, watch } from 'vue'
 
 export enum CameraType {
   Perspective = 'Perspective',
@@ -36,7 +36,7 @@ interface UseCameraReturn {
   pushCamera: (camera: Camera) => void
 }
 
-const state: CameraState = {
+const cameraState: CameraState = {
   cameras: [],
 }
 
@@ -44,9 +44,7 @@ const VERTICAL_FIELD_OF_VIEW = 45
 let camera: Camera
 
 export function useCamera(): UseCameraReturn {
-  const aspectRatio = inject<ComputedRef<number>>('aspect-ratio')
-
-  const { setState } = useTres()
+  const { state, setState } = useTres()
 
   function createCamera(
     cameraType = CameraType.Perspective,
@@ -58,8 +56,8 @@ export function useCamera(): UseCameraReturn {
         far: 1000,
         fov: VERTICAL_FIELD_OF_VIEW,
       }
-      camera = new PerspectiveCamera(fov, aspectRatio?.value || 1, near, far)
-      state.cameras.push(camera as PerspectiveCamera)
+      camera = new PerspectiveCamera(fov, state.aspectRatio?.value || 1, near, far)
+      cameraState.cameras.push(camera as PerspectiveCamera)
     } else {
       const { left, right, top, bottom, near, far } = (options as OrthographicCameraOptions) || {
         left: -100,
@@ -70,37 +68,37 @@ export function useCamera(): UseCameraReturn {
         far: 1000,
       }
       camera = new OrthographicCamera(left, right, top, bottom, near, far)
-      state.cameras.push(camera as OrthographicCamera)
+      cameraState.cameras.push(camera as OrthographicCamera)
     }
 
-    state.cameras.push(camera)
+    cameraState.cameras.push(camera)
     return camera
   }
 
-  const activeCamera = computed(() => state.cameras[0])
+  const activeCamera = computed(() => cameraState.cameras[0])
+  setState('camera', activeCamera)
 
   function updateCamera() {
-    if (activeCamera.value instanceof PerspectiveCamera && aspectRatio) {
-      activeCamera.value.aspect = aspectRatio.value
+    if (activeCamera.value instanceof PerspectiveCamera && state.aspectRatio) {
+      activeCamera.value.aspect = state.aspectRatio.value
     }
     activeCamera.value.updateProjectionMatrix()
   }
 
   function pushCamera(camera: Camera): void {
-    const currentCamera = inject<Ref<Camera>>('camera')
-    if (camera && currentCamera) {
+    /*     if (camera && currentCamera) {
       currentCamera.value = camera
-      setState('camera', currentCamera.value)
-    }
-    state.cameras.push(camera)
-    if (camera instanceof PerspectiveCamera && aspectRatio) {
-      camera.aspect = aspectRatio.value
+      setState('camera', currentCamera)
+    } */
+    cameraState.cameras.push(camera)
+    if (camera instanceof PerspectiveCamera && state.aspectRatio) {
+      camera.aspect = state.aspectRatio.value
     }
     camera.updateProjectionMatrix()
   }
 
-  if (aspectRatio) {
-    watch(aspectRatio, updateCamera)
+  if (state.aspectRatio) {
+    watch(state.aspectRatio, updateCamera)
   }
   return {
     activeCamera,

+ 12 - 13
packages/tres/src/core/useInstanceCreator/index.ts

@@ -6,9 +6,10 @@ import { useEventListener } from '@vueuse/core'
 
 import { isArray, isDefined, isFunction } from '@alvarosabu/utils'
 import { normalizeVectorFlexibleParam } from '/@/utils/normalize'
-import { useCamera, useCatalogue, useRenderLoop, useScene } from '/@/core/'
+import { useCamera, useCatalogue, useRenderLoop, useScene, useTres } from '/@/core/'
 import { useLogger } from '/@/composables'
 import { TresAttributes, TresCatalogue, TresInstance, TresVNode, TresVNodeType, TresEvent } from '/@/types'
+import { useRaycaster } from '../useRaycaster'
 
 const VECTOR3_PROPS = ['rotation', 'scale', 'position']
 
@@ -159,12 +160,10 @@ export function useInstanceCreator(prefix: string) {
           const cmp = defineComponent({
             name,
             setup(_props, { slots, attrs, ...ctx }) {
-              const { scene: fallback } = useScene()
+              const { state } = useTres()
               const { onLoop } = useRenderLoop()
-              const scene = inject<Ref<Scene>>('local-scene') || fallback
-              /* const { raycaster } = useRaycaster() */
-              const raycaster = inject<Ref<Raycaster>>('raycaster') /* 
-              const currentInstance = inject<Ref>('currentInstance') */
+              const scene = state.scene
+              const raycaster = state.raycaster
               const { pushCamera } = useCamera()
 
               let instance = createInstance(threeObj, attrs, slots)
@@ -176,15 +175,15 @@ export function useInstanceCreator(prefix: string) {
 
               // If the instance is a valid Object3D, add it to the scene
               if (instance.isObject3D) {
-                scene?.value.add(instance)
+                scene?.add(instance)
               }
 
               let prevInstance: TresEvent | null = null
               let currentInstance: TresEvent | null = null
               if (instance.isMesh) {
                 onLoop(() => {
-                  if (instance && raycaster?.value) {
-                    const intersects = raycaster?.value.intersectObjects(scene.value.children)
+                  if (instance && raycaster && scene?.children) {
+                    const intersects = raycaster.intersectObjects(scene?.children)
 
                     if (intersects.length > 0) {
                       currentInstance = intersects[0]
@@ -214,13 +213,13 @@ export function useInstanceCreator(prefix: string) {
                 })
               }
 
-              if (scene?.value && instance.isFog) {
-                scene.value.fog = instance as unknown as FogBase
+              if (scene && instance.isFog) {
+                scene.fog = instance as unknown as FogBase
               }
 
               if (import.meta.hot) {
                 import.meta.hot.on('vite:beforeUpdate', () => {
-                  scene.value.remove(instance)
+                  scene?.remove(instance)
                 })
 
                 import.meta.hot.on('vite:afterUpdate', () => {
@@ -228,7 +227,7 @@ export function useInstanceCreator(prefix: string) {
                   processProps(attrs, instance)
 
                   if (instance.isObject3D) {
-                    scene?.value.add(instance)
+                    scene?.add(instance)
                   }
                 })
               }

+ 6 - 0
packages/tres/src/core/useRaycaster/index.ts

@@ -1,11 +1,17 @@
 import { Raycaster, Vector2 } from 'three'
 import { onUnmounted, provide, ref, shallowRef } from 'vue'
+import { useTres } from '/@/core'
 
 const raycaster = shallowRef(new Raycaster())
 const pointer = ref(new Vector2())
 const currentInstance = ref(null)
 
 export function useRaycaster() {
+  const { setState } = useTres()
+  setState('raycaster', raycaster.value)
+  setState('pointer', pointer)
+  setState('currentInstance', currentInstance)
+
   provide('raycaster', raycaster)
   provide('pointer', pointer)
   provide('currentInstance', currentInstance)

+ 0 - 3
packages/tres/src/core/useRenderer/component.ts

@@ -31,11 +31,8 @@ export const TresCanvas = defineComponent({
 
     const { renderer, dispose, aspectRatio } = useRenderer(canvas, container, props)
 
-    const activeCamera = shallowRef()
-
     provide('aspect-ratio', aspectRatio)
     provide('renderer', renderer)
-    provide('camera', activeCamera)
 
     if (slots.default && !slots.default().some(node => (node.type as TresVNodeType).name === 'Scene')) {
       logError('TresCanvas must contain a Scene component.')

+ 1 - 0
packages/tres/src/core/useRenderer/index.ts

@@ -190,6 +190,7 @@ export function useRenderer(canvas: MaybeElementRef, container: MaybeElementRef,
     const { setState } = useTres()
     setState('renderer', renderer.value)
     setState('clock', new Clock())
+    setState('aspectRatio', aspectRatio)
     updateRendererOptions()
     updateRendererSize()
     resume()

+ 4 - 5
packages/tres/src/core/useScene/component.ts

@@ -1,9 +1,6 @@
-import { useCamera } from '/@/core/'
-import type { Renderer } from 'three'
 import { defineComponent, inject, provide, Ref } from 'vue'
-import { useRenderLoop } from '../useRenderLoop'
-import { useScene } from './'
-import { useRaycaster } from '../useRaycaster'
+import type { Renderer } from 'three'
+import { useCamera, useTres, useRenderLoop, useScene, useRaycaster } from '/@/core/'
 
 /**
  * Vue component for rendering a Tres component.
@@ -11,6 +8,7 @@ import { useRaycaster } from '../useRaycaster'
 export const Scene = defineComponent({
   name: 'Scene',
   setup(_props, { slots }) {
+    const { setState } = useTres()
     const { scene } = useScene()
     const renderer = inject<Ref<Renderer>>('renderer')
     const { activeCamera } = useCamera()
@@ -18,6 +16,7 @@ export const Scene = defineComponent({
     const { onLoop } = useRenderLoop()
 
     provide('local-scene', scene)
+    setState('scene', scene.value)
 
     onLoop(() => {
       raycaster.value.setFromCamera(pointer.value, activeCamera.value)

+ 10 - 3
packages/tres/src/core/useTres/index.ts

@@ -1,10 +1,17 @@
-import { WebGLRenderer } from 'three'
-import { shallowReactive, toRefs } from 'vue'
+import { Clock, EventDispatcher, Raycaster, Scene, Vector2, WebGLRenderer } from 'three'
+import { ComputedRef, shallowReactive, ShallowRef, toRefs } from 'vue'
 import { Camera } from '/@/core'
 
 export interface TresState {
-  camera?: Camera
+  camera?: ComputedRef<Camera>
+  aspectRatio?: ComputedRef<number>
   renderer?: WebGLRenderer
+  scene?: Scene
+  raycaster?: Raycaster
+  clock?: Clock
+  pointer?: Vector2
+  currentInstance?: any
+  controls?: EventDispatcher | null
   [key: string]: any
 }
 

+ 0 - 2
packages/tres/vite.config.ts

@@ -7,7 +7,6 @@ import banner from 'vite-plugin-banner'
 import dts from 'vite-plugin-dts'
 import analyze from 'rollup-plugin-analyzer'
 /* import { visualizer } from 'rollup-plugin-visualizer' */
-import glsl from 'vite-plugin-glsl'
 import { resolve } from 'pathe'
 
 import { lightGreen, yellow, gray, bold } from 'kolorist'
@@ -32,7 +31,6 @@ export default defineConfig({
     dts({
       insertTypesEntry: true,
     }),
-    glsl(),
     banner({
       content: `/**\n * name: ${pkg.name}\n * version: v${
         pkg.version