diff --git a/client/index.js b/client/index.js
index 242fc51..aabf941 100644
--- a/client/index.js
+++ b/client/index.js
@@ -239,6 +239,8 @@ async function main() {
'wasm': {},
'background_pattern': 'dots',
+
+ 'erase_candidates': tv_create(Uint32Array, 4096),
};
const context = {
diff --git a/client/math.js b/client/math.js
index cb60f30..5597bf5 100644
--- a/client/math.js
+++ b/client/math.js
@@ -200,28 +200,6 @@ function process_stroke2(zoom, points) {
return result;
}
-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];
-
- if (segments_intersect(a, b, c, d)) {
- result.push(i);
- break;
- }
- }
- }
- }
-
- return result;
-}
-
function color_to_u32(color_str) {
const r = parseInt(color_str.substring(0, 2), 16);
const g = parseInt(color_str.substring(2, 4), 16);
@@ -433,3 +411,49 @@ function random_bright_color_from_seed(seed) {
return `hsl(${h}deg ${s}% ${l}%)`;
}
+
+function dot(a, b) {
+ return a.x * b.x + a.y * b.y;
+}
+
+function clamp(x, a, b) {
+ return x < a ? a : (x > b ? b : x);
+}
+
+function length(a) {
+ return Math.sqrt(dot(a, a));
+}
+
+function circle_intersects_capsule(ax, ay, bx, by, p1, p2, cx, cy, r) {
+ // Basically the SDF computation
+ const pa = { 'x': cx - ax, 'y': cy - ay };
+ const ba = { 'x': bx - ax, 'y': by - ay };
+
+ const h = clamp(dot(pa, ba) / dot(ba, ba), 0, 1);
+ const in1 = length({ 'x': cx - (ax + ba.x * h), 'y': cy - (ay + ba.y * h) });
+ const in2 = (1 - h) * p1 + h * p2;
+ const dist = in1 - in2;
+ return dist <= r;
+}
+
+function stroke_intersects_cursor(state, stroke, canvasp, radius) {
+ const xs = state.wasm.buffers['xs'].tv.data;
+ const ys = state.wasm.buffers['ys'].tv.data;
+ const pressures = state.wasm.buffers['pressures'].tv.data;
+
+ for (let i = stroke.coords_from; i < stroke.coords_to - 1; ++i) {
+ const x1 = xs[i + 0];
+ const y1 = ys[i + 0];
+ const x2 = xs[i + 1];
+ const y2 = ys[i + 1];
+ const p1 = pressures[i + 0];
+ const p2 = pressures[i + 1];
+
+
+ if (circle_intersects_capsule(x1, y1, x2, y2, p1 * stroke.width / 255, p2 * stroke.width / 255, canvasp.x, canvasp.y, radius)) {
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/client/tools.js b/client/tools.js
index cd5f5f1..57de4e7 100644
--- a/client/tools.js
+++ b/client/tools.js
@@ -21,10 +21,11 @@ function switch_tool(state, item) {
document.querySelector('canvas').classList.add(new_class);
- if (tool === 'pointer' || tool === 'eraser') {
- document.querySelector('.brush-dom').classList.add('dhide');
- } else {
+ if (tool === 'pencil' || tool === 'eraser') {
+ update_cursor(state);
document.querySelector('.brush-dom').classList.remove('dhide');
+ } else {
+ document.querySelector('.brush-dom').classList.add('dhide');
}
}
diff --git a/client/touch.js b/client/touch.js
deleted file mode 100644
index 2d732a1..0000000
--- a/client/touch.js
+++ /dev/null
@@ -1,359 +0,0 @@
-function on_touchstart(e) {
- e.preventDefault();
-
- if (storage.touch.drawing) {
- return;
- }
-
- // First finger(s) down?
- if (storage.touch.ids.length === 0) {
- // We only handle 1 and 2
- if (e.changedTouches.length > 2) {
- return;
- }
-
- storage.touch.ids.length = 0;
-
- for (const touch of e.changedTouches) {
- storage.touch.ids.push(touch.identifier);
- }
-
- if (e.changedTouches.length === 1) {
- const touch = e.changedTouches[0];
- const x = Math.round((touch.clientX + storage.canvas.offset_x) / storage.canvas.zoom);
- const y = Math.round((touch.clientY + storage.canvas.offset_y) / storage.canvas.zoom);
-
- storage.touch.position.x = x;
- storage.touch.position.y = y;
-
- // We give a bit of time to add a second finger
- storage.touch.waiting_for_second_finger = true;
- storage.touch.moves = 0;
- storage.touch.buffered.length = 0;
- storage.ruler_origin.x = x;
- storage.ruler_origin.y = y;
-
- setTimeout(() => {
- storage.touch.waiting_for_second_finger = false;
- }, config.second_finger_timeout);
- }
-
- return;
- }
-
- // There are touches already
- if (storage.touch.waiting_for_second_finger) {
- if (e.changedTouches.length === 1) {
- const changed_touch = e.changedTouches[0];
-
- storage.touch.screen_position.x = changed_touch.clientX;
- storage.touch.screen_position.y = changed_touch.clientY;
-
- storage.touch.ids.push(e.changedTouches[0].identifier);
-
- let first_finger_position = null;
- let second_finger_position = null;
-
- // A separate loop because touches might be in different order ? (question mark)
- // IMPORTANT: e.touches, not e.changedTouches!
- for (const touch of e.touches) {
- const x = touch.clientX;
- const y = touch.clientY;
-
- if (touch.identifier === storage.touch.ids[0]) {
- first_finger_position = {'x': x, 'y': y};
- }
-
- if (touch.identifier === storage.touch.ids[1]) {
- second_finger_position = {'x': x, 'y': y};
- }
- }
-
- storage.touch.finger_distance = dist_v2(
- first_finger_position, second_finger_position);
-
- // console.log(storage.touch.finger_distance);
- }
-
- return;
- }
-}
-
-function on_touchmove(e) {
- if (storage.touch.ids.length === 1 && !storage.touch.moving) {
- storage.touch.moves += 1;
-
- if (storage.touch.moves > config.buffer_first_touchmoves) {
- storage.touch.waiting_for_second_finger = false; // Immediately start drawing on move
- storage.touch.drawing = true;
-
- if (storage.ctx1.lineWidth !== storage.cursor.width) {
- storage.ctx1.lineWidth = storage.cursor.width;
- }
- } else {
- let drawing_touch = null;
-
- for (const touch of e.changedTouches) {
- if (touch.identifier === storage.touch.ids[0]) {
- drawing_touch = touch;
- break;
- }
- }
-
- if (!drawing_touch) {
- return;
- }
-
- const last_x = storage.touch.position.x;
- const last_y = storage.touch.position.y;
-
- const x = Math.max(Math.round((drawing_touch.clientX + storage.canvas.offset_x) / storage.canvas.zoom), 0);
- const y = Math.max(Math.round((drawing_touch.clientY + storage.canvas.offset_y) / storage.canvas.zoom), 0);
-
- storage.touch.buffered.push({
- 'last_x': last_x,
- 'last_y': last_y,
- 'x': x,
- 'y': y,
- });
-
- storage.touch.position.x = x;
- storage.touch.position.y = y;
- }
- }
-
- if (storage.touch.drawing) {
- let drawing_touch = null;
-
- for (const touch of e.changedTouches) {
- if (touch.identifier === storage.touch.ids[0]) {
- drawing_touch = touch;
- break;
- }
- }
-
- if (!drawing_touch) {
- return;
- }
-
- const last_x = storage.touch.position.x;
- const last_y = storage.touch.position.y;
-
- const x = storage.touch.position.x = Math.max(Math.round((drawing_touch.clientX + storage.canvas.offset_x) / storage.canvas.zoom), 0);
- const y = storage.touch.position.y = Math.max(Math.round((drawing_touch.clientY + storage.canvas.offset_y) / storage.canvas.zoom), 0);
-
- if (storage.tools.active === 'pencil') {
- if (storage.touch.buffered.length > 0) {
- for (const p of storage.touch.buffered) {
- storage.ctx1.beginPath();
-
- storage.ctx1.moveTo(p.last_x, p.last_y);
- storage.ctx1.lineTo(p.x, p.y);
-
- storage.ctx1.stroke();
-
- const predraw = predraw_event(p.x, p.y);
- storage.current_stroke.push(predraw);
-
- fire_event(predraw);
- }
-
- storage.touch.buffered.length = 0;
- }
-
- storage.ctx1.beginPath();
-
- storage.ctx1.moveTo(last_x, last_y);
- storage.ctx1.lineTo(x, y);
-
- storage.ctx1.stroke();
-
- const predraw = predraw_event(x, y);
- storage.current_stroke.push(predraw);
-
- fire_event(predraw);
-
- storage.touch.position.x = x;
- storage.touch.position.y = y;
-
- return;
- } else if (storage.tools.active === 'eraser') {
- const erase_step = (last_x, last_y, x, y) => {
- const erased = strokes_intersect_line(last_x, last_y, x, y);
- storage.erased.push(...erased);
-
- if (erased.length > 0) {
- for (const other_event of storage.events) {
- for (const stroke_id of erased) {
- if (stroke_id === other_event.stroke_id) {
- if (!other_event.deleted) {
- other_event.deleted = true;
- const stats = stroke_stats(other_event.points, storage.cursor.width);
- redraw_region(stats.bbox);
- }
- }
- }
- }
- }
- };
-
- if (storage.touch.buffered.length > 0) {
- for (const p of storage.touch.buffered) {
- erase_step(p.last_x, p.last_y, p.x, p.y);
- }
-
- storage.touch.buffered.length = 0;
- }
-
- erase_step(last_x, last_y, x, y);
- } else if (storage.tools.active === 'ruler') {
- const old_ruler = [
- {'x': storage.ruler_origin.x, 'y': storage.ruler_origin.y},
- {'x': last_x, 'y': last_y}
- ];
-
- const stats = stroke_stats(old_ruler, storage.cursor.width);
- const bbox = stats.bbox;
-
- storage.ctx1.clearRect(bbox.xmin, bbox.ymin, bbox.xmax - bbox.xmin, bbox.ymax - bbox.ymin);
-
- storage.ctx1.beginPath();
-
- storage.ctx1.moveTo(storage.ruler_origin.x, storage.ruler_origin.y);
- storage.ctx1.lineTo(x, y);
-
- storage.ctx1.stroke();
- } else {
- console.error('fuck');
- }
- }
-
- if (storage.touch.ids.length === 2) {
- storage.touch.moving = true;
-
- let first_finger_position_screen = null;
- let second_finger_position_screen = null;
-
- let first_finger_position_canvas = null;
- let second_finger_position_canvas = null;
-
- // A separate loop because touches might be in different order ? (question mark)
- // IMPORTANT: e.touches, not e.changedTouches!
- for (const touch of e.touches) {
- const x = touch.clientX;
- const y = touch.clientY;
-
- const xc = Math.max(Math.round((touch.clientX + storage.canvas.offset_x) / storage.canvas.zoom), 0);
- const yc = Math.max(Math.round((touch.clientY + storage.canvas.offset_y) / storage.canvas.zoom), 0);
-
- if (touch.identifier === storage.touch.ids[0]) {
- first_finger_position_screen = {'x': x, 'y': y};
- first_finger_position_canvas = {'x': xc, 'y': yc};
- }
-
- if (touch.identifier === storage.touch.ids[1]) {
- second_finger_position_screen = {'x': x, 'y': y};
- second_finger_position_canvas = {'x': xc, 'y': yc};
- }
- }
-
- const new_finger_distance = dist_v2(
- first_finger_position_screen, second_finger_position_screen);
-
- const zoom_center = {
- 'x': (first_finger_position_canvas.x + second_finger_position_canvas.x) / 2.0,
- 'y': (first_finger_position_canvas.y + second_finger_position_canvas.y) / 2.0
- };
-
- for (const touch of e.changedTouches) {
- // The second finger to be down is considered the "main" one
- // Movement of the second finger is ignored
- if (touch.identifier === storage.touch.ids[1]) {
- const x = Math.round(touch.clientX);
- const y = Math.round(touch.clientY);
-
- const dx = x - storage.touch.screen_position.x;
- const dy = y - storage.touch.screen_position.y;
-
- const old_zoom = storage.canvas.zoom;
- const old_offset_x = storage.canvas.offset_x;
- const old_offset_y = storage.canvas.offset_y;
-
- storage.canvas.offset_x -= dx;
- storage.canvas.offset_y -= dy;
-
- // console.log(new_finger_distance, storage.touch.finger_distance);
-
- const scale_by = new_finger_distance / storage.touch.finger_distance;
- const dz = storage.canvas.zoom * (scale_by - 1.0);
-
- const zoom_offset_y = Math.round(dz * zoom_center.y);
- const zoom_offset_x = Math.round(dz * zoom_center.x);
-
- if (storage.min_zoom <= storage.canvas.zoom * scale_by && storage.canvas.zoom * scale_by <= storage.max_zoom) {
- storage.canvas.zoom *= scale_by;
- storage.canvas.offset_x += zoom_offset_x;
- storage.canvas.offset_y += zoom_offset_y;
- }
-
- storage.touch.finger_distance = new_finger_distance;
-
-
- if (storage.canvas.offset_x !== old_offset_x || storage.canvas.offset_y !== old_offset_y || old_zoom !== storage.canvas.zoom) {
- move_canvas();
- }
-
- storage.touch.screen_position.x = x;
- storage.touch.screen_position.y = y;
-
- break;
- }
- }
-
- return;
- }
-}
-
-async function on_touchend(e) {
- for (const touch of e.changedTouches) {
- if (storage.touch.drawing) {
- if (storage.touch.ids[0] == touch.identifier) {
- storage.touch.drawing = false;
-
- if (storage.tools.active === 'pencil') {
- const event = stroke_event();
- storage.current_stroke = [];
- await queue_event(event);
- } else if (storage.tools.active === 'eraser') {
- const events = eraser_events();
- storage.erased = [];
- if (events.length > 0) {
- for (const event of events) {
- await queue_event(event);
- }
- }
- } else if (storage.tools.active === 'ruler') {
- const event = ruler_event(storage.touch.position.x, storage.touch.position.y);
- await queue_event(event);
- } else {
- console.error('fuck');
- }
- }
- }
-
- const index = storage.touch.ids.indexOf(touch.identifier);
-
- if (index !== -1) {
- storage.touch.ids.splice(index, 1);
- }
-
- if (storage.touch.moving && storage.touch.ids.length === 0) {
- // Only allow drawing again when ALL fingers have been lifted
- storage.touch.moving = false;
- }
- }
-
- if (storage.touch.ids.length === 0) {
- waiting_for_second_finger = false;
- }
-}
\ No newline at end of file
diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js
index 398c0b5..f5c5e8d 100644
--- a/client/webgl_geometry.js
+++ b/client/webgl_geometry.js
@@ -57,7 +57,7 @@ function geometry_add_dummy_stroke(context) {
}
function geometry_add_stroke(state, context, stroke, stroke_index, skip_bvh = false) {
- if (!state.online || !stroke || stroke.coords_to - stroke.coords_from === 0) return;
+ if (!state.online || !stroke || stroke.coords_to - stroke.coords_from === 0 || stroke.deleted) return;
stroke.bbox = stroke_bbox(state, stroke);
stroke.area = box_area(stroke.bbox);
@@ -77,26 +77,6 @@ function geometry_add_stroke(state, context, stroke, stroke_index, skip_bvh = fa
if (!skip_bvh) bvh_add_stroke(state, state.bvh, stroke_index, stroke);
}
-function geometry_delete_stroke(state, context, stroke_index) {
- // NEXT: deleted wrong stroke
- let offset = 0;
-
- 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;
- }
- }
-
- const stroke = state.events[stroke_index];
-
- 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 total_points = 0;
let total_strokes = 0;
diff --git a/client/webgl_listeners.js b/client/webgl_listeners.js
index 535907a..b97cd3c 100644
--- a/client/webgl_listeners.js
+++ b/client/webgl_listeners.js
@@ -381,15 +381,28 @@ function mousemove(e, state, context) {
}
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;
+ const me = state.players[state.me];
+ const radius = Math.round(me.width / 2);
+
+ const cursor_bbox = {
+ 'x1': canvasp.x - radius,
+ 'y1': canvasp.y - radius,
+ 'x2': canvasp.x + radius,
+ 'y2': canvasp.y + radius,
+ };
+
+ tv_ensure(state.erase_candidates, round_to_pow2(state.stroke_count, 4096));
+ tv_clear(state.erase_candidates);
+ bvh_intersect_quad(state, state.bvh, cursor_bbox, state.erase_candidates);
+
+ for (let i = 0; i < state.erase_candidates.size; ++i) {
+ const stroke_id = state.erase_candidates.data[i];
+ const stroke = state.events[stroke_id];
+
+ if (!stroke.deleted && stroke_intersects_cursor(state, stroke, canvasp, radius)) {
+ stroke.deleted = true;
+ bvh_delete_stroke(state, stroke);
do_draw = true;
- geometry_delete_stroke(state, context, index);
}
}
}
@@ -477,16 +490,34 @@ function mouseleave(e, state, context) {
}
function update_cursor(state) {
+ if (!(state.me in state.players)) {
+ // we not ready yet
+ return;
+ }
+
const me = state.players[state.me];
const width = Math.max(me.width * state.canvas.zoom, 2.0);
const radius = Math.round(width / 2);
- const current_color = color_from_u32(me.color);
- const stroke = (me.color === 0xFFFFFF ? 'black' : 'white');
- const svg = ``.replaceAll('\n', ' ');
+
+ let svg;
+
+ if (state.tools.active === 'pencil') {
+ const current_color = color_from_u32(me.color);
+ const stroke = (me.color === 0xFFFFFF ? 'black' : 'white');
+
+ svg = ``.replaceAll('\n', ' ');
+ } else if (state.tools.active === 'eraser') {
+ const current_color = '#ffffff';
+ const stroke = '#000000';
+ svg = ``.replaceAll('\n', ' ');
+ }
document.querySelector('.brush-dom').innerHTML = svg;