/*-
 * Copyright (c) 1999,2000 Qube Software, Ltd.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * 1. Redistributions of the source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Any redistribution solely in binary form must conspicuously
 *    reproduce the following disclaimer in documentation provided with the
 *    binary redistribution.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'', WITHOUT ANY WARRANTIES, EXPRESS
 * OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  LICENSOR SHALL
 * NOT BE LIABLE FOR ANY LOSS OR DAMAGES RESULTING FROM THE USE OF THIS
 * SOFTWARE, EITHER ALONE OR IN COMBINATION WITH ANY OTHER SOFTWARE.
 * 
 * 	$Id: affine4.h,v 1.5 2000/04/20 16:21:57 n_hibma Exp $
 */

#ifndef __affine4f_h
#define __affine4f_h

MATHS_BEGIN

struct EXPORT_QMATHS Affine4f : public TransformFlags
{
private:
    struct Vec4x3
    {
	float		varray_[4][3];
	inline operator Vec3f*()
	    { return (Vec3f*) varray_; }
	inline operator const Vec3f*() const
	    { return (const Vec3f*) varray_; }
    };
#if 0
    struct OffsetCheck
    {
	// Generate compile error if offsets don't match float[4][3]
	int x_[sizeof(Vec3f) == (sizeof(float) * 3) ? 1 : -1];
    };
#endif
public:
    // flags inlines added for adaptability PK ie if functions code 
    // can be used in templates for matrices with no flags.
    
    unsigned int flags;

    union 
    {
	struct {
	    float       xx, xy, xz;
	    float       yx, yy, yz;
	    float       zx, zy, zz;
	    float       tx, ty, tz;
	} u;
	Vec4x3 m;
    };

    // constructors 

    Affine4f() {}
    Affine4f(const Affine4f& r)  { *this = r; } 
 
    // flags manipulation

    inline unsigned int getflags() const                { return(flags); }
    inline unsigned int getflags(unsigned int f) const  { return(flags & f); }
    inline void         setflags(unsigned int f)        { flags = f; }
    inline void         addflags(unsigned int f)        { flags |= f; }
    inline void         clearflags(unsigned int f)      { flags &= (~f); }
    
    // Array accessors
    Vec3f&		operator[](unsigned int i) { return m[i]; }
    const Vec3f&        operator[](unsigned int i) const { return m[i]; }

    Affine4f& operator =(const Matrix4f& src)
	{ set(src); return(*this); }

    // conversion

    void getMatrix4f(Maths::Matrix4f &dst) const;

    // Vector access

    const Vec3f& xvec3() const { return(*(const Vec3f*)&u.xx); }
    const Vec3f& yvec3() const { return(*(const Vec3f*)&u.yx); }
    const Vec3f& zvec3() const { return(*(const Vec3f*)&u.zx); }
    const Vec3f& tvec3() const { return(*(const Vec3f*)&u.tx); }

    Vec3f& xvec3() { return(*(Vec3f*)&u.xx); }
    Vec3f& yvec3() { return(*(Vec3f*)&u.yx); }
    Vec3f& zvec3() { return(*(Vec3f*)&u.zx); }
    Vec3f& tvec3() { return(*(Vec3f*)&u.tx); }

    // note these are from the object's point of view

#ifdef RIGHTHAND_COORS
    const Vec3f&    up()        const { return(zvec3()); }
#else
    const Vec3f&    up()        const { return(yvec3()); }
    const Vec3f&    direction() const { return(zvec3()); }
#endif

    // geometry
        
    void            clearW() {} // { xw = yw = zw = 0; tw = 1; clearflags(General); }                   

    void            buildFlags();

    inline int      isIdentity() const
	{ return(flags == Identity); }
    inline int      isTranslated() const 
	{ return(flags & (Translate)); }
    inline int      isRotated() const 
	{ return(flags & (Rotate | Shear)); }
    inline int      isScaled() const
	{ return(flags & (Scale | UniformScale | Shear)); }
    

