JAVASCRIPT
"use strict";
// CFDG 3D library [instanced geometry version]
// ge1doot - last modified 25 March 2018
// https://codepen.io/ge1doot/pen/rdwPLr/
const cfdg = function() {
// state variables
let camera,
buffers,
geometry,
width = 0,
height = 0,
minSize = 0,
seed = 0,
fov = 60,
camDist = 0,
running = false,
mustResize = true,
offsetY = 0,
rotY = 0,
background;
const stack = [], shapes = [];
// WebGL context
const canvas = document.querySelector("canvas");
const gl =
canvas.getContext("webgl", { alpha: false }) ||
canvas.getContext("experimental-webgl", { alpha: false });
if (!gl) return false;
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
// instanced geometry extension
const ext = gl.getExtension("ANGLE_instanced_arrays");
if (!ext) throw new Error("ANGLE_instanced_arrays not supported");
// init engine
const init = (p) => {
let program;
// hex to rgb
const hex2rgb = hex => {
return hex
.replace(
/^#?([a-f\d])([a-f\d])([a-f\d])$/i,
(m, r, g, b) => "#" + r + r + g + g + b + b
)
.substring(1)
.match(/.{2}/g)
.map(x => parseInt(x, 16) / 256);
};
// Compile Shaders
const compileShaders = (specular) => {
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(
vertexShader, `
precision mediump float;
uniform mat4 camProj, camView;
attribute vec3 aPosition, aNormal, aCol;
attribute vec4 aC0, aC1, aC2, aC3;
varying vec4 vPosition;
varying vec3 vNormal;
varying vec3 vColor;
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main() {
mat4 uMatrix = mat4(aC0, aC1, aC2, aC3);
vPosition = camView * uMatrix * vec4(aPosition, 1.0);
gl_Position = camProj * vPosition;
vNormal = normalize(vec3(camView * uMatrix * vec4(aNormal, 0.0)));
vColor = hsv2rgb(aCol);
}`
);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
let fragment = `
precision mediump float;
uniform vec3 uPointLightingLocation;
uniform vec3 uAmbientColor;
`;
if (specular) fragment += `
uniform vec3 uPointLightingSpecularColor;
uniform float uShininess;
`;
fragment += `
uniform vec3 uPointLightingDiffuseColor;
varying vec4 vPosition;
varying vec3 vColor;
varying vec3 vNormal;
void main() {
vec3 lightDirection = normalize(uPointLightingLocation - vPosition.xyz);
float diffuseLightWeighting = max(dot(vNormal, lightDirection), 0.0);
vec3 lightWeighting = uAmbientColor + uPointLightingDiffuseColor * diffuseLightWeighting;
`;
if (specular) fragment += `
vec3 eyeDirection = normalize(-vPosition.xyz);
vec3 reflectionDirection = reflect(-lightDirection, vNormal);
float specularLightWeighting = pow(max(dot(reflectionDirection, eyeDirection), 0.0), uShininess);
lightWeighting += uPointLightingSpecularColor * specularLightWeighting;
`;
fragment += `
vec3 col = vColor.rgb * lightWeighting;
gl_FragColor = vec4(col, 1.0);
}
`;
gl.shaderSource(fragmentShader, fragment);
gl.compileShader(fragmentShader);
// create program
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
return program;
};
// set view matrix
const Camera = () => {
const camProj = gl.getUniformLocation(program, "camProj");
const mView = new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
const camView = gl.getUniformLocation(program, "camView");
return {
move(rx, ry, y, z) {
mView.set([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, y, z, 1]);
if (rx) transforms.rx(mView, rx);
transforms.ry(mView, ry);
gl.uniformMatrix4fv(camView, false, mView);
},
projection(fov) {
const near = 0.01;
const far = 100;
const top = near * Math.tan(fov * Math.PI / 360);
const right = top * (gl.drawingBufferWidth / gl.drawingBufferHeight);
const left = -right;
const bottom = -top;
gl.uniformMatrix4fv(camProj, false, [
2 * near / (right - left),
0,
0,
0,
0,
2 * near / (top - bottom),
0,
0,
(right + left) / (right - left),
(top + bottom) / (top - bottom),
-(far + near) / (far - near),
-1,
0,
0,
0,
1
]);
}
};
};
// GPU buffers
const Buffers = () => {
let vertices = [], normals = [], vOffset = 0;
let c0 = [], c1 = [], c2 = [], c3 = [], col = [];
const geometry = [];
// buffer data
const bufferData = (buffer, data) => {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
data.length = 0;
};
// init attribute
const attrib = name => {
const buffer = gl.createBuffer();
const index = gl.getAttribLocation(program, name);
gl.enableVertexAttribArray(index);
return [buffer, index];
};
// create GPU static buffers
const [positionBuffer, aPosition] = attrib("aPosition");
const [normalBuffer, aNormal] = attrib("aNormal");
const [c0Buffer, aC0] = attrib("aC0");
const [c1Buffer, aC1] = attrib("aC1");
const [c2Buffer, aC2] = attrib("aC2");
const [c3Buffer, aC3] = attrib("aC3");
const [colBuffer, aCol] = attrib("aCol");
// Mesh class
const Mesh = class {
constructor(v, n) {
this.vNum = v.length / 3;
this.vOffset = vOffset;
this.mOffset = 0;
this.cOffset = 0;
this.iNum = 0;
this.c0 = [];
this.c1 = [];
this.c2 = [];
this.c3 = [];
this.col = [];
vertices = vertices.concat(v);
normals = normals.concat(n ? n : this.normals(v));
vOffset += v.length * Float32Array.BYTES_PER_ELEMENT;
}
normals (v) {
const n = [];
for (let i = 0; i < v.length; i += 9) {
const p1x = v[i + 3] - v[i];
const p1y = v[i + 4] - v[i + 1];
const p1z = v[i + 5] - v[i + 2];
const p2x = v[i + 6] - v[i];
const p2y = v[i + 7] - v[i + 1];
const p2z = v[i + 8] - v[i + 2];
const p3x = p1y * p2z - p1z * p2y;
const p3y = -(p1x * p2z - p1z * p2x);
const p3z = p1x * p2y - p1y * p2x;
const mag = Math.sqrt(p3x * p3x + p3y * p3y + p3z * p3z);
if (mag === 0) {
n.push(0, 0, 0, 0, 0, 0, 0, 0, 0);
} else {
n.push(p3x / mag, p3y / mag, p3z / mag);
n.push(p3x / mag, p3y / mag, p3z / mag);
n.push(p3x / mag, p3y / mag, p3z / mag);
}
}
return n;
}
// reset mesh buffers
reset() {
this.iNum = 0;
this.c0.length = 0;
this.c1.length = 0;
this.c2.length = 0;
this.c3.length = 0;
this.col.length = 0;
}
// push matrix transform
push(m) {
this.iNum++;
this.c0.push(m[0], m[1], m[2], m[3]);
this.c1.push(m[4], m[5], m[6], m[7]);
this.c2.push(m[8], m[9], m[10], m[11]);
this.c3.push(m[12], m[13], m[14], m[15]);
this.col.push(m[16] / 360, m[17], m[18]);
}
bind(buffer, size, index, offset, div) {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(index, size, gl.FLOAT, false, 0, offset);
if (div) ext.vertexAttribDivisorANGLE(index, 1);
}
drawInstance() {
// bind geometry buffers
this.bind(positionBuffer, 3, aPosition, this.vOffset, false);
this.bind(normalBuffer, 3, aNormal, this.vOffset, false);
// bind instanced buffers
this.bind(c0Buffer, 4, aC0, this.mOffset, true);
this.bind(c1Buffer, 4, aC1, this.mOffset, true);
this.bind(c2Buffer, 4, aC2, this.mOffset, true);
this.bind(c3Buffer, 4, aC3, this.mOffset, true);
this.bind(colBuffer, 3, aCol, this.cOffset, true);
// Draw the instanced meshes
ext.drawArraysInstancedANGLE(gl.TRIANGLES, 0, this.vNum, this.iNum);
}
};
// Cube
geometry[0] = new Mesh([
-0.5, -0.5, -0.5,
-0.5, 0.5, -0.5,
0.5, 0.5, -0.5,
0.5, -0.5, -0.5,
-0.5, -0.5, -0.5,
0.5, 0.5, -0.5,
0.5, 0.5, 0.5,
-0.5, 0.5, 0.5,
-0.5, -0.5, 0.5,
0.5, 0.5, 0.5,
-0.5, -0.5, 0.5,
0.5, -0.5, 0.5,
-0.5, 0.5, -0.5,
-0.5, 0.5, 0.5,
0.5, 0.5, 0.5,
0.5, 0.5, -0.5,
-0.5, 0.5, -0.5,
0.5, 0.5, 0.5,
0.5, -0.5, 0.5,
-0.5, -0.5, 0.5,
-0.5, -0.5, -0.5,
0.5, -0.5, 0.5,
-0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
-0.5, -0.5, -0.5,
-0.5, -0.5, 0.5,
-0.5, 0.5, 0.5,
-0.5, 0.5, -0.5,
-0.5, -0.5, -0.5,
-0.5, 0.5, 0.5,
0.5, 0.5, 0.5,
0.5, -0.5, 0.5,
0.5, -0.5, -0.5,
0.5, 0.5, 0.5,
0.5, -0.5, -0.5,
0.5, 0.5, -0.5
]);
// Plane
geometry[1] = new Mesh([
-0.5, 0, -0.5,
-0.5, 0, 0.5,
0.5, 0, 0.5,
0.5, 0, -0.5,
-0.5, 0, -0.5,
0.5, 0, 0.5,
0.5, 0, 0.5,
-0.5, 0, 0.5,
-0.5, 0, -0.5,
0.5, 0, 0.5,
-0.5, 0, -0.5,
0.5, 0, -0.5
]);
// tube
(() => {
const res = 22;
let angle = 0;
const alpha = 2 * Math.PI / res;
const v = [], n = [];
for ( let i = 0; i < res; i++) {
const c0 = Math.cos(angle);
const s0 = Math.sin(angle);
const c1 = Math.cos(angle + alpha);
const s1 = Math.sin(angle + alpha);
v.push(
c0 * 0.5, -0.5, s0 * 0.5,
c0 * 0.5, 0.5, s0 * 0.5,
c1 * 0.5, -0.5, s1 * 0.5,
c0 * 0.5, 0.5, s0 * 0.5,
c1 * 0.5, 0.5, s1 * 0.5,
c1 * 0.5, -0.5, s1 * 0.5
);
n.push(
c0, 1, s0,
c0, 1, s0,
c1, 1, s1,
c0, 1, s0,
c1, 1, s1,
c1, 1, s1
);
angle += alpha;
}
geometry[2] = new Mesh(v, n);
})();
// cylinder
(() => {
const res = 22;
let angle = 0;
const alpha = 2 * Math.PI / res;
const v = [], n = [];
for ( let i = 0; i < res; i++) {
const c0 = Math.cos(angle);
const s0 = Math.sin(angle);
const c1 = Math.cos(angle + alpha);
const s1 = Math.sin(angle + alpha);
v.push(
c0 * 0.5, -0.5, s0 * 0.5,
c0 * 0.5, 0.5, s0 * 0.5,
c1 * 0.5, -0.5, s1 * 0.5,
c0 * 0.5, 0.5, s0 * 0.5,
c1 * 0.5, 0.5, s1 * 0.5,
c1 * 0.5, -0.5, s1 * 0.5,
c1 * 0.5, -0.5, s1 * 0.5,
0, -0.5, 0,
c0 * 0.5, -0.5, s0 * 0.5,
c0 * 0.5, 0.5, s0 * 0.5,
0, -0.5, 0,
c1 * 0.5, 0.5, s1 * 0.5
);
n.push(
c0, 1, s0,
c0, 1, s0,
c1, 1, s1,
c0, 1, s0,
c1, 1, s1,
c1, 1, s1,
0, -1, 0,
0, -1, 0,
0, -1, 0,
0, 1, 0,
0, 1, 0,
0, 1, 0
);
angle += alpha;
}
geometry[3] = new Mesh(v, n);
})();
// sphere
(() => {
const res = 22;
const nx = [], ny = [], nz = [], v = [], n = [];
for (let i = 0; i <= res; i++) {
const theta = i * Math.PI / res;
const sinTheta = Math.sin(theta);
const cosTheta = Math.cos(theta);
for (let j = 0; j <= res; j++) {
const phi = -j * 2 * Math.PI / res;
nx.push(Math.cos(phi) * sinTheta)
ny.push(cosTheta);
nz.push(Math.sin(phi) * sinTheta);
}
}
for (let i = 0; i < res; i++) {
for (let j = 0; j < res; j++) {
const first = (i * (res + 1)) + j;
const second = first + res + 1;
v.push(
nx[first] * 0.5,
ny[first] * 0.5,
nz[first] * 0.5,
nx[second] * 0.5,
ny[second] * 0.5,
nz[second] * 0.5,
nx[first + 1] * 0.5,
ny[first + 1] * 0.5,
nz[first + 1] * 0.5,
nx[second] * 0.5,
ny[second] * 0.5,
nz[second] * 0.5,
nx[second + 1] * 0.5,
ny[second + 1] * 0.5,
nz[second + 1] * 0.5,
nx[first + 1] * 0.5,
ny[first + 1] * 0.5,
nz[first + 1] * 0.5
);
n.push(
nx[first],
ny[first],
nz[first],
nx[second],
ny[second],
nz[second],
nx[first + 1],
ny[first + 1],
nz[first + 1],
nx[second],
ny[second],
nz[second],
nx[second + 1],
ny[second + 1],
nz[second + 1],
nx[first + 1],
ny[first + 1],
nz[first + 1]
);
}
}
geometry[4] = new Mesh(v, n);
})();
// Pyramid
geometry[5] = new Mesh([
-0.5, -0.5, -0.5,
0, 0.5, 0,
0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
0, 0.5, 0,
0.5, -0.5, 0.5,
0.5, -0.5, 0.5,
0, 0.5, 0,
-0.5, -0.5, 0.5,
-0.5, -0.5, 0.5,
0, 0.5, 0,
-0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
-0.5, -0.5, 0.5,
-0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
0.5, -0.5, 0.5,
-0.5, -0.5, 0.5
]);
// load meshes into GPU
bufferData(positionBuffer, vertices);
bufferData(normalBuffer, normals);
// Interface
return {
geometry: geometry,
reset() {
c0.length = 0;
c1.length = 0;
c2.length = 0;
c3.length = 0;
col.length = 0;
for (const mesh of geometry) mesh.reset();
},
loadInstances() {
let mOffset = 0, cOffset = 0;
for (const mesh of geometry) {
if (mesh.iNum) {
// merge buffers
c0 = c0.concat(mesh.c0);
c1 = c1.concat(mesh.c1);
c2 = c2.concat(mesh.c2);
c3 = c3.concat(mesh.c3);
col = col.concat(mesh.col);
// free up memory
mesh.c0.length = 0;
mesh.c1.length = 0;
mesh.c2.length = 0;
mesh.c3.length = 0;
mesh.col.length = 0;
// compute offsets
mesh.mOffset = mOffset;
mesh.cOffset = cOffset;
mOffset += (4 * mesh.iNum * Float32Array.BYTES_PER_ELEMENT);
cOffset += (3 * mesh.iNum * Float32Array.BYTES_PER_ELEMENT);
}
}
// load buffers into GPU
bufferData(c0Buffer, c0);
bufferData(c1Buffer, c1);
bufferData(c2Buffer, c2);
bufferData(c3Buffer, c3);
bufferData(colBuffer, col);
},
// draw the scene
render() {
for (const mesh of geometry) {
if (mesh.iNum) {
mesh.drawInstance();
}
}
}
};
};
// setup
minSize = (p.minSize || 0.1) / 100;
offsetY = p.offsetY || 0.0;
fov = p.fov || 60;
rotY = p.rotY === undefined ? -0.15 : p.rotY;
camDist = p.camDist === undefined ? -10 : -p.camDist;
const specular = p.specularColor ? true : false;
// compile shaders
if (!program) {
program = compileShaders(specular);
camera = Camera();
buffers = Buffers();
geometry = buffers.geometry;
}
// init lightning
const light = (name, p) => {
const x = p ? p[0] : 0;
const y = p ? p[1] : 0;
const z = p ? p[2] : 0;
gl.uniform3f(gl.getUniformLocation(program, name), x, y, z);
};
light("uPointLightingLocation", p.lightPosition);
light("uAmbientColor", p.ambientColor);
light("uPointLightingDiffuseColor", p.diffuseColor);
if (specular) {
light("uPointLightingSpecularColor", p.specularColor);
gl.uniform1f(
gl.getUniformLocation(program, "uShininess"),
p.shininess ? p.shininess : 6.0
);
}
background = hex2rgb(p.background || "#000");
if (p.seed) seed = p.seed;
else seed = Math.round(Math.random() * 1000000);
};
// clear screen
const clearScreen = () => {
if (mustResize) resize();
gl.clearColor(background[0], background[1], background[2], 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
};
// perspective view
const resize = () => {
width = canvas.width = canvas.offsetWidth;
height = canvas.height = canvas.offsetHeight;
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
camera.projection(fov);
};
// resize windows event
window.addEventListener("resize", () => {
mustResize = true;
});
// Context Free adjustments (3D)
const transforms = {
x(m, v) {
m[12] += m[0] * v;
m[13] += m[1] * v;
m[14] += m[2] * v;
m[15] += m[3] * v;
},
y(m, v) {
m[12] += m[4] * v;
m[13] += m[5] * v;
m[14] += m[6] * v;
m[15] += m[7] * v;
},
z(m, v) {
m[12] += m[8] * v;
m[13] += m[9] * v;
m[14] += m[10] * v;
m[15] += m[11] * v;
},
s(m, v) {
const a = Array.isArray(v);
const x = a ? v[0] : v;
const y = a ? v[1] : x;
const z = a ? v[2] : x;
m[0] *= x;
m[1] *= x;
m[2] *= x;
m[3] *= x;
m[4] *= y;
m[5] *= y;
m[6] *= y;
m[7] *= y;
m[8] *= z;
m[9] *= z;
m[10] *= z;
m[11] *= z;
},
rx(m, v) {
const rad = Math.PI * (v / 180);
const s = Math.sin(rad);
const c = Math.cos(rad);
const a10 = m[4];
const a11 = m[5];
const a12 = m[6];
const a13 = m[7];
const a20 = m[8];
const a21 = m[9];
const a22 = m[10];
const a23 = m[11];
m[4] = a10 * c + a20 * s;
m[5] = a11 * c + a21 * s;
m[6] = a12 * c + a22 * s;
m[7] = a13 * c + a23 * s;
m[8] = a10 * -s + a20 * c;
m[9] = a11 * -s + a21 * c;
m[10] = a12 * -s + a22 * c;
m[11] = a13 * -s + a23 * c;
},
ry(m, v) {
const rad = Math.PI * (v / 180);
const s = Math.sin(rad);
const c = Math.cos(rad);
const a00 = m[0];
const a01 = m[1];
const a02 = m[2];
const a03 = m[3];
const a20 = m[8];
const a21 = m[9];
const a22 = m[10];
const a23 = m[11];
m[0] = a00 * c + a20 * -s;
m[1] = a01 * c + a21 * -s;
m[2] = a02 * c + a22 * -s;
m[3] = a03 * c + a23 * -s;
m[8] = a00 * s + a20 * c;
m[9] = a01 * s + a21 * c;
m[10] = a02 * s + a22 * c;
m[11] = a03 * s + a23 * c;
},
rz(m, v) {
const rad = Math.PI * (v / 180);
const s = Math.sin(rad);
const c = Math.cos(rad);
const a00 = m[0];
const a01 = m[1];
const a02 = m[2];
const a03 = m[3];
const a10 = m[4];
const a11 = m[5];
const a12 = m[6];
const a13 = m[7];
m[0] = a00 * c + a10 * s;
m[1] = a01 * c + a11 * s;
m[2] = a02 * c + a12 * s;
m[3] = a03 * c + a13 * s;
m[4] = a00 * -s + a10 * c;
m[5] = a01 * -s + a11 * c;
m[6] = a02 * -s + a12 * c;
m[7] = a03 * -s + a13 * c;
},
h(m, v) {
m[16] += v;
m[16] %= 360;
},
sat(m, v) {
this.col(m, v, 17);
},
b(m, v) {
this.col(m, v, 18);
},
col(m, v, p) {
if (v > 0) {
m[p] += v * (1 - m[p]);
} else {
m[p] += v * m[p];
}
}
};
transforms.hue = transforms.h;
transforms.scale = transforms.s;
// execute context free adjustments
const transform = (s, p, mintest) => {
let m = copy(s);
for (const c in p) {
if (transforms[c]) transforms[c](m, p[c]);
else console.log("error: " + c);
}
if (mintest) {
const x = m[0] * m[0] + m[1] * m[1] + m[2] * m[2];
const y = m[4] * m[4] + m[5] * m[5] + m[6] * m[6];
const z = m[8] * m[8] + m[9] * m[9] + m[10] * m[10];
return x < minSize || y < minSize || z < minSize ? false : m;
}
return m;
};
// execute loop
const loop = (n, s, t, f) => {
let ls = copy(s);
for (let i = 0; i < n; i++) {
f(ls, i);
ls = transform(ls, t, true);
}
};
// random seedable function
const random = _ => {
seed = (seed * 16807) % 2147483647;
return (seed - 1) / 2147483646;
};
// random integers
const randint = (s, e = 0) => {
if (e === 0) {
e = s;
s = 0;
}
return Math.floor(s + random() * (e - s + 1));
};
// return random position in array
const randpos = a => {
return a[Math.floor(random() * a.length)];
};
// copy state
const copy = s => {
return [
s[0], // a00 1
s[1], // a01 0
s[2], // a02 0
s[3], // a03 0
s[4], // a10 0
s[5], // a11 1
s[6], // a12 0
s[7], // a13 0
s[8], // a20 0
s[9], // a21 0
s[10], // a22 1
s[11], // a23 0
s[12], // a30 0
s[13], // a31 0
s[14], // a32 0
s[15], // a33 1
s[16], // hue
s[17], // saturation
s[18], // brillance
s[19] // function
];
};
// return execute function for single rule
const singlerule = i => {
return (s, t) => {
s = transform(s, t, true);
if (!s) return;
s[19] = i;
stack.push(s);
};
};
// return execute function for multiple rules
const randomrule = (totalWeight, weight, index, len) => {
return (s, t) => {
s = transform(s, t, true);
if (!s) return;
let w = 0;
const r = random() * totalWeight;
for (let i = 0; i < len; i++) {
w += weight[i];
if (r <= w) {
s[19] = index[i];
stack.push(s);
return;
}
}
};
};
// compile JS shapes functions
const compileshapes = () => {
shapes.length = 0;
this.code = this.rules();
for (const namerule in this.code) {
const rules = this.code[namerule];
if (Array.isArray(rules)) {
let totalWeight = 0;
const weight = [];
const index = [];
for (let i = 0; i < rules.length; i += 2) {
totalWeight += rules[i];
shapes.push(rules[i + 1]);
weight.push(rules[i]);
index.push(shapes.length - 1);
}
this[namerule] = randomrule(totalWeight, weight, index, index.length);
} else {
shapes.push(rules);
this[namerule] = singlerule(shapes.length - 1);
}
}
};
// flatten out recursive JS calls
const runshapes = (start, t, p) => {
let minComplexity = p.minComplexity || 100;
let comp = 0;
do {
comp = 0;
stack.length = 0;
this[start]([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0], t);
buffers.reset();
do {
const s = stack.shift();
shapes[s[19]](s);
comp++;
} while (stack.length);
} while (comp < minComplexity--);
// load GPU buffers
buffers.loadInstances();
};
// animation loop
let ry = 0;
const run = () => {
clearScreen();
ry += rotY;
camera.move(10, ry, offsetY, camDist);
buffers.render();
requestAnimationFrame(run);
};
// start first shape
const render = p => {
if (!running) init(p);
// run CFDG
if (!this.code) compileshapes();
runshapes(p.startShape, p.transform || {}, p);
// start animation loop
if (!running) {
running = true;
run();
}
};
// push matrices
const pushGeometry = (s, t, p) => {
s = transform(s, t, false);
geometry[p].push(s);
};
// primitives
this.CUBE = (s, t) => pushGeometry(s, t, 0);
this.PLANE = (s, t) => pushGeometry(s, t, 1);
this.TUBE = (s, t) => pushGeometry(s, t, 2);
this.CYLINDER = (s, t) => pushGeometry(s, t, 3);
this.SPHERE = (s, t) => pushGeometry(s, t, 4);
this.PYRAMID = (s, t) => pushGeometry(s, t, 5);
// public methods
this.random = random;
this.randint = randint;
this.randpos = randpos;
this.render = render;
this.transform = transform;
this.loop = loop;
};