superellipse visualization on canvas |x/a|^n + |y/b|^n = 1 https://en.wikipedia.org/wiki/Superellipse
A Pen by Andreas Borgen on CodePen.
main#app | |
h1 Superellipse | |
a(href="https://en.wikipedia.org/wiki/Superellipse") | |
img(src="https://wikimedia.org/api/rest_v1/media/math/render/svg/2b21da32fe407ff5714620b26c50343d21afed15") | |
section#settings.inputs(@input="recalc") | |
label | |
span a | |
input(type="range" v-model.number="config.a" min="10" max="400") | |
output {{config.a}} | |
label | |
span b | |
input(type="range" v-model.number="config.b" min="10" max="400") | |
output {{config.b}} | |
label | |
span n | |
input(type="range" v-model.number="nBase" min="0.2" max="2" step=".01") | |
output {{ config.n.toFixed(2) }} | |
p | |
label | |
span detail | |
input(type="range" v-model.number="config.detail" min="2" max="15") | |
output {{ config.detail }} | |
canvas(:width="config.a * 2" :height="config.b * 2") :( |
console.clear(); | |
(function() { | |
"use strict"; | |
const pi_ = Math.PI / 2; | |
class Superellipse { | |
static plot(config) { | |
function sgn(w) { | |
if(w === 0) { return 0; } | |
return (w > 0) ? 1 : -1; | |
} | |
const { a, b, n, detail: steps } = config, | |
n2 = 2/n, | |
coords = []; | |
//First, calculate the coordinates for a quarter squircle: | |
//https://en.wikipedia.org/wiki/Superellipse#Mathematical_properties | |
// x(t) = |cos t|^(2/n) * a*sgn(cos t) | |
// y(t) = |sin t|^(2/n) * b*sgn(sin t) | |
for(let i = 0; i <= steps; i++) { | |
const t = i * (pi_/steps), | |
cos = Math.cos(t), | |
sin = Math.sin(t); | |
const x = Math.pow(Math.abs(cos), n2) * a*sgn(cos), | |
y = Math.pow(Math.abs(sin), n2) * b*sgn(sin); | |
coords.push([x, y]); | |
} | |
//Then, clone those coordinates to plot a full squircle: | |
for(let i = steps-1; i >= 0; i--) { | |
const c = coords[i]; | |
coords.push([-c[0], c[1]]) | |
} | |
for(let i = steps*2 - 1; i > 0; i--) { | |
const c = coords[i]; | |
coords.push([c[0], -c[1]]) | |
} | |
//console.log(coords); | |
return coords; | |
} | |
} | |
//https://en.wikipedia.org/wiki/Superellipse | |
//|x/a|^n + |y/b|^n = 1 | |
const config = { | |
a: 300, | |
b: 200, | |
n: 2.5, | |
detail: 6, | |
}; | |
let canvas, ctx; | |
new Vue({ | |
el: '#app', | |
mounted: update, | |
data: { | |
config: config, | |
nBase: 1.44, | |
}, | |
watch: { | |
nBase(val) { | |
//Trial and error to change the shape smoothly: | |
this.config.n = (val < 1) ? Math.pow(val, 1.25) | |
: Math.pow(val, Math.pow(val, 2.5)); | |
}, | |
}, | |
methods: { | |
recalc() { | |
Vue.nextTick(update); | |
}, | |
}, | |
}); | |
function update() { | |
if(!canvas) { | |
canvas = document.querySelector('canvas'); | |
ctx = canvas.getContext("2d"); | |
} | |
const plot = Superellipse.plot(config); | |
draw(plot); | |
} | |
function draw(plot) { | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
ctx.lineWidth = 1; | |
ctx.strokeStyle = "dodgerblue"; | |
ctx.fillStyle = "honeydew"; | |
ctx.translate(config.a, config.b); | |
ctx.beginPath(); | |
plot.forEach(p => { | |
ctx.lineTo(p[0], p[1]); | |
}); | |
ctx.closePath(); | |
ctx.stroke(); | |
ctx.fill(); | |
ctx.setTransform(1, 0, 0, 1, 0, 0); | |
} | |
})(); |
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.js"></script> |
.inputs { | |
display: table; | |
label { | |
display: table-row; | |
> * { | |
display: table-cell; | |
vertical-align: middle; | |
} | |
span { | |
padding-right: .5em; | |
} | |
input[type="number"], input[type="range"] { | |
~ output { | |
text-align: right; | |
min-width: 3em; | |
} | |
} | |
} | |
} | |
main { | |
margin: .5em; | |
font-family: Georgia, sans-serif; | |
} | |
section { | |
margin: 1em 0; | |
} | |
#settings input[type="range"] { | |
width: 300px; | |
} | |
canvas { | |
display: block; | |
//border: 1px solid gainsboro; | |
box-shadow: 0 0 4px 0 silver; | |
} |
superellipse visualization on canvas |x/a|^n + |y/b|^n = 1 https://en.wikipedia.org/wiki/Superellipse
A Pen by Andreas Borgen on CodePen.