Files
engine/docs/en/core/math.md
2024-07-17 14:03:06 +08:00

16 KiB
Raw Blame History

order, title, type, label
order title type label
8 Mathematics Library Core Core

In a rendering scene, we often perform operations such as translation, rotation, scaling on objects (these operations are collectively referred to as transform), in order to achieve the interactive effects we desire. The calculations for these transformations are typically implemented using vectors, quaternions, matrices, etc. Therefore, we provide a mathematics library to handle operations related to vectors, quaternions, matrices, and more. Additionally, the mathematics library offers a variety of classes to help us describe points, lines, planes, geometric shapes in space, as well as determine their intersections and spatial relationships in three-dimensional space.

Type Description
BoundingBox Axis-Aligned Bounding Box (AABB)
BoundingFrustum View Frustum
BoundingSphere Bounding Sphere
CollisionUtil Provides many static methods to determine intersections and spatial relationships between objects in space
Color Color class, described using RGBA
MathUtil Utility class, provides common calculations such as comparisons, angle-radian conversions, etc.
Matrix Default 4x4 matrix, offers basic matrix operations and transformation-related operations
Matrix3x3 3x3 matrix, provides basic matrix operations and transformation-related operations
Plane Plane class, used to describe planes in three-dimensional space
Quaternion Quaternion, contains x, y, z, w components, responsible for rotation-related operations
Ray Ray class, used to describe rays in three-dimensional space
Vector2 Two-dimensional vector, contains x, y components
Vector3 Three-dimensional vector, contains x, y, z components
Vector4 Four-dimensional vector, contains x, y, z, w components

Vectors

The most basic definition of a vector is a direction. More formally, a vector has a direction (Direction) and magnitude (Magnitude, also known as strength or length). You can think of a vector as instructions on a treasure map: "Take 10 steps to the left, 3 steps north, then 5 steps to the right"; "Left" is the direction, and "10 steps" is the length of the vector. So, this treasure map has a total of 3 vectors. Vectors can exist in any dimension, but we typically use 2 to 4 dimensions. If a vector has 2 dimensions, it represents a direction in a plane (imagine a 2D image), and when it has 3 dimensions, it can express a direction in a 3D world.

In the Galacean engine, vectors are used to represent object coordinates (position), rotation, scaling, and color.

import { Vector3 } from '@galacean/engine-math';

// 创建默认三维向量,即 x,y,z 分量均为0
const v1 = new Vector3(); 

// 创建三维向量,并用给定值初始化 x,y,z 分量
const v2 = new Vector3(1, 2, 3); 

// 设置指定值
v1.set(1, 2, 2); 

// 获取各个分量
const x = v1.x;
const y = v1.y;
const z = v1.z;

// 向量相加,静态方式
const out1 = new Vector3();
Vector3.add(v1, v2, out1);

// 向量相加,实例方式
const out2 = v1.add(v2);

// 向量的标量长度
const len: number = v1.length();

// 向量归一化
v1.normalize();

// 克隆一个向量
const c1 = v1.clone();

// 将向量的值克隆到另外一个向量
const c2 = new Vector3();
v1.cloneTo(c2);

Quaternions

Quaternions are simple hypercomplex numbers, and in graphics engines, quaternions are mainly used for three-dimensional rotations (Relationship between quaternions and three-dimensional rotations), which can represent rotations not only with quaternions but also with Euler angles, axis-angle, matrices, etc. The reason for choosing quaternions is mainly due to the following advantages:

  • Solves the problem of gimbal lock
  • Requires storing only 4 floating-point numbers, making it lighter compared to matrices
  • More efficient for operations like inversion, concatenation, etc., compared to matrices

In the Galacean engine, quaternions are also used for rotation-related operations and provide APIs for converting Euler angles, matrices, etc., to quaternions.

import { Vector3, Quaternion, MathUtil } from '@galacean/engine-math';

// 创建默认四元数,即 x,y,z 分量均为0w 分量为1
const q1 = new Quaternion(); 

// 创建四元数,并用给定值初始化 x,y,z,w 分量
const q2 = new Quaternion(1, 2, 3, 4); 

// 设置指定值
q1.set(1, 2, 3, 4); 

// 判断两个四元数的值是否相等
const isEqual: boolean = Quaternion.equals(q1, q2);

const xRad = Math.PI * 0.2;
const yRad = Math.PI * 0.5;
const zRad = Math.PI * 0.3;

