New PRNG and physics fixes

This commit is contained in:
Peaceultime 2024-06-11 17:50:32 +02:00
parent 99ed6d14f7
commit 958ffa9ae9
4 changed files with 118 additions and 41 deletions

View File

@ -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);

View File

@ -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); });

View File

@ -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;
} }

View File

@ -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;
} }
} }