Skip to main content
gamedev.net
🔒 Locked

Character Rotation Problem

Started by EienYuri Apr 26, 2006 at 4:37 AM 5 replies 850+ views
Original Post
EienYuri
EienYuri
Hi all, I'm somewhat new to the whole game programming thing, so some of what I'm trying to do may be a bit weird. Please bear with me if I don't immediately grasp what you're trying to tell me. I am designing a game with a 3rd-person behind-the-back camera (i.e., the camera rotates around the character as the mouse is moved. No problems there, luckily!) Since I'm trying to do something more adventure-style (as opposed to a 3rd-person shooter like Max Payne), I want to rotate the character to face in the direction that the movement button is pressed. This I have working, /but/ the character would instantly pop to that facing if I didn't do something - I want to rotate it over the course of a second or so to face that position, rather than just jumping to it. Herein lies my problem: I figured out how to get the character to do that... but only in one direction. All my attempts to make it rotate in the opposite direction when the new facing is, say, less than 180 degrees have resulted in strange errors (usually involving either not working at all, or popping back and forth at random). The relevant section of my code is below. Any help is appreciated. Thanks in advance!
    if(charx!=oldx || charz!=oldz){
        float newfacing;
        vectorv charpos;
        vectorv oldcharpos;
        
        charpos.x=charx;
        charpos.y=0.0f;
        charpos.z=charz;
        
        oldcharpos.x=oldx;
        oldcharpos.y=0.0f;
        oldcharpos.z=oldz;
        
        charmovedist=getDist(&charpos,&oldcharpos);
        float tempx=(charx-oldx)/charmovedist, tempz=(charz-oldz)/charmovedist;
        
        if(tempx>1)
            tempx=1;
        if(tempx<-1)
            tempx=-1;
        
        if(tempx>=0.0f && tempz>=0.0f)
            newfacing=acos(-tempx)*57.2957-90;
        if(tempx<0.0f && tempz>=0.0f)
            newfacing=acos(-tempx)*57.2957-90;
        if(tempx<0.0f && tempz<0.0f)
            newfacing=-acos(-tempx)*57.2957-90;
        if(tempx>=0.0f && tempz<0.0f)
            newfacing=-acos(-tempx)*57.2957-90;

        while(newfacing<0)
            newfacing+=360;
        while(newfacing>360)
            newfacing-=360;

        if(facing!=newfacing){
            facing+=(newfacing-facing)*ticks*7.5;
        }

        while(facing>360)
            facing-=360;
        while(facing<0)
            facing+=360;
    }
Demus79
Demus79
So, practically you know the direction your character is facing and you know the direction he/she is supposed to face. What you want to know which is the shortest way to rotate the character to the new direction.

Some comments about the code:

- you could implement vector stuff as operators inside the vector class so that it would be a bit clearer and less error prone.

- You can work on radians too to avoid unnecessary and sometimes obscure multiplying and dividing.

- you can use fmodf instead of the while() things.


What I can see too is that you don't take it in account which way is the shortest way to turn.

Here is a code for calculating an angle from a vector, should work:

//assumes a normalized vectorfloat AngleFromVector(float x,float y){float temp = acosf(x);   //temp is now between 0 and piif(y < 0)temp = 2 * pi - temp; //if y value is negative, subtract from 2 * pireturn temp;}


now to get the shortest rotation (and the rotation direction), you can subtract the new angle from the old angle and check if the value is greater than 180 degrees (or pi).

[Edited by - Demus79 on April 26, 2006 5:25:33 AM]
lubby
lubby
Cross product the current forward vector of your character with the direction you wish the character to face. Get the sign of the up component of that result.

Vector3 cross = player.forward.cross(button.forward);

If you have Y as up, then the sign you need to check is Y. If the sign is > 0 then turn right, if < 0 then turn left.

To make it the character rotate at a fixed speed simply apply you rotation in speed per second, or some other constant time step.

rot = MAX_ROT_SPEED_IN_SECS * timePassedInSecs;

Now just clamp rot so that it is not > the actual rotation needed.
EienYuri
EienYuri
Quote:
Original post by Demus79
So, practically you know the direction your character is facing and you know the direction he/she is supposed to face. What you want to know which is the shortest way to rotate the character to the new direction.


You succinctly summed up what I was trying to say, thanks.

Quote:
Some comments about the code:

- you could implement vector stuff as operators inside the vector class so that it would be a bit clearer and less error prone.


I guess I have to admit that I'm not even using any classes in my code at all as of yet... As I said, I'm new to this, and I don't fully understand them. I'm reading up on it though, so maybe I'll be able to figure out what you mean in a bit.

