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(Align): add component, demo, docs #544

Merged
merged 2 commits into from
Dec 10, 2024
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 @@ -158,6 +158,7 @@ export default defineConfig({
{ text: 'Precipitation', link: '/guide/staging/precipitation' },
{ text: 'Sparkles', link: '/guide/staging/sparkles' },
{ text: 'Ocean', link: '/guide/staging/ocean' },
{ text: 'Align', link: '/guide/staging/align' },
{ text: 'SoftShadows', link: '/guide/staging/soft-shadows' },
{ text: 'Grid', link: '/guide/staging/grid' },
],
Expand Down
20 changes: 20 additions & 0 deletions docs/.vitepress/theme/components/AlignDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<script setup lang="ts">
import { Align, OrbitControls } from '@tresjs/cientos'
import { TresCanvas } from '@tresjs/core'
</script>

<template>
<TresCanvas clear-color="#222">
<TresPerspectiveCamera />
<OrbitControls />

<TresAxesHelper :scale="2" />

<Align top right back>
<TresMesh>
<TresBoxGeometry />
<TresMeshNormalMaterial />
</TresMesh>
</Align>
</TresCanvas>
</template>
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: 'Sparkles', link: '/guide/staging/sparkles' },
{ text: 'Ocean', link: '/guide/staging/ocean' },
{ text: 'Fit', link: '/guide/staging/fit' },
{ text: 'Align', link: '/guide/staging/align' },
{ text: 'SoftShadows', link: '/guide/staging/soft-shadows' },
{ text: 'Grid', link: '/guide/staging/grid' },
],
Expand Down
51 changes: 51 additions & 0 deletions docs/guide/staging/align.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Align

<DocsDemo>
<AlignDemo />
</DocsDemo>

Calculates a bounding box around its children and aligns them as a group within their parent. The component measures its contents and realigns on every frame unless `cacheKey` is set.

## Usage

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

## Props

All props are optional.

