#ifndef _OF_VEC3f
#define _OF_VEC3f


#ifndef DEG_TO_RAD
	#define DEG_TO_RAD 0.0174532925199433
#endif

#ifndef RAD_TO_DEG
	#define RAD_TO_DEG 57.2957795130823
#endif



class ofVec3f {

  public:
	
	
    //----------------  Constructors
    
    ofVec3f( const float _x = 0.0f, 
             const float _y = 0.0f,  
             const float _z = 0.0f  )
    {
        x = _x;
        y = _y;
        z = _z;
    }
	
    ofVec3f( const ofVec3f& vec )  
    {
        x = vec.x;
        y = vec.y;
        z = vec.z;
    }
    
    
    
    
    //---------------- Data Members
    float x;
    float y;
    float z;
    
   
   
   
    //---------------- Methods


    // Getters and Setters.
    //
    //     
    void set( float _x, float _y, float _z )
    {
        x = _x;
        y = _y;
        z = _z;
    }
    
    void set( const ofVec3f &vec )
    {
        x = vec.x;
        y = vec.y;
        z = vec.z;
    }

    float &operator[]( const int &i )           
    { 
        switch(i) {
            case 0:  return x;
            case 1:  return y;
            case 2:  return z;
            default: return x;
        }
    }

    
    
    // Check similarity/equality.
    //
    //        
    bool operator==( const ofVec3f vec )           
    { 
        return (x == vec.x) && (y == vec.y) && (z == vec.z);
    }

    bool operator!=( const ofVec3f vec )           
    { 
        return (x != vec.x) || (y != vec.y) || (z != vec.z);
    }
    
    bool match( const ofVec3f vec, float tollerance=0.0001) {
        return (fabs(x - vec.x) < tollerance) 
            && (fabs(y - vec.y) < tollerance)
            && (fabs(z - vec.z) < tollerance);
    }    
    
    /**
    * Checks if vectors look in the same direction.
    */      
    bool align( const ofVec3f& vec ) const
    {
        return (x*vec.x + y*vec.y + z*vec.z) > 0.0;
    }    
    
    

    // Additions and Subtractions.
    //
    //
    ofVec3f operator+( const ofVec3f& vec ) const          
    { 
        return ofVec3f( x+vec.x, y+vec.y, z+vec.z); 
    } 
    
    ofVec3f& operator+=( const ofVec3f& vec )              
    { 
        x += vec.x;
        y += vec.y;
        z += vec.z;
        return *this; 
    } 
    
    ofVec3f operator-( const ofVec3f& vec ) const          
    { 
        return ofVec3f( x-vec.x, y-vec.y, z-vec.z ); 
    } 
    
    ofVec3f& operator-=( const ofVec3f& vec )              
    { 
        x -= vec.x;
        y -= vec.y;
        z -= vec.z;
        return *this; 
    } 

    ofVec3f operator-() const                               
    { 
        return ofVec3f( -x, -y, -z ); 
    }
    


    // Scalings
    //
    //
    ofVec3f operator*( const ofVec3f& vec ) const
    { 
        return ofVec3f( x*vec.x, y*vec.y, z*vec.z ); 
    } 
    
    ofVec3f& operator*=( const ofVec3f& vec )      
    { 
        x *= vec.x;
        y *= vec.y;
        z *= vec.z;
        return *this; 
    } 

    ofVec3f operator*( const float f ) const                
    { 
        return ofVec3f( x*f, y*f, z*f );
    } 
    
    ofVec3f& operator*=( const float f )                    
    { 
        x *= f;
        y *= f;
        z *= f;
        return *this; 
    } 
    
    ofVec3f operator/( const ofVec3f& vec ) const  
    { 
        return ofVec3f( x/vec.x, y/vec.y, z/vec.z );
    } 
    
    ofVec3f& operator/=( const ofVec3f& vec )      
    { 
        x /= vec.x;
        y /= vec.y;
        z /= vec.z;
        return *this; 
    } 

    ofVec3f operator/( const float f ) const      
    { 
        return ofVec3f( x/f, y/f, z/f );
    } 

    ofVec3f& operator/=( const float f )          
    { 
        x /= f;
        y /= f;
        z /= f;
        return *this; 
    }
    
    ofVec3f rescaled( const float length ) const
    { 
        float l = (float)sqrt(x*x + y*y + z*z);
        if( l > 0 )
            return ofVec3f( (x/l)*length, (y/l)*length, (z/l)*length );
        else
            return ofVec3f();
    }
        
    ofVec3f& rescale( const float length )
    { 
        float l = (float)sqrt(x*x + y*y + z*z);
        if (l > 0) {
            x = (x/l)*length;
            y = (y/l)*length;
            z = (z/l)*length;
        }
        return *this;
    }


     
    // Rotation
    //
    //      
    ofVec3f rotated( float angle, const ofVec3f& axis ) const
    {
        ofVec3f ax = axis.normalized();        
        float a = angle*DEG_TO_RAD;
        float sina = sin( a );
        float cosa = cos( a );
        float cosb = 1.0 - cosa;
        
        return ofVec3f( x*(ax.x*ax.x*cosb + cosa) 
                      + y*(ax.x*ax.y*cosb - ax.z*sina) 
                      + z*(ax.x*ax.z*cosb + ax.y*sina),
                        x*(ax.y*ax.x*cosb + ax.z*sina) 
                      + y*(ax.y*ax.y*cosb + cosa) 
                      + z*(ax.y*ax.z*cosb - ax.x*sina),
                        x*(ax.z*ax.x*cosb - ax.y*sina) 
                      + y*(ax.z*ax.y*cosb + ax.x*sina) 
                      + z*(ax.z*ax.z*cosb + cosa) );                   
    }
    
