1939 lines
		
	
	
		
			66 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1939 lines
		
	
	
		
			66 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
| //-----------------------------------------------------------------------------
 | |
| // Torque Game Engine 
 | |
| // Copyright (C) GarageGames.com, Inc.
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| #include "map2difPlus/editGeometry.h"
 | |
| #include "map2difPlus/entityTypes.h"
 | |
| #include "core/tokenizer.h"
 | |
| #include "map2difPlus/csgBrush.h"
 | |
| #include "map2difPlus/lmapPacker.h"
 | |
| 
 | |
| #include "interior/interior.h"
 | |
| #include "dgl/gBitmap.h"
 | |
| #include "core/resManager.h"
 | |
| #include "math/mMath.h"
 | |
| #include "core/bitVector.h"
 | |
| #include "dgl/gTexManager.h"
 | |
| 
 | |
| extern bool gVerbose;
 | |
| extern const char* gWadPath;
 | |
| extern bool gTextureSearch;
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| //-------------------------------------- Utility classes
 | |
| //
 | |
| inline U32 calcHash(const Point3D& in_rPoint)
 | |
| {
 | |
|    U32 x = U32(S32(in_rPoint.x) >> 5) & 0xf;
 | |
|    U32 y = U32(S32(in_rPoint.y) >> 5) & 0xf;
 | |
|    U32 z = U32(S32(in_rPoint.z) >> 5) & 0xf;
 | |
| 
 | |
|    return (x << 8) | (y << 4) | (z << 0);
 | |
| }
 | |
| 
 | |
| inline U32 calcHash(const Point3D& in_rNormal, const F64 in_dist)
 | |
| {
 | |
|    F64 mul = getMax(getMax(mFabs(in_rNormal.x), mFabs(in_rNormal.y)), mFabs(in_rNormal.z));
 | |
|    mul = mFloor(mul * 100.0 + 0.5) / 100.0;
 | |
|    F64 val = mul * (mFloor(mFabs(in_dist) * 100.0 + 0.5) / 100.0);
 | |
| 
 | |
|    U32 hash = U32(val) % (1 << 12);
 | |
|    return hash;
 | |
| }
 | |
| 
 | |
| void flushedOutput(const char* string)
 | |
| {
 | |
|    dPrintf(string);
 | |
|    dFflushStdout();
 | |
| }
 | |
| 
 | |
| GBitmap* loadBitmap(const char* file)
 | |
