// TrueCentroid returns the true centroid of the spherical triangle ABC multiplied by the // signed area of spherical triangle ABC. The result is not normalized. // The reasons for multiplying by the signed area are (1) this is the quantity // that needs to be summed to compute the centroid of a union or difference of triangles, // and (2) it's actually easier to calculate this way. All points must have unit length. // // The true centroid (mass centroid) is defined as the surface integral // over the spherical triangle of (x,y,z) divided by the triangle area. // This is the point that the triangle would rotate around if it was // spinning in empty space. // // The best centroid for most purposes is the true centroid. Unlike the // planar and surface centroids, the true centroid behaves linearly as // regions are added or subtracted. That is, if you split a triangle into // pieces and compute the average of their centroids (weighted by triangle // area), the result equals the centroid of the original triangle. This is // not true of the other centroids. func TrueCentroid(a, b, c Point) Point { ra := float64(1) if sa := float64(b.Distance(c)); sa != 0 { ra = sa / math.Sin(sa) } rb := float64(1) if sb := float64(c.Distance(a)); sb != 0 { rb = sb / math.Sin(sb) } rc := float64(1) if sc := float64(a.Distance(b)); sc != 0 { rc = sc / math.Sin(sc) } // Now compute a point M such that: // // [Ax Ay Az] [Mx] [ra] // [Bx By Bz] [My] = 0.5 * det(A,B,C) * [rb] // [Cx Cy Cz] [Mz] [rc] // // To improve the numerical stability we subtract the first row (A) from the // other two rows; this reduces the cancellation error when A, B, and C are // very close together. Then we solve it using Cramer's rule. // // This code still isn't as numerically stable as it could be. // The biggest potential improvement is to compute B-A and C-A more // accurately so that (B-A)x(C-A) is always inside triangle ABC. x := r3.Vector{a.X, b.X - a.X, c.X - a.X} y := r3.Vector{a.Y, b.Y - a.Y, c.Y - a.Y} z := r3.Vector{a.Z, b.Z - a.Z, c.Z - a.Z} r := r3.Vector{ra, rb - ra, rc - ra} return Point{r3.Vector{y.Cross(z).Dot(r), z.Cross(x).Dot(r), x.Cross(y).Dot(r)}.Mul(0.5)} }
// RobustSign returns a Direction representing the ordering of the points. // CounterClockwise is returned if the points are in counter-clockwise order, // Clockwise for clockwise, and Indeterminate if any two points are the same (collinear), // or the sign could not completely be determined. // // This function has additional logic to make sure that the above properties hold even // when the three points are coplanar, and to deal with the limitations of // floating-point arithmetic. // // RobustSign satisfies the following conditions: // // (1) RobustSign(a,b,c) == 0 if and only if a == b, b == c, or c == a // (2) RobustSign(b,c,a) == RobustSign(a,b,c) for all a,b,c // (3) RobustSign(c,b,a) == -RobustSign(a,b,c) for all a,b,c // // In other words: // // (1) The result is zero if and only if two points are the same. // (2) Rotating the order of the arguments does not affect the result. // (3) Exchanging any two arguments inverts the result. // // On the other hand, note that it is not true in general that // RobustSign(-a,b,c) == -RobustSign(a,b,c), or any similar identities // involving antipodal points. // // C++ Equivalent: RobustCCW() func RobustSign(a, b, c Point) Direction { // This method combines the computations from the C++ methods // RobustCCW, TriageCCW, ExpensiveCCW, and StableCCW. // TODO: Split these extra functions out when the need arises. // Start with TriageCCW det := c.Cross(a.Vector).Dot(b.Vector) if det > maxDeterminantError { return CounterClockwise } if det < -maxDeterminantError { return Clockwise } // ExpensiveCCW if a == b || b == c || c == a { return Indeterminate } // StableCCW ab := a.Sub(b.Vector) ab2 := ab.Norm2() bc := b.Sub(c.Vector) bc2 := bc.Norm2() ca := c.Sub(a.Vector) ca2 := ca.Norm2() // The two shortest edges, pointing away from their common point. var e1, e2, op r3.Vector if ab2 >= bc2 && ab2 >= ca2 { // AB is the longest edge. e1, e2, op = ca, bc, c.Vector } else if bc2 >= ca2 { // BC is the longest edge. e1, e2, op = ab, ca, a.Vector } else { // CA is the longest edge. e1, e2, op = bc, ab, b.Vector } det = e1.Cross(e2).Dot(op) maxErr := detErrorMultiplier * math.Sqrt(e1.Norm2()*e2.Norm2()) // If the determinant isn't zero, we know definitively the point ordering. if det > maxErr { return CounterClockwise } if det < -maxErr { return Clockwise } // In the C++ version, the final computation is performed using OpenSSL's // Bignum exact precision math library. The existence of an equivalent // library in Go is indeterminate. In C++, using the exact precision library // to solve this stage is ~300x slower than the above checks. // TODO(roberts): Select and incorporate an appropriate Go exact precision // floating point library for the remaining calculations. return Indeterminate }
// face returns face ID from 0 to 5 containing the r. For points on the // boundary between faces, the result is arbitrary but deterministic. func face(r r3.Vector) int { abs := r.Abs() id := 0 value := r.X if abs.Y > abs.X { id = 1 value = r.Y } if abs.Z > math.Abs(value) { id = 2 value = r.Z } if value < 0 { id += 3 } return id }
// stableSign reports the direction sign of the points in a numerically stable way. // Unlike triageSign, this method can usually compute the correct determinant sign even when all // three points are as collinear as possible. For example if three points are // spaced 1km apart along a random line on the Earth's surface using the // nearest representable points, there is only a 0.4% chance that this method // will not be able to find the determinant sign. The probability of failure // decreases as the points get closer together; if the collinear points are // 1 meter apart, the failure rate drops to 0.0004%. // // This method could be extended to also handle nearly-antipodal points (and // in fact an earlier version of this code did exactly that), but antipodal // points are rare in practice so it seems better to simply fall back to // exact arithmetic in that case. func stableSign(a, b, c Point) Direction { ab := a.Sub(b.Vector) ab2 := ab.Norm2() bc := b.Sub(c.Vector) bc2 := bc.Norm2() ca := c.Sub(a.Vector) ca2 := ca.Norm2() // Now compute the determinant ((A-C)x(B-C)).C, where the vertices have been // cyclically permuted if necessary so that AB is the longest edge. (This // minimizes the magnitude of cross product.) At the same time we also // compute the maximum error in the determinant. // The two shortest edges, pointing away from their common point. var e1, e2, op r3.Vector if ab2 >= bc2 && ab2 >= ca2 { // AB is the longest edge. e1, e2, op = ca, bc, c.Vector } else if bc2 >= ca2 { // BC is the longest edge. e1, e2, op = ab, ca, a.Vector } else { // CA is the longest edge. e1, e2, op = bc, ab, b.Vector } det := e1.Cross(e2).Dot(op) maxErr := detErrorMultiplier * math.Sqrt(e1.Norm2()*e2.Norm2()) // If the determinant isn't zero, within maxErr, we know definitively the point ordering. if det > maxErr { return CounterClockwise } if det < -maxErr { return Clockwise } return Indeterminate }