Three.js: Feature request : toFixed() for vectors value

Created on 5 Nov 2019  路  19Comments  路  Source: mrdoob/three.js

Feature request

It would be nice to add a parameter to Vector3.round() and Vector3.roundToZero(), to ask for a number of decimal like Number.toFixed() in JS. If undefined, it would assume that an integer is needed, so it would not break anything.

Use case

I had to do my own function to do that, because the objects in my scene are user-transformed, then the transforms are exported as JSON. There is a big tolerance, so in order to reduce file size, I round the vector3 values. But rounding to integer is too much, that's why I did my own function.
I guess that it would also be useful in things like voxel-painter or Minecraft-likes.

I thought it might not be a big deal to add it to three.js, and it's useful, hence my feature request. If you think it's useless code, please close the issue.

Enhancement

Most helpful comment

We should not use Number.toFixed() since it returns a string. It seems that number-to-string conversion just for rounding is no good approach.

https://stackoverflow.com/questions/2283566/how-can-i-round-a-number-in-javascript-tofixed-returns-a-string#comment47147413_14978830

Can you please test if this would work for you:

THREE.Vector3.prototype.round = function( digits ) {

    var e = Math.pow( 10, digits ||聽0 );

    this.x = Math.round( this.x * e ) / e;
    this.y = Math.round( this.y * e ) / e;
    this.z = Math.round( this.z * e ) / e;

    return this;

}

Live example (check browser console): https://glitch.com/~meowing-bougon

However, I don't think this logic makes sense for Vector3.roundToZero().

All 19 comments

We should not use Number.toFixed() since it returns a string. It seems that number-to-string conversion just for rounding is no good approach.

https://stackoverflow.com/questions/2283566/how-can-i-round-a-number-in-javascript-tofixed-returns-a-string#comment47147413_14978830

Can you please test if this would work for you:

THREE.Vector3.prototype.round = function( digits ) {

    var e = Math.pow( 10, digits ||聽0 );

    this.x = Math.round( this.x * e ) / e;
    this.y = Math.round( this.y * e ) / e;
    this.z = Math.round( this.z * e ) / e;

    return this;

}

Live example (check browser console): https://glitch.com/~meowing-bougon

However, I don't think this logic makes sense for Vector3.roundToZero().

I tested your proposal for Vector3.round(), it's perfect.

Why does it make no sense for Vector3.roundToZero() ?

With your modified round() :

var v = new THREE.Vector3( -1.38943, 3.48843, -0.7685353535 );

console.log( v.round(2) );
// Vector3聽{x: -1.39, y: 3.49, z: -0.77}

With a hypothetical modified roundToZero() :

var v = new THREE.Vector3( -1.38943, 3.48843, -0.7685353535 );

console.log( v.roundToZero(2) );
// Vector3聽{x: -1.38, y: 3.48, z: -0.76}

Why does it make no sense for Vector3.roundToZero() ?

Sry, I've misunderstood the semantics of this method (I've never used it before). Since it internally works with Math.ceil() and Math.floor(), I'm not sure how to develop this accordingly. Open for suggestions here...

BTW: If Vector3.round() and Vector3.roundToZero() are enhanced, it's also necessary to do this for Vector2 and Vector4 for consistency reasons.

What about this ?

THREE.Vector3.prototype.roundToZero = function( digits ) {

    var e = Math.pow( 10, digits || 0 );

    this.x = ( ( this.x * e ) < 0 ) ? Math.ceil( ( this.x * e ) ) / e : Math.floor( ( this.x * e ) ) / e ;
    this.y = ( ( this.y * e ) < 0 ) ? Math.ceil( ( this.y * e ) ) / e : Math.floor( ( this.y * e ) ) / e ;
    this.z = ( ( this.z * e ) < 0 ) ? Math.ceil( ( this.z * e ) ) / e : Math.floor( ( this.z * e ) ) / e ;

    return this;

};


var v = new THREE.Vector3( -1.38943, 3.48843, -0.7685353535 );

console.log( v.roundToZero(2) );
// Vector3 {x: -1.38, y: 3.48, z: -0.76}

