//-----------------------------------------------------------------------------
// Torque Game Engine 
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#include "dgl/dgl.h"
#include "console/consoleTypes.h"
#include "math/mathIO.h"
#include "math/mRandom.h"
#include "sceneGraph/sceneState.h"
#include "audio/audioDataBlock.h"
#include "game/fx/weatherLightning.h"

IMPLEMENT_CO_CLIENTEVENT_V1(WeatherLightningStrikeEvent);
IMPLEMENT_CO_DATABLOCK_V1(WeatherLightningData);
IMPLEMENT_CO_NETOBJECT_V1(WeatherLightning);

MRandomLCG sgRandomGen;

S32 QSORT_CALLBACK cmpWLSounds(const void* p1, const void* p2)
{
   U32 i1 = *((const S32*)p1);
   U32 i2 = *((const S32*)p2);

   if (i1 < i2) {
      return 1;
   } else if (i1 > i2) {
      return -1;
   } else {
      return 0;
   }
}

S32 QSORT_CALLBACK cmpWLTextures(const void* t1, const void* t2)
{
   StringTableEntry ta = *(StringTableEntry*)t1;
   StringTableEntry tb = *(StringTableEntry*)t2;
   
   if(ta && ta[0] != '\0')
   {
      if(tb && tb[0] != '\0')
         return dStricmp(ta, tb);
      else
         return -1;
   }
   else
   {
      if(tb && tb[0] != '\0')
         return 1;
      else
         return 0;
   }
}

WeatherLightningStrikeEvent::WeatherLightningStrikeEvent()
{
   mLightning = NULL;
}

WeatherLightningStrikeEvent::~WeatherLightningStrikeEvent()
{
   
}

void WeatherLightningStrikeEvent::pack(NetConnection* con, BitStream* stream)
{
   if(!mLightning)
   {
      stream->writeFlag(false);
      return;
   }
   
   S32 ghostIndex = con->getGhostIndex(mLightning);
   if(ghostIndex == -1)
   {
      stream->writeFlag(false);
      return;
   }
   
   stream->writeFlag(true);
   stream->writeRangedU32(U32(ghostIndex), 0, NetConnection::MaxGhostCount);
   stream->writeFloat(mStart.x, PositionalBits);
   stream->writeFloat(mStart.y, PositionalBits);
}

void WeatherLightningStrikeEvent::unpack(NetConnection* con, BitStream* stream)
{
   if(!stream->readFlag())
      return;
   S32 ghostIndex = stream->readRangedU32(0, NetConnection::MaxGhostCount);
   mLightning = NULL;
   NetObject* pObject = con->resolveGhost(ghostIndex);
   if(pObject)
      mLightning = dynamic_cast<WeatherLightning*>(pObject);

   mStart.x = stream->readFloat(PositionalBits);
   mStart.y = stream->readFloat(PositionalBits);
}

void WeatherLightningStrikeEvent::process(NetConnection*)
{
   if (mLightning)
      mLightning->processEvent(this);
}

//--------------------------------------------------------------------------

WeatherLightningData::WeatherLightningData()
{
   dMemset(strikeTextureNames, 0, sizeof(strikeTextureNames));
   dMemset(flashTextureNames,  0, sizeof(flashTextureNames));
   dMemset(fuzzyTextureNames,  0, sizeof(fuzzyTextureNames));
   
   dMemset(strikeTextures, 0, sizeof(strikeTextures));
   dMemset(flashTextures,  0, sizeof(flashTextures));
   dMemset(fuzzyTextures,  0, sizeof(fuzzyTextures));
   
   strikeSoundId = -1;
   strikeSound = NULL_AUDIOHANDLE;
   for(U32 i = 0; i < MaxSounds; i++)
   {
      thunderSoundIds[i] = -1;
      thunderSounds[i] = NULL_AUDIOHANDLE;
   }
}

WeatherLightningData::~WeatherLightningData()
{
   //
};

