Uneven Terrain

UNEVEN TERRAIN PATHFINDING:

https://www.youtube.com/watch?v=ozja1l4rpo4


The code for this tutorial:

  1# PandAI Author: Srinavin Nair
  2# Original Author: Ryan Myers
  3# Models: Jeff Styers, Reagan Heller
  4
  5# Last Updated: 6/13/2005
  6#
  7# This tutorial provides an example of creating a character and having it walk
  8# around on uneven terrain, as well as implementing a fully rotatable camera.
  9# It uses PandAI pathfinding to move the character.
 10
 11from direct.showbase.ShowBase import ShowBase
 12from panda3d.core import CollisionTraverser, CollisionNode
 13from panda3d.core import CollisionHandlerQueue, CollisionRay
 14from panda3d.core import Filename
 15from panda3d.core import PandaNode, NodePath, TextNode
 16from panda3d.core import Vec3, BitMask32
 17from direct.gui.OnscreenText import OnscreenText
 18from direct.actor.Actor import Actor
 19from direct.task.Task import Task
 20from direct.showbase.DirectObject import DirectObject
 21import sys
 22import os
 23
 24from panda3d.ai import *
 25
 26base = ShowBase()
 27
 28SPEED = 0.5
 29
 30# Figure out what directory this program is in.
 31MYDIR = os.path.abspath(sys.path[0])
 32MYDIR = Filename.fromOsSpecific(MYDIR).getFullpath()
 33
 34font = loader.loadFont("cmss12")
 35
 36
 37# Function to put instructions on the screen.
 38def addInstructions(pos, msg):
 39    return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1), font=font,
 40                        pos=(-1.3, pos), align=TextNode.ALeft, scale=.05)
 41
 42
 43# Function to put title on the screen.
 44def addTitle(text):
 45    return OnscreenText(text=text, style=1, fg=(1, 1, 1, 1), font=font,
 46                        pos=(1.3, -0.95), align=TextNode.ARight, scale=.07)
 47
 48
 49class World(DirectObject):
 50
 51    def __init__(self):
 52        self.switchState = True
 53        self.switchCam = False
 54        self.path_no = 1
 55        self.keyMap = {
 56            "left": 0,
 57            "right": 0,
 58            "forward": 0,
 59            "cam-left": 0,
 60            "cam-right": 0
 61        }
 62        base.win.setClearColor((0, 0, 0, 1))
 63        base.cam.setPosHpr(17.79, -87.64, 90.16, 38.66, 325.36, 0)
 64        # Post the instructions
 65
 66        addTitle("Pandai Tutorial: Roaming Ralph (Walking on Uneven Terrain) "
 67                 "working with pathfinding")
 68        addInstructions(0.95, "[ESC]: Quit")
 69        addInstructions(0.90, "[Space - do Only once]: Start Pathfinding")
 70        addInstructions(0.85, "[Enter]: Change camera view")
 71        addInstructions(0.80, "[Up Arrow]: Run Ralph Forward")
 72        addInstructions(0.70, "[A]: Rotate Camera Left")
 73        addInstructions(0.65, "[S]: Rotate Camera Right")
 74
 75        # Set up the environment
 76        #
 77        # This environment model contains collision meshes.  If you look
 78        # in the egg file, you will see the following:
 79        #
 80        #    <Collide> { Polyset keep descend }
 81        #
 82        # This tag causes the following mesh to be converted to a collision
 83        # mesh -- a mesh which is optimized for collision, not rendering.
 84        # It also keeps the original mesh, so there are now two copies ---
 85        # one optimized for rendering, one for collisions.
 86
 87        self.environ = loader.loadModel("models/world")
 88        self.environ.reparentTo(render)
 89        self.environ.setPos(12, 0, 0)
 90
 91        self.box = loader.loadModel("models/box")
 92        self.box.reparentTo(render)
 93        self.box.setPos(-29.83, 0, 0)
 94        self.box.setScale(1)
 95
 96        self.box1 = loader.loadModel("models/box")
 97        self.box1.reparentTo(render)
 98        self.box1.setPos(-51.14, -17.90, 0)
 99        self.box1.setScale(1)
