A Hypocycloid
(see Wikipedia)
A
B
The HTML file:
<html>
<head>
<script id="vertex-shader" type="x-shader/x-vertex" >
attribute vec4 vPosition;
uniform float whir;
uniform float boing;
varying vec4 f_Color;
void main() {
float d;
vec4 pos;
pos = vPosition;
vec4 red = vec4(1.0, 0.0, 0.0, 1.0);
vec4 blue = vec4(0.0, 0.0, 1.0, 1.0);
d = 2.0*(sqrt(pos.x * pos.x + pos.y * pos.y));
f_Color = mix(blue, red, d);
pos.x = (-sin(whir)*vPosition.x + cos(whir)*vPosition.y) * boing;
pos.y = ( cos(whir)*vPosition.x + sin(whir)*vPosition.y) * boing;
gl_Position = pos;
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
precision mediump float;
varying vec4 f_Color;
void main() {
gl_FragColor = f_Color;
}
</script>
<script type="text/javascript" src="../Common/webgl-utils.js"></script>
<script type="text/javascript" src="../Common/initShaders.js"></script>
<script type="text/javascript" src="../Common/MV.js"></script>
<script type="text/javascript" src="hyp.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
<style>
canvas{
display:block;
border:2px solid #0094ff;
}
</style>
</head>
<body>
<h1>A Hypocycloid</h1> (see <a href="https://en.wikipedia.org/wiki/Hypocycloid">Wikipedia</a>)
<script>
var canvas = new Canvas(500, 500);
canvas.Redisplay();
</script>
A
<input
type="text"
name="r"
maxLength = "5"
size = "5"
value = "5"
id="rBox", onchange="canvas.ChangeA(this.value), canvas.Redisplay()">
B
<input
type="text"
name="k"
maxLength = "5"
size = "5"
value = "1"
id="kBox", onchange="canvas.ChangeB(this.value), canvas.Redisplay()">
</body>
The Javascript file:
function MakeCanvas(width, height, locID) {
if (width == undefined || width < 0) {
width = 300;
}
if (height == undefined || height < 0) {
height = 300;
}
var canvas = document.createElement('canvas')
// allow the canvas to take a focus.
canvas.tabIndex = 0;
canvas.height = height;
canvas.width = width;
if(locID == undefined) {
document.body.appendChild(canvas);
} else {
div = document.getElementById(locID);
if (null == div) {
document.body.appendChild(canvas);
} else {
div.appendChild(canvas);
}
}
document.body.appendChild(canvas);
return canvas;
}
function InitGL(canvas) {
var gl = WebGLUtils.setupWebGL(canvas);
if (!gl) {
alert ("WebGL isn't available");
}
return gl;
}
function Canvas(width, height, locID) {
var canvas = MakeCanvas(width, height, locID);
this.canvas = canvas;
this.gl = InitGL(canvas);
var gl = this.gl;
// I need to pass this to events, but it will have a different value when
// I actually call it, so save this.
var tmpCanvas = this;
// where am I in relationship to the main window.
this.x = canvas.offsetLeft;
this.y = canvas.offsetTop;
canvas.addEventListener("keypress",
function(evnt) {
tmpCanvas.KeyFunc(tmpCanvas, evnt);
}
);
gl.viewport(0,0, width, height);
program = initShaders(gl, "vertex-shader","fragment-shader");
gl.useProgram(program);
// somewhat new code, two vertex buffers.
this.vBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.vBuffer);
var vPosition = gl.getAttribLocation(program, "vPosition");
gl.vertexAttribPointer(vPosition,2,gl.FLOAT, false,0,0);
gl.enableVertexAttribArray(vPosition);
this.a = 5;
this.b = 1
this.MakePoints();
this.boingState = false;
this.boingPct = 1;
this.boingStep = 0.1;
this.boingLoc = gl.getUniformLocation(program, "boing");
this.contBoing = false;
gl.uniform1f(this.boingLoc, this.boingPct);
this.whirState = false;
this.contWhir = false;
this.whirPct = 0;
this.whirStep = Math.PI/100.0;
this.whirLoc = gl.getUniformLocation(program, "whir");
gl.uniform1f(this.whirLoc, this.whirPct);
this.gl.clearColor(1.0, 1.0, 1.0, 1.0);
return this;
}
Canvas.prototype = {
ChangeA: function(param) {
this.a = parseFloat(param);
this.MakePoints();
},
ChangeB: function(param) {
this.b = parseFloat(param);
this.MakePoints();
},
MakePoints: function(){
var theta;
var k = this.a/this.b;
var R = this.a*2;
var data = [];
i = 0;
for(theta = 0; theta < this.b*2*Math.PI; theta += Math.PI/300) {
var x = ((k-1)*Math.cos(theta) + Math.cos((k-1)*theta))*this.b;
var y = ((k-1)*Math.sin(theta) - Math.sin((k-1)*theta))*this.b;
vertex = [x/R,y/R];
data.push(vertex);
}
this.vertex = data;
var gl = this.gl;
gl.bufferData(gl.ARRAY_BUFFER,flatten(this.vertex),gl.DYNAMIC_DRAW);
return;
},
ChangeBoingAnimation: function() {
if (this.boingPct == 1) {
this.boingState = !this.boingState;
}
if (this.boingState) {
this.timerID = setInterval(this.AutomatedAnimationStep,30,this);
} else {
clearInterval(this.timerID);
}
},
ChangeWhirAnimation: function() {
if (this.whirPct == 0) {
this.whirState = !this.whirState;
}
if (this.whirState) {
this.timerID = setInterval(this.AutomatedAnimationStep,30,this);
} else {
clearInterval(this.timerID);
}
},
AutomatedAnimationStep: function(me) {
if (me.whirState) {
me.WhirStep();
}
if (me.boingState) {
me.BoingStep();
}
me.Redisplay();
},
WhirStep: function() {
this.whirPct += this.whirStep;
if (this.whirPct >= 2*Math.PI) {
this.whirPct = 2*Math.PI;
this.whirStep *= -1;
}
if (this.whirPct <= 0) {
this.whirPct = 0;
this.whirStep *= -1;
}
if (this.whirPct == 0 && !this.contWhir) {
this.ChangeWhirAnimation();
}
this.gl.uniform1f(this.whirLoc, this.whirPct);
},
BoingStep: function() {
this.boingPct += this.boingStep;
if (this.boingPct >= 2) {
this.boingPct = 2;
this.boingStep *= -1;
}
if (this.boingPct <= 1) {
this.boingPct = 1;
this.boingStep *= -1;
}
if (this.boingPct == 1 && !this.contBoing) {
this.ChangeBoingAnimation();
}
this.gl.uniform1f(this.boingLoc, this.boingPct);
},
Redisplay: function() {
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
this.gl.drawArrays(this.gl.LINE_LOOP, 0, this.vertex.length);
return;
},
KeyFunc: function(me, evnt) {
switch(evnt.key) {
case 'b':
this.ChangeBoingAnimation();
break;
case 'B':
this.contBoing = !this.contBoing;
this.ChangeBoingAnimation();
break;
case 'w':
this.ChangeWhirAnimation();
break;
case 'W':
this.contWhir = !this.contWhir;
this.ChangeWhirAnimation();
break;
}
},
};