void WeatherLightningData::initPersistFields()
{
   Parent::initPersistFields();
   
   addField("strikeTextures", TypeFilename, Offset(strikeTextureNames, WeatherLightningData), MaxStrikeTextures);
   addField("flashTextures",  TypeFilename, Offset(flashTextureNames,  WeatherLightningData), MaxFlashTextures);
   addField("fuzzyTextures",  TypeFilename, Offset(fuzzyTextureNames,  WeatherLightningData), MaxFuzzyTextures);
   
   addField("strikeSound",    TypeAudioProfilePtr, Offset(strikeSound,   WeatherLightningData));
   addField("thunderSounds",  TypeAudioProfilePtr, Offset(thunderSounds, WeatherLightningData), MaxSounds);
}

bool WeatherLightningData::onAdd()
{
   if(!Parent::onAdd())
      return false;
   
   if(!strikeSound && strikeSoundId != -1)
   {
      if(Sim::findObject(strikeSoundId, strikeSound) == false)
         Con::errorf(ConsoleLogEntry::General, "WeatherLightningData::onAdd: Invalid packet, bad datablockId(sound: %d", strikeSound);
   }
   
   for(U32 i = 0; i < MaxSounds; i++)
   {
      if(!thunderSounds[i] && thunderSoundIds[i] != -1)
      {
         if(Sim::findObject(thunderSoundIds[i], thunderSounds[i]) == false)
            Con::errorf(ConsoleLogEntry::General, "WeahterLightningData::onAdd: Invalid packet, bad datablockId(sound: %d", thunderSounds[i]);
      }
   }
   
   return true;
}

bool WeatherLightningData::preload(bool server, char errorBuffer[256])
{
   if(Parent::preload(server, errorBuffer) == false)
      return false;
   
   dQsort(strikeTextureNames, MaxStrikeTextures, sizeof(StringTableEntry), cmpWLTextures);
   dQsort(flashTextureNames,  MaxFlashTextures,  sizeof(StringTableEntry), cmpWLTextures);
   dQsort(fuzzyTextureNames,  MaxFuzzyTextures,  sizeof(StringTableEntry), cmpWLTextures);

   if(!server) 
   {
      for(numStrikes = 0; numStrikes < MaxStrikeTextures; numStrikes++)
      {
         if(strikeTextureNames[numStrikes] && strikeTextureNames[numStrikes][0] != '\0')
            strikeTextures[numStrikes] = TextureHandle(strikeTextureNames[numStrikes], MeshTexture);
         else
            break;
      }     
      for(numFlashes = 0; numFlashes < MaxFlashTextures && flashTextureNames[numFlashes] != NULL; numFlashes++)
      {
         if(flashTextureNames[numFlashes] && flashTextureNames[numFlashes][0] != '\0')
            flashTextures[numFlashes] = TextureHandle(flashTextureNames[numFlashes], MeshTexture);
         else
            break;
      }
      for(numFuzzes = 0; numFuzzes < MaxFuzzyTextures && fuzzyTextureNames[numFuzzes] != NULL; numFuzzes++)
      {
         if(fuzzyTextureNames[numFuzzes] && fuzzyTextureNames[numFuzzes][0] != '\0')
            fuzzyTextures[numFuzzes] = TextureHandle(fuzzyTextureNames[numFuzzes], MeshTexture);
         else
            break;
      }
   }
   
   dQsort(thunderSounds, MaxSounds, sizeof(AudioProfile*), cmpWLSounds);
   for(numSounds = 0; numSounds < MaxSounds && thunderSounds[numSounds] != NULL_AUDIOHANDLE; numSounds++) {
      //
   }

   return true;
}

void WeatherLightningData::packData(BitStream* stream)
{
   Parent::packData(stream);

   U32 i;
   for (i = 0; i < MaxStrikeTextures; i++)
      stream->writeString(strikeTextureNames[i]);
   for(i = 0; i < MaxFlashTextures; i++)
      stream->writeString(flashTextureNames[i]);
   for(i = 0; i < MaxFuzzyTextures; i++)
      stream->writeString(fuzzyTextureNames[i]);
   
   if(stream->writeFlag(strikeSound != NULL_AUDIOHANDLE))
      stream->writeRangedU32(strikeSound->getId(), DataBlockObjectIdFirst,
                                                   DataBlockObjectIdLast);
   
   for(i = 0; i < MaxSounds; i++) 
   {
      if(stream->writeFlag(thunderSounds[i] != NULL_AUDIOHANDLE)) 
         stream->writeRangedU32(thunderSounds[i]->getId(), DataBlockObjectIdFirst,
                                                           DataBlockObjectIdLast);
   }
}

void WeatherLightningData::unpackData(BitStream* stream)
{
   Parent::unpackData(stream);

   U32 i;
   for(i = 0; i < MaxStrikeTextures; i++)
      strikeTextureNames[i] = stream->readSTString();
   for(i = 0; i < MaxFlashTextures; i++)
      flashTextureNames[i] = stream->readSTString();
   for(i = 0; i < MaxFuzzyTextures; i++)
      fuzzyTextureNames[i] = stream->readSTString();
     
   if(stream->readFlag())
      strikeSoundId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
   else
      strikeSoundId = -1;   
   
   for(i = 0; i < MaxSounds; i++)
   {
      if(stream->readFlag())
         thunderSoundIds[i] = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
      else
         thunderSoundIds[i] = -1;
   }
}

//--------------------------------------------------------------------------

WeatherLightning::WeatherLightning()
{
   mNetFlags.set(Ghostable | ScopeAlways);
   mTypeMask |= StaticObjectType|EnvironmentObjectType;
   
   lastThink = 0;
   strikesPerMinute = 9;
   boltDeathAge = 1.5;
}

WeatherLightning::~WeatherLightning()
{
   //
}

void WeatherLightning::initPersistFields()
{
   Parent::initPersistFields();
   
   addNamedField(strikesPerMinute, TypeS32, WeatherLightning);
   addNamedField(boltDeathAge,     TypeF32, WeatherLightning);
}

bool WeatherLightning::onAdd()
{
   if(!Parent::onAdd())
      return false;
   
   mObjBox.min.set( -0.5, -0.5, -0.5 );
   mObjBox.max.set(  0.5,  0.5,  0.5 );
   resetWorldBox();
   
   addToScene();
   return true;
}

void WeatherLightning::onRemove()
{
   while(mActiveBolts.size())
   {
      WeatherLightningBolt* bolt = mActiveBolts[0];
      delete bolt;
      mActiveBolts.erase_fast(U32(0));
   }
   while(mSoundEvents.size())
      mSoundEvents.erase_fast(U32(0));
   
   removeFromScene();
   Parent::onRemove();
}

bool WeatherLightning::onNewDataBlock(GameBaseData* dptr)
{
   mDataBlock = dynamic_cast<WeatherLightningData*>(dptr);
   if(!mDataBlock || !Parent::onNewDataBlock(dptr))
      return false;

   scriptOnNewDataBlock();
   return true;
}

bool WeatherLightning::prepRenderImage(SceneState* state, const U32 stateKey, const U32, const bool)
{
   if(isLastState(state, stateKey))
      return false;
   setLastState(state, stateKey);

   // This should be sufficient for most objects that don't manage zones, and
   //  don't need to return a specialized RenderImage...
   if(state->isObjectRendered(this))
   {
      SceneRenderImage* image = new SceneRenderImage;
      image->obj = this;
      image->isTranslucent = true;
      image->sortType = SceneRenderImage::EndSort;
      state->insertRenderImage(image);
   }

   return false;
}

