Point & Click Turning Bug!

Return to Scripting Issues

Point & Click Turning Bug!

Postby Tiptoe » Tue Apr 18, 2006 1:50 am

Hi All, I hope you'll forgive me for starting a new thread. I was going to add this to my original thread, but that one's getting a bit long and I thought that this new post might get overlooked.

Anyway, I've made some progress on my point & click player controls. I think I've solved the problem of the model moving backwards to a clicked position. It seems that to work correctly, the model's orientation must be set in your modeling package to face away from the camera (when I used a model that had been modeled and exported facing backwards, it worked a treat :D).

But now I have a new problem. My code to make the player turn towards a clicked position, seems to have a bug in it.

This is what I want to happen. I click a position on the ground (the red X represents the clicked position).

Image

The player should make a half turn to the right and face in the direction of the clicked position:

Image

But this is what actually happens. I click this position on the ground and the player makes a full turn in the opposite direction and then faces in the right direction.

Image

If I click in the opposite corner the same thing happens:

Image

The strange thing is, this only happens when I click in the bottom corners of the map. When I click in the top corners of the map it works just fine.

Anyhow, this is the code:

Code: Select all
# This program turns a model to the position (point 3) of a left mouse click
# on a 3d surface.

import direct.directbase.DirectStart # Start Panda
from pandac.PandaModules import * # Import the Panda Modules
from direct.showbase.DirectObject import DirectObject # To listen for Events
from direct.task import Task # To use Tasks
from direct.actor import Actor # To use animated Actors
from direct.interval.IntervalGlobal import * # To use Intervals
import math # To use math (sin, cos..etc)
from math import sqrt
import sys 

class Picker(DirectObject):
    def __init__(self):
        base.disableMouse()
        # Position the camera
        camera.setPos(0, -35, 18) # X = left & right, Y = zoom, Z = Up & down.
        camera.setHpr(0, -25, 0) # Heading, pitch, roll.
        # Declare variables
        self.position = None
        # Load an environment
        self.environ = loader.loadModel("MODELS/grass")
        self.environ.reparentTo(render)
        self.environ.setPos(0, 0, 0)
        self.player = loader.loadModel("MODELS/fleet")
        self.player.reparentTo(render)
        self.player.setPos(0, 0, 0)
        self.player.setHpr(0, 0, 0)
        self.player.setColor(Vec4(0, 148, 213, 1))
        # Setup collision stuff.
        self.picker= CollisionTraverser()
        self.queue=CollisionHandlerQueue()
        self.pickerNode = CollisionNode('mouseRay')
        self.pickerNP = camera.attachNewNode(self.pickerNode)
        self.pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
        self.pickerRay = CollisionRay()
        self.pickerNode.addSolid(self.pickerRay)
        self.picker.addCollider(self.pickerNode, self.queue)
        # Setup controls
        self.accept("escape", sys.exit)
        self.accept('mouse1', self.moveToPosition)

    def getPosition(self, mousepos):
        self.pickerRay.setFromLens(base.camNode, mousepos.getX(),mousepos.getY())
        self.picker.traverse(render)
        if self.queue.getNumEntries() > 0:
            self.queue.sortEntries()
            # This is the clicked position.
            self.position = self.queue.getEntry(0).getSurfacePoint(self.environ)
            return None

    def moveToPosition(self):
        # Get the clicked position
        self.getPosition(base.mouseWatcherNode.getMouse())
        # This is the "start" position
        sPos = self.player.getPos()
        # Calculate the new hpr
        # Create a tmp NodePath and set it to the start position
        # Then make it "lookAt" the position we want to be at.
        # It will then create the hpr that we can turn to.
        a = NodePath("tmp")
        a.setPos(sPos)
        a.lookAt(self.position) # Look at the clicked position.
        newHpr = a.getHpr()
        # Create a turn animation from current hpr to the calculated new hpr.
        playerTurn = self.player.hprInterval(.2,Point3(newHpr[0],newHpr[1],newHpr[2]))
        playerTurn.start()

p = Picker()

run()


I'm really baffled by this one, so if anybody has any suggestions please advise me.

Cheers
Image Tiptoe
Tiptoe
 
Posts: 127
Joined: Fri Mar 03, 2006 11:57 pm

Postby russ » Tue Apr 18, 2006 8:55 am

You can import a function from direct.showbase.PythonUtil called closestDestAngle. It should help.

Code: Select all
def closestDestAngle(src, dest):
    # The function above didn't seem to do what I wanted. So I hacked
    # this one together. I can't really say I understand it. It's more
    # from impirical observation... GRW
    diff = src - dest
    if diff > 180:
        # if the difference is greater that 180 it's shorter to go the other way
        return src - (diff - 360)
    elif diff < -180:
        # or perhaps the OTHER other way...
        return src - (360 + diff)
    else:
        # otherwise just go to the original destination
        return dest


Also, creating a temp nodepath every move might cause some extra memory usage. Its possible Panda will keep the nodepath around in the scene graph even though your function has lost its scope. It would be better to create a single nodepath and parent it to your player. Then you can call lookAt() on that single nodepath each move instead of creating a new one each time.
russ
 
Posts: 223
Joined: Wed May 04, 2005 5:32 pm

Postby Tiptoe » Tue Apr 18, 2006 11:32 am

Thanks Russ. I've tried to implement your suggestion, but I just don't have the knowledge or experience to make it work properly. This is what I've done (don't laugh, I really tried my best :roll:):

Code: Select all
    def getPosition(self, mousepos):
        self.pickerRay.setFromLens(base.camNode, mousepos.getX(),mousepos.getY())
        self.picker.traverse(render)
        if self.queue.getNumEntries() > 0:
            self.queue.sortEntries()
            # This is the clicked position.
            self.position = self.queue.getEntry(0).getSurfacePoint(self.environ)
            return None
       
    def moveToPosition(self):
        # Get the clicked position
        self.getPosition(base.mouseWatcherNode.getMouse())
        # This is the "start" position
        sPos = self.player.getPos()
        diff = sPos - self.position # Start position - the clicked position.
        if diff > 180:
            # if the difference is greater that 180 it's shorter to go the other way
            return sPos - (diff - 360)
        elif diff < -180:
            # or perhaps the OTHER other way...
            return sPos - (360 + diff)
        else:
            # otherwise just go to the original destination
            return self.position
       
        playerTurn = self.player.hprInterval(.5,self.position)
        playerTurn.start()

