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