Here is what I need:
Given a point(x,y,z) in 3d space, and a mesh compose of some vertices(x,y,z), to calculate and return the close point coordinate on that mesh.
The function probably like this:
bool closePointOnMesh(const Point& queryPoint, const Mesh& myMesh, float maxDistance);
I have done some searching, and probably I will choose octree to reduce the calculation.
But there are still many details that I can't understand:
1: How the octree node been subdivided, so each node contains may contains 0~some triangles? It is easier to subdivided the cell further based on vertices and just store vertices directly.
2: How the octree structure helps to reduce the calculation, I know if the cell is empty I will just disregard it. But do I need to get all the closest point within each triangle face in a octree cell to the queryPoint, so I finally get the most closest point of all? that sound still heavy. Beside it will be more easier if I just iter through all the triangles, get the closest point from them, which means no need for the octree???
3: Is there a fast way to get the closest point to a point within a triangle face?
4: how the maxDistance limit helps to reduce the calculation?
For #3, here's some code on how to get the closest point of a triangle. It projects the point onto the triangle's plane, and then clamps the barycentric coordinates to [0,1], and uses those values computes the closest point.
Copied below:
vector3 closesPointOnTriangle( const vector3 *triangle, const vector3 &sourcePosition )
{
vector3 edge0 = triangle[1] - triangle[0];
vector3 edge1 = triangle[2] - triangle[0];
vector3 v0 = triangle[0] - sourcePosition;
float a = edge0.dot( edge0 );
float b = edge0.dot( edge1 );
float c = edge1.dot( edge1 );
float d = edge0.dot( v0 );
float e = edge1.dot( v0 );
float det = a*c - b*b;
float s = b*e - c*d;
float t = b*d - a*e;
if ( s + t < det )
{
if ( s < 0.f )
{
if ( t < 0.f )
{
if ( d < 0.f )
{
s = clamp( -d/a, 0.f, 1.f );
t = 0.f;
}
else
{
s = 0.f;
t = clamp( -e/c, 0.f, 1.f );
}
}
else
{
s = 0.f;
t = clamp( -e/c, 0.f, 1.f );
}
}
else if ( t < 0.f )
{
s = clamp( -d/a, 0.f, 1.f );
t = 0.f;
}
else
{
float invDet = 1.f / det;
s *= invDet;
t *= invDet;
}
}
else
{
if ( s < 0.f )
{
float tmp0 = b+d;
float tmp1 = c+e;
if ( tmp1 > tmp0 )
{
float numer = tmp1 - tmp0;
float denom = a-2*b+c;
s = clamp( numer/denom, 0.f, 1.f );
t = 1-s;
}
else
{
t = clamp( -e/c, 0.f, 1.f );
s = 0.f;
}
}
else if ( t < 0.f )
{
if ( a+d > b+e )
{
float numer = c+e-b-d;
float denom = a-2*b+c;
s = clamp( numer/denom, 0.f, 1.f );
t = 1-s;
}
else
{
s = clamp( -e/c, 0.f, 1.f );
t = 0.f;
}
}
else
{
float numer = c+e-b-d;
float denom = a-2*b+c;
s = clamp( numer/denom, 0.f, 1.f );
t = 1.f - s;
}
}
return triangle[0] + s * edge0 + t * edge1;
}
Related
I am working on a simple raytracer in c++. I am currently implementing an intersection function but have encountered some issues.
For some reason, the collision detection only works for a tiny rectangle in my image. In the image below you can see that it draws the room quite fine for a small part of the screen but fails to do so for the rest of the scene. Only a small section gets drawn correctly.
Why does my intersection detection not work? I have included the code for the intersection and draw function below.
LoadTestModel(m_Model);
m_Light.position = glm::vec3(0.0f, -1.0f, 0.0);
m_Light.color = glm::vec3(0.f, 0.f, 0.f);
m_Light.ambient = glm::vec3(0.5f, 0.5f, 0.5f);
m_Camera.position = glm::vec3(0.0, 0.0, -2.0);
m_Camera.yaw = 0.0f;
}
void Lab2Scene::Draw(Window& window)
{
if (!m_RenderNext) return;
m_RenderNext = false;
for (uint32_t y = 0; y < window.GetHeight(); ++y)
{
for (uint32_t x = 0; x < window.GetWidth(); ++x)
{
Ray ray = {};
glm::vec3 d(x - (window.GetWidth() / 2), y - (window.GetHeight() / 2), (window.GetHeight() / 2));
d = glm::normalize(d);
ray.direction = d * m_Camera.GetRotationY();
ray.start = m_Camera.position;
// Find the closest intersection of the casted ray.
Intersection nearest_intersection = {};
if (ClosestIntersection(ray, m_Model, nearest_intersection))
{
//window.PutPixel(x, y, glm::vec3(1.f, 0.f, 0.f));
window.PutPixel(x, y, DirectLight(m_Light, nearest_intersection, m_Model) + m_Model[nearest_intersection.triangleIndex].color * m_Light.ambient); // DirectLight(m_Light, intersection, m_Model)
}
else
{
window.PutPixel(x, y, m_Light.color);
}
}
}
}
bool Lab2Scene::ClosestIntersection(const Ray& ray, const std::vector<Triangle>& triangles, Intersection& intersection)
{
float m = std::numeric_limits<float>::max();
intersection.distance = m;
bool inters = false;
for (int i = 0; i < triangles.size(); ++i) {
float dot = glm::dot(ray.direction, triangles[i].normal);
if (dot != 0) {
using glm::vec3;
using glm::mat3;
vec3 v0 = triangles[i].v0;
vec3 v1 = triangles[i].v1;
vec3 v2 = triangles[i].v2;
vec3 e1 = v1 - v0;
vec3 e2 = v2 - v0;
vec3 b = ray.start - v0;
mat3 A(-ray.direction, e1, e2);
vec3 x = glm::inverse(A) * b;
if (x[1] >= 0 && x[2] >= 0 && x[1] + x[2] <= 1 && x[0] >= 0) {
vec3 intersect = ray.start + (x[0] * ray.direction);
if (glm::distance(ray.start, intersect) <= intersection.distance) {
intersection.position = intersect;
intersection.distance = glm::distance(ray.start, intersect);
intersection.triangleIndex = i;
inters = true;
}
}
}
}
return inters;
}
I'm trying to use bullet physics to draw a ray to hit a game object in the scene so I can select it, i am using the camera matrix to draw a ray and then pick an object in space and then look through a list of game objects and look for the same location.
On Mouse press I have the following code, it seems to be off and only picks the items some times:
glm::vec4 lRayStart_NDC(
((float)lastX / (float)RECT_WIDTH - 0.5f) * 2.0f,
((float)lastY / (float)RECT_HEIGHT - 0.5f) * 2.0f,
-1.0,
1.0f
);
glm::vec4 lRayEnd_NDC(
((float)lastX / (float)RECT_WIDTH - 0.5f) * 2.0f,
((float)lastY / (float)RECT_HEIGHT - 0.5f) * 2.0f,
0.0,
1.0f
);
projection = glm::perspective(glm::radians(SceneManagement::getInstance()->MainCamera->GetVOW()), (float)RECT_WIDTH / (float)RECT_HEIGHT, 0.1f, 100.0f);
glm::mat4 InverseProjectionMatrix = glm::inverse(projection);
view = SceneManagement::getInstance()->MainCamera->GetViewMatrix();
glm::mat4 InverseViewMatrix = glm::inverse(view);
glm::vec4 lRayStart_camera = InverseProjectionMatrix * lRayStart_NDC;
lRayStart_camera /= lRayStart_camera.w;
glm::vec4 lRayStart_world = InverseViewMatrix * lRayStart_camera;
lRayStart_world /= lRayStart_world.w;
glm::vec4 lRayEnd_camera = InverseProjectionMatrix * lRayEnd_NDC;
lRayEnd_camera /= lRayEnd_camera.w;
glm::vec4 lRayEnd_world = InverseViewMatrix * lRayEnd_camera;
lRayEnd_world /= lRayEnd_world.w;
glm::vec3 lRayDir_world(lRayEnd_world - lRayStart_world);
lRayDir_world = glm::normalize(lRayDir_world);
glm::vec3 out_end = SceneManagement::getInstance()->MainCamera->GetCamPosition() + SceneManagement::getInstance()->MainCamera->GetCamFront() * 1000.0f;
btCollisionWorld::ClosestRayResultCallback RayCallback(
btVector3(SceneManagement::getInstance()->MainCamera->GetCamPosition().x, SceneManagement::getInstance()->MainCamera->GetCamPosition().y, SceneManagement::getInstance()->MainCamera->GetCamPosition().z),
btVector3(out_end.x, out_end.y, out_end.z)
);
PhysicsManager::getInstance()->dynamicsWorld->rayTest(
btVector3(SceneManagement::getInstance()->MainCamera->GetCamPosition().x, SceneManagement::getInstance()->MainCamera->GetCamPosition().y, SceneManagement::getInstance()->MainCamera->GetCamPosition().z),
btVector3(out_end.x, out_end.y, out_end.z),
RayCallback
);
if (RayCallback.hasHit())
{
btTransform position = RayCallback.m_collisionObject->getInterpolationWorldTransform();
printf("Collision \n");
for (int i = 0; i < SceneManagement::getInstance()->gObjects.size(); i++)
{
if (SceneManagement::getInstance()->gObjects.at(i)->transform.Position.x == position.getOrigin().getX() &&
SceneManagement::getInstance()->gObjects.at(i)->transform.Position.y == position.getOrigin().getY() &&
SceneManagement::getInstance()->gObjects.at(i)->transform.Position.z == position.getOrigin().getZ())
{
int select = i;
SceneManagement::getInstance()->SelectedGameObject = SceneManagement::getInstance()->gObjects.at(select);
SceneManagement::getInstance()->SelectedGameObject->DisplayInspectorUI();
return;
}
}
}
This check
SceneManagement::getInstance()->gObjects.at(i)->transform.Position.x == position.getOrigin().getX() &&
SceneManagement::getInstance()->gObjects.at(i)->transform.Position.y == position.getOrigin().getY() &&
SceneManagement::getInstance()->gObjects.at(i)->transform.Position.z == position.getOrigin().getZ()
fails due to numerical precision issues.
You should check the "equality" of vectors only up to some precision.
Please, use some distance function and the following check instead of your if() condition:
const double EPSILON = 1e-4; // experiment with this value
auto objPos = SceneManagement::getInstance()->gObjects.at(i)->transform.Position;
bool isWithinRange = distance3D(objPos, position.getOrigin()) < EPSILON;
if (isWithinRange)
{
int select = i;
...
}
The distance3D function is simply the euclidean distance in 3D. The glm::distance function should do, just convert both vectors to glm::vec3 format.
i need to implement arcball camera. I got something similar, but it works very crookedly (the angle changes sharply, when turning to the right / left, the camera raises up / down strongly).
Here is my source code, can you tell me where I went wrong:
bool get_arcball_vec(double x, double y, glm::vec3& a)
{
glm::vec3 vec = glm::vec3((2.0 * x) / window.getWidth() - 1.0, 1.0 - (2.0 * y) / window.getHeight(), 0.0);
if (glm::length(vec) >= 1.0)
{
vec = glm::normalize(vec);
}
else
{
vec.z = sqrt(1.0 - pow(vec.x, 2.0) - pow(vec.y, 2.0));
}
a = vec;
return true;
}
...
void onMouseMove(double x, double y) {
if (rightMouseButtonPressed) {
glm::vec3 a,b;
cur_mx = x;
cur_my = y;
if (cur_mx != last_mx || cur_my != last_my)
if (get_arcball_vec(last_mx, last_my, a) && get_arcball_vec(cur_mx, cur_my, b))
viewport.getCamera().orbit(a,b);
last_mx = cur_mx;
last_my = cur_my;
...
void Camera::orbit(glm::vec3 a, glm::vec3 b)
{
forward = calcForward();
right = calcRight();
double alpha = acos(glm::min(1.0f, glm::dot(b, a)));
glm::vec3 axis = glm::cross(a, b);
glm::mat4 rotationComponent = glm::mat4(1.0f);
rotationComponent[0] = glm::vec4(right, 0.0f);
rotationComponent[1] = glm::vec4(up, 0.0f);
rotationComponent[2] = glm::vec4(forward, 0.0f);
glm::mat4 toWorldCameraSpace = glm::transpose(rotationComponent);
axis = toWorldCameraSpace * glm::vec4(axis, 1.0);
glm::mat4 orbitMatrix = glm::rotate(glm::mat4(1.0f), (float)alpha, axis);
eye = glm::vec4(target, 1.0) + orbitMatrix * glm::vec4(eye - target, 1.0f);
up = orbitMatrix * glm::vec4(up, 1.0f);
}
I use this code to map 2D mouse position to the sphere:
Vector3 GetArcBallVector(const Vector2f & mousePos) {
float radiusSquared = 1.0; //squared radius of the sphere
//compute mouse position from the centre of screen to interval [-half, +half]
Vector3 pt = Vector3(
mousePos.x - halfScreenW,
halfScreenH - mousePos.y,
0.0f
);
//if length squared is smaller than sphere diameter
//point is inside
float lengthSqr = pt.x * pt.x + pt.y * pt.y;
if (lengthSqr < radiusSquared){
//inside
pt.z = std::sqrtf(radiusSquared - lengthSqr);
}
else {
pt.z = 0.0f;
}
pt.z *= -1;
return pt;
}
To calculate rotation, I use the last (startPt) and current (endPt) mapped position and do:
Quaternion actRot = Quaternion::Identity();
Vector3 axis = Vector3::Cross(endPt, startPt);
if (axis.LengthSquared() > MathUtils::EPSILON) {
float angleCos = Vector3::Dot(endPt, startPt);
actRot = Quaternion(axis.x, axis.y, axis.z, angleCos);
}
I prefer to use Quaternions over matrices since they are easy to multiply (for acumulated rotation) and interpolate (for some smooting).
I have a triangle defined by 3 points in 3d space. I also have a line segment defined by 2 points in 3d space. I want to know if they intersect. I don't really need to know the point of intersection.
I don't know any calculus but I know some trig. I know some about matrices but I understand vectors well (3d vectors specifically). Please keep it simple.
Can you walk me through the example problem:
triangle:
a: -4, 3, 0
b: 4, 3, 0
c: -3, -5, 4
line segment:
d: 1, -2, 0
e: -2, 6, 2
EDIT:
I am going to use this in a c++ physics engine.
One answer involved tetrahedron volume calculation from 4 vertices. Please provide formula or show it in code.
UPDATE:
As meowgoesthedog pointed out, I could try to use the Moller-Trumbore intersection algorithm. See my answer below for an alternate solution.
Here is one way to solve your problem. Compute the volume of the tetrahedron Td =
(a,b,c,d) and Te = (a,b,c,e). If either volume of Td or Te is zero, then one endpoint of the
segment de lies on the plane containing triangle (a,b,c). If the volumes of Td and Te have the same sign,
then de lies strictly to one side, and there is no intersection. If Td and Te have opposite
signs, then de crosses the plane containing (a,b,c).
From there there are several strategies. One is to compute the point p where de crosses
that plane. Then project down to 2D, and solve the point-in-triangle problem in 2D.
Another route is to compute the volumes of the tetrahedra (a,b,d,e), (b,c,d,e), and (c,a,d,e). Then only if all three have the same sign, does de intersect the triangle (a,b,c).
How to compute the volume of a tetrahedron from its corner coordinates is all over the
web, and also in Computational Geometry in C.
I implemented the great answer that Joseph gave in python and thought I would share. The function takes a set of line segments and triangles and computes for each line segment if it intersects any of the given triangles.
The first input to the function is a 2xSx3 array of line segments where the first index specifies the start or end point of the segment, the second index refers to the s^th line segment, and the third index points to the x, y,z coordinate of the line segment point.
The second input is a 3XTX3 array of triangle vertices, where the first index specifies one of the three vertices (which don't have to be in any particular order), the second index refers to the t^th triangle, and the third index points to the x,y,z coordinates the the triangle vertex.
The output of this function is a binary array of size S which tells you whether the s^th line segment intersects any of the triangles given. If you want to know which triangles the segments intersect, then just remove the summation of the last line of the function.
def signedVolume(a, b, c, d):
"""Computes the signed volume of a series of tetrahedrons defined by the vertices in
a, b c and d. The ouput is an SxT array which gives the signed volume of the tetrahedron defined
by the line segment 's' and two vertices of the triangle 't'."""
return np.sum((a-d)*np.cross(b-d, c-d), axis=2)
def segmentsIntersectTriangles(s, t):
"""For each line segment in 's', this function computes whether it intersects any of the triangles
given in 't'."""
# compute the normals to each triangle
normals = np.cross(t[2]-t[0], t[2]-t[1])
normals /= np.linalg.norm(normals, axis=1)[:, np.newaxis]
# get sign of each segment endpoint, if the sign changes then we know this segment crosses the
# plane which contains a triangle. If the value is zero the endpoint of the segment lies on the
# plane.
# s[i][:, np.newaxis] - t[j] -> S x T x 3 array
sign1 = np.sign(np.sum(normals*(s[0][:, np.newaxis] - t[2]), axis=2)) # S x T
sign2 = np.sign(np.sum(normals*(s[1][:, np.newaxis] - t[2]), axis=2)) # S x T
# determine segments which cross the plane of a triangle. 1 if the sign of the end points of s is
# different AND one of end points of s is not a vertex of t
cross = (sign1 != sign2)*(sign1 != 0)*(sign2 != 0) # S x T
# get signed volumes
v1 = np.sign(signedVolume(t[0], t[1], s[0][:, np.newaxis], s[1][:, np.newaxis])) # S x T
v2 = np.sign(signedVolume(t[1], t[2], s[0][:, np.newaxis], s[1][:, np.newaxis])) # S x T
v3 = np.sign(signedVolume(t[2], t[0], s[0][:, np.newaxis], s[1][:, np.newaxis])) # S x T
same_volume = np.logical_and((v1 == v2), (v2 == v3)) # 1 if s and t have same sign in v1, v2 and v3
return (np.sum(cross*same_volume, axis=1) > 0)
Thanks for the help! This is an alternate solution. The question was for c++ and as meowgoesthedog pointed out, I could try to use the Moller-Trumbore intersection algorithm. This is what I came up with:
#include <math.h>
class vec3 {
public:
float x, y, z;
float dot(const vec3 & b) {
return vec3::x * b.x + vec3::y * b.y + vec3::z * b.z;
}
vec3 cross(const vec3 & b) {
return vec3::vec3(
vec3::y * b.z - vec3::z * b.y,
vec3::z * b.x - vec3::x * b.z,
vec3::x * b.y - vec3::y * b.x
);
}
vec3 normalize() {
const float s = 1.0f / sqrtf(vec3::x * vec3::x + vec3::y * vec3::y + vec3::z * vec3::z);
return vec3::vec3(vec3::x * s, vec3::y * s, vec3::z * s);
}
vec3 operator+(const vec3 & b) {
return vec3::vec3(
vec3::x + b.x,
vec3::y + b.y,
vec3::z + b.z
);
}
vec3 operator+=(const vec3 & b) {
*this = vec3::operator+(b);
return *this;
}
vec3 operator-(const vec3 & b) {
return vec3::vec3(
vec3::x - b.x,
vec3::y - b.y,
vec3::z - b.z
);
}
vec3 operator-=(const vec3 & b) {
*this = vec3::operator-(b);
return *this;
}
vec3 operator*(const vec3 & b) {
return vec3::vec3(
vec3::x * b.x,
vec3::y * b.y,
vec3::z * b.z
);
}
vec3 operator*=(const vec3 & b) {
*this = vec3::operator*(b);
return *this;
}
vec3 operator*(float b) {
return vec3::vec3(
vec3::x * b,
vec3::y * b,
vec3::z * b
);
}
vec3 operator*=(float b) {
*this = vec3::operator*(b);
return *this;
}
vec3 operator/(const vec3 & b) {
return vec3::vec3(
vec3::x / b.x,
vec3::y / b.y,
vec3::z / b.z
);
}
vec3 operator/=(const vec3 & b) {
*this = vec3::operator/(b);
return *this;
}
vec3 operator/(float b) {
return vec3::vec3(
vec3::x * b,
vec3::y * b,
vec3::z * b
);
}
vec3 operator/=(float b) {
*this = vec3::operator/(b);
return *this;
}
vec3(float x, float y, float z) {
vec3::x = x;
vec3::y = y;
vec3::z = z;
}
vec3(float x) {
vec3::x = x;
vec3::y = x;
vec3::z = x;
}
vec3() {
//
}
~vec3() {
//
}
};
#define EPSILON 0.000001f
bool lineSegIntersectTri(
vec3 line[2],
vec3 tri[3],
vec3 * point
) {
vec3 e0 = tri[1] - tri[0];
vec3 e1 = tri[2] - tri[0];
vec3 dir = line[1] - line[0];
vec3 dir_norm = dir.normalize();
vec3 h = dir_norm.cross(e1);
const float a = e0.dot(h);
if (a > -EPSILON && a < EPSILON) {
return false;
}
vec3 s = line[0] - tri[0];
const float f = 1.0f / a;
const float u = f * s.dot(h);
if (u < 0.0f || u > 1.0f) {
return false;
}
vec3 q = s.cross(e0);
const float v = f * dir_norm.dot(q);
if (v < 0.0f || u + v > 1.0f) {
return false;
}
const float t = f * e1.dot(q);
if (t > EPSILON && t < sqrtf(dir.dot(dir))) { // segment intersection
if (point) {
*point = line[0] + dir_norm * t;
}
return true;
}
return false;
}
For running a few tests:
#include <stdio.h>
const char * boolStr(bool b) {
if (b) {
return "true";
}
return "false";
}
int main() {
vec3 tri[3] = {
{ -1.0f, -1.0f, 0.0f },
{ 1.0f, -1.0f, 0.0f },
{ 1.0f, 1.0f, 0.0f },
};
vec3 line0[2] = { // should intersect
{ 0.5f, -0.5f, -1.0f },
{ 0.5f, -0.5f, 1.0f },
};
vec3 line1[2] = { // should not intersect
{ -0.5f, 0.5f, -1.0f },
{ -0.5f, 0.5f, 1.0f },
};
printf(
"line0 intersects? : %s\r\n"
"line1 intersects? : %s\r\n",
boolStr(lineSegIntersectTri(line0, tri, NULL)),
boolStr(lineSegIntersectTri(line1, tri, NULL))
);
return 0;
}
C# version:
public class AlgoritmoMollerTrumbore
{
private const double EPSILON = 0.0000001;
public static bool lineIntersectTriangle(Point3D[] line,
Point3D[] triangle,
out Point3D outIntersectionPoint)
{
outIntersectionPoint = new Point3D(0, 0, 0);
Point3D rayOrigin = line[0];
Vector3D rayVector = Point3D.Subtract(line[1], line[0]);
rayVector.Normalize();
Point3D vertex0 = triangle[0];
Point3D vertex1 = triangle[1];
Point3D vertex2 = triangle[2];
Vector3D edge1 = Point3D.Subtract(vertex1, vertex0);
Vector3D edge2 = Point3D.Subtract(vertex2, vertex0);
Vector3D h = Vector3D.CrossProduct(rayVector, edge2);
double a = Vector3D.DotProduct(edge1, h);
if (a > -EPSILON && a < EPSILON)
{
return false; // This ray is parallel to this triangle.
}
double f = 1.0 / a;
Vector3D s = Point3D.Subtract(rayOrigin, vertex0);
double u = f * (Vector3D.DotProduct(s, h));
if (u < 0.0 || u > 1.0)
{
return false;
}
Vector3D q = Vector3D.CrossProduct(s, edge1);
double v = f * Vector3D.DotProduct(rayVector, q);
if (v < 0.0 || u + v > 1.0)
{
return false;
}
// At this stage we can compute t to find out where the intersection point is on the line.
double t = f * Vector3D.DotProduct(edge2, q);
if (t > EPSILON && t < Math.Sqrt(Vector3D.DotProduct(rayVector, rayVector))) // ray intersection
{
outIntersectionPoint = rayOrigin + rayVector * t;
return true;
}
else // This means that there is a line intersection but not a ray intersection.
{
return false;
}
}
}
I'm trying to find the mouse position in world coordinates but am having trouble finding the right code. At the moment I use this to determine the ray:
float pointX, pointY;
D3DXMATRIX projectionMatrix, viewMatrix, inverseViewMatrix, worldMatrix, translateMatrix, inverseWorldMatrix;
D3DXVECTOR3 direction, origin, rayOrigin, rayDirection;
bool intersect, result;
// Move the mouse cursor coordinates into the -1 to +1 range.
pointX = ((2.0f * (float)mouseX) / (float)m_screenWidth) - 1.0f;
pointY = (((2.0f * (float)mouseY) / (float)m_screenHeight) - 1.0f) * -1.0f;
// Adjust the points using the projection matrix to account for the aspect ratio of the viewport.
m_Direct3D->GetProjectionMatrix(projectionMatrix);
pointX = pointX / projectionMatrix._11;
pointY = pointY / projectionMatrix._22;
// Get the inverse of the view matrix.
m_Camera->GetViewMatrix(viewMatrix);
D3DXMatrixInverse(&inverseViewMatrix, NULL, &viewMatrix);
// Calculate the direction of the picking ray in view space.
direction.x = (pointX * inverseViewMatrix._11) + (pointY * inverseViewMatrix._21) + inverseViewMatrix._31;
direction.y = (pointX * inverseViewMatrix._12) + (pointY * inverseViewMatrix._22) + inverseViewMatrix._32;
direction.z = (pointX * inverseViewMatrix._13) + (pointY * inverseViewMatrix._23) + inverseViewMatrix._33;
// Get the origin of the picking ray which is the position of the camera.
origin = m_Camera->GetPosition();
This gives me the origin and direction of the ray.
But...
I use a custom mesh (not the one from directX) with a heightmap, separated into quadtrees and I don't know if my logic is correct, I tried using the frustum to determine which nodes in the quadtree are visible and so do the checking intersection of triangles only on those nodes, here is this code:
Note* m_mousepos is a vector.
bool QuadTreeClass::getTriangleRay(NodeType* node, FrustumClass* frustum, ID3D10Device* device, D3DXVECTOR3 vPickRayDir, D3DXVECTOR3 vPickRayOrig){
bool result;
int count, i, j, indexCount;
unsigned int stride, offset;
float fBary1, fBary2;
float fDist;
D3DXVECTOR3 v0, v1, v2;
float p1, p2, p3;
// Check to see if the node can be viewed.
result = frustum->CheckCube(node->positionX, 0.0f, node->positionZ, (node->width / 2.0f));
if(!result)
{
return false;
}
// If it can be seen then check all four child nodes to see if they can also be seen.
count = 0;
for(i=0; i<4; i++)
{
if(node->nodes[i] != 0)
{
count++;
getTriangleRay(node->nodes[i], frustum, device, vPickRayOrig, vPickRayDir);
}
}
// If there were any children nodes then dont continue
if(count != 0)
{
return false;
}
// Now intersect each triangle in this node
j = 0;
for(i=0; i<node->triangleCount; i++){
j = i * 3;
v0 = D3DXVECTOR3( node->vertexArray[j].x, node->vertexArray[j].y, node->vertexArray[j].z);
j++;
v1 = D3DXVECTOR3( node->vertexArray[j].x, node->vertexArray[j].y, node->vertexArray[j].z);
j++;
v2 = D3DXVECTOR3( node->vertexArray[j].x, node->vertexArray[j].y, node->vertexArray[j].z);
result = IntersectTriangle( vPickRayOrig, vPickRayDir, v0, v1, v2, &fDist, &fBary1, &fBary2);
if(result == true){
// intersection = true, so get a aproximate center of the triangle on the world
p1 = (v0.x + v0.x + v0.x)/3;
p2 = (v0.y + v1.y + v2.y)/3;
p3 = (v0.z + v1.z + v2.z)/3;
m_mousepos = D3DXVECTOR3(p1, p2, p3);
return true;
}
}
}
bool QuadTreeClass::IntersectTriangle( const D3DXVECTOR3& orig, const D3DXVECTOR3& dir,D3DXVECTOR3& v0, D3DXVECTOR3& v1, D3DXVECTOR3& v2, FLOAT* t, FLOAT* u, FLOAT* v ){
// Find vectors for two edges sharing vert0
D3DXVECTOR3 edge1 = v1 - v0;
D3DXVECTOR3 edge2 = v2 - v0;
// Begin calculating determinant - also used to calculate U parameter
D3DXVECTOR3 pvec;
D3DXVec3Cross( &pvec, &dir, &edge2 );
// If determinant is near zero, ray lies in plane of triangle
FLOAT det = D3DXVec3Dot( &edge1, &pvec );
D3DXVECTOR3 tvec;
if( det > 0 )
{
tvec = orig - v0;
}
else
{
tvec = v0 - orig;
det = -det;
}
if( det < 0.0001f )
return FALSE;
// Calculate U parameter and test bounds
*u = D3DXVec3Dot( &tvec, &pvec );
if( *u < 0.0f || *u > det )
return FALSE;
// Prepare to test V parameter
D3DXVECTOR3 qvec;
D3DXVec3Cross( &qvec, &tvec, &edge1 );
// Calculate V parameter and test bounds
*v = D3DXVec3Dot( &dir, &qvec );
if( *v < 0.0f || *u + *v > det )
return FALSE;
// Calculate t, scale parameters, ray intersects triangle
*t = D3DXVec3Dot( &edge2, &qvec );
FLOAT fInvDet = 1.0f / det;
*t *= fInvDet;
*u *= fInvDet;
*v *= fInvDet;
return TRUE;
}
Please is this code right? If it is then my problem must be related to the quadtree.
Thanks!
Iterating over all visible triangle to find the intersection is very expensive. Additional the cost will rise if your heightmap gets finer.
For my heightmap I use a different approach:
I do a step-by-step search regarding the height on the clickray starting at the origin. At every step the current position is moved along the ray and tested against the height of the heightmap (therefore you need a heightfunction). If the current position is below the heightmap, the last intervall is searched again by an additional iteration to find a finer position. This works as long as your heightmap hasn't a too high frequency in the heightvalues regarding to the stepsize (otherwise you could jump over a peak).