Browse Source

Draw player pointers as colored squares with html

ssao
A.Olokhtonov 10 months ago
parent
commit
d8a5cd3fca
  1. 13
      README.md
  2. 16
      client/aux.js
  3. 30
      client/client_recv.js
  4. 14
      client/client_send.js
  5. 17
      client/default.css
  6. 1
      client/index.html
  7. 3
      client/index.js
  8. 7
      client/math.js
  9. 25
      client/webgl_draw.js
  10. 11
      client/webgl_listeners.js
  11. 6
      server/deserializer.js
  12. 4
      server/enums.js
  13. 18
      server/recv.js
  14. 26
      server/send.js
  15. 7
      server/serializer.js
  16. 2
      server/server.js

13
README.md

@ -6,6 +6,7 @@ Release:
- Z-prepass fringe bug (also, when do we enable the prepass?) - Z-prepass fringe bug (also, when do we enable the prepass?)
- Textured quads (pictures, code already written in older version) - Textured quads (pictures, code already written in older version)
- Resize and move pictures (draw handles) - Resize and move pictures (draw handles)
- Further investigate GC pauses in Firefox
- Debug - Debug
- Restore ability to limit event range - Restore ability to limit event range
* Listeners/events/multiplayer * Listeners/events/multiplayer
@ -13,22 +14,23 @@ Release:
+ Fix blinking own stroke inbetween SYN->server and SYN->client + Fix blinking own stroke inbetween SYN->server and SYN->client
+ Drag with mouse button 3 + Drag with mouse button 3
+ Investigate skipped inputs on mobile (panning, zooming) [Events were not actually getting skipped. The stroke previews were just not being drawn] + Investigate skipped inputs on mobile (panning, zooming) [Events were not actually getting skipped. The stroke previews were just not being drawn]
- Save events to indexeddb (as some kind of a blob), restore on reconnect and page reload
- Do NOT use session id as player id LUL - Do NOT use session id as player id LUL
- Save events to indexeddb (as some kind of a blob), restore on reconnect and page reload
- Local prediction for tools! - Local prediction for tools!
- Be able to have multiple "current" strokes per player. In case of bad internet this can happen! - Be able to have multiple "current" strokes per player. In case of bad internet this can happen!
- Missing features I do not consider bonus * Missing features I do not consider bonus
+ Player pointers
- Player screens
- Eraser - Eraser
- Line drawing - Line drawing
- Player screens/pointers
- Follow player (like Ligma) - Follow player (like Ligma)
- Color picker (or at the very least an Open Color color pallete) - Color picker (or at the very least an Open Color color pallete)
- Undo/redo - Undo/redo
- Dynamic svg cursor to represent the brush - Dynamic svg cursor to represent the brush
- Polish * Polish
* Use typedvector where appropriate * Use typedvector where appropriate
- Show what's happening while the desk is loading (downloading, processing, uploading to gpu) - Show what's happening while the desk is loading (downloading, processing, uploading to gpu)
- Settings panel (including the setting for "offline mode") - Settings panel for config values (including the setting for "offline mode")
- Set up VAOs - Set up VAOs
- Presentation / "marketing" - Presentation / "marketing"
- Title - Title
@ -50,7 +52,6 @@ Bonus:
- Further optimization - Further optimization
- Draw LOD size histogram for various cases (maybe we see that in our worst case 90% of strokes are down to 3-4 points) - Draw LOD size histogram for various cases (maybe we see that in our worst case 90% of strokes are down to 3-4 points)
- If we see lots of very low detail strokes, precompute zoom level for 3,4,... points left - If we see lots of very low detail strokes, precompute zoom level for 3,4,... points left
- Further investigate GC pauses on Mobile Firefox
Bonus-bonus: Bonus-bonus:
- Actually infinite canvas (replace floats with something, some kind of fixed point scheme? chunks? multilevel scheme?) - Actually infinite canvas (replace floats with something, some kind of fixed point scheme? chunks? multilevel scheme?)

16
client/aux.js

