1063 lines
34 KiB
C++
Executable File
1063 lines
34 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "map2difPlus/morianUtil.h"
|
|
#include "map2difPlus/editGeometry.h"
|
|
#include "platform/platformAssert.h"
|
|
#include "math/mMath.h"
|
|
#include "map2difPlus/csgBrush.h"
|
|
|
|
// Global brush arena...
|
|
BrushArena gBrushArena(8192);
|
|
|
|
|
|
|
|
S32 euclidGCD(S32 one, S32 two)
|
|
{
|
|
AssertFatal(one > 0 && two > 0, "Error");
|
|
|
|
S32 m = one >= two ? one : two;
|
|
S32 n = one >= two ? two : one;
|
|
|
|
while (true) {
|
|
S32 test = m % n;
|
|
if (test == 0)
|
|
return n;
|
|
|
|
m = n;
|
|
n = test;
|
|
}
|
|
}
|
|
|
|
S32 planeGCD(S32 x, S32 y, S32 z, S32 d)
|
|
{
|
|
S32* pNonZero = NULL;
|
|
if (x != 0) pNonZero = &x;
|
|
else if (y != 0) pNonZero = &y;
|
|
else if (z != 0) pNonZero = &z;
|
|
else if (d != 0) pNonZero = &d;
|
|
else {
|
|
AssertFatal(false, "Cannot pass all zero to GCD!");
|
|
return 1;
|
|
}
|
|
|
|
if (x == 0) return planeGCD(*pNonZero, y, z, d);
|
|
if (y == 0) return planeGCD(x, *pNonZero, z, d);
|
|
if (z == 0) return planeGCD(x, y, *pNonZero, d);
|
|
if (d == 0) return planeGCD(x, y, z, *pNonZero);
|
|
|
|
if (x < 0) x = -x;
|
|
if (y < 0) y = -y;
|
|
if (z < 0) z = -z;
|
|
if (d < 0) d = -d;
|
|
|
|
// Do a Euclid on these...
|
|
return euclidGCD(euclidGCD(euclidGCD(x, y), z), d);
|
|
}
|
|
|
|
void dumpWinding(const Winding& winding, const char* prefixString)
|
|
{
|
|
for (U32 i = 0; i < winding.numIndices; i++) {
|
|
const Point3D& rPoint = gWorkingGeometry->getPoint(winding.indices[i]);
|
|
dPrintf("%s(%d) %lg, %lg, %lg\n", prefixString, winding.indices[i], rPoint.x, rPoint.y, rPoint.z);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
F64 getWindingSurfaceArea(const Winding& rWinding, U32 planeEQIndex)
|
|
{
|
|
// DMMTODO poly area...
|
|
Point3D areaNormal(0, 0, 0);
|
|
for (U32 i = 0; i < rWinding.numIndices; i++) {
|
|
U32 j = (i + 1) % rWinding.numIndices;
|
|
|
|
Point3D temp;
|
|
mCross(gWorkingGeometry->getPoint(rWinding.indices[i]),
|
|
gWorkingGeometry->getPoint(rWinding.indices[j]), &temp);
|
|
|
|
areaNormal += temp;
|
|
}
|
|
|
|
F64 area = mDot(gWorkingGeometry->getPlaneEQ(planeEQIndex).normal, areaNormal);
|
|
if (area < 0.0)
|
|
area *= -0.5;
|
|
else
|
|
area *= 0.5;
|
|
|
|
return area;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Simple in-place clipper. Should be all that's required...
|
|
//
|
|
bool clipWindingToPlaneFront(Winding* winding, const PlaneEQ& rClipPlane)
|
|
{
|
|
U32 start;
|
|
for (start = 0; start < winding->numIndices; start++) {
|
|
const Point3D& rPoint = gWorkingGeometry->getPoint(winding->indices[start]);
|
|
if (rClipPlane.whichSideLow(rPoint) == PlaneFront)
|
|
break;
|
|
}
|
|
|
|
if (start == winding->numIndices) {
|
|
winding->numIndices = 0;
|
|
return true;
|
|
}
|
|
|
|
U32 finalIndices[MaxWindingPoints];
|
|
U32 numFinalIndices = 0;
|
|
|
|
U32 baseStart = start;
|
|
U32 end = (start + 1) % winding->numIndices;
|
|
|
|
bool modified = false;
|
|
while (end != baseStart) {
|
|
const Point3D& rStartPoint = gWorkingGeometry->getPoint(winding->indices[start]);
|
|
const Point3D& rEndPoint = gWorkingGeometry->getPoint(winding->indices[end]);
|
|
|
|
PlaneSide fSide = rClipPlane.whichSideLow(rStartPoint);
|
|
PlaneSide eSide = rClipPlane.whichSideLow(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
|
|
finalIndices[numFinalIndices++] = winding->indices[start];
|
|
start = end;
|
|
end = (end + 1) % winding->numIndices;
|
|
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...
|
|
//
|
|
finalIndices[numFinalIndices++] = winding->indices[start];
|
|
|
|
Point3D vector = rEndPoint - rStartPoint;
|
|
F64 t = -(rClipPlane.distanceToPlane(rStartPoint) / mDot(rClipPlane.normal, vector));
|
|
|
|
Point3D intersection = rStartPoint + (vector * t);
|
|
AssertFatal(rClipPlane.whichSideLow(intersection) == PlaneOn,
|
|
"CSGPlane::clipWindingToPlaneFront: error in computing intersection");
|
|
finalIndices[numFinalIndices++] = gWorkingGeometry->insertPoint(intersection);
|
|
|
|
U32 endSeek = (end + 1) % winding->numIndices;
|
|
while (rClipPlane.whichSideLow(gWorkingGeometry->getPoint(winding->indices[endSeek])) == PlaneBack)
|
|
endSeek = (endSeek + 1) % winding->numIndices;
|
|
|
|
PlaneSide esSide = rClipPlane.whichSideLow(gWorkingGeometry->getPoint(winding->indices[endSeek]));
|
|
if (esSide == PlaneFront) {
|
|
end = endSeek;
|
|
start = (end + (winding->numIndices - 1)) % winding->numIndices;
|
|
|
|
const Point3D& rNewStartPoint = gWorkingGeometry->getPoint(winding->indices[start]);
|
|
const Point3D& rNewEndPoint = gWorkingGeometry->getPoint(winding->indices[end]);
|
|
|
|
vector = rNewEndPoint - rNewStartPoint;
|
|
t = -(rClipPlane.distanceToPlane(rNewStartPoint) / mDot(rClipPlane.normal, vector));
|
|
|
|
intersection = rNewStartPoint + (vector * t);
|
|
AssertFatal(rClipPlane.whichSideLow(intersection) == PlaneOn,
|
|
"CSGPlane::clipWindingToPlaneFront: error in computing intersection");
|
|
|
|
winding->indices[start] = gWorkingGeometry->insertPoint(intersection);
|
|
AssertFatal(winding->indices[start] != winding->indices[end], "Error");
|
|
} else {
|
|
start = endSeek;
|
|
end = (endSeek + 1) % winding->numIndices;
|
|
}
|
|
modified = true;
|
|
}
|
|
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...
|
|
//
|
|
finalIndices[numFinalIndices++] = winding->indices[start];
|
|
|
|
U32 endSeek = (end + 1) % winding->numIndices;
|
|
while (rClipPlane.whichSideLow(gWorkingGeometry->getPoint(winding->indices[endSeek])) == PlaneBack)
|
|
endSeek = (endSeek + 1) % winding->numIndices;
|
|
|
|
PlaneSide esSide = rClipPlane.whichSideLow(gWorkingGeometry->getPoint(winding->indices[endSeek]));
|
|
if (esSide == PlaneFront) {
|
|
end = endSeek;
|
|
start = (end + (winding->numIndices - 1)) % winding->numIndices;
|
|
|
|
const Point3D& rNewStartPoint = gWorkingGeometry->getPoint(winding->indices[start]);
|
|
const Point3D& rNewEndPoint = gWorkingGeometry->getPoint(winding->indices[end]);
|
|
|
|
Point3D vector = rNewEndPoint - rNewStartPoint;
|
|
F64 t = -(rClipPlane.distanceToPlane(rNewStartPoint) / mDot(rClipPlane.normal, vector));
|
|
|
|
Point3D intersection = rNewStartPoint + (vector * t);
|
|
AssertFatal(rClipPlane.whichSideLow(intersection) == PlaneOn,
|
|
"CSGPlane::clipWindingToPlaneFront: error in computing intersection");
|
|
|
|
winding->indices[start] = gWorkingGeometry->insertPoint(intersection);
|
|
AssertFatal(winding->indices[start] != winding->indices[end], "Error");
|
|
} else {
|
|
start = endSeek;
|
|
end = (endSeek + 1) % winding->numIndices;
|
|
}
|
|
modified = true;
|
|
}
|
|
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.
|
|
AssertFatal(rClipPlane.whichSideLow(gWorkingGeometry->getPoint(winding->indices[start])) != PlaneBack,
|
|
"CSGPlane::clipWindingToPlaneFront: bad final point in clipper");
|
|
finalIndices[numFinalIndices++] = winding->indices[start];
|
|
AssertFatal(numFinalIndices >= 3, "Error, line out of clip!");
|
|
|
|
// Copy the new rWinding, and we're set!
|
|
//
|
|
dMemcpy(winding->indices, finalIndices, numFinalIndices * sizeof(U32));
|
|
winding->numIndices = numFinalIndices;
|
|
AssertISV(winding->numIndices <= MaxWindingPoints,
|
|
avar("Increase maxWindingPoints. Talk to DMoore (%d, %d)",
|
|
MaxWindingPoints, numFinalIndices));
|
|
|
|
// DEBUG POINT: No colinear points in the winding
|
|
// DEBUG POINT: all points unique in the winding
|
|
|
|
return modified;
|
|
}
|
|
|
|
|
|
bool clipWindingToPlaneFront(Winding* winding, U32 planeEQIndex)
|
|
{
|
|
// DEBUG POINT: winding contains no colinear points...
|
|
|
|
const PlaneEQ& rClipPlane = gWorkingGeometry->getPlaneEQ(planeEQIndex);
|
|
return clipWindingToPlaneFront(winding, rClipPlane);
|
|
}
|
|
|
|
|
|
U32 windingWhichSide(const Winding& rWinding, U32 windingPlaneIndex, U32 planeIndex)
|
|
{
|
|
AssertFatal(rWinding.numIndices > 2, "Error, winding with invalid number of indices...");
|
|
|
|
if (gWorkingGeometry->isCoplanar(windingPlaneIndex, planeIndex))
|
|
return PlaneSpecialOn;
|
|
|
|
const PlaneEQ& rPlane = gWorkingGeometry->getPlaneEQ(planeIndex);
|
|
|
|
U32 retSide = 0;
|
|
for (U32 i = 0; i < rWinding.numIndices; i++) {
|
|
switch (rPlane.whichSideLow(gWorkingGeometry->getPoint(rWinding.indices[i]))) {
|
|
case PlaneOn:
|
|
retSide |= PlaneSpecialOn;
|
|
break;
|
|
case PlaneFront:
|
|
retSide |= PlaneSpecialFront;
|
|
break;
|
|
case PlaneBack:
|
|
retSide |= PlaneSpecialBack;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return retSide;
|
|
}
|
|
|
|
struct EdgeConnection {
|
|
U32 start;
|
|
U32 end;
|
|
} boxEdges[] = {
|
|
{ 0, 1 },
|
|
{ 1, 2 },
|
|
{ 2, 3 },
|
|
{ 3, 0 },
|
|
{ 4, 5 },
|
|
{ 5, 6 },
|
|
{ 6, 7 },
|
|
{ 7, 4 },
|
|
{ 3, 7 },
|
|
{ 0, 4 },
|
|
{ 1, 5 },
|
|
{ 2, 6 }
|
|
};
|
|
|
|
void createBoundedWinding(const Point3D& minBound,
|
|
const Point3D& maxBound,
|
|
U32 planeEQIndex,
|
|
Winding* finalWinding)
|
|
{
|
|
Point3D boxPoints[8];
|
|
boxPoints[0].set(minBound.x, minBound.y, minBound.z);
|
|
boxPoints[1].set(maxBound.x, minBound.y, minBound.z);
|
|
boxPoints[2].set(maxBound.x, minBound.y, maxBound.z);
|
|
boxPoints[3].set(minBound.x, minBound.y, maxBound.z);
|
|
boxPoints[4].set(minBound.x, maxBound.y, minBound.z);
|
|
boxPoints[5].set(maxBound.x, maxBound.y, minBound.z);
|
|
boxPoints[6].set(maxBound.x, maxBound.y, maxBound.z);
|
|
boxPoints[7].set(minBound.x, maxBound.y, maxBound.z);
|
|
|
|
const PlaneEQ& rPlaneEQ = gWorkingGeometry->getPlaneEQ(planeEQIndex);
|
|
|
|
PlaneSide sides[8];
|
|
for (U32 i = 0; i < 8; i++)
|
|
sides[i] = rPlaneEQ.whichSide(boxPoints[i]);
|
|
|
|
UniqueVector unorderedWinding;
|
|
for (U32 i = 0; i < 8; i++)
|
|
if (sides[i] == PlaneOn)
|
|
unorderedWinding.pushBackUnique(gWorkingGeometry->insertPoint(boxPoints[i]));
|
|
|
|
for (U32 i = 0; i < sizeof(boxEdges) / sizeof(EdgeConnection); i++) {
|
|
if ((sides[boxEdges[i].start] == PlaneBack && sides[boxEdges[i].end] == PlaneFront) ||
|
|
(sides[boxEdges[i].end] == PlaneBack && sides[boxEdges[i].start] == PlaneFront)) {
|
|
// Emit the intersection...
|
|
//
|
|
Point3D vector = boxPoints[boxEdges[i].end] - boxPoints[boxEdges[i].start];
|
|
F64 t = -(rPlaneEQ.distanceToPlane(boxPoints[boxEdges[i].start]) / mDot(rPlaneEQ.normal, vector));
|
|
|
|
Point3D intersection = boxPoints[boxEdges[i].start] + (vector * t);
|
|
unorderedWinding.pushBackUnique(gWorkingGeometry->insertPoint(intersection));
|
|
}
|
|
}
|
|
|
|
if (unorderedWinding.size() <= 2) {
|
|
AssertISV(false, "createBoundedWinding: Bad face on brush. < 3 points. DMMNOTE: Handle better?");
|
|
finalWinding->numIndices = 0;
|
|
return;
|
|
}
|
|
|
|
Vector<U32> modPoints = unorderedWinding;
|
|
|
|
// Create a centroid first...
|
|
Point3D centroid(0, 0, 0);
|
|
for (U32 i = 0; i < modPoints.size(); i++)
|
|
centroid += gWorkingGeometry->getPoint(modPoints[i]);
|
|
centroid /= F64(modPoints.size());
|
|
|
|
// Ok, we have a centroid. We (arbitrarily) take the last point, and start from
|
|
// there...
|
|
finalWinding->indices[0] = modPoints[modPoints.size() - 1];
|
|
finalWinding->numIndices = 1;
|
|
modPoints.erase(modPoints.size() - 1);
|
|
|
|
while (modPoints.size() != 0) {
|
|
// Find the largest dot product of a point with a positive facing
|
|
// cross product.
|
|
//
|
|
F64 maxDot = -1e10;
|
|
S32 maxIndex = -1;
|
|
|
|
const Point3D& currPoint = gWorkingGeometry->getPoint(finalWinding->indices[finalWinding->numIndices - 1]);
|
|
Point3D currVector = currPoint - centroid;
|
|
currVector.normalize();
|
|
|
|
for (U32 i = 0; i < modPoints.size(); i++) {
|
|
const Point3D& testPoint = gWorkingGeometry->getPoint(modPoints[i]);
|
|
Point3D testVector = testPoint - centroid;
|
|
testVector.normalize();
|
|
|
|
Point3D crossVector;
|
|
mCross(currVector, testVector, &crossVector);
|
|
|
|
if (mDot(crossVector, rPlaneEQ.normal) < 0.0) {
|
|
// Candidate
|
|
F32 val = mDot(testVector, currVector);
|
|
if (val > maxDot) {
|
|
maxDot = val;
|
|
maxIndex = i;
|
|
}
|
|
}
|
|
}
|
|
AssertISV(maxIndex != -1, "createBoundedWinding: no point found for winding?");
|
|
|
|
finalWinding->indices[finalWinding->numIndices++] = modPoints[maxIndex];
|
|
modPoints.erase(maxIndex);
|
|
}
|
|
}
|
|
|
|
|
|
struct FooeyDebug {
|
|
Point3D p;
|
|
F64 d;
|
|
};
|
|
|
|
void splitBrush(CSGBrush*& inBrush,
|
|
U32 planeEQIndex,
|
|
CSGBrush*& outFront,
|
|
CSGBrush*& outBack)
|
|
{
|
|
//AssertFatal(outFront == NULL && outBack == NULL, "Must be null on entry");
|
|
|
|
//CSGBrush* pFront = gBrushArena.allocateBrush();
|
|
//CSGBrush* pBack = gBrushArena.allocateBrush();
|
|
|
|
//ConvexBrush brush, fbrush, bbrush;
|
|
|
|
//CopyCSGToConvex(inBrush, &brush);
|
|
|
|
//const PlaneEQ& rPlaneEQ = gWorkingGeometry->getPlaneEQ(planeEQIndex);
|
|
//PlaneF plane(rPlaneEQ.normal.x, rPlaneEQ.normal.y, rPlaneEQ.normal.z, rPlaneEQ.dist);
|
|
|
|
//brush.splitBrush(plane, &fbrush, &bbrush);
|
|
|
|
//if (fbrush.mStatus == ConvexBrush::Good)
|
|
//{
|
|
// CopyConvexToCSG(&fbrush, pFront);
|
|
|
|
// for (U32 i = 0; i < pFront->mPlanes.size(); i++)
|
|
// {
|
|
// for (U32 j = 0; j < inBrush->mPlanes.size(); j++)
|
|
// {
|
|
// if (pFront->mPlanes[i].planeEQIndex == inBrush->mPlanes[j].planeEQIndex)
|
|
// pFront->mPlanes[i] = inBrush->mPlanes[j];
|
|
// }
|
|
// }
|
|
|
|
// pFront->mIsAmbiguous = inBrush->mIsAmbiguous;
|
|
// pFront->mBrushType = inBrush->mBrushType;
|
|
// pFront->mMinBound = inBrush->mMinBound;
|
|
// pFront->mMaxBound = inBrush->mMaxBound;
|
|
|
|
// pFront->brushId = inBrush->brushId;
|
|
// pFront->materialType = inBrush->materialType;
|
|
|
|
// outFront = pFront;
|
|
//}
|
|
//else
|
|
//{
|
|
// gBrushArena.freeBrush(pFront);
|
|
// pFront = NULL;
|
|
//}
|
|
|
|
//if (bbrush.mStatus == ConvexBrush::Good)
|
|
//{
|
|
// CopyConvexToCSG(&bbrush, pBack);
|
|
|
|
// for (U32 i = 0; i < pBack->mPlanes.size(); i++)
|
|
// {
|
|
// for (U32 j = 0; j < inBrush->mPlanes.size(); j++)
|
|
// {
|
|
// if (pBack->mPlanes[i].planeEQIndex == inBrush->mPlanes[j].planeEQIndex)
|
|
// pBack->mPlanes[i] = inBrush->mPlanes[j];
|
|
// }
|
|
// }
|
|
|
|
// pBack->mIsAmbiguous = inBrush->mIsAmbiguous;
|
|
// pBack->mBrushType = inBrush->mBrushType;
|
|
// pBack->mMinBound = inBrush->mMinBound;
|
|
// pBack->mMaxBound = inBrush->mMaxBound;
|
|
|
|
// pBack->brushId = inBrush->brushId;
|
|
// pBack->materialType = inBrush->materialType;
|
|
|
|
// outBack = pBack;
|
|
//}
|
|
//else
|
|
//{
|
|
// gBrushArena.freeBrush(pBack);
|
|
// pBack = NULL;
|
|
//}
|
|
|
|
AssertFatal(outFront == NULL && outBack == NULL, "Must be null on entry");
|
|
|
|
CSGBrush* pFront = gBrushArena.allocateBrush();
|
|
CSGBrush* pBack = gBrushArena.allocateBrush();
|
|
|
|
pFront->copyBrush(inBrush);
|
|
pBack->copyBrush(inBrush);
|
|
|
|
bool regen = false;
|
|
for (S32 i = S32(pFront->mPlanes.size()) - 1; i >= 0; i--)
|
|
{
|
|
CSGPlane& rPlane = pFront->mPlanes[i];
|
|
|
|
if (rPlane.planeEQIndex == planeEQIndex ||
|
|
rPlane.planeEQIndex == gWorkingGeometry->getPlaneInverse(planeEQIndex))
|
|
continue;
|
|
|
|
if (rPlane.clipWindingToPlaneFront(planeEQIndex) == true)
|
|
{
|
|
// Winding was modified
|
|
regen = true;
|
|
|
|
if (rPlane.winding.numIndices == 0) {
|
|
// Plane is completely gone. nuke it.
|
|
pFront->mPlanes.erase(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pFront->mPlanes.size() <= 1)
|
|
{
|
|
// Nuke it.
|
|
gBrushArena.freeBrush(pFront);
|
|
pFront = NULL;
|
|
}
|
|
else
|
|
{
|
|
// Otherwise, we have a good brush still. Check to see if we need to add
|
|
// a plane, and regen, or can just return it...
|
|
if (regen == true) {
|
|
pFront->mPlanes.increment();
|
|
CSGPlane& rPlane = pFront->mPlanes.last();
|
|
|
|
rPlane.planeEQIndex = gWorkingGeometry->getPlaneInverse(planeEQIndex);
|
|
rPlane.pTextureName = NULL;
|
|
rPlane.flags = 0;
|
|
|
|
for (U32 i = 0; i < pFront->mPlanes.size(); i++)
|
|
pFront->mPlanes[i].winding.numIndices = 0;
|
|
|
|
pFront->selfClip();
|
|
}
|
|
|
|
outFront = pFront;
|
|
}
|
|
|
|
//-------------------------------------- now do the back...
|
|
regen = false;
|
|
for (S32 i = S32(pBack->mPlanes.size()) - 1; i >= 0; i--)
|
|
{
|
|
CSGPlane& rPlane = pBack->mPlanes[i];
|
|
|
|
if (rPlane.planeEQIndex == planeEQIndex ||
|
|
rPlane.planeEQIndex == gWorkingGeometry->getPlaneInverse(planeEQIndex))
|
|
continue;
|
|
|
|
if (rPlane.clipWindingToPlaneFront(gWorkingGeometry->getPlaneInverse(planeEQIndex)) == true)
|
|
{
|
|
// Winding was modified
|
|
regen = true;
|
|
|
|
if (rPlane.winding.numIndices == 0)
|
|
{
|
|
// Plane is completely gone. nuke it.
|
|
pBack->mPlanes.erase(i);
|
|
}
|
|
}
|
|
}
|
|
if (pBack->mPlanes.size() <= 1)
|
|
{
|
|
// Nuke it.
|
|
gBrushArena.freeBrush(pBack);
|
|
pBack = NULL;
|
|
}
|
|
else
|
|
{
|
|
// Otherwise, we have a good brush still. Check to see if we need to add
|
|
// a plane, and regen, or can just return it...
|
|
if (regen == true)
|
|
{
|
|
pBack->mPlanes.increment();
|
|
CSGPlane& rPlane = pBack->mPlanes.last();
|
|
|
|
rPlane.planeEQIndex = planeEQIndex;
|
|
rPlane.pTextureName = NULL;
|
|
rPlane.flags = 0;
|
|
|
|
for (U32 i = 0; i < pBack->mPlanes.size(); i++)
|
|
pBack->mPlanes[i].winding.numIndices = 0;
|
|
|
|
pBack->selfClip();
|
|
}
|
|
|
|
outBack = pBack;
|
|
}
|
|
}
|
|
|
|
|
|
void assessPlane(const U32 testPlane,
|
|
const CSGBrush& rTestBrush,
|
|
S32* numCoplanar,
|
|
S32* numTinyWindings,
|
|
S32* numSplits,
|
|
S32* numFront,
|
|
S32* numBack)
|
|
{
|
|
for (U32 i = 0; i < rTestBrush.mPlanes.size(); i++) {
|
|
if (gWorkingGeometry->isCoplanar(testPlane, rTestBrush.mPlanes[i].planeEQIndex)) {
|
|
// Easy. Brush abuts the plane.
|
|
(*numCoplanar)++;
|
|
if (testPlane == rTestBrush.mPlanes[i].planeEQIndex)
|
|
(*numBack)++;
|
|
else
|
|
(*numFront)++;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Ah well, more work...
|
|
const PlaneEQ& rPlane = gWorkingGeometry->getPlaneEQ(testPlane);
|
|
|
|
static UniqueVector uniquePoints(64);
|
|
for (U32 i = 0; i < rTestBrush.mPlanes.size(); i++)
|
|
{
|
|
U32 numIdx = rTestBrush.mPlanes[i].winding.numIndices;
|
|
for (U32 j = 0; j < rTestBrush.mPlanes[i].winding.numIndices; j++)
|
|
{
|
|
U32 idx = rTestBrush.mPlanes[i].winding.indices[j];
|
|
uniquePoints.pushBackUnique(rTestBrush.mPlanes[i].winding.indices[j]);
|
|
}
|
|
}
|
|
|
|
F64 maxFront = 0.0;
|
|
F64 minBack = 0.0;
|
|
|
|
for (U32 i = 0; i < uniquePoints.size(); i++) {
|
|
const Point3D& rPoint = gWorkingGeometry->getPoint(uniquePoints[i]);
|
|
|
|
F64 dist = rPlane.distanceToPlane(rPoint);
|
|
if (dist > maxFront)
|
|
maxFront = dist;
|
|
if (dist < minBack)
|
|
minBack = dist;
|
|
}
|
|
|
|
//AssertFatal(maxFront > gcPlaneDistanceEpsilon || minBack < -gcPlaneDistanceEpsilon,
|
|
// "This should only happen for coplanar windings...");
|
|
|
|
if (maxFront > gcPlaneDistanceEpsilon)
|
|
(*numFront)++;
|
|
|
|
if (minBack < -gcPlaneDistanceEpsilon)
|
|
(*numBack)++;
|
|
|
|
if (maxFront > gcPlaneDistanceEpsilon &&
|
|
minBack < -gcPlaneDistanceEpsilon)
|
|
(*numSplits)++;
|
|
|
|
|
|
if ((maxFront > 0.0 && maxFront < 1.0) ||
|
|
(minBack < 0.0 && minBack > -1.0))
|
|
(*numTinyWindings)++;
|
|
|
|
// done.
|
|
uniquePoints.clear();
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
bool windingsEquivalent(const Winding& winding1, const Winding& winding2)
|
|
{
|
|
if (winding1.numIndices != winding2.numIndices)
|
|
return false;
|
|
|
|
U32 oneCurr = 0;
|
|
S32 twoCurr = -1;
|
|
for (U32 i = 0; i < winding2.numIndices; i++) {
|
|
if (winding2.indices[i] == winding1.indices[0]) {
|
|
twoCurr = i;
|
|
break;
|
|
}
|
|
}
|
|
if (twoCurr == -1)
|
|
return false;
|
|
|
|
for (U32 i = 0; i < winding1.numIndices; i++) {
|
|
if (winding1.indices[oneCurr] != winding2.indices[twoCurr])
|
|
return false;
|
|
|
|
oneCurr = (oneCurr + 1) % winding1.numIndices;
|
|
twoCurr = (twoCurr + 1) % winding2.numIndices;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
bool canCollapseWinding(const Winding& winding1, const Winding& winding2)
|
|
{
|
|
// We are looking for an edge that is backwards in winding2 that is present
|
|
// in winding1. Note that this is not a general purpose algorithm, it's
|
|
// very tailored to the break*Winding function from the BSP.
|
|
//
|
|
AssertFatal(winding1.numIndices >= 3 && winding2.numIndices >= 3, "improper windings");
|
|
|
|
// First, lets check the ambient light condition...
|
|
if (winding1.numZoneIds == 0 || winding2.numZoneIds == 0)
|
|
return false;
|
|
|
|
if (gWorkingGeometry->mZones[winding1.zoneIds[0]]->ambientLit !=
|
|
gWorkingGeometry->mZones[winding2.zoneIds[0]]->ambientLit)
|
|
return false;
|
|
|
|
for (U32 i = 0; i < winding1.numIndices; i++) {
|
|
U32 edgeStart = winding1.indices[(i + 1) % winding1.numIndices];
|
|
U32 edgeEnd = winding1.indices[i];
|
|
|
|
for (U32 j = 0; j < winding2.numIndices; j++) {
|
|
if (winding2.indices[j] == edgeStart &&
|
|
winding2.indices[(j + 1) % winding2.numIndices] == edgeEnd) {
|
|
// Yay!
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
bool pointsAreColinear(U32 p1, U32 p2, U32 p3)
|
|
{
|
|
const Point3D& rP1 = gWorkingGeometry->getPoint(p1);
|
|
const Point3D& rP2 = gWorkingGeometry->getPoint(p2);
|
|
const Point3D& rP3 = gWorkingGeometry->getPoint(p3);
|
|
|
|
// We are testing the distance from p3 to the line defined
|
|
// by p1 and p2.
|
|
//
|
|
F64 f = rP2.x - rP1.x;
|
|
F64 g = rP2.y - rP1.y;
|
|
F64 h = rP2.z - rP1.z;
|
|
|
|
// P0 in our case is rP1. Note the the following is taken from
|
|
// "A Programmer's Geometry"
|
|
//
|
|
F64 denom = f*f + g*g + h*h;
|
|
if (denom < gcPlaneDistanceEpsilon * gcPlaneDistanceEpsilon) {
|
|
// If two points are the same, then all three are colinear...
|
|
return true;
|
|
}
|
|
|
|
F64 xj0 = rP3.x - rP1.x;
|
|
F64 yj0 = rP3.y - rP1.y;
|
|
F64 zj0 = rP3.z - rP1.z;
|
|
|
|
F64 fygx = f * yj0 - g * xj0;
|
|
F64 fzhx = f * zj0 - h * xj0;
|
|
F64 gzhy = g * zj0 - h * yj0;
|
|
|
|
F64 v1 = g * fygx + h * fzhx;
|
|
F64 v2 = h * gzhy - f * fygx;
|
|
F64 v3 = -f * fzhx - g * gzhy;
|
|
|
|
F64 dist = mSqrt(v1*v1 + v2*v2 + v3*v3) / denom;
|
|
|
|
if (dist < gcPlaneDistanceEpsilon)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
bool collapseWindings(Winding& into, const Winding& from)
|
|
{
|
|
if (canCollapseWinding(into, from) == false)
|
|
return false;
|
|
|
|
// DEBUG POINT: Check that into contains no colinear
|
|
// DEBUG POINT: Check that from contains no colinear
|
|
|
|
U32 oneStart;
|
|
U32 twoStart;
|
|
|
|
bool found = false;
|
|
for (U32 i = 0; i < into.numIndices && !found; i++) {
|
|
U32 edgeStart = into.indices[(i + 1) % into.numIndices];
|
|
U32 edgeEnd = into.indices[i];
|
|
|
|
for (U32 j = 0; j < from.numIndices && !found; j++) {
|
|
if (from.indices[j] == edgeStart &&
|
|
from.indices[(j + 1) % from.numIndices] == edgeEnd) {
|
|
found = true;
|
|
oneStart = i;
|
|
twoStart = j;
|
|
}
|
|
}
|
|
}
|
|
AssertFatal(found == true, "error, something missing in dodge city, no common edge!");
|
|
|
|
AssertFatal((into.numIndices + from.numIndices - 2) < MaxWindingPoints,
|
|
"Uh, need to increase MaxWindingPoints. Talk to DMoore");
|
|
U32 newIndices[64];
|
|
U32 newCount = 0;
|
|
|
|
for (U32 i = 0; i <= oneStart; i++) {
|
|
newIndices[newCount++] = into.indices[i];
|
|
}
|
|
for (U32 i = (twoStart + 2) % from.numIndices; i != twoStart;) {
|
|
newIndices[newCount++] = from.indices[i];
|
|
|
|
i = (i+1) % from.numIndices;
|
|
}
|
|
for (U32 i = (oneStart + 1) % into.numIndices; i > 0;) {
|
|
newIndices[newCount++] = into.indices[i];
|
|
|
|
i = (i+1) % into.numIndices;
|
|
}
|
|
|
|
// Remove any colinear edges...
|
|
for (U32 i = 0; i < newCount; i++) {
|
|
U32 index0 = i;
|
|
U32 index1 = (i + 1) % newCount;
|
|
U32 index2 = (i + 2) % newCount;
|
|
|
|
if (pointsAreColinear(newIndices[index0], newIndices[index1], newIndices[index2])) {
|
|
// Remove the edge...
|
|
U32 size = (newCount - index1 - 1) * sizeof(U32);
|
|
dMemmove(&newIndices[index1], &newIndices[index1+1], size);
|
|
newCount--;
|
|
}
|
|
}
|
|
AssertFatal(newCount >= 3 && newCount <= MaxWindingPoints, "Ah, crap, something goofed in collapseWindings. Talk to DMoore");
|
|
|
|
// See if the new winding is convex
|
|
Point3D reference;
|
|
for (U32 i = 0; i < newCount; i++) {
|
|
U32 j = (i + 1) % newCount;
|
|
U32 k = (i + 2) % newCount;
|
|
|
|
Point3D vec1 = gWorkingGeometry->getPoint(newIndices[i]) - gWorkingGeometry->getPoint(newIndices[j]);
|
|
Point3D vec2 = gWorkingGeometry->getPoint(newIndices[k]) - gWorkingGeometry->getPoint(newIndices[j]);
|
|
|
|
Point3D result;
|
|
mCross(vec1, vec2, &result);
|
|
if (i == 0) {
|
|
reference = result;
|
|
} else {
|
|
F64 dot = mDot(reference, result);
|
|
if (dot < 0.0) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy the new rWinding, and we're set!
|
|
//
|
|
dMemcpy(into.indices, newIndices, newCount * sizeof(U32));
|
|
into.numIndices = newCount;
|
|
|
|
// Make sure the nodes are merged
|
|
for (U32 i = 0; i < from.numNodes; i++) {
|
|
bool found = false;
|
|
for (U32 j = 0; j < into.numNodes; j++) {
|
|
if (into.solidNodes[j] == from.solidNodes[i]) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (found == false) {
|
|
AssertFatal(into.numNodes < MaxWindingNodes, "Error, too many solid nodes affecting poly. Plase ask DMoore to increase the allowed number!");
|
|
into.solidNodes[into.numNodes++] = from.solidNodes[i];
|
|
}
|
|
}
|
|
for (U32 i = 0; i < from.numZoneIds; i++)
|
|
into.insertZone(from.zoneIds[i]);
|
|
|
|
// DEBUG POINT: into must contain no colinear
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
void extendName(char* pName, const char* pExt)
|
|
{
|
|
AssertFatal(pName != NULL && dStrlen(pName) != 0 && pExt != NULL && pExt[0] == '.',
|
|
"Error, bad inputs to reextendName");
|
|
|
|
dStrcat(pName, pExt);
|
|
}
|
|
|
|
|
|
void reextendName(char* pName, const char* pExt)
|
|
{
|
|
AssertFatal(pName != NULL && dStrlen(pName) != 0 && pExt != NULL && pExt[0] == '.',
|
|
"Error, bad inputs to reextendName");
|
|
|
|
char* pDot = dStrrchr(pName, '.');
|
|
if (pDot != NULL) {
|
|
if (dStricmp(pDot, pExt) != 0) {
|
|
dStrcpy(pDot, pExt);
|
|
}
|
|
} else {
|
|
dStrcat(pName, pExt);
|
|
}
|
|
}
|
|
|
|
bool createBaseWinding(const Vector<U32>& rPoints, const Point3D& normal, Winding* pWinding)
|
|
{
|
|
if (rPoints.size() <= 2) {
|
|
dPrintf("::createBaseWinding: Bad face on brush. < 3 points. DMMNOTE: Handle better?\n");
|
|
return false;
|
|
}
|
|
if (rPoints.size() > 32) {
|
|
dPrintf("::createBaseWinding: Bad face on brush. > 32 points. DMMNOTE: Increase max winding points?\n");
|
|
return false;
|
|
}
|
|
|
|
|
|
Vector<U32> modPoints = rPoints;
|
|
|
|
// Create a centroid first...
|
|
Point3D centroid(0, 0, 0);
|
|
for (U32 i = 0; i < modPoints.size(); i++)
|
|
centroid += gWorkingGeometry->getPoint(modPoints[i]);
|
|
centroid /= F64(modPoints.size());
|
|
|
|
// Ok, we have a centroid. We (arbitrarily) take the last point, and start from
|
|
// there...
|
|
pWinding->indices[0] = modPoints[modPoints.size() - 1];
|
|
pWinding->numIndices = 1;
|
|
modPoints.erase(modPoints.size() - 1);
|
|
|
|
while (modPoints.size() != 0) {
|
|
// Find the largest dot product of a point with a positive facing
|
|
// cross product.
|
|
//
|
|
F64 maxDot = -1e10;
|
|
S32 maxIndex = -1;
|
|
|
|
const Point3D& currPoint = gWorkingGeometry->getPoint(pWinding->indices[pWinding->numIndices - 1]);
|
|
Point3D currVector = currPoint - centroid;
|
|
currVector.normalize();
|
|
|
|
for (U32 i = 0; i < modPoints.size(); i++) {
|
|
|
|
const Point3D& testPoint = gWorkingGeometry->getPoint(modPoints[i]);
|
|
Point3D testVector = testPoint - centroid;
|
|
testVector.normalize();
|
|
|
|
Point3D crossVector;
|
|
mCross(currVector, testVector, &crossVector);
|
|
crossVector.normalize();
|
|
|
|
if (mDot(crossVector, normal) < 0.0) {
|
|
// Candidate
|
|
F64 val = mDot(testVector, currVector);
|
|
F64 dVal = mDot(testVector, currVector);
|
|
|
|
if (val > maxDot) {
|
|
maxDot = val;
|
|
maxIndex = i;
|
|
}
|
|
}
|
|
}
|
|
//AssertISV(maxIndex != -1, "::createBaseWinding: no point found for winding?");
|
|
if(maxIndex == -1)
|
|
return false;
|
|
|
|
pWinding->indices[pWinding->numIndices++] = modPoints[maxIndex];
|
|
modPoints.erase(maxIndex);
|
|
}
|
|
|
|
pWinding->numNodes = 0;
|
|
pWinding->numZoneIds = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
BrushArena::BrushArena(U32 _arenaSize)
|
|
{
|
|
setSize(_arenaSize);
|
|
}
|
|
|
|
void BrushArena::setSize(U32 _arenaSize)
|
|
{
|
|
arenaValid = true;
|
|
arenaSize = _arenaSize;
|
|
mFreeListHead = NULL;
|
|
}
|
|
|
|
BrushArena::~BrushArena()
|
|
{
|
|
arenaValid = false;
|
|
for (U32 i = 0; i < mBuffers.size(); i++) {
|
|
delete [] mBuffers[i];
|
|
mBuffers[i] = NULL;
|
|
}
|
|
mFreeListHead = NULL;
|
|
arenaSize = 0;
|
|
}
|
|
|
|
void BrushArena::newBuffer()
|
|
{
|
|
// Otherwise, we set up some more free windings...
|
|
CSGBrush* pNewBuffer = new CSGBrush[arenaSize];
|
|
mBuffers.push_back(pNewBuffer);
|
|
for (U32 i = 0; i < arenaSize - 1; i++) {
|
|
pNewBuffer[i].pNext = &pNewBuffer[i+1];
|
|
}
|
|
pNewBuffer[arenaSize - 1].pNext = NULL;
|
|
|
|
mFreeListHead = pNewBuffer;
|
|
}
|
|
|
|
CSGBrush* BrushArena::allocateBrush()
|
|
{
|
|
AssertFatal(arenaValid == true, "Arena invalid!");
|
|
|
|
if (mFreeListHead == NULL)
|
|
newBuffer();
|
|
AssertFatal(mFreeListHead != NULL, "error, that shouldn't happen!");
|
|
|
|
CSGBrush* pRet = mFreeListHead;
|
|
mFreeListHead = pRet->pNext;
|
|
pRet->pNext = NULL;
|
|
pRet->mIsAmbiguous = false;
|
|
pRet->mPlanes.clear();
|
|
return pRet;
|
|
}
|
|
|
|
void BrushArena::freeBrush(CSGBrush* junk)
|
|
{
|
|
AssertFatal(arenaValid == true, "Arena invalid!");
|
|
AssertFatal(junk->pNext == NULL, "Error, this is still on the free list!");
|
|
|
|
junk->pNext = mFreeListHead;
|
|
mFreeListHead = junk;
|
|
}
|
|
|
|
|
|
bool intersectWGPlanes(U32 i, U32 j, U32 k,
|
|
Point3D* pOutput)
|
|
{
|
|
const PlaneEQ& rPlaneEQ1 = gWorkingGeometry->getPlaneEQ(i);
|
|
const PlaneEQ& rPlaneEQ2 = gWorkingGeometry->getPlaneEQ(j);
|
|
const PlaneEQ& rPlaneEQ3 = gWorkingGeometry->getPlaneEQ(k);
|
|
|
|
F64 bc = (rPlaneEQ2.normal.y * rPlaneEQ3.normal.z) - (rPlaneEQ3.normal.y * rPlaneEQ2.normal.z);
|
|
F64 ac = (rPlaneEQ2.normal.x * rPlaneEQ3.normal.z) - (rPlaneEQ3.normal.x * rPlaneEQ2.normal.z);
|
|
F64 ab = (rPlaneEQ2.normal.x * rPlaneEQ3.normal.y) - (rPlaneEQ3.normal.x * rPlaneEQ2.normal.y);
|
|
F64 det = (rPlaneEQ1.normal.x * bc) - (rPlaneEQ1.normal.y * ac) + (rPlaneEQ1.normal.z * ab);
|
|
|
|
if (mFabs(det) < 1e-9) {
|
|
// Parallel planes
|
|
return false;
|
|
}
|
|
|
|
F64 dc = (rPlaneEQ2.dist * rPlaneEQ3.normal.z) - (rPlaneEQ3.dist * rPlaneEQ2.normal.z);
|
|
F64 db = (rPlaneEQ2.dist * rPlaneEQ3.normal.y) - (rPlaneEQ3.dist * rPlaneEQ2.normal.y);
|
|
F64 ad = (rPlaneEQ3.dist * rPlaneEQ2.normal.x) - (rPlaneEQ2.dist * rPlaneEQ3.normal.x);
|
|
F64 detInv = 1.0 / det;
|
|
|
|
pOutput->x = ((rPlaneEQ1.normal.y * dc) - (rPlaneEQ1.dist * bc) - (rPlaneEQ1.normal.z * db)) * detInv;
|
|
pOutput->y = ((rPlaneEQ1.dist * ac) - (rPlaneEQ1.normal.x * dc) - (rPlaneEQ1.normal.z * ad)) * detInv;
|
|
pOutput->z = ((rPlaneEQ1.normal.y * ad) + (rPlaneEQ1.normal.x * db) - (rPlaneEQ1.dist * ab)) * detInv;
|
|
|
|
return true;
|
|
}
|