You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
348 lines
9.5 KiB
348 lines
9.5 KiB
14 years ago
|
/*==LICENSE==*
|
||
|
|
||
|
CyanWorlds.com Engine - MMOG client, server and tools
|
||
|
Copyright (C) 2011 Cyan Worlds, Inc.
|
||
|
|
||
|
This program is free software: you can redistribute it and/or modify
|
||
|
it under the terms of the GNU General Public License as published by
|
||
|
the Free Software Foundation, either version 3 of the License, or
|
||
|
(at your option) any later version.
|
||
|
|
||
|
This program is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
||
|
or by snail mail at:
|
||
|
Cyan Worlds, Inc.
|
||
|
14617 N Newport Hwy
|
||
|
Mead, WA 99021
|
||
|
|
||
|
*==LICENSE==*/
|
||
|
#include "plAnimEaseTypes.h"
|
||
|
#include "plAnimTimeConvert.h"
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
plATCEaseCurve *plATCEaseCurve::CreateEaseCurve(UInt8 type, hsScalar minLength, hsScalar maxLength, hsScalar length,
|
||
|
hsScalar startSpeed, hsScalar goalSpeed)
|
||
|
{
|
||
|
if (type == plAnimEaseTypes::kConstAccel)
|
||
|
return TRACKED_NEW plConstAccelEaseCurve(minLength, maxLength, length, startSpeed, goalSpeed);
|
||
|
if (type == plAnimEaseTypes::kSpline)
|
||
|
return TRACKED_NEW plSplineEaseCurve(minLength, maxLength, length, startSpeed, goalSpeed);
|
||
|
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
void plATCEaseCurve::RecalcToSpeed(hsScalar startSpeed, hsScalar goalSpeed, hsBool preserveRate /* = false */)
|
||
|
{
|
||
|
hsScalar rate = 1;
|
||
|
|
||
|
if (fSpeed == goalSpeed && fStartSpeed == startSpeed) // already there, no need to do anything
|
||
|
return;
|
||
|
|
||
|
if (preserveRate)
|
||
|
rate = (fSpeed - fStartSpeed) / fLength;
|
||
|
|
||
|
fStartSpeed = startSpeed;
|
||
|
fSpeed = goalSpeed;
|
||
|
|
||
|
if (preserveRate)
|
||
|
SetLengthOnRate(rate);
|
||
|
}
|
||
|
|
||
|
void plATCEaseCurve::SetLengthOnRate(hsScalar rate)
|
||
|
{
|
||
|
fLength = (fSpeed - fStartSpeed) / rate;
|
||
|
if (fLength < 0)
|
||
|
fLength = -fLength;
|
||
|
}
|
||
|
|
||
|
hsScalar plATCEaseCurve::GetMinDistance()
|
||
|
{
|
||
|
if (fMinLength == 0)
|
||
|
return 0;
|
||
|
|
||
|
hsScalar oldLength = fLength;
|
||
|
fLength = fMinLength;
|
||
|
hsScalar result = PositionGivenTime(fMinLength);
|
||
|
fLength = oldLength;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
hsScalar plATCEaseCurve::GetMaxDistance()
|
||
|
{
|
||
|
if (fMaxLength == 0)
|
||
|
return 0;
|
||
|
|
||
|
hsScalar oldLength = fLength;
|
||
|
fLength = fMaxLength;
|
||
|
hsScalar result = PositionGivenTime(fMaxLength);
|
||
|
fLength = oldLength;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
hsScalar plATCEaseCurve::GetNormDistance()
|
||
|
{
|
||
|
if (fNormLength == 0)
|
||
|
return 0;
|
||
|
|
||
|
hsScalar oldLength = fLength;
|
||
|
fLength = fNormLength;
|
||
|
hsScalar result = PositionGivenTime(fNormLength);
|
||
|
fLength = oldLength;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
void plATCEaseCurve::Read(hsStream *s, hsResMgr *mgr)
|
||
|
{
|
||
|
plCreatable::Read(s, mgr);
|
||
|
|
||
|
fMinLength = s->ReadSwapScalar();
|
||
|
fMaxLength = s->ReadSwapScalar();
|
||
|
fNormLength = fLength = s->ReadSwapScalar();
|
||
|
fStartSpeed = s->ReadSwapScalar();
|
||
|
fSpeed = s->ReadSwapScalar();
|
||
|
fBeginWorldTime = s->ReadSwapDouble();
|
||
|
}
|
||
|
|
||
|
void plATCEaseCurve::Write(hsStream *s, hsResMgr *mgr)
|
||
|
{
|
||
|
plCreatable::Write(s, mgr);
|
||
|
|
||
|
s->WriteSwapScalar(fMinLength);
|
||
|
s->WriteSwapScalar(fMaxLength);
|
||
|
s->WriteSwapScalar(fNormLength);
|
||
|
s->WriteSwapScalar(fStartSpeed);
|
||
|
s->WriteSwapScalar(fSpeed);
|
||
|
s->WriteSwapDouble(fBeginWorldTime);
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
plConstAccelEaseCurve::plConstAccelEaseCurve()
|
||
|
{
|
||
|
fMinLength = fMaxLength = fNormLength = fLength = 1;
|
||
|
fBeginWorldTime = 0;
|
||
|
|
||
|
RecalcToSpeed(0, 1);
|
||
|
}
|
||
|
|
||
|
plConstAccelEaseCurve::plConstAccelEaseCurve(hsScalar minLength, hsScalar maxLength, hsScalar length,
|
||
|
hsScalar startSpeed, hsScalar goalSpeed)
|
||
|
{
|
||
|
fMinLength = minLength;
|
||
|
fMaxLength = maxLength;
|
||
|
fNormLength = fLength = length;
|
||
|
fBeginWorldTime = 0;
|
||
|
|
||
|
RecalcToSpeed(startSpeed, goalSpeed);
|
||
|
}
|
||
|
|
||
|
plATCEaseCurve *plConstAccelEaseCurve::Clone() const
|
||
|
{
|
||
|
plConstAccelEaseCurve *curve = TRACKED_NEW plConstAccelEaseCurve;
|
||
|
curve->fStartSpeed = fStartSpeed;
|
||
|
curve->fMinLength = fMinLength;
|
||
|
curve->fMaxLength = fMaxLength;
|
||
|
curve->fNormLength = fNormLength;
|
||
|
curve->fBeginWorldTime = fBeginWorldTime;
|
||
|
curve->fLength = fLength;
|
||
|
curve->fSpeed = fSpeed;
|
||
|
|
||
|
return curve;
|
||
|
}
|
||
|
|
||
|
void plConstAccelEaseCurve::SetLengthOnDistance(hsScalar dist)
|
||
|
{
|
||
|
fLength = 2 * dist / (fSpeed + fStartSpeed);
|
||
|
}
|
||
|
|
||
|
hsScalar plConstAccelEaseCurve::PositionGivenTime(hsScalar time) const
|
||
|
{
|
||
|
return (hsScalar)(fStartSpeed * time + (0.5 * (fSpeed - fStartSpeed) / fLength) * time * time);
|
||
|
}
|
||
|
|
||
|
hsScalar plConstAccelEaseCurve::VelocityGivenTime(hsScalar time) const
|
||
|
{
|
||
|
return fStartSpeed + ((fSpeed - fStartSpeed) / fLength) * time;
|
||
|
}
|
||
|
|
||
|
hsScalar plConstAccelEaseCurve::TimeGivenVelocity(hsScalar velocity) const
|
||
|
{
|
||
|
return (velocity - fStartSpeed) / ((fSpeed - fStartSpeed) / fLength);
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
plSplineEaseCurve::plSplineEaseCurve()
|
||
|
{
|
||
|
fMinLength = fMaxLength = fNormLength = fLength = 1;
|
||
|
fBeginWorldTime = 0;
|
||
|
|
||
|
RecalcToSpeed(0, 1);
|
||
|
}
|
||
|
|
||
|
plSplineEaseCurve::plSplineEaseCurve(hsScalar minLength, hsScalar maxLength, hsScalar length,
|
||
|
hsScalar startSpeed, hsScalar goalSpeed)
|
||
|
{
|
||
|
fMinLength = minLength;
|
||
|
fMaxLength = maxLength;
|
||
|
fNormLength = fLength = length;
|
||
|
fBeginWorldTime = 0;
|
||
|
|
||
|
RecalcToSpeed(startSpeed, goalSpeed);
|
||
|
}
|
||
|
|
||
|
plATCEaseCurve *plSplineEaseCurve::Clone() const
|
||
|
{
|
||
|
plSplineEaseCurve *curve = TRACKED_NEW plSplineEaseCurve;
|
||
|
curve->fStartSpeed = fStartSpeed;
|
||
|
curve->fMinLength = fMinLength;
|
||
|
curve->fMaxLength = fMaxLength;
|
||
|
curve->fNormLength = fNormLength;
|
||
|
curve->fBeginWorldTime = fBeginWorldTime;
|
||
|
curve->fLength = fLength;
|
||
|
curve->fSpeed = fSpeed;
|
||
|
|
||
|
int i;
|
||
|
for (i = 0; i < 4; i++)
|
||
|
curve->fCoef[i] = fCoef[i];
|
||
|
|
||
|
return curve;
|
||
|
}
|
||
|
|
||
|
void plSplineEaseCurve::RecalcToSpeed(hsScalar startSpeed, hsScalar goalSpeed, hsBool preserveRate /* = false */)
|
||
|
{
|
||
|
plATCEaseCurve::RecalcToSpeed(startSpeed, goalSpeed, preserveRate);
|
||
|
|
||
|
// These are greatly simplified because the in/out tangents are always zero
|
||
|
// Note: "b" is always zero for the ease splines we're currently doing (and will remain that way
|
||
|
// so long as the initial acceleration is zero. Can optimize a bit of the eval math to take
|
||
|
// advantage of this.
|
||
|
hsScalar a, b, c, d;
|
||
|
|
||
|
a = fStartSpeed;
|
||
|
b = 0;
|
||
|
c = -3 * fStartSpeed + 3 * fSpeed;
|
||
|
d = 2 * fStartSpeed - 2 * fSpeed;
|
||
|
|
||
|
fCoef[0] = a;
|
||
|
fCoef[1] = b;
|
||
|
fCoef[2] = c;
|
||
|
fCoef[3] = d;
|
||
|
}
|
||
|
|
||
|
void plSplineEaseCurve::SetLengthOnDistance(hsScalar dist)
|
||
|
{
|
||
|
hsScalar curDist = PositionGivenTime(fLength);
|
||
|
|
||
|
fLength = fLength * dist / curDist;
|
||
|
}
|
||
|
|
||
|
hsScalar plSplineEaseCurve::PositionGivenTime(hsScalar time) const
|
||
|
{
|
||
|
hsScalar t1, t2, t3, t4;
|
||
|
t1 = time / fLength;
|
||
|
t2 = t1 * t1;
|
||
|
t3 = t2 * t1;
|
||
|
t4 = t3 * t1;
|
||
|
|
||
|
return fLength * (fCoef[0] * t1 + fCoef[1] * t2 / 2 + fCoef[2] * t3 / 3 + fCoef[3] * t4 / 4);
|
||
|
}
|
||
|
|
||
|
hsScalar plSplineEaseCurve::VelocityGivenTime(hsScalar time) const
|
||
|
{
|
||
|
hsScalar t1, t2, t3;
|
||
|
t1 = time / fLength;
|
||
|
t2 = t1 * t1;
|
||
|
t3 = t2 * t1;
|
||
|
return fCoef[0] + fCoef[1] * t1 + fCoef[2] * t2 + fCoef[3] * t3;
|
||
|
}
|
||
|
|
||
|
hsScalar plSplineEaseCurve::TimeGivenVelocity(hsScalar velocity) const
|
||
|
{
|
||
|
// Code based off of Graphics Gems V, pp 11-12 and
|
||
|
// http://www.worldserver.com/turk/opensource/FindCubicRoots.c.txt
|
||
|
|
||
|
// Solving the equation: fCoef[0] + fCoef[1] * t + fCoef[2] * t^2 + fCoef[3] * t^3 - velocity = 0
|
||
|
|
||
|
hsScalar root;
|
||
|
hsScalar a = (fCoef[0] - velocity) / fCoef[3];
|
||
|
hsScalar b = fCoef[1] / fCoef[3];
|
||
|
hsScalar c = fCoef[2] / fCoef[3];
|
||
|
|
||
|
hsScalar Q = (c * c - 3 * b) / 9;
|
||
|
hsScalar R = (2 * c * c * c - 9 * c * b + 27 * a) / 54;
|
||
|
hsScalar Q3 = Q * Q * Q;
|
||
|
hsScalar D = Q3 - R * R;
|
||
|
|
||
|
if (D >= 0)
|
||
|
{
|
||
|
// 3 roots, find the one in the range [0, 1]
|
||
|
const hsScalar pi = 3.14159;
|
||
|
double theta = acos(R / sqrt(Q3));
|
||
|
double sqrtQ = sqrt(Q);
|
||
|
|
||
|
root = (hsScalar)(-2 * sqrtQ * cos((theta + 4 * pi) / 3) - c / 3); // Middle root, most likely to match
|
||
|
if (root < 0.f || root > 1.f)
|
||
|
{
|
||
|
root = (hsScalar)(-2 * sqrtQ * cos((theta + 2 * pi) / 3) - c / 3); // Lower root
|
||
|
if (root < 0.f || root > 1.f)
|
||
|
{
|
||
|
root = (hsScalar)(-2 * sqrtQ * cos(theta / 3) - c / 3); // Upper root
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else // One root to the equation (I don't expect this to happen for ease splines, but JIC)
|
||
|
{
|
||
|
double E = sqrt(-D) + pow(fabs(R), 1.f / 3.f);
|
||
|
root = (hsScalar)((E + Q / E) - c / 3);
|
||
|
if (R > 0)
|
||
|
root = -root;
|
||
|
}
|
||
|
|
||
|
if (root < 0.f || root > 1.f)
|
||
|
{
|
||
|
hsAssert(false, "No valid root found while solving animation spline");
|
||
|
// Either a bug, or a rare case of floating-point inaccuracy. Either way, guess
|
||
|
// the proper root as either the start or end of the curve based on the velocity.
|
||
|
hsScalar dStart = velocity - fStartSpeed;
|
||
|
if (dStart < 0)
|
||
|
dStart = -dStart;
|
||
|
hsScalar dEnd = velocity - fSpeed;
|
||
|
if (dEnd < 0)
|
||
|
dEnd = -dEnd;
|
||
|
|
||
|
root = (dStart < dEnd ? 0.f : 1.f);
|
||
|
}
|
||
|
|
||
|
return root * fLength;
|
||
|
}
|
||
|
|
||
|
void plSplineEaseCurve::Read(hsStream *s, hsResMgr *mgr)
|
||
|
{
|
||
|
plATCEaseCurve::Read(s, mgr);
|
||
|
|
||
|
fCoef[0] = s->ReadSwapScalar();
|
||
|
fCoef[1] = s->ReadSwapScalar();
|
||
|
fCoef[2] = s->ReadSwapScalar();
|
||
|
fCoef[3] = s->ReadSwapScalar();
|
||
|
}
|
||
|
|
||
|
void plSplineEaseCurve::Write(hsStream *s, hsResMgr *mgr)
|
||
|
{
|
||
|
plATCEaseCurve::Write(s, mgr);
|
||
|
|
||
|
s->WriteSwapScalar(fCoef[0]);
|
||
|
s->WriteSwapScalar(fCoef[1]);
|
||
|
s->WriteSwapScalar(fCoef[2]);
|
||
|
s->WriteSwapScalar(fCoef[3]);
|
||
|
}
|
||
|
|