-
+
diff --git a/client/index.js b/client/index.js
index 22de0e0..335573e 100644
--- a/client/index.js
+++ b/client/index.js
@@ -25,8 +25,8 @@ const config = {
stroke_texture_size: 1024, // means no more than 1024^2 = 1M strokes in total (this is a LOT. HMH blackboard has like 80K)
dynamic_stroke_texture_size: 128, // means no more than 128^2 = 16K dynamic strokes at once
benchmark: {
- zoom: 0.035,
- offset: { x: 900, y: 400 },
+ zoom: 0.00003,
+ offset: { x: 1400, y: 400 },
frames: 500,
},
};
@@ -170,10 +170,7 @@ function main() {
'starting_index': 0,
'total_points': 0,
- 'coordinates': {
- 'data': null,
- 'count': 0,
- },
+ 'coordinates': tv_create(Float32Array, 4096),
'segments_from': {
'data': null,
@@ -191,6 +188,7 @@ function main() {
'nodes': [],
'root': null,
'pqueue': new MinQueue(1024),
+ 'traverse_stack': tv_create(Uint32Array, 1024),
},
'tools': {
@@ -209,7 +207,6 @@ function main() {
},
'players': {},
- 'onscreen_segments': new Uint32Array(1024),
'debug': {
'red': false,
diff --git a/client/math.js b/client/math.js
index 62fd7b5..f01247a 100644
--- a/client/math.js
+++ b/client/math.js
@@ -126,7 +126,7 @@ function process_stroke(state, zoom, stroke) {
}
function rdp_find_max2(points, start, end) {
- const EPS = 0.5;
+ const EPS = 0.25;
let result = -1;
let max_dist = 0;
@@ -334,68 +334,3 @@ function quad_union(a, b) {
function box_area(box) {
return (box.x2 - box.x1) * (box.y2 - box.y1);
}
-
-function segments_onscreen(state, context, do_clip) {
- // TODO: handle stroke width
-
- if (state.onscreen_segments === null) {
- let total_points = 0;
-
- for (const event of state.events) {
- if (event.type === EVENT.STROKE && !event.deleted && event.points.length > 0) {
- total_points += event.points.length - 1;
- }
- }
-
- if (total_points > 0) {
- state.onscreen_segments = new Uint32Array(total_points * 6);
- }
- }
-
- let at = 0;
-
- const screen_topleft = screen_to_canvas(state, {'x': 0, 'y': 0});
- const screen_bottomright = screen_to_canvas(state, {'x': context.canvas.width, 'y': context.canvas.height});
-
- /*
- screen_topleft.x += 300;
- screen_topleft.y += 300;
- screen_bottomright.x -= 300;
- screen_bottomright.y -= 300;
- */
-
- const screen_topright = { 'x': screen_bottomright.x, 'y': screen_topleft.y };
- const screen_bottomleft = { 'x': screen_topleft.x, 'y': screen_bottomright.y };
- const screen = {'x1': screen_topleft.x, 'y1': screen_topleft.y, 'x2': screen_bottomright.x, 'y2': screen_bottomright.y};
-
- let head = 0;
-
- for (let i = 0; i < state.events.length; ++i) {
- if (state.debug.limit_to && i >= state.debug.render_to) break;
-
- const event = state.events[i];
-
- if (!(state.debug.limit_from && i < state.debug.render_from)) {
- if (event.type === EVENT.STROKE && !event.deleted && event.points.length > 0) {
- if (!do_clip || quads_intersect(screen, event.bbox)) {
- for (let j = 0; j < event.points.length - 1; ++j) {
- let base = head + j * 4;
- // We draw quads as [1, 2, 3, 4, 3, 2]
- state.onscreen_segments[at + 0] = base + 0;
- state.onscreen_segments[at + 1] = base + 1;
- state.onscreen_segments[at + 2] = base + 2;
- state.onscreen_segments[at + 3] = base + 3;
- state.onscreen_segments[at + 4] = base + 2;
- state.onscreen_segments[at + 5] = base + 1;
-
- at += 6;
- }
- }
- }
- }
-
- head += (event.points.length - 1) * 4;
- }
-
- return at;
-}
diff --git a/client/webgl_draw.js b/client/webgl_draw.js
index 80ff898..892a60b 100644
--- a/client/webgl_draw.js
+++ b/client/webgl_draw.js
@@ -72,74 +72,78 @@ function draw(state, context) {
gl.useProgram(context.programs['sdf'].main);
bvh_clip(state, context);
+
const segment_count = geometry_write_instances(state, context);
const dynamic_segment_count = context.dynamic_segment_count;
const dynamic_stroke_count = context.dynamic_stroke_count;
// "Static" data upload
- gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance']);
- gl.bufferData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4, gl.STREAM_DRAW);
- gl.bufferSubData(gl.ARRAY_BUFFER, 0, tv_data(context.instance_data_points));
- gl.bufferSubData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4, tv_data(context.instance_data_ids));
- gl.bindTexture(gl.TEXTURE_2D, context.textures['stroke_data']);
- // TODO: this is stable data, only upload new strokes as they arrive
- upload_square_rgba16ui_texture(gl, context.stroke_data, config.stroke_texture_size);
-
- 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_stroke_count'], state.events.length);
- gl.uniform1i(locations['u_debug_mode'], state.debug.red);
- gl.uniform1i(locations['u_stroke_data'], 0);
- gl.uniform1i(locations['u_stroke_texture_size'], config.stroke_texture_size);
-
- gl.enableVertexAttribArray(locations['a_a']);
- gl.enableVertexAttribArray(locations['a_b']);
- gl.enableVertexAttribArray(locations['a_stroke_id']);
-
- // Points (a, b) and stroke ids are stored in separate cpu buffers so that points can be reused (look at stride and offset values)
- gl.vertexAttribPointer(locations['a_a'], 2, gl.FLOAT, false, 2 * 4, 0);
- gl.vertexAttribPointer(locations['a_b'], 2, gl.FLOAT, false, 2 * 4, 2 * 4);
- gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, 4, context.instance_data_points.size * 4);
-
- gl.vertexAttribDivisor(locations['a_a'], 1);
- gl.vertexAttribDivisor(locations['a_b'], 1);
- gl.vertexAttribDivisor(locations['a_stroke_id'], 1);
-
- // Static draw (everything already bound)
- gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, segment_count);
-
+ if (segment_count > 0) {
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance']);
+ gl.bufferData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4, gl.STREAM_DRAW);
+ gl.bufferSubData(gl.ARRAY_BUFFER, 0, tv_data(context.instance_data_points));
+ gl.bufferSubData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4, tv_data(context.instance_data_ids));
+ gl.bindTexture(gl.TEXTURE_2D, context.textures['stroke_data']);
+ upload_square_rgba16ui_texture(gl, context.stroke_data, config.stroke_texture_size);
+
+ 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_stroke_count'], state.events.length);
+ gl.uniform1i(locations['u_debug_mode'], state.debug.red);
+ gl.uniform1i(locations['u_stroke_data'], 0);
+ gl.uniform1i(locations['u_stroke_texture_size'], config.stroke_texture_size);
+
+ gl.enableVertexAttribArray(locations['a_a']);
+ gl.enableVertexAttribArray(locations['a_b']);
+ gl.enableVertexAttribArray(locations['a_stroke_id']);
+
+ // Points (a, b) and stroke ids are stored in separate cpu buffers so that points can be reused (look at stride and offset values)
+ gl.vertexAttribPointer(locations['a_a'], 2, gl.FLOAT, false, 2 * 4, 0);
+ gl.vertexAttribPointer(locations['a_b'], 2, gl.FLOAT, false, 2 * 4, 2 * 4);
+ gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, 4, context.instance_data_points.size * 4);
+
+ gl.vertexAttribDivisor(locations['a_a'], 1);
+ gl.vertexAttribDivisor(locations['a_b'], 1);
+ gl.vertexAttribDivisor(locations['a_stroke_id'], 1);
+
+ // Static draw (everything already bound)
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, segment_count);
+ }
+
// Dynamic strokes should be drawn above static strokes
gl.clear(gl.DEPTH_BUFFER_BIT);
// Dynamic draw (strokes currently being drawn)
- gl.uniform1i(locations['u_stroke_count'], dynamic_stroke_count);
- gl.uniform1i(locations['u_stroke_data'], 0);
- gl.uniform1i(locations['u_stroke_texture_size'], config.dynamic_stroke_texture_size);
-
- gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_dynamic_instance']);
-
- // Dynamic data upload
- gl.bufferData(gl.ARRAY_BUFFER, context.dynamic_instance_points.size * 4 + context.dynamic_instance_ids.size * 4, gl.STREAM_DRAW);
- gl.bufferSubData(gl.ARRAY_BUFFER, 0, tv_data(context.dynamic_instance_points));
- gl.bufferSubData(gl.ARRAY_BUFFER, context.dynamic_instance_points.size * 4, tv_data(context.dynamic_instance_ids));
- gl.bindTexture(gl.TEXTURE_2D, context.textures['dynamic_stroke_data']);
- upload_square_rgba16ui_texture(gl, context.dynamic_stroke_data, config.dynamic_stroke_texture_size);
-
- gl.enableVertexAttribArray(locations['a_a']);
- gl.enableVertexAttribArray(locations['a_b']);
- gl.enableVertexAttribArray(locations['a_stroke_id']);
-
- // Points (a, b) and stroke ids are stored in separate cpu buffers so that points can be reused (look at stride and offset values)
- gl.vertexAttribPointer(locations['a_a'], 2, gl.FLOAT, false, 2 * 4, 0);
- gl.vertexAttribPointer(locations['a_b'], 2, gl.FLOAT, false, 2 * 4, 2 * 4);
- gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, 4, context.dynamic_instance_points.size * 4);
-
- gl.vertexAttribDivisor(locations['a_a'], 1);
- gl.vertexAttribDivisor(locations['a_b'], 1);
- gl.vertexAttribDivisor(locations['a_stroke_id'], 1);
-
- gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, dynamic_segment_count);
+ if (dynamic_segment_count > 0) {
+ gl.uniform1i(locations['u_stroke_count'], dynamic_stroke_count);
+ gl.uniform1i(locations['u_stroke_data'], 0);
+ gl.uniform1i(locations['u_stroke_texture_size'], config.dynamic_stroke_texture_size);
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_dynamic_instance']);
+
+ // Dynamic data upload
+ gl.bufferData(gl.ARRAY_BUFFER, context.dynamic_instance_points.size * 4 + context.dynamic_instance_ids.size * 4, gl.STREAM_DRAW);
+ gl.bufferSubData(gl.ARRAY_BUFFER, 0, tv_data(context.dynamic_instance_points));
+ gl.bufferSubData(gl.ARRAY_BUFFER, context.dynamic_instance_points.size * 4, tv_data(context.dynamic_instance_ids));
+ gl.bindTexture(gl.TEXTURE_2D, context.textures['dynamic_stroke_data']);
+ upload_square_rgba16ui_texture(gl, context.dynamic_stroke_data, config.dynamic_stroke_texture_size);
+
+ gl.enableVertexAttribArray(locations['a_a']);
+ gl.enableVertexAttribArray(locations['a_b']);
+ gl.enableVertexAttribArray(locations['a_stroke_id']);
+
+ // Points (a, b) and stroke ids are stored in separate cpu buffers so that points can be reused (look at stride and offset values)
+ gl.vertexAttribPointer(locations['a_a'], 2, gl.FLOAT, false, 2 * 4, 0);
+ gl.vertexAttribPointer(locations['a_b'], 2, gl.FLOAT, false, 2 * 4, 2 * 4);
+ gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, 4, context.dynamic_instance_points.size * 4);
+
+ gl.vertexAttribDivisor(locations['a_a'], 1);
+ gl.vertexAttribDivisor(locations['a_b'], 1);
+ gl.vertexAttribDivisor(locations['a_stroke_id'], 1);
+
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, dynamic_segment_count);
+ }
document.getElementById('debug-stats').innerHTML = `
Segments onscreen: ${segment_count}
diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js
index 20649cc..af33102 100644
--- a/client/webgl_geometry.js
+++ b/client/webgl_geometry.js
@@ -82,8 +82,8 @@ function geometry_write_instances(state, context) {
state.segments_from.data = new Uint32Array(state.segments_from.cap);
}
- if (state.segments.cap < state.coordinates.count / 2) {
- state.segments.cap = round_to_pow2(state.coordinates.count, 4096);
+ if (state.segments.cap < state.coordinates.size / 2) {
+ state.segments.cap = round_to_pow2(state.coordinates.size, 4096);
state.segments.data = new Uint32Array(state.segments.cap);
}
@@ -295,8 +295,6 @@ function geometry_clear_player(state, context, player_id) {
}
function add_image(context, image_id, bitmap, p) {
- return; // TODO
-
const x = p.x;
const y = p.y;
const gl = context.gl;
diff --git a/client/webgl_listeners.js b/client/webgl_listeners.js
index 50bfe89..fac2db9 100644
--- a/client/webgl_listeners.js
+++ b/client/webgl_listeners.js
@@ -173,7 +173,7 @@ function mousedown(e, state, context) {
return;
}
- if (e.button !== 0) {
+ if (e.button !== 0 && e.button !== 1) {
return;
}
@@ -186,9 +186,14 @@ function mousedown(e, state, context) {
}
}
- if (state.spacedown) {
+ if (state.spacedown || e.button === 1) {
state.moving = true;
context.canvas.classList.add('moving');
+
+ if (e.button === 1) {
+ context.canvas.classList.add('mousemoving');
+ }
+
return;
}
@@ -258,7 +263,7 @@ function mousemove(e, state, context) {
}
function mouseup(e, state, context) {
- if (e.button !== 0) {
+ if (e.button !== 0 && e.button !== 1) {
return;
}
@@ -269,9 +274,14 @@ function mouseup(e, state, context) {
return;
}
- if (state.moving) {
+ if (state.moving || e.button === 1) {
state.moving = false;
context.canvas.classList.remove('moving');
+
+ if (e.button === 1) {
+ context.canvas.classList.remove('mousemoving');
+ }
+
return;
}
@@ -279,9 +289,12 @@ function mouseup(e, state, context) {
const stroke = geometry_prepare_stroke(state);
if (stroke) {
+ // TODO: be able to add a baked stroke locally
+
+
//geometry_add_stroke(state, context, stroke, 0);
queue_event(state, stroke_event(state));
- geometry_clear_player(state, context, state.me);
+ //geometry_clear_player(state, context, state.me);
schedule_draw(state, context);
}
@@ -479,10 +492,8 @@ function touchend(e, state, context) {
const stroke = geometry_prepare_stroke(state);
- if (false && stroke) { // TODO: FIX!
- geometry_add_stroke(state, context, stroke, 0); // TODO: stroke index
+ if (stroke) {
queue_event(state, stroke_event(state));
- geometry_clear_player(state, context, state.me);
schedule_draw(state, context);
}
diff --git a/server/recv.js b/server/recv.js
index 2281fde..268bc87 100644
--- a/server/recv.js
+++ b/server/recv.js
@@ -39,7 +39,7 @@ async function recv_syn(d, session) {
events.push(event);
}
}
-
+
desks[session.desk_id].sn += we_expect;
desks[session.desk_id].events.push(...events);
session.lsn = lsn;
diff --git a/server/send.js b/server/send.js
index 1ff6157..296e71e 100644
--- a/server/send.js
+++ b/server/send.js
@@ -231,7 +231,7 @@ async function sync_session(session_id) {
const event = desk.events[desk.events.length - 1 - i];
ser.event(s, event);
}
-
+
if (config.DEBUG_PRINT) console.log(`syn ${desk.sn} out`);
await session.ws.send(s.buffer);
diff --git a/server/storage.js b/server/storage.js
index de99b2b..03d17a0 100644
--- a/server/storage.js
+++ b/server/storage.js
@@ -124,6 +124,10 @@ export function startup() {
desks[event.desk_id].events.push(event);
}
+ for (const desk of stored_desks) {
+ desk.sn = desk.events.length;
+ }
+
for (const session of stored_sessions) {
session.state = SESSION.CLOSED;
session.ws = null;