Getting the heading from A to B

So I am writing a pathfinding algorithm, and I want to make the NPC using the algorithm move away from the player’s character when the player enters their detection range. I was wondering if there was a simple way to make the NPC rotate to the heading that will take it directly away from the player’s character.

At the moment, I am thinking of calculating the X and Y distances from the player to the NPC, and then using trigonometry to get the angle between the hypothenuse and the Y out of that (since the heading 0 takes one along the Y axis, right?), but is there some simpler way to do this?

You can first get the vector from A to B and use Vec3.angleDeg to calculate the angle.

Thanks for the tip! I’ll give it a shot.

EDIT: a little bit of fumbling and searching later, I’ve realized I don’t know how to use vectors in Panda yet. I remember the basics of vectors I learned in school, but the API reference isn’t very helpful on this point. Is there a concise tutorial somewhere I can take a look at? I am going to go hunt through the code in the sample programs to see what I can find. Mostly, Vec3 seems to be used with light, but what are the three values in the parentheses? I would have thought vectors needed a lot more numbers to be described in 3D space, since they have 3-point origins, directions (either by passing through another 3-point, or perhaps with two angles) and magnitudes all at once.

EDIT 2: Ah, I see. Some quick experimenting with lighting reveals to me that Vec3(-50, 0, 0) makes light travel from the top of the X axis downwards, and likewise for all other coordinates. I imagine that these are vectors of infinite magnitude, since there is no origin, target or magnitude in the values; changing one value doesn’t seem to matter if the other two are at 0. Then again, maybe that’s just because it’s lighting. I’ll have another look at the API to see if I can make some sense of the methods now.

EDIT 3: Enlightenment! I should calculate the X and Y differences, and use them as the X and Y components in a vector with a Z value of 0 (assuming it’s all on the same level), and then use .angleDeg() on a vector with something like (0, 10, 0) (to represent a straight line going up the Y axis from the origin, which would in theory be the player’s location), which would give me the heading which the NPC would then use. Does that sound right?

EDIT 4: All right, now onto the next problem. I’ve got a giant collision sphere around my NPC, and I want to turn the NPC away from the player when the player collides with this giant sphere. Problem is, I can’t seem to get the coordinates from the player. As it is, the player has a solid with an IntoCollisionMask, and the NPC with a FromCollisionMask, because I found it inconvenient to give the NPCs IntoCollisionMasks, given my setup. I understand that my code is failing because I am trying to query the position of the player’s CollisionNode, as this apparently isn’t possible, but I am unsure of how to get the player’s NodePath from the CollisionNode, since the CNode is attached to the player, which is in turn reparented to the player’s NodePath. Should I use .getParent(2) for this? The code I use for this is the following, as a part of a continually running task:

        for i in range(self.ghostFleeing.getNumEntries()):
            entry = self.ghostFleeing.getEntry(i)
            if (entry.getIntoNode().getName() == "pacmanHunt"):
                pacX = entry.getIntoNode().getX()
                pacY = entry.getIntoNode().getY()
                ghostX = self.ghost.getX()
                ghostY = self.ghost.getY()
                vecX = ghostX - pacX
                vecY = ghostY - pacY
                huntVec = (vecX, vecY, 0)
                fleeHeading = huntVect.angleDeg(Vec3(0, 10, 0))
                self.ghost.setH(fleeHeading)

I really recommend to get a tutorial on vectors on-line or something like that.

But to answer your questions:
In linear algebra, vectors don’t have an origin. All a vector has is a direction and length - it doesn’t matter if the origin is different, it’ll still mean the same vector.
The three coordinates are the X size, the Y size and the Z size of the vector. The total length can be calculated (by the Pythagorean theorem) using sqrt(xx + yy + z*z), or in Panda3D, using vec.length().

If you see vectors as arrows, the X coordinate would be the X coordinate of the end point minus the X coordinate of the origin. So to get the vector between point A and B, you would do B.getPos() - A.getPos().

Ah, yes, I can see I’ll need to brush up on all that. Studying for a degree in the humanities does wonders for math skills, doesn’t it? Thanks for the pointers, I’ll have to dig up those old notes, check out some introductions and give the code another go, to make sure it all makes sense. I’m starting to remember how the teachers told us we’d need this stuff even if we didn’t think we would.

So I’ve sort of succeeded, but I think the problem is my coding, now, and not my math. I’ve got the following as part of a fleeing algorithm:

        if self.isFleeing == False:
            for i in range(self.ghostFleeing.getNumEntries()):
                entry = self.ghostFleeing.getEntry(i)
                if (entry.getIntoNode().getName() == "pacmanHunt"):
                    self.isFleeing = True
                    self.pacPos = entry.getSurfacePoint(self.ghost)
                    self.ghostPos = self.ghost.getPos(self.ghost)
                    self.huntVec = self.pacPos - self.ghostPos
                    self.fleeHeading = self.huntVec.angleDeg(Vec3(0, 10, 0))
                    self.ghost.setH(self.ghost, self.fleeHeading)
                    taskMgr.doMethodLater(2, self.keepFleeing, "keepFleeing")

The result, however, is not really what I want. The ghosts tend to run away along the global Y axis when the player comes within range, and when the player stays within range, they just switch back and forth along the global Y. I’ve done some tests, and in some configurations self.fleeHeading is always either 0 or 180; in other configurations it is more varied, between 0 and 360, but it still isn’t the heading that takes the ghosts away from the player.

I tried to go at it with trigonomety, but I can’t seem to get the individual X and Y coordinates of the SurfacePoint, or of the player itself.

I’ve also tried making them lookAt() the surfacePoint, and then flip around 180°, but this doesn’t work, either, since they don’t seem to lookAt the surfacePoint’s coordinates at all.

Is there maybe some simpler way to make actors face away from other actors, without using headings and state changes?

EDIT: Ah, no, I’ve managed to make it work using a combination of trigonometry and vectors, and now it works exactly as intended. Thanks for the tips!