Browse Source

Undo is back in!

ssao
Aleksey Olokhtonov 6 months ago
parent
commit
90f0beb4f9
  1. 3
      README.txt
  2. 44
      client/bvh.js
  3. 79
      client/client_recv.js
  4. 6
      client/client_send.js
  5. 2
      client/index.js
  6. 1
      client/tools.js
  7. 10
      client/webgl_geometry.js
  8. 6
      client/webgl_listeners.js

3
README.txt

@ -40,7 +40,8 @@ Release:
+ Dynamic svg cursor to represent the brush + Dynamic svg cursor to represent the brush
- Eraser - Eraser
- Line drawing - Line drawing
- Undo/redo + Undo
- Redo
* 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)

44
client/bvh.js

@ -89,9 +89,11 @@ function bvh_find_best_sibling(bvh, leaf_index) {
return best_index; return best_index;
} }
function bvh_add_stroke(bvh, index, stroke) { function bvh_add_stroke(state, bvh, index, stroke) {
const leaf_index = bvh_make_leaf(bvh, index, stroke); const leaf_index = bvh_make_leaf(bvh, index, stroke);
stroke.bvh_node = leaf_index;
if (bvh.nodes.length === 1) { if (bvh.nodes.length === 1) {
bvh.root = leaf_index; bvh.root = leaf_index;
return; return;
@ -157,7 +159,22 @@ function bvh_add_stroke(bvh, index, stroke) {
} }
} }
function bvh_intersect_quad(bvh, quad, result_buffer) { function bvh_delete_stroke(state, stroke) {
let node = state.bvh.nodes[stroke.bvh_node];
while (node.parent_index !== null) {
if (node.is_fullnode) {
let index_index = node.stroke_indices.data.indexOf(stroke.index);
node.stroke_indices.data[index_index] = node.stroke_indices.data[node.stroke_indices.size - 1];
tv_pop(node.stroke_indices);
break;
}
node = state.bvh.nodes[node.parent_index];
}
}
function bvh_intersect_quad(state, bvh, quad, result_buffer) {
if (bvh.root === null) { if (bvh.root === null) {
return; return;
} }
@ -181,7 +198,9 @@ function bvh_intersect_quad(bvh, quad, result_buffer) {
} }
if (node.is_leaf) { if (node.is_leaf) {
tv_add(result_buffer, node.stroke_index); if (state.events[node.stroke_index].deleted !== true) {
tv_add(result_buffer, node.stroke_index);
}
} else { } else {
tv_add(bvh.traverse_stack, node.child1); tv_add(bvh.traverse_stack, node.child1);
tv_add(bvh.traverse_stack, node.child2); tv_add(bvh.traverse_stack, node.child2);
@ -211,7 +230,7 @@ function bvh_clip(state, context) {
'y2': screen_bottomright.y 'y2': screen_bottomright.y
}; };
bvh_intersect_quad(state.bvh, screen, context.clipped_indices); bvh_intersect_quad(state, state.bvh, screen, context.clipped_indices);
tv_data(context.clipped_indices).sort(); // we need to draw back to front still! tv_data(context.clipped_indices).sort(); // we need to draw back to front still!
} }
@ -241,7 +260,7 @@ function bvh_point(state, p) {
const ys = state.wasm.buffers['ys'].tv.data.subarray(stroke.coords_from, stroke.coords_to); const ys = state.wasm.buffers['ys'].tv.data.subarray(stroke.coords_from, stroke.coords_to);
const pressures = state.wasm.buffers['pressures'].tv.data.subarray(stroke.coords_from, stroke.coords_to); const pressures = state.wasm.buffers['pressures'].tv.data.subarray(stroke.coords_from, stroke.coords_to);
if (point_in_stroke(p, xs, ys, pressures, stroke.width)) { if (stroke.deleted !== true && point_in_stroke(p, xs, ys, pressures, stroke.width)) {
indices.push(node.stroke_index); indices.push(node.stroke_index);
} }
} else { } else {
@ -258,7 +277,7 @@ function bvh_point(state, p) {
return null; return null;
} }
function bvh_construct_rec(bvh, vertical, strokes, depth) { function bvh_construct_rec(state, bvh, vertical, strokes, depth) {
if (strokes.length > 1) { if (strokes.length > 1) {
// internal // internal
let sorted_strokes; let sorted_strokes;
@ -272,8 +291,8 @@ function bvh_construct_rec(bvh, vertical, strokes, depth) {
const node_index = bvh_make_internal(bvh); const node_index = bvh_make_internal(bvh);
const left_of_split_count = Math.floor(strokes.length / 2); const left_of_split_count = Math.floor(strokes.length / 2);
const child1 = bvh_construct_rec(bvh, !vertical, sorted_strokes.slice(0, left_of_split_count), depth + 1); const child1 = bvh_construct_rec(state, bvh, !vertical, sorted_strokes.slice(0, left_of_split_count), depth + 1);
const child2 = bvh_construct_rec(bvh, !vertical, sorted_strokes.slice(left_of_split_count, sorted_strokes.length), depth + 1); const child2 = bvh_construct_rec(state, bvh, !vertical, sorted_strokes.slice(left_of_split_count, sorted_strokes.length), depth + 1);
bvh.nodes[child1].parent_index = node_index; bvh.nodes[child1].parent_index = node_index;
bvh.nodes[child2].parent_index = node_index; bvh.nodes[child2].parent_index = node_index;
@ -296,12 +315,15 @@ function bvh_construct_rec(bvh, vertical, strokes, depth) {
return node_index; return node_index;
} else { } else {
// leaf // leaf
return bvh_make_leaf(bvh, strokes[0].index, strokes[0]); const leaf_index = bvh_make_leaf(bvh, strokes[0].index, strokes[0]);
state.events[strokes[0].index].bvh_node = leaf_index;
return leaf_index;
} }
} }
function bvh_construct(state) { function bvh_construct(state) {
if (state.events.length > 0) { const strokes = state.events.filter(e => e.type === EVENT.STROKE && e.deleted !== true);
state.bvh.root = bvh_construct_rec(state.bvh, true, state.events, 0); if (strokes.length > 0) {
state.bvh.root = bvh_construct_rec(state, state.bvh, true, strokes, 0);
} }
} }

79
client/client_recv.js

@ -245,7 +245,17 @@ function handle_event(state, context, event, options = {}) {
break; break;
} }
wasm_ensure_by(state, 1, event.coords.length); let last_stroke = null;
for (let i = state.events.length - 1; i >= 0; --i) {
if (state.events[i].type === EVENT.STROKE) {
last_stroke = state.events[i];
break;
}
}
const index_difference = state.events.length - (last_stroke === null ? 0 : last_stroke.index);
wasm_ensure_by(state, index_difference, event.coords.length);
const pressures = state.wasm.buffers['pressures']; const pressures = state.wasm.buffers['pressures'];
const xs = state.wasm.buffers['xs']; const xs = state.wasm.buffers['xs'];
@ -254,6 +264,13 @@ function handle_event(state, context, event, options = {}) {
event.coords_from = xs.tv.size; event.coords_from = xs.tv.size;
event.coords_to = xs.tv.size + point_count; event.coords_to = xs.tv.size + point_count;
for (let i = 0; i < index_difference - 1; ++i) {
// Create empty records for all non-stroke events that happened since the last stroke
tv_add(state.wasm.buffers['coords_from'].tv, xs.tv.size);
state.wasm.buffers['coords_from'].used += 4; // 4 bytes, not 4 ints
}
// Create actual records for this stroke
tv_add(state.wasm.buffers['coords_from'].tv, xs.tv.size + point_count); tv_add(state.wasm.buffers['coords_from'].tv, xs.tv.size + point_count);
state.wasm.buffers['coords_from'].used += 4; // 4 bytes, not 4 ints state.wasm.buffers['coords_from'].used += 4; // 4 bytes, not 4 ints
@ -285,44 +302,28 @@ function handle_event(state, context, event, options = {}) {
} }
case EVENT.UNDO: { case EVENT.UNDO: {
need_draw = true; geometry_add_dummy_stroke(context);
console.error('todo');
// for (let i = state.events.length - 1; i >=0; --i) { for (let i = state.events.length - 1; i >=0; --i) {
// const other_event = state.events[i]; const other_event = state.events[i];
// // Users can only undo their own, undeleted (not already undone) events // Users can only undo their own, undeleted (not already undone) events
// if (other_event.user_id === event.user_id && !other_event.deleted) { if (other_event.user_id === event.user_id && !other_event.deleted) {
// if (other_event.type === EVENT.STROKE) { if (other_event.type === EVENT.STROKE) {
// other_event.deleted = true; other_event.deleted = true;
// const stats = stroke_stats(other_event.points, state.cursor.width); if (other_event.bvh_node) {
// redraw_region(stats.bbox); bvh_delete_stroke(state, other_event);
// break; }
// } else if (other_event.type === EVENT.IMAGE) { need_draw = true;
// other_event.deleted = true; break;
// const item = document.querySelector(`img[data-image-id="${other_event.image_id}"]`); } else if (other_event.type === EVENT.UNDO) {
// if (item) item.remove(); // do not undo an undo, we are not Notepad
// break; } else {
// } else if (other_event.type === EVENT.ERASER) { console.error('cant undo event type', other_event.type);
// other_event.deleted = true; break;
// const erased = find_stroke_backwards(other_event.stroke_id); }
// if (erased) { }
// erased.deleted = false; }
// const stats = stroke_stats(erased.points, state.cursor.width);
// redraw_region(stats.bbox);
// }
// break;
// } else if (other_event.type === EVENT.IMAGE_MOVE) {
// const item = document.querySelector(`img[data-image-id="${other_event.image_id}"]`);
// const ix = state.images[other_event.image_id].x -= other_event.x;
// const iy = state.images[other_event.image_id].y -= other_event.y;
// item.style.transform = `translate(${ix}px, ${iy}px)`;
// break;
// }
// }
// }
break; break;
} }

6
client/client_send.js

@ -369,3 +369,9 @@ function movecanvas_event(state) {
'zoom': state.canvas.zoom, 'zoom': state.canvas.zoom,
}; };
} }
function undo_event(state) {
return {
'type': EVENT.UNDO,
};
}

