Skip to content

Instantly share code, notes, and snippets.

@tudorsaitoc
Created March 3, 2016 02:50
Show Gist options
  • Select an option

  • Save tudorsaitoc/da8e440d2e19f8b993ca to your computer and use it in GitHub Desktop.

Select an option

Save tudorsaitoc/da8e440d2e19f8b993ca to your computer and use it in GitHub Desktop.
Coiling loxodromes
<canvas id="c"></canvas>
var circles = [
{ color: '#21a5ad', size: 84, angle: Math.PI / 3 },
{ color: '#ffad10', size: 92, angle: - Math.PI / 3 },
{ color: '#ef4239', size: 100, angle: 0 }
];
var segmentsPerCircle = 200;
var speed = .8;
function twistEasing(t)
{
return (t < .5) ? 2 * t * t : 1 - 2 * (t = 1 - t) * t;
}
var c = document.getElementById('c'),
ctx = c.getContext('2d');
Math.PI2 = 2 * Math.PI;
function rotateX(p, a)
{
var d = Math.sqrt(p[2] * p[2] + p[1] * p[1]),
na = Math.atan2(p[1], p[2]) + a;
return [p[0], d * Math.sin(na), d * Math.cos(na)];
}
function rotateY(p, a)
{
var d = Math.sqrt(p[2] * p[2] + p[0] * p[0]),
na = Math.atan2(p[2], p[0]) + a;
return [d * Math.cos(na), p[1], d * Math.sin(na)];
}
function rotateZ(p, a)
{
var d = Math.sqrt(p[1] * p[1] + p[0] * p[0]),
na = Math.atan2(p[1], p[0]) + a;
return [d * Math.cos(na), d * Math.sin(na), p[2]];
}
function resize()
{
c.width = c.offsetWidth;
c.height = c.offsetHeight;
ctx.translate(c.width *.5, c.height * .5);
ctx.lineWidth = 5;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
}
window.addEventListener('resize', resize);
resize();
var angleOffset = 0;
var angleOffsetGoal = 0;
c.addEventListener('mousemove', function(e) { angleOffsetGoal = Math.PI2 * (e.clientX / c.width - .5); });
c.addEventListener('mouseout', function(e) { angleOffsetGoal = 0; });
function loxo(radius, angle, segments)
{
var r = [];
for(var i = 0; i < segments; i++)
{
var a = Math.PI2 * i / segments,
c = Math.cos(a),
s = Math.sin(a);
var ax = Math.PI * .5;
ax -= (c + 1) * .5 * angle;
r.push([radius * c, radius * s * Math.sin(ax), radius * s * Math.cos(ax)]);
}
return r;
}
function loop()
{
requestAnimationFrame(loop);
angleOffset += (angleOffsetGoal - angleOffset) * .1;
ctx.clearRect(-c.width*.5,-c.height*.5,c.width,c.height);
var t = Date.now() * 1e-3 * speed % Math.PI;
var rotationY = -t - Math.PI * .5;
var rotationZ = Math.PI * .5 * Math.cos(t);
var twist = twistEasing((Math.cos(t * 2 + Math.PI) + 1) * .5),
twistAngle = twist * 2 * Math.PI2,
twistSign = (t * 2 > Math.PI ? 1 : -1);
//var base = loxo(100, 0, 200);
var circlesPoints = [];
var i, l, j, ll;
for(i = 0, l = circles.length; i < l; i++)
{
var pts = loxo(circles[i].size, twistAngle, segmentsPerCircle);
for(j = 0, ll = segmentsPerCircle; j < ll; j++)
{
pts[j] = rotateX(pts[j], circles[i].angle * (1 - twist) * twistSign);
pts[j] = rotateY(rotateZ(rotateY(pts[j], rotationY), rotationZ), angleOffset);
}
circlesPoints.push(pts);
}
// Ugly trick for correct z-ordering : draw in 2 steps
for(i = circles.length - 1; i >= 0; i--)
{
ctx.strokeStyle = circles[i].color;
ctx.beginPath();
for(j = 0, ll = segmentsPerCircle; j < ll; j++)
{
var p = circlesPoints[i][j];
if(p[2] < 0) continue;
var prev = circlesPoints[i][j == 0 ? ll - 1 : j - 1];
ctx.moveTo(prev[0], prev[1]);
ctx.lineTo(p[0], p[1]);
}
ctx.stroke();
}
for(i = 0, l = circles.length; i < l; i++)
{
ctx.strokeStyle = circles[i].color;
ctx.beginPath();
for(j = 0, ll = segmentsPerCircle; j < ll; j++)
{
var p = circlesPoints[i][j];
if(p[2] > 0) continue;
var prev = circlesPoints[i][j == 0 ? ll - 1 : j - 1];
ctx.moveTo(prev[0], prev[1]);
ctx.lineTo(p[0], p[1]);
}
ctx.stroke();
}
}
requestAnimationFrame(loop);
body
{
background: #262626;
}
#c
{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment