3

Edit3 : My problems were in completely different functions than i expected. ill let the code stay, maybe this helps someone :) (and dont forget to debug!).

Im trying to find the vector where a line intersects with a triangle.

Current state: Random intersections even if mouse is not at the floor and camera view dependend (lookat matrix)

Steps

  1. Unproject mouse coordinations
  2. Check line / triangle intersection

Unproject mouse coordinations

I checked the source of glm::unproject and gluUnproject and created this function.

   pixel::CVector3 pixel::CVector::unproject(
    CVector2 inPosition,
    pixel::CShape window,
    pixel::matrix4 projectionMatrix,
    pixel::matrix4 modelViewMatrix,
    float depth
    )
{
    // transformation of normalized coordinates
    CVector4 inVector;
    inVector.x = (2.0f * inPosition.x) / window.width - 1.0f;
    inVector.y = (2.0f * inPosition.y) / window.height - 1.0f;
    inVector.z = 2.0f * depth - 1.0f;
    inVector.w = 1.0f;

    // multiply inverted matrix with vector
    CVector4 rayWorld = pixel::CVector::multMat4Vec4(pixel::CMatrix::invertMatrix(projectionMatrix * modelViewMatrix), inVector);

    CVector3 result;
    result.x = rayWorld.x / rayWorld.w;
    result.y = rayWorld.y / rayWorld.w;
    result.z = rayWorld.z / rayWorld.w;



    return result;
}

Checking intersection

pixel::CVector3 pixel::Ray::intersection(
    Ray ray,
    pixel::CVector3 v0,
    pixel::CVector3 v1,
    pixel::CVector3 v2
    )
{
    // compute normal
    CVector3 a, b, n;
    a = v1 - v0;
    b = v2 - v0;

    n = ray.direction.cross(b);

    // find determinant
    float det = a.dot(n);

    if (det < 0.000001f)
    {
        std::cout << "Ray intersecting with backface triangles \n";
        return pixel::CVector::vector3(0.0f, 0.0f, 0.0f);
    }
    det = 1.0f / det;

    // calculate distance from vertex0 to ray origin
    CVector3 s = ray.origin - v0;
    float u = det * s.dot(n);

    if (u < -0.000001f || u > 1.f + 0.000001f)
    {
        std::cout << "U: Intersection outside of the triangle!\n";
        return pixel::CVector::vector3(0.0f, 0.0f, 0.0f);
    }

    CVector3 r = s.cross(a);
    float v = det * ray.direction.dot(r);
    if (v < -0.000001f || u + v > 1.f + 0.000001f)
    {
        std::cout << "V/U: Intersection outside of triangle!\n";
        return pixel::CVector::vector3(0.0f, 0.0f, 0.0f);
    }

    // distance from ray to triangle
    det = det *  b.dot(r);

    std::cout << "T: " << det << "\n";

    CVector3 endPosition;
    endPosition.x = ray.origin.x + (ray.direction.x * det);
    endPosition.y = ray.origin.y + (ray.direction.y * det);
    endPosition.z = ray.origin.z + (ray.direction.z * det);

    return endPosition;
}

