You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

285 lines
8.6 KiB

// TODO: get rid of node_count
//
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.node_count === 0) {
bvh.root = leaf_index;
bvh.node_count++;
return;
}
bvh.node_count++;
if (bvh.pqueue.capacity < Math.ceil(bvh.node_count * 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);
bvh.node_count++;
// 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 === null) {
let total_points = 0;
for (const event of state.events) {
if (event.type === EVENT.STROKE && !event.deleted && event.points.length > 0) {
total_points += event.points.length - 1;
}
}
if (total_points > 0) {
state.onscreen_segments = new Uint32Array(total_points * 6);
}
}
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);
}