New Input and Selection manager

This commit is contained in:
2024-06-11 14:14:22 +02:00
parent ca7b1d1956
commit 99ed6d14f7
6 changed files with 299 additions and 74 deletions

178
src/renderer/input.class.ts Normal file
View File

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

View File

@@ -43,6 +43,10 @@ export default class Renderer
return false;
}
}
static get canvas(): HTMLCanvasElement
{
return this.renderer.domElement;
}
static get zoom(): number
{
return this.#zoom;
@@ -60,8 +64,8 @@ export default class Renderer
this.camera.position.x += x;
this.camera.position.y += y;
}
static screenSpaceToCameraSpace(x: number, y: number, offset: boolean): 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 };
static screenSpaceToCameraSpace(x: number, y: number, omit: boolean = false): Point {
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
{

View File

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