function bvh_make_leaf(bvh, index, stroke) { const leaf = { 'stroke_index': index, 'bbox': stroke.bbox, 'area': stroke.area, 'parent_index': null, 'is_leaf': true, }; bvh.nodes.push(leaf); return bvh.nodes.length - 1; } function bvh_make_internal(bvh) { const node = { 'child1': null, 'child2': null, 'parent_index': null, 'is_leaf': false, }; bvh.nodes.push(node); return bvh.nodes.length - 1; } function bvh_compute_sah(bvh, new_leaf, potential_sibling, only_parent = false) { let cost = 0; let union_box; if (!only_parent) { union_box = quad_union(new_leaf.bbox, potential_sibling.bbox); const internal_node_would_be = { 'bbox': union_box }; const new_internal_node_cost = (union_box.x2 - union_box.x1) * (union_box.y2 - union_box.y1); cost += new_internal_node_cost; } else { union_box = new_leaf.bbox; } let parent_index = potential_sibling.parent_index; while (parent_index !== null) { const current_node = bvh.nodes[parent_index]; const old_cost = current_node.area; union_box = quad_union(current_node.bbox, union_box); const new_cost = (union_box.x2 - union_box.x1) * (union_box.y2 - union_box.y1); cost += new_cost - old_cost; parent_index = current_node.parent_index; } return cost; } // todo area func function bvh_find_best_sibling(bvh, leaf_index) { // branch and bound const leaf = bvh.nodes[leaf_index]; const leaf_cost = (leaf.bbox.x2 - leaf.bbox.x1) * (leaf.bbox.y2 - leaf.bbox.y1); let best_cost = bvh_compute_sah(bvh, leaf, bvh.nodes[bvh.root]); let best_index = bvh.root; bvh.pqueue.clear(); bvh.pqueue.push(best_index, best_cost); while (bvh.pqueue.size > 0) { const current_index = bvh.pqueue.pop(); const current_node = bvh.nodes[current_index]; const cost = bvh_compute_sah(bvh, current_node, leaf); if (cost < best_cost) { best_cost = cost; best_index = current_index; } if (!current_node.is_leaf) { const child1 = bvh.nodes[current_node.child1]; const lower_bound_for_children = bvh_compute_sah(bvh, child1, leaf, true) + leaf_cost; if (lower_bound_for_children < best_cost) { bvh.pqueue.push(current_node.child1, lower_bound_for_children); bvh.pqueue.push(current_node.child2, lower_bound_for_children); } } } return best_index; } function bvh_rotate(bvh, index) { } function bvh_add_stroke(bvh, index, stroke) { const leaf_index = bvh_make_leaf(bvh, index, stroke); if (bvh.nodes.length === 1) { bvh.root = leaf_index; return; } if (bvh.pqueue.capacity < Math.ceil(bvh.nodes.length * 1.2)) { bvh.pqueue = new MinQueue(bvh.pqueue.capacity * 2); } // It's as easy as 1-2-3 // 1. Find best sibling for leaf const sibling = bvh_find_best_sibling(bvh, leaf_index); // 2. Create new parent const old_parent = bvh.nodes[sibling].parent_index; const new_parent = bvh_make_internal(bvh); bvh.nodes[new_parent].parent_index = old_parent; bvh.nodes[new_parent].bbox = quad_union(stroke.bbox, bvh.nodes[sibling].bbox); if (old_parent !== null) { // The sibling was not the root if (bvh.nodes[old_parent].child1 === sibling) { bvh.nodes[old_parent].child1 = new_parent; } else { bvh.nodes[old_parent].child2 = new_parent; } bvh.nodes[new_parent].child1 = sibling; bvh.nodes[new_parent].child2 = leaf_index; bvh.nodes[sibling].parent_index = new_parent; bvh.nodes[leaf_index].parent_index = new_parent; } else { // The sibling was the root bvh.nodes[new_parent].child1 = sibling; bvh.nodes[new_parent].child2 = leaf_index; bvh.nodes[sibling].parent_index = new_parent; bvh.nodes[leaf_index].parent_index = new_parent; bvh.root = new_parent; } const new_bbox = quad_union(bvh.nodes[bvh.nodes[new_parent].child1].bbox, bvh.nodes[bvh.nodes[new_parent].child2].bbox); bvh.nodes[new_parent].bbox = new_bbox; bvh.nodes[new_parent].area = (new_bbox.x2 - new_bbox.x1) * (new_bbox.y2 - new_bbox.y1); // 3. Refit and rotate let refit_index = bvh.nodes[leaf_index].parent_index; while (refit_index !== null) { const child1 = bvh.nodes[refit_index].child1; const child2 = bvh.nodes[refit_index].child2; bvh.nodes[refit_index].bbox = quad_union(bvh.nodes[child1].bbox, bvh.nodes[child2].bbox); bvh_rotate(bvh, refit_index); refit_index = bvh.nodes[refit_index].parent_index; } } function bvh_intersect_quad(bvh, quad) { if (bvh.root === null) { return []; } const stack = [bvh.root]; const result = []; while (stack.length > 0) { const node_index = stack.pop(); const node = bvh.nodes[node_index]; if (!quads_intersect(node.bbox, quad)) { continue; } if (node.is_leaf) { result.push(node.stroke_index); } else { stack.push(node.child1, node.child2); } } return result; } function bvh_clip(state, context) { if (state.onscreen_segments.length < Math.ceil(state.total_points * 6 * 1.2)) { state.onscreen_segments = new Uint32Array(state.total_points * 6 * 2); } let at = 0; const screen_topleft = screen_to_canvas(state, {'x': 0, 'y': 0}); const screen_bottomright = screen_to_canvas(state, {'x': context.canvas.width, 'y': context.canvas.height}); const screen_topright = { 'x': screen_bottomright.x, 'y': screen_topleft.y }; const screen_bottomleft = { 'x': screen_topleft.x, 'y': screen_bottomright.y }; const screen = {'x1': screen_topleft.x, 'y1': screen_topleft.y, 'x2': screen_bottomright.x, 'y2': screen_bottomright.y}; const stroke_indices = bvh_intersect_quad(state.bvh, screen); stroke_indices.sort((a, b) => a - b); for (const i of stroke_indices) { if (state.debug.limit_to && i >= state.debug.render_to) break; const event = state.events[i]; if (!(state.debug.limit_from && i < state.debug.render_from)) { if (event.type === EVENT.STROKE && !event.deleted && event.points.length > 0) { for (let j = 0; j < event.points.length - 1; ++j) { let base = event.starting_index + j * 4; // We draw quads as [1, 2, 3, 4, 3, 2] state.onscreen_segments[at + 0] = base + 0; state.onscreen_segments[at + 1] = base + 1; state.onscreen_segments[at + 2] = base + 2; state.onscreen_segments[at + 3] = base + 3; state.onscreen_segments[at + 4] = base + 2; state.onscreen_segments[at + 5] = base + 1; at += 6; } } } } return at; } function bvh_construct_rec(bvh, vertical, strokes) { if (strokes.length > 1) { // internal let sorted_strokes; if (vertical) { sorted_strokes = strokes.toSorted((a, b) => a.bbox.cy - b.bbox.cy); } else { sorted_strokes = strokes.toSorted((a, b) => a.bbox.cx - b.bbox.cx); } 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)); const child2 = bvh_construct_rec(bvh, !vertical, sorted_strokes.slice(left_of_split_count, sorted_strokes.length)); bvh.nodes[child1].parent_index = node_index; bvh.nodes[child2].parent_index = node_index; bvh.nodes[node_index].child1 = child1; bvh.nodes[node_index].child2 = child2; bvh.nodes[node_index].bbox = quad_union(bvh.nodes[child1].bbox, bvh.nodes[child2].bbox); return node_index; } else { // leaf return bvh_make_leaf(bvh, strokes[0].index, strokes[0]); } } function bvh_construct(state) { state.bvh.root = bvh_construct_rec(state.bvh, true, state.events); }