How to do high precision transformations?

My situation is sort of like this:

// Setup:
var spotPivot = new BABYLON.TransformNode();
var spot = new BABYLON.MeshBuilder.CreateRibbon(...);
spot.parent = spotPivot;

// Now what I am doing prior to rendering is:
var onRender = (function()
{
    var a = 0;
    return function()
    {
        spot.setParent(spotPivot);
        updateMesh(spot, a);
        spot.setParent(null);
        
        a = a < 100 ? ++a : 0;
    };
})();

Note that spotPivot is located at an extremely far distance from the camera, however, the spot transformation takes this back to very close to the camera. I am using setParent(null) to trigger a computation that calculates the real position of spot on the CPU before it’s sent to the GPU. This avoids needing a very high precision, which is not available on the GPU.

To make clear what happens: spotPivot.position.z is perhaps 10000000000, but the spot.position.z is actually -1000000000. The camera is located at (0,0,0). So therefore, after the parent is removed, the spot is actually located on like spot.position.z = 10. This avoids needing high precision on GPU, and still allows me to use extreme distances in the model keeping high precision on small scale.

However, BabylonJS does not accurately compute this new position. When I animate another axis using a in the code, then the animation is rounded off, resulting in a non-smooth animation. I believe this is due to a built-in inaccuracy in BabylonJS, and I suspect it may have something to do with the BABYLON.Epsilon value.

I’ve tried temporarily changing this value before setting parent, and resetting to original value after setParent(null), so I could have higher accuracy. However, this does not work, the epsilon value actually does not change. Also, I’ve seen other epsilon values hard-coded scattered in the source code.

I would like to know if the epsilon is responsible for these inaccurate transformations, and if so, how I can change it in the context of the code I’ve supplied.
Thank you in advance for any input!

Hi @jetibest and welcome to the forum. A playground with a simple version of your animation would help us help you. Have you tried ’ spot.getAbsolutePosition()’?

Have you already read Rotate Around an Axis About a Point - Babylon.js Documentation ?

Yes @JohnK, see this https://www.babylonjs-playground.com/#H86H70#1 link for a demonstration.

The model distance parameter should not matter for accuracy, you will see that in both cases, the sphere.position.z is the same value. Except one moves in steps, and the other moves smooth. This imprecision is caused by BabylonJS, not by Javascript on CPU or OpenGL on GPU.

The playground does not load for me when I use the link, but this is the playground code (I tried saving the code below again, but it says “Unable to save your code. It may be too long.”):

var createScene = function()
{
    var REAL_DISTANCE = 100;
    var BIG_MODEL_DISTANCE = 1000000000;
    var SMALL_MODEL_DISTANCE = 1000000;

    // This creates a basic Babylon Scene object (non-mesh)
    var scene = new BABYLON.Scene(engine);

    // This creates and positions a free camera (non-mesh)
    var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 1, 0), scene);
    camera.minZ = 0.1;
    camera.maxZ = 1000;

    // This creates a light, aiming 0,1,0 - to the sky (non-mesh)
    var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene);

    var mat = new BABYLON.StandardMaterial();
    mat.wireframe = true;

    var sphereLeftPivot = new BABYLON.TransformNode();
    sphereLeftPivot.position.z = BIG_MODEL_DISTANCE;

    // Our built-in 'sphere' shape. Params: name, subdivs, size, scene
    var sphereLeft = BABYLON.Mesh.CreateSphere("sphere1", 16, 50, scene);
    sphereLeft.position.x = -0.333*REAL_DISTANCE;
    sphereLeft.position.y = 1;
    sphereLeft.material = mat;

    var sphereRightPivot = new BABYLON.TransformNode();
    sphereRightPivot.position.z = SMALL_MODEL_DISTANCE;

    var sphereRight = BABYLON.Mesh.CreateSphere("sphere1", 16, 50, scene);
    sphereRight.position.x = 0.333*REAL_DISTANCE;
    sphereRight.position.y = 1;
    sphereRight.material = mat;

    // Our built-in 'ground' shape. Params: name, width, depth, subdivs, scene
    var ground = BABYLON.Mesh.CreateGround("ground1", 6, 6, 2, scene);

    var onRender = (function()
    {
        var a = 0, d, n = 100;
        return function()
        {
            d = Math.sin(Math.PI*(2*a/n - 1));

            sphereLeft.setParent(sphereLeftPivot);
            sphereLeft.position.z = (d+2) * REAL_DISTANCE - sphereLeftPivot.position.z;
            sphereLeft.setParent(null);

            sphereRight.setParent(sphereRightPivot);
            sphereRight.position.z = (d+2) * REAL_DISTANCE - sphereRightPivot.position.z;
            sphereRight.setParent(null);

            a = a < n ? ++a : 0;
        };
    })();
    engine.runRenderLoop(onRender);

    return scene;
};

