3d version of a spirograph. Click and drag values to change, click and drag model to rotate. Uses hypotrochoid equations rotated about the z axis. Note some weirdness due to rounding issues when the m…
_='c7Wg=h=k,l7Wm7[V"R1N"R2N"H1E3,"Steps2,"ZRotB"Dist"]Wn; _l7];d=ff<=2U+1;f+=2U/3e=)f)+2,pcos@49),qs`@49~J]7e,p,q]}} d,feeeJp=!d-d,q=#f-fAd+!dAf+#f;!=p;#=q}}^load$b.bgColorjdown$dX> -Bz(h=1,k=Yg=1K5=B0;n=jgetC^tQ"2d"_u()Kup$h=g=0Kmove$dif(g)if(hf=c[!-Y,e6>e;eJ)k>Oe+Vzk<Oe+40z(3==e?@#+=f,B>3z@3=B)):#=4==e?#+.029:#+f_)}else Y-c[!,X-c[0]c7X,Y]K u(LclearRec ,Lbeg`PaF(%rgba(2N0.5)";dddJf=1/(.002*(~G2]+5)Ll`eTo(~9+ /2-N~G!9+/2)}(%white";d6>d;dJ)TQdG!,V),TQMaF.round(100*d)/1030g||-1setTimeouu,V)K*MaF.G0]=()f)-2)U/180)functi^d.clienteG@0-1 jwidFvar cos(s`@m[0,"W[fo_^mouse);){=0;2]Lfill<l.lengF; -NOd+/1)9)l[t(jheightLstroke!1]#e$=(%Style="7=[9*f@(A;=B50FthG][J++K};Ln.N5O30*QexUPIV20W],^on_r(`inja.z&&~d';for(Y in $='~zj`_^WVUQONLKJGFBA@97%$#! ')with(_.split($[Y]))_=join(pop());eval(_)
Xz0nC2M3V2c9EGg9EGssbDdXbTdbVhAiUjERTiJSMhFOIkgRMUUzLCJTdGVwcxEyLCJaUm90EUIQIkRpc3QiXVduOwUgXxVsN107f2Q9EGYWZjw9MlUrMTtmKz0yVS8PMwIVC2U9CCkMZikrDzICDBsscANjb3NANAI5KSxxA3NgQDQCORR+Sl03ZSxwLHFdfX0FIB1kLGYVf2UWZRllShULcD0HIQxkBC0XDmQELHE9HCMMZgQtFw5mQWQEKwchDmRBZgQrHCMOZgQ7ByE9cDscIz1xfX1ebG9hZCQVYi5iZ0NvbG9yFmoTZG93biRkFQZYPgktQnooaD0xLGs9BlkUZz0xSw81Aj1CMDtuPWpnZXRDXnRRIjJkIhRfFHUoKUsTdXAkFWg9Zz0wSxNtb3ZlJGQVaWYoZylpZihoFX9mPWNbIS0GWSxlFjY+ZTtlSilrPk9lK1Z6azxPZSs0MHooMz09ZT9AIys9ZixCPg8zAnpAMwI9QikpOg8jPTQ9PWU/DyMrLjAyOToPIytmFF8pfWVsc2UgHQZZLWNbISwGWC1jWzBdFGM3BlgsBlldSwUgdSgVTGNsZWFyUmVjHRAQCSweFExiZWdgUGFGKBQfJXJnYmEoEBAyTjAuNSkiO39kFmQZZEoVC2Y9MS8oLjAwMioofkcyXSsPNQIpFExsYGVUbyh+AjkrCS8yLU5+RyE5Kx4vMil9HygUGCV3aGl0ZSI7EmQWNj5kO2RKKRhUUQ9kRyEsGlYpLBhUUU1hRi5yb3VuZCgxMDAqD2QCKS8xMBAaMzAUZ3x8HRAtMRRzZXRUaW1lb3UddSxWKUsBKk1hRi4CRzBdAz0oCCkOZiktDzICDhspAQRVLzE4MCkFZnVuY3RpXgZkLmNsaWVudAccZUcIQDACLQ8xAglqd2lkRgt2YXIgDAFjb3MoDgFzYEBtWxAwLBEiV1sSZm9fE15tb3VzZRQpOxUpexY9MDsXBzJdGExmaWxsGTxsLmxlbmdGOxoJLU5PZCsbCC8PMQIpOSkcbFsddCgeamhlaWdodB9Mc3Ryb2tlITFdI2UCJD0FKCVTdHlsZT0iNz1bOSpmQCgPQQQ7Fz0XDEI1MEZ0aEddW0orK0t9O0xuLk41EE8zMCpRZXgdVQFQSVYyMFddLF5vbl9yKGBpbmphLnomJn4cZH8SCyc7Zm9yKFkgaW4gJD0nf356amBfXldWVVFPTkxLSkdGQkFAOTclJCMhHx4dHBsaGRgXFhUUExIREA8ODAsJCAcGBQQDAgEnKXdpdGgoXy5zcGxpdCgkW1ldKSlfPWpvaW4ocG9wKCkpO2V2YWwoXyk=
//
// Jon Mullins - Spirograph3D
// Created for js1k-2016
//
// Uses hypotrochoid equations to generate spirograph patterns, then rotates them around the z axis
// Values are shown on the right hand side which can be adjusted by clicking and dragging on them
// The model can be rotated by clicking and dragging on it
//
// Equations taken from here: http://mathworld.wolfram.com/Hypotrochoid.html
//
var mousePos = []; // position of mouse cursor
var mouseDown = 0; // is a mouse button pressed?
var menuSw = 0; // is the mouse on the menu and button pressed?
var menuMousePosY; // the entry position of the mouse onto the menu
var nodes = []; // array containing all points in the model
// note: as the rotation is applied directly to the nodes, rounding issues can cause some strange stretching
// an array containing all values for menu
//
// R1 -> radius of the "inner" wheel
// R2 -> radius of the "outer" wheel
// H -> length of join between outer wheel and pen
// Steps -> amount of times to sample per rotation of inner wheel
// ZRot -> amount of times to rotate around the Z axis per rotation of inner wheel
// Dist -> camera distance
//
var values = [[200,"R1"],
[50,"R2"],
[50,"H"],
[1000,"Steps"],
[2,"ZRot"],
[500,"Dist"]
];
var perspective = 0.002; // amount of perspective to apply (should be really small)
var ctx; // canvas context
var Draw = function ()
{
ctx.strokeStyle = "rgba(0,0,250,0.5)";
// draw the lines between nodes
for (var i=0; i<nodes.length; i++)
{
var k = 1/((nodes[i][2] + values[5][0]) * perspective); // calculate perspective constant
ctx.lineTo(nodes[i][0]*k+a.width/2-50,nodes[i][1]*k+a.height/2); // draw line to perspective translated point (points are translated by a.width/2-50, a.height/2)
}
ctx.stroke();
ctx.fillStyle = "white";
// draw each part of the menu
for (var j=0;j<6;j++)
{
ctx.fillText(values[j][1], a.width-50,j*30+20);
ctx.fillText(Math.round(values[j][0]*100)/100, a.width-50,j*30+30);
}
// auto-rotate the model unless mouse is down
if (!mouseDown)
{
rotate(0,-1);
}
};
// get all the points from the hypotrochoid equation specified by R1, R2, H, ZRot and Steps
// assigns result to nodes array
var getPoints = function()
{
nodes = []; // reset the nodes array as length will be varying as values change
var j=0;
// loop through the equations in 1/steps increments
for (var i=0; i<=Math.PI*2+1; i+=(Math.PI*2/values[3][0]))
{
var x = ((values[0][0]-values[1][0])*Math.cos(i))+values[2][0]*Math.cos((values[0][0]-values[1][0]/values[1][0])*i);
var y = (((values[0][0]-values[1][0])*Math.sin(i))-values[2][0]*Math.sin((values[0][0]-values[1][0]/values[1][0])*i))*Math.cos(values[4][0]*i);
var z = (((values[0][0]-values[1][0])*Math.sin(i))-values[2][0]*Math.sin((values[0][0]-values[1][0]/values[1][0])*i))*Math.sin(values[4][0]*i);
nodes[j++] = [x, y, z];
}
}
// rotate all the points in the nodes array by xt degrees around the X direction, yt degreed around the Y axis
var rotate = function(xt,yt) {
for (var n = 0; n < nodes.length; n++) {
var x = nodes[n][1] * Math.cos(xt*Math.PI/180) - nodes[n][2] * Math.sin(xt*Math.PI/180);
var y = nodes[n][0] * Math.cos(yt*Math.PI/180) - nodes[n][2] * Math.sin(yt*Math.PI/180);
nodes[n][2] = nodes[n][2] * Math.cos(xt*Math.PI/180) + nodes[n][1] * Math.sin(xt*Math.PI/180);
nodes[n][2] = nodes[n][2] * Math.cos(yt*Math.PI/180) + nodes[n][0] * Math.sin(yt*Math.PI/180);
nodes[n][1] = x;
nodes[n][0] = y;
}
};
onload = function ()
{
b.bgColor=0; // set background colour
// only capture mousedown events on the canvas to stop rotating the model when clicking elsewhere
a.onmousedown = function (e)
{
if (e.clientX > a.width-50)
{
menuSw = 1;
menuMousePosY = e.clientY;
}
mouseDown = 1;
};
values[5][0] = 500; // set initial camera value to mostly fill the window with the starting model
ctx = a.getContext("2d");
getPoints();
GameLoop();
}
onmouseup = function ()
{
mouseDown = 0;
menuSw = 0;
}
onmousemove = function (e)
{
if (mouseDown)
{
if (menuSw)
{
var delta = mousePos[1] - e.clientY; // difference in mouse Y position since last frame
// test mouse position against menu items
// note: 6 is used a hard coded version of values.length
for (var i=0; i<6;i++)
{
if (menuMousePosY > i*30+20 && menuMousePosY < i*30+40)
{
if (i==3)
{ // adjust steps value by 10 each move and make sure its more than 50 (if it approaches 0, things go badly)
values[i][0] += delta;
if (values[3][0] < 50) { values[3][0] = 50; }
}
else if (i==4)
{ // adjust zrot value by 0.02 each move
values[i][0] += delta*0.02;
}
else
{ // adjust everything else by 1
values[i][0] += delta;
}
}
}
getPoints();
}
else
{
// rotate the model by the mouse movement in X and Y
rotate(e.clientY-mousePos[1],e.clientX-mousePos[0]);
}
}
// capture the mouse position so we can calculate differences next time round
mousePos = [e.clientX, e.clientY];
}
var clear = function ()
{
// clear the canvas and the path
// note: clearing the path is essential, otherwise each rotation would be drawn over the last
ctx.clearRect(0,0,a.width,a.height);
ctx.beginPath();
}
var GameLoop = function(){
clear();
Draw();
setTimeout(GameLoop, 20);
}