Browse Source

Undo is back in!

ssao
Aleksey Olokhtonov 6 months ago
parent
commit
90f0beb4f9
  1. 3
      README.txt
  2. 42
      client/bvh.js
  3. 75
      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. 4
      client/webgl_listeners.js

3
README.txt

@ -40,7 +40,8 @@ Release: @@ -40,7 +40,8 @@ Release:
+ Dynamic svg cursor to represent the brush
- Eraser
- Line drawing
- Undo/redo
+ Undo
- Redo
* Polish
+ Use typedvector where appropriate
- Show what's happening while the desk is loading (downloading, processing, uploading to gpu)

42
client/bvh.js

@ -89,9 +89,11 @@ function bvh_find_best_sibling(bvh, leaf_index) { @@ -89,9 +89,11 @@ function bvh_find_best_sibling(bvh, leaf_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);
stroke.bvh_node = leaf_index;
if (bvh.nodes.length === 1) {
bvh.root = leaf_index;
return;
@ -157,7 +159,22 @@ function bvh_add_stroke(bvh, index, stroke) { @@ -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) {
return;
}
@ -181,7 +198,9 @@ function bvh_intersect_quad(bvh, quad, result_buffer) { @@ -181,7 +198,9 @@ function bvh_intersect_quad(bvh, quad, result_buffer) {
}
if (node.is_leaf) {
if (state.events[node.stroke_index].deleted !== true) {
tv_add(result_buffer, node.stroke_index);
}
} else {
tv_add(bvh.traverse_stack, node.child1);
tv_add(bvh.traverse_stack, node.child2);
@ -211,7 +230,7 @@ function bvh_clip(state, context) { @@ -211,7 +230,7 @@ function bvh_clip(state, context) {
'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!
}
@ -241,7 +260,7 @@ function bvh_point(state, p) { @@ -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 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);
}
} else {
@ -258,7 +277,7 @@ function bvh_point(state, p) { @@ -258,7 +277,7 @@ function bvh_point(state, p) {
return null;
}
function bvh_construct_rec(bvh, vertical, strokes, depth) {
function bvh_construct_rec(state, bvh, vertical, strokes, depth) {
if (strokes.length > 1) {
// internal
let sorted_strokes;
@ -272,8 +291,8 @@ function bvh_construct_rec(bvh, vertical, strokes, depth) { @@ -272,8 +291,8 @@ function bvh_construct_rec(bvh, vertical, strokes, depth) {
const node_index = bvh_make_internal(bvh);
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 child2 = bvh_construct_rec(bvh, !vertical, sorted_strokes.slice(left_of_split_count, sorted_strokes.length), 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(state, bvh, !vertical, sorted_strokes.slice(left_of_split_count, sorted_strokes.length), depth + 1);
bvh.nodes[child1].parent_index = node_index;
bvh.nodes[child2].parent_index = node_index;
@ -296,12 +315,15 @@ function bvh_construct_rec(bvh, vertical, strokes, depth) { @@ -296,12 +315,15 @@ function bvh_construct_rec(bvh, vertical, strokes, depth) {
return node_index;
} else {
// 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) {
if (state.events.length > 0) {
state.bvh.root = bvh_construct_rec(state.bvh, true, state.events, 0);
const strokes = state.events.filter(e => e.type === EVENT.STROKE && e.deleted !== true);
if (strokes.length > 0) {
state.bvh.root = bvh_construct_rec(state, state.bvh, true, strokes, 0);
}
}

75
client/client_recv.js

@ -245,7 +245,17 @@ function handle_event(state, context, event, options = {}) { @@ -245,7 +245,17 @@ function handle_event(state, context, event, options = {}) {
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 xs = state.wasm.buffers['xs'];
@ -254,6 +264,13 @@ function handle_event(state, context, event, options = {}) { @@ -254,6 +264,13 @@ function handle_event(state, context, event, options = {}) {
event.coords_from = xs.tv.size;
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);
state.wasm.buffers['coords_from'].used += 4; // 4 bytes, not 4 ints
@ -285,44 +302,28 @@ function handle_event(state, context, event, options = {}) { @@ -285,44 +302,28 @@ function handle_event(state, context, event, options = {}) {
}
case EVENT.UNDO: {
need_draw = true;
console.error('todo');
// for (let i = state.events.length - 1; i >=0; --i) {
// const other_event = state.events[i];
// // 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.type === EVENT.STROKE) {
// other_event.deleted = true;
// const stats = stroke_stats(other_event.points, state.cursor.width);
// redraw_region(stats.bbox);
// break;
// } else if (other_event.type === EVENT.IMAGE) {
// other_event.deleted = true;
// const item = document.querySelector(`img[data-image-id="${other_event.image_id}"]`);
// if (item) item.remove();
// break;
// } else if (other_event.type === EVENT.ERASER) {
// other_event.deleted = true;
// 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;
geometry_add_dummy_stroke(context);
// item.style.transform = `translate(${ix}px, ${iy}px)`;
for (let i = state.events.length - 1; i >=0; --i) {
const other_event = state.events[i];
// break;
// }
// }
// }
// 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.type === EVENT.STROKE) {
other_event.deleted = true;
if (other_event.bvh_node) {
bvh_delete_stroke(state, other_event);
}
need_draw = true;
break;
} else if (other_event.type === EVENT.UNDO) {
// do not undo an undo, we are not Notepad
} else {
console.error('cant undo event type', other_event.type);
break;
}
}
}
break;
}

6
client/client_send.js

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

2
client/index.js

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

1
client/tools.js

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

10
client/webgl_geometry.js

@ -48,6 +48,14 @@ async function geometry_write_instances(state, context, callback) { @@ -48,6 +48,14 @@ async function geometry_write_instances(state, context, callback) {
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) {
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 @@ -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, 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) {

4
client/webgl_listeners.js

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

Loading…
Cancel
Save