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];