//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- #include "map2dif/morianUtil.h" #include "map2dif/editGeometry.h" #include "platform/platformAssert.h" #include "math/mMath.h" #include "map2dif/csgBrush.h" // Global brush arena... BrushArena gBrushArena(1024); 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.whichSide(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.whichSide(rStartPoint); PlaneSide eSide = rClipPlane.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 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.whichSide(intersection) == PlaneOn, "CSGPlane::clipWindingToPlaneFront: error in computing intersection"); finalIndices[numFinalIndices++] = gWorkingGeometry->insertPoint(intersection); U32 endSeek = (end + 1) % winding->numIndices; while (rClipPlane.whichSide(gWorkingGeometry->getPoint(winding->indices[endSeek])) == PlaneBack) endSeek = (endSeek + 1) % winding->numIndices; PlaneSide esSide = rClipPlane.whichSide(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.whichSide(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.whichSide(gWorkingGeometry->getPoint(winding->indices[endSeek])) == PlaneBack) endSeek = (endSeek + 1) % winding->numIndices; PlaneSide esSide = rClipPlane.whichSide(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.whichSide(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.whichSide(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.whichSide(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 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(); 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++) { for (U32 j = 0; j < rTestBrush.mPlanes[i].winding.numIndices; 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& 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 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) { 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; }