-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optimize QuantizedMeshTerrainData.interpolateHeight #7508
Optimize QuantizedMeshTerrainData.interpolateHeight #7508
Conversation
Thanks for the pull request @shehzan10!
Reviewers, don't forget to make sure that:
|
067113c
to
7b4b727
Compare
@likangning93 Do you want to do the initial review? |
Just as a record, I've tried further "enhancing" the early exit conditions, but these have all been slower than the committed code by about 10% (probably because of the extra assignment?). An example is:
|
I think I get it! For me, the intuition was as follows: The formulas that look like this: u < (u1 - u0) * (v - v0) / (v1 - v0) + u0 can be derived from: (u - u0) / (u1 - u0) < (v - v0) / (v1 - v0) This looks kind of like how you'd compute a parametric description of the
Since we assume consistent winding order, the |
The one question I have is whether or not this is faster in minified Cesium. Most references I've seen, like http://blackpawn.com/texts/pointinpoly/, claim that the barycentric method is quite fast, so I'm wondering if maybe our implementation seems slower because of the extra overhead from all the |
I also haven't convinced myself yet that we don't run the risk of algorithm differences causing points to pass this method but then produce NaNs and infinities when computing barycentric coordinates. It feels safer to use a single method if that's viable. |
This is an excellent point, @shehzan10 please profile against minified Cesium as well. if |
To add to @likangning93's question, I think it's time we figure out how to allow for the minified Cesium.js in node apps because I think it will benefit performance quite a bit. |
Instead of running with minified Cesium (since I'm testing with a node script), I commented out the checks in
While there is a significant improvement in minified version, the gains from this optimization is obvious. Edit: Just wanted to talk a bit more about the benchmark. This benchmark was done on CWT near Grand Canyon Village (tile 14/6176/11474). I constructed a grid of 256x256 points within the tile and the sampled those for heights. So it covers best, average and worst case points. |
This is an established algorithm for checking if a point is in a polygon, simplified for triangles. If it helps, here is another library doing a more general version of this algorithm https://github.com/substack/point-in-polygon. |
Just found the correct algorithm link: https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html |
I'm going to open a separate PR to enable use of minified version so that Node apps can take advantage of the performance improvement it brings. |
@@ -74,7 +74,7 @@ defineSuite([ | |||
}); | |||
|
|||
it('works for a dodgy point right near the edge of a tile', function() { | |||
var positions = [new Cartographic(0.33179290856829535, 0.7363107781851078)]; | |||
var positions = [new Cartographic(0.33179290856829535, 0.7363107781851076)]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The slight change in this spec is kind of scary. Does the spec fail without the change? Do we know why the previous implementation needed this spec to pass as-is?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previous implementation was added here: 22ff240.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this spec fail if the value is reverted? The linked commit seems to suggest that it was important for this spec to pass with the given value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the clamp
behavior is preserved in the current changes too, which may suggest that there are some positions that were inside the triangle as-per the barycentric method but not according to the raycasting method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @likangning93, changing this spec here is the wrong thing to do. It was put here specifically because we were seeing corner cases fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's a thought: can we make the raycast check more permissive than the barycentric method but still use the barycentric method for points that pass the raycast check?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just ran a small test and it looks like the triangle isn't checking for points on the triangle. I'm going to look into how we can include this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@likangning93 I reverted the spec change and checked for points on the triangle. With that change the benchmark changed from 1920ms to 2500ms (ouch). Still 4x faster.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's a thought: can we make the raycast check more permissive than the barycentric method but still use the barycentric method for points that pass the raycast check?
I don't think it would hurt, because technically it will only be done for one triangle. But then again, it is redundant. The check is stringent enough to not allow points outside the triangle from getting int.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's that redundant, it's similar to using a BVH.
The problem is that the raycast-based check has different tolerances than the barycentric check, but the barycentric check has been calibrated for corner cases that we'd need to re-evaluate.
Making the raycast check slightly more permissive would eliminate the urgency around those corner cases because they'd still be handled by the barycentric check.
Specs/Core/sampleTerrainSpec.js
Outdated
@@ -93,7 +93,7 @@ defineSuite([ | |||
}); | |||
|
|||
it('works for a dodgy point right near the edge of a tile', function() { | |||
var positions = [new Cartographic(0.33179290856829535, 0.7363107781851078)]; | |||
var positions = [new Cartographic(0.33179290856829535, 0.7363107781851076)]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above.
We may have to give up on this altogether. The algorithm is quite solid for points inside the triangle, but does not account for points on the triangle edges. The check I added in 6355a84 is not fool-proof and can result in double positives turning into negatives. To add an on-edge check for the 3 sides of the triangle for each case that fails to be inside the triangle is too expensive and ends up being as slow as the original method. |
The one thing that might make this work is if we add an epsilon to the triangle vertices, making it very slightly larger, and then checking with barycentric coordinates like @likangning93 suggested. This will make Edit: nevermind, I may have spoken too soon. "Adding an epsilon" depends on a direction which will get complicated again. |
The fix I pushed with the scaling is running in 7000ms. Commenting out the So probably the best thing to do is to close this PR and enable use of minified version, which is faster than the scaling fix. :-( |
@shehzan10 thanks for investigating. One more thought, can we get similar benefits with a simpler but even more permissive culling check? Perhaps a bounding box check instead of a raycasting? It seems like that could still "cull" a majority of triangles in a typical query from having barycentric coordinates computed. |
@shehzan10 can you confirm that #7514 really does provide you a speed-up here? |
Yes, it does.
|
I tested this out and definitely faster in development mode, with a smaller advantage in production. Instead of using the full Edit: This is also faster than using "slightly larger triangle" with the raycast test. |
Thanks @shehzan10! Code changes look good, and from offline discussion, bounding box check looks to still be ~2x better than |
Thanks for helping with the triage and ideas. Couldn't get it to be as fast as I'd initially hoped, but still better than it was before. |
QuantizedMeshTerrainData.interpolateHeight
iterates through all triangles in the tile to find the triangle which contains the point.To do this, the function was computing barycentric coordinates for each and every triangle, which is a lot of computation when done for the entire mesh.
This optimization introduces a check for point in triangle using ray casting algorithm that has early exit conditions, thus the amount of floating-point operations is reduced significantly.
I've seen speed ups of upto 5x when using
interpolateHeight
. This will also speed upsampleTerrain
,sampleTerrainMostDetailed
and other clamp to ground functions.