tge/tools/map2dif/exportGeometry.cc
2017-04-17 06:17:10 -06:00

2395 lines
93 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// Torque Game Engine
//
// Copyright (c) 2001 GarageGames.Com
//-----------------------------------------------------------------------------
// this define required for VC++ due to conflict with placement new in STL
#define __PLACEMENT_NEW_INLINE
#include <algorithm>
#include "map2dif/editGeometry.h"
#include "map2dif/entityTypes.h"
#include "map2dif/tokenizer.h"
#include "map2dif/csgBrush.h"
#include "map2dif/editInteriorRes.h"
#include "interior/interior.h"
#include "dgl/gBitmap.h"
#include "core/resManager.h"
#include "math/mMath.h"
#include "dgl/materialList.h"
#include "core/bitMatrix.h"
#include "interior/mirrorSubObject.h"
#include "interior/interiorResObjects.h"
#include "interior/forceField.h"
//------------------------------------------------------------------------------
enum PointClassification {
LEFT,
RIGHT,
BEYOND,
BEHIND,
BETWEEN,
ORIGIN,
DEST
};
PointClassification classify(const Point3D& start, const Point3D& end, const Point3D& normal, const Point3D& query)
{
Point3D edge = end - start;
Point3D test = query - start;
if (query == end)
return DEST;
if (query == start)
return ORIGIN;
Point3D edgeCopy = edge;
Point3D testCopy = test;
edgeCopy.normalize();
testCopy.normalize();
Point3D cross;
mCross(edgeCopy, testCopy, &cross);
if (cross.len() < 0.000001) {
// Colinear
F64 dot = mDot(edgeCopy, test);
if (dot < 0.0) {
return BEHIND;
} else {
F64 len0 = edge.len();
F64 len1 = test.len();
if (len0 > len1)
return BETWEEN;
else
return BEYOND;
}
} else {
// Not colinear, test against the normal to find out which side it's on...
F64 dot = mDot(cross, normal);
if (dot > 0.0)
return LEFT;
else
return RIGHT;
}
}
void EditGeometry::giftWrapPortal(Winding& winding, Portal* portal)
{
if (portal->windings.size() == 1) {
winding = portal->windings[0];
return;
}
Point3D newPoints[4];
F64 minOrthox = 1e12;
F64 maxOrthox = -1e12;
F64 minOrthoy = 1e12;
F64 maxOrthoy = -1e12;
for (U32 i = 0; i < portal->windings.size(); i++) {
for (U32 j = 0; j < portal->windings[i].numIndices; j++) {
const Point3D& rPoint = getPoint(portal->windings[i].indices[j]);
F64 dotx = mDot(portal->x, rPoint);
F64 doty = mDot(portal->y, rPoint);
if (dotx < minOrthox)
minOrthox = dotx;
if (dotx > maxOrthox)
maxOrthox = dotx;
if (doty < minOrthoy)
minOrthoy = doty;
if (doty > maxOrthoy)
maxOrthoy = doty;
}
}
AssertFatal(minOrthox != 1e12, "Screwed!");
AssertFatal(maxOrthox != -1e12, "Screwed!");
AssertFatal(minOrthoy != 1e12, "Screwed!");
AssertFatal(maxOrthoy != -1e12, "Screwed!");
const Point3D& ref = getPoint(portal->windings[0].indices[0]);
F64 refx = mDot(ref, portal->x);
F64 refy = mDot(ref, portal->y);
newPoints[0] = ref + (portal->x * (minOrthox - refx)) + (portal->y * (minOrthoy - refy));
newPoints[1] = ref + (portal->x * (minOrthox - refx)) + (portal->y * (maxOrthoy - refy));
newPoints[2] = ref + (portal->x * (maxOrthox - refx)) + (portal->y * (minOrthoy - refy));
newPoints[3] = ref + (portal->x * (maxOrthox - refx)) + (portal->y * (maxOrthoy - refy));
UniqueVector points;
points.pushBackUnique(insertPoint(newPoints[0]));
points.pushBackUnique(insertPoint(newPoints[1]));
points.pushBackUnique(insertPoint(newPoints[2]));
points.pushBackUnique(insertPoint(newPoints[3]));
Point3D cross;
const PlaneEQ& rPlane = getPlaneEQ(portal->planeEQIndex);
if (mFabs(rPlane.normal.z) < 0.99)
mCross(rPlane.normal, Point3D(0, 0, 1), &cross);
else
mCross(rPlane.normal, Point3D(0, 1, 0), &cross);
F64 minValue = 1e9;
S32 a = -1;
for (U32 i = 0; i < points.size(); i++) {
F64 dot = mDot(getPoint(points[i]), cross);
if (dot < minValue) {
minValue = dot;
a = i;
}
}
AssertFatal(a != -1, "Wtf!");
U32 n = points.size();
U32 storA = a;
points.push_back(points[a]);
winding.numIndices = 0;
for (U32 m = 0; m < n; m++) {
// Swap (p[a], p[m])
U32 temp = points[a];
points[a] = points[m];
points[m] = temp;
winding.indices[winding.numIndices++] = points[m];
AssertFatal(winding.numIndices <= 32, "Error, too many points in this winding!");
a = m + 1;
for (U32 i = m + 2; i <= n; i++) {
PointClassification c = classify(getPoint(points[m]), getPoint(points[a]), rPlane.normal, getPoint(points[i]));
if (c == LEFT || c == BEYOND)
a = i;
}
if (a == n)
break;
}
AssertFatal(winding.numIndices >= 3, "Error, bad portal generated!");
}
inline U16 createLeafIndex(U16 baseIndex,
bool isSolid)
{
AssertFatal(baseIndex <= 0x3fff, "out of range base index. Uh, oh... Talk to DMoore");
U16 baseRet;
if (isSolid)
baseRet = 0xC000;
else
baseRet = 0x8000;
return (baseRet | baseIndex);
}
inline U16 EditGeometry::remapPlaneIndex(S32 inPlaneIndex)
{
AssertFatal(inPlaneIndex >= 0 && inPlaneIndex < mPlaneEQs.size(), "Error, out of bounds plane index!");
AssertFatal(mPlaneRemaps[inPlaneIndex] != -1, "Error, this plane should have been exported, tell DMoore");
U16 ret = mPlaneRemaps[inPlaneIndex];
AssertFatal(ret < 32768, avar("Error, there are still to many dang planes! Tell DMoore: %d", ret));
ret |= (inPlaneIndex & 1) ? 0x8000 : 0;
return ret;
}
inline U16 EditGeometry::remapVehiclePlaneIndex(S32 inPlaneIndex, Interior* pRuntime)
{
const PlaneEQ& rPlane = getPlaneEQ(inPlaneIndex);
PlaneF newPlane;
newPlane.x = F32(rPlane.normal.x);
newPlane.y = F32(rPlane.normal.y);
newPlane.z = F32(rPlane.normal.z);
newPlane.d = F32(rPlane.dist) / mWorldEntity->mGeometryScale;
for (U32 i = 0; i < pRuntime->mVehiclePlanes.size(); i++)
{
if (pRuntime->mVehiclePlanes[i].x == newPlane.x &&
pRuntime->mVehiclePlanes[i].y == newPlane.y &&
pRuntime->mVehiclePlanes[i].z == newPlane.z &&
pRuntime->mVehiclePlanes[i].d == newPlane.d)
return i;
}
pRuntime->mVehiclePlanes.push_back(newPlane);
return (pRuntime->mVehiclePlanes.size() - 1);
}
inline U32 makeNullSurfaceIndex(U32 idx)
{
return idx | 0x80000000;
}
inline U32 makeVehicleNullSurfaceIndex(U32 idx)
{
return (idx | 0x80000000 | 0x40000000);
}
U16 EditGeometry::exportBSPToRuntime(Interior* pRuntime, EditBSPNode* pNode)
{
if (pNode->planeEQIndex == -1) {
if (pNode->isSolid == true) {
// solid node
pRuntime->mBSPSolidLeaves.increment();
Interior::IBSPLeafSolid& rRTLeaf = pRuntime->mBSPSolidLeaves.last();
// DMM Need to include a list of surfaces affected by this leaf
rRTLeaf.surfaceIndex = pRuntime->mSolidLeafSurfaces.size();
rRTLeaf.surfaceCount = 0;
U32 i;
for (i = 0; i < mSurfaces.size(); i++) {
Surface& rSurface = mSurfaces[i];
bool found = false;
for (U32 j = 0; j < rSurface.winding.numNodes; j++) {
if (rSurface.winding.solidNodes[j] == pNode->nodeId) {
found = true;
break;
}
}
if (found == true) {
pRuntime->mSolidLeafSurfaces.push_back(i);
rRTLeaf.surfaceCount++;
}
}
for (i = 0; i < mNullSurfaces.size(); i++) {
NullSurface& rNullSurface = mNullSurfaces[i];
bool found = false;
for (U32 j = 0; j < rNullSurface.winding.numNodes; j++) {
if (rNullSurface.winding.solidNodes[j] == pNode->nodeId) {
found = true;
break;
}
}
if (found == true) {
pRuntime->mSolidLeafSurfaces.push_back(makeNullSurfaceIndex(i));
rRTLeaf.surfaceCount++;
}
}
return createLeafIndex(pRuntime->mBSPSolidLeaves.size() - 1, true);
}
else {
if (mZones[pNode->zoneId]->active == false) {
return createLeafIndex(0x0FFF, false);
} else {
U32 actives = 0;
for (U32 i = 0; i < pNode->zoneId; i++)
if (mZones[i]->active)
actives++;
return createLeafIndex(actives, false);
}
//
// AssertFatal(pNode->zoneId < (0xFFFF & ~0xC000), "Error, this index is going to cause problems!");
// return createLeafIndex(pNode->zoneId, false);
}
}
else {
// Node
U16 retIndex = pRuntime->mBSPNodes.size();
pRuntime->mBSPNodes.increment();
Interior::IBSPNode& rRTNode = pRuntime->mBSPNodes.last();
rRTNode.planeIndex = remapPlaneIndex(pNode->planeEQIndex);
rRTNode.frontIndex = exportBSPToRuntime(pRuntime, pNode->pFront);
rRTNode.backIndex = exportBSPToRuntime(pRuntime, pNode->pBack);
return retIndex;
}
}
void EditGeometry::exportDMLToRuntime(Interior* pRuntime, Vector<char*>& textureNames)
{
AssertFatal(pRuntime->mMaterialList == NULL, "Error, material list already exists!");
Vector<char*> textureNamesCopy;
for (U32 i = 0; i < textureNames.size(); i++) {
U32 len = dStrlen(textureNames[i]) + dStrlen(mWorldEntity->mWadPrefix) + 1;
char* copyBuffer = new char[len];
dStrcpy(copyBuffer, mWorldEntity->mWadPrefix);
dStrcat(copyBuffer, textureNames[i]);
textureNamesCopy.push_back(copyBuffer);
}
pRuntime->mMaterialList = new MaterialList(textureNamesCopy.size(), (const char**)textureNamesCopy.address());
for (U32 i = 0; i < textureNamesCopy.size(); i++)
delete [] textureNamesCopy[i];
}
U32 EditGeometry::exportPointToRuntime(Interior* pRuntime, const U32 pointIndex)
{
for (U32 i = 0; i < mExportPointMap.size(); i++) {
if (mExportPointMap[i].originalIndex == pointIndex)
return mExportPointMap[i].runtimeIndex;
}
mExportPointMap.increment();
mExportPointMap.last().originalIndex = pointIndex;
mExportPointMap.last().runtimeIndex = pRuntime->mPoints.size();
const Point3D& rPoint = getPoint(pointIndex);
pRuntime->mPoints.increment();
pRuntime->mPoints.last().point.set(F32(rPoint.x) / mWorldEntity->mGeometryScale,
F32(rPoint.y) / mWorldEntity->mGeometryScale,
F32(rPoint.z) / mWorldEntity->mGeometryScale);
return mExportPointMap.last().runtimeIndex;
}
U32 EditGeometry::exportVehiclePointToRuntime(Interior* pRuntime, const U32 pointIndex)
{
for (U32 i = 0; i < mVehicleExportPointMap.size(); i++) {
if (mVehicleExportPointMap[i].originalIndex == pointIndex)
return mVehicleExportPointMap[i].runtimeIndex;
}
mVehicleExportPointMap.increment();
mVehicleExportPointMap.last().originalIndex = pointIndex;
mVehicleExportPointMap.last().runtimeIndex = pRuntime->mVehiclePoints.size();
const Point3D& rPoint = getPoint(pointIndex);
pRuntime->mVehiclePoints.increment();
pRuntime->mVehiclePoints.last().point.set(F32(rPoint.x) / mWorldEntity->mGeometryScale,
F32(rPoint.y) / mWorldEntity->mGeometryScale,
F32(rPoint.z) / mWorldEntity->mGeometryScale);
return mExportPointMap.last().runtimeIndex;
}
void EditGeometry::exportWindingToRuntime(Interior* pRuntime,
const Winding& rWinding)
{
Winding finalWinding;
finalWinding.numIndices = 0;
for (U32 i = 0; i < rWinding.numIndices; i++) {
finalWinding.indices[finalWinding.numIndices++] =
exportPointToRuntime(pRuntime, rWinding.indices[i]);
}
AssertFatal(finalWinding.numIndices == rWinding.numIndices, "Ah, crap. Paradox.");
pRuntime->mWindingIndices.increment();
pRuntime->mWindingIndices.last().windingStart = pRuntime->mWindings.size();
pRuntime->mWindingIndices.last().windingCount = finalWinding.numIndices;
for (U32 i = 0; i < finalWinding.numIndices; i++) {
pRuntime->mWindings.increment();
pRuntime->mWindings.last() = finalWinding.indices[i];
}
}
void EditGeometry::exportVehicleWindingToRuntime(Interior* pRuntime,
const Winding& rWinding)
{
Winding finalWinding;
finalWinding.numIndices = 0;
for (U32 i = 0; i < rWinding.numIndices; i++) {
finalWinding.indices[finalWinding.numIndices++] =
exportVehiclePointToRuntime(pRuntime, rWinding.indices[i]);
}
AssertFatal(finalWinding.numIndices == rWinding.numIndices, "Ah, crap. Paradox.");
pRuntime->mVehicleWindingIndices.increment();
pRuntime->mVehicleWindingIndices.last().windingStart = pRuntime->mVehicleWindings.size();
pRuntime->mVehicleWindingIndices.last().windingCount = finalWinding.numIndices;
for (U32 i = 0; i < finalWinding.numIndices; i++) {
pRuntime->mVehicleWindings.increment();
pRuntime->mVehicleWindings.last() = finalWinding.indices[i];
}
}
U32 EditGeometry::exportEmitStringToRuntime(Interior* pRuntime,
const U8* pString,
const U32 stringLen)
{
// Search for already included strings.
if (pRuntime->mConvexHullEmitStrings.size() > stringLen) {
for (S32 i = 0; i < pRuntime->mConvexHullEmitStrings.size() - stringLen; i++) {
if (dMemcmp(&pRuntime->mConvexHullEmitStrings[i], pString, stringLen) == 0)
return i;
}
}
// Insert the string
U32 insertPoint = pRuntime->mConvexHullEmitStrings.size();
if (pRuntime->mConvexHullEmitStrings.size() + stringLen >
pRuntime->mConvexHullEmitStrings.capacity()) {
if (pRuntime->mConvexHullEmitStrings.capacity() == 0)
pRuntime->mConvexHullEmitStrings.reserve(1);
pRuntime->mConvexHullEmitStrings.reserve(pRuntime->mConvexHullEmitStrings.capacity() * 2);
}
pRuntime->mConvexHullEmitStrings.setSize(pRuntime->mConvexHullEmitStrings.size() + stringLen);
dMemcpy(&pRuntime->mConvexHullEmitStrings[insertPoint],
pString, stringLen);
return insertPoint;
}
U32 EditGeometry::exportVehicleEmitStringToRuntime(Interior* pRuntime,
const U8* pString,
const U32 stringLen)
{
// Search for already included strings.
if (pRuntime->mVehicleConvexHullEmitStrings.size() > stringLen) {
for (S32 i = 0; i < pRuntime->mVehicleConvexHullEmitStrings.size() - stringLen; i++) {
if (dMemcmp(&pRuntime->mVehicleConvexHullEmitStrings[i], pString, stringLen) == 0)
return i;
}
}
// Insert the string
U32 insertPoint = pRuntime->mVehicleConvexHullEmitStrings.size();
if (pRuntime->mVehicleConvexHullEmitStrings.size() + stringLen >
pRuntime->mVehicleConvexHullEmitStrings.capacity()) {
if (pRuntime->mVehicleConvexHullEmitStrings.capacity() == 0)
pRuntime->mVehicleConvexHullEmitStrings.reserve(1);
pRuntime->mVehicleConvexHullEmitStrings.reserve(pRuntime->mVehicleConvexHullEmitStrings.capacity() * 2);
}
pRuntime->mVehicleConvexHullEmitStrings.setSize(pRuntime->mVehicleConvexHullEmitStrings.size() + stringLen);
dMemcpy(&pRuntime->mVehicleConvexHullEmitStrings[insertPoint],
pString, stringLen);
return insertPoint;
}
U16 EditGeometry::getMaterialIndex(const char* texName) const
{
for (U32 i = 0; i < mTextureNames.size(); i++) {
if (dStricmp(mTextureNames[i], texName) == 0)
return i;
}
return 0xFFFF;
}
//------------------------------------------------------------------------------
void EditGeometry::fillInLightmapInfo(Surface& rSurface)
{
AssertFatal(rSurface.textureIndex != 0, "Error, shouldn't get these here!");
//F32 lumelScale = mWorldEntity->sgGetLightingScale();
Point3D axises[3] = {
Point3D(1, 0, 0),
Point3D(0, 1, 0),
Point3D(0, 0, 1)
};
F64 bestDot = -1;
S32 bestIndex = -1;
for (U32 i = 0; i < 3; i++) {
F64 dot = mFabs(mDot(getPlaneEQ(rSurface.planeIndex).normal, axises[i]));
if (dot > bestDot) {
bestDot = dot;
bestIndex = i;
}
}
AssertFatal(bestIndex != -1 && bestDot > 0.0, "Paradox! Talk to DMoore");
S32 sc, tc;
if (axises[bestIndex].x != 0.0) {
sc = 1;
tc = 2;
} else if (axises[bestIndex].y != 0.0) {
sc = 0;
tc = 2;
} else {
sc = 0;
tc = 1;
}
F64 coords[MaxWindingPoints * 3];
for (U32 i = 0; i < rSurface.winding.numIndices; i++) {
coords[i * 3 + 0] = getPoint(rSurface.winding.indices[i]).x;
coords[i * 3 + 1] = getPoint(rSurface.winding.indices[i]).y;
coords[i * 3 + 2] = getPoint(rSurface.winding.indices[i]).z;
}
F64 minS = 1e10; S32 minSIndex = -1;
F64 maxS = -1e10; S32 maxSIndex = -1;
F64 minT = 1e10; S32 minTIndex = -1;
F64 maxT = -1e10; S32 maxTIndex = -1;
for (U32 i = 0; i < rSurface.winding.numIndices; i++) {
if (coords[i*3 + sc] < minS) {
minS = coords[i*3 + sc];
minSIndex = i;
}
if (coords[i*3 + sc] > maxS) {
maxS = coords[i*3 + sc];
maxSIndex = i;
}
if (coords[i*3 + tc] < minT) {
minT = coords[i*3 + tc];
minTIndex = i;
}
if (coords[i*3 + tc] > maxT) {
maxT = coords[i*3 + tc];
maxTIndex = i;
}
}
AssertFatal(minSIndex != -1 && maxSIndex != -1 && minTIndex != -1 && maxTIndex != -1, "Paradox!");
// Points in the lmap coord space that represent the bounds of the poly
F64 virtualMin[2], virtualMax[2], desiredStart[2], desiredEnd[2];
virtualMin[0] = coords[minSIndex*3 + sc];
virtualMin[1] = coords[minTIndex*3 + tc];
virtualMax[0] = coords[maxSIndex*3 + sc];
virtualMax[1] = coords[maxTIndex*3 + tc];
for (U32 i = 0; i < 2; i++) {
desiredStart[i] = virtualMin[i] / rSurface.sgLightingScale;
if (desiredStart[i] - mFloor(desiredStart[i]) < 0.5) {
desiredStart[i] = mFloor(desiredStart[i] - 1.0);
} else {
desiredStart[i] = mFloor(desiredStart[i]);
}
}
for (U32 i = 0; i < 2; i++) {
desiredEnd[i] = virtualMax[i] / rSurface.sgLightingScale;
if (mCeil(desiredEnd[i]) - desiredEnd[i] < 0.5) {
desiredEnd[i] = mCeil(desiredEnd[i] + 1.0);
} else {
desiredEnd[i] = mCeil(desiredEnd[i]);
}
}
// Make sure we don't lose precision to errant FP rounding. We now
// know how large the lm section should be...
rSurface.lMapDimX = U32(desiredEnd[0] - desiredStart[0] + 0.5);
rSurface.lMapDimY = U32(desiredEnd[1] - desiredStart[1] + 0.5);
AssertISV(rSurface.lMapDimX <= 256 && rSurface.lMapDimY <= 256, avar(
"Light map too large: %d x %d (where max is 256 x 256). Your have a brush that is too large, or your light map scale value may be too small.",
rSurface.lMapDimX, rSurface.lMapDimY));
AssertFatal(desiredEnd[0] > desiredStart[0] && desiredEnd[1] > desiredStart[1] &&
S32(rSurface.lMapDimX) > 0 && S32(rSurface.lMapDimY) > 0, "Aw, crap, a paradox. Please talk to DMoore");
desiredStart[0] *= rSurface.sgLightingScale;
desiredStart[1] *= rSurface.sgLightingScale;
desiredEnd[0] *= rSurface.sgLightingScale;
desiredEnd[1] *= rSurface.sgLightingScale;
for (U32 i = 0; i < 4; i++) {
rSurface.lmapTexGenX[i] = 0.0f;
rSurface.lmapTexGenY[i] = 0.0f;
}
rSurface.tempScale[0] = rSurface.tempScale[1] = 1.0 / rSurface.sgLightingScale;
rSurface.lmapTexGenX[sc] = F32(1.0 / rSurface.sgLightingScale);
rSurface.lmapTexGenX[3] = F32(-desiredStart[0] / rSurface.sgLightingScale);
rSurface.lmapTexGenY[tc] = F32(1.0 / rSurface.sgLightingScale);
rSurface.lmapTexGenY[3] = F32(-desiredStart[1] / rSurface.sgLightingScale);
if (rSurface.lMapDimX < rSurface.lMapDimY) {
// Always orient surfaces so that they lay down the long way...
rSurface.lmapTexGenSwapped = true;
U32 iTemp = rSurface.lMapDimX;
rSurface.lMapDimX = rSurface.lMapDimY;
rSurface.lMapDimY = iTemp;
F32 fTemp;
for (U32 i = 0; i < 4; i++) {
fTemp = rSurface.lmapTexGenX[i];
rSurface.lmapTexGenX[i] = rSurface.lmapTexGenY[i];
rSurface.lmapTexGenY[i] = fTemp;
}
} else {
rSurface.lmapTexGenSwapped = false;
}
rSurface.pNormalLMap = new GBitmap(rSurface.lMapDimX, rSurface.lMapDimY);
rSurface.pAlarmLMap = new GBitmap(rSurface.lMapDimX, rSurface.lMapDimY);
}
void EditGeometry::adjustLMapTexGen(Surface& rSurface)
{
//F32 lumelScale = rSurface.isInside ? mWorldEntity->mInsideLumelScale :
// mWorldEntity->mOutsideLumelScale;
F64 scaleX = 1.0 / F64(mLightmaps[rSurface.sheetIndex]->getWidth() * rSurface.sgLightingScale);
F64 scaleY = 1.0 / F64(mLightmaps[rSurface.sheetIndex]->getHeight() * rSurface.sgLightingScale);
U32 i;
for (i = 0; i < 4; i++) {
rSurface.lmapTexGenX[i] /= rSurface.tempScale[0];
rSurface.lmapTexGenY[i] /= rSurface.tempScale[1];
rSurface.lmapTexGenX[i] *= scaleX;
rSurface.lmapTexGenY[i] *= scaleY;
}
rSurface.lmapTexGenX[3] += F64(rSurface.offsetX) / F64(mLightmaps[rSurface.sheetIndex]->getWidth());
rSurface.lmapTexGenY[3] += F64(rSurface.offsetY) / F64(mLightmaps[rSurface.sheetIndex]->getHeight());
for (i = 0; i < 3; i++) {
rSurface.lmapTexGenX[i] *= mWorldEntity->mGeometryScale;
rSurface.lmapTexGenY[i] *= mWorldEntity->mGeometryScale;
}
}
void EditGeometry::dumpSurfaceToRuntime(Interior* pRuntime,
Surface& editSurface)
{
AssertFatal(editSurface.sheetIndex != 0xFFFFFFFF, "Error, shouldn't get these here!");
pRuntime->mSurfaces.increment();
pRuntime->mLMTexGenEQs.increment();
Interior::Surface& rSurface = pRuntime->mSurfaces.last();
Interior::TexGenPlanes& rLMPlanes = pRuntime->mLMTexGenEQs.last();
rSurface.planeIndex = remapPlaneIndex(editSurface.planeIndex);
rSurface.textureIndex = editSurface.textureIndex;
rLMPlanes.planeX.x = editSurface.lmapTexGenX[0];
rLMPlanes.planeX.y = editSurface.lmapTexGenX[1];
rLMPlanes.planeX.z = editSurface.lmapTexGenX[2];
rLMPlanes.planeX.d = editSurface.lmapTexGenX[3];
rLMPlanes.planeY.x = editSurface.lmapTexGenY[0];
rLMPlanes.planeY.y = editSurface.lmapTexGenY[1];
rLMPlanes.planeY.z = editSurface.lmapTexGenY[2];
rLMPlanes.planeY.d = editSurface.lmapTexGenY[3];
rSurface.texGenIndex = editSurface.texGenIndex;
// We used only runtime flags in the surface, so this is fine...
rSurface.surfaceFlags = editSurface.flags & Interior::SurfaceFlagMask;
rSurface.fanMask = editSurface.fanMask;
// Ok, this is an abuse, but it's easy. Export to the trifans,
// then steal the last one.
exportWindingToRuntime(pRuntime, editSurface.winding);
rSurface.windingStart = pRuntime->mWindingIndices.last().windingStart;
rSurface.windingCount = pRuntime->mWindingIndices.last().windingCount;
pRuntime->mWindingIndices.decrement();
AssertFatal(editSurface.lMapDimX < 256 && editSurface.lMapDimY < 256,
"Error, lightmap too large to fit into 8 bits!");
AssertFatal(editSurface.offsetX < 256 && editSurface.offsetY < 256,
"Error, lightmap too large to fit into 8 bits! (Plus, that's just plain wierd)");
rSurface.lightCount = editSurface.numLights;
rSurface.lightStateInfoStart = editSurface.stateDataStart;
rSurface.mapSizeX = editSurface.lMapDimX;
rSurface.mapSizeY = editSurface.lMapDimY;
rSurface.mapOffsetX = editSurface.offsetX;
rSurface.mapOffsetY = editSurface.offsetY;
}
void EditGeometry::dumpNullSurfaceToRuntime(Interior* pRuntime,
EditGeometry::NullSurface& editSurface)
{
pRuntime->mNullSurfaces.increment();
Interior::NullSurface& rSurface = pRuntime->mNullSurfaces.last();
rSurface.planeIndex = remapPlaneIndex(editSurface.planeIndex);
rSurface.surfaceFlags = editSurface.flags;
exportWindingToRuntime(pRuntime, editSurface.winding);
rSurface.windingStart = pRuntime->mWindingIndices.last().windingStart;
rSurface.windingCount = pRuntime->mWindingIndices.last().windingCount;
pRuntime->mWindingIndices.decrement();
}
void EditGeometry::dumpVehicleNullSurfaceToRuntime(Interior* pRuntime,
EditGeometry::NullSurface& editSurface)
{
pRuntime->mVehicleNullSurfaces.increment();
Interior::NullSurface& rSurface = pRuntime->mVehicleNullSurfaces.last();
rSurface.planeIndex = remapVehiclePlaneIndex(editSurface.planeIndex, pRuntime);
rSurface.surfaceFlags = editSurface.flags;
exportVehicleWindingToRuntime(pRuntime, editSurface.winding);
rSurface.windingStart = pRuntime->mVehicleWindingIndices.last().windingStart;
rSurface.windingCount = pRuntime->mVehicleWindingIndices.last().windingCount;
pRuntime->mVehicleWindingIndices.decrement();
}
void EditGeometry::dumpMirrorToRuntime(Interior* pRuntime,
MirrorSurfaceEntity* pEntity)
{
// Do we have _any_ windings?
if (pEntity->mRealZone == 0xFFFFFFFF)
return;
pRuntime->mSubObjects.push_back(new MirrorSubObject);
MirrorSubObject* pObject = static_cast<MirrorSubObject*>(pRuntime->mSubObjects.last());
pObject->mZone = pEntity->mRealZone;
pObject->mDetailLevel = mWorldEntity->mDetailNumber;
pObject->mAlphaLevel = pEntity->mAlphaLevel;
pObject->surfaceStart = 0xFFFFFFFF;
pObject->surfaceCount = 0;
// We know that our surfaces are marked with our zone key, and should be contiguous
// in the list of surfaces
for (U32 i = 0; i < mSurfaces.size(); i++) {
Surface& rSurface = mSurfaces[i];
if (rSurface.winding.zoneIds[0] == pEntity->mZoneKey) {
pObject->surfaceStart = i;
Point3D centroid(0, 0, 0);
for (U32 j = 0; j < rSurface.winding.numIndices; j++) {
centroid += getPoint(rSurface.winding.indices[j]);
}
centroid /= F64(rSurface.winding.numIndices) * mWorldEntity->mGeometryScale;
pObject->mCentroid.x = centroid.x;
pObject->mCentroid.y = centroid.y;
pObject->mCentroid.z = centroid.z;
break;
}
}
AssertFatal(pObject->surfaceStart != 0xFFFFFFFF, "Error, couldn't find our surface");
for (U32 i = pObject->surfaceStart; i < mSurfaces.size(); i++) {
Surface& rSurface = mSurfaces[i];
if (rSurface.winding.zoneIds[0] == pEntity->mZoneKey) {
pObject->surfaceCount++;
} else {
break;
}
}
}
void EditGeometry::dumpTriggerToRuntime(Interior* /*pRuntime*/,
InteriorResource* pResource,
TriggerEntity* pEntity)
{
AssertISV(mWorldEntity->mDetailNumber == 0, "Error, triggers only go on the 0 detail level!");
EditInteriorResource* pEditResource = dynamic_cast<EditInteriorResource*>(pResource);
InteriorResTrigger* pTrigger = new InteriorResTrigger();
dStrcpy(pTrigger->mName, pEntity->mName);
pTrigger->mDataBlock = StringTable->insert(pEntity->mDataBlock);
pTrigger->mPolyhedron = pEntity->triggerPolyhedron;
pTrigger->mOffset.x = pEntity->mOrigin.x;
pTrigger->mOffset.y = pEntity->mOrigin.y;
pTrigger->mOffset.z = pEntity->mOrigin.z;
pTrigger->mDictionary = pEntity->mDictionary;
pEditResource->insertTrigger(pTrigger);
}
void EditGeometry::dumpAISpecialToRuntime(Interior* /*pRuntime*/,
InteriorResource* pResource,
SpecialNodeEntity* pEntity)
{
AssertISV(mWorldEntity->mDetailNumber == 0, "Error, SpecialNodes only go on the 0 detail level!");
EditInteriorResource* pEditResource = dynamic_cast<EditInteriorResource*>(pResource);
AISpecialNode *pSpecialNode = new AISpecialNode();
pSpecialNode->mName = StringTable->insert(pEntity->mName);
pSpecialNode->mPos.x = (pEntity->mOrigin.x / mWorldEntity->mGeometryScale);
pSpecialNode->mPos.y = (pEntity->mOrigin.y / mWorldEntity->mGeometryScale);
pSpecialNode->mPos.z = (pEntity->mOrigin.z / mWorldEntity->mGeometryScale);
pEditResource->insertSpecialNode(pSpecialNode);
}
void EditGeometry::dumpGameEntityToRuntime(Interior* /*pRuntime*/,
InteriorResource* pResource,
GameEntity* pEntity)
{
AssertISV(mWorldEntity->mDetailNumber == 0, "Error, GameEntities only go on the 0 detail level!");
EditInteriorResource* pEditResource = dynamic_cast<EditInteriorResource*>(pResource);
ItrGameEntity *pGameEntity = new ItrGameEntity();
pGameEntity->mDataBlock = StringTable->insert(pEntity->mDatablock);
pGameEntity->mGameClass = StringTable->insert(pEntity->mGameClass);
pGameEntity->mDictionary = pEntity->mDictionary;
pGameEntity->mPos.x = (pEntity->mOrigin.x / mWorldEntity->mGeometryScale);
pGameEntity->mPos.y = (pEntity->mOrigin.y / mWorldEntity->mGeometryScale);
pGameEntity->mPos.z = (pEntity->mOrigin.z / mWorldEntity->mGeometryScale);
pEditResource->insertGameEntity(pGameEntity);
}
void EditGeometry::dumpDoorToRuntime(Interior* /*pRuntime*/,
InteriorResource* pResource,
DoorEntity* pEntity)
{
AssertISV(mWorldEntity->mDetailNumber == 0, "Error, doors only are on the 0 detail level!");
EditInteriorResource* pEditResource = dynamic_cast<EditInteriorResource*>(pResource);
if (pEntity->mInterior == NULL)
return;
// We need to set the following:
// Resource index (name will be patched in by morian later.
// Parent SO and Path indices (name patched in at runtime)
InteriorPathFollower* pChild = new InteriorPathFollower;
pEditResource->insertPathedChild(pChild);
pChild->mName = StringTable->insert(pEntity->mName);
pChild->mDataBlock = StringTable->insert(pEntity->mDataBlock);
pChild->mInteriorResIndex = pEditResource->insertSubObject(pEntity->mInterior);
pChild->mOffset.x = pEntity->mOrigin.x;
pChild->mOffset.y = pEntity->mOrigin.y;
pChild->mOffset.z = pEntity->mOrigin.z;
pChild->mDictionary = pEntity->mDictionary;
for(U32 i = 0; i < pEntity->mWayPoints.size(); i++)
pEntity->mWayPoints[i].pos /= mWorldEntity->mGeometryScale;
pChild->mWayPoints = pEntity->mWayPoints;
U32 curr = 0;
pChild->mTriggerIds = pEntity->mTriggerIds;
pEntity->mInterior = NULL;
}
void EditGeometry::dumpForceFieldToRuntime(Interior* /*pRuntime*/,
InteriorResource* pResource,
ForceFieldEntity* pEntity)
{
AssertISV(mWorldEntity->mDetailNumber == 0, "Error, force fields can only be on the 0 detail level!");
EditInteriorResource* pEditResource = dynamic_cast<EditInteriorResource*>(pResource);
if (pEntity->mInterior == NULL)
return;
// The process command for this object has created a nice interior object for us,
// let's copy out only the parts we want. Note that this is just about
// as non-optimal as you can imagine, but hey, it's easy and it works.
ForceField* pField = new ForceField;
pField->mBoundingBox = pEntity->mInterior->mBoundingBox;
pField->mBoundingSphere = pEntity->mInterior->mBoundingSphere;
pField->mPlanes = pEntity->mInterior->mPlanes;
pField->mPoints.setSize(pEntity->mInterior->mPoints.size());
for (U32 i = 0; i < pEntity->mInterior->mPoints.size(); i++)
pField->mPoints[i] = pEntity->mInterior->mPoints[i].point;
pField->mBSPNodes.setSize(pEntity->mInterior->mBSPNodes.size());
pField->mBSPSolidLeaves.setSize(pEntity->mInterior->mBSPSolidLeaves.size());
for (U32 i = 0; i < pEntity->mInterior->mBSPNodes.size(); i++) {
pField->mBSPNodes[i].planeIndex = pEntity->mInterior->mBSPNodes[i].planeIndex;
pField->mBSPNodes[i].frontIndex = pEntity->mInterior->mBSPNodes[i].frontIndex;
pField->mBSPNodes[i].backIndex = pEntity->mInterior->mBSPNodes[i].backIndex;
}
for (U32 i = 0; i < pEntity->mInterior->mBSPSolidLeaves.size(); i++) {
pField->mBSPSolidLeaves[i].surfaceIndex = pEntity->mInterior->mBSPSolidLeaves[i].surfaceIndex;
pField->mBSPSolidLeaves[i].surfaceCount = pEntity->mInterior->mBSPSolidLeaves[i].surfaceCount;
}
pField->mSolidLeafSurfaces = pEntity->mInterior->mSolidLeafSurfaces;
pField->mWindings = pEntity->mInterior->mWindings;
pField->mSurfaces.setSize(pEntity->mInterior->mSurfaces.size());
for (U32 i = 0; i < pEntity->mInterior->mSurfaces.size(); i++) {
pField->mSurfaces[i].windingStart = pEntity->mInterior->mSurfaces[i].windingStart;
pField->mSurfaces[i].fanMask = pEntity->mInterior->mSurfaces[i].fanMask;
pField->mSurfaces[i].planeIndex = pEntity->mInterior->mSurfaces[i].planeIndex;
pField->mSurfaces[i].windingCount = pEntity->mInterior->mSurfaces[i].windingCount;
pField->mSurfaces[i].surfaceFlags = pEntity->mInterior->mSurfaces[i].surfaceFlags;
}
pEditResource->insertField(pField);
pField->mName = StringTable->insert(pEntity->mName);
pField->mColor = pEntity->mColor;
U32 curr = 0;
while (pEntity->mTrigger[curr][0] != '\0')
pField->mTriggers.push_back(StringTable->insert(pEntity->mTrigger[curr++]));
}
void EditGeometry::createBoundingVolumes(Interior* pRuntime)
{
// Really slow. ah, well...
Box3F boundBox(Point3F(1e10, 1e10, 1e10), Point3F(-1e10, -1e10, -1e10), true);
Point3F centroid(0, 0, 0);
for (U32 i = 0; i < pRuntime->mPoints.size(); i++) {
boundBox.min.setMin(pRuntime->mPoints[i].point);
boundBox.max.setMax(pRuntime->mPoints[i].point);
centroid += pRuntime->mPoints[i].point;
}
centroid /= F32(pRuntime->mPoints.size());
F32 maxRSq = 0.0f;
for (U32 i = 0; i < pRuntime->mPoints.size(); i++) {
F32 lenSQ = (pRuntime->mPoints[i].point - centroid).lenSquared();
if (lenSQ > maxRSq)
maxRSq = lenSQ;
}
F32 r = mSqrt(maxRSq);
SphereF boundSphere(centroid, r);
pRuntime->mBoundingBox = boundBox;
pRuntime->mBoundingSphere = boundSphere;
}
void remapRecurse(EditBSPNode* node, Vector<S32>& remapVector, U32& currIndex)
{
if (node->planeEQIndex == -1)
return;
S32 planeIndex = node->planeEQIndex;
planeIndex &= ~1;
if (remapVector[planeIndex] == -1) {
remapVector[planeIndex] = currIndex;
remapVector[planeIndex | 1] = currIndex++;
}
remapRecurse(node->pFront, remapVector, currIndex);
remapRecurse(node->pBack, remapVector, currIndex);
}
void EditGeometry::exportPlanes(Interior* pRuntime)
{
// AssertFatal(false, avar("foeey: %d", mPlaneEQs.size()));
mPlaneRemaps.setSize(mPlaneEQs.size());
for (U32 i = 0; i < mPlaneEQs.size(); i++) {
mPlaneRemaps[i] = -1;
}
U32 currIndex = 0;
// Loop through all structures that have planeIndices.
// We'll be keeping only those planes that are actually used.
for (U32 i = 0; i < mSurfaces.size(); i++) {
S32 planeIndex = mSurfaces[i].planeIndex;
planeIndex &= ~1;
if (mPlaneRemaps[planeIndex] == -1) {
mPlaneRemaps[planeIndex] = currIndex;
mPlaneRemaps[planeIndex | 1] = currIndex++;
}
}
for (U32 i = 0; i < mNullSurfaces.size(); i++) {
S32 planeIndex = mNullSurfaces[i].planeIndex;
planeIndex &= ~1;
if (mPlaneRemaps[planeIndex] == -1) {
mPlaneRemaps[planeIndex] = currIndex;
mPlaneRemaps[planeIndex | 1] = currIndex++;
}
}
for (U32 i = 0; i < mPortals.size(); i++) {
S32 planeIndex = mPortals[i]->planeEQIndex;
planeIndex &= ~1;
if (mPlaneRemaps[planeIndex] == -1) {
mPlaneRemaps[planeIndex] = currIndex;
mPlaneRemaps[planeIndex | 1] = currIndex++;
}
}
for (U32 i = 0; i < mStructuralBrushes.size(); i++) {
CSGBrush* pBrush = mStructuralBrushes[i];
for (U32 j = 0; j < pBrush->mPlanes.size(); j++) {
S32 planeIndex = pBrush->mPlanes[j].planeEQIndex;
planeIndex &= ~1;
if (mPlaneRemaps[planeIndex] == -1) {
mPlaneRemaps[planeIndex] = currIndex;
mPlaneRemaps[planeIndex | 1] = currIndex++;
}
}
}
for (U32 i = 0; i < mDetailBrushes.size(); i++) {
CSGBrush* pBrush = mDetailBrushes[i];
for (U32 j = 0; j < pBrush->mPlanes.size(); j++) {
S32 planeIndex = pBrush->mPlanes[j].planeEQIndex;
planeIndex &= ~1;
if (mPlaneRemaps[planeIndex] == -1) {
mPlaneRemaps[planeIndex] = currIndex;
mPlaneRemaps[planeIndex | 1] = currIndex++;
}
}
}
remapRecurse(mBSPRoot, mPlaneRemaps, currIndex);
AssertFatal(currIndex < (1 << 16), "Error, too many unique planes, must be less than (1 << 16)");
pRuntime->mPlanes.setSize(currIndex);
for (U32 i = 0; i < mPlaneEQs.size(); i += 2) {
// Export if the remap vector is not -1
if (mPlaneRemaps[i] != -1) {
const PlaneEQ& rPlane = mPlaneEQs[i];
PlaneF& rRTPlane = pRuntime->mPlanes[mPlaneRemaps[i]];
rRTPlane.x = F32(rPlane.normal.x);
rRTPlane.y = F32(rPlane.normal.y);
rRTPlane.z = F32(rPlane.normal.z);
rRTPlane.d = F32(rPlane.dist) / mWorldEntity->mGeometryScale;
}
}
}
static S32 QSORT_CALLBACK cmpAnimatedLights(const void* a,const void* b)
{
const Interior::AnimatedLight* pLight1 = reinterpret_cast<const Interior::AnimatedLight*>(a);
const Interior::AnimatedLight* pLight2 = reinterpret_cast<const Interior::AnimatedLight*>(b);
if ((pLight1->flags & Interior::AnimationAmbient) == (pLight2->flags & Interior::AnimationAmbient)) {
return 0;
} else {
if (pLight1->flags & Interior::AnimationAmbient) {
// Light1 sorts to back
return 1;
} else {
return -1;
}
}
}
U32 EditGeometry::exportIntensityMap(Interior* pRuntime, GBitmap* bmp)
{
if (bmp == NULL)
return 0xFFFFFFFF;
AssertFatal(bmp->getFormat() == GBitmap::Intensity, avar("Error, wrong format! (%d)", bmp->getFormat()));
U32 retIndex = pRuntime->mStateDataBuffer.size();
pRuntime->mStateDataBuffer.setSize(retIndex + (bmp->getWidth() * bmp->getHeight()));
dMemcpy(&pRuntime->mStateDataBuffer[retIndex], bmp->getWritableBits(),
bmp->getWidth() * bmp->getHeight());
return retIndex;
}
//--------------------------------------------------------------------------
void EditGeometry::preprocessLighting()
{
for (U32 i = 0; i < mSurfaces.size(); i++)
mSurfaces[i].numLights = 0;
if (mAnimatedLights.size() == 0)
return;
// We have to make sure of the following:
// Each state has at least a NULL state data for each surface that ANY state
// affects.
// Each state data has the appropriate stateDataIndex
// Each surface has the appropriate stateDataIndex
//
BitVector surfacesAffected;
surfacesAffected.setSize(mSurfaces.size());
BitMatrix surfaceLightPairs(mSurfaces.size(), mAnimatedLights.size());
surfaceLightPairs.clearAllBits();
// First, make sure all the NULL states are filled in if necessary
for (U32 i = 0; i < mAnimatedLights.size(); i++) {
AnimatedLight* pLight = mAnimatedLights[i];
surfacesAffected.clear();
for (U32 j = 0; j < pLight->states.size(); j++) {
LightState* pState = pLight->states[j];
for (U32 k = 0; k < pState->stateData.size(); k++) {
if (pState->stateData[k].pLMap != NULL) {
surfacesAffected.set(pState->stateData[k].surfaceIndex);
surfaceLightPairs.setBit(pState->stateData[k].surfaceIndex, i);
}
}
}
// Now go back and fill in any missing states
for (U32 j = 0; j < pLight->states.size(); j++) {
LightState* pState = pLight->states[j];
for (U32 k = 0; k < mSurfaces.size(); k++) {
if (surfacesAffected.test(k)) {
// Need to make sure there is a state data for k in this state.
bool found = false;
for (U32 l = 0; l < pState->stateData.size(); l++) {
if (pState->stateData[l].surfaceIndex == k) {
found = true;
break;
}
}
if (found == false) {
pState->stateData.increment();
pState->stateData.last().pLMap = NULL;
pState->stateData.last().surfaceIndex = k;
pState->stateData.last().stateDataIndex = 0;
}
}
}
}
}
// Ok, we now have complete statedata information, mark the number of lights
// on all surfaces. This determines the start point for the statedata.
for (U32 i = 0; i < mSurfaces.size(); i++) {
for (U32 j = 0; j < mAnimatedLights.size(); j++) {
if (surfaceLightPairs.isSet(i, j))
mSurfaces[i].numLights++;
}
}
}
void EditGeometry::postProcessLighting(Interior* pRuntime)
{
if(!mSurfaces.size())
return;
BitVector surfacesAffected;
surfacesAffected.setSize(mSurfaces.size());
mSurfaces[0].stateDataStart = 0;
for (U32 i = 1; i < mSurfaces.size(); i++) {
mSurfaces[i].stateDataStart = mSurfaces[i-1].stateDataStart +
mSurfaces[i-1].numLights;
}
// OK, now, all surfaces are marked with the appropriate stateDataStart point,
// we need to zip through the lights again, and fill in the stateDataIndex field
// on the state data.
//
for (U32 i = 0; i < mAnimatedLights.size(); i++) {
AnimatedLight* pLight = mAnimatedLights[i];
surfacesAffected.clear();
for (U32 j = 0; j < pLight->states.size(); j++) {
for (U32 k = 0; k < pLight->states[j]->stateData.size(); k++) {
pLight->states[j]->stateData[k].stateDataIndex =
mSurfaces[pLight->states[j]->stateData[k].surfaceIndex].stateDataStart;
surfacesAffected.set(pLight->states[j]->stateData[k].surfaceIndex);
}
}
for (U32 j = 0; j < mSurfaces.size(); j++)
if (surfacesAffected.test(j))
mSurfaces[j].stateDataStart++;
}
// We clobbered the stateDataStart in the last pass, lets reset it...
mSurfaces[0].stateDataStart = 0;
for (U32 i = 1; i < mSurfaces.size(); i++) {
U32 correctStart = mSurfaces[i-1].stateDataStart +
mSurfaces[i-1].numLights;
AssertFatal(correctStart == mSurfaces[i].stateDataStart - mSurfaces[i].numLights, "Error, miscount somewhere");
mSurfaces[i].stateDataStart = correctStart;
}
// Finished!
pRuntime->mNumLightStateEntries += mSurfaces.last().stateDataStart + mSurfaces.last().numLights;
}
void EditGeometry::exportLightsToRuntime(Interior* pRuntime)
{
postProcessLighting(pRuntime);
for (U32 i = 0; i < mAnimatedLights.size(); i++) {
AnimatedLight* pLight = mAnimatedLights[i];
pRuntime->mAnimatedLights.increment();
Interior::AnimatedLight& rILight = pRuntime->mAnimatedLights.last();
rILight.nameIndex = pRuntime->mNameBuffer.size();
if (pLight->name == NULL) {
static U32 sUnnamedLight = 0;
pLight->name = new char[128];
dSprintf(pLight->name, 127, "UNNAMED_%d", sUnnamedLight++);
}
pRuntime->mNameBuffer.setSize(rILight.nameIndex + dStrlen(pLight->name) + 1);
dStrcpy(pRuntime->mNameBuffer.address() + rILight.nameIndex, pLight->name);
rILight.flags = pLight->type;
rILight.flags |= pLight->alarm ? Interior::AlarmLight : 0;
rILight.stateIndex = pRuntime->mLightStates.size();
rILight.stateCount = pLight->states.size();
pRuntime->mLightStates.setSize(rILight.stateIndex + rILight.stateCount);
F32 duration = 0.0f;
for (U32 j = 0; j < rILight.stateCount; j++) {
LightState* pState = pLight->states[j];
Interior::LightState& rIState = pRuntime->mLightStates[rILight.stateIndex + j];
rIState.red = pState->color.red;
rIState.green = pState->color.green;
rIState.blue = pState->color.blue;
rIState.activeTime = U32((duration * 1000.0f) + 0.5f);
// DMMFIX
rIState.dataIndex = pRuntime->mStateData.size();
rIState.dataCount = pState->stateData.size();
pRuntime->mStateData.setSize(rIState.dataIndex + rIState.dataCount);
for (U32 k = 0; k < rIState.dataCount; k++) {
Interior::LightStateData& rIStateData = pRuntime->mStateData[rIState.dataIndex + k];
StateData& rStateData = pState->stateData[k];
rIStateData.surfaceIndex = rStateData.surfaceIndex;
rIStateData.mapIndex = exportIntensityMap(pRuntime, rStateData.pLMap);
rIStateData.lightStateIndex = rStateData.stateDataIndex;
}
duration += pState->duration;
}
rILight.duration = U32((duration * 1000.0f) + 0.5f);
}
// Sort the interior lights so that the triggerables are first,
//
dQsort(pRuntime->mAnimatedLights.address(), // p
pRuntime->mAnimatedLights.size(), // nElem
sizeof(Interior::AnimatedLight), // w
cmpAnimatedLights); // cmp
}
//------------------------------------------------------------------------------
bool EditGeometry::exportToRuntime(Interior* pRuntime, InteriorResource* pResource)
{
mExportPointMap.clear();
pRuntime->mHasAlarmState = mHasAlarmState;
// The first thing to do is to export the planelist. Note that
// we're exporting only the forward facing planes. Negatives
// are indicated by the high bit being set on the index. From now
// on, we need to export planeIndices by calling convert...
//
exportPlanes(pRuntime);
// Next we export the BSP Tree. We'll need to call a recursive function
// for this.
U32 totalNodes = mNodeArena.mBuffers.size() * mNodeArena.arenaSize;
pRuntime->mBSPNodes.reserve(totalNodes);
pRuntime->mBSPSolidLeaves.reserve(totalNodes);
exportBSPToRuntime(pRuntime, mBSPRoot);
exportDMLToRuntime(pRuntime, mTextureNames);
// Now export the zones
for (U32 i = 0; i < mZones.size(); i++) {
if (mZones[i]->active == true) {
pRuntime->mZones.increment();
pRuntime->mZones.last().portalStart = pRuntime->mZonePortalList.size();
pRuntime->mZones.last().portalCount = mZones[i]->referencingPortals.size();
pRuntime->mZones.last().zoneId = i;
pRuntime->mZones.last().flags = mZones[i]->ambientLit ? 0 : Interior::ZoneInside;
for (U32 j = 0; j < mZones[i]->referencingPortals.size(); j++) {
pRuntime->mZonePortalList.increment();
pRuntime->mZonePortalList.last() = mZones[i]->referencingPortals[j];
}
}
}
// ...and the portals
for (U32 i = 0; i < mPortals.size(); i++) {
pRuntime->mPortals.increment();
pRuntime->mPortals.last().planeIndex = remapPlaneIndex(mPortals[i]->planeEQIndex);
pRuntime->mPortals.last().triFanStart = pRuntime->mWindingIndices.size();
if (mPortals[i]->windings.size() != 0)
pRuntime->mPortals.last().triFanCount = 1;
else
pRuntime->mPortals.last().triFanCount = 0;
pRuntime->mPortals.last().zoneFront = 0xFFFF;
pRuntime->mPortals.last().zoneBack = 0xFFFF;
for (U32 j = 0; j < pRuntime->mZones.size(); j++) {
if (pRuntime->mZones[j].zoneId == mPortals[i]->frontZone)
pRuntime->mPortals.last().zoneFront = j;
if (pRuntime->mZones[j].zoneId == mPortals[i]->backZone)
pRuntime->mPortals.last().zoneBack = j;
}
AssertFatal((mPortals[i]->frontZone == -1 && mPortals[i]->backZone == -1) ||
(pRuntime->mPortals.last().zoneFront != 0xFFFF && pRuntime->mPortals.last().zoneBack != 0xFFFF),
avar("Error, couldn't find the active zone corresponding to this portal! (%d, %d | %d, %d)",
mPortals[i]->frontZone,
mPortals[i]->backZone,
pRuntime->mPortals.last().zoneFront,
pRuntime->mPortals.last().zoneBack));
if (mPortals[i]->windings.size() != 0) {
Winding winding;
giftWrapPortal(winding, mPortals[i]);
exportWindingToRuntime(pRuntime, winding);
}
}
// Dump the animated lights.
exportLightsToRuntime(pRuntime);
// Dump the texgen
pRuntime->mTexGenEQs.setSize(mTexGenEQs.size());
for (U32 i = 0; i < mTexGenEQs.size(); i++) {
pRuntime->mTexGenEQs[i].planeX.x = mTexGenEQs[i].planeX.x * mWorldEntity->mGeometryScale;
pRuntime->mTexGenEQs[i].planeX.y = mTexGenEQs[i].planeX.y * mWorldEntity->mGeometryScale;
pRuntime->mTexGenEQs[i].planeX.z = mTexGenEQs[i].planeX.z * mWorldEntity->mGeometryScale;
pRuntime->mTexGenEQs[i].planeX.d = mTexGenEQs[i].planeX.d;
pRuntime->mTexGenEQs[i].planeY.x = mTexGenEQs[i].planeY.x * mWorldEntity->mGeometryScale;
pRuntime->mTexGenEQs[i].planeY.y = mTexGenEQs[i].planeY.y * mWorldEntity->mGeometryScale;
pRuntime->mTexGenEQs[i].planeY.z = mTexGenEQs[i].planeY.z * mWorldEntity->mGeometryScale;
pRuntime->mTexGenEQs[i].planeY.d = mTexGenEQs[i].planeY.d;
}
// ...and the surfaces
if (mSurfaces.size() != 0) {
for (U32 i = 0; i < mSurfaces.size(); i++) {
dumpSurfaceToRuntime(pRuntime, mSurfaces[i]);
AssertFatal(mSurfaces[i].sheetIndex < 0xFF, "Error, sheet index won't fit into 8 bits!");
pRuntime->mNormalLMapIndices.push_back(mSurfaces[i].sheetIndex);
pRuntime->mAlarmLMapIndices.push_back(mSurfaces[i].alarmSheetIndex);
}
}
// Dump any mirrors...
// Dump any triggers
// Dump any paths
// Dump any AISpecial Nodes
for (U32 i = 0; i < mEntities.size(); i++) {
if (dynamic_cast<MirrorSurfaceEntity*>(mEntities[i]) != NULL) {
dumpMirrorToRuntime(pRuntime, static_cast<MirrorSurfaceEntity*>(mEntities[i]));
}
else if (dynamic_cast<TriggerEntity*>(mEntities[i]) != NULL) {
dumpTriggerToRuntime(pRuntime, pResource,
static_cast<TriggerEntity*>(mEntities[i]));
}
else if (dynamic_cast<SpecialNodeEntity*>(mEntities[i]) != NULL) {
dumpAISpecialToRuntime(pRuntime, pResource,
static_cast<SpecialNodeEntity*>(mEntities[i]));
}
else if (dynamic_cast<GameEntity*>(mEntities[i]) != NULL) {
dumpGameEntityToRuntime(pRuntime, pResource,
static_cast<GameEntity*>(mEntities[i]));
}
}
// Dump doors (has to be after paths...
for (U32 i = 0; i < mEntities.size(); i++) {
if (dynamic_cast<DoorEntity*>(mEntities[i])) {
dumpDoorToRuntime(pRuntime, pResource, static_cast<DoorEntity*>(mEntities[i]));
}
else if (dynamic_cast<ForceFieldEntity*>(mEntities[i])) {
dumpForceFieldToRuntime(pRuntime, pResource, static_cast<ForceFieldEntity*>(mEntities[i]));
}
}
for (U32 i = 0; i < mNullSurfaces.size(); i++)
dumpNullSurfaceToRuntime(pRuntime, mNullSurfaces[i]);
for (U32 i = 0; i < mVehicleNullSurfaces.size(); i++)
dumpVehicleNullSurfaceToRuntime(pRuntime, mVehicleNullSurfaces[i]);
if (mSurfaces.size() != 0) {
for (U32 i = 0; i < pRuntime->mZones.size(); i++) {
pRuntime->mZones[i].surfaceStart = pRuntime->mZoneSurfaces.size();
pRuntime->mZones[i].surfaceCount = 0;
for (U32 j = 0; j < mSurfaces.size(); j++) {
if (mSurfaces[j].isMemberOfZone(pRuntime->mZones[i].zoneId)) {
pRuntime->mZoneSurfaces.push_back(j);
pRuntime->mZones[i].surfaceCount++;
}
}
}
} else {
for (U32 i = 0; i < pRuntime->mZones.size(); i++) {
pRuntime->mZones[i].surfaceStart = 0;
pRuntime->mZones[i].surfaceCount = 0;
}
}
// ...and the lightmaps
for (U32 i = 0; i < mLightmaps.size(); i++)
pRuntime->mLightmaps.push_back(new GBitmap(*mLightmaps[i]));
createBoundingVolumes(pRuntime);
pRuntime->mDetailLevel = mWorldEntity->mDetailNumber;
pRuntime->mMinPixels = mWorldEntity->mMinPixels;
// Build the interval trees and convex hulls for solid collisions...
if (mSpecialCollisionBrushes.size() == 0)
{
for (U32 i = 0; i < mStructuralBrushes.size(); i++)
exportHullToRuntime(pRuntime, mStructuralBrushes[i]);
for (U32 i = 0; i < mDetailBrushes.size(); i++)
exportHullToRuntime(pRuntime, mDetailBrushes[i]);
}
else
{
// Export the special hulls only...
for (U32 i = 0; i < mSpecialCollisionBrushes.size(); i++)
exportHullToRuntime(pRuntime, mSpecialCollisionBrushes[i]);
}
for (U32 i = 0; i< mVehicleCollisionBrushes.size(); i++)
{
exportVehicleHullToRuntime(pRuntime, mVehicleCollisionBrushes[i]);
}
exportHullBins(pRuntime);
// Export the ambient colors...
pRuntime->mBaseAmbient = mWorldEntity->mAmbientColor;
pRuntime->mAlarmAmbient = mWorldEntity->mEmergencyAmbientColor;
// Setup the point visibilities...
pRuntime->mPointVisibility.setSize(pRuntime->mPoints.size());
dMemset(pRuntime->mPointVisibility.address(), 0, pRuntime->mPointVisibility.size());
for (U32 i = 0; i < pRuntime->mSurfaces.size(); i++)
{
// Any point that is on an outside surface gets a 0xFF for right now...
for (U32 j = 0; j < pRuntime->mSurfaces[i].windingCount; j++)
{
pRuntime->mPointVisibility[pRuntime->mWindings[pRuntime->mSurfaces[i].windingStart + j]] = 0xFF;
}
}
// Setup the flags to keep the lightmaps if they have animated lights on them...
pRuntime->mLightmapKeep.setSize(pRuntime->mLightmaps.size());
for (U32 i = 0; i < pRuntime->mLightmapKeep.size(); i++)
pRuntime->mLightmapKeep[i] = false;
for (U32 i = 0; i < pRuntime->mStateData.size(); i++)
{
pRuntime->mLightmapKeep[pRuntime->mNormalLMapIndices[pRuntime->mStateData[i].surfaceIndex]] = true;
if (pRuntime->mHasAlarmState)
pRuntime->mLightmapKeep[pRuntime->mAlarmLMapIndices[pRuntime->mStateData[i].surfaceIndex]] = true;
}
return true;
}
struct PolySorted {
U32 numPoints;
S32* points;
U16 planeIndex;
};
int QSORT_CALLBACK cmpPointIndices(const void* p1, const void* p2)
{
const S32* pS321 = (const S32*)p1;
const S32* pS322 = (const S32*)p2;
return (*pS322) - (*pS321);
}
// God this sucks.
int QSORT_CALLBACK cmpPolySorted(const void* p1, const void* p2)
{
const PolySorted* pSorted1 = (const PolySorted*)p1;
const PolySorted* pSorted2 = (const PolySorted*)p2;
static S32 temp1[128];
static S32 temp2[128];
dMemcpy(temp1, pSorted1->points, sizeof(S32) * pSorted1->numPoints);
dMemcpy(temp2, pSorted2->points, sizeof(S32) * pSorted2->numPoints);
dQsort(temp1, pSorted1->numPoints, sizeof(S32), cmpPointIndices);
dQsort(temp2, pSorted2->numPoints, sizeof(S32), cmpPointIndices);
for (U32 i = 0; i < pSorted1->numPoints && i < pSorted2->numPoints; i++) {
if (temp1[i] != temp2[i])
return temp2[i] - temp1[i];
}
if (pSorted1->numPoints > pSorted2->numPoints)
return 1;
else if (pSorted2->numPoints > pSorted1->numPoints)
return -1;
else
return 0;
}
void EditGeometry::exportHullToRuntime(Interior* pRuntime, CSGBrush* brush)
{
S32** pWindingRemap = new S32*[brush->mPlanes.size()];
for (U32 i = 0; i < brush->mPlanes.size(); i++) {
pWindingRemap[i] = new S32[brush->mPlanes[i].winding.numIndices];
for (U32 j = 0; j < brush->mPlanes[i].winding.numIndices; j++)
pWindingRemap[i][j] = -1;
}
U32 numRemaps = 0;
// First, we remap the first winding
for (U32 i = 0; i < brush->mPlanes[0].winding.numIndices; i++)
pWindingRemap[0][i] = numRemaps++;
// Now, replace all of those indices in the other windings with the index of the
// remapped point
for (U32 i = 0; i < brush->mPlanes[0].winding.numIndices; i++) {
S32 remapIndex = brush->mPlanes[0].winding.indices[i];
S32 remapTo = i;
for (U32 j = 1; j < brush->mPlanes.size(); j++) {
for (U32 k = 0; k < brush->mPlanes[j].winding.numIndices; k++) {
if (brush->mPlanes[j].winding.indices[k] == remapIndex) {
pWindingRemap[j][k] = remapTo;
}
}
}
}
// Now, push the edge signature of the first winding onto our stack
Vector<U32> edgeSignatureStack;
for (U32 i = 0; i < brush->mPlanes[0].winding.numIndices; i++) {
S32 firstPoint = pWindingRemap[0][i];
S32 nextPoint = pWindingRemap[0][(i+1) % brush->mPlanes[0].winding.numIndices];
U32 edgeSignature = ((getMin(firstPoint, nextPoint) << 16) |
(getMax(firstPoint, nextPoint) << 0));
edgeSignatureStack.push_back(edgeSignature);
}
Vector<U32> polysLeft;
for (U32 i = 1; i < brush->mPlanes.size(); i++)
polysLeft.push_back(i);
U32 numFinds = 0;
while (edgeSignatureStack.empty() == false) {
// Find the winding with an edge signature that matches the first edge on the stack.
// we're guaranteed that this exists.
S32 polyIndex = -1;
for (U32 i = 0; i < polysLeft.size() && polyIndex == -1; i++) {
for (U32 j = 0; j < brush->mPlanes[polysLeft[i]].winding.numIndices && polyIndex == -1; j++) {
S32 firstPoint = pWindingRemap[polysLeft[i]][j];
S32 nextPoint = pWindingRemap[polysLeft[i]][(j+1) % brush->mPlanes[polysLeft[i]].winding.numIndices];
if (firstPoint == -1 || nextPoint == -1)
continue;
U32 edgeSignature = ((getMin(firstPoint, nextPoint) << 16) |
(getMax(firstPoint, nextPoint) << 0));
if (edgeSignature == edgeSignatureStack[0]) {
polyIndex = polysLeft[i];
polysLeft.erase(i);
break;
}
}
}
AssertFatal(polyIndex != -1, "Error, should have found that poly!");
// Ok, so now we know which poly is next, let's rotate it's winding until the edge
// we want is the first two indices in this poly
U32 warnCount = 0;
while (true) {
bool rotate = false;
S32 firstPoint = pWindingRemap[polyIndex][0];
S32 nextPoint = pWindingRemap[polyIndex][1];
if (firstPoint == -1 || nextPoint == -1) {
rotate = true;
} else {
U32 edgeSignature = ((getMin(firstPoint, nextPoint) << 16) |
(getMax(firstPoint, nextPoint) << 0));
if (edgeSignature != edgeSignatureStack[0])
rotate = true;
}
// OK, if we need to rotate, let's do that, otherwise, let's move on.
if (rotate == true) {
S32 stor = pWindingRemap[polyIndex][0];
dMemmove(&pWindingRemap[polyIndex][0], &pWindingRemap[polyIndex][1],
sizeof(S32) * (brush->mPlanes[polyIndex].winding.numIndices - 1));
pWindingRemap[polyIndex][brush->mPlanes[polyIndex].winding.numIndices - 1] = stor;
U32 storU = brush->mPlanes[polyIndex].winding.indices[0];
dMemmove(&brush->mPlanes[polyIndex].winding.indices[0],
&brush->mPlanes[polyIndex].winding.indices[1],
sizeof(U32) * (brush->mPlanes[polyIndex].winding.numIndices - 1));
brush->mPlanes[polyIndex].winding.indices[brush->mPlanes[polyIndex].winding.numIndices - 1] = storU;
} else {
break;
}
warnCount++;
AssertFatal(warnCount < 500, "Error, more rotations performed than possible");
}
// Ok. Now let's remap all the points necessary on this one
for (U32 i = 0; i < brush->mPlanes[polyIndex].winding.numIndices; i++) {
if (pWindingRemap[polyIndex][i] == -1) {
pWindingRemap[polyIndex][i] = numRemaps++;
// Now remap all the polys that contain that vertex as well (note that the
// zero poly is always fully remapped, so we can ignore it
S32 remapIndex = brush->mPlanes[polyIndex].winding.indices[i];
S32 remapTo = pWindingRemap[polyIndex][i];
for (U32 j = 1; j < brush->mPlanes.size(); j++) {
for (U32 k = 0; k < brush->mPlanes[j].winding.numIndices; k++) {
if (brush->mPlanes[j].winding.indices[k] == remapIndex) {
pWindingRemap[j][k] = remapTo;
}
}
}
}
}
// And push back the edge signatures for this winding, deleting any duplicates
for (U32 i = 0; i < brush->mPlanes[polyIndex].winding.numIndices; i++) {
S32 firstPoint = pWindingRemap[polyIndex][i];
S32 nextPoint = pWindingRemap[polyIndex][(i+1) % brush->mPlanes[polyIndex].winding.numIndices];
AssertFatal(firstPoint != -1 && nextPoint != -1, "Error, that's impossible!");
U32 edgeSignature = ((getMin(firstPoint, nextPoint) << 16) |
(getMax(firstPoint, nextPoint) << 0));
bool insert = true;
for (U32 j = 0; j < edgeSignatureStack.size(); j++) {
if (edgeSignatureStack[j] == edgeSignature) {
insert = false;
edgeSignatureStack.erase(j);
break;
}
}
if (insert == true)
edgeSignatureStack.push_back(edgeSignature);
}
numFinds++;
AssertFatal(numFinds <= brush->mPlanes.size(), avar("Error, too many damn finds (%d)!", brush->brushId));
}
// OK! At this point, none of the points should be uninserted, and everything is
// in canonical order. Let's whip through the remap arrays just to make sure. At
// the same time, we'll create the points array, which is a remap of indices in the
// remaps to the hullIndices.
Vector<U32> points;
points.setSize(numRemaps);
for (U32 i = 0; i < points.size(); i++)
points[i] = 0xFFFFFFFF;
for (U32 i = 0; i < brush->mPlanes.size(); i++) {
for (U32 j = 0; j < brush->mPlanes[i].winding.numIndices; j++) {
AssertFatal(pWindingRemap[i][j] != -1, "Error, there's an unremapped point here!");
if (points[pWindingRemap[i][j]] != 0xFFFFFFFF) {
AssertFatal(points[pWindingRemap[i][j]] == brush->mPlanes[i].winding.indices[j], "Error, bad remapping!");
} else {
points[pWindingRemap[i][j]] = brush->mPlanes[i].winding.indices[j];
}
}
}
for (U32 i = 0; i < points.size(); i++)
points[i] = exportPointToRuntime(pRuntime, points[i]);
pRuntime->mConvexHulls.increment();
pRuntime->mConvexHulls.last().hullStart = pRuntime->mHullIndices.size();
F32 minx = 1e8;
F32 miny = 1e8;
F32 minz = 1e8;
F32 maxx = -1e8;
F32 maxy = -1e8;
F32 maxz = -1e8;
for (U32 i = 0; i < points.size(); i++) {
pRuntime->mHullIndices.push_back(points[i]);
Point3F& rPoint = pRuntime->mPoints[points[i]].point;
if (rPoint.x < minx) minx = rPoint.x;
if (rPoint.x > maxx) maxx = rPoint.x;
if (rPoint.y < miny) miny = rPoint.y;
if (rPoint.y > maxy) maxy = rPoint.y;
if (rPoint.z < minz) minz = rPoint.z;
if (rPoint.z > maxz) maxz = rPoint.z;
}
pRuntime->mConvexHulls.last().hullCount = (pRuntime->mHullIndices.size() -
pRuntime->mConvexHulls.last().hullStart);
pRuntime->mConvexHulls.last().minX = minx;
pRuntime->mConvexHulls.last().maxX = maxx;
pRuntime->mConvexHulls.last().minY = miny;
pRuntime->mConvexHulls.last().maxY = maxy;
pRuntime->mConvexHulls.last().minZ = minz;
pRuntime->mConvexHulls.last().maxZ = maxz;
// We need a sorted array of poly objects in order to make these plane normals come
// out right.
PolySorted* pSortedPolys = new PolySorted[brush->mPlanes.size()];
for (U32 i = 0; i < brush->mPlanes.size(); i++) {
pSortedPolys[i].numPoints = brush->mPlanes[i].winding.numIndices;
pSortedPolys[i].points = pWindingRemap[i];
pSortedPolys[i].planeIndex = remapPlaneIndex(brush->mPlanes[i].planeEQIndex);
}
dQsort(pSortedPolys, brush->mPlanes.size(), sizeof(PolySorted), cmpPolySorted);
// Ok, now we have to construct an emit string for each vertex. This should be fairly
// straightforward, the procedure is: for each point:
// - find all polys that contain that point
// - find all points in those polys
// - find all edges " " "
// - enter the string
// The tricky bit is that we have to set up the emit indices to be relative to the
// hullindices.
for (U32 i = 0; i < points.size(); i++) {
static Vector<U32> emitPoints(128);
static Vector<U16> emitEdges(128);
static Vector<U32> emitPolys;
emitPoints.setSize(0);
emitEdges.setSize(0);
emitPolys.setSize(0);
for (U32 j = 0; j < brush->mPlanes.size(); j++) {
bool found = false;
for (U32 k = 0; k < pSortedPolys[j].numPoints; k++) {
if (pSortedPolys[j].points[k] == i) {
found = true;
break;
}
}
if (found) {
// Have to emit this poly...
for (U32 k = 0; k < pSortedPolys[j].numPoints; k++) {
emitPoints.push_back(pSortedPolys[j].points[k]);
S32 firstPoint = pSortedPolys[j].points[k];
S32 nextPoint = pSortedPolys[j].points[(k+1) % pSortedPolys[j].numPoints];
AssertFatal(firstPoint != -1 && nextPoint != -1, "Error, that's impossible!");
AssertFatal(firstPoint < 255 && nextPoint < 255, "Error, that's impossible!");
U16 edgeSignature = ((getMin(firstPoint, nextPoint) << 8) |
(getMax(firstPoint, nextPoint) << 0));
emitEdges.push_back(edgeSignature);
}
emitPolys.push_back(j);
}
}
// We also have to emit any polys that share the plane, but not necessarily the
// support point
for (U32 j = 0; j < brush->mPlanes.size(); j++) {
for (U32 k = 0; k < emitPolys.size(); k++) {
if (emitPolys[k] == j)
continue;
if (pSortedPolys[emitPolys[k]].planeIndex == pSortedPolys[j].planeIndex) {
// We may have to emit this as well...
bool found = false;
for (U32 l = 0; l < emitPolys.size(); l++) {
if (emitPolys[l] == j)
found = true;
}
if (!found) {
// Have to emit this poly...
for (U32 l = 0; l < pSortedPolys[j].numPoints; l++) {
emitPoints.push_back(pSortedPolys[j].points[l]);
S32 firstPoint = pSortedPolys[j].points[l];
S32 nextPoint = pSortedPolys[j].points[(l+1) % pSortedPolys[j].numPoints];
AssertFatal(firstPoint != -1 && nextPoint != -1, "Error, that's impossible!");
AssertFatal(firstPoint < 255 && nextPoint < 255, "Error, that's impossible!");
U16 edgeSignature = ((getMin(firstPoint, nextPoint) << 8) |
(getMax(firstPoint, nextPoint) << 0));
emitEdges.push_back(edgeSignature);
}
emitPolys.push_back(j);
}
}
}
}
AssertFatal(emitPoints.size() != 0 && emitEdges.size() != 0 && emitPolys.size() != 0,
"Error, should always emit something!");
std::sort(emitPoints.begin(), emitPoints.end());
U32* newend = std::unique(emitPoints.begin(), emitPoints.end());
emitPoints.setSize(newend - emitPoints.begin());
std::sort(emitPoints.begin(), emitPoints.end());
std::sort(emitEdges.begin(), emitEdges.end());
U16* newend16 = std::unique(emitEdges.begin(), emitEdges.end());
emitEdges.setSize(newend16 - emitEdges.begin());
std::sort(emitEdges.begin(), emitEdges.end());
// We need to transmute the edges to be relative to the points in the order of
// emission
for (U32 j = 0; j < emitEdges.size(); j++) {
U16 firstIndex = emitEdges[j] >> 8;
U16 nextIndex = emitEdges[j] & 0xFF;
U16 newFirst = 0xFFFF;
U16 newNext = 0xFFFF;
for (U32 k = 0; k < emitPoints.size(); k++) {
if (emitPoints[k] == firstIndex)
newFirst = k;
if (emitPoints[k] == nextIndex)
newNext = k;
}
AssertFatal(newFirst != 0xFFFF && newNext != 0xFFFF, "Error, bad edge!");
U16 newSignature = ((getMin(newFirst, newNext) << 8) |
(getMax(newFirst, newNext) << 0));
emitEdges[j] = newSignature;
}
// Emit string length is:
// 1 + NumPoints +
// 1 + (NumEdges * 2) +
// 1 + Sum_polys(1 + 1 + numVerts(poly))
U32 emitStringLen = 0;
emitStringLen += 1 + emitPoints.size();
emitStringLen += 1 + (emitEdges.size() * 2);
emitStringLen += 1;
for (U32 j = 0; j < emitPolys.size(); j++)
emitStringLen += 1 + 1 + pSortedPolys[emitPolys[j]].numPoints;
U8* emitString = new U8[emitStringLen];
U32 currPos = 0;
dMemset(emitString, 0, emitStringLen);
// First, dump the points
emitString[currPos++] = emitPoints.size();
for (U32 j = 0; j < emitPoints.size(); j++)
emitString[currPos++] = emitPoints[j];
// Now the edges
emitString[currPos++] = emitEdges.size();
for (U32 j = 0; j < emitEdges.size(); j++) {
U16 edgeP1 = emitEdges[j] >> 8;
U16 edgeP2 = emitEdges[j] & 0xFF;
emitString[currPos++] = edgeP1;
emitString[currPos++] = edgeP2;
}
// And the polys.
emitString[currPos++] = emitPolys.size();
for (U32 j = 0; j < emitPolys.size(); j++) {
// A poly is:
// vertex count
// plane index
// verts (relative)
emitString[currPos++] = pSortedPolys[emitPolys[j]].numPoints;
emitString[currPos++] = emitPolys[j];
for (U32 k = 0; k < pSortedPolys[emitPolys[j]].numPoints; k++) {
bool found = false;
for (U32 l = 0; l < emitPoints.size(); l++) {
if (emitPoints[l] == pSortedPolys[emitPolys[j]].points[k]) {
emitString[currPos++] = l;
found = true;
break;
}
}
AssertFatal(found, "Error, missed a point!");
}
}
AssertFatal(currPos == emitStringLen, "Error, miscalculation of string len!");
U32 emitStringExportedIndex = exportEmitStringToRuntime(pRuntime, emitString, emitStringLen);
pRuntime->mHullEmitStringIndices.push_back(emitStringExportedIndex);
AssertFatal(dMemcmp(&pRuntime->mConvexHullEmitStrings[emitStringExportedIndex], emitString, emitStringLen) == 0, "Bad exported string!");
delete [] emitString;
}
AssertFatal(pRuntime->mHullEmitStringIndices.size() == pRuntime->mHullIndices.size(), "Point/emitstring mismatch!");
pRuntime->mConvexHulls.last().planeStart = pRuntime->mHullPlaneIndices.size();
for (U32 i = 0; i < brush->mPlanes.size(); i++)
pRuntime->mHullPlaneIndices.push_back(pSortedPolys[i].planeIndex);
for (U32 i = 0; i < brush->mPlanes.size(); i++)
delete [] pWindingRemap[i];
delete [] pWindingRemap;
delete [] pSortedPolys;
// Now dump out the surfaces that were created by this hull that are still in
// the interior...
pRuntime->mConvexHulls.last().surfaceStart = pRuntime->mHullSurfaceIndices.size();
for (U32 i = 0; i < mSurfaces.size(); i++) {
if (mSurfaces[i].winding.brushId == brush->brushId) {
// Dump this surface
pRuntime->mHullSurfaceIndices.push_back(i);
}
}
for (U32 i = 0; i < mNullSurfaces.size(); i++) {
if (mNullSurfaces[i].winding.brushId == brush->brushId) {
// Dump this surface
pRuntime->mHullSurfaceIndices.push_back(makeNullSurfaceIndex(i));
}
}
pRuntime->mConvexHulls.last().surfaceCount = (pRuntime->mHullSurfaceIndices.size() -
pRuntime->mConvexHulls.last().surfaceStart);
}
void EditGeometry::exportVehicleHullToRuntime(Interior* pRuntime, CSGBrush* brush)
{
S32** pWindingRemap = new S32*[brush->mPlanes.size()];
for (U32 i = 0; i < brush->mPlanes.size(); i++) {
pWindingRemap[i] = new S32[brush->mPlanes[i].winding.numIndices];
for (U32 j = 0; j < brush->mPlanes[i].winding.numIndices; j++)
pWindingRemap[i][j] = -1;
}
U32 numRemaps = 0;
// First, we remap the first winding
for (U32 i = 0; i < brush->mPlanes[0].winding.numIndices; i++)
pWindingRemap[0][i] = numRemaps++;
// Now, replace all of those indices in the other windings with the index of the
// remapped point
for (U32 i = 0; i < brush->mPlanes[0].winding.numIndices; i++) {
S32 remapIndex = brush->mPlanes[0].winding.indices[i];
S32 remapTo = i;
for (U32 j = 1; j < brush->mPlanes.size(); j++) {
for (U32 k = 0; k < brush->mPlanes[j].winding.numIndices; k++) {
if (brush->mPlanes[j].winding.indices[k] == remapIndex) {
pWindingRemap[j][k] = remapTo;
}
}
}
}
// Now, push the edge signature of the first winding onto our stack
Vector<U32> edgeSignatureStack;
for (U32 i = 0; i < brush->mPlanes[0].winding.numIndices; i++) {
S32 firstPoint = pWindingRemap[0][i];
S32 nextPoint = pWindingRemap[0][(i+1) % brush->mPlanes[0].winding.numIndices];
U32 edgeSignature = (getMin(firstPoint, nextPoint) << 16) |
(getMax(firstPoint, nextPoint) << 0);
edgeSignatureStack.push_back(edgeSignature);
}
Vector<U32> polysLeft;
for (U32 i = 1; i < brush->mPlanes.size(); i++)
polysLeft.push_back(i);
U32 numFinds = 0;
while (edgeSignatureStack.empty() == false) {
// Find the winding with an edge signature that matches the first edge on the stack.
// we're guaranteed that this exists.
S32 polyIndex = -1;
for (U32 i = 0; i < polysLeft.size() && polyIndex == -1; i++) {
for (U32 j = 0; j < brush->mPlanes[polysLeft[i]].winding.numIndices && polyIndex == -1; j++) {
S32 firstPoint = pWindingRemap[polysLeft[i]][j];
S32 nextPoint = pWindingRemap[polysLeft[i]][(j+1) % brush->mPlanes[polysLeft[i]].winding.numIndices];
if (firstPoint == -1 || nextPoint == -1)
continue;
U32 edgeSignature = (getMin(firstPoint, nextPoint) << 16) |
(getMax(firstPoint, nextPoint) << 0);
if (edgeSignature == edgeSignatureStack[0]) {
polyIndex = polysLeft[i];
polysLeft.erase(i);
break;
}
}
}
AssertFatal(polyIndex != -1, "Error, should have found that poly!");
// Ok, so now we know which poly is next, let's rotate it's winding until the edge
// we want is the first two indices in this poly
U32 warnCount = 0;
while (true) {
bool rotate = false;
S32 firstPoint = pWindingRemap[polyIndex][0];
S32 nextPoint = pWindingRemap[polyIndex][1];
if (firstPoint == -1 || nextPoint == -1) {
rotate = true;
} else {
U32 edgeSignature = (getMin(firstPoint, nextPoint) << 16) |
(getMax(firstPoint, nextPoint) << 0);
if (edgeSignature != edgeSignatureStack[0])
rotate = true;
}
// OK, if we need to rotate, let's do that, otherwise, let's move on.
if (rotate == true) {
S32 stor = pWindingRemap[polyIndex][0];
dMemmove(&pWindingRemap[polyIndex][0], &pWindingRemap[polyIndex][1],
sizeof(S32) * (brush->mPlanes[polyIndex].winding.numIndices - 1));
pWindingRemap[polyIndex][brush->mPlanes[polyIndex].winding.numIndices - 1] = stor;
U32 storU = brush->mPlanes[polyIndex].winding.indices[0];
dMemmove(&brush->mPlanes[polyIndex].winding.indices[0],
&brush->mPlanes[polyIndex].winding.indices[1],
sizeof(U32) * (brush->mPlanes[polyIndex].winding.numIndices - 1));
brush->mPlanes[polyIndex].winding.indices[brush->mPlanes[polyIndex].winding.numIndices - 1] = storU;
} else {
break;
}
warnCount++;
AssertFatal(warnCount < 500, "Error, more rotations performed than possible");
}
// Ok. Now let's remap all the points necessary on this one
for (U32 i = 0; i < brush->mPlanes[polyIndex].winding.numIndices; i++) {
if (pWindingRemap[polyIndex][i] == -1) {
pWindingRemap[polyIndex][i] = numRemaps++;
// Now remap all the polys that contain that vertex as well (note that the
// zero poly is always fully remapped, so we can ignore it
S32 remapIndex = brush->mPlanes[polyIndex].winding.indices[i];
S32 remapTo = pWindingRemap[polyIndex][i];
for (U32 j = 1; j < brush->mPlanes.size(); j++) {
for (U32 k = 0; k < brush->mPlanes[j].winding.numIndices; k++) {
if (brush->mPlanes[j].winding.indices[k] == remapIndex) {
pWindingRemap[j][k] = remapTo;
}
}
}
}
}
// And push back the edge signatures for this winding, deleting any duplicates
for (U32 i = 0; i < brush->mPlanes[polyIndex].winding.numIndices; i++) {
S32 firstPoint = pWindingRemap[polyIndex][i];
S32 nextPoint = pWindingRemap[polyIndex][(i+1) % brush->mPlanes[polyIndex].winding.numIndices];
AssertFatal(firstPoint != -1 && nextPoint != -1, "Error, that's impossible!");
U32 edgeSignature = (getMin(firstPoint, nextPoint) << 16) |
(getMax(firstPoint, nextPoint) << 0);
bool insert = true;
for (U32 j = 0; j < edgeSignatureStack.size(); j++) {
if (edgeSignatureStack[j] == edgeSignature) {
insert = false;
edgeSignatureStack.erase(j);
break;
}
}
if (insert == true)
edgeSignatureStack.push_back(edgeSignature);
}
numFinds++;
AssertFatal(numFinds <= brush->mPlanes.size(), avar("Error, too many damn finds (%d)!", brush->brushId));
}
// OK! At this point, none of the points should be uninserted, and everything is
// in canonical order. Let's whip through the remap arrays just to make sure. At
// the same time, we'll create the points array, which is a remap of indices in the
// remaps to the hullIndices.
Vector<U32> points;
points.setSize(numRemaps);
for (U32 i = 0; i < points.size(); i++)
points[i] = 0xFFFFFFFF;
for (U32 i = 0; i < brush->mPlanes.size(); i++) {
for (U32 j = 0; j < brush->mPlanes[i].winding.numIndices; j++) {
AssertFatal(pWindingRemap[i][j] != -1, "Error, there's an unremapped point here!");
if (points[pWindingRemap[i][j]] != 0xFFFFFFFF) {
AssertFatal(points[pWindingRemap[i][j]] == brush->mPlanes[i].winding.indices[j], "Error, bad remapping!");
} else {
points[pWindingRemap[i][j]] = brush->mPlanes[i].winding.indices[j];
}
}
}
for (U32 i = 0; i < points.size(); i++)
points[i] = exportVehiclePointToRuntime(pRuntime, points[i]);
pRuntime->mVehicleConvexHulls.increment();
pRuntime->mVehicleConvexHulls.last().hullStart = pRuntime->mVehicleHullIndices.size();
F32 minx = 1e8;
F32 miny = 1e8;
F32 minz = 1e8;
F32 maxx = -1e8;
F32 maxy = -1e8;
F32 maxz = -1e8;
for (U32 i = 0; i < points.size(); i++) {
pRuntime->mVehicleHullIndices.push_back(points[i]);
Point3F& rPoint = pRuntime->mVehiclePoints[points[i]].point;
if (rPoint.x < minx) minx = rPoint.x;
if (rPoint.x > maxx) maxx = rPoint.x;
if (rPoint.y < miny) miny = rPoint.y;
if (rPoint.y > maxy) maxy = rPoint.y;
if (rPoint.z < minz) minz = rPoint.z;
if (rPoint.z > maxz) maxz = rPoint.z;
}
pRuntime->mVehicleConvexHulls.last().hullCount = (pRuntime->mVehicleHullIndices.size() -
pRuntime->mVehicleConvexHulls.last().hullStart);
pRuntime->mVehicleConvexHulls.last().minX = minx;
pRuntime->mVehicleConvexHulls.last().maxX = maxx;
pRuntime->mVehicleConvexHulls.last().minY = miny;
pRuntime->mVehicleConvexHulls.last().maxY = maxy;
pRuntime->mVehicleConvexHulls.last().minZ = minz;
pRuntime->mVehicleConvexHulls.last().maxZ = maxz;
// We need a sorted array of poly objects in order to make these plane normals come
// out right.
PolySorted* pSortedPolys = new PolySorted[brush->mPlanes.size()];
for (U32 i = 0; i < brush->mPlanes.size(); i++) {
pSortedPolys[i].numPoints = brush->mPlanes[i].winding.numIndices;
pSortedPolys[i].points = pWindingRemap[i];
pSortedPolys[i].planeIndex = remapVehiclePlaneIndex(brush->mPlanes[i].planeEQIndex, pRuntime);
}
dQsort(pSortedPolys, brush->mPlanes.size(), sizeof(PolySorted), cmpPolySorted);
// Ok, now we have to construct an emit string for each vertex. This should be fairly
// straightforward, the procedure is: for each point:
// - find all polys that contain that point
// - find all points in those polys
// - find all edges " " "
// - enter the string
// The tricky bit is that we have to set up the emit indices to be relative to the
// hullindices.
for (U32 i = 0; i < points.size(); i++) {
static Vector<U32> emitPoints(128);
static Vector<U16> emitEdges(128);
static Vector<U32> emitPolys;
emitPoints.setSize(0);
emitEdges.setSize(0);
emitPolys.setSize(0);
for (U32 j = 0; j < brush->mPlanes.size(); j++) {
bool found = false;
for (U32 k = 0; k < pSortedPolys[j].numPoints; k++) {
if (pSortedPolys[j].points[k] == i) {
found = true;
break;
}
}
if (found) {
// Have to emit this poly...
for (U32 k = 0; k < pSortedPolys[j].numPoints; k++) {
emitPoints.push_back(pSortedPolys[j].points[k]);
S32 firstPoint = pSortedPolys[j].points[k];
S32 nextPoint = pSortedPolys[j].points[(k+1) % pSortedPolys[j].numPoints];
AssertFatal(firstPoint != -1 && nextPoint != -1, "Error, that's impossible!");
AssertFatal(firstPoint < 255 && nextPoint < 255, "Error, that's impossible!");
U16 edgeSignature = (getMin(firstPoint, nextPoint) << 8) |
(getMax(firstPoint, nextPoint) << 0);
emitEdges.push_back(edgeSignature);
}
emitPolys.push_back(j);
}
}
// We also have to emit any polys that share the plane, but not necessarily the
// support point
for (U32 j = 0; j < brush->mPlanes.size(); j++) {
for (U32 k = 0; k < emitPolys.size(); k++) {
if (emitPolys[k] == j)
continue;
if (pSortedPolys[emitPolys[k]].planeIndex == pSortedPolys[j].planeIndex) {
// We may have to emit this as well...
bool found = false;
for (U32 l = 0; l < emitPolys.size(); l++) {
if (emitPolys[l] == j)
found = true;
}
if (!found) {
// Have to emit this poly...
for (U32 l = 0; l < pSortedPolys[j].numPoints; l++) {
emitPoints.push_back(pSortedPolys[j].points[l]);
S32 firstPoint = pSortedPolys[j].points[l];
S32 nextPoint = pSortedPolys[j].points[(l+1) % pSortedPolys[j].numPoints];
AssertFatal(firstPoint != -1 && nextPoint != -1, "Error, that's impossible!");
AssertFatal(firstPoint < 255 && nextPoint < 255, "Error, that's impossible!");
U16 edgeSignature = (getMin(firstPoint, nextPoint) << 8) |
(getMax(firstPoint, nextPoint) << 0);
emitEdges.push_back(edgeSignature);
}
emitPolys.push_back(j);
}
}
}
}
AssertFatal(emitPoints.size() != 0 && emitEdges.size() != 0 && emitPolys.size() != 0,
"Error, should always emit something!");
std::sort(emitPoints.begin(), emitPoints.end());
U32* newend = std::unique(emitPoints.begin(), emitPoints.end());
emitPoints.setSize(newend - emitPoints.begin());
std::sort(emitPoints.begin(), emitPoints.end());
std::sort(emitEdges.begin(), emitEdges.end());
U16* newend16 = std::unique(emitEdges.begin(), emitEdges.end());
emitEdges.setSize(newend16 - emitEdges.begin());
std::sort(emitEdges.begin(), emitEdges.end());
// We need to transmute the edges to be relative to the points in the order of
// emission
for (U32 j = 0; j < emitEdges.size(); j++) {
U16 firstIndex = emitEdges[j] >> 8;
U16 nextIndex = emitEdges[j] & 0xFF;
U16 newFirst = 0xFFFF;
U16 newNext = 0xFFFF;
for (U32 k = 0; k < emitPoints.size(); k++) {
if (emitPoints[k] == firstIndex)
newFirst = k;
if (emitPoints[k] == nextIndex)
newNext = k;
}
AssertFatal(newFirst != 0xFFFF && newNext != 0xFFFF, "Error, bad edge!");
U16 newSignature = (getMin(newFirst, newNext) << 8) |
(getMax(newFirst, newNext) << 0);
emitEdges[j] = newSignature;
}
// Emit string length is:
// 1 + NumPoints +
// 1 + (NumEdges * 2) +
// 1 + Sum_polys(1 + 1 + numVerts(poly))
U32 emitStringLen = 0;
emitStringLen += 1 + emitPoints.size();
emitStringLen += 1 + (emitEdges.size() * 2);
emitStringLen += 1;
for (U32 j = 0; j < emitPolys.size(); j++)
emitStringLen += 1 + 1 + pSortedPolys[emitPolys[j]].numPoints;
U8* emitString = new U8[emitStringLen];
U32 currPos = 0;
dMemset(emitString, 0, emitStringLen);
// First, dump the points
emitString[currPos++] = emitPoints.size();
for (U32 j = 0; j < emitPoints.size(); j++)
emitString[currPos++] = emitPoints[j];
// Now the edges
emitString[currPos++] = emitEdges.size();
for (U32 j = 0; j < emitEdges.size(); j++) {
U16 edgeP1 = emitEdges[j] >> 8;
U16 edgeP2 = emitEdges[j] & 0xFF;
emitString[currPos++] = edgeP1;
emitString[currPos++] = edgeP2;
}
// And the polys.
emitString[currPos++] = emitPolys.size();
for (U32 j = 0; j < emitPolys.size(); j++) {
// A poly is:
// vertex count
// plane index
// verts (relative)
emitString[currPos++] = pSortedPolys[emitPolys[j]].numPoints;
emitString[currPos++] = emitPolys[j];
for (U32 k = 0; k < pSortedPolys[emitPolys[j]].numPoints; k++) {
bool found = false;
for (U32 l = 0; l < emitPoints.size(); l++) {
if (emitPoints[l] == pSortedPolys[emitPolys[j]].points[k]) {
emitString[currPos++] = l;
found = true;
break;
}
}
AssertFatal(found, "Error, missed a point!");
}
}
AssertFatal(currPos == emitStringLen, "Error, miscalculation of string len!");
U32 emitStringExportedIndex = exportVehicleEmitStringToRuntime(pRuntime, emitString, emitStringLen);
pRuntime->mVehicleHullEmitStringIndices.push_back(emitStringExportedIndex);
AssertFatal(dMemcmp(&pRuntime->mVehicleConvexHullEmitStrings[emitStringExportedIndex], emitString, emitStringLen) == 0, "Bad exported string!");
delete [] emitString;
}
AssertFatal(pRuntime->mVehicleHullEmitStringIndices.size() == pRuntime->mVehicleHullIndices.size(),
"Point/emitstring mismatch!");
pRuntime->mVehicleConvexHulls.last().planeStart = pRuntime->mVehicleHullPlaneIndices.size();
for (U32 i = 0; i < brush->mPlanes.size(); i++)
pRuntime->mVehicleHullPlaneIndices.push_back(pSortedPolys[i].planeIndex);
for (U32 i = 0; i < brush->mPlanes.size(); i++)
delete [] pWindingRemap[i];
delete [] pWindingRemap;
delete [] pSortedPolys;
// Now dump out the surfaces that were created by this hull that are still in
// the interior...
pRuntime->mVehicleConvexHulls.last().surfaceStart = pRuntime->mVehicleHullSurfaceIndices.size();
for (U32 i = 0; i < mVehicleNullSurfaces.size(); i++) {
if (mVehicleNullSurfaces[i].winding.brushId == brush->brushId) {
// Dump this surface
pRuntime->mVehicleHullSurfaceIndices.push_back(makeVehicleNullSurfaceIndex(i));
}
}
pRuntime->mVehicleConvexHulls.last().surfaceCount = (pRuntime->mVehicleHullSurfaceIndices.size() -
pRuntime->mVehicleConvexHulls.last().surfaceStart);
}
void EditGeometry::exportHullBins(Interior* pRuntime)
{
pRuntime->mCoordBinMode = Interior::BinsXY;
for (U32 i = 0; i < Interior::NumCoordBins; i++) {
F32 minX = pRuntime->mBoundingBox.min.x;
F32 maxX = pRuntime->mBoundingBox.min.x;
minX += F32(i) * (pRuntime->mBoundingBox.len_x() / F32(Interior::NumCoordBins));
maxX += F32(i+1) * (pRuntime->mBoundingBox.len_x() / F32(Interior::NumCoordBins));
for (U32 j = 0; j < Interior::NumCoordBins; j++) {
F32 minY = pRuntime->mBoundingBox.min.y;
F32 maxY = pRuntime->mBoundingBox.min.y;
minY += F32(j) * (pRuntime->mBoundingBox.len_y() / F32(Interior::NumCoordBins));
maxY += F32(j+1) * (pRuntime->mBoundingBox.len_y() / F32(Interior::NumCoordBins));
// This isn't really the fastest way to do this, but it is convenient...
RectF binRect(Point2F(minX, minY), Point2F(maxX - minX, maxY - minY));
AssertFatal(binRect.isValidRect(), "Error, invalid rect for bin!");
U32 binIndex = (i * Interior::NumCoordBins) + j;
pRuntime->mCoordBins[binIndex].binStart = pRuntime->mCoordBinIndices.size();
for (U32 k = 0; k < pRuntime->mConvexHulls.size(); k++) {
const Interior::ConvexHull& rHull = pRuntime->mConvexHulls[k];
RectF hullRect(Point2F(rHull.minX, rHull.minY), Point2F(rHull.maxX - rHull.minX, rHull.maxY - rHull.minY));
AssertFatal(hullRect.isValidRect(), "Error, invalid rect for bin!");
if (binRect.overlaps(hullRect)) {
// Index k goes into bin [i][j]...
pRuntime->mCoordBinIndices.push_back(k);
}
}
pRuntime->mCoordBins[binIndex].binCount = (pRuntime->mCoordBinIndices.size() -
pRuntime->mCoordBins[binIndex].binStart);
}
}
}