2395 lines
93 KiB
C++
Executable File
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);
|
|
}
|
|
}
|
|
}
|