web 3d图形渲染器
 
 
 

400 lines
11 KiB

import { vec2 } from "../math/TSM";
import { CanvasKeyBoardEvent, CanvasMouseEvent, EInputEventType } from "./Event";
import { Timer, TimerCallback } from "./Timer";
export class Application implements EventListenerObject {
/**
* @var 定时器数组
*/
public timers: Timer[] = [];
/**
* @var 定时器id
*/
private _timerId: number = -1;
/**
* @var 帧率
*/
private _fps: number = 0;
/**
* @var
*/
public isFlipYCoord: boolean = false;
/**
* @var
*/
public canvas: HTMLCanvasElement;
/**
* @var 是否每次移动都会触发mouse事件
*/
public isSupportMouseMove: boolean;
/**
* @var 鼠标是否被按下
*/
protected _isMouseDown: boolean;
/**
* @var 是否按下鼠标右键
*/
protected _isRightMouseDown: boolean = false;
/**
* @var 是否进入循环
*/
protected _start: boolean = false;
/**
* @var 由Window对象的requestAnimationFrame返回的大于0的id号,
* 我们可以使用cancelAnimationFrame ( this ._requestId )来取消动画循环.
*/
protected _requestId: number = -1;
/**
* @var 计算当前更新与上一次更新之间的时间差
*/
protected _lastTime !: number;
protected _startTime !: number;
/**
* @function 渲染每帧画面的回调函数
*/
public frameCallback: ((app: Application) => void) | null;
public constructor(canvas: HTMLCanvasElement) {
this.canvas = canvas;
//设置鼠标的监听事件
this.canvas.addEventListener("mousedown", this, false);
this.canvas.addEventListener("mouseup", this, false);
this.canvas.addEventListener("mousemove", this, false);
//设置键盘监听事件
window.addEventListener("keydown", this, false);
window.addEventListener("keyup", this, false);
window.addEventListener("keypress", this, false);
//初始化时候mousedown 和 mousemvoe不开启
this._isMouseDown = false;
this.isSupportMouseMove = false;
this.frameCallback = null;
//禁止右键菜单
document.oncontextmenu = function () { return false };
}
/**
* 判断当前Application是否一直在调用requestAnimationFrame
* @returns boolean
*/
public isRunning(): boolean {
return this._start;
}
/**
* 获取当前帧率
*/
public get fps() {
return this._fps;
}
/**
* 启动渲染
*/
public start(): void {
if (this._start === false) {
this._start = true;
this._startTime = -1;
this._lastTime = -1;
this._requestId = requestAnimationFrame((msec: number): void => {
this.step(msec);
});
}
}
/**
* 循环函数
* @param timeStamp
*/
protected step(timeStamp: number): void {
if (this._startTime === -1) {
this._startTime = timeStamp;
}
if (this._lastTime === -1) {
this._lastTime = timeStamp;
}
//计算当前时间和第一次调用step时候的时间差
let elapsedMsec = timeStamp - this._startTime;
//计算当前时间和上一次调用step时候的时间差
let intervalSec = timeStamp - this._lastTime;
this._lastTime = timeStamp;
//计算帧率
if (intervalSec !== 0) {
this._fps = 1000 / intervalSec;
}
intervalSec /= 1000.0; //转成秒
//处理timer
this._handleTimers(intervalSec);
//更新
this.update(elapsedMsec, intervalSec);
//渲染
this.render();
if (this.frameCallback !== null) {
this.frameCallback(this);
}
requestAnimationFrame((elapsedMsec: number): void => {
this.step(elapsedMsec);
})
}
public stop(): void {
if (this._start) {
cancelAnimationFrame(this._requestId);
this._startTime = -1;
this._lastTime = -1;
this._start = false;
}
}
/**
* 虚函数
* @param elapsedMsec 毫秒
* @param intervalSec 秒
*/
public update(elapsedMsec: number, intervalSec: number): void { }
/**
* 虚函数
*/
public render(): void { }
public async run(): Promise<void> {
this.start();
}
handleEvent(evt: Event): void {
switch (evt.type) {
case "mousedown":
this._isMouseDown = true;
this.onMouseDown(this._toCanvasMouseEvent(evt, EInputEventType.MOUSEDOWN));
break;
case "mouseup":
this._isMouseDown = false;
this.onMouseDown(this._toCanvasMouseEvent(evt, EInputEventType.MOUSEUP));
break;
case "mousemove":
if (this.isSupportMouseMove) {
this.onMouseMove(this._toCanvasMouseEvent(evt, EInputEventType.MOUSEMOVE));
}
if (this._isMouseDown) {
this.onMouseDrag(this._toCanvasMouseEvent(evt, EInputEventType.MOUSEDRAG));
}
break;
case "keypress":
this.onKeyPress(this._toCanvasKeyBoardEvent(evt, EInputEventType.KEYPRESS));
break;
case "keydown":
this.onKeyDown(this._toCanvasKeyBoardEvent(evt, EInputEventType.MOUSEDOWN));
break;
case "keyup":
this.onKeyUp(this._toCanvasKeyBoardEvent(evt, EInputEventType.KEYUP));
break;
}
}
protected onMouseDown(evt: CanvasMouseEvent): void {
return;
}
protected onMouseUp(evt: CanvasMouseEvent): void {
return;
}
protected onMouseMove(evt: CanvasMouseEvent): void {
return;
}
protected onMouseDrag(evt: CanvasMouseEvent): void {
return;
}
protected onKeyDown(evt: CanvasKeyBoardEvent): void {
return;
}
protected onKeyUp(evt: CanvasKeyBoardEvent): void {
return;
}
protected onKeyPress(evt: CanvasKeyBoardEvent): void {
return;
}
protected getMouseCanvas(): HTMLCanvasElement {
return this.canvas;
}
/**
* 将鼠标的指针位置变换为当前canvas的位置
* @param evt
*/
private viewportToCanvasCoordinate(evt: MouseEvent): vec2 {
let rect: ClientRect = this.getMouseCanvas().getBoundingClientRect();
//target是触发事件的元素,这里是canvas
if (evt.target) {
let x: number = evt.clientX - rect.left;
let y: number = evt.clientY - rect.top;
if (this.isFlipYCoord) {
y = this.getMouseCanvas().height - y;
}
let pos: vec2 = new vec2([x, y]);
return pos;
}
throw new Error("evt.target is null");
}
/**
* 把DOM 事件转成自己的事件
* @param evt
* @param type
*/
private _toCanvasMouseEvent(evt: Event, type: EInputEventType): CanvasMouseEvent {
let event: MouseEvent = evt as MouseEvent;//向下转型,将Event转换为MouseEvent
if (type === EInputEventType.MOUSEDOWN && event.button === 2) {
this._isRightMouseDown = true;
} else if (type === EInputEventType.MOUSEUP && event.button === 2) {
this._isRightMouseDown = false;
}
let buttonNum: number = event.button;
if (this._isRightMouseDown && type === EInputEventType.MOUSEDRAG) {
buttonNum = 2;
}
//将鼠标位置变换到canvas坐标系位置
let mousePosition: vec2 = this.viewportToCanvasCoordinate(event);
let ret: CanvasMouseEvent = new CanvasMouseEvent(type, mousePosition, buttonNum, event.altKey, event.ctrlKey, event.shiftKey);
return ret;
}
/**
* 把DOM 事件转成自己的事件
* @param evt
* @param type
*/
private _toCanvasKeyBoardEvent(evt: Event, type: EInputEventType): CanvasKeyBoardEvent {
let event: KeyboardEvent = evt as KeyboardEvent;
let ret: CanvasKeyBoardEvent = new CanvasKeyBoardEvent(type, event.key, event.keyCode, event.repeat, event.altKey, event.ctrlKey, event.shiftKey);
return ret;
}
/**
* 添加一个定时器
* @param callback
* @param timeout
* @param onlyOnce
* @param data
* @returns
*/
public addTimer(callback: TimerCallback, timeout: number = 1.0, onlyOnce: boolean = false, data: any = undefined): number {
let timer: Timer;
for (let i = 0; i < this.timers.length; i++) {
timer = this.timers[i];
if (timer.enabled === false) {
timer.callback = callback;
timer.callbackData = data;
timer.timeout = timeout;
timer.enabled = true;
timer.onlyOnce = onlyOnce;
return timer.id;
}
}
timer = new Timer(callback);
timer.callback = callback;
timer.callbackData = data;
timer.timeout = timeout;
timer.enabled = true;
timer.onlyOnce = onlyOnce;
this.timers.push(timer);
timer.id = ++this._timerId;
return timer.id;
}
/**
* 清除定时器
* @param id
* @returns
*/
public removeTimer(id: number): boolean {
let ret: boolean = false;
for (let i = 0; i < this.timers.length; i++) {
if (this.timers[i].id === id) {
let timer: Timer = this.timers[i];
timer.enabled = false;
ret = true;
break;
}
}
return ret;
}
/**
*
* @param intervalSec 秒
*/
private _handleTimers(intervalSec: number): void {
for (let i = 0; i < this.timers.length; i++) {
let timer: Timer = this.timers[i];
if (timer.enabled === false) {
continue;
}
//countdown 初始化为timeout, 每次减少渲染时间
timer.countdown -= intervalSec;
if (timer.countdown <= 0.0) {
timer.callback(timer.id, timer.callbackData);
if (timer.onlyOnce) {
this.removeTimer(timer.id);
} else {
timer.callbackData = timer.timeout;
}
}
}
}
}