New PRNG and physics fixes
This commit is contained in:
parent
99ed6d14f7
commit
958ffa9ae9
|
|
@ -211,4 +211,49 @@ interface LinkedElmt<T>
|
|||
export function clamp(x: number, min: number, max: number): number
|
||||
{
|
||||
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 { FRUSTUMSIZE } from './consts';
|
||||
import Input from './renderer/input.class';
|
||||
import { clamp } from './common';
|
||||
import { Random, clamp } from './common';
|
||||
import Selector from './renderer/selector.class';
|
||||
|
||||
Renderer.init();
|
||||
Input.init(Renderer.canvas);
|
||||
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[] = [];
|
||||
for(let i = 0; i < 10; i++)
|
||||
for(let i = 0; i < 10000; i++)
|
||||
{
|
||||
assets[i] = new Asset(new THREE.Matrix4(), 1);
|
||||
|
||||
assets[i]
|
||||
.move((Math.random() - 0.5) * FRUSTUMSIZE * Renderer.aspect, (Math.random() - 0.5) * FRUSTUMSIZE)
|
||||
.rotate(Math.random() * Math.PI * 2)
|
||||
.scale(Math.random() * 0.5 + 0.05, Math.random() * 0.5 + 0.05)
|
||||
.move(r.nextFloat(-0.5 * FRUSTUMSIZE * Renderer.aspect, 0.5 * FRUSTUMSIZE * Renderer.aspect), r.nextFloat(-0.5 * FRUSTUMSIZE, 0.5 * FRUSTUMSIZE))
|
||||
.rotate(r.nextFloat(Math.PI * 2))
|
||||
.scale(r.nextFloat(0.01, 0.15), r.nextFloat(0.01, 0.15))
|
||||
|
||||
Asset.instance.setMatrixAt(i, assets[i].mat);
|
||||
quad.insert(assets[i]);
|
||||
|
|
@ -36,13 +38,16 @@ Renderer.scene.add(Asset.instance);
|
|||
|
||||
Renderer.startRendering();
|
||||
|
||||
Input.onDragStart((start, button) => { if(button & 1) Selector.hide(); });
|
||||
Input.onDragStart((_, button) => { if(button & 1) Selector.hide(); });
|
||||
Input.onDragEnd((start, end, button) => {
|
||||
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); });
|
||||
|
|
@ -51,8 +56,9 @@ Input.onClick((point, button) => {
|
|||
{
|
||||
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.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 { 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>
|
||||
{
|
||||
#bounds: AABB;
|
||||
|
|
@ -10,6 +17,7 @@ export default class Quadtree<T extends AABB>
|
|||
#content: T[];
|
||||
|
||||
#dirty: boolean = true;
|
||||
#debugRect: THREE.Box3Helper[] = [];
|
||||
|
||||
constructor(bounds: AABB, maxDepth?: number, maxElmts?: number)
|
||||
{
|
||||
|
|
@ -24,21 +32,17 @@ export default class Quadtree<T extends AABB>
|
|||
}
|
||||
fetch(x: number, y: number): T[]
|
||||
{
|
||||
const results = this.query({x1: x, x2: x, y1: y, y2: y});
|
||||
|
||||
results.length > 0 && console.log(results);
|
||||
|
||||
return results;
|
||||
return this.query({x1: x, x2: x, y1: y, y2: y});
|
||||
}
|
||||
query(aabb: AABB): T[]
|
||||
{
|
||||
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++)
|
||||
{
|
||||
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))
|
||||
result.push(e);
|
||||
});
|
||||
|
|
@ -125,30 +129,41 @@ 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[]
|
||||
{
|
||||
const stack: NodeProp[] = [], result: NodeProp[] = [];
|
||||
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)
|
||||
{
|
||||
const nodeProp = stack.pop()!;
|
||||
|
||||
//If the node contains elements
|
||||
//If this is a leaf
|
||||
if(this.#nodes[nodeProp.index].count !== -1)
|
||||
result.push(nodeProp);
|
||||
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 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.x1 <= mx)
|
||||
stack.push({ index: children[0], depth: nodeProp.depth + 1, bounds: { x1: nodeProp.bounds.x1, x2: mx, y1: nodeProp.bounds.y1, y2: 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 }});
|
||||
}
|
||||
|
|
@ -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
|
||||
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.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);
|
||||
}
|
||||
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]);
|
||||
}
|
||||
|
|
@ -209,5 +224,5 @@ interface NodeProp
|
|||
interface Node
|
||||
{
|
||||
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 #previewMesh: Three.Box3Helper;
|
||||
static #ghostMesh: Three.Box3Helper;
|
||||
static #selectionMesh: Three.Box3Helper;
|
||||
|
||||
static get selected(): boolean
|
||||
|
|
@ -18,9 +19,11 @@ export default class Selector
|
|||
static init(): void
|
||||
{
|
||||
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);
|
||||
|
||||
Renderer.scene.add(Selector.#previewMesh);
|
||||
Renderer.scene.add(Selector.#ghostMesh);
|
||||
Renderer.scene.add(Selector.#selectionMesh);
|
||||
|
||||
Selector.hide();
|
||||
|
|
@ -34,20 +37,18 @@ export default class Selector
|
|||
|
||||
Selector.#previewMesh.visible = true;
|
||||
}
|
||||
static ghostSelect(asset: Asset): void
|
||||
static ghost(asset: Asset): void
|
||||
{
|
||||
Selector.#assets = [asset];
|
||||
Selector.hide();
|
||||
|
||||
if(!asset)
|
||||
{
|
||||
Selector.#ghostMesh.visible = false;
|
||||
return;
|
||||
|
||||
Selector.#selected = false;
|
||||
}
|
||||
|
||||
Selector.#selectionMesh.box.setFromArray([asset.x1, asset.y1, 0, asset.x2, asset.y2, 0]);
|
||||
Selector.#previewMesh.updateMatrix();
|
||||
Selector.#ghostMesh.box.setFromArray([asset.x1, asset.y1, 0, asset.x2, asset.y2, 0]);
|
||||
Selector.#ghostMesh.updateMatrix();
|
||||
|
||||
Selector.#selectionMesh.visible = true;
|
||||
Selector.#ghostMesh.visible = true;
|
||||
}
|
||||
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),
|
||||
0
|
||||
]);
|
||||
Selector.#previewMesh.updateMatrix();
|
||||
Selector.#selectionMesh.updateMatrix();
|
||||
|
||||
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
|
||||
{
|
||||
|
|
@ -84,6 +94,7 @@ export default class Selector
|
|||
static hide(): void
|
||||
{
|
||||
Selector.#previewMesh.visible = false;
|
||||
Selector.#ghostMesh.visible = false;
|
||||
Selector.#selectionMesh.visible = false;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue