HumanBreak/packages/render-core/src/transform.ts

461 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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