Setting the header of an Actor

All,
I am new to Panda3D and am having some trouble. I am using Panda3D as a playback engine for personnel movement. I need to be able to place an actor at a specific point (x,y,z). I have an HDF5 file with position data and can set the positions without issue.
The problem I am having is setting the heading of an actor. I want the heading to point in the direction the object is moving, probably quite obvious :slight_smile:
I am using the roaming ralph example and setting his position explicitly based on the positions in the HDF5 file.

Any help is greatly appreciated.

Thanks in advance,
John

Can you tell us more precisely what your problem is? You want to set the actor to face a particular heading, but you can’t use actor.setH(heading) for some reason?

Or maybe you mean you’re not sure how to compute the heading based on the direction he’s moving? In that case, you can use actor.lookAt(newPoint), where newPoint is the position the actor will have after you apply his motion.

David

David,
I am not sure how to compute the heading based on the direction he’s moving? Again I am using the roaming ralph example at present and am simply setting his position based on the points from a file and not using the keyboard.
The example uses an if statement:
if (self.keyMap[“left”]!=0):
self.ralph.setH(self.ralph.getH() \
300 * globalClock.getDt())
…
I was thinking the camera looks at the floater so I oriented the floater object at +2.0 on the Y axis and set my actors lookAt vector at the floater object. But it did not work, below is a snippet of code.

Thanks again.

…more code from example
# The camera should look in ralph’s direction,
# but it should also try to stay horizontal,
# so look at
# a floater which hovers above ralph’s head.
self.floater.setPos(self.ralph.getPos())
#set the floater ahead of ralph by 2 units
#set ralph’s lookAt vector towards floater object
self.floater.setY(self.ralph.getY() + 2.0)
self.floater.setZ(self.ralph.getZ() + 2.0)
base.camera.lookAt(self.floater)

    #Right now we are using the frame number to 
    # cycle through the
    #positions, when we get to the end we need 
    # to either reset the frame number
    # or find a new index algorithm
    
    try:
        self.ralph.setPos(self.positions[task.frame][0],
            self.positions[task.frame][1],
            self.positions[task.frame][2])
        self.ralph.lookAt(self.floater)
    except IndexError, e:
        print "End of positions"

How about:

self.ralph.lookAt(self.positions[task.frame])
self.ralph.setPos(self.positions[task.frame])

David

No luck

I’ve pasted the entire example below, thanks

#!/usr/bin/env python

Author: Ryan Myers

Models: Jeff Styers, Reagan Heller

Last Updated: 6/13/2005

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 Point3,Vec3,Vec4,BitMask32
from direct.interval.IntervalGlobal import *
from direct.gui.OnscreenText import OnscreenText
from direct.showbase.DirectObject import DirectObject
from direct.fsm.FSM import FSM

import random, sys, os, math

#import my class
from AddActor import AddActor

SPEED = 0.5

#Using PyTables to read HDF5 file
import tables

Function to put instructions on the screen.

def addInstructions(pos, msg):
return OnscreenText(text=msg, style=1, fg=(1,1,1,1),
pos=(-1.3, pos), align=TextNode.ALeft, scale = .05)

Function to put title on the screen.

def addTitle(text):
return OnscreenText(text=text, style=1, fg=(1,1,1,1),
pos=(1.3,-0.95), align=TextNode.ARight, scale = .07)

class World(DirectObject):
actors = []
positions = []
start_pos = 0
intervals = []

# Game state variables
isMoving = False

def __init__(self):
    self.getpositions()
    
    base.win.setClearColor(Vec4(0,0,0,1))

    # Post the instructions

    self.title = addTitle("Panda3D Tutorial: Roaming Ralph (Walking on Uneven Terrain)")
    self.inst1 = addInstructions(0.95, "[ESC]: Quit")
    self.inst2 = addInstructions(0.90, "Playback of actor positions")
    
    # 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, from my AddActor class
    self.ralph = AddActor(position=self.getposition(0),
                        name="Ralph")
    
    #must attach actor to renderer
    self.ralph.reparentTo(render)
    
    # Create a floater object.  We use the "floater" as a temporary
    # variable in a variety of calculations.
    
    self.floater = NodePath(PandaNode("floater"))
    self.floater.reparentTo(render)

    # Accept the escape key to exit application
    self.accept("escape", sys.exit)

    taskMgr.add(self.incrementpos,"movePosTask")
    
    base.disableMouse()
    base.camera.setPos( self.ralph.getX(), self.ralph.getY() + 5, 5 )
    
    # 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.camGroundRay = CollisionRay()
    self.camGroundRay.setOrigin(0,0,1000)
    self.camGroundRay.setDirection(0,0,-1)
    self.camGroundCol = CollisionNode('camRay')
    self.camGroundCol.addSolid(self.camGroundRay)
    self.camGroundCol.setFromCollideMask(BitMask32.bit(0))
    self.camGroundCol.setIntoCollideMask(BitMask32.allOff())
    self.camGroundColNp = base.camera.attachNewNode(self.camGroundCol)
    self.camGroundHandler = CollisionHandlerQueue()
    self.cTrav.addCollider(self.camGroundColNp, self.camGroundHandler)

    # Uncomment this line to see the collision rays
    self.ralphGroundColNp.show()
    self.camGroundColNp.show()
   
    # Uncomment this line to show a visual representation of the 
    # collisions occuring
    self.cTrav.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))

def getpositions(self):
    """Read HDF5 file for actor positions"""
    h5file = tables.openFile('actor_position.h5')
    self.positions = h5file.root.actors.position.read()
    
    del h5file

def getposition(self, index):
    """Get position at index value"""
    p = Vec3( self.positions[index][0],self.positions[index][1],self.positions[index][2] )
    return p

def cross(self, a, b):
    """Compute cross product for 2 vectors"""
    c = [a[1]*b[2] - a[2]*b[1],
        a[2]*b[0] - a[0]*b[2],
        a[0]*b[1] - a[1]*b[0]]
    return c

def dot_product(self, a, b):
    """Compute ot product for 2 vectors"""
    return sum([a[i]*b[i] for i in range(len(a))])

def incrementpos(self, task):
    """Return next row"""
    if self.isMoving is False:
        print 'Moving......'
        self.ralph.loop("run")
        self.ralph.lookAt(self.positions[task.frame])
        self.isMoving = True
    else:
        pass
            
    base.camera.lookAt(self.ralph)

    # If the camera is too far from ralph, move it closer.
    # If the camera is too close to ralph, move it farther.
    camvec = self.ralph.getPos() - base.camera.getPos()
    camvec.setZ(0)
    camdist = camvec.length()
    camvec.normalize()
    if (camdist > 15.0):
        base.camera.setPos(base.camera.getPos() + camvec*(camdist-15))
        camdist = 15.0
    if (camdist < 10.0):
        base.camera.setPos(base.camera.getPos() - camvec*(10-camdist))
        camdist = 10.0

    # 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.
    entries = []
    for i in range(self.ralphGroundHandler.getNumEntries()):
        entry = self.ralphGroundHandler.getEntry(i)
        entries.append(entry)
    entries.sort(lambda x,y: cmp(y.getSurfacePoint(render).getZ(),
                                 x.getSurfacePoint(render).getZ()))
    if (len(entries)>0) and (entries[0].getIntoNode().getName() == "terrain"):
        self.ralph.setZ(entries[0].getSurfacePoint(render).getZ())
    else:
        self.ralph.setPos(self.getposition(0))

    # Keep the camera at one foot above the terrain,
    # or two feet above ralph, whichever is greater.
    entries = []
    for i in range(self.camGroundHandler.getNumEntries()):
        entry = self.camGroundHandler.getEntry(i)
        entries.append(entry)
    entries.sort(lambda x,y: cmp(y.getSurfacePoint(render).getZ(),
                                 x.getSurfacePoint(render).getZ()))
    if (len(entries)>0) and (entries[0].getIntoNode().getName() == "terrain"):
        base.camera.setZ(entries[0].getSurfacePoint(render).getZ()+1.0)
    if (base.camera.getZ() < self.ralph.getZ() + 2.0):
        base.camera.setZ(self.ralph.getZ() + 2.0)
        
    # The camera should look in ralph's direction,
    # but it should also try to stay horizontal, so look at
    # a floater which hovers above ralph's head.
    self.floater.setPos(self.ralph.getPos())
    self.floater.setY(self.ralph.getY() + 2.0)
    self.floater.setZ(self.ralph.getZ() + 2.0)
    base.camera.lookAt(self.floater)
    
    #Right now we are using the frame number to cycle through the
    #positions, when we get to the end we need to either reset the frame number
    #or find a new index algorithm
    
    try:
        self.ralph.setPos(self.positions[task.frame][0],
            self.positions[task.frame][1],self.positions[task.frame][2])
    except IndexError, e:
        print "End of positions"

    return task.cont

if name==“main”:
w = World()
run()

I meant for you to call lookAt() immediately before you call setPos(). But please use code tags in your posts, so I can read the Python code (otherwise the indents get squashed, making it unreadable).

David

David,
I forgot about the code tags, added them with this post. I added the lookAt method call before setting the actor position but it had no impact.

Regards,
John

#!/usr/bin/env python
# Author: Ryan Myers
# Models: Jeff Styers, Reagan Heller


# Last Updated: 6/13/2005
#
# 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 Point3,Vec3,Vec4,BitMask32
from direct.interval.IntervalGlobal import *
from direct.gui.OnscreenText import OnscreenText
from direct.showbase.DirectObject import DirectObject
from direct.fsm.FSM import FSM

import random, sys, os, cmath

#import my class
from AddActor import AddActor

SPEED = 0.5

#Using PyTables to read HDF5 file
import tables

# Function to put instructions on the screen.
def addInstructions(pos, msg):
    return OnscreenText(text=msg, style=1, fg=(1,1,1,1),
                        pos=(-1.3, pos), align=TextNode.ALeft, scale = .05)

# Function to put title on the screen.
def addTitle(text):
    return OnscreenText(text=text, style=1, fg=(1,1,1,1),
                        pos=(1.3,-0.95), align=TextNode.ARight, scale = .07)
                        