void WeatherLightningBolt::render(const Point3F &camPos)
{
   Point3F perpVec;
   Point3F lightUp = startPoint - endPoint;
   mCross(camPos - endPoint, lightUp, &perpVec);
   perpVec.normalize();
   
   Point3F frontVec;
   mCross(perpVec, lightUp, &frontVec);
   frontVec.normalize();
   
   glEnable(GL_DEPTH_TEST);
   glDepthMask(GL_FALSE);
   glDepthFunc(GL_LEQUAL);
   
   //
   // strike texture
   //
   
      // setup alpha value
      F32 strikeAlpha;
      if(currentAge < (strikeTime / 3.0))
      {
         strikeAlpha = currentAge / (strikeTime / 3.0);
         strikeAlpha = mPow(strikeAlpha, F32(1.0 / 3.0));
      }
      else if(currentAge < (2.0 * strikeTime / 3.0))
         strikeAlpha = 1.0;
      else
         strikeAlpha = 1.0 - ((currentAge - (2.0 * strikeTime / 3.0)) / (strikeTime / 3.0));
      glColor4f(1.0f, 1.0f, 1.0f, strikeAlpha);
      
      // generate texture coords
      Point3F points[4];
      F32 width = ((startPoint.z - endPoint.z) * 0.125f);
      points[0] = startPoint - perpVec * width;
      points[1] = startPoint + perpVec * width;
      points[2] = endPoint   + perpVec * width;
      points[3] = endPoint   - perpVec * width;
      
      // bind and draw texture
      glBindTexture(GL_TEXTURE_2D, strikeTexture->getGLName());
      glBegin(GL_TRIANGLE_FAN);
         glTexCoord2f(0, 0); glVertex3fv(points[0]);
         glTexCoord2f(0, 1); glVertex3fv(points[1]);
         glTexCoord2f(1, 1); glVertex3fv(points[2]);
         glTexCoord2f(1, 0); glVertex3fv(points[3]);
         //glTexCoord2f(0, 0); glVertex3fv(points[0]);
         //glTexCoord2f(1, 0); glVertex3fv(points[1]);
         //glTexCoord2f(1, 1); glVertex3fv(points[2]);
         //glTexCoord2f(0, 1); glVertex3fv(points[3]);
      glEnd();
   
   //
   // fuzzy texture
   //
   
      // setup alpha value
      F32 constAlpha;
      if(currentAge < strikeTime / 2.0)
         constAlpha = currentAge / (strikeTime / 2.0);
      else if(currentAge < (2.0 * strikeTime / 3.0))
         constAlpha = 1.0 - ((currentAge - (strikeTime / 2.0)) / (strikeTime / 6.0));
      else
         constAlpha = 0.0;
      glColor4f(1.0, 1.0, 1.0, constAlpha);
      
      // generate texture coords
      width *= 4;
      points[0] = startPoint - perpVec * width;
      points[1] = startPoint + perpVec * width;
      points[2] = endPoint   + perpVec * width;
      points[3] = endPoint   - perpVec * width;
      
      if(constAlpha != 0.0)
      {
         // bind and draw texture
         glBindTexture(GL_TEXTURE_2D, fuzzyTexture->getGLName());
         glBegin(GL_TRIANGLE_FAN);
            glTexCoord2f(0, 0); glVertex3fv(points[0]);
            glTexCoord2f(0, 1); glVertex3fv(points[1]);
            glTexCoord2f(1, 1); glVertex3fv(points[2]);
            glTexCoord2f(1, 0); glVertex3fv(points[3]);
            //glTexCoord2f(0, 0); glVertex3fv(points[0]);
            //glTexCoord2f(1, 0); glVertex3fv(points[1]);
            //glTexCoord2f(1, 1); glVertex3fv(points[2]);
            //glTexCoord2f(0, 1); glVertex3fv(points[3]);
         glEnd();
         
         glDepthMask(GL_TRUE);
      }
   
   //
   // flash texture
   //
   
      // setup alpha value
      glColor4f(1.0f, 1.0f, 1.0f, strikeAlpha);
      
      // generate texture coords
      points[0] = startPoint - perpVec * width + frontVec * width;
      points[1] = startPoint - perpVec * width - frontVec * width;
      points[2] = startPoint + perpVec * width - frontVec * width;
      points[3] = startPoint + perpVec * width + frontVec * width;

      // bind and draw texture
      glBindTexture(GL_TEXTURE_2D, flashTexture->getGLName());
      glBegin(GL_TRIANGLE_FAN);
         glTexCoord2f(0, 0); glVertex3fv(points[0]);
         glTexCoord2f(0, 1); glVertex3fv(points[1]);
         glTexCoord2f(1, 1); glVertex3fv(points[2]);
         glTexCoord2f(1, 0); glVertex3fv(points[3]);
         //glTexCoord2f(0, 0); glVertex3fv(points[0]);
         //glTexCoord2f(1, 0); glVertex3fv(points[1]);
         //glTexCoord2f(1, 1); glVertex3fv(points[2]);
         //glTexCoord2f(0, 1); glVertex3fv(points[3]);
      glEnd();
      
   //
   // finished
   //
   glDepthMask(GL_TRUE);
}