p = Picker()

run()


Sadly, this code doesn't work. But it looks very similar to the code I'm using to MOVE the player to the clicked position (this particular piece of code works nicely):

Code: Select all
    def getPosition(self, mousepos):
        self.pickerRay.setFromLens(base.camNode, mousepos.getX(),mousepos.getY())
        self.picker.traverse(render)
        if self.queue.getNumEntries() > 0:
            self.queue.sortEntries()
            self.position = self.queue.getEntry(0).getSurfacePoint(self.environ)
            return None

    def moveToPosition(self):
        # Get the clicked position
        self.getPosition(base.mouseWatcherNode.getMouse())
        # This is the "start" position
        sPos = self.player.getPos()
        # Calculate the distance between the start and finish positions.
        # This is then used to calculate the duration it should take to
        # travel to the new coordinates based on self.movementSpeed
        if sPos[0] > self.position[0]:
            distanceX = sPos[0] - self.position[0]
        else:
            distanceX = self.position[0] - sPos[0]
           
        if sPos[1] > self.position[1]:
            distanceY = sPos[1] - self.position[1]
        else:
            distanceY = self.position[1] - sPos[1]
           
        distance = sqrt((distanceX * distanceX) + (distanceY * distanceY))
 
        playerMove = self.player.posInterval((distance / self.movementSpeed), self.position, startPos = sPos)
        playerMove.start()


If I can get this code to work without the need for a dummy node, that'd be great, because I already have a camera_dummy_node attached to the player (so that I can rotate the camera around him).

Sorry to be such a pest. Can you see what I've done wrong?

Thanks very much.
Image Tiptoe
Tiptoe
 
Posts: 127
Joined: Fri Mar 03, 2006 11:57 pm

Postby Tiptoe » Tue Apr 18, 2006 11:47 am

Oops, I forgot to mention, I eventually plan to combine both playerTurn and playerMove into the one function and then play them in sequence. Hopefully this will make the player turn in the direction of the mouse click and then move to that position.

Something like the following code (this is Sandman's example :D):

Code: Select all
  def moveToPosition(self):
    if self.playerMovement != None:
        self.playerMovement.pause()
        self.__stopWalkAnim()
       
    # This is the "start" position
    sPos = self.player.getPos()

    # Calculate the new hpr
    # Create a tmp NodePath and set it to the start position
    # Then make it "lookAt" the position we want to be at.
    # It will then create the hpr that we can turn to.
    a = NodePath('tmp')
    a.setPos(sPos)
    a.lookAt(position)
    newHpr = a.getHpr()

    # Create a turn animation from current hpr to the calculated new hpr.
    playerTurn = self.player.hprInterval(1,Point3(newHpr[0] + self.rotationOffset,newHpr[1],newHpr[2]),startHpr = self.player.getHpr())

    # Calculate the distance between the start and finish positions.
    # This is then used to calculate the duration it should take to
    # travel to the new coordinate base on self.movementSpeed
    if sPos[0] > position[0]:
      distanceX = sPos[0] - position[0]
    else:
      distanceX = position[0] - sPos[0]
   
    if sPos[1] > position[1]:
      distanceY = sPos[1] - position[1]
    else:
      distanceY = position[1] - sPos[1]

    distance = sqrt((distanceX * distanceX) + (distanceY * distanceY))
   
    # Create a movement animation using the 2 positions
    playerMove = self.player.posInterval( (distance / self.movementSpeed), position, startPos = sPos)

    # Put the animations into a sequence and create an event that will be
    # called when its finished.
    self.playerMovement = Sequence(playerTurn, playerMove, name = 'playerMove')
    self.playerMovement.setDoneEvent('player-stopped')

    # Start the walking animation, then start the turn+move anims
    self.player.loop('walk')
    self.playerMovement.start()

  def __stopWalkAnim(self):
    # This is called when the turn+move anim has finished.
    # We can then stop the walk anim.
    self.player.stop('walk')
    self.playerMovement = None


Cheers
Image Tiptoe
Tiptoe
 
Posts: 127
Joined: Fri Mar 03, 2006 11:57 pm

Postby russ » Tue Apr 18, 2006 2:12 pm

So here is more what I was talking about. The periods represent the code already in there. Hope its not too confusing.

Code: Select all
#imports
.
.
.
from direct.showbase.PythonUtil import closestDestAngle
.
.
.
class Picker(DirectObject):
    def __init__(self):
        .
        .
        .
        #after self.player has been set up
        self.npLook = self.player.attachNewNode("npLook")
        .
        .
        .
    def moveToPosition(self):
        # Get the clicked position
        self.getPosition(base.mouseWatcherNode.getMouse())
       
        # Calculate the new hpr
        self.npLook.lookAt(self.position) # Look at the clicked position.
        currHpr = self.player.getHpr()
        newHpr = self.npLook.getHpr(render)
        newH = closestDestAngle(currHpr[0], newHpr[0])
        # Create a turn animation from current hpr to the calculated new hpr.
        playerTurn = self.player.hprInterval(.2, Point3(newH, newHpr[1], newHpr[2]))
       
        # Calculate the distance between the start and finish positions.
        # This is then used to calculate the duration it should take to
        # travel to the new coordinates based on self.movementSpeed
        travelVec = self.position - self.player.getPos()
        distance = travelVec.length()
       
        playerMove = self.player.posInterval((distance / self.movementSpeed), self.position, startPos = sPos)

        self.playerMovement = Sequence(playerTurn, playerMove)
        self.playerMovement.setDoneEvent('player-stopped')
        # Start the walking animation, then start the turn+move anims
        self.player.loop('walk')
        self.playerMovement.start()
        .
        .
        .
russ
 
Posts: 223
Joined: Wed May 04, 2005 5:32 pm

Postby Tiptoe » Wed Apr 19, 2006 1:19 am

Thankyou so much for this Russ. This time I was able to get the code working. But sadly, it's still not doing what I want it to do :evil:.

The player now makes a small quarter turn in the direction of the click, which results in him sometimes moving sideways or even backwards to the clicked position (sometimes he turns and moves correctly too, it just depends on where you click :roll:).

It's kind of difficult to explain. So it's probably better to show you. I don't know if this is of any use, or even if you'd have the time to test it, but I've uploaded the models that I'm using to Rapidshare. So if you want to, you could take a look at it with the same models:


http://rapidshare.de/files/18379082/Tes ... s.rar.html

And this is the new code (Note: The Fleet model isn't animated, so I removed the animation references):

Code: Select all
# This program turns a model to the position (point 3) of a left mouse click
# on a 3d surface and then moves it to that position.

import direct.directbase.DirectStart # Start Panda
from pandac.PandaModules import * # Import the Panda Modules
from direct.showbase.DirectObject import DirectObject # To listen for Events
from direct.task import Task # To use Tasks
from direct.actor import Actor # To use animated Actors
from direct.interval.IntervalGlobal import * # To use Intervals
import math # To use math (sin, cos..etc)
from math import sqrt
from direct.showbase.PythonUtil import closestDestAngle
import sys 

class Picker(DirectObject):
    def __init__(self):
        base.disableMouse()
        # Position the camera
        camera.setPos(0, -35, 18) # X = left & right, Y = zoom, Z = Up & down.
        camera.setHpr(0, -25, 0) # Heading, pitch, roll.
        # Declare variables
        self.position = None
        self.playerMovement = None
        self.movementSpeed = 8.0 # Controls how fast the player moves.
        # Load an environment
        self.environ = loader.loadModel("MODELS/grass")
        self.environ.reparentTo(render)
        self.environ.setPos(0, 0, 0)
        self.player = loader.loadModel("MODELS/fleet")
        self.player.reparentTo(render)
        self.player.setPos(0, 0, 0)
        self.player.setHpr(0, 0, 0)
        self.player.setColor(Vec4(0, 148, 213, 1))
        self.npLook = self.player.attachNewNode("npLook")
        # Setup collision stuff.
        self.picker= CollisionTraverser()
        self.queue=CollisionHandlerQueue()
        self.pickerNode = CollisionNode('mouseRay')
        self.pickerNP = camera.attachNewNode(self.pickerNode)
        self.pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
        self.pickerRay = CollisionRay()
        self.pickerNode.addSolid(self.pickerRay)
        self.picker.addCollider(self.pickerNode, self.queue)
        # Setup controls
        self.accept("escape", sys.exit)
        self.accept('mouse1', self.moveToPosition)

    def getPosition(self, mousepos):
        self.pickerRay.setFromLens(base.camNode, mousepos.getX(),mousepos.getY())
        self.picker.traverse(render)
        if self.queue.getNumEntries() > 0:
            self.queue.sortEntries()
            # This is the clicked position.
            self.position = self.queue.getEntry(0).getSurfacePoint(self.environ)
            return None
       
    def moveToPosition(self):
        # Get the clicked position
        self.getPosition(base.mouseWatcherNode.getMouse())
        # Calculate the new hpr
        self.npLook.lookAt(self.position) # Look at the clicked position.
        currHpr = self.player.getHpr()
        newHpr = self.npLook.getHpr(render)
        newH = closestDestAngle(currHpr[0], newHpr[0])
        # Create a turn animation from current hpr to the calculated new hpr.
        playerTurn = self.player.hprInterval(.2, Point3(newH, newHpr[1], newHpr[2]))
       
        # Calculate the distance between the start and finish positions.
        # This is then used to calculate the duration it should take to
        # travel to the new coordinates based on self.movementSpeed
        travelVec = self.position - self.player.getPos()
        distance = travelVec.length()
       
        playerMove = self.player.posInterval((distance / self.movementSpeed), self.position)

        self.playerMovement = Sequence(playerTurn, playerMove)
        self.playerMovement.start()
       
p = Picker()

run()


This has got to be one of the hardest and most frustrating things I've ever tried to do. I can't tell you how much I appreciate your help with this. My respect and awe for game programmer's has increased enormously.

Thanks heaps.
Image Tiptoe
Tiptoe
 
Posts: 127
Joined: Fri Mar 03, 2006 11:57 pm

Postby russ » Wed Apr 19, 2006 9:34 am

I didn't really test the code before I posted.

change newH to:

Code: Select all
newH = currHpr[0] + closestDestAngle(currHpr[0], newHpr[0])


I think that should fix it, but I haven't tested this either.
If it still acts funny, use some print statements to help figure out what is going on.
russ
 
Posts: 223
Joined: Wed May 04, 2005 5:32 pm

Postby russ » Wed Apr 19, 2006 11:03 am

nevermind that last post, it won't work either. Let me actually think about it and I will post something new
russ
 
Posts: 223
Joined: Wed May 04, 2005 5:32 pm

Postby russ » Wed Apr 19, 2006 12:10 pm

so this should work:

Code: Select all
.
.
.
        #in __init__
        self.npLook = render.attachNewNode("npLook")
    .
    .
    .
        #in moveToPosition
        # Calculate the new hpr
        self.npLook.setPos(self.player.getPos())
        self.npLook.lookAt(self.position) # Look at the clicked position.
        currHpr = self.player.getHpr()
        newHpr = self.npLook.getHpr()
        newH = closestDestAngle(currHpr[0], newHpr[0])
    .
    .
    .
russ
 
Posts: 223
Joined: Wed May 04, 2005 5:32 pm

Postby Tiptoe » Wed Apr 19, 2006 1:32 pm

Oh Wow!!!! This is fantastic!!!!

Russ, I think you've done it. I've just tested it, and it works almost perfectly. It's a HUGE improvement over the original code.

But there's just one little hiccup, that weird 'spin around in the opposite direction' bug is still there, but not as bad as before. This time it only occurs in 3 places.

The first time is when traveling in a straight line 'north to south' through the middle of the map, if I first click on a point in the middle of the map, the player moves nicely to that point. But if I click again to resume traveling (in the same direction), the player spins a full circle before he moves to the next click.

The next two occurences are when traveling clockwise and anti-clockwise around the edges of the map (by clicking in each corner). I’ve made a quick image to show you what I mean (this nasty little bug is consistent and repeatable, it happens in the same spots over and over).

Image

Anyway, bugs or no bugs, I can't thankyou enough for taking the time to work on this for me. This thing has had me almost in tears. I didn't think I'd ever solve it. Without you're help, I think I'd have given up on it. Thankyou a thousand times over.

Cheers
Image Tiptoe
Tiptoe
 
Posts: 127
Joined: Fri Mar 03, 2006 11:57 pm

Postby russ » Wed Apr 19, 2006 2:57 pm

Those extra spins have to do with wrapping of the heading angle. Unfortunately, closestDestAngle doesn't account for that. One way to fix it would be to correct for the wrap yourself some time after the player hits its final heading (after the turn interval or after the enitre movement sequence). You could do this with a call like:

reducedH = self.player.getH()%360.0
self.player.setH(reducedH)
russ
 
Posts: 223
Joined: Wed May 04, 2005 5:32 pm

Postby Tiptoe » Thu Apr 20, 2006 2:12 am

Thanks Russ. I've just tried it. I tried putting it after the turn interval and then after the movement sequence, but in both cases, all it seemed to do was make the player spin in each one of the corners, instead of just the one :roll:.

I've also discovered that swearing and threatening it with dire consequences doesn't work either :D. Do you have any ideas for what I should try next?

Thanks heaps
Image Tiptoe
Tiptoe
 
Posts: 127
Joined: Fri Mar 03, 2006 11:57 pm

Postby Fixer » Thu Apr 20, 2006 9:55 am

Given how many other things Python and Panda3d handle automatically, I almost wouldn't be surprised if there were a "swearing and threatening dire consequences" detector. Obviously, someone will have to build one in :wink:

Code: Select all
try:
  myFunction(defaultOptions)
except SwearingAndThreateningDireConsequences:
  myFunction(-defaultOptions)


If you find that it's spinning the wrong direction in all cases now, that's good news: it probably means something in the equation is negative that should be positive. Maybe change an addition to a subtraction and try again?

It might also be helpful to walk through the calculation on paper. Write down the starting heading and your desired heading, then see if you can guess how the lerp function will go from one to the other. Doing this can help you isolate what about the lerp's behavior isn't matching up with your expectations. Even if it doesn't solve the problem directly, you may gain a deeper insight to how the lerps work; that will almost always be helpful down the road.

Best of luck!
-Mark

Tiptoe wrote:Thanks Russ. I've just tried it. I tried putting it after the turn interval and then after the movement sequence, but in both cases, all it seemed to do was make the player spin in each one of the corners, instead of just the one :roll:.

I've also discovered that swearing and threatening it with dire consequences doesn't work either :D. Do you have any ideas for what I should try next?

Thanks heaps
[/code]
Fixer
 
Posts: 190
Joined: Tue May 17, 2005 7:03 pm
Location: Pittsburgh, PA

Postby Tiptoe » Thu Apr 20, 2006 11:20 am

Bummer! I've tried everything I can think of, I even tried defining 'closestDestAngle' as a function in my own code so that I could play with its values, but all to no effect.

I'm at my wit's end here, it just seems to be an impossible task to get this working properly. I'm beginning to suspect that Panda3D might simply not be capable of implementing point & click game controls :cry: (sadly, a lot of engines can't).

I've tried searching the internet for some type of point & click example codes (even ones written in another language, just to give me some idea of what to do) but I couldn't find anything of use. *Grrr* I just don't know what else to do.

Anyway, here is my 'almost' working code:

Code: Select all
# This program turns a model to the position (point 3) of a left mouse click
# on a 3d surface.

import direct.directbase.DirectStart # Start Panda
from pandac.PandaModules import * # Import the Panda Modules
from direct.showbase.DirectObject import DirectObject # To listen for Events
from direct.task import Task # To use Tasks
from direct.actor import Actor # To use animated Actors
from direct.interval.IntervalGlobal import * # To use Intervals
import math # To use math (sin, cos..etc)
from math import sqrt
import sys 

class Picker(DirectObject):
    def __init__(self):
        base.disableMouse()
        # Position the camera
        camera.setPos(0, -35, 18) # X = left & right, Y = zoom, Z = Up & down.
        camera.setHpr(0, -25, 0) # Heading, pitch, roll.
        # Declare variables
        self.position = None
        self.playerMovement = None
        self.movementSpeed = 8.0 # Controls how fast the player moves.
        # Load an environment
        self.environ = loader.loadModel("MODELS/grass")
        self.environ.reparentTo(render)
        self.environ.setPos(0, 0, 0)
        self.player = loader.loadModel("MODELS/fleet")
        self.player.reparentTo(render)
        self.player.setPos(0, 0, 0)
        self.player.setHpr(0, 0, 0)
        self.player.setColor(Vec4(0, 148, 213, 1))
        self.npLook = render.attachNewNode("npLook")
        # Setup collision stuff.
        self.picker= CollisionTraverser()
        self.queue=CollisionHandlerQueue()
        self.pickerNode = CollisionNode('mouseRay')
        self.pickerNP = camera.attachNewNode(self.pickerNode)
        self.pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
        self.pickerRay = CollisionRay()
        self.pickerNode.addSolid(self.pickerRay)
        self.picker.addCollider(self.pickerNode, self.queue)
        # Setup controls
        self.accept("escape", sys.exit)
        self.accept('mouse1', self.moveToPosition)
       
    def closestDestAngle(src, dest):
        diff = src - dest
        if diff > 180:
            # if the difference is greater that 180 it's shorter to go the other way
            return src - (diff - 360)
        elif diff < -180:
            # or perhaps the OTHER other way...
            return src - (360 + diff)
        else:
            # otherwise just go to the original destination
            return dest

    def getPosition(self, mousepos):
        self.pickerRay.setFromLens(base.camNode, mousepos.getX(),mousepos.getY())
        self.picker.traverse(render)
        if self.queue.getNumEntries() > 0:
            self.queue.sortEntries()
            # This is the clicked position.
            self.position = self.queue.getEntry(0).getSurfacePoint(self.environ)
            return None
       
    def moveToPosition(self):
        # Get the clicked position
        self.getPosition(base.mouseWatcherNode.getMouse())
        # Calculate the new hpr
        self.npLook.setPos(self.player.getPos())
        self.npLook.lookAt(self.position) # Look at the clicked position.
        currHpr = self.player.getHpr()
        newHpr = self.npLook.getHpr()
        newH = closestDestAngle(currHpr[0], newHpr[0])
        # Create a turn animation from current hpr to the calculated new hpr.
        playerTurn = self.player.hprInterval(.2, Point3(newHpr[0], newHpr[1], newHpr[2]))
        # Calculate the distance between the start and finish positions.
        # This is then used to calculate the duration it should take to
        # travel to the new coordinates based on self.movementSpeed.
        travelVec = self.position - self.player.getPos()
        distance = travelVec.length()
        # Put the animations into a sequence.
        playerMove = self.player.posInterval((distance / self.movementSpeed), self.position)
        self.playerMovement = Sequence(playerTurn, playerMove)
        reducedH = self.player.getH()%360.0
        self.player.setH(reducedH)
        self.playerMovement.start()
       
p = Picker()

run()


Cheers
Image Tiptoe
Tiptoe
 
Posts: 127
Joined: Fri Mar 03, 2006 11:57 pm

Postby russ » Thu Apr 20, 2006 3:22 pm

The way it is now, the heading is being reduced before the movement sequence starts. This is why the player is spinning each time. The reduction needs to happen after the turn interval has finished.

Code: Select all
    #define this somewhere
    def reducePlayerH(self):
        """reduce the player heading to eliminate wrap-around"""
        reducedH = self.player.getH()%360.0
        self.player.setH(reducedH)
        .
        .
        .
       
        #in moveToPosition
        self.playerMovement = Sequence(playerTurn, Func(self.reduceH), playerMove)
       


Alternatively, you can reduce the player heading before you calculate the closesDestAngle:

Code: Select all
        # Calculate the new hpr
        self.npLook.setPos(self.player.getPos())
        self.npLook.lookAt(self.position) # Look at the clicked position.

        reducedH = self.player.getH()%360.0
        self.player.setH(reducedH)

        currHpr = self.player.getHpr()
        newHpr = self.npLook.getHpr()
        newH = closestDestAngle(currHpr[0], newHpr[0])
russ
 
Posts: 223
Joined: Wed May 04, 2005 5:32 pm

Postby Tiptoe » Fri Apr 21, 2006 3:14 am

Thankyou for your infinite patience Russ, I am so incredibly grateful for all your help.

Anyway, I tried your second solution first (cause it looked easier :wink:) but sadly, it didn't seem to make any difference to the spin bug.

So onto the next one, I defined a reducePlayerH() function and ran the code, but I'm getting a strange error message telling me that global name 'reducedH' is not defined:

C:\TUTS>ppython getrotation7.py
DirectStart: Starting the game.
Warning: DirectNotify: category 'Interval' already exists
Known pipe types:
wglGraphicsPipe
(3 aux display modules not yet loaded.)
Traceback (most recent call last):
File "getrotation7.py", line 97, in ?
p = Picker()
File "getrotation7.py", line 36, in __init__
self.reducePlayerH()
File "getrotation7.py", line 65, in reducePlayerH
self.player.setH(reducedH)
NameError: global name 'reducedH' is not defined



I've stuffed something up again haven't I? This is how I updated the code (I tried declaring a variable 'self.reducedH = None' but that doesn't seem to help):

Code: Select all
# This program turns a model to the position (point 3) of a left mouse click
# on a 3d surface.

import direct.directbase.DirectStart # Start Panda
from pandac.PandaModules import * # Import the Panda Modules
from direct.showbase.DirectObject import DirectObject # To listen for Events
from direct.task import Task # To use Tasks
from direct.actor import Actor # To use animated Actors
from direct.interval.IntervalGlobal import * # To use Intervals
import math # To use math (sin, cos..etc)
from math import sqrt
import sys 

class Picker(DirectObject):
    def __init__(self):
        base.disableMouse()
        # Position the camera
        camera.setPos(0, -35, 18) # X = left & right, Y = zoom, Z = Up & down.
        camera.setHpr(0, -25, 0) # Heading, pitch, roll.
        # Declare variables
        self.position = None
        self.playerMovement = None
        self.movementSpeed = 8.0 # Controls how fast the player moves.
        self.reducedH = None
        # Load an environment
        self.environ = loader.loadModel("MODELS/grass")
        self.environ.reparentTo(render)
        self.environ.setPos(0, 0, 0)
        self.player = loader.loadModel("MODELS/fleet")
        self.player.reparentTo(render)
        self.player.setPos(0, 0, 0)
        self.player.setHpr(0, 0, 0)
        self.player.setColor(Vec4(0, 148, 213, 1))
        self.npLook = render.attachNewNode("npLook")
        # Declare functions.
        self.reducePlayerH()
        # Setup collision stuff.
        self.picker= CollisionTraverser()
        self.queue=CollisionHandlerQueue()
        self.pickerNode = CollisionNode('mouseRay')
        self.pickerNP = camera.attachNewNode(self.pickerNode)
        self.pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
        self.pickerRay = CollisionRay()
        self.pickerNode.addSolid(self.pickerRay)
        self.picker.addCollider(self.pickerNode, self.queue)
        # Setup controls
        self.accept("escape", sys.exit)
        self.accept('mouse1', self.moveToPosition)
       
    def closestDestAngle(src, dest):
        diff = src - dest
        if diff > 180:
            # if the difference is greater that 180 it's shorter to go the other way
            return src - (diff - 360)
        elif diff < -180:
            # or perhaps the OTHER other way...
            return src - (360 + diff)
        else:
            # otherwise just go to the original destination
            return dest

    def reducePlayerH(self):
        # Reduce the players heading to eliminate wrap-around
        self.reducedH = self.player.getH()%360.0
        self.player.setH(reducedH)

    def getPosition(self, mousepos):
        self.pickerRay.setFromLens(base.camNode, mousepos.getX(),mousepos.getY())
        self.picker.traverse(render)
        if self.queue.getNumEntries() > 0:
            self.queue.sortEntries()
            # This is the clicked position.
            self.position = self.queue.getEntry(0).getSurfacePoint(self.environ)
            return None
       
    def moveToPosition(self):
        # Get the clicked position
        self.getPosition(base.mouseWatcherNode.getMouse())
        # Calculate the new hpr
        self.npLook.setPos(self.player.getPos())
        self.npLook.lookAt(self.position) # Look at the clicked position.
        currHpr = self.player.getHpr()
        newHpr = self.npLook.getHpr()
        newH = closestDestAngle(currHpr[0], newHpr[0])
        # Create a turn animation from current hpr to the calculated new hpr.
        playerTurn = self.player.hprInterval(.2, Point3(newHpr[0], newHpr[1], newHpr[2]))
        # Calculate the distance between the start and finish positions.
        # This is then used to calculate the duration it should take to
        # travel to the new coordinates based on self.movementSpeed.
        travelVec = self.position - self.player.getPos()
        distance = travelVec.length()
        # Put the animations into a sequence.
        playerMove = self.player.posInterval((distance / self.movementSpeed), self.position)
        self.playerMovement = Sequence(playerTurn, Func(self.reducedH), playerMove)
        self.playerMovement.start()
       
p = Picker()

run()


What have I done wrong?

Thanks heaps
Image Tiptoe
Tiptoe
 
Posts: 127
Joined: Fri Mar 03, 2006 11:57 pm

Postby russ » Fri Apr 21, 2006 9:32 am

you define self.reducedH but reference reducedH which python thinks is a global variable

change reducedH to a local variable in the function so instead of

self.reducedH = player.getH()%360.0

use

reducedH = player.getH()%360
russ
 
Posts: 223
Joined: Wed May 04, 2005 5:32 pm

Postby Tiptoe » Fri Apr 21, 2006 10:14 am

Thanks Russ, I did as you suggested, but now I'm getting a new error message (:evil: it's enough to make you scream, isn't it?).

Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.

C:\Documents and Settings\Gigabyte User>cd C:\TUTS\

C:\TUTS>ppython getrotation7.py
DirectStart: Starting the game.
Warning: DirectNotify: category 'Interval' already exists
Known pipe types:
wglGraphicsPipe
(3 aux display modules not yet loaded.)
:express(warning): Adjusting global clock's real time by -3.6672 seconds.
Traceback (most recent call last):
File "getrotation7.py", line 99, in ?
run()
File "C:\Panda3D-1.0.5\direct\src\showbase\ShowBase.py", line 1603, in run
self.taskMgr.run()
File "C:\Panda3D-1.0.5\direct\src\task\Task.py", line 781, in run
self.step()
File "C:\Panda3D-1.0.5\direct\src\task\Task.py", line 728, in step
self.__stepThroughList(taskPriList)
File "C:\Panda3D-1.0.5\direct\src\task\Task.py", line 671, in __stepThroughLis
t
ret = self.__executeTask(task)
File "C:\Panda3D-1.0.5\direct\src\task\Task.py", line 602, in __executeTask
ret = task(task)
File "C:\Panda3D-1.0.5\direct\src\showbase\EventManager.py", line 32, in event
LoopTask
self.doEvents()
File "C:\Panda3D-1.0.5\direct\src\showbase\EventManager.py", line 26, in doEve
nts
self.processEvent(self.eventQueue.dequeueEvent())
File "C:\Panda3D-1.0.5\direct\src\showbase\EventManager.py", line 71, in proce
ssEvent
messenger.send(eventName)
File "C:\Panda3D-1.0.5\direct\src\showbase\Messenger.py", line 175, in send
apply(method, (extraArgs + sentArgs))
File "getrotation7.py", line 94, in moveToPosition
self.playerMovement = Sequence(playerTurn, Func(self.reducedH), playerMove)
File "C:\Panda3D-1.0.5\direct\src\interval\FunctionInterval.py", line 275, in
__init__
assert(callable(function))
AssertionError


And the new code:

Code: Select all
# This program turns a model to the position (point 3) of a left mouse click
# on a 3d surface.

import direct.directbase.DirectStart # Start Panda
from pandac.PandaModules import * # Import the Panda Modules
from direct.showbase.DirectObject import DirectObject # To listen for Events
from direct.task import Task # To use Tasks
from direct.actor import Actor # To use animated Actors
from direct.interval.IntervalGlobal import * # To use Intervals
import math # To use math (sin, cos..etc)
from math import sqrt
import sys 

class Picker(DirectObject):
    def __init__(self):
        base.disableMouse()
        # Position the camera
        camera.setPos(0, -35, 18) # X = left & right, Y = zoom, Z = Up & down.
        camera.setHpr(0, -25, 0) # Heading, pitch, roll.
        # Declare variables
        self.position = None
        self.playerMovement = None
        self.movementSpeed = 8.0 # Controls how fast the player moves.
        self.reducedH = None
        # Load an environment
        self.environ = loader.loadModel("MODELS/grass")
        self.environ.reparentTo(render)
        self.environ.setPos(0, 0, 0)
        self.player = loader.loadModel("MODELS/fleet")
        self.player.reparentTo(render)
        self.player.setPos(0, 0, 0)
        self.player.setHpr(0, 0, 0)
        self.player.setColor(Vec4(0, 148, 213, 1))
        self.npLook = render.attachNewNode("npLook")
        # Declare functions.
        self.reducePlayerH()
        # Setup collision stuff.
        self.picker= CollisionTraverser()
        self.queue=CollisionHandlerQueue()
        self.pickerNode = CollisionNode('mouseRay')
        self.pickerNP = camera.attachNewNode(self.pickerNode)
        self.pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
        self.pickerRay = CollisionRay()
        self.pickerNode.addSolid(self.pickerRay)
        self.picker.addCollider(self.pickerNode, self.queue)
        # Setup controls
        self.accept("escape", sys.exit)
        self.accept('mouse1', self.moveToPosition)
       
    def closestDestAngle(src, dest):
        diff = src - dest
        if diff > 180:
            # if the difference is greater that 180 it's shorter to go the other way
            return src - (diff - 360)
        elif diff < -180:
            # or perhaps the OTHER other way...
            return src - (360 + diff)
        else:
            # otherwise just go to the original destination
            return dest

    def reducePlayerH(self):
        # Reduce the players heading to eliminate wrap-around
        reducedH = self.player.getH()%360.0
        self.player.setH(reducedH)

    def getPosition(self, mousepos):
        self.pickerRay.setFromLens(base.camNode, mousepos.getX(),mousepos.getY())
        self.picker.traverse(render)
        if self.queue.getNumEntries() > 0:
            self.queue.sortEntries()
            # This is the clicked position.
            self.position = self.queue.getEntry(0).getSurfacePoint(self.environ)
            return None
       
    def moveToPosition(self):
        # Get the clicked position
        self.getPosition(base.mouseWatcherNode.getMouse())
        # Calculate the new hpr
        self.npLook.setPos(self.player.getPos())
        self.npLook.lookAt(self.position) # Look at the clicked position.
        currHpr = self.player.getHpr()
        newHpr = self.npLook.getHpr()
        newH = closestDestAngle(currHpr[0], newHpr[0])
        # Create a turn animation from current hpr to the calculated new hpr.
        playerTurn = self.player.hprInterval(.2, Point3(newHpr[0], newHpr[1], newHpr[2]))
        # Calculate the distance between the start and finish positions.
        # This is then used to calculate the duration it should take to
        # travel to the new coordinates based on self.movementSpeed.
        travelVec = self.position - self.player.getPos()
        distance = travelVec.length()
        # Put the animations into a sequence.
        playerMove = self.player.posInterval((distance / self.movementSpeed), self.position)
        self.playerMovement = Sequence(playerTurn, Func(self.reducedH), playerMove)
        self.playerMovement.start()
       
p = Picker()

run()



I dunno, maybe I'm just not cut out for this programming business :cry:.

Thanks again
Last edited by Tiptoe on Fri Apr 21, 2006 10:30 am, edited 1 time in total.
Image Tiptoe
Tiptoe
 
Posts: 127
Joined: Fri Mar 03, 2006 11:57 pm

Postby Martin » Fri Apr 21, 2006 10:22 am

As russ told you above: self.reduceH is a virable
Func( .. ) needs a callable object
I guess you should use Func( self.reducePlayerH ) instead
Just for the future:
Code: Select all
self.playerMovement = Sequence(playerTurn, Func(self.reducedH), playerMove)
File "C:\Panda3D-1.0.5\direct\src\interval\FunctionInterval.py", line 275, in
__init__
assert(callable(function))

assert is a functions that checks a logical expression.
You see there is something that needs to be a function but is not a function
Then look at the line bove. There you can see the Func( .. ) and then try to find whats the mistake
Hope this helps
Martin
Martin
 
Posts: 275
Joined: Wed Jul 27, 2005 4:59 pm
Location: Vienna, Austria - EU

Postby Tiptoe » Fri Apr 21, 2006 11:15 am

Thanks Martin, that fixed the AssertionError. But after all that trouble, that hateful spin bug is still there. The new function doesn't seem to have made any difference at all :(.

Again HUGE thanks to all of you.
Last edited by Tiptoe on Sat Apr 22, 2006 1:21 am, edited 1 time in total.
Image Tiptoe
Tiptoe
 
Posts: 127
Joined: Fri Mar 03, 2006 11:57 pm

Postby russ » Fri Apr 21, 2006 12:25 pm

this worked for me. I used some default models so anyone could try it. The panda is modeled backwards, so when he turns, he will face opposite to the direction he is moving.

Code: Select all
# This program turns a model to the position (point 3) of a left mouse click
# on a 3d surface and then moves it to that position.

import direct.directbase.DirectStart # Start Panda
from pandac.PandaModules import * # Import the Panda Modules
from direct.showbase.DirectObject import DirectObject # To listen for Events
from direct.task import Task # To use Tasks
from direct.actor import Actor # To use animated Actors
from direct.interval.IntervalGlobal import * # To use Intervals
from direct.showbase.PythonUtil import closestDestAngle
import sys

class Picker(DirectObject):
    def __init__(self):
        base.disableMouse()
        # Position the camera
        camera.setPos(0, -0, 100) # X = left & right, Y = zoom, Z = Up & down.
        camera.setHpr(0, -90, 0) # Heading, pitch, roll.
        # Declare variables
        self.position = None
        self.playerMovement = None
        self.movementSpeed = 8.0 # Controls how fast the player moves.
        # Load an environment
        self.environ = loader.loadModel("environment")
        self.environ.reparentTo(render)
        self.environ.setPos(0, 0, 0)
        self.player = loader.loadModel("panda")
        self.player.reparentTo(render)
        self.player.setPos(0, 0, 0)
        self.player.setHpr(0, 0, 0)
        self.player.setColor(Vec4(0, 148, 213, 1))
        self.npLook = render.attachNewNode("npLook")
        # Setup collision stuff.
        self.picker= CollisionTraverser()
        self.queue=CollisionHandlerQueue()
        self.pickerNode = CollisionNode('mouseRay')
        self.pickerNP = camera.attachNewNode(self.pickerNode)
        self.pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
        self.pickerRay = CollisionRay()
        self.pickerNode.addSolid(self.pickerRay)
        self.picker.addCollider(self.pickerNode, self.queue)
        # Setup controls
        self.accept("escape", sys.exit)
        self.accept('mouse1', self.moveToPosition)
        self.accept('r', self.reset)

    def getPosition(self, mousepos):
        self.pickerRay.setFromLens(base.camNode, mousepos.getX(),mousepos.getY())
        self.picker.traverse(render)
        if self.queue.getNumEntries() > 0:
            self.queue.sortEntries()
            # This is the clicked position.
            self.position = self.queue.getEntry(0).getSurfacePoint(self.environ)
            print "position", self.position
            return None
       
    def moveToPosition(self):
        # Get the clicked position
        self.getPosition(base.mouseWatcherNode.getMouse())
       
        # Calculate the new hpr
        self.npLook.setPos(self.player.getPos())
        self.npLook.lookAt(self.position) # Look at the clicked position.
        reducedH = self.player.getH()%360.0
        self.player.setH(reducedH)
        currHpr = self.player.getHpr()
        #print "curr", currHpr
        newHpr = self.npLook.getHpr()
        #print "new", newHpr
        #print self.npLook.getPos(render)
        newH = closestDestAngle(currHpr[0], newHpr[0])
        #print newH
        # Create a turn animation from current hpr to the calculated new hpr.
        playerTurn = self.player.hprInterval(.2, Point3(newH, newHpr[1], newHpr[2]))
       
        # Calculate the distance between the start and finish positions.
        # This is then used to calculate the duration it should take to
        # travel to the new coordinates based on self.movementSpeed
        travelVec = self.position - self.player.getPos()
        distance = travelVec.length()
       
        playerMove = self.player.posInterval((distance / self.movementSpeed), self.position)

        self.playerMovement = Sequence(playerTurn, playerMove)
        self.playerMovement.start()
        #playerTurn.start()

    def reset(self):
        self.player.setHpr(0,0,0)
p = Picker()

run()
russ
 
Posts: 223
Joined: Wed May 04, 2005 5:32 pm

Postby Tiptoe » Sat Apr 22, 2006 1:47 am

Russ!!! You did it!!! You solved it!!! Bless you a thousand times over!!! You are amazing!!!

Thankyou from the bottom of my heart. Last night I was feeling depressed and fed up and ready to chuck the whole thing in (I've edited my last post, cause it's just embarrassing now :oops:).

I didn't think this would ever work, and like a poor workman blaming his tools, I was blaming Panda (I thought it was missing some essential function that should make this work) when in reality it was my own lack at fault, not Panda's (sorry Panda, never again, I promise).

Russ, I don't know how to thankyou enough for this. Without your tremendous patience and help this would never have gotten done.

Thankyou so much.
Image Tiptoe
Tiptoe
 
Posts: 127
Joined: Fri Mar 03, 2006 11:57 pm

Postby Tiptoe » Sat Apr 22, 2006 12:35 pm

Hi again. I've been playing around with this code all day, it's just wonderful :D. I've also been studying it to try and learn what I did wrong.

However, I'm a little bit puzzled, I don't know why it works and this is something that I'd really love to understand. Anybody in the mood to give a quick programming lesson :D?

You see, in the function below, 'reducedH = self.player.getH()%360.0' and 'self.player.setH(reducedH)' seem to be called/referenced (sorry, I don't know what the right term is) before any player movement has even taken place.

To my newbie mind, it seems like they're setting the player's heading before he's even started to move. It seems to me that they should be called after the player's movement, or at least after 'playerTurn', but obviously that's not the correct approach. How come it works when it's done this way?

Code: Select all
def moveToPosition(self):
        # Get the clicked position
        self.getPosition(base.mouseWatcherNode.getMouse())
       
        # Calculate the new hpr
        self.npLook.setPos(self.player.getPos())
        self.npLook.lookAt(self.position) # Look at the clicked position.
        reducedH = self.player.getH()%360.0
        self.player.setH(reducedH)
        currHpr = self.player.getHpr()
        #print "curr", currHpr
        newHpr = self.npLook.getHpr()
        #print "new", newHpr
        #print self.npLook.getPos(render)
        newH = closestDestAngle(currHpr[0], newHpr[0])
        #print newH
        # Create a turn animation from current hpr to the calculated new hpr.
        playerTurn = self.player.hprInterval(.2, Point3(newH, newHpr[1], newHpr[2]))
       
        # Calculate the distance between the start and finish positions.
        # This is then used to calculate the duration it should take to
        # travel to the new coordinates based on self.movementSpeed
        travelVec = self.position - self.player.getPos()
        distance = travelVec.length()
       
        playerMove = self.player.posInterval((distance / self.movementSpeed), self.position)

        self.playerMovement = Sequence(playerTurn, playerMove)
        self.playerMovement.start()
        #playerTurn.start()


Sorry to be a such a pest, I'm just really trying to learn and understand all this programming stuff.

Cheers
Image Tiptoe
Tiptoe
 
Posts: 127
Joined: Fri Mar 03, 2006 11:57 pm

Postby Martin » Sat Apr 22, 2006 3:05 pm

In the case above called is right: Because: self.player.setH() is a function and you call it.
It doesn't matter if heading is set bevore player moved because H is initialy set to 0 if i'm right
0 % 360= 0 so there is no change on H.
What kind of programming lesson do you mean?
Martin
Martin
 
Posts: 275
Joined: Wed Jul 27, 2005 4:59 pm
Location: Vienna, Austria - EU

Postby Tiptoe » Sun Apr 23, 2006 12:56 am

Thanks for the reply Martin, I'm really just trying to understand why 'calling' these functions (thanks for the carification) before the player moves stops the spin bug, but calling them after playerTurn or the movement sequence doesn't seem to have any effect at all.

Logically, it seems to me, that you should tell the player to turn and then add these functions to stop him turning too far. But obviously that's not the way it's done.

Ah well, no matter, the lovely thing works and that's all that's important :D.

Cheers
Image Tiptoe
Tiptoe
 
Posts: 127
Joined: Fri Mar 03, 2006 11:57 pm


Return to Scripting Issues

Who is online

Users browsing this forum: Bing [Bot] and 1 guest

cron