Skip to content

Instantly share code, notes, and snippets.

@TheGreatRambler
Last active June 3, 2024 14:30
Show Gist options
  • Save TheGreatRambler/a3df0a351e1d56219d916273e0428fb8 to your computer and use it in GitHub Desktop.
Save TheGreatRambler/a3df0a351e1d56219d916273e0428fb8 to your computer and use it in GitHub Desktop.
colorful-life-rebooted
/*
_____ _ __ _ _ _ __ ______ _ _ _
/ __ \ | | / _| | | | | (_)/ _| | ___ \ | | | | | |
| / \/ ___ | | ___ _ __| |_ _ _| | | | _| |_ ___ | |_/ /___| |__ ___ ___ | |_ ___ __| |
| | / _ \| |/ _ \| '__| _| | | | | | | | | _/ _ \ | // _ \ '_ \ / _ \ / _ \| __/ _ \/ _` |
| \__/\ (_) | | (_) | | | | | |_| | | | |___| | || __/ | |\ \ __/ |_) | (_) | (_) | || __/ (_| |
\____/\___/|_|\___/|_| |_| \__,_|_| \_____/_|_| \___| \_| \_\___|_.__/ \___/ \___/ \__\___|\__,_|
Modern port of https://github.com/jaxry/colorful-life/ by @TheGreatRambler
*/
function AttachColorfulLifeAnimation(canvasElement: HTMLCanvasElement) {
const canvas = canvasElement;
var gl = canvas?.getContext('webgl', {antialias: false});
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;
public 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 / 2),
Math.floor(canvas.clientHeight / 2));
renderer.setRule('OrthoGo');
renderer.setBrushSize(0.3);
renderer.setColorDecay(0.7);
function animate() {
renderer.drawCellProgram();
renderer.drawScreenProgram();
// setTimeout(function() {
window.requestAnimationFrame(animate);
//}, 30);
}
animate();
// Add one cell clump
renderer.setColor(Math.random(), 0.6);
renderer.setMouseLocation(Math.random(), Math.random());
renderer.triggerDraw();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment