forestDataFile.cpp
Engine/source/forest/forestDataFile.cpp
Detailed Description
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 "platform/platform.h" 25#include "forest/forestDataFile.h" 26 27#include "forest/forest.h" 28#include "forest/forestCell.h" 29#include "T3D/physics/physicsBody.h" 30#include "core/stream/fileStream.h" 31#include "core/resource.h" 32#include "math/mathIO.h" 33#include "math/mPoint2.h" 34#include "platform/profiler.h" 35 36 37template<> ResourceBase::Signature Resource<ForestData>::signature() 38{ 39 return MakeFourCC('f','k','d','f'); 40} 41 42template<> 43void* Resource<ForestData>::create( const Torque::Path &path ) 44{ 45 FileStream stream; 46 stream.open( path.getFullPath(), Torque::FS::File::Read ); 47 if ( stream.getStatus() != Stream::Ok ) 48 return NULL; 49 50 ForestData *file = new ForestData(); 51 if ( !file->read( stream ) ) 52 { 53 delete file; 54 return NULL; 55 } 56 57 return file; 58} 59 60 61U32 ForestData::smNextItemId = 1; 62 63ForestData::ForestData() 64 : mIsDirty( false ) 65{ 66 ForestItemData::getReloadSignal().notify( this, &ForestData::_onItemReload ); 67} 68 69ForestData::~ForestData() 70{ 71 ForestItemData::getReloadSignal().remove( this, &ForestData::_onItemReload ); 72 clear(); 73} 74 75void ForestData::clear() 76{ 77 // We only have to delete the top level cells and ForestCell will 78 // clean up its sub-cells in its destructor. 79 80 BucketTable::Iterator iter = mBuckets.begin(); 81 for (; iter != mBuckets.end(); ++iter) delete iter->value; 82 mBuckets.clear(); 83 84 mIsDirty = true; 85} 86 87bool ForestData::read( Stream &stream ) 88{ 89 // Read our identifier... so we know we're 90 // not reading in pure garbage. 91 char id[4] = { 0 }; 92 stream.read( 4, id ); 93 if ( dMemcmp( id, "FKDF", 4 ) != 0 ) 94 { 95 Con::errorf( "ForestDataFile::read() - This is not a Forest planting file!" ); 96 return false; 97 } 98 99 // Empty ourselves before we really begin reading. 100 clear(); 101 102 // Now the version number. 103 U8 version; 104 stream.read( &version ); 105 if ( version > (U8)FILE_VERSION ) 106 { 107 Con::errorf( "ForestDataFile::read() - This file was created with an newer version of Forest!" ); 108 return false; 109 } 110 111 // Read in the names of the ForestItemData datablocks 112 // and recover the datablock. 113 Vector<ForestItemData*> allDatablocks; 114 U32 count; 115 stream.read( &count ); 116 allDatablocks.setSize( count ); 117 for ( U32 i=0; i < count; i++ ) 118 { 119 StringTableEntry name = stream.readSTString(); 120 ForestItemData* data = ForestItemData::find( name ); 121 122 // TODO: Change this to instead create a dummy forest data 123 // for each so that the user can swap it with the right one. 124 if ( data == NULL ) 125 { 126 Con::warnf( "ForestData::read - ForestItemData named %s was not found.", name ); 127 Con::warnf( "Note this can occur if you have deleted or renamed datablocks prior to loading this forest and is not an 'error' in this scenario." ); 128 } 129 130 allDatablocks[ i ] = data; 131 } 132 133 U8 dataIndex; 134 Point3F pos; 135 QuatF rot; 136 F32 scale; 137 ForestItemData* data; 138 MatrixF xfm; 139 140 U32 skippedItems = 0; 141 142 // Read in the items. 143 stream.read( &count ); 144 for ( U32 i=0; i < count; i++ ) 145 { 146 stream.read( &dataIndex ); 147 mathRead( stream, &pos ); 148 mathRead( stream, &rot ); 149 stream.read( &scale ); 150 151 data = allDatablocks[ dataIndex ]; 152 if ( data ) 153 { 154 rot.setMatrix( &xfm ); 155 xfm.setPosition( pos ); 156 157 addItem( smNextItemId++, data, xfm, scale ); 158 } 159 else 160 { 161 skippedItems++; 162 } 163 } 164 165 if ( skippedItems > 0 ) 166 Con::warnf( "ForestData::read - %i items were skipped because their datablocks were not found.", skippedItems ); 167 168 // Clear the dirty flag. 169 mIsDirty = false; 170 171 return true; 172} 173 174bool ForestData::write( const char *path ) 175{ 176 // Open the stream. 177 FileStream stream; 178 if ( !stream.open( path, Torque::FS::File::Write ) ) 179 { 180 Con::errorf( "ForestDataFile::write() - Failed opening stream!" ); 181 return false; 182 } 183 184 // Write our identifier... so we have a better 185 // idea if we're reading pure garbage. 186 stream.write( 4, "FKDF" ); 187 188 // Now the version number. 189 stream.write( (U8)FILE_VERSION ); 190 191 // First gather all the ForestItemData datablocks 192 // used by the items in the forest. 193 Vector<ForestItemData*> allDatablocks; 194 getDatablocks( &allDatablocks ); 195 196 // Write out the datablock list. 197 U32 count = allDatablocks.size(); 198 stream.write( count ); 199 for ( U32 i=0; i < count; i++ ) 200 { 201 StringTableEntry localName = allDatablocks[i]->getInternalName(); 202 AssertFatal( localName != NULL && localName[0] != '\0', "ForestData::write - ForestItemData had no internal name set!" ); 203 stream.writeString( allDatablocks[i]->getInternalName() ); 204 } 205 206 // Get a copy of all the items. 207 Vector<ForestItem> items; 208 getItems( &items ); 209 210 // Save the item count. 211 stream.write( (U32)items.size() ); 212 213 // Save the items. 214 Vector<ForestItem>::const_iterator iter = items.begin(); 215 for ( ; iter != items.end(); iter++ ) 216 { 217 U8 dataIndex = T3D::find( allDatablocks.begin(), allDatablocks.end(), iter->getData() ) - allDatablocks.begin(); 218 219 stream.write( dataIndex ); 220 221 mathWrite( stream, iter->getPosition() ); 222 223 QuatF quat; 224 quat.set( iter->getTransform() ); 225 mathWrite( stream, quat ); 226 227 stream.write( iter->getScale() ); 228 } 229 230 // Clear the dirty flag. 231 mIsDirty = false; 232 233 return true; 234} 235 236void ForestData::regenCells() 237{ 238 Vector<ForestItem> items; 239 getItems( &items ); 240 241 clear(); 242 243 for ( U32 i=0; i < items.size(); i++ ) 244 { 245 const ForestItem &item = items[i]; 246 addItem( item.getKey(), item.getData(), item.getTransform(), item.getScale() ); 247 } 248 249 mIsDirty = true; 250} 251 252ForestCell* ForestData::_findBucket( const Point2I &key ) const 253{ 254 BucketTable::ConstIterator iter = mBuckets.find( key ); 255 256 if ( iter != mBuckets.end() ) 257 return iter->value; 258 else 259 return NULL; 260} 261 262ForestCell* ForestData::_findOrCreateBucket( const Point3F &pos ) 263{ 264 // Look it up. 265 const Point2I key = _getBucketKey( pos ); 266 BucketTable::Iterator iter = mBuckets.find( key ); 267 268 ForestCell *bucket = NULL; 269 if ( iter != mBuckets.end() ) 270 bucket = iter->value; 271 else 272 { 273 bucket = new ForestCell( RectF( key.x, key.y, BUCKET_DIM, BUCKET_DIM ) ); 274 mBuckets.insertUnique( key, bucket ); 275 mIsDirty = true; 276 } 277 278 return bucket; 279} 280 281void ForestData::_onItemReload() 282{ 283 // Invalidate cell batches and bounds so they 284 // will be regenerated next render. 285 286 Vector<ForestCell*> stack; 287 getCells( &stack ); 288 289 ForestCell *pCell; 290 291 while ( !stack.empty() ) 292 { 293 pCell = stack.last(); 294 stack.pop_back(); 295 296 if ( !pCell ) 297 continue; 298 299 pCell->freeBatches(); 300 pCell->invalidateBounds(); 301 302 pCell->getChildren( &stack ); 303 } 304} 305 306const ForestItem& ForestData::addItem( ForestItemData *data, 307 const Point3F &position, 308 F32 rotation, 309 F32 scale ) 310{ 311 MatrixF xfm; 312 xfm.set( EulerF( 0, 0, rotation ), position ); 313 314 return addItem( smNextItemId++, 315 data, 316 xfm, 317 scale ); 318} 319 320const ForestItem& ForestData::addItem( ForestItemKey key, 321 ForestItemData *data, 322 const MatrixF &xfm, 323 F32 scale ) 324{ 325 ForestCell *bucket = _findOrCreateBucket( xfm.getPosition() ); 326 327 mIsDirty = true; 328 329 return bucket->insertItem( key, data, xfm, scale ); 330} 331 332const ForestItem& ForestData::updateItem( ForestItemKey key, 333 const Point3F &keyPosition, 334 ForestItemData *newData, 335 const MatrixF &newXfm, 336 F32 newScale ) 337{ 338 Point2I bucketKey = _getBucketKey( keyPosition ); 339 340 ForestCell *bucket = _findBucket( bucketKey ); 341 342 if ( !bucket || !bucket->removeItem( key, keyPosition, true ) ) 343 return ForestItem::Invalid; 344 345 if ( bucket->isEmpty() ) 346 { 347 delete bucket; 348 mBuckets.erase( bucketKey ); 349 } 350 351 return addItem( key, newData, newXfm, newScale ); 352} 353 354const ForestItem& ForestData::updateItem( ForestItem &item ) 355{ 356 return updateItem( item.getKey(), 357 item.getPosition(), 358 item.getData(), 359 item.getTransform(), 360 item.getScale() ); 361} 362 363bool ForestData::removeItem( ForestItemKey key, const Point3F &keyPosition ) 364{ 365 Point2I bucketkey = _getBucketKey( keyPosition ); 366 367 ForestCell *bucket = _findBucket( keyPosition ); 368 369 if ( !bucket || !bucket->removeItem( key, keyPosition, true ) ) 370 return false; 371 372 if ( bucket->isEmpty() ) 373 { 374 delete bucket; 375 mBuckets.erase( bucketkey ); 376 } 377 378 mIsDirty = true; 379 380 return true; 381} 382 383const ForestItem& ForestData::findItem( ForestItemKey key, const Point3F &keyPos ) const 384{ 385 PROFILE_SCOPE( ForestData_findItem ); 386 387 AssertFatal( key != 0, "ForestCell::findItem() - Got null key!" ); 388 389 ForestCell *cell = _findBucket( keyPos ); 390 391 while ( cell && !cell->isLeaf() ) 392 cell = cell->getChildAt( keyPos ); 393 394 U32 index; 395 if ( cell && cell->findIndexByKey( key, &index ) ) 396 return cell->getItems()[ index ]; 397 398 return ForestItem::Invalid; 399} 400 401const ForestItem& ForestData::findItem( ForestItemKey key ) const 402{ 403 PROFILE_SCOPE( ForestData_findItem_Slow ); 404 405 AssertFatal( key != 0, "ForestData::findItem() - Got null key!" ); 406 407 // Do an exhaustive search thru all the cells... this 408 // is really crappy... we shouldn't do this regularly. 409 410 Vector<const ForestCell*> stack; 411 BucketTable::ConstIterator iter = mBuckets.begin(); 412 for (; iter != mBuckets.end(); ++iter) 413 stack.push_back( iter->value ); 414 415 // Now loop till we run out of cells. 416 while ( !stack.empty() ) 417 { 418 // Pop off the next cell. 419 const ForestCell *cell = stack.last(); 420 stack.pop_back(); 421 422 // Recurse thru non-leaf cells. 423 if ( !cell->isLeaf() ) 424 { 425 cell->getChildren( &stack ); 426 continue; 427 } 428 429 // Finally search for the item. 430 U32 index; 431 if ( cell->findIndexByKey( key, &index ) ) 432 return cell->getItems()[ index ]; 433 } 434 435 return ForestItem::Invalid; 436} 437 438U32 ForestData::getItems( Vector<ForestItem> *outItems ) const 439{ 440 AssertFatal( outItems, "ForestData::getItems() - The output vector was NULL!" ); 441 442 PROFILE_SCOPE( ForestData_getItems ); 443 444 Vector<const ForestCell*> stack; 445 U32 count = 0; 446 447 BucketTable::ConstIterator iter = mBuckets.begin(); 448 for (; iter != mBuckets.end(); ++iter) 449 stack.push_back( iter->value ); 450 451 // Now loop till we run out of cells. 452 while ( !stack.empty() ) 453 { 454 // Pop off the next cell. 455 const ForestCell *cell = stack.last(); 456 stack.pop_back(); 457 458 // Recurse thru non-leaf cells. 459 if ( !cell->isLeaf() ) 460 { 461 cell->getChildren( &stack ); 462 continue; 463 } 464 465 // Get the items. 466 count += cell->getItems().size(); 467 outItems->merge( cell->getItems() ); 468 } 469 470 return count; 471} 472 473U32 ForestData::getItems( const Frustum &culler, Vector<ForestItem> *outItems ) const 474{ 475 AssertFatal( outItems, "ForestData::getItems() - The output vector was NULL!" ); 476 477 PROFILE_SCOPE( ForestData_getItems_ByFrustum ); 478 479 Vector<ForestCell*> stack; 480 getCells( &stack ); 481 Vector<ForestItem>::const_iterator iter; 482 U32 count = 0; 483 484 // Now loop till we run out of cells. 485 while ( !stack.empty() ) 486 { 487 // Pop off the next cell. 488 const ForestCell *cell = stack.last(); 489 stack.pop_back(); 490 491 if ( culler.isCulled( cell->getBounds() ) ) 492 continue; 493 494 // Recurse thru non-leaf cells. 495 if ( cell->isBranch() ) 496 { 497 cell->getChildren( &stack ); 498 continue; 499 } 500 501 // Get the items. 502 iter = cell->getItems().begin(); 503 for ( ; iter != cell->getItems().end(); iter++ ) 504 { 505 if ( !culler.isCulled( iter->getWorldBox() ) ) 506 { 507 outItems->merge( cell->getItems() ); 508 count++; 509 } 510 } 511 } 512 513 return count; 514} 515 516U32 ForestData::getItems( const Box3F &box, Vector<ForestItem> *outItems ) const 517{ 518 PROFILE_SCOPE( ForestData_getItems_ByBox ); 519 520 Vector<const ForestCell*> stack; 521 U32 count = 0; 522 523 BucketTable::ConstIterator iter = mBuckets.begin(); 524 for (; iter != mBuckets.end(); ++iter) 525 stack.push_back( iter->value ); 526 527 // Now loop till we run out of cells. 528 while ( !stack.empty() ) 529 { 530 // Pop off the next cell. 531 const ForestCell *cell = stack.last(); 532 stack.pop_back(); 533 534 // If the cell is empty or doesn't overlap the box... skip it. 535 if ( cell->isEmpty() || 536 !cell->getBounds().isOverlapped( box ) ) 537 continue; 538 539 // Recurse thru non-leaf cells. 540 if ( !cell->isLeaf() ) 541 { 542 cell->getChildren( &stack ); 543 continue; 544 } 545 546 // Finally look thru the items. 547 const Vector<ForestItem> &items = cell->getItems(); 548 Vector<ForestItem>::const_iterator item = items.begin(); 549 for ( ; item != items.end(); item++ ) 550 { 551 if ( item->getWorldBox().isOverlapped( box ) ) 552 { 553 // If we don't have an output vector then the user just 554 // wanted to know if any object existed... so early out. 555 if ( !outItems ) 556 return 1; 557 558 ++count; 559 outItems->push_back( *item ); 560 } 561 } 562 } 563 564 return count; 565} 566 567U32 ForestData::getItems( const Point3F &point, F32 radius, Vector<ForestItem> *outItems ) const 568{ 569 PROFILE_SCOPE( ForestData_getItems_BySphere ); 570 571 Vector<const ForestCell*> stack; 572 U32 count = 0; 573 574 BucketTable::ConstIterator iter = mBuckets.begin(); 575 for (; iter != mBuckets.end(); ++iter) 576 stack.push_back( iter->value ); 577 578 const F32 radiusSq = radius * radius; 579 580 // Now loop till we run out of cells. 581 while ( !stack.empty() ) 582 { 583 // Pop off the next cell. 584 const ForestCell *cell = stack.last(); 585 stack.pop_back(); 586 587 // TODO: If we could know here that the cell is fully within 588 // the sphere... we could do a fast gather of all its elements 589 // without any further testing of it or its children. 590 591 // If the cell is empty or doesn't overlap the sphere... skip it. 592 if ( cell->isEmpty() || 593 cell->getBounds().getSqDistanceToPoint( point ) > radiusSq ) 594 continue; 595 596 // Recurse thru non-leaf cells. 597 if ( !cell->isLeaf() ) 598 { 599 cell->getChildren( &stack ); 600 continue; 601 } 602 603 // Finally look thru the items. 604 const Vector<ForestItem> &items = cell->getItems(); 605 Vector<ForestItem>::const_iterator item = items.begin(); 606 for ( ; item != items.end(); item++ ) 607 { 608 if ( item->getWorldBox().getSqDistanceToPoint( point ) < radiusSq ) 609 { 610 // If we don't have an output vector then the user just 611 // wanted to know if any object existed... so early out. 612 if ( !outItems ) 613 return 1; 614 615 ++count; 616 outItems->push_back( *item ); 617 } 618 } 619 } 620 621 return count; 622} 623 624 625U32 ForestData::getItems( const Point2F &point, F32 radius, Vector<ForestItem> *outItems ) const 626{ 627 PROFILE_SCOPE( ForestData_getItems_ByCircle ); 628 629 Vector<const ForestCell*> stack; 630 U32 count = 0; 631 632 BucketTable::ConstIterator iter = mBuckets.begin(); 633 for (; iter != mBuckets.end(); ++iter) 634 stack.push_back( iter->value ); 635 636 const F32 radiusSq = radius * radius; 637 638 // Now loop till we run out of cells. 639 while ( !stack.empty() ) 640 { 641 // Pop off the next cell. 642 const ForestCell *cell = stack.last(); 643 stack.pop_back(); 644 645 // If the cell is empty or doesn't overlap the sphere... skip it. 646 if ( cell->isEmpty() || 647 cell->getRect().getSqDistanceToPoint( point ) > radiusSq ) 648 continue; 649 650 // Recurse thru non-leaf cells. 651 if ( !cell->isLeaf() ) 652 { 653 cell->getChildren( &stack ); 654 continue; 655 } 656 657 // Finally look thru the items. 658 const Vector<ForestItem> &items = cell->getItems(); 659 Vector<ForestItem>::const_iterator item = items.begin(); 660 F32 compareDist; 661 for ( ; item != items.end(); item++ ) 662 { 663 compareDist = mSquared( radius + item->getData()->mRadius ); 664 if ( item->getSqDistanceToPoint( point ) < compareDist ) 665 { 666 // If we don't have an output vector then the user just 667 // wanted to know if any object existed... so early out. 668 if ( !outItems ) 669 return 1; 670 671 ++count; 672 outItems->push_back( *item ); 673 } 674 } 675 } 676 677 return count; 678} 679 680U32 ForestData::getItems( const ForestItemData *data, Vector<ForestItem> *outItems ) const 681{ 682 AssertFatal( outItems, "ForestData::getItems() - The output vector was NULL!" ); 683 684 PROFILE_SCOPE( ForestData_getItems_ByDatablock ); 685 686 Vector<const ForestCell*> stack; 687 U32 count = 0; 688 689 BucketTable::ConstIterator iter = mBuckets.begin(); 690 for (; iter != mBuckets.end(); ++iter) 691 stack.push_back( iter->value ); 692 693 // Now loop till we run out of cells. 694 while ( !stack.empty() ) 695 { 696 // Pop off the next cell. 697 const ForestCell *cell = stack.last(); 698 stack.pop_back(); 699 700 // Recurse thru non-leaf cells. 701 if ( !cell->isLeaf() ) 702 { 703 cell->getChildren( &stack ); 704 continue; 705 } 706 707 // Get the items. 708 const Vector<ForestItem> &items = cell->getItems(); 709 Vector<ForestItem>::const_iterator item = items.begin(); 710 for ( ; item != items.end(); item++ ) 711 { 712 if ( item->getData() == data ) 713 { 714 ++count; 715 outItems->push_back( *item ); 716 } 717 } 718 } 719 720 return count; 721} 722 723void ForestData::getCells( const Frustum &frustum, Vector<ForestCell*> *outCells ) const 724{ 725 PROFILE_SCOPE( ForestData_getCells_frustum ); 726 727 BucketTable::ConstIterator iter = mBuckets.begin(); 728 for (; iter != mBuckets.end(); ++iter) 729 { 730 if ( !frustum.isCulled( iter->value->getBounds() ) ) 731 outCells->push_back( iter->value ); 732 } 733} 734 735void ForestData::getCells( Vector<ForestCell*> *outCells ) const 736{ 737 PROFILE_SCOPE( ForestData_getCells_nofrustum ); 738 739 BucketTable::ConstIterator iter = mBuckets.begin(); 740 for (; iter != mBuckets.end(); ++iter) 741 outCells->push_back( iter->value ); 742} 743 744U32 ForestData::getDatablocks( Vector<ForestItemData*> *outVector ) const 745{ 746 Vector<const ForestCell*> stack; 747 U32 count = 0; 748 749 BucketTable::ConstIterator iter = mBuckets.begin(); 750 for (; iter != mBuckets.end(); ++iter) 751 stack.push_back( iter->value ); 752 753 // Now loop till we run out of cells. 754 while ( !stack.empty() ) 755 { 756 // Pop off the next cell. 757 const ForestCell *cell = stack.last(); 758 stack.pop_back(); 759 760 // Recurse thru non-leaf cells. 761 if ( !cell->isLeaf() ) 762 { 763 cell->getChildren( &stack ); 764 continue; 765 } 766 767 // Go thru the items. 768 const Vector<ForestItem> &items = cell->getItems(); 769 Vector<ForestItem>::const_iterator item = items.begin(); 770 for ( ; item != items.end(); item++ ) 771 { 772 ForestItemData *data = item->getData(); 773 774 if (T3D::find( outVector->begin(), outVector->end(), data ) != outVector->end() ) 775 continue; 776 777 count++; 778 outVector->push_back( data ); 779 } 780 } 781 782 return count; 783} 784 785void ForestData::clearPhysicsRep( Forest *forest ) 786{ 787 Vector<ForestCell*> stack; 788 789 BucketTable::Iterator iter = mBuckets.begin(); 790 for (; iter != mBuckets.end(); ++iter) 791 stack.push_back( iter->value ); 792 793 // Now loop till we run out of cells. 794 while ( !stack.empty() ) 795 { 796 // Pop off the next cell. 797 ForestCell *cell = stack.last(); 798 stack.pop_back(); 799 800 // Recurse thru non-leaf cells. 801 if ( !cell->isLeaf() ) 802 { 803 cell->getChildren( &stack ); 804 continue; 805 } 806 807 cell->clearPhysicsRep( forest ); 808 } 809} 810 811void ForestData::buildPhysicsRep( Forest *forest ) 812{ 813 Vector<ForestCell*> stack; 814 815 BucketTable::Iterator iter = mBuckets.begin(); 816 for (; iter != mBuckets.end(); ++iter) 817 stack.push_back( iter->value ); 818 819 // Now loop till we run out of cells. 820 while ( !stack.empty() ) 821 { 822 // Pop off the next cell. 823 ForestCell *cell = stack.last(); 824 stack.pop_back(); 825 826 // Recurse thru non-leaf cells. 827 if ( !cell->isLeaf() ) 828 { 829 cell->getChildren( &stack ); 830 continue; 831 } 832 833 cell->buildPhysicsRep( forest ); 834 } 835} 836