@ -36,11 +36,13 @@ function event_size(event) {
let size = 4; // type let size = 4; // type
switch (event.type) { switch (event.type) {
case EVENT.PREDRAW: { case EVENT.PREDRAW:
case EVENT.MOVE_CURSOR: {
size += 4 * 2; size += 4 * 2;
break; break;
} }
case EVENT.LEAVE:
case EVENT.CLEAR: { case EVENT.CLEAR: {
break; break;
} }
@ -153,4 +155,16 @@ function tv_clear(tv) {
tv.size = 0; tv.size = 0;
} }
function HTML(html) {
const template = document.createElement('template');
template.innerHTML = html.trim();
return template.content.firstChild;
}
function insert_player_cursor(player_id) {
const color = color_from_u32(player_id);
const cursor = HTML(`<div class="player-cursor" data-player-id="${player_id}"></div>`);
cursor.style.background = color;
document.querySelector('.html-hud').appendChild(cursor);
return cursor;
}

30
client/client_recv.js

@ -58,10 +58,17 @@ function des_event(d, state = null) {
break; break;
} }
case EVENT.LEAVE:
case EVENT.CLEAR: { case EVENT.CLEAR: {
break; break;
} }
case EVENT.MOVE_CURSOR: {
event.x = des_f32(d);
event.y = des_f32(d);
break;
}
case EVENT.SET_COLOR: { case EVENT.SET_COLOR: {
event.color = des_u32(d); event.color = des_u32(d);
break; break;
@ -137,6 +144,8 @@ function init_player_defaults(state, player_id, color = config.default_color, wi
'color': color, 'color': color,
'width': width, 'width': width,
'points': [], 'points': [],
'online': false,
'cursor': {'x': 0, 'y': 0},
}; };
} }
@ -161,6 +170,27 @@ function handle_event(state, context, event, options = {}) {
break; break;
} }
case EVENT.LEAVE: {
if (event.user_id in state.players) {
state.players[event.user_id].online = false;
draw_html(state);
}
break;
}
case EVENT.MOVE_CURSOR: {
if (event.user_id in state.players) {
state.players[event.user_id].cursor.x = event.x;
state.players[event.user_id].cursor.y = event.y;
state.players[event.user_id].online = true;
}
// Should we syncronize this to RAF?
draw_html(state);
break;
}
case EVENT.SET_COLOR: { case EVENT.SET_COLOR: {
state.players[event.user_id].color = event.color; state.players[event.user_id].color = event.color;
break; break;

14
client/client_send.js

@ -79,6 +79,12 @@ function ser_event(s, event) {
break; break;
} }
case EVENT.MOVE_CURSOR: {
ser_f32(s, event.x);
ser_f32(s, event.y);
break;
}
case EVENT.SET_COLOR: { case EVENT.SET_COLOR: {
ser_u32(s, event.color); ser_u32(s, event.color);
break; break;
@ -312,3 +318,11 @@ function clear_event(state) {
'type': EVENT.CLEAR 'type': EVENT.CLEAR
}; };
} }
function movecursor_event(x, y) {
return {
'type': EVENT.MOVE_CURSOR,
'x': x,
'y': y,
};
}

17
client/default.css