void WeatherLightning::renderObject(SceneState* state, SceneRenderImage*)
{
   if(mActiveBolts.size())
   {
      AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry");

      RectI viewport;
      F64 farPlane;
      
      glMatrixMode(GL_PROJECTION);
      glPushMatrix();
      
      dglGetViewport(&viewport);
      farPlane = state->getFarPlane();
      const Point3F &camPos = state->getCameraPosition();
    
      // adjust far clip plane
      F64 distance = (getPosition() - camPos).lenSquared();
      state->setFarPlane(getMax(farPlane, distance));
      
      state->setupObjectProjection(this);

      glDisable(GL_CULL_FACE);
      glEnable(GL_TEXTURE_2D);
      glEnable(GL_BLEND);
      glBlendFunc(GL_SRC_ALPHA, GL_ONE);
      glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
         
      for(U32 i = 0; i < mActiveBolts.size(); i++)
         mActiveBolts[i]->render(camPos);

      glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
      glDisable(GL_BLEND);
      glDisable(GL_TEXTURE_2D);

      glMatrixMode(GL_PROJECTION);
      glPopMatrix();
      glMatrixMode(GL_MODELVIEW);
      
      state->setFarPlane(farPlane);
      dglSetViewport(viewport);

      AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit");
   }
}

void WeatherLightning::processTick(const Move *move)
{
   Parent::processTick(move);
   
   if (isServerObject()) 
   {
      S32 msBetweenStrikes = (S32)(60.0 / strikesPerMinute * 1000.0);
      
      lastThink += TickMs;
      if( lastThink > msBetweenStrikes )
      {
         strikeRandomPoint();
         lastThink -= msBetweenStrikes;
      }
   }
}

void WeatherLightning::advanceTime(F32 dt)
{
   Parent::advanceTime(dt);
   
   U32 i;
   
   // loop through and erase any dead bolts
   for(i = 0; i < mActiveBolts.size();)
   {
      WeatherLightningBolt* bolt = mActiveBolts[i];
      bolt->currentAge += dt;
      if(bolt->currentAge > bolt->deathAge)
      {
         delete bolt;
         mActiveBolts.erase_fast(i);
         continue;
      }
      
      i++;
   }
   
   // loop through and find any pending sound events
   for(i = 0; i < mSoundEvents.size();)
   {
      SoundEvent *sEvent = &mSoundEvents[i];
      sEvent->time -= dt;
      if(sEvent->time <= 0.0)
      {
         // fire off the sound
         if(sEvent->soundBlockId != -1)
            alxPlay(mDataBlock->thunderSounds[sEvent->soundBlockId], &sEvent->position);
         mSoundEvents.erase_fast(i);
         continue;
      }
      
      i++;
   } 
}

void WeatherLightning::strikeRandomPoint()
{  
   // choose random strike point within object bounds
   Point2F strikePoint;
   strikePoint.x = sgRandomGen.randF( 0.0, 1.0 );
   strikePoint.y = sgRandomGen.randF( 0.0, 1.0 );
   
   SimGroup* pClientGroup = Sim::getClientGroup();
   for (SimGroup::iterator itr = pClientGroup->begin(); itr != pClientGroup->end(); itr++)
   {
      NetConnection* nc = static_cast<NetConnection*>(*itr);

      WeatherLightningStrikeEvent* wlEvent = new WeatherLightningStrikeEvent;
      wlEvent->mLightning = this;
      wlEvent->mStart = strikePoint;

      nc->postNetEvent(wlEvent);
   }
}

TextureHandle* WeatherLightning::getRandomStrike()
{
   U32 strike = (U32)(mCeil(mDataBlock->numStrikes * sgRandomGen.randF()) - 1.0f);
   return &mDataBlock->strikeTextures[strike]; 
}

TextureHandle* WeatherLightning::getRandomFlash()
{
   U32 flash = (U32)(mCeil(mDataBlock->numFlashes * sgRandomGen.randF()) - 1.0f);
   return &mDataBlock->flashTextures[flash];
}

TextureHandle* WeatherLightning::getRandomFuzzy()
{
   U32 fuzzy = (U32)(mCeil(mDataBlock->numFuzzes * sgRandomGen.randF()) - 1.0f);
   return &mDataBlock->fuzzyTextures[fuzzy];
}

