Last active
December 27, 2020 00:16
-
-
Save mcjohnalds/14b8b5b24ad9f6395ab0 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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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