/*==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 . 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 "hsTypes.h" #include "plAnimPath.h" #include "plController.h" #include "hsFastMath.h" #include "hsResMgr.h" const hsScalar kSmallDelTime = 1.e-2f; const hsScalar kInvSmallDelTime = 1.f / kSmallDelTime; const hsScalar kTerminateDelTime = 1.e-3f; const hsScalar 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(hsScalar t, UInt32 calcFlags) { fTime = t; if( !fController ) { fPos.Set(0,0,0); fXform.Reset(); fVel.Set(0,0,0); fAccel.Set(0,0,0); return; } hsScalar 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; fAccel.Set(&(pos[2] - pos[1]), &(pos[1] - pos[0])); 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 keyTimes; pc->GetKeyTimes(keyTimes); fCenter.Set(0,0,0); for( i = 0; i < keyTimes.GetCount() ; i++ ) { pc->Interp(keyTimes[i], &pos); fCenter += pos; } fCenter *= hsScalarInvert((hsScalar)keyTimes.GetCount()); fRadius = 0; for( i = 0; i < keyTimes.GetCount(); i++ ) { pc->Interp(keyTimes[i], &pos); hsScalar rad = (pos - fCenter).Magnitude(); if( rad > fRadius ) fRadius = rad; } } hsScalar 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(); } void plAnimPath::SetTransform(const hsMatrix44& l2w, const hsMatrix44& w2l) { fLocalToWorld = l2w; fWorldToLocal = w2l; } void plAnimPath::Read(hsStream* stream, hsResMgr* mgr) { fAnimPathFlags=stream->ReadSwap32(); delete fController; fController = plCompoundController::ConvertNoRef(mgr->ReadCreatable(stream)); ICalcBounds(); fParts.Read(stream); fLocalToWorld.Read(stream); fWorldToLocal.Read(stream); fLength = stream->ReadSwapScalar(); fMinDistSq = stream->ReadSwapScalar(); Reset(); } void plAnimPath::Write(hsStream* stream, hsResMgr* mgr) { stream->WriteSwap32(fAnimPathFlags); mgr->WriteCreatable(stream, fController); fParts.Write(stream); fLocalToWorld.Write(stream); fWorldToLocal.Write(stream); stream->WriteSwapScalar(fLength); stream->WriteSwapScalar(fMinDistSq); } hsBool plAnimPath::OutOfRange(hsPoint3 &worldPt, hsScalar range) const { hsPoint3 pt = fWorldToLocal * worldPt; hsScalar radius = (pt - fCenter).Magnitude() - fRadius; return( radius > range ); } hsScalar plAnimPath::GetExtremePoint(hsPoint3 &worldPt) const { if( !fController ) return 0; hsPoint3 pt = fWorldToLocal * worldPt; plController *pc = fController->GetPosController(); hsScalar minDistSq = 1.e33f; hsScalar minTime = 0, delTime = 0; // start search by using the time of the closest ctrl point int i; hsTArray keyTimes; pc->GetKeyTimes(keyTimes); for( i = 0; i < keyTimes.GetCount(); i++ ) { hsScalar t = keyTimes[i]; hsPoint3 pos; pc->Interp(t, &pos); // handles easing hsScalar 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 { hsScalar fore = keyTimes[i + 1] - t; hsScalar back = t - keyTimes[i - 1]; delTime = hsMaximum(fore, back); } } } return GetExtremePoint(minTime, delTime, worldPt); } hsScalar plAnimPath::GetExtremePoint(hsScalar lastTime, hsScalar delTime, hsPoint3 &worldPt) const { if( !fController ) return 0; hsPoint3 pt = fWorldToLocal * worldPt; IInitInterval(lastTime, delTime, pt); return ICheckInterval(pt); } hsScalar 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(hsScalar time, hsScalar 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; } } hsScalar 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); } hsScalar 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); } hsScalar 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); } hsScalar 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& idx, hsTArray& pos, hsPoint3& p1, hsPoint3& p2) { hsVector3 del(&p2, &p1); hsVector3 up; up.Set(0,0,1.f); const hsScalar kOutLength = 0.25f; hsVector3 a = del % up; hsScalar 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& idx, hsTArray& pos) { hsMatrix44 resetL2W = GetLocalToWorld(); hsMatrix44 resetW2L = GetWorldToLocal(); hsMatrix44 ident; ident.Reset(); SetTransform(ident, ident); hsScalar numSegs = fRadius; // crude estimate of arclength if (numSegs>100) numSegs=100; hsScalar animLen = GetLength(); hsScalar timeInc = animLen/numSegs; hsScalar time=0; hsPoint3 p1, p2; SetCurTime(0, kCalcPosOnly); GetPosition(&p1); time += timeInc; hsBool 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 numSamples) { if (fArcLenDeltas.GetCount() >= numSamples) return; // already computed enough samples // compute arc len deltas fArcLenDeltas.Reset(); fArcLenDeltas.SetCount(numSamples); hsScalar animLen = GetLength(); hsScalar timeInc = animLen/(numSamples-1); // use num-1 since we'll create the zeroth entry by hand hsScalar time=0; hsPoint3 p1, p2; Int32 cnt=0; // prime initial point SetCurTime(0, kCalcPosOnly); GetPosition(&p1); ArcLenDeltaInfo aldi(time, 0); fArcLenDeltas[cnt++]=aldi; time += timeInc; hsBool quit=false; while(!quit && 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. // hsScalar plAnimPath::GetLookAheadTime(hsScalar startTime, hsScalar arcLengthIn, hsBool bwd, Int32* 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"); hsScalar 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 hsBool found=false; Int32 i; for(i=(*startSrchIdx); 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 hsScalar 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); hsScalar distInterval = hsVector3(&pos2, &pos).Magnitude(); hsScalar 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=0), "bwd: illegal look ahead time"); return timeOut; }