1 // Copyright 2005 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS-IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 16 // Original Author: ericv@google.com (Eric Veach) 17 // Converted to D: madric@gmail.com (Vijay Nayar) 18 // 19 // There are several notions of the "centroid" of a triangle. First, there 20 // is the planar centroid, which is simply the centroid of the ordinary 21 // (non-spherical) triangle defined by the three vertices. Second, there is 22 // the surface centroid, which is defined as the intersection of the three 23 // medians of the spherical triangle. It is possible to show that this 24 // point is simply the planar centroid projected to the surface of the 25 // sphere. Finally, there is the true centroid (mass centroid), which is 26 // defined as the surface integral over the spherical triangle of (x,y,z) 27 // divided by the triangle area. This is the point that the triangle would 28 // rotate around if it was spinning in empty space. 29 // 30 // The best centroid for most purposes is the true centroid. Unlike the 31 // planar and surface centroids, the true centroid behaves linearly as 32 // regions are added or subtracted. That is, if you split a triangle into 33 // pieces and compute the average of their centroids (weighted by triangle 34 // area), the result equals the centroid of the original triangle. This is 35 // not true of the other centroids. 36 // 37 // Also note that the surface centroid may be nowhere near the intuitive 38 // "center" of a spherical triangle. For example, consider the triangle 39 // with vertices A=(1,eps,0), B=(0,0,1), C=(-1,eps,0) (a quarter-sphere). 40 // The surface centroid of this triangle is at S=(0, 2*eps, 1), which is 41 // within a distance of 2*eps of the vertex B. Note that the median from A 42 // (the segment connecting A to the midpoint of BC) passes through S, since 43 // this is the shortest path connecting the two endpoints. On the other 44 // hand, the true centroid is at M=(0, 0.5, 0.5), which when projected onto 45 // the surface is a much more reasonable interpretation of the "center" of 46 // this triangle. 47 48 module s2.s2centroids; 49 50 import s2.s2point; 51 import s2.s2pointutil : isUnitLength; 52 53 import std.math; 54 55 // Return the centroid of the planar triangle ABC. This can be normalized 56 // to unit length to obtain the "surface centroid" of the corresponding 57 // spherical triangle, i.e. the intersection of the three medians. However, 58 // note that for large spherical triangles the surface centroid may be 59 // nowhere near the intuitive "center" (see example above). 60 S2Point planarCentroid(in S2Point a, in S2Point b, in S2Point c) { 61 return (1.0 / 3.0) * (a + b + c); 62 } 63 64 // Returns the true centroid of the spherical triangle ABC multiplied by the 65 // signed area of spherical triangle ABC. The reasons for multiplying by 66 // the signed area are (1) this is the quantity that needs to be summed to 67 // compute the centroid of a union or difference of triangles, and (2) it's 68 // actually easier to calculate this way. All points must have unit length. 69 S2Point trueCentroid(in S2Point a, in S2Point b, in S2Point c) 70 in { 71 assert(isUnitLength(a)); 72 assert(isUnitLength(b)); 73 assert(isUnitLength(c)); 74 } do { 75 76 // I couldn't find any references for computing the true centroid of a 77 // spherical triangle... I have a truly marvellous demonstration of this 78 // formula which this margin is too narrow to contain :) 79 80 // Use Angle() in order to get accurate results for small triangles. 81 double angle_a = b.angle(c); 82 double angle_b = c.angle(a); 83 double angle_c = a.angle(b); 84 double ra = (angle_a == 0) ? 1 : (angle_a / sin(angle_a)); 85 double rb = (angle_b == 0) ? 1 : (angle_b / sin(angle_b)); 86 double rc = (angle_c == 0) ? 1 : (angle_c / sin(angle_c)); 87 88 // Now compute a point M such that: 89 // 90 // [Ax Ay Az] [Mx] [ra] 91 // [Bx By Bz] [My] = 0.5 * det(A,B,C) * [rb] 92 // [Cx Cy Cz] [Mz] [rc] 93 // 94 // To improve the numerical stability we subtract the first row (A) from the 95 // other two rows; this reduces the cancellation error when A, B, and C are 96 // very close together. Then we solve it using Cramer's rule. 97 // 98 // TODO(ericv): This code still isn't as numerically stable as it could be. 99 // The biggest potential improvement is to compute B-A and C-A more 100 // accurately so that (B-A)x(C-A) is always inside triangle ABC. 101 auto x = S2Point(a.x(), b.x() - a.x(), c.x() - a.x()); 102 auto y = S2Point(a.y(), b.y() - a.y(), c.y() - a.y()); 103 auto z = S2Point(a.z(), b.z() - a.z(), c.z() - a.z()); 104 auto r = S2Point(ra, rb - ra, rc - ra); 105 return 0.5 * S2Point( 106 y.crossProd(z).dotProd(r), z.crossProd(x).dotProd(r), x.crossProd(y).dotProd(r)); 107 }