Browse Source

Merge pull request #375 from Areloch/LoopmodePathManager

Adds handling to the path manager so it can deal with both looping and non-looping paths
Brian Roberts 4 years ago
parent
commit
e9dba74891

+ 122 - 8
Engine/source/scene/pathManager.cpp

@@ -102,6 +102,7 @@ void PathManagerEvent::pack(NetConnection*, BitStream* stream)
    stream->write(modifiedPath);
    stream->writeFlag(clearPaths);
    stream->write(path.totalTime);
+   stream->write(path.looping);
    stream->write(path.positions.size());
 
 
@@ -131,6 +132,7 @@ void PathManagerEvent::unpack(NetConnection*, BitStream* stream)
    stream->read(&modifiedPath);
    clearPaths = stream->readFlag();
    stream->read(&path.totalTime);
+   stream->read(&path.looping);
 
    U32 numPoints;
    stream->read(&numPoints);
@@ -231,7 +233,8 @@ void PathManager::updatePath(const U32              id,
                              const Vector<Point3F>& positions,
                              const Vector<QuatF>&   rotations,
                              const Vector<U32>&     times,
-                             const Vector<U32>&     smoothingTypes)
+                             const Vector<U32>&     smoothingTypes,
+                             const bool             looping)
 {
    AssertFatal(mIsServer == true, "PathManager::updatePath: Error, must be called on the server side");
    AssertFatal(id < mPaths.size(), "PathManager::updatePath: error, id out of range");
@@ -243,6 +246,7 @@ void PathManager::updatePath(const U32              id,
    rEntry.rotations = rotations;
    rEntry.msToNext  = times;
    rEntry.smoothingType = smoothingTypes;
+   rEntry.looping = looping;
 
    rEntry.totalTime = 0;
    for (S32 i = 0; i < S32(rEntry.msToNext.size()); i++)
@@ -299,15 +303,33 @@ void PathManager::getPathPosition(const U32 id,
 
    // Ok, query holds our path information...
    F64 ms = msPosition;
-   if (ms > mPaths[id]->totalTime)
-      ms = mPaths[id]->totalTime;
+
+   //Looping vs. non-looping splines
+   F32 msTotal;
+   if (mPaths[id]->looping)
+      msTotal = mPaths[id]->totalTime;
+   else
+      //total time minus last nodes time
+      msTotal = mPaths[id]->totalTime - mPaths[id]->msToNext[mPaths[id]->msToNext.size() - 1];
+
+   if (ms > msTotal)
+      ms = msTotal;
 
    S32 startNode = 0;
    while (ms > mPaths[id]->msToNext[startNode]) {
       ms -= mPaths[id]->msToNext[startNode];
       startNode++;
    }
-   S32 endNode = (startNode + 1) % mPaths[id]->positions.size();
+
+   S32 endNode;
+
+   //Looping splines
+   if (mPaths[id]->looping)
+      endNode = (startNode + 1) % mPaths[id]->positions.size();
+   //Non-looping splines
+   else
+      endNode = getMin(startNode + 1, mPaths[id]->positions.size() - 1);
+
 
    Point3F& rStart = mPaths[id]->positions[startNode];
    Point3F& rEnd   = mPaths[id]->positions[endNode];
@@ -326,10 +348,24 @@ void PathManager::getPathPosition(const U32 id,
    {
       S32 preStart = startNode - 1;
       S32 postEnd = endNode + 1;
-      if(postEnd >= mPaths[id]->positions.size())
-         postEnd = 0;
-      if(preStart < 0)
-         preStart = mPaths[id]->positions.size() - 1;
+
+      //Looping splines
+      if (mPaths[id]->looping)
+      {
+         if (postEnd >= mPaths[id]->positions.size())
+            postEnd = 0;
+         if (preStart < 0)
+            preStart = mPaths[id]->positions.size() - 1;
+      }
+      //Non-looping splines
+      else
+      {
+         if (postEnd >= mPaths[id]->positions.size())
+            postEnd = mPaths[id]->positions.size() - 1;
+         if (preStart < 0)
+            preStart = 0;
+      }
+
       Point3F p0 = mPaths[id]->positions[preStart];
       Point3F p1 = rStart;
       Point3F p2 = rEnd;
@@ -430,5 +466,83 @@ bool PathManager::readState(BitStream* stream)
    return stream->getStatus() == Stream::Ok;
 }
 
+F64 PathManager::getClosestTimeToPoint(const U32 id, const Point3F p)
+{
+   //Ubiq: Ideally this algorithm would work directly by finding roots. However, it's a 5th order 
+   //polynomial (cannot be solved by radicals), so we're doing it iteratively instead!
+
+   //Steps:
+   //1) Termination condition: if the segment is shorter than L, return the midpoint
+   //2) Otherwise, divide the spline-segment into S sub-segments (S+1 points to test)
+   //2) Test these points against the given point and find the closest of them
+   //4) Recurse within the 2 segments surrounding the closest point
 
+   //NOTE: In a case where the spline comes near the point multiple times, the wrong local 
+   //minima of the spline may be chosen early on and then refined, like polishing a turd :)
+
+   PROFILE_START(PathManager_getClosestTimeToPoint);
+
+   F64 totalTime;
+   if (mPaths[id]->looping)
+      totalTime = mPaths[id]->totalTime;
+   else
+      totalTime = mPaths[id]->totalTime - mPaths[id]->msToNext[mPaths[id]->msToNext.size() - 1];
+
+   F64 ret = getClosestTimeToPoint(id, p, 0.0f, totalTime);
+
+   PROFILE_END();
+
+   return ret;
+}
+
+F64 PathManager::getClosestTimeToPoint(const U32 id, const Point3F p, const F64 tMin, const F64 tMax)
+{
+   F64 totalSize = tMax - tMin;
+
+   //termination condition
+   if (totalSize <= 25.0f)
+      return (tMin + tMax) / 2.0f;
+
+   U32 steps = getMax((F32)totalSize / 500.0f, 8.0f);
+   F64 stepSize = totalSize / steps;
+
+   F64 distBest = F32_MAX;
+   F64 tBest = 0;
+
+   for (U32 i = 0; i <= steps; i++)
+   {
+      F64 tTest = tMin + (stepSize * i);
+      Point3F pTest; QuatF dummy;
+      getPathPosition(id, tTest, pTest, dummy);
+
+      F64 dist = (pTest - p).lenSquared();    //no need for square root
+      if (dist < distBest)
+      {
+         tBest = tTest;
+         distBest = dist;
+      }
+   }
+
+   F64 tMinNew = tBest - stepSize;
+   F64 tMaxNew = tBest + stepSize;
+   if (mPaths[id]->looping)
+   {
+      while (tMinNew < 0)
+      {
+         tMinNew += getPathTotalTime(id);
+         tMaxNew += getPathTotalTime(id);
+      }
+      while (tMaxNew > getPathTotalTime(id))
+      {
+         tMinNew -= getPathTotalTime(id);
+         tMaxNew -= getPathTotalTime(id);
+      }
+   }
+   else
+   {
+      tMinNew = getMax(tMin, tMinNew);
+      tMaxNew = getMin(tMax, tMaxNew);
+   }
+   return getClosestTimeToPoint(id, p, tMinNew, tMaxNew);
+}
 

+ 4 - 1
Engine/source/scene/pathManager.h

@@ -49,6 +49,7 @@ class PathManager
   private:
    struct PathEntry {
       U32             totalTime;
+      bool            looping;
 
       Vector<Point3F> positions;
       Vector<QuatF>   rotations;
@@ -85,6 +86,8 @@ class PathManager
    U32  getPathTotalTime(const U32 id) const;
    U32  getPathNumWaypoints(const U32 id) const;
    U32  getWaypointTime(const U32 id, const U32 wayPoint) const;
+   F64  getClosestTimeToPoint(const U32 id, const Point3F p);
+   F64  getClosestTimeToPoint(const U32 id, const Point3F p, const F64 tMin, const F64 tMax);
 
    U32 getPathTimeBits(const U32 id);
    U32 getPathWaypointBits(const U32 id);
@@ -97,7 +100,7 @@ class PathManager
    void transmitPath(U32);
 
    U32  allocatePathId();
-   void updatePath(const U32 id, const Vector<Point3F>&, const Vector<QuatF>&, const Vector<U32> &, const Vector<U32>&);
+   void updatePath(const U32 id, const Vector<Point3F>&, const Vector<QuatF>&, const Vector<U32>&, const Vector<U32>&, const bool looping);
 
    //-------------------------------------- State dumping/reading
   public:

+ 2 - 3
Engine/source/scene/simPath.cpp

@@ -248,8 +248,7 @@ void Path::updatePath()
       }
    }
 
-   // DMMTODO: Looping paths.
-   gServerPathManager->updatePath(mPathIndex, positions, rotations, times, smoothingTypes);
+   gServerPathManager->updatePath(mPathIndex, positions, rotations, times, smoothingTypes, mIsLooping);
 }
 
 void Path::addObject(SimObject* obj)
@@ -543,4 +542,4 @@ void Marker::unpackUpdate(NetConnection* con, BitStream* stream)
    stream->readAffineTransform(&otow);
 
    setTransform(otow);
-}
+}