2
client/index.js

@ -15,7 +15,7 @@ const config = {
debug_print: false, debug_print: false,
zoom_delta: 0.05, zoom_delta: 0.05,
min_zoom_level: -250, min_zoom_level: -250,
max_zoom_level: 40, max_zoom_level: 100,
initial_offline_timeout: 1000, initial_offline_timeout: 1000,
default_color: 0x00, default_color: 0x00,
default_width: 8, default_width: 8,

1
client/tools.js

@ -2,6 +2,7 @@ function switch_tool(state, item) {
const tool = item.getAttribute('data-tool'); const tool = item.getAttribute('data-tool');
if (tool === 'undo') { if (tool === 'undo') {
queue_event(state, undo_event(state));
return; return;
} }

10
client/webgl_geometry.js

@ -48,6 +48,14 @@ async function geometry_write_instances(state, context, callback) {
return segment_count; return segment_count;
} }
function geometry_add_dummy_stroke(context) {
context.stroke_data = ser_ensure_by(context.stroke_data, config.bytes_per_stroke);
ser_u16(context.stroke_data, 0);
ser_u16(context.stroke_data, 0);
ser_u16(context.stroke_data, 0);
ser_u16(context.stroke_data, 0);
}
function geometry_add_stroke(state, context, stroke, stroke_index, skip_bvh = false) { function geometry_add_stroke(state, context, stroke, stroke_index, skip_bvh = false) {
if (!state.online || !stroke || stroke.coords_to - stroke.coords_from === 0) return; if (!state.online || !stroke || stroke.coords_to - stroke.coords_from === 0) return;
@ -66,7 +74,7 @@ function geometry_add_stroke(state, context, stroke, stroke_index, skip_bvh = fa
ser_u16(context.stroke_data, b); ser_u16(context.stroke_data, b);
ser_u16(context.stroke_data, stroke.width); ser_u16(context.stroke_data, stroke.width);
if (!skip_bvh) bvh_add_stroke(state.bvh, stroke_index, stroke); if (!skip_bvh) bvh_add_stroke(state, state.bvh, stroke_index, stroke);
} }
function geometry_delete_stroke(state, context, stroke_index) { function geometry_delete_stroke(state, context, stroke_index) {

6
client/webgl_listeners.js

@ -146,7 +146,11 @@ function keydown(e, state, context) {
} else if (e.code === 'KeyD') { } else if (e.code === 'KeyD') {
document.querySelector('.debug-window').classList.toggle('dhide'); document.querySelector('.debug-window').classList.toggle('dhide');
} else if (e.code === 'KeyZ') { } else if (e.code === 'KeyZ') {
state.zoomdown = true; if (e.ctrlKey) {
queue_event(state, undo_event(state));
} else {
state.zoomdown = true;
}
} }
} }

Loading…
Cancel
Save