/* Copyright (c) 2002-2012 Croteam Ltd. This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "Engine/StdH.h" #include <Engine/Math/Object3D.h> #include <Engine/Templates/BSP_internal.h> #include <Engine/Templates/StaticArray.cpp> #include <Engine/Templates/DynamicArray.cpp> #include <Engine/Templates/DynamicContainer.cpp> // define this for full debugging of all algorithms #define FULL_DEBUG // epsilon value used for vertex comparison //#define VTX_EPSILON (0.015625f) // 1/2^6 ~= 1.5 cm #define VTX_EPSILON DOUBLE((1.0/65536.0)/16.0*mth_fCSGEpsilon) // 1/2^16 //#define VTX_EPSILON DOUBLE(0.00390625) // 1/2^8 // grid for snapping vertices #define VTX_SNAP (VTX_EPSILON/2.0) // epsilon value used for plane comparison #define PLX_EPSILON (VTX_EPSILON/16.0*mth_fCSGEpsilon) // grid for snapping planes #define PLX_SNAP (PLX_EPSILON/2.0) // epsilon value used for edge line comparison //#define EDX_EPSILON (1E-3f) // 1E-3 == 1 mm //#define EDX_EPSILON (0.015625f) // 1/2^6 ~= 1.5 cm //#define EDX_EPSILON DOUBLE(1.0/65536.0) // 1/2^16 #define EDX_EPSILON DOUBLE(0.00390625*mth_fCSGEpsilon) // 1/2^8 // use O(nlogn) instead O(n2) algorithms for object optimization INDEX wld_bFastObjectOptimization = 1; FLOAT mth_fCSGEpsilon = 1.0f; /* * Compare two vertices. */ inline int CompareVertices(const CObjectVertex &vx0, const CObjectVertex &vx1) { if (vx0(1)-vx1(1) < -VTX_EPSILON) return -1; else if (vx0(1)-vx1(1) > +VTX_EPSILON) return 1; else if (vx0(2)-vx1(2) < -VTX_EPSILON) return -1; else if (vx0(2)-vx1(2) > +VTX_EPSILON) return 1; else if (vx0(3)-vx1(3) < -VTX_EPSILON) return -1; else if (vx0(3)-vx1(3) > +VTX_EPSILON) return 1; else return 0; } /* * Compare two vertices for quick-sort. */ static int qsort_CompareVertices(const void *pvVertex0, const void *pvVertex1) { CObjectVertex &vx0 = **(CObjectVertex **)pvVertex0; CObjectVertex &vx1 = **(CObjectVertex **)pvVertex1; return CompareVertices(vx0, vx1); } /* * Compare two along line vertices. */ static INDEX sortvertices_iMaxAxis; inline int CompareVerticesAlongLine(const CObjectVertex &vx0, const CObjectVertex &vx1) { if (vx0(sortvertices_iMaxAxis)-vx1(sortvertices_iMaxAxis) < -VTX_EPSILON) return -1; else if (vx0(sortvertices_iMaxAxis)-vx1(sortvertices_iMaxAxis) > +VTX_EPSILON) return +1; else return 0; } /* * Compare two vertices along a line for quick-sort. */ static int qsort_CompareVerticesAlongLine(const void *pvVertex0, const void *pvVertex1) { CObjectVertex &vx0 = **(CObjectVertex **)pvVertex0; CObjectVertex &vx1 = **(CObjectVertex **)pvVertex1; return CompareVerticesAlongLine(vx0, vx1); } /* * Compare two vertices along a line for quick-sort - reversely. */ static int qsort_CompareVerticesAlongLineReversely(const void *pvVertex0, const void *pvVertex1) { CObjectVertex &vx0 = **(CObjectVertex **)pvVertex0; CObjectVertex &vx1 = **(CObjectVertex **)pvVertex1; return -CompareVerticesAlongLine(vx0, vx1); } /* * Compare two planes. */ inline int ComparePlanes(const CObjectPlane &pl0, const CObjectPlane &pl1) { if (pl0(1)-pl1(1) < -PLX_EPSILON) return -1; else if (pl0(1)-pl1(1) > +PLX_EPSILON) return +1; else if (pl0(2)-pl1(2) < -PLX_EPSILON) return -1; else if (pl0(2)-pl1(2) > +PLX_EPSILON) return +1; else if (pl0(3)-pl1(3) < -PLX_EPSILON) return -1; else if (pl0(3)-pl1(3) > +PLX_EPSILON) return +1; else if (pl0.Distance()-pl1.Distance() < -PLX_EPSILON) return -1; else if (pl0.Distance()-pl1.Distance() > +PLX_EPSILON) return +1; else return 0; } /* * Compare two planes for quick-sort. */ static int qsort_ComparePlanes(const void *pvPlane0, const void *pvPlane1) { CObjectPlane &pl0 = **(CObjectPlane **)pvPlane0; CObjectPlane &pl1 = **(CObjectPlane **)pvPlane1; return ComparePlanes(pl0, pl1); } /* * Compare two edges. */ inline int CompareEdges(const CObjectEdge &ed0, const CObjectEdge &ed1) { if (ed0.oed_Vertex0<ed1.oed_Vertex0) return -1; else if (ed0.oed_Vertex0>ed1.oed_Vertex0) return +1; else if (ed0.oed_Vertex1<ed1.oed_Vertex1) return -1; else if (ed0.oed_Vertex1>ed1.oed_Vertex1) return +1; else return 0; } /* * Compare two edges for quick-sort. */ static int qsort_CompareEdges(const void *pvEdge0, const void *pvEdge1) { CObjectEdge &ed0 = **(CObjectEdge **)pvEdge0; CObjectEdge &ed1 = **(CObjectEdge **)pvEdge1; return CompareEdges(ed0, ed1); } /* * Compare two edge lines. */ inline int CompareEdgeLines(const CEdgeEx &edx0, const CEdgeEx &edx1) { if (edx0.edx_vDirection(1)-edx1.edx_vDirection(1) < -EDX_EPSILON) return -1; else if (edx0.edx_vDirection(1)-edx1.edx_vDirection(1) > +EDX_EPSILON) return +1; else if (edx0.edx_vDirection(2)-edx1.edx_vDirection(2) < -EDX_EPSILON) return -1; else if (edx0.edx_vDirection(2)-edx1.edx_vDirection(2) > +EDX_EPSILON) return +1; else if (edx0.edx_vDirection(3)-edx1.edx_vDirection(3) < -EDX_EPSILON) return -1; else if (edx0.edx_vDirection(3)-edx1.edx_vDirection(3) > +EDX_EPSILON) return +1; else if (edx0.edx_vReferencePoint(1)-edx1.edx_vReferencePoint(1) < -EDX_EPSILON) return -1; else if (edx0.edx_vReferencePoint(1)-edx1.edx_vReferencePoint(1) > +EDX_EPSILON) return +1; else if (edx0.edx_vReferencePoint(2)-edx1.edx_vReferencePoint(2) < -EDX_EPSILON) return -1; else if (edx0.edx_vReferencePoint(2)-edx1.edx_vReferencePoint(2) > +EDX_EPSILON) return +1; else if (edx0.edx_vReferencePoint(3)-edx1.edx_vReferencePoint(3) < -EDX_EPSILON) return -1; else if (edx0.edx_vReferencePoint(3)-edx1.edx_vReferencePoint(3) > +EDX_EPSILON) return +1; else return 0; } //#define EDX_EPSILON_LOOSE DOUBLE(0.00390625) // 1/2^8 /* * Compare two edge lines. */ /* inline int CompareEdgeLines_loosely(const CEdgeEx &edx0, const CEdgeEx &edx1) { if (edx0.edx_vDirection(1)-edx1.edx_vDirection(1) < -EDX_EPSILON_LOOSE) return -1; else if (edx0.edx_vDirection(1)-edx1.edx_vDirection(1) > +EDX_EPSILON_LOOSE) return +1; else if (edx0.edx_vDirection(2)-edx1.edx_vDirection(2) < -EDX_EPSILON_LOOSE) return -1; else if (edx0.edx_vDirection(2)-edx1.edx_vDirection(2) > +EDX_EPSILON_LOOSE) return +1; else if (edx0.edx_vDirection(3)-edx1.edx_vDirection(3) < -EDX_EPSILON_LOOSE) return -1; else if (edx0.edx_vDirection(3)-edx1.edx_vDirection(3) > +EDX_EPSILON_LOOSE) return +1; else if (edx0.edx_vReferencePoint(1)-edx1.edx_vReferencePoint(1) < -EDX_EPSILON_LOOSE) return -1; else if (edx0.edx_vReferencePoint(1)-edx1.edx_vReferencePoint(1) > +EDX_EPSILON_LOOSE) return +1; else if (edx0.edx_vReferencePoint(2)-edx1.edx_vReferencePoint(2) < -EDX_EPSILON_LOOSE) return -1; else if (edx0.edx_vReferencePoint(2)-edx1.edx_vReferencePoint(2) > +EDX_EPSILON_LOOSE) return +1; else if (edx0.edx_vReferencePoint(3)-edx1.edx_vReferencePoint(3) < -EDX_EPSILON_LOOSE) return -1; else if (edx0.edx_vReferencePoint(3)-edx1.edx_vReferencePoint(3) > +EDX_EPSILON_LOOSE) return +1; else return 0; }*/ /* * Compare two edge lines for quick-sort. */ static int qsort_CompareEdgeLines(const void *pvEdgeEx0, const void *pvEdgeEx1) { CEdgeEx &edx0 = **(CEdgeEx **)pvEdgeEx0; CEdgeEx &edx1 = **(CEdgeEx **)pvEdgeEx1; return CompareEdgeLines(edx0, edx1); } /* * Find an edge with given vertices in sorted array of pointers to edges. */ BOOL FindEdge( CStaticArray<CObjectEdge *> &apedSorted, CObjectVertex &vx0, CObjectVertex &vx1, CObjectEdge **ppedResult) { // create a ghost edge with given vertices CObjectEdge edWanted(vx0, vx1); CObjectEdge *pedWanted = &edWanted; // invoke binary search on the array with ghost edge CObjectEdge **ppedFound = (CObjectEdge **) bsearch( &pedWanted, &(apedSorted[0]), apedSorted.Count(), sizeof(CObjectEdge *), qsort_CompareEdges); // if some edge was found if (ppedFound!=NULL) { // return it *ppedResult= *ppedFound; return TRUE; // otherwise } else { // there is no such edge return FALSE; } } /* * Get start and end vertices. */ // rcg10162001 wtf...I had to move this into the class definition itself. // I think it's an optimization bug; I didn't have this problem when I // didn't give GCC the "-O2" option. #if 0 void CObjectPolygonEdge::GetVertices(CObjectVertex *&povxStart, CObjectVertex *&povxEnd) { ASSERT(ope_Edge!=NULL); if (ope_Backward) { povxStart = ope_Edge->oed_Vertex1; povxEnd = ope_Edge->oed_Vertex0; } else { povxStart = ope_Edge->oed_Vertex0; povxEnd = ope_Edge->oed_Vertex1; } } #endif /* * Default constructor. */ CObjectSector::CObjectSector(void) : osc_colAmbient(0), osc_colColor(0), osc_strName("") { osc_ulFlags[0] = 0; osc_ulFlags[1] = 0; osc_ulFlags[2] = 0; } /* * Destructor. */ CObjectSector::~CObjectSector(void) { } /* * Initialize the structure for the given edge. */ inline void CEdgeEx::Initialize(CObjectEdge *poedEdge) { // remember edge edx_poedEdge = poedEdge; // initialize for edge's end vertices DOUBLE3D vPoint0 = *poedEdge->oed_Vertex0; DOUBLE3D vPoint1 = *poedEdge->oed_Vertex1; Initialize(&vPoint0, &vPoint1); } inline void CEdgeEx::Initialize(const DOUBLE3D *pvPoint0, const DOUBLE3D *pvPoint1) { // if the vertices are reversed if (CompareVertices(*pvPoint0, *pvPoint1)<0) { // swap them Swap(pvPoint0, pvPoint1); // mark edge as reversed edx_bReverse = TRUE; // if the vertices are not reversed } else { // mark edge as not reversed edx_bReverse = FALSE; } const DOUBLE3D &vPoint0=*pvPoint0; const DOUBLE3D &vPoint1=*pvPoint1; // normalize the direction edx_vDirection = (vPoint1-vPoint0).Normalize(); DOUBLE fDirectionLen = edx_vDirection.Length(); ASSERT(0.999<fDirectionLen && fDirectionLen<1.001); /* calculate the reference point on the line from any point on line (p) and direction (d): r = p - ((p.d)/(d.d))*d */ edx_vReferencePoint = vPoint0 - edx_vDirection * ((vPoint0 % edx_vDirection))/(edx_vDirection % edx_vDirection); } /* Test if a point is on the edge line. */ inline BOOL CEdgeEx::PointIsOnLine(const DOUBLE3D &vPointToTest) const { // if it is the reference point if (CompareVertices(edx_vReferencePoint, vPointToTest)==0) { // it is on the line return TRUE; } // create edge line from line reference point to given point CEdgeEx edxPointToTest; edxPointToTest.Initialize(&edx_vReferencePoint, &vPointToTest); // test if it is same as this edge line return CompareEdgeLines(*this, edxPointToTest)==0; } void CObjectSector::Clear(void) { osc_aovxVertices.Clear(); osc_aoplPlanes.Clear(); osc_aoedEdges.Clear(); osc_aopoPolygons.Clear(); osc_aomtMaterials.Clear(); } /* * Lock all object sector dynamic arrays. */ void CObjectSector::LockAll( void) { osc_aovxVertices.Lock(); osc_aoplPlanes.Lock(); osc_aoedEdges.Lock(); osc_aopoPolygons.Lock(); osc_aomtMaterials.Lock(); } /* * Unlocks all object sector dynamic arrays. */ void CObjectSector::UnlockAll( void) { osc_aovxVertices.Unlock(); osc_aoplPlanes.Unlock(); osc_aoedEdges.Unlock(); osc_aopoPolygons.Unlock(); osc_aomtMaterials.Unlock(); } /* * Create indices for all members of all arrays. */ void CObjectSector::CreateIndices(void) { // lock all arrays LockAll(); // get the number of vertices in object INDEX ctVertices = osc_aovxVertices.Count(); // set vertex indices for(INDEX iVertex=0; iVertex<ctVertices; iVertex++) { osc_aovxVertices[iVertex].ovx_Index = iVertex; } // get the number of planes in object INDEX ctPlanes = osc_aoplPlanes.Count(); // set plane indices for(INDEX iPlane=0; iPlane<ctPlanes; iPlane++) { osc_aoplPlanes[iPlane].opl_Index = iPlane; } // get the number of materials in object INDEX ctMaterials = osc_aomtMaterials.Count(); // set plane indices for(INDEX iMaterial=0; iMaterial<ctMaterials; iMaterial++) { osc_aomtMaterials[iMaterial].omt_Index = iMaterial; } // get the number of edges in object INDEX ctEdges = osc_aoedEdges.Count(); // set edge indices for(INDEX iEdge=0; iEdge<ctEdges; iEdge++) { osc_aoedEdges[iEdge].oed_Index = iEdge; } // get the number of polygons in object INDEX ctPolygons = osc_aopoPolygons.Count(); // set polygon indices for(INDEX iPolygon=0; iPolygon<ctPolygons; iPolygon++) { osc_aopoPolygons[iPolygon].opo_Index = iPolygon; } // unlock all arrays UnlockAll(); } /* * Create a new polygon in given sector. */ CObjectPolygon *CObjectSector::CreatePolygon(INDEX ctVertices, DOUBLE3D avVertices[], CObjectMaterial &omaMaterial, ULONG ulFlags, BOOL bReverse) { // reset areas on axial planes DOUBLE fXY=0.0, fXZ=0.0, fYZ=0.0; // for each two vertices {for (INDEX ivx=0; ivx<ctVertices; ivx++){ const DOUBLE3D &v0 = avVertices[ivx]; const DOUBLE3D &v1 = avVertices[(ivx+1)%ctVertices]; // add the edge to all three areas fXY+=(v1(1)-v0(1))*(v0(2)+v1(2))/2.0; fXZ+=(v1(1)-v0(1))*(v0(3)+v1(3))/2.0; fYZ+=(v1(2)-v0(2))*(v0(3)+v1(3))/2.0; }} // find maximum absolute area fXY = Abs(fXY); fXZ = Abs(fXZ); fYZ = Abs(fYZ); DOUBLE fMaxArea = Max(Max(fXY, fXZ), fYZ); // if maximum area is too small if(fMaxArea<1E-8) { // do not create polygon return NULL; } // create a new polygon in object CObjectPolygon &opoPolygon = *osc_aopoPolygons.New(); // create as much polygon edges as there are vertices in the polygon CObjectPolygonEdge *popePolygonEdges = opoPolygon.opo_PolygonEdges.New(ctVertices); // for each vertex {for (INDEX iVertex=0; iVertex<ctVertices; iVertex++) { DOUBLE3D &v0=avVertices[iVertex]; // that vertex DOUBLE3D &v1=avVertices[(iVertex+1)%ctVertices]; // its next neighbour // create an edge between it and its neighbour and add it to the polygon if (bReverse) { popePolygonEdges[iVertex] = CObjectPolygonEdge(&CreateEdge(v1, v0)); } else { popePolygonEdges[iVertex] = CObjectPolygonEdge(&CreateEdge(v0, v1)); } }} // clear plane normal DOUBLE3D vNormal = DOUBLE3D(0.0f,0.0f,0.0f); // for all vertices in polygon {for(INDEX iEdge=0; iEdge<ctVertices; iEdge++) { // get two neighbouring edges CObjectEdge &oed0 = *popePolygonEdges[iEdge].ope_Edge; CObjectEdge &oed1 = *popePolygonEdges[(iEdge+1)%ctVertices].ope_Edge; // make their vectors DOUBLE3D vVector1 = *oed0.oed_Vertex0 - *oed0.oed_Vertex1; DOUBLE3D vVector2 = *oed1.oed_Vertex1 - *oed1.oed_Vertex0; // add vector product of edges to the plane normal vNormal += vVector2*vVector1; }} // add one plane to planes array CObjectPlane *pplPlane = osc_aoplPlanes.New(); // construct this plane from normal vector and one point if (bReverse) { *pplPlane= DOUBLEplane3D( -vNormal, *popePolygonEdges[0].ope_Edge->oed_Vertex0); } else { *pplPlane= DOUBLEplane3D( vNormal, *popePolygonEdges[0].ope_Edge->oed_Vertex0); } // set this polygon's plane pointer opoPolygon.opo_Plane = pplPlane; // set this polygon's material pointer and color opoPolygon.opo_Material = &omaMaterial; opoPolygon.opo_colorColor = omaMaterial.omt_Color; // set flags opoPolygon.opo_ulFlags = ulFlags; return &opoPolygon; } /* * Create a new polygon in given sector. */ CObjectPolygon *CObjectSector::CreatePolygon(INDEX ctVertices, INDEX aivVertices[], CObjectMaterial &omaMaterial, ULONG ulFlags, BOOL bReverse) { // create a new polygon in object CObjectPolygon &opoPolygon = *osc_aopoPolygons.New(); // create as much polygon edges as there are vertices in the polygon CObjectPolygonEdge *popePolygonEdges = opoPolygon.opo_PolygonEdges.New(ctVertices); // for each vertex {for (INDEX iVertex=0; iVertex<ctVertices; iVertex++) { INDEX iNextVertex = (iVertex+1)%ctVertices; // create an edge between it and its neighbour and add it to the polygon if (bReverse) { popePolygonEdges[iVertex] = CObjectPolygonEdge( &CreateEdge(aivVertices[ iVertex], aivVertices[ iNextVertex]) ); } else { popePolygonEdges[iVertex] = CObjectPolygonEdge( &CreateEdge(aivVertices[ iNextVertex], aivVertices[ iVertex]) ); } }} // clear plane normal DOUBLE3D vNormal = DOUBLE3D(0.0f,0.0f,0.0f); // for all vertices in polygon {for(INDEX iEdge=0; iEdge<ctVertices; iEdge++) { // get two neighbouring edges CObjectEdge &oed0 = *popePolygonEdges[iEdge].ope_Edge; CObjectEdge &oed1 = *popePolygonEdges[(iEdge+1)%ctVertices].ope_Edge; // make their vectors DOUBLE3D vVector1 = *oed0.oed_Vertex0 - *oed0.oed_Vertex1; DOUBLE3D vVector2 = *oed1.oed_Vertex1 - *oed1.oed_Vertex0; // add vector product of edges to the plane normal vNormal += vVector2*vVector1; }} // add one plane to planes array CObjectPlane *pplPlane = osc_aoplPlanes.New(); // construct this plane from normal vector and one point if (bReverse) { *pplPlane= DOUBLEplane3D( -vNormal, *popePolygonEdges[0].ope_Edge->oed_Vertex0); } else { *pplPlane= DOUBLEplane3D( vNormal, *popePolygonEdges[0].ope_Edge->oed_Vertex0); } // set this polygon's plane pointer opoPolygon.opo_Plane = pplPlane; // set this polygon's material pointer and color opoPolygon.opo_Material = &omaMaterial; opoPolygon.opo_colorColor = omaMaterial.omt_Color; // set flags opoPolygon.opo_ulFlags = ulFlags; return &opoPolygon; } /* * Check the optimization algorithm. */ void CObjectSector::CheckOptimizationAlgorithm(void) { // for vertices FOREACHINDYNAMICARRAY(osc_aovxVertices, CObjectVertex, itvx1) { FOREACHINDYNAMICARRAY(osc_aovxVertices, CObjectVertex, itvx2) { CObjectVertex &vx1 = itvx1.Current(); CObjectVertex &vx2 = itvx2.Current(); // !!!!why this fails sometimes ?(on spheres) ASSERT( (&vx1 == &vx2) || (CompareVertices(vx1, vx2)!=0) ); } } // for planes FOREACHINDYNAMICARRAY(osc_aoplPlanes, CObjectPlane, itpl1) { FOREACHINDYNAMICARRAY(osc_aoplPlanes, CObjectPlane, itpl2) { CObjectPlane &pl1 = itpl1.Current(); CObjectPlane &pl2 = itpl2.Current(); ASSERT( (&pl1 == &pl2) || (ComparePlanes(pl1, pl2)!=0)); } } // for edges {FOREACHINDYNAMICARRAY(osc_aoedEdges, CObjectEdge, ited1) { FOREACHINDYNAMICARRAY(osc_aoedEdges, CObjectEdge, ited2) { CObjectEdge &ed1 = ited1.Current(); CObjectEdge &ed2 = ited2.Current(); ASSERT( (&ed1 == &ed2) || (ed1.oed_Vertex0 != ed2.oed_Vertex0) || (ed1.oed_Vertex1 != ed2.oed_Vertex1) ); } }} // for inverse edges INDEX iInversesFound = 0; {FOREACHINDYNAMICARRAY(osc_aoedEdges, CObjectEdge, ited1) { FOREACHINDYNAMICARRAY(osc_aoedEdges, CObjectEdge, ited2) { CObjectEdge &ed1 = ited1.Current(); CObjectEdge &ed2 = ited2.Current(); if (&ed1 == &ed2) continue; BOOL inv1, inv2; inv1 = (&ed1 == ed2.optimize.oed_InverseEdge); inv2 = (&ed2 == ed1.optimize.oed_InverseEdge); // check against cross linked inverses ASSERT(!(inv1&&inv2)); if (inv1) { iInversesFound++; } // check against inverses that have not been found ASSERT( (inv1 || inv2) || (ed1.oed_Vertex0 != ed2.oed_Vertex1) || (ed1.oed_Vertex1 != ed2.oed_Vertex0) ); } }} // for each polygon {FOREACHINDYNAMICARRAY(osc_aopoPolygons, CObjectPolygon, itopo) { // for each edge in polygon {FOREACHINDYNAMICARRAY(itopo->opo_PolygonEdges, CObjectPolygonEdge, itope) { CObjectEdge &oedThis = *itope->ope_Edge; CObjectVertex *povxStartThis, *povxEndThis; // get start and end vertices itope->GetVertices(povxStartThis, povxEndThis); // if start vertex is not on the plane DOUBLE fDistance = itopo->opo_Plane->PointDistance(*povxStartThis); if (Abs(fDistance)>(VTX_EPSILON*16.0)) { // report it _RPT4(_CRT_WARN, "(%f, %f, %f) is 1/%f from plane ", (*povxStartThis)(1), (*povxStartThis)(2), (*povxStartThis)(3), 1.0/fDistance); _RPT4(_CRT_WARN, "(%f, %f, %f):%f\n", (*itopo->opo_Plane)(1), (*itopo->opo_Plane)(2), (*itopo->opo_Plane)(3), itopo->opo_Plane->Distance()); } }} }} } BOOL CObjectSector::ArePolygonsPlanar(void) { // for each polygon {FOREACHINDYNAMICARRAY(osc_aopoPolygons, CObjectPolygon, itopo) { // for each edge in polygon {FOREACHINDYNAMICARRAY(itopo->opo_PolygonEdges, CObjectPolygonEdge, itope) { CObjectEdge &oedThis = *itope->ope_Edge; CObjectVertex *povxStartThis, *povxEndThis; // get start and end vertices itope->GetVertices(povxStartThis, povxEndThis); // if start vertex is not on the plane DOUBLE fDistance = itopo->opo_Plane->PointDistance(*povxStartThis); if (Abs(fDistance)>(VTX_EPSILON*16.0)) { return FALSE; } }} }} return TRUE; } /* * Remap different planes with same coordinates to use only one of each. */ void CObjectSector::RemapClonedPlanes(void) { // if there are no planes in the sector if (osc_aoplPlanes.Count()==0) { // do nothing (optimization algorithms assume at least one plane present) return; } osc_aoplPlanes.Lock(); /* Prepare sorted array for remapping planes. */ // create an array of pointers for sorting planes INDEX ctPlanes = osc_aoplPlanes.Count(); CStaticArray<CObjectPlane *> applSortedPlanes; applSortedPlanes.New(ctPlanes); // for all vertices for(INDEX iPlane=0; iPlane<ctPlanes; iPlane++) { // set the pointer in sorting array applSortedPlanes[iPlane] = &osc_aoplPlanes[iPlane]; // set remap pointer to itself osc_aoplPlanes[iPlane].opl_Remap = &osc_aoplPlanes[iPlane]; } if( wld_bFastObjectOptimization) { // sort the array of pointers, so that same planes get next to each other qsort(&applSortedPlanes[0], ctPlanes, sizeof(CObjectPlane *), qsort_ComparePlanes); /* Create remapping pointers. */ // for all planes in sorted array, except the last one for(INDEX iSortedPlane=0; iSortedPlane<ctPlanes-1; iSortedPlane++) { // if the next plane is same as this one if ( ComparePlanes(*applSortedPlanes[iSortedPlane], *applSortedPlanes[iSortedPlane+1]) == 0 ) { // set its remap pointer to same as this remap pointer applSortedPlanes[iSortedPlane+1]->opl_Remap = applSortedPlanes[iSortedPlane]->opl_Remap; } } } else { // for all pairs of planes {for(INDEX iPlane1=0; iPlane1<ctPlanes; iPlane1++) { CObjectPlane &pl1 = osc_aoplPlanes[iPlane1]; if (pl1.opl_Remap != &pl1) { continue; } for(INDEX iPlane2=iPlane1+1; iPlane2<ctPlanes; iPlane2++) { CObjectPlane &pl2 = osc_aoplPlanes[iPlane2]; if (ComparePlanes(pl1, pl2)==0) { pl2.opl_Remap = pl1.opl_Remap; } } }} } /* Remap all references to planes. */ // for all polygons in object {FOREACHINDYNAMICARRAY(osc_aopoPolygons, CObjectPolygon, itopo) { // remap plane pointers itopo->opo_Plane = itopo->opo_Plane->opl_Remap; }} osc_aoplPlanes.Unlock(); } /* * Remap different vertices with same coordinates to use only one of each. */ static BOOL bBug=FALSE; void CObjectSector::RemapClonedVertices(void) { // if there are no vertices in the sector if (osc_aovxVertices.Count()==0) { // do nothing (optimization algorithms assume at least one vertex present) return; } osc_aovxVertices.Lock(); /* Prepare sorted array for remapping vertices. */ // create an array of pointers for sorting vertices INDEX ctVertices = osc_aovxVertices.Count(); CStaticArray<CObjectVertex *> apvxSortedVertices; apvxSortedVertices.New(ctVertices); // for all vertices for(INDEX iVertex=0; iVertex<ctVertices; iVertex++) { // set the pointer in sorting array apvxSortedVertices[iVertex] = &osc_aovxVertices[iVertex]; // set remap pointer to itself osc_aovxVertices[iVertex].ovx_Remap = &osc_aovxVertices[iVertex]; } if( wld_bFastObjectOptimization) { // sort the array of pointers, so that same vertices get next to each other qsort(&apvxSortedVertices[0], ctVertices, sizeof(CObjectVertex *), qsort_CompareVertices); /* Create remapping pointers. */ // for all vertices in sorted array, except the last one for(INDEX iSortedVertex=0; iSortedVertex<ctVertices-1; iSortedVertex++) { // if the next vertex is same as this one if ( CompareVertices(*apvxSortedVertices[iSortedVertex], *apvxSortedVertices[iSortedVertex+1]) == 0 ) { // set its remap pointer to same as this remap pointer apvxSortedVertices[iSortedVertex+1]->ovx_Remap = apvxSortedVertices[iSortedVertex]->ovx_Remap; // otherwise } else { #ifndef NDEBUG // check that it is really ok CObjectVertex &vx1 = *apvxSortedVertices[iSortedVertex]; CObjectVertex &vx2 = *apvxSortedVertices[iSortedVertex+1]; ASSERT( (&vx1 == &vx2) || (CompareVertices(vx1, vx2)!=0)); #endif } } } else { // for all pairs of vertices {for(INDEX iVertex1=0; iVertex1<ctVertices; iVertex1++) { CObjectVertex &vx1 = osc_aovxVertices[iVertex1]; if (vx1.ovx_Remap != &vx1) { continue; } for(INDEX iVertex2=iVertex1+1; iVertex2<ctVertices; iVertex2++) { CObjectVertex &vx2 = osc_aovxVertices[iVertex2]; if (CompareVertices(vx1, vx2)==0) { vx2.ovx_Remap = vx1.ovx_Remap; } } }} } /* Remap all references to vertices. */ // for all edges in object {FOREACHINDYNAMICARRAY(osc_aoedEdges, CObjectEdge, ited) { // remap vertex pointers ited->oed_Vertex0 = ited->oed_Vertex0->ovx_Remap; ited->oed_Vertex1 = ited->oed_Vertex1->ovx_Remap; }} osc_aovxVertices.Unlock(); #if 0 #ifndef NDEBUG if (bBug) goto skipbug; // for vertices {FOREACHINDYNAMICARRAY(osc_aovxVertices, CObjectVertex, itvx1) { FOREACHINDYNAMICARRAY(osc_aovxVertices, CObjectVertex, itvx2) { CObjectVertex &vx1 = *(itvx1->ovx_Remap); CObjectVertex &vx2 = *(itvx2->ovx_Remap); //ASSERT( (&vx1 == &vx2) || (CompareVertices(vx1, vx2)!=0)); if (!( (&vx1 == &vx2) || (CompareVertices(vx1, vx2)!=0))) { goto bug; } } }} goto skipbug; bug: bBug = TRUE; // for all vertices in sorted array, except the last one {for(INDEX iSortedVertex=0; iSortedVertex<ctVertices-1; iSortedVertex++) { CPrintF("(%f,%f,%f)-(%f,%f,%f) ", (*apvxSortedVertices[iSortedVertex])(1), (*apvxSortedVertices[iSortedVertex])(2), (*apvxSortedVertices[iSortedVertex])(3), (*apvxSortedVertices[iSortedVertex+1])(1), (*apvxSortedVertices[iSortedVertex+1])(2), (*apvxSortedVertices[iSortedVertex+1])(3) ); CPrintF("%d\n", CompareVertices(*apvxSortedVertices[iSortedVertex], *apvxSortedVertices[iSortedVertex+1]) ); }} skipbug:; #endif //NDEBUG #endif //0 } /* * Remove vertices that are not used by any edge. */ void CObjectSector::RemoveUnusedVertices(void) { // if there are no vertices in the sector if (osc_aovxVertices.Count()==0) { // do nothing (optimization algorithms assume at least one vertex present) return; } // clear all vertex tags {FOREACHINDYNAMICARRAY(osc_aovxVertices, CObjectVertex, itovx) { itovx->ovx_Tag = FALSE; }} // mark all vertices that are used by some edge {FOREACHINDYNAMICARRAY(osc_aoedEdges, CObjectEdge, ited) { ited->oed_Vertex0->ovx_Tag = TRUE; ited->oed_Vertex1->ovx_Tag = TRUE; }} // find number of used vertices INDEX ctUsedVertices = 0; {FOREACHINDYNAMICARRAY(osc_aovxVertices, CObjectVertex, itovx) { if (itovx->ovx_Tag) { ctUsedVertices++; } }} // create a new array with as much vertices as we have counted in last pass CDynamicArray<CObjectVertex> aovxNew; CObjectVertex *povxUsed = aovxNew.New(ctUsedVertices); // for each vertex {FOREACHINDYNAMICARRAY(osc_aovxVertices, CObjectVertex, itovx) { // if it is used if (itovx->ovx_Tag) { // copy it to new array *povxUsed = itovx.Current(); // set its remap pointer into new array itovx->ovx_Remap = povxUsed; povxUsed++; // if it is not used } else { // clear its remap pointer (for debugging) #ifndef NDEBUG itovx->ovx_Remap = NULL; #endif } }} // for all edges in object {FOREACHINDYNAMICARRAY(osc_aoedEdges, CObjectEdge, ited) { // remap vertex pointers ited->oed_Vertex0 = ited->oed_Vertex0->ovx_Remap; ited->oed_Vertex1 = ited->oed_Vertex1->ovx_Remap; }} // use new array of vertices instead of the old one osc_aovxVertices.Clear(); osc_aovxVertices.MoveArray(aovxNew); } /* * Remove edges that are not used by any polygon. */ void CObjectSector::RemoveUnusedEdges(void) { // if there are no edges in the sector if (osc_aoedEdges.Count()==0) { // do nothing (optimization algorithms assume at least one edge present) return; } // clear all edge tags {FOREACHINDYNAMICARRAY(osc_aoedEdges, CObjectEdge, itoed) { itoed->oed_Tag = FALSE; }} // mark all edges that are used by some polygon {FOREACHINDYNAMICARRAY(osc_aopoPolygons, CObjectPolygon, itopo) { {FOREACHINDYNAMICARRAY(itopo->opo_PolygonEdges, CObjectPolygonEdge, itope) { itope->ope_Edge->oed_Tag = TRUE; }} }} // find number of used edges INDEX ctUsedEdges = 0; {FOREACHINDYNAMICARRAY(osc_aoedEdges, CObjectEdge, itoed) { if (itoed->oed_Tag) { ctUsedEdges++; } }} // create a new array with as much edges as we have counted in last pass CDynamicArray<CObjectEdge> aoedNew; CObjectEdge *poedUsed = aoedNew.New(ctUsedEdges); // for each edge {FOREACHINDYNAMICARRAY(osc_aoedEdges, CObjectEdge, itoed) { // if it is used if (itoed->oed_Tag) { // copy it to new array *poedUsed = itoed.Current(); // set its remap pointer into new array itoed->optimize.oed_Remap = poedUsed; poedUsed++; // if it is not used } else { // clear its remap pointer (for debugging) #ifndef NDEBUG itoed->optimize.oed_Remap = NULL; #endif } }} // remap edge pointers in all polygons {FOREACHINDYNAMICARRAY(osc_aopoPolygons, CObjectPolygon, itopo) { {FOREACHINDYNAMICARRAY(itopo->opo_PolygonEdges, CObjectPolygonEdge, itope) { itope->ope_Edge = itope->ope_Edge->optimize.oed_Remap; }} }} // use new array of edges instead the old one osc_aoedEdges.Clear(); osc_aoedEdges.MoveArray(aoedNew); } /* * Remove planes that are not used by any polygon. */ void CObjectSector::RemoveUnusedPlanes(void) { // if there are no planes in the sector if (osc_aoplPlanes.Count()==0) { // do nothing (optimization algorithms assume at least one plane present) return; } // clear all plane tags {FOREACHINDYNAMICARRAY(osc_aoplPlanes, CObjectPlane, itopl) { itopl->opl_Tag = FALSE; }} // mark all planes that are used by some polygon {FOREACHINDYNAMICARRAY(osc_aopoPolygons, CObjectPolygon, itopo) { itopo->opo_Plane->opl_Tag = TRUE; }} // find number of used planes INDEX ctUsedPlanes = 0; {FOREACHINDYNAMICARRAY(osc_aoplPlanes, CObjectPlane, itopl) { if (itopl->opl_Tag) { ctUsedPlanes++; } }} // create a new array with as much planes as we have counted in last pass CDynamicArray<CObjectPlane> aoplNew; CObjectPlane *poplUsed = aoplNew.New(ctUsedPlanes); // for each plane {FOREACHINDYNAMICARRAY(osc_aoplPlanes, CObjectPlane, itopl) { // if it is used if (itopl->opl_Tag) { // copy it to new array *poplUsed = itopl.Current(); // set its remap pointer into new array itopl->opl_Remap = poplUsed; poplUsed++; // if it is not used } else { // clear its remap pointer (for debugging) #ifndef NDEBUG itopl->opl_Remap = NULL; #endif } }} // remap plane pointers in all polygons {FOREACHINDYNAMICARRAY(osc_aopoPolygons, CObjectPolygon, itopo) { itopo->opo_Plane = itopo->opo_Plane->opl_Remap; }} // use new array of planes instead the old one osc_aoplPlanes.Clear(); osc_aoplPlanes.MoveArray(aoplNew); } /* * Split an edge with a run of vertices. * NOTE: 'sortvertices_iMaxAxis' must be set correctly! */ void CObjectSector::SplitEdgeWithVertices(CObjectEdge &oedOriginal, CStaticArray<CObjectVertex *> &apvxVertices) { CObjectVertex *pvx0 = oedOriginal.oed_Vertex0; CObjectVertex *pvx1 = oedOriginal.oed_Vertex1; BOOL bReversed; // edge must have length ASSERT(CompareVertices(*pvx0, *pvx1)!=0); // if the edge is going in the direction of the run if (CompareVerticesAlongLine(*pvx0, *pvx1)<0) { // mark that it is not reversed bReversed = FALSE; // if the edge is going in opposite direction of the run } else { // swap vertices Swap(pvx0, pvx1); // mark that it is reversed bReversed = TRUE; } // init the edge as remap pointer oedOriginal.colinear1.oed_FirstChild = NULL; INDEX iVertex; // current vertex in run // skip until start vertex is found for (iVertex=0; apvxVertices[iVertex]!=pvx0; iVertex++) { NOTHING; } // for all vertices after start vertex and stopping with end vertex for (iVertex++; apvxVertices[iVertex-1]!=pvx1; iVertex++) { // if vertex is not same as last one if (apvxVertices[iVertex]!=apvxVertices[iVertex-1]) { // create a new edge between the two vertices CObjectEdge *poedNewChild = osc_aoedEdges.New(); if (bReversed) { *poedNewChild = CObjectEdge(*apvxVertices[iVertex], *apvxVertices[iVertex-1]); } else { *poedNewChild = CObjectEdge(*apvxVertices[iVertex-1], *apvxVertices[iVertex]); } ASSERT(CompareVertices(*poedNewChild->oed_Vertex0, *poedNewChild->oed_Vertex1)!=0); // link it as a child of this edge poedNewChild->colinear1.oed_NextSibling = oedOriginal.colinear1.oed_FirstChild; oedOriginal.colinear1.oed_FirstChild = poedNewChild; } } } /* * Join polygon edges that are collinear are continuing. */ void CObjectSector::JoinContinuingPolygonEdges(void) { osc_aoedEdges.Lock(); // create array of extended edge infosd CStaticArray<CEdgeEx> aedxEdgeLines; CStaticArray<CEdgeEx *> apedxSortedEdgeLines; INDEX ctEdges = osc_aoedEdges.Count(); CreateEdgeLines(aedxEdgeLines, apedxSortedEdgeLines, ctEdges); /* NOTE: The array doesn't have to be sorted because only edges in one polygon are considered. */ // for each polygon {FOREACHINDYNAMICARRAY(osc_aopoPolygons, CObjectPolygon, itopo) { // join continuing edges in it itopo->JoinContinuingEdges(osc_aoedEdges); }} osc_aoedEdges.Unlock(); } /* * Join polygon edges that are collinear and continuing. */ /* * NOTE: Edge line infos must be prepared before calling this. */ void CObjectPolygon::JoinContinuingEdges(CDynamicArray<CObjectEdge> &oedEdges) { // create an empty array for newly created edges CDynamicArray<CObjectPolygonEdge> aopeNew; // set the counter of edges to current number of edges INDEX ctEdges = opo_PolygonEdges.Count(); // for each edge {FOREACHINDYNAMICARRAY(opo_PolygonEdges, CObjectPolygonEdge, itope) { CObjectEdge &oedThis = *itope->ope_Edge; // if not already marked for removal if (&oedThis != NULL) { CObjectVertex *povxStartThis, *povxEndThis; // get start and end vertices itope->GetVertices(povxStartThis, povxEndThis); // mark the original edge for removal itope->ope_Edge = NULL; BOOL bChangeDone; // termination condition flag // repeat do { // mark that nothing is changed bChangeDone = FALSE; // for each edge {FOREACHINDYNAMICARRAY(opo_PolygonEdges, CObjectPolygonEdge, itope2) { CObjectEdge &oedOther = *itope2->ope_Edge; // if not already marked for removal if (&oedOther != NULL) { // if the two edges are collinear if ( CompareEdgeLines(*oedThis.colinear2.oed_pedxLine, *oedOther.colinear2.oed_pedxLine)==0) { CObjectVertex *povxStartOther, *povxEndOther; // get start and end vertices itope2->GetVertices(povxStartOther, povxEndOther); // if the other edge is continuing to this one if ( povxStartOther == povxEndThis ) { // extend the current edge by it povxEndThis = povxEndOther; // mark it for removal itope2->ope_Edge = NULL; // mark that a change is done bChangeDone = TRUE; // if the other edge is continued by this one } else if ( povxStartThis == povxEndOther) { // extend the current edge by it povxStartThis = povxStartOther; // mark it for removal itope2->ope_Edge = NULL; // mark that a change is done bChangeDone = TRUE; } } } }} // until a pass is made without making any change } while (bChangeDone); // create new edge with given start and end vertices CObjectEdge *poedNew = oedEdges.New(); *poedNew = CObjectEdge(*povxStartThis, *povxEndThis); // add it to new array *aopeNew.New() = CObjectPolygonEdge(poedNew); } }} // replace old array with the new one opo_PolygonEdges.Clear(); opo_PolygonEdges.MoveArray(aopeNew); } /* * Split a run of collinear edges. */ void CObjectSector::SplitCollinearEdgesRun(CStaticArray<CEdgeEx *> &apedxSortedEdgeLines, INDEX iFirstInRun, INDEX iLastInRun) { if (iFirstInRun>iLastInRun) { return; // this should not happen, but anyway! } CEdgeEx &edxLine = *apedxSortedEdgeLines[iFirstInRun]; // representative line of the run /* set up array of vertex pointers */ /* // for each vertex in sector INDEX ctVerticesOnLine=0; {FOREACHINDYNAMICARRAY(osc_aovxVertices, CObjectVertex, itovx) { // if it is on the line if (edxLine.PointIsOnLine(*itovx)) { // mark it as on the line itovx->ovx_Tag = TRUE; ctVerticesOnLine++; // if it is on the line } else { // mark it as on not the line itovx->ovx_Tag = FALSE; } }} // for all edges in run {for(INDEX iEdgeInRun=iFirstInRun; iEdgeInRun<=iLastInRun; iEdgeInRun++) { CObjectEdge &oed = *apedxSortedEdgeLines[iEdgeInRun]->edx_poedEdge; // if first vertex is not marked if (!oed.oed_Vertex0->ovx_Tag) { // mark it oed.oed_Vertex0->ovx_Tag = TRUE; ctVerticesOnLine++; } // if second vertex is not marked if (!oed.oed_Vertex1->ovx_Tag) { // mark it oed.oed_Vertex1->ovx_Tag = TRUE; ctVerticesOnLine++; } }} // if there are no vertices if (ctVerticesOnLine==0) { // do nothing return; } // create array of vertex pointers CStaticArray<CObjectVertex *> apvxSortedVertices; apvxSortedVertices.New(ctVerticesOnLine); // for each vertex in sector INDEX iVertexOnLine=0; {FOREACHINDYNAMICARRAY(osc_aovxVertices, CObjectVertex, itovx) { // if it is marked if (itovx->ovx_Tag) { // set its pointer in the array apvxSortedVertices[iVertexOnLine++] = &*itovx; } }} ASSERT(iVertexOnLine == ctVerticesOnLine); */ // create array of vertex pointers INDEX ctVerticesOnLine = (iLastInRun-iFirstInRun+1)*2; // if there are no vertices if (ctVerticesOnLine==0) { // do nothing return; } CStaticArray<CObjectVertex *> apvxSortedVertices; apvxSortedVertices.New(ctVerticesOnLine); // for all edges in run {for(INDEX iEdgeInRun=iFirstInRun; iEdgeInRun<=iLastInRun; iEdgeInRun++) { CObjectEdge &oed = *apedxSortedEdgeLines[iEdgeInRun]->edx_poedEdge; // set vertex pointers apvxSortedVertices[(iEdgeInRun-iFirstInRun)*2] = oed.oed_Vertex0; apvxSortedVertices[(iEdgeInRun-iFirstInRun)*2+1] = oed.oed_Vertex1; }} /* sort the array of vertex pointers */ // get axis direction from any edge in the run DOUBLE3D vDirection = apedxSortedEdgeLines[iFirstInRun]->edx_vDirection; // find largest axis of direction vector INDEX iMaxAxis = 0; DOUBLE fMaxAxis = 0.0f; for (INDEX iAxis=1; iAxis<=3; iAxis++) { if ( Abs(vDirection(iAxis)) > Abs(fMaxAxis) ) { fMaxAxis = vDirection(iAxis); iMaxAxis = iAxis; } } // set axis for sorting sortvertices_iMaxAxis = iMaxAxis; DOUBLE fMaxAxisSign = Sgn(fMaxAxis); ASSERT(Abs(fMaxAxisSign) > +VTX_EPSILON); // if axis is positive if (fMaxAxisSign > +VTX_EPSILON) { // sort vertex pointers along the line if (ctVerticesOnLine>0) { qsort(&apvxSortedVertices[0], ctVerticesOnLine, sizeof(CObjectVertex *), qsort_CompareVerticesAlongLine); } // if axis is negative } else if (fMaxAxisSign < -VTX_EPSILON) { // sort vertex pointers along the line reversely if (ctVerticesOnLine>0) { qsort(&apvxSortedVertices[0], ctVerticesOnLine, sizeof(CObjectVertex *), qsort_CompareVerticesAlongLine); //qsort_CompareVerticesAlongLineReversely); ? } } /* split each edge in turn */ // for all edges in run {for(INDEX iEdgeInRun=iFirstInRun; iEdgeInRun<=iLastInRun; iEdgeInRun++) { CObjectEdge &oed = *apedxSortedEdgeLines[iEdgeInRun]->edx_poedEdge; // split the edge with the vertices SplitEdgeWithVertices(oed, apvxSortedVertices); }} } /* * Create array of extended edge infos. */ void CObjectSector::CreateEdgeLines(CStaticArray<CEdgeEx> &aedxEdgeLines, CStaticArray<CEdgeEx *> &apedxSortedEdgeLines, INDEX &ctEdges) { // create array of extended edge infos ctEdges = osc_aoedEdges.Count(); aedxEdgeLines.New(ctEdges); // create array of pointers to extended edge infos for sorting edges apedxSortedEdgeLines.New(ctEdges); // for all edges for(INDEX iEdge=0; iEdge<ctEdges; iEdge++) { // create extended info aedxEdgeLines[iEdge].Initialize(&osc_aoedEdges[iEdge]); // set the pointer in sorting array apedxSortedEdgeLines[iEdge] = &aedxEdgeLines[iEdge]; // set back-reference in the edge osc_aoedEdges[iEdge].colinear2.oed_pedxLine = &aedxEdgeLines[iEdge]; } } /* * Find collinear edges and cross-split them with each other. */ void CObjectSector::SplitCollinearEdges(void) { osc_aoedEdges.Lock(); /* Prepare arrays for splitting edges. */ // create array of extended edge infosd CStaticArray<CEdgeEx> aedxEdgeLines; CStaticArray<CEdgeEx *> apedxSortedEdgeLines; INDEX ctEdges = osc_aoedEdges.Count(); CreateEdgeLines(aedxEdgeLines, apedxSortedEdgeLines, ctEdges); // sort the array of pointers, so that collinear edges get next to each other if (ctEdges>0) { qsort(&apedxSortedEdgeLines[0], ctEdges, sizeof(CEdgeEx *), qsort_CompareEdgeLines); } /* Create remapping pointers. */ // for all edges in normal sorted array, starting at second one INDEX iFirstInLine = 0; for(INDEX iSortedEdge=1; iSortedEdge<ctEdges; iSortedEdge++) { // if the previous edge is not collinear with this one if ( CompareEdgeLines(*apedxSortedEdgeLines[iSortedEdge], *apedxSortedEdgeLines[iSortedEdge-1]) != 0 ) { // split the last run of collinear edges SplitCollinearEdgesRun(apedxSortedEdgeLines, iFirstInLine, iSortedEdge-1); // start a new run of collinear edges iFirstInLine = iSortedEdge; } } // split the last run of collinear edges SplitCollinearEdgesRun(apedxSortedEdgeLines, iFirstInLine, ctEdges-1); /* Remap references to edges. */ // for all polygons in object {FOREACHINDYNAMICARRAY(osc_aopoPolygons, CObjectPolygon, itpo2) { /* create new array of polygon edges for child edges */ // for all of its edge pointers INDEX ctNewEdges = 0; {FOREACHINDYNAMICARRAY(itpo2->opo_PolygonEdges, CObjectPolygonEdge, itope) { // count all children for (CObjectEdge *poed = itope->ope_Edge->colinear1.oed_FirstChild; poed!=NULL; poed = poed->colinear1.oed_NextSibling) { ctNewEdges++; } }} // create a new array of edge references CDynamicArray<CObjectPolygonEdge> aopoNewPolygonEdges; if (ctNewEdges>0) { aopoNewPolygonEdges.New(ctNewEdges); } aopoNewPolygonEdges.Lock(); /* set the array */ // for all of its edge pointers INDEX iChildEdge = 0; {FOREACHINDYNAMICARRAY(itpo2->opo_PolygonEdges, CObjectPolygonEdge, itope) { // for all of its children for (CObjectEdge *poed = itope->ope_Edge->colinear1.oed_FirstChild; poed!=NULL; poed = poed->colinear1.oed_NextSibling) { // set the child polygon edge aopoNewPolygonEdges[iChildEdge] = CObjectPolygonEdge(poed, itope->ope_Backward); iChildEdge++; } }} /* replace old array with the new one */ aopoNewPolygonEdges.Unlock(); itpo2->opo_PolygonEdges.Clear(); itpo2->opo_PolygonEdges.MoveArray(aopoNewPolygonEdges); }} osc_aoedEdges.Unlock(); } /* * Remove polygon edges that are used twice from all polygons. */ void CObjectSector::RemoveRedundantPolygonEdges(void) { // for each polygon {FOREACHINDYNAMICARRAY(osc_aopoPolygons, CObjectPolygon, itopo) { // remove redundant edges from it itopo->RemoveRedundantEdges(); }} } /* * Remove polygon edges that are used twice from a polygon. */ /* * We will use ope_Edge set to NULL for marking an edge for removal. */ void CObjectPolygon::RemoveRedundantEdges(void) { // set the counter of edges to current number of edges INDEX ctEdges = opo_PolygonEdges.Count(); // for each edge {FOREACHINDYNAMICARRAY(opo_PolygonEdges, CObjectPolygonEdge, itope) { // if not already marked for removal if (itope->ope_Edge != NULL) { // for each edge {FOREACHINDYNAMICARRAY(opo_PolygonEdges, CObjectPolygonEdge, itope2) { // if it uses same edge in opposite direction if (itope2->ope_Edge == itope->ope_Edge && itope2->ope_Backward != itope->ope_Backward ) { // mark them both for removal itope2->ope_Edge = NULL; itope->ope_Edge = NULL; // mark that there are two edges less ctEdges -= 2; // don't test this edge any more break; } }} } }} // remove polygon edges that are mark as unused RemoveMarkedEdges(ctEdges); } /* * Remove polygon edges that are marked as unused (oed_Edge==NULL) from polygon. */ void CObjectPolygon::RemoveMarkedEdges(INDEX ctEdges) { // create a new array of edge references CDynamicArray<CObjectPolygonEdge> aoedNewPolygonEdges; if (ctEdges>0) { aoedNewPolygonEdges.New(ctEdges); } aoedNewPolygonEdges.Lock(); // for each edge INDEX iNewEdge = 0; {FOREACHINDYNAMICARRAY(opo_PolygonEdges, CObjectPolygonEdge, itope) { // if it is not marked for removal if (itope->ope_Edge != NULL) { // copy it aoedNewPolygonEdges[iNewEdge++] = itope.Current(); } }} // replace old array with the new one aoedNewPolygonEdges.Unlock(); opo_PolygonEdges.Clear(); opo_PolygonEdges.MoveArray(aoedNewPolygonEdges); } /* * Remove polygon edges that have zero length from a polygon. */ void CObjectPolygon::RemoveDummyEdgeReferences(void) { INDEX ctUsedEdges = opo_PolygonEdges.Count(); // for all edges in polygon {FOREACHINDYNAMICARRAY(opo_PolygonEdges, CObjectPolygonEdge, itope) { // if it has zero length if (itope->ope_Edge->oed_Vertex0 == itope->ope_Edge->oed_Vertex1) { ASSERT(CompareVertices(*itope->ope_Edge->oed_Vertex0, *itope->ope_Edge->oed_Vertex1)==0); // mark it for removal itope->ope_Edge = NULL; ctUsedEdges--; } else { // !!!! why this fails sometimes? ASSERT(CompareVertices(*itope->ope_Edge->oed_Vertex0, *itope->ope_Edge->oed_Vertex1)!=0); } }} // remove all marked edges from the polygon RemoveMarkedEdges(ctUsedEdges); } /* * Find edges that have zero length and remove them from all polygons. */ void CObjectSector::RemoveDummyEdgeReferences(void) { // for each polygon {FOREACHINDYNAMICARRAY(osc_aopoPolygons, CObjectPolygon, itopo) { // remove zero length edges from it itopo->RemoveDummyEdgeReferences(); }} } /* * Remap different edges with same or reverse vertices to use only one of each. */ void CObjectSector::RemapClonedEdges(void) { // if there are no edges in the sector if (osc_aoedEdges.Count()==0) { // do nothing return; } osc_aoedEdges.Lock(); /* Prepare sorted arrays for remapping edges. */ // create array of pointers for sorting edges INDEX ctEdges = osc_aoedEdges.Count(); CStaticArray<CObjectEdge *> apedSortedEdges; apedSortedEdges.New(ctEdges); // for all edges for(INDEX iEdge=0; iEdge<ctEdges; iEdge++) { // set the pointers in sorting array apedSortedEdges[iEdge] = &osc_aoedEdges[iEdge]; // set remap pointer to itself osc_aoedEdges[iEdge].optimize.oed_Remap = &osc_aoedEdges[iEdge]; // clear the inverse pointer osc_aoedEdges[iEdge].optimize.oed_InverseEdge = NULL; // clear the edge tag, meaning that this edge is unused osc_aoedEdges[iEdge].oed_Tag = FALSE; } // sort the array of pointers, so that same edges get next to each other qsort(&apedSortedEdges[0], ctEdges, sizeof(CObjectEdge *), qsort_CompareEdges); /* Create remapping pointers. */ // for all edges in normal sorted array, except the last one for(INDEX iSortedEdge=0; iSortedEdge<ctEdges-1; iSortedEdge++) { // if the next plane is same as this one if ( CompareEdges(*apedSortedEdges[iSortedEdge], *apedSortedEdges[iSortedEdge+1]) == 0 ) { // set its remap pointer to same as this remap pointer apedSortedEdges[iSortedEdge+1]->optimize.oed_Remap = apedSortedEdges[iSortedEdge]->optimize.oed_Remap; } } /* Create inverse pointers. */ // for all edges in sorted array for(INDEX iNormalEdge=0; iNormalEdge<ctEdges; iNormalEdge++) { CObjectEdge &edNormal = *apedSortedEdges[iNormalEdge]; CObjectEdge *pedInverse; // if this edge is not remapped if (edNormal.optimize.oed_Remap == &edNormal) { // if some edge with inverse vertices is found if (FindEdge(apedSortedEdges, *edNormal.oed_Vertex1, *edNormal.oed_Vertex0, &pedInverse)) { // take its remap edge CObjectEdge &edInverse = *pedInverse->optimize.oed_Remap; // if the inverse edge pointer is less than this pointer if (&edInverse < &edNormal) { // the inverse edge must not be remapped ASSERT(&edInverse == edInverse.optimize.oed_Remap); // set the inverse pointer in this edge to the inverse edge edNormal.optimize.oed_InverseEdge = &edInverse; } } } } #ifndef NDEBUG // for all edges in object {FOREACHINDYNAMICARRAY(osc_aoedEdges, CObjectEdge, ited) { CObjectEdge &edInverse = *ited->optimize.oed_InverseEdge; // check that no remapped edges have been marked as inverses ASSERT( &edInverse==NULL || edInverse.optimize.oed_Remap == &edInverse ); }} #endif // NDEBUG /* Remap all references to edges. */ // for all polygons in object {FOREACHINDYNAMICARRAY(osc_aopoPolygons, CObjectPolygon, itpo2) { // for all of its edge pointers FOREACHINDYNAMICARRAY(itpo2->opo_PolygonEdges, CObjectPolygonEdge, itope) { // get the remapped edge pointer CObjectEdge *pedNew= itope->ope_Edge->optimize.oed_Remap; // if has an inverse edge if (pedNew->optimize.oed_InverseEdge!=NULL) { // use the inverse edge pedNew = pedNew->optimize.oed_InverseEdge; // mark that the direction has changed itope->ope_Backward = !itope->ope_Backward; } // use the edge itope->ope_Edge = pedNew; } }} osc_aoedEdges.Unlock(); } /* * Remove unused polygons and polygons with less than 3 edges. */ void CObjectSector::RemoveDummyPolygons(void) { // for each polygon INDEX ctUsedPolygons = 0; {FOREACHINDYNAMICARRAY(osc_aopoPolygons, CObjectPolygon, itopo) { // if it has more than 2 edges if(itopo->opo_PolygonEdges.Count()>2) { // mark it as used itopo->opo_Tag = TRUE; // count it ctUsedPolygons++; // if it has less than 3 edges } else { // mark it as unused itopo->opo_Tag = FALSE; } }} // create a new array of polygons CDynamicArray<CObjectPolygon> aopoNew; CObjectPolygon *popoNew = aopoNew.New(ctUsedPolygons); // for each polygon in sector {FOREACHINDYNAMICARRAY(osc_aopoPolygons, CObjectPolygon, itopo) { // if it is used if (itopo->opo_Tag) { // add it to the new array *popoNew++ = itopo.Current(); } }} // replace old array with the new one osc_aopoPolygons.Clear(); osc_aopoPolygons.MoveArray(aopoNew); } /* * Remove unused and replicated elements. */ void CObjectSector::Optimize(void) { /* // for each vertex {FOREACHINDYNAMICARRAY(osc_aovxVertices, CObjectVertex, itovx) { // snap the vertex coordinates Snap((*itovx)(1), VTX_SNAP); Snap((*itovx)(2), VTX_SNAP); Snap((*itovx)(3), VTX_SNAP); }} // for each plane {FOREACHINDYNAMICARRAY(osc_aoplPlanes, CObjectPlane, itopl) { // snap the plane coordinates Snap((*itopl)(1), PLX_SNAP); Snap((*itopl)(2), PLX_SNAP); Snap((*itopl)(3), PLX_SNAP); Snap((*itopl).Distance(), PLX_SNAP); }} */ // remove polygons with less than 3 edges RemoveDummyPolygons(); // remap different vertices with same coordinates RemapClonedVertices(); // remove unused vertices RemoveUnusedVertices(); // find edges that have zero length and remove them from all polygons RemoveDummyEdgeReferences(); // remap different edges with same or reverse vertices RemapClonedEdges(); // remove unused edges RemoveUnusedEdges(); // find collinear edges and cross split them with each other SplitCollinearEdges(); // find edges that are used twice in same polygon and remove them from the polygon RemoveRedundantPolygonEdges(); // find edges that are collinear and continuing in some polygon and join them JoinContinuingPolygonEdges(); // find edges that have zero length and remove them from all polygons RemoveDummyEdgeReferences(); // remap different edges with same or reverse vertices RemapClonedEdges(); // remove unused edges RemoveUnusedEdges(); // remove unused vertices RemoveUnusedVertices(); // find collinear edges and cross split them with each other SplitCollinearEdges(); // find edges that have zero length and remove them from all polygons RemoveDummyEdgeReferences(); // remap different edges with same or reverse vertices RemapClonedEdges(); // remove unused edges RemoveUnusedEdges(); // remove unused vertices RemoveUnusedVertices(); // find edges that are used twice in same polygon and remove them from the polygon RemoveRedundantPolygonEdges(); // remap different planes with same coordinates RemapClonedPlanes(); // remove unused planes RemoveUnusedPlanes(); // remove polygons with less than 3 edges RemoveDummyPolygons(); #ifndef NDEBUG // check the optimization algorithm CheckOptimizationAlgorithm(); #endif } /* * Assignment operator. */ CObjectSector &CObjectSector::operator=(CObjectSector &oscOriginal) { // remove current contents Clear(); // copy basic properties osc_colColor = oscOriginal.osc_colColor; osc_colAmbient = oscOriginal.osc_colAmbient; osc_ulFlags[0] = oscOriginal.osc_ulFlags[0]; osc_ulFlags[1] = oscOriginal.osc_ulFlags[1]; osc_ulFlags[2] = oscOriginal.osc_ulFlags[2]; osc_strName = oscOriginal.osc_strName; // create indices in original sector oscOriginal.CreateIndices(); // copy arrays of elements from original sector osc_aovxVertices = oscOriginal.osc_aovxVertices; osc_aoplPlanes = oscOriginal.osc_aoplPlanes; osc_aomtMaterials = oscOriginal.osc_aomtMaterials; osc_aoedEdges = oscOriginal.osc_aoedEdges; osc_aopoPolygons = oscOriginal.osc_aopoPolygons; // lock all arrays LockAll(); // for all edges FOREACHINDYNAMICARRAY(osc_aoedEdges, CObjectEdge, itoed) { // use vertices in this object with same index itoed->oed_Vertex0 = &osc_aovxVertices[itoed->oed_Vertex0->ovx_Index]; itoed->oed_Vertex1 = &osc_aovxVertices[itoed->oed_Vertex1->ovx_Index]; } // for all polygons FOREACHINDYNAMICARRAY(osc_aopoPolygons, CObjectPolygon, itopo) { // use plane and material in this sector with same indices itopo->opo_Plane = &osc_aoplPlanes[itopo->opo_Plane->opl_Index]; if (itopo->opo_Material!=NULL) { itopo->opo_Material = &osc_aomtMaterials[itopo->opo_Material->omt_Index]; } // for all polygon-edges in this polygon FOREACHINDYNAMICARRAY(itopo->opo_PolygonEdges, CObjectPolygonEdge, itope) { // use edge in this sector with same index itope->ope_Edge = &osc_aoedEdges[itope->ope_Edge->oed_Index]; } } // unlock all arrays UnlockAll(); return *this; } /* * Find bounding box of the sector. */ void CObjectSector::GetBoundingBox(DOUBLEaabbox3D &boxSector) { // clear the bounding box boxSector = DOUBLEaabbox3D(); // for each vertex in the sector FOREACHINDYNAMICARRAY(osc_aovxVertices, CObjectVertex, itovx) { // add the vertex to the bounding box boxSector |= *itovx; } } /* * Create BSP tree for sector. */ void CObjectSector::CreateBSP(void) { /* prepare array of bsp polygons */ // get count of polygons in sector const INDEX ctPolygons = osc_aopoPolygons.Count(); // create array of BSP polygons CDynamicArray<DOUBLEbsppolygon3D> arbpo; // create as much BSP polygons as there are polygons in this sector arbpo.New(ctPolygons); arbpo.Lock(); osc_aopoPolygons.Lock(); // for each polygon in this sector for(INDEX iPolygon=0; iPolygon<ctPolygons; iPolygon++) { CObjectPolygon &opo = osc_aopoPolygons[iPolygon]; DOUBLEbsppolygon3D &bpo = arbpo[iPolygon]; // copy the plane (DOUBLEplane3D &)bpo = *opo.opo_Plane; STUBBED("64-bit issue"); bpo.bpo_ulPlaneTag = (ULONG)(size_t)opo.opo_Plane; // get count of edges in this polygon const INDEX ctEdges = opo.opo_PolygonEdges.Count(); // create that much edges in bsp polygon DOUBLEbspedge3D *pbed = bpo.bpo_abedPolygonEdges.New(ctEdges); opo.opo_PolygonEdges.Lock(); // for each edge in this polygon for(INDEX iEdge=0; iEdge<ctEdges; iEdge++) { CObjectPolygonEdge &ope = opo.opo_PolygonEdges[iEdge]; CObjectEdge &oed = *ope.ope_Edge; // if the edge is reversed if(ope.ope_Backward) { // add bsp edge with reversed vertices STUBBED("64-bit issue"); pbed[iEdge] = DOUBLEbspedge3D(*oed.oed_Vertex1, *oed.oed_Vertex0, (ULONG)(size_t)&oed); // otherwise } else { // add normal bsp edge STUBBED("64-bit issue"); pbed[iEdge] = DOUBLEbspedge3D(*oed.oed_Vertex0, *oed.oed_Vertex1, (ULONG)(size_t)&oed); } } opo.opo_PolygonEdges.Unlock(); } arbpo.Unlock(); osc_aopoPolygons.Unlock(); /* create bsp tree from array of bsp polygons */ osc_BSPTree.Create(arbpo); } /* * Turn sector inside-out. */ void CObjectSector::Inverse(void) { // for all planes FOREACHINDYNAMICARRAY(osc_aoplPlanes, CObjectPlane, itopl) { // flip the plane (DOUBLEplane3D &)*itopl = -(DOUBLEplane3D &)*itopl; } // for all polygons FOREACHINDYNAMICARRAY(osc_aopoPolygons, CObjectPolygon, itopo) { // for all polygon-edges in this polygon FOREACHINDYNAMICARRAY(itopo->opo_PolygonEdges, CObjectPolygonEdge, itope) { // reverse the polygon edge direction itope->ope_Backward = !itope->ope_Backward; } } } /* Recalculate all planes from vertices. (used when stretching vertices) */ void CObjectSector::RecalculatePlanes(void) { // create a container for empty polygons CDynamicContainer<CObjectPolygon> copoEmpty; // for all polygons {FOREACHINDYNAMICARRAY(osc_aopoPolygons, CObjectPolygon, itopo) { CObjectPolygon &opo = *itopo; // clear plane normal DOUBLE3D vNormal = DOUBLE3D(0.0f,0.0f,0.0f); // for all edges in polygon INDEX ctVertices = opo.opo_PolygonEdges.Count(); opo.opo_PolygonEdges.Lock(); {for(INDEX iVertex=0; iVertex<ctVertices; iVertex++) { // get its vertices in counterclockwise order CObjectPolygonEdge &ope = opo.opo_PolygonEdges[iVertex]; CObjectVertex *povx0, *povx1; if (ope.ope_Backward) { povx0 = ope.ope_Edge->oed_Vertex1; povx1 = ope.ope_Edge->oed_Vertex0; } else { povx0 = ope.ope_Edge->oed_Vertex0; povx1 = ope.ope_Edge->oed_Vertex1; } DOUBLE3D vSum = *povx0+*povx1; DOUBLE3D vDif = *povx0-*povx1; // add the edge contribution to the normal vector vNormal(1) += vDif(2)*vSum(3); vNormal(2) += vDif(3)*vSum(1); vNormal(3) += vDif(1)*vSum(2); }} // if the polygon area is too small if (vNormal.Length()<1E-8) { // mark it for removal copoEmpty.Add(&opo); // if the polygon area is ok } else { // add one plane to planes array CObjectPlane *pplPlane = osc_aoplPlanes.New(); // construct this plane from normal vector and one point *pplPlane= DOUBLEplane3D(vNormal, *opo.opo_PolygonEdges[0].ope_Edge->oed_Vertex0); opo.opo_Plane = pplPlane; } opo.opo_PolygonEdges.Unlock(); }} // for all empty polygons {FOREACHINDYNAMICCONTAINER(copoEmpty, CObjectPolygon, itopoEmpty) { // delete the polygon from sector osc_aopoPolygons.Delete(itopoEmpty); }} }