| Prop | Description |
| ------------ | ----------------------------------- |
| `top` | If `true`, aligns bounding box bottom to `0` on the y-axis |
| `bottom` | If `true`, aligns bounding box top to `0` on the y-axis. |
| `left` | If `true`, aligns bounding box right to `0` on the x-axis. |
| `right` | If `true`, aligns bounding box left to `0` on the x-axis. |
| `front` | If `true`, aligns bounding box back to `0` on the z-axis. |
| `back` | If `true`, aligns bounding box front to `0` on the z-axis. |
| `disable` | If `true`, disables alignment on all axes. |
| `disableX` | If `true`, disables alignment on the x-axis. |
| `disableY` | If `true`, disables alignment on the y-axis. |
| `disableZ` | If `true`, disables alignment on the z-axis. |
| `precise` | See [Box3.setFromObject](https://threejs.org/docs/index.html?q=box3#api/en/math/Box3.setFromObject). |
| `onAlign` | Callback that fires when updating, after measurement. |
| `cacheKey` | If set, component will only update when `cacheKey`'s value changes. If unset, component will update every frame. |

## `OnAlignCallbackProps`

```ts
export interface OnAlignCallbackProps {
/** The next parent above <Align /> */
parent: Object3D
/** The outmost container group of the <Align /> component */
container: Object3D
width: number
height: number
depth: number
boundingBox: Box3
boundingSphere: Sphere
center: Vector3
verticalAlignment: number
horizontalAlignment: number
depthAlignment: number
}
```
61 changes: 61 additions & 0 deletions playground/vue/src/pages/staging/AlignDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<script setup lang="ts">
import { Align, OrbitControls } from '@tresjs/cientos'
import { TresCanvas } from '@tresjs/core'
import { TresLeches, useControls } from '@tresjs/leches'
import '@tresjs/leches/styles'

const elapsed = shallowRef(0)
let intervalId: ReturnType<typeof setInterval>
onMounted(() => {
intervalId = setInterval(() => {
elapsed.value += 1000 / 30
}, 1000 / 30)
})

onUnmounted(() => {
clearInterval(intervalId)
})

const ctrl = useControls({
top: false,
left: false,
front: false,
bottom: false,
right: false,
back: false,
disable: false,
disableX: false,
disableY: false,
disableZ: false,
useCacheKey: false,
vIf: true,
})
</script>

<template>
<TresLeches />
<TresCanvas>
<TresPerspectiveCamera :position="[0, 0, 5]" />
<OrbitControls />
<TresAxesHelper :scale="2" :position="[0, 0, 0]" />
<Align
v-if="ctrl.vIf.value.value"
:left="ctrl.left.value.value"
:right="ctrl.right.value.value"
:top="ctrl.top.value.value"
:bottom="ctrl.bottom.value.value"
:front="ctrl.front.value.value"
:back="ctrl.back.value.value"
:disable="ctrl.disable.value.value"
:disable-x="ctrl.disableX.value.value"
:disable-y="ctrl.disableY.value.value"
:disable-z="ctrl.disableZ.value.value"
:cache-key="ctrl.useCacheKey.value.value ? (() => Math.floor(elapsed * 0.001)) : undefined"
>
<TresMesh :position-x="Math.cos(elapsed * 0.001) * 2" :position-y="Math.sin(elapsed * 0.001) * 2" :scale-z="Math.cos(elapsed * 0.001) * 2">
<TresBoxGeometry />
<TresMeshNormalMaterial />
</TresMesh>
</Align>
</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 @@ -49,6 +49,11 @@ export const stagingRoutes = [
name: 'Fit',
component: () => import('../../pages/staging/fit/index.vue'),
},
{
path: '/staging/align',
name: 'Align',
component: () => import('../../pages/staging/AlignDemo.vue'),
},
{
path: '/staging/soft-shadows',
name: 'SoftShadows',
Expand Down
142 changes: 142 additions & 0 deletions src/core/staging/Align.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<script setup lang="ts">
import { useLoop } from '@tresjs/core'
import type { Group, Object3D } from 'three'
import { Box3, Sphere, Vector3 } from 'three'
import type { MaybeRefOrGetter } from 'vue'
import { shallowRef, toValue, watchEffect } from 'vue'

// NOTE: Sources
// https://github.com/pmndrs/drei/blob/master/src/core/Center.tsx

export interface OnAlignCallbackProps {
/** The next parent above <Align /> */
parent: Object3D
/** The outmost container group of the <Align/> component */
container: Object3D
width: number
height: number
depth: number
boundingBox: Box3
boundingSphere: Sphere
center: Vector3
verticalAlignment: number
horizontalAlignment: number
depthAlignment: number
}

export interface AlignProps {
top?: boolean
right?: boolean
bottom?: boolean
left?: boolean
front?: boolean
back?: boolean
/** Disable all axes */
disable?: boolean
/** Disable x-axis alignment */
disableX?: boolean
/** Disable y-axis alignment */
disableY?: boolean
/** Disable z-axis alignment */
disableZ?: boolean
/** See https://threejs.org/docs/index.html?q=box3#api/en/math/Box3.setFromObject */
precise?: boolean
/** Callback, fires when updating, after measurement */
onAlign?: (props: OnAlignCallbackProps) => void
/** Optional cacheKey to keep the component from recalculating on every render */
cacheKey?: MaybeRefOrGetter<any>
}

const props = withDefaults(defineProps<AlignProps>(), {
precise: true,
cacheKey: undefined,
})

const ref = shallowRef<Group>()
const outer = shallowRef<Group>()
const inner = shallowRef<Group>()

const box3 = new Box3()
const center = new Vector3()
const sphere = new Sphere()

function update() {
if (!outer.value || !inner.value || !ref.value) { return }
outer.value.matrixWorld.identity()
box3.setFromObject(inner.value, props.precise)
const width = box3.max.x - box3.min.x
const height = box3.max.y - box3.min.y
const depth = box3.max.z - box3.min.z
box3.getCenter(center)
box3.getBoundingSphere(sphere)
const yAlign = props.top ? height / 2 : props.bottom ? -height / 2 : 0
const xAlign = props.left ? -width / 2 : props.right ? width / 2 : 0
const zAlign = props.front ? depth / 2 : props.back ? -depth / 2 : 0

outer.value.position.set(
props.disable || props.disableX ? 0 : -center.x + xAlign,
props.disable || props.disableY ? 0 : -center.y + yAlign,
props.disable || props.disableZ ? 0 : -center.z + zAlign,
)

// Only fire onCentered if the bounding box has changed
if (typeof props.onAlign !== 'undefined') {
props.onAlign({
parent: ref.value.parent!,
container: ref.value,
width,
height,
depth,
boundingBox: box3,
boundingSphere: sphere,
center,
verticalAlignment: yAlign,
horizontalAlignment: xAlign,
depthAlignment: zAlign,
})
}
}

let off: (() => void) | null = null
let cacheKey: any = null

// NOTE: `useLoop` requires TresContext, which is only
// availble in setup. We need a handle in useEffect,
// so keep a reference.
const loop = useLoop()

watchEffect(() => {
// NOTE: update whenever cachekey changes.

// NOTE: We might already always be updating, so `off`.
off?.()
off = null

// NOTE: Don't update if we resolve to the previous cacheKey value,
// unless cacheKey is null or undefined.
const nextKey = toValue(props.cacheKey)
if (nextKey === cacheKey && cacheKey !== null && cacheKey !== undefined) { return }
cacheKey = nextKey

if (props.cacheKey === null || props.cacheKey === undefined) {
off = loop.onBeforeRender(() => {
update()
}).off
}
else {
update()
}
})

defineExpose({ instance: ref, update })
</script>

<template>
<TresGroup ref="ref">
<TresGroup ref="outer">
<TresGroup ref="inner">
<slot></slot>
</TresGroup>
</TresGroup>
</TresGroup>
</template>
2 changes: 2 additions & 0 deletions src/core/staging/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Align from './Align.vue'
import Backdrop from './Backdrop.vue'
import ContactShadows from './ContactShadows.vue'
import Fit from './Fit.vue'
Expand All @@ -13,6 +14,7 @@ import Environment from './useEnvironment/component.vue'
import Lightformer from './useEnvironment/lightformer/index.vue'

export {
Align,
Backdrop,
ContactShadows,
Environment,
Expand Down
Loading