Conversation
Extract _vec3dToClosestFace, _vec3dToHex2d, _vec3dToFaceIjk from LatLng wrappers. Add _vec3dAzimuthRads (3D tangent-plane azimuth) and _hex2dToVec3 (inverse via Rodrigues' rotation). Add vec3ToCell and cellToVec3 public API. Existing LatLng API is unchanged — wrappers delegate to the new Vec3d functions. From uber#1052.
| if (err) { | ||
| return err; | ||
| } | ||
| printf("%.10lf\n", length); |
There was a problem hiding this comment.
Slightly different floating point math on Mac vs Linux. Reducing to 8 digits gets tests to pass on both platforms. (We do an exact string comparison in edgeLengthM.txt)
holoskii
left a comment
There was a problem hiding this comment.
Left some minor comments, nothing blocking. LGTM
| * @param out The encoded H3Index. | ||
| * @returns E_SUCCESS on success, another value otherwise | ||
| */ | ||
| H3Error vec3ToCell(const Vec3d *v, int res, H3Index *out) { |
There was a problem hiding this comment.
Should the function also check that the input vector is on the unit sphere?
src/h3lib/include/vec3d.h
Outdated
|
|
||
| static inline void vec3Normalize(Vec3d *v) { | ||
| double norm = vec3Norm(*v); | ||
| if (norm == 0.0) return; |
There was a problem hiding this comment.
Do we intentially keep == 0 instead < EPSILON? If so - could leave a comment explaining why
There was a problem hiding this comment.
+1, except should be DBL_EPSILON from <float.h>
There was a problem hiding this comment.
This is subtle, so I'm glad you're raising it, but I do think an exact zero check is correct here. And this also made me realize there's a slightly better version of this function!
A check like if (norm < DBL_EPSILON) return; would have what I'd consider undesirable behavior in that it would fail to normalize totally normalizable vectors. For example, if we were to try to normalize Vec3d v = {DBL_EPSILON / 2.0, 0, 0};, it would pass through the function unchanged (even though it should normalize to {1,0,0}), which I could imagine leading to confusing bugs.
In addition, there are vectors which are nonzero, but have norm zero, like v = {1e-163, 0, 0}. For those, we could reassign them to be exactly zero, since I could imagine someone assuming that to be the case after seeing vec3Norm(v) == 0. I don't think this case is common in real scenarios, but it's basically free and seems safer.
I'm updating the function with that slightly adjusted logic and some comments. I've also added tests for these examples. See 5cffa92
Happy to discuss more, since this is worth getting correct. Let me know what you think.
| printf("};\n"); | ||
| } | ||
|
|
||
| int main(int argc, char *argv[]) { |
There was a problem hiding this comment.
I think we still want this to be able to generate the tables that we have hard coded. See #67
| Vec3d infZ = {.x = 0.0, .y = 0.0, .z = -INFINITY}; | ||
| t_assert(vec3ToCell(&infZ, 0, &out) == E_DOMAIN, | ||
| "infinite z is rejected"); | ||
| } |
There was a problem hiding this comment.
Probably should add a test for cellToVec3 here as well?
src/h3lib/lib/faceijk.c
Outdated
| * @param v3 Output: the 3D coordinates of the cell center point | ||
| */ | ||
| void _hex2dToGeo(const Vec2d *v, int face, int res, int substrate, LatLng *g) { | ||
| void _hex2dToVec3(const Vec2d *v, int face, int res, int substrate, Vec3d *v3) { |
There was a problem hiding this comment.
True. I was just generally trying to avoid any avoidable code changes to make the diff easier to review. But making this static should be fine.
| * @param v3 Output: The 3D coordinates of the cell center point. | ||
| */ | ||
| void _faceIjkToGeo(const FaceIJK *h, int res, LatLng *g) { | ||
| void _faceIjkToVec3(const FaceIJK *h, int res, Vec3d *g) { |
There was a problem hiding this comment.
Argument name mismatch:
void _faceIjkToVec3(const FaceIJK *h, int res, Vec3d *v3);
There was a problem hiding this comment.
+1, should be fixed before landing
dmitryzv
left a comment
There was a problem hiding this comment.
Approved with a comment
src/h3lib/include/vec3d.h
Outdated
|
|
||
| static inline void vec3Normalize(Vec3d *v) { | ||
| double norm = vec3Norm(*v); | ||
| if (norm == 0.0) return; |
There was a problem hiding this comment.
+1, except should be DBL_EPSILON from <float.h>
Summary
Vec3d refactor
Based on @holoskii 's work in #1052, we cherry pick out just the
Vec3dindexing refactor.The indexing path on
mastercurrently operates on "geo" lat/lng coordinates:The new path in this PR converts from lat/lng to
Vec3dand does more operations in that representation:Additional changes
In addition to what was done in #1052, this PR makes everything go through the new
Vec3dlogic, for a complete migration:latLngToCellcellToLatLngcellToBoundarydirectedEdgeToBoundaryvertexToLatLngMoving everything let us delete all the "to geo" and "geo to" code, e.g.,
_faceIjkToGeo.Also:
vec3d.hprovided a nice win:latLngToCellwent from ~11% slower to ~3% faster thanmasterCell assignment tests
Compares this PR to what's on
master.Code in
src/apps/benchmarks/dumpCoreApi.candstress.py(will be removed before landing). Run withuv run stress.pyNote: These are on top of the existing tests,
bc*centers.txtandrand*centers.txtintests/inputfiles/.latLngToCell: random points10,000 random points (uniform on sphere, fixed seed) tested at 8 resolutions (0, 1, 2, 3, 4, 7, 10, 15) = 80,000 tests.
Result: 0 mismatches
latLngToCell: boundary pointsAll cell boundary vertices and edge midpoints at resolutions 0–3, tested at 8 resolutions = 4,654,080 tests.
Result: ~12% mismatches. But this is expected, since we arbitrarily break ties between cells, and slightly different floating point math may break ties differently.
cellToLatLngandcellToBoundaryAll cells at resolutions 0--4.
Result: all agree to within ~0.001 mm
Benchmarks
Code in
src/apps/benchmarks/benchmarkCoreApi.candbench.py. Run withuv run bench.py. (Will be removed before landing.)latLngToCellcellToLatLngcellToBoundarydirectedEdgeToBoundaryvertexToLatLngNote that one of the bottlenecks of the new
cellsToMultiPolygoncode isdirectedEdgeToBoundary, so speedups here will help that function as well!Follow-ups
Renaming
To help me understand the code, I had renamed a bunch of things, including using
Vec2andVec3consistently ("hex2d" and "vec2d" were used interchangeably, and function names didn't always match struct names). While that helped in working on the PR, it made this diff hard to read. I'll revisit in the future, since I think better naming would be nice if we continue to work on this code.Header-only allows for helper function optimizations
Compiler optimizations across translation units makes a difference. By moving
vec3d.hto be header-only andstatic inline, thelatLngToCellbenchmarks go from +11% (slower) to -3.4% (faster), compared tomaster. We can do the same elsewhere:coordijk.chas lots of small functions that get called in other.cfiles (translation units). I tested movingcoordijk.cto be header-only, and gotlatLngToCellto be -9.7% (faster) compared tomaster, andcellToLatLngandcellToBoundarywere both -33% (faster). We should do this in a follow-up PR. And there might be other files worth considering, likevec2d.h.Trig-free rotation math
The rotation math in
_vec3ToVec2translates from 3d to angle and back to 3d. That rotation can be done without any trig functions. Instead of storing angles infaceAxesAzRadsCII(and note that we store all three, but only use the first!), we could instead store theVec3i-axis for each icosahedron face. This removes 5 trig function calls per indexing operation. Bigger change, separate PR.Note, we can also remove some trig from this path in
_vec3ToHex2d:by writing
I tried this, but didn't see much movement in the benchmarks.