Rotation of model tangent to sphere

I am a little new to Panda3D and I need some help with rotation.
I have a sphere and a ship. I am keeping track of the ship’s position via latitude (up/down), longitude (left/right), and heading (rotation of the ship) values in degrees. I have a function to position the model at the correct xyz coordinates when given lat and lon, but I also need to be able to rotate the model so it is always tangent to the surface and pointed the right way. I cannot figure out the math for the life of me.

I have been able to get the ship to be tangent no matter the position by setting:
h = longitude
p = -lat + 90
r = 0
but it is always facing up (bow straight north). As soon as I try to rotate it (so that it faces bow east for example), everything goes wrong. P and R both rotate the model about the vertical axis. Can someone explain whether the hpr values are aboslute, or relative, or how they work? They seem to act totally differently depending on what values for R or P I input.

This may be a situation where you would consider Gimbal lock – Gimbal lock - Wikipedia

As for the relativity of the HPR values, I imagine it would depend on how your boat model is parented in the scenegraph. Absolute and relative positioning are available, I believe, depending on how your graph is constructed.

Obbo’s Descent Obbo's Descent (pyweek 30 submission) would seem to have a solution to this issue as a core game feature, so perhaps one of its developers could explicate the details of how that functions.

1 Like

Hi, and welcome! :slight_smile:

So what you basically want to do is to rotate the ship about the normal to the sphere-surface at the current position of the ship. This means that each time your ship changes position, a new coordinate reference frame needs to be defined, where the Z-axis points in the direction of the normal in that new position.
If you can align the ship’s local Z-axis to the normal, then you can simply rotate the ship relative to itself (if you use offset (delta-) rotations, that is).

So the first thing to do is to align your ship with the current normal vector (which should be equal to the ship’s position vector if the sphere has its origin at coordinates (0, 0, 0)). You can do this with the rotate_to function:

base = ShowBase()
ship = base.load_model(ship_model_filename)

# the following code should be part of a task

    # calculate new ship position
    ship_pos = ...
    rot_mat = Mat4()
    ship_up_vec = ship.get_quat(base.render).get_up()
    normal_vec = ship_pos
    rotate_to(rot_mat, ship_up_vec, normal_vec)
    ship_mat = ship.get_mat()
    ship.set_mat(ship_mat * rot_mat)

Then you can change the heading of the ship relative to itself:

    ship.set_h(ship, new_h - old_h)

Please note that there could be mistakes in the above code and that I currently have no time to test it all out, but it should hopefully set you (and your ship) on your way.

1 Like

That makes sense. I copied your code & made some progress, but right now my ship_pos variable is just a tuple like (45, 45), (45 degrees N and 45 degrees E), I don’t know how to get that into a vector for the rotate_to function (normal_vec). Can you also shed some light on that? Thanks!

Edit, I used the xyz position rather than my lat/lon position for that vector and it does what I need, thank you!

1 Like

If you can calculate from those values the corresponding position on the surface of the sphere, then I believe that the normal-vector at that point is the vector running from the centre of the sphere to that surface-position, normalised. Which is to say, it’s something like this:

normalVec = surfacePoint - sphereCentrePoint

To touch on an earlier point:

HPR values are, I believe, always relative. Specifically, they are by default relative to the parent of the NodePath being rotated, and they can be made relative to some other NodePath by passing that NodePath into the call to setHpr (or setH, etc.) as an optional first parameter. Like this:

# To set a NodePath's rotation relative to its parent:
myNodePath.setHpr(45, 10, 5)

# To set a NodePath's rotation relative to another NodePath:
myNodePath.setHpr(someOtherNodePath, 45, 10, 5)

# A sometimes-useful trick for rotating a NodePath around its own local axes:
myNodePath.setHpr(myNodePath, 45, 10, 5)

The same applies to other transformations, like “setPos”, “setScale”, and so on.

I think that one of the complications may come from the order in which they’re internally applied: a rotation about the x-axis followed by a rotation about the y-axis isn’t necessarily the same as a rotation about the y-axis followed by a rotation around the x-axis.