//----------------------------------------------------------------------------- // Torque Game Engine // // Copyright (c) 2003 GarageGames.Com //----------------------------------------------------------------------------- #include "dgl/gBitmap.h" #include "map2difPlus/csgBrush.h" #include "core/tokenizer.h" extern int gQuakeVersion; // For the moment these exist in InteriorMap.cc // MDFFIX: clean this up extern F32 baseaxis[18][3]; extern void textureAxisFromPlane(const VectorF& normal, F32* xv, F32* yv); extern void quakeTextureVecs(const VectorF& normal, F32 offsetX, F32 offsetY, F32 rotate, F32 scaleX, F32 scaleY, PlaneF* u, PlaneF *v); 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()); // convert tex name to upper case char *str = (char *) rPlane.pTextureName; while( *str ) { *str = dToupper( (const char) *str ); str++; } U32 bmIndex = geom.getMaterialIndex(rPlane.pTextureName); const GBitmap* pBitmap = geom.mTextures[bmIndex]; PlaneF tGenX; PlaneF tGenY; if (gQuakeVersion == 2) { //hl ( 564 -396 -672 ) ( 564 -372 -672 ) ( 512 -372 -672 ) WALL_BLOCK01 [ 1 0 0 -213 ] [ 0 -1 0 -117 ] 0 1 1 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) { //q1 ( 88 -40 8 ) ( -56 -40 8 ) ( -56 -64 8 ) block10d 0 0 0 1.000000 1.000000 //q2 ( 88 -40 8 ) ( -56 -40 8 ) ( -56 -64 8 ) block10d 0 0 0 1.000000 1.000000 //q3 ( 88 -40 8 ) ( -56 -40 8 ) ( -56 -64 8 ) block10d 0 0 0 0.500000 0.500000 0 0 0 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; pToker->advanceToken(false); pToker->advanceToken(false); pToker->advanceToken(false); // 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; } U32 CSGBrush::addPlane(CSGPlane plane) { for (U32 i = 0; i < mPlanes.size(); i++) { if (mPlanes[i].flags == plane.flags && mPlanes[i].owningEntity == plane.owningEntity && mPlanes[i].planeEQIndex == plane.planeEQIndex && mPlanes[i].pTextureName == plane.pTextureName && mPlanes[i].texGenIndex == plane.texGenIndex) return i; } mPlanes.increment(); mPlanes.last() = plane; return mPlanes.size() - 1; } 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& 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() { // MDFFIX: Really hacky way of doing it for now but it will work ConvexBrush brush; CopyCSGToConvex(this, &brush); CopyConvexToCSG(&brush, this); 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 mInteriorRes->mBrushes (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; }