import { mat3, mat4, ReadonlyMat3, ReadonlyVec3, vec2, vec3 } from 'gl-matrix'; export interface ITransformUpdatable { updateTransform?(): void; } export class Transform { mat: mat3 = mat3.create(); x: number = 0; y: number = 0; scaleX: number = 1; scaleY: number = 1; rad: number = 0; /** 有没有对这个Transform进行过修改,用于优化常规表现 */ private modified: boolean = false; /** 绑定的可更新元素 */ bindedObject?: ITransformUpdatable; /** * 对这个变换实例添加绑定对象,当矩阵变换时,自动调用其 update 函数 * @param obj 要绑定的对象 */ bind(obj?: ITransformUpdatable) { this.bindedObject = obj; } /** * 重设所有参数 */ reset() { mat3.identity(this.mat); this.x = 0; this.y = 0; this.scaleX = 1; this.scaleY = 1; this.rad = 0; this.modified = false; this.bindedObject?.updateTransform?.(); } /** * 修改缩放,叠加关系 */ scale(x: number, y: number = x): this { mat3.scale(this.mat, this.mat, [x, y]); this.scaleX *= x; this.scaleY *= y; this.modified = true; this.bindedObject?.updateTransform?.(); return this; } /** * 移动,叠加关系 */ translate(x: number, y: number): this { mat3.translate(this.mat, this.mat, [x, y]); this.x += x; this.y += y; this.modified = true; this.bindedObject?.updateTransform?.(); return this; } /** * 旋转,叠加关系 */ rotate(rad: number): this { mat3.rotate(this.mat, this.mat, rad); this.rad += rad; if (this.rad >= Math.PI * 2) { const n = Math.floor(this.rad / Math.PI / 2); this.rad -= n * Math.PI * 2; } this.modified = true; this.bindedObject?.updateTransform?.(); return this; } /** * 设置缩放,非叠加关系 */ setScale(x: number, y: number = x): this { mat3.scale(this.mat, this.mat, [x / this.scaleX, y / this.scaleY]); this.scaleX = x; this.scaleY = y; this.modified = true; this.bindedObject?.updateTransform?.(); return this; } /** * 设置位置,非叠加关系 */ setTranslate(x: number, y: number): this { mat3.translate(this.mat, this.mat, [x - this.x, y - this.y]); this.x = x; this.y = y; this.modified = true; this.bindedObject?.updateTransform?.(); return this; } /** * 设置旋转,非叠加关系 */ setRotate(rad: number): this { mat3.rotate(this.mat, this.mat, rad - this.rad); this.rad = rad; this.modified = true; this.bindedObject?.updateTransform?.(); return this; } /** * 设置变换矩阵,叠加模式 * @param a 水平缩放 * @param b 垂直倾斜 * @param c 水平倾斜 * @param d 垂直缩放 * @param e 水平移动 * @param f 垂直移动 */ transform( a: number, b: number, c: number, d: number, e: number, f: number ): this { mat3.multiply( this.mat, this.mat, mat3.fromValues(a, b, 0, c, d, 0, e, f, 1) ); this.calAttributes(); this.bindedObject?.updateTransform?.(); return this; } /** * 设置变换矩阵,非叠加模式 * @param a 水平缩放 * @param b 垂直倾斜 * @param c 水平倾斜 * @param d 垂直缩放 * @param e 水平移动 * @param f 垂直移动 */ setTransform( a: number, b: number, c: number, d: number, e: number, f: number ): this { mat3.set(this.mat, a, b, 0, c, d, 0, e, f, 1); this.calAttributes(); this.bindedObject?.updateTransform?.(); return this; } /** * 重新计算 translation scaling rotation */ calAttributes() { const [x, y] = getTranslation(this.mat); const [scaleX, scaleY] = getScaling(this.mat); const rad = getRotation(this.mat); this.x = x; this.y = y; this.scaleX = scaleX; this.scaleY = scaleY; this.rad = rad; if (x === 0 && y === 0 && scaleX === 1 && scaleY === 1 && rad === 0) { this.modified = false; } else { this.modified = true; } } /** * 与一个矩阵相乘,返回其计算结果(不改变原矩阵) * @param transform 变换矩阵 */ multiply(transform: Transform): Transform { if (this.modified) { const result = new Transform(); mat3.multiply(result.mat, this.mat, transform.mat); result.modified = true; return result; } else { return transform.clone(); } } /** * 复制这个变换矩阵 */ clone() { const transform = new Transform(); transform.mat = mat3.clone(this.mat); return transform; } /** * 根据变换矩阵的信息,将一个点转换为计算后的位置 * @param x 横坐标 * @param y 纵坐标 */ transformed(x: number, y: number): vec3 { if (!this.modified) return [x, y, 1]; return multiplyVec3(this.mat, [x, y, 1]); } /** * 根据变换矩阵的信息,将一个计算后的位置逆转换为原位置 * @param x 横坐标 * @param y 纵坐标 */ untransformed(x: number, y: number): vec3 { if (!this.modified) return [x, y, 1]; const invert = mat3.create(); mat3.invert(invert, this.mat); return multiplyVec3(invert, [x, y, 1]); } /** * 根据变换矩阵的信息,将一个点转换为计算后的位置 * @param transform 变换矩阵 * @param x 横坐标 * @param y 纵坐标 */ static transformed(transform: Transform, x: number, y: number) { return multiplyVec3(transform.mat, [x, y, 1]); } /** * 根据变换矩阵的信息,将一个计算后的位置逆转换为原位置 * @param transform 变换矩阵 * @param x 横坐标 * @param y 纵坐标 */ static untransformed(transform: Transform, x: number, y: number) { const invert = mat3.create(); mat3.invert(invert, transform.mat); return multiplyVec3(invert, [x, y, 1]); } /** 单位矩阵 */ static get identity() { return new Transform(); } } function multiplyVec3(mat: ReadonlyMat3, vec: ReadonlyVec3): vec3 { const v0 = vec[0]; const v1 = vec[1]; const v2 = vec[2]; return [ mat[0] * v0 + mat[3] * v1 + mat[6] * v2, mat[1] * v0 + mat[4] * v1 + mat[7] * v2, mat[2] * v0 + mat[5] * v1 + mat[8] * v2 ]; } function getTranslation(mat: ReadonlyMat3): vec2 { return [mat[6], mat[7]]; } function getScaling(mat: ReadonlyMat3): vec2 { return [Math.hypot(mat[0], mat[3]), Math.hypot(mat[1], mat[4])]; } function getRotation(mat: ReadonlyMat3): number { return Math.atan2(mat[3], mat[0]); } export class Transform3D { mat: mat4 = mat4.create(); /** 绑定的可更新元素 */ bindedObject?: ITransformUpdatable; /** * 绑定可更新对象 * @param obj 要绑定的对象 */ bind(obj?: ITransformUpdatable) { this.bindedObject = obj; } /** * 重置为单位矩阵 */ reset(): this { mat4.identity(this.mat); this.bindedObject?.updateTransform?.(); return this; } /** * 应用缩放变换 * @param x X轴缩放 * @param y Y轴缩放 * @param z Z轴缩放 */ scale(x: number, y: number, z: number): this { mat4.scale(this.mat, this.mat, [x, y, z]); this.bindedObject?.updateTransform?.(); return this; } /** * 应用平移变换 * @param x X轴平移 * @param y Y轴平移 * @param z Z轴平移 */ translate(x: number, y: number, z: number): this { mat4.translate(this.mat, this.mat, [x, y, z]); this.bindedObject?.updateTransform?.(); return this; } /** * 应用旋转变换 * @param rad 旋转角度(弧度) * @param axis 旋转轴 */ rotate(rad: number, axis: vec3): this { mat4.rotate(this.mat, this.mat, rad, axis); this.bindedObject?.updateTransform?.(); return this; } /** * 应用绕X轴旋转 * @param rad 旋转角度(弧度) */ rotateX(rad: number): this { return this.rotate(rad, [1, 0, 0]); } /** * 应用绕Y轴旋转 * @param rad 旋转角度(弧度) */ rotateY(rad: number): this { return this.rotate(rad, [0, 1, 0]); } /** * 应用绕Z轴旋转 * @param rad 旋转角度(弧度) */ rotateZ(rad: number): this { return this.rotate(rad, [0, 0, 1]); } /** * 设置视图矩阵 * @param eye 摄像机位置 * @param center 目标位置 * @param up 上方向量 */ lookAt(eye: vec3, center: vec3, up: vec3): this { mat4.lookAt(this.mat, eye, center, up); this.bindedObject?.updateTransform?.(); return this; } /** * 设置透视投影矩阵 * @param fovy 垂直视野角度(弧度) * @param aspect 宽高比 * @param near 近平面 * @param far 远平面 */ perspective(fovy: number, aspect: number, near: number, far: number): this { mat4.perspective(this.mat, fovy, aspect, near, far); this.bindedObject?.updateTransform?.(); return this; } /** * 设置正交投影矩阵 * @param left 左平面 * @param right 右平面 * @param bottom 底平面 * @param top 顶平面 * @param near 近平面 * @param far 远平面 */ ortho( left: number, right: number, bottom: number, top: number, near: number, far: number ): this { mat4.ortho(this.mat, left, right, bottom, top, near, far); this.bindedObject?.updateTransform?.(); return this; } /** * 与另一个变换矩阵相乘 * @param transform 另一个变换矩阵 */ multiply(transform: Transform3D): Transform3D { const result = new Transform3D(); mat4.multiply(result.mat, this.mat, transform.mat); return result; } /** * 克隆当前变换矩阵 */ clone(): Transform3D { const clone = new Transform3D(); mat4.copy(clone.mat, this.mat); return clone; } /** * 将点应用变换 * @param point 要变换的点 */ transformed(point: vec3): vec3 { const result = vec3.create(); vec3.transformMat4(result, point, this.mat); return result; } /** * 将点应用逆变换 * @param point 要逆变换的点 */ untransformed(point: vec3): vec3 { const inverse = mat4.create(); mat4.invert(inverse, this.mat); const result = vec3.create(); vec3.transformMat4(result, point, inverse); return result; } /** * 静态方法:单位矩阵 */ static get identity(): Transform3D { return new Transform3D(); } }