    inline void     set(const Affine4f& src)          
	{ *this = src; }
    
    void            set(const Maths::Matrix4f& src);

    // set to identity matrix
    void            setIdentity();

    // set rotation leaving translation unaltered
    void            setRotation(const Vec3f& axis, float angle);
    // set rotation leaving translation unaltered
    void            setRotation(const Quat4f& quat);
    
    // set rotation and clear translation
    void            setRotationT(const Vec3f& axis, float angle);
    // set rotation and clear translation
    void            setRotationT(const Quat4f& quat);

    // adds rotation to matrix including position if center is 0,0,0
    // then the translation is unaltered.
    void            addRotation(const Quat4f& quat, 
                                const Vec3f& center = Vec3f::origin());
    
    void            addRotation(const Vec3f& axis, float angle, 
                                const Vec3f& cent = Vec3f::origin());

    void            addScaling(float scale);
    void            addScaling(Vec3f& scale);
    inline void     setTranslation(const Vec3f& v) 
	{ tvec3() = v; addflags(Translate); }
    inline void     getTranslation(Vec3f& v) const 
	{ v = tvec3(); }
    inline void     addTranslation(const Vec3f& v) 
	{ tvec3() += v; addflags(Translate); }

    void            scaleTranslate(float scale, const Vec3f& Vec3f);
   
    void            rotateTo(Vec3f& d, const Vec3f& s) const
	{
	    d.x = s.x * u.xx + s.y * u.yx + s.z * u.zx;
	    d.y = s.x * u.xy + s.y * u.yy + s.z * u.zy;
	    d.z = s.x * u.xz + s.y * u.yz + s.z * u.zz;
	} 
    void            rotate(Vec3f& d) const
	{
	    float x = d.x;
	    float y = d.y;
	    float z = d.z;
	    
	    d.x = x * u.xx + y * u.yx + z * u.zx;
	    d.y = x * u.xy + y * u.yy + z * u.zy;
	    d.z = x * u.xz + y * u.yz + z * u.zz;
	} 
    void            transformTo(Vec3f& d, const Vec3f& s) const
	{
	    d.x = s.x * u.xx + s.y * u.yx + s.z * u.zx + u.tx;
	    d.y = s.x * u.xy + s.y * u.yy + s.z * u.zy + u.ty;
	    d.z = s.x * u.xz + s.y * u.yz + s.z * u.zz + u.tz;
	} 
    void            transform(Vec3f& d) const
	{
	    float x = d.x;
	    float y = d.y;
	    float z = d.z;
	    
	    d.x = x * u.xx + y * u.yx + z * u.zx + u.tx;
	    d.y = x * u.xy + y * u.yy + z * u.zy + u.ty;
	    d.z = x * u.xz + y * u.yz + z * u.zz + u.tz;
	} 
    
    void            invert();
    void            invert(const Affine4f& src);

    void            invertAffine();
    void            invertAffine(const Affine4f& src);

    void            invertOrthonormal();
    void            invertOrthonormal(const Affine4f& src);

    void            mult(const Affine4f& a, const Affine4f& b);

    inline void     mult(const Affine4f& bSrc)
	{  mult(Affine4f(*this),bSrc); }
    
 

    inline void     imult(const Affine4f& aSrc)
	{  mult(aSrc,Affine4f(*this)); }
                 
                                                    
    void            getYZAxes(Vec3f& xAxis, Vec3f& yAxis, Vec3f& zAxis) const;


    // this might be better using a quaternion
    void            makeRayToZAxis(const Vec3f& orig, const Vec3f& vec);
    void            zyNormalize();

    // *** arithmetic operator overloads
    
    inline Affine4f &operator *=(const Affine4f &from)
	{  mult(from); return(*this); }


};

MATHS_END

#endif // __affine4f_h
