From 7011cc86be6ea80f4b28551e75827a3cafc5581d Mon Sep 17 00:00:00 2001 From: "A.Olokhtonov" Date: Sun, 9 Apr 2023 20:43:21 +0300 Subject: [PATCH] Some kind of shitty webgl line renderer --- client/cursor.js | 20 ++- client/draw.js | 6 + client/index.js | 16 ++- client/math.js | 6 +- client/texput.log | 21 +++ client/webgl.html | 24 ++++ client/webgl.js | 358 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 437 insertions(+), 14 deletions(-) create mode 100644 client/texput.log create mode 100644 client/webgl.html create mode 100644 client/webgl.js diff --git a/client/cursor.js b/client/cursor.js index ede264d..f1bd462 100644 --- a/client/cursor.js +++ b/client/cursor.js @@ -4,8 +4,8 @@ function on_down(e) { // Scroll wheel (mouse button 3) if (e.button === 1) { - storage.state.moving = true; - storage.state.mousedown = true; + // storage.state.moving = true; + // storage.state.mousedown = true; return; } @@ -225,8 +225,18 @@ function on_leave(e) { } function on_resize(e) { - storage.canvas.max_scroll_x = storage.canvas.width - window.innerWidth; - storage.canvas.max_scroll_y = storage.canvas.height - window.innerHeight; + const width = window.innerWidth; + const height = window.innerHeight; + + elements.canvas0.width = elements.canvas1.width = width; + elements.canvas0.height = elements.canvas1.height = height; + + storage.ctx1.lineJoin = storage.ctx1.lineCap = storage.ctx0.lineJoin = storage.ctx0.lineCap = 'round'; + storage.ctx1.lineWidth = storage.ctx0.lineWidth = storage.cursor.width; + + redraw_region({'xmin': 0, 'xmax': width, 'ymin': 0, 'ymax': width}); + // storage.canvas.max_scroll_x = storage.canvas.width - window.innerWidth; + // storage.canvas.max_scroll_y = storage.canvas.height - window.innerHeight; } async function on_drop(e) { @@ -257,6 +267,8 @@ async function on_drop(e) { } function on_wheel(e) { + return; + const x = Math.round((e.clientX + storage.canvas.offset_x) / storage.canvas.zoom); const y = Math.round((e.clientY + storage.canvas.offset_y) / storage.canvas.zoom); diff --git a/client/draw.js b/client/draw.js index fb2189c..f4768d8 100644 --- a/client/draw.js +++ b/client/draw.js @@ -43,6 +43,8 @@ function predraw_user(user_id, event) { } function redraw_region(bbox) { + // const start = performance.now(); + if (bbox.xmin === bbox.xmax || bbox.ymin === bbox.ymax) { return; } @@ -63,4 +65,8 @@ function redraw_region(bbox) { } storage.ctx0.restore(); + + // const end = performance.now(); + + // if (config.debug_print) console.debug(`Redraw took ${end - start}ms`); } \ No newline at end of file diff --git a/client/index.js b/client/index.js index cb63690..408ac93 100644 --- a/client/index.js +++ b/client/index.js @@ -307,17 +307,18 @@ function main() { update_brush(); - storage.canvas.offset_x = window.scrollX; - storage.canvas.offset_y = window.scrollY; + // storage.canvas.offset_x = window.scrollX; + // storage.canvas.offset_y = window.scrollY; - storage.canvas.max_scroll_x = storage.canvas.width - window.innerWidth; - storage.canvas.max_scroll_y = storage.canvas.height - window.innerHeight; + // storage.canvas.max_scroll_x = storage.canvas.width - window.innerWidth; + // storage.canvas.max_scroll_y = storage.canvas.height - window.innerHeight; storage.ctx0 = elements.canvas0.getContext('2d'); storage.ctx1 = elements.canvas1.getContext('2d'); - storage.ctx1.canvas.width = storage.ctx0.canvas.width = storage.canvas.width; - storage.ctx1.canvas.height = storage.ctx0.canvas.height = storage.canvas.height; + on_resize(); + // storage.ctx1.canvas.width = storage.ctx0.canvas.width = storage.canvas.width; + // storage.ctx1.canvas.height = storage.ctx0.canvas.height = storage.canvas.height; storage.ctx1.lineJoin = storage.ctx1.lineCap = storage.ctx0.lineJoin = storage.ctx0.lineCap = 'round'; storage.ctx1.lineWidth = storage.ctx0.lineWidth = storage.cursor.width; @@ -328,7 +329,6 @@ function main() { elements.toucher.addEventListener('keydown', on_keydown); elements.toucher.addEventListener('keyup', on_keyup); - elements.toucher.addEventListener('resize', on_resize); elements.toucher.addEventListener('contextmenu', cancel); elements.toucher.addEventListener('wheel', on_wheel); @@ -343,4 +343,6 @@ function main() { elements.canvas0.addEventListener('dragover', on_move); elements.canvas0.addEventListener('drop', on_drop); elements.canvas0.addEventListener('mouseleave', on_leave); + + window.addEventListener('resize', on_resize); } diff --git a/client/math.js b/client/math.js index fc4489f..736e794 100644 --- a/client/math.js +++ b/client/math.js @@ -1,5 +1,5 @@ function rdp_find_max(points, start, end) { - const EPS = 0.5; + const EPS = 0.25; let result = -1; let max_dist = 0; @@ -75,8 +75,8 @@ function process_ewmv(points, round = false) { } function process_stroke(points) { - const result0 = process_ewmv(points); - const result1 = process_rdp(result0, true); + // const result0 = process_ewmv(points); + const result1 = process_rdp(points, true); return result1; } diff --git a/client/texput.log b/client/texput.log new file mode 100644 index 0000000..14bf232 --- /dev/null +++ b/client/texput.log @@ -0,0 +1,21 @@ +This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022/Debian) (preloaded format=pdflatex 2023.3.25) 8 APR 2023 22:14 +entering extended mode + restricted \write18 enabled. + %&-line parsing enabled. +** + +! Emergency stop. +<*> + +End of file on the terminal! + + +Here is how much of TeX's memory you used: + 3 strings out of 476091 + 111 string characters out of 5794081 + 1849330 words of memory out of 5000000 + 20488 multiletter control sequences out of 15000+600000 + 512287 words of font info for 32 fonts, out of 8000000 for 9000 + 1141 hyphenation exceptions out of 8191 + 0i,0n,0p,1b,6s stack positions out of 10000i,1000n,20000p,200000b,200000s +! ==> Fatal error occurred, no output PDF file produced! diff --git a/client/webgl.html b/client/webgl.html new file mode 100644 index 0000000..4ac192c --- /dev/null +++ b/client/webgl.html @@ -0,0 +1,24 @@ + + + + + Desk + + + + + + + + + + + \ No newline at end of file diff --git a/client/webgl.js b/client/webgl.js new file mode 100644 index 0000000..4924eb9 --- /dev/null +++ b/client/webgl.js @@ -0,0 +1,358 @@ +document.addEventListener('DOMContentLoaded', main); + +const vertex_shader_source = ` + attribute vec2 pos; + + uniform vec2 u_scale; + uniform vec2 u_res; + uniform vec2 u_translation; + uniform int u_layer; + + void main() { + vec2 screen01 = (pos * u_scale + u_translation) / u_res; + vec2 screen02 = screen01 * 2.0; + screen02.y = 2.0 - screen02.y; + vec2 screen11 = screen02 - 1.0; + gl_Position = vec4(screen11, u_layer, 1); + } +`; + +const fragment_shader_source = ` + precision mediump float; + + uniform vec3 u_color; + + void main() { + gl_FragColor = vec4(u_color, 1); + } +`; + +function create_shader(gl, type, source) { + const shader = gl.createShader(type); + + gl.shaderSource(shader, source); + gl.compileShader(shader); + + if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + return shader; + } + + console.error(type, ':', gl.getShaderInfoLog(shader)); + + gl.deleteShader(shader); +} + +function create_program(gl, vs, fs) { + const program = gl.createProgram(); + + gl.attachShader(program, vs); + gl.attachShader(program, fs); + gl.linkProgram(program); + + if (gl.getProgramParameter(program, gl.LINK_STATUS)) { + return program; + } + + console.error('link:', gl.getProgramInfoLog(program)); + + gl.deleteProgram(program); +} + +function perpendicular(ax, ay, bx, by, width) { + // Place points at (stroke_width / 2) distance from the line + // The direction is an average of perpenducalars to the previous and next points + // if (i === 0) { + const dirx = bx - ax; + const diry = by - ay; + + let pdirx = diry; + let pdiry = -dirx; + + const pdir_norm = Math.sqrt(pdirx * pdirx + pdiry * pdiry); + + pdirx /= pdir_norm; + pdiry /= pdir_norm; + + return { + 'p1': { + 'x': ax + pdirx * width / 2, + 'y': ay + pdiry * width / 2, + }, + + 'p2': { + 'x': ax - pdirx * width / 2, + 'y': ay - pdiry * width / 2, + } + }; +} + +const canvas_offset = { 'x': 0, 'y': 0 }; +let moving = false; +let spacedown = false; +let drawing = false; +let canvas_zoom = 1.0; +let current_stroke = []; + +function push_stroke_positions(stroke, stroke_width, positions) { + let last_x1; + let last_y1; + let last_x2; + let last_y2; + + const points = stroke.points; + + for (let i = 0; i < points.length; ++i) { + const px = points[i].x; + const py = points[i].y; + + // These might be undefined + let nextpx; + let nextpy; + + if (i < points.length - 1) { + nextpx = points[i + 1].x; + nextpy = points[i + 1].y; + } + + if (i === 0) { + const pps = perpendicular(px, py, nextpx, nextpy, stroke_width); + last_x1 = pps.p1.x; + last_y1 = pps.p1.y; + last_x2 = pps.p2.x; + last_y2 = pps.p2.y; + continue; + } + + // Place points at (stroke_width / 2) distance from the line + const prevpx = points[i - 1].x; + const prevpy = points[i - 1].y; + + let x1; + let y1; + let x2; + let y2; + + if (i < points.length - 1) { + const pps1 = perpendicular(px, py, nextpx, nextpy, stroke_width); + const pps2 = perpendicular(px, py, prevpx, prevpy, stroke_width); + + const dp1x = (pps1.p2.x - pps1.p1.x); + const dp1y = (pps1.p2.y - pps1.p1.y); + + const dp2x = (pps2.p2.x - pps2.p1.x); + const dp2y = (pps2.p2.y - pps2.p1.y); + + if (dp1x * dp2x + dp1y * dp2y < 0) { + x1 = (pps1.p1.x + pps2.p2.x) / 2.0; + y1 = (pps1.p1.y + pps2.p2.y) / 2.0; + + x2 = (pps1.p2.x + pps2.p1.x) / 2.0; + y2 = (pps1.p2.y + pps2.p1.y) / 2.0; + } else { + x1 = (pps1.p1.x + pps2.p1.x) / 2.0; + y1 = (pps1.p1.y + pps2.p1.y) / 2.0; + + x2 = (pps1.p2.x + pps2.p2.x) / 2.0; + y2 = (pps1.p2.y + pps2.p2.y) / 2.0; + } + } else { + const pps = perpendicular(px, py, prevpx, prevpy, stroke_width); + + x1 = pps.p2.x; + y1 = pps.p2.y; + x2 = pps.p1.x; + y2 = pps.p1.y; + } + + positions.push(last_x1, last_y1); + positions.push(x2, y2); + positions.push(last_x2, last_y2); + + positions.push(last_x1, last_y1); + positions.push(x1, y1); + positions.push(x2, y2); + + last_x1 = x1; + last_y1 = y1; + last_x2 = x2; + last_y2 = y2; + } +} + +function draw(gl, program, locations, buffers, strokes) { + const width = window.innerWidth; + const height = window.innerHeight; + + if (gl.canvas.width !== width || gl.canvas.height !== height) { + gl.canvas.width = width; + gl.canvas.height = height; + gl.viewport(0, 0, width, height); + } + + gl.clearColor(0, 0, 0, 1); + gl.clear(gl.COLOR_BUFFER_BIT); + gl.useProgram(program); + gl.enableVertexAttribArray(locations['pos']); + + gl.uniform2f(locations['u_res'], width, height); + gl.uniform2f(locations['u_scale'], canvas_zoom, canvas_zoom); + gl.uniform2f(locations['u_translation'], canvas_offset.x, canvas_offset.y); + + const positions = []; + const stroke_width = 4; + + for (const stroke of strokes) { + push_stroke_positions(stroke, stroke_width, positions); + } + + if (current_stroke.length > 0) { + push_stroke_positions({'points': current_stroke}, stroke_width, positions); + } + + gl.bindBuffer(gl.ARRAY_BUFFER, buffers['pos']); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); + + { + // Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER) + const size = 2; // 2 components per iteration + const type = gl.FLOAT; // the data is 32bit floats + const normalize = false; // don't normalize the data + const stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position + const offset = 0; // start at the beginning of the buffer + gl.vertexAttribPointer(locations['pos'], size, type, normalize, stride, offset); + } + + { + const offset = 0; + const count = positions.length / 2; + gl.uniform3f(locations['u_color'], 0.2, 0.2, 0.2); + gl.uniform1i(locations['u_layer'], 0); + gl.drawArrays(gl.TRIANGLES, offset, count); + + gl.uniform3f(locations['u_color'], 1, 0, 0); + gl.uniform1i(locations['u_layer'], 1); + gl.drawArrays(gl.POINTS, offset, count); + } + + window.requestAnimationFrame(() => draw(gl, program, locations, buffers, strokes)); +} + +function main() { + const canvas = document.querySelector('#c'); + const gl = canvas.getContext('webgl'); + + if (!gl) { + console.error('FUCK!') + return; + } + + const vertex_shader = create_shader(gl, gl.VERTEX_SHADER, vertex_shader_source); + const fragment_shader = create_shader(gl, gl.FRAGMENT_SHADER, fragment_shader_source); + const program = create_program(gl, vertex_shader, fragment_shader) + + const locations = {}; + const buffers = {}; + + locations['pos'] = gl.getAttribLocation(program, 'pos'); + locations['u_res'] = gl.getUniformLocation(program, 'u_res'); + locations['u_scale'] = gl.getUniformLocation(program, 'u_scale'); + locations['u_translation'] = gl.getUniformLocation(program, 'u_translation'); + locations['u_color'] = gl.getUniformLocation(program, 'u_color'); + locations['u_layer'] = gl.getUniformLocation(program, 'u_layer'); + + buffers['pos'] = gl.createBuffer(); + + const strokes = [ + { + 'points': [ + {'x': 100, 'y': 100}, + {'x': 200, 'y': 200}, + {'x': 300, 'y': 100}, + ] + } + ] + + window.addEventListener('keydown', (e) => { + if (e.code === 'Space') { + spacedown = true; + } + }); + + window.addEventListener('keyup', (e) => { + if (e.code === 'Space') { + spacedown = false; + moving = false; + } + }); + + canvas.addEventListener('mousedown', (e) => { + if (spacedown) { + moving = true; + return; + } + + const x = cursor_x = (e.clientX - canvas_offset.x) / canvas_zoom; + const y = cursor_y = (e.clientY - canvas_offset.y) / canvas_zoom; + + current_stroke.length = 0; + current_stroke.push({'x': x, 'y': y}); + drawing = true; + }); + + canvas.addEventListener('mousemove', (e) => { + if (moving) { + canvas_offset.x += e.movementX; + canvas_offset.y += e.movementY; + return; + } + + if (drawing) { + const x = cursor_x = (e.clientX - canvas_offset.x) / canvas_zoom; + const y = cursor_y = (e.clientY - canvas_offset.y) / canvas_zoom; + + current_stroke.push({'x': x, 'y': y}); + } + }); + + canvas.addEventListener('mouseup', (e) => { + if (spacedown) { + moving = false; + return; + } + + if (drawing) { + strokes.push({'points': process_stroke(current_stroke)}); + current_stroke.length = 0; + drawing = false; + return; + } + }); + + canvas.addEventListener('wheel', (e) => { + const x = Math.round((e.clientX - canvas_offset.x) / canvas_zoom); + const y = Math.round((e.clientY - canvas_offset.y) / canvas_zoom); + + const dz = (e.deltaY < 0 ? 0.1 : -0.1); + const old_zoom = canvas_zoom; + + canvas_zoom *= (1.0 + dz); + + if (canvas_zoom > 10.0) { + canvas_zoom = old_zoom; + return; + } + + if (canvas_zoom < 0.2) { + canvas_zoom = old_zoom; + return; + } + + const zoom_offset_x = Math.round((dz * old_zoom) * x); + const zoom_offset_y = Math.round((dz * old_zoom) * y); + + canvas_offset.x -= zoom_offset_x; + canvas_offset.y -= zoom_offset_y; + }); + + window.requestAnimationFrame(() => draw(gl, program, locations, buffers, strokes)); +} \ No newline at end of file