Browse Source

SDF town

infinite
A.Olokhtonov 2 years ago
parent
commit
8b3f28337e
  1. 23
      client/client_recv.js
  2. 8
      client/client_send.js
  3. 24
      client/index.html
  4. 31
      client/index.js
  5. 22
      client/math.js
  6. 94
      client/webgl_draw.js
  7. 229
      client/webgl_geometry.js
  8. 37
      client/webgl_listeners.js
  9. 107
      client/webgl_shaders.js

23
client/client_recv.js

@ -126,14 +126,15 @@ function bitmap_bbox(event) { @@ -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) { @@ -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) { @@ -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) { @@ -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);

8
client/client_send.js

@ -264,10 +264,12 @@ function image_move_event(image_id, x, y) { @@ -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,
};
}

24
client/index.html

@ -7,20 +7,20 @@ @@ -7,20 +7,20 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="shortcut icon" href="icons/favicon.svg" id="favicon">
<link rel="stylesheet" type="text/css" href="default.css?v=27">
<link rel="stylesheet" type="text/css" href="default.css?v=28">
<script type="text/javascript" src="aux.js?v=27"></script>
<script type="text/javascript" src="math.js?v=27"></script>
<script type="text/javascript" src="tools.js?v=27"></script>
<script type="text/javascript" src="webgl_geometry.js?v=27"></script>
<script type="text/javascript" src="webgl_shaders.js?v=27"></script>
<script type="text/javascript" src="webgl_listeners.js?v=27"></script>
<script type="text/javascript" src="webgl_draw.js?v=27"></script>
<script type="text/javascript" src="index.js?v=27"></script>
<script type="text/javascript" src="aux.js?v=28"></script>
<script type="text/javascript" src="math.js?v=28"></script>
<script type="text/javascript" src="tools.js?v=28"></script>
<script type="text/javascript" src="webgl_geometry.js?v=28"></script>
<script type="text/javascript" src="webgl_shaders.js?v=28"></script>
<script type="text/javascript" src="webgl_listeners.js?v=28"></script>
<script type="text/javascript" src="webgl_draw.js?v=28"></script>
<script type="text/javascript" src="index.js?v=28"></script>
<script type="text/javascript" src="client_send.js?v=27"></script>
<script type="text/javascript" src="client_recv.js?v=27"></script>
<script type="text/javascript" src="websocket.js?v=27"></script>
<script type="text/javascript" src="client_send.js?v=28"></script>
<script type="text/javascript" src="client_recv.js?v=28"></script>
<script type="text/javascript" src="websocket.js?v=28"></script>
</head>
<body>
<div class="main">

31
client/index.js

@ -10,11 +10,13 @@ const config = { @@ -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() { @@ -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,

22
client/math.js

@ -11,8 +11,8 @@ function point_right_of_line(a, b, p) { @@ -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) { @@ -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) { @@ -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) { @@ -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;
}

94
client/webgl_draw.js

@ -4,7 +4,6 @@ function schedule_draw(state, context) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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);
}

229
client/webgl_geometry.js

@ -1,14 +1,35 @@ @@ -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_ @@ -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_ @@ -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) { @@ -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) {

37
client/webgl_listeners.js

@ -95,8 +95,8 @@ function mousedown(e, state, context) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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;
}
}

107
client/webgl_shaders.js

@ -1,21 +1,30 @@ @@ -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 = ` @@ -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 = ` @@ -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 = ` @@ -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 = ` @@ -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 = ` @@ -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) { @@ -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) { @@ -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) { @@ -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];

Loading…
Cancel
Save