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

#include "map2dif/entityTypes.h"
#include "map2dif/tokenizer.h"
#include "map2dif/csgBrush.h"
#include "interior/interior.h"
#include "map2dif/morianUtil.h"
#include "dgl/gBitmap.h"

DoorEntity *gCurrentDoor = NULL;
U32 gTriggerId = 0;

void parseBrushList(Vector<CSGBrush*>& brushes, Tokenizer* pToker, EditGeometry& geom)
{
   // Ok, this is where it gets interesting.  We have to parse in the brushes, and place
   //  them in our list before we return to the editGeometry.  I'm copying almost all this
   //  code from the parseMapFile function in editGeometry.cc
   //
   while (pToker->getToken()[0] == '{') {
      pToker->advanceToken(true);

      brushes.push_back(gBrushArena.allocateBrush());
      CSGBrush& rBrush = *brushes.last();

      if (!parseBrush (rBrush, pToker, geom)) return;

      rBrush.disambiguate();
      pToker->advanceToken(true);
   }
}


//------------------------------------------------------------------------------
WorldSpawnEntity::WorldSpawnEntity()
{
   mDetailNumber     = 0;
   mMinPixels        = 250;

   mGeometryScale    = 32.0f;
   mLumelScale       = 32.0f;

   mAmbientColor.set(0,0,0);
   mEmergencyAmbientColor.set(0,0,0);

   mWadPrefix[0] = '\0';
}

WorldSpawnEntity::~WorldSpawnEntity()
{
   
}

bool WorldSpawnEntity::parseEntityDescription(Tokenizer* pToker)
{
   // Tokenizer comes in pointing to the classname.  We'll check that, just to be
   //  thorough.
   AssertFatal(pToker->tokenICmp(getClassName()),
               avar("Wrong class name? (expected %s, got %s)", getClassName(), pToker->getToken()));

   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {
      if (pToker->tokenICmp("detail_number")) {
         pToker->advanceToken(false, true);
         mDetailNumber = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("min_pixels")) {
         pToker->advanceToken(false, true);
         mMinPixels = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("geometry_scale")) {
         pToker->advanceToken(false, true);
         mGeometryScale = dAtof(pToker->getToken());
         AssertISV(isPow2(U32(mGeometryScale)), avar("Error in: %s.  Geometry scale must be a power of 2", pToker->getFileName()));
      }
      else if (pToker->tokenICmp("light_geometry_scale")) {
         pToker->advanceToken(false, true);
         mLumelScale = dAtof(pToker->getToken());
         AssertISV(isPow2(U32(mGeometryScale)), avar("Error in: %s.  Geometry scale must be a power of 2", pToker->getFileName()));
      }
      else if (pToker->tokenICmp("ambient_color")) {
         pToker->advanceToken(false, true);
         dSscanf(pToker->getToken(), "%f %f %f", &mAmbientColor.red, &mAmbientColor.green, &mAmbientColor.blue);
         mAmbientColor.red   /= 255.0f;
         mAmbientColor.green /= 255.0f;
         mAmbientColor.blue  /= 255.0f;
      } 
      else if (pToker->tokenICmp("emergency_ambient_color")) {
         pToker->advanceToken(false, true);
         dSscanf(pToker->getToken(), "%f %f %f", &mEmergencyAmbientColor.red, &mEmergencyAmbientColor.green, &mEmergencyAmbientColor.blue);
         mEmergencyAmbientColor.red   /= 255.0f;
         mEmergencyAmbientColor.green /= 255.0f;
         mEmergencyAmbientColor.blue  /= 255.0f;
      } else if (pToker->tokenICmp("wad")) {
         // Only need to parse the wad for T2 compatability
         char buffer[1024];
         pToker->advanceToken(false, true);
         dStrcpy(buffer, pToker->getToken());

         if (char* semi = dStrchr(buffer, ';'))  {
            dPrintf("\n  *** More than one WAD file assigned to this map\n"
                    "  *** The map may not load correctly\n");
            *semi = 0;
         }

         char* pSlash = dStrrchr(buffer, '\\');
         AssertFatal(pSlash != NULL, "Error, there should be a slash in there somewhere!");

         U32 pos = 0;
         while (pSlash[pos + 1] != '.') {
            mWadPrefix[pos] = pSlash[pos + 1];
            pos++;
         }
         mWadPrefix[pos++] = '/';
         mWadPrefix[pos] = '\0';
      }
      else {
         pToker->advanceToken(false);  // For keyless entity fields...
      }

      pToker->advanceToken(true);
   }

   return true;
}

BrushType WorldSpawnEntity::getBrushType()
{
   return StructuralBrush;
}

const char* WorldSpawnEntity::getClassName()
{
   return "worldspawn";
}

bool WorldSpawnEntity::isPointClass() const
{
   return false;
}

const char* WorldSpawnEntity::getName() const
{
   static char bogoChar = '\0';
   return &bogoChar;
}
const Point3D& WorldSpawnEntity::getOrigin() const
{
   static Point3D bogoPoint(0, 0, 0);
   return bogoPoint;
}


//------------------------------------------------------------------------------
DetailEntity::DetailEntity()
{

}

DetailEntity::~DetailEntity()
{
   
}

bool DetailEntity::parseEntityDescription(Tokenizer* pToker)
{
   // Tokenizer comes in pointing to the classname.  We'll check that, just to be
   //  thorough.
   AssertFatal(pToker->tokenICmp(getClassName()),
               avar("Wrong class name? (expected %s, got %s)", getClassName(), pToker->getToken()));

   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {
      pToker->advanceToken(false);  // For keyless entity fields...
      pToker->advanceToken(true);
   }

   return true;
}

BrushType DetailEntity::getBrushType()
{
   return DetailBrush;
}

const char* DetailEntity::getClassName()
{
   return "detail";
}

bool DetailEntity::isPointClass() const
{
   return false;
}

const char* DetailEntity::getName() const
{
   static char bogoChar = '\0';
   return &bogoChar;
}
const Point3D& DetailEntity::getOrigin() const
{
   static Point3D bogoPoint(0, 0, 0);
   return bogoPoint;
}


//------------------------------------------------------------------------------
CollisionEntity::CollisionEntity()
{

}

CollisionEntity::~CollisionEntity()
{
   
}

bool CollisionEntity::parseEntityDescription(Tokenizer* pToker)
{
   // Tokenizer comes in pointing to the classname.  We'll check that, just to be
   //  thorough.
   AssertFatal(pToker->tokenICmp(getClassName()),
               avar("Wrong class name? (expected %s, got %s)", getClassName(), pToker->getToken()));

   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {
      pToker->advanceToken(false);  // For keyless entity fields...
      pToker->advanceToken(true);
   }

   return true;
}

BrushType CollisionEntity::getBrushType()
{
   return CollisionBrush;
}

const char* CollisionEntity::getClassName()
{
   return "collision";
}

bool CollisionEntity::isPointClass() const
{
   return false;
}

const char* CollisionEntity::getName() const
{
   static char bogoChar = '\0';
   return &bogoChar;
}
const Point3D& CollisionEntity::getOrigin() const
{
   static Point3D bogoPoint(0, 0, 0);
   return bogoPoint;
}


//------------------------------------------------------------------------------
VehicleCollisionEntity::VehicleCollisionEntity()
{

}

VehicleCollisionEntity::~VehicleCollisionEntity()
{
   
}

bool VehicleCollisionEntity::parseEntityDescription(Tokenizer* pToker)
{
   // Tokenizer comes in pointing to the classname.  We'll check that, just to be
   //  thorough.
   AssertFatal(pToker->tokenICmp(getClassName()),
               avar("Wrong class name? (expected %s, got %s)", getClassName(), pToker->getToken()));

   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {
      pToker->advanceToken(false);  // For keyless entity fields...
      pToker->advanceToken(true);
   }

   return true;
}

BrushType VehicleCollisionEntity::getBrushType()
{
   return VehicleCollisionBrush;
}

const char* VehicleCollisionEntity::getClassName()
{
   return "vehicle_collision";
}

bool VehicleCollisionEntity::isPointClass() const
{
   return false;
}

const char* VehicleCollisionEntity::getName() const
{
   static char bogoChar = '\0';
   return &bogoChar;
}
const Point3D& VehicleCollisionEntity::getOrigin() const
{
   static Point3D bogoPoint(0, 0, 0);
   return bogoPoint;
}


//------------------------------------------------------------------------------
PortalEntity::PortalEntity()
{
   passAmbientLight = false;
}

PortalEntity::~PortalEntity()
{
   
}

bool PortalEntity::parseEntityDescription(Tokenizer* pToker)
{
   // Tokenizer comes in pointing to the classname.  We'll check that, just to be
   //  thorough.
   AssertFatal(pToker->tokenICmp(getClassName()),
               avar("Wrong class name? (expected %s, got %s)", getClassName(), pToker->getToken()));

   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {
      if (pToker->tokenICmp("ambient_light")) {
         pToker->advanceToken(false, true);
         passAmbientLight = dAtoi(pToker->getToken()) == 0 ? false : true;
      } else {
         pToker->advanceToken(false);  // For keyless entity fields...
      }

      pToker->advanceToken(true);
   }

   return true;
}

BrushType PortalEntity::getBrushType()
{
   return PortalBrush;
}

const char* PortalEntity::getClassName()
{
   return "portal";
}

bool PortalEntity::isPointClass() const
{
   return false;
}

const char* PortalEntity::getName() const
{
   static char bogoChar = '\0';
   return &bogoChar;
}
const Point3D& PortalEntity::getOrigin() const
{
   static Point3D bogoPoint(0, 0, 0);
   return bogoPoint;
}


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

TargetEntity::TargetEntity()
{
   mTargetName[0] = '\0';
   mOrigin.set(0,0,0);
}

TargetEntity::~TargetEntity()
{
   //
}

bool TargetEntity::parseEntityDescription(Tokenizer* pToker)
{
   // Tokenizer comes in pointing to the classname.  We'll check that, just to be
   //  thorough.
   AssertFatal(pToker->tokenICmp(getClassName()),
               avar("Wrong class name? (expected %s, got %s)", getClassName(), pToker->getToken()));

   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {

      if (pToker->tokenICmp("origin")) {
         pToker->advanceToken(false, true);
         dSscanf(pToker->getToken(), "%lf %lf %lf", &mOrigin.x, &mOrigin.y, &mOrigin.z);
      }
      else if (pToker->tokenICmp("name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 256, avar("Error: %s is an invalid target name.  Must be less than 256", pToker->getToken()));
         dStrcpy(mTargetName, pToker->getToken());
      }
      else {
         pToker->advanceToken(false);  // For keyless entity fields...
      }

      pToker->advanceToken(true);
   }

   return true;
}

BrushType TargetEntity::getBrushType()
{
   AssertISV(false, "Brushes cannot be associated with targets!");
   return DetailBrush;
}

//------------------------------------------------------------------------------
//-------------------------------------- LIGHT TYPES...
//
BaseLightEmitterEntity::BaseLightEmitterEntity()
{
   mTargetLight[0] = '\0';
   mStateIndex  = 0;
   mOrigin.set(0,0,0);
}

BrushType BaseLightEmitterEntity::getBrushType()
{
   AssertISV(false, "Brushes cannot be associated with lights!");
   return DetailBrush;
}

//------------------------------------------------------------------------------
// Light Emitters
//------------------------------------------------------------------------------

PointEmitterEntity::PointEmitterEntity()
{
   mFalloffType = BaseLightEmitterEntity::Linear;
   mFalloff1 = 10;
   mFalloff2 = 100;
   mFalloff3 = 0;
}

bool PointEmitterEntity::parseEntityDescription(Tokenizer* pToker)
{
   // Tokenizer comes in pointing to the classname.  We'll check that, just to be
   //  thorough.
   AssertFatal(pToker->tokenICmp(getClassName()),
               avar("Wrong class name? (expected %s, got %s)", getClassName(), pToker->getToken()));

   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {
      if (pToker->tokenICmp("origin")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%lf %lf %lf", &mOrigin.x, &mOrigin.y, &mOrigin.z);
      }
      else if (pToker->tokenICmp("target")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 256, avar("Error: %s is an invalid target name.  Must be less than 256", pToker->getToken()));
         dStrcpy(mTargetLight, pToker->getToken());
      }
      else if (pToker->tokenICmp("state_index")) {
         pToker->advanceToken(false, true);
         mStateIndex = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("falloff_type")) {
         pToker->advanceToken(false, true);
         mFalloffType = (dAtoi(pToker->getToken()) == 0) ? BaseLightEmitterEntity::Distance :
                                                           BaseLightEmitterEntity::Linear;
      }
      else if (pToker->tokenICmp("falloff1")) {
         pToker->advanceToken(false, true);
         mFalloff1 = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("falloff2")) {
         pToker->advanceToken(false, true);
         mFalloff2 = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("falloff3")) {
         pToker->advanceToken(false, true);
         mFalloff3 = dAtoi(pToker->getToken());
      }
      else {
         pToker->advanceToken(false);  // For keyless entity fields...
      }

      pToker->advanceToken(true);
   }

   return true;
}

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

SpotEmitterEntity::SpotEmitterEntity()
{
   mFalloffType = BaseLightEmitterEntity::Linear;
   mDirection.set(0,0,-1);
   mFalloff1   = 10;
   mFalloff2   = 100;
   mFalloff3   = 0;
   mInnerAngle = 0.2;
   mOuterAngle = 0.4;
}

bool SpotEmitterEntity::parseEntityDescription(Tokenizer* pToker)
{
   // Tokenizer comes in pointing to the classname.  We'll check that, just to be
   //  thorough.
   AssertFatal(pToker->tokenICmp(getClassName()),
               avar("Wrong class name? (expected %s, got %s)", getClassName(), pToker->getToken()));

   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {
      if (pToker->tokenICmp("origin")) {
         pToker->advanceToken(false, true);
         dSscanf(pToker->getToken(), "%lf %lf %lf", &mOrigin.x, &mOrigin.y, &mOrigin.z);
      }
      else if (pToker->tokenICmp("target")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 256, avar("Error: %s is an invalid target name.  Must be less than 256", pToker->getToken()));
         dStrcpy(mTargetLight, pToker->getToken());
      }
      else if (pToker->tokenICmp("state_index")) {
         pToker->advanceToken(false, true);
         mStateIndex = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("falloff_type")) {
         pToker->advanceToken(false, true);
         mFalloffType = (dAtoi(pToker->getToken()) == 0) ? BaseLightEmitterEntity::Distance :
                                                           BaseLightEmitterEntity::Linear;
      }
      else if (pToker->tokenICmp("falloff1")) {
         pToker->advanceToken(false, true);
         mFalloff1 = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("falloff2")) {
         pToker->advanceToken(false, true);
         mFalloff2 = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("falloff3")) {
         pToker->advanceToken(false, true);
         mFalloff3 = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("direction")) {
         pToker->advanceToken(false, true);
         dSscanf(pToker->getToken(), "%lf %lf %lf", &mDirection.x, &mDirection.y, &mDirection.z);
      }
      else if (pToker->tokenICmp("theta")) {
         pToker->advanceToken(false, true);
         mInnerAngle = dAtof(pToker->getToken());
      }
      else if (pToker->tokenICmp("phi")) {
         pToker->advanceToken(false, true);
         mOuterAngle = dAtof(pToker->getToken());
      }
      else {
         pToker->advanceToken(false);  // For keyless entity fields...
      }

      pToker->advanceToken(true);
   }

   return true;
}

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

BaseLightEntity::BaseLightEntity()
{
   mFlags         = Interior::AnimationAmbient | Interior::AnimationLoop;
   mAlarmStatus   = NormalOnly;
   mOrigin.set(0,0,0);
   mLightName[0]  = '\0';
}

BrushType BaseLightEntity::getBrushType()
{
   AssertISV(false, "Brushes cannot be associated with lights!");
   return DetailBrush;
}

