Skip to content

Instantly share code, notes, and snippets.

@Sphinxxxx
Created May 1, 2018 14:08
Show Gist options
  • Save Sphinxxxx/3d49efead25dfb84d36ba28fabcc5fc2 to your computer and use it in GitHub Desktop.
Save Sphinxxxx/3d49efead25dfb84d36ba28fabcc5fc2 to your computer and use it in GitHub Desktop.
Superellipse (include squircle)
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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment