intersectsLine() Trouble (Solved)

Edit: Solved, see below

I want to determine the four points at which the camera’s viewing frustum intersects the plane z=0.

The position of camera is set to (0,0,0) and the zoom is controlled by the y-value of base.cam.

I’m using the code:

for point in (-1,-1), (-1, 1), (1, -1), (1, 1):
    pos3d = Point3()
    nearPoint = Point3()
    farPoint = Point3()
    base.camLens.extrude(point, nearPoint, farPoint)
    if world.plane.intersectsLine(pos3d,
        render.getRelativePoint(base.cam, nearPoint),
        render.getRelativePoint(base.cam, farPoint)):
            print pos3d

This works when the angle between camera and the plane is large (larger than ~15 degrees). However, when the angle is small, intersectsLine() creates an intersection point (pos3d in the above code) that is in the opposite direction. In other words, if the intersection point should have a small y-value, it creates an intersection point with a large y-value and vice versa.

Here’s the code that works:

    def getFrustum(self):
        """ Returns corners of the view frustum. Camera must use default fov. """
        # Extrude near and far points from corners of display window
        ntl = Point3();     ftl = Point3()
        ntr = Point3();     ftr = Point3()
        nbl = Point3();     fbl = Point3()
        nbr = Point3();     fbr = Point3()

        base.camLens.extrude(Point2(-1,1), ntl, ftl)
        base.camLens.extrude(Point2(1,1), ntr, ftr)
        base.camLens.extrude(Point2(-1,-1), nbl, fbl)
        base.camLens.extrude(Point2(1,-1), nbr, fbr)

        # Convert far points to world space
        ftl = render.getRelativePoint(base.cam, ftl)
        ftr = render.getRelativePoint(base.cam, ftr)
        fbl = render.getRelativePoint(base.cam, fbl)
        fbr = render.getRelativePoint(base.cam, fbr)

        # Calculate z-intersect of lines passing through near and far points
        tl = Point3()
        tr = Point3()
        bl = Point3()
        br = Point3()

        world.plane.intersectsLine(tl, render.getRelativePoint(base.cam, ntl), ftl)
        world.plane.intersectsLine(tr, render.getRelativePoint(base.cam, ntr), ftr)
        world.plane.intersectsLine(bl, render.getRelativePoint(base.cam, nbl), fbl)
        world.plane.intersectsLine(br, render.getRelativePoint(base.cam, nbr), fbr)

        # Return far points when z-intersect is behind camera
        camPitch = camera.getP() % 360
        if (camPitch == 0) or (345 < camPitch <= 360) or (165 < camPitch <= 180):
            tl = ftl
            tr = ftr

        elif (180 < camPitch <= 195) or (0 < camPitch <= 15):
            bl = fbl
            br = fbr

        return tl, tr, bl, br

Are you sure you are converting the plane into the coordinate space of the camLens before you are performing this computation?

David

Here’s some additional code that creates a visible plane, and parents a smiley to each point of intersection, so you can see the results of the intersection. It appears to work fine for me.

Note that in this code, world.planeNP is defined to be the coordinate space of the plane (not render). Also, base.camera is defined to be the coordinate space of the camera (not base.cam)–this allows base.oobeCull() to work as intended.

Use the standard trackball controls to change your point of view, and hold down the control key to change the original camera’s position and angle relative to the plane.

Uncomment the interval at the end to prove that the plane computation continues to work correctly even as the plane’s own coordinate space rotates.

David

from direct.directbase.DirectStart import *
from pandac.PandaModules import *

class World:
    def __init__(self):
        self.plane = Plane()

        # Create a CollisionPlane, just as a convenient way to
        # visualize the plane.  We don't actually use it for
        # collisions.
        
        colPlane = CollisionPlane(self.plane)
        planeNode = CollisionNode('planeNode')
        planeNode.addSolid(colPlane)
        self.planeNP = render.attachNewNode(planeNode)
        self.planeNP.show()

        # Allow stepping outside the camera's point of view, and also
        # visualize the frustum.
        base.oobeCull()
        
        # A node for holding the visualizations of the intersection
        # test.
        self.vis = self.planeNP.attachNewNode('vis')

def showPoints(task):
    # Clean the previous frame's visualizations.
    world.vis.getChildren().detach()
    
    for point in (-1,-1), (-1, 1), (1, -1), (1, 1):
        pos3d = Point3()
        nearPoint = Point3()
        farPoint = Point3()
        base.camLens.extrude(point, nearPoint, farPoint)
        if world.plane.intersectsLine(pos3d,
            world.planeNP.getRelativePoint(base.camera, nearPoint),
            world.planeNP.getRelativePoint(base.camera, farPoint)):
                print pos3d
                s = loader.loadModel('smiley.egg')
                s.reparentTo(world.vis)
                s.setPos(pos3d)

    return task.cont


world = World()
taskMgr.add(showPoints, 'showPoints')

# Rotate the plane slowly for fun.
#i = world.planeNP.hprInterval(10, hpr = (0, 360, 0), startHpr = (0, 0, 0))
#i.loop()

I’m converting the points into the coordinate space of the plane.

Here’s some photos:

I want to draw a minimap that displays the frustum so the user knows what part of render she’s looking at.

Like this:

This also works:

However, for some values, it calculates the wrong point. This screenshot is only slightly different than the last one (.5 degree difference in camera’s pitch), but it calculates a point behind the camera and not in front of it.

This seems to happen when the camera’s pitch is > +/- 15 degrees with respect to the plane z = 0.

Are you sure that this is not the correct result? When the direction-of-view is nearly parallel with the plane, the point of intersection of the frustum edge (particularly the top frustum edge) might well be behind the camera.

David

Hmm… you could be right. Do you have any suggestions on how I could fudge it in order to get what I’m after?

How about this: creating an inverted collision sphere and then when the intersection point is behind the camera, testing the collision against the inverted sphere rather than the plane?

What you’re really interested in knowing is the intersection of the left and right planes of the viewing frustum with the plane z = 0. The intersection of two non-parallel planes is, of course, a line; and Plane.intersectsPlane() will determine this line, which you can then use to compute the 2-d points to draw on your moving map.

David

Does panda have any built in methods for determining those planes? I see that lens has a setFrustumFromCorners method, but is there any way of retrieving that data?

You can construct a plane with three points. So use camLens.extrude() to get three points from each side of the frustum.

David