Skip to content

Commit

Permalink
feat(Bounds): add component, demo, docs (#408) (#568)
Browse files Browse the repository at this point in the history
* feat(Bounds): add component, demo, docs

* refactor(Bounds): useScreenSize -> useResize

* refactor(Bounds): rename variables, remove unneeded state

* docs: fix material items merge issue

---------

Co-authored-by: alvarosabu <alvaro.saburido@gmail.com>
  • Loading branch information
andretchen0 and alvarosabu authored Jan 2, 2025
1 parent 7e6b8d0 commit 592ec68
Show file tree
Hide file tree
Showing 9 changed files with 798 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ export default defineConfig({
{ text: 'Align', link: '/guide/staging/align' },
{ text: 'SoftShadows', link: '/guide/staging/soft-shadows' },
{ text: 'Grid', link: '/guide/staging/grid' },
{ text: 'Bounds', link: '/guide/staging/bounds' },
{ text: 'RandomizedLights', link: '/guide/staging/randomized-lights' },
],
},
Expand Down
32 changes: 32 additions & 0 deletions docs/.vitepress/theme/components/BoundsDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script setup lang="ts">
import { Bounds, OrbitControls } from '@tresjs/cientos'
import { TresCanvas } from '@tresjs/core'
import { Vector3 } from 'three'
import { shallowRef } from 'vue'
const { sin, cos, PI } = Math
const positions = Array.from(
{ length: 8 },
(_, i) => new Vector3(cos(i * PI / 4) * 4, sin(i * PI / 4) * 4, 0),
)
const b = shallowRef()
</script>

<template>
<TresCanvas clear-color="#4f4f4f">
<TresPerspectiveCamera :position="[0, 0, -15]" />
<OrbitControls make-default />
<Bounds ref="b" clip use-mounted :offset="0.75">
<TresMesh
v-for="p, i of positions"
:key="i"
:position="p"
@click="(e) => b.instance.lookAt(e.object)"
>
<TresBoxGeometry />
<TresMeshNormalMaterial />
</TresMesh>
</Bounds>
</TresCanvas>
</template>
3 changes: 3 additions & 0 deletions docs/component-list/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ export default [
{
text: 'PointMaterial',
link: '/guide/materials/point-material',
},
{
text: 'MeshDiscardMaterial',
link: '/guide/materials/mesh-discard-material',
},
Expand Down Expand Up @@ -120,6 +122,7 @@ export default [
{ text: 'Align', link: '/guide/staging/align' },
{ text: 'SoftShadows', link: '/guide/staging/soft-shadows' },
{ text: 'Grid', link: '/guide/staging/grid' },
{ text: 'Bounds', link: '/guide/staging/bounds' },
{ text: 'RandomizedLights', link: '/guide/staging/randomized-lights' },
],
},
Expand Down
68 changes: 68 additions & 0 deletions docs/guide/staging/bounds.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Bounds

<DocsDemo>
<BoundsDemo />
</DocsDemo>

Calculates a boundary box and centers the camera accordingly. Its `lookAt` method accepts a target to look at imperatively e.g., after a click.

::: info
If you are using other camera controls, be sure to make them the 'default'.
```vue
<OrbitControls make-default />
```
:::

## Usage

<<< @/.vitepress/theme/components/BoundsDemo.vue

## Props

| Name | Description | Default |
| :--- | :--- | ---- |
| `duration` | Duration of the `lookAt` animation in seconds | `1.0` |
| `offset` | Additional distance from the target when using `lookAt` with a `Box3` or `Object3D` | `0.2` |
| `useResize` | Whether to re`lookAt` the last target when the screen is resized | `false` |
| `useMounted` | Whether to `lookAt` the `Bounds` object when the component is mounts | `false` |
| `clip` | Whether to adjust the camera's `near` and `far` settings when using `lookAt` | `false` |
| `easing` | Animation's easing function. `t` and the returned value should be in the interval `[0, 1]` | Cubic ease out |

## `lookAt`

`<Bounds />` `lookAt` points the camera at its first argument: an `Object3D`, `Box3` or `Vector3`.

```
/**
* Calculates a boundary box around an `Object3D` and centers the camera accordingly.
*/
lookAt(object: Object3D): void
/**
* Calculates a boundary box around an `Object3D` and centers the camera accordingly and animates the camera's `up` vector.
*/
lookAt(object: Object3D, up: VectorFlexibleParams): void
/**
* Centers the camera's viewport on a `Box3`.
*/
lookAt(box3: Box3): void
/**
* Centers the camera's viewport on a `Box3` and animates the camera's `up` vector.
*/
lookAt(box3: Box3, up: VectorFlexibleParams): void
/**
* Look at a `Vector3`.
*/
lookAt(target: VectorFlexibleParams): void
/**
* Look at a `Vector3`, if provided. Move the camera to `position`.
*/
lookAt(target: VectorFlexibleParams | undefined | null, position: VectorFlexibleParams): void
/**
* Look at a `Vector3`, if provided. Move the camera to `position` and animate the camera's `up` vector.
*/
lookAt(target: VectorFlexibleParams | undefined | null, position: VectorFlexibleParams, up: VectorFlexibleParams): void
/**
* Rerun `lookAt` using the prior arguments. If `lookAt` has never been called, uses the `Bounds` object.
*/
lookAt(): void
```
145 changes: 145 additions & 0 deletions playground/vue/src/pages/staging/BoundsDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<script setup lang="ts">
import { TresCanvas } from '@tresjs/core'
import { Bounds, Grid, OrbitControls } from '@tresjs/cientos'
import { Vector3 } from 'three'
import { TresLeches, useControls } from '@tresjs/leches'
import '@tresjs/leches/styles'
import { computed, shallowRef } from 'vue'
const c = useControls({
duration: { value: 0.5, min: 0, max: 10, step: 0.25 },
offset: { value: 1, min: -2, max: 2, step: 0.25 },
clip: false,
useMounted: true,
useOrthographic: false,
useResize: false,
isLinear: false,
enabled: true,
lookAtX: { value: 0, min: -20, max: 20, step: 0.10 },
lookAtY: { value: 0, min: -20, max: 20, step: 0.10 },
lookAtZ: { value: 0, min: -20, max: 20, step: 0.10 },
moveToX: { value: -5, min: -20, max: 20, step: 0.10 },
moveToY: { value: -5, min: -20, max: 20, step: 0.10 },
moveToZ: { value: 5, min: -20, max: 20, step: 0.10 },
upX: { value: 0, min: -1, max: 1, step: 0.10 },
upY: { value: -1, min: -1, max: 1, step: 0.10 },
upZ: { value: 0, min: -1, max: 1, step: 0.10 },
})
const { sin, cos, PI } = Math
const positions = Array.from(
{ length: 8 },
(_, i) => new Vector3(cos(i * PI / 4) * 4, sin(i * PI / 4) * 4, 0),
)
const easingFn = computed(() => c.isLinear.value.value ? (n: number) => n : undefined)
const boundsRef = shallowRef()
const startArg = shallowRef('')
const cancelArg = shallowRef('')
const endArg = shallowRef('')
const startCount = shallowRef(0)
const cancelCount = shallowRef(0)
const endCount = shallowRef(0)
const onStartFn = (v: any) => { startArg.value = v.object?.uuid; startCount.value++ }
const onCancelFn = (v: any) => { cancelArg.value = v.object?.uuid; cancelCount.value++ }
const onEndFn = (v: any) => { endArg.value = v.object?.uuid; endCount.value++ }
</script>

<template>
<TresLeches />
<OverlayInfo>
<h1>Bounds</h1>
<h2>Setup</h2>
<p>In this scene, multiple objects are children of <code>&lt;Bounds/&gt;</code>. A <code>pointerup</code> on a child should move/rotate the camera to fit the child into the view.</p>
<h2>lookAt(lookAt?, position?, up?)</h2>
<p><code>&lt;Bounds&gt;</code> has a <code>fit</code> method that can be called imperatively.</p>
<button
@pointerup="() => boundsRef.instance.lookAt(
new Vector3(c.lookAtX.value.value, c.lookAtY.value.value, c.lookAtZ.value.value),
)"
>
lookAt(new Vector(lookAtArgs))
</button><br />
<button
@pointerup="() => boundsRef.instance.lookAt(
[c.lookAtX.value.value, c.lookAtY.value.value, c.lookAtZ.value.value],
)"
>
lookAt([lookAtArgs])
</button><br />
<button
@pointerup="() => boundsRef.instance.lookAt(
[c.lookAtX.value.value, c.lookAtY.value.value, c.lookAtZ.value.value],
[c.moveToX.value.value, c.moveToY.value.value, c.moveToZ.value.value],
)"
>
lookAt([lookAtArgs], [moveToArgs])
</button><br />
<button
@pointerup="() => boundsRef.instance.lookAt(
undefined,
[c.moveToX.value.value, c.moveToY.value.value, c.moveToZ.value.value],
)"
>
lookAt(undefined, [moveToArgs])
</button><br />
<button
@pointerup="() => boundsRef.instance.lookAt(
undefined,
[c.moveToX.value.value, c.moveToY.value.value, c.moveToZ.value.value],
[c.upX.value.value, c.upY.value.value, c.upZ.value.value],
)"
>
lookAt(undefined, [moveToArgs], [upArgs])
</button><br />
<button @pointerup="() => boundsRef.instance.lookAt()">lookAt()</button><br />
<h2>Callback results</h2>
<p>onStart ({{ startCount }})</p>
{{ startArg }}
<hr />
<p>onCancel ({{ cancelCount }})</p>
{{ cancelArg }}
<hr />
<p>onEnd ({{ endCount }})</p>
{{ endArg }}
<h2>Testing Notes</h2>
<p>OrbitControls zoom using an Orthographic camera can result in parts of the scene appearing "cut off", independent of <code>&lt;Bounds/&gt;</code></p>
<p>Switching between Orthographic and Perspective Cameras leads to odd behavior, independent of <code>&lt;Bounds/&gt;</code>. To test, change <code>isOrthographicCamera</code>'s value, save and reload the page.</p>
<p>The <code>clip</code> option sets the camera's clipping to a large multiple of the internal <code>distance</code>. To test, change the component's coefficient to a smaller number.</p>
</OverlayInfo>
<TresCanvas render-mode="on-demand">
<TresOrthographicCamera v-if="c.useOrthographic.value.value" :position="[-5, 5, 5]" :zoom="1" :args="[-400, 400, 400, -400, 0, 10000]" />
<TresPerspectiveCamera v-else :position="[5, 5, 5]" />
<OrbitControls make-default />
<TresGroup>
<Bounds
v-if="c.enabled.value.value"
ref="boundsRef"
:clip="c.clip.value.value"
:duration="c.duration.value.value"
:offset="c.offset.value.value"
:use-resize="c.useResize.value.value"
:use-mounted="c.useMounted.value.value"
:easing="easingFn"
@start="onStartFn"
@cancel="onCancelFn"
@end="onEndFn"
>
<TresMesh
v-for="p, i of positions"
:key="i"
:position="p"
@pointer-up="(e) => boundsRef.instance.lookAt(e.object)"
>
<TresBoxGeometry />
<TresMeshNormalMaterial />
</TresMesh>
</Bounds>
</TresGroup>
<Grid :scale="20" :cell-size=".1" :section-size="0.3" section-color="#AAF" :infinite-grid="true" />
</TresCanvas>
</template>
5 changes: 5 additions & 0 deletions playground/vue/src/router/routes/staging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ export const stagingRoutes = [
name: 'Ocean',
component: () => import('../../pages/staging/OceanDemo.vue'),
},
{
path: '/staging/bounds',
name: 'Bounds',
component: () => import('../../pages/staging/BoundsDemo.vue'),
},
{
path: '/staging/fit',
name: 'Fit',
Expand Down
Loading

0 comments on commit 592ec68

Please sign in to comment.