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.
818 lines
20 KiB
818 lines
20 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 "HeadSpin.h" |
|
#include "plAnimPath.h" |
|
#include "plController.h" |
|
#include "hsFastMath.h" |
|
#include "hsResMgr.h" |
|
#include <math.h> |
|
|
|
const float kSmallDelTime = 1.e-2f; |
|
const float kInvSmallDelTime = 1.f / kSmallDelTime; |
|
const float kTerminateDelTime = 1.e-3f; |
|
const float kTerminateDelDistSq = .1f; |
|
|
|
plAnimPath::plAnimPath() |
|
: fController(nil), fLength(0), fMinDistSq(0), |
|
fAnimPathFlags(0) |
|
{ |
|
fLocalToWorld.Reset(); |
|
fWorldToLocal.Reset(); |
|
Reset(); |
|
} |
|
|
|
plAnimPath::~plAnimPath() |
|
{ |
|
delete fController; |
|
} |
|
|
|
void plAnimPath::Reset() |
|
{ |
|
SetCurTime(0); |
|
} |
|
|
|
void plAnimPath::SetCurTime(float t, uint32_t calcFlags) |
|
{ |
|
fTime = t; |
|
if( !fController ) |
|
{ |
|
fPos.Set(0,0,0); |
|
fXform.Reset(); |
|
fVel.Set(0,0,0); |
|
fAccel.Set(0,0,0); |
|
return; |
|
} |
|
|
|
float t0, t1, t2; |
|
|
|
if( t < kSmallDelTime ) |
|
{ |
|
t0 = t; |
|
t1 = t + kSmallDelTime; |
|
t2 = t + 2 * kSmallDelTime; |
|
} |
|
else if( t > fLength - kSmallDelTime ) |
|
{ |
|
t0 = t - 2 * kSmallDelTime; |
|
t1 = t - kSmallDelTime; |
|
t2 = t; |
|
} |
|
else |
|
{ |
|
t0 = t - kSmallDelTime; |
|
t1 = t; |
|
t2 = t + kSmallDelTime; |
|
} |
|
|
|
|
|
if (!(calcFlags & kCalcPosOnly)) |
|
{ |
|
hsPoint3 pos[3]; |
|
|
|
fController->Interp(t0, &fParts); |
|
pos[0].Set(fParts.fT.fX, fParts.fT.fY, fParts.fT.fZ); |
|
|
|
fController->Interp(t1, &fParts); |
|
pos[1].Set(fParts.fT.fX, fParts.fT.fY, fParts.fT.fZ); |
|
|
|
fController->Interp(t2, &fParts); |
|
pos[2].Set(fParts.fT.fX, fParts.fT.fY, fParts.fT.fZ); |
|
|
|
fVel.Set(pos+1, pos+0); |
|
fVel *= kInvSmallDelTime; |
|
fVel = fLocalToWorld * fVel; |
|
|
|
hsPoint3 first = pos[2] - pos[1]; |
|
hsPoint3 second = pos[1] - pos[0]; |
|
fAccel.Set(&first, &second); |
|
fAccel *= kInvSmallDelTime * kInvSmallDelTime; |
|
fAccel = fLocalToWorld * fAccel; |
|
} |
|
|
|
fController->Interp(t, &fParts); |
|
fParts.ComposeMatrix(&fXform); |
|
fXform = fLocalToWorld * fXform; |
|
fXform.GetTranslate(&fPos); |
|
|
|
} |
|
|
|
void plAnimPath::ICalcBounds() |
|
{ |
|
if( !fController ) |
|
return; |
|
|
|
plController* pc = fController->GetPosController(); |
|
|
|
int i; |
|
hsPoint3 pos; |
|
hsTArray<float> keyTimes; |
|
pc->GetKeyTimes(keyTimes); |
|
fCenter.Set(0,0,0); |
|
for( i = 0; i < keyTimes.GetCount() ; i++ ) |
|
{ |
|
pc->Interp(keyTimes[i], &pos); |
|
fCenter += pos; |
|
} |
|
fCenter *= hsInvert((float)keyTimes.GetCount()); |
|
|
|
fRadius = 0; |
|
for( i = 0; i < keyTimes.GetCount(); i++ ) |
|
{ |
|
pc->Interp(keyTimes[i], &pos); |
|
float rad = (pos - fCenter).Magnitude(); |
|
if( rad > fRadius ) |
|
fRadius = rad; |
|
} |
|
} |
|
|
|
float plAnimPath::ICalcTotalLength() |
|
{ |
|
if( !(fController && fController->GetPosController()) ) |
|
return 0; |
|
|
|
fLength = fController->GetPosController()->GetLength(); |
|
|
|
return fLength; |
|
} |
|
|
|
void plAnimPath::SetController(plCompoundController* tmc) |
|
{ |
|
hsAssert(tmc, "Bad (nil) controller for AnimPath"); |
|
hsAssert(tmc->GetPosController(), "Bad controller for AnimPath"); |
|
fController = tmc; |
|
ICalcTotalLength(); |
|
ICalcBounds(); |
|
} |
|
|
|
float plAnimPath::GetMinDistance() const |
|
{ |
|
return sqrt(fMinDistSq); |
|
} |
|
|
|
void plAnimPath::SetTransform(const hsMatrix44& l2w, const hsMatrix44& w2l) |
|
{ |
|
fLocalToWorld = l2w; |
|
fWorldToLocal = w2l; |
|
} |
|
|
|
void plAnimPath::Read(hsStream* stream, hsResMgr* mgr) |
|
{ |
|
fAnimPathFlags=stream->ReadLE32(); |
|
|
|
delete fController; |
|
fController = plCompoundController::ConvertNoRef(mgr->ReadCreatable(stream)); |
|
|
|
ICalcBounds(); |
|
|
|
fParts.Read(stream); |
|
|
|
fLocalToWorld.Read(stream); |
|
fWorldToLocal.Read(stream); |
|
|
|
fLength = stream->ReadLEScalar(); |
|
fMinDistSq = stream->ReadLEScalar(); |
|
|
|
Reset(); |
|
} |
|
|
|
void plAnimPath::Write(hsStream* stream, hsResMgr* mgr) |
|
{ |
|
stream->WriteLE32(fAnimPathFlags); |
|
|
|
mgr->WriteCreatable(stream, fController); |
|
|
|
fParts.Write(stream); |
|
|
|
fLocalToWorld.Write(stream); |
|
fWorldToLocal.Write(stream); |
|
|
|
stream->WriteLEScalar(fLength); |
|
stream->WriteLEScalar(fMinDistSq); |
|
} |
|
|
|
bool plAnimPath::OutOfRange(hsPoint3 &worldPt, float range) const |
|
{ |
|
hsPoint3 pt = fWorldToLocal * worldPt; |
|
|
|
float radius = (pt - fCenter).Magnitude() - fRadius; |
|
return( radius > range ); |
|
} |
|
|
|
float plAnimPath::GetExtremePoint(hsPoint3 &worldPt) const |
|
{ |
|
if( !fController ) |
|
return 0; |
|
|
|
hsPoint3 pt = fWorldToLocal * worldPt; |
|
|
|
plController *pc = fController->GetPosController(); |
|
|
|
float minDistSq = 1.e33f; |
|
float minTime = 0, delTime = 0; |
|
// start search by using the time of the closest ctrl point |
|
int i; |
|
hsTArray<float> keyTimes; |
|
pc->GetKeyTimes(keyTimes); |
|
for( i = 0; i < keyTimes.GetCount(); i++ ) |
|
{ |
|
float t = keyTimes[i]; |
|
hsPoint3 pos; |
|
pc->Interp(t, &pos); // handles easing |
|
float distSq = (pt - pos).MagnitudeSquared(); |
|
if( distSq < minDistSq ) |
|
{ |
|
minDistSq = distSq; |
|
minTime = t; |
|
if( 0 == i ) |
|
delTime = keyTimes[i+1] - t; |
|
else if( keyTimes.GetCount() - 1 == i ) |
|
delTime = t - keyTimes[i - 1]; |
|
else |
|
{ |
|
float fore = keyTimes[i + 1] - t; |
|
float back = t - keyTimes[i - 1]; |
|
delTime = hsMaximum(fore, back); |
|
} |
|
} |
|
} |
|
|
|
return GetExtremePoint(minTime, delTime, worldPt); |
|
} |
|
|
|
float plAnimPath::GetExtremePoint(float lastTime, float delTime, hsPoint3 &worldPt) const |
|
{ |
|
if( !fController ) |
|
return 0; |
|
|
|
hsPoint3 pt = fWorldToLocal * worldPt; |
|
|
|
IInitInterval(lastTime, delTime, pt); |
|
return ICheckInterval(pt); |
|
} |
|
|
|
float plAnimPath::ICheckInterval(hsPoint3 &pt) const |
|
{ |
|
if( fDelTime <= kTerminateDelTime && |
|
hsVector3(&fCurPos, &fPrevPos).MagnitudeSquared() < kTerminateDelDistSq) |
|
{ |
|
return IBestTime(); |
|
} |
|
|
|
if( fThisTime < 0 ) |
|
return 0; |
|
|
|
if( fThisTime > fLength ) |
|
return fLength; |
|
|
|
if( GetFarthest() ) |
|
{ |
|
if( (fLastDistSq > fThisDistSq)&&(fLastDistSq >= fNextDistSq) ) |
|
return IShiftBack(pt); |
|
|
|
if( (fNextDistSq > fThisDistSq)&&(fNextDistSq >= fLastDistSq) ) |
|
return IShiftFore(pt); |
|
|
|
if( (fThisDistSq >= fLastDistSq)&&(fLastDistSq >= fNextDistSq) ) |
|
return ISubDivBack(pt); |
|
|
|
if( (fThisDistSq >= fNextDistSq)&&(fNextDistSq >= fLastDistSq) ) |
|
return ISubDivFore(pt); |
|
} |
|
else |
|
{ |
|
if( (fLastDistSq < fThisDistSq)&&(fLastDistSq <= fNextDistSq) ) |
|
return IShiftBack(pt); |
|
|
|
if( (fNextDistSq < fThisDistSq)&&(fNextDistSq <= fLastDistSq) ) |
|
return IShiftFore(pt); |
|
|
|
if( (fThisDistSq <= fLastDistSq)&&(fLastDistSq <= fNextDistSq) ) |
|
return ISubDivBack(pt); |
|
|
|
if( (fThisDistSq <= fNextDistSq)&&(fNextDistSq <= fLastDistSq) ) |
|
return ISubDivFore(pt); |
|
} |
|
|
|
hsAssert(false, "Shouldn't have gotten here"); |
|
return 0; |
|
} |
|
|
|
void plAnimPath::IInitInterval(float time, float delTime, hsPoint3 &pt) const |
|
{ |
|
plController* pc = fController->GetPosController(); |
|
|
|
hsPoint3 pos; |
|
|
|
fDelTime = delTime; |
|
if( fDelTime <= kTerminateDelTime ) |
|
fDelTime = kTerminateDelTime * 2; |
|
else |
|
if( fDelTime > fLength * 0.5f ) |
|
fDelTime = fLength * 0.5f; |
|
|
|
fThisTime = time; |
|
if( fThisTime < fDelTime ) |
|
fThisTime = fDelTime; |
|
else if( fThisTime > fLength - fDelTime ) |
|
fThisTime = fLength - fDelTime; |
|
pc->Interp(fThisTime, &pos); |
|
fPrevPos=fCurPos=pos; |
|
fThisDistSq = (pos - pt).MagnitudeSquared(); |
|
|
|
fNextTime = fThisTime + delTime; |
|
if( fNextTime > fLength ) |
|
fNextTime = fLength; |
|
if (!(GetAnimPathFlags() & kFavorBwdSearch)) |
|
{ |
|
pc->Interp(fNextTime, &pos); |
|
fNextDistSq = (pos - pt).MagnitudeSquared(); |
|
} |
|
else |
|
{ |
|
fNextDistSq = 1.e33f; |
|
} |
|
|
|
fLastTime = fThisTime - delTime; |
|
if( fLastTime < 0 ) |
|
fLastTime = 0; |
|
if (!(GetAnimPathFlags() & kFavorFwdSearch)) |
|
{ |
|
pc->Interp(fLastTime, &pos); |
|
fLastDistSq = (pos - pt).MagnitudeSquared(); |
|
} |
|
else |
|
{ |
|
fLastDistSq = 1.e33f; |
|
} |
|
|
|
if( fMinDistSq != 0 ) |
|
{ |
|
fThisDistSq -= fMinDistSq; |
|
if( fThisDistSq < 0 ) |
|
fThisDistSq = -fThisDistSq; |
|
|
|
fNextDistSq -= fMinDistSq; |
|
if( fNextDistSq < 0 ) |
|
fNextDistSq = -fNextDistSq; |
|
|
|
fLastDistSq -= fMinDistSq; |
|
if( fLastDistSq < 0 ) |
|
fLastDistSq = -fLastDistSq; |
|
} |
|
} |
|
|
|
float plAnimPath::ISubDivBack(hsPoint3 &pt) const |
|
{ |
|
fNextTime = fThisTime; |
|
fNextDistSq = fThisDistSq; |
|
|
|
fDelTime *= 0.5f; |
|
|
|
fThisTime -= fDelTime; |
|
if (fThisTime<0) |
|
fThisTime = GetWrap() ? fLength + fThisTime : 0; |
|
|
|
plController* pc = fController->GetPosController(); |
|
hsPoint3 pos; |
|
pc->Interp(fThisTime, &pos); |
|
fThisDistSq = (pos - pt).MagnitudeSquared() - fMinDistSq; |
|
if( fThisDistSq < 0 ) |
|
fThisDistSq = -fThisDistSq; |
|
|
|
fPrevPos=fCurPos; |
|
fCurPos=pos; |
|
|
|
return ICheckInterval(pt); |
|
} |
|
|
|
float plAnimPath::ISubDivFore(hsPoint3 &pt) const |
|
{ |
|
fLastTime = fThisTime; |
|
fLastDistSq = fThisDistSq; |
|
|
|
fDelTime *= 0.5f; |
|
|
|
fThisTime += fDelTime; |
|
if (fThisTime>fLength) |
|
fThisTime = GetWrap() ? fThisTime-fLength : fLength; |
|
|
|
plController* pc = fController->GetPosController(); |
|
hsPoint3 pos; |
|
pc->Interp(fThisTime, &pos); |
|
fThisDistSq = (pos - pt).MagnitudeSquared() - fMinDistSq; |
|
if( fThisDistSq < 0 ) |
|
fThisDistSq = -fThisDistSq; |
|
|
|
fPrevPos=fCurPos; |
|
fCurPos=pos; |
|
|
|
return ICheckInterval(pt); |
|
} |
|
|
|
float plAnimPath::IShiftBack(hsPoint3 &pt) const |
|
{ |
|
if( !GetWrap() && (fLastTime <= 0) ) |
|
return ISubDivBack(pt); |
|
|
|
fNextTime = fThisTime; |
|
fNextDistSq = fThisDistSq; |
|
|
|
fThisTime = fLastTime; |
|
fThisDistSq = fLastDistSq; |
|
|
|
fLastTime -= fDelTime; |
|
if( fLastTime < 0 ) |
|
fLastTime = GetWrap() ? fLength + fLastTime : 0; |
|
plController* pc = fController->GetPosController(); |
|
hsPoint3 pos; |
|
pc->Interp(fLastTime, &pos); |
|
fLastDistSq = (pos - pt).MagnitudeSquared() - fMinDistSq; |
|
if( fLastDistSq < 0 ) |
|
fLastDistSq = -fLastDistSq; |
|
|
|
fPrevPos=fCurPos; |
|
fCurPos=pos; |
|
|
|
return ICheckInterval(pt); |
|
} |
|
|
|
float plAnimPath::IShiftFore(hsPoint3 &pt) const |
|
{ |
|
if( !GetWrap() &&(fNextTime >= fLength) ) |
|
return ISubDivFore(pt); |
|
|
|
fLastTime = fThisTime; |
|
fLastDistSq = fThisDistSq; |
|
|
|
fThisTime = fNextTime; |
|
fThisDistSq = fNextDistSq; |
|
|
|
fNextTime += fDelTime; |
|
if( fNextTime > fLength ) |
|
fNextTime = GetWrap() ? fNextTime - fLength : fLength; |
|
plController* pc = fController->GetPosController(); |
|
hsPoint3 pos; |
|
pc->Interp(fNextTime, &pos); |
|
fNextDistSq = (pos - pt).MagnitudeSquared() - fMinDistSq; |
|
if( fNextDistSq < 0 ) |
|
fNextDistSq = -fNextDistSq; |
|
|
|
fPrevPos=fCurPos; |
|
fCurPos=pos; |
|
|
|
return ICheckInterval(pt); |
|
} |
|
|
|
// |
|
// wireframe debug draw method. |
|
// doesn't use any fancy subdivision or curvature measure when drawing. |
|
// Changes current time. |
|
// |
|
void plAnimPath::IMakeSegment(hsTArray<uint16_t>& idx, hsTArray<hsPoint3>& pos, |
|
hsPoint3& p1, hsPoint3& p2) |
|
{ |
|
hsVector3 del(&p2, &p1); |
|
hsVector3 up; |
|
up.Set(0,0,1.f); |
|
|
|
const float kOutLength = 0.25f; |
|
|
|
hsVector3 a = del % up; |
|
float magSq = a.MagnitudeSquared(); |
|
if( magSq < 1.e-3f ) |
|
{ |
|
a.Set(kOutLength, 0, 0); |
|
} |
|
else |
|
{ |
|
a *= hsFastMath::InvSqrtAppr(magSq); |
|
a *= kOutLength; |
|
} |
|
|
|
hsVector3 b = a % del; |
|
hsFastMath::Normalize(b); |
|
b *= kOutLength; |
|
|
|
hsPoint3 p1out, p2out; |
|
|
|
int baseIdx = pos.GetCount(); |
|
|
|
pos.Append(p1); |
|
pos.Append(p2); |
|
|
|
p1out = p1; |
|
p1out += a; |
|
p2out = p2; |
|
p2out += a; |
|
|
|
pos.Append(p1out); |
|
pos.Append(p2out); |
|
|
|
p1out += -2.f * a; |
|
p2out += -2.f * a; |
|
|
|
pos.Append(p1out); |
|
pos.Append(p2out); |
|
|
|
p1out = p1; |
|
p1out += b; |
|
p2out = p2; |
|
p2out += b; |
|
|
|
pos.Append(p1out); |
|
pos.Append(p2out); |
|
|
|
p1out += -2.f * b; |
|
p2out += -2.f * b; |
|
|
|
pos.Append(p1out); |
|
pos.Append(p2out); |
|
|
|
int i; |
|
for( i = 0; i < 4; i++ ) |
|
{ |
|
int outIdx = baseIdx + 2 + i * 2; |
|
idx.Append(baseIdx); |
|
idx.Append(baseIdx + 1); |
|
idx.Append(baseIdx + outIdx); |
|
|
|
idx.Append(baseIdx + outIdx); |
|
idx.Append(baseIdx + 1); |
|
idx.Append(baseIdx + outIdx + 1); |
|
} |
|
} |
|
|
|
void plAnimPath::MakeDrawList(hsTArray<uint16_t>& idx, hsTArray<hsPoint3>& pos) |
|
{ |
|
hsMatrix44 resetL2W = GetLocalToWorld(); |
|
hsMatrix44 resetW2L = GetWorldToLocal(); |
|
|
|
hsMatrix44 ident; |
|
ident.Reset(); |
|
SetTransform(ident, ident); |
|
|
|
float numSegs = fRadius; // crude estimate of arclength |
|
if (numSegs>100) |
|
numSegs=100; |
|
float animLen = GetLength(); |
|
float timeInc = animLen/numSegs; |
|
float time=0; |
|
hsPoint3 p1, p2; |
|
|
|
SetCurTime(0, kCalcPosOnly); |
|
GetPosition(&p1); |
|
|
|
time += timeInc; |
|
bool quit=false; |
|
while(! quit && time < animLen+timeInc) |
|
{ |
|
if (time > animLen) |
|
{ |
|
time = animLen; |
|
quit=true; |
|
} |
|
|
|
SetCurTime(time, kCalcPosOnly); |
|
GetPosition(&p2); |
|
|
|
IMakeSegment(idx, pos, p1, p2); |
|
|
|
time += timeInc; |
|
|
|
p1 = p2; |
|
} |
|
|
|
SetTransform(resetL2W, resetW2L); |
|
} |
|
|
|
// |
|
// Precompute array of arclen deltas for lookahead ability. |
|
// Changes current time! |
|
// |
|
void plAnimPath::ComputeArcLenDeltas(int32_t numSamples) |
|
{ |
|
if (fArcLenDeltas.GetCount() >= numSamples) |
|
return; // already computed enough samples |
|
|
|
// compute arc len deltas |
|
fArcLenDeltas.Reset(); |
|
fArcLenDeltas.SetCount(numSamples); |
|
float animLen = GetLength(); |
|
float timeInc = animLen/(numSamples-1); // use num-1 since we'll create the zeroth entry by hand |
|
float time=0; |
|
hsPoint3 p1, p2; |
|
|
|
int32_t cnt=0; |
|
|
|
// prime initial point |
|
SetCurTime(0, kCalcPosOnly); |
|
GetPosition(&p1); |
|
ArcLenDeltaInfo aldi(time, 0); |
|
fArcLenDeltas[cnt++]=aldi; |
|
time += timeInc; |
|
|
|
bool quit=false; |
|
while(!quit && time<animLen+timeInc) |
|
{ |
|
if (time > animLen || cnt+1 == numSamples) |
|
{ |
|
time = animLen; |
|
quit=true; |
|
} |
|
|
|
SetCurTime(time, kCalcPosOnly); |
|
GetPosition(&p2); |
|
|
|
ArcLenDeltaInfo aldi(time, hsVector3(&p2, &p1).Magnitude()); |
|
fArcLenDeltas[cnt++]=aldi; |
|
|
|
time += timeInc; |
|
p1 = p2; |
|
} |
|
hsAssert(fArcLenDeltas.GetCount()==numSamples, "arcLenArray size wrong?"); |
|
hsAssert(cnt==numSamples, "arcLenArray size wrong?"); |
|
} |
|
|
|
// |
|
// Returns time of point (at least) arcLength units away from point at startTime. |
|
// Also sets strtSrchIdx for incremental searching. |
|
// |
|
float plAnimPath::GetLookAheadTime(float startTime, float arcLengthIn, bool bwd, |
|
int32_t* startSrchIdx) |
|
{ |
|
if (arcLengthIn==0) |
|
return startTime; // default is no look ahead |
|
|
|
if (startTime==GetLength() && !bwd) |
|
return GetLength(); |
|
|
|
if (startTime==0 && bwd) |
|
return 0; |
|
|
|
hsAssert(startSrchIdx, "nil var for startSrchIdx"); |
|
|
|
float oldTime=fTime; |
|
|
|
ComputeArcLenDeltas(); // precompute first time only |
|
|
|
// save and change time if necessary |
|
if (fTime!=startTime) |
|
SetCurTime(startTime, kCalcPosOnly); |
|
|
|
// find nearest (forward) arcLen sample point, use starting srch index provided |
|
bool found=false; |
|
int32_t i; |
|
for(i=(*startSrchIdx); i<fArcLenDeltas.GetCount()-1; i++) |
|
{ |
|
if (fArcLenDeltas[i].fT<=startTime && startTime<fArcLenDeltas[i+1].fT) |
|
{ |
|
*startSrchIdx=i; |
|
found=true; |
|
break; |
|
} |
|
} |
|
|
|
if (!found) |
|
{ |
|
// check if equal to last index |
|
if (startTime==fArcLenDeltas[fArcLenDeltas.GetCount()-1].fT) |
|
{ |
|
*startSrchIdx=fArcLenDeltas.GetCount()-1; |
|
found=true; |
|
} |
|
else |
|
{ |
|
for(i=0; i<*startSrchIdx; i++) |
|
{ |
|
if (fArcLenDeltas[i].fT<=startTime && startTime<fArcLenDeltas[i+1].fT) |
|
{ |
|
*startSrchIdx=i; |
|
found=true; |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
// find distance to nearest arcLen sample point |
|
int32_t nearestIdx = bwd ? *startSrchIdx : *startSrchIdx+1; |
|
hsAssert(found, "couldn't find arcLength sample"); |
|
|
|
hsPoint3 pos; |
|
GetPosition(&pos); // startTime position |
|
|
|
hsPoint3 pos2; |
|
float endTime = fArcLenDeltas[nearestIdx].fT; |
|
SetCurTime(endTime, kCalcPosOnly); |
|
GetPosition(&pos2); // position at nearest sample point |
|
|
|
float curArcLen = hsVector3(&pos2, &pos).Magnitude(); |
|
float curTime=0; |
|
bool quit=false; |
|
float timeOut = 0; |
|
int32_t inc = bwd ? -1 : 1; |
|
// now sum distance deltas until we exceed the desired arcLen |
|
if (curArcLen<arcLengthIn) |
|
{ |
|
for(i=bwd ? nearestIdx : nearestIdx+inc; i<fArcLenDeltas.GetCount() && i>=0; i+=inc) |
|
{ |
|
if (curArcLen+fArcLenDeltas[i].fArcLenDelta>arcLengthIn) |
|
{ |
|
// gone tooFar |
|
endTime = fArcLenDeltas[i].fT; |
|
curTime = fArcLenDeltas[i-1].fT; |
|
break; |
|
} |
|
curArcLen += fArcLenDeltas[i].fArcLenDelta; |
|
} |
|
|
|
if ( (i==fArcLenDeltas.GetCount() && !bwd) || (i<0 && bwd) ) |
|
{ |
|
quit=true; |
|
timeOut = bwd ? 0 : GetLength(); |
|
} |
|
} |
|
else |
|
{ |
|
curArcLen = 0; |
|
curTime = startTime; |
|
} |
|
|
|
if (!quit) |
|
{ |
|
// interp remaining interval |
|
|
|
// 1. compute necessary distToGoal |
|
float distToGoal = arcLengthIn-curArcLen; |
|
hsAssert(distToGoal, "0 length distToGoal?"); |
|
|
|
// 2. compute % of dist interval which gives distToGoal |
|
SetCurTime(curTime, kCalcPosOnly); |
|
GetPosition(&pos); |
|
|
|
SetCurTime(endTime, kCalcPosOnly); |
|
GetPosition(&pos2); |
|
|
|
float distInterval = hsVector3(&pos2, &pos).Magnitude(); |
|
float percent = distToGoal/distInterval; |
|
hsAssert(percent>=0 && percent<=1, "illegal percent value"); |
|
|
|
// 3. compute interpolated time value using percent |
|
if (!bwd) |
|
timeOut = curTime + (endTime-curTime)*percent; |
|
else |
|
timeOut = endTime - (endTime-curTime)*percent; |
|
hsAssert((timeOut>=curTime && timeOut<=endTime), "illegal interpolated time value"); |
|
// hsAssert(!bwd || (timeOut<=curTime && timeOut>=endTime), "bwd: illegal interpolated time value"); |
|
} |
|
|
|
// restore time |
|
if (fTime != oldTime) |
|
SetCurTime(oldTime); |
|
|
|
hsAssert(bwd || (timeOut>startTime && timeOut<=GetLength()), "fwd: illegal look ahead time"); |
|
hsAssert(!bwd || (timeOut<startTime && timeOut>=0), "bwd: illegal look ahead time"); |
|
|
|
return timeOut; |
|
} |
|
|
|
|