Usage

    if (event.button.button == SDL_BUTTON_RIGHT)
            {

                camera->setCameraActive();
                float mx = event.motion.x;
                float my = window->info.height - event.motion.y;
                // ray casting
                pixel::Ray ray;

                std::cout << "\n\n";

                    // near
                pixel::CVector3 rayNear = pixel::CVector::unproject(
                    pixel::CVector::vector2(mx, my),
                    pixel::CVector::shape2(window->info.internalWidth, window->info.internalHeight),
                    camera->camInfo.currentProjection,
                    camera->camInfo.currentView,
                    1.0f
                    );
                // far
                pixel::CVector3 rayFar = pixel::CVector::unproject(
                    pixel::CVector::vector2(mx, my),
                    pixel::CVector::shape2(window->info.internalWidth, window->info.internalHeight),
                    camera->camInfo.currentProjection,
                    camera->camInfo.currentView,
                    0.0f
                    );


                // normalized direction results in the same behavior
                ray.origin = cameraPosition;

                ray.direction = pixel::CVector::normalize(rayFar- rayNear);

                std::cout << "Raycast \n";
                std::cout << "Mouse Position: " << mx << " - " << my << "\n";
                std::cout << "Camera Position: " << ray.origin.x << " - " << ray.origin.y << " - " << ray.origin.z << "\n";
                std::cout << "Ray direction: " << ray.direction.x << " - " << ray.direction.y << " - " << ray.direction.z << "\n";


                pixel::CVector3 vertOne = pixel::CVector::vector3(0.0f, 0.0f, -300.0f);
                pixel::CVector3 vertTwo = pixel::CVector::vector3(0.0f, 0.0f, 0.0f);
                pixel::CVector3 vertThree = pixel::CVector::vector3(300.0f, 0.0f, 0.0f);
                pixel::CVector3 vertFour = pixel::CVector::vector3(300.0f, 0.0f, -300.0f);


                pixel::CVector3 rayHit = pixel::Ray::intersection(ray, vertOne, vertTwo, vertThree);
                pixel::CVector3 rayHit2 = pixel::Ray::intersection(ray, vertThree, vertFour, vertOne);
                std::cout << "Ray hit: " << rayHit.x << " - " << rayHit.y << " - " << rayHit.z << "\n";
                std::cout << "Ray hit: " << rayHit2.x << " - " << rayHit2.y << " - " << rayHit2.z << "\n";
                std::cout << "--------------------\n";
                towerHouse->modelMatrix = pixel::CMatrix::translateMatrix(rayHit);

Output

As ive never used glm::unproject or gluUnproject, i dont know how the normal output should look like, but im getting results like:

Ray direction: 0.109035 -0.0380502 0.0114562

Doesnt look right to me, but checking my code against other sources (mentioned above), i dont see the mistake/s.

Ray intersection works in some special cases (camera rotation) and even then i get intersections even if i dont click on the floor. Same goes with intersection output varying from backface hits to outside of the triangle.

All those errors look like the main source of problem is the unprojection.

Any hints in the right direction?

Olivier Moindrot
  • 27,908
  • 11
  • 92
  • 91
Raphael Mayer
  • 667
  • 6
  • 19
  • One thing I noticed: your `unproject` function does _not_ return a direction, but a point. As you prject back some point on the near plane into the "world space" by using inv(proj*biew), you get the world space coordinate of said point. You need to subtract the world space camera position to get the direction for the ray. – derhass Dec 30 '13 at 16:46
  • @derhass thanks, sounds reasonable. and i indeed get better results. way more accurate. i should rename the unproject function to screenPointToWorldPos. Its now moving the object into the direction of the cursor, but in far to little steps (e.g. mouse is at x=30, object is at x=5, if mouse is at x=50, object is at around x=7) – Raphael Mayer Dec 30 '13 at 18:12
  • Why are you getting world-space coordinates out of your `UnProject` function? If you followed glm and GLU, you probably would have noticed that they use the ModelView matrix and give you **object-space** coordinates. Are you genuinely using only a view matrix in this example? And your calculation of NDC coordinates is way off, Y is inverted and Z is not properly adjusted for depth range. Z should be 2 * depth - 1.0 because depth in NDC ranges from [-1,1] instead of [0,1] (default depth range) in window space. – Andon M. Coleman Dec 30 '13 at 18:21
  • I wrote a general overview of the math involved in unprotecting from window (screen) space to object-space in [another answer](http://stackoverflow.com/questions/20693770/using-gluunproject-to-map-touches-to-x-y-cords-on-z-0-plane-in-android-opengl-es/20694910#20694910). You may find that useful, it would certainly help explain why you cannot simply plug in your depth value like you are doing right now. – Andon M. Coleman Dec 30 '13 at 18:23
  • Thanks for the information @AndonM.Coleman . I had depth range implemented, same es not inverting the y axis, what i posted here is the result of days of searching for information and mixing them up. I tried it with view * modelMatrix but it also resulted in failures. is it the modelview matrix or only the model matrix? glm only has modelMatrix as parameter. – Raphael Mayer Dec 30 '13 at 18:26
  • If you want to transform into object-space, it is ModelView. Which is the combination of those two matrices. Since GL uses column-major matrices, this matrix is the result of: `View * Model` (read matrix multiplication from right-to-left). – Andon M. Coleman Dec 30 '13 at 18:35
  • @AndonM.Coleman i tried this several times: getting near point with -1.0f depth and far point with 1.0f depth and then normalize far - near to get direction, this results in direction of 0 , 0, 0.25xxx depth. as viewMatrix i used camera->camInfo.currentView * floor->modelMatrix. Still no success. – Raphael Mayer Dec 30 '13 at 18:56
  • Your ray is going the wrong direction if you do that. I honestly have no idea why `floor` is introduced to this discussion, that would be the transform matrix for a single object in your scene. If you have no object->world transform that is applied to each object (this would be combined into a ModelView matrix) then you can just use the view matrix like you were originally. Given the (currently backwards) ray that you cast, you need to add its origin to your eye point (in world-space) and test for intersection against each object (after applying their object->world matrices). – Andon M. Coleman Dec 30 '13 at 19:10
  • Object- or view-space would be much simpler coordinate spaces to test in. – Andon M. Coleman Dec 30 '13 at 19:13
  • @AndonM.Coleman its not just the test i need. I needthe world space position of the intersection as im trying to move an object to that "floor-click-position". i have updated the answer with the edited unproject function. – Raphael Mayer Dec 30 '13 at 19:20

2 Answers2

2

This is nowhere close to an answer to this question, but this is too complicated to explain in comments or chat.

First of all:

            // near
            pixel::CVector3 rayNear = pixel::CVector::raycast(
                pixel::CVector::vector2(mx, my),
                pixel::CVector::shape2(window->info.internalWidth, window->info.internalHeight),
                camera->camInfo.currentProjection,
                camera->camInfo.currentView,
                1.0f // WRONG
                );
            // far
            pixel::CVector3 rayFar = pixel::CVector::raycast(
                pixel::CVector::vector2(mx, my),
                pixel::CVector::shape2(window->info.internalWidth, window->info.internalHeight),
                camera->camInfo.currentProjection,
                camera->camInfo.currentView,
                0.0f // WRONG
                );

Near is 0.0 in window-space, and far is 1.0 (depends on the depth range, but if you changed the depth range you should already know this).

In your ray cast function, you have:

CVector3 result;
result.x = rayWorld.x / rayWorld.w;
result.y = rayWorld.y / rayWorld.w;
result.z = rayWorld.z / rayWorld.w;

There is a chance that w == 0.0, and the result is not yet a ray at this time... it is a position in object-space (not world). Generally you are always going to be working with well-behaved matrices, but if you ever look at a formal implementation of UnProject (...) you will notice that they handle the case where w == 0.0 with a special return value or by setting a status flag.

            pixel::CVector3 vertOne = pixel::CVector::vector3(0.0f, 0.0f, -300.0f);
            pixel::CVector3 vertTwo = pixel::CVector::vector3(0.0f, 0.0f, 0.0f);
            pixel::CVector3 vertThree = pixel::CVector::vector3(300.0f, 0.0f, 0.0f);
            pixel::CVector3 vertFour = pixel::CVector::vector3(300.0f, 0.0f, -300.0f);

What coordinate space are these verts in? Presumably object-space, which means if you are casting a ray from your camera's eye point (defined in world-space) that passes through a point on your far plane, and try to test for intersection against a triangle in object-space more often than not you will miss. This is because the origin, scale and rotation for each of these spaces may differ. You need to transform those points into world-space (your original code had a floor->modelMatrix that would work well for this purpose) before you try this test.

Andon M. Coleman
  • 42,359
  • 2
  • 81
  • 106
  • thanks for this detailed answer. The verts are in obj space, but i dont translate/rotate/scale them so they are also in world space. But what bothers me is that i still get the same values for near and far. the same x,y,z. no matter to what depth value i pass to the function. – Raphael Mayer Dec 30 '13 at 21:18
  • Often when you get the same X and Y values out of an unproject function for different values of WinZ it is because you are using an orthographic projection matrix (as opposed to perspective, which will scale the X and Y values with distance). But getting the same Z value out is unusual... I would consider checking the values ***before*** you normalize the vector. You probably are not getting the same Z value each time, just that since there is no variation in X or Y the vector normalizes to (0,0,1). – Andon M. Coleman Dec 30 '13 at 21:22
  • i set the projection right before i use it. i display zFar and zNear seperate from the direction. i even removed the w divide. they are always the same. changing depth to 0.1 and 1000.0f in unproject() gives me different z values (of course not in -1 / 1 range) and same x,y values. – Raphael Mayer Dec 30 '13 at 21:40
  • Ok i have tested unprojection with glm and ofc it works fine. I also recreated my unprojection with glm and it returns the same as the glm function. The error has to be somewhere in my math functions (mult4vec4, matrix inversion, etc) – Raphael Mayer Dec 31 '13 at 10:25
  • even though my problems were with my own functions, i wouldnt have been able to get it working without your good explanations. thanks! – Raphael Mayer Jan 01 '14 at 15:03
1

I tracked down the problem and fixed bugs. i had wrong matrix*matrix and matrix*vector multiplications operators.

Raphael Mayer
  • 667
  • 6
  • 19