diff --git a/client/math.js b/client/math.js index f0e2579..cb60f30 100644 --- a/client/math.js +++ b/client/math.js @@ -60,20 +60,24 @@ function process_rdp_indices(state, zoom, stroke) { return npoints; } -function process_ewmv(points, round = false) { - const result = []; +function exponential_smoothing(points, last, up_to) { const alpha = 0.5; - result.push(points[0]); + let pr = 0; - for (let i = 1; i < points.length; ++i) { + let start = points.length - up_to; + if (start < 0) { + start = 0; + } + + for (let i = start; i < points.length; ++i) { const p = points[i]; - const x = Math.round(alpha * p.x + (1 - alpha) * result[result.length - 1].x); - const y = Math.round(alpha * p.y + (1 - alpha) * result[result.length - 1].y); - result.push({'x': x, 'y': y}); + pr = alpha * p.pressure + (1 - alpha) * pr; } - return result; + pr = alpha * last.pressure + (1 - alpha) * pr; + + return pr; } function process_stroke(state, zoom, stroke) { @@ -92,7 +96,7 @@ function process_stroke(state, zoom, stroke) { } function rdp_find_max2(zoom, points, start, end) { - const EPS = 1.0 / zoom; + const EPS = 0.125 / zoom; let result = -1; let max_dist = 0; @@ -107,7 +111,7 @@ function rdp_find_max2(zoom, points, start, end) { const sin_theta = dy / dist_ab; const cos_theta = dx / dist_ab; - for (let i = start; i < end; ++i) { + for (let i = start + 1; i < end; ++i) { const p = points[i]; const ox = p.x - a.x; @@ -132,7 +136,7 @@ function rdp_find_max2(zoom, points, start, end) { function process_rdp_r2(zoom, points, start, end) { let result = []; - + const max = rdp_find_max2(zoom, points, start, end); if (max !== -1) { @@ -145,9 +149,48 @@ function process_rdp_r2(zoom, points, start, end) { } function process_rdp2(zoom, points) { - const result = process_rdp_r2(zoom, points, 0, points.length - 1); - result.unshift(points[0]); + const result = []; + const stack = []; + + stack.push({ + 'type': 0, + 'start': 0, + 'end': points.length - 1, + }); + + result.push(points[0]); + + while (stack.length > 0) { + const entry = stack.pop(); + + if (entry.type === 0) { + const max = rdp_find_max2(zoom, points, entry.start, entry.end); + + if (max !== -1) { + stack.push({ + 'type': 0, + 'start': max, + 'end': entry.end + }); + + stack.push({ + 'type': 1, + 'index': max, + }); + + stack.push({ + 'type': 0, + 'start': entry.start, + 'end': max, + }); + } + } else { + result.push(points[entry.index]); + } + } + result.push(points[points.length - 1]); + return result; } diff --git a/client/speed.js b/client/speed.js index 0eabc76..eda7432 100644 --- a/client/speed.js +++ b/client/speed.js @@ -120,8 +120,9 @@ function wasm_ensure_by(state, nstrokes, ncoords) { if (realloc) { const current_pages = Math.ceil(state.wasm.memory.buffer.byteLength / (4096 * 16)); - const need_pages = Math.ceil((state.wasm.coords_bytes * 3 + state.wasm.stroke_bytes * 2) / (4096 * 16)); // TODO: figure out actual memory requirements + const need_pages = 2 * Math.ceil((state.wasm.coords_bytes * 3 + state.wasm.stroke_bytes * 2) / (4096 * 16)); // TODO: figure out actual memory requirements const grow_by = Math.max(1, need_pages - current_pages); + // const grow_by = 16; state.wasm.memory.grow(grow_by); state.wasm.exports.free_static(); diff --git a/client/wasm/lod.c b/client/wasm/lod.c index 0e6ce43..f4bde15 100644 --- a/client/wasm/lod.c +++ b/client/wasm/lod.c @@ -220,6 +220,7 @@ do_lod(int *clipped_indices, int clipped_count, float zoom, int segments_head = 0; int stack[4096]; // TODO: what's a reasonable max size for this? + int max_stack_size = 0; for (int i = 0; i < clipped_count; ++i) { int stroke_index = clipped_indices[i]; @@ -243,6 +244,7 @@ do_lod(int *clipped_indices, int clipped_count, float zoom, stack[stack_head++] = point_count - 1; while (stack_head > 0) { + if (stack_head > max_stack_size) { max_stack_size = stack_head; } int end = stack[--stack_head]; int start = stack[--stack_head]; int type = stack[--stack_head]; diff --git a/client/wasm/lod.wasm b/client/wasm/lod.wasm index 2e56d77..2682500 100755 Binary files a/client/wasm/lod.wasm and b/client/wasm/lod.wasm differ diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js index 5d3f86b..8af79df 100644 --- a/client/webgl_geometry.js +++ b/client/webgl_geometry.js @@ -151,10 +151,39 @@ function recompute_dynamic_data(state, context) { context.dynamic_stroke_count = total_strokes; } +let _head = null; + function geometry_add_point(state, context, player_id, point) { if (!state.online) return; - state.players[player_id].points.push(point); + + const points = state.players[player_id].points; + + if (false && points.length === 1) { + // Fix up pressure of first point to get rid of ugly spike bit + const first_point = points[0]; + if (first_point.pressure === 0) { + first_point.pressure = point.pressure; + } + } + + if (points.length > 0) { + // pulled from "perfect-freehand" package. MIT + // https://github.com/steveruizok/perfect-freehand/ + const streamline = 0.5; + const t = 0.15 + (1 - streamline) * 0.85 + const smooth_pressure = exponential_smoothing(points, point, 3); + points.push({ + 'x': _head.x * t + point.x * (1 - t), + 'y': _head.y * t + point.y * (1 - t), + 'pressure': _head.pressure * t + smooth_pressure * (1 - t), + }); + point.pressure = smooth_pressure; + } else { + state.players[player_id].points.push(point); + } + recompute_dynamic_data(state, context); + _head = point; } function geometry_clear_player(state, context, player_id) { diff --git a/client/webgl_listeners.js b/client/webgl_listeners.js index 7b3d0a6..dfe4a3f 100644 --- a/client/webgl_listeners.js +++ b/client/webgl_listeners.js @@ -210,7 +210,7 @@ function mousedown(e, state, context) { } if (state.tools.active === 'pencil') { - canvasp.pressure = 128; + canvasp.pressure = Math.ceil(e.pressure * 255); geometry_clear_player(state, context, state.me); geometry_add_point(state, context, state.me, canvasp); @@ -249,9 +249,10 @@ function mousemove(e, state, context) { if (state.me in state.players) { const me = state.players[state.me]; const width = Math.max(me.width * state.canvas.zoom, 2.0); - const brush_x = screenp.x - width / 2 - 2; - const brush_y = screenp.y - width / 2 - 2; - document.querySelector('.brush-dom').style.transform = `translate(${Math.round(brush_x)}px, ${Math.round(brush_y)}px)`; + const radius = Math.round(width / 2); + const brush_x = screenp.x - radius - 2; + const brush_y = screenp.y - radius - 2; + document.querySelector('.brush-dom').style.transform = `translate(${brush_x}px, ${brush_y}px)`; } if (state.me in state.players && dist_v2(state.players[state.me].cursor, canvasp) > 5) { @@ -376,7 +377,7 @@ function update_cursor(state) { const me = state.players[state.me]; const width = Math.max(me.width * state.canvas.zoom, 2.0); - const radius = width / 2; + const radius = Math.round(width / 2); const current_color = color_from_u32(me.color); const stroke = (me.color === 0xFFFFFF ? 'black' : 'white'); const svg = `