Forráskód Böngészése

feat: `useFrame` and `useRender` instead of `onLoop`

alvarosabu 1 éve
szülő
commit
3b6cb20848

+ 19 - 21
docs/api/composables.md

@@ -62,12 +62,12 @@ const context = useTresContext()
 | **advance** | a method to advance the render loop. This is only required if you set the `render-mode` prop to `manual`. |
 | **loop** | the renderer loop |
 
-### useLoop <Badge text="v4.0.0" />
+### useFrame <Badge text="v4.1.0" />
 
-This composable allows you to execute a callback on every rendered frame, similar to `useRenderLoop` but unique to each `TresCanvas` instance. However, this composable can only be used inside a `TresCanvas` component since it relies on the context provided by `TresCanvas`.
+This composable allows you to execute a callback on every rendered frame, similar to `useRenderLoop` but unique to each `TresCanvas` instance and with access to the [context](#usetrescontext).
 
 ::: warning
-`useLoop` can be only be used inside of a `TresCanvas` since this component acts as the provider for the context data.
+`useFrame` can be only be used inside of a `TresCanvas` since this component acts as the provider for the context data.
 :::
 
 ::: code-group
@@ -79,9 +79,7 @@ import AnimatedBox from './AnimatedBox.vue'
 </script>
 
 <template>
-  <TresCanvas
-    render-mode="manual"
-  >
+  <TresCanvas>
     <AnimatedBox />
   </TresCanvas>
 </template>
@@ -89,11 +87,11 @@ import AnimatedBox from './AnimatedBox.vue'
 
 ```vue [AnimatedBox.vue]
 <script setup>
-import { useLoop } from '@tresjs/core'
+import { useFrame } from '@tresjs/core'
 
 const boxRef = ref()
 
-useLoop(({ delta }) => {
+useFrame(({ delta }) => {
   boxRef.value.rotation.y += 0.01
 })
 </script>
@@ -112,27 +110,27 @@ Your callback function will be triggered just before a frame is rendered and it
 
 #### Render priority
 
-The `useLoop` composable accepts a second argument `index` which is used to determine the order in which the loop functions are executed. The default value is `1` (see [below](#take-control-of-the-render-loop)) and the lower the value, the earlier the function will be executed.
+The `useFrame` composable accepts a second argument `index` which is used to determine the order in which the loop functions are executed. The default value is `0` which means the function will be executed after the renderer updates the scene. If you set the value to `-1`, the function will be executed before the renderer updates the scene.
 
 ```ts
-useLoop(() => {
+useFrame(() => {
   console.count('before renderer')
 }, -1)
 
-useLoop(() => {
+useFrame(() => {
   console.count('after renderer')
-}, 2)
+}, 1)
 ```
 
-#### Take control of the render-loop
+### `useRender` (Take control of the render loop)
 
-By default, the render-loop is automatically started when the component is mounted. However, you can take control of the render-loop by overwriting the main loop function setting the `index` value to `1` .
+By default, the render-loop is automatically started when the component is mounted. However, you can take control of the render-loop by using this composable.
 
 ```ts
-useLoop(({ ctx }) => {
+useRender(({ renderer, scene, camera }) => {
   // Takes over the render-loop, the user has the responsibility to render
-  ctx.renderer.value.render(ctx.scene.value, ctx.camera.value)
-}, 1)
+  renderer.value.render(scene.value, camera.value)
+})
 ```
 
 ## useLoader
@@ -259,14 +257,14 @@ watch(character, ({ model }) => {
 })
 ```
 
-## useRenderLoop <Badge type="warning" text="to be deprecated on V5" />
+## useRenderLoop
+
+The `useRenderLoop` composable can be use for animations that doesn't require access to the [context](#usetrescontext). It allows you to register a callback that will be called on native refresh rate.
 
 ::: warning
-This composable will be deprecated on V5. Use `useLoop` instead. [Read why](#useloop)
+ Since v4.1.0, `useRenderLoop` is no longer used internally to control the rendering, if you want to use conditional rendering, multiple canvases or need access to state please `useLoop` instead. [Read why](#useloop)
 :::
 
-The `useRenderLoop` composable is the core of **TresJS** animations. It allows you to register a callback that will be called on native refresh rate.
-
 ```ts
 const { onLoop, resume } = useRenderLoop()
 

+ 3 - 1
playground/components.d.ts

@@ -9,10 +9,11 @@ declare module 'vue' {
   export interface GlobalComponents {
     AkuAku: typeof import('./src/components/AkuAku.vue')['default']
     AnimatedModel: typeof import('./src/components/AnimatedModel.vue')['default']
+    AnimatedObjectUseFrame: typeof import('./src/components/AnimatedObjectUseFrame.vue')['default']
     BlenderCube: typeof import('./src/components/BlenderCube.vue')['default']
     CameraOperator: typeof import('./src/components/CameraOperator.vue')['default']
     Cameras: typeof import('./src/components/Cameras.vue')['default']
-    copy: typeof import('./src/components/TheBasic copy.vue')['default']
+    copy: typeof import('./src/components/TheSphere copy.vue')['default']
     DanielTest: typeof import('./src/components/DanielTest.vue')['default']
     DebugUI: typeof import('./src/components/DebugUI.vue')['default']
     DeleteMe: typeof import('./src/components/DeleteMe.vue')['default']
@@ -29,6 +30,7 @@ declare module 'vue' {
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
     ShadersExperiment: typeof import('./src/components/shaders-experiment/index.vue')['default']
+    TakeOverLoopExperience: typeof import('./src/components/TakeOverLoopExperience.vue')['default']
     TestSphere: typeof import('./src/components/TestSphere.vue')['default']
     Text3D: typeof import('./src/components/Text3D.vue')['default']
     TheBasic: typeof import('./src/components/TheBasic.vue')['default']

+ 47 - 0
playground/src/components/AnimatedObjectUseFrame.vue

@@ -0,0 +1,47 @@
+<!-- eslint-disable no-console -->
+<script setup lang="ts">
+import { useFrame, useRender } from '@tresjs/core'
+
+const sphereRef = ref()
+const { pause, isActive, resume } = useFrame((state) => {
+  if (!sphereRef.value) { return }
+  sphereRef.value.position.y += Math.sin(state.elapsed) * 0.01
+})
+
+function onSphereClick() {
+  console.log('sphere clicked', { isActive })
+
+  if (isActive.value) {
+    pause()
+  }
+  else {
+    resume()
+  }
+}
+/* useFrame(() => {
+  console.count('before renderer')
+}, -1)
+
+useFrame(() => {
+  console.log('this should be just before render')
+}, 1)
+
+useFrame((state) => {
+  if (!sphereRef.value) { return }
+  console.count('after renderer')
+  sphereRef.value.position.y += Math.sin(state.elapsed) * 0.01
+}, 2) */
+</script>
+
+<template>
+  <TresMesh
+    ref="sphereRef"
+    :position="[2, 2, 0]"
+    name="sphere"
+    cast-shadow
+    @click="onSphereClick"
+  >
+    <TresSphereGeometry />
+    <TresMeshToonMaterial color="#FBB03B" />
+  </TresMesh>
+</template>

+ 17 - 0
playground/src/components/TakeOverLoopExperience.vue

@@ -0,0 +1,17 @@
+<script setup lang="ts">
+import { useRender } from '@tresjs/core'
+import { OrbitControls } from '@tresjs/cientos'
+
+useRender(({ renderer, scene, camera }) => {
+  /* console.log('this should replace the renderer', renderer.value) */
+  renderer.value.render(scene.value, camera.value)
+})
+</script>
+
+<template>
+  <TresPerspectiveCamera :position="[3, 3, 3]" />
+  <OrbitControls />
+  <AnimatedObjectUseFrame />
+  <TresAmbientLight :intensity="1" />
+  <TresGridHelper />
+</template>

+ 2 - 22
playground/src/components/TheSphere.vue

@@ -1,32 +1,12 @@
 <!-- eslint-disable no-console -->
 <script setup lang="ts">
-import { useLoop } from '@tresjs/core'
-
-/* const { invalidate } = useTres() */
+import { useFrame } from '@tresjs/core'
 
 const sphereRef = ref()
-useLoop((state) => {
+useFrame((state) => {
   if (!sphereRef.value) { return }
   sphereRef.value.position.y += Math.sin(state.elapsed) * 0.01
 })
-/* useLoop(() => {
-  console.count('before renderer')
-}, -1)
-
-useLoop(() => {
-  console.count('after renderer')
-}, 2)
-
-useLoop((state) => {
-  if (!sphereRef.value) { return }
-  console.log('this should be just before render')
-  sphereRef.value.position.y += Math.sin(state.elapsed) * 0.01
-})
-
-useLoop(({ ctx }) => {
-  console.log('this should replace the renderer', ctx)
-  ctx.renderer.value.render(ctx.scene.value, ctx.camera.value)
-}, 1) */
 </script>
 
 <template>

+ 0 - 0
playground/src/pages/perf/OnDemand.vue → playground/src/pages/advanced/OnDemand.vue


+ 19 - 0
playground/src/pages/advanced/TakeOverLoop.vue

@@ -0,0 +1,19 @@
+<script setup lang="ts">
+import { TresCanvas } from '@tresjs/core'
+import { BasicShadowMap, NoToneMapping, SRGBColorSpace } from 'three'
+
+const gl = {
+  clearColor: '#82DBC5',
+  shadows: true,
+  alpha: false,
+  shadowMapType: BasicShadowMap,
+  outputColorSpace: SRGBColorSpace,
+  toneMapping: NoToneMapping,
+}
+</script>
+
+<template>
+  <TresCanvas v-bind="gl">
+    <TakeOverLoopExperience />
+  </TresCanvas>
+</template>

+ 0 - 0
playground/src/pages/perf/index.vue → playground/src/pages/advanced/index.vue


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

@@ -67,7 +67,6 @@ const sphereExists = ref(true)
       <TresPlaneGeometry :args="[10, 10, 10, 10]" />
       <TresMeshToonMaterial />
     </TresMesh>
-    <TheSphere />
     <TresDirectionalLight
       :position="[0, 2, 4]"
       :intensity="1"

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

@@ -1,16 +1,16 @@
 <script setup lang="ts">
 import {
+  advancedRoutes,
   basicRoutes,
   cameraRoutes,
   eventsRoutes,
   miscRoutes,
   modelsRoutes,
-  perfRoutes,
 } from '../router/routes'
 
 const sections = [
   { icon: '📦', title: 'Basic', routes: basicRoutes },
-  { icon: '🏎️', title: 'Perf', routes: perfRoutes },
+  { icon: '🤓', title: 'Advanced', routes: advancedRoutes },
   { icon: '📣', title: 'Events', routes: eventsRoutes },
   { icon: '📷', title: 'Camera', routes: cameraRoutes },
   { icon: '🐇', title: 'Models', routes: modelsRoutes },

+ 2 - 2
playground/src/router/index.ts

@@ -1,6 +1,6 @@
 import { createRouter, createWebHistory } from 'vue-router'
 import { basicRoutes } from './routes/basic'
-import { cameraRoutes, eventsRoutes, miscRoutes, modelsRoutes, perfRoutes } from './routes'
+import { advancedRoutes, cameraRoutes, eventsRoutes, miscRoutes, modelsRoutes } from './routes'
 
 const routes = [
   {
@@ -9,7 +9,7 @@ const routes = [
     component: () => import('../pages/index.vue'),
   },
   ...basicRoutes,
-  ...perfRoutes,
+  ...advancedRoutes,
   ...eventsRoutes,
   ...cameraRoutes,
   ...modelsRoutes,

+ 12 - 0
playground/src/router/routes/advanced.ts

@@ -0,0 +1,12 @@
+export const advancedRoutes = [
+  {
+    path: '/advanced/on-demand',
+    name: 'On Demand',
+    component: () => import('../../pages/advanced/OnDemand.vue'),
+  },
+  {
+    path: '/advanced/take-over-loop',
+    name: 'Take Over loop',
+    component: () => import('../../pages/advanced/TakeOverLoop.vue'),
+  },
+]

+ 2 - 2
playground/src/router/routes/index.ts

@@ -2,12 +2,12 @@ import { modelsRoutes } from './models'
 import { cameraRoutes } from './cameras'
 import { eventsRoutes } from './events'
 import { basicRoutes } from './basic'
-import { perfRoutes } from './performance'
+import { advancedRoutes } from './advanced'
 import { miscRoutes } from './misc'
 
 export {
   basicRoutes,
-  perfRoutes,
+  advancedRoutes,
   eventsRoutes,
   cameraRoutes,
   modelsRoutes,

+ 0 - 7
playground/src/router/routes/performance.ts

@@ -1,7 +0,0 @@
-export const perfRoutes = [
-  {
-    path: '/perf/on-demand',
-    name: 'On Demand',
-    component: () => import('../../pages/perf/OnDemand.vue'),
-  },
-]

+ 2 - 1
src/composables/index.ts

@@ -8,4 +8,5 @@ export * from './useLogger'
 export * from './useSeek'
 export * from './usePointerEventHandler'
 export * from './useTresContextProvider'
-export * from './useLoop/'
+export * from './useFrame'
+export * from './useRender'

+ 25 - 0
src/composables/useFrame/index.ts

@@ -0,0 +1,25 @@
+import type { Fn } from '@vueuse/core'
+import { useTresContext } from '../useTresContextProvider'
+
+export function useFrame(cb: (arg0: any) => void, index = 0) {
+  // We force users to use `useRender` to take over the main loop, so if anyone uses
+  // `useFrame` with index 1, we will automatically change it to index 2 so
+  // it doesn't interfere with the main loop
+
+  const priority = index === 1 ? 2 : index
+  const {
+    camera,
+    scene,
+    renderer,
+    loop,
+  } = useTresContext()
+  const wrappedCallback = (params: any) => {
+    cb({ ...params, camera, scene, renderer })
+  }
+  loop.onLoop(wrappedCallback as Fn, priority)
+  return {
+    pause: loop.pause,
+    resume: loop.resume,
+    isActive: loop.isActive,
+  }
+}

+ 0 - 10
src/composables/useLoop/index.ts

@@ -1,10 +0,0 @@
-import type { Fn } from '@vueuse/core'
-import { useTresContext } from '../useTresContextProvider'
-
-export function useLoop(cb: (arg0: any) => void, index = 0) {
-  const ctx = useTresContext()
-  const wrappedCallback = (params: any) => {
-    cb({ ...params, ctx })
-  }
-  return ctx.loop.onLoop(wrappedCallback as Fn, index)
-}

+ 22 - 0
src/composables/useRender/index.ts

@@ -0,0 +1,22 @@
+import type { Fn } from '@vueuse/core'
+
+import { useTresContext } from '../useTresContextProvider'
+
+export function useRender(cb: (arg0: any) => void) {
+  const {
+    camera,
+    scene,
+    renderer,
+    loop,
+    invalidate,
+  } = useTresContext()
+  const wrappedCallback = (params: any) => {
+    cb({ ...params, camera, scene, renderer, invalidate })
+  }
+  loop.onLoop(wrappedCallback as Fn, 1)
+  return {
+    pause: loop.pause,
+    resume: loop.resume,
+    isActive: loop.isActive,
+  }
+}

+ 0 - 1
src/composables/useRenderLoop/index.ts

@@ -41,7 +41,6 @@ onAfterLoop.on(() => {
 })
 
 export const useRenderLoop = (): UseRenderLoopReturn => {
-  // TODO: add migration guide link here
   return {
     onBeforeLoop: onBeforeLoop.on,
     onLoop: onLoop.on,

+ 1 - 1
src/composables/useTresContextProvider/index.ts

@@ -1,6 +1,6 @@
 import { useFps, useMemory, useRafFn } from '@vueuse/core'
 import { computed, inject, onUnmounted, provide, readonly, ref, shallowRef } from 'vue'
-import type { Camera, EventDispatcher, Object3D, WebGLRenderer } from 'three'
+import type { Camera, Clock, EventDispatcher, Object3D, WebGLRenderer } from 'three'
 import { Raycaster } from 'three'
 import type { ComputedRef, DeepReadonly, MaybeRef, MaybeRefOrGetter, Ref, ShallowRef } from 'vue'
 import { calculateMemoryUsage } from '../../utils/perf'

+ 18 - 3
src/core/loop.ts

@@ -9,6 +9,8 @@ export interface RendererLoop {
   onLoop: (callback: Fn, index: number) => void
   start: () => void
   stop: () => void
+  pause: () => void
+  resume: () => void
   isActive: Ref<boolean>
 }
 
@@ -57,6 +59,18 @@ export function createRenderLoop(): RendererLoop {
     }
   }
 
+  function pause() {
+    // Stops the clock but keeps the loop running
+    clock.stop()
+    isActive.value = false
+  }
+
+  function resume() {
+    // Resumes the clock and the loop
+    clock.start()
+    isActive.value = true
+  }
+
   function loop() {
     const delta = clock.getDelta()
     const elapsed = clock.getElapsedTime()
@@ -67,13 +81,12 @@ export function createRenderLoop(): RendererLoop {
       .sort((a, b) => a - b) // Ensure numerical order
       .forEach((index) => {
         subscribers.get(index).forEach((callback: LoopCallback) => {
+          if (index !== 1 && !isActive.value) { return }
           callback({ delta, elapsed, clock })
         })
       })
 
-    if (isActive.value) {
-      animationFrameId = requestAnimationFrame(loop)
-    }
+    animationFrameId = requestAnimationFrame(loop)
   }
 
   return {
@@ -81,6 +94,8 @@ export function createRenderLoop(): RendererLoop {
     loopId,
     start,
     stop,
+    pause,
+    resume,
     onLoop: (callback: LoopCallback, index = 0) => registerCallback(callback, index),
     isActive,
   }