From bf2eace6fea5516fb4b70e56ef033b06669cc4b3 Mon Sep 17 00:00:00 2001 From: "A.Olokhtonov" Date: Fri, 10 May 2024 15:06:29 +0300 Subject: [PATCH] Grid background pattern --- client/index.js | 4 ++- client/webgl_draw.js | 61 +++++++++++++++++++++++++++----- client/webgl_geometry.js | 24 +++++++++++++ client/webgl_shaders.js | 75 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 154 insertions(+), 10 deletions(-) diff --git a/client/index.js b/client/index.js index 0115db1..c686b5e 100644 --- a/client/index.js +++ b/client/index.js @@ -14,7 +14,7 @@ const config = { buffer_first_touchmoves: 5, debug_print: false, zoom_delta: 0.05, - min_zoom_level: -150, + min_zoom_level: -250, max_zoom_level: 40, initial_offline_timeout: 1000, default_color: 0x00, @@ -230,6 +230,8 @@ async function main() { 'color_picked': null, 'wasm': {}, + + 'background_pattern': 'dots', }; const context = { diff --git a/client/webgl_draw.js b/client/webgl_draw.js index 9613ed0..9b9e83b 100644 --- a/client/webgl_draw.js +++ b/client/webgl_draw.js @@ -103,11 +103,12 @@ async function draw(state, context) { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Draw the background pattern - gl.useProgram(context.programs['pattern'].dots); - buffers = context.buffers['pattern']; - locations = context.locations['pattern'].dots; - { - gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance']); + if (state.background_pattern === 'dots') { + gl.useProgram(context.programs['pattern'].dots); + buffers = context.buffers['pattern']; + locations = context.locations['pattern'].dots; + + gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance_dot']); gl.enableVertexAttribArray(locations['a_center']); gl.vertexAttribPointer(locations['a_center'], 2, gl.FLOAT, false, 2 * 4, 0); gl.vertexAttribDivisor(locations['a_center'], 1); @@ -129,7 +130,7 @@ async function draw(state, context) { gl.uniform1f(locations['u_fadeout'], t); - gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance']); + gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance_dot']); gl.bufferData(gl.ARRAY_BUFFER, dot_instances, gl.STREAM_DRAW); gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, dot_instances.length / 2); @@ -142,14 +143,58 @@ async function draw(state, context) { gl.uniform1f(locations['u_fadeout'], t); - gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance']); + gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance_dot']); gl.bufferData(gl.ARRAY_BUFFER, dot_instances, gl.STREAM_DRAW); gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, dot_instances.length / 2); } + } else if (state.background_pattern === 'grid') { + const zoom = state.canvas.zoom; + const zoom_log10 = Math.log10(zoom); + const zoom_previous = Math.pow(10, Math.floor(zoom_log10)); + + gl.useProgram(context.programs['pattern'].grid); + buffers = context.buffers['pattern']; + locations = context.locations['pattern'].grid; + + gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance_grid']); + gl.enableVertexAttribArray(locations['a_data']); + gl.vertexAttribPointer(locations['a_data'], 2, gl.FLOAT, false, 2 * 4, 0); + gl.vertexAttribDivisor(locations['a_data'], 1); + + gl.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height); + gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom); + gl.uniform2f(locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y); + gl.uniform1f(locations['u_fadeout'], 1.0); + + // Previous level + { + const grid_instances = new Float32Array(geometry_gen_fullscreen_grid_1d(state, context, 10 / zoom_previous, 10 / zoom_previous)); + const t = Math.min(1.0, (zoom / zoom_previous) / 10.0); + + gl.uniform1f(locations['u_fadeout'], t); + + gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance_grid']); + gl.bufferData(gl.ARRAY_BUFFER, grid_instances, gl.STREAM_DRAW); + + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, grid_instances.length / 2); + } + + // Next level + { + const grid_instances = new Float32Array(geometry_gen_fullscreen_grid_1d(state, context, 100 / zoom_previous, 100 / zoom_previous)); + const t = Math.min(1.0, 1.0 - (zoom / zoom_previous) / 10.0); + + gl.uniform1f(locations['u_fadeout'], t); + + gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance_grid']); + gl.bufferData(gl.ARRAY_BUFFER, grid_instances, gl.STREAM_DRAW); + + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, grid_instances.length / 2); + } } - gl.clear(gl.DEPTH_BUFFER_BIT); + gl.clear(gl.DEPTH_BUFFER_BIT); // draw strokes above the background pattern gl.useProgram(context.programs['sdf'].main); buffers = context.buffers['sdf']; locations = context.locations['sdf'].main; diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js index c4c0578..e881107 100644 --- a/client/webgl_geometry.js +++ b/client/webgl_geometry.js @@ -333,3 +333,27 @@ function geometry_gen_fullscreen_grid(state, context, step_x, step_y) { return result; } + +function geometry_gen_fullscreen_grid_1d(state, context, step_x, step_y) { + const result = []; + const width = context.canvas.width; + const height = context.canvas.height; + const topleft = screen_to_canvas(state, {'x': 0, 'y': 0}); + const bottomright = screen_to_canvas(state, {'x': width, 'y': height}); + + topleft.x = Math.floor(topleft.x / step_x) * step_x; + topleft.y = Math.ceil(topleft.y / step_y) * step_y; + + bottomright.x = Math.floor(bottomright.x / step_x) * step_x; + bottomright.y = Math.ceil(bottomright.y / step_y) * step_y; + + for (let x = topleft.x; x <= bottomright.x; x += step_x) { + result.push(1, x); + } + + for (let y = topleft.y; y <= bottomright.y; y += step_y) { + result.push(-1, y); + } + + return result; +} diff --git a/client/webgl_shaders.js b/client/webgl_shaders.js index 1bc311b..d4f5120 100644 --- a/client/webgl_shaders.js +++ b/client/webgl_shaders.js @@ -249,6 +249,66 @@ const tquad_fs_src = `#version 300 es } `; +const grid_vs_src = `#version 300 es + in vec2 a_data; // per-instance + + out float v_fadeout; + + uniform vec2 u_scale; + uniform vec2 u_res; + uniform vec2 u_translation; + uniform float u_fadeout; + + void main() { + vec2 origin; + vec2 minor_offset; + vec2 major_offset; + + vec2 pixel = 2.0 / u_res; + + if (a_data.x > 0.0) { + // Vertical, treat Y as X + float x = a_data.y; + origin = vec2(x, 0.0); + minor_offset = pixel * vec2(1.0, 0.0); + major_offset = vec2(0.0, 2.0); + } else { + // Horizontal, treat Y as Y + float y = a_data.y; + origin = vec2(0.0, y); + minor_offset = pixel * vec2(0.0, 1.0); + major_offset = vec2(2.0, 0.0); + } + + vec2 v = (origin * u_scale + u_translation) / u_res * 2.0; + vec2 pos; + + if (a_data.x > 0.0) { + v.y = 0.0; + } else { + v.x = 0.0; + } + + if (gl_VertexID % 6 == 0) { + pos = v; + } else if (gl_VertexID % 6 == 1 || gl_VertexID % 6 == 5) { + pos = v + (a_data.x > 0.0 ? minor_offset : major_offset); + //pos = v + minor_offset; + } else if (gl_VertexID % 6 == 2 || gl_VertexID % 6 == 4) { + pos = v + (a_data.x > 0.0 ? major_offset : minor_offset); + //pos = v + major_offset; + } else if (gl_VertexID % 6 == 3) { + pos = v + major_offset + minor_offset; + //pos = v + major_offset + minor_offset; + } + + vec2 screen02 = pos; + screen02.y = 2.0 - screen02.y; + v_fadeout = u_fadeout; + gl_Position = vec4(screen02 - 1.0, 0.0, 1.0); + } +`; + const dots_vs_src = `#version 300 es in vec2 a_center; // per-instance @@ -334,6 +394,8 @@ function init_webgl(state, context) { const dots_vs = create_shader(gl, gl.VERTEX_SHADER, dots_vs_src); const dots_fs = create_shader(gl, gl.FRAGMENT_SHADER, dots_fs_src); + const grid_vs = create_shader(gl, gl.VERTEX_SHADER, grid_vs_src); + context.programs['image'] = create_program(gl, quad_vs, quad_fs); context.programs['debug'] = create_program(gl, simple_vs, simple_fs); context.programs['sdf'] = { @@ -342,6 +404,7 @@ function init_webgl(state, context) { }; context.programs['pattern'] = { 'dots': create_program(gl, dots_vs, dots_fs), + 'grid': create_program(gl, grid_vs, dots_fs), }; context.locations['debug'] = { @@ -389,6 +452,15 @@ function init_webgl(state, context) { 'u_scale': gl.getUniformLocation(context.programs['pattern'].dots, 'u_scale'), 'u_translation': gl.getUniformLocation(context.programs['pattern'].dots, 'u_translation'), 'u_fadeout': gl.getUniformLocation(context.programs['pattern'].dots, 'u_fadeout'), + }, + + 'grid': { + 'a_data': gl.getAttribLocation(context.programs['pattern'].grid, 'a_data'), + + 'u_res': gl.getUniformLocation(context.programs['pattern'].grid, 'u_res'), + 'u_scale': gl.getUniformLocation(context.programs['pattern'].grid, 'u_scale'), + 'u_translation': gl.getUniformLocation(context.programs['pattern'].grid, 'u_translation'), + 'u_fadeout': gl.getUniformLocation(context.programs['pattern'].grid, 'u_fadeout'), } }; @@ -402,7 +474,8 @@ function init_webgl(state, context) { }; context.buffers['pattern'] = { - 'b_instance': gl.createBuffer(), + 'b_instance_dot': gl.createBuffer(), + 'b_instance_grid': gl.createBuffer(), 'b_dot': gl.createBuffer(), };