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 }