A.Olokhtonov
12 months ago
14 changed files with 640 additions and 74 deletions
@ -1,3 +1,4 @@
@@ -1,3 +1,4 @@
|
||||
server/images |
||||
doca.txt |
||||
data/ |
||||
client/*.dot |
||||
|
@ -0,0 +1,284 @@
@@ -0,0 +1,284 @@
|
||||
// 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(); |
||||
|
||||
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); |
||||
} |
@ -0,0 +1,130 @@
@@ -0,0 +1,130 @@
|
||||
// translated by esbuild to js. original typescript source MIT licensed at https://github.com/luciopaiva/heapify
|
||||
|
||||
const ROOT_INDEX = 1; |
||||
class MinQueue { |
||||
constructor(capacity = 64, keys = [], priorities = [], KeysBackingArrayType = Uint32Array, PrioritiesBackingArrayType = Uint32Array) { |
||||
this._capacity = capacity; |
||||
this._keys = new KeysBackingArrayType(capacity + ROOT_INDEX); |
||||
this._priorities = new PrioritiesBackingArrayType(capacity + ROOT_INDEX); |
||||
this._hasPoppedElement = false; |
||||
if (keys.length !== priorities.length) { |
||||
throw new Error("Number of keys does not match number of priorities provided."); |
||||
} |
||||
if (capacity < keys.length) { |
||||
throw new Error("Capacity less than number of provided keys."); |
||||
} |
||||
for (let i = 0; i < keys.length; i++) { |
||||
this._keys[i + ROOT_INDEX] = keys[i]; |
||||
this._priorities[i + ROOT_INDEX] = priorities[i]; |
||||
} |
||||
this.length = keys.length; |
||||
for (let i = keys.length >>> 1; i >= ROOT_INDEX; i--) { |
||||
this.bubbleDown(i); |
||||
} |
||||
} |
||||
get capacity() { |
||||
return this._capacity; |
||||
} |
||||
clear() { |
||||
this.length = 0; |
||||
this._hasPoppedElement = false; |
||||
} |
||||
bubbleUp(index) { |
||||
const key = this._keys[index]; |
||||
const priority = this._priorities[index]; |
||||
while (index > ROOT_INDEX) { |
||||
const parentIndex = index >>> 1; |
||||
if (this._priorities[parentIndex] <= priority) { |
||||
break; |
||||
} |
||||
this._keys[index] = this._keys[parentIndex]; |
||||
this._priorities[index] = this._priorities[parentIndex]; |
||||
index = parentIndex; |
||||
} |
||||
this._keys[index] = key; |
||||
this._priorities[index] = priority; |
||||
} |
||||
bubbleDown(index) { |
||||
const key = this._keys[index]; |
||||
const priority = this._priorities[index]; |
||||
const halfLength = ROOT_INDEX + (this.length >>> 1); |
||||
const lastIndex = this.length + ROOT_INDEX; |
||||
while (index < halfLength) { |
||||
const left = index << 1; |
||||
let childPriority = this._priorities[left]; |
||||
let childKey = this._keys[left]; |
||||
let childIndex = left; |
||||
const right = left + 1; |
||||
if (right < lastIndex) { |
||||
const rightPriority = this._priorities[right]; |
||||
if (rightPriority < childPriority) { |
||||
childPriority = rightPriority; |
||||
childKey = this._keys[right]; |
||||
childIndex = right; |
||||
} |
||||
} |
||||
if (childPriority >= priority) { |
||||
break; |
||||
} |
||||
this._keys[index] = childKey; |
||||
this._priorities[index] = childPriority; |
||||
index = childIndex; |
||||
} |
||||
this._keys[index] = key; |
||||
this._priorities[index] = priority; |
||||
} |
||||
push(key, priority) { |
||||
if (this.length === this._capacity) { |
||||
throw new Error("Heap has reached capacity, can't push new items"); |
||||
} |
||||
if (this._hasPoppedElement) { |
||||
this._keys[ROOT_INDEX] = key; |
||||
this._priorities[ROOT_INDEX] = priority; |
||||
this.length++; |
||||
this.bubbleDown(ROOT_INDEX); |
||||
this._hasPoppedElement = false; |
||||
} else { |
||||
const pos = this.length + ROOT_INDEX; |
||||
this._keys[pos] = key; |
||||
this._priorities[pos] = priority; |
||||
this.length++; |
||||
this.bubbleUp(pos); |
||||
} |
||||
} |
||||
pop() { |
||||
if (this.length === 0) { |
||||
return void 0; |
||||
} |
||||
this.removePoppedElement(); |
||||
this.length--; |
||||
this._hasPoppedElement = true; |
||||
return this._keys[ROOT_INDEX]; |
||||
} |
||||
peekPriority() { |
||||
this.removePoppedElement(); |
||||
return this._priorities[ROOT_INDEX]; |
||||
} |
||||
peek() { |
||||
this.removePoppedElement(); |
||||
return this._keys[ROOT_INDEX]; |
||||
} |
||||
removePoppedElement() { |
||||
if (this._hasPoppedElement) { |
||||
this._keys[ROOT_INDEX] = this._keys[this.length + ROOT_INDEX]; |
||||
this._priorities[ROOT_INDEX] = this._priorities[this.length + ROOT_INDEX]; |
||||
this.bubbleDown(ROOT_INDEX); |
||||
this._hasPoppedElement = false; |
||||
} |
||||
} |
||||
get size() { |
||||
return this.length; |
||||
} |
||||
dumpRawPriorities() { |
||||
this.removePoppedElement(); |
||||
const result = Array(this.length - ROOT_INDEX); |
||||
for (let i = 0; i < this.length; i++) { |
||||
result[i] = this._priorities[i + ROOT_INDEX]; |
||||
} |
||||
return `[${result.join(" ")}]`; |
||||
} |
||||
} |
Loading…
Reference in new issue