std::tuple<vec3, vec3, vec3> fitPlaneAlignedOrientedBoundingBox2D(const std::vector<vec3>& points, const Plane& plane) { vec3 u, v; if (fabs(plane.getNormal().x) > fabs(plane.getNormal().y)) { u = glm::normalize(plane.projectPoint(vec3(1.f, 0.f, 0)) - plane.getPoint()); } else { u = glm::normalize(plane.projectPoint(vec3(0.f, 1.f, 0)) - plane.getPoint()); } v = glm::normalize(glm::cross(plane.getNormal(), u)); std::vector<vec2> projectedPoints; projectPointsOnPlane(points, plane, u, v, projectedPoints); auto convexHull = geometry::convexHull2D(projectedPoints); OrientedBoundingBox2D boundingBox = geometry::mimumBoundingRectangle(convexHull); vec3 boundingBoxOrigin = plane.getPoint() + boundingBox.origin.x*u + boundingBox.origin.y*v; return std::make_tuple(boundingBoxOrigin, (boundingBox.u.x*u + boundingBox.u.y*v), (boundingBox.v.x*u + boundingBox.v.y*v)); }
forAll (masterPatch_, faceMi) { // First, we make sure that all the master faces points are // recomputed onto the 2D plane defined by the master faces // normals. // For triangles, this is useless, but for N-gons // with more than 3 points, this is essential. // The intersection between the master and slave faces will be // done in these 2D reference frames // A few basic information to keep close-by vector currentMasterFaceNormal = masterPatchNormals[faceMi]; vector currentMasterFaceCentre = masterPatch_[faceMi].centre(masterPatchPoints); scalarField facePolygonErrorProjection; // Project the master faces points onto the normal face plane to // form a flattened polygon masterFace2DPolygon[faceMi] = projectPointsOnPlane ( masterPatch_[faceMi].points(masterPatchPoints), currentMasterFaceCentre, currentMasterFaceNormal, facePolygonErrorProjection ); // Next we compute an orthonormal basis (u, v, w) aligned with // the face normal for doing the 3D to 2D projection. // // "w" is aligned on the face normal. We need to select a "u" // direction, it can be anything as long as it lays on the // projection plane. We chose to use the direction from the // master face center to the most distant projected master face // point on the plane. Finally, we get "v" by evaluating the // cross-product w^u = v. And we make sure that u, v, and w are // normalized. // // // u = vector from face center to most distant projected master face point. // / . // ^y / | . .w = normal to master face // | / | . . // | / | . . // | | | . // | | / . // | | / . // | | / . // ---------> x |/ . // / v = w^u // / // / // z // // orthoNormalBasis uvw = computeOrthonormalBasis ( currentMasterFaceCentre, currentMasterFaceNormal, masterFace2DPolygon[faceMi] ); // Recompute the master polygon into this orthoNormalBasis // We should only see a rotation along the normal of the face here List<point2D> masterPointsInUV; scalarField masterErrorProjectionAlongW; masterPointsInUV = projectPoints3Dto2D ( uvw, currentMasterFaceCentre, masterFace2DPolygon[faceMi], masterErrorProjectionAlongW // Should be at zero all the way ); // Compute the surface area of the polygon; // We need this for computing the weighting factors scalar surfaceAreaMasterPointsInUV = area2D(masterPointsInUV); // Check if polygon is CW.. Should not, it should be CCW; but // better and cheaper to check here if (surfaceAreaMasterPointsInUV < 0.0) { reverse(masterPointsInUV); surfaceAreaMasterPointsInUV = -surfaceAreaMasterPointsInUV; // Just generate a warning until we can verify this is a non issue InfoIn ( "void GGIInterpolation<MasterPatch, SlavePatch>::" "calcAddressing()" ) << "The master projected polygon was CW instead of CCW. " << "This is strange..." << endl; } // Next, project the candidate master neighbours faces points // onto the same plane using the new orthonormal basis const labelList& curCMN = candidateMasterNeighbors[faceMi]; forAll (curCMN, neighbI) { // For each points, compute the dot product with u,v,w. The // [u,v] component will gives us the 2D cordinates we are // looking for for doing the 2D intersection The w component // is basically the projection error normal to the projection // plane // NB: this polygon is most certainly CW w/r to the uvw // axis because of the way the normals are oriented on // each side of the GGI interface... We will switch the // polygon to CCW in due time... List<point2D> neighbPointsInUV; scalarField neighbErrorProjectionAlongW; // We use the xyz points directly, with a possible transformation pointField curSlaveFacePoints = slavePatch_[curCMN[neighbI]].points(slavePatch_.points()); if (doTransform()) { // Transform points to master plane if (forwardT_.size() == 1) { transform ( curSlaveFacePoints, forwardT_[0], curSlaveFacePoints ); } else { transform ( curSlaveFacePoints, forwardT_[curCMN[neighbI]], curSlaveFacePoints ); } } // Apply the translation offset in order to keep the // neighbErrorProjectionAlongW values to a minimum if (doSeparation()) { if (forwardSep_.size() == 1) { curSlaveFacePoints += forwardSep_[0]; } else { curSlaveFacePoints += forwardSep_[curCMN[neighbI]]; } } neighbPointsInUV = projectPoints3Dto2D ( uvw, currentMasterFaceCentre, curSlaveFacePoints, neighbErrorProjectionAlongW ); // We are now ready to filter out the "bad" neighbours. // For this, we will apply the Separating Axes Theorem // http://en.wikipedia.org/wiki/Separating_axis_theorem. // This will be the second and last quick reject test. // We will use the 2D projected points for both the master // patch and its neighbour candidates if ( detect2dPolygonsOverlap ( masterPointsInUV, neighbPointsInUV, sqrt(areaErrorTol_()) // distErrorTol ) ) { // We have an overlap between the master face and this // neighbor face. label faceMaster = faceMi; label faceSlave = curCMN[neighbI]; // Compute the surface area of the neighbour polygon; // We need this for computing the weighting factors scalar surfaceAreaNeighbPointsInUV = area2D(neighbPointsInUV); // Check for CW polygons. It most certainly is, and // the polygon intersection algorithms are expecting // to work with CCW point ordering for the polygons if (surfaceAreaNeighbPointsInUV < 0.0) { reverse(neighbPointsInUV); surfaceAreaNeighbPointsInUV = -surfaceAreaNeighbPointsInUV; } // We compute the intersection area using the // Sutherland-Hodgman algorithm. Of course, if the // intersection area is 0, that would constitute the last and // final reject test, but it would also be an indication that // our 2 previous rejection tests are a bit laxed... or that // maybe we are in presence of concave polygons.... scalar intersectionArea = polygonIntersection ( masterPointsInUV, neighbPointsInUV ); if (intersectionArea > VSMALL) // Or > areaErrorTol_ ??? { // We compute the GGI weights based on this // intersection area, and on the individual face // area on each side of the GGI. // Since all the intersection have been computed // in the projected UV space we need to compute // the weights using the surface area from the // faces projection as well. That way, we make // sure all our factors will sum up to 1.0. masterNeighbors[faceMaster].append(faceSlave); slaveNeighbors[faceSlave].append(faceMaster); masterNeighborsWeights[faceMaster].append ( intersectionArea/surfaceAreaMasterPointsInUV ); slaveNeighborsWeights[faceSlave].append ( intersectionArea/surfaceAreaNeighbPointsInUV ); } else { WarningIn ( "GGIInterpolation<MasterPatch, SlavePatch>::" "calcAddressing()" ) << "polygonIntersection is returning a " << "zero surface area between " << nl << " Master face: " << faceMi << " and Neighbour face: " << curCMN[neighbI] << " intersection area = " << intersectionArea << nl << "Please check the two quick-check algorithms for " << "GGIInterpolation. Something is missing." << endl; } } }