本帖最后由 MxDraw 于 2024-2-21 15:50 编辑
前言在CAD二次开发中, 正确的使用数学库是十分重要的, 我们不需要会很多数学知识, 只要会普通的四则运算和调用mxcad提供的api即可,通过快速入门了解了打开图纸后,如果要对图形进行处理,就需要各种计算, mxcad提供了一些类来参与计算或者表示一些数据结构,相关的API查询如下:
几何图形信息的数学体系在cad 中我们要描述图形的顶点、边、线、面、等信息有很多不同的方法,如果我们使用不同的绘图系统,就有不同的方式或特定的API来解决问题,然而选择较多,我们很难找到最合适的工具,另一方面如果只有解决问题的工具,没有统一的方法也无没办法一劳永逸,因此在图形学中, 我们建立了一套描述几何图形信息与各个图形系统无关联的、简单的基于向量和矩阵运算的数学体系,以及如何用该体系解决可视化图形问题的一些方法。
坐标系与坐标映射首先我们来看看mxcad可能会用到的一些坐标系: 1. 窗口坐标系:HTML 采用的是此坐标系,左上角为坐标原点,x 轴向右,y 轴向下,坐标值对应像素值, 在CAD中我们一般称为屏幕坐标。 2. webgl 坐标系:mxcad依赖mxdraw,而mxdraw 内部使用了特定版本修改后的three.js 所以这种情况我们一般指的是Three.js的坐标系。 3. 绘图坐标系:就是cad中的绘图坐标系,在mxcad和mxdraw中基于它的坐标系的坐标, 一般称为文档坐标系。
mxcad的坐标体系相互转换的方法 方法都 在[mxdraw 坐标转换中全部列举出来了,我们可以直接使用mxdraw提供的API对mxcad相关坐标进行转换,示例代码如下:- import { MxFun } from "mxdraw"
- import { McGePoint3d } from "mxcad"
- const pt = new McGePoint3d()
- // CAD图纸坐标转文档坐标
- MxFun.cadCoord2Doc(pt.x, pt.y, pt.z)
复制代码尽管这四个坐标系在原点位置、坐标轴方向、坐标范围上有所区别,但都是直角坐标系,所以它们都满足直角坐标系的特性:不管原点和轴的方向怎么变,用同样的方法绘制几何图形,它们的形状和相对位置都不变。
向量 McGeVector3引入mxcad后,会自动在全局挂载THREE变量,表示three.js,如果发现McGeVector3d 可以调用`toVector3` 方法得到THREE.Vector3,可以使用three.js提供的API进行向量运算,而要将THREE.Vector3变成McGeVector3d,只需要将THREE.Vector3当成new McGeVector3d 的参数就可以了。 那么在直角坐标系如何表示一个点和一段线呢?前面的例子包含 x、y 、z三个轴,所以构成了一个绘图的三维空间,但通常我们只需要考虑x、y。因此我们可以用二维向量来表示这个平面上的点和线段,二维向量其实就是一个包含了两个数值的数组,一个是 x 坐标值,一个是 y 坐标值。
假设现在这个平面直角坐标系上有一个向量v: 向量v有两个含义,一是可以表示该坐标系下位于 (x, y) 处的一个点;二是可以表示从原点 (0,0) 到坐标 (x,y) 的一根线段
同样的两个向量一样可以进行数学运算,比如有两个向量,v1和 v2,如果让它们相加,其结果相当于将 v1向量的终点(x1, y1),沿着 v2向量的方向移动一段距离,这段距离等于 v2向量的长度。 这样,我们就可以在平面上得到一个新的点(x1 + x2, y1 + y2),一条新的线段[(0, 0), (x1 + x2, y1 + y2)],以及一段折线:[(0, 0), (x1, y1) , (x1 + x2, y1 + y2)],向量的数学运算如下图:
其次,一个向量包含有长度和方向信息。它的长度可以用向量的 x、y 的平方和的平方根来表示,代码如下: - v.length = function(){return Math.hypot(this.x, this.y)};
复制代码它的方向可以用与 x 轴的夹角来表示,代码如下: - v.dir = function() { return Math.atan2(this.y, this.x);}
复制代码在上面的代码里,Math.atan2 的取值范围是 -π到π,负数表示在 x 轴下方,正数表示在 x 轴上方,最后根据长度和方向的定义,我们还能推导出一组关系式,代码如下: - v.x = v.length * Math.cos(v.dir);
- v.y = v.length * Math.sin(v.dir);
复制代码这个推论意味着一个重要的事实:我们可以很简单地构造出一个绘图向量。也就是说,如果我们希望以点 (x0, y0) 为起点,沿着某个方向画一段长度为 length 的线段,我们只需要构造出如下的一个向量就可以了,代码如下: - // dir 是一个向量的方向(与x轴的夹角), length 是向量的长度
- function createV1(dir, length) {
- return {
- x: length * Math.cos(dir),
- y: length * Math.sin(dir)
- }
- }
- var v0 = { x: 0, y: 0 }
- var v1 = createV1(Math.PI / 5, 30)
- // 然后我们把向量 (x0, y0) 与这个向量 v1相加,得到的就是这条线段的终点
复制代码
而在mxcad中也提供了对应的方法`McGeVector3d.length` 和`McGeVector3d.angleTo1` 来求向量长度和方向角度,还有两个向量相加的`McGeVector3d.add` 你可以选择使用我们提供的这些API来简化上述运算。
在three.js 中有向量`THREE.Vector3` 而对应的在mxcad也存在[McGeVector3d]表示3D空间中的矢量(向量),在该类中存在四个轴`kXAxis`、`kYAxis`、`kZAxis`、`kNegateZAxis` 分别都是固定的向量,`THREE.Vector3` 与 `McGeVector3d` 是完全等价的,只是在mxcad中与其他数据参与运算的是`McGeVector3d`。
下面简单说明了一下mxcad提供的一些向量的运算方法,代码如下: - import { McGeVector3d } from "mxcad"
- const vet = new McGeVector3d(1, 0, 0)
- // 得到THREE.Vector3
- const tVet = vet.toVector3()
- const newVet = new McGeVector3d(tVet)
- // 旋转
- tVet.rotateBy(Math.PI. McGeVector3d.kXAxis)
- // 取反
- vet.negate()
- // 垂直90度
- vet.perpVector()
- // 计算两个向量之间的角度
- vet.angleTo1(newVet)
- vet.angleTo1(newVet, McGeVector3d.kZAxis)
- // 归一化
- vet.normalize()
- // 点积
- vet.dotProduct(newVet)
- // 交叉积
- vet.crossProduct(newVet)
- // 是否相等
- vet.isEqualTo(newVet)
- // 向量与某个值相乘
- vet.mult(10)
复制代码可以参考[数学库演示效果]中点击获取向量长度和角度和根据方向和距离通过一个向量得到一条线,查看如果正确使用对应方法。
向量的加减法他们的运算比较容易计算,示例代码如下: - // 比如向量v + 向量v1:
- (v.x + v1.x, v.y + v1.y)
- // 比如向量v - 向量v1:
- (v.x - v1.x, v.y - v1.y)
复制代码
如何理解向量的加减: 1.向量相加:将各个向量依次首尾顺次相接,结果为第一个向量的起点指向最后一个向量的终点向量a加向量b,将a和b首尾相连后,由a的起点指向b的终点,就是a + b,如下图所示: 2.向量相减:将两个向量平移至公共起点O,从减数向量的终点B指向被减向量的终点A为相减结果将向量a和向量b的起点移到左下角的公共起点O,从点B指向点A的向量就是a - b,如下图: 可能理解起来比较抽象,下面我们通过查看[数学库演示效果中点击向量的加减,查看具体的效果和源码可以更容易理解。
向量的乘法向量乘法有两种,一种是点乘,一种是叉乘,它们有着不同的几何和物理含义, 如果你阅读后不是很理解可以在[数学库演示效果中点击向量乘法,查看它的实际应用, 阅读源码更容易理解其概念。
点乘假设现在有两个 N 维向量 a 和 b,其中a = [a1, a2, ...an],b = [b1, b2, ...bn],那向量的点乘代码如下: - a*b = a1*b1 + a2*b2 + ... + an*bn
复制代码在 N 维线性空间中,a、b 向量点积的几何含义是a 向量乘以 b 向量在 a 向量上的投影分量,它的物理含义相当于 a 力作用于物体,产生 b 位移所做的功。点积公式如下图所示: 当然点乘还有两种特殊情况: 1. a、b向量平行,那么它们的夹角是0°, 那么a·b=|a|*|b| 用 JavaScript 代码表示如下: - a.x * b.x + a.y * b.y === a.length * b.length<font face="宋体">;</font>
复制代码2. a、b向量垂直,那么它们的夹角是90°,那么a·b=0 用 JavaScript 代码表示如下: - a.x * b.x + a.y * b.y === 0;
复制代码
叉乘叉乘和点乘有两点不同,首先向量叉乘运算的结果不是标量,而是一个向量;其次,两个向量的叉积与两个向量组成的坐标平面垂直,以二维空间为例,向量 a 和 b 的叉积,就相当于向量 a(蓝色带箭头线段)与向量 b 沿垂直方向的投影(红色带箭头线段)的乘积 那如下图所示,二维向量叉积的几何意义就是向量a、b 组成的平行四边形的面积,向量叉乘如下图:
那叉乘在数学上该怎么计算呢?假设,现在有两个三维向量 a(x1, y1, z1) 和 b(x2, y2, z2),那么,a 与 b 的叉积可以表示为一个如下图的行列式:
其中 i、j、k 分别是 x、y、z 轴的单位向量。我们把这个行列式展开,就能得到如下公式: a * b = [y1 * z2 - y2 * z1, - (x1 * z2 - x2 * z1), x1 * y2 - x2 * y1]
二维空间中向量叉乘的物理意义就是 a 和 b 的力矩(力矩你可以理解为一个物体在力的作用下,绕着一个轴转动的趋向。
3D点 McGePoint3d这是最常使用的一个类[McGePoint3d]代表3D空间中的一个点的构造类,由`x`、`y`、`z` 三个双精度数值组成的结构, 当然 McGePoint3d 本质上也是一个向量, 上面讲了向量也可以表示一个点,之所以区分是因为它的含义就是表示坐标系上的一个点, 而不是表示一个量,如果要改变这个点的位置,就需要仿射变换,我们可以通过对向量进行运算得到新的点位置。 下面是该类常用的一些方法,主要是与向量进行数学运算得到新的点的位置,代码如下: - import { McGePoint3d } from "mxcad"
- const pt1 = new McGePoint3d(0, 0, 0)
- // 或者
- const pt2 new McGePoint3d({ x: 0, y: 0, z: 0})
- // 提供了一些实用的方法
- // 判断两个点是否相等
- pt1.isEqualTo(pt2)
- // 计算两点距离
- pt1.distanceTo(pt2)
- // 将three.js 的向量设置成点
- pt1.setFromVector3(new THREE.Vector3())
- // 得到点对应的three.js 向量
- pt1.toVector3()
- // 两点相减得到新向量
- const vet = pt1.sub(pt2)
- // 加上向量的新位置
- pt1.addvec(vet)
- // 简写
- pt1.av(vet)
- // 减去向量的新位置
- pt1.subvec(vet)
- // 简写
- pt1.sv(vet)
复制代码
什么是仿射变换仿射变换简单来说就是“线性变换 + 平移”,比如对元素设置 CSS 的 transform 属性就是对元素应用仿射变换,而几何图形的仿射变换具有以下 2 个性质: 1、仿射变换前是直线段的,仿射变换后依然是直线段 2、对两条直线段 a 和 b 应用同样的仿射变换,变换前后线段长度比例保持不变
常见的仿射变换形式包括平移、旋转、缩放以及它们的组合,最简单的就是平移,在mxcad中你可以直接理解为McGePoint3d点通过addvec方法加上一个向量McGeVector3d,就是朝该向量所代表的方向上平移向量的距离。
矩阵 McGeMatrix3d 以上我们知道了如何平移一个点,同样我们可以通过线性变换对一个点进行旋转和缩放,那么什么是线性变换呢? 我们通过向量运算的方式, 得到如何旋转和缩放的方式,只是旋转和缩放, 我们选择用矩阵的形式表示,通过矩阵与向量相乘形式的变换就叫做线性变换。 线性变换除了可以满足仿射变换的2个性质之外,还有2个额外的性质: 1、线性变换不改变坐标原点(因为如果 x0、y0等于零,那么 x、y 肯定等于 0); 2、线性变换可以叠加,多个线性变换的叠加结果就是将线性变换的矩阵依次相乘,再与原始向量相乘。
那根据线性变换的第 2 条性质,我们就能总结出一个通用的线性变换公式,即一个原始向量 P0经过 M1、M2、…Mn 次的线性变换之后得到最终的坐标P, 在mxcad 在中[McGeMatrix3d]类表示3D空间的仿射变换,通常情况下需要将平移、旋转、缩放等组合形成的各种复杂的放射变换都通过线性变换来表示,我们只需要将原本 n 维的坐标转换为了 n+1 维的坐标,这种 n+1 维坐标被称为齐次坐标,对应的矩阵就被称为齐次矩阵。 McGeMatrix3d 也是齐次矩阵, 可以直接通过McGeMatrix3d 进行各种线性变换,最终再通过向量的`transformBy`方法应用这个仿射变换。 同样的矩阵也可以应用在mxcad中所有的几何实体[McDbEntity.transformBy]进行仿射变换, 因为所有的几何图形都是基于点和线构成的,而我们可以将点或者线看作一个向量,对实体进行放射变换就相当于对组成实体的所有点进行放射变换。
矩阵的乘法矩阵的乘法其实对应的就是上面讲述的线性变换可以叠加的这一特性,我们希望通过一个一个的矩阵组合形成一个复杂的仿射变换, 本质是通过矩阵一个一个的相乘得到的最终的矩阵,组合形成的复杂的仿射变换,其中两个A、B矩阵相乘, 以A为例, 这个时候A可以选择左乘或者右乘矩阵B,左乘就是`B * A` 右乘就是`A * B`,通过下图可以理解左乘和右乘的区别,首先假设矩阵A,如下图: 设列向量,如下图: 用列向量去右乘矩阵A,相当于对矩阵A中的列向量做线性组合,如下图:
用列向量左乘矩阵A,相当于对矩阵A中的行向量左线性组合,如下图:
根据上述概念扩展到矩阵乘法中的左乘与右乘, 思路是一样的,设置一个矩阵B,如下图: 用矩阵B来左乘矩阵A,如下图:
因此矩阵B左乘矩阵A得到的新矩阵的每一行都是矩阵A的行向量的线性组合,同理,矩阵B右乘矩阵A得到的新矩阵的每一列都是矩阵A的列向量的线性组合
下面列举一些McGeMatrix3d提供的方法: - import { McGeMatrix3d, McGePoint3d, McGeVector3d } from "mxcad"
- // 乘法的单位矩阵
- McGeMatrix3d.kIdentity
- const m = new McGeMatrix3d()
- const m1 = new McGeMatrix3d()
- // 设置为单位矩阵。
- m.setToIdentity()
- // 左乘指定的矩阵。
- const m3 = m.preMultBy(m1)
- // 右乘指定的矩阵。
- m3.postMultBy(m1)
- // 矩阵设置为两个矩阵的乘积。
- new McGeMatrix3d().setToProduct(m1, m2)
- // 逆矩阵。
- m1.invert()
- // 是否为奇异矩阵。
- m1.isSingular()
- // 转置
- m1.transposeIt()
- // 是否相等
- m1.isEqualTo(m2)
- // 矩阵的行列式。
- m1.det()
- // 将矩阵设置为指定的坐标系。参数分别是原点、xyz轴
- m1.setCoordSystem(new McGePoint3d(), new McGeVector3d(), new McGeVector3d(), new McGeVector3d())
- // 平移
- m1.setToTranslation(new McGeVector3d(0, 1, 0))
- // 旋转 参数:角度、轴、旋转中心点
- m1.setToRotation(Math.PI, McGeVector3d.kXAxis, new McGePoint3d())
- // 缩放 参数: 缩放因子、缩放中心点
- m1.setToScaling(0.5, new McGePoint3d())
- // 设置为镜向矩阵
- m1.setMirror(new McGePoint3d(), new McGePoint3d())
- // 获取缩放因子
- m1.scale()
- // 获取矩阵中指定位置的元素值 参数 行索引、列索引
- m1.getData(0, 0)
复制代码如何在mxcad中使用矩阵,可以查看[数学库演示效果]中对点的旋转平移和缩放|对实体进行仿射变换|查看具体的效果和源码可以更容易理解使用。
MxCADResbuf- import { MxCADSelectionSet, MxCADResbuf } from "mxcad"
- let ss = new MxCADSelectionSet();
- let filter = new MxCADResbuf();
- // 这里添加查询字符"0" 第二个参数是数据类型8 在CAD二次开发中 表示这是一个空指针(RTNUL)即该 resbuf 结构不包含任何有效数据,通常在链表的末尾作为终止符使用
- filter.AddString("0", 8);
- // 选择图层0的所有实体
- ss.allSelect(filter);
- ss.forEach((objId)=> console.log(objId))
复制代码 calcBulge 计算圆弧凸度- import { MxCADUtility, McGePoint3d, McDbPolyline } from "mxcad"
- // 圆弧开始点
- const startPoint = new McGePoint3d(0, 0, 0);
- // 圆弧中点
- const midPoint = new McGePoint3d(0, 0, 0);
- // 圆弧结束点
- const endPoint = new McGePoint3d(0, 0, 0);
- const bulge = MxCADUtility.calcBulge(startPoint, midPoint, endPoint).val
- const pl = new McDbPolyline()
- pl.addVertexAt(startPoint, bulge)
- pl.addVertexAt(endPoint)
复制代码如果你并不知道以上三个参数中的任意一个,参考下图圆弧凸度计算公式自行计算凸度: 凸度值就是圆弧的开始点与结束点距离的一半去除以 圆弧的开始点与结束点相连这条线段的中点到圆弧圆心的距离
|