I’m trying to generalize simulated drift into 3 dimensions. The way this is implemented is that the direction of motion lags behind the direction the vehicle is facing and must catch up. I’ve got it working for heading or roll so long as none of the other directions are changed. Pitch freaks out (seems to roll 180 degrees and then jiggle wildly) after I pitch past vertical.
The other methods also only work as long as I haven’t changed direction. For example simDriftHeading works so long as I haven’t rolled.
It seems to be a problem with changing the coordinates that the motion is with respect to, but I don’t understand what is going on. Please help if you can.
My code is adapted from working code in the Panda3D Beginner’s Guide by Packt Publishing. You don’t need to have read the book to help with this.
I can provide additional code and comments as needed. Thank you for any time and insights you can provide.
See simDriftHeading, simDriftPitch, or simDriftRoll in the following code.
Here is the cycle class:
''' Cycle Class
Each instance of this class will be one of the cycles
racing on the track. This class handles all of the
variables and components necessary for a cycle controlled
by the player.
In addition, there is an option to have this class create
an instance of the CycleAI class to control it, instead of
using player input.'''
from direct.showbase.DirectObject import DirectObject
from pandac.PandaModules import *
from CycleAIClass import CycleAI
class Cycle(DirectObject):
def __init__(self, inputManager, track, startPos, name, ai = None):
# Stores a reference to the InputManager to access user input.
self.inputManager = inputManager
# Sets up initial variables, NodePaths, and collision objects.
self.setupVarsNPs(startPos, name)
# if the ai input is passed anything, it won't default to None, and the cycle will create an AI to control itself.
if(ai == True):
self.ai = CycleAI(self)
# If the cycle isn't using an AI, activate player control.
# With this set up, if AI == False, the cycle will be completely uncontrolled.
elif(ai == None):
taskMgr.add(self.cycleControl, "Cycle Control")
def setupVarsNPs(self, startPos, name):
'''setupVarsNPs: Initializes most of the non-collision variables and NodePaths needed by the cycle.'''
# Stores a unique name for this cycle.
self.name = name
# Creates and stores the NodePath that will be used as the root of the cycle
# for the purpose of movement.
self.root = render.attachNewNode("Root")
# Sets the root to a new position according to what place it is given.
self.root.setPos(5,0,0)
self.cycle = loader.loadModel("../Models/RedCycle.bam")
# Sets basic variables for the cycle's racing attributes.
self.speed = 0
self.maxSpeed = 200
self.handling = 20
# Loads the visual model for the cycle and reparents it to the root.
self.cycle.reparentTo(self.root)
# Creates a NodePaths to use as a reference during various calculations
# the cycle performs.
self.refNP = self.root.attachNewNode("RefNP")
#NodePath for the direction the cycle is facing.
self.dirNP = self.root.attachNewNode("DirNP")
# Connects the camera to dirNP so the camera will follow and
# rotate with that node. Also moves it backward 5 meters.
base.camera.reparentTo(self.dirNP)
base.camera.setY(base.camera, -10)
#Direction vector used for calculating move
# Creates and stores 3 vector objects to be used in simulating drift when the
# cycle turns.
self.dirVec = Vec3(0,0,0)
self.cycleVec = Vec3(0,0,0)
self.refVec = Vec3(0,0,0)
def cycleControl(self, task):
'''cycleControl: Manages the cycle's behavior when under player control.'''
# Gets the amount of time that has passed since the last frame
#from the global clock. If the value is too large, there has
#been a hiccup and the frame will be skipped.
dt = globalClock.getDt()
if( dt > .20): return task.cont
# Checks the InputManager for turning initiated by the user and performs it.
if(self.inputManager.keyMap["d"] == True):
self.turn("r", dt)
elif(self.inputManager.keyMap["a"] == True):
self.turn("l", dt)
#Changing pitch
if(self.inputManager.keyMap["arrow_up"]):
self.changePitch('up', dt)
elif(self.inputManager.keyMap["arrow_down"]):
self.changePitch('down', dt)
#Rolling
if(self.inputManager.keyMap["arrow_left"]):
self.roll('counter', dt)
elif(self.inputManager.keyMap["arrow_right"]):
self.roll('clockwise', dt)
# Calls the methods that control cycle behavior frame-by-frame.
self.simDriftPitch(dt)
#self.simDriftHeading(dt)
#self.simDriftRoll(dt)
return task.cont
def turn(self, dir, dt):
# turn: Rotates the cycle based on its speed to execute turns.
# Determines the current turn rate of the cycle according to its speed.
turnRate = self.handling * (2 - (self.speed / self.maxSpeed))
# If this is a right turn, then turnRate should be negative.
if(dir == "r"):
turnRate = -turnRate
# Rotates the cycle according to the turnRate and time.
self.cycle.setH(self.cycle, turnRate * dt)
def changePitch(self, dir, dt):
'''dir = up/down'''
turnRate = self.handling * (2 - (self.speed / self.maxSpeed))
if(dir == "down"):
turnRate = -turnRate
self.cycle.setP(self.cycle, turnRate * dt)
def roll(self, dir, dt):
'''dir = counter/clockwise'''
turnRate = self.handling * (2 - (self.speed / self.maxSpeed))
if(dir == "counter"):
turnRate = -turnRate
self.cycle.setR(self.cycle, turnRate * dt)
def simDriftPitch(self, dt):
'''simDrift: This function simulates the cycle drifting when the cycle pitches by causing the dirNP, which faces the direction the cycle is moving in, to slowly catch up to the actual facing of the cycle over time.'''
# Uses refNP to get a vector that describes the facing of dirNP.
self.refNP.setPos(self.dirNP, 0, 1, 0)
self.dirVec.set(self.refNP.getX(), self.refNP.getY(), self.refNP.getZ())
# Uses refNP to get a vector that describes the facing of the cycle.
#The height value is discarded as it is unnecessary.
self.refNP.setPos(self.cycle, 0, 1, 0)
self.cycleVec.set(self.refNP.getX(), self.refNP.getY(), self.refNP.getZ())
# Sets refVec to point straight up. This vector will be the axis
#used to determine the difference in the angle between dirNP and the cycle.
self.refVec.set(1,0,0)
# Gets a signed angle that describes the difference between the
#facing of dirNP and the cycle.
vecDiff = self.dirVec.signedAngleDeg(self.cycleVec, self.refVec)
# if the difference between the two facings is insignificant, set
#dirNP to face the same direction as the cycle.
#print str(vecDiff)+' - '+str(self.dirVec)+' - '+str(self.cycleVec)
if(vecDiff < .1 and vecDiff > -.1):
self.dirNP.setP(self.cycle.getP())
# If the difference is significant, tell dirNP to slowly rotate to
#try and catch up to the cycle's facing.
else:
self.dirNP.setHpr(self.dirNP, 0, vecDiff * dt * 2.5, 0)
# Constrains dirNP heading and roll to the cycle.
self.dirNP.setH(self.cycle.getH())
self.dirNP.setR(self.cycle.getR())
def simDriftHeading(self, dt):
'''simDrift: This function simulates the cycle drifting when it turns by causing the dirNP, which faces the direction the cycle is moving in, to slowly catch up to the actual facing of the cycle over time.'''
# Uses refNP to get a vector that describes the facing of dirNP.
self.refNP.setPos(self.dirNP, 0, 1, 0)
self.dirVec.set(self.refNP.getX(), self.refNP.getY(), self.refNP.getZ())
# Uses refNP to get a vector that describes the facing of the cycle.
self.refNP.setPos(self.cycle, 0, 1, 0)
self.cycleVec.set(self.refNP.getX(), self.refNP.getY(), self.refNP.getZ())
# Sets refVec to point straight up. This vector will be the axis
#used to determine the difference in the angle between dirNP and the cycle.
self.refVec.set(0,0,1)
# Gets a signed angle that describes the difference between the
#facing of dirNP and the cycle.
vecDiff = self.dirVec.signedAngleDeg(self.cycleVec, self.refVec)
# if the difference between the two facings is insignificant, set
#dirNP to face the same direction as the cycle.
#print vecDiff
if(vecDiff < .1 and vecDiff > -.1):
self.dirNP.setH(self.cycle.getH())
# If the difference is significant, tell dirNP to slowly rotate to
#try and catch up to the cycle's facing.
else:
self.dirNP.setHpr(self.dirNP, vecDiff * dt * 2.5, 0, 0)
# Constrains dirNP pitch and roll to the cycle.
self.dirNP.setP(self.cycle.getP())
self.dirNP.setR(self.cycle.getR())
def simDriftRoll(self, dt):
'''simDrift: This function simulates the cycle drifting when it rolls by causing the dirNP, which faces the direction the cycle is moving in, to slowly catch up to the actual facing of the cycle over time.'''
# Uses refNP to get a vector that describes the facing of dirNP.
self.refNP.setPos(self.dirNP, 0, 0, 1)
self.dirVec.set(self.refNP.getX(), self.refNP.getY(), self.refNP.getZ())
# Uses refNP to get a vector that describes the facing of the cycle.
self.refNP.setPos(self.cycle, 0, 0, 1)
self.cycleVec.set(self.refNP.getX(), self.refNP.getY(), self.refNP.getZ())
# Sets refVec to point straight up. This vector will be the axis
#used to determine the difference in the angle between dirNP and the cycle.
self.refVec.set(0,1,0)
# Gets a signed angle that describes the difference between the
#facing of dirNP and the cycle.
vecDiff = self.dirVec.signedAngleDeg(self.cycleVec, self.refVec)
#print str(vecDiff)+' - '+str(self.dirVec)+' - '+str(self.cycleVec)
# if the difference between the two facings is insignificant, set
#dirNP to face the same direction as the cycle.
if(vecDiff < .1 and vecDiff > -.1):
self.dirNP.setR(self.cycle.getR())
# If the difference is significant, tell dirNP to slowly rotate to
#try and catch up to the cycle's facing.
else:
self.dirNP.setHpr(self.dirNP, 0, 0, vecDiff * dt * 2.5)
# Constrains dirNP heading and pitch to the cycle.
self.dirNP.setH(self.cycle.getH())
self.dirNP.setP(self.cycle.getP())