// 根据 yaw、pitch、roll 生成四元数
const out1 = new Quaternion();
Quaternion.rotationYawPitchRoll(yRad, xRad, zRad, out1);

// 根据 x,y,z 轴的旋转欧拉角(弧度)生成四元数
const out2 = new Quaternion();
// 等价于 Quaternion.rotationYawPitchRoll(yRad, xRad, zRad, out2)
Quaternion.rotationEuler(xRad, yRad, zRad, out2); 

// 绕 X、Y、Z 轴旋转生成四元数,我们以绕 X 轴为例
const out3 = new Quaternion();
Quaternion.rotationX(xRad, out3);

// 当前四元数依次绕 X、Y、Z 轴旋转
const q3 = new Quaternion();
q3.rotateX(xRad).rotateY(yRad).rotateZ(zRad);

// 获取当前四元数的欧拉角(弧度)
const eulerV = new Vector3();
q3.toEuler(eulerV);

// 弧度转角度
eulerV.scale(MathUtil.radToDegreeFactor); 

Matrices

In 3D graphics engines, calculations can be performed in multiple different Cartesian coordinate spaces, and transforming from one coordinate space to another requires the use of transformation matrices, which is the purpose of the Matrix module in our mathematics library.

In the Galacean engine, there are local coordinates, global coordinates, view coordinates, clip coordinates, etc., and the transformation of objects between these coordinates is achieved through transformation matrices.

import { Vector3, Matrix3x3, Matrix } from '@galacean/engine-math';

// 创建默认4x4矩阵默认为单位矩阵
const m1 = new Matrix(); 

// 创建4x4矩阵并按给定值初始化
const m2 = new Matrix(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);

// 将 m2 设置为单位矩阵
m2.identity(); 

// 判断两个矩阵的值是否相等 true
const isEqual1: boolean = Matrix.equals(m1, m2);

// 矩阵相乘 静态方式
const m3 = new Matrix(1, 2, 3.3, 4, 5, 6, 7, 8, 9, 10.9, 11, 12, 13, 14, 15, 16);
const m4 = new Matrix(16, 15, 14, 13, 12, 11, 10, 9, 8.88, 7, 6, 5, 4, 3, 2, 1);
const out1 = new Matrix();
Matrix.multiply(m3, m4, out1);

// 矩阵相乘,实例方式
const out2 = m3.multiply(m4);

// 判断两个矩阵的值是否相等 true
const isEqual2: boolean = Matrix.equals(out1, out2);

// 求矩阵行列式
const m5 = new Matrix(1, 2, 3, 4, 5, 6, 7, 8, 9, 10.9, 11, 12, 13, 14, 15, 16);
const det: number = m5.determinant();

// 4x4矩阵转3x3矩阵
const m6 = new Matrix3x3();
m6.setValueByMatrix(m5);

// 创建4x4矩阵并按给定值初始化
const m7 = new Matrix(1, 2, 3, 4, 5, 6, 7, 8, 9, 10.9, 11, 12, 13, 14, 15, 16);

// 求矩阵的转置矩阵,静态方式
Matrix.transpose(m7, m7); 

// 求矩阵的转置矩阵。实例方式
m7.transpose(); 

// 绕 Y 轴旋转生成4x4矩阵
const axis = new Vector3(0, 1, 0); 
const out4 = new Matrix();
Matrix.rotationAxisAngle(axis, Math.PI * 0.25, out4);

Color

import { Color } from "@galacean/engine-math";

// 创建 Color 对象
const color1 = new Color(1, 0.5, 0.5, 1);
const color2 = new Color();
color2.r = 1;
color2.g = 0.5;
color2.b = 0.5;
color2.a = 1;

// linear 空间转 gamma 空间
const gammaColor = new Color();
color1.toGamma(gammaColor);

// gamma 空间转 linear 空间
const linearColor = new Color();
color2.toLinear(linearColor);

Plane

import { Plane, Vector3 } from "@galacean/engine-math";

// 通过三角形的三个顶点创建平面
const point1 = new Vector3(0, 1, 0);
const point2 = new Vector3(0, 1, 1);
const point3 = new Vector3(1, 1, 0);
const plane1 = new Plane();
Plane.fromPoints(point1, point2, point3, plane1);
// 通过平面的法线以及法线距离原点距离创建平面
const plane2 = new Plane(new Vector3(0, 1, 0), -1);

Bounding Box

import { BoundingBox, BoundingSphere, Matrix, Vector3 } from "@galacean/engine-math";