@ -51,6 +51,23 @@ canvas.mousemoving {
cursor: move; cursor: move;
} }
.html-hud {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1;
}
.html-hud .player-cursor {
position: absolute;
width: 16px;
height: 16px;
background: red;
}
.tools-wrapper { .tools-wrapper {
position: fixed; position: fixed;
bottom: 0; bottom: 0;

1
client/index.html

@ -27,6 +27,7 @@
<body> <body>
<div class="main"> <div class="main">
<canvas id="c"></canvas> <canvas id="c"></canvas>
<div class="html-hud"></div>
<!-- <svg viewBox="0 0 600 600" xmlns="http://www.w3.org/2000/svg" style="position: absolute; left:0; top: 0; pointer-events: none;"> <!-- <svg viewBox="0 0 600 600" xmlns="http://www.w3.org/2000/svg" style="position: absolute; left:0; top: 0; pointer-events: none;">
<polyline points="150,150 225,230 280,120" / fill="none" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"> <polyline points="150,150 225,230 280,120" / fill="none" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="5">

3
client/index.js

@ -36,6 +36,9 @@ const EVENT = Object.freeze({
SET_COLOR: 11, SET_COLOR: 11,
SET_WIDTH: 12, SET_WIDTH: 12,
CLEAR: 13, // clear predraw events from me (because I started a pan instead of drawing) CLEAR: 13, // clear predraw events from me (because I started a pan instead of drawing)
MOVE_CURSOR: 14,
MOVE_SCREEN: 15,
LEAVE: 16,
STROKE: 20, STROKE: 20,
RULER: 21, // gets re-written with EVENT.STROKE before sending to server RULER: 21, // gets re-written with EVENT.STROKE before sending to server

7
client/math.js

@ -9,6 +9,13 @@ function screen_to_canvas(state, p) {
return {'x': xc, 'y': yc}; return {'x': xc, 'y': yc};
} }
function canvas_to_screen(state, p) {
const xs = p.x * state.canvas.zoom + state.canvas.offset.x;
const ys = p.y * state.canvas.zoom + state.canvas.offset.y;
return {'x': xs, 'y': ys};
}
/* /*
function rdp_find_max(state, zoom, stroke, start, end) { function rdp_find_max(state, zoom, stroke, start, end) {
// Finds a point from the range [start, end) with the maximum distance from the line (start--end) that is also further than EPS // Finds a point from the range [start, end) with the maximum distance from the line (start--end) that is also further than EPS

25
client/webgl_draw.js

@ -1,7 +1,7 @@
function schedule_draw(state, context) { function schedule_draw(state, context) {
if (!state.timers.raf) { if (!state.timers.raf) {
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
draw(state, context) draw(state, context);
}); });
state.timers.raf = true; state.timers.raf = true;
} }
@ -45,6 +45,29 @@ function upload_square_rgba16ui_texture(gl, serializer, texture_size) {
} }
} }
function draw_html(state) {
// HUD-like things. Player cursors, screens
for (const player_id in state.players) {
if (player_id === state.me) continue;
const player = state.players[player_id];
let player_cursor_element = document.querySelector(`.player-cursor[data-player-id="${player_id}"]`);
if (player_cursor_element === null && player.online) {
player_cursor_element = insert_player_cursor(player_id);
}
if (!player.online && player_cursor_element !== null) {
player_cursor_element.remove();
}
if (player_cursor_element && player.online) {
const screenp = canvas_to_screen(state, player.cursor);
player_cursor_element.style.transform = `translate(${Math.round(screenp.x)}px, ${Math.round(screenp.y)}px)`;
}
}
}
function draw(state, context) { function draw(state, context) {
const cpu_before = performance.now(); const cpu_before = performance.now();

11
client/webgl_listeners.js

@ -217,6 +217,14 @@ function mousemove(e, state, context) {
let do_draw = false; let do_draw = false;
const screenp = {'x': window.devicePixelRatio * e.clientX, 'y': window.devicePixelRatio * e.clientY};
const canvasp = screen_to_canvas(state, screenp);
if (state.me in state.players && dist_v2(state.players[state.me].cursor, canvasp) > 5) {
state.players[state.me].cursor = canvasp;
fire_event(state, movecursor_event(canvasp.x, canvasp.y));
}
if (state.moving) { if (state.moving) {
state.canvas.offset.x += e.movementX; state.canvas.offset.x += e.movementX;
state.canvas.offset.y += e.movementY; state.canvas.offset.y += e.movementY;
@ -230,9 +238,6 @@ function mousemove(e, state, context) {
do_draw = true; do_draw = true;
} }
const screenp = {'x': window.devicePixelRatio * e.clientX, 'y': window.devicePixelRatio * e.clientY};
const canvasp = screen_to_canvas(state, screenp);
if (state.drawing) { if (state.drawing) {
geometry_add_point(state, context, state.me, canvasp); geometry_add_point(state, context, state.me, canvasp);
fire_event(state, predraw_event(canvasp.x, canvasp.y)); fire_event(state, predraw_event(canvasp.x, canvasp.y));

6
server/deserializer.js

@ -56,6 +56,12 @@ export function event(d) {
break; break;
} }
case EVENT.MOVE_CURSOR: {
event.x = f32(d);
event.y = f32(d);
break;
}
case EVENT.CLEAR: { case EVENT.CLEAR: {
break; break;
} }

4
server/enums.js

@ -9,6 +9,10 @@ export const EVENT = Object.freeze({
SET_COLOR: 11, SET_COLOR: 11,
SET_WIDTH: 12, SET_WIDTH: 12,
CLEAR: 13, CLEAR: 13,
MOVE_CURSOR: 14,
MOVE_SCREEN: 15,
LEAVE: 16,
STROKE: 20, STROKE: 20,
UNDO: 30, UNDO: 30,
REDO: 31, REDO: 31,

18
server/recv.js

@ -87,23 +87,7 @@ function recv_fire(d, session) {
} }
} }
for (const sid in sessions) { send.fire_event(session, event);
const other = sessions[sid];
if (other.id === session.id) {
continue;
}
if (other.state !== SESSION.READY) {
continue;
}
if (other.desk_id != session.desk_id) {
continue;
}
send.send_fire(other.ws, event);
}
} }
function handle_event(session, event) { function handle_event(session, event) {

26
server/send.js

@ -10,11 +10,13 @@ function event_size(event) {
let size = 4 + 4; // type + user_id let size = 4 + 4; // type + user_id
switch (event.type) { switch (event.type) {
case EVENT.PREDRAW: { case EVENT.PREDRAW:
case EVENT.MOVE_CURSOR: {
size += 4 * 2; size += 4 * 2;
break; break;
} }
case EVENT.LEAVE:
case EVENT.CLEAR: { case EVENT.CLEAR: {
break; break;
} }
@ -179,7 +181,7 @@ export function send_ack(ws, lsn) {
ws.send(s.buffer); ws.send(s.buffer);
} }
export function send_fire(ws, event) { function send_fire(ws, event) {
if (!ws) { if (!ws) {
return; return;
} }
@ -192,6 +194,26 @@ export function send_fire(ws, event) {
ws.send(s.buffer); ws.send(s.buffer);
} }
export function fire_event(from_session, event) {
for (const sid in sessions) {
const other = sessions[sid];
if (other.id === from_session.id) {
continue;
}
if (other.state !== SESSION.READY) {
continue;
}
if (other.desk_id != from_session.desk_id) {
continue;
}
send_fire(other.ws, event);
}
}
async function sync_session(session_id) { async function sync_session(session_id) {
if (!(session_id in sessions)) { if (!(session_id in sessions)) {
return; return;

7
server/serializer.js

@ -54,6 +54,13 @@ export function event(s, event) {
break; break;
} }
case EVENT.MOVE_CURSOR: {
f32(s, event.x);
f32(s, event.y);
break;
}
case EVENT.LEAVE:
case EVENT.CLEAR: { case EVENT.CLEAR: {
break; break;
} }

2
server/server.js

@ -61,6 +61,8 @@ export function startup() {
close(ws, code, message) { close(ws, code, message) {
if (ws.data.session_id in sessions) { if (ws.data.session_id in sessions) {
const leave_event = {'type': EVENT.LEAVE, 'user_id': ws.data.session_id};
send.fire_event(sessions[ws.data.session_id], leave_event);
console.log(`session ${ws.data.session_id} closed`); console.log(`session ${ws.data.session_id} closed`);
sessions[ws.data.session_id].state = SESSION.CLOSED; sessions[ws.data.session_id].state = SESSION.CLOSED;
sessions[ws.data.session_id].ws = null; sessions[ws.data.session_id].ws = null;

Loading…
Cancel
Save