/// <summary> /// Test a triangle for quality and size. /// </summary> /// <param name="testtri">Triangle to check.</param> /// <remarks> /// Tests a triangle to see if it satisfies the minimum angle condition and /// the maximum area condition. Triangles that aren't up to spec are added /// to the bad triangle queue. /// </remarks> public void TestTriangle(ref Otri testtri) { Otri tri1 = default(Otri), tri2 = default(Otri); Osub testsub = default(Osub); Vertex torg, tdest, tapex; Vertex base1, base2; Vertex org1, dest1, org2, dest2; Vertex joinvertex; double dxod, dyod, dxda, dyda, dxao, dyao; double dxod2, dyod2, dxda2, dyda2, dxao2, dyao2; double apexlen, orglen, destlen, minedge; double angle; double area; double dist1, dist2; double maxangle; torg = testtri.Org(); tdest = testtri.Dest(); tapex = testtri.Apex(); dxod = torg.x - tdest.x; dyod = torg.y - tdest.y; dxda = tdest.x - tapex.x; dyda = tdest.y - tapex.y; dxao = tapex.x - torg.x; dyao = tapex.y - torg.y; dxod2 = dxod * dxod; dyod2 = dyod * dyod; dxda2 = dxda * dxda; dyda2 = dyda * dyda; dxao2 = dxao * dxao; dyao2 = dyao * dyao; // Find the lengths of the triangle's three edges. apexlen = dxod2 + dyod2; orglen = dxda2 + dyda2; destlen = dxao2 + dyao2; if ((apexlen < orglen) && (apexlen < destlen)) { // The edge opposite the apex is shortest. minedge = apexlen; // Find the square of the cosine of the angle at the apex. angle = dxda * dxao + dyda * dyao; angle = angle * angle / (orglen * destlen); base1 = torg; base2 = tdest; testtri.Copy(ref tri1); } else if (orglen < destlen) { // The edge opposite the origin is shortest. minedge = orglen; // Find the square of the cosine of the angle at the origin. angle = dxod * dxao + dyod * dyao; angle = angle * angle / (apexlen * destlen); base1 = tdest; base2 = tapex; testtri.Lnext(ref tri1); } else { // The edge opposite the destination is shortest. minedge = destlen; // Find the square of the cosine of the angle at the destination. angle = dxod * dxda + dyod * dyda; angle = angle * angle / (apexlen * orglen); base1 = tapex; base2 = torg; testtri.Lprev(ref tri1); } if (behavior.VarArea || behavior.fixedArea || (behavior.UserTest != null)) { // Check whether the area is larger than permitted. area = 0.5 * (dxod * dyda - dyod * dxda); if (behavior.fixedArea && (area > behavior.MaxArea)) { // Add this triangle to the list of bad triangles. queue.Enqueue(ref testtri, minedge, tapex, torg, tdest); return; } // Nonpositive area constraints are treated as unconstrained. if ((behavior.VarArea) && (area > testtri.triangle.area) && (testtri.triangle.area > 0.0)) { // Add this triangle to the list of bad triangles. queue.Enqueue(ref testtri, minedge, tapex, torg, tdest); return; } // Check whether the user thinks this triangle is too large. if (behavior.UserTest != null) { if (behavior.UserTest(testtri.triangle, area)) { queue.Enqueue(ref testtri, minedge, tapex, torg, tdest); return; } } } // find the maximum edge and accordingly the pqr orientation if ((apexlen > orglen) && (apexlen > destlen)) { // The edge opposite the apex is longest. // maxedge = apexlen; // Find the cosine of the angle at the apex. maxangle = (orglen + destlen - apexlen) / (2 * Math.Sqrt(orglen * destlen)); } else if (orglen > destlen) { // The edge opposite the origin is longest. // maxedge = orglen; // Find the cosine of the angle at the origin. maxangle = (apexlen + destlen - orglen) / (2 * Math.Sqrt(apexlen * destlen)); } else { // The edge opposite the destination is longest. // maxedge = destlen; // Find the cosine of the angle at the destination. maxangle = (apexlen + orglen - destlen) / (2 * Math.Sqrt(apexlen * orglen)); } // Check whether the angle is smaller than permitted. if ((angle > behavior.goodAngle) || (maxangle < behavior.maxGoodAngle && behavior.MaxAngle != 0.0)) { // Use the rules of Miller, Pav, and Walkington to decide that certain // triangles should not be split, even if they have bad angles. // A skinny triangle is not split if its shortest edge subtends a // small input angle, and both endpoints of the edge lie on a // concentric circular shell. For convenience, I make a small // adjustment to that rule: I check if the endpoints of the edge // both lie in segment interiors, equidistant from the apex where // the two segments meet. // First, check if both points lie in segment interiors. if ((base1.type == VertexType.SegmentVertex) && (base2.type == VertexType.SegmentVertex)) { // Check if both points lie in a common segment. If they do, the // skinny triangle is enqueued to be split as usual. tri1.SegPivot(ref testsub); if (testsub.seg == Mesh.dummysub) { // No common segment. Find a subsegment that contains 'torg'. tri1.Copy(ref tri2); do { tri1.OprevSelf(); tri1.SegPivot(ref testsub); } while (testsub.seg == Mesh.dummysub); // Find the endpoints of the containing segment. org1 = testsub.SegOrg(); dest1 = testsub.SegDest(); // Find a subsegment that contains 'tdest'. do { tri2.DnextSelf(); tri2.SegPivot(ref testsub); } while (testsub.seg == Mesh.dummysub); // Find the endpoints of the containing segment. org2 = testsub.SegOrg(); dest2 = testsub.SegDest(); // Check if the two containing segments have an endpoint in common. joinvertex = null; if ((dest1.x == org2.x) && (dest1.y == org2.y)) { joinvertex = dest1; } else if ((org1.x == dest2.x) && (org1.y == dest2.y)) { joinvertex = org1; } if (joinvertex != null) { // Compute the distance from the common endpoint (of the two // segments) to each of the endpoints of the shortest edge. dist1 = ((base1.x - joinvertex.x) * (base1.x - joinvertex.x) + (base1.y - joinvertex.y) * (base1.y - joinvertex.y)); dist2 = ((base2.x - joinvertex.x) * (base2.x - joinvertex.x) + (base2.y - joinvertex.y) * (base2.y - joinvertex.y)); // If the two distances are equal, don't split the triangle. if ((dist1 < 1.001 * dist2) && (dist1 > 0.999 * dist2)) { // Return now to avoid enqueueing the bad triangle. return; } } } } // Add this triangle to the list of bad triangles. queue.Enqueue(ref testtri, minedge, tapex, torg, tdest); } }