* Copyright 2005 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.geometry;
import java.util.logging.Logger;
public strictfp class S2Test extends GeometryTestCase {
private static Logger logger = Logger.getLogger(S2Test.class.getName());
private static int swapAxes(int ij) {
return ((ij >> 1) & 1) + ((ij & 1) << 1);
}
private static int invertBits(int ij) {
return ij ^ 3;
}
public void testTraversalOrder() {
for (int r = 0; r < 4; ++r) {
for (int i = 0; i < 4; ++i) {
assertEquals(S2.ijToPos(r, i),
S2.ijToPos(r ^ S2.SWAP_MASK, swapAxes(i)));
assertEquals(S2.posToIJ(r, i),
swapAxes(S2.posToIJ(r ^ S2.SWAP_MASK, i)));
assertEquals(S2.ijToPos(r, i),
S2.ijToPos(r ^ S2.INVERT_MASK, invertBits(i)));
assertEquals(S2.posToIJ(r, i),
invertBits(S2.posToIJ(r ^ S2.INVERT_MASK, i)));
assertEquals(S2.ijToPos(r, S2.posToIJ(r, i)), i);
assertEquals(S2.posToIJ(r, S2.ijToPos(r, i)), i);
}
}
}
public void testSTUV() {
for (double x = -1; x <= 1; ++x) {
assertEquals(S2Projections.stToUV(x), x);
assertEquals(S2Projections.uvToST(x), x);
}
for (double x = -1; x <= 1; x += 0.0001) {
assertDoubleNear(S2Projections.uvToST(S2Projections.stToUV(x)), x);
assertDoubleNear(S2Projections.stToUV(S2Projections.uvToST(x)), x);
}
}
public void testFaceUVtoXYZ() {
S2Point sum = new S2Point();
for (int face = 0; face < 6; ++face) {
S2Point center = S2Projections.faceUvToXyz(face, 0, 0);
assertEquals(S2Projections.getNorm(face), center);
assertEquals(Math.abs(center.get(center.largestAbsComponent())), 1.0);
sum = S2Point.add(sum, S2Point.fabs(center));
}
assertEquals(sum, new S2Point(2, 2, 2));
for (int face = 0; face < 6; ++face) {
assertEquals(
S2Point.crossProd(S2Projections.getUAxis(face), S2Projections.getVAxis(face)).dotProd(
S2Projections.faceUvToXyz(face, 0, 0)), 1.0);
}
for (int face = 0; face < 6; ++face) {
int sign = ((face & S2.SWAP_MASK) != 0) ? -1 : 1;
assertEquals(S2Projections.faceUvToXyz(face, sign, -sign),
S2Projections.faceUvToXyz((face + 1) % 6, -1, -1));
}
}
public void testUVNorms() {
for (int face = 0; face < 6; ++face) {
for (double x = -1; x <= 1; x += 1 / 1024.) {
assertDoubleNear(
S2Point.crossProd(
S2Projections.faceUvToXyz(face, x, -1), S2Projections.faceUvToXyz(face, x, 1))
.angle(S2Projections.getUNorm(face, x)), 0);
assertDoubleNear(
S2Point.crossProd(
S2Projections.faceUvToXyz(face, -1, x), S2Projections.faceUvToXyz(face, 1, x))
.angle(S2Projections.getVNorm(face, x)), 0);
}
}
}
public void testUVAxes() {
for (int face = 0; face < 6; ++face) {
assertEquals(S2Projections.getUAxis(face), S2Point.sub(
S2Projections.faceUvToXyz(face, 1, 0), S2Projections.faceUvToXyz(face, 0, 0)));
assertEquals(S2Projections.getVAxis(face), S2Point.sub(
S2Projections.faceUvToXyz(face, 0, 1), S2Projections.faceUvToXyz(face, 0, 0)));
}
}
public void testAngleArea() {
S2Point pz = new S2Point(0, 0, 1);
S2Point p000 = new S2Point(1, 0, 0);
S2Point p045 = new S2Point(1, 1, 0);
S2Point p090 = new S2Point(0, 1, 0);
S2Point p180 = new S2Point(-1, 0, 0);
assertDoubleNear(S2.angle(p000, pz, p045), S2.M_PI_4);
assertDoubleNear(S2.angle(p045, pz, p180), 3 * S2.M_PI_4);
assertDoubleNear(S2.angle(p000, pz, p180), S2.M_PI);
assertDoubleNear(S2.angle(pz, p000, pz), 0);
assertDoubleNear(S2.angle(pz, p000, p045), S2.M_PI_2);
assertDoubleNear(S2.area(p000, p090, pz), S2.M_PI_2);
assertDoubleNear(S2.area(p045, pz, p180), 3 * S2.M_PI_4);
final double eps = 1e-10;
S2Point pepsx = new S2Point(eps, 0, 1);
S2Point pepsy = new S2Point(0, eps, 1);
double expected1 = 0.5 * eps * eps;
assertDoubleNear(S2.area(pepsx, pepsy, pz), expected1, 1e-14 * expected1);
S2Point pr = new S2Point(0.257, -0.5723, 0.112);
S2Point pq = new S2Point(-0.747, 0.401, 0.2235);
assertEquals(S2.area(pr, pr, pr), 0.0);
assertDoubleNear(S2.area(pr, pq, pr), 0);
assertEquals(S2.area(p000, p045, p090), 0.0);
double maxGirard = 0;
for (int i = 0; i < 10000; ++i) {
S2Point p0 = randomPoint();
S2Point d1 = randomPoint();
S2Point d2 = randomPoint();
S2Point p1 = S2Point.add(p0, S2Point.mul(d1, 1e-15));
S2Point p2 = S2Point.add(p0, S2Point.mul(d2, 1e-15));
assertTrue(S2.area(p0, p1, p2) < 0.7e-30);
maxGirard = Math.max(maxGirard, S2.girardArea(p0, p1, p2));
}
logger.info("Worst case Girard for triangle area 1e-30: " + maxGirard);
S2Point p045eps = new S2Point(1, 1, eps);
double expected2 = 5.8578643762690495119753e-11;
assertDoubleNear(S2.area(p000, p045eps, p090), expected2, 1e-9 * expected2);
final double eps2 = 1e-10;
S2Point p000eps2 = new S2Point(1, 0.1 * eps2, eps2);
double quarterArea1 =
S2.area(p000eps2, p000, p090) + S2.area(p000eps2, p090, p180) + S2.area(p000eps2, p180, pz)
+ S2.area(p000eps2, pz, p000);
assertDoubleNear(quarterArea1, S2.M_PI);
S2Point p045eps2 = new S2Point(1, 1, eps2);
double quarterArea2 =
S2.area(p045eps2, p000, p090) + S2.area(p045eps2, p090, p180) + S2.area(p045eps2, p180, pz)
+ S2.area(p045eps2, pz, p000);
assertDoubleNear(quarterArea2, S2.M_PI);
}
public void testCCW() {
S2Point a = new S2Point(0.72571927877036835, 0.46058825605889098, 0.51106749730504852);
S2Point b = new S2Point(0.7257192746638208, 0.46058826573818168, 0.51106749441312738);
S2Point c = new S2Point(0.72571927671709457, 0.46058826089853633, 0.51106749585908795);
assertTrue(S2.robustCCW(a, b, c) != 0);
}
class MetricBundle {
public MetricBundle(S2.Metric min, S2.Metric max, S2.Metric avg) {
this.min_ = min;
this.max_ = max;
this.avg_ = avg;
}
S2.Metric min_;
S2.Metric max_;
S2.Metric avg_;
}
public void testMinMaxAvg(MetricBundle bundle) {
assertTrue(bundle.min_.deriv() < bundle.avg_.deriv());
assertTrue(bundle.avg_.deriv() < bundle.max_.deriv());
}
public void testLessOrEqual(MetricBundle a, MetricBundle b) {
assertTrue(a.min_.deriv() <= b.min_.deriv());
assertTrue(a.max_.deriv() <= b.max_.deriv());
assertTrue(a.avg_.deriv() <= b.avg_.deriv());
}
public void testMetrics() {
MetricBundle angleSpan = new MetricBundle(
S2Projections.MIN_ANGLE_SPAN, S2Projections.MAX_ANGLE_SPAN, S2Projections.AVG_ANGLE_SPAN);
MetricBundle width =
new MetricBundle(S2Projections.MIN_WIDTH, S2Projections.MAX_WIDTH, S2Projections.AVG_WIDTH);
MetricBundle edge =
new MetricBundle(S2Projections.MIN_EDGE, S2Projections.MAX_EDGE, S2Projections.AVG_EDGE);
MetricBundle diag =
new MetricBundle(S2Projections.MIN_DIAG, S2Projections.MAX_DIAG, S2Projections.AVG_DIAG);
MetricBundle area =
new MetricBundle(S2Projections.MIN_AREA, S2Projections.MAX_AREA, S2Projections.AVG_AREA);
testMinMaxAvg(angleSpan);
testMinMaxAvg(width);
testMinMaxAvg(edge);
testMinMaxAvg(diag);
testMinMaxAvg(area);
assertTrue(S2Projections.MAX_EDGE_ASPECT >= 1.0);
assertTrue(S2Projections.MAX_EDGE_ASPECT
< S2Projections.MAX_EDGE.deriv() / S2Projections.MIN_EDGE.deriv());
assertTrue(S2Projections.MAX_DIAG_ASPECT >= 1);
assertTrue(S2Projections.MAX_DIAG_ASPECT
< S2Projections.MAX_DIAG.deriv() / S2Projections.MIN_DIAG.deriv());
testLessOrEqual(width, angleSpan);
testLessOrEqual(width, edge);
testLessOrEqual(edge, diag);
assertTrue(S2Projections.MIN_AREA.deriv()
>= S2Projections.MIN_WIDTH.deriv() * S2Projections.MIN_EDGE.deriv() - 1e-15);
assertTrue(S2Projections.MAX_AREA.deriv()
< S2Projections.MAX_WIDTH.deriv() * S2Projections.MAX_EDGE.deriv() + 1e-15);
for (int level = -2; level <= S2CellId.MAX_LEVEL + 3; ++level) {
double dWidth = (2 * S2Projections.MIN_WIDTH.deriv()) * Math.pow(2, -level);
if (level >= S2CellId.MAX_LEVEL + 3) {
dWidth = 0;
}
int expectedLevel = Math.max(0, Math.min(S2CellId.MAX_LEVEL, level));
assertEquals(S2Projections.MIN_WIDTH.getMinLevel(dWidth), expectedLevel);
assertEquals(S2Projections.MIN_WIDTH.getMaxLevel(dWidth), expectedLevel);
assertEquals(S2Projections.MIN_WIDTH.getClosestLevel(dWidth), expectedLevel);
assertEquals(S2Projections.MIN_WIDTH.getMinLevel(1.2 * dWidth), expectedLevel);
assertEquals(S2Projections.MIN_WIDTH.getMaxLevel(0.8 * dWidth), expectedLevel);
assertEquals(S2Projections.MIN_WIDTH.getClosestLevel(1.2 * dWidth), expectedLevel);
assertEquals(S2Projections.MIN_WIDTH.getClosestLevel(0.8 * dWidth), expectedLevel);
double area1 = (4 * S2Projections.MIN_AREA.deriv()) * Math.pow(4, -level);
if (level <= -3) {
area1 = 0;
}
assertEquals(S2Projections.MIN_AREA.getMinLevel(area1), expectedLevel);
assertEquals(S2Projections.MIN_AREA.getMaxLevel(area1), expectedLevel);
assertEquals(S2Projections.MIN_AREA.getClosestLevel(area1), expectedLevel);
assertEquals(S2Projections.MIN_AREA.getMinLevel(1.2 * area1), expectedLevel);
assertEquals(S2Projections.MIN_AREA.getMaxLevel(0.8 * area1), expectedLevel);
assertEquals(S2Projections.MIN_AREA.getClosestLevel(1.2 * area1), expectedLevel);
assertEquals(S2Projections.MIN_AREA.getClosestLevel(0.8 * area1), expectedLevel);
}
}
public void testExp() {
for (int i = 0; i < 10; ++i) {
assertEquals(i + 1, S2.exp(Math.pow(2, i)));
}
for (int i = 0; i < 10; ++i) {
assertEquals(i + 1, S2.exp(-Math.pow(2, i)));
}
assertEquals(0, S2.exp(0));
assertEquals(2, S2.exp(3));
assertEquals(3, S2.exp(5));
}
}