pathManager.cpp
Engine/source/scene/pathManager.cpp
Classes:
class
Public Variables
bool
For frame signal.
Public Functions
ConsoleDocClass(PathManagerEvent , "@brief Class responsible <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> the registration, transmission , and management " "of paths on client and <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">server.\n\n</a>" "For internal use only, not intended <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> use in TorqueScript or game <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">development\n\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">internal\n</a>" )
DefineEngineFunction(clearClientPaths , void , () , "" )
DefineEngineFunction(clearServerPaths , void , () , "" )
Detailed Description
Public Variables
gClientPathManager
bool gEditingMission
For frame signal.
gServerPathManager
MODULE_END
MODULE_INIT
MODULE_SHUTDOWN
Public Functions
ConsoleDocClass(PathManagerEvent , "@brief Class responsible <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> the registration, transmission , and management " "of paths on client and <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">server.\n\n</a>" "For internal use only, not intended <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> use in TorqueScript or game <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">development\n\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">internal\n</a>" )
DefineEngineFunction(clearClientPaths , void , () , "" )
DefineEngineFunction(clearServerPaths , void , () , "" )
IMPLEMENT_CO_NETEVENT_V1(PathManagerEvent )
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24#include "gfx/gfxDevice.h" 25#include "scene/pathManager.h" 26#include "sim/netConnection.h" 27#include "core/stream/bitStream.h" 28#include "scene/simPath.h" 29#include "math/mathIO.h" 30#include "scene/sceneRenderState.h" 31#include "scene/sceneManager.h" 32#include "platform/profiler.h" 33#include "core/module.h" 34#include "console/engineAPI.h" 35 36extern bool gEditingMission; 37 38 39namespace { 40 41U32 countNumBits(U32 n) 42{ 43 U32 count = 0; 44 while (n != 0) { 45 n >>= 1; 46 count++; 47 } 48 49 return count ? count : 1; 50} 51 52} // namespace {} 53 54 55MODULE_BEGIN( PathManager ) 56 57 MODULE_INIT 58 { 59 AssertFatal(gClientPathManager == NULL && gServerPathManager == NULL, "Error, already initialized the path manager!"); 60 61 gClientPathManager = new PathManager(false); 62 gServerPathManager = new PathManager(true); 63 } 64 65 MODULE_SHUTDOWN 66 { 67 AssertFatal(gClientPathManager != NULL && gServerPathManager != NULL, "Error, path manager not initialized!"); 68 69 delete gClientPathManager; 70 gClientPathManager = NULL; 71 delete gServerPathManager; 72 gServerPathManager = NULL; 73 } 74 75MODULE_END; 76 77 78//-------------------------------------------------------------------------- 79//-------------------------------------- PathManagerEvent 80// 81class PathManagerEvent : public NetEvent 82{ 83 public: 84 U32 modifiedPath; 85 bool clearPaths; 86 PathManager::PathEntry path; 87 88 public: 89 typedef NetEvent Parent; 90 PathManagerEvent() : modifiedPath(0), clearPaths(false) { } 91 92 void pack(NetConnection*, BitStream*); 93 void write(NetConnection*, BitStream*); 94 void unpack(NetConnection*, BitStream*); 95 void process(NetConnection*); 96 97 DECLARE_CONOBJECT(PathManagerEvent); 98}; 99 100void PathManagerEvent::pack(NetConnection*, BitStream* stream) 101{ 102 // Write out the modified path... 103 stream->write(modifiedPath); 104 stream->writeFlag(clearPaths); 105 stream->write(path.totalTime); 106 stream->write(path.looping); 107 stream->write(path.positions.size()); 108 109 110 // This is here for safety. You can remove it if you want to try your luck at bigger sizes. -- BJG 111 AssertWarn(path.positions.size() < 1500/40, "Warning! Path size is pretty big - may cause packet overrun!"); 112 113 // Each one of these is about 8 floats and 2 ints 114 // so we'll say it's about 40 bytes in size, which is where the 40 in the above calc comes from. 115 for (U32 j = 0; j < path.positions.size(); j++) 116 { 117 mathWrite(*stream, path.positions[j]); 118 mathWrite(*stream, path.rotations[j]); 119 stream->write(path.msToNext[j]); 120 stream->write(path.smoothingType[j]); 121 } 122} 123 124void PathManagerEvent::write(NetConnection*nc, BitStream *stream) 125{ 126 pack(nc, stream); 127} 128 129void PathManagerEvent::unpack(NetConnection*, BitStream* stream) 130{ 131 // Read in the modified path... 132 133 stream->read(&modifiedPath); 134 clearPaths = stream->readFlag(); 135 stream->read(&path.totalTime); 136 stream->read(&path.looping); 137 138 U32 numPoints; 139 stream->read(&numPoints); 140 path.positions.setSize(numPoints); 141 path.rotations.setSize(numPoints); 142 path.msToNext.setSize(numPoints); 143 path.smoothingType.setSize(numPoints); 144 for (U32 j = 0; j < path.positions.size(); j++) 145 { 146 mathRead(*stream, &path.positions[j]); 147 mathRead(*stream, &path.rotations[j]); 148 stream->read(&path.msToNext[j]); 149 stream->read(&path.smoothingType[j]); 150 } 151} 152 153void PathManagerEvent::process(NetConnection*) 154{ 155 if (clearPaths) 156 { 157 // Clear out all the client's paths... 158 gClientPathManager->clearPaths(); 159 } 160 AssertFatal(modifiedPath <= gClientPathManager->mPaths.size(), "Error out of bounds path!"); 161 if (modifiedPath == gClientPathManager->mPaths.size()) { 162 PathManager::PathEntry *pe = new PathManager::PathEntry; 163 *pe = path; 164 gClientPathManager->mPaths.push_back(pe); 165 } 166 else 167 *(gClientPathManager->mPaths[modifiedPath]) = path; 168} 169 170IMPLEMENT_CO_NETEVENT_V1(PathManagerEvent); 171 172// Will be internalized once the @internal tag is working 173ConsoleDocClass( PathManagerEvent, 174 "@brief Class responsible for the registration, transmission, and management " 175 "of paths on client and server.\n\n" 176 177 "For internal use only, not intended for use in TorqueScript or game development\n\n" 178 179 "@internal\n" 180); 181 182//-------------------------------------------------------------------------- 183//-------------------------------------- PathManager Implementation 184// 185PathManager* gClientPathManager = NULL; 186PathManager* gServerPathManager = NULL; 187 188//-------------------------------------------------------------------------- 189PathManager::PathManager(const bool isServer) 190{ 191 VECTOR_SET_ASSOCIATION(mPaths); 192 193 mIsServer = isServer; 194} 195 196PathManager::~PathManager() 197{ 198 clearPaths(); 199} 200 201void PathManager::clearPaths() 202{ 203 for (U32 i = 0; i < mPaths.size(); i++) 204 delete mPaths[i]; 205 mPaths.setSize(0); 206#ifdef TORQUE_DEBUG 207 // This gets rid of the memory used by the vector. 208 // Prevents it from showing up in memory leak logs. 209 mPaths.compact(); 210#endif 211} 212 213DefineEngineFunction( clearServerPaths, void, ( ), , "") 214{ 215 gServerPathManager->clearPaths(); 216} 217 218DefineEngineFunction( clearClientPaths, void, ( ), , "") 219{ 220 gClientPathManager->clearPaths(); 221} 222 223//-------------------------------------------------------------------------- 224U32 PathManager::allocatePathId() 225{ 226 mPaths.increment(); 227 mPaths.last() = new PathEntry; 228 229 return (mPaths.size() - 1); 230} 231 232 233void PathManager::updatePath(const U32 id, 234 const Vector<Point3F>& positions, 235 const Vector<QuatF>& rotations, 236 const Vector<U32>& times, 237 const Vector<U32>& smoothingTypes, 238 const bool looping) 239{ 240 AssertFatal(mIsServer == true, "PathManager::updatePath: Error, must be called on the server side"); 241 AssertFatal(id < mPaths.size(), "PathManager::updatePath: error, id out of range"); 242 AssertFatal(positions.size() == times.size() && positions.size() == smoothingTypes.size(), "Error, times and positions must match!"); 243 244 PathEntry& rEntry = *mPaths[id]; 245 246 rEntry.positions = positions; 247 rEntry.rotations = rotations; 248 rEntry.msToNext = times; 249 rEntry.smoothingType = smoothingTypes; 250 rEntry.looping = looping; 251 252 rEntry.totalTime = 0; 253 for (S32 i = 0; i < S32(rEntry.msToNext.size()); i++) 254 rEntry.totalTime += rEntry.msToNext[i]; 255 256 transmitPath(id); 257} 258 259 260//-------------------------------------------------------------------------- 261void PathManager::transmitPaths(NetConnection* nc) 262{ 263 AssertFatal(mIsServer, "Error, cannot call transmitPaths on client path manager!"); 264 265 // Send over paths 266 for(S32 i = 0; i < mPaths.size(); i++) 267 { 268 PathManagerEvent* event = new PathManagerEvent; 269 event->clearPaths = (i == 0); 270 event->modifiedPath = i; 271 event->path = *(mPaths[i]); 272 nc->postNetEvent(event); 273 } 274} 275 276void PathManager::transmitPath(const U32 id) 277{ 278 AssertFatal(mIsServer, "Error, cannot call transmitNewPath on client path manager!"); 279 280 // Post to all active clients that have already received their paths... 281 // 282 SimGroup* pClientGroup = Sim::getClientGroup(); 283 for (SimGroup::iterator itr = pClientGroup->begin(); itr != pClientGroup->end(); itr++) { 284 NetConnection* nc = dynamic_cast<NetConnection*>(*itr); 285 if (nc && nc->missionPathsSent()) 286 { 287 // Transmit the updated path... 288 PathManagerEvent* event = new PathManagerEvent; 289 event->modifiedPath = id; 290 event->clearPaths = false; 291 event->path = *(mPaths[id]); 292 nc->postNetEvent(event); 293 } 294 } 295} 296 297void PathManager::getPathPosition(const U32 id, 298 const F64 msPosition, 299 Point3F& rPosition, 300 QuatF &rotation) 301{ 302 AssertFatal(isValidPath(id), "Error, this is not a valid path!"); 303 PROFILE_START(PathManGetPos); 304 305 // Ok, query holds our path information... 306 F64 ms = msPosition; 307 308 //Looping vs. non-looping splines 309 F32 msTotal; 310 if (mPaths[id]->looping) 311 msTotal = mPaths[id]->totalTime; 312 else 313 //total time minus last nodes time 314 msTotal = mPaths[id]->totalTime - mPaths[id]->msToNext[mPaths[id]->msToNext.size() - 1]; 315 316 if (ms > msTotal) 317 ms = msTotal; 318 319 S32 startNode = 0; 320 while (ms > mPaths[id]->msToNext[startNode]) { 321 ms -= mPaths[id]->msToNext[startNode]; 322 startNode++; 323 } 324 325 S32 endNode; 326 327 //Looping splines 328 if (mPaths[id]->looping) 329 endNode = (startNode + 1) % mPaths[id]->positions.size(); 330 //Non-looping splines 331 else 332 endNode = getMin(startNode + 1, mPaths[id]->positions.size() - 1); 333 334 335 Point3F& rStart = mPaths[id]->positions[startNode]; 336 Point3F& rEnd = mPaths[id]->positions[endNode]; 337 338 F64 interp = ms / F32(mPaths[id]->msToNext[startNode]); 339 if(mPaths[id]->smoothingType[startNode] == Marker::SmoothingTypeLinear) 340 { 341 rPosition = (rStart * (1.0 - interp)) + (rEnd * interp); 342 } 343 else if(mPaths[id]->smoothingType[startNode] == Marker::SmoothingTypeAccelerate) 344 { 345 interp = mSin(interp * M_PI - (M_PI / 2)) * 0.5 + 0.5; 346 rPosition = (rStart * (1.0 - interp)) + (rEnd * interp); 347 } 348 else if(mPaths[id]->smoothingType[startNode] == Marker::SmoothingTypeSpline) 349 { 350 S32 preStart = startNode - 1; 351 S32 postEnd = endNode + 1; 352 353 //Looping splines 354 if (mPaths[id]->looping) 355 { 356 if (postEnd >= mPaths[id]->positions.size()) 357 postEnd = 0; 358 if (preStart < 0) 359 preStart = mPaths[id]->positions.size() - 1; 360 } 361 //Non-looping splines 362 else 363 { 364 if (postEnd >= mPaths[id]->positions.size()) 365 postEnd = mPaths[id]->positions.size() - 1; 366 if (preStart < 0) 367 preStart = 0; 368 } 369 370 Point3F p0 = mPaths[id]->positions[preStart]; 371 Point3F p1 = rStart; 372 Point3F p2 = rEnd; 373 Point3F p3 = mPaths[id]->positions[postEnd]; 374 rPosition.x = mCatmullrom(interp, p0.x, p1.x, p2.x, p3.x); 375 rPosition.y = mCatmullrom(interp, p0.y, p1.y, p2.y, p3.y); 376 rPosition.z = mCatmullrom(interp, p0.z, p1.z, p2.z, p3.z); 377 } 378 rotation.interpolate( mPaths[id]->rotations[startNode], mPaths[id]->rotations[endNode], interp ); 379 PROFILE_END(); 380} 381 382U32 PathManager::getPathTotalTime(const U32 id) const 383{ 384 AssertFatal(isValidPath(id), "Error, this is not a valid path!"); 385 386 return mPaths[id]->totalTime; 387} 388 389U32 PathManager::getPathNumWaypoints(const U32 id) const 390{ 391 AssertFatal(isValidPath(id), "Error, this is not a valid path!"); 392 393 return mPaths[id]->positions.size(); 394} 395 396U32 PathManager::getWaypointTime(const U32 id, const U32 wayPoint) const 397{ 398 AssertFatal(isValidPath(id), "Error, this is not a valid path!"); 399 AssertFatal(wayPoint < getPathNumWaypoints(id), "Invalid waypoint!"); 400 401 U32 time = 0; 402 for (U32 i = 0; i < wayPoint; i++) 403 time += mPaths[id]->msToNext[i]; 404 405 return time; 406} 407 408U32 PathManager::getPathTimeBits(const U32 id) 409{ 410 AssertFatal(isValidPath(id), "Error, this is not a valid path!"); 411 412 return countNumBits(mPaths[id]->totalTime); 413} 414 415U32 PathManager::getPathWaypointBits(const U32 id) 416{ 417 AssertFatal(isValidPath(id), "Error, this is not a valid path!"); 418 419 return countNumBits(mPaths[id]->positions.size()); 420} 421 422 423bool PathManager::dumpState(BitStream* stream) const 424{ 425 stream->write(mPaths.size()); 426 427 for (U32 i = 0; i < mPaths.size(); i++) { 428 const PathEntry& rEntry = *mPaths[i]; 429 stream->write(rEntry.totalTime); 430 431 stream->write(rEntry.positions.size()); 432 for (U32 j = 0; j < rEntry.positions.size(); j++) { 433 mathWrite(*stream, rEntry.positions[j]); 434 stream->write(rEntry.msToNext[j]); 435 } 436 } 437 438 return stream->getStatus() == Stream::Ok; 439} 440 441bool PathManager::readState(BitStream* stream) 442{ 443 U32 i; 444 for (i = 0; i < mPaths.size(); i++) 445 delete mPaths[i]; 446 447 U32 numPaths; 448 stream->read(&numPaths); 449 mPaths.setSize(numPaths); 450 451 for (i = 0; i < mPaths.size(); i++) { 452 mPaths[i] = new PathEntry; 453 PathEntry& rEntry = *mPaths[i]; 454 455 stream->read(&rEntry.totalTime); 456 457 U32 numPositions; 458 stream->read(&numPositions); 459 rEntry.positions.setSize(numPositions); 460 rEntry.msToNext.setSize(numPositions); 461 for (U32 j = 0; j < rEntry.positions.size(); j++) { 462 mathRead(*stream, &rEntry.positions[j]); 463 stream->read(&rEntry.msToNext[j]); 464 } 465 } 466 467 return stream->getStatus() == Stream::Ok; 468} 469 470F64 PathManager::getClosestTimeToPoint(const U32 id, const Point3F p) 471{ 472 //Ubiq: Ideally this algorithm would work directly by finding roots. However, it's a 5th order 473 //polynomial (cannot be solved by radicals), so we're doing it iteratively instead! 474 475 //Steps: 476 //1) Termination condition: if the segment is shorter than L, return the midpoint 477 //2) Otherwise, divide the spline-segment into S sub-segments (S+1 points to test) 478 //2) Test these points against the given point and find the closest of them 479 //4) Recurse within the 2 segments surrounding the closest point 480 481 //NOTE: In a case where the spline comes near the point multiple times, the wrong local 482 //minima of the spline may be chosen early on and then refined, like polishing a turd :) 483 484 PROFILE_START(PathManager_getClosestTimeToPoint); 485 486 F64 totalTime; 487 if (mPaths[id]->looping) 488 totalTime = mPaths[id]->totalTime; 489 else 490 totalTime = mPaths[id]->totalTime - mPaths[id]->msToNext[mPaths[id]->msToNext.size() - 1]; 491 492 F64 ret = getClosestTimeToPoint(id, p, 0.0f, totalTime); 493 494 PROFILE_END(); 495 496 return ret; 497} 498 499F64 PathManager::getClosestTimeToPoint(const U32 id, const Point3F p, const F64 tMin, const F64 tMax) 500{ 501 F64 totalSize = tMax - tMin; 502 503 //termination condition 504 if (totalSize <= 25.0f) 505 return (tMin + tMax) / 2.0f; 506 507 U32 steps = getMax((F32)totalSize / 500.0f, 8.0f); 508 F64 stepSize = totalSize / steps; 509 510 F64 distBest = F32_MAX; 511 F64 tBest = 0; 512 513 for (U32 i = 0; i <= steps; i++) 514 { 515 F64 tTest = tMin + (stepSize * i); 516 Point3F pTest; QuatF dummy; 517 getPathPosition(id, tTest, pTest, dummy); 518 519 F64 dist = (pTest - p).lenSquared(); //no need for square root 520 if (dist < distBest) 521 { 522 tBest = tTest; 523 distBest = dist; 524 } 525 } 526 527 F64 tMinNew = tBest - stepSize; 528 F64 tMaxNew = tBest + stepSize; 529 if (mPaths[id]->looping) 530 { 531 while (tMinNew < 0) 532 { 533 tMinNew += getPathTotalTime(id); 534 tMaxNew += getPathTotalTime(id); 535 } 536 while (tMaxNew > getPathTotalTime(id)) 537 { 538 tMinNew -= getPathTotalTime(id); 539 tMaxNew -= getPathTotalTime(id); 540 } 541 } 542 else 543 { 544 tMinNew = getMax(tMin, tMinNew); 545 tMaxNew = getMin(tMax, tMaxNew); 546 } 547 return getClosestTimeToPoint(id, p, tMinNew, tMaxNew); 548} 549 550