Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(useIntersect): add function, demo, docs #550

Open
wants to merge 1 commit into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export default defineConfig({
{ text: 'StatsGl', link: '/guide/misc/stats-gl' },
{ text: 'useGLTFExporter', link: '/guide/misc/use-gltf-exporter' },
{ text: 'BakeShadows', link: '/guide/misc/bake-shadows' },
{ text: 'useIntersect', link: '/guide/misc/use-intersect' },
],
},
],
Expand Down
1 change: 1 addition & 0 deletions docs/component-list/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export default [
{ text: 'StatsGl', link: '/guide/misc/stats-gl' },
{ text: 'useGLTFExporter', link: '/guide/misc/use-gltf-exporter' },
{ text: 'BakeShadows', link: '/guide/misc/bake-shadows' },
{ text: 'useIntersect', link: '/guide/misc/use-intersect' },
],
},
{
Expand Down
37 changes: 37 additions & 0 deletions docs/guide/misc/use-intersect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# useIntersect

`useIntersect` is a function that returns `intersect`, a `Ref<boolean>` that's updated when the observed object enters or leaves the screen. This relies on [THREE.Object3D.onBeforeRender](https://threejs.org/docs/#api/en/core/Object3D.onBeforeRender) so it only works on objects that are effectively rendered, like meshes, lines, sprites. It won't work on other types like group, object3d, bone, etc.

## Usage

::: warning
`useIntersect` requires a `TresCanvas` context, so it is only available in `TresCanvas` descendant components' `<script setup>`.
:::

```vue
<script setup lang="ts">
import { Torus, useIntersect } from '@tresjs/cientos'

const { ref, intersect, off } = useIntersect()
</script>

<template>
<Torus ref="ref">
<TresMeshNormalMaterial />
</Torus>
</template>
```

## Arguments

| Name | Description | Type |
| :----------- | ----------- | ----------- |
| **onChange** | Optional callback function triggered when the observed object enters or leaves the screen. | `(isIntersected: boolean) => void` |

## Return

| Name | Description | Type |
| :----------- | ----------- | ----------- |
| **ref** | Vue `ShallowRef` to pass to the object to be observed. | `ShallowRef<Object3D>` |
| **intersects** | Updates when the observed object's intersect status changes. | `ShallowRef<boolean>` |
| **off** | Calling this function stops `useIntersect` until `ref` changes. | `() => void` |
16 changes: 16 additions & 0 deletions playground/vue/src/pages/misc/useIntersect/TheExperience.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script setup lang="ts">
import { Torus, useIntersect } from '@tresjs/cientos'

const emit = defineEmits(['intersect'])

const { ref, intersect, off } = useIntersect()

watch(intersect, () => emit('intersect', intersect.value))
defineExpose({ off })
</script>

<template>
<Torus ref="ref">
<TresMeshNormalMaterial />
</Torus>
</template>
67 changes: 67 additions & 0 deletions playground/vue/src/pages/misc/useIntersect/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<script setup lang="ts">
import { TresCanvas } from '@tresjs/core'
import TheExperience from './TheExperience.vue'

const r = shallowRef({ off: () => {} })
const txt = shallowRef('')
const ry = shallowRef(0)

let elapsed = 0
let intervalId: ReturnType<typeof setInterval>

function update() {
elapsed += 1000 / 30
ry.value = elapsed * 0.001
}
onMounted(() => { intervalId = setInterval(update, 1000 / 30) })
onUnmounted(() => clearInterval(intervalId))
</script>

<template>
<div class="overlay-info">
<h1><code>useIntersect</code></h1>
<h2>Setup</h2>
<p>The camera rotates, sending a torus around the screen.</p>
<p>The torus' ref has been sent to <code>useIntersect</code>.</p>
<h2>Intersect status</h2>
<p>{{ txt }}</p>
<h2><code>off</code></h2>
<p>Hitting this button should stop <code>useIntersect</code> updates.</p>
<button @pointerdown="r.off()">Off</button>
</div>
<TresCanvas clear-color="#333">
<TresPerspectiveCamera :rotation-y="ry" />
<TheExperience ref="r" @intersect="(b) => txt = b" />
</TresCanvas>
</template>

<style scoped>
.overlay-info {
position: fixed;
top: 0;
left: 0;
margin: 10px;
padding: 16px;
max-width: 400px;
z-index: 1000;
font-family:
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
'Helvetica Neue',
Arial,
'Noto Sans',
sans-serif,
'Apple Color Emoji',
'Segoe UI Emoji',
'Segoe UI Symbol',
'Noto Color Emoji';
font-size: small;
background-color: white;
border-radius: 6px;
overflow: auto;
}
</style>
5 changes: 5 additions & 0 deletions playground/vue/src/router/routes/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,9 @@ export const miscRoutes = [
name: 'GLTFExporter',
component: () => import('../../pages/misc/GLTFExporterDemo.vue'),
},
{
path: '/misc/useIntersect',
name: 'useIntersect',
component: () => import('../../pages/misc/useIntersect/index.vue'),
},
]
3 changes: 2 additions & 1 deletion src/core/misc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import Html from './html/HTML.vue'
import { Stats } from './Stats'
import { StatsGl } from './StatsGl'
import { useGLTFExporter } from './useGLTFExporter'
import { useIntersect } from './useIntersect'

export { BakeShadows, Html, Stats, StatsGl, useGLTFExporter }
export { BakeShadows, Html, Stats, StatsGl, useGLTFExporter, useIntersect }
70 changes: 70 additions & 0 deletions src/core/misc/useIntersect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useLoop } from '@tresjs/core'
import type { Object3D } from 'three'
import type { Ref } from 'vue'
import { shallowRef, unref, watch } from 'vue'

// NOTE: Inspiration
// https://github.com/pmndrs/drei/blob/master/src/core/useIntersect.tsx

type UseIntersectCallback = (isIntersected: boolean) => void

// NOTE: As of this writing, Cientos components
// use `defineExpose` in this form:
// defineExpose({ instance: THE_COMPONENT })
//
// This means they have to be accessed like
// `obj.instance`, and not merely `obj`
interface CientosExposed { instance: Object3D }
type ObjOrCientosExposed = Object3D | CientosExposed
function normalizeCientosInstance(obj: ObjOrCientosExposed) {
if ('onBeforeRender' in obj && 'onAfterRender' in obj) { return obj }
return obj.instance
}

export function useIntersect<T extends Object3D>(onChange: Ref<UseIntersectCallback> | UseIntersectCallback = () => {}) {
const ref = shallowRef<T>()
const intersect = shallowRef(false)
let _isIntersected = false
let _oldIsIntersected = false

const loop = useLoop()

function setup(objOrCientosExposed: ObjOrCientosExposed) {
const obj = normalizeCientosInstance(objOrCientosExposed)

let oldOnRender = obj.onBeforeRender

const { off: off0 } = loop.onBeforeRender(() => {
_isIntersected = false

// NOTE: If the object is inside the frustum, THREE will call onBeforeRender.
oldOnRender = obj.onBeforeRender
obj.onBeforeRender = () => (_isIntersected = true)
})

const { off: off1 } = loop.onAfterRender(() => {
if (_isIntersected !== _oldIsIntersected) {
intersect.value = _isIntersected
unref(onChange)?.(_isIntersected)
_oldIsIntersected = _isIntersected
}
})

return () => {
off0()
off1()
obj.onBeforeRender = oldOnRender
}
}

let teardown = () => { }

watch(ref, () => {
teardown()
if (ref.value) {
teardown = setup(ref.value)
}
})

return { ref, intersect, off: () => teardown() }
}
Loading