/*****************************************************************************
 * $Id: eng_billboard.hh,v 1.11 2006/11/25 17:26:21 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Billboard class.
 *//*
 * LEGAL:	COPYRIGHT (C) 2006 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

////////////////////////////////////////////////////////////////////////////////
/// @class Billboard
/// @brief Billboard composed of stacked textures and oriented to viewplane and eye's up vector.
///
/// @verbatim
///
/// Features:
/// ---------
///
/// - Oriented in two ways (in this order):
///   1. Billboard geometry is placed parallel to the viewplane.
///   2. Billboard is rotated around Z axis to align Billboard's up vector with Eye's up vector.
/// - Composed of stacked textures.
/// - One Billboard can be shared by BillboardOwner objects (to share textures).
/// - For higher resolution with fewer texture resources, a higher number of "slices"
///   blended into a lesser number of actual texture objects.
/// - Billboard class is a factory that produces BillboardSlice objects.
///
/// Description:
/// ------------
///
/// Billboard is a horizontal stack of textured quads facing the viewpoint.
/// Billboarding is a form of volume-rendering.
/// A 3D object is divided into a set of "slices".
/// Each slice is a 2D image, identified by a Z coordinate in range {-0.5,..,0.5}.
///
/// Imagine looking at a cube at zero angle.
/// Then slice the cube vertically and arrange the slices in a horizontal stack.
/// This is a side-view (imagine standing to the side of a monitor for a moment).
///
///              | | | |
///  O ----->    | | | |     side-view
/// eye          | | | |     of slices 
///              0 1 2 3
///
///     +------+ slice[2]
///    /-------/ slice[1]      billboard would be facing directly, of course,
///   +-------+ slice[0]       not at an angle in this ASCII text drawing
///   |       |
///   |   o   |  <-- center
///   |       |
///   +-------+
///
/// Slice/texture ratio:
/// --------------------
///
/// For a compromise between image quality and texture resource usage,
/// a Billboard is specified with a slice/texture ratio.
/// For example, a Billboard with 64 slices and a slice/texture ratio of 4
/// will have every 4 slices blended into one composite texture object,
/// and will be rendered by 16 textured quads.
///
/// Two ways to orient a billboard:
/// -------------------------------
/// See Billboard::GetEyeVertexs().
///
/// Transforming Billboard geometry:
/// --------------------------------
/// [This applies to Billboard::GetEyeVertexs()]
/// 
/// A Billboard's geometry is transformed based on these principles/observations:
/// - When the Eye is rolled, billboards need to roll.
/// - When the eye is pitched or yawed, billboards don't need to rotate.
/// - Billboard geometry can/should be parallel to the viewplane
///
///     eye                       eye         eye
///     +Y                        +Y          +Y
/// +---------+        +---------+        +---------+
/// |         |        |         |        |         |
/// |   +-+   |eye     |    /\   |        |   +-+   | eye
/// |   | |   |+X      |   / /   |        |   | |   | +X
/// |   +-+   |        |   \/    |        |   +-+   |
/// |         |        |         |eye     |         |
/// +---------+        +---------++X      +---------+
/// billboard          billboard         vertical orbit
/// inside             rolls as          over billboard
/// viewport           eye rolls         (geometry stays constant)
///
/// In other words, this billboard is oriented in two ways:
/// First, it is oriented with the viewplane.
/// Second, the billboard's up vector is oriented with the Eye's up vector.
/// Rolling the billboard in sync with the Eye is sufficient to maintain
/// the billboard's illusion, even though this has no mathematical basis.
///
/// The Eye object simply adds every degree of roll, regardless of the state
/// of its matrix.  For example, if the Eye is looking at a Billboard,
/// does a 360' roll, orbits some, does another 360' roll, the Billboard's
/// roll will be the same both times.  The Eye accumulated {0..360} and {360..720}
/// degrees but for rotation both range of degrees are the same.
///
/// Billboard class manages resources and compositing images:
/// ---------------------------------------------------------
///
/// The Billboard class is dedicated to managing the texture images
/// (blending and loading images into texture objects).
/// The Billboard class is designed to allow sharing of texture objects.
/// Billboard is a standalone class unrelated to the Object class.
/// It isn't rendered, rather, Billboard is a factory that creates
/// BillboardSlice objects.  BillboardSlice is derived from the Object class.
/// BillboardSlice, being an Object, it is sorted and rendered in distance-order
/// like other Objects, which is necessary to use blending when rendering slices.
///
/// Usage:
/// ------
///
/// The clients (owners) of a Billboard must inherit from the Object and
/// and BillboardOwner classes.  Billboard uses the BillboardOwner
/// interface to request that the owner generate the texels for a slice.
/// Billboard takes care of blending slice images and loading
/// the composite image into an actual texture object.
///
/// class Cloud : public Object, public BillboardOwner
/// {
///     // Object interface:
///     GetPosition();
///     ...
///
///     // BillboardOwner interface:
///     Paint( slice info, image buffer, ... );
/// };
///
/// The Billboard class decides when images need to be generated
/// (at construction time, or when view angle changes if specified).
/// But, since the Billboard is a general class, it doesn't know how
/// to generate specific images, so it delegates to BillboardOwner::Paint().
/// BillboardOwner writes texels to the passed buffer, and Billboard
/// takes care of the image (compositing, loading images into OpenGL textures).
///
/// Sharing Billboards is done using Billboard::MakeInstance().
/// MakeInstance() is a factory function that makes a set of
/// BillboardSlice objects from the BillboardOwner.
/// The BillSlices are positioned in world space relative
/// to the BillboardOwner's position.  The advantange of sharing
/// Billboards is faster rendering and lower resource usage,
/// but the disadvantage is they all look the same.
///
/// Cloud::Cloud()
/// {
///     // Create one shared Billboard object.
///     if ( msBillboard == NULL )
///     {
///         msBillboard = new Billboard( *this );
///     }
///
///     // Create an instance of a cloud.
///     msBillboard->MakeInstance( *this, ... );
/// }
///
/// Design notes:
/// -------------
///
/// What proved to be impractical in the original Billboard design:
/// - A small threshold for the angle between viewpoint and billboard.
///   Generating images can be an extremely slow operation.
/// - Compositing all slices into one texture when LOD decreases.
///   Using gl*Pixels() to blend images produces a composite image
///   that looks different than rendering all the slices,
///   and is noticeable when rendering switches them according to LOD.
///   This lead to the alternative of a slice/texture ratio.
///
/// @endverbatim
////////////////////////////////////////////////////////////////////////////////

#ifndef ENG_BILLBOARD_HH
#define ENG_BILLBOARD_HH 1

#include "gfx_texture.hh"
#include "eng_object.hh"

class BillboardOwner;
class BillboardSlice;

// Z coord range {-0.5,..,0.5}.
const fp BILLBOARD_SLICE_MIN_Z = -0.5;
const fp BILLBOARD_SLICE_MAX_Z =  0.5;

////////////////////////////////////////////////////////////////////////////////
/// Info for smooth-shading a quad.
///
class QuadShade
{
COPYABLE(QuadShade)
public:
	QuadShade( void )
	{
		// NOP (relies on RGBA default ctor 0,0,0,0)
	}
	QuadShade( const RGBA rgba[4] )
	{
		mRgba[0] = rgba[0];  // copy RGBAs for the 4 vertexs of quad
		mRgba[1] = rgba[1];
		mRgba[2] = rgba[2];
		mRgba[3] = rgba[3];
	}
	~QuadShade()
	{
		// NOP
	}
	void Enable( bool enable )
	{
		// For disabling, clear colors.  Do nothing for enabling.
		if ( ! enable )
		{
			mRgba[0].Set( 0 );
			mRgba[1].Set( 0 );
			mRgba[2].Set( 0 );
			mRgba[3].Set( 0 );
		}
	}
	bool IfEnabled( void ) const
	{
		// Assume disable if no color at all (this assumption minimizes footprint, no mValid member).
		return ! mRgba[0].IfZero()
            || ! mRgba[1].IfZero()
            || ! mRgba[2].IfZero()
            || ! mRgba[3].IfZero();
	}
	/// @return RGBA of 1 of the 4 vertexs of quad.
	RGBA GetRgba( uint i ) const
	{
		ASSERT( i < 4 );
		return mRgba[i];
	}
	/// Set RGBA for 1 of the 4 vertexs of quad.
	void SetRgba( uint i, RGBA rgba )
	{
		ASSERT( i < 4 );
		mRgba[i] = rgba;
	}
private:
	RGBA	mRgba[4];  // 4 corners of quad
};

////////////////////////////////////////////////////////////////////////////////
// Billboard.
//
class Billboard
{
PREVENT_COPYING(Billboard)

public:
    friend class BillboardSlice;

public:
    ////////////////////////////////////////////////////////////////////////////
    /// Billboard paarameters for when BillboardOwner procedurally-generates
    /// texture images or specifies pre-loaded textures.
    ///
    class Parm
    {
		COPYABLE(Parm)
    public:
		typedef std::vector<string> TextureNameContainer;

        // For procedular textures (the paint method).
        Parm( uint sliceCnt,
              uint sliceTextureRatio,
              uint textureWidth );

        // For pre-loaded textures (.tga files).
        Parm( const TextureNameContainer& textureNames );

        // Setter methods omitted.  Ctor has done assertions.
        bool IfProceduralTextures( void ) const { return mTextureNames.empty(); }
        bool IfPreloadedTextures( void ) const { return ! mTextureNames.empty(); }
        uint GetSliceCnt( void ) const { return mSliceCnt; }
        uint GetSliceTextureRatio( void ) const { return mSliceTextureRatio; }
        uint GetTextureWidth( void ) const { return mTextureWidth; }
        const uint GetTextureNameCnt( void ) const { return mTextureNames.size(); }
        const string GetTextureNameIdx( uint i ) const { ASSERT( i < mTextureNames.size() ); return mTextureNames[i]; }

    private:
        DEBUG_CODE( void CheckCtor( void ); )
        uint        mSliceCnt;               // applies whether procudular/pre-loaded
        uint        mSliceTextureRatio;      // ditto
        uint        mTextureWidth;           // ditto
        TextureNameContainer mTextureNames;  // names of pre-loaded textures (N/A procedural)
    };

public:
                    Billboard( BillboardOwner& billboardOwner, const Parm& parm );
               		~Billboard();
    void            MakeInstance( BillboardOwner& billboardOwner, Object& object, SafePtr<QuadShade> shade = &msQuadShadeNone );
    void            SetViewAngleThreshold( Radian rad );  // (stub)

    /// @return Number of slices (not the same as number of textures).
	uint			GetSliceCnt( void ) const
	{
	ASSERT( mBits.mSliceCnt >= MIN_SLICES );
    ASSERT( mBits.mSliceCnt <= MAX_SLICES );
		return mBits.mSliceCnt;
	}

    /// @return Number of textures (not the same as number of slices).
	uint			GetTextureCnt( void ) const
	{
	ASSERT( mBits.mTextureCnt >= MIN_TEXTURES );
    ASSERT( mBits.mTextureCnt <= MAX_TEXTURES );
		return mBits.mTextureCnt;
	}

    uint            GetTextureWidth( void ) const
    {
    ASSERT( mBits.mTextureWidth >= MIN_TEXTURE_WIDTH );
    ASSERT( mBits.mTextureWidth <= MAX_TEXTURE_WIDTH );
        return mBits.mTextureWidth;
    }

    /// A billboard has more slices than textures.
    /// So several slice images are composited as one texture.
    /// Eg, if sliceCnt=64 and textureCnt=4, then sliceTextureRatio=16.
    uint            GetSliceTextureRatio( void ) const
    {
        // Must be evenly divisible (integral).
        ASSERT( (GetSliceCnt() % GetTextureCnt()) == 0 );

        uint ratio = GetSliceCnt() / GetTextureCnt();
    ASSERT( ratio > 0 );
        return ratio;
    }

    uint            GetTexelCnt( void ) const
    {
        uint texelCnt = SQUARE(mBits.mTextureWidth);
    ASSERT( texelCnt >= MIN_TEXELS );
    ASSERT( texelCnt <= MAX_TEXELS );
        return texelCnt;
    }

    Texture::eTexelType GetTexelType( void ) const
    {
        return TEXEL_TYPE;
    }

    typedef Texture::Texel32 Texel;  // 32-bit BGRA8 texel in fact

private:
#if GCC29
public:
#endif
    static const uint SLICE_CNT_BITS        = 7;    // 1<<7  = 128
    static const uint TEXTURE_CNT_BITS      = 7;    // 1<<7  = 128
    static const uint TEXTURE_WIDTH_BITS    = Texture2::MAX_TEXTURE_WIDTH_BITS;

    static const uint MIN_SLICES            = 1;  // 1 for single-slice billboards
    static const uint MAX_SLICES            = (1 << SLICE_CNT_BITS)-1;

    static const uint MIN_TEXTURES          = 1;  // 1 for single-slice billboards
    static const uint MAX_TEXTURES          = (1 << TEXTURE_CNT_BITS)-1;

    static const uint MIN_TEXTURE_WIDTH     = 8;                     // catch absurd values
    static const uint MAX_TEXTURE_WIDTH     = Texture2::MAX_TEXTURE_WIDTH;

	static const uint MIN_TEXELS            = MIN_TEXTURE_WIDTH * MIN_TEXTURE_WIDTH;
	static const uint MAX_TEXELS            = MAX_TEXTURE_WIDTH * MAX_TEXTURE_WIDTH;

	static const uint TEXEL_LEN                 = Texture::TEXEL32_LEN;
	static const Texture::eTexelType TEXEL_TYPE = Texture::eTexelType_Texel32;

private:
    Texture2*       NewTexture( const uint textureWidth );
	void			Paint( BillboardOwner& billboardOwner );
    void            Paint( BillboardOwner& billboardOwner,
                           const uint textureIdx,
                           const uint sliceBegin, const uint sliceEnd, // end as in STL
                           const uint texelCnt, const uint imgLen );

    /// @return Z coordinate of slice in local/billboard space.
    /// If single slice, z = 0 always.
    /// If stacked slices, z = {-0.5, 0.0, 0,5} for {first, middle, last} slices.
    fp              GetSliceZ( const uint sliceIdx ) const
                    {
                        return GetZ_( sliceIdx, GetSliceCnt() );
                    }
    fp              GetTextureZ( const uint textureIdx ) const
                    {
                        return GetZ_( textureIdx, GetTextureCnt() );
                    }
    fp              GetZ_( const fp idx, const fp cnt ) const
                    {
                        fp z = 0.0;
                        if ( ! mBits.mSingleSlice )
                            z = fp(idx) / fp(cnt) - 0.5;
                    ASSERT( z >= BILLBOARD_SLICE_MIN_Z );
                    ASSERT( z <= BILLBOARD_SLICE_MAX_Z );
                        return z;
                    }

private:
    // BillboardOwner is ephemeral so it shouldn't be stored as a data member.
    // That's because Billboards are often shared by multiple BillboardOwners.
    //
    // mRequestedSliceCnt is the number passed to ctor.
    // Actual slice count varies according to distance from Billboard (1 or mSlices.size()).
    // mSlices has either 1 or multiple elements if slices are combined or stacked, resp.
    // mSlices[0] either holds the combined slice or one of many slices in the stack.

    struct Bits
    {
        uint    mSliceCnt			: SLICE_CNT_BITS;       // passed to ctor
        uint    mTextureCnt			: TEXTURE_CNT_BITS;     // passed to ctor
       	uint    mTextureWidth       : TEXTURE_WIDTH_BITS;   // passed to ctor
        uint    mSingleSlice        : 1;
    };
    Bits                mBits;
    vector< SafePtr<Texture2> >  mTextures;  // SafePtr to prevent deleting (complicated)
	static QuadShade	msQuadShadeNone;
};

////////////////////////////////////////////////////////////////////////////////
/// BillboardOwner is an Object that temporarily owns a Billboard object.
/// The owner paints billboard slices (since Object class knows its shape).
///
class BillboardOwner
{
public:
                    BillboardOwner( void ) { }
	virtual         ~BillboardOwner() { }
    virtual void    Paint( Billboard& billboard, const uint sliceIdx,
                           Billboard::Texel* const texels, const uint texelCnt ) = 0;
};

////////////////////////////////////////////////////////////////////////////////
/// BillboardSlice represents a textured-quad Object.
/// A BillboardSlice typically is in fact multiple temporary slices
/// that were composited into a single image to minimize texture resources.
///
class BillboardSlice : public ObjectData
{
PREVENT_COPYING(BillboardSlice)
typedef ObjectData Parent;

public:
                        BillboardSlice( Object& object, Texture2& texture, const fp z, SafePtr<QuadShade> shade );
                        ~BillboardSlice() { }

    fp                  GetZ( void ) const
                        {
                        ASSERT( mZ >= BILLBOARD_SLICE_MIN_Z );
                        ASSERT( mZ <= BILLBOARD_SLICE_MAX_Z );
                            return mZ;
                        }

    // Object methods:
    void                FinalConstruction( void ) { }
    const string        GetName( void ) { return string("BillboardSlice"); }
    bool                Reproducible( void ) { return true; }
    void                Draw( void );
    void                SetPosition( const Position& pos ) { } // NOP
    Position            GetPosition( void ) { return mObject.GetPosition(); }
    fp                  GetDistance( void );
    const EyeVertex*    GetEyeVertexs( void );

private:
    Object&         mObject;    // Object that owns this (billboard owner in another sense)
    Texture2&       mTexture;   // encapsulates an OpenGL 2D texture
    fp              mZ;         // Z coordinate in local space {-0.5,...,0.5}
	SafePtr<QuadShade> mShade;  // should always point to a QuadShade (it can be enabled or disabled)
};

#if DEBUG
typedef       SafePtr<Billboard> BillboardPtr;
typedef const SafePtr<Billboard> BillboardPtrConst;
#else
typedef Billboard*       BillboardPtr;
typedef Billboard* const BillboardPtrConst;
#endif

#endif // ENG_BILLBOARD_HH
