tge/tools/map2difPlus/editGeometry.cc
2017-04-17 06:17:10 -06:00

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;
}