| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 |
- import { Rect } from "./2d";
- import { Animator, Interpolator } from "./Animator";
- import { Gesture } from "./Gesture";
- import { m4 } from "./m4";
- export interface Disposable {
- dispose(): void;
- }
- export interface Layer {
- preDraw(): void;
- draw(): void;
- tap(cx: number, cy: number, sx: number, sy: number): void;
- scale(scale: number): void;
- dispose(): void;
- }
- export class LayerAB implements Layer {
- preDraw(): void {}
- draw(): void {}
- tap(cx: number, cy: number, sx: number, sy: number): void {}
- scale(scale: number): void {}
- dispose(): void {}
- }
- export class Padding {
- constructor(
- readonly left: number,
- readonly top: number,
- readonly right: number,
- readonly bottom: number,
- ) {}
- equals(other: Padding) {
- return (
- this.left == other.left &&
- this.top == other.top &&
- this.right == other.right &&
- this.bottom == other.bottom
- );
- }
- }
- export class Scene implements Disposable {
- layers: Array<Layer> = [];
- testLayers: Array<Layer> = [];
- animators: Array<Animator> = [];
- userMat: m4.Matrix4 = m4.identity();
- bestFitMat: m4.Matrix4 = m4.identity();
- projectionMat: m4.Matrix4 = m4.identity();
- resultMat: m4.Matrix4 = m4.identity();
- width: number = 0;
- height: number = 0;
- contentWidth: number = 0;
- contentHeight: number = 0;
- padding = new Padding(0, 0, 0, 0);
- pendingDraw = false;
- // clearColorValue: [number, number, number, number] = [1, 1, 1, 1]; // 默认白色
- clearColorValue: [number, number, number, number] = [0, 0, 0, 0]; // 默认透明
- /** 游戏完成后调用,将 canvas 底色改为透明,让 body 渐变色透出 */
- setClearTransparent() {
- this.clearColorValue = [0, 0, 0, 0];
- this.invalidate();
- }
- constructor(
- readonly gl: WebGL2RenderingContext,
- public readonly ratio: number,
- public animationFrameProvider: AnimationFrameProvider = window,
- public interact: boolean = true,
- ) {
- this.updateViewport();
- let self = this;
- if (window && interact) {
- new Gesture(gl.canvas as HTMLElement, {
- // drag: self.drag.bind(self),
- // zoom: self.scaleAt.bind(self),
- tap: self.tap.bind(self),
- });
- }
- m4.identity();
- }
- updateViewport() {
- console.log("viewport update.");
- const gl = this.gl;
- const canvas = gl.canvas as HTMLCanvasElement;
- //canvas.width = canvas.clientWidth * this.ratio
- //canvas.height = canvas.clientHeight * this.ratio
- this.invalidate();
- if (canvas.width != this.width || canvas.height != this.height) {
- this.width = canvas.width;
- this.height = canvas.height;
- m4.projection(this.width, this.height, this.projectionMat);
- this.updateBestFit();
- this.updateResultMat();
- }
- }
- private isBestMatSet: boolean = false;
- private updateBestFit() {
- if (this.contentWidth == 0 || this.width == 0) return;
- const box = new Rect(
- this.padding.left,
- this.padding.top,
- this.width - this.padding.right - this.padding.left,
- this.height - this.padding.top - this.padding.bottom,
- );
- const scale = Math.min(
- box.width / this.contentWidth,
- box.height / this.contentHeight,
- );
- const tx = box.center.x - (this.contentWidth * scale) / 2;
- const ty = box.center.y - (this.contentHeight * scale) / 2;
- m4.identity(this.bestFitMat);
- this.bestFitMat[0] = scale;
- this.bestFitMat[5] = scale;
- this.bestFitMat[12] = tx;
- this.bestFitMat[13] = ty;
- if (!this.isBestMatSet) {
- m4.copy(this.bestFitMat, this.userMat);
- this.invalidate();
- this.isBestMatSet = true;
- }
- }
- updateResultMat() {
- if (this.contentWidth == 0 || this.width == 0) return;
- const box = new Rect(
- this.padding.left,
- this.padding.top,
- this.width - this.padding.right - this.padding.left,
- this.height - this.padding.top - this.padding.bottom,
- );
- // const scale = Math.min(box.width / this.contentWidth, box.height / this.contentHeight)
- // const tx = box.center.x - this.contentWidth * scale / 2
- // const ty = box.center.y - this.contentHeight * scale / 2
- // m4.identity(this.resultMat)
- // this.resultMat[0] = scale
- // this.resultMat[5] = scale
- // this.resultMat[12] = tx
- // this.resultMat[13] = ty
- let rate = this.width > this.height ? 0.7 : 0.8;
- let scaleX = (this.width * rate) / this.contentWidth;
- let scaleY = (this.height * rate) / this.contentHeight;
- let scale = Math.min(scaleX, scaleY);
- const tx = box.center.x - (this.contentWidth * scale) / 2;
- const ty = box.center.y - (this.contentHeight * scale) / 2;
- m4.identity(this.resultMat);
- this.resultMat[0] = scale;
- this.resultMat[5] = scale;
- this.resultMat[12] = tx;
- this.resultMat[13] = ty;
- }
- setContentPadding(padding: Padding) {
- if (!this.padding.equals(padding)) {
- this.padding = padding;
- this.updateBestFit();
- this.updateResultMat();
- }
- }
- setContentSize(width: number, height: number) {
- if (width != this.contentWidth || height != this.contentWidth) {
- this.contentWidth = width;
- this.contentHeight = height;
- this.updateBestFit();
- this.updateResultMat();
- }
- }
- addLayer(layer: Layer) {
- this.layers.push(layer);
- this.invalidate();
- }
- invalidate() {
- if (this.pendingDraw) return;
- this.pendingDraw = true;
- //requestAnimationFrame(() => {
- this.animationFrameProvider.requestAnimationFrame(() => {
- this.draw();
- });
- }
- draw() {
- this.animators.forEach((a) => a.update());
- this.layers.forEach((l) => l.preDraw());
- const gl = this.gl;
- gl.viewport(0, 0, this.width, this.height);
- gl.clearColor(...this.clearColorValue);
- gl.clear(gl.COLOR_BUFFER_BIT);
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
- gl.enable(gl.BLEND);
- gl.disable(gl.DEPTH_TEST);
- this.layers.forEach((l) => l.draw());
- this.pendingDraw = false;
- this.animators = this.animators.filter((a) => !a.removable());
- if (this.animators.length > 0) {
- this.invalidate();
- }
- }
- get drawMatrix(): m4.Matrix4 {
- return m4.multiply(this.projectionMat, this.userMat);
- }
- drag(dx: number, dy: number) {
- let t = m4.translation(dx * this.ratio, dy * this.ratio, 0);
- this.userMat = m4.multiply(t, this.userMat);
- this.invalidate();
- }
- scaleAt(scale: number, focusX: number, focusY: number) {
- focusX *= this.ratio;
- focusY *= this.ratio;
- let t = m4.scaleAt(scale, scale, focusX, focusY);
- this.userMat = m4.multiply(t, this.userMat);
- this.layers.forEach((l) => l.scale(this.userMat[0]));
- this.invalidate();
- }
- tap(x: number, y: number) {
- let sx = x * this.ratio;
- let sy = y * this.ratio;
- let [cx, cy] = m4.transformPoint(
- m4.inverse(this.userMat),
- new Float32Array([sx, sy, 0]),
- );
- this.layers.forEach((l) => l.tap(cx, cy, sx, sy));
- }
- addAnimator(animator: Animator) {
- this.animators.push(animator);
- this.invalidate();
- }
- addTestLayer(layer: Layer) {
- this.testLayers.push(layer);
- if (this.testLayers.length <= 1) this.layers.push(layer);
- this.invalidate();
- }
- toggleTestLayer() {
- if (this.testLayers.length == 0) return;
- let testLayerIndex = this.testLayers.findIndex((l) => {
- return this.layers.indexOf(l) >= 0;
- });
- let next = (testLayerIndex + 1) % this.testLayers.length;
- let nextLayer = this.testLayers[next];
- if (testLayerIndex >= 0) {
- this.layers = this.layers.filter(
- (l) => l != this.testLayers[testLayerIndex],
- );
- }
- console.log(`toggleTestLayer, layer=${nextLayer}`);
- this.layers.push(nextLayer);
- this.invalidate();
- }
- setScale(scale: number) {
- m4.scaling(scale, scale, 1, this.userMat);
- this.invalidate();
- }
- updateUserMat(mat: m4.Matrix4) {
- m4.copy(mat, this.userMat);
- this.invalidate();
- }
- matrixAnimationTo(
- endMat: m4.Matrix4,
- duration: number,
- interpolator?: Interpolator,
- onEnd?: Function,
- ) {
- const startMat = m4.copy(this.userMat);
- const mat = new Float32Array(16);
- const animator = new Animator(
- duration,
- () => {
- m4.lerp(startMat, endMat, mat, animator.value());
- this.updateUserMat(mat);
- },
- () => {
- onEnd?.();
- },
- interpolator,
- );
- this.addAnimator(animator);
- }
- resetToBestFit() {
- this.matrixAnimationTo(this.bestFitMat, 600);
- }
- dispose() {
- while (this.layers.length > 0) {
- let layer = this.layers.pop();
- layer?.dispose();
- }
- }
- }
|