Test optimizing quadtree
This commit is contained in:
parent
958ffa9ae9
commit
1d907c8ab9
194
src/common.ts
194
src/common.ts
|
|
@ -1,7 +1,149 @@
|
|||
const DEFAULT_BUCKET_SIZE = 64;
|
||||
const DEFAULT_SIZE = 128;
|
||||
|
||||
export class FreeList<T>
|
||||
{
|
||||
#data: T[] = [];
|
||||
#free: LinkedList<number> = new LinkedList<number>();
|
||||
|
||||
export class FastStack<T>
|
||||
#length: number = 0;
|
||||
|
||||
constructor(arr?: T[])
|
||||
{
|
||||
if(arr && arr.length > 0)
|
||||
this.#data = arr;
|
||||
}
|
||||
|
||||
insert(element: T): number
|
||||
{
|
||||
if(this.#free.empty)
|
||||
{
|
||||
return this.#data.push(element) - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
const index = this.#free.pop()!;
|
||||
this.#data[index] = element;
|
||||
return index;
|
||||
}
|
||||
}
|
||||
erase(n: number): void
|
||||
{
|
||||
delete this.#data[n];
|
||||
this.#free.add(n);
|
||||
}
|
||||
clear(): void
|
||||
{
|
||||
this.#data.length = 0;
|
||||
this.#free.clear();
|
||||
}
|
||||
|
||||
get(index: number): T
|
||||
{
|
||||
return this.#data[index];
|
||||
}
|
||||
set(index: number, value: T): void
|
||||
{
|
||||
this.#data[index] = value;
|
||||
}
|
||||
get length(): number
|
||||
{
|
||||
return this.#length;
|
||||
}
|
||||
}
|
||||
export class IntList
|
||||
{
|
||||
#data: Uint32Array;
|
||||
#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 Uint32Array(this.#capacity * fields);
|
||||
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 since they *must* always 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.#capacity *= 2;
|
||||
|
||||
//@ts-ignore
|
||||
this.#data.buffer.transferToFixedLength(this.#capacity);
|
||||
}
|
||||
|
||||
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 Array.from(this.#data);
|
||||
}
|
||||
}
|
||||
export class Stack<T>
|
||||
{
|
||||
#arr: T[];
|
||||
|
||||
|
|
@ -12,7 +154,7 @@ export class FastStack<T>
|
|||
|
||||
constructor(size?: number)
|
||||
{
|
||||
this.#bucketSize = size ?? DEFAULT_BUCKET_SIZE;
|
||||
this.#bucketSize = size ?? DEFAULT_SIZE;
|
||||
|
||||
this.#arr = new Array(this.#bucketSize * this.#bucketCount);
|
||||
this.#pos = 0;
|
||||
|
|
@ -56,7 +198,7 @@ export class FastStack<T>
|
|||
this.#arr.length = this.#bucketSize * this.#bucketCount;
|
||||
}
|
||||
}
|
||||
export class FastQueue<T>
|
||||
export class Queue<T>
|
||||
{
|
||||
#arr: T[];
|
||||
|
||||
|
|
@ -68,7 +210,7 @@ export class FastQueue<T>
|
|||
|
||||
constructor(size?: number)
|
||||
{
|
||||
this.#bucketSize = size ?? DEFAULT_BUCKET_SIZE;
|
||||
this.#bucketSize = size ?? DEFAULT_SIZE;
|
||||
|
||||
this.#arr = new Array(this.#bucketSize * this.#bucketCount);
|
||||
this.#idx = 0;
|
||||
|
|
@ -127,20 +269,19 @@ export class FastQueue<T>
|
|||
export class LinkedList<T>
|
||||
{
|
||||
#head: LinkedElmt<T> | null;
|
||||
#tail: LinkedElmt<T> | null; //Extension
|
||||
constructor()
|
||||
{
|
||||
this.#head = null;
|
||||
this.#tail = null;
|
||||
}
|
||||
get empty(): boolean
|
||||
{
|
||||
return this.#head === null;
|
||||
}
|
||||
add(item: T): void
|
||||
{
|
||||
const tail = this.#tail;
|
||||
const head = this.#head;
|
||||
|
||||
this.#tail = { elmt: item, next: null, prev: tail };
|
||||
|
||||
if(tail) tail.next = this.#tail;
|
||||
else this.#head = this.#tail;
|
||||
this.#head = { elmt: item, next: head };
|
||||
}
|
||||
pop(): T | null
|
||||
{
|
||||
|
|
@ -150,31 +291,11 @@ export class LinkedList<T>
|
|||
const head = this.#head;
|
||||
this.#head = head.next;
|
||||
|
||||
if(head === this.#tail) this.#tail = null;
|
||||
if(head.next) head.next.prev = null;
|
||||
if(head.next && head.next === this.#tail) this.#tail = head.next;
|
||||
|
||||
return head.elmt;
|
||||
}
|
||||
remove(item: T): boolean
|
||||
peek(): T | null
|
||||
{
|
||||
let current = this.#head;
|
||||
while(current)
|
||||
{
|
||||
if(current.elmt === item)
|
||||
{
|
||||
const prev = current.prev;
|
||||
const next = current.next;
|
||||
|
||||
if(prev) prev.next = next;
|
||||
if(next) next.prev = prev;
|
||||
|
||||
return true;
|
||||
}
|
||||
current = current.next;
|
||||
}
|
||||
|
||||
return false;
|
||||
return this.#head?.elmt ?? null;
|
||||
}
|
||||
forEach(cb: (e: T, i: number) => void): void
|
||||
{
|
||||
|
|
@ -194,11 +315,7 @@ export class LinkedList<T>
|
|||
}
|
||||
clear()
|
||||
{
|
||||
if(this.#head) this.#head.next = null;
|
||||
if(this.#tail) this.#tail.prev = null;
|
||||
|
||||
this.#head = null;
|
||||
this.#tail = null;
|
||||
}
|
||||
}
|
||||
interface LinkedElmt<T>
|
||||
|
|
@ -206,7 +323,6 @@ interface LinkedElmt<T>
|
|||
elmt: T;
|
||||
|
||||
next: LinkedElmt<T> | null;
|
||||
prev: LinkedElmt<T> | null;
|
||||
}
|
||||
export function clamp(x: number, min: number, max: number): number
|
||||
{
|
||||
|
|
|
|||
13
src/main.ts
13
src/main.ts
|
|
@ -7,6 +7,11 @@ import Input from './renderer/input.class';
|
|||
import { Random, clamp } from './common';
|
||||
import Selector from './renderer/selector.class';
|
||||
|
||||
if(!ArrayBuffer.prototype.hasOwnProperty("transferToFixedLength"))
|
||||
{
|
||||
throw new Error("Your web browser doesn't includes the latest features needed to make this website work properly.\nPlease upgrade your browser and try again.");
|
||||
}
|
||||
|
||||
Renderer.init();
|
||||
Input.init(Renderer.canvas);
|
||||
Selector.init();
|
||||
|
|
@ -26,7 +31,7 @@ for(let i = 0; i < 10000; i++)
|
|||
.scale(r.nextFloat(0.01, 0.15), r.nextFloat(0.01, 0.15))
|
||||
|
||||
Asset.instance.setMatrixAt(i, assets[i].mat);
|
||||
quad.insert(assets[i]);
|
||||
quad.insert(i, assets[i]);
|
||||
}
|
||||
|
||||
Asset.instance.count = assets.length;
|
||||
|
|
@ -43,7 +48,7 @@ Input.onDragEnd((start, end, button) => {
|
|||
if(button & 1)
|
||||
{
|
||||
const s = performance.now();
|
||||
const selection = quad.query({x1: Math.min(start.x, end.x), x2: Math.max(start.x, end.x), y1: Math.min(start.y, end.y), y2: Math.max(start.y, end.y)}) as Asset[];
|
||||
const selection = quad.query({x1: Math.min(start.x, end.x), x2: Math.max(start.x, end.x), y1: Math.min(start.y, end.y), y2: Math.max(start.y, end.y)}).map(e => assets[e]);
|
||||
console.log("Fetching %s out of %s elements in %sms", selection.length, assets.length, performance.now() - s);
|
||||
|
||||
if(Input.keys['Shift']) Selector.toggle(selection);
|
||||
|
|
@ -54,11 +59,11 @@ Input.onDrag((delta, start, end, button) => { if(button & 1) Selector.preview(st
|
|||
Input.onClick((point, button) => {
|
||||
if(button & 1)
|
||||
{
|
||||
const selection = quad.fetch(point.x, point.y) as Asset[];
|
||||
const selection = quad.fetch(point.x, point.y).map(e => assets[e]);
|
||||
|
||||
if(Input.keys['Shift']) Selector.toggle(selection);
|
||||
else Selector.select(selection);
|
||||
}
|
||||
});
|
||||
Input.onWheel(delta => Renderer.zoom = clamp(Renderer.zoom * 1 + (delta * -0.001), 1, 5));
|
||||
Input.onMove(p => { if(!Input.dragging) Selector.ghost(quad.fetch(p.x, p.y)[0] as Asset); });
|
||||
Input.onMove(p => { if(!Input.dragging) Selector.ghost(assets[quad.fetch(p.x, p.y)[0]]); });
|
||||
|
|
@ -11,7 +11,7 @@ export interface AABB
|
|||
y2: number;
|
||||
}
|
||||
|
||||
export function intersects(a: AABB, b: AABB | Point): Boolean
|
||||
export function intersectsObj(a: AABB, b: AABB | Point): Boolean
|
||||
{
|
||||
if(b.hasOwnProperty("x") && b.hasOwnProperty("y"))
|
||||
{
|
||||
|
|
@ -26,3 +26,7 @@ export function intersects(a: AABB, b: AABB | Point): Boolean
|
|||
return a.x1 <= b.x2 && a.x2 >= b.x1 && a.y1 <= b.y2 && a.y2 >= b.y1;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { LinkedList } from "../common";
|
||||
import { FreeList, IntList, LinkedList } from "../common";
|
||||
import Renderer from "../renderer/renderer.class";
|
||||
import { AABB, intersects } from "./common";
|
||||
import * as THREE from 'three';
|
||||
|
|
@ -8,13 +8,31 @@ import * as THREE from 'three';
|
|||
* 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. (Not memory friendly in JS ?)
|
||||
*/
|
||||
export default class Quadtree<T extends AABB>
|
||||
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 _process = new IntList(1);
|
||||
const _nodeProcess = new IntList(QuadConsts.PropCount);
|
||||
|
||||
export default class Quadtree
|
||||
{
|
||||
#bounds: AABB;
|
||||
#maxDepth: number = 8;
|
||||
#maxElmts: number = 4;
|
||||
#nodes: Node[];
|
||||
#content: T[];
|
||||
#nodes: IntList = new IntList(2);
|
||||
#enodes: IntList = new IntList(2);
|
||||
#content: IntList = new IntList(5);
|
||||
|
||||
#dirty: boolean = true;
|
||||
#debugRect: THREE.Box3Helper[] = [];
|
||||
|
|
@ -25,51 +43,104 @@ export default class Quadtree<T extends AABB>
|
|||
this.#maxDepth = maxDepth ?? this.#maxDepth;
|
||||
this.#maxElmts = maxElmts ?? this.#maxElmts;
|
||||
|
||||
this.#nodes = [];
|
||||
this.#content = [];
|
||||
|
||||
this.#nodes.push({ children: new LinkedList<number>(), count: 0 });
|
||||
this.#nodes.insert();
|
||||
this.#nodes.set(0, QuadConsts.NodeIFirst, -1);
|
||||
this.#nodes.set(0, QuadConsts.NodeICount, 0);
|
||||
}
|
||||
fetch(x: number, y: number): T[]
|
||||
fetch(x: number, y: number): number[]
|
||||
{
|
||||
return this.query({x1: x, x2: x, y1: y, y2: y});
|
||||
}
|
||||
query(aabb: AABB): T[]
|
||||
query(aabb: AABB): number[]
|
||||
{
|
||||
const result: number[] = [];
|
||||
_process.clear();
|
||||
|
||||
const leaves = this.#find_leaves(0, 0, this.#bounds, aabb); //Search every mathed leaves
|
||||
for(let i = 0; i < leaves.length; i++)
|
||||
const leaves = this.#findLeaves(0, 0, this.#bounds.x1, this.#bounds.x2, this.#bounds.y1, this.#bounds.y2, aabb.x1, aabb.x2, aabb.y1, aabb.y2);
|
||||
|
||||
const tmp: Record<number, boolean> = {};
|
||||
|
||||
for(let i = 0; i < leaves.length; ++i)
|
||||
{
|
||||
const node = this.#nodes[leaves[i].index];
|
||||
node.children.forEach(e => { //Test every leaves elements
|
||||
if(!result.includes(e) && intersects(this.#content[e], aabb))
|
||||
result.push(e);
|
||||
});
|
||||
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 left = this.#content.get(elmt, QuadConsts.ElmtILft),
|
||||
right = this.#content.get(elmt, QuadConsts.ElmtIRgt),
|
||||
top = this.#content.get(elmt, QuadConsts.ElmtITop),
|
||||
bottom = this.#content.get(elmt, QuadConsts.ElmtIBtm);
|
||||
|
||||
if(!tmp[elmt] && intersects(aabb.x1, aabb.y1, aabb.x2, aabb.y2, left, top, right, bottom))
|
||||
{
|
||||
_process.set(_process.push(), 0, elmt);
|
||||
tmp[elmt] = true;
|
||||
}
|
||||
node = this.#enodes.get(node, QuadConsts.ENodeINext);
|
||||
}
|
||||
}
|
||||
|
||||
return result.map(e => this.#content[e]);
|
||||
return _process.toArray();
|
||||
}
|
||||
insert(item: T): number
|
||||
insert(id: number, aabb: AABB): number
|
||||
{
|
||||
const idx = this.#content.push(item) - 1;
|
||||
this.#node_insert(0, 0, this.#bounds, idx);
|
||||
return idx;
|
||||
const index = this.#content.insert();
|
||||
|
||||
this.#content.set(index, QuadConsts.ElmtILft, aabb.x1);
|
||||
this.#content.set(index, QuadConsts.ElmtIRgt, aabb.x2);
|
||||
this.#content.set(index, QuadConsts.ElmtITop, aabb.y1);
|
||||
this.#content.set(index, QuadConsts.ElmtIBtm, aabb.y2);
|
||||
this.#content.set(index, QuadConsts.ElmtIId, id);
|
||||
|
||||
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)
|
||||
throw new Error("Out of bound exception.");
|
||||
if(index >= this.#content.length || index < 0)
|
||||
throw new Error("Provided index is out of bounds.");
|
||||
|
||||
const elmt = this.#content[index];
|
||||
const leaves = this.#find_leaves(0, 0, this.#bounds, elmt);
|
||||
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++)
|
||||
for(let i = 0; i < leaves.length; ++i)
|
||||
{
|
||||
if(this.#nodes[leaves[i].index].children.remove(index))
|
||||
this.#nodes[leaves[i].index].count--;
|
||||
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);
|
||||
}
|
||||
this.#dirty = true;
|
||||
|
||||
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.#dirty = false;
|
||||
|
||||
//DEBUG
|
||||
this.#debugRect.forEach(e => e.parent?.remove(e));
|
||||
this.#debugRect = [];
|
||||
}
|
||||
cleanup(): void
|
||||
{
|
||||
|
|
@ -78,54 +149,78 @@ export default class Quadtree<T extends AABB>
|
|||
if(!this.#dirty)
|
||||
return;
|
||||
|
||||
const stack: number[] = [];
|
||||
_process.clear();
|
||||
|
||||
if(this.#nodes[0].count)
|
||||
stack.push(0);
|
||||
if(this.#nodes.get(0, QuadConsts.NodeICount) === -1)
|
||||
_process.set(_process.push(), 0, 0);
|
||||
|
||||
while(stack.length !== 0)
|
||||
while(_process.length > 0)
|
||||
{
|
||||
const node = this.#nodes[stack.pop()!];
|
||||
const node = _process.get(_process.length - 1, 0);
|
||||
const fc = this.#nodes.get(node, QuadConsts.NodeIFirst);
|
||||
|
||||
let empty_leaves = 0;
|
||||
let empty = 0;
|
||||
_process.pop();
|
||||
|
||||
node.children.forEach(e => {
|
||||
const child = this.#nodes[e];
|
||||
|
||||
if(child.count === 0)
|
||||
empty_leaves++;
|
||||
else if(child.count === -1)
|
||||
stack.push(e);
|
||||
});
|
||||
|
||||
if(empty_leaves === 4)
|
||||
for(let i = 0; i < 4; ++i)
|
||||
{
|
||||
node.count = 0;
|
||||
node.children.clear();
|
||||
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
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
traverse(cb: (prop: NodeProp) => void): void
|
||||
traverse(cb: (index: number, depth: number, left: number, top: number, right: number, bottom: number, leaf: boolean) => void): void
|
||||
{
|
||||
const stack: NodeProp[] = [];
|
||||
stack.push({ index: 0, depth: 0, bounds: this.#bounds });
|
||||
_nodeProcess.clear();
|
||||
|
||||
while(stack.length > 0)
|
||||
insert(_nodeProcess, 0, 0, this.#bounds.x1, this.#bounds.x2, this.#bounds.y1, this.#bounds.y2);
|
||||
|
||||
while(_nodeProcess.length > 0)
|
||||
{
|
||||
const nodeProp = stack.pop()!;
|
||||
const last = _nodeProcess.length - 1;
|
||||
|
||||
cb(nodeProp);
|
||||
const node = _nodeProcess.get(last, QuadConsts.PropIIdx);
|
||||
const left = _nodeProcess.get(last, QuadConsts.PropILft);
|
||||
const right = _nodeProcess.get(last, QuadConsts.PropIRgt);
|
||||
const top = _nodeProcess.get(last, QuadConsts.PropITop);
|
||||
const bottom = _nodeProcess.get(last, QuadConsts.PropIBtm);
|
||||
const depth = _nodeProcess.get(last, QuadConsts.PropIDpt);
|
||||
|
||||
//If the node contains elements
|
||||
if (this.#nodes[nodeProp.index].count === -1)
|
||||
_nodeProcess.pop();
|
||||
|
||||
const count = this.#nodes.get(node, QuadConsts.NodeICount);
|
||||
|
||||
cb(left, top, right, bottom, node, depth, count !== -1);
|
||||
|
||||
//If it's a branch
|
||||
if (count === -1)
|
||||
{
|
||||
const children = this.#nodes[nodeProp.index].children.toArray();
|
||||
const mx = nodeProp.bounds.x1 + (nodeProp.bounds.x2 - nodeProp.bounds.x1) / 2, my = nodeProp.bounds.y1 + (nodeProp.bounds.y2 - nodeProp.bounds.y1) / 2;
|
||||
const fc = this.#nodes.get(node, QuadConsts.NodeIFirst);
|
||||
const mx = left + (right - left) / 2, my = top + (bottom - top) / 2;
|
||||
|
||||
stack.push({ index: children[0], depth: nodeProp.depth + 1, bounds: { x1: nodeProp.bounds.x1, x2: mx, y1: nodeProp.bounds.y1, y2: my } });
|
||||
stack.push({ index: children[1], depth: nodeProp.depth + 1, bounds: { x1: mx, x2: nodeProp.bounds.x2, y1: nodeProp.bounds.y1, y2: my } });
|
||||
stack.push({ index: children[2], depth: nodeProp.depth + 1, bounds: { x1: nodeProp.bounds.x1, x2: mx, y1: my, y2: nodeProp.bounds.y2 } });
|
||||
stack.push({ index: children[3], depth: nodeProp.depth + 1, bounds: { x1: mx, x2: nodeProp.bounds.x2, y1: my, y2: nodeProp.bounds.y2 } });
|
||||
insert(_nodeProcess, fc + 0, depth + 1, left, mx, top, my);
|
||||
insert(_nodeProcess, fc + 1, depth + 1, mx, right, top, my);
|
||||
insert(_nodeProcess, fc + 2, depth + 1, left, mx, my, bottom);
|
||||
insert(_nodeProcess, fc + 3, depth + 1, mx, right, my, bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -134,95 +229,128 @@ export default class Quadtree<T extends AABB>
|
|||
Renderer.scene.remove(...this.#debugRect);
|
||||
this.#debugRect = [];
|
||||
|
||||
this.traverse((prop) => {
|
||||
this.#debugRect.push(new THREE.Box3Helper(new THREE.Box3(new THREE.Vector3(prop.bounds.x1, prop.bounds.y1, 0), new THREE.Vector3(prop.bounds.x2, prop.bounds.y2, 0))));
|
||||
this.traverse((index, depth, left, top, right, bottom, leaf) => {
|
||||
this.#debugRect.push(new THREE.Box3Helper(new THREE.Box3(new THREE.Vector3(left, top, 0), new THREE.Vector3(right, bottom, 0))));
|
||||
});
|
||||
|
||||
Renderer.scene.add(...this.#debugRect);
|
||||
}
|
||||
|
||||
#find_leaves(index: number, depth: number, bounds: AABB, elmtBounds: AABB): NodeProp[]
|
||||
#findLeaves(index: number, depth: number, lleft: number, lright: number, ltop: number, lbottom: number, eleft: number, eright: number, etop: number, ebottom: number): IntList
|
||||
{
|
||||
const stack: NodeProp[] = [], result: NodeProp[] = [];
|
||||
stack.push({index: index, depth: depth, bounds: bounds});
|
||||
const leaves = new IntList(QuadConsts.PropCount);
|
||||
_nodeProcess.clear();
|
||||
insert(_nodeProcess, index, depth, lleft, lright, ltop, lbottom);
|
||||
|
||||
//Fetch every nodes to search for leaves that match the element bounds
|
||||
while(stack.length > 0)
|
||||
while (_nodeProcess.length > 0)
|
||||
{
|
||||
const nodeProp = stack.pop()!;
|
||||
const last = _nodeProcess.length - 1;
|
||||
|
||||
//If this is a leaf
|
||||
if(this.#nodes[nodeProp.index].count !== -1)
|
||||
result.push(nodeProp);
|
||||
const nodeLeft = _nodeProcess.get(last, QuadConsts.PropILft);
|
||||
const nodeRight = _nodeProcess.get(last, QuadConsts.PropIRgt);
|
||||
const nodeTop = _nodeProcess.get(last, QuadConsts.PropITop);
|
||||
const nodeBottom = _nodeProcess.get(last, QuadConsts.PropIBtm);
|
||||
const nodeIndex = _nodeProcess.get(last, QuadConsts.PropIIdx);
|
||||
const nodeDepth = _nodeProcess.get(last, QuadConsts.PropIDpt);
|
||||
|
||||
_nodeProcess.pop();
|
||||
|
||||
if(this.#nodes.get(nodeIndex, QuadConsts.NodeICount) !== -1)
|
||||
insert(leaves, nodeIndex, nodeDepth, nodeLeft, nodeRight, nodeTop, nodeBottom);
|
||||
else
|
||||
{
|
||||
//Otherwise, check intersection on each 4 sides of the node
|
||||
const children = this.#nodes[nodeProp.index].children.toArray();
|
||||
const mx = nodeProp.bounds.x1 + (nodeProp.bounds.x2 - nodeProp.bounds.x1) / 2, my = nodeProp.bounds.y1 + (nodeProp.bounds.y2 - nodeProp.bounds.y1) / 2;
|
||||
const fc = this.#nodes.get(nodeIndex, QuadConsts.NodeIFirst);
|
||||
|
||||
if(elmtBounds.y1 <= my)
|
||||
{
|
||||
if(elmtBounds.x1 <= mx) //Add a new
|
||||
stack.push({ index: children[0], depth: nodeProp.depth + 1, bounds: { x1: nodeProp.bounds.x1, x2: mx, y1: nodeProp.bounds.y1, y2: my }});
|
||||
if(elmtBounds.x2 > mx)
|
||||
stack.push({ index: children[1], depth: nodeProp.depth + 1, bounds: { x1: mx, x2: nodeProp.bounds.x2, y1: nodeProp.bounds.y1, y2: my }});
|
||||
}
|
||||
if(elmtBounds.y2 > my)
|
||||
{
|
||||
if(elmtBounds.x1 <= mx)
|
||||
stack.push({ index: children[2], depth: nodeProp.depth + 1, bounds: { x1: nodeProp.bounds.x1, x2: mx, y1: my, y2: nodeProp.bounds.y2 }});
|
||||
if(elmtBounds.x2 > mx)
|
||||
stack.push({ index: children[3], depth: nodeProp.depth + 1, bounds: { x1: mx, x2: nodeProp.bounds.x2, y1: my, y2: nodeProp.bounds.y2 }});
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#leaf_insert(index: number, depth: number, bounds: AABB, elmt: number): void
|
||||
{
|
||||
const node = this.#nodes[index];
|
||||
node.children.add(elmt);
|
||||
const mx = nodeLeft + (nodeRight - nodeLeft) / 2, my = nodeTop + (nodeBottom - nodeTop) / 2;
|
||||
|
||||
//Split if the max amount of element is reached and the max depth isn't
|
||||
if(node.count == this.#maxElmts && depth < this.#maxDepth)
|
||||
if(etop <= my)
|
||||
{
|
||||
const elmts = node.children.toArray(); //Get every children
|
||||
node.children.clear();
|
||||
node.count = -1;
|
||||
if(eleft <= mx) //Add a new
|
||||
insert(_nodeProcess, fc + 0, nodeDepth + 1, nodeLeft, mx, nodeTop, my);
|
||||
if(eright > mx)
|
||||
insert(_nodeProcess, fc + 1, nodeDepth + 1, mx, nodeRight, nodeTop, my);
|
||||
}
|
||||
if(ebottom > my)
|
||||
{
|
||||
if(eleft <= mx)
|
||||
insert(_nodeProcess, fc + 2, nodeDepth + 1, nodeLeft, mx, my, nodeBottom);
|
||||
if(eright > mx)
|
||||
insert(_nodeProcess, fc + 3, nodeDepth + 1, mx, nodeRight, my, nodeBottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(let i = 0; i < 4; i++) //Split in 4 nodes
|
||||
return leaves;
|
||||
}
|
||||
#insertLeaf(index: number, depth: number, left: number, top: number, right: number, bottom: number, elmt: number): void
|
||||
{
|
||||
node.children.add(this.#nodes.push({ children: new LinkedList<number>(), count: 0 }) - 1);
|
||||
}
|
||||
for(let i = 0; i < elmts.length; i++) //Insert back the children
|
||||
{
|
||||
this.#node_insert(index, depth, bounds, elmts[i]);
|
||||
}
|
||||
}
|
||||
else //Otherwise, just increase the count
|
||||
{
|
||||
node.count++;
|
||||
}
|
||||
}
|
||||
#node_insert(index: number, depth: number, bounds: AABB, elmt: number): void
|
||||
{
|
||||
const aabb = this.#content[elmt];
|
||||
const leaves = this.#find_leaves(index, depth, bounds, aabb);
|
||||
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);
|
||||
|
||||
for(let i = 0; i < leaves.length; i++)
|
||||
if(this.#nodes.get(index, QuadConsts.NodeICount) === this.#maxElmts && depth < this.#maxDepth)
|
||||
{
|
||||
this.#leaf_insert(leaves[i].index, leaves[i].depth, leaves[i].bounds, elmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
interface NodeProp
|
||||
_process.clear();
|
||||
//For each element of the leaf
|
||||
while(this.#nodes.get(index, QuadConsts.NodeIFirst) !== -1)
|
||||
{
|
||||
index: number;
|
||||
depth: number;
|
||||
bounds: AABB;
|
||||
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);
|
||||
}
|
||||
interface Node
|
||||
|
||||
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, top, right, bottom, _process.get(i, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
children: LinkedList<number>;
|
||||
count: number;
|
||||
this.#nodes.set(index, QuadConsts.NodeICount, this.#nodes.get(index, QuadConsts.NodeICount) + 1);
|
||||
}
|
||||
}
|
||||
#insertNode(index: number, depth: number, left: number, top: number, right: 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);
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ export default class Input
|
|||
static #dragCb?: (delta: Point, start: Point, end: Point, button: number) => void;
|
||||
static #dragEndCb?: (start: Point, end: Point, button: number) => void;
|
||||
static #wheelCb?: (delta: number) => void;
|
||||
static #inputCb?: () => void;
|
||||
static #inputCb?: (input: string) => void;
|
||||
|
||||
static init(canvas: HTMLCanvasElement)
|
||||
{
|
||||
|
|
@ -95,7 +95,7 @@ export default class Input
|
|||
{
|
||||
Input.#wheelCb = cb;
|
||||
}
|
||||
static onInput(cb: () => void): void
|
||||
static onInput(cb: (input: string) => void): void
|
||||
{
|
||||
Input.#inputCb = cb;
|
||||
}
|
||||
|
|
@ -173,6 +173,6 @@ export default class Input
|
|||
{
|
||||
Input.#keys[e.key] = false;
|
||||
|
||||
Input.#inputCb && Input.#inputCb();
|
||||
Input.#inputCb && Input.#inputCb(e.key);
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,10 @@ export default class Selector
|
|||
{
|
||||
return Selector.#selected;
|
||||
}
|
||||
static get selection(): Asset[]
|
||||
{
|
||||
return Selector.#assets;
|
||||
}
|
||||
static init(): void
|
||||
{
|
||||
Selector.#previewMesh = new Three.Box3Helper(new Three.Box3(), 0x2980B9);
|
||||
|
|
|
|||
Loading…
Reference in New Issue