S32 WeatherLightning::getRandomSound()
{
   U32 sound = (U32)(mCeil(mDataBlock->numSounds * sgRandomGen.randF()) - 1.0f);
   if(mDataBlock->thunderSounds[sound] != NULL_AUDIOHANDLE)
      return sound;
   
   return -1;
}

void WeatherLightning::processEvent(WeatherLightningStrikeEvent* wlEvent)
{
   AssertFatal(wlEvent->mStart.x >= 0 && wlEvent->mStart.x <= 1.0, "Out of bounds coord!");

   mActiveBolts.push_back(new WeatherLightningBolt);
   WeatherLightningBolt* bolt = mActiveBolts.last();
   
   Point3F strikePoint(0.0, 0.0, 0.0);
   strikePoint.x = wlEvent->mStart.x;
   strikePoint.y = wlEvent->mStart.y;
   strikePoint *= mObjScale;
   strikePoint += getPosition();
   strikePoint += Point3F( -mObjScale.x * 0.5, -mObjScale.y * 0.5, 0.0 );

   RayInfo rayInfo;
   Point3F start = strikePoint;
   start.z = mObjScale.z * 0.5 + getPosition().z;
   strikePoint.z += -mObjScale.z * 0.5;
   bool rayHit = gClientContainer.castRay(start, strikePoint, (STATIC_COLLISION_MASK | WaterObjectType), &rayInfo);
   if(rayHit)
      strikePoint.z = rayInfo.point.z;
   
   F32 height = mObjScale.z * 0.5 + getPosition().z;
   
   bolt->startPoint = Point3F(strikePoint.x, strikePoint.y, height);
   bolt->endPoint   = strikePoint;
   
   bolt->currentAge       = 0.0f;
   bolt->deathAge         = boltDeathAge;
   bolt->strikeTime       = 0.35;
   
   bolt->strikeTexture = getRandomStrike();
   bolt->flashTexture  = getRandomFlash();
   bolt->fuzzyTexture  = getRandomFuzzy();
   
   // setup a thunder sound event
   Point3F listener;
   alxGetListenerPoint3F(AL_POSITION, &listener);
   
   mSoundEvents.increment();
   SoundEvent& sEvent = mSoundEvents.last();
   
   // find the length to the closest point on the bolt
   Point3F dHat = bolt->startPoint - bolt->endPoint;
   F32 boltLength = dHat.len();
   dHat /= boltLength;
   F32 distAlong = mDot((listener - bolt->endPoint), dHat);
   
   Point3F contactPoint;
   if(distAlong >= boltLength)
      contactPoint = bolt->startPoint;
   else if(distAlong <= 0.0)
      contactPoint = bolt->endPoint;
   else
      contactPoint = bolt->endPoint + dHat * distAlong;
      
   F32 delayDist = (listener - contactPoint).len();
   U32 delayTime = U32((delayDist / 330.0f) * 100.0f);
   
   MatrixF trans(true);
   trans.setPosition(contactPoint);
   
   sEvent.soundBlockId = getRandomSound();
   sEvent.position = trans;
   sEvent.time = delayTime;
   
   // play strike sound
   trans.setPosition(strikePoint);
   if(mDataBlock->strikeSound)
      alxPlay(mDataBlock->strikeSound, &trans);
}

U32 WeatherLightning::packUpdate(NetConnection *conn, U32 mask, BitStream *stream)
{
   U32 retMask = Parent::packUpdate(conn, mask, stream);

   // Only write data if this is the initial packet or we've been inspected.  
   if (stream->writeFlag(mask & (InitialUpdateMask | ExtendedInfoMask))) 
   {
      // Initial update
      mathWrite(*stream, getPosition());
      mathWrite(*stream, mObjScale);
   }

   return retMask;
}

void WeatherLightning::unpackUpdate(NetConnection *conn, BitStream *stream)
{
   Parent::unpackUpdate(conn, stream);

   if (stream->readFlag())
   {
      // Initial update
      Point3F pos;
      mathRead(*stream, &pos);
      setPosition( pos );

      mathRead(*stream, &mObjScale);
   }
}
