Vehicle On Terrain

This is a simple way to keep a vehicle aligned to the Terrain it is on.

To get to the good part, skip to the collisionUpdate function.

#
# Tank.py
#
# Author: mss
#

#
# Imports
#

from direct.showbase import DirectObject
from panda3d.core import BitMask32
from panda3d.core import CollisionNode
from panda3d.core import CollisionSphere
from panda3d.core import CollisionRay
from panda3d.core import CollisionSegment
from panda3d.core import CollisionHandlerGravity
from panda3d.core import CollisionHandlerQueue
from panda3d.core import Vec3
from panda3d.core import Point3
from panda3d.core import rad2Deg
from direct.interval.IntervalGlobal import *

from Game import Game

#
# Tank Class
#

class Tank(DirectObject.DirectObject):
    
    def __init__(self):

        self.Reverse = False
        self.Left = False
        self.Right = False
        self.Forward = False
        self.Velocity = 0

        self.Tank = Game.getLoader().loadModel("content/Tank.egg")
        self.Tank.setPos(0,0,10)   
        self.Tank.reparentTo(Game.getRender())

        self.Handler = CollisionHandlerGravity()
        self.cQueue = CollisionHandlerQueue()

        #
        # Setup the CollisionRay.
        # This is used for collisions against the Terrain.
        # I change the bitmask so the ray doesn't collide with the polyset
        # mesh below.
        #
                
        self.Ray = CollisionRay(0,0,1,0,0,-1)        
        self.RayCollider = self.Tank.attachNewNode(CollisionNode("CollisionRay"))
        self.RayCollider.node().addSolid(self.Ray)
        self.RayCollider.setCollideMask(BitMask32.allOff())
        self.Handler.addCollider(self.RayCollider, self.Tank)
        Game.getTraverser().addCollider(self.RayCollider, self.Handler)        
        Game.getTraverser().addCollider(self.RayCollider, self.cQueue)        

        #
        # Setup the polyset Collision mesh created in Blender.
        # Bitmask is all 1's to ignore collisions with the ray.
        #

        self.Collider = self.Tank.find("*/Collision")        
        self.Collider.node().addSolid(self.Ray)        
        self.Collider.setCollideMask(BitMask32.allOn())
        self.Handler.addCollider(self.Collider, self.Tank)
        Game.getTraverser().addCollider(self.Collider, self.Handler)

        #
        # Init Key Input
        #
        
        self.accept('w', self.wKey)
        self.accept('w-up', self.wKeyUp)
        self.accept('s', self.sKey)
        self.accept('s-up', self.sKeyUp)

        self.accept('a', self.aKey)
        self.accept('a-up', self.aKeyUp)
        self.accept('d', self.dKey)
        self.accept('d-up', self.dKeyUp)

        #
        # Tasks
        #

        taskMgr.add(self.moveUpdate, "TankMoveUpdate")
        taskMgr.add(self.collisionUpdate, "TankCollisionUpdate")

        return

    #
    # Collision Task
    #

    def collisionUpdate(self, task):
        if self.cQueue.getNumEntries() > 0:

            #
            # For now, the Tank only collides with the Terrain so I don't
            # have to check to make sure this is the collision I want.
            #
            
            entry = self.cQueue.getEntry(0)
            if entry.hasSurfacePoint():
                np = entry.getIntoNodePath()
                point = Vec3(entry.getSurfacePoint(Game.getRender()))                
                normal = Vec3(entry.getSurfaceNormal(Game.getRender()))
                pos = Vec3(self.Tank.getPos(render))

                #
                # Align Tank To Terrain
                #
                # I use headsUp to orientate the tank to the slope of the
                # terrain, however: I have to hack the heading so headsUp()
                # doesn't mistakingly change it, which it does on "harder"
                # angles.
                #
                # I have the Pitch and Roll move to the Pitch and Roll
                # given by headsUp, over a set amount of time.
                # LerpIntervals will not work because it will lerp all HPR
                # values, constraining user heading changes.
                #
                #  It gets pretty nasty on steep angles, but tanks shouldn't
                # be going up them anyway..
                #
                
                fwd = Game.getRender().getRelativePoint(self.Tank, (0,1,0))                
                oldHpr = self.Tank.getHpr()
                self.Tank.headsUp(fwd, normal)
                newHpr = self.Tank.getHpr()
                newHpr.setX(oldHpr.getX())
                self.Tank.setHpr(oldHpr)

                #
                # The magic number.
                # (How fast the tank changes Pitch and Roll over time)
                #
                
                magicNumber = 90.0

                if self.Tank.getP() > newHpr.getY():
                    self.Tank.setP( self.Tank.getP() - magicNumber * Game.getClock().getDt())

                if self.Tank.getP() < newHpr.getY():
                    self.Tank.setP( self.Tank.getP() + magicNumber * Game.getClock().getDt())

                if self.Tank.getR() > newHpr.getZ():
                    self.Tank.setR( self.Tank.getR() - magicNumber * Game.getClock().getDt())

                if self.Tank.getR() < newHpr.getZ():
                    self.Tank.setR( self.Tank.getR() + magicNumber * Game.getClock().getDt())
                                    
        return task.cont

    #
    # KEYS
    #
    
    def wKey(self):
        self.Reverse = False
        self.Forward = True

    def wKeyUp(self):
        self.Forward = False

    def sKey(self):
        self.Forward = False
        self.Reverse = True

    def sKeyUp(self):
        self.Reverse = False

    def aKey(self):
        self.Right = False
        self.Left = True

    def aKeyUp(self):
        self.Left = False

    def dKey(self):        
        self.Left = False
        self.Right = True

    def dKeyUp(self):
        self.Right = False        

    #
    # Move Task
    #
    
    def moveUpdate(self, task):

        if self.Left:
            hpr = self.Tank.getHpr()
            hpr.setX(hpr.getX() + (100.0 * Game.getClock().getDt()) )            
            self.Tank.setHpr(hpr)

        if self.Right:
            hpr = self.Tank.getHpr()
            hpr.setX( hpr.getX() - (100.0 * Game.getClock().getDt()) )            
            self.Tank.setHpr(hpr)
                  
        if self.Forward:
            if self.Velocity < 0.1:
                self.Velocity += 1.0 * Game.getClock().getDt()

        if self.Reverse:
            if self.Velocity > -0.1:
                self.Velocity -= 1.0 * Game.getClock().getDt()

        pos = self.Tank.getPos(self.Tank)
        pos -= Vec3(0,self.Velocity,0)
        self.Tank.setPos(self.Tank, pos)

        if self.Velocity > 0:
            self.Velocity -= 0.75 * Game.getClock().getDt()
            if self.Velocity < 0.001:
                self.Velocity = 0                
                
        if self.Velocity < 0:
            self.Velocity += 0.75 * Game.getClock().getDt()
            if self.Velocity > -0.001:
                self.Velocity = 0

        #print self.Velocity
        
        return task.cont

#
# Instatiate Tank Object
#
        
Tank = Tank()
        

Hi, this looks cool, pretty much exactly what I want but I’m getting confused with headsUp(), do you have the code for that method anywhere?

Thanks

Poncho, it looks as though constantexpected is using [urlhttp://www.panda3d.org/reference/1.8.0/python/classpanda3d.core.NodePath.php#a7ca60ef5f87290aab210490ec661db88]this “headsUp” method[/url].

constantexpected, I haven’t tried out your code, but it looks like a rather cool and potentially useful piece of code. Thank you for sharing it! :slight_smile: