Jump to content


- - - - -

Normalizing vectors with very small components.


7 replies to this topic

#1 broli86

    Member

  • Members
  • PipPip
  • 81 posts

Posted 08 August 2008 - 07:31 AM

How do I handle this gracefuly ?
vector2d *vector2d_normalize(vector2d *a)
{
    double length;

    length = vector2d_length(a);
    if (length != 0.0)
    {
        a->x /= length;
        a->y /= length;
    }
    return (a);
}

But what if I want to calculate some vector even when the components are very small ?? The above approach won't normalize the vector and simply leave it as it is.

#2 Goz

    Senior Member

  • Members
  • PipPipPipPip
  • 574 posts

Posted 08 August 2008 - 10:18 AM

So when you debug it does length come out as 0.0?

If so .. there is your problem. vector2d_length may be using a very poor square root ...

#3 broli86

    Member

  • Members
  • PipPip
  • 81 posts

Posted 08 August 2008 - 12:34 PM

Goz said:

So when you debug it does length come out as 0.0?

If so .. there is your problem. vector2d_length may be using a very poor square root ...

Yes the length does come out as 0.0 and I have caught it many times using the assert macro. What do you mean by poor square root ? I'm using the sqrt function from math.h

#4 .oisyn

    DevMaster Staff

  • Moderators
  • 1810 posts

Posted 08 August 2008 - 12:44 PM

Goz said:

If so .. there is your problem. vector2d_length may be using a very poor square root ...
I think it's not the square root that's the problem, it's the quadratation of the components before doing the square root that causes trouble. A float has an 8-bit exponent ranging from -127 to 128. Squaring values between 0 and 1 effectively halves the exponent, so as soon as the values get smaller than around 2-64 (~1e-20) you're in trouble.

If you want to be able to normalize vectors with such small components, first scale them up, then normalize them.

Something like
vector normalize(vector v)
{
    const float smallvalue = 1.0e-20f;
    const float largevalue = 1.0e20f;
    if ((abs(v.x) < smallvalue && abs(v.y) < largevalue) ||
        (abs(v.y) < smallvalue && abs(v.x) < largevalue))
    {
        v.x *= smallvalue;
        v.y *= smallvalue;
    }

    // do the rest of the normalization here
}

.edit: made it somewhat more robust.
C++ addict
-
Currently working on: the 3D engine for Tomb Raider.

#5 Nick

    Senior Member

  • Members
  • PipPipPipPip
  • 1225 posts

Posted 08 August 2008 - 12:44 PM

vector2d_length computes the length as sqrt(x * x + y * y). So if x and/or y are very small then squaring them could result in underflow (zero), or denormal numbers (using only part of the normal precision). It's possible that sqrt turns denormals into zero too.

The solution: scale both x and y up by a power of two (say 2^20), then normalize.

#6 Blaxill

    Member

  • Members
  • PipPip
  • 66 posts

Posted 08 August 2008 - 03:29 PM

.oisyn said:

vector normalize(vector v)
{
    const float smallvalue = 1.0e-20f;
    const float largevalue = 1.0e20f;
    if ((abs(v.x) < smallvalue && abs(v.y) < largevalue) ||
        (abs(v.y) < smallvalue && abs(v.x) < largevalue))
    {
        v.x *= smallvalue;// here and
        v.y *= smallvalue;// here
    }

    // do the rest of the normalization here
}
Shouldn't the multiplies be 'largevalue'

#7 broli86

    Member

  • Members
  • PipPip
  • 81 posts

Posted 08 August 2008 - 03:39 PM

Blaxill said:

Shouldn't the multiplies be 'largevalue'

Yes I think it should be largevalue and we can apply the same logic for 3d vectors as well:


vector *vector_normalize(vector *a)

{

    double length;    

    const double smallvalue = 1.0e-20;

    const double largevalue = 1.0e20;


     if ((fabs(a->x) < smallvalue && fabs(a->y) < largevalue && fabs(a->z) < largevalue) ||

         (fabs(a->y) < smallvalue && fabs(a->x) < largevalue && fabs(a->z) < largevalue) ||

         (fabs(a->z) < smallvalue && fabs(a->x) < largevalue && fabs(a->y) < largevalue))

    {

        a->x *= largevalue;

        a->y *= largevalue;

        a->z *= largevalue;

    }


    length = vector_length(a);

    if (length != 0.0)

    {

      a->x /= length;

      a->y /= length;

      a->z /= length;      

    }


    return (a);

}


#8 .oisyn

    DevMaster Staff

  • Moderators
  • 1810 posts

Posted 08 August 2008 - 04:26 PM

Blaxill said:

Shouldn't the multiplies be 'largevalue'
uh yes indeed B)
C++ addict
-
Currently working on: the 3D engine for Tomb Raider.





1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users