Point & Click Turning Bug!

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.

# 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!!! 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 :blush:).

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.

Hi again. I’ve been playing around with this code all day, it’s just wonderful :smiley:. 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 :smiley:?

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?

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

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

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 :smiley:.

Cheers