I’ve found the problem. Apparently Float32Arrays are used for matrix calculations. This is fine when directly dealing with the GPU, but not so much for internal model transformations. To demonstrate this:

// low precision
var lowp = new Float32Array(1);
lowp[0] = 999999868.4547106;
console.log(lowp[0]); // = 999999872

// high precision
var highp = new Float64Array(1);
highp[0] = 999999868.4547106;
console.log(highp[0]); // = 999999868.4547106

I wanted to backtrack the source code where the issue was. But the BabylonJS source code was just too much to walk through. Then I tried a ThreeJS playground, and it seemed the issue happened there too. So then I looked into the ThreeJS source code, and it is much less bloated, and made for efficiency. There I found that the matrix calculation was using Float32Arrays.

I’d like to create my own custom function that does the necessary calculations, and then using Float32Arrays, since I’d rather not modify the BabylonJS library directly for consistency reasons. Not sure how easy this will be, but I’ll let you know what I came up with when I’m done, if I don’t switch to using ThreeJS.

I disagree :slight_smile:.

Here are our math functions: Babylon.js/math.ts at master · BabylonJS/Babylon.js · GitHub

Here is your playground btw: Babylon.js Playground

And the place where the world matrix is computed:

Nope. Not at all. All math are done using pure JS functions. No epsilon is used in the math computations. Epsilon is used only for collision and physics. The accuracy issue comes from JavaScript. We are using number values for matrices or vector3.

Hey man, I’m not judging, only observing. The fact remains that the code in ThreeJS for that particular function is much smaller and simpler to debug for a complete stranger to both libraries. See for yourself: https://github.com/mrdoob/three.js/blob/dev/src/core/Object3D.js#L635
Maybe because BabylonJS is more feature-rich, I’m not saying it’s necessarily a bad thing.

And indeed, I found the problem already. It is because you use FloatArray32 in your computations, which makes sense when you deal with the GPU, but maybe you could fix your library to set a high precision mode, which enables the use of FloatArray64 for computations that don’t directly deal with GPU yet.

Thank you for your explanation on the BABYLON.Epsilon constant.

Sorry, I surely misinterpreted the tone. No worries

Moving to float64array could be doable with a Matrix64 class that could inherit from Matrix. The only change that I see is making sure to store data in a Float64Array and changing the way data is sent to shaders

Ok, so I figured, instead of hacking my way into the library (which is kind of difficult due to the source code being in typescript and with the constants or readonly values etc), I download the git repository and implement that Matrix64 you mentioned myself. However, this installs all the other components too. I really just want to be compile the Javascript library. Do you have any quick instructions for that? That would help me a lot.

I followed the instructions on the github page, using npm and gulp commands. But I’m getting:

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
 1: 0x55783722c6f1 node::Abort() [gulp]
...
Aborted (core dumped)

yeah the build is kind of heavy :smiley:
just run ‘npm run build’ in the tools/gulp folder

I’ve tried it, it takes very long, and then there was an error. Fixed it, and then later another error. These might be simple errors, but I’m not going to invest more time in this, so I’m sorry, I won’t be implementing this :wink:

PS I switched to ThreeJS, where I only had to change this.elements = [...]; to this.elements = new Float64Array([...]); in the constructor of the Matrix4 class. See also https://github.com/mrdoob/three.js/issues/1898 where this is discussed. In their latest code the uniformMatrix4fv patch is not even needed anymore. It works beautifully now :slight_smile:

Well or you could have read the doc
https://doc.babylonjs.com/how_to/how_to_start

Doing the change took me 3 minutes within vscode.

Glad that you found a way to do it anyway. This is the only important thing