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.
363 lines
10 KiB
363 lines
10 KiB
/*==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/>. |
|
|
|
Additional permissions under GNU GPL version 3 section 7 |
|
|
|
If you modify this Program, or any covered work, by linking or |
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, |
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent |
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK |
|
(or a modified version of those libraries), |
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, |
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG |
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the |
|
licensors of this Program grant you additional |
|
permission to convey the resulting work. Corresponding Source for a |
|
non-source form of such a combination shall include the source code for |
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered |
|
work. |
|
|
|
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]); |
|
} |
|
|
|
|