| {
 | |
|    FileStream loadStream;
 | |
|    int len = dStrlen(file);
 | |
|    char* buf = new char[len + 5];
 | |
|    char* ext = &buf[len];
 | |
|    dStrcpy(buf,file);
 | |
| 
 | |
|    // PNG first
 | |
|    dStrcpy(ext,".png");
 | |
|    loadStream.open(buf, FileStream::Read);
 | |
|    if (loadStream.getStatus() == Stream::Ok)
 | |
|    {
 | |
|       GBitmap * bitmap = new GBitmap;
 | |
|       if(!bitmap->readPNG(loadStream))
 | |
|       {
 | |
|          AssertISV(false,avar("Bad PNG file: %s.\n", file));
 | |
|       }
 | |
|       return bitmap;
 | |
|    }
 | |
| 
 | |
|    // JPEG next
 | |
|    dStrcpy(ext,".jpg");
 | |
|    loadStream.open(buf, FileStream::Read);
 | |
|    if (loadStream.getStatus() == Stream::Ok)
 | |
|    {
 | |
|       GBitmap * bitmap = new GBitmap;
 | |
|       if(!bitmap->readJPEG(loadStream))
 | |
|       {
 | |
|          AssertISV(false,avar("Bad JPEG file: %s.\n", file));
 | |
|       }
 | |
|       return bitmap;
 | |
|    }
 | |
| 
 | |
|    // If unable to load texture in current directory look
 | |
|    // in the parent directory. But never look in the root.
 | |
|    if (gTextureSearch)
 | |
|    {
 | |
|       // If we don't flip the slashes then 
 | |
|       // texture search will fail.
 | |
|       char* rev;
 | |
|       while( rev = dStrrchr( buf, '\\' ) )
 | |
|       {
 | |
|          *rev = '/';
 | |
|       }
 | |
| 
 | |
|       *ext = 0;
 | |
|       char *name = dStrrchr(buf, '/');
 | |
|       if(name)
 | |
|       {
 | |
|          *name++ = 0;
 | |
|          char *parent = dStrrchr(buf, '/');
 | |
|          if(parent)
 | |
|          {
 | |
|             parent[1] = 0;
 | |
|             dStrcat(buf, name);
 | |
|             return loadBitmap(buf);
 | |
|          }
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    return NULL;
 | |
| }
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| //-------------------------------------- EditGeometry functionality
 | |
| //
 | |
| EditGeometry* gWorkingGeometry = NULL;
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| EditGeometry::EditGeometry()
 | |
|    : mPoints(1 << 15),
 | |
|      mPlaneEQs(1 << 11),
 | |
|      mNodeArena(512),
 | |
|      mVisLinkArena(8192),
 | |
|      mPlaneHashArena(1024),
 | |
|      mPointHashArena(1024),
 | |
|      mHasAlarmState(false)
 | |
| {
 | |
|    mNumAmbiguousBrushes = 0;
 | |
|    mNumOrphanPolys = 0;
 | |
| 
 | |
|    for (U32 i = 0; i < (1 << 12); i++) {
 | |
|       mPointHashTable[i].pNext = NULL;
 | |
|       mPlaneHashTable[i].pNext = NULL;
 | |
|    }
 | |
| 
 | |
|    mBSPRoot = NULL;
 | |
| 
 | |
|    mOutsideZoneIndex = -1;
 | |
| 
 | |
|    mMinBound =  Point3D(1 << 30, 1 << 30, 1 << 30);
 | |
|    mMaxBound = -Point3D(1 << 30, 1 << 30, 1 << 30);
 | |
| 
 | |
|    mCurrBrushId = 0;
 | |
|    mSurfaceKey  = 0;
 | |
| 
 | |
|    mWorldEntity = NULL;
 | |
| 
 | |
|    setGraphGeneration(false);
 | |
| }
 | |
| 
 | |
| EditGeometry::~EditGeometry()
 | |
| {
 | |
|    for (U32 i = 0; i < mEntities.size(); i++) {
 | |
|       delete mEntities[i];
 | |
|       mEntities[i] = NULL;
 | |
|    }
 | |
|    delete mWorldEntity;
 | |
|    mWorldEntity = NULL;
 | |
| 
 | |
|    for (U32 i = 0; i < mStructuralBrushes.size(); i++) {
 | |
|       gBrushArena.freeBrush(mStructuralBrushes[i]);
 | |
|       mStructuralBrushes[i] = NULL;
 | |
|    }
 | |
|    for (U32 i = 0; i < mDetailBrushes.size(); i++) {
 | |
|       gBrushArena.freeBrush(mDetailBrushes[i]);
 | |
|       mDetailBrushes[i] = NULL;
 | |
|    }
 | |
|    for (U32 i = 0; i < mPortalBrushes.size(); i++) {
 | |
|       gBrushArena.freeBrush(mPortalBrushes[i]);
 | |
|       mPortalBrushes[i] = NULL;
 | |
|    }
 | |
|    for (U32 i = 0; i < mSpecialCollisionBrushes.size(); i++) {
 | |
|       gBrushArena.freeBrush(mSpecialCollisionBrushes[i]);
 | |
|       mSpecialCollisionBrushes[i] = NULL;
 | |
|    }
 | |
|    for (U32 i = 0; i < mVehicleCollisionBrushes.size(); i++) {
 | |
|       gBrushArena.freeBrush(mVehicleCollisionBrushes[i]);
 | |
|       mVehicleCollisionBrushes[i] = NULL;
 | |
|    }
 | |
|    for (U32 i = 0; i < mTextureNames.size(); i++) {
 | |
|       delete [] mTextureNames[i];
 | |
|       mTextureNames[i] = NULL;
 | |
|    }
 | |
|    for (U32 i = 0; i < mTextures.size(); i++) {
 | |
|       delete mTextures[i];
 | |
|       mTextures[i] = NULL;
 | |
|    }
 | |
|    for (U32 i = 0; i < mZones.size(); i++) {
 | |
|       delete mZones[i];
 | |
|       mZones[i] = NULL;
 | |
|    }
 | |
|    for (U32 i = 0; i < mPortals.size(); i++) {
 | |
|       delete mPortals[i];
 | |
|       mPortals[i] = NULL;
 | |
|    }
 | |
| 
 | |
|    for (U32 i = 0; i < mSurfaces.size(); i++) {
 | |
|       delete mSurfaces[i].pNormalLMap;
 | |
|       delete mSurfaces[i].pAlarmLMap;
 | |
|       mSurfaces[i].pNormalLMap = NULL;
 | |
|       mSurfaces[i].pAlarmLMap  = NULL;
 | |
|    }
 | |
|    for (U32 i = 0; i < mLightmaps.size(); i++) {
 | |
|       delete mLightmaps[i];
 | |
|       mLightmaps[i] = NULL;
 | |
|    }
 | |
| 
 | |
|    for (U32 i = 0; i < mAnimatedLights.size(); i++) {
 | |
|       AnimatedLight* pLight = mAnimatedLights[i];
 | |
|       delete [] pLight->name;
 | |
|       pLight->name = NULL;
 | |
|       pLight->type = 0;
 | |
| 
 | |
|       for (U32 j = 0; j < pLight->states.size(); j++) {
 | |
|          LightState* pState = pLight->states[j];
 | |
|          for (U32 k = 0; k < pState->stateData.size(); k++)
 | |
|             delete pState->stateData[k].pLMap;
 | |
| 
 | |
|          delete pState;
 | |
|       }
 | |
| 
 | |
|       delete pLight;
 | |
|    }
 | |
| }
 | |
| 
 | |
| void EditGeometry::nudge()
 | |
| {
 | |
| }
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| bool EditGeometry::parseMapFile(Tokenizer* pToker)
 | |
| {
 | |
|    extern int gQuakeVersion;
 | |
| 
 | |
|    // Make sure that "NULL" is the first (0 index) textures
 | |
|    AssertFatal(mTextureNames.size() == 0, "Internal Error, mTextureNames must be empty here!");
 | |
|    insertTexture("NULL");
 | |
|    insertTexture("ORIGIN");
 | |
|    insertTexture("TRIGGER");
 | |
|    insertTexture("EMITTER");
 | |
| 
 | |
|    while (pToker->advanceToken(true)) {
 | |
|       if (pToker->getToken()[0] != '{')
 | |
|          goto EntityError;
 | |
| 
 | |
| 
 | |
|       pToker->advanceToken(true);
 | |
|       if (pToker->tokenICmp("classname") == false)
 | |
|          goto EntityError;
 | |
| 
 | |
|       if (pToker->advanceToken(false) == false)
 | |
|          goto EntityError;
 | |
| 
 | |
|       Entity* pEntity = parseEntity(pToker);
 | |
|       if (dynamic_cast<WorldSpawnEntity*>(pEntity) != NULL) {
 | |
|          if (mWorldEntity == NULL)
 | |
|             mWorldEntity = static_cast<WorldSpawnEntity*>(pEntity);
 | |
|          else
 | |
|             mEntities.push_back(pEntity);
 | |
|       } else {
 | |
|          // Some other sort of entity.  Need to save these...
 | |
|          if (pEntity != NULL)
 | |
|             mEntities.push_back(pEntity);
 | |
|       }
 | |
|       AssertISV(pEntity != NULL, avar("Unknown entity classname: %s", pToker->getToken()));
 | |
| 
 | |
|       while (pToker->getToken()[0] == '{') {
 | |
|          // In case we don't know what brush type we're dealing with, we can
 | |
|          //  throw it out...
 | |
|          CSGBrush  trashBrush;
 | |
|          CSGBrush* pBrush;
 | |
|          
 | |
|          if (pEntity != NULL) {
 | |
|             switch (pEntity->getBrushType()) {
 | |
|                case StructuralBrush:
 | |
|                   mStructuralBrushes.push_back(gBrushArena.allocateBrush());
 | |
|                   pBrush = mStructuralBrushes.last();
 | |
|                   pBrush->mBrushType = StructuralBrush;
 | |
| 
 | |
|                   pBrush->materialType = 0;
 | |
|                   pBrush->brushId      = mCurrBrushId++;
 | |
|                   break;
 | |
|                case DetailBrush:
 | |
|                   mDetailBrushes.push_back(gBrushArena.allocateBrush());
 | |
|                   pBrush = mDetailBrushes.last();
 | |
|                   pBrush->mBrushType = DetailBrush;
 | |
| 
 | |
|                   pBrush->materialType = 0;
 | |
|                   pBrush->brushId      = mCurrBrushId++;
 | |
|                   break;
 | |
|                case PortalBrush:
 | |
|                   AssertFatal(mWorldEntity != NULL, "World entity should have been first!");
 | |
|                   mPortalBrushes.push_back(gBrushArena.allocateBrush());
 | |
|                   mPortalEntities.push_back(pEntity);
 | |
|                   pBrush = mPortalBrushes.last();
 | |
|                   pBrush->mBrushType = PortalBrush;
 | |
| 
 | |
|                   pBrush->materialType = 0;
 | |
|                   pBrush->brushId      = mCurrBrushId++;
 | |
|                   break;
 | |
|                case CollisionBrush:
 | |
|                   mSpecialCollisionBrushes.push_back(gBrushArena.allocateBrush());
 | |
|                   pBrush = mSpecialCollisionBrushes.last();
 | |
|                   pBrush->mBrushType = CollisionBrush;
 | |
| 
 | |
|                   pBrush->materialType = 0;
 | |
|                   pBrush->brushId      = mCurrBrushId++;
 | |
|                   break;
 | |
| 
 | |
|                case VehicleCollisionBrush:
 | |
|                   mVehicleCollisionBrushes.push_back(gBrushArena.allocateBrush());
 | |
|                   pBrush = mVehicleCollisionBrushes.last();
 | |
|                   pBrush->mBrushType = VehicleCollisionBrush;
 | |
| 
 | |
|                   pBrush->materialType = 0;
 | |
|                   pBrush->brushId      = mCurrBrushId++;
 | |
|                   break;
 | |
| 
 | |
|                default:
 | |
|                   AssertISV(false, "Unknown brush type returned from entity");
 | |
|                   pBrush = &trashBrush;
 | |
|                   break;
 | |
|             }
 | |
|          }
 | |
|          else {
 | |
|             pBrush = &trashBrush;
 | |
|          }
 | |
|          CSGBrush& rBrush = *pBrush;
 | |
| 
 | |
|          pToker->advanceToken(true);
 | |
| 
 | |
|          if (!parseBrush (rBrush, pToker, *this)) goto EntityBrushlistError;
 | |
| 
 | |
|          if (pToker->getToken()[0] != '}')
 | |
|             goto EntityBrushlistError;
 | |
| 
 | |
|          mNumAmbiguousBrushes += (rBrush.disambiguate() == true) ? 1 : 0;
 | |
|          pToker->advanceToken(true);
 | |
|       }
 | |
|       
 | |
|       if (pToker->getToken()[0] != '}')
 | |
|          goto EntityBrushlistError;
 | |
|    }
 | |
| 
 | |
|    for (U32 i = 0; i < mEntities.size(); i++) {
 | |
|       if (dynamic_cast<MirrorSurfaceEntity*>(mEntities[i]) != NULL) {
 | |
|          MirrorSurfaceEntity* entity = static_cast<MirrorSurfaceEntity*>(mEntities[i]);
 | |
| 
 | |
|          entity->markSurface(mStructuralBrushes, mDetailBrushes);
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    // Finished!  At this point, all the Entities should be entered, each with
 | |
|    //  their Brushes planed and entered into the appropriate brush list...
 | |
|    //
 | |
|    AssertISV(mWorldEntity != NULL, "Invalid map: must have a worldspawn entity");
 | |
|    return true;
 | |
| 
 | |
|    // Parsing errors. Yuck. Gotos.
 | |
|    //
 | |
| EntityError:
 | |
|    dPrintf("Error processing entity on or near line: %s: %d", pToker->getFileName(),
 | |
|            pToker->getCurrentLine());
 | |
|    return false;
 | |
| 
 | |
| EntityBrushlistError:
 | |
|    dPrintf("Error processing entity brushlist on or near line: %s: %d", pToker->getFileName(),
 | |
|            pToker->getCurrentLine());
 | |
|    return false;
 | |
| }   
 | |
| 
 | |
| 
 | |
| EditGeometry::Entity* EditGeometry::parseEntity(Tokenizer* pToker)
 | |
| {
 | |
|    Entity* pEntity = NULL;
 | |
| 
 | |
|    //if (pToker->tokenICmp(WorldSpawnEntity::getClassName()))
 | |
|    //   pEntity = new WorldSpawnEntity;
 | |
|    //else if (pToker->tokenICmp(DetailEntity::getClassName()))
 | |
|    //   pEntity = new DetailEntity;
 | |
|    //else if (pToker->tokenICmp(CollisionEntity::getClassName()))
 | |
|    //   pEntity = new CollisionEntity;
 | |
|    //else if (pToker->tokenICmp(VehicleCollisionEntity::getClassName()))
 | |
|    //   pEntity = new VehicleCollisionEntity;
 | |
|    //else if (pToker->tokenICmp(PortalEntity::getClassName()))
 | |
|    //   pEntity = new PortalEntity;
 | |
|    //else if (pToker->tokenICmp(TargetEntity::getClassName()))
 | |
|    //   pEntity = new TargetEntity;
 | |
| 
 | |
|    //// lighting: emitters
 | |
|    //else if (pToker->tokenICmp(PointEmitterEntity::getClassName()))
 | |
|    //   pEntity = new PointEmitterEntity;
 | |
|    //else if (pToker->tokenICmp(SpotEmitterEntity::getClassName()))
 | |
|    //   pEntity = new SpotEmitterEntity;
 | |
| 
 | |
|    //// lighting: lights
 | |
|    //else if (pToker->tokenICmp(LightEntity::getClassName()))
 | |
|    //   pEntity = new LightEntity;
 | |
| 
 | |
|    //// lighting: scripted lights
 | |
|    //else if (pToker->tokenICmp(LightOmniEntity::getClassName()))
 | |
|    //   pEntity = new LightOmniEntity;
 | |
|    //else if (pToker->tokenICmp(LightSpotEntity::getClassName()))
 | |
|    //   pEntity = new LightSpotEntity;
 | |
| 
 | |
|    //// lighting: animated lights
 | |
|    //else if (pToker->tokenICmp(LightStrobeEntity::getClassName()))
 | |
|    //   pEntity = new LightStrobeEntity;
 | |
|    //else if (pToker->tokenICmp(LightPulseEntity::getClassName()))
 | |
|    //   pEntity = new LightPulseEntity;
 | |
|    //else if (pToker->tokenICmp(LightPulse2Entity::getClassName()))
 | |
|    //   pEntity = new LightPulse2Entity;
 | |
|    //else if (pToker->tokenICmp(LightFlickerEntity::getClassName()))
 | |
|    //   pEntity = new LightFlickerEntity;
 | |
|    //else if (pToker->tokenICmp(LightRunwayEntity::getClassName()))
 | |
|    //   pEntity = new LightRunwayEntity;
 | |
| 
 | |
|    //// Special classes
 | |
|    //else if (pToker->tokenICmp(MirrorSurfaceEntity::getClassName()))
 | |
|    //   pEntity = new MirrorSurfaceEntity;
 | |
|    //else if (pToker->tokenICmp(DoorEntity::getClassName()))
 | |
|    //   pEntity = new DoorEntity;
 | |
|    //else if (pToker->tokenICmp(ForceFieldEntity::getClassName()))
 | |
|    //   pEntity = new ForceFieldEntity;
 | |
|    //else if (pToker->tokenICmp(SpecialNodeEntity::getClassName()))
 | |
|    //   pEntity = new SpecialNodeEntity;
 | |
| 
 | |
|    // Path classes
 | |
|    //else if (pToker->tokenICmp(PathNodeEntity::getClassName()))
 | |
|    //   pEntity = new PathNodeEntity;
 | |
| 
 | |
|    //// Trigger Classes
 | |
|    //else if (pToker->tokenICmp(TriggerEntity::getClassName()))
 | |
|    //   pEntity = new TriggerEntity;
 | |
| 
 | |
|    //else {
 | |
|    //   pEntity = new GameEntity(pToker->getToken());
 | |
|    //   // Unknown
 | |
|    //}
 | |
| 
 | |
|    //if (pEntity) {
 | |
|    //   if (pEntity->parseEntityDescription(pToker) == false) {
 | |
|    //      dPrintf("Error processing entity on or near line: %s: %d", pToker->getFileName(),
 | |
|    //              pToker->getCurrentLine());
 | |
|    //      delete pEntity;
 | |
|    //      return NULL;
 | |
|    //   }
 | |
|    //}
 | |
| 
 | |
|    return pEntity;
 | |
| }
 | |
| 
 | |
| 
 | |
| const EditGeometry::Entity* EditGeometry::getNamedEntity(const char* name) const
 | |
| {
 | |
|    for (U32 i = 0; i < mEntities.size(); i++)
 | |
|       if (dStricmp(name, mEntities[i]->getName()) == 0)
 | |
|          return mEntities[i];
 | |
| 
 | |
|    return NULL;
 | |
| }
 | |
| 
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| const char* EditGeometry::insertTexture(const char* pInsert)
 | |
| {
 | |
|    AssertFatal(pInsert != NULL && pInsert[0] != '\0', "EditGeometry::insertTexture: Bad texture name");
 | |
| 
 | |
|    for (U32 i = 0; i < mTextureNames.size(); i++) {
 | |
|       if (dStricmp(mTextureNames[i], pInsert) == 0)
 | |
|          return mTextureNames[i];
 | |
|    }
 | |
| 
 | |
|    char* pCopy = new char[dStrlen(pInsert) + 1];
 | |
|    dStrcpy(pCopy, pInsert);
 | |
|    mTextureNames.push_back(pCopy);
 | |
| 
 | |
|    return pCopy;
 | |
| }
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| 
 | |
| // F64   gPlaneNormThresh = 1.0;
 | |
| // F64   gPlaneDistThresh = 0.0;
 | |
| static F64   gPlaneNormThresh64 = 0.99;
 | |
| static F64   gPlaneDistThresh64 = 0.01;
 | |
| 
 | |
| U32 EditGeometry::insertPlaneEQ(const Point3D& normal, const F64 dist)
 | |
| {
 | |
|    Point3D insertNormal = normal;
 | |
|    F64  insertDist   = dist;
 | |
| 
 | |
|    F64 len    = insertNormal.len();
 | |
|    insertNormal /= len;
 | |
|    insertDist   /= len;
 | |
| 
 | |
|    U32 hash = calcHash(insertNormal, insertDist);
 | |
|    
 | |
|    PlaneHashEntry* pSearch = mPlaneHashTable[hash].pNext;
 | |
|    if( mHashPlanes ){
 | |
|       while (pSearch != NULL) {
 | |
|          if (mDot(insertNormal, mPlaneEQs[pSearch->planeIndex].normal) > gPlaneNormThresh64 &&
 | |
|              mFabs(insertDist - mPlaneEQs[pSearch->planeIndex].dist) < gPlaneDistThresh64) {
 | |
|             return pSearch->planeIndex;
 | |
|          }
 | |
| 
 | |
|          pSearch = pSearch->pNext;
 | |
|       }
 | |
|    }
 | |
|    else{
 | |
|       while (pSearch != NULL) {
 | |
|          if( insertNormal == mPlaneEQs[pSearch->planeIndex].normal &&
 | |
|              insertDist == mPlaneEQs[pSearch->planeIndex].dist )
 | |
|             return pSearch->planeIndex;
 | |
|          pSearch = pSearch->pNext;
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    // Gotta insert!  Also, put it's negative in the table as well...
 | |
|    //
 | |
|    PlaneHashEntry* pInsert     = mPlaneHashArena.allocateEntry();
 | |
|    pInsert->pNext              = mPlaneHashTable[hash].pNext;
 | |
|    mPlaneHashTable[hash].pNext = pInsert;
 | |
|    pInsert->planeIndex         = mPlaneEQs.size();
 | |
| 
 | |
|    mPlaneEQs.increment();
 | |
|    mPlaneEQs.last().normal.x  = insertNormal.x;
 | |
|    mPlaneEQs.last().normal.y  = insertNormal.y;
 | |
|    mPlaneEQs.last().normal.z  = insertNormal.z;
 | |
|    mPlaneEQs.last().dist      = insertDist;
 | |
|    
 | |
|    hash = calcHash(-insertNormal, -insertDist);
 | |
| 
 | |
|    PlaneHashEntry* pNegative   = mPlaneHashArena.allocateEntry();
 | |
|    pNegative->pNext            = mPlaneHashTable[hash].pNext;
 | |
|    mPlaneHashTable[hash].pNext = pNegative;
 | |
|    pNegative->planeIndex       = mPlaneEQs.size();
 | |
| 
 | |
|    mPlaneEQs.increment();
 | |
|    mPlaneEQs.last().normal.x  = -insertNormal.x;
 | |
|    mPlaneEQs.last().normal.y  = -insertNormal.y;
 | |
|    mPlaneEQs.last().normal.z  = -insertNormal.z;
 | |
|    mPlaneEQs.last().dist      = -insertDist;
 | |
| 
 | |
|    return pInsert->planeIndex;
 | |
| }
 | |
| 
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| U32 EditGeometry::insertPoint(const Point3D& rPoint)
 | |
| {
 | |
|    U32 baseHash = calcHash(rPoint);
 | |
| 
 | |
|    if( ! mHashPoints ){
 | |
|       // keeping all points distinct
 | |
|       PointHashEntry * pSearch = mPointHashTable[baseHash].pNext;
 | |
|       while( pSearch != NULL ) {
 | |
|          if( mPoints[pSearch->pointIndex] == rPoint )
 | |
|             return pSearch->pointIndex;
 | |
|          pSearch = pSearch->pNext;
 | |
|       }
 | |
|       // exact match not found, insert
 | |
|       PointHashEntry * pInsert = mPointHashArena.allocateEntry();
 | |
|       pInsert->pNext = mPointHashTable[baseHash].pNext;
 | |
|       mPointHashTable[baseHash].pNext = pInsert;
 | |
|       mPoints.push_back( rPoint );
 | |
|       return( pInsert->pointIndex = mPoints.size() - 1 );
 | |
|    }
 | |
| 
 | |
|    PointHashEntry* pSearch = mPointHashTable[baseHash].pNext;
 | |
|    while (pSearch != NULL) {
 | |
|       if (mFabs(mPoints[pSearch->pointIndex].x - rPoint.x) < gcPointEpsilon &&
 | |
|           mFabs(mPoints[pSearch->pointIndex].y - rPoint.y) < gcPointEpsilon &&
 | |
|           mFabs(mPoints[pSearch->pointIndex].z - rPoint.z) < gcPointEpsilon) {
 | |
|          if ((mPoints[pSearch->pointIndex] - rPoint).len() < gcPointEpsilon) {
 | |
|             return pSearch->pointIndex;
 | |
|          }
 | |
|       }
 | |
|       pSearch = pSearch->pNext;
 | |
|    }
 | |
| 
 | |
|    // Oops, not found.  Insert it.  Lame and slow, but hey.
 | |
|    const static F64 dists[3] = { -gcPointEpsilon, 0.0, gcPointEpsilon };
 | |
| 
 | |
|    UniqueVector bins(27);
 | |
|    for (U32 i = 0; i < 3; i++) 
 | |
|       for (U32 j = 0; j < 3; j++) 
 | |
|          for (U32 k = 0; k < 3; k++)
 | |
|             bins.pushBackUnique(calcHash(rPoint + Point3D(dists[i], dists[j], dists[k])));
 | |
| 
 | |
|    for (U32 i = 0; i < bins.size(); i++) {
 | |
|       PointHashEntry* pInsert = mPointHashArena.allocateEntry();
 | |
|       pInsert->pNext = mPointHashTable[bins[i]].pNext;
 | |
|       mPointHashTable[bins[i]].pNext = pInsert;
 | |
|       pInsert->pointIndex = mPoints.size();
 | |
|    }
 | |
| 
 | |
|    mPoints.increment();
 | |
|    mPoints.last() = rPoint;
 | |
| 
 | |
|    return mPoints.size() - 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| bool EditGeometry::createBSP()
 | |
| {
 | |
|    createBrushPolys();
 | |
|    
 | |
|    buildBSP();
 | |
| 
 | |
|    //if (gVerbose) {
 | |
|    //   U32 totalLeaves    = 0;
 | |
|    //   U32 totalLeafDepth = 0;
 | |
|    //   U32 maxLeafDepth   = 0;
 | |
|    //   mBSPRoot->gatherBSPStats(&totalLeaves, &totalLeafDepth, &maxLeafDepth, 0);
 | |
| 
 | |
|    //   dPrintf("     * BSP Stats\n"
 | |
|    //           "        Total Nodes:  %d\n"
 | |
|    //           "        (Av./Max) Depth: %g / %d\n", (mNodeArena.mBuffers.size() - 1) * mNodeArena.arenaSize + mNodeArena.currPosition,
 | |
|    //           F32(totalLeafDepth) / F32(totalLeaves),
 | |
|    //           maxLeafDepth);
 | |
|    //   flushedOutput("\n");
 | |
|    //}
 | |
| 
 | |
|    return true;
 | |
| }
 | |
| 
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| void EditGeometry::createBrushPolys()
 | |
| {
 | |
|    // Has the side effect of creating the Geometry's bounding box.  We need
 | |
|    //  to expand that box slightly at the end of the operation, to ensure
 | |
|    //  good vislinks between nodes on the outside in some cases.
 | |
| 
 | |
|    // Structural
 | |
|    for (U32 i = 0; i < mStructuralBrushes.size();)
 | |
|    {
 | |
|       if(mStructuralBrushes[i]->selfClip())
 | |
|          i++;
 | |
|       else
 | |
|          mStructuralBrushes.erase(i);
 | |
|    }
 | |
|    // Detail
 | |
|    for (U32 i = 0; i < mDetailBrushes.size();)
 | |
|    {
 | |
|       if(mDetailBrushes[i]->selfClip())
 | |
|           i++;
 | |
|       else
 | |
|          mDetailBrushes.erase(i);
 | |
|    }
 | |
| 
 | |
|    // Portal
 | |
|    for (U32 i = 0; i < mPortalBrushes.size();)
 | |
|    {
 | |
|       if(mPortalBrushes[i]->selfClip())
 | |
|          i++;
 | |
|       else
 | |
|          mPortalBrushes.erase(i);
 | |
|    }
 | |
| 
 | |
|    mMinBound -= Point3D(1, 1, 1);
 | |
|    mMaxBound += Point3D(1, 1, 1);
 | |
| }
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| void EditGeometry::buildBSP()
 | |
| {
 | |
|    Vector<CSGBrush*> copyStructural;
 | |
|    Vector<CSGBrush*> copyDetail;
 | |
|    for (U32 i = 0; i < mStructuralBrushes.size(); i++) {
 | |
|       copyStructural.push_back(gBrushArena.allocateBrush());
 | |
|       copyStructural.last()->copyBrush(mStructuralBrushes[i]);
 | |
|    }
 | |
|    for (U32 i = 0; i < mDetailBrushes.size(); i++) {
 | |
|       copyDetail.push_back(gBrushArena.allocateBrush());
 | |
|       copyDetail.last()->copyBrush(mDetailBrushes[i]);
 | |
|    }
 | |
| 
 | |
|    mBSPRoot               = mNodeArena.allocateNode(NULL);
 | |
|    mBSPRoot->planeType    = EditBSPNode::Structural;
 | |
| 
 | |
|    //-------------------------------------- Structural Brushes
 | |
|    //
 | |
|    mBSPRoot->brushList.reserve(copyStructural.size());
 | |
|    for (U32 i = 0; i < copyStructural.size(); i++)
 | |
|       mBSPRoot->brushList.push_back(copyStructural[i]);
 | |
|    copyStructural.clear();
 | |
| 
 | |
|    mBSPRoot->createBSP(EditBSPNode::Structural);
 | |
| 
 | |
|    //-------------------------------------- Portal Brushes
 | |
|    //
 | |
|    Vector<EditBSPNode::PortalEntry*> portalEntries(mPortalBrushes.size());
 | |
|    for (U32 i = 0; i < mPortalEntities.size(); i++) {
 | |
|       CSGBrush* pBrush = mPortalBrushes[i];
 | |
|       EditBSPNode::PortalEntry* pMunged = mBSPRoot->mungePortalBrush(pBrush);
 | |
| 
 | |
|       if (pMunged != NULL) {
 | |
|          Entity* pEntity = mPortalEntities[i];
 | |
|          PortalEntity* pPortalEntity = dynamic_cast<PortalEntity*>(pEntity);
 | |
|          AssertFatal(pPortalEntity, "Internal Error: we should have had a portal here");
 | |
| 
 | |
|          portalEntries.push_back(pMunged);
 | |
|          portalEntries.last()->portalId = portalEntries.size() - 1;
 | |
| 
 | |
|          // Get our own portal entry setup
 | |
|          mPortals.push_back(new Portal);
 | |
|          mPortals.last()->portalId         = portalEntries.size() - 1;
 | |
|          mPortals.last()->planeEQIndex     = -1;
 | |
|          mPortals.last()->frontZone        = -1;
 | |
|          mPortals.last()->backZone         = -1;
 | |
|          mPortals.last()->passAmbientLight = pPortalEntity->passAmbientLight;
 | |
|          mPortals.last()->x                = portalEntries.last()->ortho1;
 | |
|          mPortals.last()->y                = portalEntries.last()->ortho2;
 | |
|       }
 | |
|    }
 | |
|    for (U32 i = 0; i < portalEntries.size(); i++)
 | |
|       mBSPRoot->insertPortalEntry(portalEntries[i]);
 | |
|    portalEntries.clear();
 | |
|    
 | |
|    mBSPRoot->createVisLinks();
 | |
| 
 | |
|    if (gVerbose) {
 | |
|       U32 emptyLeaves = 0;
 | |
|       U32 totalLinks  = 0;
 | |
|       U32 maxLinks    = 0;
 | |
|       mBSPRoot->gatherVISStats(&emptyLeaves, &totalLinks, &maxLinks);
 | |
|    
 | |
|       dPrintf("     * VIS Stats\n"
 | |
|               "        # Empty leaves: %d\n"
 | |
|               "        Av. Links/Node: %g\n"
 | |
|               "        Max Links/Node: %d", emptyLeaves,
 | |
|               F32(totalLinks) / F32(emptyLeaves),
 | |
|               maxLinks);
 | |
|       flushedOutput("\n");
 | |
|    }
 | |
| 
 | |
|    // Very lame here.  We need to ensure that the outside zone is zero, so find the leaf
 | |
|    //  for a point way outside the interior, and flood that manually.  Then we can proceed
 | |
|    //  with the recursive function.
 | |
|    Point3D testPoint = mMinBound - Point3D(10, 10, 10);
 | |
|    EditBSPNode* currNode = mBSPRoot;
 | |
|    while (currNode->planeEQIndex != -1) {
 | |
|       const PlaneEQ& rPlane = getPlaneEQ(currNode->planeEQIndex);
 | |
|       if (rPlane.whichSide(testPoint) == PlaneBack) currNode = currNode->pBack;
 | |
|       else                                          currNode = currNode->pFront;
 | |
|    }
 | |
|    AssertFatal(currNode->isSolid == false, "Internal Error: should not be solid!");
 | |
|    mZones.push_back(new EditGeometry::Zone);
 | |
|    S32 markZoneId = (mZones.size() - 1);
 | |
|    mZones.last()->zoneId = markZoneId;
 | |
|    currNode->floodZone(markZoneId);
 | |
| 
 | |
|    mBSPRoot->zoneFlood();
 | |
|    findOutsideZone();
 | |
|    AssertFatal(mOutsideZoneIndex == 0, "Internal Error: outside zone must always be 0");
 | |
| 
 | |
|    mBSPRoot->gatherPortals();
 | |
|    for (U32 i = 0; i < mPortals.size(); i++) {
 | |
|       Portal* pPortal = mPortals[i];
 | |
| 
 | |
|       pPortal->mMin = Point3D( 1e10,  1e10,  1e10);
 | |
|       pPortal->mMax = Point3D(-1e10, -1e10, -1e10);
 | |
| 
 | |
|       for (U32 j = 0; j < pPortal->windings.size(); j++) {
 | |
|          for (U32 k = 0; k < pPortal->windings[j].numIndices; k++) {
 | |
|             pPortal->mMin.setMin(getPoint(pPortal->windings[j].indices[k]));
 | |
|             pPortal->mMax.setMax(getPoint(pPortal->windings[j].indices[k]));
 | |
|          }
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    enterPortalZoneRefs();
 | |
| 
 | |
|    floodAmbientLight();
 | |
| 
 | |
|    mBSPRoot->removeVISInfo();
 | |
|    mVisLinkArena.clearAll();
 | |
| 
 | |
|    //-------------------------------------- Detail and Brushes
 | |
|    //
 | |
|    mBSPRoot->brushList.reserve(copyDetail.size());
 | |
|    for (U32 i = 0; i < copyDetail.size(); i++)
 | |
|       mBSPRoot->brushList.push_back(copyDetail[i]);
 | |
|    copyDetail.clear();
 | |
|    mBSPRoot->createBSP(EditBSPNode::Detail);
 | |
| }
 | |
| 
 | |
| void EditGeometry::enterPortalZoneRefs()
 | |
| {
 | |
|    for (U32 i = 0; i < mZones.size(); i++) {
 | |
|       Zone* pZone = mZones[i];
 | |
|       for (U32 j = 0; j < mPortals.size(); j++) {
 | |
|          Portal* pPortal = mPortals[j];
 | |
| 
 | |
|          if (U32(pPortal->frontZone) == U32(pZone->zoneId) ||
 | |
|              U32(pPortal->backZone)  == U32(pZone->zoneId))
 | |
|             pZone->referencingPortals.push_back(j);
 | |
|       }
 | |
|    }
 | |
| }
 | |
| 
 | |
| 
 | |
| void EditGeometry::ambientVisitZone(const U32 zone)
 | |
| {
 | |
|    if (mZones[zone]->ambientLit == true)
 | |
|       return;
 | |
| 
 | |
|    Zone* pZone = mZones[zone];
 | |
| 
 | |
|    pZone->ambientLit = true;
 | |
|    for (U32 i = 0; i < pZone->referencingPortals.size(); i++) {
 | |
|       Portal* pPortal = mPortals[pZone->referencingPortals[i]];
 | |
| 
 | |
|       S32 otherZone = -1;
 | |
|       if (pPortal->frontZone != pZone->zoneId)
 | |
|          otherZone = pPortal->frontZone;
 | |
|       if (pPortal->backZone != pZone->zoneId)
 | |
|          otherZone = pPortal->backZone;
 | |
| 
 | |
|       if (otherZone != -1 && pPortal->passAmbientLight)
 | |
|          ambientVisitZone(U32(otherZone));
 | |
|    }
 | |
| }
 | |
| 
 | |
| void EditGeometry::floodAmbientLight()
 | |
| {
 | |
|    for (U32 i = 0; i < mZones.size(); i++)
 | |
|       mZones[i]->ambientLit = false;
 | |
| 
 | |
|    ambientVisitZone(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| void EditGeometry::createSurfaces()
 | |
| {
 | |
|    for (U32 i = 0; i < mStructuralBrushes.size(); i++)
 | |
|       createBrushSurfaces(*mStructuralBrushes[i]);
 | |
|    sortSurfaces();
 | |
| 
 | |
|    for (U32 i = 0; i < mDetailBrushes.size(); i++)
 | |
|       createBrushSurfaces(*mDetailBrushes[i]);
 | |
|    sortSurfaces();
 | |
| 
 | |
|    for (U32 i = 0; i < mSurfaces.size(); i++) {
 | |
|       mSurfaces[i].originalWinding = mSurfaces[i].winding;
 | |
|       mSurfaces[i].pNormalLMap = NULL;
 | |
|       mSurfaces[i].pAlarmLMap  = NULL;
 | |
|    }
 | |
| 
 | |
|    sortSurfaces();
 | |
|    while (fixTJuncs() == false)
 | |
|       sortSurfaces();
 | |
| 
 | |
|    markSurfaceOriginalPoints();
 | |
|    convertToStrips();
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| //--------------------------------------------------------------------------
 | |
| void EditGeometry::markEmptyZones()
 | |
| {
 | |
|    // Always leave zone 0, but strip out any zones that don't have
 | |
|    for (U32 i = 0; i < mZones.size(); i++) {
 | |
|       //if (i == 0 || mZones[i]->referencingPortals.size() != 0)
 | |
|          mZones[i]->active = true;
 | |
|       //else
 | |
|       //   mZones[i]->active = false;
 | |
|    }
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| void EditGeometry::createBrushSurfaces(const CSGBrush& brush)
 | |
| {
 | |
|    static U32 sPassCount = 0;
 | |
|    sPassCount++;
 | |
| 
 | |
|    Vector<Winding> planeFinalWindings(64);
 | |
|    Vector<Winding> tempWindings(1);
 | |
|    for (U32 i = 0; i < brush.mPlanes.size(); i++) {
 | |
|       const CSGPlane& rPlane = brush.mPlanes[i];
 | |
| 
 | |
|       tempWindings.increment();
 | |
|       tempWindings.last() = rPlane.winding;
 | |
|       tempWindings.last().numNodes   = 0;
 | |
|       tempWindings.last().numZoneIds = 0;
 | |
| 
 | |
|       mBSPRoot->breakWinding(tempWindings, rPlane.planeEQIndex,
 | |
|                              &brush, planeFinalWindings);
 | |
| 
 | |
|       if (planeFinalWindings.size() != 0) {
 | |
|          // Need to create a surface for each of these windings...
 | |
|          //
 | |
|          bool active = false;
 | |
|          for (U32 k = 0; k < planeFinalWindings.size() && !active; k++) {
 | |
|             for (U32 j = 0; j < planeFinalWindings[k].numZoneIds; j++) {
 | |
|                if (mZones[planeFinalWindings[k].zoneIds[j]]->active == true)
 | |
|                   active = true;
 | |
|             }
 | |
|          }
 | |
| 
 | |
|          if (rPlane.pTextureName &&
 | |
|              dStricmp(rPlane.pTextureName, "NULL")    != 0 &&
 | |
|              dStricmp(rPlane.pTextureName, "ORIGIN")  != 0 &&
 | |
|              dStricmp(rPlane.pTextureName, "TRIGGER") != 0 &&
 | |
|              active == true) {
 | |
|             U32 texGenIndex = rPlane.texGenIndex;
 | |
| 
 | |
|             for (U32 j = 0; j < planeFinalWindings.size(); j++) {
 | |
|                mSurfaces.increment();
 | |
|                Surface& rSurface = mSurfaces.last();
 | |
|                rSurface.uniqueKey = mSurfaceKey++;
 | |
| 
 | |
|                rSurface.sgLightingScale = brush.sgLightingScale;
 | |
| 			      rSurface.sgSelfIllumination = brush.sgSelfIllumination;
 | |
| 
 | |
|                rSurface.planeIndex   = rPlane.planeEQIndex;
 | |
| 
 | |
|                rSurface.textureIndex = getMaterialIndex(rPlane.pTextureName);
 | |
|                rSurface.texGenIndex  = texGenIndex;
 | |
| 
 | |
|                rSurface.winding         = planeFinalWindings[j];
 | |
|                rSurface.winding.brushId = brush.brushId;
 | |
| 
 | |
|                // Set flags...
 | |
| 
 | |
|                rSurface.flags  = (brush.mBrushType == DetailBrush)      ? Interior::SurfaceDetail    : 0;
 | |
|                rSurface.flags |= (brush.mIsAmbiguous == true)           ? Interior::SurfaceAmbiguous : 0;
 | |
|                rSurface.flags |= (planeFinalWindings[j].numNodes == 0)  ? Interior::SurfaceOrphan    : 0;
 | |
| 
 | |
|                // We know that either all of a surfaces zones are outside zones, or they're
 | |
|                //  all inside zones.  Therefore, we can just look at the first...
 | |
|                if (rSurface.winding.numZoneIds > 0)
 | |
|                   rSurface.flags |= (mZones[rSurface.winding.zoneIds[0]]->ambientLit) ? Interior::SurfaceOutsideVisible : 0;
 | |
| 
 | |
|                if (planeFinalWindings[i].numNodes == 0)
 | |
|                   mNumOrphanPolys++;
 | |
| 
 | |
|                if (rPlane.owningEntity != NULL) {
 | |
|                   // If the entity grabs the surface but leaves it in the interior
 | |
|                   //  for processing (mirrors, for instance), it does it here.
 | |
|                   rPlane.owningEntity->grabSurface(rSurface);
 | |
|                }
 | |
|             }
 | |
|          } else {
 | |
|             for (U32 j = 0; j < planeFinalWindings.size(); j++) {
 | |
|                mNullSurfaces.increment();
 | |
|                NullSurface& rSurface = mNullSurfaces.last();
 | |
| 
 | |
|                rSurface.planeIndex      = rPlane.planeEQIndex;
 | |
|                rSurface.winding         = planeFinalWindings[j];
 | |
|                rSurface.winding.brushId = brush.brushId;
 | |
| 
 | |
|                rSurface.flags  = (brush.mBrushType == DetailBrush)      ? Interior::SurfaceDetail    : 0;
 | |
|                rSurface.flags |= (brush.mIsAmbiguous == true)           ? Interior::SurfaceAmbiguous : 0;
 | |
|                rSurface.flags |= (planeFinalWindings[j].numNodes == 0)  ? Interior::SurfaceOrphan    : 0;
 | |
| 
 | |
|                if (planeFinalWindings[i].numNodes == 0)
 | |
|                   mNumOrphanPolys++;
 | |
|             }
 | |
|          }
 | |
|       }
 | |
| 
 | |
|       planeFinalWindings.clear();
 | |
|       tempWindings.clear();
 | |
|    }
 | |
| }
 | |
| 
 | |
| //--------------------------------------------------------------------------
 | |
| void EditGeometry::rotateSurfaceToNonColinear(Surface& surface)
 | |
| {
 | |
|    // Assumes the originalWinding and winding members are the same.  Make sure that
 | |
|    //  the first three verts are non-colinear.  Might be best to search for the best
 | |
|    //  corner...
 | |
|    AssertISV(surface.originalWinding.numIndices == surface.winding.numIndices, "Internal Error: This should definitely never happen!");
 | |
| 
 | |
|    U32 i;
 | |
|    U32 bestIndex = 0xFFFFFFFF;
 | |
|    F64 bestDot   = 1.0f;
 | |
| 
 | |
|    for (i = 0; i < surface.winding.numIndices; i++) {
 | |
|       U32 idx0 = (i + 0) % surface.winding.numIndices;
 | |
|       U32 idx1 = (i + 1) % surface.winding.numIndices;
 | |
|       U32 idx2 = (i + 2) % surface.winding.numIndices;
 | |
| 
 | |
|       Point3D vec0 = getPoint(surface.winding.indices[idx1]) - getPoint(surface.winding.indices[idx0]);
 | |
|       Point3D vec1 = getPoint(surface.winding.indices[idx2]) - getPoint(surface.winding.indices[idx1]);
 | |
|       vec0.normalize();
 | |
|       vec1.normalize();
 | |
| 
 | |
|       F64 dot = mFabs(mDot(vec0, vec1));
 | |
|       if (dot < bestDot) {
 | |
|          bestIndex = i;
 | |
|          bestDot = dot;
 | |
|       }
 | |
|    }
 | |
|    AssertFatal(bestIndex != 0xFFFFFFFF, "Internal Error: that friggin poly has got to turn a corner at some point!");
 | |
| 
 | |
|    for (i = bestIndex; i < surface.winding.numIndices; i++)
 | |
|       surface.winding.indices[i - bestIndex] = surface.originalWinding.indices[i];
 | |
| 
 | |
|    for (i = 0; i < bestIndex; i++)
 | |
|       surface.winding.indices[i + (surface.winding.numIndices - bestIndex)] = surface.originalWinding.indices[i];
 | |
| 
 | |
|    surface.originalWinding = surface.winding;
 | |
| }
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| bool EditGeometry::fixTJuncs()
 | |
| {
 | |
|    
 | |
|    // We need to do two passes here.  First structural, then detail...
 | |
|    //  DMMNOTE: This just yells out for consolidation, also, it may not
 | |
|    //   be necessary to restart the process after every insertion of a
 | |
|    //   new surface anymore.  Check that out.
 | |
| 
 | |
|    // First, lets get all the unique points in this zone.  These are the
 | |
|    //  only points that we are allowed to consider...
 | |
|    BitVector includedPoints(mPoints.size());
 | |
|    includedPoints.clear();
 | |
| 
 | |
|    Vector<U32> uniquePoints(mSurfaces.size() * 3);
 | |
|    UniqueVector coplanar;
 | |
| 
 | |
|    for (U32 i = 0; i < mSurfaces.size(); i++) {
 | |
|       if (mSurfaces[i].flags & Interior::SurfaceDetail)
 | |
|          continue;
 | |
|       for (U32 j = 0; j < mSurfaces[i].winding.numIndices; j++)
 | |
|          includedPoints.set(mSurfaces[i].winding.indices[j]);
 | |
|    }
 | |
|    for (U32 i = 0; i < mPoints.size(); i++) {
 | |
|       if (includedPoints.test(i))
 | |
|          uniquePoints.push_back(i);
 | |
|    }
 | |
| 
 | |
|    static const U32 maxTJuncPoints = 256;
 | |
|    U32 finalPoints[256];
 | |
|    U32 finalNumPoints;
 | |
|    for (U32 i = 0; i < mSurfaces.size(); i++) {
 | |
|       coplanar.clear();
 | |
|       Surface& rSurface = mSurfaces[i];
 | |
|       if (rSurface.flags & Interior::SurfaceDetail)
 | |
|          continue;
 | |
|       dMemcpy(finalPoints, rSurface.winding.indices, sizeof(U32) * rSurface.winding.numIndices);
 | |
|       finalNumPoints = rSurface.winding.numIndices;
 | |
| 
 | |
|       const PlaneEQ& rPlane = getPlaneEQ(rSurface.planeIndex);
 | |
| 
 | |
|       // Ok, only points that are coplanar are considered.  Make sure the
 | |
|       //  winding points already in are at the front so we can disregard them...
 | |
|       //
 | |
|       for (U32 j = 0; j < finalNumPoints; j++)
 | |
|          coplanar.pushBackUnique(finalPoints[j]);
 | |
|       U32 myPoints = coplanar.size();
 | |
| 
 | |
|       for (U32 j = 0; j < uniquePoints.size(); j++)
 | |
|          if (rPlane.whichSide(getPoint(uniquePoints[j])) == PlaneOn)
 | |
|             coplanar.pushBackUnique(uniquePoints[j]);
 | |
| 
 | |
|       // Ok, we have a list of coplanar points.  We need to remove our own points
 | |
|       //  from this list.  Slow.
 | |
|       for (U32 j = 0; j < myPoints; j++)
 | |
|          coplanar.erase(U32(0));
 | |
| 
 | |
|       // Now, spin through the edges, and insert any points that are colinear with
 | |
|       //  an edge, and lie within that edge...
 | |
|       U32 origSize = coplanar.size();
 | |
|       while (coplanar.size() != 0) {
 | |
|          U32 cp = coplanar.last();
 | |
|          coplanar.decrement();
 | |
| 
 | |
|          U32 start = 0;
 | |
|          U32 end   = 1;
 | |
|          do {
 | |
|             U32 ps = finalPoints[start];
 | |
|             U32 pe = finalPoints[end];
 | |
| 
 | |
|             if (pointsAreColinear(ps, pe, cp)) {
 | |
|                // Ok, the points are colinear, does coplanar[j] fall between ps and pe?
 | |
|                Point3D v = getPoint(pe) - getPoint(ps);
 | |
|                v.normalize();
 | |
| 
 | |
|                F64 dot = mDot(getPoint(cp) - getPoint(ps), v);
 | |
|                if (dot > 0.0 &&
 | |
|                    dot < mDot(getPoint(pe) - getPoint(ps), v)) {
 | |
|                   // Yup!  Insert the sucker...
 | |
|                   finalNumPoints++;
 | |
|                   AssertFatal(finalNumPoints <= maxTJuncPoints, "Internal Error: too many points!");
 | |
|                   if (end < start) {
 | |
|                      // In this scenario, this can only happen if the point needs to
 | |
|                      //  go at the very end.
 | |
|                      finalPoints[finalNumPoints-1] = cp;
 | |
|                   } else {
 | |
|                      dMemmove(&finalPoints[end + 1],
 | |
|                               &finalPoints[end],
 | |
|                               (finalNumPoints - end - 1) * sizeof(U32));
 | |
|                      finalPoints[end] = cp;
 | |
|                   }
 | |
|                   break;
 | |
|                }
 | |
|             }
 | |
| 
 | |
|             start = end;
 | |
|             end   = (end + 1) % finalNumPoints;
 | |
|          } while (end != 1);
 | |
|       }
 | |
| 
 | |
|       // Copy the points back in...
 | |
|       if (finalNumPoints <= 32) {
 | |
|          dMemcpy(rSurface.winding.indices, finalPoints, sizeof(U32) * finalNumPoints);
 | |
|          rSurface.winding.numIndices = finalNumPoints;
 | |
|       } else {
 | |
|          while (finalNumPoints > 32) {
 | |
|             U32 poly1[256];
 | |
|             U32 poly2[256];
 | |
|             U32 poly1Verts = 32;
 | |
|             U32 poly2Verts = finalNumPoints - 30;
 | |
| 
 | |
|             U32 splitPoint;
 | |
|             for (splitPoint = 0; splitPoint < finalNumPoints; splitPoint++) {
 | |
|                for (U32 j = 0; j < 32; j++) {
 | |
|                   poly1[j] = finalPoints[(splitPoint + j) % finalNumPoints];
 | |
|                }
 | |
|                poly2[0] = poly1[0];
 | |
|                poly2[1] = poly1[31];
 | |
|                for (U32 j = 32; j < finalNumPoints; j++) {
 | |
|                   poly2[j - 30] = finalPoints[(splitPoint + j) % finalNumPoints];
 | |
|                }
 | |
| 
 | |
|                // Ok, check to make sure that both of these have at least _some_
 | |
|                //  non-colinear verts...
 | |
|                //
 | |
|                bool poly1Colinear = true;
 | |
|                bool poly2Colinear = true;
 | |
|                for (U32 j = 0; j < 32; j++) {
 | |
|                   U32 j1 = (j + 1) % 32;
 | |
|                   U32 j2 = (j + 2) % 32;
 | |
| 
 | |
|                   if (pointsAreColinear(poly1[j], poly1[j1], poly1[j2]) == false) {
 | |
|                      poly1Colinear = false;
 | |
|                      break;
 | |
|                   }
 | |
|                }
 | |
| 
 | |
|                for (U32 j = 0; j < poly2Verts; j++) {
 | |
|                   U32 j1 = (j + 1) % poly2Verts;
 | |
|                   U32 j2 = (j + 2) % poly2Verts;
 | |
| 
 | |
|                   if (pointsAreColinear(poly2[j], poly2[j1], poly2[j2]) == false) {
 | |
|                      poly2Colinear = false;
 | |
|                      break;
 | |
|                   }
 | |
|                }
 | |
| 
 | |
|                if (poly1Colinear == false && poly2Colinear == false)
 | |
|                   break;
 | |
|             }
 | |
|             AssertFatal(splitPoint != finalNumPoints, "Internal Error: there must be a possible split somewhere!");
 | |
| 
 | |
|             // Poly1 Holds the vertices for the new surface, poly2 is copied back
 | |
|             //  into final points...
 | |
|             mSurfaces.increment();
 | |
|             Surface& lastSurface = mSurfaces.last();   // NOTENOTENOTE: rSurface can become invalid here!
 | |
|             lastSurface = mSurfaces[i];
 | |
|             lastSurface.uniqueKey = mSurfaceKey++;
 | |
| 
 | |
|             // We copy out the first 32 verts of the finalPoints
 | |
|             dMemcpy(lastSurface.winding.indices, poly1, sizeof(U32) * 32);
 | |
|             lastSurface.winding.numIndices = 32;
 | |
| 
 | |
|             lastSurface.originalWinding = lastSurface.winding;
 | |
|             rotateSurfaceToNonColinear(lastSurface);
 | |
| 
 | |
|             dMemcpy(finalPoints, poly2, sizeof(U32) * poly2Verts);
 | |
|             finalNumPoints = poly2Verts;
 | |
|          }
 | |
| 
 | |
|          // rSurface could have become invalid above...
 | |
|          dMemcpy(mSurfaces[i].winding.indices, finalPoints, sizeof(U32) * finalNumPoints);
 | |
|          mSurfaces[i].winding.numIndices = finalNumPoints;
 | |
|          mSurfaces[i].originalWinding = rSurface.winding;
 | |
|          rotateSurfaceToNonColinear(mSurfaces[i]);
 | |
| 
 | |
|          return false;
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    includedPoints.clear();
 | |
|    for (U32 i = 0; i < mSurfaces.size(); i++) {
 | |
|       if (mSurfaces[i].flags & Interior::SurfaceDetail)
 | |
|          for (U32 j = 0; j < mSurfaces[i].winding.numIndices; j++)
 | |
|             includedPoints.set(mSurfaces[i].winding.indices[j]);
 | |
|    }
 | |
|    for (U32 i = 0; i < mPoints.size(); i++) {
 | |
|       if (includedPoints.test(i))
 | |
|          uniquePoints.push_back(i);
 | |
|    }
 | |
| 
 | |
|    for (U32 i = 0; i < mSurfaces.size(); i++) {
 | |
|       coplanar.clear();
 | |
|       Surface& rSurface = mSurfaces[i];
 | |
|       if (!(rSurface.flags & Interior::SurfaceDetail))
 | |
|          continue;
 | |
|       dMemcpy(finalPoints, rSurface.winding.indices, sizeof(U32) * rSurface.winding.numIndices);
 | |
|       finalNumPoints = rSurface.winding.numIndices;
 | |
| 
 | |
|       const PlaneEQ& rPlane = getPlaneEQ(rSurface.planeIndex);
 | |
| 
 | |
|       // Ok, only points that are coplanar are considered.  Make sure the
 | |
|       //  winding points already in are at the front so we can disregard them...
 | |
|       //
 | |
|       for (U32 j = 0; j < finalNumPoints; j++)
 | |
|          coplanar.pushBackUnique(finalPoints[j]);
 | |
|       U32 myPoints = coplanar.size();
 | |
| 
 | |
|       for (U32 j = 0; j < uniquePoints.size(); j++)
 | |
|          if (rPlane.whichSide(getPoint(uniquePoints[j])) == PlaneOn)
 | |
|             coplanar.pushBackUnique(uniquePoints[j]);
 | |
| 
 | |
|       // Ok, we have a list of coplanar points.  We need to remove our own points
 | |
|       //  from this list.  Slow.
 | |
|       for (U32 j = 0; j < myPoints; j++)
 | |
|          coplanar.erase(U32(0));
 | |
| 
 | |
|       // Now, spin through the edges, and insert any points that are colinear with
 | |
|       //  an edge, and lie within that edge...
 | |
|       while (coplanar.size() != 0) {
 | |
|          U32 cp = coplanar.last();
 | |
|          coplanar.decrement();
 | |
| 
 | |
|          U32 start = 0;
 | |
|          U32 end   = 1;
 | |
|          do {
 | |
|             U32 ps = finalPoints[start];
 | |
|             U32 pe = finalPoints[end];
 | |
| 
 | |
|             if (pointsAreColinear(ps, pe, cp)) {
 | |
|                // Ok, the points are colinear, does coplanar[j] fall between ps and pe?
 | |
|                Point3D v = getPoint(pe) - getPoint(ps);
 | |
|                v.normalize();
 | |
| 
 | |
|                F64 dot = mDot(getPoint(cp) - getPoint(ps), v);
 | |
|                if (dot > 0.0 &&
 | |
|                    dot < mDot(getPoint(pe) - getPoint(ps), v)) {
 | |
|                   // Yup!  Insert the sucker...
 | |
|                   finalNumPoints++;
 | |
|                   AssertFatal(finalNumPoints <= MaxWindingPoints, "Internal Error: too many points!");
 | |
|                   if (end < start) {
 | |
|                      // In this scenario, this can only happen if the point needs to
 | |
|                      //  go at the very end.
 | |
|                      finalPoints[finalNumPoints-1] = cp;
 | |
|                   } else {
 | |
|                      dMemmove(&finalPoints[end + 1],
 | |
|                               &finalPoints[end],
 | |
|                               (finalNumPoints - end - 1) * sizeof(U32));
 | |
|                      finalPoints[end] = cp;
 | |
|                   }
 | |
|                   break;
 | |
|                }
 | |
|             }
 | |
| 
 | |
|             start = end;
 | |
|             end   = (end + 1) % finalNumPoints;
 | |
|          } while (end != 1);
 | |
|       }
 | |
| 
 | |
|       // Copy the points back in...
 | |
|       if (finalNumPoints <= 32) {
 | |
|          dMemcpy(rSurface.winding.indices, finalPoints, sizeof(U32) * finalNumPoints);
 | |
|          rSurface.winding.numIndices = finalNumPoints;
 | |
|       } else {
 | |
|          while (finalNumPoints > 32) {
 | |
|             U32 poly1[256];
 | |
|             U32 poly2[256];
 | |
|             U32 poly1Verts = 32;
 | |
|             U32 poly2Verts = finalNumPoints - 30;
 | |
| 
 | |
|             U32 splitPoint;
 | |
|             for (splitPoint = 0; splitPoint < finalNumPoints; splitPoint++) {
 | |
|                for (U32 j = 0; j < 32; j++) {
 | |
|                   poly1[j] = finalPoints[(splitPoint + j) % finalNumPoints];
 | |
|                }
 | |
|                poly2[0] = poly1[0];
 | |
|                poly2[1] = poly1[31];
 | |
|                for (U32 j = 32; j < finalNumPoints; j++) {
 | |
|                   poly2[j - 30] = finalPoints[(splitPoint + j) % finalNumPoints];
 | |
|                }
 | |
| 
 | |
|                // Ok, check to make sure that both of these have at least _some_
 | |
|                //  non-colinear verts...
 | |
|                //
 | |
|                bool poly1Colinear = true;
 | |
|                bool poly2Colinear = true;
 | |
|                for (U32 j = 0; j < 32; j++) {
 | |
|                   U32 j1 = (j + 1) % 32;
 | |
|                   U32 j2 = (j + 2) % 32;
 | |
| 
 | |
|                   if (pointsAreColinear(poly1[j], poly1[j1], poly1[j2]) == false) {
 | |
|                      poly1Colinear = false;
 | |
|                      break;
 | |
|                   }
 | |
|                }
 | |
| 
 | |
|                for (U32 j = 0; j < poly2Verts; j++) {
 | |
|                   U32 j1 = (j + 1) % poly2Verts;
 | |
|                   U32 j2 = (j + 2) % poly2Verts;
 | |
| 
 | |
|                   if (pointsAreColinear(poly2[j], poly2[j1], poly2[j2]) == false) {
 | |
|                      poly2Colinear = false;
 | |
|                      break;
 | |
|                   }
 | |
|                }
 | |
| 
 | |
|                if (poly1Colinear == false && poly2Colinear == false)
 | |
|                   break;
 | |
|             }
 | |
|             AssertFatal(splitPoint != finalNumPoints, "Internal Error: there must be a possible split somewhere!");
 | |
| 
 | |
|             // Poly1 Holds the vertices for the new surface, poly2 is copied back
 | |
|             //  into final points...
 | |
|             mSurfaces.increment();
 | |
|             Surface& lastSurface = mSurfaces.last();   // NOTENOTENOTE: rSurface can become invalid here!
 | |
|             lastSurface = mSurfaces[i];
 | |
|             lastSurface.uniqueKey = mSurfaceKey++;
 | |
| 
 | |
|             // We copy out the first 32 verts of the finalPoints
 | |
|             dMemcpy(lastSurface.winding.indices, poly1, sizeof(U32) * 32);
 | |
|             lastSurface.winding.numIndices = 32;
 | |
| 
 | |
|             lastSurface.originalWinding = lastSurface.winding;
 | |
|             rotateSurfaceToNonColinear(lastSurface);
 | |
| 
 | |
|             dMemcpy(finalPoints, poly2, sizeof(U32) * poly2Verts);
 | |
|             finalNumPoints = poly2Verts;
 | |
|          }
 | |
| 
 | |
|          // rSurface could have become invalid above...
 | |
|          dMemcpy(mSurfaces[i].winding.indices, finalPoints, sizeof(U32) * finalNumPoints);
 | |
|          mSurfaces[i].winding.numIndices = finalNumPoints;
 | |
|          mSurfaces[i].originalWinding = rSurface.winding;
 | |
|          rotateSurfaceToNonColinear(mSurfaces[i]);
 | |
| 
 | |
|          return false;
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    return true;
 | |
| }
 | |
| 
 | |
| void EditGeometry::markSurfaceOriginalPoints()
 | |
| {
 | |
|    for (U32 i = 0; i < mSurfaces.size(); i++) {
 | |
|       Surface& rSurface = mSurfaces[i];
 | |
|       U32 mask = 0;
 | |
| 
 | |
|       AssertFatal(mSurfaces[i].originalWinding.numIndices <= rSurface.winding.numIndices,
 | |
|                   avar("How did we lose verts? (%d / %d)", mSurfaces[i].originalWinding.numIndices, rSurface.winding.numIndices));
 | |
| 
 | |
|       for (U32 j = 0; j < rSurface.winding.numIndices; j++) {
 | |
|          bool found = false;
 | |
|          for (U32 k = 0; k < mSurfaces[i].originalWinding.numIndices; k++) {
 | |
| 
 | |
|             if (rSurface.winding.indices[j] == mSurfaces[i].originalWinding.indices[k]) {
 | |
|                found = true;
 | |
|                break;
 | |
|             }
 | |
|          }
 | |
| 
 | |
|          if (found)
 | |
|             mask |= (1 << j);
 | |
|       }
 | |
| 
 | |
|       rSurface.fanMask = mask;
 | |
|    }
 | |
| }
 | |
| 
 | |
| void EditGeometry::convertToStrips()
 | |
| {
 | |
|    for (U32 i = 0; i < mSurfaces.size(); i++) {
 | |
|       Surface& rSurface = mSurfaces[i];
 | |
| 
 | |
|       // Convert to strip order.  This proceedes by taking the first three
 | |
|       //  verts from the fan, and then the last, the fourth, then the next
 | |
|       //  to last, etc, etc.
 | |
|       U32 newWinding[32];
 | |
| 
 | |
|       newWinding[0] = rSurface.winding.indices[0];
 | |
|       U32 idx = 1;
 | |
| 
 | |
|       U32 front = 1;
 | |
|       U32 back  = rSurface.winding.numIndices - 1;
 | |
|       while (idx < rSurface.winding.numIndices) {
 | |
|          // Get the back
 | |
|          newWinding[idx++] = rSurface.winding.indices[front++];
 | |
|          if (idx >= rSurface.winding.numIndices)
 | |
|             break;
 | |
|          newWinding[idx++] = rSurface.winding.indices[back--];
 | |
|       }
 | |
|       AssertFatal(idx == rSurface.winding.numIndices, "Internal Error, problem converting fan to strip!");
 | |
|       dMemcpy(rSurface.winding.indices, newWinding, idx * sizeof(U32));
 | |
|    }
 | |
| }
 | |
| 
 | |
| 
 | |
| //--------------------------------------------------------------------------
 | |
| // NOTE! DO NOT Change this without knowing EXACTLY what you're doing.  Rendering
 | |
| //  performance for the interiors can be _extremely_ sensitive to surface sort
 | |
| //  order.
 | |
| //--------------------------------------------------------------------------
 | |
| S32 QSORT_CALLBACK
 | |
| cmpSurfaceFunc(const void* p1, const void* p2)
 | |
| {
 | |
|    // Sort criteria is:
 | |
|    //  Alarm/Normal lightmap
 | |
|    //  Zone    id
 | |
|    //  Texture index
 | |
|    //  TexGen  plane eq
 | |
|    //
 | |
|    const EditGeometry::Surface* pS1 = reinterpret_cast<const EditGeometry::Surface*>(p1);
 | |
|    const EditGeometry::Surface* pS2 = reinterpret_cast<const EditGeometry::Surface*>(p2);
 | |
| 
 | |
|    if ((pS1->pAlarmLMap == NULL) != (pS2->pAlarmLMap == NULL)) {
 | |
|       // Surfaces sharing a lightmap go to the front...
 | |
|       if (pS1->pAlarmLMap == NULL)
 | |
|          return 1;
 | |
|       else
 | |
|          return -1;
 | |
|    } else {
 | |
|       if (pS1->textureIndex != pS2->textureIndex) {
 | |
|          return S32(pS1->textureIndex - pS2->textureIndex);
 | |
|       } else {
 | |
|          // Let's try to sort by zone...
 | |
|          int index = 0;
 | |
|          while (index < pS1->winding.numZoneIds && index < pS2->winding.numZoneIds) {
 | |
|             if (pS1->winding.zoneIds[index] != pS2->winding.zoneIds[index]) {
 | |
|                if (pS1->winding.zoneIds[index] < pS2->winding.zoneIds[index])
 | |
|                   return -1;
 | |
|                else
 | |
|                   return 1;
 | |
|             }
 | |
|             index++;
 | |
|          }
 | |
| 
 | |
|          if (pS1->winding.numZoneIds < pS2->winding.numZoneIds)
 | |
|             return -1;
 | |
|          else if (pS2->winding.numZoneIds < pS1->winding.numZoneIds)
 | |
|             return 1;
 | |
|          else {
 | |
|             // Well, the zones are the same, let's sort on plane...
 | |
|             if (pS1->planeIndex > pS2->planeIndex)
 | |
|                return -1;
 | |
|             else if (pS2->planeIndex > pS1->planeIndex)
 | |
|                return 1;
 | |
|             else
 | |
|                return 0;
 | |
|          }
 | |
|       }
 | |
|    }
 | |
| }
 | |
| 
 | |
| 
 | |
| //--------------------------------------------------------------------------
 | |
| // NOTE! DO NOT Change this without knowing EXACTLY what you're doing.  Rendering
 | |
| //  performance for the interiors can be _extremely_ sensitive to surface sort
 | |
| //  order.
 | |
| //--------------------------------------------------------------------------
 | |
| S32 QSORT_CALLBACK
 | |
| cmpSurfaceLitFunc(const void* p1, const void* p2)
 | |
| {
 | |
|    // Sort criteria is:
 | |
|    //  Alarm/Normal lightmap
 | |
|    //  Animated lights affect surface/don't affect surface
 | |
|    //  Texture index
 | |
|    //  Zone    id
 | |
|    //  TexGen  plane eq
 | |
|    //
 | |
|    const EditGeometry::Surface* pS1 = reinterpret_cast<const EditGeometry::Surface*>(p1);
 | |
|    const EditGeometry::Surface* pS2 = reinterpret_cast<const EditGeometry::Surface*>(p2);
 | |
| 
 | |
|    if ((pS1->pAlarmLMap == NULL) != (pS2->pAlarmLMap == NULL)) {
 | |
|       // Surfaces sharing a lightmap go to the front...
 | |
|       if (pS1->pAlarmLMap == NULL)
 | |
|          return 1;
 | |
|       else
 | |
|          return -1;
 | |
|    } else {
 | |
|       // If one surface isn't affected by animated lights, and the other is
 | |
|       if ((pS1->numLights == 0) != (pS2->numLights == 0))
 | |
|       {
 | |
|          // sort the "not" to the front
 | |
|          if (pS1->numLights == 0)
 | |
|             return -1;
 | |
|          else
 | |
|             return 1;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|          if (pS1->textureIndex != pS2->textureIndex)
 | |
|          {
 | |
|             return S32(pS1->textureIndex - pS2->textureIndex);
 | |
|          } else
 | |
|          {
 | |
|             // Let's try to sort by zone...
 | |
|             int index = 0;
 | |
|             while (index < pS1->winding.numZoneIds && index < pS2->winding.numZoneIds)
 | |
|             {
 | |
|                if (pS1->winding.zoneIds[index] != pS2->winding.zoneIds[index])
 | |
|                {
 | |
|                   if (pS1->winding.zoneIds[index] < pS2->winding.zoneIds[index])
 | |
|                      return -1;
 | |
|                   else
 | |
|                      return 1;
 | |
|                }
 | |
|                index++;
 | |
|             }
 | |
| 
 | |
|             if (pS1->winding.numZoneIds < pS2->winding.numZoneIds)
 | |
|             {
 | |
|                return -1;
 | |
|             }
 | |
|             else if (pS2->winding.numZoneIds < pS1->winding.numZoneIds)
 | |
|             {
 | |
|                return 1;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                // Well, the zones are the same, let's sort on plane...
 | |
|                if (pS1->planeIndex > pS2->planeIndex)
 | |
|                   return -1;
 | |
|                else if (pS2->planeIndex > pS1->planeIndex)
 | |
|                   return 1;
 | |
|                else
 | |
|                   return 0;
 | |
|             }
 | |
|          }
 | |
|       }
 | |
|    }
 | |
| }
 | |
| 
 | |
| S32 QSORT_CALLBACK
 | |
| cmpSurfaceLitIndexFunc(const void* p1, const void* p2)
 | |
| {
 | |
|    U32 idx1 = *((U32*)p1);
 | |
|    U32 idx2 = *((U32*)p2);
 | |
| 
 | |
|    const EditGeometry::Surface* pS1 = &gWorkingGeometry->mSurfaces[idx1];
 | |
|    const EditGeometry::Surface* pS2 = &gWorkingGeometry->mSurfaces[idx2];
 | |
| 
 | |
|    return cmpSurfaceLitFunc(pS1, pS2);
 | |
| }
 | |
| 
 | |
| S32 QSORT_CALLBACK
 | |
| cmpZoneIds(const void* p1, const void* p2)
 | |
| {
 | |
|    U32 i1 = *((const U32*)p1);
 | |
|    U32 i2 = *((const U32*)p2);
 | |
| 
 | |
|    if (i1 < i2)
 | |
|       return 1;
 | |
|    else if (i2 < i1)
 | |
|       return -1;
 | |
|    else
 | |
|       return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| void EditGeometry::sortSurfaces()
 | |
| {
 | |
|    for (U32 i = 0; i < mSurfaces.size(); i++) {
 | |
|       if (mSurfaces[i].winding.numZoneIds > 1) {
 | |
|          dQsort(mSurfaces[i].winding.zoneIds, mSurfaces[i].winding.numZoneIds, sizeof(U32), cmpZoneIds);
 | |
|          dMemcpy(mSurfaces[i].originalWinding.zoneIds, mSurfaces[i].winding.zoneIds, sizeof(U32) * 8);
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    dQsort(mSurfaces.address(), mSurfaces.size(), sizeof(Surface), cmpSurfaceFunc);
 | |
| }
 | |
| 
 | |
| 
 | |
| void EditGeometry::sortLitSurfaces()
 | |
| {
 | |
|    U32* pIndices        = new U32[mSurfaces.size()];
 | |
|    U32* pReverseIndices = new U32[mSurfaces.size()];
 | |
|    for (U32 i = 0; i < mSurfaces.size(); i++)
 | |
|       pIndices[i] = i;
 | |
| 
 | |
|    dQsort(pIndices, mSurfaces.size(), sizeof(U32), cmpSurfaceLitIndexFunc);
 | |
|    for (U32 i = 0; i < mSurfaces.size(); i++)
 | |
|    {
 | |
|       mSurfaces[i].temptemptemp = i;
 | |
|       pReverseIndices[pIndices[i]] = i;
 | |
|    }
 | |
| 
 | |
|    // Back patch the surfaces into the lights
 | |
|    for (U32 i = 0; i < mAnimatedLights.size(); i++)
 | |
|    {
 | |
|       AnimatedLight* pLight = mAnimatedLights[i];
 | |
|       for (U32 j = 0; j < pLight->states.size(); j++)
 | |
|       {
 | |
|          LightState* pState = pLight->states[j];
 | |
|          for (U32 k = 0; k < pState->stateData.size(); k++)
 | |
|          {
 | |
|             StateData& rData = pState->stateData[k];
 | |
|             rData.surfaceIndex = pReverseIndices[rData.surfaceIndex];
 | |
|          }
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    // Actually do the surface sort
 | |
|    dQsort(mSurfaces.address(), mSurfaces.size(), sizeof(Surface), cmpSurfaceLitFunc);
 | |
|    for (U32 i = 0; i < mSurfaces.size(); i++)
 | |
|    {
 | |
|       AssertFatal(mSurfaces[i].temptemptemp == pIndices[i], "Internal Error: sorts mismatch!");
 | |
|    }
 | |
|    
 | |
|    delete [] pIndices;
 | |
|    delete [] pReverseIndices;
 | |
| }
 | |
| 
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| void EditGeometry::packLMaps()
 | |
| {
 | |
|    // We need to sort the lightmaps into 8 catagories
 | |
|    // Shared Alarm/Normal
 | |
|    //   Indoor
 | |
|    //     Unanimated
 | |
|    //     Animated
 | |
|    //   Outdoor
 | |
|    //     Unanimated
 | |
|    //     Animated
 | |
|    // Normal/Alarm
 | |
|    //   Indoor
 | |
|    //     Unanimated
 | |
|    //     Animated
 | |
|    //   Outdoor
 | |
|    //     Unanimated
 | |
|    //     Animated
 | |
|    //
 | |
|    // Arbitrarily, well just assign enums to the top level as follows:
 | |
|    //  Normal: 0
 | |
|    //  Shared: 1
 | |
| 
 | |
|    for (U32 i = 0; i < mSurfaces.size(); i++)
 | |
|    {
 | |
|       mSurfaces[i].sheetIndex = 0xFFFFFFFF;
 | |
|       mSurfaces[i].alarmSheetIndex = 0xFFFFFFFF;
 | |
|    }
 | |
|    
 | |
|    Vector<U32> surfaceLists[8];
 | |
| 
 | |
|    if (!mHasAlarmState)
 | |
|    {
 | |
|       for (U32 i = 0; i < mSurfaces.size(); i++)
 | |
|       {
 | |
|          U32 code = 0;
 | |
|          U32 zoneId = mSurfaces[i].winding.zoneIds[0];
 | |
|          if (mZones[zoneId] && mZones[zoneId]->ambientLit)
 | |
|             code |= 0x2;
 | |
|          if (mSurfaces[i].numLights != 0)
 | |
|             code |= 0x1;
 | |
|          surfaceLists[code].push_back(i);
 | |
|       }      
 | |
|       for (U32 i = 0; i < 4; i++)
 | |
|       {
 | |
|          SheetManager* pManager = new SheetManager;
 | |
|          
 | |
|          pManager->begin();
 | |
|          for (U32 j = 0; j < surfaceLists[i].size(); j++)
 | |
|          {
 | |
|             Surface& rSurface = mSurfaces[(surfaceLists[i])[j]];
 | |
| 
 | |
|             AssertFatal(rSurface.pNormalLMap != NULL, "Internal Error: No lightmap?");
 | |
|             rSurface.sheetIndex = pManager->enterLightMap(rSurface.pNormalLMap);
 | |
|          }
 | |
|          pManager->end();
 | |
| 
 | |
|          for (U32 j = 0; j < surfaceLists[i].size(); j++)
 | |
|          {
 | |
|             Surface& rSurface = mSurfaces[(surfaceLists[i])[j]];
 | |
|             SheetManager::LightMapEntry rEntry = pManager->getLightmap(rSurface.sheetIndex);
 | |
|             rSurface.sheetIndex      = mLightmaps.size() + rEntry.sheetId;
 | |
| 
 | |
|             //
 | |
|             // Support for interior light map border sizes.
 | |
|             //          
 | |
|             rSurface.offsetX         = rEntry.x + SG_LIGHTMAP_BORDER_SIZE;
 | |
|             rSurface.offsetY         = rEntry.y + SG_LIGHTMAP_BORDER_SIZE;
 | |
| 
 | |
|             AssertFatal(U32(rSurface.lMapDimX) == U32(rEntry.width - (SG_LIGHTMAP_BORDER_SIZE * 2)) &&
 | |
|                 U32(rSurface.lMapDimY) == U32(rEntry.height - (SG_LIGHTMAP_BORDER_SIZE * 2)),
 | |
|                         "Internal Error: Something got mixed up here");
 | |
|          }
 | |
|          for (U32 i = 0; i < pManager->m_sheets.size(); i++)
 | |
|             mLightmaps.push_back(new GBitmap(*pManager->m_sheets[i].pData));
 | |
| 
 | |
|          delete pManager;
 | |
|       }
 | |
|    }
 | |
|    else
 | |
|    {
 | |
|       for (U32 i = 0; i < mSurfaces.size(); i++) {
 | |
|          if (mSurfaces[i].pNormalLMap != NULL) {
 | |
|             AssertFatal(mSurfaces[i].pAlarmLMap,
 | |
|                         "Internal Error: if we have an alarm state, any surface with a normal lightmap must have an alarm equivalent");
 | |
| 
 | |
|             const U8* normalBits = mSurfaces[i].pNormalLMap->getBits();
 | |
|             const U8* alarmBits  = mSurfaces[i].pAlarmLMap->getBits();
 | |
| 
 | |
|             if (dMemcmp(alarmBits, normalBits, mSurfaces[i].pNormalLMap->byteSize) == 0) {
 | |
|                // Lightmaps are the same.  Nuke the alarm version...
 | |
|                delete mSurfaces[i].pAlarmLMap;
 | |
|                mSurfaces[i].pAlarmLMap = NULL;
 | |
|             }
 | |
|          }
 | |
|       }
 | |
|       
 | |
|       for (U32 i = 0; i < mSurfaces.size(); i++)
 | |
|       {
 | |
|          U32 code = 0;
 | |
|          if (mSurfaces[i].pNormalLMap != NULL && mSurfaces[i].pAlarmLMap == NULL)
 | |
|             code = 1 << 2;
 | |
|          if (mZones[mSurfaces[i].winding.zoneIds[0]]->ambientLit)
 | |
|             code |= 0x2;
 | |
|          if (mSurfaces[i].numLights != 0)
 | |
|             code |= 0x1;
 | |
|          surfaceLists[code].push_back(i);
 | |
|       }      
 | |
| 
 | |
|       // Do the shared lightmaps first
 | |
|       for (U32 i = 4; i < 8; i++)
 | |
|       {
 | |
|          SheetManager* pManager = new SheetManager;
 | |
|          
 | |
|          pManager->begin();
 | |
|          for (U32 j = 0; j < surfaceLists[i].size(); j++)
 | |
|          {
 | |
|             Surface& rSurface = mSurfaces[(surfaceLists[i])[j]];
 | |
| 
 | |
|             AssertFatal(rSurface.pNormalLMap != NULL, "Internal Error: No lightmap?");
 | |
|             rSurface.sheetIndex = pManager->enterLightMap(rSurface.pNormalLMap);
 | |
|          }
 | |
|          pManager->end();
 | |
| 
 | |
|          for (U32 j = 0; j < surfaceLists[i].size(); j++)
 | |
|          {
 | |
|             Surface& rSurface = mSurfaces[(surfaceLists[i])[j]];
 | |
|             SheetManager::LightMapEntry rEntry = pManager->getLightmap(rSurface.sheetIndex);
 | |
|             rSurface.sheetIndex      = mLightmaps.size() + rEntry.sheetId;
 | |
|             rSurface.alarmSheetIndex = mLightmaps.size() + rEntry.sheetId;
 | |
|             rSurface.offsetX         = rEntry.x;
 | |
|             rSurface.offsetY         = rEntry.y;
 | |
| 
 | |
|             AssertFatal(U32(rSurface.lMapDimX) == U32(rEntry.width) && U32(rSurface.lMapDimY) == U32(rEntry.height),
 | |
|                         "Internal Error: Something got mixed up here");
 | |
|          }
 | |
|          for (U32 i = 0; i < pManager->m_sheets.size(); i++)
 | |
|             mLightmaps.push_back(new GBitmap(*pManager->m_sheets[i].pData));
 | |
| 
 | |
|          delete pManager;
 | |
|       }
 | |
|       
 | |
|       // Do the normal/alarm pairs next
 | |
|       for (U32 i = 0; i < 4; i++)
 | |
|       {
 | |
|          SheetManager* pManager = new SheetManager;
 | |
|          
 | |
|          pManager->begin();
 | |
|          for (U32 j = 0; j < surfaceLists[i].size(); j++)
 | |
|          {
 | |
|             Surface& rSurface = mSurfaces[(surfaceLists[i])[j]];
 | |
|             AssertFatal(rSurface.pNormalLMap != NULL, "Internal Error: No lightmap?");
 | |
|             rSurface.sheetIndex = pManager->enterLightMap(rSurface.pNormalLMap);
 | |
|          }
 | |
|          pManager->end();
 | |
| 
 | |
|          for (U32 j = 0; j < surfaceLists[i].size(); j++)
 | |
|          {
 | |
|             Surface& rSurface = mSurfaces[(surfaceLists[i])[j]];
 | |
|             SheetManager::LightMapEntry rEntry = pManager->getLightmap(rSurface.sheetIndex);
 | |
|             rSurface.sheetIndex      = mLightmaps.size() + (rEntry.sheetId * 2) + 0;
 | |
|             rSurface.alarmSheetIndex = mLightmaps.size() + (rEntry.sheetId * 2) + 1;
 | |
|             rSurface.offsetX         = rEntry.x;
 | |
|             rSurface.offsetY         = rEntry.y;
 | |
| 
 | |
|             AssertFatal(U32(rSurface.lMapDimX) == U32(rEntry.width) && U32(rSurface.lMapDimY) == U32(rEntry.height),
 | |
|                         "Internal Error: Something got mixed up here");
 | |
|          }
 | |
|          for (U32 i = 0; i < pManager->m_sheets.size(); i++)
 | |
|          {
 | |
|             mLightmaps.push_back(new GBitmap(*pManager->m_sheets[i].pData));
 | |
|             mLightmaps.push_back(new GBitmap(*pManager->m_sheets[i].pData));
 | |
|          }
 | |
| 
 | |
|          for (U32 j = 0; j < surfaceLists[i].size(); j++)
 | |
|          {
 | |
|             Surface& rSurface = mSurfaces[(surfaceLists[i])[j]];
 | |
|             GBitmap* pMap = mLightmaps[rSurface.alarmSheetIndex];
 | |
|             for (U32 y = 0; y < rSurface.lMapDimY; y++)
 | |
|             {
 | |
|                const U8* pSrc = rSurface.pAlarmLMap->getAddress(0, y);
 | |
|                U8* pDst       = pMap->getAddress(rSurface.offsetX, rSurface.offsetY + y);
 | |
|                dMemcpy(pDst, pSrc, rSurface.lMapDimX * rSurface.pAlarmLMap->bytesPerPixel);
 | |
|             }
 | |
|          }         
 | |
|          
 | |
|          delete pManager;
 | |
|       }
 | |
|    }
 | |
|    
 | |
|    // Remove the lightmaps, and adjust the texgen for all surfaces
 | |
|    //
 | |
|    for (U32 i = 0; i < mSurfaces.size(); i++)
 | |
|    {
 | |
|       delete mSurfaces[i].pNormalLMap;
 | |
|       delete mSurfaces[i].pAlarmLMap;
 | |
|       mSurfaces[i].pNormalLMap = NULL;
 | |
|       mSurfaces[i].pAlarmLMap  = NULL;
 | |
| 
 | |
|       AssertFatal(mSurfaces[i].sheetIndex != 0xFFFFFFFF, "Internal Error: Bogus sheet index!");
 | |
|       if (mHasAlarmState)
 | |
|          AssertFatal(mSurfaces[i].alarmSheetIndex != 0xFFFFFFFF, "Internal Error: Bogus sheet index!");
 | |
| 
 | |
|       adjustLMapTexGen(mSurfaces[i]);
 | |
|    }
 | |
| }
 | |
| 
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| void EditGeometry::findOutsideZone()
 | |
| {
 | |
|    // Really easy.  Basically, just create a point outside the bounding
 | |
|    //  box of the shape, find the leaf that it's in, and grab it's zone
 | |
|    //  id.  We require that the leaf be empty, and the zoneId not be -1.
 | |
| 
 | |
|    Point3D testPoint = mMaxBound + Point3D(10, 10, 10);
 | |
|    EditBSPNode* currNode = mBSPRoot;
 | |
| 
 | |
|    while (currNode->planeEQIndex != -1) {
 | |
|       const PlaneEQ& rPlane = getPlaneEQ(currNode->planeEQIndex);
 | |
| 
 | |
|       // We arbitrarily place "on" points in the front.  it shouldn't matter.
 | |
|       //
 | |
|       if (rPlane.whichSide(testPoint) == PlaneBack)
 | |
|          currNode = currNode->pBack;
 | |
|       else
 | |
|          currNode = currNode->pFront;
 | |
|    }
 | |
| 
 | |
|    AssertFatal(currNode->isSolid == false, "Internal Error: we should be in an empty leaf");
 | |
|    AssertFatal(currNode->zoneId != -1, "Internal Error: leaf's zoneId cannot be -1");
 | |
| 
 | |
|    mOutsideZoneIndex = currNode->zoneId;
 | |
| }
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| //-------------------------------------- Arenas
 | |
| //
 | |
| EditGeometry::PlaneHashArena::PlaneHashArena(U32 _arenaSize)
 | |
| {
 | |
|    AssertFatal(_arenaSize > 0, "Internal Error: impossible size");
 | |
|    arenaSize = _arenaSize;
 | |
| 
 | |
|    currBuffer   = new EditGeometry::PlaneHashEntry[arenaSize];
 | |
|    currPosition = 0;
 | |
|    mBuffers.push_back(currBuffer);
 | |
| }
 | |
| 
 | |
| EditGeometry::PlaneHashArena::~PlaneHashArena()
 | |
| {
 | |
|    arenaSize    = 0;
 | |
|    currBuffer   = NULL;
 | |
|    currPosition = 0;
 | |
| 
 | |
|    for (U32 i = 0; i < mBuffers.size(); i++) {
 | |
|       delete [] mBuffers[i];
 | |
|       mBuffers[i] = NULL;
 | |
|    }
 | |
| }
 | |
| 
 | |
| EditGeometry::PointHashArena::PointHashArena(U32 _arenaSize)
 | |
| {
 | |
|    AssertFatal(_arenaSize > 0, "Internal Error: impossible size");
 | |
|    arenaSize = _arenaSize;
 | |
| 
 | |
|    currBuffer   = new EditGeometry::PointHashEntry[arenaSize];
 | |
|    currPosition = 0;
 | |
|    mBuffers.push_back(currBuffer);
 | |
| }
 | |
| 
 | |
| EditGeometry::PointHashArena::~PointHashArena()
 | |
| {
 | |
|    arenaSize    = 0;
 | |
|    currBuffer   = NULL;
 | |
|    currPosition = 0;
 | |
| 
 | |
|    for (U32 i = 0; i < mBuffers.size(); i++) {
 | |
|       delete [] mBuffers[i];
 | |
|       mBuffers[i] = NULL;
 | |
|    }
 | |
| }
 | |
| 
 | |
| 
 | |
| int FN_CDECL cmpZoneNum(const void* p1, const void* p2)
 | |
| {
 | |
|    U32 u1 = *((const U32*)p1);
 | |
|    U32 u2 = *((const U32*)p2);
 | |
| 
 | |
|    if ((u1 & 0x80000000) || (u2 & 0x80000000)) {
 | |
|       // special zones, make sure they sort to the end...
 | |
|       if ((u1 & 0x80000000) && (u2 & 0x80000000)) {
 | |
|          return S32(u1 & 0x7FFFFFFF) - S32(u2 & 0x7FFFFFFF);
 | |
|       } else if (u1 & 0x80000000) {
 | |
|          // u1 to end
 | |
|          return -1;
 | |
|       } else {
 | |
|          // u2 to end
 | |
|          return 1;
 | |
|       }
 | |
|    } else {
 | |
|       return S32(u1) - S32(u2);
 | |
|    }
 | |
| }
 | |
| 
 | |
| bool EditGeometry::Surface::isMemberOfZone(U32 zone)
 | |
| {
 | |
|    for (U32 i = 0; i < winding.numZoneIds; i++)
 | |
|       if (winding.zoneIds[i] == zone)
 | |
|          return true;
 | |
|    return false;
 | |
| }
 | 
