New Input and Selection manager
This commit is contained in:
parent
ca7b1d1956
commit
99ed6d14f7
92
src/main.ts
92
src/main.ts
|
|
@ -3,106 +3,56 @@ import Renderer from './renderer/renderer.class';
|
||||||
import Asset from './assets/asset.class';
|
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 { clamp } from './common';
|
import { clamp } from './common';
|
||||||
|
import Selector from './renderer/selector.class';
|
||||||
|
|
||||||
performance.mark("start");
|
|
||||||
Renderer.init();
|
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 quad = new Quadtree({x1: -FRUSTUMSIZE * Renderer.aspect, x2: FRUSTUMSIZE * Renderer.aspect, y1: -FRUSTUMSIZE, y2: FRUSTUMSIZE});
|
||||||
|
|
||||||
performance.mark("init");
|
|
||||||
const assets: Asset[] = [];
|
const assets: Asset[] = [];
|
||||||
for(let i = 0; i < 10000; i++)
|
for(let i = 0; i < 10; 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((Math.random() - 0.5) * FRUSTUMSIZE * Renderer.aspect, (Math.random() - 0.5) * FRUSTUMSIZE)
|
||||||
.rotate(Math.random() * Math.PI * 2)
|
.rotate(Math.random() * Math.PI * 2)
|
||||||
.scale(Math.random() * 0.05 + 0.005, Math.random() * 0.05 + 0.005)
|
.scale(Math.random() * 0.5 + 0.05, Math.random() * 0.5 + 0.05)
|
||||||
|
|
||||||
Asset.instance.setMatrixAt(i, assets[i].mat);
|
Asset.instance.setMatrixAt(i, assets[i].mat);
|
||||||
quad.insert(assets[i]);
|
quad.insert(assets[i]);
|
||||||
}
|
}
|
||||||
performance.mark("ready");
|
|
||||||
|
|
||||||
const highlightBox = new THREE.Box3();
|
|
||||||
const highlightHelper = new THREE.Box3Helper(highlightBox, 0xffffff);
|
|
||||||
highlightHelper.visible = false;
|
|
||||||
|
|
||||||
let overallTimer = 0, overallSamples = 0;
|
|
||||||
|
|
||||||
Asset.instance.count = assets.length;
|
Asset.instance.count = assets.length;
|
||||||
|
|
||||||
Asset.instance.computeBoundingBox();
|
Asset.instance.computeBoundingBox();
|
||||||
Asset.instance.computeBoundingSphere();
|
Asset.instance.computeBoundingSphere();
|
||||||
|
|
||||||
const sphere = new THREE.SphereGeometry(0.05);
|
|
||||||
const sphereMesh = new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({ wireframe: true, }));
|
|
||||||
|
|
||||||
Renderer.scene.add(Asset.instance);
|
Renderer.scene.add(Asset.instance);
|
||||||
Renderer.scene.add(highlightHelper);
|
|
||||||
Renderer.scene.add(sphereMesh);
|
|
||||||
|
|
||||||
Renderer.startRendering();
|
Renderer.startRendering();
|
||||||
|
|
||||||
window.addEventListener('mousedown', drag);
|
Input.onDragStart((start, button) => { if(button & 1) Selector.hide(); });
|
||||||
window.addEventListener('mouseup', select);
|
Input.onDragEnd((start, end, button) => {
|
||||||
window.addEventListener('mousemove', hover);
|
if(button & 1)
|
||||||
|
|
||||||
window.addEventListener('wheel', zoom);
|
|
||||||
|
|
||||||
console.log("Start time: %sms", performance.measure("Start time", "start", "init").duration);
|
|
||||||
console.log("Init time: %sms", performance.measure("Init time", "init", "ready").duration);
|
|
||||||
|
|
||||||
let cursor = { x: 0, y: 0 };
|
|
||||||
let dragCursor = {x: 0, y: 0 }, dragging = false;
|
|
||||||
|
|
||||||
function drag(e: MouseEvent): void
|
|
||||||
{
|
|
||||||
dragCursor = Renderer.screenSpaceToCameraSpace(e.clientX, e.clientY, false);
|
|
||||||
dragging = true;
|
|
||||||
}
|
|
||||||
function select(e: MouseEvent): void
|
|
||||||
{
|
|
||||||
dragging = false;
|
|
||||||
}
|
|
||||||
function hover(e: MouseEvent): void
|
|
||||||
{
|
|
||||||
if(dragging === true)
|
|
||||||
{
|
{
|
||||||
const cursor = Renderer.screenSpaceToCameraSpace(e.clientX, e.clientY, false);
|
const selection = quad.query({x1: start.x, x2: end.x, y1: start.y, y2: end.y}) as Asset[];
|
||||||
Renderer.move(dragCursor.x - cursor.x, dragCursor.y - cursor.y);
|
|
||||||
|
|
||||||
dragCursor = cursor;
|
if(Input.keys['Shift']) Selector.add(selection); else Selector.select(selection);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
else
|
});
|
||||||
|
Input.onDrag((delta, start, end, button) => { if(button & 1) Selector.preview(start, end); else Renderer.move(-delta.x, -delta.y); });
|
||||||
|
Input.onClick((point, button) => {
|
||||||
|
if(button & 1)
|
||||||
{
|
{
|
||||||
const cursor = Renderer.screenSpaceToCameraSpace(e.clientX, e.clientY, true);
|
const selection = quad.fetch(point.x, point.y) as Asset[];
|
||||||
|
|
||||||
sphereMesh.position.set(cursor.x, cursor.y, 0);
|
if(Input.keys['Shift']) Selector.add(selection); else Selector.select(selection);
|
||||||
|
|
||||||
overallTimer -= performance.now();
|
|
||||||
const assets = quad.fetch(cursor.x, cursor.y) as Asset[];
|
|
||||||
overallTimer += performance.now();
|
|
||||||
overallSamples++;
|
|
||||||
|
|
||||||
if (assets.length > 0) {
|
|
||||||
highlightHelper.box.setFromArray([assets[0].x1, assets[0].y1, 0, assets[0].x2, assets[0].y2, 0]);
|
|
||||||
highlightHelper.updateMatrixWorld();
|
|
||||||
|
|
||||||
highlightHelper.visible = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
highlightHelper.visible = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
function zoom(e: WheelEvent): void
|
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); });
|
||||||
Renderer.zoom = clamp(Renderer.zoom * 1 + (e.deltaY * -0.001), 1, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
setInterval(() => {
|
|
||||||
console.log("Average query time: %s µs", overallTimer / overallSamples * 1000);
|
|
||||||
}, 1000);
|
|
||||||
|
|
@ -24,7 +24,11 @@ export default class Quadtree<T extends AABB>
|
||||||
}
|
}
|
||||||
fetch(x: number, y: number): T[]
|
fetch(x: number, y: number): T[]
|
||||||
{
|
{
|
||||||
return this.query({x1: x, x2: x, y1: y, y2: y});
|
const results = 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[]
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
import { Point } from "../physics/common";
|
||||||
|
import Renderer from "./renderer.class";
|
||||||
|
|
||||||
|
const dblClickTiming = 1000;
|
||||||
|
|
||||||
|
export default class Input
|
||||||
|
{
|
||||||
|
static #cursor: Point = {x: 0, y: 0};
|
||||||
|
static #delta: Point = {x: 0, y: 0};
|
||||||
|
static #holdButtons: number;
|
||||||
|
static #previousClickTime: number;
|
||||||
|
|
||||||
|
//Drag util fields
|
||||||
|
static #dragging: boolean = false;
|
||||||
|
static #dragStarted: boolean = false;
|
||||||
|
static #dragInitPos: Point = {x: 0, y: 0};
|
||||||
|
static #dragPrevPos: Point = {x: 0, y: 0};
|
||||||
|
|
||||||
|
static #scroll: number = 0;
|
||||||
|
static #keys: Record<string, boolean> = {};
|
||||||
|
|
||||||
|
static #canvas: HTMLCanvasElement;
|
||||||
|
|
||||||
|
static #clickCb?: (point: Point, button: number) => void;
|
||||||
|
static #dblClickCb?: (point: Point, button: number) => void;
|
||||||
|
static #moveCb?: (point: Point) => void;
|
||||||
|
static #dragStartCb?: (start: Point, button: number) => void;
|
||||||
|
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 init(canvas: HTMLCanvasElement)
|
||||||
|
{
|
||||||
|
Input.#canvas = canvas;
|
||||||
|
|
||||||
|
canvas.addEventListener('mousedown', Input.#mousedown.bind(Input), false);
|
||||||
|
canvas.addEventListener('mouseup', Input.#mouseup.bind(Input), false);
|
||||||
|
canvas.addEventListener('mousemove', Input.#mousemove.bind(Input), false);
|
||||||
|
|
||||||
|
canvas.addEventListener('wheel', Input.#wheel.bind(Input), false);
|
||||||
|
|
||||||
|
window.addEventListener('keydown', Input.#keydown.bind(Input), false);
|
||||||
|
window.addEventListener('keyup', Input.#keyup.bind(Input), false);
|
||||||
|
|
||||||
|
canvas.addEventListener('contextmenu', (e) => e.preventDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
static get dragging()
|
||||||
|
{
|
||||||
|
return Input.#dragging && Input.#dragStarted;
|
||||||
|
}
|
||||||
|
static get cursor()
|
||||||
|
{
|
||||||
|
return Input.#cursor;
|
||||||
|
}
|
||||||
|
static get delta()
|
||||||
|
{
|
||||||
|
return Input.#delta;
|
||||||
|
}
|
||||||
|
static get scroll()
|
||||||
|
{
|
||||||
|
return Input.#scroll;
|
||||||
|
}
|
||||||
|
static get keys()
|
||||||
|
{
|
||||||
|
return Input.#keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
static onClick(cb: (point: Point, button: number) => void): void
|
||||||
|
{
|
||||||
|
Input.#clickCb = cb;
|
||||||
|
}
|
||||||
|
static onMove(cb: (point: Point) => void): void
|
||||||
|
{
|
||||||
|
Input.#moveCb = cb;
|
||||||
|
}
|
||||||
|
static onDblClick(cb: (point: Point, button: number) => void): void
|
||||||
|
{
|
||||||
|
Input.#dblClickCb = cb;
|
||||||
|
}
|
||||||
|
static onDragStart(cb: (start: Point, button: number) => void): void
|
||||||
|
{
|
||||||
|
Input.#dragStartCb = cb;
|
||||||
|
}
|
||||||
|
static onDrag(cb: (delta: Point, start: Point, end: Point, button: number) => void): void
|
||||||
|
{
|
||||||
|
Input.#dragCb = cb;
|
||||||
|
}
|
||||||
|
static onDragEnd(cb: (start: Point, end: Point, button: number) => void): void
|
||||||
|
{
|
||||||
|
Input.#dragEndCb = cb;
|
||||||
|
}
|
||||||
|
static onWheel(cb: (delta: number) => void): void
|
||||||
|
{
|
||||||
|
Input.#wheelCb = cb;
|
||||||
|
}
|
||||||
|
static onInput(cb: () => void): void
|
||||||
|
{
|
||||||
|
Input.#inputCb = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
static #mousedown(e: MouseEvent): void
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
Input.#holdButtons = e.buttons;
|
||||||
|
|
||||||
|
Input.#dragging = true;
|
||||||
|
Input.#dragStarted = false;
|
||||||
|
Input.#dragInitPos = Renderer.screenSpaceToCameraSpace(e.clientX, e.clientY);
|
||||||
|
Input.#dragPrevPos = Renderer.screenSpaceToCameraSpace(e.clientX, e.clientY, true);
|
||||||
|
}
|
||||||
|
static #mousemove(e: MouseEvent): void
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if(Input.#dragging && !Input.#dragStarted)
|
||||||
|
{
|
||||||
|
Input.#dragStartCb && Input.#dragStartCb(Input.#dragInitPos, e.buttons);
|
||||||
|
Input.#dragStarted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cursor = Renderer.screenSpaceToCameraSpace(e.clientX, e.clientY);
|
||||||
|
const cursorOmitted = Renderer.screenSpaceToCameraSpace(e.clientX, e.clientY, true);
|
||||||
|
|
||||||
|
Input.#cursor = cursor;
|
||||||
|
|
||||||
|
if(Input.dragging)
|
||||||
|
{
|
||||||
|
Input.#delta.x = cursorOmitted.x - this.#dragPrevPos.x;
|
||||||
|
Input.#delta.y = cursorOmitted.y - this.#dragPrevPos.y;
|
||||||
|
|
||||||
|
Input.#dragPrevPos = cursorOmitted;
|
||||||
|
|
||||||
|
Input.#dragCb && Input.#dragCb(Input.#delta, Input.#dragInitPos, Input.#cursor, e.buttons);
|
||||||
|
}
|
||||||
|
|
||||||
|
Input.#moveCb && Input.#moveCb(Input.#cursor);
|
||||||
|
}
|
||||||
|
static #mouseup(e: MouseEvent): void
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if(Input.dragging)
|
||||||
|
Input.#dragEndCb && Input.#dragEndCb(Input.#dragInitPos, Input.#cursor, Input.#holdButtons);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Input.#clickCb && Input.#clickCb(Input.#cursor, Input.#holdButtons);
|
||||||
|
const timing = performance.now();
|
||||||
|
|
||||||
|
if(timing - Input.#previousClickTime <= dblClickTiming)
|
||||||
|
Input.#dblClickCb && Input.#dblClickCb(Input.#cursor, Input.#holdButtons);
|
||||||
|
|
||||||
|
Input.#previousClickTime = timing;
|
||||||
|
}
|
||||||
|
|
||||||
|
Input.#holdButtons = e.buttons;
|
||||||
|
Input.#dragging = false;
|
||||||
|
}
|
||||||
|
static #wheel(e: WheelEvent): void
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
Input.#scroll = e.deltaY;
|
||||||
|
Input.#wheelCb && Input.#wheelCb(Input.scroll);
|
||||||
|
}
|
||||||
|
static #keydown(e: KeyboardEvent): void
|
||||||
|
{
|
||||||
|
Input.#keys[e.key] = true;
|
||||||
|
}
|
||||||
|
static #keyup(e: KeyboardEvent): void
|
||||||
|
{
|
||||||
|
Input.#keys[e.key] = false;
|
||||||
|
|
||||||
|
Input.#inputCb && Input.#inputCb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -43,6 +43,10 @@ export default class Renderer
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
static get canvas(): HTMLCanvasElement
|
||||||
|
{
|
||||||
|
return this.renderer.domElement;
|
||||||
|
}
|
||||||
static get zoom(): number
|
static get zoom(): number
|
||||||
{
|
{
|
||||||
return this.#zoom;
|
return this.#zoom;
|
||||||
|
|
@ -60,8 +64,8 @@ export default class Renderer
|
||||||
this.camera.position.x += x;
|
this.camera.position.x += x;
|
||||||
this.camera.position.y += y;
|
this.camera.position.y += y;
|
||||||
}
|
}
|
||||||
static screenSpaceToCameraSpace(x: number, y: number, offset: boolean): Point {
|
static screenSpaceToCameraSpace(x: number, y: number, omit: boolean = false): Point {
|
||||||
return { x: ((x / window.innerWidth - 0.5) * FRUSTUMSIZE * this.aspect - (offset ? this.#pos.x : 0)) / this.#zoom, y: (- (y / window.innerHeight - 0.5) * FRUSTUMSIZE - (offset ? this.#pos.x : 0)) / this.zoom };
|
return { x: ((x / window.innerWidth - 0.5) * FRUSTUMSIZE * this.aspect) / this.#zoom + (omit ? 0 : this.#pos.x), y: (- (y / window.innerHeight - 0.5) * FRUSTUMSIZE) / this.zoom + (omit ? 0 : this.#pos.y) };
|
||||||
}
|
}
|
||||||
static #resize(): void
|
static #resize(): void
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
import * as Three from "three";
|
||||||
|
import Asset from "../assets/asset.class";
|
||||||
|
import { Point } from "../physics/common";
|
||||||
|
import Renderer from "./renderer.class";
|
||||||
|
|
||||||
|
export default class Selector
|
||||||
|
{
|
||||||
|
static #assets: Asset[];
|
||||||
|
static #selected: boolean = false;
|
||||||
|
|
||||||
|
static #previewMesh: Three.Box3Helper;
|
||||||
|
static #selectionMesh: Three.Box3Helper;
|
||||||
|
|
||||||
|
static get selected(): boolean
|
||||||
|
{
|
||||||
|
return Selector.#selected;
|
||||||
|
}
|
||||||
|
static init(): void
|
||||||
|
{
|
||||||
|
Selector.#previewMesh = new Three.Box3Helper(new Three.Box3(), 0x2980B9);
|
||||||
|
Selector.#selectionMesh = new Three.Box3Helper(new Three.Box3(), 0xffffff);
|
||||||
|
|
||||||
|
Renderer.scene.add(Selector.#previewMesh);
|
||||||
|
Renderer.scene.add(Selector.#selectionMesh);
|
||||||
|
|
||||||
|
Selector.hide();
|
||||||
|
}
|
||||||
|
static preview(start: Point, end: Point): void
|
||||||
|
{
|
||||||
|
Selector.hide();
|
||||||
|
|
||||||
|
Selector.#previewMesh.box.setFromArray([start.x, start.y, 0, end.x, end.y, 0]);
|
||||||
|
Selector.#previewMesh.updateMatrix();
|
||||||
|
|
||||||
|
Selector.#previewMesh.visible = true;
|
||||||
|
}
|
||||||
|
static ghostSelect(asset: Asset): void
|
||||||
|
{
|
||||||
|
Selector.#assets = [asset];
|
||||||
|
Selector.hide();
|
||||||
|
|
||||||
|
if(!asset)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Selector.#selected = false;
|
||||||
|
|
||||||
|
Selector.#selectionMesh.box.setFromArray([asset.x1, asset.y1, 0, asset.x2, asset.y2, 0]);
|
||||||
|
Selector.#previewMesh.updateMatrix();
|
||||||
|
|
||||||
|
Selector.#selectionMesh.visible = true;
|
||||||
|
}
|
||||||
|
static select(assets: Asset[]): void
|
||||||
|
{
|
||||||
|
Selector.#assets = assets;
|
||||||
|
Selector.hide();
|
||||||
|
|
||||||
|
if(assets.length <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Selector.#selected = true;
|
||||||
|
|
||||||
|
Selector.#selectionMesh.box.setFromArray([
|
||||||
|
assets.map(e => e.x1).reduce((p, v) => Math.min(p, v), Infinity),
|
||||||
|
assets.map(e => e.y1).reduce((p, v) => Math.min(p, v), Infinity),
|
||||||
|
0,
|
||||||
|
assets.map(e => e.x2).reduce((p, v) => Math.max(p, v), -Infinity),
|
||||||
|
assets.map(e => e.y2).reduce((p, v) => Math.max(p, v), -Infinity),
|
||||||
|
0
|
||||||
|
]);
|
||||||
|
Selector.#previewMesh.updateMatrix();
|
||||||
|
|
||||||
|
Selector.#selectionMesh.visible = true;
|
||||||
|
}
|
||||||
|
static add(assets: Asset[]): void
|
||||||
|
{
|
||||||
|
Selector.select([...Selector.#assets, ...assets].filter((e, i, a) => a.indexOf(e) === i));
|
||||||
|
}
|
||||||
|
static clear(): void
|
||||||
|
{
|
||||||
|
Selector.#assets = [];
|
||||||
|
|
||||||
|
Selector.hide();
|
||||||
|
}
|
||||||
|
static hide(): void
|
||||||
|
{
|
||||||
|
Selector.#previewMesh.visible = false;
|
||||||
|
Selector.#selectionMesh.visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue