Moving cube in cube's heading?

Hello all,

I am trying to move an object its direction. The cube would originally move in the X and Y axis regardless of its direction and I tried to solve this with trigonometry.

        if self.keyMap["forward"]:
            self.y += 17*dt*(math.cos(self.heading))
            self.x += 17*dt*(math.sin(self.heading))

The cube isn’t moving in its heading, it isn’t moving straight along the y axis all the time but the direction in which it moves in is quite random, is there anything wrong with the math?

There are three parts to my answer:

The first is to check the value of “self.heading”: The “sin” and “cos” functions of the “math” module expect their parameters to be given in radians. Thus, if the value of “self.heading” is in degrees (and values taken from Panda will tend to be so, for example), then the mismatch can produce unexpected results.

The second is to note that you can have a NodePath move along its local axes by passing it in as the first parameter to NodePath’s “setPos” (or “setX”, “setY”, etc.) method.

Specifically, those methods allow an optional first parameter that indicates a NodePath relative to which the new position is considered to be. So calling “myNodePath.setPos(render, 0, 0, 0)” sets the position of “myNodePath” to be (0, 0, 0) relative to render.

Thus we can move an object along one of its axes by calling something like this: “myNodePath.setY(myNodePath, 5.0*dt)”.

Essentially, what we’re doing above is setting the object’s y-position to be placed at a location of 5.0*dt units relative to itself.

There is a caveat to this approach, however: it’s affected by things like scaling, or the transformations of any parent objects. If that might cause trouble, then another approach (such as your current one) might be better.

And third and finally, you can get the “forward” vector of a NodePath, and then move that NodePath along that vector. This is done by getting the quaternion that represents the NodePath’s orientation, which in turn provides a method that returns the vector in question. (And don’t worry if you’re not familiar with quaternions; for this approach, you don’t need to be.)

Like so:

# I like to get the quaternion relative to render, to 
# prevent parent-nodes from affecting things.
# Adapt this to your application as called for, if at all.
quat = myNodePath.getQuat(render)

forwardVec = quat.getForward()

# Likewise, I'm getting the NodePath's position relative
# to render. Again, adapt to your application, if called for.
myNodePath.setPos(myNodePath.getPos(render) + forward*5.0*dt)
2 Likes

Hello, thanks for your reply. The third method you provided works perfectly, thanks a lot. I am still bothered by why the first method still doesn’t work. Here is the code as a result of your feedback:

        if self.keyMap["forward"]:
            self.y += 7*dt*(math.cos(math.radians(self.heading)))
            self.x += 7*dt*(math.sin(math.radians(self.heading)))

EDIT: The line outside the if statement:

        self.cube.setPos(self.x,self.y,self.z)
1 Like

It’s my pleasure. :slight_smile:

Hmm… Where are you getting the value of “self.heading”? And do any nodes above “self.cube” in the scene-graph–that is, its parent, its parent’s parent, and so on–have transformations of any sort–that is, rotations, scalings, etc.?

1 Like

So basically self.cube is a child of render and camera is a child of self.cube. That’s pretty much the entire scene graph with environment a child of render.

self.heading is initially set to 0, with it changing with this:

            self.heading += 40*dt
        if self.keyMap["lookR"]:
            self.heading -= 40*dt
        if self.heading > 360:
            self.heading -= 360
        elif self.heading < 0:
            self.heading += 360

If you simply want the entire code, here it is:

from panda3d.core import loadPrcFile
import math
loadPrcFile("configuration.prc")
from direct.showbase.ShowBase import ShowBase
class MyGame(ShowBase):
    def __init__(self):
        super().__init__()
        self.disableMouse()
        self.test = self.loader.loadModel("models/environment")
        self.test.reparentTo(self.render)
        self.x = 0
        self.y = -140
        self.z = 5
        self.heading = 0
        self.cube = self.loader.loadModel("cube.obj")
        self.cube.reparentTo(self.render)
        self.camera.setPos(0,-20,2)
        self.camera.reparentTo(self.cube)
        self.cube.setPos(self.x,self.y,self.z)
        self.keyMap = {
            "forward": False,
            "backward": False,
            "right": False,
            "left": False,
            "Rotate": False,
            "lookL": False,
            "lookR": False
        }
        self.accept("w", self.updateKeyMap, ["forward", True])
        self.accept("w-up", self.updateKeyMap, ["forward", False])

        self.accept("a", self.updateKeyMap, ["left", True])
        self.accept("a-up", self.updateKeyMap, ["left", False])

        self.accept("s", self.updateKeyMap, ["backward", True])
        self.accept("s-up", self.updateKeyMap, ["backward", False])

        self.accept("d", self.updateKeyMap, ["right", True])
        self.accept("d-up", self.updateKeyMap, ["right", False])

        self.accept("arrow_left", self.updateKeyMap, ["lookL", True])
        self.accept("arrow_left-up", self.updateKeyMap, ["lookL", False])

        self.accept("arrow_right", self.updateKeyMap, ["lookR", True])
        self.accept("arrow_right-up", self.updateKeyMap, ["lookR", False])
        self.taskMgr.add(self.update, "update")


    def updateKeyMap(self, controlName, controlState):
        self.keyMap[controlName] = controlState
    def update(self,task):
        dt = globalClock.getDt()

        # If any movement keys are pressed, use the above time
        # to calculate how far to move the character, and apply that.
        if self.keyMap["forward"]:
            #self.y += 7*dt*(math.cos(math.radians(self.heading)))
            #self.x += 7*dt*(math.sin(math.radians(self.heading)))
            quat = self.cube.getQuat(self.render)

            forwardVec = quat.getForward()
            self.cube.setPos(self.cube.getPos(self.render) + forwardVec * 40 * dt)

        if self.keyMap["backward"]:
            self.y += -17*dt
        if self.keyMap["left"]:
            self.x += -17*dt
        if self.keyMap["right"]:
            self.x += 17*dt
        if self.keyMap["lookL"]:
            self.heading += 40*dt
        if self.keyMap["lookR"]:
            self.heading -= 40*dt
        if self.heading > 360:
            self.heading -= 360
        elif self.heading < 0:
            self.heading += 360
        #self.cube.setPos(self.x,self.y,self.z)
        self.cube.setH(self.heading)
        return task.cont


game = MyGame()
game.run()

Copy-pasting that code (and replacing the cube-model with the standard panda-model), it looks like the only problem with your “cos-and-sin” approach was that the call to “math.sin” for the x-coordinate wanted for negation. That is, the working lines look like this:

            self.y += 7*dt*(math.cos(math.radians(self.heading)))
            self.x += 7*dt*(-math.sin(math.radians(self.heading)))

(Minus-signs are somewhat of a bane in coding, I think; a frequent source of trip-ups!)

1 Like

This works, thanks a lot.

Whats the purpose of the dash before math.sin?

It’s not a dash–it’s a minus-sign. That is, it takes the result of the call to “math.sin” and negates it.

So, if the result of “math.sin(math.radians(self.heading)))” is, say, 0.45, it becomes -0.45 (i.e. negative zero-point-five). (And conversely, if the result was originally negative, it makes that result positive.)

1 Like

oh okay, I get it now, thanks very much for your help :slight_smile:

1 Like

I’m having a similar issue, but my code is different than his. I’m trying to get the player to rotate, in terms of XYZ coordinates, left or right, based on whether the mouse position is not in center (300, 300).

I do not know if Quat could solve this issue. My code is down below and are in snipplets:

from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from panda3d.core import AmbientLight
from panda3d.core import DirectionalLight
from panda3d.core import Vec4, Vec3, Vec2
from panda3d.core import CollisionBox, CollisionNode
from panda3d.core import TextureStage
from panda3d.core import WindowProperties
from panda3d.core import MouseButton
from panda3d.core import Point3
from panda3d.core import Filename
from panda3d.core import CompassEffect
from panda3d.bullet import BulletWorld
from panda3d.bullet import BulletPlaneShape
from panda3d.bullet import BulletRigidBodyNode
from panda3d.bullet import BulletBoxShape
from panda3d.bullet import BulletCharacterControllerNode
from panda3d.bullet import BulletCapsuleShape
from panda3d.bullet import ZUp
from panda3d.core import BitMask32
from panda3d.core import Quat

import sys, os
from math import pi, sin, cos

class Game(ShowBase):
        def __init__(self):
                super().__init__()

                # -snip-

                # Variables

                ## Boolean to test the in-game camera

                setCameraTest = True

                self.heading = 0
                self.pitch = 0
                self.oldMouseX = 300
                self.oldMouseY = 300

                ###

                # -snip-

                # Setting up an object in Bullet Physics engine

                # Setting up a world in Bullet Physics engine

                self.world = BulletWorld()
                self.world.setGravity(Vec3(0, 0, -9.81))
                shape = BulletBoxShape(Vec3(30, 30, 30))
                node = BulletRigidBodyNode('World')
                node.addShape(shape)

                self.worldNP = render.attachNewNode(node)

                ###

                # -snip-

                # Setting up a player

                ## Player's variables

                height = 1.75
                radius = 0.4
                shape = BulletCapsuleShape(radius, height - 2*radius, ZUp)

                ## Setting the player in Bullet Physics universe

                self.playerNode = BulletCharacterControllerNode(shape, 0.4, 'Player')
                playerNP = self.worldNP.attachNewNode(self.playerNode)
                playerNP.setPos(-2, 0, 14)
                playerNP.setCollideMask(BitMask32.allOn())

                self.world.attachCharacter(playerNP.node())

                ## Setting the player in visual universe

                ### Setting up a texture for the player

                playerBack = loader.loadTexture(mydir + "/characters/player/imgPlayer0.png")
                self.player = loader.loadModel(mydir + "/characters/charBase")

                ####

                # Use 0, 5, 0 as a way to test gravity.      #
                # Otherwise 0, 5, -0.07 for ground movement. #

                self.player.flattenLight()
                self.player.setTexture(playerBack)
                self.player.setTransparency(True)
                self.player.setPos(0, 5, -0.07)
                self.player.setScale(0.1, 0.1, 0.1)
                self.player.reparentTo(render)

                self.player.setBillboardAxis()

                ### Things that relates to player

                self.playerKeyControl = {
                    "up" : False,
                    "down" : False,
                    "left" : False,
                    "right" : False}

                ####

                ###

                # Camera

                if setCameraTest:
                    base.disableMouse()
            
                    props = WindowProperties()
                    props.setCursorHidden(True)
                    base.win.requestProperties(props)

                    self.dummyParentCamera = render.attachNewNode('dummyCamera')
                    self.dummyParentCamera.reparentTo(self.player)
                    self.dummyParentCamera.setEffect(CompassEffect.make(render))

                    self.camera.reparentTo(self.dummyParentCamera)
                    self.camera.setY(-50)
                    self.camera.lookAt(self.dummyParentCamera)
                    self.taskMgr.add(self.MouseDebug, "MouseDebug")

                # Tasks to add

                taskMgr.add(self.CheckControl, 'CheckControl')
                taskMgr.add(self.update, 'update')

                ###

        def CheckControl(self, task):
            base.accept('w', self.UpdateControl, ["up", True])
            base.accept('w-up', self.UpdateControl, ["up", False])
            base.accept('s', self.UpdateControl, ["down", True])
            base.accept('s-up', self.UpdateControl, ["down", False])
            base.accept('a', self.UpdateControl, ["left", True])
            base.accept('a-up', self.UpdateControl, ["left", False])
            base.accept('d', self.UpdateControl, ["right", True])
            base.accept('d-up', self.UpdateControl, ["right", False])

            return task.cont

        def MouseDebug(self, task):
            md = base.win.getPointer(0)
            x = md.getX()
            y = md.getY()

            if base.win.movePointer(0, 300, 300):
               self.heading = self.heading - (x - 300) * 0.5
               self.pitch = self.pitch - (y - 300) * 0.5

               ####### This is the code where I am trying to apply forward rotation #######

               if self.oldMouseX != x and self.playerKeyControl["up"]:
                    if self.oldMouseX < x:
                        self.player.setPos(self.player.getPos() + Vec3(0.01, 0, 0)) 
                    else:
                        self.player.setPos(self.player.getPos() + Vec3(-0.01, 0, 0))
   
            self.dummyParentCamera.setHpr(self.heading, self.pitch, 0)
            base.accept('wheel_up', lambda : base.camera.setY(base.camera.getY() + 200 * globalClock.getDt()))
            base.accept('wheel_down', lambda : base.camera.setY(base.camera.getY() - 200 * globalClock.getDt()))
        
            return Task.cont

        def UpdateControl(self, controlName, controlState):
            self.playerKeyControl[controlName] = controlState

        def update(self, task):
                md = base.win.getPointer(0)
                dt = globalClock.getDt()
                self.world.doPhysics(dt, 10, 1.0/180.0)

                if self.playerKeyControl["up"]:
                    self.player.setPos(self.player.getPos() + Vec3(0, 5.0 * dt, 0))
                if self.playerKeyControl["down"]:
                    self.player.setPos(self.player.getPos() + Vec3(0, -5.0 * dt, 0))
                if self.playerKeyControl["left"]:
                    self.player.setPos(self.player.getPos() + Vec3(-5.0 * dt, 0, 0))
                if self.playerKeyControl["right"]:
                    self.player.setPos(self.player.getPos() + Vec3(5.0 * dt, 0, 0))

                self.oldMouseX = md.getX()
                self.oldMouseY = md.getY()

                return task.cont

game = Game()
game.run()

Hmm… It looks like you’re applying a CompassEffect to the “dummyParentCamera” node–that might be overriding your attempts to rotate the camera as it attempts to keep the node’s orientation static relative to “render”.

@Thaumaturge I’m very sorry that my question was misunderstood. Let me say it again with North South West East compass keywords.

When I press ‘W’, the player only runs north. I wanted that when the player faces, for example, east, their movement goes forward on east and that their left movement goes north, right being south, and backwards being west. This thinking applies in all North South West East situations towards where the player is facing with their respective movement.

Maybe this thing would require to apply some sort of geometry algorithm since it deals with circular values. Then again, if I remember it correctly, HPR values does not affect the coordinate system.

Ah, I see. In that case, yes, the “quat”-based method that I gave above should work, indeed. (As should the other methods described in this thread above.)

I’m not entirely sure of what you mean by this, but setting a node’s HPR values does alter that node’s local coordinate system, I believe. The system would, of course, appear unchanged from the perspective of the node itself–the y-axis would always be “forward”, for example–but from the perspective of the scene-root the node’s coordinate system would be rotated.

True. My apologies that my words sounds odds. That was what I was trying to refer on XYZ being the coordinate system.

1 Like

Just to make sure that I’m conveying this clearly: the x-, y-, and z- axes for a given node are not necessarily the same as they for another node. If one node is rotated relative to another, then its x-, y-, and z- axes are also rotated relative to the x-, y-, and z- axes of the other node.