New PRNG and physics fixes
This commit is contained in:
parent
99ed6d14f7
commit
958ffa9ae9
|
|
@ -212,3 +212,48 @@ export function clamp(x: number, min: number, max: number): number
|
||||||
{
|
{
|
||||||
return x > max ? max : x < min ? min : x;
|
return x > max ? max : x < min ? min : x;
|
||||||
}
|
}
|
||||||
|
export function lerp(x: number, a: number, b: number): number
|
||||||
|
{
|
||||||
|
return (1-x)*a+x*b;
|
||||||
|
}
|
||||||
|
export class Random
|
||||||
|
{
|
||||||
|
#seed: number;
|
||||||
|
#hasher: () => number;
|
||||||
|
|
||||||
|
constructor(seed: number)
|
||||||
|
{
|
||||||
|
this.#seed = seed;
|
||||||
|
this.#hasher = mb32(hash(seed));
|
||||||
|
}
|
||||||
|
next(): number
|
||||||
|
{
|
||||||
|
return this.#hasher();
|
||||||
|
}
|
||||||
|
nextInt(min?: number, max?: number): number
|
||||||
|
{
|
||||||
|
return Math.floor(this.nextFloat(min, max));
|
||||||
|
}
|
||||||
|
nextFloat(min?: number, max?: number): number
|
||||||
|
{
|
||||||
|
if(min === undefined && max === undefined)
|
||||||
|
{
|
||||||
|
min = 0;
|
||||||
|
max = 1;
|
||||||
|
}
|
||||||
|
else if(max === undefined)
|
||||||
|
{
|
||||||
|
max = min;
|
||||||
|
min = 0;
|
||||||
|
}
|
||||||
|
else if(min === undefined)
|
||||||
|
{
|
||||||
|
min = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lerp(this.#hasher() / 2**32, min!, max!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hash = (n: number) => Math.imul(n,2654435761) >>> 0;
|
||||||
|
const mb32 = (a: number) => (t?: number) => (a = a + 1831565813|0, t = Math.imul(a^a>>>15,1|a), t = t + Math.imul(t^t>>>7,61|t)^t, (t^t>>>14)>>>0);
|
||||||
28
src/main.ts
28
src/main.ts
|
|
@ -4,24 +4,26 @@ import Asset from './assets/asset.class';
|
||||||
import Quadtree from './physics/quadtree.class';
|
import Quadtree from './physics/quadtree.class';
|
||||||
import { FRUSTUMSIZE } from './consts';
|
import { FRUSTUMSIZE } from './consts';
|
||||||
import Input from './renderer/input.class';
|
import Input from './renderer/input.class';
|
||||||
import { clamp } from './common';
|
import { Random, clamp } from './common';
|
||||||
import Selector from './renderer/selector.class';
|
import Selector from './renderer/selector.class';
|
||||||
|
|
||||||
Renderer.init();
|
Renderer.init();
|
||||||
Input.init(Renderer.canvas);
|
Input.init(Renderer.canvas);
|
||||||
Selector.init();
|
Selector.init();
|
||||||
|
|
||||||
const quad = new Quadtree({x1: -FRUSTUMSIZE * Renderer.aspect, x2: FRUSTUMSIZE * Renderer.aspect, y1: -FRUSTUMSIZE, y2: FRUSTUMSIZE});
|
const r = new Random(0);
|
||||||
|
|
||||||
|
const quad = new Quadtree({x1: -FRUSTUMSIZE * Renderer.aspect / 2, x2: FRUSTUMSIZE * Renderer.aspect / 2, y1: -FRUSTUMSIZE / 2, y2: FRUSTUMSIZE / 2});
|
||||||
|
|
||||||
const assets: Asset[] = [];
|
const assets: Asset[] = [];
|
||||||
for(let i = 0; i < 10; i++)
|
for(let i = 0; i < 10000; i++)
|
||||||
{
|
{
|
||||||
assets[i] = new Asset(new THREE.Matrix4(), 1);
|
assets[i] = new Asset(new THREE.Matrix4(), 1);
|
||||||
|
|
||||||
assets[i]
|
assets[i]
|
||||||
.move((Math.random() - 0.5) * FRUSTUMSIZE * Renderer.aspect, (Math.random() - 0.5) * FRUSTUMSIZE)
|
.move(r.nextFloat(-0.5 * FRUSTUMSIZE * Renderer.aspect, 0.5 * FRUSTUMSIZE * Renderer.aspect), r.nextFloat(-0.5 * FRUSTUMSIZE, 0.5 * FRUSTUMSIZE))
|
||||||
.rotate(Math.random() * Math.PI * 2)
|
.rotate(r.nextFloat(Math.PI * 2))
|
||||||
.scale(Math.random() * 0.5 + 0.05, Math.random() * 0.5 + 0.05)
|
.scale(r.nextFloat(0.01, 0.15), r.nextFloat(0.01, 0.15))
|
||||||
|
|
||||||
Asset.instance.setMatrixAt(i, assets[i].mat);
|
Asset.instance.setMatrixAt(i, assets[i].mat);
|
||||||
quad.insert(assets[i]);
|
quad.insert(assets[i]);
|
||||||
|
|
@ -36,13 +38,16 @@ Renderer.scene.add(Asset.instance);
|
||||||
|
|
||||||
Renderer.startRendering();
|
Renderer.startRendering();
|
||||||
|
|
||||||
Input.onDragStart((start, button) => { if(button & 1) Selector.hide(); });
|
Input.onDragStart((_, button) => { if(button & 1) Selector.hide(); });
|
||||||
Input.onDragEnd((start, end, button) => {
|
Input.onDragEnd((start, end, button) => {
|
||||||
if(button & 1)
|
if(button & 1)
|
||||||
{
|
{
|
||||||
const selection = quad.query({x1: start.x, x2: end.x, y1: start.y, y2: end.y}) as Asset[];
|
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[];
|
||||||
|
console.log("Fetching %s out of %s elements in %sms", selection.length, assets.length, performance.now() - s);
|
||||||
|
|
||||||
if(Input.keys['Shift']) Selector.add(selection); else Selector.select(selection);
|
if(Input.keys['Shift']) Selector.toggle(selection);
|
||||||
|
else Selector.select(selection);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Input.onDrag((delta, start, end, button) => { if(button & 1) Selector.preview(start, end); else Renderer.move(-delta.x, -delta.y); });
|
Input.onDrag((delta, start, end, button) => { if(button & 1) Selector.preview(start, end); else Renderer.move(-delta.x, -delta.y); });
|
||||||
|
|
@ -51,8 +56,9 @@ Input.onClick((point, button) => {
|
||||||
{
|
{
|
||||||
const selection = quad.fetch(point.x, point.y) as Asset[];
|
const selection = quad.fetch(point.x, point.y) as Asset[];
|
||||||
|
|
||||||
if(Input.keys['Shift']) Selector.add(selection); else Selector.select(selection);
|
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.onWheel(delta => Renderer.zoom = clamp(Renderer.zoom * 1 + (delta * -0.001), 1, 5));
|
||||||
Input.onMove(p => { if(!Input.dragging && !Selector.selected) Selector.ghostSelect(quad.fetch(p.x, p.y)[0] as Asset); });
|
Input.onMove(p => { if(!Input.dragging) Selector.ghost(quad.fetch(p.x, p.y)[0] as Asset); });
|
||||||
|
|
@ -1,6 +1,13 @@
|
||||||
import { LinkedList } from "../common";
|
import { LinkedList } from "../common";
|
||||||
import { AABB, Point, intersects } from "./common";
|
import Renderer from "../renderer/renderer.class";
|
||||||
|
import { AABB, intersects } from "./common";
|
||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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. (Not memory friendly in JS ?)
|
||||||
|
*/
|
||||||
export default class Quadtree<T extends AABB>
|
export default class Quadtree<T extends AABB>
|
||||||
{
|
{
|
||||||
#bounds: AABB;
|
#bounds: AABB;
|
||||||
|
|
@ -10,6 +17,7 @@ export default class Quadtree<T extends AABB>
|
||||||
#content: T[];
|
#content: T[];
|
||||||
|
|
||||||
#dirty: boolean = true;
|
#dirty: boolean = true;
|
||||||
|
#debugRect: THREE.Box3Helper[] = [];
|
||||||
|
|
||||||
constructor(bounds: AABB, maxDepth?: number, maxElmts?: number)
|
constructor(bounds: AABB, maxDepth?: number, maxElmts?: number)
|
||||||
{
|
{
|
||||||
|
|
@ -24,21 +32,17 @@ export default class Quadtree<T extends AABB>
|
||||||
}
|
}
|
||||||
fetch(x: number, y: number): T[]
|
fetch(x: number, y: number): T[]
|
||||||
{
|
{
|
||||||
const results = this.query({x1: x, x2: x, y1: y, y2: y});
|
return this.query({x1: x, x2: x, y1: y, y2: y});
|
||||||
|
|
||||||
results.length > 0 && console.log(results);
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
}
|
||||||
query(aabb: AABB): T[]
|
query(aabb: AABB): T[]
|
||||||
{
|
{
|
||||||
const result: number[] = [];
|
const result: number[] = [];
|
||||||
|
|
||||||
const leaves = this.#find_leaves(0, 0, this.#bounds, aabb);
|
const leaves = this.#find_leaves(0, 0, this.#bounds, aabb); //Search every mathed leaves
|
||||||
for(let i = 0; i < leaves.length; i++)
|
for(let i = 0; i < leaves.length; i++)
|
||||||
{
|
{
|
||||||
const node = this.#nodes[leaves[i].index];
|
const node = this.#nodes[leaves[i].index];
|
||||||
node.children.forEach(e => {
|
node.children.forEach(e => { //Test every leaves elements
|
||||||
if(!result.includes(e) && intersects(this.#content[e], aabb))
|
if(!result.includes(e) && intersects(this.#content[e], aabb))
|
||||||
result.push(e);
|
result.push(e);
|
||||||
});
|
});
|
||||||
|
|
@ -125,29 +129,40 @@ export default class Quadtree<T extends AABB>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
render(): void
|
||||||
|
{
|
||||||
|
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))));
|
||||||
|
});
|
||||||
|
|
||||||
|
Renderer.scene.add(...this.#debugRect);
|
||||||
|
}
|
||||||
|
|
||||||
#find_leaves(index: number, depth: number, bounds: AABB, elmtBounds: AABB): NodeProp[]
|
#find_leaves(index: number, depth: number, bounds: AABB, elmtBounds: AABB): NodeProp[]
|
||||||
{
|
{
|
||||||
const stack: NodeProp[] = [], result: NodeProp[] = [];
|
const stack: NodeProp[] = [], result: NodeProp[] = [];
|
||||||
stack.push({index: index, depth: depth, bounds: bounds});
|
stack.push({index: index, depth: depth, bounds: bounds});
|
||||||
|
|
||||||
//Fetch every nodes intersecting the element bounds
|
//Fetch every nodes to search for leaves that match the element bounds
|
||||||
while(stack.length > 0)
|
while(stack.length > 0)
|
||||||
{
|
{
|
||||||
const nodeProp = stack.pop()!;
|
const nodeProp = stack.pop()!;
|
||||||
|
|
||||||
//If the node contains elements
|
//If this is a leaf
|
||||||
if(this.#nodes[nodeProp.index].count !== -1)
|
if(this.#nodes[nodeProp.index].count !== -1)
|
||||||
result.push(nodeProp);
|
result.push(nodeProp);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//Check intersection on each 4 sides of the node
|
//Otherwise, check intersection on each 4 sides of the node
|
||||||
const children = this.#nodes[nodeProp.index].children.toArray();
|
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 mx = nodeProp.bounds.x1 + (nodeProp.bounds.x2 - nodeProp.bounds.x1) / 2, my = nodeProp.bounds.y1 + (nodeProp.bounds.y2 - nodeProp.bounds.y1) / 2;
|
||||||
|
|
||||||
if(elmtBounds.y1 <= my)
|
if(elmtBounds.y1 <= my)
|
||||||
{
|
{
|
||||||
if(elmtBounds.x1 <= mx)
|
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 }});
|
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)
|
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 }});
|
stack.push({ index: children[1], depth: nodeProp.depth + 1, bounds: { x1: mx, x2: nodeProp.bounds.x2, y1: nodeProp.bounds.y1, y2: my }});
|
||||||
|
|
@ -171,15 +186,15 @@ export default class Quadtree<T extends AABB>
|
||||||
//Split if the max amount of element is reached and the max depth isn't
|
//Split if the max amount of element is reached and the max depth isn't
|
||||||
if(node.count == this.#maxElmts && depth < this.#maxDepth)
|
if(node.count == this.#maxElmts && depth < this.#maxDepth)
|
||||||
{
|
{
|
||||||
const elmts = node.children.toArray();
|
const elmts = node.children.toArray(); //Get every children
|
||||||
node.children.clear();
|
node.children.clear();
|
||||||
node.count = -1;
|
node.count = -1;
|
||||||
|
|
||||||
for(let i = 0; i < 4; i++)
|
for(let i = 0; i < 4; i++) //Split in 4 nodes
|
||||||
{
|
{
|
||||||
node.children.add(this.#nodes.push({ children: new LinkedList<number>(), count: 0 }) - 1);
|
node.children.add(this.#nodes.push({ children: new LinkedList<number>(), count: 0 }) - 1);
|
||||||
}
|
}
|
||||||
for(let i = 0; i < elmts.length; i++)
|
for(let i = 0; i < elmts.length; i++) //Insert back the children
|
||||||
{
|
{
|
||||||
this.#node_insert(index, depth, bounds, elmts[i]);
|
this.#node_insert(index, depth, bounds, elmts[i]);
|
||||||
}
|
}
|
||||||
|
|
@ -209,5 +224,5 @@ interface NodeProp
|
||||||
interface Node
|
interface Node
|
||||||
{
|
{
|
||||||
children: LinkedList<number>;
|
children: LinkedList<number>;
|
||||||
count: number; //The count is used to get the amount of T elements in the current node. If the Node only contains other nodes, the count is = -1.
|
count: number;
|
||||||
}
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ export default class Selector
|
||||||
static #selected: boolean = false;
|
static #selected: boolean = false;
|
||||||
|
|
||||||
static #previewMesh: Three.Box3Helper;
|
static #previewMesh: Three.Box3Helper;
|
||||||
|
static #ghostMesh: Three.Box3Helper;
|
||||||
static #selectionMesh: Three.Box3Helper;
|
static #selectionMesh: Three.Box3Helper;
|
||||||
|
|
||||||
static get selected(): boolean
|
static get selected(): boolean
|
||||||
|
|
@ -18,9 +19,11 @@ export default class Selector
|
||||||
static init(): void
|
static init(): void
|
||||||
{
|
{
|
||||||
Selector.#previewMesh = new Three.Box3Helper(new Three.Box3(), 0x2980B9);
|
Selector.#previewMesh = new Three.Box3Helper(new Three.Box3(), 0x2980B9);
|
||||||
|
Selector.#ghostMesh = new Three.Box3Helper(new Three.Box3(), 0xffffff);
|
||||||
Selector.#selectionMesh = new Three.Box3Helper(new Three.Box3(), 0xffffff);
|
Selector.#selectionMesh = new Three.Box3Helper(new Three.Box3(), 0xffffff);
|
||||||
|
|
||||||
Renderer.scene.add(Selector.#previewMesh);
|
Renderer.scene.add(Selector.#previewMesh);
|
||||||
|
Renderer.scene.add(Selector.#ghostMesh);
|
||||||
Renderer.scene.add(Selector.#selectionMesh);
|
Renderer.scene.add(Selector.#selectionMesh);
|
||||||
|
|
||||||
Selector.hide();
|
Selector.hide();
|
||||||
|
|
@ -34,20 +37,18 @@ export default class Selector
|
||||||
|
|
||||||
Selector.#previewMesh.visible = true;
|
Selector.#previewMesh.visible = true;
|
||||||
}
|
}
|
||||||
static ghostSelect(asset: Asset): void
|
static ghost(asset: Asset): void
|
||||||
{
|
{
|
||||||
Selector.#assets = [asset];
|
|
||||||
Selector.hide();
|
|
||||||
|
|
||||||
if(!asset)
|
if(!asset)
|
||||||
|
{
|
||||||
|
Selector.#ghostMesh.visible = false;
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Selector.#selected = false;
|
Selector.#ghostMesh.box.setFromArray([asset.x1, asset.y1, 0, asset.x2, asset.y2, 0]);
|
||||||
|
Selector.#ghostMesh.updateMatrix();
|
||||||
|
|
||||||
Selector.#selectionMesh.box.setFromArray([asset.x1, asset.y1, 0, asset.x2, asset.y2, 0]);
|
Selector.#ghostMesh.visible = true;
|
||||||
Selector.#previewMesh.updateMatrix();
|
|
||||||
|
|
||||||
Selector.#selectionMesh.visible = true;
|
|
||||||
}
|
}
|
||||||
static select(assets: Asset[]): void
|
static select(assets: Asset[]): void
|
||||||
{
|
{
|
||||||
|
|
@ -67,13 +68,22 @@ export default class Selector
|
||||||
assets.map(e => e.y2).reduce((p, v) => Math.max(p, v), -Infinity),
|
assets.map(e => e.y2).reduce((p, v) => Math.max(p, v), -Infinity),
|
||||||
0
|
0
|
||||||
]);
|
]);
|
||||||
Selector.#previewMesh.updateMatrix();
|
Selector.#selectionMesh.updateMatrix();
|
||||||
|
|
||||||
Selector.#selectionMesh.visible = true;
|
Selector.#selectionMesh.visible = true;
|
||||||
}
|
}
|
||||||
static add(assets: Asset[]): void
|
static toggle(assets: Asset[]): void
|
||||||
{
|
{
|
||||||
Selector.select([...Selector.#assets, ...assets].filter((e, i, a) => a.indexOf(e) === i));
|
for(let i = 0; i < assets.length; i++)
|
||||||
|
{
|
||||||
|
const index = Selector.#assets.indexOf(assets[i]);
|
||||||
|
|
||||||
|
if(index === -1)
|
||||||
|
Selector.#assets.push(assets[i]);
|
||||||
|
else
|
||||||
|
Selector.#assets.splice(index, 1);
|
||||||
|
}
|
||||||
|
Selector.select(Selector.#assets);
|
||||||
}
|
}
|
||||||
static clear(): void
|
static clear(): void
|
||||||
{
|
{
|
||||||
|
|
@ -84,6 +94,7 @@ export default class Selector
|
||||||
static hide(): void
|
static hide(): void
|
||||||
{
|
{
|
||||||
Selector.#previewMesh.visible = false;
|
Selector.#previewMesh.visible = false;
|
||||||
|
Selector.#ghostMesh.visible = false;
|
||||||
Selector.#selectionMesh.visible = false;
|
Selector.#selectionMesh.visible = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue