0
\$\begingroup\$

I want to figure out if 2 3D models overlap on screen. My general idea is:

  1. Compute the bounding box of each model
  2. Transform the bounding box to the View Volume using the ViewProjection matrix
  3. Check if bounding boxes overlap in the view volume, ignoring the Z-axis

For step 1 I've used the answer to this question. But I'm having problems implementing step two.

I wrote the function below but its giving me inconsistent results. The numbers seem good for corners that fall inside the view volume (after multiplying by the ViewProjection matrix). But if an object falls off the screen the numbers seem incorrect. What am I doing wrong?

public static BoundingRectangle ProjectBoundingBox(BoundingBox box, Matrix viewProjection)
{
    var minX = float.MaxValue;
    var maxX = float.MinValue;

    var minY = float.MaxValue;            
    var maxY = float.MinValue;

    var corners = box.GetCorners();

    foreach (var corner in corners)
    {
        var projectedCorner = Vector4.Transform(corner, viewProjection);
        projectedCorner /= projectedCorner.W;

        minX = Math.Min(minX, projectedCorner.X);                
        maxX = Math.Max(maxX, projectedCorner.X);

        minY = Math.Min(minY, projectedCorner.Y);
        maxY = Math.Max(maxY, projectedCorner.Y);
    }

    return new BoundingRectangle(minX, maxX, minY, maxY);
}

Edit: the numbers

If you see the attached screenshot you see two semi-transparent stained glass windows.

Screenshot

Fully visible window:
    Bounding Box: Min( -43.62131  25.6  -10.32131 ), Max( -37.38873  34.4  -4.08873 )
    Projected Coordinates: Min(-0.7539723,-0.5401732), Max(-0.2793865, 0.27869606)

Partially visible window:
    Bounding Box: Min( -40.5142  25.6  -1.2 )  Max( -40.5  34.4  7.6 )
    Projected Coordinates: Min(-32.07214, -23.4672337), Max(47.5241241, 0.334790081)

As you can see the projected coordinates for the only partially visible window are gigantic. I would expect MaxX to be something like -0.9 and MinX maybe -5. But the numbers are way off.

The culprit seems to be some of the corners of the bounding box. For example (-40.5142 34.4 7.6) is transformed to (47.52412 -12.65482 1.539964). What is interesting here is that the W coordinate is negative and less than 1 (-0.18), while for the corners that do seem correct w is positive and larger than 1.0.

Edit #2:

Here's a second picture where I've added a visualization of the projected rectangle. On the left you see the exact scenario where it goes wrong. The right side of the rectangle for the partially visible window is incorrect, while if I slightly wiggle the camera either way you get the result on the right with the correct rectangles around the models.

screenshot2

\$\endgroup\$
  • \$\begingroup\$ Can you describe the specific symptoms you observe? There are lots of ways for numbers to "seem incorrect" - but the particular values you get can tell us a lot about where it's going wrong. Try sharing an explicit test case with all your input values, your expected output values, and the output values you get instead. \$\endgroup\$ – DMGregory Sep 12 '18 at 14:53
  • \$\begingroup\$ @DMGregory you're definitely right. I took me a while to get a bit 'nice' numbers. But I've added them. \$\endgroup\$ – Roy T. Sep 12 '18 at 16:13
1
\$\begingroup\$

you may try this 3D position to Screen position , I use this and works on my end ^ _^ y

USAGE :

 Vector2   mScrPosMax = Position3DToScreenPoint( _MyEntityBBox.Max, _MyCamViewProj, 1024, 700);
 Vector2   mScrPosMin = Position3DToScreenPoint( _MyEntityBBox.Min, _MyCamViewPorj, 1024, 700);
 //
 int       mRectWidth  = (int)( Math.Abs(mScrPosMax.X - mScrPosMin.X) );
 int       mRectHeight = (int)( Math.Abs(mScrPosMax.Y - mScrPosMin.Y) );
 //
 Rectangle mRect = new Rectangle((int)mScrPosMax.X, (int)mScrPosMax.Y, mRectWidth, mRectHeight);

