Koch Snowflake

0 1 2 3 4 5 6

The HTML file:

<html>
<head>
    <script src="koch.js" defer></script>
</head>
<body>
    <h1> Koch Snowflake</h1>
    <canvas id="theCanvas" style="border:1px solid black;"></canvas>
    <table>
    <tr>
       <td> <input type = "checkbox" id="0" checked> 0 </input></td>
       <td> <input type = "checkbox" id="1"> 1 </input></td>
       <td> <input type = "checkbox" id="2"> 2 </input></td>
       <td> <input type = "checkbox" id="3"> 3 </input></td>
       <td> <input type = "checkbox" id="4"> 4 </input></td>
       <td> <input type = "checkbox" id="5"> 5 </input></td>
       <td> <input type = "checkbox" id="6"> 6 </input></td>
   </tr>
   <tr> 
       <td colspan= 7> <button id="doIt">DoIt</button> </td>
   </tr>
   </table>
</body>
</html>

koch.js

"use strict"

const canvas = document.getElementById("theCanvas");
canvas.width = 500;
canvas.height = 500;
const ctx = canvas.getContext('2d');

let colors=['black', 'green', 'red', 'blue', 'cyan', 'magenta', 'yellow'];
let buttons = [true, false,   false, false, false, false, false];

function Distance(p1, p2) {
    let dx = p2.x - p1.x;
    let dy = p2.y - p1.y;
    let d = Math.sqrt(dx*dx + dy*dy)
    return d;
}

function FindPerp(p1, p2, dist) {

    // need uint vectors for the perpendicular
    let dx = p2.x - p1.x;
    let dy = p1.y - p2.y;

    // make this line have length 1
    let len = Distance(p1, p2); 
    if (len > 0) {
        dx /= len;
        dy /= len;
    }

    let newPt = {
       x : p2.x +  dy * dist,
       y : p2.y +  dx * dist,
    }

    return  newPt
}


function OnLine(p1, p2, length) {
    let dx = p2.x - p1.x;
    let dy = p2.y - p1.y;
    let newPoint = {x: p1.x + length * dx,
                    y: p1.y + length * dy};

    return newPoint
}

function FindHeight(length) {
    return length * Math.sqrt(3)/2;
}

function DoStep(p1, p2) {
    let rv = []

    let A = OnLine(p1, p2, 1/3);
    let B = OnLine(p1, p2, 2/3);
    let mid = OnLine(p1, p2, 1/2);

    let length = Distance(p1, A);
    let C = FindPerp(p2, mid, FindHeight(length))

    rv.push(A)
    rv.push(C)
    rv.push(B)

    return rv;
}

function DrawShape(shape) {
   let i = 1;

   ctx.beginPath();

   if (shape.length > 0) {
      ctx.moveTo(shape[0].x, shape[0].y);
   }

   while (i < shape.length) {
      ctx.lineTo(shape[i].x, shape[i].y);
      i++;
   }

   if (shape.length > 0) {
      ctx.lineTo(shape[0].x, shape[0].y);
   }
   ctx.stroke();
}

function DrawShapes() {
    for(let i =0; i < buttons.length; ++i) {
         if (buttons[i]) {
              ctx.strokeStyle = colors[i];
              DrawShape(shapes[i]);
         }
    }
}

function Update() {
    ctx.fillStyle = "white";
    ctx.fillRect(0,0, canvas.width, canvas.height);

    for(let i = 0; i < buttons.length; ++i) {
        let box = document.getElementById(i);
        console.dir(box);
        buttons[i] = box.checked;
    }
    DrawShapes();
}

function DoKoch(shape) {
    if (shape.length > 1) {
        let newShape = [];
        let i = 1;
        while (i < shape.length) {
           newShape.push(shape[i-1]);
           let tmpShape = DoStep(shape[i-1],shape[i])
           newShape = newShape.concat(tmpShape);
           i++;
        }

        newShape.push(shape[i-1]);
        newShape = newShape.concat(DoStep(shape[i-1],shape[0]));

        return newShape;
    } else {
        return shape;
    }
}

// all generations of the curve
let shapes = [];
// the current generation.
let shape = [];

// Generate an equlateral triangle
/*
let p1 = {x: -canvas.width / 3,    y: 2 * canvas.height / 3};
let p2 = {x: canvas.width * 4 / 3, y: 2 * canvas.height / 3};

shape.push(p1);
shape.push(p2);

shape = DoKoch(shape).slice(1,4);
*/
/**/
 
// generate a square 

let width = canvas.width;
let height = canvas.height;
let scale = 0.23;

let p1= {x: width * scale,  y: height * scale};
let p2= {x: width * scale,  y: canvas.height*(1-scale)};
let p3= {x:canvas.width *(1 - scale),  y: canvas.height * (1 - scale)};
let p4= {x:canvas.width *(1 - scale),  y: height * scale};

// change to 1 2 3 4 for an inside out square.
shape.push(p1);
shape.push(p2);
shape.push(p3);
shape.push(p4);
/**/

// note this will do a "deep copy"
// ... will serialize the values
// and [] will create a new vector with these values.
// but it is only a first level deep copy.
// which is fine in this case.
shapes.push([...shape]);

for(let i = 1; i < colors.length; ++i){
   shape = DoKoch(shape);
   shapes.push([...shape]);
}

DrawShapes();

let doItButton = document.getElementById("doIt")
doItButton.addEventListener("click",Update);