Super-fast mouse ray collisions with ground plane

I was shocked to learn that many users do not know about the existence of many useful mathematical classes and functions that Panda provides. Thus, many users use Panda’s collision system if they want to detect where a mouse ray hit a ground plane (for point-n-click movement in a 2d world) etc etc.
Thus, I quickly threw this snippet together that shows how to use the Plane class to calculate the intersection point with a mouse line. Super-fast. (Note that there are many more uses to this.)

from direct.showbase.ShowBase import ShowBase
from panda3d.core import Plane, Vec3, Point3, CardMaker

class YourClass(ShowBase):
  def __init__(self):
    ShowBase.__init__(self)
    self.disableMouse()
    self.camera.setPos(0, 60, 25)
    self.camera.lookAt(0, 0, 0)
    z = 0
    self.plane = Plane(Vec3(0, 0, 1), Point3(0, 0, z))
    self.model = loader.loadModel("jack")
    self.model.reparentTo(render)
    cm = CardMaker("blah")
    cm.setFrame(-100, 100, -100, 100)
    render.attachNewNode(cm.generate()).lookAt(0, 0, -1)
    taskMgr.add(self.__getMousePos, "_YourClass__getMousePos")
  
  def __getMousePos(self, task):
    if base.mouseWatcherNode.hasMouse():
      mpos = base.mouseWatcherNode.getMouse()
      pos3d = Point3()
      nearPoint = Point3()
      farPoint = Point3()
      base.camLens.extrude(mpos, nearPoint, farPoint)
      if self.plane.intersectsLine(pos3d,
          render.getRelativePoint(camera, nearPoint),
          render.getRelativePoint(camera, farPoint)):
        print "Mouse ray intersects ground plane at ", pos3d
        self.model.setPos(render, pos3d)
    return task.again

YourClass()
base.taskMgr.run()
2 Likes

Ooh, that looks good, pro-rsoft - thank you for it. :slight_smile:

that’s very good :slight_smile:

I have just found that in certain circumstances the intersection point may be not in front of the camera but behind it: imagine the plane is represented by ground, and your character with camera in eyes looks towards the horizon. If the mouse is above the horizon line then the intersection of the point and plane is behind the camera. Therefore, sometimes you will need the additional test, for example:

p = base.cam.getRelativePoint(render, pos3d)
if not base.cam.node().isInView(p)
    pos3d = False

You could first check to make sure that the mouse ray is not pointing in the same general direction of the plane’s normal (using the dot product) to avoid the problem of the intersection point being behind the camera.

Just wanted to note how easy it was to get this method to work. It’s almost been a year now since the last post, has there been any improvements or “better” way to achieve the same thing?

Why are the following lines used in the code?
cm = CardMaker(“blah”)
cm.setFrame(-100, 100, -100, 100)
render.attachNewNode(cm.generate()).lookAt(0, 0, -1)

Oh, that just creates a big quad to visualise the ground plane.

@rdb thanks

Thanks for sharing this code.
This looks like something i really could need for a part of my collision detection. I just was looking for a good way to find the intersection of a line and a plane in 3d.
Is there a way to create the plane with 3 points directly or do i have to calculate a vector first?
Unfortunately i couldn’t find the API for the ‘direct.showbase.ShowBase’ and the plane class.
(Any hints where i can find the API are very welcome)
I noticed as well that there is a Vec3, Point3… in both: ShowBase and panda3d.core.
Are there any drawbacks to using the panda Vec3 class?

I imagine that this is the “Plane” class; if so, then it does indeed seem to have a constructor that takes three points (wound counter-clockwise as seen from the front of the plane).