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(Bounds): add component, demo, docs (#408) #568

Merged
merged 5 commits into from
Jan 2, 2025
Merged
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 @@ -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
Loading