Um, why is ( ( this.x * e ) < 0 ) necessary? I thought this statement is only relevant to detect negative numbers.

You are right, it's not necessary, this is better :

THREE.Vector3.prototype.roundToZero = function( digits ) {

    var e = Math.pow( 10, digits || 0 );

    this.x = this.x < 0 ? Math.ceil( this.x * e ) / e : Math.floor( this.x * e ) / e ;
    this.y = this.y < 0 ? Math.ceil( this.y * e ) / e : Math.floor( this.y * e ) / e ;
    this.z = this.z < 0 ? Math.ceil( this.z * e ) / e : Math.floor( this.z * e ) / e ;

    return this;

};

Well, if @mrdoob approves I think it's okay to enhance round() and roundToZero() like presented here.

A PR should change Vector2, Vector3 and Vector4, the docs, TS files and the unit tests. Vector2.tests.js already has some rounding tests but I think it's better to remove them and implement the methods instead.

Careful, these functions might not have the expected behavior when the input precision digits are bigger than the original precision.

https://glitch.com/~hallowed-city (check console)

@sciecode Can you please explain in more detail? After a quick glance, it's not obvious to me what's going wrong.

In some cases it may lead to floating point imprecision. For example:

-1.38 rounded to 4-digits goes to -1.3799, instead of the expected -1.38.

image

edit: I think this only happens on the roundToZero method, and I'm unsure about what specifically causes this.

The semantics of Number.toFixed() and Vector3.toFixed() would be different since the native function returns a string. Hence, it's confusing to reuse the same name.

I think enhancing the methods is okay. If we can solve the issue @sciecode mentioned.

I know this is not very elegant, but it fixes the issue with roundToZero, and it's still better than converting to string :

THREE.Vector3.prototype.roundToZero = function( digits ) {

    var e = Math.pow( 10, digits || 0 );

        // return number of decimal places of a
    function precision( v ) {

        var e = 1, p = 0 ;

        while ( Math.round( v * e ) / e !== v ) {
            e *= 10 ;
            p ++ ;
        };

        return p ;
    };

    function roundToZero( v, e ) {

        var vDigits = precision( v );

        e = vDigits < digits ? Math.pow( 10, vDigits || 0 ) : e ;

        return v < 0 ? Math.ceil( v * e ) / e : Math.floor( v * e ) / e ;

    };

    this.x = roundToZero( this.x, e );
    this.y = roundToZero( this.y, e );
    this.z = roundToZero( this.z, e );

    return this;

};

That looks indeed a bit strange. Especially since creating precision() and roundToZero() each invocation of Vector3.roundToZero() is no good practice. It would be better to define these functions once in module scope.

@felixmariotto Anyway, would it be okay for you to just enhance Vector*.round() for now? I don't think we are going to enhance Vector*.roundToZero() if we need to introduce this complexity.

@Mugen87 It's totally fine for me, my use case was with round, it just seemed right to consider enhancing roundToZero as well. But I agree that it shouldn't introduce too much completexity.

Feel free to file a PR then 馃槉.

the objects in my scene are user-transformed, then the transforms are exported as JSON. There is a big tolerance, so in order to reduce file size, I round the vector3 values.

It doesn't make sense to "round" a number in binary to a certain number of decimal digits in base-10.

You can format the number when represented as a string, however.

Also, the term "round" is used to round to integers only.

If you think it's useless code, please close the issue.

If you are happy with the results you are getting, it is fine for your app -- just not three.js.

You can format the number when represented as a string, however.

You mean clamping to a given number of decimal character ? This is not like rounding, since 1.19 clamped to one decimal would give 1.1, whereas rounding gives 1.2.

It doesn't make sense to "round" a number in binary to a certain number of decimal digits in base-10.

If it's a matter of semantic, maybe you could elaborate on a alternative ?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

yqrashawn picture yqrashawn  路  3Comments

jlaquinte picture jlaquinte  路  3Comments

zsitro picture zsitro  路  3Comments

konijn picture konijn  路  3Comments

seep picture seep  路  3Comments