// 通过不同的方式创建同样的包围盒
const box1 = new BoundingBox();
const box2 = new BoundingBox();
const box3 = new BoundingBox();

// 通过中心点和盒子范围来创建
BoundingBox.fromCenterAndExtent(new Vector3(0, 0, 0), new Vector3(1, 1, 1), box1);

// 通过很多点来创建
const points = [
  new Vector3(0, 0, 0),
  new Vector3(-1, 0, 0),
  new Vector3(1, 0, 0),
  new Vector3(0, 1, 0),
  new Vector3(0, 1, 1),
  new Vector3(1, 0, 1),
  new Vector3(0, 0.5, 0.5),
  new Vector3(0, -0.5, 0.5),
  new Vector3(0, -1, 0.5),
  new Vector3(0, 0, -1),
];
BoundingBox.fromPoints(points, box2);

// 通过包围球来创建
const sphere = new BoundingSphere(new Vector3(0, 0, 0), 1);
BoundingBox.fromSphere(sphere, box3);

// 通过矩阵来对包围盒进行变换
const box = new BoundingBox(new Vector3(-1, -1, -1), new Vector3(1, 1, 1));
const matrix = new Matrix(
  2, 0, 0, 0,
  0, 2, 0, 0,
  0, 0, 2, 0,
  1, 0.5, -1, 1
);
const newBox = new BoundingBox();
BoundingBox.transform(box, matrix, newBox);

// 合并两个包围盒 box1, box2 成为一个新的包围盒 box
BoundingBox.merge(box1, box2, box);

// 获取包围盒的中心点和范围
const center = new Vector3();
box.getCenter(center);
const extent = new Vector3();
box.getExtent(extent);

// 获取包围盒的8个顶点
const corners = [
  new Vector3(), new Vector3(), new Vector3(), new Vector3(),
  new Vector3(), new Vector3(), new Vector3(), new Vector3()
];
box.getCorners(corners);

Bounding Sphere

import { BoundingBox, BoundingSphere, Vector3 } from "@galacean/engine-math";

// 通过不同方式来创建包围球
const sphere1 = new BoundingSphere();
const sphere2 = new BoundingSphere();

// 通过很多点来创建
const points = [
  new Vector3(0, 0, 0),
  new Vector3(-1, 0, 0),
  new Vector3(0, 0, 0),
  new Vector3(0, 1, 0),
  new Vector3(1, 1, 1),
  new Vector3(0, 0, 1),
  new Vector3(-1, -0.5, -0.5),
  new Vector3(0, -0.5, -0.5),
  new Vector3(1, 0, -1),
  new Vector3(0, -1, 0),
];
BoundingSphere.fromPoints(points, sphere1);

// 通过包围盒来创建
const box = new BoundingBox(new Vector3(-1, -1, -1), new Vector3(1, 1, 1));
BoundingSphere.fromBox(box, sphere2);

Frustum

import { BoundingBox, BoundingSphere, BoundingFrustum,Matrix, Vector3 } from "@galacean/engine-math";

// 根据 VP 矩阵创建视锥体,实际项目中,一般从相机中获取 view matrix 和 projection matrix
const viewMatrix = new Matrix(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, -20, 1);
const projectionMatrix = new Matrix(0.03954802080988884, 0, 0, 0, 0, 0.10000000149011612, 0, 0, 0, 0, -0.0200200192630291, 0, -0, -0, -1.0020020008087158, 1);
const vpMatrix = new Matrix();
Matrix.multiply(projectionMatrix, viewMatrix, vpMatrix);
const frustum = new BoundingFrustum(vpMatrix);

// 判断是否和 AABB 包围盒相交
const box1 = new BoundingBox(new Vector3(-2, -2, -2), new Vector3(2, 2, 2));
const isIntersect1 = frustum.intersectsBox(box1);
const box2 = new BoundingBox(new Vector3(-32, -2, -2), new Vector3(-28, 2, 2));
const isIntersect2 = frustum.intersectsBox(box2);

// 判断是否和包围球相交
const sphere1 = new BoundingSphere();
BoundingSphere.fromBox(box1, sphere1);
const isIntersect3 = frustum.intersectsSphere(sphere1);
const sphere2 = new BoundingSphere();
BoundingSphere.fromBox(box2, sphere2);
const isIntersect4 = frustum.intersectsSphere(sphere2);

Ray

import { BoundingBox, BoundingSphere, Plane, Ray, Vector3 } from "@galacean/engine-math";

