2222 lines
77 KiB
C++
Executable File
2222 lines
77 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "interior/interior.h"
|
|
#include "sceneGraph/sceneState.h"
|
|
#include "sceneGraph/sceneGraph.h"
|
|
|
|
#include "dgl/dgl.h"
|
|
#include "dgl/gBitmap.h"
|
|
#include "dgl/gTexManager.h"
|
|
#include "math/mMatrix.h"
|
|
#include "math/mRect.h"
|
|
#include "dgl/materialList.h"
|
|
#include "dgl/materialPropertyMap.h"
|
|
#include "interior/interiorSubObject.h"
|
|
#include "interior/fogCalc.h"
|
|
#include "core/bitVector.h"
|
|
#include "core/frameAllocator.h"
|
|
#include "sceneGraph/sgUtil.h"
|
|
#include "platform/profiler.h"
|
|
|
|
U32 Interior::smRenderMode = 0;
|
|
bool Interior::smFocusedDebug = false;
|
|
bool Interior::smRenderEnvironmentMaps = true;
|
|
bool Interior::smUseVertexLighting = false;
|
|
bool Interior::smUseTexturedFog = false;
|
|
bool Interior::smLockArrays = true;
|
|
bool Interior::smLightingCastRays = false;
|
|
|
|
// These are setup by setupActivePolyList
|
|
U16* sgActivePolyList = NULL;
|
|
U32 sgActivePolyListSize = 0;
|
|
U16* sgEnvironPolyList = NULL;
|
|
U32 sgEnvironPolyListSize = 0;
|
|
U16* sgFogPolyList = NULL;
|
|
U32 sgFogPolyListSize = 0;
|
|
bool sgFogActive = false;
|
|
|
|
// Always the same size as the mPoints array
|
|
Point2F* sgFogTexCoords = NULL;
|
|
|
|
class PlaneRange
|
|
{
|
|
public:
|
|
U32 start;
|
|
U32 count;
|
|
};
|
|
|
|
namespace {
|
|
|
|
struct PortalRenderInfo
|
|
{
|
|
bool render;
|
|
|
|
F64 frustum[4];
|
|
RectI viewport;
|
|
};
|
|
|
|
//-------------------------------------- Rendering state variables.
|
|
Point3F sgCamPoint;
|
|
F64 sgStoredFrustum[6];
|
|
RectI sgStoredViewport;
|
|
|
|
Vector<PortalRenderInfo> sgZoneRenderInfo(__FILE__, __LINE__);
|
|
|
|
// Takes OS coords to clip space...
|
|
MatrixF sgWSToOSMatrix;
|
|
MatrixF sgProjMatrix;
|
|
|
|
PlaneF sgOSPlaneFar;
|
|
PlaneF sgOSPlaneXMin;
|
|
PlaneF sgOSPlaneXMax;
|
|
PlaneF sgOSPlaneYMin;
|
|
PlaneF sgOSPlaneYMax;
|
|
|
|
struct ZoneRect {
|
|
RectD rect;
|
|
bool active;
|
|
};
|
|
|
|
Vector<ZoneRect> sgZoneRects(__FILE__, __LINE__);
|
|
|
|
//-------------------------------------- Little utility functions
|
|
RectD outlineRects(const Vector<RectD>& rects)
|
|
{
|
|
F64 minx = 1e10;
|
|
F64 maxx = -1e10;
|
|
F64 miny = 1e10;
|
|
F64 maxy = -1e10;
|
|
|
|
for (U32 i = 0; i < rects.size(); i++) {
|
|
if (rects[i].point.x < minx)
|
|
minx = rects[i].point.x;
|
|
if (rects[i].point.y < miny)
|
|
miny = rects[i].point.y;
|
|
|
|
if (rects[i].point.x + rects[i].extent.x > maxx)
|
|
maxx = rects[i].point.x + rects[i].extent.x;
|
|
if (rects[i].point.y + rects[i].extent.y > maxy)
|
|
maxy = rects[i].point.y + rects[i].extent.y;
|
|
}
|
|
|
|
return RectD(minx, miny, maxx - minx, maxy - miny);
|
|
}
|
|
|
|
void insertZoneRects(ZoneRect& rZoneRect, const RectD* rects, const U32 numRects)
|
|
{
|
|
F64 minx = 1e10;
|
|
F64 maxx = -1e10;
|
|
F64 miny = 1e10;
|
|
F64 maxy = -1e10;
|
|
|
|
for (U32 i = 0; i < numRects; i++) {
|
|
if (rects[i].point.x < minx)
|
|
minx = rects[i].point.x;
|
|
if (rects[i].point.y < miny)
|
|
miny = rects[i].point.y;
|
|
|
|
if (rects[i].point.x + rects[i].extent.x > maxx)
|
|
maxx = rects[i].point.x + rects[i].extent.x;
|
|
if (rects[i].point.y + rects[i].extent.y > maxy)
|
|
maxy = rects[i].point.y + rects[i].extent.y;
|
|
}
|
|
|
|
if (rZoneRect.active == false && numRects != 0) {
|
|
rZoneRect.rect = RectD(minx, miny, maxx - minx, maxy - miny);
|
|
rZoneRect.active = true;
|
|
} else {
|
|
if (rZoneRect.rect.point.x < minx)
|
|
minx = rZoneRect.rect.point.x;
|
|
if (rZoneRect.rect.point.y < miny)
|
|
miny = rZoneRect.rect.point.y;
|
|
|
|
if (rZoneRect.rect.point.x + rZoneRect.rect.extent.x > maxx)
|
|
maxx = rZoneRect.rect.point.x + rZoneRect.rect.extent.x;
|
|
if (rZoneRect.rect.point.y + rZoneRect.rect.extent.y > maxy)
|
|
maxy = rZoneRect.rect.point.y + rZoneRect.rect.extent.y;
|
|
|
|
rZoneRect.rect = RectD(minx, miny, maxx - minx, maxy - miny);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void fixupViewport(PortalRenderInfo& rInfo)
|
|
{
|
|
F64 widthV = rInfo.frustum[1] - rInfo.frustum[0];
|
|
F64 heightV = rInfo.frustum[3] - rInfo.frustum[2];
|
|
|
|
F64 fx0 = (rInfo.frustum[0] - sgStoredFrustum[0]) / (sgStoredFrustum[1] - sgStoredFrustum[0]);
|
|
F64 fx1 = (sgStoredFrustum[1] - rInfo.frustum[1]) / (sgStoredFrustum[1] - sgStoredFrustum[0]);
|
|
|
|
F64 dV0 = F64(sgStoredViewport.point.x) + fx0 * F64(sgStoredViewport.extent.x);
|
|
F64 dV1 = F64(sgStoredViewport.point.x +
|
|
sgStoredViewport.extent.x) - fx1 * F64(sgStoredViewport.extent.x);
|
|
|
|
F64 fdV0 = getMax(mFloorD(dV0), F64(sgStoredViewport.point.x));
|
|
F64 cdV1 = getMin(mCeilD(dV1), F64(sgStoredViewport.point.x + sgStoredViewport.extent.x));
|
|
|
|
// If the width is 1 pixel, we need to widen it up a bit...
|
|
if ((cdV1 - fdV0) <= 1.0)
|
|
cdV1 = fdV0 + 1;
|
|
AssertFatal((fdV0 >= sgStoredViewport.point.x &&
|
|
cdV1 <= sgStoredViewport.point.x + sgStoredViewport.extent.x),
|
|
"Out of bounds viewport bounds");
|
|
|
|
F64 new0 = rInfo.frustum[0] - ((dV0 - fdV0) * (widthV / F64(sgStoredViewport.extent.x)));
|
|
F64 new1 = rInfo.frustum[1] + ((cdV1 - dV1) * (widthV / F64(sgStoredViewport.extent.x)));
|
|
|
|
rInfo.frustum[0] = new0;
|
|
rInfo.frustum[1] = new1;
|
|
|
|
rInfo.viewport.point.x = S32(fdV0);
|
|
rInfo.viewport.extent.x = S32(cdV1) - rInfo.viewport.point.x;
|
|
|
|
F64 fy0 = (sgStoredFrustum[3] - rInfo.frustum[3]) / (sgStoredFrustum[3] - sgStoredFrustum[2]);
|
|
F64 fy1 = (rInfo.frustum[2] - sgStoredFrustum[2]) / (sgStoredFrustum[3] - sgStoredFrustum[2]);
|
|
|
|
dV0 = F64(sgStoredViewport.point.y) + fy0 * F64(sgStoredViewport.extent.y);
|
|
dV1 = F64(sgStoredViewport.point.y +
|
|
sgStoredViewport.extent.y) - fy1 * F64(sgStoredViewport.extent.y);
|
|
fdV0 = getMax(mFloorD(dV0), F64(sgStoredViewport.point.y));
|
|
cdV1 = getMin(mCeilD(dV1), F64(sgStoredViewport.point.y + sgStoredViewport.extent.y));
|
|
|
|
// If the width is 1 pixel, we need to widen it up a bit...
|
|
if ((cdV1 - fdV0) <= 1.0)
|
|
cdV1 = fdV0 + 1;
|
|
AssertFatal((fdV0 >= sgStoredViewport.point.y &&
|
|
cdV1 <= sgStoredViewport.point.y + sgStoredViewport.extent.y),
|
|
"Out of bounds viewport bounds");
|
|
|
|
new0 = rInfo.frustum[2] - ((cdV1 - dV1) * (heightV / F64(sgStoredViewport.extent.y)));
|
|
new1 = rInfo.frustum[3] + ((dV0 - fdV0) * (heightV / F64(sgStoredViewport.extent.y)));
|
|
rInfo.frustum[2] = new0;
|
|
rInfo.frustum[3] = new1;
|
|
|
|
rInfo.viewport.point.y = S32(fdV0);
|
|
rInfo.viewport.extent.y = S32(cdV1) - rInfo.viewport.point.y;
|
|
}
|
|
|
|
RectD convertToRectD(const F64 inResult[4])
|
|
{
|
|
F64 minx = ((inResult[0] + 1.0f) / 2.0f) * (sgStoredFrustum[1] - sgStoredFrustum[0]) + sgStoredFrustum[0];
|
|
F64 maxx = ((inResult[2] + 1.0f) / 2.0f) * (sgStoredFrustum[1] - sgStoredFrustum[0]) + sgStoredFrustum[0];
|
|
|
|
F64 miny = ((inResult[1] + 1.0f) / 2.0f) * (sgStoredFrustum[3] - sgStoredFrustum[2]) + sgStoredFrustum[2];
|
|
F64 maxy = ((inResult[3] + 1.0f) / 2.0f) * (sgStoredFrustum[3] - sgStoredFrustum[2]) + sgStoredFrustum[2];
|
|
|
|
return RectD(minx, miny, (maxx - minx), (maxy - miny));
|
|
}
|
|
|
|
void convertToFrustum(PortalRenderInfo& zrInfo, const RectD& finalRect)
|
|
{
|
|
zrInfo.frustum[0] = finalRect.point.x; // left
|
|
zrInfo.frustum[1] = finalRect.point.x + finalRect.extent.x; // right
|
|
zrInfo.frustum[2] = finalRect.point.y; // bottom
|
|
zrInfo.frustum[3] = finalRect.point.y + finalRect.extent.y; // top
|
|
|
|
fixupViewport(zrInfo);
|
|
}
|
|
|
|
} // namespace {}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
//-------------------------------------- IMPLEMENTATION
|
|
//
|
|
Interior::Interior()
|
|
{
|
|
mMaterialList = NULL;
|
|
mWhite = NULL;
|
|
mWhiteRGB = NULL;
|
|
mLightFalloff = NULL;
|
|
|
|
// By default, no alarm state, no animated light states
|
|
mHasAlarmState = false;
|
|
|
|
mNumLightStateEntries = 0;
|
|
mNumTriggerableLights = 0;
|
|
|
|
mLMHandle = LM_HANDLE(-1);
|
|
mLightMapBorderSize = 0;
|
|
|
|
mPreppedForRender = false;;
|
|
|
|
mSearchTag = 0;
|
|
|
|
mLightMapBorderSize = 0;
|
|
|
|
// Bind our vectors
|
|
VECTOR_SET_ASSOCIATION(mPlanes);
|
|
VECTOR_SET_ASSOCIATION(mPoints);
|
|
VECTOR_SET_ASSOCIATION(mBSPNodes);
|
|
VECTOR_SET_ASSOCIATION(mBSPSolidLeaves);
|
|
VECTOR_SET_ASSOCIATION(mEnvironMaps);
|
|
VECTOR_SET_ASSOCIATION(mEnvironFactors);
|
|
VECTOR_SET_ASSOCIATION(mWindings);
|
|
VECTOR_SET_ASSOCIATION(mTexGenEQs);
|
|
VECTOR_SET_ASSOCIATION(mLMTexGenEQs);
|
|
VECTOR_SET_ASSOCIATION(mWindingIndices);
|
|
VECTOR_SET_ASSOCIATION(mSurfaces);
|
|
VECTOR_SET_ASSOCIATION(mNullSurfaces);
|
|
VECTOR_SET_ASSOCIATION(mSolidLeafSurfaces);
|
|
VECTOR_SET_ASSOCIATION(mZones);
|
|
VECTOR_SET_ASSOCIATION(mZonePlanes);
|
|
VECTOR_SET_ASSOCIATION(mZoneSurfaces);
|
|
VECTOR_SET_ASSOCIATION(mZonePortalList);
|
|
VECTOR_SET_ASSOCIATION(mPortals);
|
|
VECTOR_SET_ASSOCIATION(mSubObjects);
|
|
VECTOR_SET_ASSOCIATION(mLightmaps);
|
|
VECTOR_SET_ASSOCIATION(mLightmapKeep);
|
|
VECTOR_SET_ASSOCIATION(mNormalLMapIndices);
|
|
VECTOR_SET_ASSOCIATION(mAlarmLMapIndices);
|
|
VECTOR_SET_ASSOCIATION(mAnimatedLights);
|
|
VECTOR_SET_ASSOCIATION(mLightStates);
|
|
VECTOR_SET_ASSOCIATION(mStateData);
|
|
VECTOR_SET_ASSOCIATION(mStateDataBuffer);
|
|
VECTOR_SET_ASSOCIATION(mNameBuffer);
|
|
VECTOR_SET_ASSOCIATION(mConvexHulls);
|
|
VECTOR_SET_ASSOCIATION(mConvexHullEmitStrings);
|
|
VECTOR_SET_ASSOCIATION(mHullIndices);
|
|
VECTOR_SET_ASSOCIATION(mHullEmitStringIndices);
|
|
VECTOR_SET_ASSOCIATION(mHullSurfaceIndices);
|
|
VECTOR_SET_ASSOCIATION(mHullPlaneIndices);
|
|
VECTOR_SET_ASSOCIATION(mPolyListPlanes);
|
|
VECTOR_SET_ASSOCIATION(mPolyListPoints);
|
|
VECTOR_SET_ASSOCIATION(mPolyListStrings);
|
|
VECTOR_SET_ASSOCIATION(mCoordBinIndices);
|
|
|
|
VECTOR_SET_ASSOCIATION(mVehicleConvexHulls);
|
|
VECTOR_SET_ASSOCIATION(mVehicleConvexHullEmitStrings);
|
|
VECTOR_SET_ASSOCIATION(mVehicleHullIndices);
|
|
VECTOR_SET_ASSOCIATION(mVehicleHullEmitStringIndices);
|
|
VECTOR_SET_ASSOCIATION(mVehicleHullSurfaceIndices);
|
|
VECTOR_SET_ASSOCIATION(mVehicleHullPlaneIndices);
|
|
VECTOR_SET_ASSOCIATION(mVehiclePolyListPlanes);
|
|
VECTOR_SET_ASSOCIATION(mVehiclePolyListPoints);
|
|
VECTOR_SET_ASSOCIATION(mVehiclePolyListStrings);
|
|
VECTOR_SET_ASSOCIATION(mVehiclePoints);
|
|
VECTOR_SET_ASSOCIATION(mVehicleNullSurfaces);
|
|
VECTOR_SET_ASSOCIATION(mVehiclePlanes);
|
|
}
|
|
|
|
Interior::~Interior()
|
|
{
|
|
U32 i;
|
|
delete mMaterialList;
|
|
mMaterialList = NULL;
|
|
delete mWhite;
|
|
mWhite = NULL;
|
|
delete mWhiteRGB;
|
|
mWhiteRGB = NULL;
|
|
delete mLightFalloff;
|
|
mLightFalloff = NULL;
|
|
|
|
// remove from lightmap manager
|
|
if(mLMHandle != LM_HANDLE(-1))
|
|
gInteriorLMManager.removeInterior(mLMHandle);
|
|
|
|
for (i = 0; i < mLightmaps.size(); i++) {
|
|
delete mLightmaps[i];
|
|
mLightmaps[i] = NULL;
|
|
}
|
|
|
|
for (i = 0; i < mEnvironMaps.size(); i++) {
|
|
delete mEnvironMaps[i];
|
|
mEnvironMaps[i] = NULL;
|
|
}
|
|
|
|
for (i = 0; i < mSubObjects.size(); i++) {
|
|
delete mSubObjects[i];
|
|
mSubObjects[i] = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool Interior::prepForRendering(const char* path)
|
|
{
|
|
if (mPreppedForRender == true)
|
|
return true;
|
|
|
|
// Before we load the material list we temporarily remove
|
|
// some special texture names so that we don't get bogus
|
|
// texture load warnings in the console.
|
|
VectorPtr<char*> matNames;
|
|
matNames = mMaterialList->mMaterialNames;
|
|
VectorPtr<char*>::iterator iter = mMaterialList->mMaterialNames.begin();
|
|
|
|
for ( ; iter != mMaterialList->mMaterialNames.end(); iter++ )
|
|
{
|
|
if ( *iter &&
|
|
( dStrcmp( *iter, "NULL" ) == 0 ||
|
|
dStrcmp( *iter, "ORIGIN" ) == 0 ||
|
|
dStrcmp( *iter, "TRIGGER" ) == 0 ||
|
|
dStrcmp( *iter, "FORCEFIELD" ) == 0 ) )
|
|
{
|
|
*iter = NULL;
|
|
}
|
|
}
|
|
|
|
// Load the material list
|
|
bool matListSuccess = mMaterialList->load(InteriorTexture, path, false);
|
|
|
|
// Now restore the material names since someone later may
|
|
// count on the special texture names being present.
|
|
mMaterialList->mMaterialNames = matNames;
|
|
|
|
if(!matListSuccess)
|
|
return false;
|
|
|
|
// lightmap manager steals the lightmaps here...
|
|
gInteriorLMManager.addInterior(mLMHandle, mLightmaps.size(), this);
|
|
AssertFatal(!mLightmaps.size(), "Failed to process lightmaps");
|
|
|
|
// And the environment maps...
|
|
MaterialPropertyMap* pMatMap = static_cast<MaterialPropertyMap*>(Sim::findObject("MaterialPropertyMap"));
|
|
|
|
mEnvironMaps.setSize(mMaterialList->getMaterialCount());
|
|
mEnvironFactors.setSize(mMaterialList->getMaterialCount());
|
|
mValidEnvironMaps = 0;
|
|
for (U32 i = 0; i < mMaterialList->getMaterialCount(); i++) {
|
|
mEnvironFactors[i] = 1.0f;
|
|
|
|
const char* pName = mMaterialList->getMaterialName(i);
|
|
const MaterialPropertyMap::MapEntry* pEntry = pMatMap->getMapEntry(pName);
|
|
if (pEntry != NULL) {
|
|
if (pEntry->environMapName != NULL) {
|
|
mEnvironMaps[i] = new TextureHandle(pEntry->environMapName, MeshTexture);
|
|
mEnvironFactors[i] = pEntry->environMapFactor;
|
|
mValidEnvironMaps += mEnvironMaps[i] != NULL ? 1 : 0;
|
|
} else {
|
|
mEnvironMaps[i] = NULL;
|
|
}
|
|
} else {
|
|
mEnvironMaps[i] = NULL;
|
|
}
|
|
}
|
|
mWhite = new TextureHandle("common/lighting/whiteAlpha255", MeshTexture);
|
|
mWhiteRGB = new TextureHandle("common/lighting/whiteNoAlpha", MeshTexture);
|
|
mLightFalloff = new TextureHandle("common/lighting/lightFalloffMono", BitmapTexture, true);
|
|
|
|
mPreppedForRender = matListSuccess;
|
|
|
|
// Setup the average texgen length...
|
|
setupAveTexGenLength();
|
|
|
|
detailMapping.sgInitDetailMapping(mMaterialList);
|
|
|
|
return matListSuccess;
|
|
}
|
|
|
|
|
|
void Interior::setupAveTexGenLength()
|
|
{
|
|
F32 len = 0.0f;
|
|
for (U32 i = 0; i < mSurfaces.size(); i++)
|
|
{
|
|
// We're going to assume that most textures don't have separate scales for
|
|
// x and y...
|
|
F32 lenx = mTexGenEQs[mSurfaces[i].texGenIndex].planeX.len();
|
|
len += F32((*mMaterialList)[mSurfaces[i].textureIndex].getWidth()) * lenx;
|
|
}
|
|
len /= F32(mSurfaces.size());
|
|
mAveTexGenLength = len;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool Interior::prepRender(SceneState* state,
|
|
S32 containingZone,
|
|
S32 baseZone,
|
|
U32 zoneOffset,
|
|
const MatrixF& OSToWS,
|
|
const Point3F& objScale,
|
|
const bool modifyBaseState,
|
|
const bool dontRestrictOutside,
|
|
const bool flipClipPlanes)
|
|
{
|
|
// Store off the viewport and frustum
|
|
if (modifyBaseState || dontRestrictOutside ) {
|
|
sgStoredViewport = state->getBaseZoneState().viewport;
|
|
sgStoredFrustum[0] = state->getBaseZoneState().frustum[0];
|
|
sgStoredFrustum[1] = state->getBaseZoneState().frustum[1];
|
|
sgStoredFrustum[2] = state->getBaseZoneState().frustum[2];
|
|
sgStoredFrustum[3] = state->getBaseZoneState().frustum[3];
|
|
sgStoredFrustum[4] = state->getNearPlane();
|
|
sgStoredFrustum[5] = state->getFarPlane();
|
|
} else {
|
|
sgStoredViewport = state->getZoneState(containingZone).viewport;
|
|
sgStoredFrustum[0] = state->getZoneState(containingZone).frustum[0];
|
|
sgStoredFrustum[1] = state->getZoneState(containingZone).frustum[1];
|
|
sgStoredFrustum[2] = state->getZoneState(containingZone).frustum[2];
|
|
sgStoredFrustum[3] = state->getZoneState(containingZone).frustum[3];
|
|
sgStoredFrustum[4] = state->getNearPlane();
|
|
sgStoredFrustum[5] = state->getFarPlane();
|
|
}
|
|
|
|
// Camera point is given by the state. We need the projection matrix.
|
|
// OS->WS and scale are given. This is an ugly way to do this...
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPushMatrix();
|
|
glLoadIdentity();
|
|
dglSetFrustum(sgStoredFrustum[0], sgStoredFrustum[1],
|
|
sgStoredFrustum[2], sgStoredFrustum[3],
|
|
sgStoredFrustum[4], sgStoredFrustum[5],dglIsOrtho());
|
|
dglGetProjection(&sgProjMatrix);
|
|
glPopMatrix();
|
|
glMatrixMode(GL_MODELVIEW);
|
|
|
|
MatrixF finalModelView;
|
|
dglGetModelview(&finalModelView);
|
|
finalModelView.mul(OSToWS);
|
|
finalModelView.scale(Point3F(objScale.x, objScale.y, objScale.z));
|
|
sgProjMatrix.mul(finalModelView);
|
|
|
|
finalModelView.inverse();
|
|
finalModelView.mulP(Point3F(0.0f, 0.0f, 0.0f), &sgCamPoint);
|
|
sgWSToOSMatrix = finalModelView;
|
|
|
|
// do the zone traversal
|
|
if (mZones.size() == 0)
|
|
return false;
|
|
|
|
sgZoneRenderInfo.setSize(mZones.size());
|
|
zoneTraversal(baseZone, flipClipPlanes);
|
|
|
|
// Copy out the information for all zones but the outside zone.
|
|
for (U32 i = 1; i < mZones.size(); i++) {
|
|
AssertFatal(zoneOffset != 0xFFFFFFFF, "Error, this should never happen!");
|
|
U32 globalIndex = i + zoneOffset - 1;
|
|
|
|
SceneState::ZoneState& rState = state->getZoneStateNC(globalIndex);
|
|
rState.render = sgZoneRenderInfo[i].render;
|
|
if (rState.render) {
|
|
rState.frustum[0] = sgZoneRenderInfo[i].frustum[0];
|
|
rState.frustum[1] = sgZoneRenderInfo[i].frustum[1];
|
|
rState.frustum[2] = sgZoneRenderInfo[i].frustum[2];
|
|
rState.frustum[3] = sgZoneRenderInfo[i].frustum[3];
|
|
rState.viewport = sgZoneRenderInfo[i].viewport;
|
|
}
|
|
}
|
|
|
|
if (modifyBaseState) {
|
|
// Need to modify the state's baseZoneState based on the outside zone's (0),
|
|
// parameters.
|
|
if (sgZoneRenderInfo[0].render == true) {
|
|
SceneState::ZoneState& rState = state->getBaseZoneStateNC();
|
|
rState.frustum[0] = sgZoneRenderInfo[0].frustum[0];
|
|
rState.frustum[1] = sgZoneRenderInfo[0].frustum[1];
|
|
rState.frustum[2] = sgZoneRenderInfo[0].frustum[2];
|
|
rState.frustum[3] = sgZoneRenderInfo[0].frustum[3];
|
|
rState.viewport = sgZoneRenderInfo[0].viewport;
|
|
}
|
|
}
|
|
destroyZoneRectVectors();
|
|
|
|
// If zone 0 is rendered, then we return true...
|
|
return sgZoneRenderInfo[0].render;
|
|
}
|
|
|
|
|
|
void Interior::prepTempRender(SceneState* state,
|
|
S32 containingZone,
|
|
S32 baseZone,
|
|
const MatrixF& OSToWS,
|
|
const Point3F& objScale,
|
|
const bool flipClipPlanes)
|
|
{
|
|
PROFILE_START(InteriorPrepTempRender);
|
|
sgStoredViewport = state->getZoneState(containingZone).viewport;
|
|
sgStoredFrustum[0] = state->getZoneState(containingZone).frustum[0];
|
|
sgStoredFrustum[1] = state->getZoneState(containingZone).frustum[1];
|
|
sgStoredFrustum[2] = state->getZoneState(containingZone).frustum[2];
|
|
sgStoredFrustum[3] = state->getZoneState(containingZone).frustum[3];
|
|
sgStoredFrustum[4] = state->getNearPlane();
|
|
sgStoredFrustum[5] = state->getFarPlane();
|
|
|
|
// Camera point is given by the state. We need the projection matrix.
|
|
// OS->WS and scale are given. This is an ugly way to do this...
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPushMatrix();
|
|
glLoadIdentity();
|
|
dglSetFrustum(sgStoredFrustum[0], sgStoredFrustum[1],
|
|
sgStoredFrustum[2], sgStoredFrustum[3],
|
|
sgStoredFrustum[4], sgStoredFrustum[5],dglIsOrtho());
|
|
dglGetProjection(&sgProjMatrix);
|
|
glPopMatrix();
|
|
glMatrixMode(GL_MODELVIEW);
|
|
|
|
MatrixF finalModelView;
|
|
dglGetModelview(&finalModelView);
|
|
finalModelView.mul(OSToWS);
|
|
finalModelView.scale(Point3F(objScale.x, objScale.y, objScale.z));
|
|
sgProjMatrix.mul(finalModelView);
|
|
|
|
finalModelView.inverse();
|
|
finalModelView.mulP(Point3F(0.0f, 0.0f, 0.0f), &sgCamPoint);
|
|
sgWSToOSMatrix = finalModelView;
|
|
|
|
// do the zone traversal
|
|
sgZoneRenderInfo.setSize(mZones.size());
|
|
zoneTraversal(baseZone, flipClipPlanes);
|
|
destroyZoneRectVectors();
|
|
PROFILE_END();
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
S32 Interior::getZoneForPoint(const Point3F& rPoint) const
|
|
{
|
|
if (mBSPNodes.size() == 0)
|
|
return -1;
|
|
|
|
const IBSPNode* pNode = &mBSPNodes[0];
|
|
|
|
while (true)
|
|
{
|
|
F32 dist = getPlane(pNode->planeIndex).distToPlane(rPoint);
|
|
if (planeIsFlipped(pNode->planeIndex))
|
|
dist = -dist;
|
|
|
|
U16 traverseIndex;
|
|
if (dist >= 0.0f)
|
|
traverseIndex = pNode->frontIndex;
|
|
else
|
|
traverseIndex = pNode->backIndex;
|
|
|
|
if (isBSPLeafIndex(traverseIndex))
|
|
{
|
|
if (isBSPSolidLeaf(traverseIndex))
|
|
{
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
U16 zone = getBSPEmptyLeafZone(traverseIndex);
|
|
if (zone == 0x0FFF)
|
|
return -1;
|
|
else
|
|
return zone;
|
|
}
|
|
}
|
|
|
|
pNode = &mBSPNodes[traverseIndex];
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
static void itrClipToPlane(Point3F* points, U32& rNumPoints, const PlaneF& rPlane)
|
|
{
|
|
S32 start = -1;
|
|
for (U32 i = 0; i < rNumPoints; i++) {
|
|
if (rPlane.whichSide(points[i]) == PlaneF::Front) {
|
|
start = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Nothing was in front of the plane...
|
|
if (start == -1) {
|
|
rNumPoints = 0;
|
|
return;
|
|
}
|
|
|
|
static Point3F finalPoints[128];
|
|
U32 numFinalPoints = 0;
|
|
|
|
U32 baseStart = start;
|
|
U32 end = (start + 1) % rNumPoints;
|
|
|
|
while (end != baseStart) {
|
|
const Point3F& rStartPoint = points[start];
|
|
const Point3F& rEndPoint = points[end];
|
|
|
|
PlaneF::Side fSide = rPlane.whichSide(rStartPoint);
|
|
PlaneF::Side eSide = rPlane.whichSide(rEndPoint);
|
|
|
|
S32 code = fSide * 3 + eSide;
|
|
switch (code) {
|
|
case 4: // f f
|
|
case 3: // f o
|
|
case 1: // o f
|
|
case 0: // o o
|
|
// No Clipping required
|
|
finalPoints[numFinalPoints++] = points[start];
|
|
start = end;
|
|
end = (end + 1) % rNumPoints;
|
|
break;
|
|
|
|
|
|
case 2: // f b
|
|
{
|
|
// In this case, we emit the front point, Insert the intersection,
|
|
// and advancing to point to first point that is in front or on...
|
|
//
|
|
finalPoints[numFinalPoints++] = points[start];
|
|
|
|
Point3F vector = rEndPoint - rStartPoint;
|
|
F32 t = -(rPlane.distToPlane(rStartPoint) / mDot(rPlane, vector));
|
|
|
|
Point3F intersection = rStartPoint + (vector * t);
|
|
finalPoints[numFinalPoints++] = intersection;
|
|
|
|
U32 endSeek = (end + 1) % rNumPoints;
|
|
while (rPlane.whichSide(points[endSeek]) == PlaneF::Back)
|
|
endSeek = (endSeek + 1) % rNumPoints;
|
|
|
|
end = endSeek;
|
|
start = (end + (rNumPoints - 1)) % rNumPoints;
|
|
|
|
const Point3F& rNewStartPoint = points[start];
|
|
const Point3F& rNewEndPoint = points[end];
|
|
|
|
vector = rNewEndPoint - rNewStartPoint;
|
|
t = -(rPlane.distToPlane(rNewStartPoint) / mDot(rPlane, vector));
|
|
|
|
intersection = rNewStartPoint + (vector * t);
|
|
points[start] = intersection;
|
|
}
|
|
break;
|
|
|
|
case -1: // o b
|
|
{
|
|
// In this case, we emit the front point, and advance to point to first
|
|
// point that is in front or on...
|
|
//
|
|
finalPoints[numFinalPoints++] = points[start];
|
|
|
|
U32 endSeek = (end + 1) % rNumPoints;
|
|
while (rPlane.whichSide(points[endSeek]) == PlaneF::Back)
|
|
endSeek = (endSeek + 1) % rNumPoints;
|
|
|
|
end = endSeek;
|
|
start = (end + (rNumPoints - 1)) % rNumPoints;
|
|
|
|
const Point3F& rNewStartPoint = points[start];
|
|
const Point3F& rNewEndPoint = points[end];
|
|
|
|
Point3F vector = rNewEndPoint - rNewStartPoint;
|
|
F32 t = -(rPlane.distToPlane(rNewStartPoint) / mDot(rPlane, vector));
|
|
|
|
Point3F intersection = rNewStartPoint + (vector * t);
|
|
points[start] = intersection;
|
|
}
|
|
break;
|
|
|
|
case -2: // b f
|
|
case -3: // b o
|
|
case -4: // b b
|
|
// In the algorithm used here, this should never happen...
|
|
AssertISV(false, "CSGPlane::clipWindingToPlaneFront: error in polygon clipper");
|
|
break;
|
|
|
|
default:
|
|
AssertFatal(false, "CSGPlane::clipWindingToPlaneFront: bad outcode");
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
// Emit the last point.
|
|
finalPoints[numFinalPoints++] = points[start];
|
|
AssertFatal(numFinalPoints >= 3, avar("Error, this shouldn't happen! Invalid winding in itrClipToPlane: %d", numFinalPoints));
|
|
|
|
// Copy the new rWinding, and we're set!
|
|
//
|
|
dMemcpy(points, finalPoints, numFinalPoints * sizeof(Point3F));
|
|
rNumPoints = numFinalPoints;
|
|
AssertISV(rNumPoints <= 128, "Increase maxWindingPoints. Talk to DMoore");
|
|
}
|
|
|
|
bool Interior::projectClipAndBoundFan(U32 fanIndex, F64* pResult)
|
|
{
|
|
const TriFan& rFan = mWindingIndices[fanIndex];
|
|
|
|
static Point3F windingPoints[128];
|
|
U32 numPoints = rFan.windingCount;
|
|
U32 i;
|
|
for (i = 0; i < numPoints; i++)
|
|
windingPoints[i] = mPoints[mWindings[rFan.windingStart + i]].point;
|
|
|
|
itrClipToPlane(windingPoints, numPoints, sgOSPlaneFar);
|
|
if (numPoints != 0)
|
|
itrClipToPlane(windingPoints, numPoints, sgOSPlaneXMin);
|
|
if (numPoints != 0)
|
|
itrClipToPlane(windingPoints, numPoints, sgOSPlaneXMax);
|
|
if (numPoints != 0)
|
|
itrClipToPlane(windingPoints, numPoints, sgOSPlaneYMin);
|
|
if (numPoints != 0)
|
|
itrClipToPlane(windingPoints, numPoints, sgOSPlaneYMax);
|
|
|
|
if (numPoints == 0) {
|
|
pResult[0] =
|
|
pResult[1] =
|
|
pResult[2] =
|
|
pResult[3] = 0.0f;
|
|
return false;
|
|
}
|
|
|
|
F32 minX = 1e10f;
|
|
F32 maxX = -1e10f;
|
|
F32 minY = 1e10f;
|
|
F32 maxY = -1e10f;
|
|
static Point4F projPoints[128];
|
|
for (i = 0; i < numPoints; i++) {
|
|
projPoints[i].set(windingPoints[i].x, windingPoints[i].y, windingPoints[i].z, 1.0);
|
|
sgProjMatrix.mul(projPoints[i]);
|
|
|
|
AssertFatal(projPoints[i].w != 0.0, "Error, that's bad!");
|
|
projPoints[i].x /= projPoints[i].w;
|
|
projPoints[i].y /= projPoints[i].w;
|
|
|
|
if (projPoints[i].x < minX)
|
|
minX = projPoints[i].x;
|
|
if (projPoints[i].x > maxX)
|
|
maxX = projPoints[i].x;
|
|
if (projPoints[i].y < minY)
|
|
minY = projPoints[i].y;
|
|
if (projPoints[i].y > maxY)
|
|
maxY = projPoints[i].y;
|
|
}
|
|
|
|
if (minX < -1.0f) minX = -1.0f;
|
|
if (minY < -1.0f) minY = -1.0f;
|
|
if (maxX > 1.0f) maxX = 1.0f;
|
|
if (maxY > 1.0f) maxY = 1.0f;
|
|
|
|
pResult[0] = minX;
|
|
pResult[1] = minY;
|
|
pResult[2] = maxX;
|
|
pResult[3] = maxY;
|
|
return true;
|
|
}
|
|
|
|
void Interior::createZoneRectVectors()
|
|
{
|
|
sgZoneRects.setSize(mZones.size());
|
|
for (U32 i = 0; i < mZones.size(); i++)
|
|
sgZoneRects[i].active = false;
|
|
}
|
|
|
|
void Interior::destroyZoneRectVectors()
|
|
{
|
|
|
|
}
|
|
|
|
void Interior::traverseZone(const RectD* inputRects, const U32 numInputRects, U32 currZone, Vector<U32>& zoneStack)
|
|
{
|
|
PROFILE_START(InteriorTraverseZone);
|
|
// First, we push onto our rect list all the inputRects...
|
|
insertZoneRects(sgZoneRects[currZone], inputRects, numInputRects);
|
|
|
|
// A portal is a valid traversal if the camera point is on the
|
|
// same side of it's plane as the zone. It must then pass the
|
|
// clip/project test.
|
|
U32 i;
|
|
const Zone& rZone = mZones[currZone];
|
|
for (i = rZone.portalStart; i < U32(rZone.portalStart + rZone.portalCount); i++) {
|
|
const Portal& rPortal = mPortals[mZonePortalList[i]];
|
|
AssertFatal(U32(rPortal.zoneFront) == currZone || U32(rPortal.zoneBack) == currZone,
|
|
"Portal doesn't reference this zone?");
|
|
|
|
S32 camSide = getPlane(rPortal.planeIndex).whichSide(sgCamPoint);
|
|
if (planeIsFlipped(rPortal.planeIndex))
|
|
camSide = -camSide;
|
|
S32 zoneSide = (U32(rPortal.zoneFront) == currZone) ? 1 : -1;
|
|
U16 otherZone = (U32(rPortal.zoneFront) == currZone) ? rPortal.zoneBack : rPortal.zoneFront;
|
|
|
|
// Make sure this isn't a free floating portal...
|
|
if (otherZone == currZone)
|
|
continue;
|
|
|
|
// Make sure we haven't encountered this zone already in this traversal
|
|
bool onStack = false;
|
|
for (U32 i = 0; i < zoneStack.size(); i++) {
|
|
if (otherZone == zoneStack[i]) {
|
|
onStack = true;
|
|
break;
|
|
}
|
|
}
|
|
if (onStack == true)
|
|
continue;
|
|
|
|
if (camSide == zoneSide) {
|
|
// Can traverse. Note: special case PlaneF::On
|
|
// here to prevent possible w == 0 problems and infinite recursion
|
|
// Vector<RectD> newRects;
|
|
// VECTOR_SET_ASSOCIATION(newRects);
|
|
|
|
// We're abusing the heck out of the allocator here.
|
|
U32 waterMark = FrameAllocator::getWaterMark();
|
|
RectD* newRects = (RectD*)FrameAllocator::alloc(1);
|
|
U32 numNewRects = 0;
|
|
|
|
for (S32 j = 0; j < rPortal.triFanCount; j++) {
|
|
F64 result[4];
|
|
if (projectClipAndBoundFan(rPortal.triFanStart + j, result)) {
|
|
// Have a good rect from this.
|
|
RectD possible = convertToRectD(result);
|
|
|
|
for (U32 k = 0; k < numInputRects; k++) {
|
|
RectD copy = possible;
|
|
if (copy.intersect(inputRects[k]))
|
|
newRects[numNewRects++] = copy;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (numNewRects != 0) {
|
|
FrameAllocator::alloc((sizeof(RectD) * numNewRects) - 1);
|
|
|
|
U32 prevStackSize = zoneStack.size();
|
|
zoneStack.push_back(currZone);
|
|
traverseZone(newRects, numNewRects, otherZone, zoneStack);
|
|
zoneStack.pop_back();
|
|
AssertFatal(zoneStack.size() == prevStackSize, "Error, stack size changed!");
|
|
}
|
|
FrameAllocator::setWaterMark(waterMark);
|
|
}
|
|
else if (camSide == PlaneF::On) {
|
|
U32 waterMark = FrameAllocator::getWaterMark();
|
|
RectD* newRects = (RectD*)FrameAllocator::alloc(numInputRects * sizeof(RectD));
|
|
dMemcpy(newRects, inputRects, sizeof(RectD) * numInputRects);
|
|
|
|
U32 prevStackSize = zoneStack.size();
|
|
zoneStack.push_back(currZone);
|
|
traverseZone(newRects, numInputRects, otherZone, zoneStack);
|
|
zoneStack.pop_back();
|
|
AssertFatal(zoneStack.size() == prevStackSize, "Error, stack size changed!");
|
|
FrameAllocator::setWaterMark(waterMark);
|
|
}
|
|
}
|
|
PROFILE_END();
|
|
}
|
|
|
|
void Interior::zoneTraversal(S32 baseZone, const bool flipClip)
|
|
{
|
|
PROFILE_START(InteriorZoneTraversal);
|
|
// If we're in solid, render everything...
|
|
if (baseZone == -1) {
|
|
for (U32 i = 0; i < mZones.size(); i++) {
|
|
sgZoneRenderInfo[i].render = true;
|
|
|
|
sgZoneRenderInfo[i].frustum[0] = sgStoredFrustum[0];
|
|
sgZoneRenderInfo[i].frustum[1] = sgStoredFrustum[1];
|
|
sgZoneRenderInfo[i].frustum[2] = sgStoredFrustum[2];
|
|
sgZoneRenderInfo[i].frustum[3] = sgStoredFrustum[3];
|
|
sgZoneRenderInfo[i].viewport = sgStoredViewport;
|
|
}
|
|
PROFILE_END();
|
|
return;
|
|
}
|
|
|
|
// Otherwise, we're going to have to do some work...
|
|
createZoneRectVectors();
|
|
U32 i;
|
|
for (i = 0; i < mZones.size(); i++)
|
|
sgZoneRenderInfo[i].render = false;
|
|
|
|
// Create the object space clipping planes...
|
|
sgComputeOSFrustumPlanes(sgStoredFrustum,
|
|
sgWSToOSMatrix,
|
|
sgCamPoint,
|
|
sgOSPlaneFar,
|
|
sgOSPlaneXMin,
|
|
sgOSPlaneXMax,
|
|
sgOSPlaneYMin,
|
|
sgOSPlaneYMax);
|
|
|
|
if (flipClip == true) {
|
|
sgOSPlaneXMin.neg();
|
|
sgOSPlaneXMax.neg();
|
|
sgOSPlaneYMin.neg();
|
|
sgOSPlaneYMax.neg();
|
|
}
|
|
// First, the current zone gets the full clipRect, and marked as rendering...
|
|
static const F64 fullResult[4] = { -1, -1, 1, 1 };
|
|
|
|
static Vector<U32> zoneStack;
|
|
zoneStack.clear();
|
|
VECTOR_SET_ASSOCIATION(zoneStack);
|
|
|
|
RectD baseRect = convertToRectD(fullResult);
|
|
traverseZone(&baseRect, 1, baseZone, zoneStack);
|
|
|
|
for (i = 0; i < mZones.size(); i++) {
|
|
if (sgZoneRects[i].active == true) {
|
|
sgZoneRenderInfo[i].render = true;
|
|
convertToFrustum(sgZoneRenderInfo[i], sgZoneRects[i].rect);
|
|
}
|
|
}
|
|
PROFILE_END();
|
|
}
|
|
|
|
|
|
void mergeSurfaceVectors(const U16* from0,
|
|
const U32 size0,
|
|
const U16* from1,
|
|
const U32 size1,
|
|
U16* output,
|
|
U32* outputSize)
|
|
{
|
|
U32 pos0 = 0;
|
|
U32 pos1 = 0;
|
|
U32 outputCount = 0;
|
|
while (pos0 < size0 && pos1 < size1) {
|
|
if (from0[pos0] < from1[pos1]) {
|
|
output[outputCount++] = from0[pos0++];
|
|
} else if (from0[pos0] == from1[pos1]) {
|
|
// Equal, output one, and inc both counts
|
|
output[outputCount++] = from0[pos0++];
|
|
pos1++;
|
|
} else {
|
|
output[outputCount++] = from1[pos1++];
|
|
}
|
|
}
|
|
AssertFatal(pos0 == size0 || pos1 == size1, "Error, one of these must have reached the end!");
|
|
|
|
// Copy the dregs...
|
|
if (pos0 != size0) {
|
|
dMemcpy(&output[outputCount], &from0[pos0], sizeof(U16) * (size0 - pos0));
|
|
outputCount += size0 - pos0;
|
|
} else if (pos1 != size1) {
|
|
dMemcpy(&output[outputCount], &from1[pos1], sizeof(U16) * (size1 - pos1));
|
|
outputCount += size1 - pos1;
|
|
}
|
|
|
|
*outputSize = outputCount;
|
|
}
|
|
|
|
struct ItrMergeStruct
|
|
{
|
|
U16* array;
|
|
U32 size;
|
|
};
|
|
|
|
bool Interior::useFogCoord()
|
|
{
|
|
return (dglDoesSupportFogCoord() && smUseTexturedFog == false);
|
|
}
|
|
|
|
void Interior::doFogActive( const bool environmentActive,
|
|
const SceneState* state,
|
|
const U32 mergeArrayCount, const U16* mergeArray,
|
|
const PlaneF &distPlane,
|
|
const F32 distOffset,
|
|
const Point3F &worldP,
|
|
const Point3F &osZVec,
|
|
const F32 worldZ )
|
|
{
|
|
// Point setup
|
|
BitVector activePoints( mPoints.size() );
|
|
activePoints.clear();
|
|
|
|
sgActivePolyList = (U16*)FrameAllocator::alloc(mSurfaces.size() * sizeof(U16));
|
|
sgEnvironPolyList = NULL;
|
|
sgActivePolyListSize = 0;
|
|
sgEnvironPolyListSize = 0;
|
|
sgFogPolyList = (U16*)FrameAllocator::alloc(mSurfaces.size() * sizeof(U16));
|
|
sgFogTexCoords = (Point2F*)FrameAllocator::alloc(mPoints.size() * sizeof(Point2F));
|
|
sgFogPolyListSize = 0;
|
|
|
|
const FogCalc fogCalc( distPlane, distOffset, osZVec, worldZ, worldP.z, state );
|
|
|
|
if (useFogCoord())
|
|
{
|
|
// FC fog
|
|
for (U32 i = 0; i < mergeArrayCount; ++i)
|
|
{
|
|
const U16 oIndex = mergeArray[i];
|
|
|
|
sgActivePolyList[sgActivePolyListSize++] = oIndex;
|
|
|
|
const Surface& rSurface = mSurfaces[oIndex];
|
|
|
|
if (environmentActive && (mEnvironMaps[rSurface.textureIndex] != NULL))
|
|
{
|
|
if ( sgEnvironPolyList == NULL )
|
|
sgEnvironPolyList = (U16*)FrameAllocator::alloc(mSurfaces.size() * sizeof(U16));
|
|
|
|
sgEnvironPolyList[sgEnvironPolyListSize++] = oIndex;
|
|
}
|
|
|
|
// Fog the unfogged points...
|
|
const U32 count = rSurface.windingStart + rSurface.windingCount;
|
|
|
|
for (U32 j = rSurface.windingStart; j < count; ++j)
|
|
{
|
|
const U32 index = mWindings[j];
|
|
|
|
if ( !activePoints.test( index ) )
|
|
{
|
|
activePoints.set( index );
|
|
|
|
mPoints[index].fogCoord = fogCalc.calcFC( mPoints[index].point );
|
|
|
|
AssertFatal(mPoints[index].fogCoord >= 0.0f, "Error, neg fog coord!");
|
|
}
|
|
}
|
|
}
|
|
|
|
return; // we're done with FC, so return
|
|
}
|
|
|
|
// Textured fog
|
|
for (U32 i = 0; i < mergeArrayCount; ++i)
|
|
{
|
|
const U16 oIndex = mergeArray[i];
|
|
|
|
sgActivePolyList[sgActivePolyListSize++] = oIndex;
|
|
|
|
const Surface& rSurface = mSurfaces[oIndex];
|
|
|
|
if (environmentActive && (mEnvironMaps[rSurface.textureIndex] != NULL))
|
|
{
|
|
if ( sgEnvironPolyList == NULL )
|
|
sgEnvironPolyList = (U16*)FrameAllocator::alloc(mSurfaces.size() * sizeof(U16));
|
|
|
|
sgEnvironPolyList[sgEnvironPolyListSize++] = oIndex;
|
|
}
|
|
|
|
// Fog the unfogged points...
|
|
const U32 count = rSurface.windingStart + rSurface.windingCount;
|
|
|
|
for (U32 j = rSurface.windingStart; j < count; ++j)
|
|
{
|
|
const U32 pIndex = mWindings[j];
|
|
|
|
if ( !activePoints.test( pIndex ) )
|
|
{
|
|
activePoints.set( pIndex );
|
|
|
|
sgFogTexCoords[pIndex] = fogCalc.calcTextured( mPoints[pIndex].point );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Interior::setupActivePolyList(ZoneVisDeterminer& zoneDeterminer,
|
|
SceneState* state,
|
|
const Point3F& rPoint,
|
|
const Point3F& osCamVector,
|
|
const Point3F& osZVec,
|
|
const F32 worldZ,
|
|
const Point3F& scale)
|
|
{
|
|
if (mZones.size() == 0)
|
|
return;
|
|
|
|
PROFILE_START(InteriorSetupActivePolyList);
|
|
|
|
// Here's the deal. We loop through each of the zones, and create a merged master
|
|
// list of polygons that are the union of all the zones render sets. While we're
|
|
// doing this, we'll be setting up each zone's list of planes. I've got these
|
|
// processes separated out for now, but they could be merged. After we have the
|
|
// master list of polys, and the list of active planes, we'll copy the list
|
|
// (culling the backfaces) into the ActivePolyList.
|
|
|
|
// There's some trickiness here. We use the high bit of this U16 to test against the
|
|
// flip bit in the surfaces planeindex.
|
|
U16* planeSides = (U16*)FrameAllocator::alloc(sizeof(U16) * mPlanes.size());
|
|
|
|
// We'll never need more than twice the number of zones for merging...
|
|
ItrMergeStruct* mergeArray = (ItrMergeStruct*)FrameAllocator::alloc((mZones.size() * 2) * sizeof(ItrMergeStruct));
|
|
U32 numMergeStructs = 0;
|
|
|
|
PROFILE_START(ISAPL_Merge);
|
|
for (U32 i = 0; i < mZones.size(); i++)
|
|
{
|
|
if (zoneDeterminer.isZoneVisible(i) == false)
|
|
continue;
|
|
|
|
if (mZones[i].surfaceCount == 0)
|
|
continue;
|
|
|
|
// Setup the plane directionals
|
|
for (U32 j = mZones[i].planeStart; j < mZones[i].planeStart + mZones[i].planeCount; j++) {
|
|
if (getPlane(mZonePlanes[j]).distToPlane(rPoint) >= 0.0f)
|
|
planeSides[mZonePlanes[j]] = 0x8000;
|
|
else
|
|
planeSides[mZonePlanes[j]] = 0x0000;
|
|
}
|
|
|
|
// Create a merge struct for this zone
|
|
ItrMergeStruct& rMerge = mergeArray[numMergeStructs++];
|
|
rMerge.size = mZones[i].surfaceCount;
|
|
rMerge.array = (U16*)FrameAllocator::alloc(rMerge.size * sizeof(U16));
|
|
dMemcpy( rMerge.array, &mZoneSurfaces[mZones[i].surfaceStart], rMerge.size * sizeof(U16) );
|
|
}
|
|
|
|
if (numMergeStructs == 0)
|
|
{
|
|
PROFILE_END();
|
|
PROFILE_END();
|
|
|
|
return;
|
|
}
|
|
|
|
// Merge the arrays into the final version
|
|
U32 finalArray = 0xFFFFFFFF;
|
|
{
|
|
U32 begin = 0;
|
|
U32 end = numMergeStructs;
|
|
while ((end - begin) > 1)
|
|
{
|
|
U32 newEnd = end;
|
|
|
|
U32 i;
|
|
for (i = begin; (i + 1) < end; i += 2)
|
|
{
|
|
const ItrMergeStruct& rMerge0 = mergeArray[i];
|
|
const ItrMergeStruct& rMerge1 = mergeArray[i+1];
|
|
|
|
// Create the new structure to merge into
|
|
ItrMergeStruct& rMergeOut = mergeArray[newEnd++];
|
|
rMergeOut.array = (U16*)FrameAllocator::alloc(mSurfaces.size() * sizeof(U16));
|
|
|
|
mergeSurfaceVectors(rMerge0.array, rMerge0.size,
|
|
rMerge1.array, rMerge1.size,
|
|
rMergeOut.array,
|
|
&rMergeOut.size);
|
|
}
|
|
|
|
begin = i;
|
|
end = newEnd;
|
|
}
|
|
|
|
finalArray = begin;
|
|
}
|
|
AssertFatal(finalArray < mZones.size() * 2, "Error, final array out of bounds!");
|
|
|
|
U16* output = mergeArray[finalArray].array;
|
|
U32 outputCount = mergeArray[finalArray].size;
|
|
U32 pos = 0;
|
|
|
|
// remove back faced polys from list
|
|
for (U32 i = 0; i < outputCount; ++i)
|
|
{
|
|
const U16 oIndex = output[i];
|
|
const U16 rSurfacePlaneIndex = mSurfaces[oIndex].planeIndex;
|
|
|
|
if ( (planeSides[getPlaneIndex(rSurfacePlaneIndex)] ^ rSurfacePlaneIndex) & 0x8000 )
|
|
output[pos++] = oIndex;
|
|
}
|
|
|
|
outputCount = pos;
|
|
|
|
PROFILE_END();
|
|
|
|
// Before we go and fog this object, we'll test the points of our bounding box. If
|
|
// they are all unfogged, then we have no need to do any fogging. If there are
|
|
// all fogged though, we cannot turn off rendering of the object, as it's possible
|
|
// that they extend into fog planes.
|
|
PlaneF distPlane;
|
|
|
|
// Setup the dist plane
|
|
const Point3F &closest = getBoundingBox().getClosestPoint(rPoint);
|
|
Point3F n = ( closest - rPoint );
|
|
n.convolve( scale );
|
|
|
|
const F32 distOffset = n.len();
|
|
if (distOffset != 0)
|
|
{
|
|
distPlane.set(closest, n);
|
|
}
|
|
else
|
|
{
|
|
// Oops, we're inside the bounding box. distnormal is the view vector in object space
|
|
distPlane.set(closest, osCamVector);
|
|
}
|
|
distPlane.x /= scale.x;
|
|
distPlane.y /= scale.y;
|
|
distPlane.z /= scale.z;
|
|
|
|
const Point3F &worldP = state->getCameraPosition();
|
|
|
|
F32 maxFog = -1;
|
|
const Point3F fp[2] = { getBoundingBox().min, getBoundingBox().max };
|
|
|
|
for (U32 i = 0; i < 8; i++)
|
|
{
|
|
Point3F test;
|
|
|
|
if (i & 0x1) test.x = fp[0].x;
|
|
else test.x = fp[1].x;
|
|
if (i & 0x2) test.y = fp[0].y;
|
|
else test.y = fp[1].y;
|
|
if (i & 0x4) test.z = fp[0].z;
|
|
else test.z = fp[1].z;
|
|
|
|
F32 hazeVal = state->getHazeAndFog(mFabs(distPlane.distToPlane(test)) + distOffset,
|
|
(mDot(test, osZVec) + worldZ) - worldP.z);
|
|
if (hazeVal > maxFog)
|
|
maxFog = hazeVal;
|
|
}
|
|
|
|
PROFILE_START(ISAPL_Setup);
|
|
|
|
bool environmentActive = (dglDoesSupportARBMultitexture() &&
|
|
smRenderEnvironmentMaps &&
|
|
mValidEnvironMaps != 0);
|
|
|
|
if (maxFog >= 1.0f/255.0f)
|
|
{
|
|
// Sigh. Gotta do it
|
|
sgFogActive = true;
|
|
|
|
doFogActive( environmentActive,
|
|
state,
|
|
outputCount, output,
|
|
distPlane,
|
|
distOffset,
|
|
worldP,
|
|
osZVec,
|
|
worldZ );
|
|
|
|
PROFILE_END();
|
|
PROFILE_END();
|
|
return;
|
|
}
|
|
|
|
// Unfogged. We can turn off this part of the setup...
|
|
sgFogActive = false;
|
|
|
|
// Point setup
|
|
sgActivePolyList = (U16*)FrameAllocator::alloc(mSurfaces.size() * sizeof(U16));
|
|
sgEnvironPolyList = NULL;
|
|
sgActivePolyListSize = 0;
|
|
sgEnvironPolyListSize = 0;
|
|
sgFogPolyList = NULL;
|
|
sgFogTexCoords = NULL;
|
|
sgFogPolyListSize = 0;
|
|
|
|
// No Fog
|
|
if (environmentActive)
|
|
{
|
|
sgEnvironPolyList = (U16*)FrameAllocator::alloc(mSurfaces.size() * sizeof(U16));
|
|
|
|
// Environ
|
|
for (U32 i = 0; i < outputCount; ++i)
|
|
{
|
|
const U16 oIndex = output[i];
|
|
|
|
sgActivePolyList[sgActivePolyListSize++] = oIndex;
|
|
|
|
const Surface& rSurface = mSurfaces[oIndex];
|
|
|
|
if (mEnvironMaps[rSurface.textureIndex] != NULL)
|
|
sgEnvironPolyList[sgEnvironPolyListSize++] = oIndex;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (U32 i = 0; i < outputCount; ++i)
|
|
{
|
|
sgActivePolyList[sgActivePolyListSize++] = output[i];
|
|
}
|
|
}
|
|
|
|
PROFILE_END();
|
|
PROFILE_END();
|
|
}
|
|
|
|
|
|
void Interior::scopeZone(const U32 currZone,
|
|
bool* interiorScopingState,
|
|
const Point3F& interiorRoot,
|
|
Vector<U32>& zoneStack,
|
|
Vector<PlaneF>& planeStack,
|
|
Vector<PlaneRange>& planeRangeStack)
|
|
{
|
|
// First, if we're here, this zone is scoped...
|
|
interiorScopingState[currZone] = true;
|
|
|
|
// A portal is a valid traversal if the camera point is on the
|
|
// same side of it's plane as the zone. It must then pass the
|
|
// clip/project test.
|
|
const Zone& rZone = mZones[currZone];
|
|
for (S32 i = rZone.portalStart; i < U32(rZone.portalStart + rZone.portalCount); i++) {
|
|
const Portal& rPortal = mPortals[mZonePortalList[i]];
|
|
AssertFatal(U32(rPortal.zoneFront) == currZone || U32(rPortal.zoneBack) == currZone,
|
|
"Portal doesn't reference this zone?");
|
|
|
|
S32 camSide = getPlane(rPortal.planeIndex).whichSide(interiorRoot);
|
|
if (planeIsFlipped(rPortal.planeIndex))
|
|
camSide = -camSide;
|
|
S32 zoneSide = (U32(rPortal.zoneFront) == currZone) ? 1 : -1;
|
|
U16 otherZone = (U32(rPortal.zoneFront) == currZone) ? rPortal.zoneBack : rPortal.zoneFront;
|
|
|
|
// Make sure this isn't a free floating portal...
|
|
if (otherZone == currZone)
|
|
continue;
|
|
|
|
// Make sure we haven't encountered this zone already in this traversal
|
|
bool onStack = false;
|
|
for (U32 i = 0; i < zoneStack.size(); i++) {
|
|
if (otherZone == zoneStack[i]) {
|
|
onStack = true;
|
|
break;
|
|
}
|
|
}
|
|
if (onStack == true)
|
|
continue;
|
|
|
|
if (camSide == zoneSide) {
|
|
// Can traverse. Note: special case PlaneF::On
|
|
|
|
// push ourselves onto the zonestack
|
|
zoneStack.push_back(currZone);
|
|
|
|
for (S32 j = 0; j < rPortal.triFanCount; j++) {
|
|
U32 k;
|
|
const TriFan& rFan = mWindingIndices[rPortal.triFanStart + j];
|
|
|
|
// Create the winding for this portal
|
|
//
|
|
static Point3F windingPoints[128];
|
|
U32 numPoints = rFan.windingCount;
|
|
for (k = 0; k < numPoints; k++)
|
|
windingPoints[k] = mPoints[mWindings[rFan.windingStart + k]].point;
|
|
|
|
// Clip the winding against the planes in the current range
|
|
for (k = 0; k < planeRangeStack.last().count; k++) {
|
|
const PlaneF& rPlane = planeStack[planeRangeStack.last().start + k];
|
|
itrClipToPlane(windingPoints, numPoints, rPlane);
|
|
if (numPoints == 0)
|
|
break;
|
|
}
|
|
// If the winding is now empty, bail
|
|
//
|
|
if (numPoints == 0)
|
|
continue;
|
|
|
|
// create new planes and range from the winding that remains. There is one
|
|
// plane for each winding point.
|
|
//
|
|
planeRangeStack.increment();
|
|
planeRangeStack.last().start = planeStack.size();
|
|
planeRangeStack.last().count = numPoints;
|
|
planeStack.increment(numPoints);
|
|
for (k = 0; k < numPoints; k++) {
|
|
U32 s = k;
|
|
U32 e = (k + 1) % numPoints;
|
|
|
|
planeStack[planeRangeStack.last().start + k].set(interiorRoot,
|
|
windingPoints[e],
|
|
windingPoints[s]);
|
|
if (zoneSide == -1)
|
|
planeStack[planeRangeStack.last().start + k].neg();
|
|
}
|
|
|
|
// traverse into new zone
|
|
scopeZone(otherZone,
|
|
interiorScopingState,
|
|
interiorRoot,
|
|
zoneStack,
|
|
planeStack,
|
|
planeRangeStack);
|
|
|
|
// pop off range, planes
|
|
planeStack.decrement(planeRangeStack.last().count);
|
|
planeRangeStack.pop_back();
|
|
}
|
|
|
|
// Pop ourselves off the stack
|
|
zoneStack.pop_back();
|
|
}
|
|
else if (camSide == PlaneF::On) {
|
|
// Special case. Have to drill down with the same frustums...
|
|
PlaneRange copy = planeRangeStack.last();
|
|
copy.start += copy.count;
|
|
planeRangeStack.push_back(copy);
|
|
planeStack.increment(copy.count);
|
|
for (U32 i = 0; i < copy.count; i++)
|
|
planeStack[copy.start + i] = planeStack[copy.start + i - copy.count];
|
|
|
|
zoneStack.push_back(currZone);
|
|
scopeZone(otherZone, interiorScopingState, interiorRoot, zoneStack, planeStack, planeRangeStack);
|
|
planeStack.decrement(copy.count);
|
|
planeRangeStack.decrement();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool Interior::scopeZones(const S32 baseZone,
|
|
const Point3F& interiorRoot,
|
|
bool* interiorScopingState)
|
|
{
|
|
// If we are in solid, scope everything, and return ourselves as continuing out...
|
|
if (baseZone == -1) {
|
|
for (U32 i = 0; i < mZones.size(); i++)
|
|
interiorScopingState[i] = true;
|
|
return true;
|
|
}
|
|
|
|
Vector<U32> zoneStack(64, __FILE__, __LINE__);
|
|
Vector<PlaneF> planeStack(1024, __FILE__, __LINE__);
|
|
Vector<PlaneRange> planeRangeStack(64, __FILE__, __LINE__);
|
|
|
|
PlaneRange initial;
|
|
initial.start = 0;
|
|
initial.count = 0;
|
|
planeRangeStack.push_back(initial);
|
|
scopeZone(baseZone, interiorScopingState, interiorRoot, zoneStack, planeStack, planeRangeStack);
|
|
|
|
return interiorScopingState[0];
|
|
}
|
|
|
|
|
|
// Remove any collision hulls, interval trees, etc...
|
|
//
|
|
void Interior::purgeLODData()
|
|
{
|
|
mConvexHulls.clear();
|
|
mHullIndices.clear();
|
|
mHullEmitStringIndices.clear();
|
|
mHullSurfaceIndices.clear();
|
|
mCoordBinIndices.clear();
|
|
mConvexHullEmitStrings.clear();
|
|
for (U32 i = 0; i < NumCoordBins * NumCoordBins; i++) {
|
|
mCoordBins[i].binStart = 0;
|
|
mCoordBins[i].binCount = 0;
|
|
}
|
|
}
|
|
|
|
|
|
struct TempProcSurface {
|
|
U32 numPoints;
|
|
U32 pointIndices[32];
|
|
U16 planeIndex;
|
|
U8 mask;
|
|
};
|
|
|
|
struct PlaneGrouping {
|
|
U32 numPlanes;
|
|
U16 planeIndices[32];
|
|
U8 mask;
|
|
};
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
void Interior::processHullPolyLists()
|
|
{
|
|
Vector<U16> planeIndices(256, __FILE__, __LINE__);
|
|
Vector<U32> pointIndices(256, __FILE__, __LINE__);
|
|
Vector<U8> pointMasks(256, __FILE__, __LINE__);
|
|
Vector<U8> planeMasks(256, __FILE__, __LINE__);
|
|
Vector<TempProcSurface> tempSurfaces(128, __FILE__, __LINE__);
|
|
Vector<PlaneGrouping> planeGroups(32, __FILE__, __LINE__);
|
|
|
|
// Reserve space in the vectors
|
|
{
|
|
mPolyListStrings.setSize(0);
|
|
mPolyListStrings.reserve(128 << 10);
|
|
|
|
mPolyListPoints.setSize(0);
|
|
mPolyListPoints.reserve(32 << 10);
|
|
|
|
mPolyListPlanes.setSize(0);
|
|
mPolyListPlanes.reserve(16 << 10);
|
|
}
|
|
|
|
for (U32 i = 0; i < mConvexHulls.size(); i++) {
|
|
U32 j, k, l, m;
|
|
|
|
ConvexHull& rHull = mConvexHulls[i];
|
|
|
|
planeIndices.setSize(0);
|
|
pointIndices.setSize(0);
|
|
tempSurfaces.setSize(0);
|
|
|
|
// Extract all the surfaces from this hull into our temporary processing format
|
|
{
|
|
for (j = 0; j < rHull.surfaceCount; j++) {
|
|
tempSurfaces.increment();
|
|
TempProcSurface& temp = tempSurfaces.last();
|
|
|
|
U32 surfaceIndex = mHullSurfaceIndices[j + rHull.surfaceStart];
|
|
if (isNullSurfaceIndex(surfaceIndex)) {
|
|
const NullSurface& rSurface = mNullSurfaces[getNullSurfaceIndex(surfaceIndex)];
|
|
|
|
temp.planeIndex = rSurface.planeIndex;
|
|
temp.numPoints = rSurface.windingCount;
|
|
for (k = 0; k < rSurface.windingCount; k++)
|
|
temp.pointIndices[k] = mWindings[rSurface.windingStart + k];
|
|
} else {
|
|
const Surface& rSurface = mSurfaces[surfaceIndex];
|
|
|
|
temp.planeIndex = rSurface.planeIndex;
|
|
collisionFanFromSurface(rSurface, temp.pointIndices, &temp.numPoints);
|
|
}
|
|
}
|
|
}
|
|
|
|
// First order of business: extract all unique planes and points from
|
|
// the list of surfaces...
|
|
{
|
|
for (j = 0; j < tempSurfaces.size(); j++) {
|
|
const TempProcSurface& rSurface = tempSurfaces[j];
|
|
|
|
bool found = false;
|
|
for (k = 0; k < planeIndices.size() && !found; k++) {
|
|
if (rSurface.planeIndex == planeIndices[k])
|
|
found = true;
|
|
}
|
|
if (!found)
|
|
planeIndices.push_back(rSurface.planeIndex);
|
|
|
|
for (k = 0; k < rSurface.numPoints; k++) {
|
|
found = false;
|
|
for (l = 0; l < pointIndices.size(); l++) {
|
|
if (pointIndices[l] == rSurface.pointIndices[k])
|
|
found = true;
|
|
}
|
|
if (!found)
|
|
pointIndices.push_back(rSurface.pointIndices[k]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now that we have all the unique points and planes, remap the surfaces in
|
|
// terms of the offsets into the unique point list...
|
|
{
|
|
for (j = 0; j < tempSurfaces.size(); j++) {
|
|
TempProcSurface& rSurface = tempSurfaces[j];
|
|
|
|
// Points
|
|
for (k = 0; k < rSurface.numPoints; k++) {
|
|
bool found = false;
|
|
for (l = 0; l < pointIndices.size(); l++) {
|
|
if (pointIndices[l] == rSurface.pointIndices[k]) {
|
|
rSurface.pointIndices[k] = l;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
AssertISV(found, "Error remapping point indices in interior collision processing");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ok, at this point, we have a list of unique points, unique planes, and the
|
|
// surfaces all remapped in those terms. We need to check our error conditions
|
|
// that will make sure that we can properly encode this hull:
|
|
{
|
|
AssertISV(planeIndices.size() < 256, "Error, > 256 planes on an interior hull");
|
|
AssertISV(pointIndices.size() < 63356, "Error, > 65536 points on an interior hull");
|
|
AssertISV(tempSurfaces.size() < 256, "Error, > 256 surfaces on an interior hull");
|
|
}
|
|
|
|
// Now we group the planes together, and merge the closest groups until we're left
|
|
// with <= 8 groups
|
|
{
|
|
planeGroups.setSize(planeIndices.size());
|
|
for (j = 0; j < planeIndices.size(); j++) {
|
|
planeGroups[j].numPlanes = 1;
|
|
planeGroups[j].planeIndices[0] = planeIndices[j];
|
|
}
|
|
|
|
while (planeGroups.size() > 8) {
|
|
// Find the two closest groups. If mdp(i, j) is the value of the
|
|
// largest pairwise dot product that can be computed from the vectors
|
|
// of group i, and group j, then the closest group pair is the one
|
|
// with the smallest value of mdp.
|
|
F32 currmin = 2;
|
|
S32 firstGroup = -1;
|
|
S32 secondGroup = -1;
|
|
|
|
for (j = 0; j < planeGroups.size(); j++) {
|
|
PlaneGrouping& first = planeGroups[j];
|
|
for (k = j + 1; k < planeGroups.size(); k++) {
|
|
PlaneGrouping& second = planeGroups[k];
|
|
|
|
F32 max = -2;
|
|
for (l = 0; l < first.numPlanes; l++) {
|
|
for (m = 0; m < second.numPlanes; m++) {
|
|
Point3F firstNormal = getPlane(first.planeIndices[l]);
|
|
if (planeIsFlipped(first.planeIndices[l]))
|
|
firstNormal.neg();
|
|
Point3F secondNormal = getPlane(second.planeIndices[m]);
|
|
if (planeIsFlipped(second.planeIndices[m]))
|
|
secondNormal.neg();
|
|
|
|
F32 dot = mDot(firstNormal, secondNormal);
|
|
if (dot > max)
|
|
max = dot;
|
|
}
|
|
}
|
|
|
|
if (max < currmin) {
|
|
currmin = max;
|
|
firstGroup = j;
|
|
secondGroup = k;
|
|
}
|
|
}
|
|
}
|
|
AssertFatal(firstGroup != -1 && secondGroup != -1, "Error, unable to find a suitable pairing?");
|
|
|
|
// Merge first and second
|
|
PlaneGrouping& to = planeGroups[firstGroup];
|
|
PlaneGrouping& from = planeGroups[secondGroup];
|
|
while (from.numPlanes != 0) {
|
|
to.planeIndices[to.numPlanes++] = from.planeIndices[from.numPlanes - 1];
|
|
from.numPlanes--;
|
|
}
|
|
|
|
// And remove the merged group
|
|
planeGroups.erase(secondGroup);
|
|
}
|
|
AssertFatal(planeGroups.size() <= 8, "Error, too many plane groupings!");
|
|
|
|
|
|
// Assign a mask to each of the plane groupings
|
|
for (j = 0; j < planeGroups.size(); j++)
|
|
planeGroups[j].mask = (1 << j);
|
|
}
|
|
|
|
// Now, assign the mask to each of the temp polys
|
|
{
|
|
for (j = 0; j < tempSurfaces.size(); j++) {
|
|
bool assigned = false;
|
|
for (k = 0; k < planeGroups.size() && !assigned; k++) {
|
|
for (l = 0; l < planeGroups[k].numPlanes; l++) {
|
|
if (planeGroups[k].planeIndices[l] == tempSurfaces[j].planeIndex) {
|
|
tempSurfaces[j].mask = planeGroups[k].mask;
|
|
assigned = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
AssertFatal(assigned, "Error, missed a plane somewhere in the hull poly list!");
|
|
}
|
|
}
|
|
|
|
// Copy the appropriate group mask to the plane masks
|
|
{
|
|
planeMasks.setSize(planeIndices.size());
|
|
dMemset(planeMasks.address(), 0, planeMasks.size() * sizeof(U8));
|
|
|
|
for (j = 0; j < planeIndices.size(); j++) {
|
|
bool found = false;
|
|
for (k = 0; k < planeGroups.size() && !found; k++) {
|
|
for (l = 0; l < planeGroups[k].numPlanes; l++) {
|
|
if (planeGroups[k].planeIndices[l] == planeIndices[j]) {
|
|
planeMasks[j] = planeGroups[k].mask;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
AssertFatal(planeMasks[j] != 0, "Error, missing mask for plane!");
|
|
}
|
|
}
|
|
|
|
// And whip through the points, constructing the total mask for that point
|
|
{
|
|
pointMasks.setSize(pointIndices.size());
|
|
dMemset(pointMasks.address(), 0, pointMasks.size() * sizeof(U8));
|
|
|
|
for (j = 0; j < pointIndices.size(); j++) {
|
|
for (k = 0; k < tempSurfaces.size(); k++) {
|
|
for (l = 0; l < tempSurfaces[k].numPoints; l++) {
|
|
if (tempSurfaces[k].pointIndices[l] == j) {
|
|
pointMasks[j] |= tempSurfaces[k].mask;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
AssertFatal(pointMasks[j] != 0, "Error, point must exist in at least one surface!");
|
|
}
|
|
}
|
|
|
|
// Create the emit strings, and we're done!
|
|
{
|
|
// Set the range of planes
|
|
rHull.polyListPlaneStart = mPolyListPlanes.size();
|
|
mPolyListPlanes.setSize(rHull.polyListPlaneStart + planeIndices.size());
|
|
for (j = 0; j < planeIndices.size(); j++)
|
|
mPolyListPlanes[j + rHull.polyListPlaneStart] = planeIndices[j];
|
|
|
|
// Set the range of points
|
|
rHull.polyListPointStart = mPolyListPoints.size();
|
|
mPolyListPoints.setSize(rHull.polyListPointStart + pointIndices.size());
|
|
for (j = 0; j < pointIndices.size(); j++)
|
|
mPolyListPoints[j + rHull.polyListPointStart] = pointIndices[j];
|
|
|
|
// Now the emit string. The emit string goes like: (all fields are bytes)
|
|
// NumPlanes (PLMask) * NumPlanes
|
|
// NumPointsHi NumPointsLo (PtMask) * NumPoints
|
|
// NumSurfaces
|
|
// (NumPoints SurfaceMask PlOffset (PtOffsetHi PtOffsetLo) * NumPoints) * NumSurfaces
|
|
//
|
|
U32 stringLen = 1 + planeIndices.size();
|
|
stringLen += 2 + pointIndices.size();
|
|
stringLen += 1;
|
|
for (j = 0; j < tempSurfaces.size(); j++)
|
|
stringLen += 1 + 1 + 1 + (tempSurfaces[j].numPoints * 2);
|
|
|
|
rHull.polyListStringStart = mPolyListStrings.size();
|
|
mPolyListStrings.setSize(rHull.polyListStringStart + stringLen);
|
|
|
|
U8* pString = &mPolyListStrings[rHull.polyListStringStart];
|
|
U32 currPos = 0;
|
|
|
|
// Planes
|
|
pString[currPos++] = planeIndices.size();
|
|
for (j = 0; j < planeIndices.size(); j++)
|
|
pString[currPos++] = planeMasks[j];
|
|
|
|
// Points
|
|
pString[currPos++] = (pointIndices.size() >> 8) & 0xFF;
|
|
pString[currPos++] = (pointIndices.size() >> 0) & 0xFF;
|
|
for (j = 0; j < pointIndices.size(); j++)
|
|
pString[currPos++] = pointMasks[j];
|
|
|
|
// Surfaces
|
|
pString[currPos++] = tempSurfaces.size();
|
|
for (j = 0; j < tempSurfaces.size(); j++) {
|
|
pString[currPos++] = tempSurfaces[j].numPoints;
|
|
pString[currPos++] = tempSurfaces[j].mask;
|
|
|
|
bool found = false;
|
|
for (k = 0; k < planeIndices.size(); k++) {
|
|
if (planeIndices[k] == tempSurfaces[j].planeIndex) {
|
|
pString[currPos++] = k;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
AssertFatal(found, "Error, missing planeindex!");
|
|
|
|
for (k = 0; k < tempSurfaces[j].numPoints; k++) {
|
|
pString[currPos++] = (tempSurfaces[j].pointIndices[k] >> 8) & 0xFF;
|
|
pString[currPos++] = (tempSurfaces[j].pointIndices[k] >> 0) & 0xFF;
|
|
}
|
|
}
|
|
AssertFatal(currPos == stringLen, "Error, mismatched string length!");
|
|
}
|
|
} // for (i = 0; i < mConvexHulls.size(); i++)
|
|
|
|
// Compact the used vectors
|
|
{
|
|
mPolyListStrings.compact();
|
|
mPolyListPoints.compact();
|
|
mPolyListPlanes.compact();
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
void Interior::processVehicleHullPolyLists()
|
|
{
|
|
Vector<U16> planeIndices(256, __FILE__, __LINE__);
|
|
Vector<U32> pointIndices(256, __FILE__, __LINE__);
|
|
Vector<U8> pointMasks(256, __FILE__, __LINE__);
|
|
Vector<U8> planeMasks(256, __FILE__, __LINE__);
|
|
Vector<TempProcSurface> tempSurfaces(128, __FILE__, __LINE__);
|
|
Vector<PlaneGrouping> planeGroups(32, __FILE__, __LINE__);
|
|
|
|
// Reserve space in the vectors
|
|
{
|
|
mVehiclePolyListStrings.setSize(0);
|
|
mVehiclePolyListStrings.reserve(128 << 10);
|
|
|
|
mVehiclePolyListPoints.setSize(0);
|
|
mVehiclePolyListPoints.reserve(32 << 10);
|
|
|
|
mVehiclePolyListPlanes.setSize(0);
|
|
mVehiclePolyListPlanes.reserve(16 << 10);
|
|
}
|
|
|
|
for (U32 i = 0; i < mVehicleConvexHulls.size(); i++) {
|
|
U32 j, k, l, m;
|
|
|
|
ConvexHull& rHull = mVehicleConvexHulls[i];
|
|
|
|
planeIndices.setSize(0);
|
|
pointIndices.setSize(0);
|
|
tempSurfaces.setSize(0);
|
|
|
|
// Extract all the surfaces from this hull into our temporary processing format
|
|
{
|
|
for (j = 0; j < rHull.surfaceCount; j++) {
|
|
tempSurfaces.increment();
|
|
TempProcSurface& temp = tempSurfaces.last();
|
|
|
|
U32 surfaceIndex = mVehicleHullSurfaceIndices[j + rHull.surfaceStart];
|
|
const NullSurface& rSurface = mVehicleNullSurfaces[getVehicleNullSurfaceIndex(surfaceIndex)];
|
|
|
|
temp.planeIndex = rSurface.planeIndex;
|
|
temp.numPoints = rSurface.windingCount;
|
|
for (k = 0; k < rSurface.windingCount; k++)
|
|
temp.pointIndices[k] = mVehicleWindings[rSurface.windingStart + k];
|
|
}
|
|
}
|
|
|
|
// First order of business: extract all unique planes and points from
|
|
// the list of surfaces...
|
|
{
|
|
for (j = 0; j < tempSurfaces.size(); j++) {
|
|
const TempProcSurface& rSurface = tempSurfaces[j];
|
|
|
|
bool found = false;
|
|
for (k = 0; k < planeIndices.size() && !found; k++) {
|
|
if (rSurface.planeIndex == planeIndices[k])
|
|
found = true;
|
|
}
|
|
if (!found)
|
|
planeIndices.push_back(rSurface.planeIndex);
|
|
|
|
for (k = 0; k < rSurface.numPoints; k++) {
|
|
found = false;
|
|
for (l = 0; l < pointIndices.size(); l++) {
|
|
if (pointIndices[l] == rSurface.pointIndices[k])
|
|
found = true;
|
|
}
|
|
if (!found)
|
|
pointIndices.push_back(rSurface.pointIndices[k]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now that we have all the unique points and planes, remap the surfaces in
|
|
// terms of the offsets into the unique point list...
|
|
{
|
|
for (j = 0; j < tempSurfaces.size(); j++) {
|
|
TempProcSurface& rSurface = tempSurfaces[j];
|
|
|
|
// Points
|
|
for (k = 0; k < rSurface.numPoints; k++) {
|
|
bool found = false;
|
|
for (l = 0; l < pointIndices.size(); l++) {
|
|
if (pointIndices[l] == rSurface.pointIndices[k]) {
|
|
rSurface.pointIndices[k] = l;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
AssertISV(found, "Error remapping point indices in interior collision processing");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ok, at this point, we have a list of unique points, unique planes, and the
|
|
// surfaces all remapped in those terms. We need to check our error conditions
|
|
// that will make sure that we can properly encode this hull:
|
|
{
|
|
AssertISV(planeIndices.size() < 256, "Error, > 256 planes on an interior hull");
|
|
AssertISV(pointIndices.size() < 63356, "Error, > 65536 points on an interior hull");
|
|
AssertISV(tempSurfaces.size() < 256, "Error, > 256 surfaces on an interior hull");
|
|
}
|
|
|
|
// Now we group the planes together, and merge the closest groups until we're left
|
|
// with <= 8 groups
|
|
{
|
|
planeGroups.setSize(planeIndices.size());
|
|
for (j = 0; j < planeIndices.size(); j++) {
|
|
planeGroups[j].numPlanes = 1;
|
|
planeGroups[j].planeIndices[0] = planeIndices[j];
|
|
}
|
|
|
|
while (planeGroups.size() > 8) {
|
|
// Find the two closest groups. If mdp(i, j) is the value of the
|
|
// largest pairwise dot product that can be computed from the vectors
|
|
// of group i, and group j, then the closest group pair is the one
|
|
// with the smallest value of mdp.
|
|
F32 currmin = 2;
|
|
S32 firstGroup = -1;
|
|
S32 secondGroup = -1;
|
|
|
|
for (j = 0; j < planeGroups.size(); j++) {
|
|
PlaneGrouping& first = planeGroups[j];
|
|
for (k = j + 1; k < planeGroups.size(); k++) {
|
|
PlaneGrouping& second = planeGroups[k];
|
|
|
|
F32 max = -2;
|
|
for (l = 0; l < first.numPlanes; l++) {
|
|
for (m = 0; m < second.numPlanes; m++) {
|
|
Point3F firstNormal = mVehiclePlanes[first.planeIndices[l]];
|
|
Point3F secondNormal = mVehiclePlanes[second.planeIndices[m]];
|
|
|
|
F32 dot = mDot(firstNormal, secondNormal);
|
|
if (dot > max)
|
|
max = dot;
|
|
}
|
|
}
|
|
|
|
if (max < currmin) {
|
|
currmin = max;
|
|
firstGroup = j;
|
|
secondGroup = k;
|
|
}
|
|
}
|
|
}
|
|
AssertFatal(firstGroup != -1 && secondGroup != -1, "Error, unable to find a suitable pairing?");
|
|
|
|
// Merge first and second
|
|
PlaneGrouping& to = planeGroups[firstGroup];
|
|
PlaneGrouping& from = planeGroups[secondGroup];
|
|
while (from.numPlanes != 0) {
|
|
to.planeIndices[to.numPlanes++] = from.planeIndices[from.numPlanes - 1];
|
|
from.numPlanes--;
|
|
}
|
|
|
|
// And remove the merged group
|
|
planeGroups.erase(secondGroup);
|
|
}
|
|
AssertFatal(planeGroups.size() <= 8, "Error, too many plane groupings!");
|
|
|
|
|
|
// Assign a mask to each of the plane groupings
|
|
for (j = 0; j < planeGroups.size(); j++)
|
|
planeGroups[j].mask = (1 << j);
|
|
}
|
|
|
|
// Now, assign the mask to each of the temp polys
|
|
{
|
|
for (j = 0; j < tempSurfaces.size(); j++) {
|
|
bool assigned = false;
|
|
for (k = 0; k < planeGroups.size() && !assigned; k++) {
|
|
for (l = 0; l < planeGroups[k].numPlanes; l++) {
|
|
if (planeGroups[k].planeIndices[l] == tempSurfaces[j].planeIndex) {
|
|
tempSurfaces[j].mask = planeGroups[k].mask;
|
|
assigned = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
AssertFatal(assigned, "Error, missed a plane somewhere in the hull poly list!");
|
|
}
|
|
}
|
|
|
|
// Copy the appropriate group mask to the plane masks
|
|
{
|
|
planeMasks.setSize(planeIndices.size());
|
|
dMemset(planeMasks.address(), 0, planeMasks.size() * sizeof(U8));
|
|
|
|
for (j = 0; j < planeIndices.size(); j++) {
|
|
bool found = false;
|
|
for (k = 0; k < planeGroups.size() && !found; k++) {
|
|
for (l = 0; l < planeGroups[k].numPlanes; l++) {
|
|
if (planeGroups[k].planeIndices[l] == planeIndices[j]) {
|
|
planeMasks[j] = planeGroups[k].mask;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
AssertFatal(planeMasks[j] != 0, "Error, missing mask for plane!");
|
|
}
|
|
}
|
|
|
|
// And whip through the points, constructing the total mask for that point
|
|
{
|
|
pointMasks.setSize(pointIndices.size());
|
|
dMemset(pointMasks.address(), 0, pointMasks.size() * sizeof(U8));
|
|
|
|
for (j = 0; j < pointIndices.size(); j++) {
|
|
for (k = 0; k < tempSurfaces.size(); k++) {
|
|
for (l = 0; l < tempSurfaces[k].numPoints; l++) {
|
|
if (tempSurfaces[k].pointIndices[l] == j) {
|
|
pointMasks[j] |= tempSurfaces[k].mask;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
AssertFatal(pointMasks[j] != 0, "Error, point must exist in at least one surface!");
|
|
}
|
|
}
|
|
|
|
// Create the emit strings, and we're done!
|
|
{
|
|
// Set the range of planes
|
|
rHull.polyListPlaneStart = mVehiclePolyListPlanes.size();
|
|
mVehiclePolyListPlanes.setSize(rHull.polyListPlaneStart + planeIndices.size());
|
|
for (j = 0; j < planeIndices.size(); j++)
|
|
mVehiclePolyListPlanes[j + rHull.polyListPlaneStart] = planeIndices[j];
|
|
|
|
// Set the range of points
|
|
rHull.polyListPointStart = mVehiclePolyListPoints.size();
|
|
mVehiclePolyListPoints.setSize(rHull.polyListPointStart + pointIndices.size());
|
|
for (j = 0; j < pointIndices.size(); j++)
|
|
mVehiclePolyListPoints[j + rHull.polyListPointStart] = pointIndices[j];
|
|
|
|
// Now the emit string. The emit string goes like: (all fields are bytes)
|
|
// NumPlanes (PLMask) * NumPlanes
|
|
// NumPointsHi NumPointsLo (PtMask) * NumPoints
|
|
// NumSurfaces
|
|
// (NumPoints SurfaceMask PlOffset (PtOffsetHi PtOffsetLo) * NumPoints) * NumSurfaces
|
|
//
|
|
U32 stringLen = 1 + planeIndices.size();
|
|
stringLen += 2 + pointIndices.size();
|
|
stringLen += 1;
|
|
for (j = 0; j < tempSurfaces.size(); j++)
|
|
stringLen += 1 + 1 + 1 + (tempSurfaces[j].numPoints * 2);
|
|
|
|
rHull.polyListStringStart = mVehiclePolyListStrings.size();
|
|
mVehiclePolyListStrings.setSize(rHull.polyListStringStart + stringLen);
|
|
|
|
U8* pString = &mVehiclePolyListStrings[rHull.polyListStringStart];
|
|
U32 currPos = 0;
|
|
|
|
// Planes
|
|
pString[currPos++] = planeIndices.size();
|
|
for (j = 0; j < planeIndices.size(); j++)
|
|
pString[currPos++] = planeMasks[j];
|
|
|
|
// Points
|
|
pString[currPos++] = (pointIndices.size() >> 8) & 0xFF;
|
|
pString[currPos++] = (pointIndices.size() >> 0) & 0xFF;
|
|
for (j = 0; j < pointIndices.size(); j++)
|
|
pString[currPos++] = pointMasks[j];
|
|
|
|
// Surfaces
|
|
pString[currPos++] = tempSurfaces.size();
|
|
for (j = 0; j < tempSurfaces.size(); j++) {
|
|
pString[currPos++] = tempSurfaces[j].numPoints;
|
|
pString[currPos++] = tempSurfaces[j].mask;
|
|
|
|
bool found = false;
|
|
for (k = 0; k < planeIndices.size(); k++) {
|
|
if (planeIndices[k] == tempSurfaces[j].planeIndex) {
|
|
pString[currPos++] = k;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
AssertFatal(found, "Error, missing planeindex!");
|
|
|
|
for (k = 0; k < tempSurfaces[j].numPoints; k++) {
|
|
pString[currPos++] = (tempSurfaces[j].pointIndices[k] >> 8) & 0xFF;
|
|
pString[currPos++] = (tempSurfaces[j].pointIndices[k] >> 0) & 0xFF;
|
|
}
|
|
}
|
|
AssertFatal(currPos == stringLen, "Error, mismatched string length!");
|
|
}
|
|
} // for (i = 0; i < mConvexHulls.size(); i++)
|
|
|
|
// Compact the used vectors
|
|
{
|
|
mVehiclePolyListStrings.compact();
|
|
mVehiclePolyListPoints.compact();
|
|
mVehiclePolyListPlanes.compact();
|
|
}
|
|
}
|
|
|
|
|
|
void Interior::rebuildVertexColors(LM_HANDLE instanceHandle,
|
|
Vector<ColorI>* normal,
|
|
Vector<ColorI>* alarm)
|
|
{
|
|
normal->setSize(mWindings.size());
|
|
if (mHasAlarmState)
|
|
alarm->setSize(mWindings.size());
|
|
else
|
|
alarm->clear();
|
|
|
|
for (U32 i = 0; i < mSurfaces.size(); i++)
|
|
{
|
|
TextureHandle* normalLMapHandle = gInteriorLMManager.getHandle(getLMHandle(), instanceHandle,
|
|
mNormalLMapIndices[i]);
|
|
AssertFatal(normalLMapHandle != NULL, "Bad texture handle in rebuildVertexColors");
|
|
const GBitmap* pNormalLMap = normalLMapHandle->getBitmap();
|
|
AssertFatal(pNormalLMap != NULL, "Bad texture handle in rebuildVertexColors");
|
|
|
|
TextureHandle* alarmLMapHandle = NULL;
|
|
const GBitmap* pAlarmLMap = NULL;
|
|
if (mHasAlarmState)
|
|
{
|
|
alarmLMapHandle = gInteriorLMManager.getHandle(getLMHandle(), instanceHandle,
|
|
mAlarmLMapIndices[i]);
|
|
pAlarmLMap = alarmLMapHandle->getBitmap();
|
|
}
|
|
|
|
const Surface& rSurface = mSurfaces[i];
|
|
const TexGenPlanes& rPlanes = mLMTexGenEQs[i];
|
|
|
|
for (U32 j = rSurface.windingStart; j < rSurface.windingStart + rSurface.windingCount; j++)
|
|
{
|
|
const ItrPaddedPoint& rPoint = mPoints[mWindings[j]];
|
|
ColorI color;
|
|
|
|
F32 s = rPlanes.planeX.distToPlane(rPoint.point);
|
|
F32 t = rPlanes.planeY.distToPlane(rPoint.point);
|
|
|
|
s *= pNormalLMap->getWidth();
|
|
t *= pNormalLMap->getHeight();
|
|
AssertFatal(s >= 0.5 && s <= (F32(pNormalLMap->getWidth()) - 0.5), "Error, bad lmap coord!");
|
|
AssertFatal(t >= 0.5 && t <= (F32(pNormalLMap->getHeight()) - 0.5), "Error, bad lmap coord!");
|
|
s = mFloor(s);
|
|
t = mFloor(t);
|
|
|
|
pNormalLMap->getColor(U32(s), U32(t), color);
|
|
color.alpha = 0xFF;
|
|
(*normal)[j] = color;
|
|
|
|
if (mHasAlarmState)
|
|
{
|
|
pAlarmLMap->getColor(U32(s), U32(t), color);
|
|
color.alpha = 0xFF;
|
|
(*alarm)[j] = color;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
void ZoneVisDeterminer::runFromState(SceneState* state, U32 offset, U32 parentZone)
|
|
{
|
|
mMode = FromState;
|
|
mState = state;
|
|
mZoneRangeOffset = offset;
|
|
mParentZone = parentZone;
|
|
}
|
|
|
|
void ZoneVisDeterminer::runFromRects(SceneState* state, U32 offset, U32 parentZone)
|
|
{
|
|
mMode = FromRects;
|
|
mState = state;
|
|
mZoneRangeOffset = offset;
|
|
mParentZone = parentZone;
|
|
}
|
|
|
|
bool ZoneVisDeterminer::isZoneVisible(const U32 zone) const
|
|
{
|
|
if (zone == 0)
|
|
return mState->getZoneState(mParentZone).render;
|
|
|
|
if (mMode == FromState) {
|
|
return mState->getZoneState(zone + mZoneRangeOffset - 1).render;
|
|
} else {
|
|
return sgZoneRenderInfo[zone].render;
|
|
}
|
|
}
|
|
|
|
void Interior::buildSurfaceZones()
|
|
{
|
|
mSurfaceZone.clear();
|
|
mSurfaceZone.setSize(mSurfaces.size());
|
|
|
|
for(U32 i=0; i<getSurfaceCount(); i++)
|
|
{
|
|
mSurfaceZone[i] = -1;
|
|
}
|
|
|
|
for(U32 z=0; z<mZones.size(); z++)
|
|
{
|
|
Interior::Zone &zone = mZones[z];
|
|
for(U32 s=zone.surfaceStart; s<(zone.surfaceStart + zone.surfaceCount); s++)
|
|
{
|
|
mSurfaceZone[mZoneSurfaces[s]] = (z - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
|