-
-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Superformula): add superformula (#274)
* 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
1 parent
8b5577e
commit 19130b4
Showing
8 changed files
with
313 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
38
docs/.vitepress/theme/components/SuperformulaLechesDemo.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, 1.3, 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, 1.3, 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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters