<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta name="robots" content="noindex">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Fibonacci lattice</title>
<style>
html,
body,
canvas {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<!--
© Adam Murray 2024
https://adammurray.link/
Creative Commons License
Attribution-NonCommercial-ShareAlike 4.0 International
https://creativecommons.org/licenses/by-nc-sa/4.0/
-->
<script type="x-shader/x-vertex">
#version 300 es
uniform float numVertices;
uniform vec2 canvasSize;
const float TWO_PI_OVER_PHI = 6.2831853072 / 1.61803398875;
vec2 polarToCartesian(float radius, float angle) {
return vec2(radius * cos(angle), radius * sin(angle));
}
void main() {
float i = float(gl_VertexID) + 1.; // the point at index 0 looks out of place so start from 1
vec2 point = polarToCartesian(
sqrt(i/numVertices),
i * TWO_PI_OVER_PHI
);
// compensate for aspect ratio:
if (canvasSize.x > canvasSize.y) {
point.x *= canvasSize.y/canvasSize.x;
} else {
point.y *= canvasSize.x/canvasSize.y;
}
gl_PointSize = 3.;
gl_Position = vec4(point, 0, 1);
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
#version 300 es
precision highp float;
out vec4 fragColor;
void main() {
fragColor = vec4(0, 0, 0, 1);
}
</script>
<script>
const canvas = document.querySelector("canvas");
const vertexShader = document.querySelector('script[type="x-shader/x-vertex"]');
const fragmentShader = document.querySelector('script[type="x-shader/x-fragment"]');
const gl = canvas.getContext("webgl2");
if (!gl) {
main.innerHTML =
'<p>Error: WebGL2 is <a href="https://get.webgl.org/webgl2/">not supported by your browser</a></p>';
throw "WebGL2 not supported";
}
function createShader(shaderType, sourceCode) {
const shader = gl.createShader(shaderType);
gl.shaderSource(shader, sourceCode.trim());
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw gl.getShaderInfoLog(shader);
}
return shader;
}
const program = gl.createProgram();
gl.attachShader(program, createShader(gl.VERTEX_SHADER, vertexShader.textContent.trim()));
gl.attachShader(program, createShader(gl.FRAGMENT_SHADER, fragmentShader.textContent.trim()));
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
throw gl.getProgramInfoLog(program);
}
gl.useProgram(program);
const numVertices = 4000;
const numVerticesUniform = gl.getUniformLocation(program, "numVertices");
gl.uniform1f(numVerticesUniform, numVertices);
const canvasSizeUniform = gl.getUniformLocation(program, "canvasSize");
function draw() {
const width = canvas.clientWidth;
const height = canvas.clientHeight;
canvas.width = width;
canvas.height = height;
gl.viewport(0, 0, width, height);
gl.uniform2f(canvasSizeUniform, width, height);
// Since we are determining all vertex positions inside the vertex
// shader, we don't actually need to pass in a list of vertices.
// Instead we tell it how many vertices to draw and rely on the
// vertex index (gl_VertexID) to calculate position.
gl.drawArrays(gl.POINTS, 0, numVertices);
}
window.addEventListener("resize", () => draw());
draw();
</script>
<style>
canvas {
background-color: white;
}
</style>
</body>
</html>