Create a forward vector from H and P (math help)

Hey everyone, anyway, I wanted to know if anyone knew how to create a forward vector from a heading and pitch, I have a cutscene feature in my game where the script sometimes needs fixed movement, and feeding a created forward vector to node.lookat would do the trick.

I found somewhere in this forum code to create a vector from just the heading, it is here.

surfnorm = Vec3(math.cos(heading / 180 * math.pi), math.sin(heading / 180 * math.pi), 0)

I think I got it from this thread though.

as you can see it creates a 2d vector from the heading, but I need a 3d vector, you can see the 3d (3rd) parameter is zero making it flat, I did some research and I am not great at math but it said somewhere you a axis?

Anyway, I was wondering if anyone could help me complete the above code so it can calculate the pitch into the 3rd parameter to make it a 3d vector? I would be vary grateful, once thanks to anyone who is wiling to help, have a great night guys!

I didn’t completely understand. What is the problem.

I’m wondering whether there isn’t an easier way.

You said that you have a heading and a pitch–are you then getting these from a NodePath? If so, you could perhaps get it’s orientation-quaternion (via “getQuat()”), and then get the “forward” vector from that via “getForward()” (or the “right” vector, or the “up” vector, as appropriate).

Like so:

myNodePath.getQuat().getForward()

If that doesn’t work, then we can look at the trigonometric approach, which should be quite doable with what you have, I think!

@panda3dmastercoder
@Thaumaturge
my apologies guys, I should have explained more, my scene script sometimes demands the character in a fixed HPR position, not wanting to complicate my scene task more, I have one singular lookAt that moves each scene prop in the HPR positions commanded, this is mostly to direct the props towards a target position.

I wanted to add a intercept that allows the script to tweak the vector before it is applied to my lookAt, for example let’s say I had a vector pointing towards a target but the target is slightly above the prop, to prevent the prop from tilting up, I would have a intercept that sets the pitch of the vector back to 0 before it is applied to my lookAt.

The mathematical approach would be better as the calculation needs to be made quick

Okay, fair enough.

That can still be done in the way that I suggested, I believe: just use a stand-in NodePath, apply your changes to that, and get the forward-vector as described above.

Are you sure that the NodePath version will be too slow? Have you tried it, or calculated that it’ll not be fast enough?

If not, I suggest trying it before taking on a more-complex approach. Optimising prematurely can make your life more difficult than it might be!

For garbage collection purposes I would prefer to avoid node paths, I know it sounds insane, but it is much cleaner using mathematics, as using mathematics more have solved issues I have had in my 2 previous projects and even resolved some unsolvable glitches I had.

EDIT: unless there is a way to edit vectors in a node path that is

I mean, it’s your choice, but I really think that you’re overcomplicating things in this case.

Bear in mind that you need only keep one such temporary NodePath, so garbage collection needn’t be significantly affected.

[edit]

You don’t have to: just set your NodePath to have the relevant HPR values (and position, if called for), tweak those values as you like (such as by setting the pitch to zero), and then call “getQuat().getForward()”.

[/edit]

However, as I said, it is your choice, and I’ve made my case, so let me try to help with what you’re asking:

I think that there are essentially two steps to add to your process:

  • First, the calculation of the z-coordinate of your vector.
  • And second, the calculation of the length of the horizontal component of your vector.

The first is fairly straightforward: it should be much as you already have for x and y, but using the pitch instead of the heading. Presuming that a pitch of zero corresponds to a horizontal vector, I think that this would mean that the z-coordinate would be calculated using sine.

The second may call for some explanation:
Consider an arbitrary vector:
(ASCII art time! :P)

  ^
  |
  |......
z |    /:
  |  /  :
  |/____:______>
 0    horizontal

The more vertical the vector–the higher the z-value–the less horizontal it is. A fully-vertical vector has a horizontal length of zero, and a fully-horizontal vector has a vertical length of zero.

The horizontal component of the vector is, of course, composed of the x- and y- coordinates of the vector.

So, to account for this matter we can adjust the length of the x- and y- components of our vector.

Simply put, as we calculate the length of our vertical (z-) component via sine, we calculate (if I have it aright) the horizontal component via cosine.

This gives us a length for the entire horizontal component, not the individual x- and y- components. But since the previously-calculated x- and y- components are essentially the proportions that those components make up from a unit-length, we can just multiply each by the new horizontal length and get the final result!

Now, let me note: I haven’t double-checked any of this, so I may have made mistakes.

1 Like

It’s possible to do the same without NodePath:

q = Quat()
q.setHpr((180, 0, 0))
print(q.getForward())
1 Like

this works! but sadly does not suit my current situation, yes it converts Hpr into a vector, but I want to adjust a currently existing vector’s pitch by euler Pitch. but I will use this for another feature, a huge thanks for this!

EDIT: unless there is a way to replace this vector’s pitch with a currently existing vector pitch as a intercept.
EDIT2: I mean I could try this.

        euler = [math.atan2(vec.getX(), vec.getY()) * 180 / math.pi, math.asin(vec.getZ()) * 180 / math.pi]
        q = Quat()
        if intercept:
            euler[1] = interceptedNumber
        q.setHpr((euler[0], euler[1], 0))
        newVec = q.getFroward()

I but I am afraid of gimball lock during transition

Can you not just replace the value being passed in to “setHpr”?

If I’m missing something, could you perhaps give a step-by-step example of what you’re trying to achieve? I fear that I’m really not getting it. ^^;

Either change the desired component in what you’re passing to setHpr, or if you want to combine two rotations, you can create a separate Quat object with the desired rotation you want to combine with and multiply them together.

1 Like

If you already have a vector, you can compute its heading and pitch without using a NodePath like this:

quat = Quat()
look_at(quat, vec, Vec3.up())
h, p, r = quat.get_hpr()

(The look_at function being used here is this one, not NodePath.look_at.)

It’s important to keep in mind that the roll value here will always be zero, though.

1 Like

my apologies everyone I am little tired as I am typing this
@rdb is it only euler that could be inputted or could I fill x and y with the x and y quat components of a existing vector?
@Epihaius I am trying to achieved the reverse of what you are offering, trying to convert HPR into a vector, but in this case replace pitch of a currently existing vector with that of a euler pitch.
@Thaumaturge same response as to rdb, but give me a moment to post a example code, here is the sample code

standP = 0.1 # euler angle
fwdVec = self.frobj[pridx].getPos(self.render) - target.getPos(self.render)
fwdVec.normalize()
if gravityOn: #checks if character is grounded, if so this intercepts the pitch change and switches it with the pitch declared as the grounded pitch 
    euler = #converts standP into the 3rd component of a vector
    fwdVec.setZ(euler)
node.lookAt(node.getPos(self.render) + fwdVec, node.getQuat().getUp())
1 Like

It was meant as an easy way to retrieve the direction (most importantly the heading) from the existing vector. You can then combine that heading with the desired pitch value using the code rdb gave earlier:

quat = Quat()
look_at(quat, vec, Vec3.up())
h, p, r = quat.get_hpr()
q = Quat()
q.setHpr((h, new_pitch, 0))
print(q.getForward())

But if the pitch needs to become zero, you can simply normalize the existing vector after setting its z-component to zero:

fwdVec.z = 0
fwdVec.normalize()

Only thing to look out for in this case is that the x- and/or y-components of the vector should be non-zero, or you will end up with a null vector (zero-length).

1 Like

yes, but getHPR turns the heading into a euler angle right? if so, there is a risk of gimball lock which I am trying avoid, I want the pitch to be controllable though script, the 0 is simple example.

So guess everyone pointing towards rdbs solution, well if I ignore possible gimball lock during the euler angle transition, this code may be the answer (a modified version of rib’s solution), well that is that then, then I will use rdb’s solution, thank you everyone involved, you have been kind and a great help to me.

self.scnquat = Quat()
lookAt(self.scnquat, fwdVec, self.render.getQuat().getUp())
eulerVec = self.scnquat.getHpr()
if abs(int(scnpivot[1])) > 0: # script can intercept Heading
    eulerVec.setX(0.1 * int(scnpivot[1]))
if abs(int(scnpivot[2])) > 0: # script can intercept Pitch
    eulerVec.setY(0.1 * int(scnpivot[2]))
self.scnquat.setHpr((eulerVec.getX(), eulerVec.getY(), 0))
fwdVec = self.scnquat.getForward()

Edit: so the final result is Epilhaius’s solution combined with rib’s was the answer, and the code above is the result of it, oh and Thuamaturge, I found that your solution using a node is already used by my swimming code, except I,am getting the Up vector instead of the forward.

1 Like