-
已知有平面上三个点(ABC)坐标,直线上两个点(DE)坐标,求平面ABC与直线DE的交点坐标。
解:
/**
* 平面与直线的交点
*
* @param plantA
* @param plantB
* @param plantC
* @param lineD
* @param lineE
* @return
*/
public static double[] getCrossPointOfPlaneAndLine3D(double[] plantA, double[] plantB, double[] plantC,
double[] lineD, double[] lineE) {
// ∵点A,点B,点C在平面ABC上
// ∴向量AB和向量AC在平面ABC上
double[] arrowAB = arrow(plantA, plantB);
double[] arrowAC = arrow(plantA, plantC);
// 由AB和AC可以计算出平面ABC的法向量plantSide
double[] plantSide = crossMultiply3D(arrowAB, arrowAC);
// 若法向量长度为0,则点A点B点C在同一直线上,不能确定平面
if (Math.sqrt(dotMultiply3D(plantSide, plantSide)) == 0) {
// 面积为0,平面向量不可用
StaticJavaUtils.exit("不可使用平行的向量定位平面");
}
// 三棱锥ABCD的体积的3倍为向量AB×AC·AD
double[] arrowAD = arrow(plantA, lineD);
double volumeD = dotMultiply3D(plantSide, arrowAD);
// 此步之前已证明底面积不为0,若体积为0,则D点在平面ABC上
if (volumeD == 0) {
return lineD;
}
// 三棱锥ABCE的体积的3倍为向量AB×AC·AE
double[] arrowAE = arrow(plantA, lineE);
double volumeE = dotMultiply3D(plantSide, arrowAE);
// 此步之前已证明底面积不为0,若体积为0,则E点在平面ABC上
if (volumeE == 0) {
return lineE;
}
// ABCD与ABCE体积相等,说明D与E到平面的距离相等,且在同一侧,即线段与平面平行
if (volumeD == volumeE) {
// 直线与平面平行
return null;
}
// 若直线DE与平面ABC不平行,则必能在直线DE上找到一点F,使三棱锥ABCF的体积为0,点F即直线DE与平面ABC的交点
double[] ans = new double[3];
for (int i = 0; i < ans.length; ++i) {
// 坐标缩放和移动,先把原始坐标减原始坐标系缩放中心的坐标,得到相对缩放中心坐标,再按缩放比例(volumeD-volumeE):(lineD的坐标-lineE的坐标)缩放到目标坐标系的相对缩放中心坐标,再加上目标坐标系的缩放中心坐标,就能把原坐标系的坐标线性映射到目标坐标系。此步相当于普通的一元一次函数。这一步将得到直线上0体积的点的坐标。
ans[i] = StaticMathUtils.axis(volumeD, volumeE, lineD[i], lineE[i], 0)[0];
}
return ans;
}
axis函数和Matlab的axis函数类似,但是这个axis是对数字进行缩放,不会作用于显示器。与点积、叉积不同,估计很多人不知道axis是怎样实现的。这里附上axis函数:
/**
* 缩放坐标(不支持整数操作,因为整数操作比例计算时会失去小数部分,使比例不正确)
*
* @param srcMin 原域最小值
* @param srcMax 原域最大值
* @param dstMin 新域最小值
* @param dstMax 新域最大值
* @param x 坐标数组
* @return 新坐标
*/
public static double[] axis(double srcMin, double srcMax, double dstMin, double dstMax, double... x) {
if (x.length == 0) {
return null;
}
// 假想的摄像机坐标范围
double mathRange = srcMax - srcMin;
// 显示器的尺寸范围
double displayRange = dstMax - dstMin;
// 两个范围相除就是两个坐标系的缩放比例
double slope = displayRange / mathRange;
double[] ret = new double[x.length];
for (int i = 0; i < x.length; ++i) {
// 减去缩放中心的坐标,就变成相对缩放中心的临时坐标
double relativeSrc = x[i] - srcMin;
// 相对缩放中心进行缩放
double relativeDst = relativeSrc * slope;
// 最后加上显示器的缩放中心坐标
ret[i] = relativeDst + dstMin;
}
return ret;
}
axis是可以反向映射的,也就是把源坐标范围或目标坐标范围的最大值和最小值换位,就可以变成源坐标增加,目标坐标减小。axis也支持把一个有长度的范围映射到0长度的范围,就是把目标坐标的最大最小设为相等。这个缩放函数与Matlab图表坐标范围设置不同,Matlab有x和y两个缩放比例,这个axis只有一个缩放比例,如果要像Matlab的axis一样缩放,需要分别为x轴和y轴调用axis,并输入各自的映射范围。Matlab的axis的显示器坐标是自动的,此处的这个axis用于数字的换算,换算的范围和比例是未知的,所以才会有一个目标范围。