diff --git a/src/common.ts b/src/common.ts index 5086488..90e23c9 100644 --- a/src/common.ts +++ b/src/common.ts @@ -1,7 +1,149 @@ -const DEFAULT_BUCKET_SIZE = 64; +const DEFAULT_SIZE = 128; +export class FreeList +{ + #data: T[] = []; + #free: LinkedList = new LinkedList(); -export class FastStack + #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 { #arr: T[]; @@ -12,7 +154,7 @@ export class FastStack 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 this.#arr.length = this.#bucketSize * this.#bucketCount; } } -export class FastQueue +export class Queue { #arr: T[]; @@ -68,7 +210,7 @@ export class FastQueue 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 export class LinkedList { #head: LinkedElmt | null; - #tail: LinkedElmt | 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 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 } clear() { - if(this.#head) this.#head.next = null; - if(this.#tail) this.#tail.prev = null; - this.#head = null; - this.#tail = null; } } interface LinkedElmt @@ -206,7 +323,6 @@ interface LinkedElmt elmt: T; next: LinkedElmt | null; - prev: LinkedElmt | null; } export function clamp(x: number, min: number, max: number): number { diff --git a/src/main.ts b/src/main.ts index d2a6f5e..96506ab 100644 --- a/src/main.ts +++ b/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); }); \ No newline at end of file +Input.onMove(p => { if(!Input.dragging) Selector.ghost(assets[quad.fetch(p.x, p.y)[0]]); }); \ No newline at end of file diff --git a/src/physics/common.ts b/src/physics/common.ts index 9e9ff9d..1319ccf 100644 --- a/src/physics/common.ts +++ b/src/physics/common.ts @@ -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")) { @@ -25,4 +25,8 @@ 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; } \ No newline at end of file diff --git a/src/physics/quadtree.class.ts b/src/physics/quadtree.class.ts index b4848be..b7546c1 100644 --- a/src/physics/quadtree.class.ts +++ b/src/physics/quadtree.class.ts @@ -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 +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,107 +43,184 @@ export default class Quadtree this.#maxDepth = maxDepth ?? this.#maxDepth; this.#maxElmts = maxElmts ?? this.#maxElmts; - this.#nodes = []; - this.#content = []; - - this.#nodes.push({ children: new LinkedList(), 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 = {}; + + 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); + } + + 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.#dirty = true; + + 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 { - //Only cleanup if it's dirty. + //Only cleanup if it's dirty. //Allows the system to call the function at each loop iteration. 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); + + _nodeProcess.pop(); - //If the node contains elements - if (this.#nodes[nodeProp.index].count === -1) + 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 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}); - - //Fetch every nodes to search for leaves that match the element bounds - while(stack.length > 0) + const leaves = new IntList(QuadConsts.PropCount); + _nodeProcess.clear(); + insert(_nodeProcess, index, depth, lleft, lright, ltop, lbottom); + + 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); + + const mx = nodeLeft + (nodeRight - nodeLeft) / 2, my = nodeTop + (nodeBottom - nodeTop) / 2; - if(elmtBounds.y1 <= my) + if(etop <= 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(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(elmtBounds.y2 > my) + if(ebottom > 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 }}); + 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); } } } - return result; + + return leaves; } - #leaf_insert(index: number, depth: number, bounds: AABB, elmt: number): void + #insertLeaf(index: number, depth: number, left: number, top: number, right: number, bottom: number, elmt: number): void { - const node = this.#nodes[index]; - node.children.add(elmt); + 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); - //Split if the max amount of element is reached and the max depth isn't - if(node.count == this.#maxElmts && depth < this.#maxDepth) + if(this.#nodes.get(index, QuadConsts.NodeICount) === this.#maxElmts && depth < this.#maxDepth) { - const elmts = node.children.toArray(); //Get every children - node.children.clear(); - node.count = -1; + _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); - for(let i = 0; i < 4; i++) //Split in 4 nodes - { - node.children.add(this.#nodes.push({ children: new LinkedList(), count: 0 }) - 1); - } - for(let i = 0; i < elmts.length; i++) //Insert back the children - { - this.#node_insert(index, depth, bounds, elmts[i]); + //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, top, right, bottom, _process.get(i, 0)); } - else //Otherwise, just increase the count + else { - node.count++; + this.#nodes.set(index, QuadConsts.NodeICount, this.#nodes.get(index, QuadConsts.NodeICount) + 1); } } - #node_insert(index: number, depth: number, bounds: AABB, elmt: number): void + #insertNode(index: number, depth: number, left: number, top: number, right: number, bottom: number, elmt: number): void { - const aabb = this.#content[elmt]; - const leaves = this.#find_leaves(index, depth, bounds, aabb); + 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.#leaf_insert(leaves[i].index, leaves[i].depth, leaves[i].bounds, elmt); - } + 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); } } -interface NodeProp + +function insert(list: IntList, index: number, depth: number, left: number, right: number, top: number, bottom: number): void { - index: number; - depth: number; - bounds: AABB; -} -interface Node -{ - children: LinkedList; - count: number; + 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); } \ No newline at end of file diff --git a/src/renderer/input.class.ts b/src/renderer/input.class.ts index bf9ab1e..8e64fab 100644 --- a/src/renderer/input.class.ts +++ b/src/renderer/input.class.ts @@ -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); } } \ No newline at end of file diff --git a/src/renderer/selector.class.ts b/src/renderer/selector.class.ts index 6699b8c..3b85bf6 100644 --- a/src/renderer/selector.class.ts +++ b/src/renderer/selector.class.ts @@ -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); @@ -25,7 +29,7 @@ export default class Selector Renderer.scene.add(Selector.#previewMesh); Renderer.scene.add(Selector.#ghostMesh); Renderer.scene.add(Selector.#selectionMesh); - + Selector.hide(); } static preview(start: Point, end: Point): void