    ofVec3f& rotate( float angle, const ofVec3f& axis )
    {
        ofVec3f ax = axis.normalized();        
        float a = angle*DEG_TO_RAD;
        float sina = sin( a );
        float cosa = cos( a );
        float cosb = 1.0 - cosa;
        
        x = x*(ax.x*ax.x*cosb + cosa) 
          + y*(ax.x*ax.y*cosb - ax.z*sina) 
          + z*(ax.x*ax.z*cosb + ax.y*sina);
        y = x*(ax.y*ax.x*cosb + ax.z*sina) 
          + y*(ax.y*ax.y*cosb + cosa) 
          + z*(ax.y*ax.z*cosb - ax.x*sina);
        z = x*(ax.z*ax.x*cosb - ax.y*sina) 
          + y*(ax.z*ax.y*cosb + ax.x*sina) 
          + z*(ax.z*ax.z*cosb + cosa);                   
                        
        return *this;
    }

    ofVec3f rotated(float ax, float ay, float az) 
    {
        float a = cos(DEG_TO_RAD*(ax));
        float b = sin(DEG_TO_RAD*(ax));
        float c = cos(DEG_TO_RAD*(ay));
        float d = sin(DEG_TO_RAD*(ay));
        float e = cos(DEG_TO_RAD*(az));
        float f = sin(DEG_TO_RAD*(az));

        float nx = c * e * x - c * f * y + d * z;
        float ny = (a * f + b * d * e) * x + (a * e - b * d * f) * y - b * c * z;
        float nz = (b * f - a * d * e) * x + (a * d * f + b * e) * y + a * c * z;

        return ofVec3f( nx, ny, nz );
    }
        
    ofVec3f& rotate(float ax, float ay, float az) 
    {
        float a = cos(DEG_TO_RAD*(ax));
        float b = sin(DEG_TO_RAD*(ax));
        float c = cos(DEG_TO_RAD*(ay));
        float d = sin(DEG_TO_RAD*(ay));
        float e = cos(DEG_TO_RAD*(az));
        float f = sin(DEG_TO_RAD*(az));

        float nx = c * e * x - c * f * y + d * z;
        float ny = (a * f + b * d * e) * x + (a * e - b * d * f) * y - b * c * z;
        float nz = (b * f - a * d * e) * x + (a * d * f + b * e) * y + a * c * z;

        x = nx; y = ny; z = nz;
        return *this;
    }    
    
    

    // Normalization
    //
    //
    ofVec3f normalized() const 
    { 
        float length = (float)sqrt(x*x + y*y + z*z);
        if( length > 0 ) {
            return ofVec3f( x/length, y/length, z/length ); 
        } else { 
            return ofVec3f(); 
        }
    } 

    ofVec3f& normalize()                  
    { 
        float lenght = (float)sqrt(x*x + y*y + z*z);
        if( lenght > 0 ) { 
            x /= lenght; 
            y /= lenght; 
            z /= lenght; 
        }
        return *this;
	} 
    
    

    // Limit length.
    //
    //         
	 ofVec3f limited(float max) const
     {
        float length = (float)sqrt(x*x + y*y + z*z);
		if( length > max && length > 0 ) {
            return ofVec3f( (x/length)*max, (y/length)*max, (z/length)*max );
		} else {
            return ofVec3f( x, y, z ); 
        }
	}
    
    ofVec3f& limit(float max)
    {
        float length = (float)sqrt(x*x + y*y + z*z);
		if( length > max && length > 0 ) {
            x = (x/length)*max;
            y = (y/length)*max;
            z = (z/length)*max;
        }
        return *this;
    }    
    


    // Perpendicular vector.
    //
    //
    ofVec3f cross( const ofVec3f& vec ) const      
    { 
        return ofVec3f( y*vec.z - z*vec.y, 
                        z*vec.x - x*vec.z, 
                        x*vec.y - y*vec.x ); 
    } 
    
    /**
    * Normalized perpendicular.
    */
    ofVec3f perpendicular( const ofVec3f& vec ) const
    {
        float crossX = y*vec.z - z*vec.y;
        float crossY = z*vec.x - x*vec.z;
        float crossZ = x*vec.y - y*vec.x;
        
        float length = (float)sqrt(crossX*crossX + 
                                   crossY*crossY + 
                                   crossZ*crossZ);
        
        if( length > 0 )
            return ofVec3f( crossX/length, crossY/length, crossZ/length );
        else
            return ofVec3f();
    } 
    


    // Length
    //
    //
    float length() const                
    { 
        return (float)sqrt( x*x + y*y + z*z ); 
    } 
    
    float lengthSquared() const                
    { 
        return (float)(x*x + y*y + z*z); 
    } 

    
    
    /**
    * Dot Product.
    */    
    float dot( const ofVec3f& vec ) const
    {     
        return x*vec.x + y*vec.y + z*vec.z;
    }    
	
};

#endif