diff --git a/Caddyfile b/Caddyfile
index 9d42ea1..d773b40 100644
--- a/Caddyfile
+++ b/Caddyfile
@@ -1,7 +1,8 @@
-desk.local {
+localhost {
header {
Cross-Origin-Opener-Policy same-origin
Cross-Origin-Embedder-Policy require-corp
+ Cross-Origin-Resource-Policy same-origin
}
redir /ws /ws/
diff --git a/README.txt b/README.txt
index e373c6c..d3c252b 100644
--- a/README.txt
+++ b/README.txt
@@ -81,7 +81,7 @@ Bonus:
+ Account for pressure in quad/bbox calc
+ Adjust curve simplification to include pressure info
+ Migrate old non-pressure desks
- - Check out e.pressure on touch devices
+ + Check out e.pressure on touch devices
- Send pressure in PREDRAW event
- Stroke smoothing
https://github.com/xournalpp/xournalpp/issues/2320
diff --git a/client/bvh.js b/client/bvh.js
index 0ce7712..6c1988f 100644
--- a/client/bvh.js
+++ b/client/bvh.js
@@ -335,9 +335,9 @@ function bvh_construct_rec(state, bvh, strokes, depth) {
const vertical = (max_y - min_y) > (max_x - min_x);
if (vertical) {
- sorted_strokes = strokes.toSorted((a, b) => a.bbox.cy - b.bbox.cy);
+ sorted_strokes = [...strokes].sort();
} else {
- sorted_strokes = strokes.toSorted((a, b) => a.bbox.cx - b.bbox.cx);
+ sorted_strokes = [...strokes].sort();
}
const node_index = bvh_make_internal(bvh);
diff --git a/client/client_send.js b/client/client_send.js
index 5ce8b83..e512cda 100644
--- a/client/client_send.js
+++ b/client/client_send.js
@@ -248,7 +248,7 @@ function sync_queue(state) {
ws.close();
}
- setTimeout(() => sync_queue(state), config.sync_timeout);
+ state.timers.queue_sync = setTimeout(() => sync_queue(state), config.sync_timeout);
}
function push_event(state, event) {
diff --git a/client/config.js b/client/config.js
index a0c1301..ddb3cc3 100644
--- a/client/config.js
+++ b/client/config.js
@@ -32,9 +32,11 @@ const config = {
frames: 500,
},
debug_force_lod: null,
+ demetri_ms: 40,
+ p: 'demetri',
/*
- * points of interest (desk/zoomlevel/x/y
+ * points of interest (desk/zoomlevel/x/y)
* 1/32/-2075/1020
*/
};
diff --git a/client/default.css b/client/default.css
index c110912..fa61d62 100644
--- a/client/default.css
+++ b/client/default.css
@@ -359,6 +359,10 @@ canvas.mousemoving {
}
@media (hover: none) and (pointer: coarse) {
+ html, body {
+ touch-action: none;
+ }
+
.phone-extra-controls {
display: flex;
}
diff --git a/client/math.js b/client/math.js
index 46da54c..e1262d7 100644
--- a/client/math.js
+++ b/client/math.js
@@ -196,6 +196,7 @@ function process_rdp2(zoom, points) {
// TODO: unify with regular process stroke
function process_stroke2(zoom, points) {
+ //const result = smooth_curve(points);
const result = process_rdp2(zoom, points);
return result;
}
@@ -286,6 +287,16 @@ function dot(a, b) {
return a.x * b.x + a.y * b.y;
}
+function dotn(a, b) {
+ let r = 0;
+
+ for (let i = 0; i < a.length; ++i) {
+ r += a[i] * b[i];
+ }
+
+ return r;
+}
+
function mix(a, b, t) {
return a * t + b * (1 - t);
}
@@ -466,3 +477,86 @@ function stroke_intersects_capsule(state, stroke, a, b, radius) {
return false;
}
+
+function estimate_next_point(ts, xs, ys, dt=0) {
+ const N = ts.length;
+
+ // mean values
+ let mx = 0, my = 0, mt = 0;
+
+ for (let i = 0; i < N; ++i) {
+ mt += ts[i];
+ mx += xs[i];
+ my += ys[i];
+ }
+
+ mt /= N;
+ mx /= N;
+ my /= N;
+
+ if (N < 2) {
+ return [xs[N - 1], ys[N - 1]];
+ }
+
+ // orthogonalize against constant term
+ for (let i = 0; i < N; ++i) {
+ ts[i] -= mt;
+ xs[i] -= mx;
+ ys[i] -= my;
+ }
+
+ // dot products against time basis
+ const dtt = dotn(ts, ts);
+ const dtx = dotn(ts, xs);
+ const dty = dotn(ts, ys);
+
+ // reconstruction coefficients
+ const cx = dtx / dtt;
+ const cy = dty / dtt;
+
+ // estimated next values
+ const nx = cx * (ts[N - 1] + dt) + mx;
+ const ny = cy * (ts[N - 1] + dt) + my;
+
+ return [nx, ny];
+}
+
+function smooth_curve(points, window_ms=config.demetri_ms, dt=0) {
+ const result = [];
+
+ for (let i = 0; i < points.length; ++i) {
+ let start_i = i;
+ let end_i = i;
+ const curr_t = points[i].t;
+
+ while (start_i - 1 >= 0) {
+ if (curr_t - points[start_i - 1].t < window_ms) {
+ start_i--;
+ } else {
+ break;
+ }
+ }
+
+ const t_window = [];
+ const x_window = [];
+ const y_window = []
+
+ for (let j = start_i; j < i + 1; ++j) {
+ const p = points[j];
+ t_window.push(p.t);
+ x_window.push(p.x);
+ y_window.push(p.y);
+ }
+
+ const [nx, ny] = estimate_next_point(t_window, x_window, y_window, dt);
+
+ result.push({
+ 'x': nx,
+ 'y': ny,
+ 'pressure': points[i].pressure,
+ });
+ }
+
+ return result;
+}
+
diff --git a/client/offline.html b/client/offline.html
new file mode 100644
index 0000000..b164655
--- /dev/null
+++ b/client/offline.html
@@ -0,0 +1,9 @@
+
+
diff --git a/client/speed.js b/client/speed.js
index 1fdc480..61261b2 100644
--- a/client/speed.js
+++ b/client/speed.js
@@ -20,7 +20,10 @@ function workers_thread_message(workers, message, thread_field=null) {
for (let i = 0; i < workers.length; ++i) {
if (thread_field !== null) {
- const m = structuredClone(message);
+ const m = {};
+ for (const key in message) {
+ m[key] = message[key];
+ }
m[thread_field] = i;
messages.push(m);
} else {
@@ -33,15 +36,24 @@ function workers_thread_message(workers, message, thread_field=null) {
async function init_wasm(state) {
const memory = new WebAssembly.Memory({
- initial: 16384, // F U
- maximum: 16384, // 1GiB
+ initial: 2048, // F U
+ maximum: 2048, // 128MiB
shared: true,
});
- // "Master thread" to do maintance on (static allocations, merging results etc)
- const master_wasm = await WebAssembly.instantiateStreaming(fetch('wasm/lod.wasm'), {
- env: { 'memory': memory }
- });
+ let master_wasm;
+ if (WebAssembly.hasOwnProperty('instantiateStreaming')) {
+ // "Master thread" to do maintance on (static allocations, merging results etc)
+ master_wasm = await WebAssembly.instantiateStreaming(fetch('wasm/lod.wasm'), {
+ env: { 'memory': memory }
+ });
+ } else {
+ const f = await fetch('wasm/lod.wasm');
+ const bytes = await f.arrayBuffer();
+ master_wasm = await WebAssembly.instantiate(bytes, {
+ env: { 'memory': memory }
+ });
+ }
const nworkers = navigator.hardwareConcurrency;
diff --git a/client/wasm/lod.c b/client/wasm/lod.c
index c3ae222..2785d5c 100644
--- a/client/wasm/lod.c
+++ b/client/wasm/lod.c
@@ -1,4 +1,6 @@
+#ifndef FORCE_SCALAR
#include
+#endif
extern char __heap_base;
@@ -82,7 +84,7 @@ rdp_find_max(float *xs, float *ys, unsigned char *pressures, float zoom, int coo
float dir_nx = dy / dist_ab * 255.0f;
float dir_ny = -dx / dist_ab * 255.0f;
-#if 0
+#ifdef FORCE_SCALAR
// Scalar version preserved for reference
for (int i = segment_start + 1; i < segment_end; ++i) {
diff --git a/client/wasm/lod.wasm b/client/wasm/lod.wasm
index 269a030..6d825d3 100755
Binary files a/client/wasm/lod.wasm and b/client/wasm/lod.wasm differ
diff --git a/client/webgl_draw.js b/client/webgl_draw.js
index f99cc85..cb18230 100644
--- a/client/webgl_draw.js
+++ b/client/webgl_draw.js
@@ -131,6 +131,7 @@ function draw_strokes(state, width, height, programs, gl, lod_levels, segment_co
stroke_texture_size,
stroke_data,
stroke_count,
+ opacity_multiplier,
) {
const pr = programs['main'];
@@ -176,6 +177,7 @@ function draw_strokes(state, width, height, programs, gl, lod_levels, segment_co
gl.uniform1i(pr.locations['u_debug_mode'], state.debug.red);
gl.uniform1i(pr.locations['u_stroke_data'], 0);
gl.uniform1i(pr.locations['u_stroke_texture_size'], config.stroke_texture_size);
+ gl.uniform1f(pr.locations['u_opacity_multipliter'], opacity_multiplier);
gl.enableVertexAttribArray(pr.locations['a_pos']);
gl.enableVertexAttribArray(pr.locations['a_a']);
@@ -441,6 +443,7 @@ async function draw(state, context, animate, ts) {
config.stroke_texture_size,
context.stroke_data,
state.events.length, // not really
+ 1.0,
);
}
@@ -463,6 +466,7 @@ async function draw(state, context, animate, ts) {
config.stroke_texture_size,
context.dynamic_stroke_data,
context.dynamic_stroke_count,
+ 0.5,
);
}
@@ -491,7 +495,8 @@ async function draw(state, context, animate, ts) {
textures['ui'],
config.ui_texture_size,
handles.stroke_data,
- 8
+ 8,
+ 1.0,
);
}
diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js
index d7bdc89..dd0bfcb 100644
--- a/client/webgl_geometry.js
+++ b/client/webgl_geometry.js
@@ -193,6 +193,7 @@ function geometry_start_prestroke(state, player_id) {
player.strokes.push({
'empty': false,
'points': [],
+ 'raw_points': [],
'head': null,
'color': player.color,
'width': player.width,
@@ -208,6 +209,97 @@ function geometry_end_prestroke(state, player_id) {
}
function geometry_add_prepoint(state, context, player_id, point, is_pen, raw = false) {
+ if (config.p === 'demetri') {
+ return geometry_add_prepoint_demetri(state, context, player_id, point, is_pen, raw);
+ } else if (config.p == 'raw') {
+ return geometry_add_prepoint_raw(state, context, player_id, point, is_pen, raw);
+ } else if (config.p == 'avg') {
+ return geometry_add_prepoint_new(state, context, player_id, point, is_pen, raw);
+ } else if (config.p == 'perf') {
+ return geometry_add_prepoint_old(state, context, player_id, point, is_pen, raw);
+ }
+}
+
+function geometry_add_prepoint_demetri(state, context, player_id, point, is_pen, raw = false) {
+ if (!state.online) return;
+
+ const player = state.players[player_id];
+ const stroke = player.strokes[player.strokes.length - 1];
+
+ stroke.raw_points.push(point);
+ stroke.points = smooth_curve(stroke.raw_points);
+
+ if (point.pressure < config.min_pressure) {
+ point.pressure = config.min_pressure;
+ }
+
+ recompute_dynamic_data(state, context);
+}
+
+function geometry_add_prepoint_raw(state, context, player_id, point, is_pen, raw = false) {
+ if (!state.online) return;
+
+ const player = state.players[player_id];
+ const stroke = player.strokes[player.strokes.length - 1];
+ const points = stroke.points;
+
+ if (point.pressure < config.min_pressure) {
+ point.pressure = config.min_pressure;
+ }
+
+ points.push(point);
+
+ recompute_dynamic_data(state, context);
+}
+
+function geometry_add_prepoint_new(state, context, player_id, point, is_pen, raw = false) {
+ if (!state.online) return;
+
+ const player = state.players[player_id];
+ const stroke = player.strokes[player.strokes.length - 1];
+ const points = stroke.points;
+
+ if (point.pressure < config.min_pressure) {
+ point.pressure = config.min_pressure;
+ }
+
+ const exp_window = 3;
+
+ if (points.length > exp_window) {
+ let xsum = 0;
+ let ysum = 0;
+
+ const screen_last = canvas_to_screen(state, points[points.length - 1]);
+ const screen_this = canvas_to_screen(state, point);
+
+ const screen_dx = screen_this.x - screen_last.x;
+ const screen_dy = screen_this.y - screen_last.y;
+
+ // TODO: Higher (screen space!) speed gives more weight to the new point
+ const weight_x = 1;
+ const weight_y = 1;
+
+ for (let i = points.length - exp_window; i < points.length; ++i) {
+ xsum += points[i].x * weight_x;
+ ysum += points[i].y * weight_y;
+ }
+
+ xsum += point.x * weight_x;
+ ysum += point.y * weight_y
+
+ points.push({
+ 'x': xsum / (exp_window + 1),
+ 'y': ysum / (exp_window + 1),
+ 'pressure': point.pressure
+ });
+ } else {
+ points.push(point);
+ }
+
+ recompute_dynamic_data(state, context);
+}
+
+function geometry_add_prepoint_old(state, context, player_id, point, is_pen, raw = false) {
if (!state.online) return;
const player = state.players[player_id];
diff --git a/client/webgl_listeners.js b/client/webgl_listeners.js
index d66d027..edb0bba 100644
--- a/client/webgl_listeners.js
+++ b/client/webgl_listeners.js
@@ -196,6 +196,8 @@ function pointerdown(e, state, context) {
const canvasp = screen_to_canvas(state, screenp);
const raw_canvasp = {...canvasp};
+ canvasp.t = performance.now();
+
if (state.snap === 'grid') {
const step = grid_snap_step(state);
canvasp.x = Math.round(canvasp.x / step) * step;
@@ -317,6 +319,8 @@ function pointermove(e, state, context) {
const canvasp = screen_to_canvas(state, screenp);
const raw_canvasp = {...canvasp};
+ canvasp.t = performance.now();
+
if (state.snap === 'grid') {
const step = grid_snap_step(state);
canvasp.x = Math.round(canvasp.x / step) * step;
@@ -430,6 +434,7 @@ function pointermove(e, state, context) {
}
if (state.drawing) {
+ //console.debug(performance.now(), screenp.x, screenp.y);
canvasp.pressure = Math.ceil(e.pressure * 255);
geometry_add_prepoint(state, context, state.me, canvasp, e.pointerType === "pen");
fire_event(state, predraw_event(canvasp.x, canvasp.y));
diff --git a/client/webgl_shaders.js b/client/webgl_shaders.js
index dfe78f8..7a6654e 100644
--- a/client/webgl_shaders.js
+++ b/client/webgl_shaders.js
@@ -77,6 +77,7 @@ const sdf_fs_src = `#version 300 es
uniform int u_debug_mode;
uniform vec3 u_debug_color;
+ uniform float u_opacity_multipliter;
in vec3 v_color;
@@ -84,7 +85,7 @@ const sdf_fs_src = `#version 300 es
void main() {
if (u_debug_mode == 0) {
- float alpha = 0.75;
+ float alpha = 0.75 * u_opacity_multipliter;
FragColor = vec4(v_color * alpha, alpha);
} else {
FragColor = vec4(u_debug_color, 0.8);