//------------------------------------------------------------------------------
// Light
//------------------------------------------------------------------------------

LightEntity::LightEntity()
{
   mNumStates     = 0;
   dMemset(mStates, 0, sizeof(mStates));
}

bool LightEntity::parseEntityDescription(Tokenizer* pToker)
{
   // Tokenizer comes in pointing to the classname.  We'll check that, just to be
   //  thorough.
   AssertFatal(pToker->tokenICmp(getClassName()),
               avar("Wrong class name? (expected %s, got %s)", getClassName(), pToker->getToken()));

   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {

      if (pToker->tokenICmp("origin")) {
         pToker->advanceToken(false, true);
         dSscanf(pToker->getToken(), "%lf %lf %lf", &mOrigin.x, &mOrigin.y, &mOrigin.z);
      }
      else if (pToker->tokenICmp("name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 256, avar("Error: %s is an invalid target name.  Must be less than 256", pToker->getToken()));
         dStrcpy(mLightName, pToker->getToken());
      }
      else if (pToker->tokenICmp("spawnflags")) {
         pToker->advanceToken(false, true);
         mFlags = dAtoi(pToker->getToken()) & Interior::AnimationTypeMask;
      }
      else if (pToker->tokenICmp("alarm_type")) {
         pToker->advanceToken(false, true);
         mAlarmStatus = (AlarmStatus)dAtoi(pToker->getToken());
      }
      else if(dStrncmp(pToker->getToken(), "state", 5))
      {
         char buffer[Tokenizer::MaxTokenSize];
         dStrcpy(buffer, pToker->getToken() + 5);
   
         pToker->advanceToken(false, true);

         // parse the number
         char * end = dStrchr(buffer, '_');
         if(!end)
         {
            pToker->advanceToken(true);
            continue;
         }
         *end++ = '\0';
         S32 stateNum = dAtoi(buffer);
         if(stateNum < 0 || stateNum > StateInfo::MaxStates)
         {
            pToker->advanceToken(true);
            continue;
         }

         // read in the value
         if(!dStricmp(end, "duration"))
         {
            mStates[stateNum].mDuration = dAtof(pToker->getToken());
            mStates[stateNum].mFlags |= StateInfo::DurationValid;
         }
         else if(!dStricmp(end, "color"))
         {
            dSscanf(pToker->getToken(), "%f %f %f", 
               &mStates[stateNum].mColor.red,
               &mStates[stateNum].mColor.green,
               &mStates[stateNum].mColor.blue);
            
            mStates[stateNum].mColor.red /= 255.0f;
            mStates[stateNum].mColor.green /= 255.0f;
            mStates[stateNum].mColor.blue /= 255.0f;

            mStates[stateNum].mFlags |= StateInfo::ColorValid;
         }
      }
      else {
         pToker->advanceToken(false);  // For keyless entity fields...
      }

      pToker->advanceToken(true);
   }
   
   // check the state info
   if(!mNumStates)
      return(false);

   for(U32 i = 0; i < mNumStates; i++)
      if(!(mStates[i].mFlags & StateInfo::Valid))
         return(false);

   return true;
}

//------------------------------------------------------------------------------
// static stock lights
//------------------------------------------------------------------------------

LightOmniEntity::LightOmniEntity()
{
   mColor.set(1,1,1);
   mFalloff1 = 10;
   mFalloff2 = 100;
}

bool LightOmniEntity::parseEntityDescription(Tokenizer* pToker)
{
   // Tokenizer comes in pointing to the classname.  We'll check that, just to be
   //  thorough.
   AssertFatal(pToker->tokenICmp(getClassName()),
               avar("Wrong class name? (expected %s, got %s)", getClassName(), pToker->getToken()));

   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {

      if (pToker->tokenICmp("origin")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%lf %lf %lf", &mOrigin.x, &mOrigin.y, &mOrigin.z);
      }
      else if (pToker->tokenICmp("name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 256, avar("Error: %s is an invalid target name.  Must be less than 256", pToker->getToken()));
         dStrcpy(mLightName, pToker->getToken());
      }
      else if (pToker->tokenICmp("color")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%f %f %f", &mColor.red, &mColor.green, &mColor.blue);
         mColor.red   /= 255.0f;
         mColor.green /= 255.0f;
         mColor.blue  /= 255.0f;
      }
      else if (pToker->tokenICmp("alarm_type")) {
         pToker->advanceToken(false, true);
         mAlarmStatus = (AlarmStatus)dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("falloff1")) {
         pToker->advanceToken(false, true);
         mFalloff1 = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("falloff2")) {
         pToker->advanceToken(false, true);
         mFalloff2 = dAtoi(pToker->getToken());
      }
      else {
         pToker->advanceToken(false);  // For keyless entity fields...
      }

      pToker->advanceToken(true);
   }

   return true;
}

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

LightSpotEntity::LightSpotEntity()
{
   mTarget[0]     = '\0';
   mColor.set(1,1,1);
   mInnerDistance = 10;
   mOuterDistance = 100;
   mFalloff1      = 10;
   mFalloff2      = 100;
}

bool LightSpotEntity::parseEntityDescription(Tokenizer * pToker)
{
   // Tokenizer comes in pointing to the classname.  We'll check that, just to be
   //  thorough.
   AssertFatal(pToker->tokenICmp(getClassName()),
               avar("Wrong class name? (expected %s, got %s)", getClassName(), pToker->getToken()));

   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {

      if (pToker->tokenICmp("origin")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%lf %lf %lf", &mOrigin.x, &mOrigin.y, &mOrigin.z);
      }
      else if (pToker->tokenICmp("name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 256, avar("Error: %s is an invalid target name.  Must be less than 256", pToker->getToken()));
         dStrcpy(mLightName, pToker->getToken());
      }
      else if (pToker->tokenICmp("target")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 256, avar("Error: %s is an invalid target name.  Must be less than 256", pToker->getToken()));
         dStrcpy(mTarget, pToker->getToken());
      }
      else if (pToker->tokenICmp("color")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%f %f %f", &mColor.red, &mColor.green, &mColor.blue);
         mColor.red   /= 255.0f;
         mColor.green /= 255.0f;
         mColor.blue  /= 255.0f;
      }
      else if (pToker->tokenICmp("alarm_type")) {
         pToker->advanceToken(false, true);
         mAlarmStatus = (AlarmStatus)dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("falloff1")) {
         pToker->advanceToken(false, true);
         mFalloff1 = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("falloff2")) {
         pToker->advanceToken(false, true);
         mFalloff2 = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("distance1")) {
         pToker->advanceToken(false, true);
         mInnerDistance = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("distance2")) {
         pToker->advanceToken(false, true);
         mOuterDistance = dAtoi(pToker->getToken());
      }
      else {
         pToker->advanceToken(false);  // For keyless entity fields...
      }

      pToker->advanceToken(true);
   }

   return true;
}

//------------------------------------------------------------------------------
// animated stock lignts
//------------------------------------------------------------------------------

LightStrobeEntity::LightStrobeEntity()
{
   mSpeed      = Normal;
   mColor1.set(0,0,0);
   mColor2.set(1,1,1);
   mFalloff1   = 10;
   mFalloff2   = 100;
}

bool LightStrobeEntity::parseEntityDescription(Tokenizer* pToker)
{
   // Tokenizer comes in pointing to the classname.  We'll check that, just to be
   //  thorough.
   AssertFatal(pToker->tokenICmp(getClassName()),
               avar("Wrong class name? (expected %s, got %s)", getClassName(), pToker->getToken()));

   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {

      if (pToker->tokenICmp("origin")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%lf %lf %lf", &mOrigin.x, &mOrigin.y, &mOrigin.z);
      }
      else if (pToker->tokenICmp("spawnflags")) {
         pToker->advanceToken(false, true);
         mFlags = dAtoi(pToker->getToken()) & Interior::AnimationTypeMask;
      }
      else if (pToker->tokenICmp("name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 256, avar("Error: %s is an invalid target name.  Must be less than 256", pToker->getToken()));
         dStrcpy(mLightName, pToker->getToken());
      }
      else if (pToker->tokenICmp("color1")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%f %f %f", &mColor1.red, &mColor1.green, &mColor1.blue);
         mColor1.red   /= 255.0f;
         mColor1.green /= 255.0f;
         mColor1.blue  /= 255.0f;
      }
      else if (pToker->tokenICmp("color2")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%f %f %f", &mColor2.red, &mColor2.green, &mColor2.blue);
         mColor2.red   /= 255.0f;
         mColor2.green /= 255.0f;
         mColor2.blue  /= 255.0f;
      }
      else if (pToker->tokenICmp("alarm_type")) {
         pToker->advanceToken(false, true);
         mAlarmStatus = (AlarmStatus)dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("falloff1")) {
         pToker->advanceToken(false, true);
         mFalloff1 = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("falloff2")) {
         pToker->advanceToken(false, true);
         mFalloff2 = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("speed")) {
         pToker->advanceToken(false, true);
         mSpeed = dAtoi(pToker->getToken());
      }
      else {
         pToker->advanceToken(false);  // For keyless entity fields...
      }

      pToker->advanceToken(true);
   }

   return true;
}

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

LightPulseEntity::LightPulseEntity()
{
   mSpeed      = Normal;
   mColor1.set(0,0,0);
   mColor2.set(1,1,1);
   mFalloff1   = 10;
   mFalloff2   = 100;
}

bool LightPulseEntity::parseEntityDescription(Tokenizer* pToker)
{
   // Tokenizer comes in pointing to the classname.  We'll check that, just to be
   //  thorough.
   AssertFatal(pToker->tokenICmp(getClassName()),
               avar("Wrong class name? (expected %s, got %s)", getClassName(), pToker->getToken()));

   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {

      if (pToker->tokenICmp("origin")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%lf %lf %lf", &mOrigin.x, &mOrigin.y, &mOrigin.z);
      }
      else if (pToker->tokenICmp("spawnflags")) {
         pToker->advanceToken(false, true);
         mFlags = dAtoi(pToker->getToken()) & Interior::AnimationTypeMask;
      }
      else if (pToker->tokenICmp("name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 256, avar("Error: %s is an invalid target name.  Must be less than 256", pToker->getToken()));
         dStrcpy(mLightName, pToker->getToken());
      }
      else if (pToker->tokenICmp("color1")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%f %f %f", &mColor1.red, &mColor1.green, &mColor1.blue);
         mColor1.red   /= 255.0f;
         mColor1.green /= 255.0f;
         mColor1.blue  /= 255.0f;
      }
      else if (pToker->tokenICmp("color2")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%f %f %f", &mColor2.red, &mColor2.green, &mColor2.blue);
         mColor2.red   /= 255.0f;
         mColor2.green /= 255.0f;
         mColor2.blue  /= 255.0f;
      }
      else if (pToker->tokenICmp("alarm_type")) {
         pToker->advanceToken(false, true);
         mAlarmStatus = (AlarmStatus)dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("falloff1")) {
         pToker->advanceToken(false, true);
         mFalloff1 = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("falloff2")) {
         pToker->advanceToken(false, true);
         mFalloff2 = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("speed")) {
         pToker->advanceToken(false, true);
         mSpeed = dAtoi(pToker->getToken());
      }
      else {
         pToker->advanceToken(false);  // For keyless entity fields...
      }

      pToker->advanceToken(true);
   }

   return true;
}

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

LightPulse2Entity::LightPulse2Entity()
{
   mColor1.set(0,0,0);
   mColor2.set(1,1,1);
   mFalloff1   = 10;
   mFalloff2   = 100;
   mAttack = mSustain1 = mDecay = mSustain2 = 1.f;
}

bool LightPulse2Entity::parseEntityDescription(Tokenizer* pToker)
{
   // Tokenizer comes in pointing to the classname.  We'll check that, just to be
   //  thorough.
   AssertFatal(pToker->tokenICmp(getClassName()),
               avar("Wrong class name? (expected %s, got %s)", getClassName(), pToker->getToken()));

   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {

      if (pToker->tokenICmp("origin")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%lf %lf %lf", &mOrigin.x, &mOrigin.y, &mOrigin.z);
      }
      else if (pToker->tokenICmp("spawnflags")) {
         pToker->advanceToken(false, true);
         mFlags = dAtoi(pToker->getToken()) & Interior::AnimationTypeMask;
      }
      else if (pToker->tokenICmp("name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 256, avar("Error: %s is an invalid target name.  Must be less than 256", pToker->getToken()));
         dStrcpy(mLightName, pToker->getToken());
      }
      else if (pToker->tokenICmp("color1")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%f %f %f", &mColor1.red, &mColor1.green, &mColor1.blue);
         mColor1.red   /= 255.0f;
         mColor1.green /= 255.0f;
         mColor1.blue  /= 255.0f;
      }
      else if (pToker->tokenICmp("color2")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%f %f %f", &mColor2.red, &mColor2.green, &mColor2.blue);
         mColor2.red   /= 255.0f;
         mColor2.green /= 255.0f;
         mColor2.blue  /= 255.0f;
      }
      else if (pToker->tokenICmp("alarm_type")) {
         pToker->advanceToken(false, true);
         mAlarmStatus = (AlarmStatus)dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("falloff1")) {
         pToker->advanceToken(false, true);
         mFalloff1 = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("falloff2")) {
         pToker->advanceToken(false, true);
         mFalloff2 = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("attack")) {
         pToker->advanceToken(false, true);
         mAttack = dAtof(pToker->getToken());
      }
      else if (pToker->tokenICmp("sustain1")) {
         pToker->advanceToken(false, true);
         mSustain1  = dAtof(pToker->getToken());
      }
      else if (pToker->tokenICmp("decay")) {
         pToker->advanceToken(false, true);
         mDecay = dAtof(pToker->getToken());
      }
      else if (pToker->tokenICmp("sustain2")) {
         pToker->advanceToken(false, true);
         mSustain2 = dAtof(pToker->getToken());
      }
      else {
         pToker->advanceToken(false);  // For keyless entity fields...
      }

      pToker->advanceToken(true);
   }

   return true;
}

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

LightFlickerEntity::LightFlickerEntity()
{
   mSpeed      = Normal;
   mColor1.set(1,1,1);
   mColor2.set(0,0,0);
   mColor3.set(0,0,0);
   mColor4.set(0,0,0);
   mColor5.set(0,0,0);
   mFalloff1   = 10;
   mFalloff2   = 100;
}

bool LightFlickerEntity::parseEntityDescription(Tokenizer* pToker)
{
   // Tokenizer comes in pointing to the classname.  We'll check that, just to be
   //  thorough.
   AssertFatal(pToker->tokenICmp(getClassName()),
               avar("Wrong class name? (expected %s, got %s)", getClassName(), pToker->getToken()));

   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {

      if (pToker->tokenICmp("origin")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%lf %lf %lf", &mOrigin.x, &mOrigin.y, &mOrigin.z);
      }
      else if (pToker->tokenICmp("spawnflags")) {
         pToker->advanceToken(false, true);
         mFlags = dAtoi(pToker->getToken()) & Interior::AnimationTypeMask;
      }
      else if (pToker->tokenICmp("name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 256, avar("Error: %s is an invalid target name.  Must be less than 256", pToker->getToken()));
         dStrcpy(mLightName, pToker->getToken());
      }
      else if (pToker->tokenICmp("color1")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%f %f %f", &mColor1.red, &mColor1.green, &mColor1.blue);
         mColor1.red   /= 255.0f;
         mColor1.green /= 255.0f;
         mColor1.blue  /= 255.0f;
      }
      else if (pToker->tokenICmp("color2")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%f %f %f", &mColor2.red, &mColor2.green, &mColor2.blue);
         mColor2.red   /= 255.0f;
         mColor2.green /= 255.0f;
         mColor2.blue  /= 255.0f;
      }
      else if (pToker->tokenICmp("color3")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%f %f %f", &mColor3.red, &mColor3.green, &mColor3.blue);
         mColor3.red   /= 255.0f;
         mColor3.green /= 255.0f;
         mColor3.blue  /= 255.0f;
      }
      else if (pToker->tokenICmp("color4")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%f %f %f", &mColor4.red, &mColor4.green, &mColor4.blue);
         mColor4.red   /= 255.0f;
         mColor4.green /= 255.0f;
         mColor4.blue  /= 255.0f;
      }
      else if (pToker->tokenICmp("color5")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%f %f %f", &mColor5.red, &mColor5.green, &mColor5.blue);
         mColor5.red   /= 255.0f;
         mColor5.green /= 255.0f;
         mColor5.blue  /= 255.0f;
      }
      else if (pToker->tokenICmp("alarm_type")) {
         pToker->advanceToken(false, true);
         mAlarmStatus = (AlarmStatus)dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("falloff1")) {
         pToker->advanceToken(false, true);
         mFalloff1 = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("falloff2")) {
         pToker->advanceToken(false, true);
         mFalloff2 = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("speed")) {
         pToker->advanceToken(false, true);
         mSpeed = dAtoi(pToker->getToken());
      }
      else {
         pToker->advanceToken(false);  // For keyless entity fields...
      }

      pToker->advanceToken(true);
   }

   return true;
}

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

LightRunwayEntity::LightRunwayEntity()
{
   mEndTarget[0]  = '\0';
   mColor.set(1,1,1);
   mSpeed         = Normal;
   mPingPong      = false;
   mSteps         = 0;
   mFalloff1      = 10;
   mFalloff2      = 100;
}

bool LightRunwayEntity::parseEntityDescription(Tokenizer* pToker)
{
   // Tokenizer comes in pointing to the classname.  We'll check that, just to be
   //  thorough.
   AssertFatal(pToker->tokenICmp(getClassName()),
               avar("Wrong class name? (expected %s, got %s)", getClassName(), pToker->getToken()));

   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {

      if (pToker->tokenICmp("origin")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%lf %lf %lf", &mOrigin.x, &mOrigin.y, &mOrigin.z);
      }
      else if (pToker->tokenICmp("spawnflags")) {
         pToker->advanceToken(false, true);
         mFlags = dAtoi(pToker->getToken()) & Interior::AnimationTypeMask;
      }
      else if (pToker->tokenICmp("name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 256, avar("Error: %s is an invalid target name.  Must be less than 256", pToker->getToken()));
         dStrcpy(mLightName, pToker->getToken());
      }
      else if (pToker->tokenICmp("target")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 256, avar("Error: %s is an invalid target name.  Must be less than 256", pToker->getToken()));
         dStrcpy(mEndTarget, pToker->getToken());
      }
      else if (pToker->tokenICmp("color")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%f %f %f", &mColor.red, &mColor.green, &mColor.blue);
         mColor.red   /= 255.0f;
         mColor.green /= 255.0f;
         mColor.blue  /= 255.0f;
      }
      else if (pToker->tokenICmp("alarm_type")) {
         pToker->advanceToken(false, true);
         mAlarmStatus = (AlarmStatus)dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("falloff1")) {
         pToker->advanceToken(false, true);
         mFalloff1 = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("falloff2")) {
         pToker->advanceToken(false, true);
         mFalloff2 = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("speed")) {
         pToker->advanceToken(false, true);
         mSpeed = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("steps")) {
         pToker->advanceToken(false, true);
         mSteps = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("pingpong")) {
         pToker->advanceToken(false, true);
         mPingPong = dAtoi(pToker->getToken()) ? true : false;
      }
      else {
         pToker->advanceToken(false);  // For keyless entity fields...
      }

      pToker->advanceToken(true);
   }

   return true;
}

//--------------------------------------------------------------------------
//--------------------------------------
U32 MirrorSurfaceEntity::smZoneKeyAllocator = 0;

MirrorSurfaceEntity::MirrorSurfaceEntity()
{
   mZoneKey = smZoneKeyAllocator | 0x40000000;
   smZoneKeyAllocator++;

   mRealZone = 0xFFFFFFFF;
}

bool MirrorSurfaceEntity::parseEntityDescription(Tokenizer* pToker)
{
   // Tokenizer comes in pointing to the classname.  We'll check that, just to be
   //  thorough.
   AssertFatal(pToker->tokenICmp(getClassName()),
               avar("Wrong class name? (expected %s, got %s)", getClassName(), pToker->getToken()));

   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {

      if (pToker->tokenICmp("origin")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%lf %lf %lf", &mOrigin.x, &mOrigin.y, &mOrigin.z);
      }
      else if (pToker->tokenICmp("alpha_level")) {
         pToker->advanceToken(false, true);
         S32 alevel = dAtoi(pToker->getToken());
         if (alevel < 0)
            alevel = 0;
         else if (alevel > 6)
            alevel = 6;

         switch (alevel) {
           case 0: mAlphaLevel = 0.0;           break;
           case 1: mAlphaLevel = 0.1666666666;  break;
           case 2: mAlphaLevel = 0.3333333333;  break;
           case 3: mAlphaLevel = 0.5;           break;
           case 4: mAlphaLevel = 0.6666666666;  break;
           case 5: mAlphaLevel = 0.8333333333;  break;
           case 6: mAlphaLevel = 1.0;           break;
         }
      }
      else {
         pToker->advanceToken(false);  // For keyless entity fields...
      }

      pToker->advanceToken(true);
   }

   return true;
}

BrushType MirrorSurfaceEntity::getBrushType()
{
   AssertISV(false, "Brushes cannot be associated with mirrors!");
   return DetailBrush;
}

void MirrorSurfaceEntity::markSurface(Vector<CSGBrush*>& rStructural,
                                      Vector<CSGBrush*>& /*rDetail*/)
{
   U32 brushIndex = 0;
   U32 planeIndex = 0;
   F64 minDist    = 1e10;
   for (U32 i = 0; i < rStructural.size(); i++) {
      CSGBrush* pBrush = rStructural[i];

      for (U32 j = 0; j < pBrush->mPlanes.size(); j++) {
         CSGPlane& rPlane        = pBrush->mPlanes[j];
         const PlaneEQ& rPlaneEQ = gWorkingGeometry->getPlaneEQ(rPlane.planeEQIndex);
         F64 dist                = mFabs(rPlaneEQ.distanceToPlane(getOrigin()));

         if (dist < minDist) {
            bool goodPlane = true;

            // First, confirm that the point is behind all the other planes...
            for (U32 k = 0; k < pBrush->mPlanes.size(); k++) {
               if (k == j)
                  continue;

               CSGPlane& rPlaneTest        = pBrush->mPlanes[k];
               const PlaneEQ& rPlaneEQTest = gWorkingGeometry->getPlaneEQ(rPlaneTest.planeEQIndex);

               if (rPlaneEQTest.whichSidePerfect(getOrigin()) == PlaneFront) {
                  goodPlane = false;
                  break;
               }
            }

            if (goodPlane == true) {
               minDist = dist;
               brushIndex = i;
               planeIndex = j;
            }
         }
      }
   }

   // Plane must be very close to be considered
   if (minDist < 1.0f) {
      CSGBrush* pBrush = rStructural[brushIndex];
      CSGPlane& rPlane = pBrush->mPlanes[planeIndex];

      AssertFatal(rPlane.owningEntity == NULL, "Error, brush already owned!");
      rPlane.owningEntity = this;
   }
}

void MirrorSurfaceEntity::grabSurface(EditGeometry::Surface& rSurface)
{
   if (mRealZone == 0xFFFFFFFF) {
      mRealZone = rSurface.winding.zoneIds[0];
   } else {
      if (rSurface.winding.zoneIds[0] != mRealZone) {
         dPrintf("\n  *** ACK!  Mirrored brush surfaces may NOT cross zone boundaries.  Surface disregarded");
         return;
      }
   }

   // This must be unique to the entity...
   rSurface.winding.zoneIds[0] = mZoneKey;
}


//--------------------------------------------------------------------------
DoorEntity::DoorEntity()
{
   dStrcpy(mName, "MustChange");
   mDataBlock[0] = 0;

   mCurrBrushId = 0;
   mInterior    = NULL;

   gCurrentDoor = this;
}

DoorEntity::~DoorEntity()
{
   for (U32 i = 0; i < mBrushes.size(); i++)
      gBrushArena.freeBrush(mBrushes[i]);
   
   delete mInterior;
   mInterior = NULL;
}

bool DoorEntity::parseEntityDescription(Tokenizer* pToker)
{
   // Tokenizer comes in pointing to the classname.  We'll check that, just to be
   //  thorough.
   AssertFatal(pToker->tokenICmp(getClassName()),
               avar("Wrong class name? (expected %s, got %s)", getClassName(), pToker->getToken()));

   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {
      if (pToker->tokenICmp("name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 255, avar("Error: %s is an door name.  Must be less than 255", pToker->getToken()));
         dStrcpy(mName, pToker->getToken());
      }
      else if (pToker->tokenICmp("datablock")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 255, avar("Error: %s is a datablock name.  Must be less than 255", pToker->getToken()));
         dStrcpy(mDataBlock, pToker->getToken());
      }
      else if (pToker->tokenICmp("path_name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 255, avar("Error: %s is an path name.  Must be less than 255", pToker->getToken()));
         dStrcpy(mPath, pToker->getToken());
      }
      else {
         InteriorDictEntry e;
         dStrncpy(e.name, pToker->getToken(), sizeof(e.name)-1);
         pToker->advanceToken(false, true);
         dStrncpy(e.value, pToker->getToken(), sizeof(e.value)-1);
         if(dStrlen(e.value))
            mDictionary.push_back(e);
      }

      pToker->advanceToken(true);
   }

   // Ok, this is where it gets interesting.  We have to parse in the brushes, and place
   //  them in our list before we return to the editGeometry.  I'm copying almost all this
   //  code from the parseMapFile function in editGeometry.cc
   parseBrushList(mBrushes, pToker, *gWorkingGeometry);

   // We need to find the origin brush...
   CSGBrush* pOrigin = NULL;
   for (U32 i = 0; i < mBrushes.size(); i++) {
      if (dStricmp(mBrushes[i]->mPlanes[0].pTextureName, "ORIGIN") == 0) {
         // That's the one!
         pOrigin = mBrushes[i];
         mBrushes.erase(i);
         break;
      }
   }

   if (pOrigin) {
      pOrigin->selfClip();
      // Grab the centroid of this brush...
      Point3D centroid(0, 0, 0);
      U32 numPoints = 0;
      for (U32 i = 0; i < pOrigin->mPlanes.size(); i++) {
         for (U32 j = 0; j < pOrigin->mPlanes[i].winding.numIndices; j++) {
            U32 index = pOrigin->mPlanes[i].winding.indices[j];
            centroid += gWorkingGeometry->getPoint(index);
            numPoints++;
         }
      }
      centroid /= F64(numPoints);
      mOrigin = centroid / gWorkingGeometry->mWorldEntity->mGeometryScale;
   } else {
      mOrigin.set(0, 0, 0);
   }


   return true;
}

BrushType DoorEntity::getBrushType()
{
   AssertISV(false, "Brushes should have been parsed out in the entity!");
   return DetailBrush;
}

void DoorEntity::addPathNode(PathNodeEntity *ent)
{
   InteriorPathFollower::WayPoint wp;
   *((Point3F *) &wp.pos) = Point3F(ent->getOrigin().x, ent->getOrigin().y, ent->getOrigin().z);
   wp.rot.set(1,0,0,0);
   wp.msToNext = ent->mNumMSToNextNode;
   wp.smoothingType = ent->mSmoothingType;
   mWayPoints.push_back(wp);
   if(mWayPoints.size() > 1)
      mTotalMS += mWayPoints[mWayPoints.size() - 2].msToNext;
}

void DoorEntity::process()
{
   U32 i, j;

   EditGeometry* pOldGeometry = gWorkingGeometry;

   gWorkingGeometry = new EditGeometry;
   gWorkingGeometry->mWorldEntity = pOldGeometry->mWorldEntity;
   gWorkingGeometry->insertTexture("NULL");
   gWorkingGeometry->insertTexture("ORIGIN");
   gWorkingGeometry->insertTexture("TRIGGER");
   gWorkingGeometry->insertTexture("FORCEFIELD");

   // Now, we have some trickiness here.  We already have our brushes parsed in,
   //  but all their plane indices are in the old geometry.  Arg.  We'll move them
   //  over to the new geometry.  Really, this should be fixed.  DMM
   for (i = 0; i < mBrushes.size(); i++) {
      CSGBrush* pBrush = mBrushes[i];

      for (j = 0; j < pBrush->mPlanes.size(); j++) {
         CSGPlane& rPlane          = pBrush->mPlanes[j];
         const PlaneEQ& oldPlaneEQ = pOldGeometry->getPlaneEQ(rPlane.planeEQIndex);

         rPlane.planeEQIndex = gWorkingGeometry->insertPlaneEQ(oldPlaneEQ.normal, oldPlaneEQ.dist);
         rPlane.pTextureName = gWorkingGeometry->insertTexture(rPlane.pTextureName);
         U32 texGenIndex = gWorkingGeometry->mTexGenEQs.size();
         gWorkingGeometry->mTexGenEQs.push_back(pOldGeometry->mTexGenEQs[rPlane.texGenIndex]);
         rPlane.texGenIndex = texGenIndex;
      }
   }

   for (i = 0; i < mBrushes.size(); i++) {
      gWorkingGeometry->mStructuralBrushes.push_back(mBrushes[i]);
      gWorkingGeometry->mStructuralBrushes.last()->brushId = mCurrBrushId++;
      mBrushes[i] = NULL;
   }
   mBrushes.clear();

   if (gWorkingGeometry->createBSP() == false) {
      // Handle the error.  Since we don't mark ourselves as valid, we won't be exported
      //
      gWorkingGeometry->mWorldEntity = NULL;
      delete gWorkingGeometry;
      gWorkingGeometry = pOldGeometry;
      return;
   }

   gWorkingGeometry->markEmptyZones();
   gWorkingGeometry->createSurfaces();

   extern bool gBuildAsLowDetail;
   bool store = gBuildAsLowDetail;
   gBuildAsLowDetail = true;
   gWorkingGeometry->computeLightmaps(false);
   gWorkingGeometry->computeLightmaps(true);
   gWorkingGeometry->preprocessLighting();
   gWorkingGeometry->sortLitSurfaces();
   gBuildAsLowDetail = store;
   gWorkingGeometry->packLMaps();

   // Yes!  Everything worked perfectly!  Export ourselves, and we're through!
   mInterior = new Interior;
   gWorkingGeometry->exportToRuntime(mInterior, NULL);

   gWorkingGeometry->mWorldEntity = NULL;
   delete gWorkingGeometry;
   gWorkingGeometry = pOldGeometry;
}


//--------------------------------------------------------------------------
//--------------------------------------
//
PathNodeEntity::PathNodeEntity()
{
   mNumMSToNextNode = 1000;
   mSmoothingType = 0;

   mOrigin.set(0, 0, 0);
}

PathNodeEntity::~PathNodeEntity()
{
   //
}

BrushType PathNodeEntity::getBrushType()
{
   AssertISV(false, "Brushes cannot be associated with Path nodes!");
   return DetailBrush;
}

bool PathNodeEntity::parseEntityDescription(Tokenizer* pToker)
{
   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {
      if (pToker->tokenICmp("origin")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%lf %lf %lf", &mOrigin.x, &mOrigin.y, &mOrigin.z);
      }
      else if (pToker->tokenICmp("next_time")) {
         pToker->advanceToken(false, true);
         mNumMSToNextNode = dAtoi(pToker->getToken());
         if (mNumMSToNextNode < 0)
            mNumMSToNextNode = 0;
      }
      else if (pToker->tokenICmp("smoothing")) {
         pToker->advanceToken(false, true);
         mSmoothingType = dAtoi(pToker->getToken());
      }
      else {
         pToker->advanceToken(false);  // For keyless entity fields...
      }

      pToker->advanceToken(true);
   }
   gCurrentDoor->addPathNode(this);

   return true;
}

//--------------------------------------------------------------------------
//--------------------------------------
//
TriggerEntity::TriggerEntity()
{
   dStrcpy(mName, "MustChange");
   mDataBlock[0] = 0;

   mOrigin.set(0, 0, 0);

   mCurrBrushId = 0;
   mValid       = false;
}

TriggerEntity::~TriggerEntity()
{
   for (U32 i = 0; i < mBrushes.size(); i++)
      gBrushArena.freeBrush(mBrushes[i]);
}

BrushType TriggerEntity::getBrushType()
{
   AssertISV(false, "Brushes should have been parsed out in the entity!");
   return DetailBrush;
}

bool TriggerEntity::parseEntityDescription(Tokenizer* pToker)
{
   // Tokenizer comes in pointing to the classname.  We'll check that, just to be
   //  thorough.
   AssertFatal(pToker->tokenICmp(getClassName()),
               avar("Wrong class name? (expected %s, got %s)", getClassName(), pToker->getToken()));

   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {

      if (pToker->tokenICmp("name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 255, avar("Error: %s is an trigger name.  Must be less than 255", pToker->getToken()));
         dStrcpy(mName, pToker->getToken());
      }
      else if(pToker->tokenICmp("datablock")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 255, avar("Error: %s is a trigger datablock.  Must be less than 255", pToker->getToken()));
         dStrcpy(mDataBlock, pToker->getToken());
      }
      else
      {
         InteriorDictEntry e;
         dStrncpy(e.name, pToker->getToken(), sizeof(e.name)-1);
         pToker->advanceToken(false, true);
         dStrncpy(e.value, pToker->getToken(), sizeof(e.value)-1);
         if(dStrlen(e.value))
            mDictionary.push_back(e);
      }
      pToker->advanceToken(true);
   }

   // Ok, this is where it gets interesting.  We have to parse in the brushes, and place
   //  them in our list before we return to the editGeometry.  I'm copying almost all this
   //  code from the parseMapFile function in editGeometry.cc
   parseBrushList(mBrushes, pToker, *gWorkingGeometry);
   generateTrigger();
   if(gCurrentDoor)
      gCurrentDoor->mTriggerIds.push_back(gTriggerId);
   gTriggerId++;
   return true;
}

void TriggerEntity::generateTrigger()
{
   if (mBrushes.size() != 1) {
      dPrintf(" * Error * Trigger must have one and only one brush (name: %s has %d)", getName(), mBrushes.size());
      mValid = false;
      return;
   }

   // Ok, we assume that this brush is a cube, check by looking at the number of planes.
   //  Note that this is a really lousy check, we might want to add some better ones later.
   //  Also, most of this is really hacky, there's definitely a better way to extract the
   //  canonical box...
   //
   CSGBrush* pBrush = mBrushes[0];
   if (pBrush->mPlanes.size() != 6) {
      dPrintf(" * Error * Trigger must be a rectilinear polytope (name: %s isn't)", getName());
      mValid = false;
      return;
   }

   Point3D normals[3];
   U32     normalIndices[3];

   normals[0]       = pBrush->mPlanes[0].getNormal();
   normalIndices[0] = 0;

   U32 currentNormal = 1;
   for (U32 i = 1; i < 6 && currentNormal < 3; i++) {
      bool ortho = true;
      for (U32 j = 0; j < currentNormal; j++) {
         if (mFabs(mDot(pBrush->mPlanes[i].getNormal(), normals[j])) > 0.01)
            ortho = false;
      }

      // Found the next normal?
      if (ortho) {
         normals[currentNormal]       = pBrush->mPlanes[i].getNormal();
         normalIndices[currentNormal] = i;
         currentNormal++;
      }
   }

   if (currentNormal < 3) {
      dPrintf(" * Error * Trigger missing 3 ortho planes. (name: %s)", getName());
      mValid = false;
      return;
   }

   F64 diameters[3] = { 0, 0, 0 };
   for (U32 i = 0; i < 3; i++) {
      for (U32 j = 0; j < 6; j++) {
         if (mDot(pBrush->mPlanes[j].getNormal(), normals[i]) < -0.999) {
            // Counter part
            diameters[i] += pBrush->mPlanes[j].getDist();
         } else if (mDot(pBrush->mPlanes[j].getNormal(), normals[i]) > 0.9999) {
            // actual
            diameters[i] += pBrush->mPlanes[j].getDist();
         }
      }

      diameters[i] = mFabs(diameters[i]);
   }

   // This is a really hacky way to get the center coordinate...
   U32 antiNormalIndices[3];
   U32 currentAnti = 0;
   for (U32 i = 0; i < 6; i++) {
      bool n = false;
      for (U32 j = 0; j < 3; j++)
         if (normalIndices[j] == i)
            n = true;
      if (n == false)
         antiNormalIndices[currentAnti++] = i;
   }
   AssertFatal(currentAnti == 3, "Hm, that's bad");

   Point3D min, antiMin;

   bool intersect = pBrush->intersectPlanes(normalIndices[0], normalIndices[1], normalIndices[2], &min);
   AssertFatal(intersect, "Also, this is bad");
   intersect = pBrush->intersectPlanes(antiNormalIndices[0], antiNormalIndices[1], antiNormalIndices[2], &antiMin);
   AssertFatal(intersect, "Also, this is bad");

   mOrigin = (min + antiMin) * 0.5;
   mOrigin /= gWorkingGeometry->mWorldEntity->mGeometryScale;
   
   Box3F tempBox;
   tempBox.min.set(-diameters[0] / 2.0, -diameters[1] / 2.0, -diameters[2] / 2.0);
   tempBox.min /= gWorkingGeometry->mWorldEntity->mGeometryScale;
   tempBox.max.set( diameters[0] / 2.0,  diameters[1] / 2.0,  diameters[2] / 2.0);
   tempBox.max /= gWorkingGeometry->mWorldEntity->mGeometryScale;

   Point3F temp;
   Point3D realNormals[3];
   realNormals[0] = pBrush->mPlanes[normalIndices[0]].getNormal();
   realNormals[1] = pBrush->mPlanes[normalIndices[1]].getNormal();
   realNormals[2] = pBrush->mPlanes[normalIndices[2]].getNormal();
   mCross(realNormals[0], realNormals[1], &realNormals[2]);
   realNormals[2].neg();
   mCross(realNormals[0], realNormals[2], &realNormals[1]);
   realNormals[0].normalize();
   realNormals[1].normalize();
   realNormals[2].normalize();

   MatrixF tempXForm(true);
   for (U32 i = 0; i < 3; i++) {
      const Point3D& rNorm = realNormals[i];
      temp.x = rNorm.x;
      temp.y = rNorm.y;
      temp.z = rNorm.z;

      tempXForm.setColumn(i, temp);
   }
   // Export the polyhedron...
   triggerPolyhedron.buildBox(tempXForm, tempBox);

   mValid = true;
}


//--------------------------------------------------------------------------
ForceFieldEntity::ForceFieldEntity()
{
   dStrcpy(mName, "MustChange");
   mMSToFade = 1000;

   mColor = ColorF(125.0/255.0, 216.0/255.0, 255.0/255.0);

   mCurrBrushId = 0;
   mInterior    = NULL;

   for (U32 i = 0; i < 8; i++)
      mTrigger[i][0] = '\0';
}

ForceFieldEntity::~ForceFieldEntity()
{
   for (U32 i = 0; i < mBrushes.size(); i++)
      gBrushArena.freeBrush(mBrushes[i]);
   
   delete mInterior;
   mInterior = NULL;
}

bool ForceFieldEntity::parseEntityDescription(Tokenizer* pToker)
{
   // Tokenizer comes in pointing to the classname.  We'll check that, just to be
   //  thorough.
   AssertFatal(pToker->tokenICmp(getClassName()),
               avar("Wrong class name? (expected %s, got %s)", getClassName(), pToker->getToken()));

   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {
      if (pToker->tokenICmp("name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 255, avar("Error: %s is an door name.  Must be less than 255", pToker->getToken()));
         dStrcpy(mName, pToker->getToken());
      }
      else if (pToker->tokenICmp("color")) {
         pToker->advanceToken(false, true);
         U32 r, g, b;
         dSscanf(pToker->getToken(), "%d %d %d", &r, &g, &b);
         mColor = ColorF(F32(r) / 255.0f, F32(g) / 255.0f, F32(b) / 255.0f);

         mMSToFade = dAtoi(pToker->getToken());
      }
      else if (pToker->tokenICmp("trigger0_name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 255, avar("Error: %s is an trigger name.  Must be less than 255", pToker->getToken()));
         dStrcpy(mTrigger[0], pToker->getToken());
      }
      else if (pToker->tokenICmp("trigger1_name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 255, avar("Error: %s is an trigger name.  Must be less than 255", pToker->getToken()));
         dStrcpy(mTrigger[1], pToker->getToken());
      }
      else if (pToker->tokenICmp("trigger2_name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 255, avar("Error: %s is an trigger name.  Must be less than 255", pToker->getToken()));
         dStrcpy(mTrigger[2], pToker->getToken());
      }
      else if (pToker->tokenICmp("trigger3_name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 255, avar("Error: %s is an trigger name.  Must be less than 255", pToker->getToken()));
         dStrcpy(mTrigger[3], pToker->getToken());
      }
      else if (pToker->tokenICmp("trigger4_name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 255, avar("Error: %s is an trigger name.  Must be less than 255", pToker->getToken()));
         dStrcpy(mTrigger[4], pToker->getToken());
      }
      else if (pToker->tokenICmp("trigger5_name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 255, avar("Error: %s is an trigger name.  Must be less than 255", pToker->getToken()));
         dStrcpy(mTrigger[5], pToker->getToken());
      }
      else if (pToker->tokenICmp("trigger6_name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 255, avar("Error: %s is an trigger name.  Must be less than 255", pToker->getToken()));
         dStrcpy(mTrigger[6], pToker->getToken());
      }
      else if (pToker->tokenICmp("trigger7_name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 255, avar("Error: %s is an trigger name.  Must be less than 255", pToker->getToken()));
         dStrcpy(mTrigger[7], pToker->getToken());
      }
      else {
      }

      pToker->advanceToken(true);
   }

   // Ok, this is where it gets interesting.  We have to parse in the brushes, and place
   //  them in our list before we return to the editGeometry.  I'm copying almost all this
   //  code from the parseMapFile function in editGeometry.cc
   parseBrushList(mBrushes, pToker, *gWorkingGeometry);

   return true;
}

BrushType ForceFieldEntity::getBrushType()
{
   AssertISV(false, "Brushes should have been parsed out in the entity!");
   return DetailBrush;
}

void ForceFieldEntity::process()
{
   U32 i, j;

   EditGeometry* pOldGeometry = gWorkingGeometry;

   gWorkingGeometry = new EditGeometry;
   gWorkingGeometry->mWorldEntity = pOldGeometry->mWorldEntity;
   gWorkingGeometry->insertTexture("NULL");
   gWorkingGeometry->insertTexture("ORIGIN");
   gWorkingGeometry->insertTexture("TRIGGER");
   gWorkingGeometry->insertTexture("FORCEFIELD");

   // Now, we have some trickiness here.  We already have our brushes parsed in,
   //  but all their plane indices are in the old geometry.  Arg.  We'll move them
   //  over to the new geometry.  Really, this should be fixed.  DMM
   for (i = 0; i < mBrushes.size(); i++) {
      CSGBrush* pBrush = mBrushes[i];

      for (j = 0; j < pBrush->mPlanes.size(); j++) {
         CSGPlane& rPlane          = pBrush->mPlanes[j];
         const PlaneEQ& oldPlaneEQ = pOldGeometry->getPlaneEQ(rPlane.planeEQIndex);

         rPlane.planeEQIndex = gWorkingGeometry->insertPlaneEQ(oldPlaneEQ.normal, oldPlaneEQ.dist);
         rPlane.pTextureName = gWorkingGeometry->insertTexture(rPlane.pTextureName);
      }
   }

   for (i = 0; i < mBrushes.size(); i++) {
      gWorkingGeometry->mStructuralBrushes.push_back(mBrushes[i]);
      gWorkingGeometry->mStructuralBrushes.last()->brushId = mCurrBrushId++;
      mBrushes[i] = NULL;
   }
   mBrushes.clear();

   if (gWorkingGeometry->createBSP() == false) {
      // Handle the error.  Since we don't mark ourselves as valid, we won't be exported
      //
      gWorkingGeometry->mWorldEntity = NULL;
      delete gWorkingGeometry;
      gWorkingGeometry = pOldGeometry;
      return;
   }

   gWorkingGeometry->createSurfaces();

   extern bool gBuildAsLowDetail;
   bool store = gBuildAsLowDetail;
   gBuildAsLowDetail = true;
   gWorkingGeometry->computeLightmaps(false);
   gBuildAsLowDetail = store;
   gWorkingGeometry->packLMaps();

   // Yes!  Everything worked perfectly!  Export ourselves, and we're through!
   mInterior = new Interior;
   gWorkingGeometry->exportToRuntime(mInterior, NULL);

   gWorkingGeometry->mWorldEntity = NULL;
   delete gWorkingGeometry;
   gWorkingGeometry = pOldGeometry;
}

//--------------------------------------------------------------------------
//--------------------------------------
//
SpecialNodeEntity::SpecialNodeEntity()
{
   mName[0]         = '\0';
   mOrigin.set(0, 0, 0);
}

SpecialNodeEntity::~SpecialNodeEntity()
{
   //
}

BrushType SpecialNodeEntity::getBrushType()
{
   AssertISV(false, "Brushes cannot be associated with Special nodes!");
   return DetailBrush;
}

bool SpecialNodeEntity::parseEntityDescription(Tokenizer* pToker)
{
   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {
      if (pToker->tokenICmp("origin")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%lf %lf %lf", &mOrigin.x, &mOrigin.y, &mOrigin.z);
      }
      else if (pToker->tokenICmp("name")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 256, avar("Error: %s is an invalid target name.  Must be less than 256", pToker->getToken()));
         dStrcpy(mName, pToker->getToken());
      }
      else {
         pToker->advanceToken(false);  // For keyless entity fields...
      }

      pToker->advanceToken(true);
   }

   return true;
}

GameEntity::GameEntity(const char *datablock)
{
   dStrcpy(mDatablock, datablock);
   mOrigin.set(0, 0, 0);
}

GameEntity::~GameEntity()
{
   //
}

BrushType GameEntity::getBrushType()
{
   AssertISV(false, "Brushes cannot be associated with GameEntities!");
   return DetailBrush;
}

bool GameEntity::parseEntityDescription(Tokenizer* pToker)
{
   pToker->advanceToken(true);
   while (pToker->getToken()[0] != '{' &&
          pToker->getToken()[0] != '}') {
      if (pToker->tokenICmp("origin")) {
         pToker->advanceToken(false, true);

         dSscanf(pToker->getToken(), "%lf %lf %lf", &mOrigin.x, &mOrigin.y, &mOrigin.z);
      }
      else if (pToker->tokenICmp("game_class")) {
         pToker->advanceToken(false, true);
         AssertISV(dStrlen(pToker->getToken()) < 256, avar("Error: %s is an invalid target name.  Must be less than 256", pToker->getToken()));
         dStrcpy(mGameClass, pToker->getToken());
      }
      else {
         InteriorDictEntry e;
         dStrncpy(e.name, pToker->getToken(), sizeof(e.name)-1);
         pToker->advanceToken(false, true);
         dStrncpy(e.value, pToker->getToken(), sizeof(e.value)-1);
         if(dStrlen(e.value))
            mDictionary.push_back(e);
      }

      pToker->advanceToken(true);
   }

   return true;
}
