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;
	}
    },

};