Vectors & Physics :: Gamedev Panda3D cookbook

Based on Daniel Shiffman’s essays (http://www.shiffman.net/teaching/nature/)

In order to simulate movements in games, basically we change object’s coordinates values along software’s execution cycles; The result is the illusion of motion.
There some classical way to change those coordinates:
– Simply change the values…;
– Calculating vectors and implement kinematics;
– Physics simulation;

sample 1:

[code lang=”python”]
from direct.showbase.ShowBase import ShowBase
from direct.task import Task

class MyApp(ShowBase):

def __init__(self):
ShowBase.__init__(self)
self.taskMgr.add(self.taskFunction, “testing Task”)
self.cube = self.loader.loadModel(“box”);
self.cube.reparentTo(self.render)
self.cube.setScale(1)
self.cube.setPos(-3,10,-2)

def taskFunction(self, task):
print “Cube’s position is: “, self.cube.getPos()
self.cube.setX(self.cube.getX()+0.01)
return Task.cont

app = MyApp()
app.run()
[/code]

This first sample, show how to create a simple motion by simply changing this cube’s x coordinate value. Pay attention in function taskFunction, over there, the cube’s ‘x’ position is incremented in 0.01 each time the task is executed.

sample 2:

[code lang=”python”]

from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from pandac.PandaModules import Vec3

class MyApp(ShowBase):

def __init__(self):
ShowBase.__init__(self)

self.vector_left = Vec3(-0.01,0, 0)
self.vector_right= Vec3( 0.01,0, 0)
self.vector_up = Vec3( 0 ,0, 0.01)
self.vector_down = Vec3( 0 ,0,-0.01)
self.main_vec = Vec3(0,0,0)

self.taskMgr.add(self.taskFunction, “testing Task”)
self.cube = self.loader.loadModel(“box”);
self.cube.reparentTo(self.render)
self.cube.setScale(1)
self.cube.setPos(-3,10,-2)

self.accept(‘d’, self.changeDirection, [self.vector_right])
self.accept(‘a’, self.changeDirection, [self.vector_left])
self.accept(‘w’, self.changeDirection, [self.vector_up])
self.accept(‘s’, self.changeDirection, [self.vector_down])

def changeDirection(self, direction):
self.main_vec = direction

def taskFunction(self, task):
self.cube.setPos(self.cube.getPos()+self.main_vec)
return Task.cont

app = MyApp()
app.run()

[/code]

This second sample, shows a very simple way to use vectors instead changing coordinates values. Let’s take a closer look on vectors now.

Vectors: ========================================
references:

When an object moves through out a space, there is a very handing way to represent this motion: Vectors. That’s why you use vectors on all those physics equations on school! Vectors host key values to describe motion: Current position, motion direction, motion intensity, and even distances. Vectors might be 2D vectors and 3D vectors, well, theoretically vectors can have infinite dimensions, but let’s keep focus on game usage: 2D or 3D.

Let’s consider a rectangle moving around the screen. In a certain moment, this rectangle will have this description:
[quote]
Location – the point where the rect is now.
Speed – value that represents the distance the rect moves per time unity.
Acceleration – value that interferer on speed: increasing or decreasing it.
[/quote]
Note: This are all vectors, that might be 2D or 3D, depending on how many dimensions the space uses.

Technically loacation is a point, speed and acceleration are vectors, but for our purposes let’s use them all as vectors. Take a look at the next “pseudocode”, to see how location, speed and acceleration interacts to create a motion based on classic physics:

[quote]
speed = speed + acceleration
Location = location + speed
Than draw the thing on scene.
[/quote]

According this very simple algorithm, what we have: acceleration interfere on speed – speed interfere on location – to finish, we have to draw the thing on screen.

sample 3:
[code lang=”python”]
from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from pandac.PandaModules import Vec3

class MyApp(ShowBase):

def __init__(self):
ShowBase.__init__(self)

self.vector_left = Vec3(-0.01,0, 0)
self.vector_right= Vec3( 0.01,0, 0)
self.vector_up = Vec3( 0 ,0, 0.01)
self.vector_down = Vec3( 0 ,0,-0.01)
self.main_vec = Vec3(0,0,0)

self.taskMgr.add(self.taskFunction, “testing Task”)
self.cube = self.loader.loadModel(“box”);
self.cube.reparentTo(self.render)
self.cube.setScale(1)
self.cube.setPos(-3,10,-2)

self.cubeSpeed = Vec3(0,0,0)
self.cubeAcc = Vec3(0,0,0)

self.accept(‘d’, self.changeDirection, [self.vector_right])
self.accept(‘a’, self.changeDirection, [self.vector_left])

def changeDirection(self, direction):
self.main_vec = direction

def taskFunction(self, task):
self.updateCube()
return Task.cont

def updateCube(self):
self.cubeAcc = Vec3(self.main_vec)
self.cubeSpeed += self.cubeAcc
self.cube.setPos( self.cube.getPos() + self.cubeSpeed )

app = MyApp()
app.run()
[/code]

The sample above, show how to implement vector kinematics on our previous vector sample. Pay attention on self.updateCube function, that small algorithm is there. In this sample you will notice that, as soon as you press ‘a’ ou ‘d’ key, the cube will begin move slowly, but it’s speed will increase fast. That happens because when acceleration is constant, the speed will increase at every instant, accumulating it’s previous values, and incrementing to it new acceleration additions.
To change this scenario compare to this code:

sample 3b:

[code lang=”python”]
from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from pandac.PandaModules import Vec3

class MyApp(ShowBase):

def __init__(self):
ShowBase.__init__(self)

self.vector_left = Vec3(-0.01,0, 0)
self.vector_right= Vec3( 0.01,0, 0)
self.vector_up = Vec3( 0 ,0, 0.01)
self.vector_down = Vec3( 0 ,0,-0.01)
self.main_vec = Vec3(0,0,0)

self.taskMgr.add(self.taskFunction, “testing Task”)
self.cube = self.loader.loadModel(“box”);
self.cube.reparentTo(self.render)
self.cube.setScale(1)
self.cube.setPos(-3,10,-2)

self.cubeSpeed = Vec3(0,0,0)
self.cubeAcc = Vec3(0,0,0)

self.accept(‘d’, self.changeDirection, [self.vector_right])
self.accept(‘a’, self.changeDirection, [self.vector_left])

def changeDirection(self, direction):
self.main_vec = direction
self.cubeAcc = Vec3(self.main_vec)

def taskFunction(self, task):
self.updateCube()
return Task.cont

def updateCube(self):
self.cubeSpeed += self.cubeAcc
self.cube.setPos( self.cube.getPos() + self.cubeSpeed )
self.cubeAcc *= 0

app = MyApp()
app.run()
[/code]

A few differences here:
– Note that self.cubeAcc is define only when the key is pressed.
– Note that, self.cubeAcc is set to zero (at the end of function updateCube)

This small differences make the cube accelerates only when you press the keys, after that, the speed gets constant.

In practice, on vectors we have:
– positioning
– direction
– intensity

Those properties are very handy when we think about movements. Thanks to Python, we can calculate all that vectors easily, but that doesn’t happen very often in other languages. Usually we have to watch to vector operations in order to get different vectors, to use them in different motions… Probably the most used vector operation is vector subtraction. Usually we use vector subtraction, in order to make an object pursue another object, the magic happens because: When B vector is subtracted by A vector, the resultant C vector, points to B vector from A vector, with the same size as the distance between A and B.

The next sample shows an usage of this vector subtraction. A sphere goes moving from cube to cube calculating vector subtraction. All the work is in function update (inside Smiley class).

[code lang=”python”]
from pandac.PandaModules import loadPrcFileData
loadPrcFileData(”,’show-frame-rate-meter 1′)

from direct.showbase.ShowBase import ShowBase
from direct.showbase.DirectObject import DirectObject
from direct.task import Task
from pandac.PandaModules import *

class Cube(DirectObject):
loc = Vec3(0,0,0)

def __init__(self, pos):
self.loc = pos
self.loadModels()

def loadModels(self):
self.cube = loader.loadModel(‘box’)
self.cube.setPos(self.loc)
self.cube.reparentTo(render)

# Class smiley ————————————
class Smiley(DirectObject):
loc = Vec3(0,0,1)
speed = Vec3(0,0,0)
acc = Vec3(0,0,0)
mass = 1
maxForce = 0.1

def __init__(self, pathToFollow):
self.loadModels()
self.way = pathToFollow
self.nextIt = 1;
self.next = self.way[1].loc
print “next pos: “, self.next

def loadModels(self):
self.smiley = loader.loadModel(‘smiley’)
self.smiley.setPos(self.loc)
self.smiley.reparentTo(render)

def update(self):
target = self.next
direction = target – self.loc
distance = direction.length()
direction.normalize()
direction *= self.maxForce

self.loc += direction
if(distance = len(self.way) ):
self.nextIt=0
self.next = self.way[self.nextIt].loc
self.smiley.setPos(self.loc)

#game Class —————————————
class MyApp(ShowBase):

def __init__(self):
ShowBase.__init__(self)

self.box0 = Cube(Vec3(0,0,1))
self.box1 = Cube(Vec3(10,0,1))
self.box2 = Cube(Vec3(5,10,1))
self.box3 = Cube(Vec3(-10,10,1))
self.box4 = Cube(Vec3(-10,-5,1))
self.box5 = Cube(Vec3(0,-10,1))

self.wayPoints = (self.box0, self.box1, self.box2, self.box3, self.box4, self.box5)

self.smileyForce = 0.5
self.smiley = Smiley(self.wayPoints)

#code for floor
self.cm = CardMaker(“ground”)
self.cm.setFrame(-20, 20, -20, 20)
self.ground = render.attachNewNode(self.cm.generate())
self.ground.setPos(0, 0, 0)
self.ground.lookAt(0, 0, -1)

self.taskMgr.doMethodLater(4, self.update, “update task”)

# Set the camera position
base.disableMouse()
base.camera.setPos(15, -30, 20)
base.camera.lookAt(0, 0, 0)

self.fiatLux()

def update(self, task):
self.smiley.update()
return Task.cont

def fiatLux(self):

# Directional light 01
self.directionalLight = DirectionalLight(‘directionalLight’)
self.directionalLight.setColor(Vec4(0.95, 0.95, 0.8, 1))
self.directionalLightNP = render.attachNewNode(self.directionalLight)
# This light is facing backwards, towards the camera.
self.directionalLightNP.setHpr(45, -15, 0)
render.setLight(self.directionalLightNP)

game = MyApp()

run()
[/code]

Physics ========================================

In order to simplify physics simulations, game engines provide physics engines. In Panda3D, the default physics engine is ODE.
Those physics engines, do all the physics calculus, collisions, joints, and provide data to update our scene.

sample 4:
[code lang=”python”]

from pandac.PandaModules import loadPrcFileData
loadPrcFileData(”,’show-frame-rate-meter 1′)

from direct.showbase.ShowBase import ShowBase
from direct.showbase.DirectObject import DirectObject
from direct.task import Task
from pandac.PandaModules import *

class MyApp(ShowBase):

def __init__(self):
ShowBase.__init__(self)

self.fiatLux()

#Create physics world
self.world = OdeWorld()
self.world.setGravity(0,0,-9.81)

#prepare for collisions
self.world.initSurfaceTable(1)
self.world.setSurfaceEntry(0, 0, 150, 0.0, 9.1, 0.9, 0.00001, 0.0, 0.002)

#create a space to allow collision events
self.space = OdeSimpleSpace()
self.space.setAutoCollideWorld(self.world)
self.contactgroup = OdeJointGroup()
self.space.setAutoCollideJointGroup(self.contactgroup)

#creates a floor in game scene, and physics world
self.cm = CardMaker(“ground”)
self.cm.setFrame(-20, 20, -20, 20)
self.ground = render.attachNewNode(self.cm.generate())
self.ground.setPos(0, 0, 0); self.ground.lookAt(0, 0, -1)
self.groundGeom = OdePlaneGeom(self.space, Vec4(0, 0, 1, 0))

#Creates a cube in game scene
self.box = loader.loadModel(‘../assets/eggs/box_c.egg’)
self.box.reparentTo(render)
self.box.setPos(0,9.5,7)
self.box.setHpr(5,35,57)
#self.box.setScale(1)
self.box.setColor(0.5, 0.5, 0.5, 1)

#creates a “body”: representation of the cube in physic world.
self.boxBody = OdeBody(self.world)
self.boxMass = OdeMass()
self.boxMass.setBox(100,1,1,1)
self.boxBody.setMass(self.boxMass)
self.boxBody.setPosition(self.box.getPos(render))
#print self.box.getQuat(render)
self.boxBody.setQuaternion(self.box.getQuat(render))
#print self.boxBody.getQuaternion()
#Creates a collision solid for the body
self.boxGeom = OdeBoxGeom(self.space, 2,2,2)
self.boxGeom.setBody(self.boxBody)

# Set the camera position
base.disableMouse()
base.camera.setPos(40, 40, 20)
base.camera.lookAt(0, 0, 0)

self.taskMgr.doMethodLater(4, self.update, “update task”)

def update(self, task):
#mandatory
self.space.autoCollide()
self.world.quickStep(globalClock.getDt())

#Update cube rendering, based on physics world data
self.box.setPosQuat(render, self.boxBody.getPosition(), Quat(self.boxBody.getQuaternion()))

#mandatory when you need collision events
self.contactgroup.empty()
return Task.cont

def fiatLux(self):

# Directional light 01
self.directionalLight = DirectionalLight(‘directionalLight’)
self.directionalLight.setColor(Vec4(0.95, 0.95, 0.8, 1))
self.directionalLightNP = render.attachNewNode(self.directionalLight)
# This light is facing backwards, towards the camera.
self.directionalLightNP.setHpr(45, -15, 0)
render.setLight(self.directionalLightNP)

game = MyApp()

run()

[/code]

In the sample above we have a very simple scene, a cube that falls, and hits a table, but all the motion is simulated by physics engine. The usage of physics engines demand some specific setups, and integration with rendering system, but all the hard work is made by the physics engine.

We also can simulate some forces been applied into objects!! like a kick on a ball; throw a stone; whatever you want… Let’s see a sample, when simulation starts, press ‘f’ key:

[code lang=”python”]

from pandac.PandaModules import loadPrcFileData
loadPrcFileData(”,’show-frame-rate-meter 1′)

from direct.showbase.ShowBase import ShowBase
from direct.showbase.DirectObject import DirectObject
from direct.task import Task
from pandac.PandaModules import *

class MyApp(ShowBase):

def __init__(self):
ShowBase.__init__(self)

self.fiatLux()

self.world = OdeWorld()
self.world.setGravity(0,0,-9.81)

self.world.initSurfaceTable(1)
self.world.setSurfaceEntry(0, 0, 150, 0.0, 9.1, 0.9, 0.00001, 0.0, 0.002)

self.space = OdeSimpleSpace()
self.space.setAutoCollideWorld(self.world)
self.contactgroup = OdeJointGroup()
self.space.setAutoCollideJointGroup(self.contactgroup)

self.cm = CardMaker(“ground”)
self.cm.setFrame(-20, 20, -20, 20)
self.ground = render.attachNewNode(self.cm.generate())
self.ground.setPos(0, 0, 0); self.ground.lookAt(0, 0, -1)
self.groundGeom = OdePlaneGeom(self.space, Vec4(0, 0, 1, 0))

self.box = loader.loadModel(‘../assets/eggs/box_c.egg’)
self.box.reparentTo(render)
self.box.setPos(0,9.5,7)
self.box.setHpr(5,35,57)
self.box.setColor(0.5, 0.5, 0.5, 1)

self.boxBody = OdeBody(self.world)
self.boxMass = OdeMass()
self.boxMass.setBox(100,1,1,1)
self.boxBody.setMass(self.boxMass)
self.boxBody.setPosition(self.box.getPos(render))
self.boxBody.setQuaternion(self.box.getQuat(render))
#collision solid in physics world
self.boxGeom = OdeBoxGeom(self.space, 2,2,2)
self.boxGeom.setBody(self.boxBody)

# Set the camera position
base.disableMouse()
base.camera.setPos(40, 40, 20)
base.camera.lookAt(0, 0, 0)

#press ‘f’ key
self.accept(‘f’, self.applyForce)

self.taskMgr.doMethodLater(4, self.update, “update task”)

def update(self, task):
#mandatory
self.space.autoCollide()
self.world.quickStep(globalClock.getDt())

#update rendering system
self.box.setPosQuat(render, self.boxBody.getPosition(), Quat(self.boxBody.getQuaternion()))

#mandatory for collision events
self.contactgroup.empty()
return Task.cont

def fiatLux(self):

# Directional light 01
self.directionalLight = DirectionalLight(‘directionalLight’)
self.directionalLight.setColor(Vec4(0.95, 0.95, 0.8, 1))
self.directionalLightNP = render.attachNewNode(self.directionalLight)
# This light is facing backwards, towards the camera.
self.directionalLightNP.setHpr(45, -15, 0)
render.setLight(self.directionalLightNP)

def applyForce(self):
print “apply force!”
print self.boxBody.getMass()
self.boxBody.setForce(100000,0,0)

game = MyApp()

run()

[/code]

Leave a Reply

Your email address will not be published. Required fields are marked *