Damped spring effects for camera

Started by
9 comments, last by deavik 18 years, 9 months ago
Hi I am trying a 3d driving sim. I have got the camera and stuff all set, and I am looking to give a 'dampened spring' effect to the camera. Here is the snap_car function which sets the camera to folow the car. It is called every frame. The part of interest is below the // try damping comment. cam.pos_t is the target position vector. I am trying to make the camera move a fraction of the distance it should to be at the target position (cam.pos_v is the position vector of the camera). The thing is that it seems like it is working, but as soon as the car stops moving, so does the camera. I mean, since this function is called each frame, and _offset (the modulus of the offset between target and current positions) is not 0 (I checked), the camera should keep moving until it is at the target position, right?

void snap_car (camera &cam, car_instance car)
{
   vector_t _vec1, _vec2, _offsetv;
   // this is how far behind the camera is
   float _dist = cam.profile.chasedist * cosine(90.0f - cam.profile.recline);   
   // get the camera behind in the right place on x-y plane
   _vec2 = car.pos_v - (car.dir_v * _dist); // behind car
   // add the height
   _vec2.k_cap += cam.profile.height;
   
   cam.pos_t = _vec2;
   
   // try damping
   float _offset = vector_mod(cam.pos_t - cam.pos_v);
   _offsetv = cam.pos_t - cam.pos_v;
   cam.pos_v = cam.pos_v + (_offsetv*0.7f);
     
   // dir_v of cam = pos_v of car - pos_v of cam - math does come in handy ;)
   cam.dir_v = return_unit(car.pos_v - cam.pos_v);
   
   cam.snapped = true;
}

Any help regarding my code, (or maybe code or links to tutorials for emulating dampened spring effects by other ways) would be much appreciated, thanks!
Advertisement
GPG4 has an article on using critically damped springs. Personally, here is the function I came up with:

// use a damped spring to move v0 towards target given a current velocity, // time over which the spring would cover 90% of the distance from rest; // and dt, the change in time.template<typename T>inline void damp_spring(T& v0, const T& target, T& vel, float time_90, float dt){	const float c0 = dt * 3.75f / time_90;	if(c0 >= 1.0f)	{		// here, constant is too small, spring too stiff.  so go the whole way to prevent oscillation.		v0 = target;		vel = T(0.0f);		return;	}	const T delta = target - v0;	const T force = delta - 2.0f * vel;	v0 += vel * c0;	vel += force * c0;}


Try it. It should be fairly easy to convert. So, basically, your camera object keeps track of its position and velocity. It figures out its ideal position (target) each frame, and you feed all that in along with the timestep and damping factor (represented as the amount of time it should take v0 to reach 90% of the distance toward target, which is generally a more useful way to conceptualize a damping constant). Note that the input velocity and position are modified as non-const references.

So anyway, it's really sweet and simple. And it works for tons of things besides just camera work. I wrote another version that critically damps the camera's orientation as well if you need something like that.
Sorry for the humungous delay, I had MAJOR computer troubles. Thanks for your reply, ajas. I couldn't follow what you meant by the timestep factor or how to calculate the timestep. That is my foremost problem. How could I do that on a SDL platform? If you could just explain a bit further ... with simpler code, maybe? I have put up a request for an article here because I think this topic deserves a pretty large treatment for blockheads like me.
The timestep factor is just your frame rate. So if you have 60 fps, then your timestep is (1.0f/60.0f == 0.01667f) seconds per frame. That's easy enough, right? Then the damping factor is the amount of time to reach 90% of the distance to its target position.

So, if you used:
timestep = 0.02f (50 fps)
time_90 = 0.2f (1/5 of a second)

Then the spring would move the camera to its target in an infinitely smooth manner over the course of approximately 11 frames. And if you set the target position to a different place before it gets there, the equation takes the current velocity of the camera into account and keeps the movement infinitely smooth as it starts moving toward the new location.
Hi

I just stumbled across this in the forums - it might help stop the occilations in my Rock, Paper, Scissors game!

Can you explain the 3.75 please? I don't like hard-coded constants in my code you see, I like to make them parameters :)

Thanks!
ajas, I just implemented your code and it is ruddy brilliant. I'll give you the highest rating I can, just for your code! But at the same time there are a couple of things which I'm not clear about, and I don't believe in copying and pasting code, so I want to ask you about those:

a) Why are the 3.75f, 2.0f factors? And I know this is going to make you curse me, but I still don't really get what time_90 is and how we are getting the value.

b) The action seemed a little jerky at the start with the constants as they were in your code. I saw that increasing time_90 and / or decreasing te 3.75f make the action smoother. But then the camera lags too much. What would I do to decrease the lag?

c) I understand the mechanics behind most of the code, just this line:
force = delta - 2.0f * vel;
Why is force = delta - vel*2.0f?

Once again I can't thank you enough for your help, because this particular topic is very sparsely documented over the web and where it is it's extremely complicated.

DrGUI, I am curious as to what this sort of thing is doing in a rock paper scissors game? :)
Quote:And I know this is going to make you curse me, but I still don't really get what time_90 is and how we are getting the value.


Curse you! time_90 means... okay say you have the camera at (0,0,0) and you set the target as (0,0,10). If you set time_90 to 1.0, that means it will take 1.0 seconds for the camera to cover 90% of the distance, in other words: to get to (0,0,9). Of course, once it's gotten that far, it will take it another 1.0 seconds to get 90% of the remaining distance, so at the end of 2.0 seconds it will be at (0, 0, 9.9), after 3.0 seconds it'll be at (0, 0, 9.99), and so on. It's like xeno's paradox.

So, you can see why increasing time_90 makes the camera's motion gentler. I generally use values between 0.2 and 0.5

3.75 ... now that's a different story. That number is utter magic. Basically, I came up with this equation in Excel. I knew the parameters I wanted to pass in and the behavior I wanted and I kept trying various permutations of expressions until the curve fit what I wanted. Then I decided that the easiest way to define the spring/damping constant was based on how long it took to resolve 90% of the distance to target. It happened that 3.75 just universally worked for a whole wide range of inputs. 2.0 is similar, in that it happens to be the smallest number you can use without inducing oscillation.

I'm sure there are real physical meanings to these values... I just don't know what they are! Here is the damped orientation I was talking about earlier. It's a total hack. Essentially, (assuming the current facing of the camera is its z-axis) it uses the damped spring to interpolate the current z-axis of the camera toward some input target z-axis, then rebuilds the orientation matrix.

void Frame::spring_facing(float time_90, float dt){	vec4 z(m_ornt.z_axis());	damp_spring<vec4>(z, m_target_z, m_zvel, time_90, dt);		// now make sure that normalizing z preserves the y coord.	// linearly interpolating the z-axis is an approximation, 	// and the y distortion is most noticeable.	const float D = 1.0f - z.y * z.y;	const float L = z.x * z.x + z.z * z.z;	const float mult = sqrtf(D / L);	z.x *= mult;	z.z *= mult;	m_ornt.face(z);}


If you get that working, you'll have a camera damped in both orientation and translation, at which point you will officially be 1787569
Your spring worked wonderfully for my hands (in Rock Paper Scissors)! Occillation is fine for my camera (which floats randomly around an anchor) but it looks horrible when the game objects are bouncing around. Thank you!
I looked in my GPG4 book last night though but didn't find anything in the contents on springs. Which chapter?

deavik, I use the springs as anchors for when the hands and faces are waiting for the user to make their choice.
Confusing? Faces? You can all have a look at my blog if you're interested. *Crosses fingers and hopes that people are* It doesn't have any screenshots up and the moment but...perhaps..you..might..add it to your favorites?

I'd be interested to see your projects too!

*Sees to ratings...*

EDIT: ooo and since you seem to be an expert on occillation can you take a look at this for me please?
Cheers
ajas, I was very saddenned by you actually deciding to curse me ("No one loves me ... Boo hoo!"), but at the same time I'm really sorry that I'm driving you to the point of madness. If you really cared about your rating, maybe I could bribe you with that but I have a feeling that isn't going to work.

Thanks for your response: I know 1 thing now, 2.0 is the best value in place of itself. But haven't you ever felt that the camera was a bit jerky? Did you find a nice permutaion to remedy that? (part (b) of the list of questions in my previous post)

PS. About the bribing thing, if I ever make a really cool game; your name will be the first on the credits list. If I sell it you get 10 percent. What d'you think of that?
DrGUI, I'm almost positive I saw something like this in GPG4 (maybe GPG3?). Except it was more complicated and I don't recall any additional benefits (though probably a much better explanation than I've come up with :)

Also, glancing at your code, I see what you're trying to do but that is an uphill battle! First, you're not treating your current angular velocity properly outside the plane defined by axisOfRotation. So you might accelerate toward your target orientation, but you're not properly negating the velocity component projected onto axisOfRotation. In fact, I don't think there's a simple way to do it correctly. I computed it once using quaternions and it was very expensive (like 3 slerps and some more tricky stuff).

So I highly recommend that you immediately implement the orientation code I just posted, and forget about the problem forever! Actually, it's a very interesting problem. You should consider it very carefully along with what my code is trying to accomplish, see what my solution's weakness is and hopefully improve it for me :)

Deavik, do I think it is too jerky? No... are you sure that your velocity is initialized properly? (recall that your original problem was that your velocity was discontinuous... if you bring your camera to a stop manually are you setting the velocity to 0.0 also?)

Increasing time_90 and decreasing 3.75 have the same effect. Note that they're combined into a constant in the first step and never used again. Remember that you don't have to pass the same time_90 value every time... but before I went fiddling with that I would eliminate any possibility of a velocity setting bug which might contribute to the jerkiness.

Bribing me with rating? No way! Actually, my goal is to get to 1337 and stay there forever :) 10% is very generous... I would probably settle for 5. But hey, 10 it is.

No seriously, I have very strong feelings about this stuff. I feel like people waste so much time on writing crappy camera code and player control code, and I personally have wasted a ton of time on this, and it's a solved problem! Ever since Jak & Daxter, 5 years ago, which seemed to use damped springs for everything, and there are professionals that still don't get it! Anyway, improve it, share it with your friends, and let's all move on to the interesting stuff, like pathfinding a camera through a 3D environment (up next: k-d trees).

This topic is closed to new replies.

Advertisement