Browse Source

Significantly improve stroke smoothing and pressure handling. "Fix" issue with underallocation of WASM memory by doubling the size

ssao
A.Olokhtonov 6 months ago
parent
commit
eb66ffbcad
  1. 67
      client/math.js
  2. 3
      client/speed.js
  3. 2
      client/wasm/lod.c
  4. BIN
      client/wasm/lod.wasm
  5. 29
      client/webgl_geometry.js
  6. 11
      client/webgl_listeners.js

67
client/math.js

@ -60,20 +60,24 @@ function process_rdp_indices(state, zoom, stroke) { @@ -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;
let start = points.length - up_to;
if (start < 0) {
start = 0;
}
for (let i = 1; i < points.length; ++i) {
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) { @@ -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) { @@ -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;
@ -145,9 +149,48 @@ function process_rdp_r2(zoom, points, start, end) { @@ -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;
}

3
client/speed.js

@ -120,8 +120,9 @@ function wasm_ensure_by(state, nstrokes, ncoords) { @@ -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();

2
client/wasm/lod.c

@ -220,6 +220,7 @@ do_lod(int *clipped_indices, int clipped_count, float zoom, @@ -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, @@ -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];

BIN
client/wasm/lod.wasm

Binary file not shown.

29
client/webgl_geometry.js

@ -151,10 +151,39 @@ function recompute_dynamic_data(state, context) { @@ -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;
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) {

11
client/webgl_listeners.js

@ -210,7 +210,7 @@ function mousedown(e, state, context) { @@ -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) { @@ -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) { @@ -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 = `<svg style="display: block" xmlns="http://www.w3.org/2000/svg" width="${width + 4}" height="${width + 4}">

Loading…
Cancel
Save