EncodedCartesian3.encode improvement?

Hi!

We have noticed that using 65536.0 as a fixedpoint factor in EncodedCartesian3.encode causes inaccuracy in sub-centimeter details - like very accurate Billboard positioning.

We changed the factor to 2048.0 and everything works very well… In webgl / opengl 32-bit float seems to start having accuracy problems after about 50 000 meters. Using 65536.0 as a factor in EncodedCartesian3.encode results in encoded.low values above 50 000 meters on the surface of the globe. And this is exactly what we are able to fix by using the 2048.0 there.

Would be awesome to hear any thoughts on this… Could @Kevin_Ring be watching this by any chance? :slight_smile:

Here is a sandcastle to demonstrate the issue:

If you zoom in to the dark dot on one corner, it “jumps” while camera is moving:

Problem is really in the details, it does not matter if one is not using centimeter level details…

Hi @v12424124_34,

I wasn’t watching, but I am now! :slight_smile: Thanks for raising this, and for the Sandcastle. Very interesting!

Cesium’s EncodedCartesian (as mentioned in the docs) is based on the GPUE RTE technique described in this blog post:Precisions, Precisions | STK Components for .NET 2021 r3

That article claims that GPU RTE, parameterized the way it is in EncodedCartesian3, should guarantee position accuracy of about 1.353 cm. I modified your Sandcastle to draw another point 1cm away to help judge the distances. Just eyeballing it, I’d say it’s achieving much better than that in this case, maybe an order of magnitude better. Still, it’s possible to zoom in close enough that that even a millimeter of error maps to multiple pixels on the screen and creates some visible jitter.

So why not use a smaller divisor as you’ve suggested? Well the problem with that is just that it limits our range of possible coordinate values, too. Going from 65536 to 2048 will give us 32x more precision in each direction, or approximately 0.027 cm overall error, according to Deron’s computation in the blog. But it will also limit the range of possible coordinate values to 1/32 of what was previously possible.

Admittedly, though, that will still allow coordinate values that are more than large enough for the vast majority of Cesium users. Perhaps it’s worth taking a look at what it would take to make this value configurable, and even defaulting it to a more precise but Earth-centric value.

If you’re up for it, I’d love to see a pull request for the change to a smaller divisor. I can’t promise we’ll merge it, but it will be useful regardless because it will make it easier for other developers to take a look and try it out and discuss. I’m not officially working on CesiumJS at the moment. If you feel like taking a stab at making it configurable, that would be even better!

Thanks,
Kevin

Thank you, I really appreciate your answer! I have been searching for that AGI link, it was mentioned somewhere in the code but the link was not working.

I need to study the provided math a bit more myself, but right now I am also trying to figure out the cons of this approach:

EncodedCartesian3.encode = function (value, result) {
  //>>includeStart('debug', pragmas.debug);
  Check.typeOf.number("value", value);
  //>>includeEnd('debug');

  if (!defined(result)) {
    result = {
      high: 0.0,
      low: 0.0,
    };
  }

  var doubleHigh;
  doubleHigh = Math.round(value);
  result.high = doubleHigh;
  result.low = value - doubleHigh;

  return result;
};

There must by something I am missing on that idea, but it seems to solve our first test scenarios very well. :thinking:

edit: I guess this boils down to: “But it will also limit the range of possible coordinate values to 1/32 of what was previously possible.” => My Math.round-idea reduces the range down to 1/65536 of previous. After all this is maybe a use-case specific thing: When building on earth, we might need 1mm to 1cm accuracy, but for planetary scale visualization the range is very different. Altough I would bet that 80% of Cesium usage is pretty earth centric. :slight_smile: This is just a wild guess though!

Here is a sandcastle to study this “phenomena” a bit using Math.fround. I am not sure yet if fround matches the float spec we have running in the GPUs… But anyways, this might already show some of the characteristicts. (When running, see the console log.)

I am trying to figure out if there was a generic way to serve “best of both worlds”… For example what would happen if we use 2048.0 for smaller numbers and 63556.0 after a certain threshold. And could the diviver even be calculated continously?


Source: Demystifying Floating Point Precision « The blog at the bottom of the sea

I was still trying to see what is the large number end of this issue and prepared a sandcastle that puts billboards to 100 million kilometers altitude:

The interesting thing is that with divider 65536.0 billboards work but movement jitters - with 2048.0 they also work, just without any jitter! So right now I am struggling a bit to find the negative effect of using the 2048.0 divider. :slight_smile:

I found one test case where long distance starts causing, ellipsoid culling:

Right at the horizon, the label disappears too early after this frame already:

But again… Even this sandcastle behaves the same way with both divisors.

After investigating this we ended up using the divisor 2048.0 in our use cases. I couldn’t really advance on a more generic solution as I couldn’t find a test case where more precision would be needed when distances grow really big. The 2048.0 works really well with millimeter level details and it seems to work up to 100 million kilometers!

I tried to think on using dynamic divisor based on input range but did not advance this idea as I do not fully understand the challenge on the larger distances.