// NEXT: fire events for brush changes document.addEventListener('DOMContentLoaded', main); function draw(state, context) { state.timers.raf = false; const gl = context.gl; const width = window.innerWidth; const height = window.innerHeight; let locations; let buffers; gl.viewport(0, 0, context.canvas.width, context.canvas.height); gl.clearColor(context.bgcolor.r, context.bgcolor.g, context.bgcolor.b, 1); gl.clear(gl.COLOR_BUFFER_BIT); // Draw images locations = context.locations['quad']; buffers = context.buffers['quad']; gl.useProgram(context.programs['quad']); gl.enableVertexAttribArray(locations['a_pos']); gl.enableVertexAttribArray(locations['a_texcoord']); 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.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); gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_texcoord']); gl.vertexAttribPointer(locations['a_texcoord'], 2, gl.FLOAT, false, 0, 0); gl.bufferData(gl.ARRAY_BUFFER, context.quad_texcoords_f32, gl.STATIC_DRAW); let tex_index = 0; for (const key in context.textures) { gl.bindTexture(gl.TEXTURE_2D, context.textures[key]); gl.drawArrays(gl.TRIANGLES, tex_index * 6, 6); ++tex_index; } // Draw strokes locations = context.locations['stroke']; buffers = context.buffers['stroke']; 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); } const config = { ws_url: 'ws://192.168.100.2/ws/', image_url: 'http://192.168.100.2/images/', sync_timeout: 1000, ws_reconnect_timeout: 10000, brush_preview_timeout: 1000, second_finger_timeout: 500, buffer_first_touchmoves: 5, debug_print: true, min_zoom: 0.01, max_zoom: 100.0, default_color: 0x00, default_width: 8, }; const EVENT = Object.freeze({ PREDRAW: 10, SET_COLOR: 11, SET_WIDTH: 12, STROKE: 20, RULER: 21, /* gets re-written with EVENT.STROKE before sending to server */ UNDO: 30, REDO: 31, IMAGE: 40, IMAGE_MOVE: 41, ERASER: 50, }); const MESSAGE = Object.freeze({ INIT: 100, SYN: 101, ACK: 102, FULL: 103, FIRE: 104, JOIN: 105, }); function event_size(event) { let size = 1 + 3; // type + padding switch (event.type) { case EVENT.PREDRAW: { size += 4 * 2; break; } case EVENT.SET_COLOR: { size += 4; break; } case EVENT.SET_WIDTH: { size += 2; break; } case EVENT.STROKE: { size += 4 + 2 + 2 + 4 + event.points.length * 4 * 2; // u32 stroke id + u16 (count) + u16 (width) + u32 (color + count * (f32, f32) points break; } case EVENT.UNDO: case EVENT.REDO: { break; } case EVENT.IMAGE: case EVENT.IMAGE_MOVE: { size += 4 + 4 + 4; // file id + x + y break; } case EVENT.ERASER: { size += 4; // stroke id break; } default: { console.error('fuck'); } } return size; } function main() { const state = { 'me': 333, 'canvas': { 'offset': { 'x': 0, 'y': 0 }, 'zoom': 1.0, }, 'cursor': { 'x': 0, 'y': 0, }, 'sn': 0, 'lsn': 0, 'server_lsn': 0, 'touch': { 'moves': 0, 'drawing': false, 'moving': false, 'waiting_for_second_finger': false, 'first_finger_position': null, 'second_finger_position': null, 'buffered': [], 'ids': [], }, 'moving': false, 'drawing': false, 'spacedown': false, 'current_strokes': {}, 'fire_queue': [], 'queue': [], 'events': [], 'tools': { 'active': null, 'active_element': null, }, 'colors': { 'active_element': null, }, 'timers': { 'ws_reconnect': null, 'hide_preview': null, 'raf': false, }, 'players': {}, }; const context = { 'canvas': null, 'gl': null, 'programs': {}, 'buffers': {}, 'locations': {}, 'textures': {}, 'dynamic_positions': {}, 'dynamic_colors': {}, 'quad_positions': [], 'quad_texcoords': [], 'static_positions': [], 'static_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), 'quad_positions_f32': new Float32Array(0), 'quad_texcoords_f32': new Float32Array(0), 'bgcolor': {'r': 1.0, 'g': 1.0, 'b': 1.0}, }; const url = new URL(window.location.href); const parts = url.pathname.split('/'); state.desk_id = parts.length > 0 ? parts[parts.length - 1] : 0; init_webgl(state, context); init_listeners(state, context); init_tools(state); ws_connect(state, context, true); schedule_draw(state, context); } function schedule_draw(state, context) { if (!state.timers.raf) { window.requestAnimationFrame(() => draw(state, context)); state.timers.raf = true; } }