// Fixed point operations for the Mellanox TILE-Gx integer raytracer.
// Written by Nils Liaaen Corneliusen 2017.
// License: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication license
// Please refer to the article at http://www.ignorantus.com/pages/tilegx_integer_raytracer/
// for more information.
#ifndef IVEC3H
#define IVEC3H

#define FRACBITS 20

typedef int64_t fixed;

typedef struct {
    fixed x;
    fixed y;
    fixed z;
} ivec3;

union fi32_u
{
    float f;
    int32_t i;
};

union fu32_u
{
    float f;
    uint32_t u;
};

// Float range -256.x to 255.x, mn 9.14
static inline int32_t f2i( float f )
{
    union fi32_u x;

    x.f  = f;
    x.f += 768.0f;

    x.i  &= 0x7fffff;
    x.i  -= (1<<8)<<14;
    x.i <<= (FRACBITS-14);

    return x.i;
}

static inline ivec3 v2iv( vec3 v )
{
    ivec3 iv;

    iv.x = f2i( v.x );
    iv.y = f2i( v.y );
    iv.z = f2i( v.z );

    return iv;
}

static inline fixed imul( fixed a, fixed b )
{
    return __insn_mul_ls_ls( a, b )>>FRACBITS;
}

static inline ivec3 ivec3_set( fixed x, fixed y, fixed z )
{
    ivec3 iv;

    iv.x = x;
    iv.y = y;
    iv.z = z;

    return iv;
}

static inline ivec3 ivec3_add( ivec3 a, ivec3 b )
{
    ivec3 iv;

    iv.x = a.x + b.x;
    iv.y = a.y + b.y;
    iv.z = a.z + b.z;

    return iv;
}

static inline ivec3 ivec3_add1( ivec3 a, fixed b )
{
    ivec3 iv;

    iv.x = a.x + b;
    iv.y = a.y + b;
    iv.z = a.z + b;

    return iv;
}

static inline ivec3 ivec3_sub( ivec3 a, ivec3 b )
{
    ivec3 iv;

    iv.x = a.x - b.x;
    iv.y = a.y - b.y;
    iv.z = a.z - b.z;

    return iv;
}

static inline ivec3 ivec3_scale( ivec3 a, fixed b )
{
    ivec3 iv;

    iv.x = imul( a.x, b );
    iv.y = imul( a.y, b );
    iv.z = imul( a.z, b );

    return iv;
}

static inline fixed idot( ivec3 a, ivec3 b )
{
    fixed r = __insn_mul_ls_ls(     a.x, b.x );
          r = __insn_mula_ls_ls( r, a.y, b.y );
          r = __insn_mula_ls_ls( r, a.z, b.z );
    return r>>FRACBITS;
}

static inline fixed idotsq( ivec3 a, ivec3 b )
{
    fixed r = __insn_mul_ls_ls(     a.x, b.x );
          r = __insn_mula_ls_ls( r, a.y, b.y );
          r = __insn_mula_ls_ls( r, a.z, b.z );
          r >>= FRACBITS;
    return imul( r, r );
}

static inline fixed inorm2( ivec3 a )
{
    fixed r = __insn_mul_ls_ls(     a.x, a.x );
          r = __insn_mula_ls_ls( r, a.y, a.y );
          r = __insn_mula_ls_ls( r, a.z, a.z );
    return r>>FRACBITS;
}

static inline fixed imax( fixed a, fixed b )
{
    return a > b ? a : b;
}

static inline fixed imin( fixed a, fixed b )
{
    return a < b ? a : b;
}

static inline fixed ipower_spec( fixed a )
{
    fixed b = imul( a, a );
    fixed c = imul( b, b );
    return imul( c, c );
}

// Note: Range 0.x-7.x, mn 3.20
static inline fixed isqrt( fixed num )
{
    union fu32_u x;

    x.u  = num;
//    x.u >>= FRACBITS-20;
    x.u |= 0x41000000;
    x.f -= 8.0f;

    x.u >>= 1;
    x.u  += (1<<29)-(1<<22);

    x.f += 8.0f;
    x.u &= 0x7fffff;
//    x.u <<= FRACBITS-20;

    return x.u;
}

// Note: Range 0.x-511.x, mn 9.14
static inline ivec3 inormalize( ivec3 a )
{
    union fu32_u x;

    fixed aa = inorm2( a );

    x.u   = aa;
    x.u >>= FRACBITS-14;
    x.u  |= 0x44000000;
    x.f  -= 512.0f;

    x.u = 0x5f3759df - (x.u >> 1);

    x.f  += 512.0f;
    x.u  &= 0x7fffff;
    x.u <<= FRACBITS-14;

    aa >>= 1;

    x.u = imul( x.u, (0x03<<(FRACBITS-1)) - imul( imul( x.u, x.u ), aa ) );
    x.u = imul( x.u, (0x03<<(FRACBITS-1)) - imul( imul( x.u, x.u ), aa ) );

    return ivec3_scale( a, x.u );
}
#endif
