数学基础
1. 点、向量与方向
在笛卡尔坐标系中,三维的点、向量和方向都可以用三元数表示,如(x,y,z)。在显示引擎中,一般用Vector3表示点、向量和方向。但在几何造型引擎中,点、向量和方向代表不同的含义,故使用不同的类来表示。
三维空间定义:
类 | 含义 | 备注 | 内置常量 |
---|---|---|---|
GPnt | 点 | 三维空间位置 | 原点: GP.Origin() |
GVec | 向量 | 具有大小和方向的三维量 | - |
GDir | 方向 | 只代表三维方向,长度为1的向量 | X方向:GP.DX(); Y方向: GP.DY(); Z方向: GP.DZ() |
GXYZ | 三维的XYZ | 三元数 | - |
在二维空间中的定义:
类 | 含义 | 备注 | 内置常量 |
---|---|---|---|
GPnt2d | 点 | 二维空间位置 | 原点: GP.Origin2d() |
GVec2d | 向量 | 具有大小和方向的量 | - |
GDir2d | 方向 | 只代表方向,长度为1的向量 | X方向:GP.DX2d(); Y方向: GP.DY2d(); |
[TestMethod]
public void 构造基本的点向量和方向()
{
// 1. 点
// 构造两个点
var pt1 = new GPnt(10, 10, 0);
var pt2 = new GPnt(10, 10, 10);
// 2 向量
// 两个点构造向量:从pt1指向pt2,即 pt2 - pt1
var vec1 = new GVec(pt1, pt2);
Assert.IsTrue(vec1.IsEqual(new GVec(0,0,10), 0, 0));
// GPnt、GVec、GDir都可以通过其XYZ()方法得到GXYZ值
// 通过GXYZ值构造向量
var vec2 = new GVec(pt1.XYZ());
Assert.IsTrue(vec2.XYZ().IsEqual(pt1.XYZ(), 0));
// 叉乘
GVec cross = vec1.Crossed(vec2);
// 点乘
double dot = vec1.Dot(vec2);
// 向量夹角
double angle = vec1.Angle(vec2);
// 模,长度
double mod = vec1.Magnitude();
// 3 方向
// 通过向量构造方向
var dir1 = new GDir(vec1);
// 方向变成单位向量
Assert.IsTrue(dir1.IsEqual(new GDir(0, 0, 1), 0));
}
显示引擎中点、向量、方向的定义:
类 | 含义 | 内置常量 |
---|---|---|
Vector2 | 二维float2 | - |
Vector3 | 三维float3 | Vector3.Zero, Vector3.UNIT_X, Vector3.UNIT_Y, Vector3.UNIT_Z |
Vector3d | 三维double3 | Vector3d.Zero, Vector3d.UNIT_X, Vector3d.UNIT_Y, Vector3d.UNIT_Z |
注:与GPnt/GVec/GDir相比,Vector3d更加灵活,支持直接的加减操作。
- Vector3d的使用
var v1 = new Vector3d(10, 10, 0);
var v2 = new Vector3d(10, 10, 10);
// 相减
var v3 = v2 - v1;
// 相加
var v4 = v2 + v1;
// 叉乘
var v5 = v1.cross(v2);
// 点乘
var dot = v1.dot(v3);
// 夹角,弧度
var angle = v1.angleTo(v4);
// 单位化
var d1 = v1.normalized();
- Vector3d支持GPnt/GVec/GDir互相转换:
var v1 = new Vector3d(10, 10, 0);
// Vector3d转换成GPnt/GVec/GDir
var pt = new GPnt(v1);
var v2 = new GVec(v1);
var v3 = v1.normalized();
var d1 = new GDir(v3);
var xyz = new GXYZ(v1);
// GPnt/GVec/GDir转换成Vector3d
var w1 = new Vector3d(pt);
var w2 = new Vector3d(v2);
var w3 = new Vector3d(d1);
var w4 = new Vector3d(xyz);
2. 坐标轴/坐标系
类 | 含义 | 备注 | 内置常量 |
---|---|---|---|
GAx1 | 三维空间的一个轴 | 由一个点和方向确定的坐标轴 | GP.OX(); GP.OY(); GP.OZ() |
GAx2 | 三维右手坐标系 | 只能是右手坐标系 | GP.XOY(); GP.YOZ(); GP.ZOX() |
GAx3 | 三维坐标系 | 可以是右手也可以是左手坐标系 | - |
GAx2d | 二维坐标系 | 二维空间中的点和方向确定的坐标系 | GP.OX2d(); GP.OY2d(); GP.OZ2d() |
//构造一个在指定位置和方向的轴
var ax1 = new GAx1(new GPnt(10,10,0), new GDir(1,0,0));
// 构造右手坐标系,指定Z和X方向
var ax2 = new GAx2(new GPnt(10, 10, 0), new GDir(0, 0, 1), new GDir(1, 0, 0));
// 使用右手坐标系来构造GAx3坐标系
var ax3 = new GAx3(ax2);
// 绕着ax1转45度
ax3.Rotate(ax1, System.Math.PI / 4);
// 沿着X方向移动10个单位
ax3.Translate(new GVec(10, 0, 0));
// 转换成变换
var trsf = GTrsf.From(ax3);
3. 矩阵变换
图形学中常用的矩阵变换有平移矩阵(Translation)、旋转矩阵(Rotation)、缩放矩阵(Scale)、镜像矩阵(Mirror)等。组合使用基本的矩阵变换可以用来实现装配零件、模拟物体运动等功能。
AnyCAD中有两种表示矩阵变换的方法GTrsf和Matrix4d,其中GTrsf主要用于几何变换,Matrix4主要用于显示对象变换。GTrsf和Matrix4可以互相转换。
3.2 GTrsf
- GTrsf 用来表示平移、等比缩放、旋转、镜像等矩阵变换
变换 | GTrsf构造方法 |
---|---|
平移 | SetTranslation |
仅设置平移部分 | SetTranslationPart |
旋转 | SetRotation |
仅设置旋转部分 | SetRotationPart |
缩放 | SetScale |
镜像 | SetMirror |
矩阵相乘 | Multiply |
- GQuaternion 四元数,用来表示旋转
四元数可以与欧拉角进行互相转换。欧拉角比GQuaternion更具可读性,在界面上展示更加直观。
若对四元数和欧拉角不熟悉,可暂忽略,不影响简单的应用场景。
- 示例:
var trsf = new GTrsf();
// 构造一个平移变换,x轴平移10个单位
trsf.SetTranslation(new GVec(10,0,0));
var rot = new GTrsf();
// 绕着Z轴旋转90度
trsf.SetRotation(new GAx1(new GPnt(0, 0, 0), new GDir(0, 0, 1)), 90.0 / 180.0 * System.Math.PI);
// 变换累加
// trsf * rot
var m = trsf.Multiplied(rot);
// 使用四元数表示的旋转变换
GQuaternion rotation = trsf.GetRotation();
// 从四元数中提取欧拉角
double u = 0, v = 0, w = 0;
rotation.GetEulerAngles(GEulerSequence.gp_Extrinsic_XYX, ref u, ref v, ref w);
3.3 Matrix4
Matrix4 可以表示任意矩阵变换
Matrix4提供了静态方法用于构造基本的变换矩阵:
变换 | Matrix4方法 |
---|---|
平移 | Matrix4 makeTranslation(Vector3 v) |
缩放 | Matrix4 makeScale(float x, float y, float z) |
旋转 | Matrix4 makeRotationAxis(Vector3 axis, float angle) |
Matrix4d 是Matrix4的双精度版本
// 构造平移矩阵,沿着x轴移动10个单位
var trans = Matrix4.makeTranslation(10,0,0);
// 构造旋转矩阵,从一个方向变换到另外个方向的旋转矩阵
var rot = Matrix4.makeRotation(new Vector3(1,0,0), new Vector3(1,1,1).normalized());
// 绕着Z轴转45度
var rot2 = Matrix4.makeRotationAxis(new Vector3(0, 0, 1), 45.0f / 180.0f * System.MathF.PI);
// 先旋转,在平移
var mat = trans * rot2;
3.4 矩阵相乘
矩阵相乘不符合交换律,即M1和M2以下的相乘结果不一定相等:
M2 = M1 * M2
M3 = M2 * M1
在对物体进行组合变换的时候需要考虑变换的顺序,
M1 = T * S * R // 先旋转、再缩放、最后平移
M2 = R * S * T // 先平移、再缩放、最后旋转
3.5 矩阵变换的应用
- 点、向量、方向空间变换
GPnt/GVec/GDir可以直接与GTrsf相乘,以实现空间变换
var trsf = new GTrsf();
// 构造一个平移变换,x轴平移10个单位
trsf.SetTranslation(new GVec(10, 0, 0));
var pt = new GPnt();
// 点沿着X轴平移了10个单位
pt.Transform(trsf);
Assert.IsTrue(pt.IsEqual(new GPnt(10, 0, 0), 0));
var vec = new GVec(0, 0, 1);
// 向量沿着X轴平移了10个单位,大小不变
vec.Transform(trsf);
Assert.IsTrue(vec.IsEqual(new GVec(0, 0, 1), 0, 0));
var dir = new GDir(0, 0, 1);
// 方向沿着X轴平移N个单位,没有变化
dir.Transform(trsf);
Assert.IsTrue(dir.IsEqual(new GDir(0, 0, 1), 0));
- 使用TransformTool对TopoShape进行矩阵变换:
变换 | TransformTool方法 |
---|---|
平移 | TopoShape Translate(TopoShape pShape, GVec vec) |
旋转 | TopoShape Rotation(TopoShape pShape, GAx1 axis, double angle) |
缩放 | TopoShape Scale(TopoShape pShape, GPnt center, double scale) |
镜像 | TopoShape Mirror(TopoShape shape, GAx1 axis) |
组合变换 | TopoShape Transform(TopoShape pShape, GTrsf trf) |
var trsf = new GTrsf();
trsf.SetTranslation(new GVec(10,0,0));
var shape = ShapeBuilder.MakeBox(new GAx2(), 10, 10, 10);
// x轴平移10个单位
var newShape = TransformTool.Transform(shape, trsf);
- 显示对象矩阵变换
例,对SceneNode平移后再旋转:
var matrixR = Matrix4.makeRotationAxis(new Vector3(0, 0, 1), Math.PI);
var matrixT = Matrix4.makeTranslation(-50, 0, 0);
ConeNode1.SetTransform(matrixR * matrixT);
renderView.RequstUpdate();
几何变换是对TopoShape变换,显示变换时对场景节点SceneNode变换。对SceneNode变换不会影响TopoShape。
相比几何变换,显示变换更加轻量,即速度更快(快的不是一点点)。
4. 小结
矩阵变换是图形学里面基本的概念,对于几何对象和显示对象有着不同的应用场景。
在动画模拟的过程中,一般对显示对象进行变换即可;在需要进行几何对象操作的,比如布尔运算的情况下,则需要对几何对象进行矩阵变换。
矩阵变换是建模和显示最基本的技能,一定需要掌握
5. 思考问题
- 如何实现绕着某个点旋转?
- 如何使用矩阵变换实现自传和公转?
- 几何对象和显示对象进行矩阵变换的应用场景分别是什么?
- 对模型旋转何缩放的时候,模型的最终位置与模型的局部坐标系的原点有关系吗?