Quote:
- You can work on radians too to avoid unnecessary and sometimes obscure multiplying and dividing.


Since the function that I'm using to draw my character calls for degrees, I was trying to use them. Additionally, radians are something I never really learned either so I'll have to look into that as well. Thanks for the advice though. It's helping me figure out where my knowledge is lacking. Always good to know what to read up on.

Quote:
- you can use fmodf instead of the while() things.


How so? I don't quite follow...

Quote:
What I can see too is that you don't take it in account which way is the shortest way to turn.

Here is a code for calculating an angle from a vector, should work:

*** Source Snippet Removed ***

now to get the shortest rotation (and the rotation direction), you can subtract the new angle from the old angle and check if the value is greater than 180 degrees (or pi).


Looking at that code, am I just doing the math wrong, or is that going to return the same value (pi) if the unitized vector provided is (-1,-1) or (-1,1)? (And if it is, is that what it's supposed to do?)

Demus79
Demus79
Quote:

Quote:

- you can use fmodf instead of the while() things.

How so? I don't quite follow...



float angle = fmodf(angle, 360.0f); //now angle is between ]-360.0,360[if(angle < 0)angle += 360.0f;//now angle is between [0.0,360[


Quote:

Looking at that code, am I just doing the math wrong, or is that going to return the same value (pi) if the unitized vector provided is (-1,-1) or (-1,1)? (And if it is, is that what it's supposed to do?)


(-1,-1) and (-1,1) aren't unitized vectors, but I think that I understand what you mean. If these vectors were unitized, the code would return different values, something like 135 degrees for the first, and 225 for the second.

The code I wrote works in 2d space. In your code you have done already the calculation of the direction vector (and normalization). The code I wrote takes in the normalized direction vector and returns the angle you need in [0,PI[ range, which you can convert to angles

You can make a simple macro to convert degrees to radians and vice versa

#define deg2rad(x) (x * pi / 360.0f)
#define rad2deg(x) (x / 360.0f * pi)
EienYuri
EienYuri
Ah, now I understand several things a little better. Thanks.

This is what my code looks like now (as you can see, I'm using your AngleFromVector function):

    if(charx!=oldx || charz!=oldz){        float newfacing;        vectorv charpos;        vectorv oldcharpos;                charpos.x=charx;        charpos.y=0.0f;        charpos.z=charz;                oldcharpos.x=oldx;        oldcharpos.y=0.0f;        oldcharpos.z=oldz;                charmovedist=getDist(&charpos,&oldcharpos);        float tempx=(charx-oldx)/charmovedist, tempz=(charz-oldz)/charmovedist;                if(tempx>1)            tempx=1;        if(tempx<-1)            tempx=-1;        if(tempz>1)            tempz=1;        if(tempz<-1)            tempz=-1;                    newfacing=AngleFromVector(tempz,tempx);        newfacing=(newfacing*57.2957);                newfacing=fmodf(newfacing, 360.0f);         if(newfacing<0)newfacing+=360.0f;                facing=fmodf(facing, 360.0f);         if(facing<0)facing+=360.0f;                if(facing!=newfacing){            if((newfacing-facing)>180){                facing-=ticks*500;            }            else{                facing+=ticks*500;            }        }     }


As you can see, it's certainly a lot cleaner than what I was doing before. However, it's still not doing what I want it to! Now the character mostly rotates in one direction infinitely except when it decides to stick on what seems to be the positive direction on the x and z axis. Obviously I'm doing something stupid that I'm just not seeing.

I'd love to continue trying to fix this problem, but it's 6 am and I think I'm getting too tired to do anything more right now. I'll get some sleep and try again when I'm fresh.

Thanks for your continued help!
Demus79
Demus79
Well the next part would be actually to calculate the direction :)

- you don't need to check the ranges of x and z since unitizing will quarantee them to be in range. I'd be more worried about dividing by zero when unitizing.

- you need to do the fmodf thing only once

- If I understand correctly the code you are always recalculating everything.

Consider this:
void On_Click(x,y){   calculate the direction unit vector;   get the new angle;}void On_Update(){   float angle_difference = new_angle - current_angle;   if(angle_difference > 180)angle_difference = angle_difference - 360;   //angle_difference has now the value which should be added to the    //current_angle in order to reach the destination angle. }


We don't want the current_angle to reach the new_angle directly so we have to do some magic here such as

- multiply the angle_difference by some value like 0.5 so the character will change his/her direction half of the angle difference and eventually he/she'll reach the final angle or an angle close enough to it.

or

- use a constant step such as :

if(angle_difference > 0)current_angle -= 1;
if(angle_difference < 0)current_angle += 1;

or

something else

Hope this helps you to reach your goal

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.