Project P1a was meant as an exercise on how to visualize real world motion concepts such as acceleration, movement, stopping, etc..
and then how one would go about shaping the curves to define that motion. The basics of any animation starts with motion, for without motion
you don't have an animation at all --just a static image. It also will help us in creating any motion functions need for further portions
of the accident re-creation project.
Jarek's hint tells us that the acceleration/deceleration to create this graph is constant.
Acceleration is the 2nd derivative of the function we are seeking.
Integrating twice gets us the function we need.
f''(t) = 1 f''(t) = -1
f'(t) = x f'(t) = -x
f(t) = x^2/2 f(t) = -x^2/2
The [0,0.5] f(t) must be scaled to fit the desired range; this is a simple multiply.
The [0.5,1] f(t) has it's input t manipulated to vary the t parameter from 0.5 down to 0.
The resultant curve must be scaled (same as before) and translated (add 1).
float f2(float t) {
if( t <= 0.5 ) {
f2Pos = t*t*f2expand; // varies 0.0 --> 0.5
}
else
{
t = 1 - t; // varies 0.5 --> 0.0
f2Pos = -t*t*f2expand; // p(t) * expand ... varies -0.5 --> 0.0
f2Pos = f2Pos + 1.0; // shift the curve over
}
return f2Pos;
}
|
From Jarek's hint, we know the graph of the *acceleration* (2nd derivative) looks like the [0,pi] range of the cosine graph.
So take integrals to get us back to f(t).
f''(t) = cos(t)
f'(t) = sin(t)
f(t) = -cos(t)
So, the [0,pi] domain of f(t) above is the one we need, but it needs to be squished into range [0,1].
It's current range is [-1,1]. To do that we add 1 (making the range [0,2]), and then divide the range by two.
So, f3 in it's simple, yet strangely painful(!), glory becomes...
float f3(float t) {
f3Pos = -cos(pi*t); // f(t) ... range [-1,1]
f3Pos += 1.0; // shift range up [0,2]
f3Pos /= 2.0; // squish range [0,1]
return f3Pos;
}
|
To get this one we looked at the cosine^2 function.
The section of the curve that had similar properties to what we wanted was the range [pi/2,pi]
(a long time getting away from zero and a capping out at 1). After comparing this to Jarek's picture,
it was obvious that the car was not getting a slow enough start. To create this, we simply squared the
function again (cos^4).
float f4(float t) {
f4Pos = cos(pi/2 + pi/2*t); // varies 0.0 --> -1.0
f4Pos = f4Pos * f4Pos; // squared
f4Pos *= f4Pos; // to the 4th
return f4Pos;
}
|
To create this curve we defined a cubic bezier curve as discussed in class.
We created the "hull" ABDE (actually linear here so hull may be a misnomer). A and E were fixed at 0 & 1, respectively.
That left just B and D to play with to get the correct curve shape. B needed to be negative to get the
"wind-up" curve shape and D fairly close to, but greater than 1, in order to get the desired steepness at
the end of the curve. The values that we settled on were -0.3 and 1.1, respectively.
To find points on the curve we used the De Casteljau construction technique. In the code, the "slide"
function and slide_vals array (calculation workspace) implement this.
float ptA = 0.0;
float ptB = -0.3; // value picked to match Jarek's curve.
float ptD = 1.1; // value picked to match Jarek's curve.
float ptE = 1.0;
float slide_vals[] = new float[3]; // workspace during computation
float f5(float t) {
slide_vals[0] = slide(ptA,ptB,t);
slide_vals[1] = slide(ptB,ptD,t);
slide_vals[2] = slide(ptD,ptE,t);
slide_vals[0] = slide(slide_vals[0], slide_vals[1],t);
slide_vals[1] = slide(slide_vals[1], slide_vals[2],t);
f5Pos = slide(slide_vals[0], slide_vals[1],t);
return(f5Pos);
}
/**
* finds the position along the vector AB indicated by param
**/
float slide(float a, float b, float param) {
return (b-a)*param + a;
}
|
The hint suggested a cubic so we started by looking at t^3.
It is easy to see that this is the right curve shape, but we needed it in a different orientation.
To achieve this we made the t parameter run from 1 down to 0 instead of 0 to 1 (via t=1-t). This creates
a curve that goes from 1 down to 0, but which has the correct shape still. Subtracting this curve from 1
gave us the desired f(t).
float f6(float t) {
t = 1-t;
f6Pos = 1 - t*t*t;
return f6Pos;
}
|
For this we just set the central 4 constraints to all have position of 0.5. There are also 2 constraints at the beginning, each set
to position 0.0, and 2 constraints at the end set to position 1.0.
Basically we added the stop_and_go function to pre-load up all of our constraints when the application loads, thus creating the un-smoothed graph at the start.
These constraints are hard coded to always be there.
void stop_and_go() {
V[0]=0; C[0]=true; S[0]=0; // original constraint
V[2]=0; C[2]=true; S[2]=0;
V[20]=0.5; C[20]=true; S[20]=0.5;
V[21]=0.5; C[21]=true; S[21]=0.5;
V[29]=0.5; C[29]=true; S[29]=0.5;
V[30]=0.5; C[30]=true; S[30]=0.5;
V[N-3]=1; C[N-3]=true; S[N-3]=1;
V[N-1]=1; C[N-1]=true; S[N-1]=1; // original constraint
}
|
|