100
101        # Create the main character, Ralph
102
103        #ralphStartPos = self.environ.find("**/start_point").getPos()
104        ralphStartPos = Vec3(-98.64, -20.60, 0)
105        self.ralph = Actor("models/ralph",
106                           {"run": "models/ralph-run",
107                            "walk": "models/ralph-walk"})
108        self.ralph.reparentTo(render)
109        self.ralph.setScale(1)
110        self.ralph.setPos(ralphStartPos)
111
112        self.ralphai = Actor("models/ralph",
113                             {"run": "models/ralph-run",
114                              "walk": "models/ralph-walk"})
115
116        self.pointer = loader.loadModel("models/arrow")
117        self.pointer.setColor(1, 0, 0)
118        self.pointer.setPos(-7.5, -1.2, 0)
119        self.pointer.setScale(3)
120        self.pointer.reparentTo(render)
121
122        self.pointer1 = loader.loadModel("models/arrow")
123        self.pointer1.setColor(1, 0, 0)
124        self.pointer1.setPos(-98.64, -20.60, 0)
125        self.pointer1.setScale(3)
126        #self.pointer.reparentTo(render)
127
128        # Create a floater object.  We use the "floater" as a temporary
129        # variable in a variety of calculations.
130
131        self.floater = NodePath(PandaNode("floater"))
132        self.floater.reparentTo(render)
133
134        # Accept the control keys for movement and rotation
135
136        self.accept("escape", sys.exit)
137        self.accept("enter", self.activateCam)
138        self.accept("arrow_left", self.setKey, ["left", 1])
139        self.accept("arrow_right", self.setKey, ["right", 1])
140        self.accept("arrow_up", self.setKey, ["forward", 1])
141        self.accept("a", self.setKey, ["cam-left", 1])
142        self.accept("s", self.setKey, ["cam-right", 1])
143        self.accept("arrow_left-up", self.setKey, ["left", 0])
144        self.accept("arrow_right-up", self.setKey, ["right", 0])
145        self.accept("arrow_up-up", self.setKey, ["forward", 0])
146        self.accept("a-up", self.setKey, ["cam-left", 0])
147        self.accept("s-up", self.setKey, ["cam-right", 0])
148
149        #taskMgr.add(self.move,"moveTask")
150
151        # Game state variables
152        self.isMoving = False
153
154        # Set up the camera
155
156        #base.disableMouse()
157        #base.camera.setPos(self.ralph.getX(), self.ralph.getY() + 10, 2)
158
159        # We will detect the height of the terrain by creating a collision
160        # ray and casting it downward toward the terrain.  One ray will
161        # start above ralph's head, and the other will start above the camera.
162        # A ray may hit the terrain, or it may hit a rock or a tree.  If it
163        # hits the terrain, we can detect the height.  If it hits anything
164        # else, we rule that the move is illegal.
165
166        self.cTrav = CollisionTraverser()
167
168        self.ralphGroundRay = CollisionRay()
169        self.ralphGroundRay.setOrigin(0, 0, 1000)
170        self.ralphGroundRay.setDirection(0, 0, -1)
171        self.ralphGroundCol = CollisionNode('ralphRay')
172        self.ralphGroundCol.addSolid(self.ralphGroundRay)
173        self.ralphGroundCol.setFromCollideMask(BitMask32.bit(0))
174        self.ralphGroundCol.setIntoCollideMask(BitMask32.allOff())
175        self.ralphGroundColNp = self.ralph.attachNewNode(self.ralphGroundCol)
176        self.ralphGroundHandler = CollisionHandlerQueue()
177        self.cTrav.addCollider(self.ralphGroundColNp, self.ralphGroundHandler)
178
179        self.camGroundRay = CollisionRay()
180        self.camGroundRay.setOrigin(0, 0, 1000)
181        self.camGroundRay.setDirection(0, 0, -1)
182        self.camGroundCol = CollisionNode('camRay')
183        self.camGroundCol.addSolid(self.camGroundRay)
184        self.camGroundCol.setFromCollideMask(BitMask32.bit(0))
185        self.camGroundCol.setIntoCollideMask(BitMask32.allOff())
186        self.camGroundColNp = base.camera.attachNewNode(self.camGroundCol)
187        self.camGroundHandler = CollisionHandlerQueue()
188        self.cTrav.addCollider(self.camGroundColNp, self.camGroundHandler)
189
190        # Uncomment this line to see the collision rays
191        #self.ralphGroundColNp.show()
192        #self.camGroundColNp.show()
193
194        #Uncomment this line to show a visual representation of the
195        #collisions occuring
196        #self.cTrav.showCollisions(render)
197
198        self.setAI()
199
200    def activateCam(self):
201        self.switchCam = not self.switchCam
202        if self.switchCam is True:
203            base.cam.setPosHpr(0, 0, 0, 0, 0, 0)
204            base.cam.reparentTo(self.ralph)
205            base.cam.setY(base.cam.getY() + 30)
206            base.cam.setZ(base.cam.getZ() + 10)
207            base.cam.setHpr(180, -15, 0)
208        else:
209            base.cam.reparentTo(render)
210            base.cam.setPosHpr(17.79, -87.64, 90.16, 38.66, 325.36, 0)
211            #base.camera.setPos(self.ralph.getX(),self.ralph.getY()+10,2)
212
213    # Records the state of the arrow keys
214    def setKey(self, key, value):
215        self.keyMap[key] = value
216
217    # Accepts arrow keys to move either the player or the menu cursor,
218    # Also deals with grid checking and collision detection
219    def move(self):
220
221        # Get the time elapsed since last frame. We need this
222        # for framerate-independent movement.
223        elapsed = globalClock.getDt()
224
225        # If the camera-left key is pressed, move camera left.
226        # If the camera-right key is pressed, move camera right.
227        if self.switchState is False:
228            base.camera.lookAt(self.ralph)
229            if self.keyMap["cam-left"] != 0:
230                base.camera.setX(base.camera, -(elapsed * 20))
231            if self.keyMap["cam-right"] != 0:
232                base.camera.setX(base.camera, +(elapsed * 20))
233
234        # save ralph's initial position so that we can restore it,
235        # in case he falls off the map or runs into something.
236
237        startpos = self.ralph.getPos()
238
239        # If a move-key is pressed, move ralph in the specified direction.
240
241        if self.keyMap["left"] != 0:
242            self.ralph.setH(self.ralph.getH() + elapsed * 300)
243        if self.keyMap["right"] != 0:
244            self.ralph.setH(self.ralph.getH() - elapsed * 300)
245        if self.keyMap["forward"] != 0:
246            self.ralph.setY(self.ralph, -(elapsed * 25))
247
248        # If ralph is moving, loop the run animation.
249        # If he is standing still, stop the animation.
250
251        if self.keyMap["forward"] != 0 or self.keyMap["left"] != 0 or self.keyMap["right"] != 0:
252            if self.isMoving is False:
253                self.ralph.loop("run")
254                self.isMoving = True
255        else:
256            if self.isMoving:
257                self.ralph.stop()
258                self.ralph.pose("walk", 5)
259                self.isMoving = False
260
261        # If the camera is too far from ralph, move it closer.
262        # If the camera is too close to ralph, move it farther.
263        if self.switchState is False:
264            camvec = self.ralph.getPos() - base.camera.getPos()
265            camvec.setZ(0)
266            camdist = camvec.length()
267            camvec.normalize()
268            if camdist > 10.0:
269                base.camera.setPos(base.camera.getPos() + camvec * (camdist - 10))
270                camdist = 10.0
271            if camdist < 5.0:
272                base.camera.setPos(base.camera.getPos() - camvec * (5 - camdist))
273                camdist = 5.0
274
275        # Now check for collisions.
276
277        self.cTrav.traverse(render)
278
279        # Adjust ralph's Z coordinate.  If ralph's ray hit terrain,
280        # update his Z. If it hit anything else, or didn't hit anything, put
281        # him back where he was last frame.
282
283        #print(self.ralphGroundHandler.getNumEntries())
284
285        entries = []
286        for i in range(self.ralphGroundHandler.getNumEntries()):
287            entry = self.ralphGroundHandler.getEntry(i)
288            entries.append(entry)
289        entries.sort(lambda x, y: cmp(y.getSurfacePoint(render).z,
290                                      x.getSurfacePoint(render).z))
291        if entries and entries[0].getIntoNode().getName() == "terrain":
292            self.ralph.setZ(entries[0].getSurfacePoint(render).z)
293        else:
294            self.ralph.setPos(startpos)
295
296        # Keep the camera at one foot above the terrain,
297        # or two feet above ralph, whichever is greater.
298
299        if self.switchState is False:
300            entries = []
301            for i in range(self.camGroundHandler.getNumEntries()):
302                entry = self.camGroundHandler.getEntry(i)
303                entries.append(entry)
304            entries.sort(lambda x, y: cmp(y.getSurfacePoint(render).z,
305                                          x.getSurfacePoint(render).z))
306            if entries and entries[0].getIntoNode().getName() == "terrain":
307                base.camera.setZ(entries[0].getSurfacePoint(render).z + 1.0)
308            if base.camera.getZ() < self.ralph.getZ() + 2.0:
309                base.camera.setZ(self.ralph.getZ() + 2.0)
310
311            # The camera should look in ralph's direction,
312            # but it should also try to stay horizontal, so look at
313            # a floater which hovers above ralph's head.
314
315            self.floater.setPos(self.ralph.getPos())
316            self.floater.setZ(self.ralph.getZ() + 2.0)
317            base.camera.setZ(base.camera.getZ())
318            base.camera.lookAt(self.floater)
319
320        self.ralph.setP(0)
321        return Task.cont
322
323    def setAI(self):
324        # Creating AI World
325        self.AIworld = AIWorld(render)
326
327        self.accept("space", self.setMove)
328        self.AIchar = AICharacter("ralph", self.ralph, 60, 0.05, 25)
329        self.AIworld.addAiChar(self.AIchar)
330        self.AIbehaviors = self.AIchar.getAiBehaviors()
331
332        self.AIbehaviors.initPathFind("models/navmesh.csv")
333
334        # AI World update
335        taskMgr.add(self.AIUpdate, "AIUpdate")
336
337    def setMove(self):
338        self.AIbehaviors.addStaticObstacle(self.box)
339        self.AIbehaviors.addStaticObstacle(self.box1)
340        self.AIbehaviors.pathFindTo(self.pointer)
341        self.ralph.loop("run")
342
343    # To update the AIWorld
344    def AIUpdate(self, task):
345        self.AIworld.update()
346        self.move()
347
348        if self.path_no == 1 and self.AIbehaviors.behaviorStatus("pathfollow") == "done":
349            self.path_no = 2
350            self.AIbehaviors.pathFindTo(self.pointer1, "addPath")
351            print("inside")
352
353        if self.path_no == 2 and self.AIbehaviors.behaviorStatus("pathfollow") == "done":
354            print("inside2")
355            self.path_no = 1
356            self.AIbehaviors.pathFindTo(self.pointer, "addPath")
357
358        return Task.cont
359
360
361w = World()
362base.run()

The full working demo can be downloaded at:

https://sites.google.com/site/etcpandai/documentation/pathfinding/UnevenTerrainPathFinding.zip?attredirects=0&d=1