[SOLVED] relative-absolute rotation: different degrees?


It seems to me that Panda3d uses different units for relative rotation than for absolute rotation. I have made a little experiment: I made two different pitch functions - one of them is using relative rotation, the other absolute rotation. In that experiment the starting position is (0,0,0) and I am changing only P whilst H and R are not changing, so the outcome should be the same for both variants. But it is not.

   def pitch(self):
        self.countInterval = self.countInterval + 1
   # "absolute" rotation
        pitch = self.my_model.hprInterval(0.4,Vec3(self.my_model.getH(),self.my_model.getP()+10,self.my_model.getR()))
        print "after", self.countInterval, ":", self.my_model.getP()

The outcome:
after 1 : 0.0
after 2 : 10.0
after 3 : 20.0
after 4 : 30.0
after 5 : 40.0
after 6 : 50.0
after 7 : 60.0
after 8 : 70.0
after 9 : 80.0
after 10 : 90.0
after 11 : 100.0
after 12 : 110.0
after 13 : 120.0
after 14 : 130.0
after 15 : 140.0
after 16 : 150.0
after 17 : 160.0
after 18 : 170.0
after 19 : 180.0
after 20 : 190.0
after 21 : 200.0
after 22 : 210.0

    def pitch(self):
        self.countInterval = self.countInterval + 1
        # rotation relative to the position
        pitch = self.my_model.hprInterval(0.4,Vec3(0,0+10,0),other=self.my_model)
        print "after", self.countInterval, ":", self.my_model.getP()

The outcome:
after 1 : 0.0
after 2 : 33.2709236145
after 3 : 67.4320373535
after 4 : 78.4958114624
after 5 : 44.5862159729
after 6 : 11.893951416
after 7 : -19.476474762
after 8 : -52.7802200317
after 9 : -86.1420974731
after 10 : -59.8163261414
after 11 : -26.9915027618
after 12 : 4.35094738007
after 13 : 37.6598205566
after 14 : 71.8005218506
after 15 : 74.1335296631
after 16 : 72.0877990723
after 17 : 37.9427261353
after 18 : 3.84448432922
after 19 : -28.9742698669

I don’t get it.

  1. Why is the relative rotation turning the model by 33 degrees and not just 10 degrees - there is “Vec3(0,0+10,0)” so it should be just 10 degrees - right?

  2. What degrees does getP() return while doing relative rotation? I understand that rotation 1 x around the clock should be 360 degrees - but when I am rotating my model around the clock than getP() goes up to almost 90, then back to 0, then to almost -90 and back to 0 again.

Where have I made mistake?
Thank you.

Your first confusion is assuming that:

node.setHpr(other.getH(), other.getP() + 10, other.getR())

is equivalent to:

node.setHpr(other, 0, 10, 0)

They are not, particularly if “other” has a nonzero H or R. The former adjusts the P only, the latter might adjust all three H, P, R, as necessary, in order to set the rotation of “node” to 10 degrees higher than that of “other”. The fact is that H, P, and R all interrelate, so you can’t change one without changing the range in which the others operate. This is why the P value appears to be changing in larger increments in the second case. If you printed my_model.getHpr() instead of just my_model.getP(), you would see that in fact all of the components are being adjusted together, but the net effect of all three changes will be just a total change of 10 degrees each time.

As to why the range of P only persists between -90 and 90, well, P is only unique between -90 and 90. Rotations beyond that are redundant with other values. (For instance, a P of 360 is the same thing as a P of 0. A P of 180 is the same thing as a H of 180 and an R of 180.) When you set a HPR value directly, Panda can remember precisely the value that you set. But when you set a relative HPR, Panda has to compute a different HPR that means the same thing in relative space, and the different HPR that it computes is going to be constrained to the range -90 to 90 in P.


Thank you very much for your answer.

That is why I’ve set up that experiment so that starting position is (0,0,0) and I am changing only P. That way at least the first step should be the same for both relative and absolute rotation (P rotation 10 degrees does not change H and R). I tried to print getHpr() instead of getP():

The outcome:
after 1 : VBase3(0, 0, 0)
after 2 : VBase3(0, 31.5181, 0)
after 3 : VBase3(0, 64.9015, 0)
after 4 : VBase3(-180, 81.7721, 180)
after 5 : VBase3(180, 48.5725, -180)
after 6 : VBase3(-180, 17.4019, 180)

I understand that H, P and R are interrelated - but:

  1. Relative rotation should also adjust ‘absolute’ H and R (and P) when changing ‘relative’ P, right? How would that even be possible to keep changing P whithout change of H and R? So there should be no difference between relative and absolute rotation except for the frame of reference.

  2. Why does P change about more 30 degrees and not just 10 degrees - even the first steps when H and R are still 0?

I just tried on a Windows machine:
relative rotation:
after 1 : 0.0
after 2: -58.8611564636
after 3 : -62.1199645996

The same test, different behavior.
And I tried again:
after 1 : 0.0
after 2 : -40.0345077515
after 3 : -89.5971069336 - different numbers.

… the model is rotating like crazy. Where those numbers come from?

The most important thing is: how do I rotate the model so that it pitches relatively (a reference frame embedded in a body - like this: http://en.wikipedia.org/wiki/File:Euler2.gif ) by 10 degrees

Thanx a lot for your time, I really appreciate it.

I think my own explanation of my problem is confusing - I am sorry about that.

I mean - I know that when I am changing relative P, I understand that absolute H, P adn R will change. As an example of my problem I was talking about special case - I start at (0,0,0) and I change only P so H and R should change only after the model ‘flips’ on it’s back - just like when you are rotating normally - absolutely.

I am trying to explain that my problem is not that when I am using relative rotation, absolute H, P and R are changed more or less than exactly 10 degrees - my problem is that when I am using relative rotation, relative change of P in comparison to its starting position is not exactly 10 degrees.

I am sorry about the confusion - I just hope it’s more clear now.

Ah, right. The other thing I missed was the nature of your intervals. The “other” parameter on an interval means the relative operation is reevaluated each frame the interval runs, not just at the beginning. On the other hand, in your “absolute” case, you are only computing the addition at the beginning, when the interval is created. The “other” parameter here is not intended to point back to the node itself–you are causing a recursive loop, which means each frame the relative change is compounded.

Your intervals are running over a duration of 0.4 seconds, which is likely to include several frames. In the “absolute” case, it doesn’t matter how many frames the interval takes to run to completion. But in your “relative” case, the more frames it runs, the larger the difference, because of the compounding recursion.


Thank you. I did not know that. If I understand correctly that also explains why relative rotation interval gives different numbers/angles on different hardware - the faster PC, the more frames it runs, the larger the difference - am I right? If yes - why is it implemented like that? And how am I supposed to do interval with relative rotation without being dependant on FPS? Just 10 degrees no matter of FPS?

BTW, how do you know all this stuff? I would love to find my solution without wasting your time but I could not find anything - AFAIK there is nothing about this stuff in manual or reference??

Thanx again.

Exactly right.

The “other” parameter is intended for cases when you want to move something relative to an object which is itself moving, for instance, to lerp a homing missile closer to a randomly moving ship, or something like that. It’s simply a mistake to pass other = self.

If you want to perform a lerp to a target computed relative to oneself, then compute the target ahead of time, before you begin the lerp.

hpr = origHpr
np.setHpr(np, 0, 10, 0)
targetHpr = np.getHpr()
i = np.hprInterval(0.4, targetHpr)

It’s true this is a bit clumsier. It would be easier if we had an operation like np.getRelativeHpr(np, 0, 10, 0). We already have getRelativePoint() and getRelativeHpr(); I should add getRelativeHpr() to be consistent.

In my case, it’s just years of experience. The manual does a good job of covering the basics, but Panda is a big system, and the manual isn’t nearly complete enough to cover all these details. It is, however, freely editable by anyone, and it has been gradually growing over the years. If you would like to contribute to the project, thoughtful additions to the manual would be very welcome.

The manual is implemented as a wiki; click on the tiny dot below any page to log on or create an account for yourself. The only thing I ask is that you try to keep it structured like a manual, which is supposed to provide a structured explanation of the engine: introduce fundamental concepts first and explain them clearly before going on to describe new concepts that build on them. Create a new page when needed to describe a concept more fully. Wikis have a tendency to devolve into loose collections of “if you have this obscure bug, try this highly specific solution” reports, which I’d rather avoid.


Hey David
Thank you very much, you have helped me a lot.

I will certainly try as soon as I get to know Panda3d a bit better.


I am sorry to bother you again but I still have a little problem with this:

I have tried to do it like this (did I understand correctly what you meant?):

        origHpr = my_model.getHpr()
        self.my_model.setHpr(self.my_model, 0,10,0)
        targetHpr = self.my_model.getHpr()
        pitch = self.My_model.hprInterval(0.4, Vec3(targetHpr))

The model does turn 10 degrees relatively to its position (thanx again) but when it flips over its back - its behavior gets weird - it gets to correct target position but it does not go straight from original position to target position but it rotates over like crazy. When I print original position and target position I get this:

origHpr VBase3(0, 80, 0)
targetHpr VBase3(0, 90, 0)
origHpr VBase3(0, 90, 0)
targetHpr VBase3(-180, 80, 180)
origHpr VBase3(-180, 80, 180)
targetHpr VBase3(180, 70, -180)
origHpr VBase3(180, 70, -180)
targetHpr VBase3(-180, 60, 180)

I guess the problem has something to do with the fact that H and R keep changing from 180 to -180. What could be the problem? I have tried to change just P instead of Hpr but the result is the same.

When I change not only P but also H and R - the behavior seems to be similar and the output looks like this:

origHpr VBase3(174.571, 18.7473, -158.827)
targetHpr VBase3(178.215, 9.3913, -159.716)
origHpr VBase3(178.215, 9.3913, -159.716)
targetHpr VBase3(-178.333, 1.5528e-05, -160)
origHpr VBase3(-178.333, 1.5528e-05, -160)
targetHpr VBase3(-174.882, -9.39127, -159.716)

In this example the problem appeared when H value changed from 178 to -178. Why is it like that? I have spent a couple of hours trying to make it work but I just keep making some stupid mistake and I cannot figure it out.

BTW, you can see that I am in no position to write any manual.

Sure, this is another problem with hpr’s. It’s because 180 == -180, so 178 == -182, and -178 == 182, and 178 + 4 == -178. When you linear lerp componentwise from one hpr to another, you’re liable to end up going the wrong way around the circle.

One solution is to constrain the rotation this way:

from direct.showbase.PythonUtil import fitDestAngle2Src
targetHpr = VBase3(fitDestAngle2Src(origHpr[0], targetHpr[0]),
   fitDestAngle2Src(origHpr[1], targetHpr[1]),
   fitDestAngle2Src(origHpr[2], targetHpr[2]))

The function fitDestAngle2Src() simply adjusts the destination angle so that it is within +/- 180 degrees of the source angle, ensuring that you always rotate around the short way.

Another solution would be to use quaternions to perform your lerp instead of hprs. Quaternions are much better for doing lerps like this, because they don’t suffer from this problem, and they don’t suffer from another problem you haven’t run into yet called gimbal lock. The only problem with quaternions is it’s hard to visualize a particular rotation given the four quaternion numbers, so normally you use hpr rotations whenever a human (you) needs to supply a rotation, but you use quaternion numbers whenever you are doing any actual relative computations.

        origHpr = my_model.getHpr()
        self.my_model.setHpr(self.my_model, 0,10,0)
        targetQuat = self.my_model.getQuat()
        pitch = self.My_model.quatInterval(0.4, targetQuat)


Hi David,

I cannot say I fully understand quaternians but thanx to you it does exactly what I want it to do - thank you very very much.