Created
June 3, 2022 23:48
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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); | |
})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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