如果给定一条线(由一个向量或线上的两个点表示),我如何找到该线与平面相交的点?我在这方面找到了大量资源,但我无法理解那里的方程式(它们似乎不是标准的代数)。我想要一个可以由标准编程语言(我使用的是 Java)解释的方程(无论多长时间)。
8 回答
这是一个 Python 示例,它找到一条线和一个平面的交点。
在下面的示例中,平面可以是点和法线,也可以是 4d 向量(法线形式)(提供了两者的代码)。
另请注意,此函数会计算一个值,该值表示该点在线上的位置(fac
在下面的代码中调用)。您可能也想返回它,因为从 0 到 1 的值与线段相交 - 这可能对调用者有用。
代码注释中注明的其他细节。
注意:此示例使用纯函数,没有任何依赖关系 - 以便轻松迁移到其他语言。使用Vector
数据类型和运算符重载,它可以更简洁(包括在下面的示例中)。
# intersection function
def isect_line_plane_v3(p0, p1, p_co, p_no, epsilon=1e-6):
"""
p0, p1: Define the line.
p_co, p_no: define the plane:
p_co Is a point on the plane (plane coordinate).
p_no Is a normal vector defining the plane direction;
(does not need to be normalized).
Return a Vector or None (when the intersection can't be found).
"""
u = sub_v3v3(p1, p0)
dot = dot_v3v3(p_no, u)
if abs(dot) > epsilon:
# The factor of the point between p0 -> p1 (0 - 1)
# if 'fac' is between (0 - 1) the point intersects with the segment.
# Otherwise:
# < 0.0: behind p0.
# > 1.0: infront of p1.
w = sub_v3v3(p0, p_co)
fac = -dot_v3v3(p_no, w) / dot
u = mul_v3_fl(u, fac)
return add_v3v3(p0, u)
# The segment is parallel to plane.
return None
# ----------------------
# generic math functions
def add_v3v3(v0, v1):
return (
v0[0] + v1[0],
v0[1] + v1[1],
v0[2] + v1[2],
)
def sub_v3v3(v0, v1):
return (
v0[0] - v1[0],
v0[1] - v1[1],
v0[2] - v1[2],
)
def dot_v3v3(v0, v1):
return (
(v0[0] * v1[0]) +
(v0[1] * v1[1]) +
(v0[2] * v1[2])
)
def len_squared_v3(v0):
return dot_v3v3(v0, v0)
def mul_v3_fl(v0, f):
return (
v0[0] * f,
v0[1] * f,
v0[2] * f,
)
如果平面定义为 4d 向量(范式),我们需要在平面上找到一个点,然后像以前一样计算交点(参见p_co
赋值)。
def isect_line_plane_v3_4d(p0, p1, plane, epsilon=1e-6):
u = sub_v3v3(p1, p0)
dot = dot_v3v3(plane, u)
if abs(dot) > epsilon:
# Calculate a point on the plane
# (divide can be omitted for unit hessian-normal form).
p_co = mul_v3_fl(plane, -plane[3] / len_squared_v3(plane))
w = sub_v3v3(p0, p_co)
fac = -dot_v3v3(plane, w) / dot
u = mul_v3_fl(u, fac)
return add_v3v3(p0, u)
return None
为了进一步参考,这取自 Blender 并适用于 Python。
isect_line_plane_v3()
在math_geom.c中
为清楚起见,这里是使用mathutils API的版本(可以为其他具有运算符重载的数学库进行修改)。
# point-normal plane
def isect_line_plane_v3(p0, p1, p_co, p_no, epsilon=1e-6):
u = p1 - p0
dot = p_no * u
if abs(dot) > epsilon:
w = p0 - p_co
fac = -(plane * w) / dot
return p0 + (u * fac)
return None
# normal-form plane
def isect_line_plane_v3_4d(p0, p1, plane, epsilon=1e-6):
u = p1 - p0
dot = plane.xyz * u
if abs(dot) > epsilon:
p_co = plane.xyz * (-plane[3] / plane.xyz.length_squared)
w = p0 - p_co
fac = -(plane * w) / dot
return p0 + (u * fac)
return None
这是 Java 中的一种方法,用于查找直线和平面之间的交点。有一些向量方法不包括在内,但它们的功能很容易解释。
/**
* Determines the point of intersection between a plane defined by a point and a normal vector and a line defined by a point and a direction vector.
*
* @param planePoint A point on the plane.
* @param planeNormal The normal vector of the plane.
* @param linePoint A point on the line.
* @param lineDirection The direction vector of the line.
* @return The point of intersection between the line and the plane, null if the line is parallel to the plane.
*/
public static Vector lineIntersection(Vector planePoint, Vector planeNormal, Vector linePoint, Vector lineDirection) {
if (planeNormal.dot(lineDirection.normalize()) == 0) {
return null;
}
double t = (planeNormal.dot(planePoint) - planeNormal.dot(linePoint)) / planeNormal.dot(lineDirection.normalize());
return linePoint.plus(lineDirection.normalize().scale(t));
}
您需要考虑三种情况:
- 平面平行于直线,直线不位于平面内(不相交)
- 平面不平行于线(一个交点)
- 平面包含线(线在其上的每个点相交)
您可以以参数化的形式表达该行,如下所示:
http://answers.yahoo.com/question/index?qid=20080830195656AA3aEBr
本讲座的前几页对飞机做了同样的事情:
http://math.mit.edu/classes/18.02/notes/lecture5compl-09.pdf
如果平面的法线垂直于沿线的方向,那么您有一个边缘情况,需要查看它是否完全相交,或者位于平面内。
否则,您有一个交点,并且可以解决它。
我知道这不是代码,但要获得一个强大的解决方案,您可能希望将其放在应用程序的上下文中。
编辑:这是一个只有一个交点的例子。假设您从第一个链接中的参数化方程开始:
x = 5 - 13t
y = 5 - 11t
z = 5 - 8t
参数t
可以是任何东西。(x, y, z)
满足这些方程的所有(无限)集合构成了这条线。然后,如果你有一个平面的方程,说:
x + 2y + 2z = 5
(取自此处)您可以将、 和以上的方程代入平面的方程中x
,该方程现在仅存在于参数中。求解。这是位于平面中的那条线的特定值。然后,您可以通过返回线方程并代入来求解、和。y
z
t
t
t
x
y
z
t
使用 numpy 和 python:
#Based on http://geomalgorithms.com/a05-_intersect-1.html
from __future__ import print_function
import numpy as np
epsilon=1e-6
#Define plane
planeNormal = np.array([0, 0, 1])
planePoint = np.array([0, 0, 5]) #Any point on the plane
#Define ray
rayDirection = np.array([0, -1, -1])
rayPoint = np.array([0, 0, 10]) #Any point along the ray
ndotu = planeNormal.dot(rayDirection)
if abs(ndotu) < epsilon:
print ("no intersection or line is within plane")
w = rayPoint - planePoint
si = -planeNormal.dot(w) / ndotu
Psi = w + si * rayDirection + planePoint
print ("intersection at", Psi)
如果你有两个点 p 和 q 定义了一条直线和一个平面,一般笛卡尔形式 ax+by+cz+d = 0,你可以使用参数化方法。
如果您出于编码目的需要它,这里有一个 javascript 片段:
/**
* findLinePlaneIntersectionCoords (to avoid requiring unnecessary instantiation)
* Given points p with px py pz and q that define a line, and the plane
* of formula ax+by+cz+d = 0, returns the intersection point or null if none.
*/
function findLinePlaneIntersectionCoords(px, py, pz, qx, qy, qz, a, b, c, d) {
var tDenom = a*(qx-px) + b*(qy-py) + c*(qz-pz);
if (tDenom == 0) return null;
var t = - ( a*px + b*py + c*pz + d ) / tDenom;
return {
x: (px+t*(qx-px)),
y: (py+t*(qy-py)),
z: (pz+t*(qz-pz))
};
}
// Example (plane at y = 10 and perpendicular line from the origin)
console.log(JSON.stringify(findLinePlaneIntersectionCoords(0,0,0,0,1,0,0,1,0,-10)));
// Example (no intersection, plane and line are parallel)
console.log(JSON.stringify(findLinePlaneIntersectionCoords(0,0,0,0,0,1,0,1,0,-10)));
基于此Matlab 代码(减去交叉检查),在 Python
# n: normal vector of the Plane
# V0: any point that belongs to the Plane
# P0: end point 1 of the segment P0P1
# P1: end point 2 of the segment P0P1
n = np.array([1., 1., 1.])
V0 = np.array([1., 1., -5.])
P0 = np.array([-5., 1., -1.])
P1 = np.array([1., 2., 3.])
w = P0 - V0;
u = P1-P0;
N = -np.dot(n,w);
D = np.dot(n,u)
sI = N / D
I = P0+ sI*u
print I
结果
[-3.90909091 1.18181818 -0.27272727]
我以图形方式检查它似乎有效,
我相信这是之前共享的链接的更强大的实现
这个问题很老,但是由于有一个更方便的解决方案,我认为它可能会对某人有所帮助。
当用齐次坐标表示时,平面和线的交点非常优雅,但假设您只需要解决方案:
有一个向量 4x1 p 描述了平面,对于平面上的任何齐次点,p^T*x =0。接下来计算线 L=ab^T - ba^T 的采摘器坐标,其中 a = {point_1; 1}, b={point_2;1}, 都是 4x1 就行了
计算:x=L*p = {x0,x1,x2,x3}
x_intersect=({x0,x1,x2}/x3) 如果 x3 为零,则在欧几里得意义上没有交集。
只是为了扩展ZGorlock 的答案,我已经完成了 3D 矢量的点积、加法和缩放。这些计算的参考是点积、添加两个 3D 向量和缩放。注意:Vec3D 只是一个自定义类,它有点:x、y 和 z。
/**
* Determines the point of intersection between a plane defined by a point and a normal vector and a line defined by a point and a direction vector.
*
* @param planePoint A point on the plane.
* @param planeNormal The normal vector of the plane.
* @param linePoint A point on the line.
* @param lineDirection The direction vector of the line.
* @return The point of intersection between the line and the plane, null if the line is parallel to the plane.
*/
public static Vec3D lineIntersection(Vec3D planePoint, Vec3D planeNormal, Vec3D linePoint, Vec3D lineDirection) {
//ax × bx + ay × by
int dot = (int) (planeNormal.x * lineDirection.x + planeNormal.y * lineDirection.y);
if (dot == 0) {
return null;
}
// Ref for dot product calculation: https://www.mathsisfun.com/algebra/vectors-dot-product.html
int dot2 = (int) (planeNormal.x * planePoint.x + planeNormal.y * planePoint.y);
int dot3 = (int) (planeNormal.x * linePoint.x + planeNormal.y * linePoint.y);
int dot4 = (int) (planeNormal.x * lineDirection.x + planeNormal.y * lineDirection.y);
double t = (dot2 - dot3) / dot4;
float xs = (float) (lineDirection.x * t);
float ys = (float) (lineDirection.y * t);
float zs = (float) (lineDirection.z * t);
Vec3D lineDirectionScale = new Vec3D( xs, ys, zs);
float xa = (linePoint.x + lineDirectionScale.x);
float ya = (linePoint.y + lineDirectionScale.y);
float za = (linePoint.z + lineDirectionScale.z);
return new Vec3D(xa, ya, za);
}