// 创建 ray
const ray = new Ray(new Vector3(0, 0, 0), new Vector3(0, 1, 0));
const plane = new Plane(new Vector3(0, 1, 0), -3);
// 判断射线是否和平面相交,相交的话 distance 为射线到平面距离,不相交的话 distance 为 -1
let distance = ray.intersectPlane(plane);

const sphere = new BoundingSphere(new Vector3(0, 5, 0), 1);
// 判断射线是否和包围球相交,相交的话 distance 为射线到平面距离,不相交的话 distance 为 -1
distance = ray.intersectSphere(sphere);

const box = new BoundingBox();
BoundingBox.fromCenterAndExtent(new Vector3(0, 20, 0), new Vector3(5, 5, 5), box);
// 判断射线是否和包围盒 (AABB) 相交,相交的话 distance 为射线到平面距离,不相交的话 distance 为 -1
distance = ray.intersectBox(box);

// 到射线起点指定距离的点
const out = new Vector3();
ray.getPoint(10, out);

Rand

The math library has added a random number generator Rand, which is based on the xorshift128+ algorithm (also used in V8, Safari, and Firefox), providing a fast, high-quality, and fully-periodic pseudo-random number generation algorithm.

// 初始化随机数生成器实例
const rand = new Rand(0, 0xf3857f6f);

// 生成区间在[0, 0xffffffff)的随机整数
const num1 = rand.randomInt32();
const num2 = rand.randomInt32();
const num3 = rand.randomInt32();

// 生成区间在[0, 1)的随机数
const num4 = rand.random();
const num5 = rand.random();
const num6 = rand.random();

// 重置种子
rand.reset(0, 0x96aa4de3);

CollisionUtil

import { 
  BoundingBox,
  BoundingSphere,
  BoundingFrustum,
  Matrix,
  Plane,
  Ray,
  Vector3,
  CollisionUtil
} from "@galacean/engine-math";

const plane = new Plane(new Vector3(0, 1, 0), -5);
const viewMatrix = new Matrix(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, -20, 1);
const projectionMatrix = new Matrix(0.03954802080988884, 0, 0, 0, 0, 0.10000000149011612, 0, 0, 0, 0, -0.0200200192630291, 0, -0, -0, -1.0020020008087158, 1);
const vpMatrix = new Matrix();
Matrix.multiply(projectionMatrix, viewMatrix, vpMatrix);
const frustum = new BoundingFrustum(vpMatrix);

// 点和面之间的距离
const point = new Vector3(0, 10, 0);
let distance = CollisionUtil.distancePlaneAndPoint(plane, point);

// 判断点和面的空间关系
const point1 = new Vector3(0, 10, 0);
const point2 = new Vector3(2, 5, -9);
const point3 = new Vector3(0, 3, 0);
const intersection1 = CollisionUtil.intersectsPlaneAndPoint(plane, point1);
const intersection2 = CollisionUtil.intersectsPlaneAndPoint(plane, point2);
const intersection3 = CollisionUtil.intersectsPlaneAndPoint(plane, point3);

// 判断面和包围盒的空间关系
const box1 = new BoundingBox(new Vector3(-1, 6, -2), new Vector3(1, 10, 3));
const box2 = new BoundingBox(new Vector3(-1, 5, -2), new Vector3(1, 10, 3));
const box3 = new BoundingBox(new Vector3(-1, 4, -2), new Vector3(1, 5, 3));
const box4 = new BoundingBox(new Vector3(-1, -5, -2), new Vector3(1, 4.9, 3));
const intersection11 = CollisionUtil.intersectsPlaneAndBox(plane, box1);
const intersection22 = CollisionUtil.intersectsPlaneAndBox(plane, box2);
const intersection33 = CollisionUtil.intersectsPlaneAndBox(plane, box3);
const intersection44 = CollisionUtil.intersectsPlaneAndBox(plane, box4);

// 判断射线和平面的空间关系
const ray1 = new Ray(new Vector3(0, 0, 0), new Vector3(0, 1, 0));
const ray2 = new Ray(new Vector3(0, 0, 0), new Vector3(0, -1, 0));
const distance1 = CollisionUtil.intersectsRayAndPlane(ray1, plane);
const distance2 = CollisionUtil.intersectsRayAndPlane(ray2, plane);

// 判断视锥体和包围盒的空间关系
const contain1 = CollisionUtil.frustumContainsBox(frustum, box1);
const contain2 = CollisionUtil.frustumContainsBox(frustum, box2);
const contain3 = CollisionUtil.frustumContainsBox(frustum, box3);