• Advertisement

Archived

This topic is now archived and is closed to further replies.

Jittering quakestyle movement when circle strafing.

This topic is 6442 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi there, I've got a problem with quake/unreal-like movement in my engine. Although the engine is running at 60+ FPS. camera movement when 'circle strafing' jitters too much to my liking. Forward, backward and left/right strafing is smooth. In Quake/Unreal or most other 3d games it is much smoother even at lower framerates. How do they do it. Do they use somekind of filtering or interpolation between frames??? Here is the code that updates the camera object (its not optimized so don't bother about that). The messages it receives come from a DirectInput (i use immidiate mode. i.o.w i get state data from DirectInput not buffered data) wrapper. The variable sys_frameTime holds the time elapsed since the last frame. switch(event->eventType) { case INP_EV_DRAG: if (event->key == STRAFE_LEFT_KEY) { pcamera->worldposition.SetX(pcamera->worldposition.X() + (float)sin((pcamera->heading - 90) * deg2rad) * (STRAFE_VELOCITY * sys_frameTime)); pcamera->worldposition.SetZ(pcamera->worldposition.Z() + (float)cos((pcamera->heading - 90) * deg2rad) * (STRAFE_VELOCITY * sys_frameTime)); } else if (event->key == STRAFE_RIGHT_KEY) { pcamera->worldposition.SetX(pcamera->worldposition.X() + (float)sin((pcamera->heading + 90) * deg2rad) * (STRAFE_VELOCITY * sys_frameTime)); pcamera->worldposition.SetZ(pcamera->worldposition.Z() + (float)cos((pcamera->heading + 90) * deg2rad) * (STRAFE_VELOCITY * sys_frameTime)); } else if (event->key == MOVE_BACKWARD_KEY) { pcamera->worldposition.SetX(pcamera->worldposition.X() + (float)sin(pcamera->heading * deg2rad) * (BACKWARD_VELOCITY * sys_frameTime)); pcamera->worldposition.SetZ(pcamera->worldposition.Z() + (float)cos(pcamera->heading * deg2rad) * (BACKWARD_VELOCITY * sys_frameTime)); } else if (event->key == MOVE_FORWARD_KEY) { pcamera->worldposition.SetX(pcamera->worldposition.X() - (float)sin(pcamera->heading * deg2rad) * (FORWARD_VELOCITY * sys_frameTime)); pcamera->worldposition.SetZ(pcamera->worldposition.Z() - (float)cos(pcamera->heading * deg2rad) * (FORWARD_VELOCITY * sys_frameTime)); } break; case INP_EV_MOUSEMOVE: { pcamera->leftright = pcamera->heading - (event->mouseDeltaX / MOUSE_COMPENSATION); pcamera->updown = pcamera->updown + (event->mouseDeltaY / MOUSE_COMPENSATION); } break; } the main frame function looks like this. void SYS_Frame() { // Update time data updateFrameTimes(); // Take input from user(s). INP_Frame(); // Determine visibilty of objects pworld->determineVisibility(sys_frameId); // Draw the scene. REN_drawFrame(pworld, pcamera, sys_frameId); // Update frame id sys_frameId++; } any suggestion are appreciated. Thanks Erik Labree, the Netherlands Edited by - Erik Labree on 6/25/00 8:16:32 AM

Share this post


Link to post
Share on other sites
Advertisement
Guest Anonymous Poster
Make heading more precise.
Don''t use degrees. Divide the circle to 1000, or more
"degrees". Then heading will be in range [0,999] and
sin( heading * PI / 500 ) will give you requested sine.




Share this post


Link to post
Share on other sites
Erik, if pcamera->heading is a FLOAT, then multiplying it by some constant like 1000 won''t increase precision.

You mentioned interpolating/filtering the mouse input, which Half-life optionally does (it averages mouse input over the last 2 frames). I was wondering about why they did that, and here''s what I''m guessing: At high frame-rates, the mouse delta is going to be really small, so small that maybe it starts pushing the limit of its mechanical precision (...Remember that high-rez ''gaming'' mouse released recently?). So I would try averaging the mouse input over the last coupla'' frames.

Share this post


Link to post
Share on other sites
The jittering only happens when you''re sidestepping and turning at the same time? Maybe it comes from recalculating the view once for the strafe, and then calculating a new angle for the rotation and calculating the view, which could make the display look like step-turn-step-turn-step-turn really fast, instead of step&turn,step&turn,step&turn. Does each frame take into account both the movement and the rotation since the last frame?

Hmm. From looking at the code, it does... What triggers a new frame? Does it just draw as fast as it can?

Share this post


Link to post
Share on other sites
Yo, try taking the else''s off your if''s and tell me what happeneds(just a thought).

Share this post


Link to post
Share on other sites
Thanks everyone for the replies.

Anonymous, Tried it but it didn''t work out.

Eric, I had already tried that and it didn''t solve the problems. I even tried to interpolate over the last 10 frames wich of course gave very bad respons for mousemovments.

Chiroptera, Yes right that''s what you call circle strafing. You take a step to the left or right and turn you body at the same time and that''s where the problem is.
Each frame does take into account both the movement and the rotation since the last frame accoording to the time elapsed since the previous frame.
When a frame is finished the program starts with the next one. (See SYS_Frame())

Esap1, The elses are there to make sure when an if evaluates to TRUE it won''t keep on evaluating the remaining if''s wich of course would be redundant.

...another thing, my framerate was limited to 60 because of the refreshrate of my hardware. So the program was performing better then what the hardware could display, always hitting 60 FPS. So i increased the resolution of the screen and now the program runs at around 35 FPS. This does seem work a bit better but still not as smooth as Q3, HL or UT. It''s more fluent but sometimes it jitters. It''s just looks like the amount of rotation and movement must be within a certain ratio for a couple of frames to make it smooth.

Gonna play some UT deathmatch with a piece of paper and a pencil and do some drawing. Maybe i''ll see the light...

Frag''em

Erik Labree




Share this post


Link to post
Share on other sites
quote:
Original post by Esap1

Yo, try taking the else's off your if's and tell me what happeneds(just a thought).


I believe this is the problem - I've made this mistake too...
If you're circle strifing you gotta do two of those each frame, otherwise it'll jerk between the two



Edited by - Magmai Kai Holmlor on June 25, 2000 4:50:12 PM

Share this post


Link to post
Share on other sites
Magmai Kai Holmlor,

I did try it and it didn''t solve the problem.
Every possible input event goes trough this piece of code each frame. For instance, when a player is circle strafing, pressing the ''a'' key and moving the mouse along the X axis these input events will get handled by each frame(with or without elses). All key and mouse events are passed trough this code and when something evaluates to TRUE it gets handled.
It''s based on an input system from an article by Chris Hargrove from his excellent Code on the Cob column.

http://www.loonygames.com/content/1.5/cotc/

Thanks Erik.

Share this post


Link to post
Share on other sites
I know what Im talking about, If you are cheking an array of keys, and see'ing if any of them are TRUE, YOU CANNOT USE ELSE's or it will only evaluate 1 movement a frame, K
But you not so just ignore me

Edited by - Esap1 on June 26, 2000 6:55:23 PM

Share this post


Link to post
Share on other sites
Erik , if, let's say, a player is holding down the 'left' key, are you sure that event fires exactly once per frame? If your game loop takes, say, 10 ms and the event were to fire every 9 ms, then you'd get a "jerk" about every 9 frames.

I think checking the key states, like Esap1 mentioned, is a better way to do input for a game.

Edited by - Eric on June 27, 2000 2:48:20 AM

Share this post


Link to post
Share on other sites
Esap1 & Eric,

The piece of code you see is placed in a callback wich is called for every key each frame so every event gets a chance to get handled. So it basically does the same as 'checking an array of keys without elses' It's only a different implementation wich has some advantages as pointed out in the article by Chris Hargrove.
I apoligize, you guys couldn't make that up from the code in the original post. Sorry.
Let me put it differently have you guys got it working smoothly with an 'array of keys' ? If so i'll rewrite the code to do so too.

Regards Erik

Edited by - Erik Labree on June 27, 2000 7:06:04 AM

Edited by - Erik Labree on June 27, 2000 7:07:29 AM

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Not really experienced with this sort of stuff, but I''ll throw in my views anyway...

From what I see, you have a loop like:
   Check for ''Up / Down / Strafe'' movement; Move the camera appropriately.
Check for ''Mouse Look'' movement; Move the camera appripriately.


Right? If you just turn around, or simply head forward, you''re moving the camera once. The scene is updated, and you''ve got your smooth screen movement.

Ok, so with each loop you circle-strafe instead, you''re moving the camera twice. Once for the Strafe, and once for the Mouse Look... You''re jittering the camera a bit then, aren''t you? Within a few moments, you''re repositioning the camera.

The solution would, of course, (if I''m right) be:
   Check for ''Up / Down / Strave movement; Record what should be the camera movement.
Check for ''Mouse Look'' movement; Record what should be the camera movement.
Move the camera using the variables we''ve recorded.


With any luck, this might help, or at least put you on track... Hope I''ve been useful. =)

