refactor(simulation): Higher simulation settings for high-resolution monitors

This commit is contained in:
Timofey Gelazoniya 2021-11-06 11:29:25 +03:00
parent ba5b63f719
commit 25929b73ec
Signed by: zeldon
GPG Key ID: 047886915281DD2A
1 changed files with 124 additions and 233 deletions

View File

@ -48,7 +48,7 @@ let config = {
BLOOM: true, BLOOM: true,
BLOOM_ITERATIONS: 8, BLOOM_ITERATIONS: 8,
BLOOM_RESOLUTION: 256, BLOOM_RESOLUTION: 256,
BLOOM_INTENSITY: 0.8, BLOOM_INTENSITY: 0.3,
BLOOM_THRESHOLD: 0.6, BLOOM_THRESHOLD: 0.6,
BLOOM_SOFT_KNEE: 0.7, BLOOM_SOFT_KNEE: 0.7,
SUNRAYS: true, SUNRAYS: true,
@ -56,10 +56,8 @@ let config = {
SUNRAYS_WEIGHT: 1.0, SUNRAYS_WEIGHT: 1.0,
}; };
class pointerPrototype class pointerPrototype {
{ constructor() {
constructor()
{
this.id = -1; this.id = -1;
this.texcoordX = 0; this.texcoordX = 0;
this.texcoordY = 0; this.texcoordY = 0;
@ -79,20 +77,24 @@ pointers.push(new pointerPrototype());
const { gl, ext } = getWebGLContext(canvas); const { gl, ext } = getWebGLContext(canvas);
if (isMobile()) if (isMobile()) {
{
config.DYE_RESOLUTION = 512; config.DYE_RESOLUTION = 512;
} }
if (!ext.supportLinearFiltering)
{ if (window.screen.availHeight >= 1400) {
config.SIM_RESOLUTION = 256;
config.DYE_RESOLUTION = 4096;
config.BLOOM_RESOLUTION = 512;
}
if (!ext.supportLinearFiltering) {
config.DYE_RESOLUTION = 512; config.DYE_RESOLUTION = 512;
config.SHADING = false; config.SHADING = false;
config.BLOOM = false; config.BLOOM = false;
config.SUNRAYS = false; config.SUNRAYS = false;
} }
function getWebGLContext(canvas) function getWebGLContext(canvas) {
{
const params = { alpha: true, depth: false, stencil: false, antialias: false, preserveDrawingBuffer: false }; const params = { alpha: true, depth: false, stencil: false, antialias: false, preserveDrawingBuffer: false };
let gl = canvas.getContext('webgl2', params); let gl = canvas.getContext('webgl2', params);
@ -102,12 +104,10 @@ function getWebGLContext(canvas)
let halfFloat; let halfFloat;
let supportLinearFiltering; let supportLinearFiltering;
if (isWebGL2) if (isWebGL2) {
{
gl.getExtension('EXT_color_buffer_float'); gl.getExtension('EXT_color_buffer_float');
supportLinearFiltering = gl.getExtension('OES_texture_float_linear'); supportLinearFiltering = gl.getExtension('OES_texture_float_linear');
} else } else {
{
halfFloat = gl.getExtension('OES_texture_half_float'); halfFloat = gl.getExtension('OES_texture_half_float');
supportLinearFiltering = gl.getExtension('OES_texture_half_float_linear'); supportLinearFiltering = gl.getExtension('OES_texture_half_float_linear');
} }
@ -119,14 +119,12 @@ function getWebGLContext(canvas)
let formatRG; let formatRG;
let formatR; let formatR;
if (isWebGL2) if (isWebGL2) {
{
formatRGBA = getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, halfFloatTexType); formatRGBA = getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, halfFloatTexType);
formatRG = getSupportedFormat(gl, gl.RG16F, gl.RG, halfFloatTexType); formatRG = getSupportedFormat(gl, gl.RG16F, gl.RG, halfFloatTexType);
formatR = getSupportedFormat(gl, gl.R16F, gl.RED, halfFloatTexType); formatR = getSupportedFormat(gl, gl.R16F, gl.RED, halfFloatTexType);
} }
else else {
{
formatRGBA = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType); formatRGBA = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
formatRG = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType); formatRG = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
formatR = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType); formatR = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
@ -144,12 +142,9 @@ function getWebGLContext(canvas)
}; };
} }
function getSupportedFormat(gl, internalFormat, format, type) function getSupportedFormat(gl, internalFormat, format, type) {
{ if (!supportRenderTextureFormat(gl, internalFormat, format, type)) {
if (!supportRenderTextureFormat(gl, internalFormat, format, type)) switch (internalFormat) {
{
switch (internalFormat)
{
case gl.R16F: case gl.R16F:
return getSupportedFormat(gl, gl.RG16F, gl.RG, type); return getSupportedFormat(gl, gl.RG16F, gl.RG, type);
case gl.RG16F: case gl.RG16F:
@ -165,8 +160,7 @@ function getSupportedFormat(gl, internalFormat, format, type)
}; };
} }
function supportRenderTextureFormat(gl, internalFormat, format, type) function supportRenderTextureFormat(gl, internalFormat, format, type) {
{
let texture = gl.createTexture(); let texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture); gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
@ -183,13 +177,11 @@ function supportRenderTextureFormat(gl, internalFormat, format, type)
return status == gl.FRAMEBUFFER_COMPLETE; return status == gl.FRAMEBUFFER_COMPLETE;
} }
function isMobile() function isMobile() {
{
return /Mobi|Android/i.test(navigator.userAgent); return /Mobi|Android/i.test(navigator.userAgent);
} }
function captureScreenshot() function captureScreenshot() {
{
let res = getResolution(config.CAPTURE_RESOLUTION); let res = getResolution(config.CAPTURE_RESOLUTION);
let target = createFBO(res.width, res.height, ext.formatRGBA.internalFormat, ext.formatRGBA.format, ext.halfFloatTexType, gl.NEAREST); let target = createFBO(res.width, res.height, ext.formatRGBA.internalFormat, ext.formatRGBA.format, ext.halfFloatTexType, gl.NEAREST);
render(target); render(target);
@ -203,8 +195,7 @@ function captureScreenshot()
URL.revokeObjectURL(datauri); URL.revokeObjectURL(datauri);
} }
function framebufferToTexture(target) function framebufferToTexture(target) {
{
gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo); gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo);
let length = target.width * target.height * 4; let length = target.width * target.height * 4;
let texture = new Float32Array(length); let texture = new Float32Array(length);
@ -212,14 +203,11 @@ function framebufferToTexture(target)
return texture; return texture;
} }
function normalizeTexture(texture, width, height) function normalizeTexture(texture, width, height) {
{
let result = new Uint8Array(texture.length); let result = new Uint8Array(texture.length);
let id = 0; let id = 0;
for (let i = height - 1; i >= 0; i--) for (let i = height - 1; i >= 0; i--) {
{ for (let j = 0; j < width; j++) {
for (let j = 0; j < width; j++)
{
let nid = i * width * 4 + j * 4; let nid = i * width * 4 + j * 4;
result[nid + 0] = clamp01(texture[id + 0]) * 255; result[nid + 0] = clamp01(texture[id + 0]) * 255;
result[nid + 1] = clamp01(texture[id + 1]) * 255; result[nid + 1] = clamp01(texture[id + 1]) * 255;
@ -231,13 +219,11 @@ function normalizeTexture(texture, width, height)
return result; return result;
} }
function clamp01(input) function clamp01(input) {
{
return Math.min(Math.max(input, 0), 1); return Math.min(Math.max(input, 0), 1);
} }
function textureToCanvas(texture, width, height) function textureToCanvas(texture, width, height) {
{
let captureCanvas = document.createElement('canvas'); let captureCanvas = document.createElement('canvas');
let ctx = captureCanvas.getContext('2d'); let ctx = captureCanvas.getContext('2d');
captureCanvas.width = width; captureCanvas.width = width;
@ -250,10 +236,8 @@ function textureToCanvas(texture, width, height)
return captureCanvas; return captureCanvas;
} }
class Material class Material {
{ constructor(vertexShader, fragmentShaderSource) {
constructor(vertexShader, fragmentShaderSource)
{
this.vertexShader = vertexShader; this.vertexShader = vertexShader;
this.fragmentShaderSource = fragmentShaderSource; this.fragmentShaderSource = fragmentShaderSource;
this.programs = []; this.programs = [];
@ -261,15 +245,13 @@ class Material
this.uniforms = []; this.uniforms = [];
} }
setKeywords(keywords) setKeywords(keywords) {
{
let hash = 0; let hash = 0;
for (let i = 0; i < keywords.length; i++) for (let i = 0; i < keywords.length; i++)
hash += hashCode(keywords[i]); hash += hashCode(keywords[i]);
let program = this.programs[hash]; let program = this.programs[hash];
if (program == null) if (program == null) {
{
let fragmentShader = compileShader(gl.FRAGMENT_SHADER, this.fragmentShaderSource, keywords); let fragmentShader = compileShader(gl.FRAGMENT_SHADER, this.fragmentShaderSource, keywords);
program = createProgram(this.vertexShader, fragmentShader); program = createProgram(this.vertexShader, fragmentShader);
this.programs[hash] = program; this.programs[hash] = program;
@ -281,29 +263,24 @@ class Material
this.activeProgram = program; this.activeProgram = program;
} }
bind() bind() {
{
gl.useProgram(this.activeProgram); gl.useProgram(this.activeProgram);
} }
} }
class Program class Program {
{ constructor(vertexShader, fragmentShader) {
constructor(vertexShader, fragmentShader)
{
this.uniforms = {}; this.uniforms = {};
this.program = createProgram(vertexShader, fragmentShader); this.program = createProgram(vertexShader, fragmentShader);
this.uniforms = getUniforms(this.program); this.uniforms = getUniforms(this.program);
} }
bind() bind() {
{
gl.useProgram(this.program); gl.useProgram(this.program);
} }
} }
function createProgram(vertexShader, fragmentShader) function createProgram(vertexShader, fragmentShader) {
{
let program = gl.createProgram(); let program = gl.createProgram();
gl.attachShader(program, vertexShader); gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader); gl.attachShader(program, fragmentShader);
@ -315,20 +292,17 @@ function createProgram(vertexShader, fragmentShader)
return program; return program;
} }
function getUniforms(program) function getUniforms(program) {
{
let uniforms = []; let uniforms = [];
let uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); let uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
for (let i = 0; i < uniformCount; i++) for (let i = 0; i < uniformCount; i++) {
{
let uniformName = gl.getActiveUniform(program, i).name; let uniformName = gl.getActiveUniform(program, i).name;
uniforms[uniformName] = gl.getUniformLocation(program, uniformName); uniforms[uniformName] = gl.getUniformLocation(program, uniformName);
} }
return uniforms; return uniforms;
} }
function compileShader(type, source, keywords) function compileShader(type, source, keywords) {
{
source = addKeywords(source, keywords); source = addKeywords(source, keywords);
const shader = gl.createShader(type); const shader = gl.createShader(type);
@ -341,12 +315,10 @@ function compileShader(type, source, keywords)
return shader; return shader;
}; };
function addKeywords(source, keywords) function addKeywords(source, keywords) {
{
if (keywords == null) return source; if (keywords == null) return source;
let keywordsString = ''; let keywordsString = '';
keywords.forEach(keyword => keywords.forEach(keyword => {
{
keywordsString += '#define ' + keyword + '\n'; keywordsString += '#define ' + keyword + '\n';
}); });
return keywordsString + source; return keywordsString + source;
@ -827,8 +799,7 @@ const gradientSubtractShader = compileShader(gl.FRAGMENT_SHADER, `
} }
`); `);
const blit = (() => const blit = (() => {
{
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]), gl.STATIC_DRAW); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]), gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer()); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
@ -836,20 +807,16 @@ const blit = (() =>
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(0); gl.enableVertexAttribArray(0);
return (target, clear = false) => return (target, clear = false) => {
{ if (target == null) {
if (target == null)
{
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.bindFramebuffer(gl.FRAMEBUFFER, null);
} }
else else {
{
gl.viewport(0, 0, target.width, target.height); gl.viewport(0, 0, target.width, target.height);
gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo); gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo);
} }
if (clear) if (clear) {
{
gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT); gl.clear(gl.COLOR_BUFFER_BIT);
} }
@ -858,8 +825,7 @@ const blit = (() =>
}; };
})(); })();
function CHECK_FRAMEBUFFER_STATUS() function CHECK_FRAMEBUFFER_STATUS() {
{
let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (status != gl.FRAMEBUFFER_COMPLETE) if (status != gl.FRAMEBUFFER_COMPLETE)
console.trace("Framebuffer error: " + status); console.trace("Framebuffer error: " + status);
@ -897,8 +863,7 @@ const gradienSubtractProgram = new Program(baseVertexShader, gradientSubtractSha
const displayMaterial = new Material(baseVertexShader, displayShaderSource); const displayMaterial = new Material(baseVertexShader, displayShaderSource);
function initFramebuffers() function initFramebuffers() {
{
let simRes = getResolution(config.SIM_RESOLUTION); let simRes = getResolution(config.SIM_RESOLUTION);
let dyeRes = getResolution(config.DYE_RESOLUTION); let dyeRes = getResolution(config.DYE_RESOLUTION);
@ -928,8 +893,7 @@ function initFramebuffers()
initSunraysFramebuffers(); initSunraysFramebuffers();
} }
function initBloomFramebuffers() function initBloomFramebuffers() {
{
let res = getResolution(config.BLOOM_RESOLUTION); let res = getResolution(config.BLOOM_RESOLUTION);
const texType = ext.halfFloatTexType; const texType = ext.halfFloatTexType;
@ -939,8 +903,7 @@ function initBloomFramebuffers()
bloom = createFBO(res.width, res.height, rgba.internalFormat, rgba.format, texType, filtering); bloom = createFBO(res.width, res.height, rgba.internalFormat, rgba.format, texType, filtering);
bloomFramebuffers.length = 0; bloomFramebuffers.length = 0;
for (let i = 0; i < config.BLOOM_ITERATIONS; i++) for (let i = 0; i < config.BLOOM_ITERATIONS; i++) {
{
let width = res.width >> (i + 1); let width = res.width >> (i + 1);
let height = res.height >> (i + 1); let height = res.height >> (i + 1);
@ -951,8 +914,7 @@ function initBloomFramebuffers()
} }
} }
function initSunraysFramebuffers() function initSunraysFramebuffers() {
{
let res = getResolution(config.SUNRAYS_RESOLUTION); let res = getResolution(config.SUNRAYS_RESOLUTION);
const texType = ext.halfFloatTexType; const texType = ext.halfFloatTexType;
@ -963,8 +925,7 @@ function initSunraysFramebuffers()
sunraysTemp = createFBO(res.width, res.height, r.internalFormat, r.format, texType, filtering); sunraysTemp = createFBO(res.width, res.height, r.internalFormat, r.format, texType, filtering);
} }
function createFBO(w, h, internalFormat, format, type, param) function createFBO(w, h, internalFormat, format, type, param) {
{
gl.activeTexture(gl.TEXTURE0); gl.activeTexture(gl.TEXTURE0);
let texture = gl.createTexture(); let texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture); gl.bindTexture(gl.TEXTURE_2D, texture);
@ -990,8 +951,7 @@ function createFBO(w, h, internalFormat, format, type, param)
height: h, height: h,
texelSizeX, texelSizeX,
texelSizeY, texelSizeY,
attach(id) attach(id) {
{
gl.activeTexture(gl.TEXTURE0 + id); gl.activeTexture(gl.TEXTURE0 + id);
gl.bindTexture(gl.TEXTURE_2D, texture); gl.bindTexture(gl.TEXTURE_2D, texture);
return id; return id;
@ -999,8 +959,7 @@ function createFBO(w, h, internalFormat, format, type, param)
}; };
} }
function createDoubleFBO(w, h, internalFormat, format, type, param) function createDoubleFBO(w, h, internalFormat, format, type, param) {
{
let fbo1 = createFBO(w, h, internalFormat, format, type, param); let fbo1 = createFBO(w, h, internalFormat, format, type, param);
let fbo2 = createFBO(w, h, internalFormat, format, type, param); let fbo2 = createFBO(w, h, internalFormat, format, type, param);
@ -1009,24 +968,19 @@ function createDoubleFBO(w, h, internalFormat, format, type, param)
height: h, height: h,
texelSizeX: fbo1.texelSizeX, texelSizeX: fbo1.texelSizeX,
texelSizeY: fbo1.texelSizeY, texelSizeY: fbo1.texelSizeY,
get read() get read() {
{
return fbo1; return fbo1;
}, },
set read(value) set read(value) {
{
fbo1 = value; fbo1 = value;
}, },
get write() get write() {
{
return fbo2; return fbo2;
}, },
set write(value) set write(value) {
{
fbo2 = value; fbo2 = value;
}, },
swap() swap() {
{
let temp = fbo1; let temp = fbo1;
fbo1 = fbo2; fbo1 = fbo2;
fbo2 = temp; fbo2 = temp;
@ -1034,8 +988,7 @@ function createDoubleFBO(w, h, internalFormat, format, type, param)
}; };
} }
function resizeFBO(target, w, h, internalFormat, format, type, param) function resizeFBO(target, w, h, internalFormat, format, type, param) {
{
let newFBO = createFBO(w, h, internalFormat, format, type, param); let newFBO = createFBO(w, h, internalFormat, format, type, param);
copyProgram.bind(); copyProgram.bind();
gl.uniform1i(copyProgram.uniforms.uTexture, target.attach(0)); gl.uniform1i(copyProgram.uniforms.uTexture, target.attach(0));
@ -1043,8 +996,7 @@ function resizeFBO(target, w, h, internalFormat, format, type, param)
return newFBO; return newFBO;
} }
function resizeDoubleFBO(target, w, h, internalFormat, format, type, param) function resizeDoubleFBO(target, w, h, internalFormat, format, type, param) {
{
if (target.width == w && target.height == h) if (target.width == w && target.height == h)
return target; return target;
target.read = resizeFBO(target.read, w, h, internalFormat, format, type, param); target.read = resizeFBO(target.read, w, h, internalFormat, format, type, param);
@ -1056,8 +1008,7 @@ function resizeDoubleFBO(target, w, h, internalFormat, format, type, param)
return target; return target;
} }
function createTextureAsync(url) function createTextureAsync(url) {
{
let texture = gl.createTexture(); let texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture); gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
@ -1070,8 +1021,7 @@ function createTextureAsync(url)
texture, texture,
width: 1, width: 1,
height: 1, height: 1,
attach(id) attach(id) {
{
gl.activeTexture(gl.TEXTURE0 + id); gl.activeTexture(gl.TEXTURE0 + id);
gl.bindTexture(gl.TEXTURE_2D, texture); gl.bindTexture(gl.TEXTURE_2D, texture);
return id; return id;
@ -1079,8 +1029,7 @@ function createTextureAsync(url)
}; };
let image = new Image(); let image = new Image();
image.onload = () => image.onload = () => {
{
obj.width = image.width; obj.width = image.width;
obj.height = image.height; obj.height = image.height;
gl.bindTexture(gl.TEXTURE_2D, texture); gl.bindTexture(gl.TEXTURE_2D, texture);
@ -1091,8 +1040,7 @@ function createTextureAsync(url)
return obj; return obj;
} }
function updateKeywords() function updateKeywords() {
{
let displayKeywords = []; let displayKeywords = [];
if (config.SHADING) displayKeywords.push("SHADING"); if (config.SHADING) displayKeywords.push("SHADING");
if (config.BLOOM) displayKeywords.push("BLOOM"); if (config.BLOOM) displayKeywords.push("BLOOM");
@ -1108,8 +1056,7 @@ let lastUpdateTime = Date.now();
let colorUpdateTimer = 0.0; let colorUpdateTimer = 0.0;
update(); update();
function update() function update() {
{
const dt = calcDeltaTime(); const dt = calcDeltaTime();
if (resizeCanvas()) if (resizeCanvas())
initFramebuffers(); initFramebuffers();
@ -1121,8 +1068,7 @@ function update()
requestAnimationFrame(update); requestAnimationFrame(update);
} }
function calcDeltaTime() function calcDeltaTime() {
{
let now = Date.now(); let now = Date.now();
let dt = (now - lastUpdateTime) / 1000; let dt = (now - lastUpdateTime) / 1000;
dt = Math.min(dt, 0.016666); dt = Math.min(dt, 0.016666);
@ -1130,12 +1076,10 @@ function calcDeltaTime()
return dt; return dt;
} }
function resizeCanvas() function resizeCanvas() {
{
let width = scaleByPixelRatio(canvas.clientWidth); let width = scaleByPixelRatio(canvas.clientWidth);
let height = scaleByPixelRatio(canvas.clientHeight); let height = scaleByPixelRatio(canvas.clientHeight);
if (canvas.width != width || canvas.height != height) if (canvas.width != width || canvas.height != height) {
{
canvas.width = width; canvas.width = width;
canvas.height = height; canvas.height = height;
return true; return true;
@ -1143,38 +1087,31 @@ function resizeCanvas()
return false; return false;
} }
function updateColors(dt) function updateColors(dt) {
{
if (!config.COLORFUL) return; if (!config.COLORFUL) return;
colorUpdateTimer += dt * config.COLOR_UPDATE_SPEED; colorUpdateTimer += dt * config.COLOR_UPDATE_SPEED;
if (colorUpdateTimer >= 1) if (colorUpdateTimer >= 1) {
{
colorUpdateTimer = wrap(colorUpdateTimer, 0, 1); colorUpdateTimer = wrap(colorUpdateTimer, 0, 1);
pointers.forEach(p => pointers.forEach(p => {
{
p.color = generateColor(); p.color = generateColor();
}); });
} }
} }
function applyInputs() function applyInputs() {
{
if (splatStack.length > 0) if (splatStack.length > 0)
multipleSplats(splatStack.pop()); multipleSplats(splatStack.pop());
pointers.forEach(p => pointers.forEach(p => {
{ if (p.moved) {
if (p.moved)
{
p.moved = false; p.moved = false;
splatPointer(p); splatPointer(p);
} }
}); });
} }
function step(dt) function step(dt) {
{
gl.disable(gl.BLEND); gl.disable(gl.BLEND);
curlProgram.bind(); curlProgram.bind();
@ -1205,8 +1142,7 @@ function step(dt)
pressureProgram.bind(); pressureProgram.bind();
gl.uniform2f(pressureProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); gl.uniform2f(pressureProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
gl.uniform1i(pressureProgram.uniforms.uDivergence, divergence.attach(0)); gl.uniform1i(pressureProgram.uniforms.uDivergence, divergence.attach(0));
for (let i = 0; i < config.PRESSURE_ITERATIONS; i++) for (let i = 0; i < config.PRESSURE_ITERATIONS; i++) {
{
gl.uniform1i(pressureProgram.uniforms.uPressure, pressure.read.attach(1)); gl.uniform1i(pressureProgram.uniforms.uPressure, pressure.read.attach(1));
blit(pressure.write); blit(pressure.write);
pressure.swap(); pressure.swap();
@ -1240,23 +1176,19 @@ function step(dt)
dye.swap(); dye.swap();
} }
function render(target) function render(target) {
{
if (config.BLOOM) if (config.BLOOM)
applyBloom(dye.read, bloom); applyBloom(dye.read, bloom);
if (config.SUNRAYS) if (config.SUNRAYS) {
{
applySunrays(dye.read, dye.write, sunrays); applySunrays(dye.read, dye.write, sunrays);
blur(sunrays, sunraysTemp, 1); blur(sunrays, sunraysTemp, 1);
} }
if (target == null || !config.TRANSPARENT) if (target == null || !config.TRANSPARENT) {
{
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.BLEND); gl.enable(gl.BLEND);
} }
else else {
{
gl.disable(gl.BLEND); gl.disable(gl.BLEND);
} }
@ -1267,22 +1199,19 @@ function render(target)
drawDisplay(target); drawDisplay(target);
} }
function drawColor(target, color) function drawColor(target, color) {
{
colorProgram.bind(); colorProgram.bind();
gl.uniform4f(colorProgram.uniforms.color, color.r, color.g, color.b, 1); gl.uniform4f(colorProgram.uniforms.color, color.r, color.g, color.b, 1);
blit(target); blit(target);
} }
function drawCheckerboard(target) function drawCheckerboard(target) {
{
checkerboardProgram.bind(); checkerboardProgram.bind();
gl.uniform1f(checkerboardProgram.uniforms.aspectRatio, canvas.width / canvas.height); gl.uniform1f(checkerboardProgram.uniforms.aspectRatio, canvas.width / canvas.height);
blit(target); blit(target);
} }
function drawDisplay(target) function drawDisplay(target) {
{
let width = target == null ? gl.drawingBufferWidth : target.width; let width = target == null ? gl.drawingBufferWidth : target.width;
let height = target == null ? gl.drawingBufferHeight : target.height; let height = target == null ? gl.drawingBufferHeight : target.height;
@ -1290,8 +1219,7 @@ function drawDisplay(target)
if (config.SHADING) if (config.SHADING)
gl.uniform2f(displayMaterial.uniforms.texelSize, 1.0 / width, 1.0 / height); gl.uniform2f(displayMaterial.uniforms.texelSize, 1.0 / width, 1.0 / height);
gl.uniform1i(displayMaterial.uniforms.uTexture, dye.read.attach(0)); gl.uniform1i(displayMaterial.uniforms.uTexture, dye.read.attach(0));
if (config.BLOOM) if (config.BLOOM) {
{
gl.uniform1i(displayMaterial.uniforms.uBloom, bloom.attach(1)); gl.uniform1i(displayMaterial.uniforms.uBloom, bloom.attach(1));
gl.uniform1i(displayMaterial.uniforms.uDithering, ditheringTexture.attach(2)); gl.uniform1i(displayMaterial.uniforms.uDithering, ditheringTexture.attach(2));
let scale = getTextureScale(ditheringTexture, width, height); let scale = getTextureScale(ditheringTexture, width, height);
@ -1302,8 +1230,7 @@ function drawDisplay(target)
blit(target); blit(target);
} }
function applyBloom(source, destination) function applyBloom(source, destination) {
{
if (bloomFramebuffers.length < 2) if (bloomFramebuffers.length < 2)
return; return;
@ -1321,8 +1248,7 @@ function applyBloom(source, destination)
blit(last); blit(last);
bloomBlurProgram.bind(); bloomBlurProgram.bind();
for (let i = 0; i < bloomFramebuffers.length; i++) for (let i = 0; i < bloomFramebuffers.length; i++) {
{
let dest = bloomFramebuffers[i]; let dest = bloomFramebuffers[i];
gl.uniform2f(bloomBlurProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY); gl.uniform2f(bloomBlurProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY);
gl.uniform1i(bloomBlurProgram.uniforms.uTexture, last.attach(0)); gl.uniform1i(bloomBlurProgram.uniforms.uTexture, last.attach(0));
@ -1333,8 +1259,7 @@ function applyBloom(source, destination)
gl.blendFunc(gl.ONE, gl.ONE); gl.blendFunc(gl.ONE, gl.ONE);
gl.enable(gl.BLEND); gl.enable(gl.BLEND);
for (let i = bloomFramebuffers.length - 2; i >= 0; i--) for (let i = bloomFramebuffers.length - 2; i >= 0; i--) {
{
let baseTex = bloomFramebuffers[i]; let baseTex = bloomFramebuffers[i];
gl.uniform2f(bloomBlurProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY); gl.uniform2f(bloomBlurProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY);
gl.uniform1i(bloomBlurProgram.uniforms.uTexture, last.attach(0)); gl.uniform1i(bloomBlurProgram.uniforms.uTexture, last.attach(0));
@ -1351,8 +1276,7 @@ function applyBloom(source, destination)
blit(destination); blit(destination);
} }
function applySunrays(source, mask, destination) function applySunrays(source, mask, destination) {
{
gl.disable(gl.BLEND); gl.disable(gl.BLEND);
sunraysMaskProgram.bind(); sunraysMaskProgram.bind();
gl.uniform1i(sunraysMaskProgram.uniforms.uTexture, source.attach(0)); gl.uniform1i(sunraysMaskProgram.uniforms.uTexture, source.attach(0));
@ -1364,11 +1288,9 @@ function applySunrays(source, mask, destination)
blit(destination); blit(destination);
} }
function blur(target, temp, iterations) function blur(target, temp, iterations) {
{
blurProgram.bind(); blurProgram.bind();
for (let i = 0; i < iterations; i++) for (let i = 0; i < iterations; i++) {
{
gl.uniform2f(blurProgram.uniforms.texelSize, target.texelSizeX, 0.0); gl.uniform2f(blurProgram.uniforms.texelSize, target.texelSizeX, 0.0);
gl.uniform1i(blurProgram.uniforms.uTexture, target.attach(0)); gl.uniform1i(blurProgram.uniforms.uTexture, target.attach(0));
blit(temp); blit(temp);
@ -1379,17 +1301,14 @@ function blur(target, temp, iterations)
} }
} }
function splatPointer(pointer) function splatPointer(pointer) {
{
let dx = pointer.deltaX * config.SPLAT_FORCE; let dx = pointer.deltaX * config.SPLAT_FORCE;
let dy = pointer.deltaY * config.SPLAT_FORCE; let dy = pointer.deltaY * config.SPLAT_FORCE;
splat(pointer.texcoordX, pointer.texcoordY, dx, dy, pointer.color); splat(pointer.texcoordX, pointer.texcoordY, dx, dy, pointer.color);
} }
function multipleSplats(amount) function multipleSplats(amount) {
{ for (let i = 0; i < amount; i++) {
for (let i = 0; i < amount; i++)
{
const color = generateColor(); const color = generateColor();
color.r *= 10.0; color.r *= 10.0;
color.g *= 10.0; color.g *= 10.0;
@ -1402,8 +1321,7 @@ function multipleSplats(amount)
} }
} }
function splat(x, y, dx, dy, color) function splat(x, y, dx, dy, color) {
{
splatProgram.bind(); splatProgram.bind();
gl.uniform1i(splatProgram.uniforms.uTarget, velocity.read.attach(0)); gl.uniform1i(splatProgram.uniforms.uTarget, velocity.read.attach(0));
gl.uniform1f(splatProgram.uniforms.aspectRatio, canvas.width / canvas.height); gl.uniform1f(splatProgram.uniforms.aspectRatio, canvas.width / canvas.height);
@ -1419,16 +1337,14 @@ function splat(x, y, dx, dy, color)
dye.swap(); dye.swap();
} }
function correctRadius(radius) function correctRadius(radius) {
{
let aspectRatio = canvas.width / canvas.height; let aspectRatio = canvas.width / canvas.height;
if (aspectRatio > 1) if (aspectRatio > 1)
radius *= aspectRatio; radius *= aspectRatio;
return radius; return radius;
} }
canvas.addEventListener('mousedown', e => canvas.addEventListener('mousedown', e => {
{
let posX = scaleByPixelRatio(e.offsetX); let posX = scaleByPixelRatio(e.offsetX);
let posY = scaleByPixelRatio(e.offsetY); let posY = scaleByPixelRatio(e.offsetY);
let pointer = pointers.find(p => p.id == -1); let pointer = pointers.find(p => p.id == -1);
@ -1437,41 +1353,34 @@ canvas.addEventListener('mousedown', e =>
updatePointerDownData(pointer, -1, posX, posY); updatePointerDownData(pointer, -1, posX, posY);
}); });
setTimeout(() => setTimeout(() => {
{ canvas.addEventListener('mousemove', e => {
canvas.addEventListener('mousemove', e =>
{
let posX = scaleByPixelRatio(e.offsetX); let posX = scaleByPixelRatio(e.offsetX);
let posY = scaleByPixelRatio(e.offsetY); let posY = scaleByPixelRatio(e.offsetY);
updatePointerMoveData(pointers[0], posX, posY); updatePointerMoveData(pointers[0], posX, posY);
}); });
}, 500); }, 500);
window.addEventListener('mouseup', () => window.addEventListener('mouseup', () => {
{
updatePointerUpData(pointers[0]); updatePointerUpData(pointers[0]);
}); });
canvas.addEventListener('touchstart', e => canvas.addEventListener('touchstart', e => {
{
e.preventDefault(); e.preventDefault();
const touches = e.targetTouches; const touches = e.targetTouches;
while (touches.length >= pointers.length) while (touches.length >= pointers.length)
pointers.push(new pointerPrototype()); pointers.push(new pointerPrototype());
for (let i = 0; i < touches.length; i++) for (let i = 0; i < touches.length; i++) {
{
let posX = scaleByPixelRatio(touches[i].pageX); let posX = scaleByPixelRatio(touches[i].pageX);
let posY = scaleByPixelRatio(touches[i].pageY); let posY = scaleByPixelRatio(touches[i].pageY);
updatePointerDownData(pointers[i + 1], touches[i].identifier, posX, posY); updatePointerDownData(pointers[i + 1], touches[i].identifier, posX, posY);
} }
}); });
canvas.addEventListener('touchmove', e => canvas.addEventListener('touchmove', e => {
{
e.preventDefault(); e.preventDefault();
const touches = e.targetTouches; const touches = e.targetTouches;
for (let i = 0; i < touches.length; i++) for (let i = 0; i < touches.length; i++) {
{
let pointer = pointers[i + 1]; let pointer = pointers[i + 1];
if (!pointer.down) continue; if (!pointer.down) continue;
let posX = scaleByPixelRatio(touches[i].pageX); let posX = scaleByPixelRatio(touches[i].pageX);
@ -1480,27 +1389,23 @@ canvas.addEventListener('touchmove', e =>
} }
}, false); }, false);
window.addEventListener('touchend', e => window.addEventListener('touchend', e => {
{
const touches = e.changedTouches; const touches = e.changedTouches;
for (let i = 0; i < touches.length; i++) for (let i = 0; i < touches.length; i++) {
{
let pointer = pointers.find(p => p.id == touches[i].identifier); let pointer = pointers.find(p => p.id == touches[i].identifier);
if (pointer == null) continue; if (pointer == null) continue;
updatePointerUpData(pointer); updatePointerUpData(pointer);
} }
}); });
window.addEventListener('keydown', e => window.addEventListener('keydown', e => {
{
if (e.code === 'KeyP') if (e.code === 'KeyP')
config.PAUSED = !config.PAUSED; config.PAUSED = !config.PAUSED;
if (e.key === ' ') if (e.key === ' ')
splatStack.push(parseInt(Math.random() * 20) + 5); splatStack.push(parseInt(Math.random() * 20) + 5);
}); });
function updatePointerDownData(pointer, id, posX, posY) function updatePointerDownData(pointer, id, posX, posY) {
{
pointer.id = id; pointer.id = id;
pointer.down = true; pointer.down = true;
pointer.moved = false; pointer.moved = false;
@ -1513,8 +1418,7 @@ function updatePointerDownData(pointer, id, posX, posY)
pointer.color = generateColor(); pointer.color = generateColor();
} }
function updatePointerMoveData(pointer, posX, posY) function updatePointerMoveData(pointer, posX, posY) {
{
pointer.prevTexcoordX = pointer.texcoordX; pointer.prevTexcoordX = pointer.texcoordX;
pointer.prevTexcoordY = pointer.texcoordY; pointer.prevTexcoordY = pointer.texcoordY;
pointer.texcoordX = posX / canvas.width; pointer.texcoordX = posX / canvas.width;
@ -1524,27 +1428,23 @@ function updatePointerMoveData(pointer, posX, posY)
pointer.moved = Math.abs(pointer.deltaX) > 0 || Math.abs(pointer.deltaY) > 0; pointer.moved = Math.abs(pointer.deltaX) > 0 || Math.abs(pointer.deltaY) > 0;
} }
function updatePointerUpData(pointer) function updatePointerUpData(pointer) {
{
pointer.down = false; pointer.down = false;
} }
function correctDeltaX(delta) function correctDeltaX(delta) {
{
let aspectRatio = canvas.width / canvas.height; let aspectRatio = canvas.width / canvas.height;
if (aspectRatio < 1) delta *= aspectRatio; if (aspectRatio < 1) delta *= aspectRatio;
return delta; return delta;
} }
function correctDeltaY(delta) function correctDeltaY(delta) {
{
let aspectRatio = canvas.width / canvas.height; let aspectRatio = canvas.width / canvas.height;
if (aspectRatio > 1) delta /= aspectRatio; if (aspectRatio > 1) delta /= aspectRatio;
return delta; return delta;
} }
function generateColor() function generateColor() {
{
let c = HSVtoRGB(Math.random(), 1.0, 1.0); let c = HSVtoRGB(Math.random(), 1.0, 1.0);
c.r *= 0.15; c.r *= 0.15;
c.g *= 0.15; c.g *= 0.15;
@ -1552,8 +1452,7 @@ function generateColor()
return c; return c;
} }
function HSVtoRGB(h, s, v) function HSVtoRGB(h, s, v) {
{
let r, g, b, i, f, p, q, t; let r, g, b, i, f, p, q, t;
i = Math.floor(h * 6); i = Math.floor(h * 6);
f = h * 6 - i; f = h * 6 - i;
@ -1561,8 +1460,7 @@ function HSVtoRGB(h, s, v)
q = v * (1 - f * s); q = v * (1 - f * s);
t = v * (1 - (1 - f) * s); t = v * (1 - (1 - f) * s);
switch (i % 6) switch (i % 6) {
{
case 0: r = v, g = t, b = p; break; case 0: r = v, g = t, b = p; break;
case 1: r = q, g = v, b = p; break; case 1: r = q, g = v, b = p; break;
case 2: r = p, g = v, b = t; break; case 2: r = p, g = v, b = t; break;
@ -1578,8 +1476,7 @@ function HSVtoRGB(h, s, v)
}; };
} }
function normalizeColor(input) function normalizeColor(input) {
{
let output = { let output = {
r: input.r / 255, r: input.r / 255,
g: input.g / 255, g: input.g / 255,
@ -1588,15 +1485,13 @@ function normalizeColor(input)
return output; return output;
} }
function wrap(value, min, max) function wrap(value, min, max) {
{
let range = max - min; let range = max - min;
if (range == 0) return min; if (range == 0) return min;
return (value - min) % range + min; return (value - min) % range + min;
} }
function getResolution(resolution) function getResolution(resolution) {
{
let aspectRatio = gl.drawingBufferWidth / gl.drawingBufferHeight; let aspectRatio = gl.drawingBufferWidth / gl.drawingBufferHeight;
if (aspectRatio < 1) if (aspectRatio < 1)
aspectRatio = 1.0 / aspectRatio; aspectRatio = 1.0 / aspectRatio;
@ -1610,26 +1505,22 @@ function getResolution(resolution)
return { width: min, height: max }; return { width: min, height: max };
} }
function getTextureScale(texture, width, height) function getTextureScale(texture, width, height) {
{
return { return {
x: width / texture.width, x: width / texture.width,
y: height / texture.height y: height / texture.height
}; };
} }
function scaleByPixelRatio(input) function scaleByPixelRatio(input) {
{
let pixelRatio = window.devicePixelRatio || 1; let pixelRatio = window.devicePixelRatio || 1;
return Math.floor(input * pixelRatio); return Math.floor(input * pixelRatio);
} }
function hashCode(s) function hashCode(s) {
{
if (s.length == 0) return 0; if (s.length == 0) return 0;
let hash = 0; let hash = 0;
for (let i = 0; i < s.length; i++) for (let i = 0; i < s.length; i++) {
{
hash = (hash << 5) - hash + s.charCodeAt(i); hash = (hash << 5) - hash + s.charCodeAt(i);
hash |= 0; // Convert to 32bit integer hash |= 0; // Convert to 32bit integer
} }