Pathfinding issues

So I’ve just come back to my point and click experiment and I’ve managed to get it working (an issue with setting base.cam rather than base.camera).

My code appears to work just fine if I set PATHFINDING to zero (Ralph walks straight to the destination), but if I use the pandaai pathfinding code it doesn’t seem to work very well.

My main two issues are:

  1. Ralph doesn’t seem to ever make it to the destination - he gets close but not quite there. I’m guessing this is a result of the A* pathfinding mechanism using a matrix of tiles.
  2. More importantly as Ralph to a destination on a hill he seems to fall through and doesn’t track the terrain. I can’t see why though, it works fine without the pathfinding code and I’m adjusting Ralphs height after pandaai has updated his position.

Can anyone help?

(run this code with the models from the Roaming-Ralph example)


# Author: Richard Cooper ... based on Ryan Myers original
# Models: Jeff Styers, Reagan Heller


# Last Updated: 2/4/2011
#
# 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 panda3d.core import CollisionTraverser,CollisionNode
from panda3d.core import CollisionHandlerQueue,CollisionRay
from panda3d.core import Filename,AmbientLight,DirectionalLight
from panda3d.core import PandaNode,NodePath,Camera,TextNode
from panda3d.core import Vec3,Vec4,BitMask32, GeomNode
from direct.gui.OnscreenText import OnscreenText
from direct.actor.Actor import Actor
from direct.showbase.DirectObject import DirectObject
from direct.showbase.ShowBase import Vec3, Point3
import random, sys, os, math

#for Pandai
from panda3d.ai import *

SPEED = 0.5
PATHFINDING = 1


class World(DirectObject):

	def __init__(self):
		self.walking = Vec3()
		self.isMoving = False
		self.dest = None
		base.win.setClearColor(Vec4(0,0,0,1))

		# 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(0, 0, 0)
		
		# Create the main character, Ralph
		ralphStartPos = self.environ.find("**/start_point").getPos()
		self.ralph = Actor("models/ralph",
								 {"run":"models/ralph-run",
								  "walk":"models/ralph-walk"})
		self.ralph.reparentTo(render)
		self.ralph.setScale(1)
		ralphStartPos = Vec3(-98.64, -20.60, 0)
		self.ralph.setPos(ralphStartPos)

		# click to move
		self.accept("mouse1", self.findDest) 

		# Set up the camera - be carefully to always set base.camera; if you set base.cam the click
		# to move doesn't work properly - read up on the differences between base.cam and base.camera
		base.disableMouse()
		base.camera.setPosHpr(17.79,-87.64,90.16,38.66,325.36,0)
		#base.camera.setPosHpr(-98, -20, 5, 0, 0 , 0)
			
		# 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.clickTrav = CollisionTraverser()
		
		self.pickerNode = CollisionNode('mouseRay')
		self.pickerNP = base.camera.attachNewNode(self.pickerNode)
		self.pickerNode.setFromCollideMask(BitMask32.bit(0))
		self.pickerNode.setIntoCollideMask(BitMask32.allOff())
		self.pickerRay = CollisionRay()
		self.pickerNode.addSolid(self.pickerRay)
		self.pickerHandler = CollisionHandlerQueue()
		self.clickTrav.addCollider(self.pickerNP, self.pickerHandler)
		
		# Uncomment this line to show a visual representation of the 
		# collisions occuring
		self.cTrav.showCollisions(render)
		self.clickTrav.showCollisions(render)
		
		# Create some lighting
		ambientLight = AmbientLight("ambientLight")
		ambientLight.setColor(Vec4(.3, .3, .3, 1))
		directionalLight = DirectionalLight("directionalLight")
		directionalLight.setDirection(Vec3(-5, -5, -5))
		directionalLight.setColor(Vec4(1, 1, 1, 1))
		directionalLight.setSpecularColor(Vec4(1, 1, 1, 1))
		render.setLight(render.attachNewNode(ambientLight))
		render.setLight(render.attachNewNode(directionalLight))
		
		self.setAI()


	def setAI(self):
		self.AIworld = AIWorld(render)
		self.AIchar = AICharacter("ralph",self.ralph, 60, 0.05, 25) # mass, force maxforce
		self.AIworld.addAiChar(self.AIchar)
		self.AIbehaviors = self.AIchar.getAiBehaviors()
		
		self.AIbehaviors.initPathFind("models/navmesh.csv")
		
	
	# find the place to walk to
	def findDest(self):
		# First we check that the mouse is not outside the screen.
		if base.mouseWatcherNode.hasMouse():
			# This gives up the screen coordinates of the mouse.
			mpos = base.mouseWatcherNode.getMouse()
		
			# This makes the ray's origin the camera and makes the ray point 
			# to the screen coordinates of the mouse.
			self.pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())
			self.clickTrav.traverse(render)
			self.pickerHandler.sortEntries()
			
			# walk through collision until we find one with the terrain - allows us to click behind things
			for i in xrange(0, self.pickerHandler.getNumEntries()):
				pickedObj = self.pickerHandler.getEntry(i).getIntoNode()
				#print pickedObj.getName()
				if pickedObj.getName() == 'terrain':
					#print pickedObj
					self.dest = self.pickerHandler.getEntry(i).getSurfacePoint(render)
					
					taskMgr.remove("moveTask") # stop any previous move
					if not PATHFINDING:
						self.ralph.lookAt(self.dest.x, self.dest.y, self.ralph.getZ())
						self.ralph.setHpr(self.ralph.getH() - 180, self.ralph.getP(), self.ralph.getR())
					else:
						self.AIbehaviors.pathFindTo(self.dest, "addPath")
					taskMgr.add(self.aimove, "moveTask")
					break


	def aimove(self, task):
		retVal = task.cont
		self.AIworld.update()
		retVal = self.move(task)
		if ( retVal == task.done or 
			 self.AIbehaviors.behaviorStatus("pathfollow") == "done"):
			self.ralph.stop()
			self.ralph.pose("walk", 5)
			self.isMoving = False
			return task.done
		
		if self.isMoving is False:
			self.ralph.loop("run")
			self.isMoving = True
		return retVal


	def move(self, task):
		retVal = task.cont
		# save ralph's initial position so that we can restore it,
		# in case he falls off the map or runs into something.
		startpos = self.ralph.getPos()

		if not PATHFINDING:
			# If ralph is moving, loop the run animation.
			# If he is standing still, stop the animation.
			if self.dest:
				self.walking = Vec3(self.dest.x - self.ralph.getX(), 
									self.dest.y - self.ralph.getY(), 
									0)
	
			if self.walking.length() > 1:
				self.ralph.setY(self.ralph, -25 * globalClock.getDt())
			else:
				retVal = task.done

		# 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.
		self.ralphGroundHandler.sortEntries()
		for i in xrange(0, self.ralphGroundHandler.getNumEntries()):
			entry = self.ralphGroundHandler.getEntry(i).getIntoNode()
			if entry.getName() == "terrain":
				self.ralph.setZ(self.ralphGroundHandler.getEntry(i).getSurfacePoint(render).getZ())
				break;
		self.ralph.setP(0) # make sure the head is always upright - before we try colliding
		return retVal


w = World()
run()