ScriptKeeper.
scriptkeeper@www.com

Share this post


Link to post
Share on other sites
Hi, I would try making a seperate case that''s handled for circle strafe. Then when this happens smoothly rotate your guy around
a point a few feet in front of his body. Try something like this


you.z = focus_position.z +
(distance_to_focus * cos(angle_to_rotate) );
you.x = focus_position.x +
(distance_to_focus * sin(angle) );


I

Share this post


Link to post
Share on other sites
Guest Anonymous Poster

Erik, try this :

float dx ;
static float old_dx = 0 ;

float dalpha ;
static float old_dalpha = 0 ;

dx = ( ( float ) mouseDeltaX + old_dx ) / 2.0 ;
old_dx = dx ; //or old_dx=mouseDeltaX, choose what looks better
to you
//dx is smoother, mouseDeltaX gives better mouse
response

dalpha = ( dx / MOUSE_COMPENSATION + old_dalpha ) / 2.0 ;
old_dalpha = dalpha ;

//strafe( STRAFE )
//rotate( dalpha )

...or if it''s still not smooth enough try this :


float dx ;
static float old_dx = 0, old2_dx = 0 ;

float dalpha ;
static float old_dalpha = 0, old2_dalpha = 0 ;

dx = ( ( float ) mouseDeltaX + old_dx + old2_dx ) / 3.0 ;
old2_dx = old_dx ;
old_dx = mouseDeltaX ; //better, because more you''re averaging,
poorer response you get
//mouseDeltaX instead of dx will
compensate that a bit

dalpha = ( dx / MOUSE_COMPENSATION + old_dalpha +
old2_dalpha ) / 3.0 ;
old2_dalpha = old_dalpha ;
old_dalpha = dalpha ;

//strafe( STRAFE )
//rotate( dalpha )



Instead of :

//strafe( STRAFE )
//rotate( dalpha )

...you could also :

//rotate( dalpha / 2 )
//strafe( STRAFE )
//rotate( dalpha / 2 )

(rotate/2, strafe, rotate/2) is something in between
of (rotate,strafe) and (strafe,rotate).

Try to draw these 3 cases on paper with dalpha = 45 degrees.
You will see what I mean.
It''s some kind of aproximation of rotating and moving at
the same time.

Let me know if this helped !

Bojan Urosevic (aka Coyote)
coyotelb@yahoo.com

Share this post


Link to post
Share on other sites
Bojan,

Interesting ideas. Especially case 3. Work is bit hectic so i''ll have to wait till the weekend to try''m out. I''ll let you know how it went.

Thanks Erik.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
A little mathematical supplemental about moving and rotating
at the same time. (by Coyote)

Note: All of this is almost academic, because it is hard to
notice differences in a fast-paced real-time motion.
Although, it''s interesting.

!! ALMOST EVERY VARIABLE IS FLOAT, ANGLES ARE IN RADIANS !!

To be honest, (rotate/2, strafe, rotate/2) (read my previous post)
was taken from a small sketch on paper. It seemed much more accurate
position after rotating and moving then (rotate, strafe) or
(strafe, rotate), especially for big angles.

If you try to draw several (r,s) for some large angle, you will
see that points that this motion produces are on displaced circle.
Also, when strafing left and rotating right (Erik called this
circular strafe) point of view moves around a circle, and it shoudn''t.

Several (r/2,s,r/2), on the other hand, makes the point of view
stationary, and removes displacement of motion circle.

I will demonstrate 2 ways of finding accurate position after strafing
and rotating at the same time.

First way is, let''s say, programmatic. It''s dividing the strafe and angle
into N parts, and then doing it N times:

//angle is amount of turn, relative to the heading
//for the simplicity:
// strafe is amount of move in the heading direction (forward)
// let everything happens in xy coordinate system
// let the heading be 0 and let that means the x-axis direction
// let the old position is at origin (0,0)

new_x = new_y = 0 ;
for( i = 1 ; i < N ; i++ ) {
new_x = new_x + ( strafe / N ) * cos( i * angle / N ) ;
new_y = new_y + ( strafe / N ) * sin( i * angle / N ) ;
}

Note that this rotates than moves because of i*angle/N.
Other way is (i-1)*angle/N which yields move followed by a rotation.
If N is great both converges to the same.

And to what does this remind you to ?
Yes, when N->infinity, it can be (mathematically) written as:

new_x = strafe * integral(0,1)cos(angle*x)dx
new_y = strafe * integral(0,1)sin(angle*x)dx

(note that x under the integral have no corespondence to position of
any kind, it''s simply integration variable, and angle is a constant
in that integration)

When we evaulate this integrals we get the following formulae:

new_x = strafe * sin( angle ) / angle ;
new_y = strafe * ( 1 - cos( angle ) ) / angle ;

This is accurate position after moving and rotating at the same time.
One more thing. What if angle of desired relative rotation is 0 ?
Well it''s pretty easy to show that:

lim(angle->0)sin(angle)/angle = 1
lim(angle->0)(1-cos(angle))/angle = 0

(it''s 0/0 - use the l''Hospitale rule)

So, if abs(angle) is less than, say, 0.0001 we do not calculate the
formulae but instead we put :

new_x = strafe ;
new_y = 0 ;

Note that if angle is 0 and we are heading to positive x direction this
is exactly where we wanna be !

Point (new_x,new_y) is on the line through origin and that line makes
angle/2 angle with the x-axis. It''s easy to show that. With some simple
trigonometry you will be able to show that :

new_y/new_x = tan(angle/2) ;

( 1-cos(a)=2sin^2(a/2), sin(a)=2sin(a/2)cos(a/2) )

Note that (rotate/2,strafe,rotate/2) gives a point also on that line !

For small angles it''s quite a good approximation, but let see if we can
do it better.

Instead of moving along angle/2 line for strafe amount, why wouldn''t we
calculate exact distance that we should pass :

sqrt(new_x^2 + new_y^2) = strafe * 2*sin(angle/2)/angle

( sin^2(a)+cos^2(a)=1, 1-cos(a)=2sin^2(a/2) )
Note that because the sin(angle/2) and angle are of the same sign
this is correct. Beware of sqrt(a^2) !! sqrt(a^2) = /a/, not a !!

Again l''Hospital rule(z) : lim(angle->0)2*sin(angle/2)/angle=1

So we come to the exact position after rotating (for angle) and moving
forward (for strafe) with :

if( fabs( angle ) < 0.0001 ) k = 1 ;
else k = 2 * sin( angle / 2 ) / angle ;

(rotate(angle/2), strafe( k * strafe), rotate(angle/2))

Other way for reaching the sin(a)/a and (1-cos(a))/a is to note that we
want to move around the circle for L=strafe. This leads to r=strafe/a
(L=r*a). And if we draw that circle it''s simple to "read" the formulae
from the picture. (bottom of the circle should be in origin,
center should be somewhere on the positive y-axis (for positive a))

And yes, don''t strafe left and then strafe forward if left and forward
are pressed together ! Combine it to one strafe at 45 degrees angle !
Otherwise your character will move faster diagonally.

!! ANGLES ARE DEGREES !!

float dir[16] = { -1, 270, 90, -1, 180, 225, 135, 180, 0, 315, 45, 0, -1, 270, 90, -1 } ;
//-1 means that no keys are pressed or (left and right) and/or (forward and back) are
//pressed simuntaneously

dir_ind = forward_key_down * 8 + backward_key_down * 4 + left_key_down * 2 + right_key_down ;
if( dir[dir_ind] >= 0 ) {
if( fabs( angle ) < 0.0001 ) k = 1 ;
else k = 2 * sin( ( angle / 2 ) * PI / 180.0 ) / ( angle * PI / 180.0 ) ;
heading += angle / 2 ; //rotating
strafe( heading + dir[dir_ind], k * strafe_velocity ) ;
heading += angle / 2 ;
} else {
heading += angle ;
}

Sorry for such a long post. I hope you enjoyed it.


Bojan Urosevic (aka Coyote)
coyotelb@yahoo.com

P.S.:
If someone would like to convert this into some kind of a tutorial with
pictures and nice formulae, please do so, but don''t forget to credit me !


Share this post


Link to post
Share on other sites
My game is designed a little differently such that I might be insulated from this, I''ll share the method in case someone finds it interesting.

Its a multiplayer 3d shooter:
-Players input is sampled and from that I derive messages that are placed on a global internal queue. The keybindging are translater to "Player X pressed forward", "Player X Fired"
-Network layer grabs the echo''s of other players and places them on the global queue as well.
-A bunch of objects read the queue for messages that relate to their function, like Firing, Movement etc. The world representation of the players are modified the same for all players.

The reason I think telling you this might help is that in my game all input is treated equally and processed independantly each frame. So strafing while turning will just produce two independant events that will both modify movement.

As a side note this method is extremely network friendly.
HTH

gimp

Share this post


Link to post
Share on other sites

  • Advertisement