THE FUNCTION :

    /// <summary>
    /// 3D position to screen point
    /// <param name="point3D">Point in 3D space</param>
    /// <param name="camViewProj">Camera view projection</param>
    /// <param name="screenWidth">Screen width</param>
    /// <param name="screenHeight">Screen height</param>
    /// <returns>Returns position on screen.</returns>
    public static Vector2 Position3DToScreenPoint( Vector3 point3D, Matrix camViewProj, float screenWidth, float screenHeight )
    {
        float m_ScreenProjX  = ( point3D.X * camViewProj.M11) + ( point3D.Y * camViewProj.M21) + ( point3D.Z * camViewProj.M31) + camViewProj.M41;
        float m_ScreenProjY  = ( point3D.X * camViewProj.M12) + ( point3D.Y * camViewProj.M22) + ( point3D.Z * camViewProj.M32) + camViewProj.M42;
        float m_ScreenProjW  = ( point3D.X * camViewProj.M14) + ( point3D.Y * camViewProj.M24) + ( point3D.Z * camViewProj.M34) + camViewProj.M44;
        //
        m_ScreenProjX  = m_ScreenProjX / m_ScreenProjW;
        m_ScreenProjY  = m_ScreenProjY / m_ScreenProjW;
        //
        m_ScreenProjX  = ((float)(((m_ScreenProjX + 1.0) * 0.5) * screenWidth  ));
        m_ScreenProjY  = ((float)(((1.0 - m_ScreenProjY) * 0.5) * screenHeight ));
        //
        return new Vector2( m_ScreenProjX, m_ScreenProjY );
    }
\$\endgroup\$
  • \$\begingroup\$ This answer would be even better if it explained why this code solves the problem, or the principles by which it works. \$\endgroup\$ – DMGregory Sep 17 '18 at 17:17
  • \$\begingroup\$ I tried this out, and its has the same 'bug' when you're inside the bounding box. (In which case in your function m_ScreenProjW is negative). But it does do a lot less multiplications than I was doing, so its definitely something I will use! Thanks! \$\endgroup\$ – Roy T. Sep 18 '18 at 16:51
1
\$\begingroup\$

I forgot to mention that there's a built-in Viewport.Project and Viewport.Unproject from the framework that intended to do exactly the same thing.

But Heres a good read for for projecting 3d position to screen space : https://www.scratchapixel.com/lessons/3d-basic-rendering/computing-pixel-coordinates-of-3d-point/mathematics-computing-2d-coordinates-of-3d-points

Cheers ^ _^ Y

\$\endgroup\$
0
\$\begingroup\$

So @DexterZ splitting out everything that happens with the matrix transformation helped me reason about the code.

It turns out w is only negative when the point being transformed is behind the camera instead of in front of it. A simple abs() reflects the w component so that its correctly transformed to screen space again. An ugly corner case.

This is what I ended up with in the end, and it seems to work well so far:

private static Vector2 ToViewSpace(Vector3 worldPosition, Matrix viewProjection)
    {
        var x = (worldPosition.X * viewProjection.M11) + (worldPosition.Y * viewProjection.M21)
                                                       + (worldPosition.Z * viewProjection.M31)
                                                       + viewProjection.M41;

        var y = (worldPosition.X * viewProjection.M12) + (worldPosition.Y * viewProjection.M22)
                                                       + (worldPosition.Z * viewProjection.M32)
                                                       + viewProjection.M42;

        var w = (worldPosition.X * viewProjection.M14) + (worldPosition.Y * viewProjection.M24)
                                                       + (worldPosition.Z * viewProjection.M34)
                                                       + viewProjection.M44;

        // w is negative when the camera has zoomed beyond the world position in which case
        // it needs to be negated to be able to compute the correct screen position
        w = Math.Abs(w);

        x = x / w;
        y = y / w;

        return new Vector2(x, y);
    }
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.