class World(DirectObject):
    actors = []
    positions = []
    start_pos = 0
    intervals = []
    
    # Game state variables
    isMoving = False
    
    def __init__(self):
        self.getpositions()
        
        base.win.setClearColor(Vec4(0,0,0,1))

        # Post the instructions

        self.title = addTitle("Panda3D Tutorial: Roaming Ralph (Walking on Uneven Terrain)")
        self.inst1 = addInstructions(0.95, "[ESC]: Quit")
        self.inst2 = addInstructions(0.90, "Playback of actor positions")
        
        # 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, from my AddActor class
        self.ralph = AddActor(position=self.getposition(0),
                            name="Ralph")
        #must attach actor to renderer
        self.ralph.reparentTo(render)
        
        # Create a floater object.  We use the "floater" as a temporary
        # variable in a variety of calculations.
        
        self.floater = NodePath(PandaNode("floater"))
        self.floater.reparentTo(render)

        # Accept the escape key to exit application
        self.accept("escape", sys.exit)

        taskMgr.add(self.incrementpos,"movePosTask")
        
        base.disableMouse()
        base.camera.setPos( self.ralph.getX(), self.ralph.getY() + 5, 5 )
        
        # 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.camGroundRay = CollisionRay()
        self.camGroundRay.setOrigin(0,0,1000)
        self.camGroundRay.setDirection(0,0,-1)
        self.camGroundCol = CollisionNode('camRay')
        self.camGroundCol.addSolid(self.camGroundRay)
        self.camGroundCol.setFromCollideMask(BitMask32.bit(0))
        self.camGroundCol.setIntoCollideMask(BitMask32.allOff())
        self.camGroundColNp = base.camera.attachNewNode(self.camGroundCol)
        self.camGroundHandler = CollisionHandlerQueue()
        self.cTrav.addCollider(self.camGroundColNp, self.camGroundHandler)

        # Uncomment this line to see the collision rays
        self.ralphGroundColNp.show()
        self.camGroundColNp.show()
       
        # Uncomment this line to show a visual representation of the 
        # collisions occuring
        self.cTrav.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))
        
    def getpositions(self):
        """Read HDF5 file for actor positions"""
        h5file = tables.openFile('actor_position.h5')
        self.positions = h5file.root.actors.position.read()
        
        del h5file
    
    def getposition(self, index):
        """Get position at index value"""
        p = Vec3( self.positions[index][0],self.positions[index][1],self.positions[index][2] )
        return p
    
    def cross(self, a, b):
        """Compute cross product for 2 vectors"""
        c = [a[1]*b[2] - a[2]*b[1],
            a[2]*b[0] - a[0]*b[2],
            a[0]*b[1] - a[1]*b[0]]
        return c
    
    def dot_product(self, a, b):
        """Compute ot product for 2 vectors"""
        return sum([a[i]*b[i] for i in range(len(a))])
    
    def getdegrees(self, dot):
        """Convert radians to degrees"""
        return math.acos(dot)
    
    def incrementpos(self, task):
        """Return next row"""
        if self.isMoving is False:
            print 'Moving......'
            self.ralph.loop("run")
            self.isMoving = True
        else:
            pass
                
        base.camera.lookAt(self.ralph)

        # If the camera is too far from ralph, move it closer.
        # If the camera is too close to ralph, move it farther.
        camvec = self.ralph.getPos() - base.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if (camdist > 15.0):
            base.camera.setPos(base.camera.getPos() + camvec*(camdist-15))
            camdist = 15.0
        if (camdist < 10.0):
            base.camera.setPos(base.camera.getPos() - camvec*(10-camdist))
            camdist = 10.0

        # 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.
        entries = []
        for i in range(self.ralphGroundHandler.getNumEntries()):
            entry = self.ralphGroundHandler.getEntry(i)
            entries.append(entry)
        entries.sort(lambda x,y: cmp(y.getSurfacePoint(render).getZ(),
                                     x.getSurfacePoint(render).getZ()))
        if (len(entries)>0) and (entries[0].getIntoNode().getName() == "terrain"):
            self.ralph.setZ(entries[0].getSurfacePoint(render).getZ())
        else:
            self.ralph.setPos(self.getposition(0))

        # Keep the camera at one foot above the terrain,
        # or two feet above ralph, whichever is greater.
        entries = []
        for i in range(self.camGroundHandler.getNumEntries()):
            entry = self.camGroundHandler.getEntry(i)
            entries.append(entry)
        entries.sort(lambda x,y: cmp(y.getSurfacePoint(render).getZ(),
                                     x.getSurfacePoint(render).getZ()))
        if (len(entries)>0) and (entries[0].getIntoNode().getName() == "terrain"):
            base.camera.setZ(entries[0].getSurfacePoint(render).getZ()+1.0)
        if (base.camera.getZ() < self.ralph.getZ() + 2.0):
            base.camera.setZ(self.ralph.getZ() + 2.0)
            
        # The camera should look in ralph's direction,
        # but it should also try to stay horizontal, so look at
        # a floater which hovers above ralph's head.
        self.floater.setPos(self.ralph.getPos())
        self.floater.setY(self.ralph.getY() + 10.0)
        self.floater.setZ(self.ralph.getZ() + 2.0)
        
        #Right now we are using the frame number to cycle through the
        #positions, when we get to the end we need to either reset the frame number
        #or find a new index algorithm
        
        try:
            self.ralph.lookAt(self.floater)
            self.ralph.setPos(self.positions[task.frame][0],
                self.positions[task.frame][1],self.positions[task.frame][2])
        except IndexError, e:
            print "End of positions"

        return task.cont
    
if __name__=="__main__":
    w = World()
    run()