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

641 lines
23 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// Torque Game Engine
//
// Copyright (c) 2003 GarageGames.Com
//-----------------------------------------------------------------------------
#include "dgl/gBitmap.h"
#include "map2dif/csgBrush.h"
#include "map2dif/tokenizer.h"
extern int gQuakeVersion;
F32 baseaxis[18][3] =
{
{0,0,1}, {1,0,0}, {0,-1,0}, // floor
{0,0,-1}, {1,0,0}, {0,-1,0}, // ceiling
{1,0,0}, {0,1,0}, {0,0,-1}, // west wall
{-1,0,0}, {0,1,0}, {0,0,-1}, // east wall
{0,1,0}, {1,0,0}, {0,0,-1}, // south wall
{0,-1,0}, {1,0,0}, {0,0,-1} // north wall
};
//------------------------------------------------------------------------------
void textureAxisFromPlane(const VectorF& normal, F32* xv, F32* yv)
{
int bestaxis;
F32 dot,best;
int i;
best = 0;
bestaxis = 0;
for (i=0 ; i<6 ; i++) {
dot = mDot (normal, VectorF (baseaxis[i*3][0], baseaxis[i*3][1], baseaxis[i*3][2]));
if (dot > best)
{
best = dot;
bestaxis = i;
}
}
xv[0] = baseaxis [bestaxis * 3 + 1][0];
xv[1] = baseaxis [bestaxis * 3 + 1][1];
xv[2] = baseaxis [bestaxis * 3 + 1][2];
yv[0] = baseaxis [bestaxis * 3 + 2][0];
yv[1] = baseaxis [bestaxis * 3 + 2][1];
yv[2] = baseaxis [bestaxis * 3 + 2][2];
}
//------------------------------------------------------------------------------
void quakeTextureVecs(const VectorF& normal,
F32 offsetX,
F32 offsetY,
F32 rotate,
F32 scaleX,
F32 scaleY,
PlaneF* u,
PlaneF *v)
{
F32 vecs[2][3];
int sv, tv;
F32 ang, sinv, cosv;
F32 ns, nt;
int i, j;
textureAxisFromPlane(normal, vecs[0], vecs[1]);
ang = rotate / 180 * M_PI;
sinv = sin(ang);
cosv = cos(ang);
if (vecs[0][0] != 0.0)
sv = 0;
else if (vecs[0][1] != 0.0)
sv = 1;
else
sv = 2;
if (vecs[1][0] != 0.0)
tv = 0;
else if (vecs[1][1] != 0.0)
tv = 1;
else
tv = 2;
for (i=0 ; i<2 ; i++) {
ns = cosv * vecs[i][sv] - sinv * vecs[i][tv];
nt = sinv * vecs[i][sv] + cosv * vecs[i][tv];
vecs[i][sv] = ns;
vecs[i][tv] = nt;
}
u->x = vecs[0][0] / scaleX;
u->y = vecs[0][1] / scaleX;
u->z = vecs[0][2] / scaleX;
u->d = offsetX;
v->x = vecs[1][0] / scaleY;
v->y = vecs[1][1] / scaleY;
v->z = vecs[1][2] / scaleY;
v->d = offsetY;
}
bool parseBrush (CSGBrush& rBrush, Tokenizer* pToker, EditGeometry& geom)
{
while (pToker->getToken()[0] == '(') {
// Enter the plane...
F64 points[3][3];
for (S32 i = 0; i < 3; i++) {
if (pToker->getToken()[0] != '(')
goto EntityBrushlistError;
for (S32 j = 0; j < 3; j++) {
pToker->advanceToken(false);
points[i][j] = dAtof(pToker->getToken());
}
pToker->advanceToken(false);
if (pToker->getToken()[0] != ')')
goto EntityBrushlistError;
pToker->advanceToken(false);
}
CSGPlane& rPlane = rBrush.constructBrushPlane(Point3D(points[0][0], points[0][1], points[0][2]),
Point3D(points[1][0], points[1][1], points[1][2]),
Point3D(points[2][0], points[2][1], points[2][2]));
// advanced already...
if (pToker->tokenAvailable() == false)
goto EntityBrushlistError;
rPlane.pTextureName = geom.insertTexture(pToker->getToken());
U32 bmIndex = geom.getMaterialIndex(rPlane.pTextureName);
const GBitmap* pBitmap = geom.mTextures[bmIndex];
PlaneF tGenX;
PlaneF tGenY;
if (gQuakeVersion == 2)
{
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
if (pToker->getToken()[0] != '[') goto EntityBrushlistError;
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
tGenX.x = dAtof(pToker->getToken());
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
tGenX.y = dAtof(pToker->getToken());
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
tGenX.z = dAtof(pToker->getToken());
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
tGenX.d = dAtof(pToker->getToken());
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
if (pToker->getToken()[0] != ']') goto EntityBrushlistError;
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
if (pToker->getToken()[0] != '[') goto EntityBrushlistError;
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
tGenY.x = dAtof(pToker->getToken());
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
tGenY.y = dAtof(pToker->getToken());
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
tGenY.z = dAtof(pToker->getToken());
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
tGenY.d = dAtof(pToker->getToken());
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
if (pToker->getToken()[0] != ']') goto EntityBrushlistError;
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
F32 scaleX = dAtof(pToker->getToken());
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
F32 scaleY = dAtof(pToker->getToken());
tGenX.x /= scaleX * F32(pBitmap->getWidth());
tGenX.y /= scaleX * F32(pBitmap->getWidth());
tGenX.z /= scaleX * F32(pBitmap->getWidth());
tGenX.d /= F32(pBitmap->getWidth());
tGenY.x /= scaleY * F32(pBitmap->getHeight());
tGenY.y /= scaleY * F32(pBitmap->getHeight());
tGenY.z /= scaleY * F32(pBitmap->getHeight());
tGenY.d /= F32(pBitmap->getHeight());
}
else if (gQuakeVersion == 3)
{
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
F32 shiftU = dAtof (pToker->getToken());
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
F32 shiftV = dAtof (pToker->getToken());
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
F32 rot = dAtof (pToker->getToken());
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
F32 scaleX = dAtof(pToker->getToken());
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
F32 scaleY = dAtof(pToker->getToken());
// Skip last 3 tokens
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
if (pToker->advanceToken(false) == false) goto EntityBrushlistError;
// Compute the normal
VectorF normal;
VectorF t1, t2;
t1 = VectorF (points[0][0], points[0][1], points[0][2]) - VectorF (points[1][0], points[1][1], points[1][2]);
t2 = VectorF (points[2][0], points[2][1], points[2][2]) - VectorF (points[1][0], points[1][1], points[1][2]);
mCross (t1, t2, normal);
normal.normalize ();
quakeTextureVecs (normal, shiftU, shiftV, rot, scaleX, scaleY, &tGenX, &tGenY);
F32 width = F32(pBitmap->getWidth());
F32 height = F32(pBitmap->getHeight());
tGenX.x /= F32(pBitmap->getWidth());
tGenX.y /= F32(pBitmap->getWidth());
tGenX.z /= F32(pBitmap->getWidth());
tGenX.d /= F32(pBitmap->getWidth());
tGenY.x /= F32(pBitmap->getHeight());
tGenY.y /= F32(pBitmap->getHeight());
tGenY.z /= F32(pBitmap->getHeight());
tGenY.d /= F32(pBitmap->getHeight());
}
else
{
goto EntityBrushlistError;
}
// add it...
bool found = false;
for(U32 i = 0; !found && (i < geom.mTexGenEQs.size()); i++)
if(!dMemcmp(&geom.mTexGenEQs[i].planeX, &tGenX, sizeof(PlaneF)) &&
!dMemcmp(&geom.mTexGenEQs[i].planeY, &tGenY, sizeof(PlaneF)))
{
found = true;
rPlane.texGenIndex = i;
}
//
if(!found)
{
geom.mTexGenEQs.increment();
geom.mTexGenEQs.last().planeX = tGenX;
geom.mTexGenEQs.last().planeY = tGenY;
rPlane.texGenIndex = geom.mTexGenEQs.size() - 1;
}
pToker->advanceToken(true);
}
return true;
EntityBrushlistError:
return false;
}
void CSGPlane::construct(const Point3D& Point1, const Point3D& Point2, const Point3D& Point3)
{
// |yi zi 1| |xi zi 1| |xi yi 1| |xi yi zi|
// |yj zj 1| x + |xj zj 1| y + |xj yj 1| z = |xj yj zj|
// |yk zk 1| |xk zk 1| |xk yk 1| |xk yk zk|
//
Point3D normal;
F64 dist;
normal.x = Point1.y * Point2.z - Point1.y * Point3.z +
Point3.y * Point1.z - Point2.y * Point1.z +
Point2.y * Point3.z - Point3.y * Point2.z;
normal.y = Point1.x * Point2.z - Point1.x * Point3.z +
Point3.x * Point1.z - Point2.x * Point1.z +
Point2.x * Point3.z - Point3.x * Point2.z;
normal.z = Point1.x * Point2.y - Point1.x * Point3.y +
Point3.x * Point1.y - Point2.x * Point1.y +
Point2.x * Point3.y - Point3.x * Point2.y;
dist = Point1.x * Point2.y * Point3.z - Point1.x * Point2.z * Point3.y +
Point1.y * Point2.z * Point3.x - Point1.y * Point2.x * Point3.z +
Point1.z * Point2.x * Point3.y - Point1.z * Point2.y * Point3.x;
normal.x = -normal.x;
normal.z = -normal.z;
//
planeEQIndex = gWorkingGeometry->insertPlaneEQ(normal, dist);
flags = 0;
}
//------------------------------------------------------------------------------
// Needs to insert new plane equation. parameter is positive amount
// to extrude outward.
void CSGPlane::extrude(F64 byAmount)
{
const PlaneEQ & eq = gWorkingGeometry->getPlaneEQ(planeEQIndex);
planeEQIndex = gWorkingGeometry->insertPlaneEQ(eq.normal, eq.dist - byAmount);
}
//------------------------------------------------------------------------------
bool CSGPlane::createBaseWinding(const Vector<U32>& rPoints)
{
return ::createBaseWinding(rPoints, getNormal(), &winding);
}
//------------------------------------------------------------------------------
// Try a more accurate version of this.
PlaneSide CSGPlane::sideCheckEpsilon(const Point3D& testPoint, F64 epsilon) const
{
F64 distance = distanceToPlane(testPoint);
if (distance < - epsilon)
return PlaneBack;
else if (distance > epsilon)
return PlaneFront;
else
return PlaneOn;
}
// Need more info for asserts.
const char* CSGPlane::sideCheckInfo(const Point3D & P, const char * msg, F64 E) const
{
F64 D = distanceToPlane(P);
const char * txt = D < -E ? "BACK" : (D > E ? "FRONT" : "ON");
return avar( "%s: Side=%s E=%lf D=%1.20lf P=(%lf,%lf,%lf)", msg, txt, E, D, P.x, P.y, P.z );
}
// See if this plane lies along an edge of the supplied winding. Return the
// points if so (they're needed), else return NULL for false.
//
// Maybe need a better point here.
Point3D * CSGPlane::sharesEdgeWith(const Winding & W) const
{
static Point3D edgePts[2];
for( U32 i = 0; i < W.numIndices; i++ )
{
edgePts[0] = gWorkingGeometry->getPoint(W.indices[i]);
// if( sideCheckEpsilon( edgePts[0] ) == PlaneOn )
if( whichSide( edgePts[0] ) == PlaneOn )
{
edgePts[1] = gWorkingGeometry->getPoint(W.indices[(i + 1) % W.numIndices]);
// if( sideCheckEpsilon( edgePts[1] ) == PlaneOn )
if( whichSide( edgePts[1] ) == PlaneOn )
return edgePts;
}
}
return NULL;
}
// Assuming this is a brush that has already been selfClip()d, make
// and return a new brush that is extruded version of this
// one (tho not clipped) in given direction.
//
// If doBoth, then extrude also in negative of direction.
//
// Adds additional planes on those edges which are boundaries with
// respect to the direction axis. That is, those edges which will
// will be on the outside of the shape perimeter when viewing
// along the given direction axis, and so planes need to be added
// to limit unwanted extrusion outside of the intended axis.
//
CSGBrush* CSGBrush::createExtruded(const Point3D & extrudeDir, bool doBoth) const
{
CSGBrush * newBrush = gBrushArena.allocateBrush();
newBrush->copyBrush( this );
for( U32 i = 0; i < mPlanes.size(); i++ )
{
// Get extrusion component along normal.
const Point3D & curNormal = mPlanes[i].getNormal();
F64 extrudeComponent = mDot( extrudeDir, curNormal );
if( mFabsD(extrudeComponent) > 0.001 )
{
const Winding & curWinding = mPlanes[i].winding;
// Look for planes that are opposite with respect to this
// direction and which have a shared edge. We create 'clamping'
// planes along such edges to avoid spikes.
for( U32 j = i + 1; j < mPlanes.size(); j++ )
{
if( mDot(mPlanes[j].getNormal(),extrudeDir) * extrudeComponent < -0.0001 )
{
if( Point3D * edgePts = mPlanes[j].sharesEdgeWith(curWinding) )
{
Point3D other = edgePts[0] + extrudeDir;
CSGPlane & rPlane = (extrudeComponent > 0)
?
newBrush->constructBrushPlane( other, edgePts[0], edgePts[1] )
:
newBrush->constructBrushPlane( edgePts[1], edgePts[0], other );
rPlane.pTextureName = mPlanes[0].pTextureName;
rPlane.xShift = 0;
rPlane.yShift = 0;
rPlane.rotation = 0;
rPlane.xScale = rPlane.yScale = 1.0;
}
}
}
if( extrudeComponent > 0 || doBoth )
newBrush->mPlanes[i].extrude( mFabsD( extrudeComponent ) );
}
}
AssertFatal( newBrush->mPlanes.size() >= mPlanes.size(),
"CSGBrush::createExtruded(): new brush shouldn't be smaller" );
return newBrush;
}
//------------------------------------------------------------------------------
bool CSGBrush::intersectPlanes(U32 i, U32 j, U32 k,
Point3D* pOutput)
{
AssertFatal(i < mPlanes.size() && j < mPlanes.size() && k < mPlanes.size() &&
i != j && i != k && j != k, "CSGBrush::intersectPlanes: bad plane indices");
CSGPlane& rPlane1 = mPlanes[i];
CSGPlane& rPlane2 = mPlanes[j];
CSGPlane& rPlane3 = mPlanes[k];
const PlaneEQ& rPlaneEQ1 = gWorkingGeometry->getPlaneEQ(rPlane1.planeEQIndex);
const PlaneEQ& rPlaneEQ2 = gWorkingGeometry->getPlaneEQ(rPlane2.planeEQIndex);
const PlaneEQ& rPlaneEQ3 = gWorkingGeometry->getPlaneEQ(rPlane3.planeEQIndex);
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-7) {
// 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;
}
//------------------------------------------------------------------------------
bool CSGBrush::selfClip()
{
static const U32 VectorBufferSize = 64;
static UniqueVector pPlanePoints[VectorBufferSize];
AssertISV(mPlanes.size() < VectorBufferSize,
"CSGBrush::selfClip: talk to Dave Moore. Must increase planepoint buffer size");
for (U32 i = 0; i < mPlanes.size(); i++) {
for (U32 j = i+1; j < mPlanes.size(); j++) {
for (U32 k = j+1; k < mPlanes.size(); k++) {
Point3D intersection;
bool doesSersect = intersectPlanes(i, j, k, &intersection);
if (doesSersect) {
F64 E = gcPlaneDistanceEpsilon;
const char * msg = "CSGBrush::selfClip: intersectionPoint not on plane";
AssertFatal( mPlanes[i].sideCheckEpsilon(intersection,E) == PlaneOn,
mPlanes[i].sideCheckInfo(intersection, msg, E) );
AssertFatal( mPlanes[j].sideCheckEpsilon(intersection,E) == PlaneOn,
mPlanes[j].sideCheckInfo(intersection, msg, E) );
AssertFatal( mPlanes[k].sideCheckEpsilon(intersection,E) == PlaneOn,
mPlanes[k].sideCheckInfo(intersection, msg, E) );
bool inOrOn = true;
for (U32 l = 0; l < mPlanes.size(); l++) {
if (mPlanes[l].whichSide(intersection) == PlaneFront) {
inOrOn = false;
break;
}
}
if (inOrOn == true) {
U32 pIndex = gWorkingGeometry->insertPoint(intersection);
pPlanePoints[i].pushBackUnique(pIndex);
pPlanePoints[j].pushBackUnique(pIndex);
pPlanePoints[k].pushBackUnique(pIndex);
gWorkingGeometry->mMinBound.setMin(intersection);
gWorkingGeometry->mMaxBound.setMax(intersection);
}
}
}
}
}
mMinBound.set(1e10, 1e10, 1e10);
mMaxBound.set(-1e10, -1e10, -1e10);
for (U32 i = 0; i < mPlanes.size(); i++) {
bool success = mPlanes[i].createBaseWinding(pPlanePoints[i]);
if(!success)
return false;
for (U32 j = 0; j < mPlanes[i].winding.numIndices; j++) {
const Point3D& rPoint = gWorkingGeometry->getPoint(mPlanes[i].winding.indices[j]);
mMinBound.setMin(rPoint);
mMaxBound.setMax(rPoint);
}
AssertISV(success, "Error creating winding. DMM - Need to remove brush here.");
pPlanePoints[i].clear();
}
return true;
}
void CSGBrush::copyBrush(const CSGBrush* pCopy)
{
mPlanes = pCopy->mPlanes;
mIsAmbiguous = pCopy->mIsAmbiguous;
mBrushType = pCopy->mBrushType;
mMinBound = pCopy->mMinBound;
mMaxBound = pCopy->mMaxBound;
sgLightingScale = pCopy->sgLightingScale;
sgSelfIllumination = pCopy->sgSelfIllumination;
brushId = pCopy->brushId;
materialType = pCopy->materialType;
}
//------------------------------------------------------------------------------
bool CSGBrush::disambiguate()
{
AssertFatal(mIsAmbiguous == false, "error, already disambiguated?");
for (U32 i = 0; i < mPlanes.size(); i++) {
for (U32 j = i + 1; j < mPlanes.size();) {
// Compare i to j. if j == i, with different tex parameters, increment
// ambiguous brushes (once only), and remove the plane.
//
CSGPlane& rPlane1 = mPlanes[i];
CSGPlane& rPlane2 = mPlanes[j];
if (rPlane1.planeEQIndex == rPlane2.planeEQIndex) {
// Possible ambiguous plane pairing...
//
if (rPlane1.pTextureName != rPlane2.pTextureName ||
rPlane1.xShift != rPlane2.xShift ||
rPlane1.yShift != rPlane2.yShift ||
rPlane1.rotation != rPlane2.rotation ||
rPlane1.xScale != rPlane2.xScale ||
rPlane1.yScale != rPlane2.yScale) {
mIsAmbiguous = true;
} else {
// Same texture parameters, should be fine, just erase it...
//
}
mPlanes.erase(j);
} else {
// Plane is fine...
j++;
}
}
}
return mIsAmbiguous;
}
//------------------------------------------------------------------------------
/*CSGPlane& CSGBrush::constructBrushPlane(const Point3I& rPoint1,
const Point3I& rPoint2,
const Point3I& rPoint3)
{
mPlanes.increment();
CSGPlane& rPlane = mPlanes.last();
rPlane.flags = 0;
rPlane.owningEntity = NULL;
rPlane.winding.numNodes = 0;
rPlane.winding.numZoneIds = 0;
Point3D Point1(rPoint1.x, rPoint1.y, rPoint1.z);
Point3D Point2(rPoint2.x, rPoint2.y, rPoint2.z);
Point3D Point3(rPoint3.x, rPoint3.y, rPoint3.z);
rPlane.construct(Point1, Point2, Point3);
if (rPlane.getNormal().x == 0 && rPlane.getNormal().y == 0 && rPlane.getNormal().z == 0) {
AssertISV(false, "Error, degenerate plane (colinear points)");
mPlanes.decrement();
return *((CSGPlane*)NULL);
}
return rPlane;
}*/
CSGPlane& CSGBrush::constructBrushPlane(const Point3D& Point1,
const Point3D& Point2,
const Point3D& Point3)
{
mPlanes.increment();
CSGPlane& rPlane = mPlanes.last();
rPlane.flags = 0;
rPlane.owningEntity = NULL;
rPlane.winding.numNodes = 0;
rPlane.winding.numZoneIds = 0;
rPlane.construct(Point1, Point2, Point3);
if (rPlane.getNormal().x == 0 && rPlane.getNormal().y == 0 && rPlane.getNormal().z == 0) {
AssertISV(false, "Error, degenerate plane (colinear points)");
mPlanes.decrement();
return *((CSGPlane*)NULL);
}
return rPlane;
}
bool CSGBrush::isEquivalent(const CSGBrush& testBrush) const
{
for (U32 i = 0; i < mPlanes.size(); i++) {
U32 j;
for (j = 0; j < testBrush.mPlanes.size(); j++) {
if (testBrush.mPlanes[j].planeEQIndex == mPlanes[i].planeEQIndex)
break;
}
if (j == testBrush.mPlanes.size())
return false;
}
return true;
}