terrFile.cpp

Engine/source/terrain/terrFile.cpp

More...

Public Functions

Detailed Description

Public Functions

calcDev(const PlaneF & pl, const Point3F & pt)

checkSquare(TerrainSquare * parent, const TerrainSquare * child)

getMinMax(U16 & inMin, U16 & inMax, U16 height)

getMostSignificantBit(U32 v)

Umax(U16 u1, U16 u2)

  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 "terrain/terrFile.h"
 26
 27#include "core/stream/fileStream.h"
 28#include "core/resourceManager.h"
 29#include "terrain/terrMaterial.h"
 30#include "gfx/gfxTextureHandle.h"
 31#include "gfx/bitmap/gBitmap.h"
 32#include "platform/profiler.h"
 33#include "math/mPlane.h"
 34
 35
 36template<>
 37void* Resource<TerrainFile>::create( const Torque::Path &path )
 38{
 39   return TerrainFile::load( path );
 40}
 41
 42template<> ResourceBase::Signature Resource<TerrainFile>::signature()
 43{
 44   return MakeFourCC('t','e','r','d');
 45}
 46
 47
 48TerrainFile::TerrainFile()
 49   : mSize( 256 ),
 50     mGridLevels(0),
 51     mFileVersion( FILE_VERSION ),
 52     mNeedsResaving( false )
 53{
 54   mLayerMap.setSize( mSize * mSize );
 55   dMemset( mLayerMap.address(), 0, mLayerMap.memSize() );
 56
 57   mHeightMap.setSize( mSize * mSize );
 58   dMemset( mHeightMap.address(), 0, mHeightMap.memSize() );
 59}
 60
 61TerrainFile::~TerrainFile()
 62{
 63}
 64
 65static U16 calcDev( const PlaneF &pl, const Point3F &pt )
 66{
 67   F32 z = (pl.d + pl.x * pt.x + pl.y * pt.y) / -pl.z;
 68   F32 diff = z - pt.z;
 69   if(diff < 0.0f)
 70      diff = -diff;
 71
 72   if(diff > 0xFFFF)
 73      return 0xFFFF;
 74   else
 75      return U16(diff);
 76}
 77
 78static U16 Umax( U16 u1, U16 u2 )
 79{
 80   return u1 > u2 ? u1 : u2;
 81}
 82
 83
 84inline U32 getMostSignificantBit( U32 v )
 85{
 86   U32 bit = 0;
 87
 88   while ( v >>= 1 )
 89     bit++;
 90
 91   return bit;
 92}
 93
 94void TerrainFile::_buildGridMap()
 95{
 96   // The grid level count is the same as the
 97   // most significant bit of the size.  While 
 98   // we loop we take the time to calculate the
 99   // grid memory pool size.
100   mGridLevels = 0;
101   U32 size = mSize;
102   U32 poolSize = size * size;
103   while ( size >>= 1 )
104   {
105      poolSize += size * size;
106      mGridLevels++;
107   }
108
109   mGridMapPool.setSize( poolSize ); 
110   mGridMapPool.compact();
111   mGridMap.setSize( mGridLevels + 1 );
112   mGridMap.compact();
113
114   // Assign memory from the pool to each grid level.
115   TerrainSquare *grid = mGridMapPool.address();
116   for ( S32 i = mGridLevels; i >= 0; i-- )
117   {
118      mGridMap[i] = grid;
119     grid += 1 << ( 2 * ( mGridLevels - i ) );
120   }
121
122   for( S32 i = mGridLevels; i >= 0; i-- )
123   {
124      S32 squareCount = 1 << ( mGridLevels - i );
125      S32 squareSize = mSize / squareCount;
126
127      for ( S32 squareX = 0; squareX < squareCount; squareX++ )
128      {
129         for ( S32 squareY = 0; squareY < squareCount; squareY++ )
130         {
131            U16 min = 0xFFFF;
132            U16 max = 0;
133            U16 mindev45 = 0;
134            U16 mindev135 = 0;
135
136            // determine max error for both possible splits.
137
138            const Point3F p1(0, 0, getHeight(squareX * squareSize, squareY * squareSize));
139            const Point3F p2(0, (F32)squareSize, getHeight(squareX * squareSize, squareY * squareSize + squareSize));
140            const Point3F p3((F32)squareSize, (F32)squareSize, getHeight(squareX * squareSize + squareSize, squareY * squareSize + squareSize));
141            const Point3F p4((F32)squareSize, 0, getHeight(squareX * squareSize + squareSize, squareY * squareSize));
142
143            // pl1, pl2 = split45, pl3, pl4 = split135
144            const PlaneF pl1(p1, p2, p3);
145            const PlaneF pl2(p1, p3, p4);
146            const PlaneF pl3(p1, p2, p4);
147            const PlaneF pl4(p2, p3, p4);
148
149            bool parentSplit45 = false;
150            TerrainSquare *parent = NULL;
151            if ( i < mGridLevels )
152            {
153               parent = findSquare( i+1, squareX * squareSize, squareY * squareSize );
154               parentSplit45 = parent->flags & TerrainSquare::Split45;
155            }
156
157            bool empty = true;
158            bool hasEmpty = false;
159
160            for ( S32 sizeX = 0; sizeX <= squareSize; sizeX++ )
161            {
162               for ( S32 sizeY = 0; sizeY <= squareSize; sizeY++ )
163               {
164                  S32 x = squareX * squareSize + sizeX;
165                  S32 y = squareY * squareSize + sizeY;
166
167                  if(sizeX != squareSize && sizeY != squareSize)
168                  {
169                     if ( !isEmptyAt( x, y ) )
170                        empty = false;
171                     else
172                        hasEmpty = true;
173                  }
174
175                  U16 ht = getHeight( x, y );
176                  if ( ht < min )
177                     min = ht;
178                  if( ht > max )
179                     max = ht;
180
181                  Point3F pt( (F32)sizeX, (F32)sizeY, (F32)ht );
182                  U16 dev;
183
184                  if(sizeX < sizeY)
185                     dev = calcDev(pl1, pt);
186                  else if(sizeX > sizeY)
187                     dev = calcDev(pl2, pt);
188                  else
189                     dev = Umax(calcDev(pl1, pt), calcDev(pl2, pt));
190
191                  if(dev > mindev45)
192                     mindev45 = dev;
193
194                  if(sizeX + sizeY < squareSize)
195                     dev = calcDev(pl3, pt);
196                  else if(sizeX + sizeY > squareSize)
197                     dev = calcDev(pl4, pt);
198                  else
199                     dev = Umax(calcDev(pl3, pt), calcDev(pl4, pt));
200
201                  if(dev > mindev135)
202                     mindev135 = dev;
203               }
204            }
205
206            TerrainSquare *sq = findSquare( i, squareX * squareSize, squareY * squareSize );
207            sq->minHeight = min;
208            sq->maxHeight = max;
209
210            sq->flags = empty ? TerrainSquare::Empty : 0;
211            if ( hasEmpty )
212               sq->flags |= TerrainSquare::HasEmpty;
213
214            bool shouldSplit45 = ((squareX ^ squareY) & 1) == 0;
215            bool split45;
216
217            //split45 = shouldSplit45;
218            if ( i == 0 )
219               split45 = shouldSplit45;
220            else if( i < 4 && shouldSplit45 == parentSplit45 )
221               split45 = shouldSplit45;
222            else
223               split45 = mindev45 < mindev135;
224
225            //split45 = shouldSplit45;
226            if(split45)
227            {
228               sq->flags |= TerrainSquare::Split45;
229               sq->heightDeviance = mindev45;
230            }
231            else
232               sq->heightDeviance = mindev135;
233
234            if( parent )
235               if (  parent->heightDeviance < sq->heightDeviance )
236                     parent->heightDeviance = sq->heightDeviance;
237         }
238      }
239   }
240
241   /*
242   for ( S32 y = 0; y < mSize; y += 2 )
243   {
244      for ( S32 x=0; x < mSize; x += 2 )
245      {
246         GridSquare *sq = findSquare(1, Point2I(x, y));
247         GridSquare *s1 = findSquare(0, Point2I(x, y));
248         GridSquare *s2 = findSquare(0, Point2I(x+1, y));
249         GridSquare *s3 = findSquare(0, Point2I(x, y+1));
250         GridSquare *s4 = findSquare(0, Point2I(x+1, y+1));
251         sq->flags |= (s1->flags | s2->flags | s3->flags | s4->flags) & ~(GridSquare::MaterialStart -1);
252      }
253   }
254   */
255}
256
257void TerrainFile::_initMaterialInstMapping()
258{
259   mMaterialInstMapping.clearMatInstList();
260   
261   for( U32 i = 0; i < mMaterials.size(); ++ i )
262   {
263      Torque::Path path( mMaterials[ i ]->getDiffuseMap() );
264      mMaterialInstMapping.push_back( path.getFileName() );
265   }
266   
267   mMaterialInstMapping.mapMaterials();
268}
269
270bool TerrainFile::save( const char *filename )
271{
272   FileStream stream;
273   stream.open( filename, Torque::FS::File::Write );
274   if ( stream.getStatus() != Stream::Ok )
275      return false;
276
277   stream.write( (U8)FILE_VERSION );
278
279   stream.write( mSize );
280
281   // Write out the height map.
282   for ( U32 i=0; i < mHeightMap.size(); i++)
283      stream.write( mHeightMap[i] );
284
285   // Write out the layer map.
286   for ( U32 i=0; i < mLayerMap.size(); i++)
287      stream.write( mLayerMap[i] );
288
289   // Write out the material names.
290   stream.write( (U32)mMaterials.size() );
291   for ( U32 i=0; i < mMaterials.size(); i++ )
292      stream.write( String( mMaterials[i]->getInternalName() ) );
293
294   return stream.getStatus() == FileStream::Ok;
295}
296
297TerrainFile* TerrainFile::load( const Torque::Path &path )
298{
299   FileStream stream;
300
301   stream.open( path.getFullPath(), Torque::FS::File::Read );
302   if ( stream.getStatus() != Stream::Ok )
303   {
304      Con::errorf( "Resource<TerrainFile>::create - could not open '%s'", path.getFullPath().c_str() );
305      return NULL;
306   }
307
308   U8 version;
309   stream.read(&version);
310   if (version > TerrainFile::FILE_VERSION)
311   {
312      Con::errorf( "Resource<TerrainFile>::create - file version '%i' is newer than engine version '%i'", version, TerrainFile::FILE_VERSION );
313      return NULL;
314   }
315
316   TerrainFile *ret = new TerrainFile;
317   ret->mFileVersion = version;
318   ret->mFilePath = path;
319
320   if ( version >= 7 )
321      ret->_load( stream );
322   else
323      ret->_loadLegacy( stream );
324
325   // Update the collision structures.
326   ret->_buildGridMap();
327   
328   // Do the material mapping.
329   ret->_initMaterialInstMapping();
330   
331   return ret;
332}
333
334void TerrainFile::_load( FileStream &stream )
335{
336   // NOTE: We read using a loop instad of in one large chunk
337   // because the stream will do endian conversions for us when
338   // reading one type at a time.
339
340   stream.read( &mSize );
341
342   // Load the heightmap.
343   mHeightMap.setSize( mSize * mSize );
344   for ( U32 i=0; i < mHeightMap.size(); i++ )
345      stream.read( &mHeightMap[i] );
346
347   // Load the layer index map.
348   mLayerMap.setSize( mSize * mSize );
349   for ( U32 i=0; i < mLayerMap.size(); i++ )
350      stream.read( &mLayerMap[i] );
351
352   // Get the material name count.
353   U32 materialCount;
354   stream.read( &materialCount );
355   Vector<String> materials;
356   materials.setSize( materialCount );
357
358   // Load the material names.
359   for ( U32 i=0; i < materialCount; i++ )
360      stream.read( &materials[i] );
361
362   // Resolve the TerrainMaterial objects from the names.
363   _resolveMaterials( materials );
364}
365
366void TerrainFile::_loadLegacy(  FileStream &stream )
367{
368   // Some legacy constants.
369   enum 
370   {
371      MaterialGroups = 8,
372      BlockSquareWidth = 256,
373   };
374
375   const U32 sampleCount = BlockSquareWidth * BlockSquareWidth;
376   mSize = BlockSquareWidth;
377
378   // Load the heightmap.
379   mHeightMap.setSize( sampleCount );
380   for ( U32 i=0; i < mHeightMap.size(); i++ )
381      stream.read( &mHeightMap[i] );
382
383   // Prior to version 7 we stored this weird material struct.
384   const U32 MATERIAL_GROUP_MASK = 0x7;
385   struct Material 
386   {
387      enum Flags 
388      {
389         Plain          = 0,
390         Rotate         = 1,
391         FlipX          = 2,
392         FlipXRotate    = 3,
393         FlipY          = 4,
394         FlipYRotate    = 5,
395         FlipXY         = 6,
396         FlipXYRotate   = 7,
397         RotateMask     = 7,
398         Empty          = 8,
399         Modified       = BIT(7),
400
401         // must not clobber TerrainFile::MATERIAL_GROUP_MASK bits!
402         PersistMask       = BIT(7)
403      };
404
405      U8 flags;
406      U8 index;
407   };
408
409   // Temp locals for loading before we convert to the new
410   // version 7+ format.
411   U8 baseMaterialMap[sampleCount] = { 0 };
412   U8 *materialAlphaMap[MaterialGroups] = { 0 };
413   Material materialMap[BlockSquareWidth * BlockSquareWidth];
414
415   // read the material group map and flags...
416   dMemset(materialMap, 0, sizeof(materialMap));
417
418   AssertFatal(!(Material::PersistMask & MATERIAL_GROUP_MASK),
419      "Doh! We have flag clobberage...");
420
421   for (S32 j=0; j < sampleCount; j++)
422   {
423      U8 val;
424      stream.read(&val);
425
426      //
427      baseMaterialMap[j] = val & MATERIAL_GROUP_MASK;
428      materialMap[j].flags = val & Material::PersistMask;
429   }
430
431   // Load the material names.
432   Vector<String> materials;
433   for ( U32 i=0; i < MaterialGroups; i++ )
434   {
435      String matName;
436      stream.read( &matName );
437      if ( matName.isEmpty() )
438         continue;
439
440      if ( mFileVersion > 3 && mFileVersion < 6 )
441      {
442         // Between version 3 and 5 we store the texture file names 
443         // relative to the terrain file.  We restore the full path
444         // here so that we can create a TerrainMaterial from it.
445         materials.push_back( Torque::Path::CompressPath( mFilePath.getRoot() + mFilePath.getPath() + '/' + matName ) );
446      }
447      else
448         materials.push_back( matName );
449   }
450
451   if ( mFileVersion <= 3 )
452   {
453      GFXTexHandle terrainMat;
454      Torque::Path matRelPath;
455
456      // Try to automatically fix up our material file names
457      for (U32 i = 0; i < materials.size(); i++)
458      {
459         if ( materials[i].isEmpty() )
460            continue;
461            
462         terrainMat.set( materials[i], &GFXTexturePersistentSRGBProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ) );
463         if ( terrainMat )
464            continue;
465
466         matRelPath = materials[i];
467
468         String path = matRelPath.getPath();
469
470         String::SizeType n = path.find( '/', 0, String::NoCase );
471         if ( n != String::NPos )
472         {
473            matRelPath.setPath( String(Con::getVariable( "$defaultGame" )) + path.substr( n, path.length() - n ) );
474
475            terrainMat.set( matRelPath, &GFXTexturePersistentSRGBProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ) );
476            if ( terrainMat )
477            {
478               materials[i] = matRelPath.getFullPath();
479               mNeedsResaving = true;
480            }
481         }
482         
483      } // for (U32 i = 0; i < TerrainBlock::MaterialGroups; i++)
484      
485   } // if ( mFileVersion <= 3 )
486
487   if ( mFileVersion == 1 )
488   {
489      for( S32 j = 0; j < sampleCount; j++ )
490      {
491         if ( materialAlphaMap[baseMaterialMap[j]] == NULL )
492         {
493            materialAlphaMap[baseMaterialMap[j]] = new U8[sampleCount];
494            dMemset(materialAlphaMap[baseMaterialMap[j]], 0, sampleCount);
495         }
496
497         materialAlphaMap[baseMaterialMap[j]][j] = 255;
498      }
499   }
500   else
501   {
502      for( S32 k=0; k < materials.size(); k++ )
503      {
504         AssertFatal(materialAlphaMap[k] == NULL, "Bad assumption.  There should be no alpha map at this point...");
505         materialAlphaMap[k] = new U8[sampleCount];
506         stream.read(sampleCount, materialAlphaMap[k]);
507      }
508   }
509
510   // Throw away the old texture and heightfield scripts.
511   if ( mFileVersion >= 3 )
512   {
513      U32 len;
514      stream.read(&len);
515      char *textureScript = (char *)dMalloc(len + 1);
516      stream.read(len, textureScript);
517      dFree( textureScript );
518
519      stream.read(&len);
520      char *heightfieldScript = (char *)dMalloc(len + 1);
521      stream.read(len, heightfieldScript);
522      dFree( heightfieldScript );
523   }
524
525   // Load and throw away the old edge terrain paths.
526   if ( mFileVersion >= 5 )
527   {
528      stream.readSTString(true);
529      stream.readSTString(true);
530   }
531
532   U32 layerCount = materials.size() - 1;
533
534   // Ok... time to convert all this mess to the layer index map!
535   for ( U32 i=0; i < sampleCount; i++ )
536   {
537      // Find the greatest layer.
538      U32 layer = 0;
539      U32 lastValue = 0;
540      for ( U32 k=0; k < MaterialGroups; k++ )
541      {
542         if ( materialAlphaMap[k] && materialAlphaMap[k][i] > lastValue )
543         {
544            layer = k;
545            lastValue = materialAlphaMap[k][i];
546         }
547      }
548
549      // Set the layer index.   
550      mLayerMap[i] = getMin( layer, layerCount );
551   }
552   
553   // Cleanup.
554   for ( U32 i=0; i < MaterialGroups; i++ )
555      delete [] materialAlphaMap[i];
556
557   // Force resaving on these old file versions.
558   //mNeedsResaving = false;
559
560   // Resolve the TerrainMaterial objects from the names.
561   _resolveMaterials( materials );
562}
563
564void TerrainFile::_resolveMaterials( const Vector<String> &materials )
565{
566   mMaterials.clear();
567
568   for ( U32 i=0; i < materials.size(); i++ )
569      mMaterials.push_back( TerrainMaterial::findOrCreate( materials[i] ) );
570
571   // If we didn't get any materials then at least
572   // add a warning material so we will render.
573   if ( mMaterials.empty() )
574      mMaterials.push_back( TerrainMaterial::getWarningMaterial() );
575}
576
577void TerrainFile::setSize( U32 newSize, bool clear )
578{
579   // Make sure the resolution is a power of two.
580   newSize = getNextPow2( newSize );
581
582   // 
583   if ( clear )
584   {
585      mLayerMap.setSize( newSize * newSize );
586      mLayerMap.compact();
587      dMemset( mLayerMap.address(), 0, mLayerMap.memSize() );
588
589      // Initialize the elevation to something above
590      // zero so that we have room to excavate by default.
591      U16 elev = floatToFixed( 512.0f );
592
593      mHeightMap.setSize( newSize * newSize );
594      mHeightMap.compact();
595      for ( U32 i = 0; i < mHeightMap.size(); i++ )
596         mHeightMap[i] = elev;
597   }
598   else
599   {
600      // We're resizing here!
601
602
603
604   }
605
606   mSize = newSize;
607
608   _buildGridMap();
609}
610
611void TerrainFile::smooth( F32 factor, U32 steps, bool updateCollision )
612{
613   const U32 blockSize = mSize * mSize;
614
615   // Grab some temp buffers for our smoothing results.
616   Vector<F32> h1, h2;
617   h1.setSize( blockSize );
618   h2.setSize( blockSize );
619
620   // Fill the first buffer with the current heights.   
621   for ( U32 i=0; i < blockSize; i++ )
622      h1[i] = (F32)mHeightMap[i];
623
624   // factor of 0.0 = NO Smoothing
625   // factor of 1.0 = MAX Smoothing
626   const F32 matrixM = 1.0f - getMax(0.0f, getMin(1.0f, factor));
627   const F32 matrixE = (1.0f-matrixM) * (1.0f/12.0f) * 2.0f;
628   const F32 matrixC = matrixE * 0.5f;
629
630   // Now loop for our interations.
631   F32 *src = h1.address();
632   F32 *dst = h2.address();
633   for ( U32 s=0; s < steps; s++ )
634   {
635      for ( S32 y=0; y < mSize; y++ )
636      {
637         for ( S32 x=0; x < mSize; x++ )
638         {
639            F32 samples[9];
640
641            S32 c = 0;
642            for (S32 i = y-1; i < y+2; i++)
643               for (S32 j = x-1; j < x+2; j++)
644               {
645                  if ( i < 0 || j < 0 || i >= mSize || j >= mSize )
646                     samples[c++] = src[ x + ( y * mSize ) ];
647                  else
648                     samples[c++] = src[ j + ( i * mSize ) ];
649               }
650
651            //  0  1  2
652            //  3 x,y 5
653            //  6  7  8
654
655            dst[ x + ( y * mSize ) ] =
656               ((samples[0]+samples[2]+samples[6]+samples[8]) * matrixC) +
657               ((samples[1]+samples[3]+samples[5]+samples[7]) * matrixE) +
658               (samples[4] * matrixM);
659         }
660      }
661
662      // Swap!
663      F32 *tmp = dst;
664      dst = src;
665      src = tmp;
666   }
667
668   // Copy the results back to the height map.
669   for ( U32 i=0; i < blockSize; i++ )
670      mHeightMap[i] = (U16)mCeil( (F32)src[i] );
671
672   if ( updateCollision )
673      _buildGridMap();
674}
675
676void TerrainFile::setHeightMap( const Vector<U16> &heightmap, bool updateCollision )
677{
678   AssertFatal( mHeightMap.size() == heightmap.size(), "TerrainFile::setHeightMap - Incorrect heightmap size!" );
679   dMemcpy( mHeightMap.address(), heightmap.address(), mHeightMap.size() ); 
680
681   if ( updateCollision )
682      _buildGridMap();
683}
684
685void TerrainFile::import(  const GBitmap &heightMap, 
686                           F32 heightScale,
687                           const Vector<U8> &layerMap, 
688                           const Vector<String> &materials,
689                           bool flipYAxis )
690{
691   AssertFatal( heightMap.getWidth() == heightMap.getHeight(), "TerrainFile::import - Height map is not square!" );
692   AssertFatal( isPow2( heightMap.getWidth() ), "TerrainFile::import - Height map is not power of two!" );
693
694   const U32 newSize = heightMap.getWidth();
695   if ( newSize != mSize )
696   {
697      mHeightMap.setSize( newSize * newSize );
698      mHeightMap.compact();
699      mSize = newSize;
700   }
701
702   // Convert the height map to heights.
703   U16 *oBits = mHeightMap.address();
704   if ( heightMap.getFormat() == GFXFormatL16)
705   {
706      const F32 toFixedPoint = ( 1.0f / (F32)U16_MAX ) * floatToFixed( heightScale );
707      const U16 *iBits = (const U16*)heightMap.getBits();
708      if ( flipYAxis )
709      {
710         for ( U32 i = 0; i < mSize * mSize; i++ )
711         {
712            U16 height = convertBEndianToHost( *iBits );
713            *oBits = (U16)mCeil( (F32)height * toFixedPoint );
714            ++oBits;
715            ++iBits;
716         }
717      }
718      else
719      {
720         for(S32 y = mSize - 1; y >= 0; y--) {
721            for(U32 x = 0; x < mSize; x++) {
722               U16 height = convertBEndianToHost( *iBits );
723               mHeightMap[x + y * mSize] = (U16)mCeil( (F32)height * toFixedPoint );
724               ++iBits;
725            }
726         }
727      }
728   }
729   else
730   {
731      const F32 toFixedPoint = ( 1.0f / (F32)U8_MAX ) * floatToFixed( heightScale );
732      const U8 *iBits = heightMap.getBits();
733      if ( flipYAxis )
734      {
735         for ( U32 i = 0; i < mSize * mSize; i++ )
736         {
737            *oBits = (U16)mCeil( ((F32)*iBits) * toFixedPoint );
738            ++oBits;
739            iBits += heightMap.getBytesPerPixel();
740         }
741      }
742      else
743      {
744         for(S32 y = mSize - 1; y >= 0; y--) {
745            for(U32 x = 0; x < mSize; x++) {
746               mHeightMap[x + y * mSize] = (U16)mCeil( ((F32)*iBits) * toFixedPoint );
747               iBits += heightMap.getBytesPerPixel();
748            }
749         }
750      }
751   }
752
753   // Copy over the layer map.
754   AssertFatal( layerMap.size() == mHeightMap.size(), "TerrainFile::import - Layer map is the wrong size!" );
755   mLayerMap = layerMap;
756   mLayerMap.compact();
757
758   // Resolve the materials.
759   _resolveMaterials( materials );
760
761   // Rebuild the collision grid map.
762   _buildGridMap();
763}
764
765
766void TerrainFile::create(  String *inOutFilename, 
767                           U32 newSize, 
768                           const Vector<String> &materials )
769{
770   // Determine the path and basename
771   Torque::Path basePath( *inOutFilename );
772   if ( !basePath.getExtension().equal("ter") )
773   {
774      // Use the default path and filename
775      String terrainDirectory( Con::getVariable( "$pref::Directories::Terrain" ) );
776      if ( terrainDirectory.isEmpty() )
777      {
778         terrainDirectory = "data/terrains";
779      }
780      basePath.setPath( terrainDirectory );
781      basePath.setFileName( "terrain" );
782   }
783
784   // Construct a default file name
785   (*inOutFilename) = Torque::FS::MakeUniquePath( basePath.getRootAndPath(), basePath.getFileName(), "ter" );
786
787   // Create the file
788   TerrainFile *file = new TerrainFile;
789
790   for ( U32 i=0; i < materials.size(); i++ )
791      file->mMaterials.push_back( TerrainMaterial::findOrCreate( materials[i] ) );
792
793   file->setSize( newSize, true );
794   file->save( *inOutFilename );
795
796   delete file;
797}
798
799inline void getMinMax( U16 &inMin, U16 &inMax, U16 height )
800{
801   if ( height < inMin )
802      inMin = height;
803   if ( height > inMax )
804      inMax = height;
805}
806
807inline void checkSquare( TerrainSquare *parent, const TerrainSquare *child )
808{
809   if(parent->minHeight > child->minHeight)
810      parent->minHeight = child->minHeight;
811   if(parent->maxHeight < child->maxHeight)
812      parent->maxHeight = child->maxHeight;
813
814   if ( child->flags & (TerrainSquare::Empty | TerrainSquare::HasEmpty) )
815      parent->flags |= TerrainSquare::HasEmpty;
816}
817
818void TerrainFile::updateGrid( const Point2I &minPt, const Point2I &maxPt )
819{
820   // here's how it works:
821   // for the current terrain renderer we only care about
822   // the minHeight and maxHeight on the GridSquare
823   // so we do one pass through, updating minHeight and maxHeight
824   // on the level 0 squares, then we loop up the grid map from 1 to
825   // the top, expanding the bounding boxes as necessary.
826   // this should end up being way, way, way, way faster for the terrain
827   // editor
828
829   PROFILE_SCOPE( TerrainFile_UpdateGrid );
830
831   for ( S32 y = minPt.y - 1; y < maxPt.y + 1; y++ )
832   {
833      for ( S32 x = minPt.x - 1; x < maxPt.x + 1; x++ )
834      {
835         S32 px = x;
836         S32 py = y;
837         if ( px < 0 )
838            px += mSize;
839         if ( py < 0 )
840            py += mSize;
841
842         TerrainSquare *sq = findSquare( 0, px, py );
843         sq->minHeight = 0xFFFF;
844         sq->maxHeight = 0;
845
846         // Update the empty state.
847         if ( isEmptyAt( x, y ) )
848            sq->flags |= TerrainSquare::Empty;
849         else
850            sq->flags &= ~<a href="/coding/class/structterrainsquare/">TerrainSquare</a>::Empty;
851
852         getMinMax( sq->minHeight, sq->maxHeight, getHeight( x, y ) );
853         getMinMax( sq->minHeight, sq->maxHeight, getHeight( x+1, y ) );
854         getMinMax( sq->minHeight, sq->maxHeight, getHeight( x, y+1 ) );
855         getMinMax( sq->minHeight, sq->maxHeight, getHeight( x+1, y+1 ) );
856      }
857   }
858
859   // ok, all the level 0 grid squares are updated:
860   // now update all the parent grid squares that need to be updated:
861   for( S32 level = 1; level <= mGridLevels; level++ )
862   {
863      S32 size = 1 << level;
864      S32 halfSize = size >> 1;  
865
866      for( S32 y = (minPt.y - 1) >> level; y < (maxPt.y + size) >> level; y++ )
867      {
868         for ( S32 x = (minPt.x - 1) >> level; x < (maxPt.x + size) >> level; x++ )
869         {
870            S32 px = x << level;
871            S32 py = y << level;
872
873            TerrainSquare *sq = findSquare(level, px, py);
874            sq->minHeight = 0xFFFF;
875            sq->maxHeight = 0;
876            sq->flags &= ~( TerrainSquare::Empty | TerrainSquare::HasEmpty );
877
878            checkSquare( sq, findSquare( level - 1, px, py ) );
879            checkSquare( sq, findSquare( level - 1, px + halfSize, py ) );
880            checkSquare( sq, findSquare( level - 1, px, py + halfSize ) );
881            checkSquare( sq, findSquare( level - 1, px + halfSize, py + halfSize ) );
882         }
883      }
884   }
885}
886