Quat.angleDeg and Quat.setHpr with roll

I’ve discovered what seems to be either an oddity in the Quat class or an issue with quaternions that I could use a solution to:

Consider the following (tested using 1.8.1, I think that it was):

from panda3d.core import Quat, Vec3

quat1 = Quat()
quat2 = Quat()

quat1.setHpr(Vec3(0, 0, 0))
quat2.setHpr(Vec3(0, 0, 90))

print quat1.angleDeg(quat2)

Note that the two Quats differ only in the component specified by “roll”, but that they do differ.

The output, for me, at least, seems to be a very small number – in other words, the result seems to be that they are considered to be more or less the same rotation, when one would expect a 90-degree angle between them.

On the other hand,

Is this an issue in either Quat.setHpr or Quat.angleDeg, or is it a quaternion issue? (I’ll confess that my knowledge of quaternions is somewhat superficial.)

For context, I’m attempting to slerp between two Quats based on time; since the process is intended to respond to user input, I would prefer to not use a LerpQuatInterval. I’m currently attempting to calculate the appropriate ratio to pass to my slerp method by calculating the angle between the desired Quat and the current Quat, and then defining the ratio to be my maximum rotation for this frame divided by the above angle (and skipping the slerp if the angle is less than that maximum rotation). Unfortunately, since roll is included in the angles that compose my Quats, I’m finding that in cases in which there is significant difference in roll but little difference in heading or pitch, the angle returned is very small and so either the slerp runs very quickly or is skipped entirely.

If this is a quaternion issue, then what might I do about it?

My thanks for any help given. :slight_smile:

1 Like

Quat.angleDeg(Quat) only computes the angle of the “forward” vector of each quaternion to each other, which excludes the roll component. It can’t compute any other angle, since the total rotation between two quaternions can only be expressed as a three-dimensional rotation, e.g. with another quaternion.

But if you want to smoothly lerp from one quaternion to another, you can just use the standard lerp equation, e.g. r = A + t * (B - A) to compute r = A when t = 0, r = B when t = 1, and all rotations in between for t in (0, 1).

David

Ah, I see, thank you.

With regard to lerping, I think that that still leaves me stuck on how to determine the value of “t”.

To explain, I want the angle between my quaternions is because I want to compare that offset to my maximum rotation for a given frame; if the offset rotation is less than (or equal to) that maximum rotation, I simply set my NodePath’s orientation to the desired orientation, but if the offset is greater, then I take (maximum rotation for this frame)/(offset rotation) as my value of “t” for my slerp. It seems to me that this problem remains

In case I’m not explaining myself clearly (I’ll confess that I’m perhaps not at my best ^^; ), perhaps an excerpt from my code, with comments added, will clarify my intent:

        # "self.weaponPivotNP" is a NodePath used to rotate an object -- a sword in this case.
        armQuat = self.weaponPivotNP.getQuat()
        
        # Find the offset from the NodePath's current orientation to the desired orientation
        armDQuat = self.swordAngles[0].angleDeg(armQuat)
        
        #  Since we have a variable time-step, calculate the largest rotation that we want to
        # allow in this frame
        maxRot = self.weaponAcceleration*dt
        
        if armDQuat > maxRot:
            #  If our desired rotation is larger than our maximum, slerp, using as our "t" value
            # the ratio of maximum rotation for this frame to desired rotational offset.
            newQuat = Common.slerp(armQuat, self.swordAngles[0], maxRot/armDQuat)
            self.weaponPivotNP.setQuat(newQuat)
        else:
            # If the desired rotation is close enough, just assign it.
            self.weaponPivotNP.setQuat(self.swordAngles[0])

Hmm, I guess you will need to define precisely what you mean by “maximum rotation” in a 3-dimensional environment. If nothing else, you can compute the different rotations between two quaternions as another quaternion (use D = A * inv(B), which you can think of as D = A / B), and then convert that difference into HPR, and then examine H, P, and R individually. Or you can take the forward vector angle as one component and the roll as another component, which you treat individually. Or a number of other plausible approaches, I guess.

David

Ah, both of those options look promising (although I want to think about them again when I’m rather less tired than I am at the moment! ^^; ) – thank you!

As to what I mean by a “maximum rotation”, my apologies – I perhaps worded that poorly. What I mean is simply that I have a “rotation speed” (let’s call that “rotSpeed”); for a given frame and dt, then, I have a rotation for that frame of rotSpeeddt. However, I may want either a smaller or larger rotation for a given quaternion; smaller rotations can simply be applied in full, but larger ones would be expected to only occur only in part, completing in a later frame. Thus rotSpeeddt effectively becomes is the maximum rotation for that frame.

Hmm… I may well be missing something, but you see anything wrong with taking D = A*inv(B), as you suggest, and then simply using the angle of D? A few tentative experiments seem promising, but I’m honestly not sure of how to check the validity of the numbers that I’m getting for cases in which more than one of h, p or r differ between Quats A and B…

By “angle of D”, do you mean D.getAngle()? That’s only part of the description of the rotation; the other part is D.getAxis(). If you only consider D.getAngle(), you’re ignoring large changes of direction between A and B.

David

Ah, narg. But fair enough, and thank you – I’ll probably look at getting the h, p and r of that difference Quat, as you suggested, in that case.