Tresjs / tres

Declarative ThreeJS using Vue Components
https://tresjs.org
MIT License
1.93k stars 86 forks source link

feat: 633 `useFrame` and `useRender` #640

Closed alvarosabu closed 2 months ago

alvarosabu commented 2 months ago

Closes #633

Based on the team dicussion. This PR introduces two new APIs.

useFrame

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.

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.

[!WARNING]
useFrame can be only be used inside of a TresCanvas since this component acts as the provider for the context data.

<script setup>
import { TresCanvas } from '@tresjs/core'
import AnimatedBox from './AnimatedBox.vue'
</script>

<template>
  <TresCanvas>
    <AnimatedBox />
  </TresCanvas>
</template>
<script setup>
import { useFrame } from '@tresjs/core'

const boxRef = ref()

useFrame(({ delta }) => {
  boxRef.value.rotation.y += 0.01
})
</script>

<template>
  <TresMesh ref="boxRef">
    <TresBoxGeometry />
    <TresMeshBasicMaterial color="teal" />
  </TresMesh>
</template>

Your callback function will be triggered just before a frame is rendered and it will be unmounted automatically when the component is destroyed.

Render priority

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.

useFrame(() => {
  console.count('before renderer')
}, -1)

useFrame(() => {
  console.count('after renderer')
}, 1)

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 using this composable.

useRender(({ renderer, scene, camera }) => {
  // Takes over the render-loop, the user has the responsibility to render
  renderer.value.render(scene.value, camera.value)
})
netlify[bot] commented 2 months ago

Deploy Preview for tresjs-docs ready!

Name Link
Latest commit 2ff1c15910dc2b306cf06ea2b1ef6a7142742198
Latest deploy log https://app.netlify.com/sites/tresjs-docs/deploys/661a98290f7e3f0008df204f
Deploy Preview https://deploy-preview-640--tresjs-docs.netlify.app
Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

andretchen0 commented 2 months ago

Hey @alvarosabu ,

This approach doesn't work for directives, at least not directly.

import type { Object3D } from 'three'
import type { Ref } from 'vue'
import { extractBindingPosition } from '../utils'
import type { TresVector3 } from '../types'
import { useLogger, useLoop } from '../composables'

const { logWarning } = useLogger()

export const vAlwaysLookAt = {
  updated: (el: Object3D, binding: Ref<TresVector3>) => {
    const observer = extractBindingPosition(binding)
    if (!observer) {
      logWarning(`v-always-look-at: problem with binding value: ${binding.value}`)
      return
    }
    useLoop(() => {
      el.lookAt(observer)
    })
  },
}

Any thoughts on how to proceed?

andretchen0 commented 2 months ago

@alvarosabu

Do you still want a review on this PR?

alvarosabu commented 2 months ago

Hey @andretchen0 not yet, will update it depending of the result of the dicussion on Discord.

I still need to figure out how to not break directives

alvarosabu commented 2 months ago

Hey @alvarosabu ,

This approach doesn't work for directives, at least not directly.

import type { Object3D } from 'three'
import type { Ref } from 'vue'
import { extractBindingPosition } from '../utils'
import type { TresVector3 } from '../types'
import { useLogger, useLoop } from '../composables'

const { logWarning } = useLogger()

export const vAlwaysLookAt = {
  updated: (el: Object3D, binding: Ref<TresVector3>) => {
    const observer = extractBindingPosition(binding)
    if (!observer) {
      logWarning(`v-always-look-at: problem with binding value: ${binding.value}`)
      return
    }
    useLoop(() => {
      el.lookAt(observer)
    })
  },
}

Any thoughts on how to proceed?

Doing a search on GitHub about usage of the affected directives:

v-always-look-at https://github.com/search?q=v-always-look-at&type=code&p=1 v-rotate: https://github.com/search?q=v-rotate&type=code&p=1

Couting @JaimeTorrealba there are like 2 more users currently using those. I will suggest updating the directives to useFrame and help you and hawk migrate their code to use subcomponents, otherwise they would not work with the new features

andretchen0 commented 2 months ago

@alvarosabu

For sure. It's not so much "don't break existing directives" as "we need to create a convenient API for getting a contextful useLoop outside of the provide tree".

alvarosabu commented 2 months ago

Closed this in favor of #673