• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.location.geometry;
18 
19 import android.annotation.NonNull;
20 
21 import java.util.Arrays;
22 import java.util.Locale;
23 
24 /**
25  * Provides lightweight S2 cell ID utilities without traditional geometry dependencies.
26  *
27  * <p>See <a href="https://s2geometry.io/">the S2 Geometry Library website</a> for more details.
28  */
29 public final class S2CellIdUtils {
30 
31     /** The level of all leaf S2 cells. */
32     public static final int MAX_LEVEL = 30;
33 
34     private static final int MAX_SIZE = 1 << MAX_LEVEL;
35     private static final double ONE_OVER_MAX_SIZE = 1.0 / MAX_SIZE;
36     private static final int NUM_FACES = 6;
37     private static final int POS_BITS = 2 * MAX_LEVEL + 1;
38     private static final int SWAP_MASK = 0x1;
39     private static final int LOOKUP_BITS = 4;
40     private static final int LOOKUP_MASK = (1 << LOOKUP_BITS) - 1;
41     private static final int INVERT_MASK = 0x2;
42     private static final int LEAF_MASK = 0x1;
43     private static final int[] LOOKUP_POS = new int[1 << (2 * LOOKUP_BITS + 2)];
44     private static final int[] LOOKUP_IJ = new int[1 << (2 * LOOKUP_BITS + 2)];
45     private static final int[] POS_TO_ORIENTATION = {SWAP_MASK, 0, 0, INVERT_MASK + SWAP_MASK};
46     private static final int[][] POS_TO_IJ =
47             {{0, 1, 3, 2}, {0, 2, 3, 1}, {3, 2, 0, 1}, {3, 1, 0, 2}};
48     private static final double UV_LIMIT = calculateUvLimit();
49     private static final UvTransform[] UV_TRANSFORMS = createUvTransforms();
50     private static final XyzTransform[] XYZ_TRANSFORMS = createXyzTransforms();
51     private static final long MAX_SI_TI = 1L << (MAX_LEVEL + 1);
52 
53     // Used to encode (i, j, o) coordinates into primitive longs.
54     private static final int I_SHIFT = 33;
55     private static final int J_SHIFT = 2;
56     private static final long J_MASK = (1L << 31) - 1;
57 
58     // Used to insert latitude and longitude values into arrays.
59     public static final int LAT_LNG_MIN_LENGTH = 2;
60     public static final int LAT_INDEX = 0;
61     public static final int LNG_INDEX = 1;
62 
63     // Used to encode (si, ti) coordinates into primitive longs.
64     private static final int SI_SHIFT = 32;
65     private static final long TI_MASK = (1L << 32) - 1;
66 
67     static {
initLookupCells()68         initLookupCells();
69     }
70 
71     /** Prevents instantiation. */
S2CellIdUtils()72     private S2CellIdUtils() {
73     }
74 
75     /**
76      * Inserts into {@code latLngDegrees} the centroid latitude and longitude, in that order and
77      * both measured in degrees, for the specified S2 cell ID. This array must be non-null and of
78      * minimum length two. A reference to this array is returned.
79      *
80      * <p>Behavior is undefined for invalid S2 cell IDs.
81      */
toLatLngDegrees(long s2CellId, double[] latLngDegrees)82     public static double[] toLatLngDegrees(long s2CellId, double[] latLngDegrees) {
83         // Used latLngDegrees as scratchpad for toLatLngRadians(long, double[]).
84         final double[] latLngRadians = latLngDegrees;
85         toLatLngRadians(s2CellId, latLngRadians);
86         latLngDegrees[LAT_INDEX] = Math.toDegrees(latLngRadians[LAT_INDEX]);
87         latLngDegrees[LNG_INDEX] = Math.toDegrees(latLngRadians[LNG_INDEX]);
88         return latLngDegrees;
89     }
90 
91 
92     /**
93      * Inserts into {@code latLngRadians} the centroid latitude and longitude, in that order and
94      * both measured in radians, for the specified S2 cell ID. This array must be non-null and of
95      * minimum length two. A reference to this array is returned.
96      *
97      * <p>Behavior is undefined for invalid S2 cell IDs.
98      */
toLatLngRadians(long s2CellId, double[] latLngRadians)99     public static double[] toLatLngRadians(long s2CellId, double[] latLngRadians) {
100         checkNotNull(latLngRadians);
101         checkLengthGreaterThanOrEqualTo(LAT_LNG_MIN_LENGTH, latLngRadians.length);
102 
103         final long siTi = toSiTi(s2CellId);
104         final double u = siTiToU(siTi);
105         final double v = siTiToV(siTi);
106 
107         final int face = getFace(s2CellId);
108         final XyzTransform xyzTransform = faceToXyzTransform(face);
109         final double x = xyzTransform.uvToX(u, v);
110         final double y = xyzTransform.uvToY(u, v);
111         final double z = xyzTransform.uvToZ(u, v);
112 
113         latLngRadians[LAT_INDEX] = xyzToLatRadians(x, y, z);
114         latLngRadians[LNG_INDEX] = xyzToLngRadians(x, y);
115         return latLngRadians;
116     }
117 
toSiTi(long s2CellId)118     private static long toSiTi(long s2CellId) {
119         final long ijo = toIjo(s2CellId);
120         final int i = ijoToI(ijo);
121         final int j = ijoToJ(ijo);
122         int delta = isLeaf(s2CellId) ? 1 : (((i ^ (((int) s2CellId) >>> 2)) & 1) != 0) ? 2 : 0;
123         return (((long) (2 * i + delta)) << SI_SHIFT) | ((2 * j + delta) & TI_MASK);
124     }
125 
siTiToSi(long siTi)126     private static int siTiToSi(long siTi) {
127         return (int) (siTi >> SI_SHIFT);
128     }
129 
siTiToTi(long siTi)130     private static int siTiToTi(long siTi) {
131         return (int) siTi;
132     }
133 
siTiToU(long siTi)134     private static double siTiToU(long siTi) {
135         final int si = siTiToSi(siTi);
136         return siToU(si);
137     }
138 
siTiToV(long siTi)139     private static double siTiToV(long siTi) {
140         final int ti = siTiToTi(siTi);
141         return tiToV(ti);
142     }
143 
siToU(long si)144     private static double siToU(long si) {
145         final double s = (1.0 / MAX_SI_TI) * si;
146         if (s >= 0.5) {
147             return (1 / 3.) * (4 * s * s - 1);
148         }
149         return (1 / 3.) * (1 - 4 * (1 - s) * (1 - s));
150     }
151 
tiToV(long ti)152     private static double tiToV(long ti) {
153         // Same calculation as siToU.
154         return siToU(ti);
155     }
156 
faceToXyzTransform(int face)157     private static XyzTransform faceToXyzTransform(int face) {
158         // We map illegal face indices to the largest face index to preserve legacy behavior, i.e.,
159         // we do not want to throw an index out of bounds exception. Note that getFace(s2CellId) is
160         // guaranteed to return a non-negative face index even for invalid S2 cells, so it is
161         // sufficient to just map all face indices greater than the largest face index to the
162         // largest face index.
163         return XYZ_TRANSFORMS[Math.min(NUM_FACES - 1, face)];
164     }
165 
xyzToLngRadians(double x, double y)166     private static double xyzToLngRadians(double x, double y) {
167         return Math.atan2(y, x);
168     }
169 
xyzToLatRadians(double x, double y, double z)170     private static double xyzToLatRadians(double x, double y, double z) {
171         return Math.atan2(z, Math.sqrt(x * x + y * y));
172     }
173 
checkNotNull(Object object)174     private static void checkNotNull(Object object) {
175         if (object == null) {
176             throw new NullPointerException("Given array cannot be null.");
177         }
178     }
179 
checkLengthGreaterThanOrEqualTo(int minLength, int actualLength)180     private static void checkLengthGreaterThanOrEqualTo(int minLength, int actualLength) {
181         if (actualLength < minLength) {
182             throw new IllegalArgumentException(
183                 "Given array of length " + actualLength + " needs to be of minimum length "
184                 + minLength);
185         }
186     }
187 
188     /**
189      * Returns true if the provided S2 cell contains the provided latitude/longitude, both measured
190      * in degrees.
191      */
containsLatLngDegrees(long s2CellId, double latDegrees, double lngDegrees)192     public static boolean containsLatLngDegrees(long s2CellId, double latDegrees,
193             double lngDegrees) {
194         int level = getLevel(s2CellId);
195         long leafCellId = fromLatLngDegrees(latDegrees, lngDegrees);
196         return (getParent(leafCellId, level) == s2CellId);
197     }
198 
199     /**
200      * Returns the leaf S2 cell ID for the specified latitude and longitude, both measured in
201      * degrees.
202      */
fromLatLngDegrees(double latDegrees, double lngDegrees)203     public static long fromLatLngDegrees(double latDegrees, double lngDegrees) {
204         return fromLatLngRadians(Math.toRadians(latDegrees), Math.toRadians(lngDegrees));
205     }
206 
207     /** Returns the leaf S2 cell ID of the specified (face, i, j) coordinate. */
fromFij(int face, int i, int j)208     public static long fromFij(int face, int i, int j) {
209         int bits = (face & SWAP_MASK);
210         // Update most significant bits.
211         long msb = ((long) face) << (POS_BITS - 33);
212         for (int k = 7; k >= 4; --k) {
213             bits = lookupBits(i, j, k, bits);
214             msb = updateBits(msb, k, bits);
215             bits = maskBits(bits);
216         }
217         // Update least significant bits.
218         long lsb = 0;
219         for (int k = 3; k >= 0; --k) {
220             bits = lookupBits(i, j, k, bits);
221             lsb = updateBits(lsb, k, bits);
222             bits = maskBits(bits);
223         }
224         return (((msb << 32) + lsb) << 1) + 1;
225     }
226 
227     /**
228      * Returns the face of the specified S2 cell. The returned face is in [0, 5] for valid S2 cell
229      * IDs. Behavior is undefined for invalid S2 cell IDs.
230      */
getFace(long s2CellId)231     public static int getFace(long s2CellId) {
232         return (int) (s2CellId >>> POS_BITS);
233     }
234 
235     /**
236      * Returns the ID of the parent of the specified S2 cell at the specified parent level.
237      * Behavior is undefined for invalid S2 cell IDs or parent levels not in
238      * [0, {@code getLevel(s2CellId)}[.
239      */
getParent(long s2CellId, int level)240     public static long getParent(long s2CellId, int level) {
241         long newLsb = getLowestOnBitForLevel(level);
242         return (s2CellId & -newLsb) | newLsb;
243     }
244 
245     /**
246      * Inserts into {@code neighbors} the four S2 cell IDs corresponding to the neighboring
247      * cells adjacent across the specified cell's four edges. This array must be of minimum
248      * length four, and elements at the tail end of the array not corresponding to a neighbor
249      * are set to zero. A reference to this array is returned.
250      *
251      * <p>Inserts in the order of down, right, up, and left directions, in that order. All
252      * neighbors are guaranteed to be distinct.
253      */
getEdgeNeighbors(long s2CellId, @NonNull long[] neighbors)254     public static void getEdgeNeighbors(long s2CellId, @NonNull long[] neighbors) {
255         int level = getLevel(s2CellId);
256         int size = levelToSizeIj(level);
257         int face = getFace(s2CellId);
258         long ijo = toIjo(s2CellId);
259         int i = ijoToI(ijo);
260         int j = ijoToJ(ijo);
261 
262         int iPlusSize = i + size;
263         int iMinusSize = i - size;
264         int jPlusSize = j + size;
265         int jMinusSize = j - size;
266         boolean iPlusSizeLtMax = iPlusSize < MAX_SIZE;
267         boolean iMinusSizeGteZero = iMinusSize >= 0;
268         boolean jPlusSizeLtMax = jPlusSize < MAX_SIZE;
269         boolean jMinusSizeGteZero = jMinusSize >= 0;
270 
271         int index = 0;
272         // Down direction.
273         neighbors[index++] = getParent(fromFijSame(face, i, jMinusSize, jMinusSizeGteZero),
274                 level);
275         // Right direction.
276         neighbors[index++] = getParent(fromFijSame(face, iPlusSize, j, iPlusSizeLtMax), level);
277         // Up direction.
278         neighbors[index++] = getParent(fromFijSame(face, i, jPlusSize, jPlusSizeLtMax), level);
279         // Left direction.
280         neighbors[index++] = getParent(fromFijSame(face, iMinusSize, j, iMinusSizeGteZero),
281                 level);
282 
283         // Pad end of neighbor array with zeros.
284         Arrays.fill(neighbors, index, neighbors.length, 0);
285     }
286 
287     /** Returns the "i" coordinate for the specified S2 cell. */
getI(long s2CellId)288     public static int getI(long s2CellId) {
289         return ijoToI(toIjo(s2CellId));
290     }
291 
292     /** Returns the "j" coordinate for the specified S2 cell. */
getJ(long s2CellId)293     public static int getJ(long s2CellId) {
294         return ijoToJ(toIjo(s2CellId));
295     }
296 
297     /**
298      * Returns the leaf S2 cell ID for the specified latitude and longitude, both measured in
299      * radians.
300      */
fromLatLngRadians(double latRadians, double lngRadians)301     private static long fromLatLngRadians(double latRadians, double lngRadians) {
302         double cosLat = Math.cos(latRadians);
303         double x = Math.cos(lngRadians) * cosLat;
304         double y = Math.sin(lngRadians) * cosLat;
305         double z = Math.sin(latRadians);
306         return fromXyz(x, y, z);
307     }
308 
309     /**
310      * Returns the level of the specified S2 cell. The returned level is in [0, 30] for valid
311      * S2 cell IDs. Behavior is undefined for invalid S2 cell IDs.
312      */
getLevel(long s2CellId)313     public static int getLevel(long s2CellId) {
314         if (isLeaf(s2CellId)) {
315             return MAX_LEVEL;
316         }
317         return MAX_LEVEL - (Long.numberOfTrailingZeros(s2CellId) >> 1);
318     }
319 
320     /** Returns the lowest-numbered bit that is on for the specified S2 cell. */
getLowestOnBit(long s2CellId)321     static long getLowestOnBit(long s2CellId) {
322         return s2CellId & -s2CellId;
323     }
324 
325     /** Returns the lowest-numbered bit that is on for any S2 cell on the specified level. */
getLowestOnBitForLevel(int level)326     static long getLowestOnBitForLevel(int level) {
327         return 1L << (2 * (MAX_LEVEL - level));
328     }
329 
330     /**
331      * Returns the ID of the first S2 cell in a traversal of the children S2 cells at the specified
332      * level, in Hilbert curve order.
333      */
getTraversalStart(long s2CellId, int level)334     public static long getTraversalStart(long s2CellId, int level) {
335         return s2CellId - getLowestOnBit(s2CellId) + getLowestOnBitForLevel(level);
336     }
337 
338     /** Returns the ID of the next S2 cell at the same level along the Hilbert curve. */
getTraversalNext(long s2CellId)339     public static long getTraversalNext(long s2CellId) {
340         return s2CellId + (getLowestOnBit(s2CellId) << 1);
341     }
342 
343     /**
344      * Encodes the S2 cell id to compact text strings suitable for display or indexing. Cells at
345      * lower levels (i.e., larger cells) are encoded into fewer characters.
346      */
347     @NonNull
getToken(long s2CellId)348     public static String getToken(long s2CellId) {
349         if (s2CellId == 0) {
350             return "X";
351         }
352 
353         // Convert to a hex string with as many digits as necessary.
354         String hex = Long.toHexString(s2CellId).toLowerCase(Locale.US);
355         // Prefix 0s to get a length 16 string.
356         String padded = padStart(hex);
357         // Trim zeroes off the end.
358         return padded.replaceAll("0*$", "");
359     }
360 
padStart(String string)361     private static String padStart(String string) {
362         if (string.length() >= 16) {
363             return string;
364         }
365         return "0".repeat(16 - string.length()) + string;
366     }
367 
368     /** Returns the leaf S2 cell ID of the specified (x, y, z) coordinate. */
fromXyz(double x, double y, double z)369     private static long fromXyz(double x, double y, double z) {
370         int face = xyzToFace(x, y, z);
371         UvTransform uvTransform = UV_TRANSFORMS[face];
372         double u = uvTransform.xyzToU(x, y, z);
373         double v = uvTransform.xyzToV(x, y, z);
374         return fromFuv(face, u, v);
375     }
376 
377     /** Returns the leaf S2 cell ID of the specified (face, u, v) coordinate. */
fromFuv(int face, double u, double v)378     private static long fromFuv(int face, double u, double v) {
379         int i = uToI(u);
380         int j = vToJ(v);
381         return fromFij(face, i, j);
382     }
383 
fromFijWrap(int face, int i, int j)384     private static long fromFijWrap(int face, int i, int j) {
385         double u = iToU(i);
386         double v = jToV(j);
387 
388         XyzTransform xyzTransform = XYZ_TRANSFORMS[face];
389         double x = xyzTransform.uvToX(u, v);
390         double y = xyzTransform.uvToY(u, v);
391         double z = xyzTransform.uvToZ(u, v);
392 
393         int newFace = xyzToFace(x, y, z);
394         UvTransform uvTransform = UV_TRANSFORMS[newFace];
395         double newU = uvTransform.xyzToU(x, y, z);
396         double newV = uvTransform.xyzToV(x, y, z);
397 
398         int newI = uShiftIntoI(newU);
399         int newJ = vShiftIntoJ(newV);
400         return fromFij(newFace, newI, newJ);
401     }
402 
fromFijSame(int face, int i, int j, boolean isSameFace)403     private static long fromFijSame(int face, int i, int j, boolean isSameFace) {
404         if (isSameFace) {
405             return fromFij(face, i, j);
406         }
407         return fromFijWrap(face, i, j);
408     }
409 
410     /**
411      * Returns the face associated with the specified (x, y, z) coordinate. For a coordinate
412      * on a face boundary, the returned face is arbitrary but repeatable.
413      */
xyzToFace(double x, double y, double z)414     private static int xyzToFace(double x, double y, double z) {
415         double absX = Math.abs(x);
416         double absY = Math.abs(y);
417         double absZ = Math.abs(z);
418         if (absX > absY) {
419             if (absX > absZ) {
420                 return (x < 0) ? 3 : 0;
421             }
422             return (z < 0) ? 5 : 2;
423         }
424         if (absY > absZ) {
425             return (y < 0) ? 4 : 1;
426         }
427         return (z < 0) ? 5 : 2;
428     }
429 
uToI(double u)430     private static int uToI(double u) {
431         double s;
432         if (u >= 0) {
433             s = 0.5 * Math.sqrt(1 + 3 * u);
434         } else {
435             s = 1 - 0.5 * Math.sqrt(1 - 3 * u);
436         }
437         return Math.max(0, Math.min(MAX_SIZE - 1, (int) Math.round(MAX_SIZE * s - 0.5)));
438     }
439 
vToJ(double v)440     private static int vToJ(double v) {
441         // Same calculation as uToI.
442         return uToI(v);
443     }
444 
lookupBits(int i, int j, int k, int bits)445     private static int lookupBits(int i, int j, int k, int bits) {
446         bits += ((i >> (k * LOOKUP_BITS)) & LOOKUP_MASK) << (LOOKUP_BITS + 2);
447         bits += ((j >> (k * LOOKUP_BITS)) & LOOKUP_MASK) << 2;
448         return LOOKUP_POS[bits];
449     }
450 
updateBits(long sb, int k, int bits)451     private static long updateBits(long sb, int k, int bits) {
452         return sb | ((((long) bits) >> 2) << ((k & 0x3) * 2 * LOOKUP_BITS));
453     }
454 
maskBits(int bits)455     private static int maskBits(int bits) {
456         return bits & (SWAP_MASK | INVERT_MASK);
457     }
458 
isLeaf(long s2CellId)459     private static boolean isLeaf(long s2CellId) {
460         return ((int) s2CellId & LEAF_MASK) != 0;
461     }
462 
iToU(int i)463     private static double iToU(int i) {
464         int satI = Math.max(-1, Math.min(MAX_SIZE, i));
465         return Math.max(
466                 -UV_LIMIT,
467                 Math.min(UV_LIMIT, ONE_OVER_MAX_SIZE * ((satI << 1) + 1 - MAX_SIZE)));
468     }
469 
jToV(int j)470     private static double jToV(int j) {
471         // Same calculation as iToU.
472         return iToU(j);
473     }
474 
toIjo(long s2CellId)475     private static long toIjo(long s2CellId) {
476         int face = getFace(s2CellId);
477         int bits = face & SWAP_MASK;
478         int i = 0;
479         int j = 0;
480         for (int k = 7; k >= 0; --k) {
481             int nbits = (k == 7) ? (MAX_LEVEL - 7 * LOOKUP_BITS) : LOOKUP_BITS;
482             bits += ((int) (s2CellId >>> (k * 2 * LOOKUP_BITS + 1)) & ((1 << (2 * nbits))
483                     - 1)) << 2;
484             bits = LOOKUP_IJ[bits];
485             i += (bits >> (LOOKUP_BITS + 2)) << (k * LOOKUP_BITS);
486             j += ((bits >> 2) & ((1 << LOOKUP_BITS) - 1)) << (k * LOOKUP_BITS);
487             bits &= (SWAP_MASK | INVERT_MASK);
488         }
489         int orientation =
490                 ((getLowestOnBit(s2CellId) & 0x1111111111111110L) != 0) ? (bits ^ SWAP_MASK)
491                         : bits;
492         return (((long) i) << I_SHIFT) | (((long) j) << J_SHIFT) | orientation;
493     }
494 
ijoToI(long ijo)495     private static int ijoToI(long ijo) {
496         return (int) (ijo >>> I_SHIFT);
497     }
498 
ijoToJ(long ijo)499     private static int ijoToJ(long ijo) {
500         return (int) ((ijo >>> J_SHIFT) & J_MASK);
501     }
502 
uShiftIntoI(double u)503     private static int uShiftIntoI(double u) {
504         double s = 0.5 * (u + 1);
505         return Math.max(0, Math.min(MAX_SIZE - 1, (int) Math.round(MAX_SIZE * s - 0.5)));
506     }
507 
vShiftIntoJ(double v)508     private static int vShiftIntoJ(double v) {
509         // Same calculation as uShiftIntoI.
510         return uShiftIntoI(v);
511     }
512 
levelToSizeIj(int level)513     private static int levelToSizeIj(int level) {
514         return 1 << (MAX_LEVEL - level);
515     }
516 
initLookupCells()517     private static void initLookupCells() {
518         initLookupCell(0, 0, 0, 0, 0, 0);
519         initLookupCell(0, 0, 0, SWAP_MASK, 0, SWAP_MASK);
520         initLookupCell(0, 0, 0, INVERT_MASK, 0, INVERT_MASK);
521         initLookupCell(0, 0, 0, SWAP_MASK | INVERT_MASK, 0, SWAP_MASK | INVERT_MASK);
522     }
523 
initLookupCell( int level, int i, int j, int origOrientation, int pos, int orientation)524     private static void initLookupCell(
525             int level, int i, int j, int origOrientation, int pos, int orientation) {
526         if (level == LOOKUP_BITS) {
527             int ij = (i << LOOKUP_BITS) + j;
528             LOOKUP_POS[(ij << 2) + origOrientation] = (pos << 2) + orientation;
529             LOOKUP_IJ[(pos << 2) + origOrientation] = (ij << 2) + orientation;
530         } else {
531             level++;
532             i <<= 1;
533             j <<= 1;
534             pos <<= 2;
535             for (int subPos = 0; subPos < 4; subPos++) {
536                 int ij = POS_TO_IJ[orientation][subPos];
537                 int orientationMask = POS_TO_ORIENTATION[subPos];
538                 initLookupCell(
539                         level,
540                         i + (ij >>> 1),
541                         j + (ij & 0x1),
542                         origOrientation,
543                         pos + subPos,
544                         orientation ^ orientationMask);
545             }
546         }
547     }
548 
calculateUvLimit()549     private static double calculateUvLimit() {
550         double machEps = 1.0;
551         do {
552             machEps /= 2.0f;
553         } while ((1.0 + (machEps / 2.0)) != 1.0);
554         return 1.0 + machEps;
555     }
556 
557     @NonNull
createUvTransforms()558     private static UvTransform[] createUvTransforms() {
559         UvTransform[] uvTransforms = new UvTransform[NUM_FACES];
560         uvTransforms[0] =
561                 new UvTransform() {
562 
563                     @Override
564                     public double xyzToU(double x, double y, double z) {
565                         return y / x;
566                     }
567 
568                     @Override
569                     public double xyzToV(double x, double y, double z) {
570                         return z / x;
571                     }
572                 };
573         uvTransforms[1] =
574                 new UvTransform() {
575 
576                     @Override
577                     public double xyzToU(double x, double y, double z) {
578                         return -x / y;
579                     }
580 
581                     @Override
582                     public double xyzToV(double x, double y, double z) {
583                         return z / y;
584                     }
585                 };
586         uvTransforms[2] =
587                 new UvTransform() {
588 
589                     @Override
590                     public double xyzToU(double x, double y, double z) {
591                         return -x / z;
592                     }
593 
594                     @Override
595                     public double xyzToV(double x, double y, double z) {
596                         return -y / z;
597                     }
598                 };
599         uvTransforms[3] =
600                 new UvTransform() {
601 
602                     @Override
603                     public double xyzToU(double x, double y, double z) {
604                         return z / x;
605                     }
606 
607                     @Override
608                     public double xyzToV(double x, double y, double z) {
609                         return y / x;
610                     }
611                 };
612         uvTransforms[4] =
613                 new UvTransform() {
614 
615                     @Override
616                     public double xyzToU(double x, double y, double z) {
617                         return z / y;
618                     }
619 
620                     @Override
621                     public double xyzToV(double x, double y, double z) {
622                         return -x / y;
623                     }
624                 };
625         uvTransforms[5] =
626                 new UvTransform() {
627 
628                     @Override
629                     public double xyzToU(double x, double y, double z) {
630                         return -y / z;
631                     }
632 
633                     @Override
634                     public double xyzToV(double x, double y, double z) {
635                         return -x / z;
636                     }
637                 };
638         return uvTransforms;
639     }
640 
641     @NonNull
createXyzTransforms()642     private static XyzTransform[] createXyzTransforms() {
643         XyzTransform[] xyzTransforms = new XyzTransform[NUM_FACES];
644         xyzTransforms[0] =
645                 new XyzTransform() {
646 
647                     @Override
648                     public double uvToX(double u, double v) {
649                         return 1;
650                     }
651 
652                     @Override
653                     public double uvToY(double u, double v) {
654                         return u;
655                     }
656 
657                     @Override
658                     public double uvToZ(double u, double v) {
659                         return v;
660                     }
661                 };
662         xyzTransforms[1] =
663                 new XyzTransform() {
664 
665                     @Override
666                     public double uvToX(double u, double v) {
667                         return -u;
668                     }
669 
670                     @Override
671                     public double uvToY(double u, double v) {
672                         return 1;
673                     }
674 
675                     @Override
676                     public double uvToZ(double u, double v) {
677                         return v;
678                     }
679                 };
680         xyzTransforms[2] =
681                 new XyzTransform() {
682 
683                     @Override
684                     public double uvToX(double u, double v) {
685                         return -u;
686                     }
687 
688                     @Override
689                     public double uvToY(double u, double v) {
690                         return -v;
691                     }
692 
693                     @Override
694                     public double uvToZ(double u, double v) {
695                         return 1;
696                     }
697                 };
698         xyzTransforms[3] =
699                 new XyzTransform() {
700 
701                     @Override
702                     public double uvToX(double u, double v) {
703                         return -1;
704                     }
705 
706                     @Override
707                     public double uvToY(double u, double v) {
708                         return -v;
709                     }
710 
711                     @Override
712                     public double uvToZ(double u, double v) {
713                         return -u;
714                     }
715                 };
716         xyzTransforms[4] =
717                 new XyzTransform() {
718 
719                     @Override
720                     public double uvToX(double u, double v) {
721                         return v;
722                     }
723 
724                     @Override
725                     public double uvToY(double u, double v) {
726                         return -1;
727                     }
728 
729                     @Override
730                     public double uvToZ(double u, double v) {
731                         return -u;
732                     }
733                 };
734         xyzTransforms[5] =
735                 new XyzTransform() {
736 
737                     @Override
738                     public double uvToX(double u, double v) {
739                         return v;
740                     }
741 
742                     @Override
743                     public double uvToY(double u, double v) {
744                         return u;
745                     }
746 
747                     @Override
748                     public double uvToZ(double u, double v) {
749                         return -1;
750                     }
751                 };
752         return xyzTransforms;
753     }
754 
755     /**
756      * Transform from (x, y, z) coordinates to (u, v) coordinates, indexed by face. For a
757      * (x, y, z) coordinate within a face, each element of the resulting (u, v) coordinate
758      * should lie in the inclusive range [-1, 1], with the face center having a (u, v)
759      * coordinate equal to (0, 0).
760      */
761     private interface UvTransform {
762 
763         /**
764          * Returns for the specified (x, y, z) coordinate the corresponding u-coordinate
765          * (which may lie outside the range [-1, 1]).
766          */
xyzToU(double x, double y, double z)767         double xyzToU(double x, double y, double z);
768 
769         /**
770          * Returns for the specified (x, y, z) coordinate the corresponding v-coordinate
771          * (which may lie outside the range [-1, 1]).
772          */
xyzToV(double x, double y, double z)773         double xyzToV(double x, double y, double z);
774     }
775 
776     /**
777      * Transform from (u, v) coordinates to (x, y, z) coordinates, indexed by face. The
778      * resulting vectors are not necessarily of unit length.
779      */
780     private interface XyzTransform {
781 
782         /** Returns for the specified (u, v) coordinate the corresponding x-coordinate. */
uvToX(double u, double v)783         double uvToX(double u, double v);
784 
785         /** Returns for the specified (u, v) coordinate the corresponding y-coordinate. */
uvToY(double u, double v)786         double uvToY(double u, double v);
787 
788         /** Returns for the specified (u, v) coordinate the corresponding z-coordinate. */
uvToZ(double u, double v)789         double uvToZ(double u, double v);
790     }
791 }
792