给定一个边界框,定义如下:bounds.min.(x/y/z)
,bounds.max.(x/y/z)
,以及两个在3D空间中的点(表示为Vector3
对象),我该如何确定由这两个点组成的直线是否与边界框相交?
这里有一个C++实现的在线版本:线框交叉(http://www.3dkingdoms.com/weekly/weekly.php?a=3)
另一个链接,提供了许多交叉测试的参考文献和代码:http://www.realtimerendering.com/intersections.html
如果您想了解更多关于交叉测试的内容,这本书是圣经:实时碰撞检测(亚马逊)
编辑:这篇论文中的算法(一种高效且强大的光线盒相交算法,Amy Williams、Steve Barrus、R. Keith Morley和Peter Shirley;图形、GPU和游戏工具杂志,第10卷(1),49-54,2005年)看起来特别简洁,并且也附带有(C++)源代码。
这是一段代码,看起来可以正常工作,从Greg S的答案转换成了C#:
bool CheckLineBox(Vector3 B1, Vector3 B2, Vector3 L1, Vector3 L2, ref Vector3 Hit)
{
if (L2.x < B1.x && L1.x < B1.x) return false;
if (L2.x > B2.x && L1.x > B2.x) return false;
if (L2.y < B1.y && L1.y < B1.y) return false;
if (L2.y > B2.y && L1.y > B2.y) return false;
if (L2.z < B1.z && L1.z < B1.z) return false;
if (L2.z > B2.z && L1.z > B2.z) return false;
if (L1.x > B1.x && L1.x < B2.x &&
L1.y > B1.y && L1.y < B2.y &&
L1.z > B1.z && L1.z < B2.z)
{
Hit = L1;
return true;
}
if ((GetIntersection(L1.x - B1.x, L2.x - B1.x, L1, L2, ref Hit) && InBox(Hit, B1, B2, 1))
|| (GetIntersection(L1.y - B1.y, L2.y - B1.y, L1, L2, ref Hit) && InBox(Hit, B1, B2, 2))
|| (GetIntersection(L1.z - B1.z, L2.z - B1.z, L1, L2, ref Hit) && InBox(Hit, B1, B2, 3))
|| (GetIntersection(L1.x - B2.x, L2.x - B2.x, L1, L2, ref Hit) && InBox(Hit, B1, B2, 1))
|| (GetIntersection(L1.y - B2.y, L2.y - B2.y, L1, L2, ref Hit) && InBox(Hit, B1, B2, 2))
|| (GetIntersection(L1.z - B2.z, L2.z - B2.z, L1, L2, ref Hit) && InBox(Hit, B1, B2, 3)))
return true;
return false;
}
bool GetIntersection(float fDst1, float fDst2, Vector3 P1, Vector3 P2, ref Vector3 Hit)
{
if ((fDst1 * fDst2) >= 0.0f) return false;
if (fDst1 == fDst2) return false;
Hit = P1 + (P2 - P1) * (-fDst1 / (fDst2 - fDst1));
return true;
}
bool InBox(Vector3 Hit, Vector3 B1, Vector3 B2, int Axis)
{
if (Axis == 1 && Hit.z > B1.z && Hit.z < B2.z && Hit.y > B1.y && Hit.y < B2.y) return true;
if (Axis == 2 && Hit.z > B1.z && Hit.z < B2.z && Hit.x > B1.x && Hit.x < B2.x) return true;
if (Axis == 3 && Hit.x > B1.x && Hit.x < B2.x && Hit.y > B1.y && Hit.y < B2.y) return true;
return false;
}
GetIntersection
的第一个条件不匹配,第二个条件怎么可能匹配呢? - ishahak基于SpikeX答案和glMatrix的JavaScript版本:
// all args are Vec3, Hit will be filled by this algo
function checkLineBox( B1, B2, L1, L2, Hit)
{
if (L2[0] < B1[0] && L1[0] < B1[0]) return false;
if (L2[0] > B2[0] && L1[0] > B2[0]) return false;
if (L2[1] < B1[1] && L1[1] < B1[1]) return false;
if (L2[1] > B2[1] && L1[1] > B2[1]) return false;
if (L2[2] < B1[2] && L1[2] < B1[2]) return false;
if (L2[2] > B2[2] && L1[2] > B2[2]) return false;
if (L1[0] > B1[0] && L1[0] < B2[0] &&
L1[1] > B1[1] && L1[1] < B2[1] &&
L1[2] > B1[2] && L1[2] < B2[2])
{
vec3.set( L1, Hit);
return true;
}
if ((getIntersection(L1[0] - B1[0], L2[0] - B1[0], L1, L2, Hit) && inBox(Hit, B1, B2, 1))
|| (getIntersection(L1[1] - B1[1], L2[1] - B1[1], L1, L2, Hit) && inBox(Hit, B1, B2, 2))
|| (getIntersection(L1[2] - B1[2], L2[2] - B1[2], L1, L2, Hit) && inBox(Hit, B1, B2, 3))
|| (getIntersection(L1[0] - B2[0], L2[0] - B2[0], L1, L2, Hit) && inBox(Hit, B1, B2, 1))
|| (getIntersection(L1[1] - B2[1], L2[1] - B2[1], L1, L2, Hit) && inBox(Hit, B1, B2, 2))
|| (getIntersection(L1[2] - B2[2], L2[2] - B2[2], L1, L2, Hit) && inBox(Hit, B1, B2, 3)))
return true;
return false;
}
var temp = vec3.create();
function getIntersection( fDst1, fDst2, P1, P2, Hit)
{
if ((fDst1 * fDst2) >= 0) return false;
if (fDst1 == fDst2) return false;
vec3.subtract(P2, P1, temp);
vec3.scale( temp, (-fDst1 / (fDst2 - fDst1)));
vec3.add( temp, P1, Hit);
return true;
}
function inBox(Hit, B1, B2, Axis)
{
if (Axis == 1 && Hit[2] > B1[2] && Hit[2] < B2[2] && Hit[1] > B1[1] && Hit[1] < B2[1]) return true;
if (Axis == 2 && Hit[2] > B1[2] && Hit[2] < B2[2] && Hit[0] > B1[0] && Hit[0] < B2[0]) return true;
if (Axis == 3 && Hit[0] > B1[0] && Hit[0] < B2[0] && Hit[1] > B1[1] && Hit[1] < B2[1]) return true;
return false;
}
你可以将包围盒表示为12个三角形(每个面2个)。然后,你可以检查你的线段与它们中的每一个的相交情况。我有一个线段-三角形相交函数,但它是为我的自己的软件渲染引擎编写的,而不是为D3D编写的。如果你需要代码,我可以尝试转换它。
添加一个Python实现。请不要在没有进行广泛测试的情况下依赖它!
from __future__ import annotations # allow using Vec3 within Vec3
from dataclasses import dataclass
# based on https://gist.github.com/davidnuon/3816736
@dataclass
class Vec3:
# default of 'inf' means 'unset'
x: float = float('inf')
y: float = float('inf')
z: float = float('inf')
# String represntation
def __str__(self):
return '<%s, %s, %s>' % (self.x, self.y, self.z)
# Produce a copy of itself
def __copy(self):
return Vec3(self.x, self.y, self.z)
# Signing
def __neg__(self):
return Vec3(-self.x, -self.y, -self.z)
# Scalar Multiplication
def __mul__(self, number):
return Vec3(self.x * number, self.y * number, self.z * number)
# Arithmetic Operations
def __add__(self, operand):
return Vec3(self.x + operand.x, self.y + operand.y, self.z + operand.z)
def __sub__(self, operand):
return self.__copy() + -operand
def assign_from(self, another: Vec3):
self.x = another.x
self.y = another.y
self.z = another.z
class Geometry:
# based on https://dev59.com/Pk_Sa4cB1Zd3GeqP8gea#3235596
@staticmethod
def CheckLineBox(B1: Vec3, B2: Vec3, L1: Vec3, L2: Vec3, Hit: Vec3) -> bool:
def GetIntersection(fDst1: float, fDst2: float, P1: Vec3, P2: Vec3) -> bool:
if (fDst1 * fDst2) >= 0.0: # both line values are at same side relative to box side
return False
if fDst1 == fDst2: # can't happen!
return False
Hit.assign_from(P1 + (P2 - P1) * (-fDst1 / (fDst2 - fDst1)))
return True
def InBox(Axis: int):
if Axis == 1 and B1.z < Hit.z < B2.z and B1.y < Hit.y < B2.y:
return True
if Axis == 2 and B1.z < Hit.z < B2.z and B1.x < Hit.x < B2.x:
return True
if Axis == 3 and B1.x < Hit.x < B2.x and B1.y < Hit.y < B2.y:
return True
return False
def VerifyBox():
# box must be specified correctly with nearest and farthest corners
ok = B1.x < B2.x and B1.y < B2.y and B1.z < B2.z
if not ok:
print('bad box data')
return ok
print(f'Box: ({B1}:{B2}), Line: {L1}:{L2}')
if not VerifyBox():
return False
# if at any axis, the line values are before/after the box, return False:
if ((L2.x < B1.x and L1.x < B1.x) or
(L2.x > B2.x and L1.x > B2.x) or
(L2.y < B1.y and L1.y < B1.y) or
(L2.y > B2.y and L1.y > B2.y) or
(L2.z < B1.z and L1.z < B1.z) or
(L2.z > B2.z and L1.z > B2.z)):
return False
# if L1 is within the box, return it
if B1.x < L1.x < B2.x and B1.y < L1.y < B2.y and B1.z < L1.z < B2.z:
Hit.assign_from(L1)
return True
if ((GetIntersection(L1.x - B1.x, L2.x - B1.x, L1, L2) and InBox(1))
or (GetIntersection(L1.y - B1.y, L2.y - B1.y, L1, L2) and InBox(2))
or (GetIntersection(L1.z - B1.z, L2.z - B1.z, L1, L2) and InBox(3))
or (GetIntersection(L1.x - B2.x, L2.x - B2.x, L1, L2) and InBox(1))
or (GetIntersection(L1.y - B2.y, L2.y - B2.y, L1, L2) and InBox(2))
or (GetIntersection(L1.z - B2.z, L2.z - B2.z, L1, L2) and InBox(3))):
return True
return False
g = Geometry()
hit = Vec3()
rc = g.CheckLineBox(Vec3(0, 0, 0), Vec3(3, 3, 3), Vec3(-1, -1, -1), Vec3(0.1, 0, 0), hit)
print(f'rc={rc}, hit={hit}')