diff --git a/client/index.js b/client/index.js
index ef16ee1..9fe280b 100644
--- a/client/index.js
+++ b/client/index.js
@@ -15,7 +15,7 @@ const config = {
initial_offline_timeout: 1000,
default_color: 0x00,
default_width: 8,
- bytes_per_point: 20,
+ bytes_per_point: 8,
initial_static_bytes: 4096,
};
@@ -68,6 +68,7 @@ function main() {
'moves': 0,
'drawing': false,
'moving': false,
+ 'erasing': false,
'waiting_for_second_finger': false,
'first_finger_position': null,
'second_finger_position': null,
@@ -113,11 +114,9 @@ function main() {
'locations': {},
'textures': {},
- 'quad_positions': [],
- 'quad_texcoords': [],
-
- 'static_stroke_serializer': serializer_create(config.initial_static_bytes),
- 'dynamic_stroke_serializer': serializer_create(config.initial_static_bytes),
+ 'point_serializer': serializer_create(config.initial_static_bytes),
+ 'index_serializer': serializer_create(config.initial_static_bytes),
+ 'quad_serializer': serializer_create(config.initial_static_bytes),
'bgcolor': {'r': 1.0, 'g': 1.0, 'b': 1.0},
diff --git a/client/index.log b/client/index.log
new file mode 100644
index 0000000..ae24f14
--- /dev/null
+++ b/client/index.log
@@ -0,0 +1,36 @@
+This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022/Debian) (preloaded format=pdflatex 2023.4.13) 27 APR 2023 21:39
+entering extended mode
+ restricted \write18 enabled.
+ %&-line parsing enabled.
+**/code/desk2/client/index.js
+(/code/desk2/client/index.js
+LaTeX2e <2022-11-01> patch level 1
+L3 programming layer <2023-01-16>
+
+! LaTeX Error: Missing \begin{document}.
+
+See the LaTeX manual or LaTeX Companion for explanation.
+Type H for immediate help.
+ ...
+
+l.1 d
+ ocument.addEventListener('DOMContentLoaded', main);
+?
+! Emergency stop.
+ ...
+
+l.1 d
+ ocument.addEventListener('DOMContentLoaded', main);
+You're in trouble here. Try typing to proceed.
+If that doesn't work, type X to quit.
+
+
+Here is how much of TeX's memory you used:
+ 18 strings out of 476091
+ 566 string characters out of 5794081
+ 1849330 words of memory out of 5000000
+ 20499 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
+ 13i,0n,12p,112b,14s stack positions out of 10000i,1000n,20000p,200000b,200000s
+! ==> Fatal error occurred, no output PDF file produced!
diff --git a/client/math.js b/client/math.js
index 666f4b6..09353fe 100644
--- a/client/math.js
+++ b/client/math.js
@@ -6,11 +6,6 @@ function screen_to_canvas(state, p) {
return {'x': xc, 'y': yc};
}
-function point_right_of_line(a, b, p) {
- // a bit of cross-product tomfoolery (we check sign of z of the crossproduct)
- return ((b.x - a.x) * (a.y - p.y) - (a.y - b.y) * (p.x - a.x)) <= 0;
-}
-
function rdp_find_max(state, points, start, end) {
const EPS = 0.5 / state.canvas.zoom;
// const EPS = 10.0;
@@ -94,78 +89,28 @@ function process_stroke(state, points) {
return result1;
}
-function stroke_stats(points, width) {
- if (points.length === 0) {
- const bbox = {
- 'xmin': 0,
- 'ymin': 0,
- 'xmax': 0,
- 'ymax': 0
- };
-
- return {
- 'bbox': bbox,
- 'length': 0,
- };
- }
+function strokes_intersect_line(state, a, b) {
+ // TODO: handle stroke / eraser width
+ const result = [];
+
+ for (let i = 0; i < state.events.length; ++i) {
+ const event = state.events[i];
+ if (event.type === EVENT.STROKE && !event.deleted) {
+ for (let i = 0; i < event.points.length - 1; ++i) {
+ const c = event.points[i + 0];
+ const d = event.points[i + 1];
- let length = 0;
- let xmin = points[0].x, ymin = points[0].y;
- let xmax = xmin, ymax = ymin;
-
- for (let i = 0; i < points.length; ++i) {
- const point = points[i];
- if (point.x < xmin) xmin = point.x;
- if (point.y < ymin) ymin = point.y;
- if (point.x > xmax) xmax = point.x;
- if (point.y > ymax) ymax = point.y;
-
- if (i > 0) {
- const last = points[i - 1];
- const dx = point.x - last.x;
- const dy = point.y - last.y;
- length += Math.sqrt(dx * dx + dy * dy);
+ if (segments_intersect(a, b, c, d)) {
+ result.push(i);
+ break;
+ }
+ }
}
}
- xmin -= width;
- ymin -= width;
- xmax += width * 2;
- ymax += width * 2;
-
- const bbox = {
- 'xmin': Math.floor(xmin),
- 'ymin': Math.floor(ymin),
- 'xmax': Math.ceil(xmax),
- 'ymax': Math.ceil(ymax)
- };
-
- return {
- 'bbox': bbox,
- 'length': length,
- };
-}
-
-function rectangles_intersect(a, b) {
- const result = (
- a.xmin <= b.xmax
- && a.xmax >= b.xmin
- && a.ymin <= b.ymax
- && a.ymax >= b.ymin
- );
-
return result;
}
-function stroke_intersects_region(points, bbox) {
- if (points.length === 0) {
- return false;
- }
-
- const stats = stroke_stats(points, storage.cursor.width);
- return rectangles_intersect(stats.bbox, bbox);
-}
-
function color_to_u32(color_str) {
const r = parseInt(color_str.substring(0, 2), 16);
const g = parseInt(color_str.substring(2, 4), 16);
@@ -199,39 +144,6 @@ function segments_intersect(A, B, C, D) {
return ccw(A, C, D) != ccw(B, C, D) && ccw(A, B, C) !== ccw(A, B, D);
}
-function strokes_intersect_line(x1, y1, x2, y2) {
- const result = [];
-
- for (const event of storage.events) {
- if (event.type === EVENT.STROKE && !event.deleted) {
- if (event.points.length < 2) {
- continue;
- }
-
- for (let i = 0; i < event.points.length - 1; ++i) {
- const sx1 = event.points[i].x;
- const sy1 = event.points[i].y;
-
- const sx2 = event.points[i + 1].x;
- const sy2 = event.points[i + 1].y;
-
- const A = {'x': x1, 'y': y1};
- const B = {'x': x2, 'y': y2};
-
- const C = {'x': sx1, 'y': sy1};
- const D = {'x': sx2, 'y': sy2};
-
- if (segments_intersect(A, B, C, D)) {
- result.push(event.stroke_id);
- break;
- }
- }
- }
- }
-
- return result;
-}
-
function dist_v2(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
@@ -243,30 +155,4 @@ function mid_v2(a, b) {
'x': (a.x + b.x) / 2.0,
'y': (a.y + b.y) / 2.0,
};
-}
-
-function perpendicular(ax, ay, bx, by, width) {
- // Place points at (stroke_width / 2) distance from the line
- 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,
- }
- };
}
\ No newline at end of file
diff --git a/client/webgl_draw.js b/client/webgl_draw.js
index 2433740..9434f35 100644
--- a/client/webgl_draw.js
+++ b/client/webgl_draw.js
@@ -11,7 +11,7 @@ function draw(state, context) {
const gl = context.gl;
const width = window.innerWidth;
const height = window.innerHeight;
-
+
let locations;
let buffers;
@@ -19,74 +19,96 @@ function draw(state, context) {
gl.clearColor(context.bgcolor.r, context.bgcolor.g, context.bgcolor.b, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
- // Images
- locations = context.locations['quad'];
- buffers = context.buffers['quad'];
+ // SDF
+ locations = context.locations['sdf'];
+ buffers = context.buffers['sdf'];
+ textures = context.textures['sdf'];
- gl.useProgram(context.programs['quad']);
-
- gl.enableVertexAttribArray(locations['a_pos']);
- gl.enableVertexAttribArray(locations['a_texcoord']);
+ gl.useProgram(context.programs['sdf']);
- 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.uniform1i(locations['u_texture'], 0);
-
- gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_pos']);
- gl.vertexAttribPointer(locations['a_pos'], 2, gl.FLOAT, false, 0, 0);
- gl.bufferData(gl.ARRAY_BUFFER, context.quad_positions_f32, gl.STATIC_DRAW);
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_packed']);
- gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_texcoord']);
- gl.vertexAttribPointer(locations['a_texcoord'], 2, gl.FLOAT, false, 0, 0);
- gl.bufferData(gl.ARRAY_BUFFER, context.quad_texcoords_f32, gl.STATIC_DRAW);
+ gl.enableVertexAttribArray(locations['a_pos']);
+ gl.enableVertexAttribArray(locations['a_color']);
- const count = Object.keys(context.textures).length;
- let active_image_index = -1;
+ gl.vertexAttribPointer(locations['a_pos'], 2, gl.FLOAT, false, 4 * 3, 0);
+ gl.vertexAttribPointer(locations['a_color'], 3, gl.UNSIGNED_BYTE, true, 4 * 3, 4 * 2);
- gl.uniform1i(locations['u_outline'], 0);
+ gl.activeTexture(gl.TEXTURE0 + 0);
+ gl.bindTexture(gl.TEXTURE_2D, textures['points']);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
- for (let key = 0; key < count; ++key) {
- if (context.textures[key].image_id === context.active_image) {
- active_image_index = key;
- continue;
- }
+ const npoints = context.point_serializer.offset / (4 * 2);
+ const nstrokes = context.quad_serializer.offset / (6 * 3 * 4);
- gl.bindTexture(gl.TEXTURE_2D, context.textures[key].texture);
- gl.drawArrays(gl.TRIANGLES, key * 6, 6);
+ // TOOD: if points changed
+ if (true) {
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RG32F, npoints, 1, 0, gl.RG, gl.FLOAT, new Float32Array(context.point_serializer.buffer, 0, context.point_serializer.offset / 4));
}
- if (active_image_index !== -1) {
- gl.uniform1i(locations['u_outline'], 1);
- gl.bindTexture(gl.TEXTURE_2D, context.textures[active_image_index].texture);
- gl.drawArrays(gl.TRIANGLES, active_image_index * 6, 6);
- }
+ gl.activeTexture(gl.TEXTURE0 + 1);
+ gl.bindTexture(gl.TEXTURE_2D, textures['indices']);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
- // Strokes
- locations = context.locations['stroke'];
- buffers = context.buffers['stroke'];
-
- gl.useProgram(context.programs['stroke']);
-
- gl.enableVertexAttribArray(locations['a_type']);
- gl.enableVertexAttribArray(locations['a_pos']);
- gl.enableVertexAttribArray(locations['a_texcoord']);
- gl.enableVertexAttribArray(locations['a_color']);
+ // TOOD: if points changed
+ if (true) {
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RG32UI, nstrokes, 1, 0, gl.RG_INTEGER, gl.UNSIGNED_INT, new Uint32Array(context.index_serializer.buffer, 0, context.index_serializer.offset / 4));
+ }
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.uniform1i(locations['u_texture_points'], 0);
+ gl.uniform1i(locations['u_texture_indices'], 1);
- gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_packed']);
-
- gl.vertexAttribPointer(locations['a_pos'], 2, gl.FLOAT, false, config.bytes_per_point, 0);
- gl.vertexAttribPointer(locations['a_texcoord'], 2, gl.FLOAT, false, config.bytes_per_point, 8);
- gl.vertexAttribPointer(locations['a_color'], 3, gl.UNSIGNED_BYTE, true, config.bytes_per_point, 16);
- gl.vertexAttribPointer(locations['a_type'], 1, gl.UNSIGNED_BYTE, false, config.bytes_per_point, 19);
+ gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array(context.quad_serializer.buffer, 0, context.quad_serializer.offset), gl.STATIC_DRAW);
+ gl.drawArrays(gl.TRIANGLES, 0, nstrokes * 6);
- gl.bufferData(gl.ARRAY_BUFFER, context.static_stroke_serializer.buffer, gl.STATIC_DRAW);
- gl.drawArrays(gl.TRIANGLES, 0, context.static_stroke_serializer.offset / config.bytes_per_point);
+ // Images
+ // locations = context.locations['image'];
+ // buffers = context.buffers['image'];
+ // textures = context.textures['image'];
- gl.bufferData(gl.ARRAY_BUFFER, context.dynamic_stroke_serializer.buffer, gl.STATIC_DRAW);
- gl.drawArrays(gl.TRIANGLES, 0, context.dynamic_stroke_serializer.offset / config.bytes_per_point);
+ // gl.useProgram(context.programs['image']);
+
+ // gl.enableVertexAttribArray(locations['a_pos']);
+ // gl.enableVertexAttribArray(locations['a_texcoord']);
+
+ // 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.uniform1i(locations['u_texture'], 0);
+
+ // if (context.quad_positions_f32.byteLength > 0) {
+ // gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_pos']);
+ // gl.vertexAttribPointer(locations['a_pos'], 2, gl.FLOAT, false, 0, 0);
+ // gl.bufferData(gl.ARRAY_BUFFER, context.quad_positions_f32, gl.STATIC_DRAW);
+
+ // gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_texcoord']);
+ // gl.vertexAttribPointer(locations['a_texcoord'], 2, gl.FLOAT, false, 0, 0);
+ // gl.bufferData(gl.ARRAY_BUFFER, context.quad_texcoords_f32, gl.STATIC_DRAW);
+ // }
+
+ // const count = Object.keys(textures).length;
+ // let active_image_index = -1;
+
+ // gl.uniform1i(locations['u_outline'], 0);
+
+ // for (let key = 0; key < count; ++key) {
+ // if (textures[key].image_id === context.active_image) {
+ // active_image_index = key;
+ // continue;
+ // }
+
+ // gl.bindTexture(gl.TEXTURE_2D, textures[key].texture);
+ // gl.drawArrays(gl.TRIANGLES, key * 6, 6);
+ // }
+
+ // if (active_image_index !== -1) {
+ // gl.uniform1i(locations['u_outline'], 1);
+ // gl.bindTexture(gl.TEXTURE_2D, textures[active_image_index].texture);
+ // gl.drawArrays(gl.TRIANGLES, active_image_index * 6, 6);
+ // }
}
\ No newline at end of file
diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js
index f1ea6a7..6da0866 100644
--- a/client/webgl_geometry.js
+++ b/client/webgl_geometry.js
@@ -1,38 +1,28 @@
-function push_point(s, x, y, u, v, r, g, b, type) {
+function push_point_xy(s, x, y) {
+ ser_f32(s, x);
+ ser_f32(s, y);
+}
+
+function push_point_xyrgb(s, x, y, r, g, b) {
ser_f32(s, x);
ser_f32(s, y);
- ser_f32(s, u);
- ser_f32(s, v);
- // ser_u8(s, Math.floor(Math.random() * 255));
- // ser_u8(s, Math.floor(Math.random() * 255));
- // ser_u8(s, Math.floor(Math.random() * 255));
ser_u8(s, r);
ser_u8(s, g);
ser_u8(s, b);
- ser_u8(s, type);
-}
-
-function push_circle(s, cx, cy, radius, r, g, b) {
- push_point(s, cx - radius, cy - radius, 0, 0, r, g, b, 1);
- push_point(s, cx - radius, cy + radius, 0, 1, r, g, b, 1);
- push_point(s, cx + radius, cy - radius, 1, 0, r, g, b, 1);
-
- push_point(s, cx + radius, cy + radius, 1, 1, r, g, b, 1);
- push_point(s, cx + radius, cy - radius, 1, 0, r, g, b, 1);
- push_point(s, cx - radius, cy + radius, 0, 1, r, g, b, 1);
+ ser_align(s, 4);
}
-function push_quad(s, p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y, r, g, b) {
- push_point(s, p1x, p1y, 0, 0, r, g, b, 0);
- push_point(s, p2x, p2y, 0, 1, r, g, b, 0);
- push_point(s, p3x, p3y, 1, 0, r, g, b, 0);
+function push_quad_xyrgb(s, p1x, p1y, p4x, p4y, r, g, b) {
+ push_point_xyrgb(s, p1x, p1y, r, g, b);
+ push_point_xyrgb(s, p4x, p1y, r, g, b);
+ push_point_xyrgb(s, p1x, p4y, r, g, b);
- push_point(s, p4x, p4y, 1, 1, r, g, b, 0);
- push_point(s, p3x, p3y, 1, 0, r, g, b, 0);
- push_point(s, p2x, p2y, 0, 1, r, g, b, 0);
+ push_point_xyrgb(s, p4x, p4y, r, g, b);
+ push_point_xyrgb(s, p1x, p4y, r, g, b);
+ push_point_xyrgb(s, p4x, p1y, r, g, b);
}
-function push_stroke(s, stroke) {
+function push_stroke(context, stroke) {
const stroke_width = stroke.width;
const points = stroke.points;
const color_u32 = stroke.color;
@@ -45,47 +35,29 @@ function push_stroke(s, stroke) {
return;
}
- if (points.length === 1) {
- push_circle(s, points[0].x, points[0].y, stroke_width / 2, r, g, b);
- return;
- }
-
- for (let i = 0; i < points.length - 1; ++i) {
- const px = points[i].x;
- const py = points[i].y;
-
- const nextpx = points[i + 1].x;
- const nextpy = points[i + 1].y;
-
- const d1x = nextpx - px;
- const d1y = nextpy - py;
+ const points_from = context.point_serializer.offset / (4 * 2); // 4 is sizeof(f32) btw, just sain'
+ const points_to = points_from + points.length;
- // Perpendicular to (d1x, d1y), points to the LEFT
- let perp1x = -d1y;
- let perp1y = d1x;
+ ser_u32(context.index_serializer, points_from);
+ ser_u32(context.index_serializer, points_to);
- const perpnorm1 = Math.sqrt(perp1x * perp1x + perp1y * perp1y);
+ let min_x, min_y, max_x, max_y;
- perp1x /= perpnorm1;
- perp1y /= perpnorm1;
+ min_x = Math.floor(points[0].x);
+ max_x = Math.ceil(points[0].x);
- const s1x = px + perp1x * stroke_width / 2;
- const s1y = py + perp1y * stroke_width / 2;
- const s2x = px - perp1x * stroke_width / 2;
- const s2y = py - perp1y * stroke_width / 2;
+ min_y = Math.floor(points[0].y);
+ max_y = Math.ceil(points[0].y);
- const s3x = nextpx + perp1x * stroke_width / 2;
- const s3y = nextpy + perp1y * stroke_width / 2;
- const s4x = nextpx - perp1x * stroke_width / 2;
- const s4y = nextpy - perp1y * stroke_width / 2;
-
- push_quad(s, s2x, s2y, s1x, s1y, s4x, s4y, s3x, s3y, r, g, b);
- push_circle(s, px, py, stroke_width / 2, r, g, b);
+ for (const p of points) {
+ min_x = Math.min(min_x, Math.floor(p.x));
+ min_y = Math.min(min_y, Math.floor(p.y));
+ max_x = Math.max(max_x, Math.ceil(p.x));
+ max_y = Math.max(max_y, Math.ceil(p.y));
+ push_point_xy(context.point_serializer, p.x, p.y);
}
- const lastp = points[points.length - 1];
-
- push_circle(s, lastp.x, lastp.y, stroke_width / 2, r, g, b);
+ push_quad_xyrgb(context.quad_serializer, min_x, min_y, max_x, max_y, r, g, b);
}
function geometry_prepare_stroke(state) {
@@ -104,48 +76,67 @@ function geometry_prepare_stroke(state) {
function geometry_add_stroke(state, context, stroke) {
if (!state.online || !stroke) return;
- const bytes_left = context.static_stroke_serializer.size - context.static_stroke_serializer.offset;
- const bytes_needed = (stroke.points.length * 12 + 6) * config.bytes_per_point;
+ const bytes_left = context.point_serializer.size - context.point_serializer.offset;
+ const bytes_needed = stroke.points.length * config.bytes_per_point;
if (bytes_left < bytes_needed) {
- const old_view = context.static_stroke_serializer.strview;
- const old_offset = context.static_stroke_serializer.offset;
-
- const new_size = Math.ceil((context.static_stroke_serializer.size + bytes_needed) * 1.62);
+ const extend_points_by = Math.ceil((context.point_serializer.size + bytes_needed) * 1.62);
+ const extend_indices_by = Math.ceil((context.index_serializer.size + stroke.points.length * 4 * 2) * 1.62);
+ const extend_quads_by = Math.ceil((context.quad_serializer.size + 6 * (4 * 3)) * 1.62);
- context.static_stroke_serializer = serializer_create(new_size);
- context.static_stroke_serializer.strview.set(old_view);
- context.static_stroke_serializer.offset = old_offset;
+ context.point_serializer = ser_extend(context.point_serializer, extend_points_by);
+ context.index_serializer = ser_extend(context.index_serializer, extend_indices_by);
+ context.quad_serializer = ser_extend(context.quad_serializer, extend_quads_by);
}
- push_stroke(context.static_stroke_serializer, stroke);
+ push_stroke(context, stroke);
}
-function recompute_dynamic_data(state, context) {
- let bytes_needed = 0;
+function geometry_delete_stroke(state, context, stroke_index) {
+ // NEXT: deleted wrong stroke
+ let offset = 0;
- for (const player_id in state.players) {
- const player = state.players[player_id];
- if (player.points.length > 0) {
- bytes_needed += (player.points.length * 12 + 6) * config.bytes_per_point;
- }
+ for (let i = 0; i < stroke_index; ++i) {
+ const event = state.events[i];
+
+ if (event.type === EVENT.STROKE) {
+ offset += (event.points.length * 12 + 6) * config.bytes_per_point;
+ }
}
- if (bytes_needed > context.dynamic_stroke_serializer.size) {
- context.dynamic_stroke_serializer = serializer_create(Math.ceil(bytes_needed * 1.62));
- } else {
- context.dynamic_stroke_serializer.offset = 0;
- }
+ const stroke = state.events[stroke_index];
- for (const player_id in state.players) {
- // player has the same data as their current stroke: points, color, width
- const player = state.players[player_id];
- if (player.points.length > 0) {
- push_stroke(context.dynamic_stroke_serializer, player);
- }
+ for (let i = 0; i < stroke.points.length * 12 + 6; ++i) {
+ context.static_stroke_serializer.view.setUint8(offset + config.bytes_per_point - 1, 125);
+ offset += config.bytes_per_point;
}
}
+function recompute_dynamic_data(state, context) {
+ // let bytes_needed = 0;
+
+ // for (const player_id in state.players) {
+ // const player = state.players[player_id];
+ // if (player.points.length > 0) {
+ // bytes_needed += player.points.length * config.bytes_per_point;
+ // }
+ // }
+
+ // if (bytes_needed > context.dynamic_stroke_serializer.size) {
+ // context.dynamic_stroke_serializer = serializer_create(Math.ceil(bytes_needed * 1.62));
+ // } else {
+ // context.dynamic_stroke_serializer.offset = 0;
+ // }
+
+ // for (const player_id in state.players) {
+ // // player has the same data as their current stroke: points, color, width
+ // const player = state.players[player_id];
+ // if (player.points.length > 0) {
+ // push_stroke(context.dynamic_stroke_serializer, player);
+ // }
+ // }
+}
+
function geometry_add_point(state, context, player_id, point) {
if (!state.online) return;
state.players[player_id].points.push(point);
@@ -162,15 +153,15 @@ function add_image(context, image_id, bitmap, p) {
const x = p.x;
const y = p.y;
const gl = context.gl;
- const id = Object.keys(context.textures).length;
+ const id = Object.keys(context.textures['image']).length;
- context.textures[id] = {
+ context.textures['image'][id] = {
'texture': gl.createTexture(),
'image_id': image_id
};
gl.bindTexture(gl.TEXTURE_2D, context.textures[id].texture);
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,gl.UNSIGNED_BYTE, bitmap);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bitmap);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
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);
@@ -202,10 +193,10 @@ function move_image(context, image_event) {
const x = image_event.x;
const y = image_event.y;
- const count = Object.keys(context.textures).length;
+ const count = Object.keys(context.textures['image']).length;
for (let id = 0; id < count; ++id) {
- const image = context.textures[id];
+ const image = context.textures['image'][id];
if (image.image_id === image_event.image_id) {
context.quad_positions[id * 12 + 0] = x;
context.quad_positions[id * 12 + 1] = y;
diff --git a/client/webgl_listeners.js b/client/webgl_listeners.js
index 2ed2f9b..1c9dc35 100644
--- a/client/webgl_listeners.js
+++ b/client/webgl_listeners.js
@@ -95,13 +95,19 @@ function mousedown(e, state, context) {
return;
}
- geometry_clear_player(state, context, state.me);
- geometry_add_point(state, context, state.me, canvasp);
+ if (state.tools.active === 'pencil') {
+ geometry_clear_player(state, context, state.me);
+ geometry_add_point(state, context, state.me, canvasp);
- state.drawing = true;
- context.active_image = null;
+ state.drawing = true;
+ context.active_image = null;
- schedule_draw(state, context);
+ schedule_draw(state, context);
+ } else if (state.tools.active === 'ruler') {
+
+ } else if (state.tools.active === 'eraser') {
+ state.erasing = true;
+ }
}
function mousemove(e, state, context) {
@@ -125,18 +131,32 @@ function mousemove(e, state, context) {
const screenp = {'x': window.devicePixelRatio * e.clientX, 'y': window.devicePixelRatio * e.clientY};
const canvasp = screen_to_canvas(state, screenp);
- state.cursor = screenp;
-
if (state.drawing) {
geometry_add_point(state, context, state.me, canvasp);
fire_event(state, predraw_event(canvasp.x, canvasp.y));
do_draw = true;
}
+ if (state.erasing) {
+ const p1 = screen_to_canvas(state, state.cursor);
+ const p2 = { 'x': canvasp.x, 'y': canvasp.y };
+ const erased = strokes_intersect_line(state, p1, p2);
+
+ for (const index of erased) {
+ if (!state.events[index].deleted) {
+ state.events[index].deleted = true;
+ do_draw = true;
+ geometry_delete_stroke(state, context, index);
+ }
+ }
+ }
+
if (do_draw) {
schedule_draw(state, context);
}
+ state.cursor = screenp;
+
return false;
}
@@ -172,6 +192,11 @@ function mouseup(e, state, context) {
return;
}
+
+ if (state.erasing) {
+ state.erasing = false;
+ return;
+ }
}
function wheel(e, state, context) {
diff --git a/client/webgl_shaders.js b/client/webgl_shaders.js
index 4d8a897..3937df7 100644
--- a/client/webgl_shaders.js
+++ b/client/webgl_shaders.js
@@ -1,59 +1,77 @@
-const stroke_vs_src = `
- attribute float a_type;
- attribute vec2 a_pos;
- attribute vec2 a_texcoord;
- attribute vec3 a_color;
+const sdf_vs_src = `#version 300 es
+ in vec2 a_pos;
+ in vec3 a_color;
uniform vec2 u_scale;
uniform vec2 u_res;
uniform vec2 u_translation;
- varying vec3 v_color;
- varying vec2 v_texcoord;
- varying float v_type;
+ out vec2 v_texcoord;
+ out vec3 v_color;
+ flat out int v_vertexid;
void main() {
vec2 screen01 = (a_pos * u_scale + u_translation) / u_res;
vec2 screen02 = screen01 * 2.0;
-
screen02.y = 2.0 - screen02.y;
+ v_texcoord = a_pos + vec2(0.5);
+ v_vertexid = gl_VertexID;
v_color = a_color;
- v_texcoord = a_texcoord;
- v_type = a_type;
gl_Position = vec4(screen02 - 1.0, 0, 1);
}
`;
-const stroke_fs_src = `
- #extension GL_OES_standard_derivatives : enable
-
+const sdf_fs_src = `#version 300 es
precision mediump float;
- varying vec3 v_color;
- varying vec2 v_texcoord;
- varying float v_type;
+ uniform sampler2D u_texture_points;
+ uniform highp usampler2D u_texture_indices;
+
+ in vec2 v_texcoord;
+ in vec3 v_color;
+ flat in int v_vertexid;
+
+ out vec4 FragColor;
void main() {
- vec2 uv = v_texcoord * 2.0 - 1.0;
- float sdf = 1.0 - mix(abs(uv.y), length(uv), v_type);
- float pd = fwidth(sdf);
- float alpha = 1.0 - smoothstep(pd, 0.0, sdf);
+ float mindist = 99999.9;
+ float th = 5.0;
+
+ uvec4 indices = texelFetch(u_texture_indices, ivec2(v_vertexid / 6, 0), 0);
+
+ uint v_from = indices.x;
+ uint v_to = indices.y;
+
+ for (uint i = v_from; i < v_to - uint(1); ++i) {
+ vec4 a = texelFetch(u_texture_points, ivec2(i, 0), 0);
+ vec4 b = texelFetch(u_texture_points, ivec2(i + uint(1), 0), 0);
+
+ vec2 pa = v_texcoord - a.xy, ba = b.xy - a.xy;
+ float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
+ float dist = length(pa - ba * h) - th;
+
+ mindist = min(mindist, dist);
+ }
- gl_FragColor = vec4(v_color * alpha, alpha);
+ float fade = 0.5 * length(fwidth(v_texcoord));
+ float alpha = 1.0 - smoothstep(-fade, fade, mindist);
+
+ FragColor = vec4(v_color * alpha, 0.1 + alpha);
+ // FragColor = vec4(v_color, 1.0);
}
`;
-const tquad_vs_src = `
- attribute vec2 a_pos;
- attribute vec2 a_texcoord;
+const tquad_vs_src = `#version 300 es
+ in vec2 a_pos;
+ in vec2 a_texcoord;
uniform vec2 u_scale;
uniform vec2 u_res;
uniform vec2 u_translation;
- varying vec2 v_texcoord;
+ out vec2 v_texcoord;
void main() {
vec2 screen01 = (a_pos * u_scale + u_translation) / u_res;
@@ -65,26 +83,28 @@ const tquad_vs_src = `
}
`;
-const tquad_fs_src = `
+const tquad_fs_src = `#version 300 es
precision mediump float;
- varying vec2 v_texcoord;
+ in vec2 v_texcoord;
uniform sampler2D u_texture;
uniform bool u_outline;
+ out vec4 FragColor;
+
void main() {
if (!u_outline) {
- gl_FragColor = texture2D(u_texture, v_texcoord);
+ FragColor = texture(u_texture, v_texcoord);
} else {
- gl_FragColor = mix(texture2D(u_texture, v_texcoord), vec4(0.7, 0.7, 0.95, 1), 0.5);
+ FragColor = mix(texture(u_texture, v_texcoord), vec4(0.7, 0.7, 0.95, 1), 0.5);
}
}
`;
function init_webgl(state, context) {
context.canvas = document.querySelector('#c');
- context.gl = context.canvas.getContext('webgl', {
+ context.gl = context.canvas.getContext('webgl2', {
'preserveDrawingBuffer': true,
'desynchronized': true,
'antialias': false,
@@ -94,47 +114,52 @@ function init_webgl(state, context) {
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
- gl.getExtension('OES_standard_derivatives');
-
- const stroke_vs = create_shader(gl, gl.VERTEX_SHADER, stroke_vs_src);
- const stroke_fs = create_shader(gl, gl.FRAGMENT_SHADER, stroke_fs_src);
const quad_vs = create_shader(gl, gl.VERTEX_SHADER, tquad_vs_src);
const quad_fs = create_shader(gl, gl.FRAGMENT_SHADER, tquad_fs_src);
- context.programs['stroke'] = create_program(gl, stroke_vs, stroke_fs);
- context.programs['quad'] = create_program(gl, quad_vs, quad_fs);
+ const sdf_vs = create_shader(gl, gl.VERTEX_SHADER, sdf_vs_src);
+ const sdf_fs = create_shader(gl, gl.FRAGMENT_SHADER, sdf_fs_src);
- context.locations['stroke'] = {
- 'a_type': gl.getAttribLocation(context.programs['stroke'], 'a_type'),
- 'a_pos': gl.getAttribLocation(context.programs['stroke'], 'a_pos'),
- 'a_texcoord': gl.getAttribLocation(context.programs['stroke'], 'a_texcoord'),
- 'a_color': gl.getAttribLocation(context.programs['stroke'], 'a_color'),
+ context.programs['image'] = create_program(gl, quad_vs, quad_fs);
+ context.programs['sdf'] = create_program(gl, sdf_vs, sdf_fs);
- 'u_res': gl.getUniformLocation(context.programs['stroke'], 'u_res'),
- 'u_scale': gl.getUniformLocation(context.programs['stroke'], 'u_scale'),
- 'u_translation': gl.getUniformLocation(context.programs['stroke'], 'u_translation'),
- };
+ context.locations['image'] = {
+ 'a_pos': gl.getAttribLocation(context.programs['image'], 'a_pos'),
+ 'a_texcoord': gl.getAttribLocation(context.programs['image'], 'a_texcoord'),
- context.locations['quad'] = {
- 'a_pos': gl.getAttribLocation(context.programs['quad'], 'a_pos'),
- 'a_texcoord': gl.getAttribLocation(context.programs['quad'], 'a_texcoord'),
- 'u_res': gl.getUniformLocation(context.programs['quad'], 'u_res'),
- 'u_scale': gl.getUniformLocation(context.programs['quad'], 'u_scale'),
- 'u_translation': gl.getUniformLocation(context.programs['quad'], 'u_translation'),
- 'u_outline': gl.getUniformLocation(context.programs['quad'], 'u_outline'),
- 'u_texture': gl.getUniformLocation(context.programs['quad'], 'u_texture'),
+ 'u_res': gl.getUniformLocation(context.programs['image'], 'u_res'),
+ 'u_scale': gl.getUniformLocation(context.programs['image'], 'u_scale'),
+ 'u_translation': gl.getUniformLocation(context.programs['image'], 'u_translation'),
+ 'u_outline': gl.getUniformLocation(context.programs['image'], 'u_outline'),
+ 'u_texture': gl.getUniformLocation(context.programs['image'], 'u_texture'),
};
- context.buffers['stroke'] = {
- 'b_packed': context.gl.createBuffer(),
+ context.locations['sdf'] = {
+ 'a_pos': gl.getAttribLocation(context.programs['sdf'], 'a_pos'),
+ 'a_color': gl.getAttribLocation(context.programs['sdf'], 'a_color'),
+
+ 'u_res': gl.getUniformLocation(context.programs['sdf'], 'u_res'),
+ 'u_scale': gl.getUniformLocation(context.programs['sdf'], 'u_scale'),
+ 'u_translation': gl.getUniformLocation(context.programs['sdf'], 'u_translation'),
+ 'u_texture_points': gl.getUniformLocation(context.programs['sdf'], 'u_texture_points'),
+ 'u_texture_indices': gl.getUniformLocation(context.programs['sdf'], 'u_texture_indices'),
};
- context.buffers['quad'] = {
+ context.buffers['image'] = {
'b_pos': context.gl.createBuffer(),
'b_texcoord': context.gl.createBuffer(),
};
+ context.buffers['sdf'] = {
+ 'b_packed': context.gl.createBuffer(),
+ };
+
+ context.textures['sdf'] = {
+ 'points': gl.createTexture(),
+ 'indices': gl.createTexture()
+ };
+
const resize_canvas = (entries) => {
// https://www.khronos.org/webgl/wiki/HandlingHighDPI
const entry = entries[0];