Picking in 3D ... not working

I’m trying to take the “walking on uneven ground” sample http://www.panda3d.org/manual/index.php/Uneven_Terrain and make ralph walk to wherever is clicked.

It seemed simple enough, I’ve already managed to get “picking” working in another sample; but I can’t get it working here.

As I understand it, whenever I click I should get:

  • one or more hits in the CollisionHandlerQueue
  • see my collisionRay change direction whilst its origin stays fixed (at the cameras)

neither of these things appear to happen but I can’t figure out why. As a result I don’t get the right location when I click.

Can anyone help

Rich

# Author: Ryan Myers
# Models: Jeff Styers, Reagan Heller


# Last Updated: 6/13/2005
#
# This tutorial provides an example of creating a character
# and having it walk around on uneven terrain, as well
# as implementing a fully rotatable camera.

import direct.directbase.DirectStart
from pandac.PandaModules import CollisionTraverser,CollisionNode
from pandac.PandaModules import CollisionHandlerQueue,CollisionRay
from pandac.PandaModules import Filename
from pandac.PandaModules import PandaNode,NodePath,Camera,TextNode
from pandac.PandaModules import Vec3,Vec4,BitMask32, GeomNode
from direct.gui.OnscreenText import OnscreenText
from direct.actor.Actor import Actor
from direct.task.Task import Task
from direct.showbase.DirectObject import DirectObject
import random, sys, os, math

#for Pandai
from panda3d.ai import *

SPEED = 0.5


class World(DirectObject):
	def __init__(self):
		self.moving = False
		
		base.win.setClearColor(Vec4(0,0,0,1))
		base.cam.setPosHpr(17.79,-87.64,90.16,38.66,325.36,0)
	  
		# Set up the environment
		#
		# This environment model contains collision meshes.	 If you look
		# in the egg file, you will see the following:
		#
		#	 <Collide> { Polyset keep descend }
		#
		# This tag causes the following mesh to be converted to a collision
		# mesh -- a mesh which is optimized for collision, not rendering.
		# It also keeps the original mesh, so there are now two copies ---
		# one optimized for rendering, one for collisions.	
		self.environ = loader.loadModel("models/world")
		self.environ.reparentTo(render)
		self.environ.setPos(12,0,0)
		
		self.box = loader.loadModel("models/box")  
		self.box.reparentTo(render)
		self.box.setPos(-29.83,0,0)
		self.box.setScale(1)
		
		self.box1 = loader.loadModel("models/box")	
		self.box1.reparentTo(render)
		self.box1.setPos(-51.14,-17.90,0)
		self.box1.setScale(1)
		
		# Create the main character, Ralph
		#ralphStartPos = self.environ.find("**/start_point").getPos()
		ralphStartPos = Vec3(-98.64,-20.60,0)
		self.ralph = Actor("models/ralph",
								 {"run":"models/ralph-run",
								  "walk":"models/ralph-walk"})
		self.ralph.reparentTo(render)
		self.ralph.setScale(1)
		self.ralph.setPos(ralphStartPos)
		
		ralphaiStartPos = Vec3(-50,20,0)
		self.ralphai = Actor("models/ralph",
								 {"run":"models/ralph-run",
								  "walk":"models/ralph-walk"})
		
		self.pointer = loader.loadModel("models/arrow")
		self.pointer.setColor(1,0,0)
		self.pointer.setPos(-7.5,-1.2,0)
		self.pointer.setScale(3)
		self.pointer.reparentTo(render)
		
		# Game state variables
		self.isMoving = False

		# Set up the camera
		base.disableMouse()
		
		# We will detect the height of the terrain by creating a collision
		# ray and casting it downward toward the terrain.  One ray will
		# start above ralph's head, and the other will start above the camera.
		# A ray may hit the terrain, or it may hit a rock or a tree.  If it
		# hits the terrain, we can detect the height.  If it hits anything
		# else, we rule that the move is illegal.
		self.cTrav = CollisionTraverser()

		self.ralphGroundRay = CollisionRay()
		self.ralphGroundRay.setOrigin(0,0,1000)
		self.ralphGroundRay.setDirection(0,0,-1)
		self.ralphGroundCol = CollisionNode('ralphRay')
		self.ralphGroundCol.addSolid(self.ralphGroundRay)
		self.ralphGroundCol.setFromCollideMask(BitMask32.bit(0))
		self.ralphGroundCol.setIntoCollideMask(BitMask32.allOff())
		self.ralphGroundColNp = self.ralph.attachNewNode(self.ralphGroundCol)
		self.ralphGroundHandler = CollisionHandlerQueue()
		#self.cTrav.addCollider(self.ralphGroundColNp, self.ralphGroundHandler)

		self.camGroundRay = CollisionRay()
		self.camGroundCol = CollisionNode('camRay')
		self.camGroundCol.addSolid(self.camGroundRay)
		#self.camGroundCol.setFromCollideMask(GeomNode.getDefaultCollideMask())
		self.camGroundCol.setFromCollideMask(BitMask32.bit(0))
		self.camGroundCol.setIntoCollideMask(BitMask32.allOff())
		self.camGroundColNp = base.camera.attachNewNode(self.camGroundCol)
		self.camGroundHandler = CollisionHandlerQueue()
		self.cTrav.addCollider(self.camGroundColNp, self.camGroundHandler)

		# Uncomment this line to see the collision rays
		#self.ralphGroundColNp.show()
		self.camGroundColNp.show()
	   
		#Uncomment this line to show a visual representation of the 
		#collisions occuring
		self.cTrav.showCollisions(render)
	   
		#self.setAI()
		self.accept("mouse1", self.setMove) 

	
	def setMove(self):
		if base.mouseWatcherNode.hasMouse():
			mpos = base.mouseWatcherNode.getMouse()
			self.camGroundRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())
			
			self.cTrav.traverse(render)
			print "trav: %u" % (self.camGroundHandler.getNumEntries())
			print self.camGroundRay.getOrigin()
			print self.camGroundRay.getDirection()
			
			if self.camGroundHandler.getNumEntries() > 1:
				# This is so we get the closest object
				self.camGroundHandler.sortEntries()
				pickedObj = self.camGroundHandler.getEntry(1).getIntoNodePath()
				print pickedObj.getPos()
				if (not pickedObj.isEmpty() and
					pickedObj.getName() == 'terrain'):
					dest = self.camGroundHandler.getEntry(1).getSurfacePoint(render)
					self.pointer.setPos(dest)
	
					#self.AIbehaviors.pathFindTo(self.pointer)
					#self.ralph.loop("run")
					#self.moving = True
		
	
	# Accepts arrow keys to move either the player or the menu cursor,
	# Also deals with grid checking and collision detection
	def move(self):
		# Get the time elapsed since last frame. We need this
		# for framerate-independent movement.
		elapsed = globalClock.getDt()
		
		startpos = self.ralph.getPos()

		# Now check for collisions.
		self.cTrav.traverse(render)

		# Adjust ralph's Z coordinate.	If ralph's ray hit terrain,
		# update his Z. If it hit anything else, or didn't hit anything, put
		# him back where he was last frame.
		entries = []
		for i in range(self.ralphGroundHandler.getNumEntries()):
			entry = self.ralphGroundHandler.getEntry(i)
			entries.append(entry)
		entries.sort(lambda x,y: cmp(y.getSurfacePoint(render).getZ(),
									 x.getSurfacePoint(render).getZ()))
		if (len(entries)>0) and (entries[0].getIntoNode().getName() == "terrain"):
			self.ralph.setZ(entries[0].getSurfacePoint(render).getZ())
		else:
			self.ralph.setPos(startpos)

		self.ralph.setP(0)
		return Task.cont
		
	
	def setAI(self):
		#Creating AI World
		self.AIworld = AIWorld(render)
		
		self.accept("space", self.setMove)
		self.AIchar = AICharacter("ralph",self.ralph, 60, 0.05, 25)
		self.AIworld.addAiChar(self.AIchar)
		self.AIbehaviors = self.AIchar.getAiBehaviors()
		
		self.AIbehaviors.initPathFind("models/navmesh.csv")

		self.AIbehaviors.addStaticObstacle(self.box)
		self.AIbehaviors.addStaticObstacle(self.box1)
		
		#AI World update		
		taskMgr.add(self.AIUpdate,"AIUpdate")
		
		
	#to update the AIWorld	  
	def AIUpdate(self,task):
		self.AIworld.update()

		if self.moving:
			if self.AIbehaviors.behaviorStatus("pathfollow") == "done":
				self.ralph.stop()
				self.ralph.pose("walk", 5)
				self.moving = False
			else:
				self.move()
						
		return Task.cont
	


