检查 ofRay 是否与 ofMeshFace 相交
Check if ofRay intersects with ofMeshFace
我已经尝试了 openframeworks 论坛和文档中的许多代码示例,但我找不到一个可以做我想做的事。
我有一个 ofRay(来自 ofxRay)和一个 of3dPrimitive 列表。我试图弄清楚光线是否与图元相交,如果是,则要知道光线与哪个图元相交 "first"(例如,哪个最接近屏幕)。
void renderer::selectPrimitive(int x, int y, bool shiftHeld)
{
ofVec3f screenToWorld = (**cam).screenToWorld(ofVec3f(x, y, 0.0));
primitive* intersectPrim = nullptr;
int distanceClosest = std::numeric_limits<int>::max();
ofVec3f vectNow = (screenToWorld - (**cam).getPosition());
vectNow = vectNow.normalize();
ofRay ray((**cam).getPosition(), vectNow, true);
// To draw the ray on screen, for debugging
// rays.push_back(ray);
for (primitive& p : *scn)
{
if (!shiftHeld)
{
p.setSelected(false);
}
float* distance = new float(0);
bool found = p.checkIntersectionPlaneAndLine(ray, distance);
if (found)// && *distance >= 0 && *distance < distanceClosest)
{
intersectPrim = &p;
//distanceClosest = *distance;
}
}
if (distanceClosest < (std::numeric_limits<int>::max() - 1))
{
intersectPrim->setSelected(!intersectPrim->getSelected());
std::cout << "Selected Primitive" << std::endl;
}
else
{
std::cout << "Selected Nothing" << std::endl;
}
}
以下是我尝试过的不同方法,这些方法是根据许多网站上的许多示例拼凑而成的,但其中 none 可以正常工作。
第一次尝试:
bool primitive3d::calcTriangleIntersection(ofRay ray, float *result) const {
ofMesh mesh = prim->getMesh();
std::vector<ofMeshFace> indices = mesh.getUniqueFaces();
for (std::vector<ofMeshFace>::iterator i = indices.begin(); i != indices.end(); ++i) {
ofMeshFace face = *i;
ofVec3f edge1, edge2, tvec, pvec, qvec;
float det;
float u, v;
const float EPSILON = 0.000001f;
edge1 = face.getVertex(1) - face.getVertex(0);
edge2 = face.getVertex(2) - face.getVertex(0);
pvec = ray.t.getCrossed(edge2);
det = edge1.dot(pvec);
#if 0 // we don't want to backface cull
if (det >= EPSILON)
{
tvec = getOrigin() - vert0;
u = tvec.dot(pvec);
if (!((u < 0.0f) || (u > det)))
{
qvec = tvec.getCrossed(edge1);
v = getDirection().dot(qvec);
if (!(v < 0.0f || u + v > det))
{
*result = edge2.dot(qvec) / det;
return true;
}
}
}
#else
if (!(det > -EPSILON && det < EPSILON))
{
float inv_det = 1.0f / det;
tvec = ray.s - face.getVertex(0);
u = tvec.dot(pvec) * inv_det;
if (!(u < 0.0f || u > 1.0f))
{
qvec = tvec.getCrossed(edge1);
v = ray.t.dot(qvec) * inv_det;
if (!(v < 0.0f || u + v > 1.0f))
{
*result = edge2.dot(qvec) * inv_det;
return true;
}
}
}
#endif
}
return false;
}
第二次尝试:
bool primitive3d::checkIntersectionPlaneAndLine(ofRay ray, float *result) const {
ofMesh mesh = prim->getMesh();
std::vector<ofMeshFace> indices = mesh.getUniqueFaces();
for (std::vector<ofMeshFace>::iterator i = indices.begin(); i != indices.end(); ++i)
{
ofMeshFace face = *i;
ofVec3f P1, P2;
P1 = ray.getStart();
P2 = ray.getEnd();
ofVec3f p1, p2, p3;
p1 = face.getVertex(0);
p2 = face.getVertex(1);
p3 = face.getVertex(2);
ofVec3f v1 = p1 - p2;
ofVec3f v2 = p3 - p2;
float a, b, c, d;
a = v1.y * v2.z - v1.z * v2.y;
b = -(v1.x * v2.z - v1.z * v2.x);
c = v1.x * v2.y - v1.y * v2.x;
d = -(a * p1.x + b * p1.y + c * p1.z);
ofVec3f O = P1;
ofVec3f V = P2 - P1;
float t;
t = -(a * O.x + b * O.y + c * O.z + d) / (a * V.x + b * V.y + c * V.z);
ofVec3f p = O + V * t;
float xmin = std::min(P1.x, P2.x);
float ymin = std::min(P1.y, P2.y);
float zmin = std::min(P1.z, P2.z);
float xmax = std::max(P1.x, P2.x);
float ymax = std::max(P1.y, P2.y);
float zmax = std::max(P1.z, P2.z);
if (inside(p, xmin, xmax, ymin, ymax, zmin, zmax)) {
*result = p.length();
return true;
}
}
return false;
}
bool primitive3d::inside(ofVec3f p, float xmin, float xmax, float ymin, float ymax, float zmin, float zmax) const {
if (p.x >= xmin && p.x <= xmax && p.y >= ymin && p.y <= ymax && p.z >= zmin && p.z <= zmax)
return true;
return false;
}
第三次尝试:
#define SMALL_NUM 0.00000001 // anything that avoids division overflow
// dot product (3D) which allows vector operations in arguments
#define dot(u,v) ((u).x * (v).x + (u).y * (v).y + (u).z * (v).z)
bool primitive3d::checkIntersectionTriangleRay(ofRay ray, ofPoint* inter)
{
ofMesh mesh = prim->getMesh();
std::vector<ofMeshFace> indices = mesh.getUniqueFaces();
for (std::vector<ofMeshFace>::iterator i = indices.begin(); i != indices.end(); ++i)
{
ofMeshFace triangle = *i;
ofVec3f u, v, n; // Vecs of triangle
ofVec3f dir, w0, w; // Vecs of ofRay
float r, a, b; // params to calc ray-plane intersect
// get triangle edge vectors and plane normal
u = triangle.getVertex(1) - triangle.getVertex(0);
v = triangle.getVertex(2) - triangle.getVertex(0);
n = u * v; // cross product
if (!(n == ofVec3f(0, 0, 0))) // if triangle is not degenerate
{
dir = ray.getEnd() - ray.getStart(); // ray direction vector
w0 = ray.getStart() - triangle.getVertex(0);
a = -dot(n, w0);
b = dot(n, dir);
if (!(fabs(b) < SMALL_NUM))
{ // if ray is not parallel to triangle
// get intersect point of ray with triangle plane
r = a / b;
if (!(r < 0.0)) // ray goes toward the triangle
{
// for a segment, also test if (r > 1.0) => no intersect
*inter = ray.getStart() + r * dir; // intersect point of ray and plane
// is I inside T?
float uu, uv, vv, wu, wv, D;
uu = dot(u, u);
uv = dot(u, v);
vv = dot(v, v);
w = *inter - triangle.getVertex(0);
wu = dot(w, u);
wv = dot(w, v);
D = uv * uv - uu * vv;
// get and test parametric coords
float s, t;
s = (uv * wv - vv * wu) / D;
if (!(s < 0.0 || s > 1.0)) // I is inside T
{
t = (uv * wu - uu * wv) / D;
if (!(t < 0.0 || (s + t) > 1.0)) // I is inside T
return true; // I is in T
}
}
}
}
}
return false;
}
我已经尝试了很多方法,但其中 none 行得通。我也将我的光线绘制到屏幕上,所以我知道它们确实被正确创建并且朝着正确的方向移动了无限距离
为了清楚起见,我删除了很多代码以使其易于阅读。我只是想念
// 在这里检测
部分在第二种方法中,因为我不知道如何让它工作。
假设您在互联网上尝试过大量 triangle/mesh 光线投射代码可以解决您的问题,我认为您的问题出在第一种方法中,您在其中设置了一个 "found" 变量,但是如果找到或选择具有最小距离的对象,请不要从方法中 return。
您提供的代码将return最后一个原语的命中测试结果。
如果您过度简化了代码,请再次 post 提供更多详细信息。
编辑:
您从网格面获得的坐标在对象 space 中。在任何计算之前,您需要将它们转换为世界 space,或者更好的是,将射线转换为对象 space。
请参阅此代码以了解工作实施:
https://github.com/mrdoob/three.js/blob/master/src/objects/Mesh.js
注意应用世界矩阵的 applyMatrix4 调用将它们带到相同的 space。
我已经尝试了 openframeworks 论坛和文档中的许多代码示例,但我找不到一个可以做我想做的事。
我有一个 ofRay(来自 ofxRay)和一个 of3dPrimitive 列表。我试图弄清楚光线是否与图元相交,如果是,则要知道光线与哪个图元相交 "first"(例如,哪个最接近屏幕)。
void renderer::selectPrimitive(int x, int y, bool shiftHeld)
{
ofVec3f screenToWorld = (**cam).screenToWorld(ofVec3f(x, y, 0.0));
primitive* intersectPrim = nullptr;
int distanceClosest = std::numeric_limits<int>::max();
ofVec3f vectNow = (screenToWorld - (**cam).getPosition());
vectNow = vectNow.normalize();
ofRay ray((**cam).getPosition(), vectNow, true);
// To draw the ray on screen, for debugging
// rays.push_back(ray);
for (primitive& p : *scn)
{
if (!shiftHeld)
{
p.setSelected(false);
}
float* distance = new float(0);
bool found = p.checkIntersectionPlaneAndLine(ray, distance);
if (found)// && *distance >= 0 && *distance < distanceClosest)
{
intersectPrim = &p;
//distanceClosest = *distance;
}
}
if (distanceClosest < (std::numeric_limits<int>::max() - 1))
{
intersectPrim->setSelected(!intersectPrim->getSelected());
std::cout << "Selected Primitive" << std::endl;
}
else
{
std::cout << "Selected Nothing" << std::endl;
}
}
以下是我尝试过的不同方法,这些方法是根据许多网站上的许多示例拼凑而成的,但其中 none 可以正常工作。
第一次尝试:
bool primitive3d::calcTriangleIntersection(ofRay ray, float *result) const {
ofMesh mesh = prim->getMesh();
std::vector<ofMeshFace> indices = mesh.getUniqueFaces();
for (std::vector<ofMeshFace>::iterator i = indices.begin(); i != indices.end(); ++i) {
ofMeshFace face = *i;
ofVec3f edge1, edge2, tvec, pvec, qvec;
float det;
float u, v;
const float EPSILON = 0.000001f;
edge1 = face.getVertex(1) - face.getVertex(0);
edge2 = face.getVertex(2) - face.getVertex(0);
pvec = ray.t.getCrossed(edge2);
det = edge1.dot(pvec);
#if 0 // we don't want to backface cull
if (det >= EPSILON)
{
tvec = getOrigin() - vert0;
u = tvec.dot(pvec);
if (!((u < 0.0f) || (u > det)))
{
qvec = tvec.getCrossed(edge1);
v = getDirection().dot(qvec);
if (!(v < 0.0f || u + v > det))
{
*result = edge2.dot(qvec) / det;
return true;
}
}
}
#else
if (!(det > -EPSILON && det < EPSILON))
{
float inv_det = 1.0f / det;
tvec = ray.s - face.getVertex(0);
u = tvec.dot(pvec) * inv_det;
if (!(u < 0.0f || u > 1.0f))
{
qvec = tvec.getCrossed(edge1);
v = ray.t.dot(qvec) * inv_det;
if (!(v < 0.0f || u + v > 1.0f))
{
*result = edge2.dot(qvec) * inv_det;
return true;
}
}
}
#endif
}
return false;
}
第二次尝试:
bool primitive3d::checkIntersectionPlaneAndLine(ofRay ray, float *result) const {
ofMesh mesh = prim->getMesh();
std::vector<ofMeshFace> indices = mesh.getUniqueFaces();
for (std::vector<ofMeshFace>::iterator i = indices.begin(); i != indices.end(); ++i)
{
ofMeshFace face = *i;
ofVec3f P1, P2;
P1 = ray.getStart();
P2 = ray.getEnd();
ofVec3f p1, p2, p3;
p1 = face.getVertex(0);
p2 = face.getVertex(1);
p3 = face.getVertex(2);
ofVec3f v1 = p1 - p2;
ofVec3f v2 = p3 - p2;
float a, b, c, d;
a = v1.y * v2.z - v1.z * v2.y;
b = -(v1.x * v2.z - v1.z * v2.x);
c = v1.x * v2.y - v1.y * v2.x;
d = -(a * p1.x + b * p1.y + c * p1.z);
ofVec3f O = P1;
ofVec3f V = P2 - P1;
float t;
t = -(a * O.x + b * O.y + c * O.z + d) / (a * V.x + b * V.y + c * V.z);
ofVec3f p = O + V * t;
float xmin = std::min(P1.x, P2.x);
float ymin = std::min(P1.y, P2.y);
float zmin = std::min(P1.z, P2.z);
float xmax = std::max(P1.x, P2.x);
float ymax = std::max(P1.y, P2.y);
float zmax = std::max(P1.z, P2.z);
if (inside(p, xmin, xmax, ymin, ymax, zmin, zmax)) {
*result = p.length();
return true;
}
}
return false;
}
bool primitive3d::inside(ofVec3f p, float xmin, float xmax, float ymin, float ymax, float zmin, float zmax) const {
if (p.x >= xmin && p.x <= xmax && p.y >= ymin && p.y <= ymax && p.z >= zmin && p.z <= zmax)
return true;
return false;
}
第三次尝试:
#define SMALL_NUM 0.00000001 // anything that avoids division overflow
// dot product (3D) which allows vector operations in arguments
#define dot(u,v) ((u).x * (v).x + (u).y * (v).y + (u).z * (v).z)
bool primitive3d::checkIntersectionTriangleRay(ofRay ray, ofPoint* inter)
{
ofMesh mesh = prim->getMesh();
std::vector<ofMeshFace> indices = mesh.getUniqueFaces();
for (std::vector<ofMeshFace>::iterator i = indices.begin(); i != indices.end(); ++i)
{
ofMeshFace triangle = *i;
ofVec3f u, v, n; // Vecs of triangle
ofVec3f dir, w0, w; // Vecs of ofRay
float r, a, b; // params to calc ray-plane intersect
// get triangle edge vectors and plane normal
u = triangle.getVertex(1) - triangle.getVertex(0);
v = triangle.getVertex(2) - triangle.getVertex(0);
n = u * v; // cross product
if (!(n == ofVec3f(0, 0, 0))) // if triangle is not degenerate
{
dir = ray.getEnd() - ray.getStart(); // ray direction vector
w0 = ray.getStart() - triangle.getVertex(0);
a = -dot(n, w0);
b = dot(n, dir);
if (!(fabs(b) < SMALL_NUM))
{ // if ray is not parallel to triangle
// get intersect point of ray with triangle plane
r = a / b;
if (!(r < 0.0)) // ray goes toward the triangle
{
// for a segment, also test if (r > 1.0) => no intersect
*inter = ray.getStart() + r * dir; // intersect point of ray and plane
// is I inside T?
float uu, uv, vv, wu, wv, D;
uu = dot(u, u);
uv = dot(u, v);
vv = dot(v, v);
w = *inter - triangle.getVertex(0);
wu = dot(w, u);
wv = dot(w, v);
D = uv * uv - uu * vv;
// get and test parametric coords
float s, t;
s = (uv * wv - vv * wu) / D;
if (!(s < 0.0 || s > 1.0)) // I is inside T
{
t = (uv * wu - uu * wv) / D;
if (!(t < 0.0 || (s + t) > 1.0)) // I is inside T
return true; // I is in T
}
}
}
}
}
return false;
}
我已经尝试了很多方法,但其中 none 行得通。我也将我的光线绘制到屏幕上,所以我知道它们确实被正确创建并且朝着正确的方向移动了无限距离
为了清楚起见,我删除了很多代码以使其易于阅读。我只是想念 // 在这里检测 部分在第二种方法中,因为我不知道如何让它工作。
假设您在互联网上尝试过大量 triangle/mesh 光线投射代码可以解决您的问题,我认为您的问题出在第一种方法中,您在其中设置了一个 "found" 变量,但是如果找到或选择具有最小距离的对象,请不要从方法中 return。
您提供的代码将return最后一个原语的命中测试结果。
如果您过度简化了代码,请再次 post 提供更多详细信息。
编辑:
您从网格面获得的坐标在对象 space 中。在任何计算之前,您需要将它们转换为世界 space,或者更好的是,将射线转换为对象 space。
请参阅此代码以了解工作实施: https://github.com/mrdoob/three.js/blob/master/src/objects/Mesh.js
注意应用世界矩阵的 applyMatrix4 调用将它们带到相同的 space。