Skip to content

Instantly share code, notes, and snippets.

@ThreePointSquare
Forked from mcjohnalds/kochsnowflake.html
Created December 27, 2020 00:16
Show Gist options
  • Save ThreePointSquare/2258b56853d306b8b4c2c14648fef6ad to your computer and use it in GitHub Desktop.
Save ThreePointSquare/2258b56853d306b8b4c2c14648fef6ad to your computer and use it in GitHub Desktop.
A Koch snowflake. Uses WebGL for no particular reason. Doesn't use any trig b.c trig is lame.
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec2 aPosition;
void main(void) {
gl_Position = vec4(aPosition, 0.0, 1.0);
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
void main(void) {
gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
}
</script>
<script type="text/javascript">
var SNOWFLAKE_ITERATIONS = 5;
var SNOWFLAKE_SIZE = 1.5;
// How much smaller a triangle's child should be. A traditional Kotch
// snowflake should be 1/3. Change this value to get cool shapes.
var SNOWFLAKE_CHILD_SCALE = 1 / 3;
// Canvas element
var canvas;
// WebGL context
var gl;
// Vertices of the snowflake
var snowflakeVerticies;
// Buffer storing the snowflake's vertices
var snowflakeVertexBuffer;
// Vertex shader attribute
var aPositionAttrib;
function main() {
canvas = document.getElementById("c");
gl = canvas.getContext("webgl");
initSnowflakeVertices();
initShaders();
initBuffers();
drawScene();
}
// Initialize the snowflake's vertices
function initSnowflakeVertices() {
// We have two Koch triangles that make up the snowflake: t1 and t2. t1
// is the top and sides of the snowflake, t2 gives the bottom.
var t1Side = SNOWFLAKE_SIZE;
var t2Side = t1Side * SNOWFLAKE_CHILD_SCALE;
var t1Height = eqTriHeight(t1Side);
var t2Height = eqTriHeight(t2Side);
var snowFlakeHeight = t1Height + t2Height;
var base = vec2(0.0, t2Height - snowFlakeHeight / 2);
var t1Dir = vec2(0.0, 1.0);
var t2Dir = vec2(0.0, -1.0);
var t1 = kochTriangle(base, t1Dir, t1Side, SNOWFLAKE_ITERATIONS);
var t2 = kochTriangle(base, t2Dir, t2Side, SNOWFLAKE_ITERATIONS - 1);
// To clearly see the difference between t1 and t2, you can remove
// the .concat(t2) to hide t2.
snowflakeVerticies = t1.concat(t2);
}
// Initialize the shader program
function initShaders() {
var program = gl.createProgram();
gl.attachShader(program, compileShader("shader-fs"));
gl.attachShader(program, compileShader("shader-vs"));
gl.linkProgram(program);
gl.useProgram(program);
aPositionAttrib = gl.getAttribLocation(program, "aPosition");
gl.enableVertexAttribArray(aPositionAttrib);
}
// Initialize a buffer and put the snowflake's vertices in it
function initBuffers() {
snowflakeVertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, snowflakeVertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(snowflakeVerticies), gl.STATIC_DRAW);
}
function drawScene() {
gl.clearColor(1.0, 1.0, 1.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, snowflakeVertexBuffer);
gl.vertexAttribPointer(aPositionAttrib, 2, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, snowflakeVerticies.length / 2);
}
// Create the vertices of a Koch triangle who's left and right sides have
// Koch triangles sticking out of them (recursive).
//
// base: The position vector of the base of the triangle
// dir: The unit vector direction triangle should point in
// side: The length of one side of the triangle
// iterations: The number of triangle babies to make
//
// Note that the bottom of the Koch triangle doesn't have another triangle
// sticking out, this is so we don't create unecessary triangles.
function kochTriangle(base, dir, side, iterations) {
// tri is the big triangle that has two little triangles sticking out of
// it's sides.
var tri = eqTri(base, dir, side);
var leftVert = vec2(tri[0], tri[1]);
var rightVert = vec2(tri[2], tri[3]);
var topVert = vec2(tri[4], tri[5]);
if (iterations == 1) {
return tri;
} else {
var leftBase = midpoint(leftVert, topVert);
var leftDir = topVert.minus(leftVert).rotate90DegreesCCW().normalize();
var leftTri = kochTriangle(leftBase, leftDir, side * SNOWFLAKE_CHILD_SCALE, iterations - 1);
var rightBase = midpoint(rightVert, topVert);
var rightDir = topVert.minus(rightVert).rotate90DegreesCW().normalize();
var rightTri = kochTriangle(rightBase, rightDir, side * SNOWFLAKE_CHILD_SCALE, iterations - 1);
return tri.concat(leftTri).concat(rightTri);
}
}
// Create the vertices of an equilateral triangle.
//
// base: The position vector of the base of the triangle
// dir: The unit vector direction triangle should point in
// side: The length of one side of the triangle
function eqTri(base, dir, side) {
var height = eqTriHeight(side);
var leftVert = dir.rotate90DegreesCCW().scale(side / 2).plus(base);
var rightVert = dir.rotate90DegreesCW().scale(side / 2).plus(base);
var topVert = dir.scale(height).plus(base);
return [
leftVert.x, leftVert.y,
rightVert.x, rightVert.y,
topVert.x, topVert.y
];
}
// Get the height of an equilateral triangle with a given side length
function eqTriHeight(side) {
return Math.sqrt(3) / 2 * side;
}
// A minimal 2D vector class. Example usage:
//
// x = vec2(1, 2);
// y = x.rotate90DegreesCW();
// z = x.plus(y);
function vec2(x, y) {
var v = {x: x, y: y};
v.plus = function(w) {
return vec2(v.x + w.x, v.y + w.y);
};
v.minus = function(w) {
return vec2(v.x - w.x, v.y - w.y);
};
v.scale = function(a) {
return vec2(v.x * a, v.y * a);
};
v.rotate90DegreesCW = function() {
return vec2(v.y, -v.x);
};
v.rotate90DegreesCCW = function() {
return vec2(-v.y, v.x);
};
v.normalize = function() {
return v.scale(1 / v.length());
};
v.length = function() {
return Math.sqrt(v.x * v.x + v.y * v.y);
};
return v;
}
// Return the midpoint of two vectors
function midpoint(v, w) {
return v.plus(w).scale(1 / 2);
}
// Compile and return the shader in the given element.
function compileShader(id) {
var script = document.getElementById(id);
if (!script) {
return null;
}
var str = "";
var k = script.firstChild;
while (k) {
if (k.nodeType == 3) {
str += k.textContent;
}
k = k.nextSibling;
}
var shader;
if (script.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (script.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
return null;
}
gl.shaderSource(shader, str);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(id, gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
</script>
<body onload="main();">
<canvas id="c" width="500" height="500"></canvas>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment