Getting the right sprite direction like in Xenogears

Started by
12 comments, last by rm3 21 years ago
I''m trying to recreate the Xenogears engine, which I really like, mainly because of the interesting way they did the characters. So I have a billboarded sprite in space at position x1,y1,z1, and a camera at position x2,y2,z2, rotated around that position''s axis at theta degrees, pointed at the sprite. Having numerous sprites, how could I choose the right ANGLE frame (one of 8 directions) to display on the character, based on the direction of the character (0 through 7, 8 possible directions... 0, 45, 90, 135, 180, 225, 270, 315 degrees), the x/z (y is generally fixed and I don''t think it would matter) and the angle of the camera? ... for every sprite. What my first guess was is almost laughable: fabs(Camera.ang - Object.ang) / 45 (div by 45 so we can choose between the 8 direction frames). This works only if the Object.ang is straight ahead (I think it only works if Object.ang is 0, because it seems to act strangely otherwise), and I don''t think this would pick the accurate direction if the camera moved around freely, instead of just rotating around the character. If anyone can give any tips or ideas I would really appreciate it... I really want to get this to work and I know little math to think up an angle-relationship algorithm thingy on my own. Thanks =)
Advertisement
Argh, I solved this problem last year, but I don't have the code for it anymore >_<. Let's see what I can recall from memory... (I hope you have a basic understanding of trig - can't explain it otherwise)

First, I'm drawing a diagram because it will help explain things. This is a bird's eye view:

          z          |  D          | /          |/----B-----O---F---K-- x          |          |   C      |x,z are axis.F is the front direction (0 degrees)B is the back direction (180 degrees)C is the camera's positionD is the direction the sprite is currently facing (degrees)K is the base camera position (for explanation purposes)In this diagram, the user is looking at the sprite's left shoulder from the back.  


By making this resemble a trig problem as much as possible, I find it makes it easier to solve. We'll start with the facing direction F. This is where your character is facing at angle 0. Let's say this is in front. If you want to make front a different angle, just add/subtract 90 degrees from the answer.

If the camera were at position K looking at the sprite, then this would be easy - it's a regular trig problem of finding the angle between DOF (note: DOF should form a right-angled triangle). In this case, it would be Acos(OF, OD). From the looks of this diagram, the answer should be around 45 or 90 degrees.

Now the tough part is accounting for the camera at any position C. You need to get the angle between COF, possibly using B since it's in one of the back quadrants. You'll have to account for the different quadrants and their effect on Acos here. You may also need your camera direction angle here.

Once you have the angle for DOF and COF, now you can take the absolute value of the subtraction between the two, ie. Abs(DOF - COF). Now is where you should restrict the angle to one of your 45 degree numbers. So in the diagram, if DOF is 70 degrees and COF is 210 degrees, then the angle to display to the user would be 210-70 = 140, which, when divided by 45 and rounded, returns 3. 3 represents 135, which would be the angle of the sprite to display to the user.

I hope this helps, but I can't guarantee complete correctness (it has been at least a year after all). Still, it's a starting point. Play with it, and you'll get it eventually.

Latest project: Ariene Game Engine (.NET/DX9)

[edited by - Tri on March 24, 2003 4:15:30 PM]
Thank you so much for your explanation! I am having a bit of trouble though. So, to get it, it would be (using your diagram):

fabs (acos (DOF) - acos (COF)) / 45.0f;

What I am having trouble is relating your diagram to world space. DOF and COF... what are they?

D, O, and F are points? C is a point, I know that heh... D is an angle, so how do I find the angle between an angle, a point (assuming O is the sprite position), and something else (F is a point? or an angle?)

I think I understand the method. Finding the angles using inverse cosine, then the absolute difference. But could you explain what D,O, and F are a bit more?

Once again thanks. This problem looks very solvable now. Hopefully I'll have it working soon.

[edited by - rm3 on March 24, 2003 8:08:44 PM]
Okay, I can see how D and F aren't very clearly explained. oops :o So...

DOF is the angle between lines DO and OF. COF is the angle between lines CO and OF. The formula you gave is correct, the only difficulty now is in determining DOF and COF (and I would round the final result to an integer).

O is the position of the sprite.

F is a actually a point in front of O when the sprite is facing direction 0 (zero). So if O is (0,0,0) and the sprite is facing 0 (zero), then F is (1,0,0). F could also be (2,0,0) or (3,0,0), or whatever, but it should be of the form (x,0,0). The y and z coords are the same as O's.

D is the point that is in front of O when the sprite is facing angle theta. To get this point, take the unit vector of O and add it on to O. So D = O + UnitVector(O). Here's how you calculate the unit vector:
    Public Function UnitVector(ByVal A As Vector) As Vector        Dim Result As Vector        Dim Magnitude As Single = 1 / Math.Sqrt(A.X ^ 2 + A.Y ^ 2 + A.Z ^ 2)        Result.X = Magnitude * A.X        Result.Y = Magnitude * A.Y        Result.Z = Magnitude * A.Z        Return Result    End Function    


To calculate F from D, set F = (Dx, Oy, Oz), ie. the x-coord from D, and the Y and Z coords from O.

Sorry, I should've explained that part a bit more. Hope that helps.

[edited by - Tri on March 24, 2003 9:15:57 PM]
Alright. I feel we''re almost there! =) Sorry for not grasping it completely yet. I appreciate the help though.

Okay so I''ve got

vector3d D = pos + pos.UnitVector();

pos being the sprite''s position vector. Now where does theta (the sprite''s angle, 0,45,90, etc) come into this equation of finding D? Or is it not needed (can''t understand that but maybe).

Once that''s cleared up, I''ll have three points: O (the sprite position), D (a point the sprite''s facing (right?)), and C (the position of the camera). Are those all the points I need to get the angles and the difference?

Thank you so much for your help!! :D
You don''t need theta to calculate D. The unit vector is a vector in the same direction as the vector it''s calculated from. So adding it on to O gives us another vector in the sprite''s facing direction. It''s _almost_ a direction vector, and would be, if we didn''t add O to it.

You''ve got O, D, and C yes, but you''ll also need F, since it''s your starting point. Notice that all the angles have an F in them. F is easy to get, and so is D. I think calculating the angle involving C will be the toughest since you have to take into account all four quadrants (I remember that giving me a headache when I worked on this).
It's too bad you don't have the orig code lol =) Anyway, here's what I've come up with:

		vector3d O = pos;							// position		vector3d D = O + O.UnitVector();			// facing		vector3d C = vector3d(CamX,CamY,CamZ);		// camera		vector3d F = vector3d(D.x,O.x,O.z);		// front point		const double cosDeg = 			(Distance2d (O.x,O.z,F.x,F.z) / Distance2d(C.x,C.z,O.x,O.z));				const double theta = acos(DEG2RAD(cosDeg));		Direction = ABS((int)CameraAngle - (int)RAD2DEG(theta)) / 45;  


Sorry if its sloppy. Now I'm not really sure about this code, and my general idea. Hopefully you or someone else can clear a few things up for me.

1. Why is the sprite's angle not included? The O (sprite position) vector is just a point in space, I don't see how it holds anything about the sprite's direction. Wouldn't the angle have to be used somewhere, so that when you PRESS left and the sprite angle goes from 0 to 180 the sprite changes.

2. Am I calculating the cosine of theta right (Distance between O and F in the xz plane divided by distance between C and O in the xz plane?) ?

Thanks once again Tri!

[edited by - rm3 on March 24, 2003 11:04:10 PM]
Hmm...you know what? I think D is calculated wrong :/ It doesn't say anything about the facing angle; all it says is "in front." Change D to:

D = O + (O.x * sin(SpriteAngle), O.y, O.z * cos(SpriteAngle));

where SpriteAngle is the angle the sprite is facing (in radians). Then when the user presses left or right, all you have to do is change this angle.

F should be (D.x, O.y, O.z). I don't see why O.x was in the y spot, but I think that could be a problem.

cosDeg can't be calculated like that since the triangle formed by OFC isn't a right-angled triangle. But the triangle between OFD IS right-angled. Replace C.x,C.z with D.x,D.z in cosDeg's definition.

Finally, I don't think you need convert degrees to radians in the theta assignment since cosDeg isn't an angle, but a ratio. Just leave it as acos(cosDeg).

Is CameraAngle set to the same scale as the diagram? That is, with 0 degrees along the positive x-axis? If so, then that's fine. I think you've got this one externally, so you don't need C in the diagram.

I think that should do it. Yeah, I wish I had my code too

[edited by - Tri on March 24, 2003 11:44:38 PM]

[edited by - Tri on March 24, 2003 11:48:28 PM]
"I don't see why O.x was in the y spot"

Heh typo

Anyway, the camera position isn't used at all?

OK, here's what I have. "xAng" is the angle of the character, CameraAngle is the angle of the camera between 0 and 359.

		const float Angle = DEG2RAD(xAng);		vector3d O = pos;							// position				vector3d D = O + 			vector3d(O.x * sin(Angle), O.y, 			O.z * cos(Angle));				vector3d C = vector3d(CamX,CamY,CamZ);		// camera		vector3d F = vector3d(D.x,O.y,O.z);			// front point		const double cosDeg = 			(Distance2d (O.x,O.z,F.x,F.z) / Distance2d(D.x,D.z,O.x,O.z));				const double theta = acos(cosDeg);		Direction = 			FABS((int)CameraAngle - (int)RAD2DEG(theta)) / 45;  


In the controls of the character, I set the angle as follows (no movement right now):

if (Right) { xAng = 0; }if (Up) { xAng = 90; }if (Left) { xAng = 180; }if (Down) { xAng = 270; }  


Rotating around the character with the camera, I get the correct directions (looks good), but only when the player is at 90 or 270 degrees. When I press Left or Right, the rotation messes up... the wrong directions are chosen.

Now I can think of 2 reasons:

1. This has to do with arccos's quadrants... Something I haven't accounted for.

2. There's another error somewhere in my code... Something I haven't accounted for.

Here's what happens on each direction:

0 Doesn't rotate correctly (picks wrong frames)
90 Rotates perfectly
180 Doesn't rotate correctly (ditto)
270 Rotates perfectly

Another odd thing... if I'm facing the back of the character, the character facing 90o, pressing Left or Right turns him right, and if facing the front, pressing left or right turns him left... whew boy my head.

Yawn, it's getting late. I don't know if it's late where you are, if it is I don't want to keep you up or anything.

I really appreciate the help and time you're giving me and I'd like it if we could perhaps continue tomorrow. I'll try fiddling with it to see if I can get it working on all directions.



[edited by - rm3 on March 24, 2003 12:15:27 AM]
The only thing we needed the camera position for was to get its direction. When I originally did this, I had camera position/target, but since you've already got its direction, then there's no need.

Try to work with the quadrants now. Here's a little kickstart for you, taken from another function I wrote:
    If Not (Hypotenuse = 0) Then        If Not ((Adjacent / Hypotenuse) = 1) Then            THETA = ArcCos(Adjacent / Hypotenuse)                        ' Take cases            If (vDest.z > vSrc.z) And (vDest.X >= vSrc.X) Then   ' Quadrant 1                ' Don't do anything            End If                        If (vDest.z >= vSrc.z) And (vDest.X < vSrc.X) Then   ' Quadrant 2                THETA = THETA + PI / 2            End If                        If (vDest.z < vSrc.z) And (vDest.X < vSrc.X) Then   ' Quadrant 3                THETA = THETA + PI            End If                        If (vDest.z <= vSrc.z) And (vDest.X >= vSrc.X) Then   ' Quadrant 4                THETA = THETA + 3 * PI / 2            End If                        ' You may not need this line, I put it in for corrective measures originally            THETA = THETA + PI / 2                      Else            THETA = srcAng        End If    Else        THETA = srcAng    End If  

Hypotenuse would be Distance2d(D.x,D.z,O.x,O.z) and Adjacent would be the distance from O to F. srcAng would be xAng in your case. At the end, I reduced theta so that it was between 0 and 2 * PI. vSrc would be the point O, and vDest would be D.

For the quadrants, you'll just have to play with it and see what works. You may also have to play around with quadrants when calculating D - adding the x-coord, but subtracting the z in on quadrant, while subtracting x and adding z in another, for example.

Have fun! :D

[edited by - Tri on March 25, 2003 9:27:32 AM]

This topic is closed to new replies.

Advertisement