Skip to content

Instantly share code, notes, and snippets.

@TheGreatRambler
Created June 3, 2022 23:48
Show Gist options
  • Save TheGreatRambler/8743173a0a010299b880d93fde83a1f5 to your computer and use it in GitHub Desktop.
Save TheGreatRambler/8743173a0a010299b880d93fde83a1f5 to your computer and use it in GitHub Desktop.
Cleanup of code from jaxry/colorful-life to run on your own website, including typescript implementation
(function() {
var canvas = document.getElementById("canvas");
var gl = canvas.getContext("webgl");
if(!gl) {
alert('Could not load WebGL');
}
class Renderer {
constructor(canvas, gl, renderWidth, renderHeight) {
this.canvas = canvas;
this.gl = gl;
this.renderWidth = renderWidth;
this.renderHeight = renderHeight;
this.displayWidth = canvas.clientWidth;
this.displayHeight = canvas.clientHeight;
this.top = 1;
this.right = 1;
this.bottom = 0;
this.left = 0;
this.correctDimensions();
this.paintSaturation = 0.2;
this.paintColor = {
h: Math.random(),
s: this.paintSaturation,
v: 1,
};
this.paintColorDecay = 0.4;
this.cellStates = 1;
this.mouseX = 0;
this.mouseY = 0;
this.brushSize = 0.00064;
this.brushErase = false;
this.brushSolid = false;
this.brushPixel = false;
this.vertex_shader = `
attribute vec2 a_position;
varying vec2 v_texCoord;
void main() {
vec2 clipSpace = a_position * 2.0 - 1.0;
gl_Position = vec4(clipSpace, 0, 1);
v_texCoord = a_position;
}
`;
this.screen_shader = `
precision mediump float;
uniform sampler2D u_screenBuffer;
uniform vec4 u_surface; //top, right, bottom left
varying vec2 v_texCoord;
// http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
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() {
vec2 uv = u_surface.wz + v_texCoord * (u_surface.yx - u_surface.wz);
vec3 hsv = texture2D(u_screenBuffer, fract(uv)).rgb;
gl_FragColor = vec4(hsv2rgb(hsv), hsv.z);
}
`;
this.cell_life_shader = `
precision mediump float;
uniform vec2 u_bufferResolution;
uniform vec4 u_surface;
uniform float u_colorDecay;
uniform sampler2D u_buffer;
uniform sampler2D u_rules; // a 9x1 texture containing alive states in the red channel and dead states in the green channel
varying vec2 v_texCoord;
#define TWO_PI 6.28318530718
#define RULES_COUNT 8.0
#define DECAY vec4(0, 0, 0.002, 1)
vec4 firstDecay = vec4(0, -1.0, u_colorDecay, 1.0);
vec2 onePixel = vec2(1.0, 1.0) / u_bufferResolution;
vec4 isAlive(int x, int y) {
vec4 pixel = texture2D(u_buffer, fract(v_texCoord + vec2(x, y) * onePixel));
// if cell is alive, calculate Cartesian coordinates of cell's hue for use in circular averaging.
return pixel.a > 0.0 ?
vec4(cos(TWO_PI * pixel.r), sin(TWO_PI * pixel.r), pixel.g, 1.0) :
vec4(0);
}
void main() {
vec4 n = isAlive(0, 1)
+ isAlive(1, 1)
+ isAlive(1, 0)
+ isAlive(1, -1)
+ isAlive(0, -1)
+ isAlive(-1, -1)
+ isAlive(-1, 0)
+ isAlive(-1, 1);
// alive
if (texture2D(u_buffer, fract(v_texCoord)).a == 1.0) {
float state = texture2D(u_rules, vec2(n.a / RULES_COUNT, 0)).r;
gl_FragColor = texture2D(u_buffer, v_texCoord) - (1.0 - state) * firstDecay;
}
// dead
else {
float state = texture2D(u_rules, vec2(n.a / RULES_COUNT, 0) ).g;
gl_FragColor = state > 0.0 ?
vec4(fract(1.0 + atan(n.y, n.x) / TWO_PI), n.b / n.a, 1, 1) : // circular average of colors
texture2D(u_buffer, v_texCoord) - DECAY;
}
}
`;
this.cell_generations_shader = `
precision mediump float;
uniform vec2 u_bufferResolution;
uniform vec4 u_surface;
uniform float u_colorDecay;
uniform int u_cellStates;
uniform sampler2D u_buffer;
uniform sampler2D u_rules;
varying vec2 v_texCoord;
#define TWO_PI 6.28318530718
#define RULES_COUNT 8.0
float singleState = 1.0 / float(u_cellStates - 1);
vec4 firstDecay = vec4(0, -1.0, u_colorDecay, singleState);
vec4 decay = vec4(0, 0, 0.002, singleState);
vec2 onePixel = vec2(1.0, 1.0) / u_bufferResolution;
vec4 isAlive(int x, int y) {
vec4 pixel = texture2D(u_buffer, fract(v_texCoord + vec2(x, y) * onePixel));
return pixel.a == 1.0 ?
vec4(cos(TWO_PI * pixel.r), sin(TWO_PI * pixel.r), pixel.g, 1.0) :
vec4(0, 0, 0, 0);
}
void main() {
vec4 n = isAlive(0, 1)
+ isAlive(1, 1)
+ isAlive(1, 0)
+ isAlive(1, -1)
+ isAlive(0, -1)
+ isAlive(-1, -1)
+ isAlive(-1, 0)
+ isAlive(-1, 1);
float currentCell = texture2D(u_buffer, fract(v_texCoord)).a;
// alive
if (currentCell == 1.0) {
float state = texture2D(u_rules, vec2(n.a / RULES_COUNT, 0)).r;
gl_FragColor = texture2D(u_buffer, v_texCoord) - (1.0 - state) * firstDecay;
}
// dead
else {
float state = (1.0 - ceil(currentCell)) * texture2D(u_rules, vec2(n.a / RULES_COUNT, 0) ).g;
gl_FragColor = state == 1.0 ?
vec4(fract(1.0 + atan(n.y, n.x) / TWO_PI), n.b / n.a, 1, 1) :
texture2D(u_buffer, v_texCoord) - decay;
}
}
`;
this.mouse_shader = `
precision mediump float;
uniform vec2 u_bufferResolution;
uniform vec2 u_mouse;
uniform vec4 u_color;
uniform float u_brushSize;
uniform bool u_brushErase;
uniform bool u_brushSolid;
uniform bool u_brushPixel;
uniform float u_colorDecay;
uniform float u_random;
uniform vec4 u_surface;
uniform sampler2D u_buffer;
varying vec2 v_texCoord;
vec4 firstDecay = vec4(0, -1.0, u_colorDecay, 1.0);
float rand(vec2 co) {
return fract(sin(dot(fract(co.xy + u_random), vec2(12.9898,78.233))) * 43758.5453);
}
void main() {
vec2 mouseCoord = fract(u_surface.wz + u_mouse * (u_surface.yx - u_surface.wz));
vec2 distance = abs(mouseCoord - v_texCoord);
if (u_brushPixel) {
distance *= u_bufferResolution;
if ( dot(distance, distance) < 0.25 )
gl_FragColor = u_brushErase ? vec4(0) : u_color;
else
gl_FragColor = texture2D(u_buffer, v_texCoord);
}
else {
distance = min(distance, 1. - distance);
distance.x *= u_bufferResolution.x / u_bufferResolution.y;
if ( dot(distance, distance) < u_brushSize ) {
gl_FragColor = u_brushErase ?
vec4(0.06, 0.06, 0.06, 0) :
u_color - float(!u_brushSolid) * step(0.5, rand(gl_FragCoord.xy / u_bufferResolution)) * firstDecay;
} else {
gl_FragColor = texture2D(u_buffer, v_texCoord);
}
}
}
`;
this.possibleRules = [
{ name: "Dry Life", alive: [0, 0, 1, 1, 0, 0, 0, 0, 0], dead: [0, 0, 0, 1, 0, 0, 0, 1, 0], type: 0 },
{ name: "2x2", alive: [0, 1, 1, 0, 0, 1, 0, 0, 0], dead: [0, 0, 0, 1, 0, 0, 1, 0, 0], type: 0 },
{ name: "34 Life", alive: [0, 0, 0, 1, 1, 0, 0, 0, 0], dead: [0, 0, 0, 1, 1, 0, 0, 0, 0], type: 0 },
{ name: "Amoeba", alive: [0, 1, 0, 1, 0, 1, 0, 0, 1], dead: [0, 0, 0, 1, 0, 1, 0, 1, 0], type: 0 },
{ name: "Assimilation", alive: [0, 0, 0, 0, 1, 1, 1, 1, 0], dead: [0, 0, 0, 1, 1, 1, 0, 0, 0], type: 0 },
{ name: "Coagulations", alive: [0, 0, 1, 1, 0, 1, 1, 1, 1], dead: [0, 0, 0, 1, 0, 0, 0, 1, 1], type: 0 },
{ name: "Conway's Life", alive: [0, 0, 1, 1, 0, 0, 0, 0, 0], dead: [0, 0, 0, 1, 0, 0, 0, 0, 0], type: 0 },
{ name: "Coral", alive: [0, 0, 0, 0, 1, 1, 1, 1, 1], dead: [0, 0, 0, 1, 0, 0, 0, 0, 0], type: 0 },
{ name: "Day & Night", alive: [0, 0, 0, 1, 1, 0, 1, 1, 1], dead: [0, 0, 0, 1, 0, 0, 1, 1, 1], type: 0 },
{ name: "Diamoeba", alive: [0, 0, 0, 0, 0, 1, 1, 1, 1], dead: [0, 0, 0, 1, 0, 1, 1, 1, 1], type: 0 },
{ name: "Dot Life", alive: [1, 0, 1, 1, 0, 0, 0, 0, 0], dead: [0, 0, 0, 1, 0, 0, 0, 0, 0], type: 0 },
{ name: "Flakes", alive: [1, 1, 1, 1, 1, 1, 1, 1, 1], dead: [0, 0, 0, 1, 0, 0, 0, 0, 0], type: 0 },
{ name: "Fredkin", alive: [1, 0, 1, 0, 1, 0, 1, 0, 1], dead: [0, 1, 0, 1, 0, 1, 0, 1, 0], type: 0 },
{ name: "Gnarl", alive: [0, 1, 0, 0, 0, 0, 0, 0, 0], dead: [0, 1, 0, 0, 0, 0, 0, 0, 0], type: 0 },
{ name: "High Life", alive: [0, 0, 1, 1, 0, 0, 0, 0, 0], dead: [0, 0, 0, 1, 0, 0, 1, 0, 0], type: 0 },
{
name: "Live Free or Die",
alive: [1, 0, 0, 0, 0, 0, 0, 0, 0],
dead: [0, 0, 1, 0, 0, 0, 0, 0, 0],
type: 0
},
{ name: "Long life", alive: [0, 0, 0, 0, 0, 1, 0, 0, 0], dead: [0, 0, 0, 1, 1, 1, 0, 0, 0], type: 0 },
{ name: "Maze", alive: [0, 1, 1, 1, 1, 1, 0, 0, 0], dead: [0, 0, 0, 1, 0, 0, 0, 0, 0], type: 0 },
{ name: "Mazectric", alive: [0, 1, 1, 1, 1, 0, 0, 0, 0], dead: [0, 0, 0, 1, 0, 0, 0, 0, 0], type: 0 },
{ name: "Move", alive: [0, 0, 1, 0, 1, 1, 0, 0, 0], dead: [0, 0, 0, 1, 0, 0, 1, 0, 1], type: 0 },
{ name: "Pseudo life", alive: [0, 0, 1, 1, 0, 0, 0, 0, 1], dead: [0, 0, 0, 1, 0, 1, 0, 1, 0], type: 0 },
{ name: "Replicator", alive: [0, 1, 0, 1, 0, 1, 0, 1, 0], dead: [0, 1, 0, 1, 0, 1, 0, 1, 0], type: 0 },
{ name: "Seeds", alive: [0, 0, 0, 0, 0, 0, 0, 0, 0], dead: [0, 0, 1, 0, 0, 0, 0, 0, 0], type: 0 },
{ name: "Serviettes", alive: [0, 0, 0, 0, 0, 0, 0, 0, 0], dead: [0, 0, 1, 1, 1, 0, 0, 0, 0], type: 0 },
{ name: "Stains", alive: [0, 0, 1, 1, 0, 1, 1, 1, 1], dead: [0, 0, 0, 1, 0, 0, 1, 1, 1], type: 0 },
{ name: "Vote", alive: [0, 0, 0, 0, 1, 1, 1, 1, 1], dead: [0, 0, 0, 0, 0, 1, 1, 1, 1], type: 0 },
{ name: "Vote 4/5", alive: [0, 0, 0, 0, 1, 1, 1, 1, 1], dead: [0, 0, 0, 0, 0, 1, 1, 1, 1], type: 0 },
{ name: "Walled Cities", alive: [0, 0, 1, 1, 1, 1, 0, 0, 0], dead: [0, 0, 0, 0, 1, 1, 1, 1, 1], type: 0 },
{
name: "Banners",
alive: [0, 0, 1, 1, 0, 0, 1, 1, 0],
dead: [0, 0, 0, 1, 1, 1, 0, 1, 0],
cellStates: 5,
type: 1
},
{
name: "BelZhab",
alive: [0, 0, 1, 1, 0, 0, 0, 0, 0],
dead: [0, 0, 1, 1, 0, 0, 0, 0, 0],
cellStates: 8,
type: 1
},
{
name: "BelZhab Sediment",
alive: [0, 1, 0, 0, 1, 1, 1, 1, 1],
dead: [0, 0, 1, 1, 0, 0, 0, 0, 0],
cellStates: 8,
type: 1
},
{
name: "Bloomerang",
alive: [0, 0, 1, 1, 1, 0, 0, 0, 0],
dead: [0, 0, 0, 1, 1, 0, 1, 1, 1],
cellStates: 24,
type: 1
},
{
name: "Bombers",
alive: [0, 0, 0, 1, 1, 1, 0, 0, 0],
dead: [0, 0, 1, 0, 1, 0, 0, 0, 0],
cellStates: 25,
type: 1
},
{
name: "Brain 6",
alive: [0, 0, 0, 0, 0, 0, 1, 0, 0],
dead: [0, 0, 1, 0, 1, 0, 1, 0, 0],
cellStates: 3,
type: 1
},
{
name: "Brian's Brain",
alive: [0, 0, 0, 0, 0, 0, 0, 0, 0],
dead: [0, 0, 1, 0, 0, 0, 0, 0, 0],
cellStates: 3,
type: 1
},
{
name: "Burst",
alive: [1, 0, 1, 1, 0, 1, 1, 1, 1],
dead: [0, 0, 0, 1, 1, 0, 1, 0, 1],
cellStates: 9,
type: 1
},
{
name: "Burst II",
alive: [0, 0, 1, 1, 0, 1, 1, 1, 1],
dead: [0, 0, 0, 1, 1, 0, 1, 0, 1],
cellStates: 9,
type: 1
},
{
name: "Caterpillars",
alive: [0, 1, 1, 0, 1, 1, 1, 1, 0],
dead: [0, 0, 0, 1, 0, 0, 0, 1, 1],
cellStates: 4,
type: 1
},
{
name: "Chenille",
alive: [1, 0, 0, 0, 0, 1, 1, 1, 1],
dead: [0, 0, 1, 0, 1, 1, 1, 1, 0],
cellStates: 6,
type: 1
},
{
name: "Circuit Genesis",
alive: [0, 0, 1, 1, 1, 1, 0, 0, 0],
dead: [0, 1, 1, 1, 1, 0, 0, 0, 0],
cellStates: 8,
type: 1
},
{
name: "Cooties",
alive: [0, 0, 1, 1, 0, 0, 0, 0, 0],
dead: [0, 0, 1, 0, 0, 0, 0, 0, 0],
cellStates: 8,
type: 1
},
{
name: "Ebb&Flow",
alive: [1, 1, 1, 0, 1, 0, 0, 1, 1],
dead: [0, 0, 0, 1, 0, 0, 1, 0, 0],
cellStates: 18,
type: 1
},
{
name: "Ebb&Flow II",
alive: [1, 1, 1, 0, 1, 0, 1, 0, 1],
dead: [0, 0, 0, 1, 0, 0, 0, 1, 0],
cellStates: 18,
type: 1
},
{
name: "Faders",
alive: [0, 0, 1, 0, 0, 0, 0, 0, 0],
dead: [0, 0, 1, 0, 0, 0, 0, 0, 0],
cellStates: 25,
type: 1
},
{
name: "Fireworks",
alive: [0, 0, 1, 0, 0, 0, 0, 0, 0],
dead: [0, 1, 0, 1, 0, 0, 0, 0, 0],
cellStates: 21,
type: 1
},
{
name: "Flaming Starbows",
alive: [0, 0, 0, 1, 1, 0, 0, 1, 0],
dead: [0, 0, 1, 1, 0, 0, 0, 0, 0],
cellStates: 8,
type: 1
},
{
name: "Frogs",
alive: [0, 1, 1, 0, 0, 0, 0, 0, 0],
dead: [0, 0, 0, 1, 1, 0, 0, 0, 0],
cellStates: 3,
type: 1
},
{
name: "Frozen spirals",
alive: [0, 0, 0, 1, 0, 1, 1, 0, 0],
dead: [0, 0, 1, 1, 0, 0, 0, 0, 0],
cellStates: 6,
type: 1
},
{
name: "Glisserati",
alive: [1, 0, 0, 1, 0, 1, 1, 1, 1],
dead: [0, 0, 1, 0, 1, 1, 1, 1, 1],
cellStates: 7,
type: 1
},
{
name: "Glissergy",
alive: [1, 0, 0, 1, 0, 1, 1, 1, 1],
dead: [0, 0, 1, 0, 1, 1, 1, 1, 1],
cellStates: 5,
type: 1
},
{
name: "Lava",
alive: [0, 1, 1, 1, 1, 1, 0, 0, 0],
dead: [0, 0, 0, 0, 1, 1, 1, 1, 1],
cellStates: 8,
type: 1
},
{
name: "Lines",
alive: [1, 1, 1, 1, 1, 1, 0, 0, 0],
dead: [0, 0, 0, 0, 1, 1, 0, 0, 1],
cellStates: 3,
type: 1
},
{
name: "Living On The Edge",
alive: [0, 0, 0, 1, 1, 1, 0, 0, 0],
dead: [0, 0, 0, 1, 0, 0, 0, 0, 0],
cellStates: 6,
type: 1
},
{
name: "Meteor Guns",
alive: [1, 1, 1, 0, 1, 1, 1, 1, 1],
dead: [0, 0, 0, 1, 0, 0, 0, 0, 0],
cellStates: 8,
type: 1
},
{
name: "Nova",
alive: [0, 0, 0, 0, 1, 1, 1, 1, 1],
dead: [0, 0, 1, 0, 1, 0, 0, 1, 1],
cellStates: 25,
type: 1
},
{
name: "OrthoGo",
alive: [0, 0, 0, 1, 0, 0, 0, 0, 0],
dead: [0, 0, 1, 0, 0, 0, 0, 0, 0],
cellStates: 4,
type: 1
},
{
name: "Prairie on fire",
alive: [0, 0, 0, 1, 1, 1, 0, 0, 0],
dead: [0, 0, 0, 1, 1, 0, 0, 0, 0],
cellStates: 6,
type: 1
},
{
name: "RainZha",
alive: [0, 0, 1, 0, 0, 0, 0, 0, 0],
dead: [0, 0, 1, 1, 0, 0, 0, 0, 0],
cellStates: 8,
type: 1
},
{
name: "Rake",
alive: [0, 0, 0, 1, 1, 0, 1, 1, 0],
dead: [0, 0, 1, 0, 0, 0, 1, 1, 1],
cellStates: 6,
type: 1
},
{
name: "SediMental",
alive: [0, 0, 0, 0, 1, 1, 1, 1, 1],
dead: [0, 0, 1, 0, 0, 1, 1, 1, 1],
cellStates: 4,
type: 1
},
{
name: "Snake",
alive: [1, 0, 0, 1, 1, 0, 1, 1, 0],
dead: [0, 0, 1, 0, 0, 1, 0, 0, 0],
cellStates: 6,
type: 1
},
{
name: "SoftFreeze",
alive: [0, 1, 0, 1, 1, 1, 0, 0, 1],
dead: [0, 0, 0, 1, 0, 0, 0, 0, 1],
cellStates: 6,
type: 1
},
{
name: "Spirals",
alive: [0, 0, 1, 0, 0, 0, 0, 0, 0],
dead: [0, 0, 1, 1, 1, 0, 0, 0, 0],
cellStates: 5,
type: 1
},
{
name: "Star Wars",
alive: [0, 0, 0, 1, 1, 1, 0, 0, 0],
dead: [0, 0, 1, 0, 0, 0, 0, 0, 0],
cellStates: 4,
type: 1
},
{
name: "Sticks",
alive: [0, 0, 0, 1, 1, 1, 1, 0, 0],
dead: [0, 0, 1, 0, 0, 0, 0, 0, 0],
cellStates: 6,
type: 1
},
{
name: "Swirl",
alive: [0, 0, 1, 1, 0, 0, 0, 0, 0],
dead: [0, 0, 0, 1, 1, 0, 0, 0, 0],
cellStates: 8,
type: 1
},
{
name: "ThrillGrill",
alive: [0, 1, 1, 1, 1, 0, 0, 0, 0],
dead: [0, 0, 0, 1, 1, 0, 0, 0, 0],
cellStates: 48,
type: 1
},
{
name: "Transers",
alive: [0, 0, 0, 1, 1, 1, 0, 0, 0],
dead: [0, 0, 1, 0, 0, 0, 1, 0, 0],
cellStates: 5,
type: 1
},
{
name: "Transers II",
alive: [1, 0, 0, 1, 1, 1, 0, 0, 0],
dead: [0, 0, 1, 0, 0, 0, 1, 0, 0],
cellStates: 6,
type: 1
},
{
name: "Wanderers",
alive: [0, 0, 0, 1, 1, 1, 0, 0, 0],
dead: [0, 0, 0, 1, 1, 0, 1, 1, 1],
cellStates: 5,
type: 1
},
{
name: "Worms",
alive: [0, 0, 0, 1, 1, 0, 1, 1, 0],
dead: [0, 0, 1, 0, 0, 1, 0, 0, 0],
cellStates: 6,
type: 1
},
{
name: "Xtasy",
alive: [0, 1, 0, 0, 1, 1, 1, 0, 0],
dead: [0, 0, 1, 1, 0, 1, 1, 0, 0],
cellStates: 16,
type: 1
},
];
this.front = {};
this.createTarget(this.front);
this.back = {};
this.createTarget(this.back);
this.createMouseProgram();
this.createScreenProgram();
}
setRule(name) {
var chosenRule = this.possibleRules.find(rule => rule.name === name);
this.setCellRules(chosenRule.alive, chosenRule.dead);
if(chosenRule.type) {
this.createGenerationsProgram();
this.cellStates = chosenRule.cellStates;
} else {
this.createLifeProgram();
}
}
setColor(hue, saturation) {
this.paintColor = { h: hue, s: saturation, v: 1 };
}
setColorDecay(decay) {
this.paintColorDecay = decay;
}
setMouseLocation(e) {
var rect = e.target.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
self.mouseX = x / canvas.offsetWidth;
self.mouseY = 1 - (y / canvas.offsetHeight);
}
setBrushSize(size) {
// Between 0 and 0.08
this.brushSize = size / this.renderWidth;
}
setMouseLocation(x, y) {
this.mouseX = x;
this.mouseY = y;
}
triggerDraw() {
this.drawMouseProgram();
}
swap() {
var gl = this.gl;
var tmp = this.front;
this.front = this.back;
this.back = tmp;
gl.bindFramebuffer(gl.FRAMEBUFFER, this.front.fbo);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.back.texture);
};
createTarget(target) {
var gl = this.gl;
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA, this.renderWidth, this.renderHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
var fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
gl.bindTexture(gl.TEXTURE_2D, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
target.texture = texture;
target.fbo = fbo;
}
createScreenProgram() {
var gl = this.gl;
var program = this.createProgram(this.vertex_shader, this.screen_shader);
var uniforms = this.getUniformLocations(program, ['u_surface']);
// static uniforms
gl.useProgram(program);
gl.uniform1i(gl.getUniformLocation(program, 'u_screenBuffer'), 1);
gl.useProgram(null);
var locVertexCoords = gl.getAttribLocation(program, 'a_position');
gl.enableVertexAttribArray(locVertexCoords);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.vertexAttribPointer(locVertexCoords, 2, gl.FLOAT, false, 0, 0);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, 0, 1, 1, 1, 1, 0]), gl.STATIC_DRAW);
this.screenProgram = program;
this.screenUniforms = uniforms;
}
drawScreenProgram() {
var canvas = this.canvas;
var gl = this.gl;
var program = this.screenProgram;
var uniforms = this.screenUniforms;
gl.viewport(0, 0, canvas.width, canvas.height);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.useProgram(program);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, this.front.texture);
gl.uniform4f(uniforms.u_surface, this.top, this.right, this.bottom, this.left);
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
}
createCellProgram(shader) {
var gl = this.gl;
var program = this.createProgram(this.vertex_shader, shader);
var uniforms = this.getUniformLocations(program, ['u_bufferResolution', 'u_colorDecay', 'u_cellStates']);
// static uniforms
gl.useProgram(program);
gl.uniform1i(gl.getUniformLocation(program, 'u_buffer'), 0);
gl.uniform1i(gl.getUniformLocation(program, 'u_rules'), 2);
gl.useProgram(null);
this.cellProgram = program;
this.cellUniforms = uniforms;
}
createLifeProgram() {
this.createCellProgram(this.cell_life_shader);
}
createGenerationsProgram() {
this.createCellProgram(this.cell_generations_shader);
}
drawCellProgram() {
var program = this.cellProgram;
var uniforms = this.cellUniforms;
gl.viewport(0, 0, this.renderWidth, this.renderHeight);
this.swap();
gl.useProgram(program);
gl.uniform2f(uniforms.u_bufferResolution, this.renderWidth, this.renderHeight);
gl.uniform1f(uniforms.u_colorDecay, this.paintColorDecay);
gl.uniform1i(uniforms.u_cellStates, this.cellStates);
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
}
setCellRules(alive, dead) {
var gl = this.gl;
var data = [];
for(var i = 0; i < alive.length; i++) {
data.push(alive[i] * 255, dead[i] * 255, 0, 0);
}
var texture = gl.createTexture();
gl.activeTexture(gl.TEXTURE2);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, alive.length, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(data));
}
createMouseProgram() {
var gl = this.gl;
var program = this.createProgram(this.vertex_shader, this.mouse_shader);
var uniforms = this.getUniformLocations(program, [
'u_bufferResolution', 'u_mouse', 'u_brushSize', 'u_color', 'u_brushErase', 'u_brushSolid', 'u_brushPixel',
'u_colorDecay', 'u_random', 'u_surface'
]);
// static uniforms
gl.useProgram(program);
gl.uniform1i(gl.getUniformLocation(program, 'u_buffer'), 0);
gl.useProgram(null);
this.mouseProgram = program;
this.mouseUniforms = uniforms;
}
drawMouseProgram() {
var gl = this.gl;
var program = this.mouseProgram;
var uniforms = this.mouseUniforms;
gl.viewport(0, 0, this.renderWidth, this.renderHeight);
this.swap();
gl.useProgram(program);
gl.uniform2f(uniforms.u_bufferResolution, this.renderWidth, this.renderHeight);
gl.uniform2f(uniforms.u_mouse, this.mouseX, this.mouseY);
gl.uniform4f(uniforms.u_color, this.paintColor.h, this.paintColor.s, this.paintColor.v, 1.0);
gl.uniform1i(uniforms.u_brushErase, this.brushErase);
gl.uniform1i(uniforms.u_brushSolid, this.brushSolid);
gl.uniform1i(uniforms.u_brushPixel, this.brushPixel);
gl.uniform1f(uniforms.u_brushSize, this.brushSize);
gl.uniform1f(uniforms.u_colorDecay, this.paintColorDecay);
gl.uniform1f(uniforms.u_random, Math.random());
gl.uniform4f(uniforms.u_surface, this.top, this.right, this.bottom, this.left);
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
}
correctDimensions() {
this.displayWidth = this.canvas.clientWidth;
this.displayHeight = this.canvas.clientHeight;
this.canvas.width = this.displayWidth;
this.canvas.height = this.displayHeight;
}
registerSizeChanges() {
var self = this;
window.onresize = function() {
self.correctDimensions();
};
this.correctDimensions();
}
registerDefaultListeners() {
var canvas = this.canvas;
var self = this;
this.registerSizeChanges();
canvas.onmousemove = function(e) {
var rect = e.target.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
self.mouseX = x / canvas.offsetWidth;
self.mouseY = 1 - (y / canvas.offsetHeight);
};
canvas.onmousedown = function(e) {
// left click
if(e.which === 1) {
self.paintColor = { h: Math.random(), s: self.paintSaturation, v: 1 };
// if(this.pauseOnDraw) {
// this.pauseCells = true;
// }
self.drawMouseProgram();
canvas.addEventListener('mousemove', self.drawMouseProgram);
}
// right click
else if(e.which === 3) {
// panningHandler.mouseLastX = this.mouseX;
// panningHandler.mouseLastY = this.mouseY;
// canvas.addEventListener('mousemove', panningHandler);
}
};
canvas.onmouseup = function(e) {
// left click
if(e.which === 1) {
// if(params.pauseOnDraw && !params.pauseButton) {
// params.pauseCells = false;
// }
canvas.removeEventListener('mousemove', self.drawMouseProgram);
}
// right click
else if(e.which === 3) {
// canvas.removeEventListener('mousemove', panningHandler);
}
};
canvas.onresize = function() {
self.correctDimensions();
};
this.correctDimensions();
}
createProgram(vertexSource, fragmentSource) {
function compileAndCheck(program, shader, source) {
gl.shaderSource(shader, source);
gl.compileShader(shader);
if(!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(program, shader, source);
console.log(gl.getShaderInfoLog(shader));
}
gl.attachShader(program, shader);
}
var program = gl.createProgram();
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
compileAndCheck(program, vertexShader, vertexSource);
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
compileAndCheck(program, fragmentShader, fragmentSource);
gl.linkProgram(program);
return program;
}
getUniformLocations(program, uniformNames) {
var uniforms = [];
for(var i = 0; i < uniformNames.length; i++) {
uniforms[uniformNames[i]] = gl.getUniformLocation(program, uniformNames[i]);
}
return uniforms;
}
}
var renderer = new Renderer(canvas, gl, Math.floor(canvas.clientWidth / 10), Math.floor(canvas.clientHeight / 10));
renderer.setRule("OrthoGo");
renderer.setBrushSize(0.07);
renderer.setColorDecay(0.7);
function animate() {
renderer.drawCellProgram();
renderer.drawScreenProgram();
// setTimeout(function() {
window.requestAnimationFrame(animate);
//}, 30);
}
animate();
setInterval(function() {
renderer.setColor(Math.random(), 0.6);
renderer.setMouseLocation(Math.random(), Math.random());
renderer.triggerDraw();
}, 1000);
})();
export default function AttachAnimation(canvasElement: React.RefObject<HTMLCanvasElement>) {
React.useEffect(() => {
const canvas = canvasElement?.current;
var gl = canvas?.getContext("webgl");
if(canvas && gl) {
class Renderer {
private canvas: HTMLCanvasElement;
private gl: WebGLRenderingContext;
private renderWidth: number;
private renderHeight: number;
private displayWidth: number;
private displayHeight: number;
private top: number;
private right: number;
private bottom: number;
private left: number;
private paintSaturation: number;
private paintColor: { h: number, s: number, v: number };
private paintColorDecay: number;
private cellStates: number;
private mouseX: number;
private mouseY: number;
private brushSize: number;
private brushErase: boolean;
private brushSolid: boolean;
private brushPixel: boolean;
private vertex_shader: string;
private screen_shader: string;
private cell_life_shader: string;
private cell_generations_shader: string;
private mouse_shader: string;
private possibleRules: { name: string, alive: number[], dead: number[], cellStates?: number }[];
private front: { texture: WebGLTexture|null, fbo: WebGLFramebuffer|null };
private back: { texture: WebGLTexture|null, fbo: WebGLFramebuffer|null };
private screenProgram?: WebGLProgram;
private screenUniforms?: { [n: string]: WebGLUniformLocation };
private cellProgram?: WebGLProgram;
private cellUniforms?: { [n: string]: WebGLUniformLocation };
private mouseProgram?: WebGLProgram;
private mouseUniforms?: { [n: string]: WebGLUniformLocation };
constructor(
canvas: HTMLCanvasElement, gl: WebGLRenderingContext, renderWidth: number, renderHeight: number) {
this.canvas = canvas;
this.gl = gl;
this.renderWidth = renderWidth;
this.renderHeight = renderHeight;
this.displayWidth = canvas.clientWidth;
this.displayHeight = canvas.clientHeight;
this.top = 1;
this.right = 1;
this.bottom = 0;
this.left = 0;
this.correctDimensions();
this.paintSaturation = 0.2;
this.paintColor = {
h: Math.random(),
s: this.paintSaturation,
v: 1,
};
this.paintColorDecay = 0.4;
this.cellStates = 1;
this.mouseX = 0;
this.mouseY = 0;
this.brushSize = 0.00064;
this.brushErase = false;
this.brushSolid = false;
this.brushPixel = false;
this.vertex_shader = `
attribute vec2 a_position;
varying vec2 v_texCoord;
void main() {
vec2 clipSpace = a_position * 2.0 - 1.0;
gl_Position = vec4(clipSpace, 0, 1);
v_texCoord = a_position;
}
`;
this.screen_shader = `
precision mediump float;
uniform sampler2D u_screenBuffer;
uniform vec4 u_surface; //top, right, bottom left
varying vec2 v_texCoord;
// http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
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() {
vec2 uv = u_surface.wz + v_texCoord * (u_surface.yx - u_surface.wz);
vec3 hsv = texture2D(u_screenBuffer, fract(uv)).rgb;
gl_FragColor = vec4(hsv2rgb(hsv), hsv.z);
}
`;
this.cell_life_shader = `
precision mediump float;
uniform vec2 u_bufferResolution;
uniform vec4 u_surface;
uniform float u_colorDecay;
uniform sampler2D u_buffer;
uniform sampler2D u_rules; // a 9x1 texture containing alive states in the red channel and dead states in the green channel
varying vec2 v_texCoord;
#define TWO_PI 6.28318530718
#define RULES_COUNT 8.0
#define DECAY vec4(0, 0, 0.002, 1)
vec4 firstDecay = vec4(0, -1.0, u_colorDecay, 1.0);
vec2 onePixel = vec2(1.0, 1.0) / u_bufferResolution;
vec4 isAlive(int x, int y) {
vec4 pixel = texture2D(u_buffer, fract(v_texCoord + vec2(x, y) * onePixel));
// if cell is alive, calculate Cartesian coordinates of cell's hue for use in circular averaging.
return pixel.a > 0.0 ?
vec4(cos(TWO_PI * pixel.r), sin(TWO_PI * pixel.r), pixel.g, 1.0) :
vec4(0);
}
void main() {
vec4 n = isAlive(0, 1)
+ isAlive(1, 1)
+ isAlive(1, 0)
+ isAlive(1, -1)
+ isAlive(0, -1)
+ isAlive(-1, -1)
+ isAlive(-1, 0)
+ isAlive(-1, 1);
// alive
if (texture2D(u_buffer, fract(v_texCoord)).a == 1.0) {
float state = texture2D(u_rules, vec2(n.a / RULES_COUNT, 0)).r;
gl_FragColor = texture2D(u_buffer, v_texCoord) - (1.0 - state) * firstDecay;
}
// dead
else {
float state = texture2D(u_rules, vec2(n.a / RULES_COUNT, 0) ).g;
gl_FragColor = state > 0.0 ?
vec4(fract(1.0 + atan(n.y, n.x) / TWO_PI), n.b / n.a, 1, 1) : // circular average of colors
texture2D(u_buffer, v_texCoord) - DECAY;
}
}
`;
this.cell_generations_shader = `
precision mediump float;
uniform vec2 u_bufferResolution;
uniform vec4 u_surface;
uniform float u_colorDecay;
uniform int u_cellStates;
uniform sampler2D u_buffer;
uniform sampler2D u_rules;
varying vec2 v_texCoord;
#define TWO_PI 6.28318530718
#define RULES_COUNT 8.0
float singleState = 1.0 / float(u_cellStates - 1);
vec4 firstDecay = vec4(0, -1.0, u_colorDecay, singleState);
vec4 decay = vec4(0, 0, 0.002, singleState);
vec2 onePixel = vec2(1.0, 1.0) / u_bufferResolution;
vec4 isAlive(int x, int y) {
vec4 pixel = texture2D(u_buffer, fract(v_texCoord + vec2(x, y) * onePixel));
return pixel.a == 1.0 ?
vec4(cos(TWO_PI * pixel.r), sin(TWO_PI * pixel.r), pixel.g, 1.0) :
vec4(0, 0, 0, 0);
}
void main() {
vec4 n = isAlive(0, 1)
+ isAlive(1, 1)
+ isAlive(1, 0)
+ isAlive(1, -1)
+ isAlive(0, -1)
+ isAlive(-1, -1)
+ isAlive(-1, 0)
+ isAlive(-1, 1);
float currentCell = texture2D(u_buffer, fract(v_texCoord)).a;
// alive
if (currentCell == 1.0) {
float state = texture2D(u_rules, vec2(n.a / RULES_COUNT, 0)).r;
gl_FragColor = texture2D(u_buffer, v_texCoord) - (1.0 - state) * firstDecay;
}
// dead
else {
float state = (1.0 - ceil(currentCell)) * texture2D(u_rules, vec2(n.a / RULES_COUNT, 0) ).g;
gl_FragColor = state == 1.0 ?
vec4(fract(1.0 + atan(n.y, n.x) / TWO_PI), n.b / n.a, 1, 1) :
texture2D(u_buffer, v_texCoord) - decay;
}
}
`;
this.mouse_shader = `
precision mediump float;
uniform vec2 u_bufferResolution;
uniform vec2 u_mouse;
uniform vec4 u_color;
uniform float u_brushSize;
uniform bool u_brushErase;
uniform bool u_brushSolid;
uniform bool u_brushPixel;
uniform float u_colorDecay;
uniform float u_random;
uniform vec4 u_surface;
uniform sampler2D u_buffer;
varying vec2 v_texCoord;
vec4 firstDecay = vec4(0, -1.0, u_colorDecay, 1.0);
float rand(vec2 co) {
return fract(sin(dot(fract(co.xy + u_random), vec2(12.9898,78.233))) * 43758.5453);
}
void main() {
vec2 mouseCoord = fract(u_surface.wz + u_mouse * (u_surface.yx - u_surface.wz));
vec2 distance = abs(mouseCoord - v_texCoord);
if (u_brushPixel) {
distance *= u_bufferResolution;
if ( dot(distance, distance) < 0.25 )
gl_FragColor = u_brushErase ? vec4(0) : u_color;
else
gl_FragColor = texture2D(u_buffer, v_texCoord);
}
else {
distance = min(distance, 1. - distance);
distance.x *= u_bufferResolution.x / u_bufferResolution.y;
if ( dot(distance, distance) < u_brushSize ) {
gl_FragColor = u_brushErase ?
vec4(0.06, 0.06, 0.06, 0) :
u_color - float(!u_brushSolid) * step(0.5, rand(gl_FragCoord.xy / u_bufferResolution)) * firstDecay;
} else {
gl_FragColor = texture2D(u_buffer, v_texCoord);
}
}
}
`;
this.possibleRules = [
{ name: "Dry Life", alive: [0, 0, 1, 1, 0, 0, 0, 0, 0], dead: [0, 0, 0, 1, 0, 0, 0, 1, 0] },
{ name: "2x2", alive: [0, 1, 1, 0, 0, 1, 0, 0, 0], dead: [0, 0, 0, 1, 0, 0, 1, 0, 0] },
{ name: "34 Life", alive: [0, 0, 0, 1, 1, 0, 0, 0, 0], dead: [0, 0, 0, 1, 1, 0, 0, 0, 0] },
{ name: "Amoeba", alive: [0, 1, 0, 1, 0, 1, 0, 0, 1], dead: [0, 0, 0, 1, 0, 1, 0, 1, 0] },
{ name: "Assimilation", alive: [0, 0, 0, 0, 1, 1, 1, 1, 0], dead: [0, 0, 0, 1, 1, 1, 0, 0, 0] },
{ name: "Coagulations", alive: [0, 0, 1, 1, 0, 1, 1, 1, 1], dead: [0, 0, 0, 1, 0, 0, 0, 1, 1] },
{
name: "Conway's Life",
alive: [0, 0, 1, 1, 0, 0, 0, 0, 0],
dead: [0, 0, 0, 1, 0, 0, 0, 0, 0]
},
{ name: "Coral", alive: [0, 0, 0, 0, 1, 1, 1, 1, 1], dead: [0, 0, 0, 1, 0, 0, 0, 0, 0] },
{ name: "Day & Night", alive: [0, 0, 0, 1, 1, 0, 1, 1, 1], dead: [0, 0, 0, 1, 0, 0, 1, 1, 1] },
{ name: "Diamoeba", alive: [0, 0, 0, 0, 0, 1, 1, 1, 1], dead: [0, 0, 0, 1, 0, 1, 1, 1, 1] },
{ name: "Dot Life", alive: [1, 0, 1, 1, 0, 0, 0, 0, 0], dead: [0, 0, 0, 1, 0, 0, 0, 0, 0] },
{ name: "Flakes", alive: [1, 1, 1, 1, 1, 1, 1, 1, 1], dead: [0, 0, 0, 1, 0, 0, 0, 0, 0] },
{ name: "Fredkin", alive: [1, 0, 1, 0, 1, 0, 1, 0, 1], dead: [0, 1, 0, 1, 0, 1, 0, 1, 0] },
{ name: "Gnarl", alive: [0, 1, 0, 0, 0, 0, 0, 0, 0], dead: [0, 1, 0, 0, 0, 0, 0, 0, 0] },
{ name: "High Life", alive: [0, 0, 1, 1, 0, 0, 0, 0, 0], dead: [0, 0, 0, 1, 0, 0, 1, 0, 0] },
{
name: "Live Free or Die",
alive: [1, 0, 0, 0, 0, 0, 0, 0, 0],
dead: [0, 0, 1, 0, 0, 0, 0, 0, 0]
},
{ name: "Long life", alive: [0, 0, 0, 0, 0, 1, 0, 0, 0], dead: [0, 0, 0, 1, 1, 1, 0, 0, 0] },
{ name: "Maze", alive: [0, 1, 1, 1, 1, 1, 0, 0, 0], dead: [0, 0, 0, 1, 0, 0, 0, 0, 0] },
{ name: "Mazectric", alive: [0, 1, 1, 1, 1, 0, 0, 0, 0], dead: [0, 0, 0, 1, 0, 0, 0, 0, 0] },
{ name: "Move", alive: [0, 0, 1, 0, 1, 1, 0, 0, 0], dead: [0, 0, 0, 1, 0, 0, 1, 0, 1] },
{ name: "Pseudo life", alive: [0, 0, 1, 1, 0, 0, 0, 0, 1], dead: [0, 0, 0, 1, 0, 1, 0, 1, 0] },
{ name: "Replicator", alive: [0, 1, 0, 1, 0, 1, 0, 1, 0], dead: [0, 1, 0, 1, 0, 1, 0, 1, 0] },
{ name: "Seeds", alive: [0, 0, 0, 0, 0, 0, 0, 0, 0], dead: [0, 0, 1, 0, 0, 0, 0, 0, 0] },
{ name: "Serviettes", alive: [0, 0, 0, 0, 0, 0, 0, 0, 0], dead: [0, 0, 1, 1, 1, 0, 0, 0, 0] },
{ name: "Stains", alive: [0, 0, 1, 1, 0, 1, 1, 1, 1], dead: [0, 0, 0, 1, 0, 0, 1, 1, 1] },
{ name: "Vote", alive: [0, 0, 0, 0, 1, 1, 1, 1, 1], dead: [0, 0, 0, 0, 0, 1, 1, 1, 1] },
{ name: "Vote 4/5", alive: [0, 0, 0, 0, 1, 1, 1, 1, 1], dead: [0, 0, 0, 0, 0, 1, 1, 1, 1] },
{
name: "Walled Cities",
alive: [0, 0, 1, 1, 1, 1, 0, 0, 0],
dead: [0, 0, 0, 0, 1, 1, 1, 1, 1]
},
{
name: "Banners",
alive: [0, 0, 1, 1, 0, 0, 1, 1, 0],
dead: [0, 0, 0, 1, 1, 1, 0, 1, 0],
cellStates: 5
},
{
name: "BelZhab",
alive: [0, 0, 1, 1, 0, 0, 0, 0, 0],
dead: [0, 0, 1, 1, 0, 0, 0, 0, 0],
cellStates: 8
},
{
name: "BelZhab Sediment",
alive: [0, 1, 0, 0, 1, 1, 1, 1, 1],
dead: [0, 0, 1, 1, 0, 0, 0, 0, 0],
cellStates: 8
},
{
name: "Bloomerang",
alive: [0, 0, 1, 1, 1, 0, 0, 0, 0],
dead: [0, 0, 0, 1, 1, 0, 1, 1, 1],
cellStates: 24
},
{
name: "Bombers",
alive: [0, 0, 0, 1, 1, 1, 0, 0, 0],
dead: [0, 0, 1, 0, 1, 0, 0, 0, 0],
cellStates: 25
},
{
name: "Brain 6",
alive: [0, 0, 0, 0, 0, 0, 1, 0, 0],
dead: [0, 0, 1, 0, 1, 0, 1, 0, 0],
cellStates: 3
},
{
name: "Brian's Brain",
alive: [0, 0, 0, 0, 0, 0, 0, 0, 0],
dead: [0, 0, 1, 0, 0, 0, 0, 0, 0],
cellStates: 3
},
{
name: "Burst",
alive: [1, 0, 1, 1, 0, 1, 1, 1, 1],
dead: [0, 0, 0, 1, 1, 0, 1, 0, 1],
cellStates: 9
},
{
name: "Burst II",
alive: [0, 0, 1, 1, 0, 1, 1, 1, 1],
dead: [0, 0, 0, 1, 1, 0, 1, 0, 1],
cellStates: 9
},
{
name: "Caterpillars",
alive: [0, 1, 1, 0, 1, 1, 1, 1, 0],
dead: [0, 0, 0, 1, 0, 0, 0, 1, 1],
cellStates: 4
},
{
name: "Chenille",
alive: [1, 0, 0, 0, 0, 1, 1, 1, 1],
dead: [0, 0, 1, 0, 1, 1, 1, 1, 0],
cellStates: 6
},
{
name: "Circuit Genesis",
alive: [0, 0, 1, 1, 1, 1, 0, 0, 0],
dead: [0, 1, 1, 1, 1, 0, 0, 0, 0],
cellStates: 8
},
{
name: "Cooties",
alive: [0, 0, 1, 1, 0, 0, 0, 0, 0],
dead: [0, 0, 1, 0, 0, 0, 0, 0, 0],
cellStates: 8
},
{
name: "Ebb&Flow",
alive: [1, 1, 1, 0, 1, 0, 0, 1, 1],
dead: [0, 0, 0, 1, 0, 0, 1, 0, 0],
cellStates: 18
},
{
name: "Ebb&Flow II",
alive: [1, 1, 1, 0, 1, 0, 1, 0, 1],
dead: [0, 0, 0, 1, 0, 0, 0, 1, 0],
cellStates: 18
},
{
name: "Faders",
alive: [0, 0, 1, 0, 0, 0, 0, 0, 0],
dead: [0, 0, 1, 0, 0, 0, 0, 0, 0],
cellStates: 25
},
{
name: "Fireworks",
alive: [0, 0, 1, 0, 0, 0, 0, 0, 0],
dead: [0, 1, 0, 1, 0, 0, 0, 0, 0],
cellStates: 21
},
{
name: "Flaming Starbows",
alive: [0, 0, 0, 1, 1, 0, 0, 1, 0],
dead: [0, 0, 1, 1, 0, 0, 0, 0, 0],
cellStates: 8
},
{
name: "Frogs",
alive: [0, 1, 1, 0, 0, 0, 0, 0, 0],
dead: [0, 0, 0, 1, 1, 0, 0, 0, 0],
cellStates: 3
},
{
name: "Frozen spirals",
alive: [0, 0, 0, 1, 0, 1, 1, 0, 0],
dead: [0, 0, 1, 1, 0, 0, 0, 0, 0],
cellStates: 6
},
{
name: "Glisserati",
alive: [1, 0, 0, 1, 0, 1, 1, 1, 1],
dead: [0, 0, 1, 0, 1, 1, 1, 1, 1],
cellStates: 7
},
{
name: "Glissergy",
alive: [1, 0, 0, 1, 0, 1, 1, 1, 1],
dead: [0, 0, 1, 0, 1, 1, 1, 1, 1],
cellStates: 5
},
{
name: "Lava",
alive: [0, 1, 1, 1, 1, 1, 0, 0, 0],
dead: [0, 0, 0, 0, 1, 1, 1, 1, 1],
cellStates: 8
},
{
name: "Lines",
alive: [1, 1, 1, 1, 1, 1, 0, 0, 0],
dead: [0, 0, 0, 0, 1, 1, 0, 0, 1],
cellStates: 3
},
{
name: "Living On The Edge",
alive: [0, 0, 0, 1, 1, 1, 0, 0, 0],
dead: [0, 0, 0, 1, 0, 0, 0, 0, 0],
cellStates: 6
},
{
name: "Meteor Guns",
alive: [1, 1, 1, 0, 1, 1, 1, 1, 1],
dead: [0, 0, 0, 1, 0, 0, 0, 0, 0],
cellStates: 8
},
{
name: "Nova",
alive: [0, 0, 0, 0, 1, 1, 1, 1, 1],
dead: [0, 0, 1, 0, 1, 0, 0, 1, 1],
cellStates: 25
},
{
name: "OrthoGo",
alive: [0, 0, 0, 1, 0, 0, 0, 0, 0],
dead: [0, 0, 1, 0, 0, 0, 0, 0, 0],
cellStates: 4
},
{
name: "Prairie on fire",
alive: [0, 0, 0, 1, 1, 1, 0, 0, 0],
dead: [0, 0, 0, 1, 1, 0, 0, 0, 0],
cellStates: 6
},
{
name: "RainZha",
alive: [0, 0, 1, 0, 0, 0, 0, 0, 0],
dead: [0, 0, 1, 1, 0, 0, 0, 0, 0],
cellStates: 8
},
{
name: "Rake",
alive: [0, 0, 0, 1, 1, 0, 1, 1, 0],
dead: [0, 0, 1, 0, 0, 0, 1, 1, 1],
cellStates: 6
},
{
name: "SediMental",
alive: [0, 0, 0, 0, 1, 1, 1, 1, 1],
dead: [0, 0, 1, 0, 0, 1, 1, 1, 1],
cellStates: 4
},
{
name: "Snake",
alive: [1, 0, 0, 1, 1, 0, 1, 1, 0],
dead: [0, 0, 1, 0, 0, 1, 0, 0, 0],
cellStates: 6
},
{
name: "SoftFreeze",
alive: [0, 1, 0, 1, 1, 1, 0, 0, 1],
dead: [0, 0, 0, 1, 0, 0, 0, 0, 1],
cellStates: 6
},
{
name: "Spirals",
alive: [0, 0, 1, 0, 0, 0, 0, 0, 0],
dead: [0, 0, 1, 1, 1, 0, 0, 0, 0],
cellStates: 5
},
{
name: "Star Wars",
alive: [0, 0, 0, 1, 1, 1, 0, 0, 0],
dead: [0, 0, 1, 0, 0, 0, 0, 0, 0],
cellStates: 4
},
{
name: "Sticks",
alive: [0, 0, 0, 1, 1, 1, 1, 0, 0],
dead: [0, 0, 1, 0, 0, 0, 0, 0, 0],
cellStates: 6
},
{
name: "Swirl",
alive: [0, 0, 1, 1, 0, 0, 0, 0, 0],
dead: [0, 0, 0, 1, 1, 0, 0, 0, 0],
cellStates: 8
},
{
name: "ThrillGrill",
alive: [0, 1, 1, 1, 1, 0, 0, 0, 0],
dead: [0, 0, 0, 1, 1, 0, 0, 0, 0],
cellStates: 48
},
{
name: "Transers",
alive: [0, 0, 0, 1, 1, 1, 0, 0, 0],
dead: [0, 0, 1, 0, 0, 0, 1, 0, 0],
cellStates: 5
},
{
name: "Transers II",
alive: [1, 0, 0, 1, 1, 1, 0, 0, 0],
dead: [0, 0, 1, 0, 0, 0, 1, 0, 0],
cellStates: 6
},
{
name: "Wanderers",
alive: [0, 0, 0, 1, 1, 1, 0, 0, 0],
dead: [0, 0, 0, 1, 1, 0, 1, 1, 1],
cellStates: 5
},
{
name: "Worms",
alive: [0, 0, 0, 1, 1, 0, 1, 1, 0],
dead: [0, 0, 1, 0, 0, 1, 0, 0, 0],
cellStates: 6
},
{
name: "Xtasy",
alive: [0, 1, 0, 0, 1, 1, 1, 0, 0],
dead: [0, 0, 1, 1, 0, 1, 1, 0, 0],
cellStates: 16
},
];
this.front = this.createTarget();
this.back = this.createTarget();
this.createMouseProgram();
this.createScreenProgram();
}
setRule(name: string) {
var chosenRule = this.possibleRules.find(rule => rule.name === name);
if(chosenRule) {
this.setCellRules(chosenRule.alive, chosenRule.dead);
if(chosenRule.cellStates) {
this.createGenerationsProgram();
this.cellStates = chosenRule.cellStates;
} else {
this.createLifeProgram();
}
}
}
setColor(hue: number, saturation: number) {
this.paintColor = { h: hue, s: saturation, v: 1 };
}
setColorDecay(decay: number) {
this.paintColorDecay = decay;
}
setMouseLocationEvent(e: MouseEvent) {
if(e.target) {
var rect = (e.target as HTMLElement).getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
this.mouseX = x / this.canvas.offsetWidth;
this.mouseY = 1 - (y / this.canvas.offsetHeight);
}
}
setBrushSize(size: number) {
// Between 0 and 0.08
this.brushSize = size / this.renderWidth;
}
setMouseLocation(x: number, y: number) {
this.mouseX = x;
this.mouseY = y;
}
triggerDraw() {
this.drawMouseProgram();
}
swap() {
var gl = this.gl;
var tmp = this.front;
this.front = this.back;
this.back = tmp;
gl.bindFramebuffer(gl.FRAMEBUFFER, this.front.fbo);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.back.texture);
};
createTarget() {
var gl = this.gl;
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.renderWidth, this.renderHeight, 0, gl.RGBA,
gl.UNSIGNED_BYTE, null);
var fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
gl.bindTexture(gl.TEXTURE_2D, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
return { texture, fbo };
}
createScreenProgram() {
var gl = this.gl;
var program = this.createProgram(this.vertex_shader, this.screen_shader);
if(program) {
var uniforms = this.getUniformLocations(program, ['u_surface']);
// static uniforms
gl.useProgram(program);
gl.uniform1i(gl.getUniformLocation(program, 'u_screenBuffer'), 1);
gl.useProgram(null);
var locVertexCoords = gl.getAttribLocation(program, 'a_position');
gl.enableVertexAttribArray(locVertexCoords);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.vertexAttribPointer(locVertexCoords, 2, gl.FLOAT, false, 0, 0);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, 0, 1, 1, 1, 1, 0]), gl.STATIC_DRAW);
this.screenProgram = program;
this.screenUniforms = uniforms;
}
}
drawScreenProgram() {
var canvas = this.canvas;
var gl = this.gl;
var program = this.screenProgram;
var uniforms = this.screenUniforms;
if(program && uniforms) {
gl.viewport(0, 0, canvas.width, canvas.height);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.useProgram(program);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, this.front.texture);
gl.uniform4f(uniforms.u_surface, this.top, this.right, this.bottom, this.left);
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
}
}
createCellProgram(shader: string) {
var gl = this.gl;
var program = this.createProgram(this.vertex_shader, shader);
if(program) {
var uniforms
= this.getUniformLocations(program, ['u_bufferResolution', 'u_colorDecay', 'u_cellStates']);
// static uniforms
gl.useProgram(program);
gl.uniform1i(gl.getUniformLocation(program, 'u_buffer'), 0);
gl.uniform1i(gl.getUniformLocation(program, 'u_rules'), 2);
gl.useProgram(null);
this.cellProgram = program;
this.cellUniforms = uniforms;
}
}
createLifeProgram() {
this.createCellProgram(this.cell_life_shader);
}
createGenerationsProgram() {
this.createCellProgram(this.cell_generations_shader);
}
drawCellProgram() {
var gl = this.gl;
var program = this.cellProgram;
var uniforms = this.cellUniforms;
if(program && uniforms) {
gl.viewport(0, 0, this.renderWidth, this.renderHeight);
this.swap();
gl.useProgram(program);
gl.uniform2f(uniforms.u_bufferResolution, this.renderWidth, this.renderHeight);
gl.uniform1f(uniforms.u_colorDecay, this.paintColorDecay);
gl.uniform1i(uniforms.u_cellStates, this.cellStates);
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
}
}
setCellRules(alive: number[], dead: number[]) {
var gl = this.gl;
var data = [];
for(var i = 0; i < alive.length; i++) {
data.push(alive[i] * 255, dead[i] * 255, 0, 0);
}
var texture = gl.createTexture();
gl.activeTexture(gl.TEXTURE2);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA, alive.length, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(data));
}
createMouseProgram() {
var gl = this.gl;
var program = this.createProgram(this.vertex_shader, this.mouse_shader);
if(program) {
var uniforms = this.getUniformLocations(program, [
'u_bufferResolution', 'u_mouse', 'u_brushSize', 'u_color', 'u_brushErase', 'u_brushSolid',
'u_brushPixel', 'u_colorDecay', 'u_random', 'u_surface'
]);
// static uniforms
gl.useProgram(program);
gl.uniform1i(gl.getUniformLocation(program, 'u_buffer'), 0);
gl.useProgram(null);
this.mouseProgram = program;
this.mouseUniforms = uniforms;
}
}
drawMouseProgram() {
var gl = this.gl;
var program = this.mouseProgram;
var uniforms = this.mouseUniforms;
if(program && uniforms) {
gl.viewport(0, 0, this.renderWidth, this.renderHeight);
this.swap();
gl.useProgram(program);
gl.uniform2f(uniforms.u_bufferResolution, this.renderWidth, this.renderHeight);
gl.uniform2f(uniforms.u_mouse, this.mouseX, this.mouseY);
gl.uniform4f(uniforms.u_color, this.paintColor.h, this.paintColor.s, this.paintColor.v, 1.0);
gl.uniform1i(uniforms.u_brushErase, Number(this.brushErase));
gl.uniform1i(uniforms.u_brushSolid, Number(this.brushSolid));
gl.uniform1i(uniforms.u_brushPixel, Number(this.brushPixel));
gl.uniform1f(uniforms.u_brushSize, this.brushSize);
gl.uniform1f(uniforms.u_colorDecay, this.paintColorDecay);
gl.uniform1f(uniforms.u_random, Math.random());
gl.uniform4f(uniforms.u_surface, this.top, this.right, this.bottom, this.left);
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
}
}
correctDimensions() {
this.displayWidth = this.canvas.clientWidth;
this.displayHeight = this.canvas.clientHeight;
this.canvas.width = this.displayWidth;
this.canvas.height = this.displayHeight;
}
registerSizeChanges() {
var self = this;
window.onresize = function() {
self.correctDimensions();
};
this.correctDimensions();
}
registerDefaultListeners() {
var canvas = this.canvas;
var self = this;
this.registerSizeChanges();
canvas.onmousemove = function(e) {
if(e.target) {
var rect = (e.target as HTMLElement).getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
self.mouseX = x / canvas.offsetWidth;
self.mouseY = 1 - (y / canvas.offsetHeight);
}
};
canvas.onmousedown = function(e) {
// left click
if(e.which === 1) {
self.paintColor = { h: Math.random(), s: self.paintSaturation, v: 1 };
// if(this.pauseOnDraw) {
// this.pauseCells = true;
// }
self.drawMouseProgram();
canvas.addEventListener('mousemove', self.drawMouseProgram);
}
// right click
else if(e.which === 3) {
// panningHandler.mouseLastX = this.mouseX;
// panningHandler.mouseLastY = this.mouseY;
// canvas.addEventListener('mousemove', panningHandler);
}
};
canvas.onmouseup = function(e) {
// left click
if(e.which === 1) {
// if(params.pauseOnDraw && !params.pauseButton) {
// params.pauseCells = false;
// }
canvas.removeEventListener('mousemove', self.drawMouseProgram);
}
// right click
else if(e.which === 3) {
// canvas.removeEventListener('mousemove', panningHandler);
}
};
canvas.onresize = function() {
self.correctDimensions();
};
this.correctDimensions();
}
createProgram(vertexSource: string, fragmentSource: string) {
var gl = this.gl;
function compileAndCheck(program: WebGLProgram, shader: WebGLShader, source: string) {
gl.shaderSource(shader, source);
gl.compileShader(shader);
if(!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(program, shader, source);
console.log(gl.getShaderInfoLog(shader));
}
gl.attachShader(program, shader);
}
var program = gl.createProgram();
if(program) {
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
if(vertexShader)
compileAndCheck(program, vertexShader, vertexSource);
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
if(fragmentShader)
compileAndCheck(program, fragmentShader, fragmentSource);
gl.linkProgram(program);
}
return program;
}
getUniformLocations(program: WebGLProgram, uniformNames: string[]) {
var uniforms: { [n: string]: WebGLUniformLocation } = {};
for(var i = 0; i < uniformNames.length; i++) {
const uniform = this.gl.getUniformLocation(program, uniformNames[i]);
if(uniform !== null) {
uniforms[uniformNames[i]] = uniform;
}
}
return uniforms;
}
}
var renderer
= new Renderer(canvas, gl, Math.floor(canvas.clientWidth / 10), Math.floor(canvas.clientHeight / 10));
renderer.setRule("OrthoGo");
renderer.setBrushSize(0.07);
renderer.setColorDecay(0.7);
function animate() {
renderer.drawCellProgram();
renderer.drawScreenProgram();
// setTimeout(function() {
window.requestAnimationFrame(animate);
//}, 30);
}
animate();
setInterval(function() {
renderer.setColor(Math.random(), 0.6);
renderer.setMouseLocation(Math.random(), Math.random());
renderer.triggerDraw();
}, 1000);
}
}, [canvasElement]);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment