You've already forked obsidian-visualiser
Revert "Update packages. Add quadtree (still need update for ID handling instead of index)."
This reverts commit 685bd47fc4.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import type { CanvasContent } from '~/types/canvas';
|
||||
import type { FileType } from '~/types/content';
|
||||
import type { ContentMap, FileType } from '~/types/content';
|
||||
|
||||
export function unifySlug(slug: string | string[]): string
|
||||
{
|
||||
|
||||
@@ -1,478 +0,0 @@
|
||||
import type { CanvasContent } from "~/types/canvas";
|
||||
import { clamp } from "./general.utils";
|
||||
|
||||
export function intersects(aLeft: number, aTop: number, aRight: number, aBottom: number, bLeft: number, bTop: number, bRight: number, bBottom: number): boolean
|
||||
{
|
||||
return aLeft <= bRight && aRight >= bLeft && aTop <= bBottom && aBottom >= bTop;
|
||||
}
|
||||
|
||||
enum QuadConsts
|
||||
{
|
||||
ENodeINext = 0,
|
||||
ENodeIElmt = 1,
|
||||
ElmtILft = 0, ElmtIRgt = 1, ElmtITop = 2, ElmtIBtm = 3,
|
||||
ElmtIId = 4,
|
||||
NodeIFirst = 0,
|
||||
NodeICount = 1,
|
||||
PropCount = 6,
|
||||
PropILft = 0, PropIRgt = 1, PropITop = 2, PropIBtm = 3,
|
||||
PropIIdx = 4,
|
||||
PropIDpt = 5,
|
||||
}
|
||||
|
||||
const DEFAULT_SIZE = 128;
|
||||
const MAX_BYTE_LENGTH = 1024*1024*16;
|
||||
|
||||
export interface AABB {
|
||||
x1: number;
|
||||
x2: number;
|
||||
y1: number;
|
||||
y2: number;
|
||||
}
|
||||
|
||||
export class IntList
|
||||
{
|
||||
#data: Int32Array;
|
||||
#fields: number;
|
||||
#capacity: number = DEFAULT_SIZE;
|
||||
#length: number = 0;
|
||||
#free: number = -1;
|
||||
|
||||
constructor(fields: number)
|
||||
{
|
||||
if(fields <= 0)
|
||||
throw new Error("Invalid field count");
|
||||
|
||||
this.#data = new Int32Array(new ArrayBuffer(this.#capacity * fields * Int32Array.BYTES_PER_ELEMENT, { maxByteLength: MAX_BYTE_LENGTH }));
|
||||
this.#fields = fields;
|
||||
}
|
||||
get length(): number
|
||||
{
|
||||
return this.#length;
|
||||
}
|
||||
|
||||
get(index: number, field: number): number
|
||||
{
|
||||
if(index >= this.#length || index < 0)
|
||||
throw new Error("Invalid index");
|
||||
|
||||
if(field >= this.#fields || field < 0)
|
||||
throw new Error("Invalid field");
|
||||
|
||||
return this.#data[index*this.#fields + field];
|
||||
}
|
||||
set(index: number, field: number, value: number): void
|
||||
{
|
||||
if(index >= this.#length || index < 0)
|
||||
throw new Error("Invalid index");
|
||||
|
||||
if(field >= this.#fields || field < 0)
|
||||
throw new Error("Invalid field");
|
||||
|
||||
this.#data[index*this.#fields + field] = value;
|
||||
}
|
||||
clear(): void
|
||||
{
|
||||
//Never edit the array during clear, we'll keep every data as they *must* be override before being read.
|
||||
this.#length = 0;
|
||||
this.#free = -1;
|
||||
}
|
||||
push(): number
|
||||
{
|
||||
const pos = (this.#length + 1) * this.#fields;
|
||||
|
||||
if(pos > this.#capacity * this.#fields)
|
||||
{
|
||||
this.#capacity *= 2;
|
||||
|
||||
if((this.#data.buffer as ArrayBuffer).resizable)
|
||||
{
|
||||
(this.#data.buffer as ArrayBuffer).resize(clamp(this.#capacity * this.#fields * Int32Array.BYTES_PER_ELEMENT, 1, MAX_BYTE_LENGTH));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Error("Cannot resize the buffer");
|
||||
}
|
||||
}
|
||||
|
||||
return this.#length++;
|
||||
}
|
||||
pop(): void
|
||||
{
|
||||
if(this.#length <= 0)
|
||||
return;
|
||||
|
||||
--this.#length;
|
||||
}
|
||||
insert(): number
|
||||
{
|
||||
if(this.#free !== -1)
|
||||
{
|
||||
const index = this.#free;
|
||||
this.#free = this.#data[index * this.#fields];
|
||||
return index;
|
||||
}
|
||||
else
|
||||
return this.push();
|
||||
}
|
||||
erase(index: number): void
|
||||
{
|
||||
if(index >= this.#length || index < 0)
|
||||
throw new Error("Invalid index");
|
||||
|
||||
this.#data[index * this.#fields] = this.#free;
|
||||
this.#free = index;
|
||||
}
|
||||
|
||||
toArray(): number[]
|
||||
{
|
||||
return [...this.#data.slice(0, this.#length)];
|
||||
}
|
||||
|
||||
//DEBUG
|
||||
/* printReadable(names: string[] = []): void
|
||||
{
|
||||
const size = this.#length * this.#fields;
|
||||
for(let i = 0; i < size; i)
|
||||
{
|
||||
const obj: Record<string, number> = {};
|
||||
for(let j = 0; j < this.#fields; ++i, ++j)
|
||||
{
|
||||
obj[names[j] ?? j] = this.#data[i];
|
||||
}
|
||||
console.log(obj);
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
||||
const _process = new IntList(1);
|
||||
const _nodes = new IntList(QuadConsts.PropCount);
|
||||
|
||||
/**
|
||||
* A node that contains elments is called a leaf. Its count is equals to the amount of children it holds.
|
||||
* A node that contains other nodes is called a branch. Its count is equals to -1.
|
||||
* The AABB of each node isn't stored and is computed on the go.
|
||||
*/
|
||||
export class Quadtree
|
||||
{
|
||||
#bounds: AABB;
|
||||
#maxDepth: number = 8;
|
||||
#maxElmts: number = 4;
|
||||
#nodes: IntList = new IntList(2);
|
||||
#enodes: IntList = new IntList(2);
|
||||
#content: IntList = new IntList(5);
|
||||
|
||||
#ids: string[] = [];
|
||||
|
||||
ref: Ref<CanvasContent>;
|
||||
|
||||
constructor(bounds: AABB, ref: Ref<CanvasContent>, maxDepth?: number, maxElmts?: number)
|
||||
{
|
||||
this.#bounds = bounds;
|
||||
this.#bounds.x1 = Math.round(this.#bounds.x1);
|
||||
this.#bounds.x2 = Math.round(this.#bounds.x2);
|
||||
this.#bounds.y1 = Math.round(this.#bounds.y1);
|
||||
this.#bounds.y2 = Math.round(this.#bounds.y2);
|
||||
|
||||
this.#maxDepth = maxDepth ?? this.#maxDepth;
|
||||
this.#maxElmts = maxElmts ?? this.#maxElmts;
|
||||
|
||||
this.ref = ref;
|
||||
|
||||
this.#nodes.insert();
|
||||
this.#nodes.set(0, QuadConsts.NodeIFirst, -1);
|
||||
this.#nodes.set(0, QuadConsts.NodeICount, 0);
|
||||
}
|
||||
fetch(x: number, y: number): string[]
|
||||
{
|
||||
return this.query({x1: x, x2: x, y1: y, y2: y});
|
||||
}
|
||||
query(aabb: AABB): string[]
|
||||
{
|
||||
_process.clear();
|
||||
|
||||
const leaves = this.#findLeaves(0, 0, this.#bounds.x1, this.#bounds.x2, this.#bounds.y1, this.#bounds.y2, Math.floor(aabb.x1), Math.floor(aabb.x2), Math.floor(aabb.y1), Math.floor(aabb.y2));
|
||||
|
||||
const tmp: Record<number, boolean> = {};
|
||||
|
||||
for(let i = 0; i < leaves.length; ++i)
|
||||
{
|
||||
const index = leaves.get(i, QuadConsts.PropIIdx);
|
||||
|
||||
let node = this.#nodes.get(index, QuadConsts.NodeIFirst);
|
||||
while(node !== -1)
|
||||
{
|
||||
const elmt = this.#enodes.get(node, QuadConsts.ENodeIElmt);
|
||||
|
||||
const idIndex = this.#content.get(elmt, QuadConsts.ElmtIId);
|
||||
const id = this.#ids[idIndex];
|
||||
const canvasNode = this.ref.value.nodes?.find(e => e.id === id)!;
|
||||
|
||||
if (!tmp[elmt] && intersects(aabb.x1, aabb.y1, aabb.x2, aabb.y2, canvasNode.x, canvasNode.y, canvasNode.x + canvasNode.width, canvasNode.y + canvasNode.height))
|
||||
{
|
||||
_process.set(_process.push(), 0, idIndex);
|
||||
tmp[elmt] = true;
|
||||
}
|
||||
node = this.#enodes.get(node, QuadConsts.ENodeINext);
|
||||
}
|
||||
}
|
||||
|
||||
return _process.toArray().map(e => this.#ids[e]);
|
||||
}
|
||||
insert(id: string, aabb: AABB): number
|
||||
{
|
||||
const index = this.#content.insert();
|
||||
const idIndex = this.#ids.push(id) - 1;
|
||||
|
||||
this.#content.set(index, QuadConsts.ElmtILft, Math.floor(aabb.x1));
|
||||
this.#content.set(index, QuadConsts.ElmtIRgt, Math.floor(aabb.x2));
|
||||
this.#content.set(index, QuadConsts.ElmtITop, Math.floor(aabb.y1));
|
||||
this.#content.set(index, QuadConsts.ElmtIBtm, Math.floor(aabb.y2));
|
||||
this.#content.set(index, QuadConsts.ElmtIId, idIndex);
|
||||
|
||||
this.#insertNode(0, 0, this.#bounds.x1, this.#bounds.x2, this.#bounds.y1, this.#bounds.y2, index);
|
||||
return index;
|
||||
}
|
||||
remove(index: number): void
|
||||
{
|
||||
if(index >= this.#content.length || index < 0)
|
||||
throw new Error("Provided index is out of bounds.");
|
||||
|
||||
const leaves = this.#findLeaves(0, 0, this.#bounds.x1, this.#bounds.x2, this.#bounds.y1, this.#bounds.y2, this.#content.get(index, QuadConsts.ElmtILft), this.#content.get(index, QuadConsts.ElmtIRgt), this.#content.get(index, QuadConsts.ElmtITop), this.#content.get(index, QuadConsts.ElmtIBtm))
|
||||
|
||||
for(let i = 0; i < leaves.length; ++i)
|
||||
{
|
||||
const prop = leaves.get(i, QuadConsts.PropIIdx);
|
||||
|
||||
let node = this.#nodes.get(prop, QuadConsts.NodeIFirst), prev = -1;
|
||||
while(node !== -1 && this.#enodes.get(node, QuadConsts.ENodeIElmt) !== index)
|
||||
{
|
||||
prev = node;
|
||||
node = this.#enodes.get(node, QuadConsts.ENodeINext);
|
||||
}
|
||||
|
||||
if(node !== -1)
|
||||
{
|
||||
const next = this.#enodes.get(node, QuadConsts.ENodeINext);
|
||||
|
||||
if(prev == -1)
|
||||
this.#nodes.set(prop, QuadConsts.NodeIFirst, next);
|
||||
else
|
||||
this.#enodes.set(prev, QuadConsts.ENodeINext, next);
|
||||
|
||||
this.#nodes.set(prop, QuadConsts.NodeICount, this.#nodes.get(prop, QuadConsts.NodeICount) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
this.#content.erase(index);
|
||||
}
|
||||
clear(): void
|
||||
{
|
||||
this.#nodes.clear();
|
||||
this.#content.clear();
|
||||
this.#enodes.clear();
|
||||
this.#ids.length = 0;
|
||||
|
||||
this.#nodes.insert();
|
||||
this.#nodes.set(0, QuadConsts.NodeIFirst, -1);
|
||||
this.#nodes.set(0, QuadConsts.NodeICount, 0);
|
||||
}
|
||||
cleanup(): void
|
||||
{
|
||||
let updated = false;
|
||||
_process.clear();
|
||||
|
||||
if(this.#nodes.get(0, QuadConsts.NodeICount) === -1)
|
||||
_process.set(_process.push(), 0, 0);
|
||||
|
||||
while(_process.length > 0)
|
||||
{
|
||||
const node = _process.get(_process.length - 1, 0);
|
||||
const fc = this.#nodes.get(node, QuadConsts.NodeIFirst);
|
||||
|
||||
let empty = 0;
|
||||
_process.pop();
|
||||
|
||||
for(let i = 0; i < 4; ++i)
|
||||
{
|
||||
const current = fc + i;
|
||||
|
||||
const count = this.#nodes.get(current, QuadConsts.NodeICount);
|
||||
|
||||
if(count === 0) //Count the amount of empty leaves
|
||||
++empty
|
||||
else if(count === -1) //Add this node to the check process if it's a branch
|
||||
_process.set(_process.push(), 0, current);
|
||||
}
|
||||
|
||||
if(empty === 4)
|
||||
{
|
||||
//Because of the way the IntList is made, it's preferable to erase in the reversed order as the last erased index becomes the first available index.
|
||||
this.#nodes.erase(fc + 3);
|
||||
this.#nodes.erase(fc + 2);
|
||||
this.#nodes.erase(fc + 1);
|
||||
this.#nodes.erase(fc + 0);
|
||||
|
||||
//The branch becomes a empty leaf
|
||||
this.#nodes.set(node, QuadConsts.NodeICount, 0);
|
||||
this.#nodes.set(node, QuadConsts.NodeIFirst, -1);
|
||||
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
traverse(cb: (index: number, depth: number, left: number, top: number, right: number, bottom: number, leaf: boolean) => void): void
|
||||
{
|
||||
_nodes.clear();
|
||||
|
||||
insert(_nodes, 0, 0, this.#bounds.x1, this.#bounds.x2, this.#bounds.y1, this.#bounds.y2);
|
||||
|
||||
while(_nodes.length > 0)
|
||||
{
|
||||
const last = _nodes.length - 1;
|
||||
|
||||
const node = _nodes.get(last, QuadConsts.PropIIdx);
|
||||
const left = _nodes.get(last, QuadConsts.PropILft);
|
||||
const right = _nodes.get(last, QuadConsts.PropIRgt);
|
||||
const top = _nodes.get(last, QuadConsts.PropITop);
|
||||
const bottom = _nodes.get(last, QuadConsts.PropIBtm);
|
||||
const depth = _nodes.get(last, QuadConsts.PropIDpt);
|
||||
|
||||
_nodes.pop();
|
||||
|
||||
const count = this.#nodes.get(node, QuadConsts.NodeICount);
|
||||
|
||||
cb(node, depth, left, top, right, bottom, count !== -1);
|
||||
|
||||
//If it's a branch
|
||||
if (count === -1)
|
||||
{
|
||||
const fc = this.#nodes.get(node, QuadConsts.NodeIFirst);
|
||||
const mx = left + (right - left) / 2, my = top + (bottom - top) / 2;
|
||||
|
||||
insert(_nodes, fc + 0, depth + 1, left, mx, top, my);
|
||||
insert(_nodes, fc + 1, depth + 1, mx, right, top, my);
|
||||
insert(_nodes, fc + 2, depth + 1, left, mx, my, bottom);
|
||||
insert(_nodes, fc + 3, depth + 1, mx, right, my, bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#findLeaves(index: number, depth: number, lleft: number, lright: number, ltop: number, lbottom: number, eleft: number, eright: number, etop: number, ebottom: number): IntList
|
||||
{
|
||||
const leaves = new IntList(QuadConsts.PropCount);
|
||||
_nodes.clear();
|
||||
insert(_nodes, index, depth, lleft, lright, ltop, lbottom);
|
||||
|
||||
while (_nodes.length > 0)
|
||||
{
|
||||
const last = _nodes.length - 1;
|
||||
|
||||
const nodeLeft = _nodes.get(last, QuadConsts.PropILft);
|
||||
const nodeRight = _nodes.get(last, QuadConsts.PropIRgt);
|
||||
const nodeTop = _nodes.get(last, QuadConsts.PropITop);
|
||||
const nodeBottom = _nodes.get(last, QuadConsts.PropIBtm);
|
||||
const nodeIndex = _nodes.get(last, QuadConsts.PropIIdx);
|
||||
const nodeDepth = _nodes.get(last, QuadConsts.PropIDpt);
|
||||
|
||||
_nodes.pop();
|
||||
|
||||
if(this.#nodes.get(nodeIndex, QuadConsts.NodeICount) !== -1)
|
||||
insert(leaves, nodeIndex, nodeDepth, nodeLeft, nodeRight, nodeTop, nodeBottom);
|
||||
else
|
||||
{
|
||||
const fc = this.#nodes.get(nodeIndex, QuadConsts.NodeIFirst);
|
||||
|
||||
const mx = nodeLeft + (nodeRight - nodeLeft) / 2, my = nodeTop + (nodeBottom - nodeTop) / 2;
|
||||
|
||||
if(etop <= my)
|
||||
{
|
||||
if(eleft <= mx) //Add a new
|
||||
insert(_nodes, fc + 0, nodeDepth + 1, nodeLeft, mx, nodeTop, my);
|
||||
if(eright > mx)
|
||||
insert(_nodes, fc + 1, nodeDepth + 1, mx, nodeRight, nodeTop, my);
|
||||
}
|
||||
if(ebottom > my)
|
||||
{
|
||||
if(eleft <= mx)
|
||||
insert(_nodes, fc + 2, nodeDepth + 1, nodeLeft, mx, my, nodeBottom);
|
||||
if(eright > mx)
|
||||
insert(_nodes, fc + 3, nodeDepth + 1, mx, nodeRight, my, nodeBottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return leaves;
|
||||
}
|
||||
#insertLeaf(index: number, depth: number, left: number, top: number, right: number, bottom: number, elmt: number): void
|
||||
{
|
||||
const fc = this.#nodes.get(index, QuadConsts.NodeIFirst);
|
||||
this.#nodes.set(index, QuadConsts.NodeIFirst, this.#enodes.insert());
|
||||
this.#enodes.set(this.#nodes.get(index, QuadConsts.NodeIFirst), QuadConsts.ENodeINext, fc);
|
||||
this.#enodes.set(this.#nodes.get(index, QuadConsts.NodeIFirst), QuadConsts.ENodeIElmt, elmt);
|
||||
|
||||
if(this.#nodes.get(index, QuadConsts.NodeICount) === this.#maxElmts && depth < this.#maxDepth)
|
||||
{
|
||||
_process.clear();
|
||||
//For each element of the leaf
|
||||
while(this.#nodes.get(index, QuadConsts.NodeIFirst) !== -1)
|
||||
{
|
||||
const current = this.#nodes.get(index, QuadConsts.NodeIFirst);
|
||||
const next = this.#enodes.get(current, QuadConsts.ENodeINext);
|
||||
const value = this.#enodes.get(current, QuadConsts.ENodeIElmt);
|
||||
|
||||
//Remove the element
|
||||
this.#nodes.set(index, QuadConsts.NodeIFirst, next);
|
||||
this.#enodes.erase(current);
|
||||
|
||||
//Store it for reprocess
|
||||
_process.set(_process.push(), 0, value);
|
||||
}
|
||||
|
||||
const newFc = this.#nodes.insert();
|
||||
this.#nodes.set(newFc + 0, QuadConsts.NodeIFirst, -1);
|
||||
this.#nodes.set(newFc + 0, QuadConsts.NodeICount, 0);
|
||||
|
||||
this.#nodes.insert();
|
||||
this.#nodes.set(newFc + 1, QuadConsts.NodeIFirst, -1);
|
||||
this.#nodes.set(newFc + 1, QuadConsts.NodeICount, 0);
|
||||
|
||||
this.#nodes.insert();
|
||||
this.#nodes.set(newFc + 2, QuadConsts.NodeIFirst, -1);
|
||||
this.#nodes.set(newFc + 2, QuadConsts.NodeICount, 0);
|
||||
|
||||
this.#nodes.insert();
|
||||
this.#nodes.set(newFc + 3, QuadConsts.NodeIFirst, -1);
|
||||
this.#nodes.set(newFc + 3, QuadConsts.NodeICount, 0);
|
||||
|
||||
this.#nodes.set(index, QuadConsts.NodeIFirst, newFc);
|
||||
|
||||
this.#nodes.set(index, QuadConsts.NodeICount, -1);
|
||||
|
||||
for(let i = 0; i < _process.length; ++i)
|
||||
this.#insertNode(index, depth, left, right, top, bottom, _process.get(i, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.#nodes.set(index, QuadConsts.NodeICount, this.#nodes.get(index, QuadConsts.NodeICount) + 1);
|
||||
}
|
||||
}
|
||||
#insertNode(index: number, depth: number, left: number, right: number, top: number, bottom: number, elmt: number): void
|
||||
{
|
||||
const leaves = this.#findLeaves(index, depth, left, right, top, bottom, this.#content.get(elmt, QuadConsts.ElmtILft), this.#content.get(elmt, QuadConsts.ElmtIRgt), this.#content.get(elmt, QuadConsts.ElmtITop), this.#content.get(elmt, QuadConsts.ElmtIBtm));
|
||||
|
||||
for(let i = 0; i < leaves.length; ++i)
|
||||
this.#insertLeaf(leaves.get(i, QuadConsts.PropIIdx), leaves.get(i, QuadConsts.PropIDpt), leaves.get(i, QuadConsts.PropILft), leaves.get(i, QuadConsts.PropITop), leaves.get(i, QuadConsts.PropIRgt), leaves.get(i, QuadConsts.PropIBtm), elmt);
|
||||
}
|
||||
}
|
||||
|
||||
function insert(list: IntList, index: number, depth: number, left: number, right: number, top: number, bottom: number): void
|
||||
{
|
||||
const idx = list.push();
|
||||
list.set(idx, QuadConsts.PropILft, left);
|
||||
list.set(idx, QuadConsts.PropIRgt, right);
|
||||
list.set(idx, QuadConsts.PropITop, top);
|
||||
list.set(idx, QuadConsts.PropIBtm, bottom);
|
||||
list.set(idx, QuadConsts.PropIIdx, index);
|
||||
list.set(idx, QuadConsts.PropIDpt, depth);
|
||||
}
|
||||
Reference in New Issue
Block a user