Pitch never appears to get outside of range -90 to 90, but still rotates?

Im very confused, im trying to clamp a free look camera so it stops in the range -90 to 90.

The problem is that the pitch never gets outside of that range despite it continuing to rotate.

Let me explain. I increase pitch relative to mouse movement in the y. It rotates as it should. However, once looking straight up at 90 degrees, it starts to decrease despite adding more! How is this possible?

One would expect that continuing to increase pitch would result in a number increasing beyond 90, more baffeling is if i try to setP(95) just straight up, its value is 85???

So to get around this, i have to just a projected pitch value before setting and then essentially manually clamp. Can anyone explain to me whats going on here?

def _handle_gimbal(self, task):
        if base.mouseWatcherNode.hasMouse() :
            x = base.mouseWatcherNode.getMouseX()
            y = base.mouseWatcherNode.getMouseY()
            delta_x = self.last_mouse_x - x
            delta_y = self.last_mouse_y - y
            self.last_mouse_x = x
            self.last_mouse_y = y
            if self.input_map["right_mouse_down"]:
                self.gimbal_x.setH(self.gimbal_x, delta_x * 10000 * globalClock.get_dt())
                pitch = -(delta_y * 10000 * globalClock.get_dt())
                projected_pitch = self.gimbal_y.getP() + pitch
                if projected_pitch > 90:
                    self.gimbal_y.setP(90)
                elif projected_pitch < -90:
                    self.gimbal_y.setP(-90)
                else:
                    self.gimbal_y.setP(self.gimbal_y, pitch)

                velocity = Vec3()
                if self.input_map["w"]:
                     velocity += Vec3.forward()
                if self.input_map["s"]:
                     velocity += Vec3.back()
                if self.input_map["a"]:
                    velocity += Vec3.left()
                if self.input_map["d"]:
                    velocity += Vec3.right()
                self.gimbal_y.setPos(self.gimbal_y, velocity * 250 * globalClock.get_dt())
                self.gimbal_y.wrtReparentTo(self.render)
                self.gimbal_x.setPos(self.gimbal_y.getPos(self.render))
                self.gimbal_y.wrtReparentTo(self.gimbal_x)

        
        return task.cont

As an added bonus, if you can also tell he how to rotate about the global z axis that would be great, then I can get rid of this whole parenting / parenting x thing im using to do heading.

I figured out the second part, i just move gimbal_x relative to gimbal_y instead.

I’m not sure about your first issue–looking back at an old project of mine, I used a similar approach to the one that you’re using.

Regarding the second issue, you say that you’ve figured it out, but as you don’t say what solution you found I would like to note the following just in case:

One can rotate around the global Z-axis simply like so, I believe:
myNodePath.setH(render, newRotation)

(Where, should you want to offset the NodePath’s rotation, “newRotation” can be something like “myNodePath.getH(render) + someValue”.)

Also, just to check, do I take it correctly that you’re no longer using the sequence of calls to “setPos” and “wrtReparentTo” at the end of your original code? If so, then that’s good to read! (That approach was arguably not incorrect, but it was a bit convoluted, I feel.)

(By the way, please note that you can edit your posts to include new information.)

Thank you for response. Yes, ive solved all issues, but, am still terribly confused by the pitch number.

I solved the re-parenting mess with:

self.gimbal_x.setPos(self.gimbal_y, velocity * 250 * globalClock.get_dt())

Essentially moving x with respect to ys coordinate space, I think this works because they effectively share the exact same heading (y is a child of x), but I also needed to travel freely along the forward axis of y without rotating y. So positioning is done with respect to y, but rotation is done only on the heading of x.

Code For “Free Roam” camera with pitch constraints:

def _handle_gimbal(self, task):
        if base.mouseWatcherNode.hasMouse():
            x = base.mouseWatcherNode.getMouseX()
            y = base.mouseWatcherNode.getMouseY()
            delta_x = self.last_mouse_x - x
            delta_y = self.last_mouse_y - y
            self.last_mouse_x = x
            self.last_mouse_y = y
            velocity = Vec3()
            if self.input_map["w"]:
                    velocity += Vec3.forward()
            if self.input_map["s"]:
                    velocity += Vec3.back()
            if self.input_map["a"]:
                velocity += Vec3.left()
            if self.input_map["d"]:
                velocity += Vec3.right()
            if self.input_map["right_mouse_down"]:
                self.gimbal_x.setH(self.gimbal_x, delta_x * 10000 * globalClock.get_dt())
                pitch = -(delta_y * 10000 * globalClock.get_dt())
                projected_pitch = self.gimbal_y.getP() + pitch
                if projected_pitch > 90:
                    self.gimbal_y.setP(90)
                elif projected_pitch < -90:
                    self.gimbal_y.setP(-90)
                else:
                    self.gimbal_y.setP(self.gimbal_y, pitch)

            self.gimbal_x.setPos(self.gimbal_y, velocity * 250 * globalClock.get_dt())
1 Like

myNodePath.setH(render, newRotation)

So I tried this originally and it starts to rotate but this like “fights” the rotation. Idk how to best explain that. It like “jitters” and remains looking straight ahead

Yup, that looks good to me! :slight_smile:

I’d probably handle the matter of “moving in the forward direction” a little differently–but your approach looks at least as good as mine, at a glance! :slight_smile:

Looking further at the results of changing pitch, it looks like what happens is that the heading and roll values change as one passes a pitch of 90. I’m guessing that this is some artefact of either Euler angles (that is, the “heading, pitch, roll” system) or converting to Euler angles from the (presumed) internal representation.

I think that you’d find that no such issue would present if the code were rewritten to use quaternions, instead.

I think that you’d find that no such issue would present if the code were rewritten to use quaternions, instead.

I considered this but I have no earthly understanding of the concept and by extension panda3ds version. =)

That’s very fair! I make only sparing use of them myself, and my own understanding is limited, I fear! But I wanted the point mentioned as it is an approach in which the problem in question doesn’t show up, I do believe!

1 Like