Skip to content

Commit

Permalink
feat(Superformula): add superformula (#274)
Browse files Browse the repository at this point in the history
* feat(Superformula): add superformula

* feat(Superformula): remove unused ref

* feat(superformula): fix linter errors

* feat(Superformula): group exponent args

* feat(Superformula): merge main
  • Loading branch information
andretchen0 authored Nov 3, 2023
1 parent 8b5577e commit 19130b4
Show file tree
Hide file tree
Showing 8 changed files with 313 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 @@ -90,6 +90,7 @@ export default defineConfig({
{ text: 'Plane', link: '/guide/shapes/plane' },
{ text: 'Ring', link: '/guide/shapes/ring' },
{ text: 'Sphere', link: '/guide/shapes/sphere' },
{ text: 'Superformula', link: '/guide/shapes/superformula' },
{ text: 'Tetrahedron', link: '/guide/shapes/tetrahedron' },
{ text: 'Torus', link: '/guide/shapes/torus' },
{ text: 'TorusKnot', link: '/guide/shapes/torus-knot' },
Expand Down
16 changes: 16 additions & 0 deletions docs/.vitepress/theme/components/SuperformulaDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script setup lang="ts">
import { TresCanvas } from '@tresjs/core'
import { Superformula, OrbitControls } from '@tresjs/cientos'
</script>

<template>
<TresCanvas clear-color="#777">
<Superformula
:num-arms-b="24"
:exp-b="[40, 30, 20]"
>
<TresMeshNormalMaterial />
</Superformula>
<OrbitControls />
</TresCanvas>
</template>
38 changes: 38 additions & 0 deletions docs/.vitepress/theme/components/SuperformulaLechesDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script setup lang="ts">
import { AmbientLight, Color, DirectionalLight, MeshPhongMaterial } from 'three'
import { TresCanvas } from '@tresjs/core'
import { Superformula } from '@tresjs/cientos'
import { useControls, TresLeches } from '@tresjs/leches'
import '@tresjs/leches/styles'
const { numArmsA, numArmsB, expA1 } = useControls({
numArmsA: { value: 1, min: 1, max: 40, step: 1 },
numArmsB: { value: 1, min: 1, max: 40, step: 0.1 },
expA1: { value: 8, min: 4, max: 40, step: 0.01 },
})
const material = new MeshPhongMaterial({ color: '#fbb03b', shininess: 1000 })
const directionalLight = new DirectionalLight('white', 4)
directionalLight.position.set(1, 1, 1)
const ambientLight = new AmbientLight('pink', 1)
</script>

<template>
<TresLeches class="important-top-4 important-left-4" />
<TresCanvas clear-color="#777">
<primitive :object="directionalLight" />
<primitive :object="ambientLight" />
<Superformula
:position="[1.5, 0.7, 0]"
:width-segments="128"
:height-segments="128"
:num-arms-a="numArmsA.value"
:num-arms-b="numArmsB.value"
:exp-a="[expA1.value, 8, 0]"
:exp-b="[2, 1, 2]"
color="orange"
>
<primitive :object="material" />
</Superformula>
</TresCanvas>
</template>
30 changes: 30 additions & 0 deletions docs/guide/shapes/superformula.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Superformula

<DocsDemo>
<SuperformulaLechesDemo />
</DocsDemo>

The `cientos` package provides a `<Superformula />` component that produces a configurable [3D plot of the superformula](https://en.wikipedia.org/wiki/Superformula).

## Usage
<DocsDemo>
<SuperformulaDemo />
</DocsDemo>

<<< @/.vitepress/theme/components/SuperformulaDemo.vue{3,8-13}

## Props

The `<Superformula />` 3D plot is the product of 2 2D superformulas, referred to as "A" and "B" in the props. See this [Wikipedia article about the superformula](https://en.wikipedia.org/wiki/Superformula) for more information about the function's arguments.

<table><thead><tr class="row-header"><th class="col-name">Name</th><th class="col-description">Description</th><th class="col-default">Default</th></tr></thead><tbody><tr class="row-width-segments"><td class="col-name"><strong><nobr>widthSegments</nobr></strong></td><td class="col-description">Number of horizontal mesh segments<br>
</td><td class="col-default"><code>32</code></td></tr><tr class="row-height-segments"><td class="col-name"><strong><nobr>heightSegments</nobr></strong></td><td class="col-description">Number of vertical mesh segments<br>
</td><td class="col-default"><code>32</code></td></tr><tr class="row-num-arms-a"><td class="col-name"><strong><nobr>numArmsA</nobr></strong></td><td class="col-description">For A, number of radial arms/ripples</td><td class="col-default"><code>4</code></td></tr><tr class="row-exp-a"><td class="col-name"><strong><nobr>expA</nobr></strong></td><td class="col-description">A's 3 exponents<br>
</td><td class="col-default"><code>[40,&nbsp;1.3,&nbsp;0.9]</code></td></tr><tr class="row-num-arms-b"><td class="col-name"><strong><nobr>numArmsB</nobr></strong></td><td class="col-description">For B, number of radial arms/ripples<br>
</td><td class="col-default"><code>4</code></td></tr><tr class="row-exp-b1"><td class="col-name"><strong><nobr>expB</nobr></strong></td><td class="col-description">B's 3 exponents<br>
</td><td class="col-default"><code>[40,&nbsp;1.3,&nbsp;0.9]</code></td></tr><tr class="row-color"><td class="col-name"><strong><nobr>color</nobr></strong></td><td class="col-description">If no material is provided, a color for the default material<br>
</td><td class="col-default"><code>'white'</code></td></tr></tbody></table>

## Slot

`<Superformula />` has a single slot for an optional material.
54 changes: 54 additions & 0 deletions playground/src/pages/shapes/SuperformulaDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<script setup lang="ts">
import { Color } from 'three'
import { TresCanvas, useRenderLoop } from '@tresjs/core'
import { OrbitControls, Superformula } from '@tresjs/cientos'
const numArmsA = shallowRef(1)
const numArmsB = shallowRef(1)
const expA1 = shallowRef(1)
const expA2 = shallowRef(1)
const expA3 = shallowRef(1)
const expB1 = shallowRef(1)
const expB2 = shallowRef(1)
const expB3 = shallowRef(1)
const { sin, cos } = Math
useRenderLoop().onLoop(({ elapsed }) => {
const e = elapsed * 0.1
numArmsA.value = sin(e * Math.PI) * 24
expA1.value = (sin(e * Math.PI) + 2 ) * 30
expA2.value = (sin(e * Math.E) + 2) * 30
expA3.value = (sin(e * Math.SQRT2) + 2) * 30
numArmsB.value = cos(e) * 24
expB1.value = (cos(e * Math.PI) + 2) * 30
expB2.value = (cos(e * Math.E) + 2) * 30
expB3.value = (cos(e * Math.SQRT2) + 2) * 30
})
</script>

<template>
<TresCanvas clear-color="#777">
<TresDirectionalLight
:position="[3, 2, 1]"
:intensity="8"
/>
<TresAmbientLight
:position="[3, 2, 1]"
:intensity="1"
:color="new Color('pink')"
/>
<Superformula
:width-segments="256"
:height-segments="256"
:num-arms-a="numArmsA"
:exp-a="[expA1, expA2, expA3]"
:num-arms-b="numArmsB"
:exp-b="[expB1, expB2, expB3]"
>
<TresMeshNormalMaterial />
</Superformula>
<TresGridHelper />
<OrbitControls />
</TresCanvas>
</template>
5 changes: 5 additions & 0 deletions playground/src/router/routes/shapes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,9 @@ export const shapesRoutes = [
name: 'Line2',
component: () => import('../../pages/shapes/Line2Demo.vue'),
},
{
path: '/shapes/superformula',
name: 'Superformula',
component: () => import('../../pages/shapes/SuperformulaDemo.vue'),
},
]
167 changes: 167 additions & 0 deletions src/core/shapes/Superformula.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<script setup lang="ts">
import { BufferAttribute, BufferGeometry } from 'three'
import { shallowRef, watch, onUnmounted } from 'vue'
import type { TresColor } from '@tresjs/core'
export type Float3 = [number, number, number]
export interface SuperFormulaProps {
/**
* Number of horizontal mesh segments
*/
widthSegments?: number
/**
* Number of vertical mesh segments
*/
heightSegments?: number
/**
* The 3D Superformula is the spherical product of 2 2D superformula curves: here called curves "A" and "B".
* Number of radial arms/ripples of A, corresponding to "m" [in this article.](https://en.wikipedia.org/wiki/Superformula)
*/
numArmsA?: number
/**
* A's 3 exponents
*/
expA?: Float3
/**
* For B, number of radial arms/ripples
*/
numArmsB?: number
/**
* B's 3 exponents
*/
expB?: Float3
/**
* If no material is provided, a color for the default material
*/
color?: TresColor
}
const props = withDefaults(defineProps<SuperFormulaProps>(), {
widthSegments: 32,
heightSegments: 32,
numArmsA: 4,
expA: () => [40, 1.3, 0.9],
numArmsB: 4,
expB: () => [40, 1.3, 0.9],
color: 'white',
})
const { cos, sin, abs } = Math
const geometry = shallowRef()
const color = shallowRef(props.color)
function makeGeometry(widthSegments: number, heightSegments: number) {
const geometry = new BufferGeometry()
const numPoints = widthSegments * heightSegments
const vertices = new Float32Array(new Array(3 * numPoints).fill(0))
const normals = new Float32Array(new Array(3 * numPoints).fill(0))
const indices: number[] = []
for (let h = 0; h < heightSegments - 1; h++) {
for (let w = 0; w < widthSegments - 1; w++) {
const tl = h * widthSegments + w
const tr = tl + 1
const bl = tl + widthSegments
const br = tr + widthSegments
indices.push(tl, bl, tr)
indices.push(bl, br, tr)
}
const tl = h * widthSegments + widthSegments - 1
const tr = h * widthSegments
const bl = tl + widthSegments
const br = tr + widthSegments
indices.push(tl, bl, tr)
indices.push(bl, br, tr)
}
geometry.setIndex(indices)
geometry.setAttribute('position', new BufferAttribute(vertices, 3))
geometry.setAttribute('normal', new BufferAttribute(normals, 3))
return geometry
}
// Source:
// https://en.wikipedia.org/wiki/Superformula
// NOTE: Superformula 2D
function r(theta: number, numArms: number, exp1: number, exp2: number, exp3: number): number {
return (abs(cos(numArms * theta * 0.25)) ** exp2 + abs(sin(numArms * theta * 0.25)) ** exp3) ** (-1 / exp1)
}
// NOTE: Superformula 3D
function updateGeometry(geometry: BufferGeometry,
numArmsA: number, expA1: number, expA2: number, expA3: number,
numArmsB: number, expB1: number, expB2: number, expB3: number,
widthSegments: number, heightSegments: number,
) {
const thetaStep = 2 * Math.PI / widthSegments
const thetaStart = -Math.PI
const phiStep = Math.PI / (heightSegments - 1)
const phiStart = -0.5 * Math.PI
const positionAttribute = geometry.getAttribute('position')
let i = 0
let theta = 0
let phi = phiStart
for (let pi = 0; pi < heightSegments; pi++) {
theta = thetaStart
for (let ti = 0; ti < widthSegments; ti++) {
const rA = r(theta, numArmsA, expA1, expA2, expA3)
const rB = r(phi, numArmsB, expB1, expB2, expB3)
positionAttribute.setXYZ(
i,
rA * cos(theta) * rB * cos(phi),
rB * sin(phi),
rA * sin(theta) * rB * cos(phi),
)
i++
theta += thetaStep
}
phi += phiStep
}
positionAttribute.needsUpdate = true
geometry.computeVertexNormals()
}
watch(() => props.color, () => color.value = props.color)
watch(() => [props.widthSegments, props.heightSegments], () => {
if (geometry.value) {
geometry.value.dispose()
}
geometry.value = makeGeometry(props.widthSegments, props.heightSegments)
}, { immediate: true })
watch(() => [
props.numArmsA, props.expA[0], props.expA[1], props.expA[2],
props.numArmsB, props.expB[0], props.expB[1], props.expB[2],
],
() => updateGeometry(geometry.value,
props.numArmsA, props.expA[0], props.expA[1], props.expA[2],
props.numArmsB, props.expB[0], props.expB[1], props.expB[2],
props.widthSegments, props.heightSegments,
), { immediate: true })
onUnmounted(() => {
if (geometry.value) {
geometry.value.dispose()
}
})
const superformulaRef = shallowRef()
defineExpose({
value: superformulaRef,
})
</script>

<template>
<TresMesh
ref="superformulaRef"
v-bind="$attrs"
:geometry="geometry"
>
<slot>
<TresMeshBasicMaterial :color="color" />
</slot>
</TresMesh>
</template>
2 changes: 2 additions & 0 deletions src/core/shapes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Octahedron from './Octahedron.vue'
import Plane from './Plane.vue'
import Ring from './Ring.vue'
import Sphere from './Sphere.vue'
import Superformula from './Superformula.vue'
import Tetrahedron from './Tetrahedron.vue'
import Torus from './Torus.vue'
import TorusKnot from './TorusKnot.vue'
Expand All @@ -26,6 +27,7 @@ export {
Plane,
Ring,
Sphere,
Superformula,
Tetrahedron,
Torus,
TorusKnot,
Expand Down

0 comments on commit 19130b4

Please sign in to comment.