w = World()
run()

bump?

Anybody … even ideas of how I can fault find? I’m completely out of ideas.

Rich

Hi

I don’t see any setCollideMask for your terrain.
Maybe you need self.environ.setCollideMask(BitMask32.bit(0))

And are you sure you only want to check on more than one collision ?
if self.camGroundHandler.getNumEntries() > 1

Nope, doesn’t help :frowning:

Your probably right about the self.camGroundHandler.getNumEntries check though.

The thing I don’t understand though, is if I ignore all the collision stuff and just pay attention to the CollisionRay, I expected (from the docs) that after calling:

mpos = base.mouseWatcherNode.getMouse()
self.camGroundRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())

the rays origin would be the same as the cameras (its attached to the camera after all).

The strange thing is that if I print it out, not only is it not the same as the camera’s but it moves! As this would cause the collision to be evaluated elsewhere I can’t help but think that this may be the source of my problem.

Is my assumption incorrect?

Rich

The ray’s origin will not be (0, 0, 0) (which would be the camera’s origin). Instead, it will be a point on the camera’s near plane. This means that the ray’s origin will always be (x, y, z), where z is the value of your camera’s near distance, and x and y are relatively small values that correspond to your mouse’s position onscreen.

David

oh, that makes sense. I read the docs as setting the rays origin to the cameras origin but setting it to the intersection of the near plane makes sense. It also explains why I’m seeing it moving.

So thats not my problem … humm.

Thanks though, thats one less thing which can be wrong then

Rich