diff --git a/client/client_recv.js b/client/client_recv.js
index 4ff729f..b8d16b4 100644
--- a/client/client_recv.js
+++ b/client/client_recv.js
@@ -126,14 +126,15 @@ function bitmap_bbox(event) {
return bbox;
}
-function init_player_defaults(state, player_id) {
+function init_player_defaults(state, player_id, color = config.default_color, width = config.default_width) {
state.players[player_id] = {
- 'color': config.default_color,
- 'width': config.default_width,
+ 'color': color,
+ 'width': width,
+ 'points': [],
};
}
-function handle_event(state, context, event, relax = false) {
+function handle_event(state, context, event) {
if (config.debug_print) console.debug(`event type ${event.type} from user ${event.user_id}`);
let need_draw = false;
@@ -144,7 +145,7 @@ function handle_event(state, context, event, relax = false) {
switch (event.type) {
case EVENT.PREDRAW: {
- update_dynamic_stroke(state, context, event.user_id, {'x': event.x, 'y': event.y});
+ geometry_add_point(state, context, event.user_id, {'x': event.x, 'y': event.y});
need_draw = true;
break;
}
@@ -161,11 +162,11 @@ function handle_event(state, context, event, relax = false) {
case EVENT.STROKE: {
if (event.user_id != state.me) {
- clear_dynamic_stroke(state, context, event.user_id);
+ geometry_clear_player(state, context, event.user_id);
need_draw = true;
}
- add_static_stroke(state, context, event, relax);
+ geometry_add_stroke(state, context, event);
break;
}
@@ -335,21 +336,17 @@ async function handle_message(state, context, d) {
const user_color = des_u32(d);
const user_width = des_u16(d);
- state.players[user_id] = {
- 'color': user_color,
- 'width': user_width,
- };
+ init_player_defaults(state, user_id, user_color, user_width);
}
for (let i = 0; i < event_count; ++i) {
const event = des_event(d);
- handle_event(state, context, event, true);
+ handle_event(state, context, event);
state.events.push(event);
}
do_draw = true;
- recompute_static_data(context);
send_ack(event_count);
sync_queue(state);
diff --git a/client/client_send.js b/client/client_send.js
index 4b10340..571d618 100644
--- a/client/client_send.js
+++ b/client/client_send.js
@@ -264,10 +264,12 @@ function image_move_event(image_id, x, y) {
}
function stroke_event(state) {
+ const stroke = geometry_prepare_stroke(state);
+
return {
'type': EVENT.STROKE,
- 'points': process_stroke(state.current_strokes[state.me].points),
- 'width': state.current_strokes[state.me].width,
- 'color': state.current_strokes[state.me].color,
+ 'points': stroke.points,
+ 'width': stroke.width,
+ 'color': stroke.color,
};
}
diff --git a/client/index.html b/client/index.html
index 1b637dc..575a3bc 100644
--- a/client/index.html
+++ b/client/index.html
@@ -7,20 +7,20 @@
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
+
+
+
diff --git a/client/index.js b/client/index.js
index edcad31..ef16ee1 100644
--- a/client/index.js
+++ b/client/index.js
@@ -10,11 +10,13 @@ const config = {
second_finger_timeout: 500,
buffer_first_touchmoves: 5,
debug_print: true,
- min_zoom: 0.01,
- max_zoom: 100.0,
+ min_zoom: 0.05,
+ max_zoom: 10.0,
initial_offline_timeout: 1000,
default_color: 0x00,
default_width: 8,
+ bytes_per_point: 20,
+ initial_static_bytes: 4096,
};
const EVENT = Object.freeze({
@@ -105,33 +107,18 @@ function main() {
const context = {
'canvas': null,
'gl': null,
+
'programs': {},
'buffers': {},
'locations': {},
'textures': {},
- 'dynamic_positions': {},
- 'dynamic_colors': {},
-
- 'dynamic_circle_positions': {},
- 'dynamic_circle_colors': {},
-
'quad_positions': [],
'quad_texcoords': [],
- 'static_positions': [],
- 'static_colors': [],
- 'static_circle_positions': [],
- 'static_circle_colors': [],
- 'static_positions_f32': new Float32Array(0),
- 'dynamic_positions_f32': new Float32Array(0),
- 'static_colors_u8': new Uint8Array(0),
- 'dynamic_colors_u8': new Uint8Array(0),
- 'static_circle_positions_f32': new Float32Array(0),
- 'dynamic_circle_positions_f32': new Float32Array(0),
- 'static_circle_colors_u8': new Uint8Array(0),
- 'dynamic_circle_colors_u8': new Uint8Array(0),
- 'quad_positions_f32': new Float32Array(0),
- 'quad_texcoords_f32': new Float32Array(0),
+
+ 'static_stroke_serializer': serializer_create(config.initial_static_bytes),
+ 'dynamic_stroke_serializer': serializer_create(config.initial_static_bytes),
+
'bgcolor': {'r': 1.0, 'g': 1.0, 'b': 1.0},
'active_image': null,
diff --git a/client/math.js b/client/math.js
index b11ffd9..0f16bd2 100644
--- a/client/math.js
+++ b/client/math.js
@@ -11,8 +11,8 @@ function point_right_of_line(a, b, p) {
return ((b.x - a.x) * (a.y - p.y) - (a.y - b.y) * (p.x - a.x)) <= 0;
}
-function rdp_find_max(points, start, end) {
- const EPS = 0.5; // TODO: base this on zoom (and/or "speed")
+function rdp_find_max(state, points, start, end) {
+ const EPS = 0.5 / state.canvas.zoom;
let result = -1;
let max_dist = 0;
@@ -50,22 +50,22 @@ function rdp_find_max(points, start, end) {
return result;
}
-function process_rdp_r(points, start, end) {
+function process_rdp_r(state, points, start, end) {
let result = [];
- const max = rdp_find_max(points, start, end);
+ const max = rdp_find_max(state, points, start, end);
if (max !== -1) {
- const before = process_rdp_r(points, start, max);
- const after = process_rdp_r(points, max, end);
+ const before = process_rdp_r(state, points, start, max);
+ const after = process_rdp_r(state, points, max, end);
result = [...before, points[max], ...after];
}
return result;
}
-function process_rdp(points) {
- const result = process_rdp_r(points, 0, points.length - 1);
+function process_rdp(state, points) {
+ const result = process_rdp_r(state, points, 0, points.length - 1);
result.unshift(points[0]);
result.push(points[points.length - 1]);
return result;
@@ -73,7 +73,7 @@ function process_rdp(points) {
function process_ewmv(points, round = false) {
const result = [];
- const alpha = 0.4;
+ const alpha = 0.5;
result.push(points[0]);
@@ -87,9 +87,9 @@ function process_ewmv(points, round = false) {
return result;
}
-function process_stroke(points) {
+function process_stroke(state, points) {
// const result0 = process_ewmv(points);
- const result1 = process_rdp(points, true);
+ const result1 = process_rdp(state, points, true);
return result1;
}
diff --git a/client/webgl_draw.js b/client/webgl_draw.js
index ed26b63..e50810a 100644
--- a/client/webgl_draw.js
+++ b/client/webgl_draw.js
@@ -4,7 +4,6 @@ function schedule_draw(state, context) {
state.timers.raf = true;
}
}
-
function draw(state, context) {
state.timers.raf = false;
@@ -19,7 +18,7 @@ function draw(state, context) {
gl.clearColor(context.bgcolor.r, context.bgcolor.g, context.bgcolor.b, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
- // Draw images
+ // Images
locations = context.locations['quad'];
buffers = context.buffers['quad'];
@@ -31,9 +30,8 @@ function draw(state, context) {
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_layer'], 0);
gl.uniform1i(locations['u_texture'], 0);
-
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_pos']);
gl.vertexAttribPointer(locations['a_pos'], 2, gl.FLOAT, false, 0, 0);
gl.bufferData(gl.ARRAY_BUFFER, context.quad_positions_f32, gl.STATIC_DRAW);
@@ -45,7 +43,6 @@ function draw(state, context) {
const count = Object.keys(context.textures).length;
let active_image_index = -1;
- gl.uniform1i(locations['u_layer'], 0);
gl.uniform1i(locations['u_outline'], 0);
for (let key = 0; key < count; ++key) {
@@ -59,7 +56,6 @@ function draw(state, context) {
}
if (active_image_index !== -1) {
- gl.uniform1i(locations['u_layer'], 1);
gl.uniform1i(locations['u_outline'], 1);
gl.bindTexture(gl.TEXTURE_2D, context.textures[active_image_index].texture);
gl.drawArrays(gl.TRIANGLES, active_image_index * 6, 6);
@@ -71,38 +67,7 @@ function draw(state, context) {
gl.useProgram(context.programs['stroke']);
- gl.enableVertexAttribArray(locations['a_pos']);
- gl.enableVertexAttribArray(locations['a_color']);
-
- 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_layer'], 1);
-
- const total_pos_size = context.static_positions_f32.byteLength + context.dynamic_positions_f32.byteLength;
- const total_color_size = context.static_colors_u8.byteLength + context.dynamic_colors_u8.byteLength;
- const total_point_count = (context.static_positions.length + total_dynamic_positions(context)) / 2;
-
- gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_pos']);
- gl.vertexAttribPointer(locations['a_pos'], 2, gl.FLOAT, false, 0, 0);
- gl.bufferData(gl.ARRAY_BUFFER, total_pos_size, gl.DYNAMIC_DRAW);
- gl.bufferSubData(gl.ARRAY_BUFFER, 0, context.static_positions_f32);
- gl.bufferSubData(gl.ARRAY_BUFFER, context.static_positions_f32.byteLength, context.dynamic_positions_f32);
-
- gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_color']);
- gl.vertexAttribPointer(locations['a_color'], 3, gl.UNSIGNED_BYTE, true, 0, 0);
- gl.bufferData(gl.ARRAY_BUFFER, total_color_size, gl.DYNAMIC_DRAW);
- gl.bufferSubData(gl.ARRAY_BUFFER, 0, context.static_colors_u8);
- gl.bufferSubData(gl.ARRAY_BUFFER, context.static_colors_u8.byteLength, context.dynamic_colors_u8);
-
- gl.drawArrays(gl.TRIANGLES, 0, total_point_count);
-
- // Circles
- locations = context.locations['circle'];
- buffers = context.buffers['circle'];
-
- gl.useProgram(context.programs['circle']);
-
+ gl.enableVertexAttribArray(locations['a_type']);
gl.enableVertexAttribArray(locations['a_pos']);
gl.enableVertexAttribArray(locations['a_texcoord']);
gl.enableVertexAttribArray(locations['a_color']);
@@ -110,50 +75,17 @@ function draw(state, context) {
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_layer'], 1);
-
- const total_circle_pos_size = context.static_circle_positions_f32.byteLength + context.dynamic_circle_positions_f32.byteLength;
- const total_circle_color_size = context.static_circle_colors_u8.byteLength + context.dynamic_circle_colors_u8.byteLength;
- const total_circle_point_count = (context.static_circle_positions.length + total_dynamic_circle_positions(context)) / 2;
-
- gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_pos']);
- gl.vertexAttribPointer(locations['a_pos'], 2, gl.FLOAT, false, 0, 0);
- gl.bufferData(gl.ARRAY_BUFFER, total_circle_pos_size, gl.DYNAMIC_DRAW);
- gl.bufferSubData(gl.ARRAY_BUFFER, 0, context.static_circle_positions_f32);
- gl.bufferSubData(gl.ARRAY_BUFFER, context.static_circle_positions_f32.byteLength, context.dynamic_circle_positions_f32);
-
- gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_color']);
- gl.vertexAttribPointer(locations['a_color'], 3, gl.UNSIGNED_BYTE, true, 0, 0);
- gl.bufferData(gl.ARRAY_BUFFER, total_circle_color_size, gl.DYNAMIC_DRAW);
- gl.bufferSubData(gl.ARRAY_BUFFER, 0, context.static_circle_colors_u8);
- gl.bufferSubData(gl.ARRAY_BUFFER, context.static_circle_colors_u8.byteLength, context.dynamic_circle_colors_u8);
-
- // TODO: move this somewhere?
- const circle_quad_uv = new Float32Array(total_circle_point_count * 2);
- for (let quad = 0; quad < total_circle_point_count / 6; ++quad) {
- circle_quad_uv[quad * 12 + 0] = 0;
- circle_quad_uv[quad * 12 + 1] = 0;
-
- circle_quad_uv[quad * 12 + 2] = 0;
- circle_quad_uv[quad * 12 + 3] = 1;
-
- circle_quad_uv[quad * 12 + 4] = 1;
- circle_quad_uv[quad * 12 + 5] = 0;
-
- circle_quad_uv[quad * 12 + 6] = 1;
- circle_quad_uv[quad * 12 + 7] = 1;
-
- circle_quad_uv[quad * 12 + 8] = 1;
- circle_quad_uv[quad * 12 + 9] = 0;
-
- circle_quad_uv[quad * 12 + 10] = 0;
- circle_quad_uv[quad * 12 + 11] = 1;
- }
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_packed']);
+
+ gl.vertexAttribPointer(locations['a_pos'], 2, gl.FLOAT, false, config.bytes_per_point, 0);
+ gl.vertexAttribPointer(locations['a_texcoord'], 2, gl.FLOAT, false, config.bytes_per_point, 8);
+ gl.vertexAttribPointer(locations['a_color'], 3, gl.UNSIGNED_BYTE, true, config.bytes_per_point, 16);
+ gl.vertexAttribPointer(locations['a_type'], 1, gl.UNSIGNED_BYTE, false, config.bytes_per_point, 19);
- gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_texcoord']);
- gl.vertexAttribPointer(locations['a_texcoord'], 2, gl.FLOAT, false, 0, 0);
- gl.bufferData(gl.ARRAY_BUFFER, circle_quad_uv, gl.DYNAMIC_DRAW);
+ gl.bufferData(gl.ARRAY_BUFFER, context.static_stroke_serializer.buffer, gl.STATIC_DRAW);
+ gl.drawArrays(gl.TRIANGLES, 0, context.static_stroke_serializer.offset / config.bytes_per_point);
- gl.drawArrays(gl.TRIANGLES, 0, total_circle_point_count);
+ gl.bufferData(gl.ARRAY_BUFFER, context.dynamic_stroke_serializer.buffer, gl.STATIC_DRAW);
+ gl.drawArrays(gl.TRIANGLES, 0, context.dynamic_stroke_serializer.offset / config.bytes_per_point);
}
\ No newline at end of file
diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js
index 1a3be14..2696f55 100644
--- a/client/webgl_geometry.js
+++ b/client/webgl_geometry.js
@@ -1,14 +1,35 @@
-function push_circle_at(circle_positions, cl, r, g, b, c, radius) {
- circle_positions.push(c.x - radius, c.y - radius, c.x - radius, c.y + radius, c.x + radius, c.y - radius);
- circle_positions.push(c.x + radius, c.y + radius, c.x + radius, c.y - radius, c.x - radius, c.y + radius);
+function push_point(s, x, y, u, v, r, g, b, type) {
+ ser_f32(s, x);
+ ser_f32(s, y);
+ ser_f32(s, u);
+ ser_f32(s, v);
+ ser_u8(s, r);
+ ser_u8(s, g);
+ ser_u8(s, b);
+ ser_u8(s, type);
+}
- for (let i = 0; i < 6; ++i) {
- cl.push(r, g, b);
- }
+function push_circle(s, cx, cy, radius, r, g, b) {
+ push_point(s, cx - radius, cy - radius, 0, 0, r, g, b, 1);
+ push_point(s, cx - radius, cy + radius, 0, 1, r, g, b, 1);
+ push_point(s, cx + radius, cy - radius, 1, 0, r, g, b, 1);
+
+ push_point(s, cx + radius, cy + radius, 1, 1, r, g, b, 1);
+ push_point(s, cx + radius, cy - radius, 1, 0, r, g, b, 1);
+ push_point(s, cx - radius, cy + radius, 0, 1, r, g, b, 1);
+}
+
+function push_quad(s, p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y, r, g, b) {
+ push_point(s, p1x, p1y, 0, 0, r, g, b, 0);
+ push_point(s, p2x, p2y, 0, 1, r, g, b, 0);
+ push_point(s, p3x, p3y, 1, 0, r, g, b, 0);
+
+ push_point(s, p4x, p4y, 1, 1, r, g, b, 0);
+ push_point(s, p3x, p3y, 1, 0, r, g, b, 0);
+ push_point(s, p2x, p2y, 0, 1, r, g, b, 0);
}
-function push_stroke(state, stroke, positions, colors, circle_positions, circle_colors) {
- const starting_length = positions.length;
+function push_stroke(s, stroke) {
const stroke_width = stroke.width;
const points = stroke.points;
const color_u32 = stroke.color;
@@ -17,15 +38,14 @@ function push_stroke(state, stroke, positions, colors, circle_positions, circle_
const g = (color_u32 >> 8) & 0xFF;
const b = color_u32 & 0xFF;
- if (points.length < 2) {
- // TODO
- stroke.popcount = 0;
+ if (points.length === 0) {
return;
}
- // Simple 12 point circle (store offsets and reuse)
- const POINTS = 12;
- const phi_step = 2 * Math.PI / POINTS;
+ if (points.length === 1) {
+ push_circle(s, points[0].x, points[0].y, stroke_width / 2, r, g, b);
+ return;
+ }
for (let i = 0; i < points.length - 1; ++i) {
const px = points[i].x;
@@ -56,44 +76,16 @@ function push_stroke(state, stroke, positions, colors, circle_positions, circle_
const s4x = nextpx - perp1x * stroke_width / 2;
const s4y = nextpy - perp1y * stroke_width / 2;
- positions.push(s1x, s1y, s2x, s2y, s4x, s4y);
- positions.push(s1x, s1y, s4x, s4y, s3x, s3y);
-
- for (let j = 0; j < 6; ++j) {
- colors.push(r, g, b);
- }
-
- // Rotate circle offsets so that the diameter of the circle is
- // perpendicular to the (dx, dy) vector. This way the circle won't
- // "poke out" of the rectangle
- const angle = Math.atan(Math.abs(s3x - s4x), Math.abs(s3y - s4y));
-
- push_circle_at(circle_positions, circle_colors, r, g, b, points[i], stroke_width / 2);
+ push_quad(s, s2x, s2y, s1x, s1y, s4x, s4y, s3x, s3y, r, g, b);
+ push_circle(s, px, py, stroke_width / 2, r, g, b);
}
- push_circle_at(circle_positions, circle_colors, r, g, b, points[points.length - 1], stroke_width / 2);
+ const lastp = points[points.length - 1];
- stroke.popcount = positions.length - starting_length;
+ push_circle(s, lastp.x, lastp.y, stroke_width / 2, r, g, b);
}
-function pop_stroke(state, context) {
- console.error('undo')
- // if (state.strokes.length > 0) {
- // // TODO: this will not work once we have multiple players
- // // because there can be others strokes after mine
- // console.error('TODO: multiplayer undo');
-
- // const popped = state.strokes.pop();
-
- // context.static_positions.length -= popped.popcount;
- // context.static_colors.length -= popped.popcount / 2 * 3;
-
- // context.static_positions_f32 = new Float32Array(context.static_positions);
- // context.static_colors_u8 = new Uint8Array(context.static_colors);
- // }
-}
-
-function get_static_stroke(state) {
+function geometry_prepare_stroke(state) {
if (!state.online) {
return null;
}
@@ -101,143 +93,66 @@ function get_static_stroke(state) {
return {
'color': state.players[state.me].color,
'width': state.players[state.me].width,
- 'points': process_stroke(state.current_strokes[state.me].points),
+ 'points': process_stroke(state, state.players[state.me].points),
'user_id': state.me,
};
}
-function add_static_stroke(state, context, stroke, relax = false) {
+function geometry_add_stroke(state, context, stroke) {
if (!state.online || !stroke) return;
- push_stroke(state, stroke, context.static_positions, context.static_colors, context.static_circle_positions, context.static_circle_colors);
+ const bytes_left = context.static_stroke_serializer.size - context.static_stroke_serializer.offset;
+ const bytes_needed = (stroke.points.length * 12 + 6) * config.bytes_per_point;
- if (!relax) {
- // TODO: incremental
+ if (bytes_left < bytes_needed) {
+ const old_view = context.static_stroke_serializer.strview;
+ const old_offset = context.static_stroke_serializer.offset;
- context.static_positions_f32 = new Float32Array(context.static_positions);
- context.static_colors_u8 = new Uint8Array(context.static_colors);
+ const new_size = Math.ceil((context.static_stroke_serializer.size + bytes_needed) * 1.62);
- context.static_circle_positions_f32 = new Float32Array(context.static_circle_positions);
- context.static_circle_colors_u8 = new Uint8Array(context.static_circle_colors);
+ context.static_stroke_serializer = serializer_create(new_size);
+ context.static_stroke_serializer.strview.set(old_view);
+ context.static_stroke_serializer.offset = old_offset;
}
-}
-function recompute_static_data(context) {
- context.static_positions_f32 = new Float32Array(context.static_positions);
- context.static_colors_u8 = new Uint8Array(context.static_colors);
-
- context.static_circle_positions_f32 = new Float32Array(context.static_circle_positions);
- context.static_circle_colors_u8 = new Uint8Array(context.static_circle_colors);
+ push_stroke(context.static_stroke_serializer, stroke);
}
-function total_dynamic_positions(context) {
- let total_dynamic_length = 0;
+function recompute_dynamic_data(state, context) {
+ let bytes_needed = 0;
- for (const player_id in context.dynamic_positions) {
- total_dynamic_length += context.dynamic_positions[player_id].length;
+ for (const player_id in state.players) {
+ const player = state.players[player_id];
+ if (player.points.length > 0) {
+ bytes_needed += (player.points.length * 12 + 6) * config.bytes_per_point;
+ }
}
- return total_dynamic_length;
-}
-
-function total_dynamic_circle_positions(context) {
- let total_dynamic_length = 0;
-
- for (const player_id in context.dynamic_circle_positions) {
- total_dynamic_length += context.dynamic_circle_positions[player_id].length;
+ if (bytes_needed > context.dynamic_stroke_serializer.size) {
+ context.dynamic_stroke_serializer = serializer_create(Math.ceil(bytes_needed * 1.62));
+ } else {
+ context.dynamic_stroke_serializer.offset = 0;
}
- return total_dynamic_length;
-}
-
-function recompute_dynamic_data(state, context) {
- const total_dynamic_length = total_dynamic_positions(context);
- const total_dynamic_circles_length = total_dynamic_circle_positions(context);
-
- context.dynamic_positions_f32 = new Float32Array(total_dynamic_length);
- context.dynamic_colors_u8 = new Uint8Array(total_dynamic_length / 2 * 3);
-
- context.dynamic_circle_positions_f32 = new Float32Array(total_dynamic_circles_length);
- context.dynamic_circle_colors_u8 = new Uint8Array(total_dynamic_circles_length / 2 * 3);
-
- let at = 0;
- let at_circle = 0;
-
- for (const player_id in context.dynamic_positions) {
- context.dynamic_positions_f32.set(context.dynamic_positions[player_id], at);
- context.dynamic_circle_positions_f32.set(context.dynamic_circle_positions[player_id], at_circle);
-
- const color_u32 = state.players[player_id].color;
-
- const r = (color_u32 >> 16) & 0xFF;
- const g = (color_u32 >> 8) & 0xFF;
- const b = color_u32 & 0xFF;
-
- for (let i = 0; i < context.dynamic_positions[player_id].length; ++i) {
- context.dynamic_colors_u8[at / 2 * 3 + i * 3 + 0] = r;
- context.dynamic_colors_u8[at / 2 * 3 + i * 3 + 1] = g;
- context.dynamic_colors_u8[at / 2 * 3 + i * 3 + 2] = b;
- }
-
- for (let i = 0; i < context.dynamic_circle_positions[player_id].length; ++i) {
- context.dynamic_circle_colors_u8[at_circle / 2 * 3 + i * 3 + 0] = r;
- context.dynamic_circle_colors_u8[at_circle / 2 * 3 + i * 3 + 1] = g;
- context.dynamic_circle_colors_u8[at_circle / 2 * 3 + i * 3 + 2] = b;
+ for (const player_id in state.players) {
+ // player has the same data as their current stroke: points, color, width
+ const player = state.players[player_id];
+ if (player.points.length > 0) {
+ push_stroke(context.dynamic_stroke_serializer, player);
}
-
- at += context.dynamic_positions[player_id].length;
- at_circle += context.dynamic_circle_positions[player_id].length;
}
}
-function update_dynamic_stroke(state, context, player_id, point) {
+function geometry_add_point(state, context, player_id, point) {
if (!state.online) return;
-
- if (!(player_id in state.current_strokes)) {
- state.current_strokes[player_id] = {
- 'points': [],
- 'width': state.players[player_id].width,
- 'color': state.players[player_id].color,
- };
-
- context.dynamic_positions[player_id] = [];
- context.dynamic_colors[player_id] = [];
-
- context.dynamic_circle_positions[player_id] = [];
- context.dynamic_circle_colors[player_id] = [];
- }
-
- state.current_strokes[player_id].color = state.players[player_id].color;
- state.current_strokes[player_id].width = state.players[player_id].width;
-
- // TODO: incremental
- context.dynamic_positions[player_id].length = 0;
- context.dynamic_colors[player_id].length = 0;
-
- context.dynamic_circle_positions[player_id].length = 0;
- context.dynamic_circle_colors[player_id].length = 0;
-
- state.current_strokes[player_id].points.push(point);
-
- push_stroke(state, state.current_strokes[player_id],
- context.dynamic_positions[player_id], context.dynamic_colors[player_id],
- context.dynamic_circle_positions[player_id], context.dynamic_circle_colors[player_id]
- );
-
+ state.players[player_id].points.push(point);
recompute_dynamic_data(state, context);
}
-function clear_dynamic_stroke(state, context, player_id) {
+function geometry_clear_player(state, context, player_id) {
if (!state.online) return;
-
- if (player_id in state.current_strokes) {
- state.current_strokes[player_id].points.length = 0;
- state.current_strokes[player_id].color = state.players[state.me].color;
- state.current_strokes[player_id].width = state.players[state.me].width;
- context.dynamic_positions[player_id].length = 0;
- context.dynamic_circle_positions[player_id].length = 0;
- recompute_dynamic_data(state, context);
- }
+ state.players[player_id].points.length = 0;
+ recompute_dynamic_data(state, context);
}
function add_image(context, image_id, bitmap, p) {
diff --git a/client/webgl_listeners.js b/client/webgl_listeners.js
index 859b2ad..2ed2f9b 100644
--- a/client/webgl_listeners.js
+++ b/client/webgl_listeners.js
@@ -95,8 +95,8 @@ function mousedown(e, state, context) {
return;
}
- clear_dynamic_stroke(state, context, state.me);
- update_dynamic_stroke(state, context, state.me, canvasp);
+ geometry_clear_player(state, context, state.me);
+ geometry_add_point(state, context, state.me, canvasp);
state.drawing = true;
context.active_image = null;
@@ -128,7 +128,7 @@ function mousemove(e, state, context) {
state.cursor = screenp;
if (state.drawing) {
- update_dynamic_stroke(state, context, state.me, canvasp);
+ geometry_add_point(state, context, state.me, canvasp);
fire_event(state, predraw_event(canvasp.x, canvasp.y));
do_draw = true;
}
@@ -159,12 +159,12 @@ function mouseup(e, state, context) {
}
if (state.drawing) {
- const stroke = get_static_stroke(state);
+ const stroke = geometry_prepare_stroke(state);
if (stroke) {
- add_static_stroke(state, context, stroke);
+ geometry_add_stroke(state, context, stroke);
queue_event(state, stroke_event(state));
- clear_dynamic_stroke(state, context, state.me);
+ geometry_clear_player(state, context, state.me);
schedule_draw(state, context);
}
@@ -281,17 +281,17 @@ function touchmove(e, state, context) {
} else {
// Handle buffered moves
if (state.touch.buffered.length > 0) {
- clear_dynamic_stroke(state, context, state.me);
+ geometry_clear_player(state, context, state.me);
for (const p of state.touch.buffered) {
- update_dynamic_stroke(state, context, state.me, p);
+ geometry_add_point(state, context, state.me, p);
fire_event(state, predraw_event(p.x, p.y));
}
state.touch.buffered.length = 0;
}
- update_dynamic_stroke(state, context, state.me, canvasp);
+ geometry_add_point(state, context, state.me, canvasp);
fire_event(state, predraw_event(canvasp.x, canvasp.y));
schedule_draw(state, context);
@@ -367,19 +367,16 @@ function touchend(e, state, context) {
// const event = stroke_event();
// await queue_event(event);
- const stroke = {
- 'color': state.players[state.me].color,
- 'width': state.players[state.me].width,
- 'points': process_stroke(state.current_strokes[state.me].points),
- 'user_id': state.me,
- };
+ const stroke = geometry_prepare_stroke(state);
- add_static_stroke(state, context, stroke);
- queue_event(state, stroke_event(state));
- clear_dynamic_stroke(state, context, state.me);
- state.touch.drawing = false;
+ if (stroke) {
+ geometry_add_stroke(state, context, stroke);
+ queue_event(state, stroke_event(state));
+ geometry_clear_player(state, context, state.me);
+ schedule_draw(state, context);
+ }
- window.requestAnimationFrame(() => draw(state, context))
+ state.touch.drawing = false;
}
}
diff --git a/client/webgl_shaders.js b/client/webgl_shaders.js
index 41bba3c..1591867 100644
--- a/client/webgl_shaders.js
+++ b/client/webgl_shaders.js
@@ -1,21 +1,30 @@
const stroke_vs_src = `
+ attribute float a_type;
attribute vec2 a_pos;
+ attribute vec2 a_texcoord;
attribute vec3 a_color;
uniform vec2 u_scale;
uniform vec2 u_res;
uniform vec2 u_translation;
- uniform int u_layer;
varying vec3 v_color;
+ varying vec2 v_texcoord;
+ varying float v_type;
+ varying float v_scale;
void main() {
vec2 screen01 = (a_pos * u_scale + u_translation) / u_res;
vec2 screen02 = screen01 * 2.0;
+
screen02.y = 2.0 - screen02.y;
- vec2 screen11 = screen02 - 1.0;
+
v_color = a_color;
- gl_Position = vec4(screen11, u_layer, 1);
+ v_texcoord = a_texcoord * 2.0 - 1.0;
+ v_type = a_type;
+ v_scale = u_scale.x;
+
+ gl_Position = vec4(screen02 - 1.0, 0, 1);
}
`;
@@ -23,9 +32,21 @@ const stroke_fs_src = `
precision mediump float;
varying vec3 v_color;
+ varying vec2 v_texcoord;
+ varying float v_type;
+ varying float v_scale;
void main() {
- gl_FragColor = vec4(v_color, 1.0);
+ float v;
+
+ if (v_type > 0.5) {
+ v = length(v_texcoord);
+ } else {
+ v = abs(v_texcoord.y);
+ }
+
+ float col = smoothstep(1.0, (1.0 - 0.05 / v_scale), v);
+ gl_FragColor = vec4(col * v_color, col);
}
`;
@@ -36,7 +57,6 @@ const tquad_vs_src = `
uniform vec2 u_scale;
uniform vec2 u_res;
uniform vec2 u_translation;
- uniform int u_layer;
varying vec2 v_texcoord;
@@ -46,7 +66,7 @@ const tquad_vs_src = `
screen02.y = 2.0 - screen02.y;
vec2 screen11 = screen02 - 1.0;
v_texcoord = a_texcoord;
- gl_Position = vec4(screen11, u_layer, 1);
+ gl_Position = vec4(screen11, 0, 1);
}
`;
@@ -57,7 +77,7 @@ const tquad_fs_src = `
uniform sampler2D u_texture;
uniform bool u_outline;
-
+
void main() {
if (!u_outline) {
gl_FragColor = texture2D(u_texture, v_texcoord);
@@ -67,49 +87,12 @@ const tquad_fs_src = `
}
`;
-const tcircle_vs_src = `
- attribute vec2 a_pos;
- attribute vec2 a_texcoord;
- attribute vec3 a_color;
-
- uniform vec2 u_scale;
- uniform vec2 u_res;
- uniform vec2 u_translation;
- uniform int u_layer;
-
- varying vec2 v_texcoord;
- varying vec3 v_color;
-
- void main() {
- vec2 screen01 = (a_pos * u_scale + u_translation) / u_res;
- vec2 screen02 = screen01 * 2.0;
- screen02.y = 2.0 - screen02.y;
- vec2 screen11 = screen02 - 1.0;
- v_texcoord = a_texcoord * 2.0 - 1.0;
- v_color = a_color;
- gl_Position = vec4(screen11, u_layer, 1);
- }
-`;
-
-const tcircle_fs_src = `
- precision mediump float;
-
- varying vec2 v_texcoord;
- varying vec3 v_color;
-
- void main() {
- float val = smoothstep(1.0, 0.995, length(v_texcoord));
- gl_FragColor = vec4(vec3(val * v_color), val);
- // gl_FragColor = vec4(v_texcoord, 0, 1);
- }
-`;
-
function init_webgl(state, context) {
context.canvas = document.querySelector('#c');
context.gl = context.canvas.getContext('webgl', {
- 'preserveDrawingBuffer': true,
+ 'preserveDrawingBuffer': false,
'desynchronized': true,
- 'antialias': true,
+ 'antialias': false,
});
const gl = context.gl;
@@ -123,20 +106,18 @@ function init_webgl(state, context) {
const quad_vs = create_shader(gl, gl.VERTEX_SHADER, tquad_vs_src);
const quad_fs = create_shader(gl, gl.FRAGMENT_SHADER, tquad_fs_src);
- const circle_vs = create_shader(gl, gl.VERTEX_SHADER, tcircle_vs_src);
- const circle_fs = create_shader(gl, gl.FRAGMENT_SHADER, tcircle_fs_src);
-
context.programs['stroke'] = create_program(gl, stroke_vs, stroke_fs);
context.programs['quad'] = create_program(gl, quad_vs, quad_fs);
- context.programs['circle'] = create_program(gl, circle_vs, circle_fs);
context.locations['stroke'] = {
- 'a_pos': gl.getAttribLocation(context.programs['stroke'], 'a_pos'),
- 'a_color': gl.getAttribLocation(context.programs['stroke'], 'a_color'),
+ 'a_type': gl.getAttribLocation(context.programs['stroke'], 'a_type'),
+ 'a_pos': gl.getAttribLocation(context.programs['stroke'], 'a_pos'),
+ 'a_texcoord': gl.getAttribLocation(context.programs['stroke'], 'a_texcoord'),
+ 'a_color': gl.getAttribLocation(context.programs['stroke'], 'a_color'),
+
'u_res': gl.getUniformLocation(context.programs['stroke'], 'u_res'),
'u_scale': gl.getUniformLocation(context.programs['stroke'], 'u_scale'),
'u_translation': gl.getUniformLocation(context.programs['stroke'], 'u_translation'),
- 'u_layer': gl.getUniformLocation(context.programs['stroke'], 'u_layer'),
};
context.locations['quad'] = {
@@ -145,24 +126,12 @@ function init_webgl(state, context) {
'u_res': gl.getUniformLocation(context.programs['quad'], 'u_res'),
'u_scale': gl.getUniformLocation(context.programs['quad'], 'u_scale'),
'u_translation': gl.getUniformLocation(context.programs['quad'], 'u_translation'),
- 'u_layer': gl.getUniformLocation(context.programs['quad'], 'u_layer'),
'u_outline': gl.getUniformLocation(context.programs['quad'], 'u_outline'),
'u_texture': gl.getUniformLocation(context.programs['quad'], 'u_texture'),
};
- context.locations['circle'] = {
- 'a_pos': gl.getAttribLocation(context.programs['circle'], 'a_pos'),
- 'a_texcoord': gl.getAttribLocation(context.programs['circle'], 'a_texcoord'),
- 'a_color': gl.getAttribLocation(context.programs['circle'], 'a_color'),
- 'u_res': gl.getUniformLocation(context.programs['circle'], 'u_res'),
- 'u_scale': gl.getUniformLocation(context.programs['circle'], 'u_scale'),
- 'u_translation': gl.getUniformLocation(context.programs['circle'], 'u_translation'),
- 'u_layer': gl.getUniformLocation(context.programs['circle'], 'u_layer'),
- };
-
context.buffers['stroke'] = {
- 'b_pos': context.gl.createBuffer(),
- 'b_color': context.gl.createBuffer(),
+ 'b_packed': context.gl.createBuffer(),
};
context.buffers['quad'] = {
@@ -170,12 +139,6 @@ function init_webgl(state, context) {
'b_texcoord': context.gl.createBuffer(),
};
- context.buffers['circle'] = {
- 'b_pos': context.gl.createBuffer(),
- 'b_texcoord': context.gl.createBuffer(),
- 'b_color': context.gl.createBuffer(),
- };
-
const resize_canvas = (entries) => {
// https://www.khronos.org/webgl/wiki/HandlingHighDPI
const entry = entries[0];