
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <gl\gl.h>
#include <gl\glu.h>

#include "PajaTypes.h"
#include "Vector2C.h"
#include "ColorC.h"
#include <vector>
#include "SWFShape.h"
#include "SWFChar.h"
#include "SWFBitmapC.h"

using namespace PajaTypes;


//
// Gradient
//


SWFGradientC::SWFGradientC( uint32 ui32Type ) :
	m_ui32Type( ui32Type ),
	m_ui32TexId( 0 )
{
	// empty
}

SWFGradientC::~SWFGradientC()
{
	if( m_ui32TexId )
		glDeleteTextures( 1, &m_ui32TexId );
}

uint32
SWFGradientC::get_type() const
{
	return m_ui32Type;
}

void
SWFGradientC::add_color( const ColorC& rCol, uint32 ui32Pos  )
{
	m_rColors.push_back( rCol );
	m_rPositions.push_back( ui32Pos );
}

uint32
SWFGradientC::get_color_count() const
{
	return m_rColors.size();
}

const ColorC&
SWFGradientC::get_color( uint32 ui32Index ) const
{
	return m_rColors[ui32Index];
}

uint32
SWFGradientC::get_position( uint32 ui32Index ) const
{
	return m_rPositions[ui32Index];
}

void
SWFGradientC::build()
{
	int32	i, j, k;

	if( m_ui32TexId )
		return;

	glGenTextures( 1, &m_ui32TexId);
	glBindTexture( GL_TEXTURE_2D, m_ui32TexId );

	if( m_ui32Type == GRADIENT_RADIAL ) {
		// Build radial gradient

		uint8	ui8Tex[32 * 32 * 4];
		uint8	ui8R, ui8G, ui8B, ui8A;
		uint8*	pTex;

		// fill background

		pTex = ui8Tex;

		uint32	ui32MinPos = m_rPositions[0];
		uint32	ui32MaxPos = m_rPositions[m_rPositions.size() - 1];

		for( i = 0; i < 32; i++ ) {
			for( j = 0; j < 32; j++ ) {

				ColorC	rResCol;
				float32	f32X = (float32)j - 16.0f;
				float32	f32Y = (float32)i - 16.0f;
				uint32	ui32Rad = (uint32)(sqrt( f32X * f32X + f32Y * f32Y ) * 16.0);

				if( ui32Rad < ui32MinPos )
					rResCol = m_rColors[0];
				else if( ui32Rad >= ui32MaxPos )
					rResCol = m_rColors[m_rColors.size() - 1];
				else {
					for( k = 0; k < m_rColors.size() - 1; k++ ) {
						uint32	ui32StartPos = m_rPositions[k];
						uint32	ui32EndPos = m_rPositions[k + 1];
						if( ui32Rad >= ui32StartPos && ui32Rad < ui32EndPos ) {
							ColorC	rStartCol = m_rColors[k];
							ColorC	rEndCol = m_rColors[k + 1];
							uint32	ui32DeltaPos = ui32EndPos - ui32StartPos;
							float32	f32Alpha = (float32)(ui32Rad - ui32StartPos) / (float32)ui32DeltaPos;
							rResCol = rStartCol * (1.0f - f32Alpha) + rEndCol * f32Alpha;
							break;
						}
					}
				}

				ui8R = (uint8)(rResCol[0] * 255.0f * rResCol[3]);
				ui8G = (uint8)(rResCol[1] * 255.0f * rResCol[3]);
				ui8B = (uint8)(rResCol[2] * 255.0f * rResCol[3]);
				ui8A = (uint8)(rResCol[3] * 255.0f);

				*pTex++ = ui8R;
				*pTex++ = ui8G;
				*pTex++ = ui8B;
				*pTex++ = ui8A;
			}
		}

		glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, ui8Tex );
	}
	else {
		// Build linear gradient
		uint8	ui8Tex[8 * 32 * 4];
		uint8	ui8R, ui8G, ui8B, ui8A;
		uint8*	pTex;

		for( i = 0; i < m_rColors.size() - 1; i++ ) {

			ColorC	rStartCol = m_rColors[i];
			ColorC	rEndCol = m_rColors[i + 1];
			ColorC	rResCol;
			uint32	ui32StartPos = m_rPositions[i] / 8;
			uint32	ui32EndPos = m_rPositions[i + 1] / 8;
			uint32	ui32DeltaPos = ui32EndPos - ui32StartPos;

			for( j = ui32StartPos; j < ui32EndPos; j++ ) {
				float32	f32Alpha = (float32)(j - ui32StartPos) / (float32)ui32DeltaPos;
				rResCol = rStartCol * (1.0f - f32Alpha) + rEndCol * f32Alpha;
				ui8R = (uint8)(rResCol[0] * 255.0f * rResCol[3]);
				ui8G = (uint8)(rResCol[1] * 255.0f * rResCol[3]);
				ui8B = (uint8)(rResCol[2] * 255.0f * rResCol[3]);
				ui8A = (uint8)(rResCol[3] * 255.0f);

				pTex = &ui8Tex[j * (8 * 4)];

				for( k = 0; k < 8; k++ ) {
					*pTex++ = ui8R; *pTex++ = ui8G; *pTex++ = ui8B; *pTex++ = ui8A;
				}
			}
		}

		ColorC	rResCol;

		// fill start
		rResCol = m_rColors[0];
		ui8R = (uint8)(rResCol[0] * 255.0f * rResCol[3]);
		ui8G = (uint8)(rResCol[1] * 255.0f * rResCol[3]);
		ui8B = (uint8)(rResCol[2] * 255.0f * rResCol[3]);
		ui8A = (uint8)(rResCol[3] * 255.0f);
		for( i = 0; i < m_rPositions[0] / 8; i++ ) {
			pTex = &ui8Tex[i * 8 * 4];
			for( j = 0; j < 8; j++ ) {
				*pTex++ = ui8R; *pTex++ = ui8G; *pTex++ = ui8B; *pTex++ = ui8A;
			}
		}

		// fill end
		rResCol = m_rColors[m_rColors.size() - 1];
		ui8R = (uint8)(rResCol[0] * 255.0f * rResCol[3]);
		ui8G = (uint8)(rResCol[1] * 255.0f * rResCol[3]);
		ui8B = (uint8)(rResCol[2] * 255.0f * rResCol[3]);
		ui8A = (uint8)(rResCol[3] * 255.0f);
		for( i = m_rPositions[m_rPositions.size() - 1] / 8; i < 32; i++ ) {
			pTex = &ui8Tex[i * 8 * 4];
			for( j = 0; j < 8; j++ ) {
				*pTex++ = ui8R; *pTex++ = ui8G; *pTex++ = ui8B; *pTex++ = ui8A;
			}
		}

		glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 8, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, ui8Tex );
	}


	glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
}

uint32
SWFGradientC::get_texture_id() const
{
	return m_ui32TexId;
}

//
// Fillstyle
//

SWFFillStyleC::SWFFillStyleC( PajaTypes::uint32 ui32Type ) :
	m_ui32Type( ui32Type )
{
	// empty
}

SWFFillStyleC::~SWFFillStyleC()
{
	// empty
}

uint32
SWFFillStyleC::get_type() const
{
	return m_ui32Type;
}

void
SWFFillStyleC::set_color( const ColorC& rCol )
{
	m_rColor = rCol;
}

ColorC
SWFFillStyleC::get_color() const
{
	return m_rColor;
}

void
SWFFillStyleC::set_matrix( const Matrix2C& rMat )
{
	m_rMatrix = rMat;
}

const Matrix2C&
SWFFillStyleC::get_matrix() const
{
	return m_rMatrix;
}

void
SWFFillStyleC::set_gradient( SWFGradientC* pGrad )
{
	m_rGradient = *pGrad;
}

SWFGradientC*
SWFFillStyleC::get_gradient()
{
	return &m_rGradient;
}

void
SWFFillStyleC::set_bitmap_id( uint32 ui32Id )
{
	m_ui32BitmapId = ui32Id;
}

uint32
SWFFillStyleC::get_bitmap_id() const
{
	return m_ui32BitmapId;
}



//
// Line style
//

SWFLineStyleC::SWFLineStyleC( float32 f32Width, const ColorC& rColor ) :
	m_f32Width( f32Width ),
	m_rColor( rColor )
{
	// empty
}

SWFLineStyleC::~SWFLineStyleC()
{
	// empty
}

ColorC
SWFLineStyleC::get_color() const
{
	return m_rColor;
}

float32
SWFLineStyleC::get_width() const
{
	return m_f32Width;
}


//
// Edge
//

SWFEdgeC::SWFEdgeC() :
	m_bCurve( false ),
	m_ui16FillStyle0( 0 ),
	m_ui16FillStyle1( 0 ),
	m_ui16LineStyle( 0 )
{
	// empty
}

SWFEdgeC::~SWFEdgeC()
{
	// empty
}

void
SWFEdgeC::set_is_curve( bool bCurve )
{
	m_bCurve = bCurve;
}

void
SWFEdgeC::set_start_point( const Vector2C& rPt )
{
	m_rStartPt = rPt;
}

void
SWFEdgeC::set_end_point( const Vector2C& rPt )
{
	m_rEndPt = rPt;
}

void
SWFEdgeC::set_control_point( const Vector2C& rPt )
{
	m_rControlPt = rPt;
}

void
SWFEdgeC::set_fill_style0( uint16 ui16Fill )
{
	m_ui16FillStyle0 = ui16Fill;
}

void
SWFEdgeC::set_fill_style1( uint16 ui16Fill )
{
	m_ui16FillStyle1 = ui16Fill;
}

void
SWFEdgeC::set_line_style( uint16 ui16Line )
{
	m_ui16LineStyle = ui16Line;
}

bool
SWFEdgeC::is_curve() const
{
	return m_bCurve;
}

const Vector2C&
SWFEdgeC::get_start_point() const
{
	return m_rStartPt;
}

const Vector2C&
SWFEdgeC::get_end_point() const
{
	return m_rEndPt;
}

const Vector2C&
SWFEdgeC::get_control_point() const
{
	return m_rControlPt;
}

uint16
SWFEdgeC::get_fill_style0() const
{
	return m_ui16FillStyle0;
}

uint16
SWFEdgeC::get_fill_style1() const
{
	return m_ui16FillStyle1;
}

uint16
SWFEdgeC::get_line_style() const
{
	return m_ui16LineStyle;
}

//
// Shape displaylist
//

SWFRenderDispListC::SWFRenderDispListC() :
	m_ui32DispList( 0 ),
	m_ui32Texture( 0 ),
	m_ui32FillStyle( 0 )
{
	// empty
}

SWFRenderDispListC::~SWFRenderDispListC()
{
	if( m_ui32DispList )
		glDeleteLists( m_ui32DispList, 1 );
}

void
SWFRenderDispListC::set_color( const ColorC& rColor )
{
	m_rColor = rColor;
}

const ColorC&
SWFRenderDispListC::get_color() const
{
	return m_rColor;
}

void
SWFRenderDispListC::set_displist( uint32 ui32List )
{
	m_ui32DispList = ui32List;
}

uint32
SWFRenderDispListC::get_displist() const
{
	return m_ui32DispList;
}

void
SWFRenderDispListC::set_texture( uint32 ui32Tex )
{
	m_ui32Texture = ui32Tex;
}

uint32
SWFRenderDispListC::get_texture() const
{
	return m_ui32Texture;
}

void
SWFRenderDispListC::set_fillstyle( uint32 ui32Style )
{
	m_ui32FillStyle = ui32Style;
}

uint32
SWFRenderDispListC::get_fillstyle() const
{
	return m_ui32FillStyle;
}


void
SWFRenderDispListC::add_edge( const SWFRenderEdgeS* pEdge )
{
	m_rEdges.push_back( *pEdge );
}

uint32
SWFRenderDispListC::get_edge_count() const
{
	return m_rEdges.size();
}

SWFRenderEdgeS*
SWFRenderDispListC::get_edge( uint32 ui32Index )
{
	return &(m_rEdges[ui32Index]);
}



SWFRenderShapeC::SWFRenderShapeC()
{
	// empty
}

SWFRenderShapeC::~SWFRenderShapeC()
{
	for( uint32 i = 0; i < m_rDispLists.size(); i++ )
		delete m_rDispLists[i];
	m_rDispLists.clear();
}

void
SWFRenderShapeC::add_displist( SWFRenderDispListC* pList )
{
	m_rDispLists.push_back( pList );
}

uint32
SWFRenderShapeC::get_displist_count() const
{
	return m_rDispLists.size();
}

SWFRenderDispListC*
SWFRenderShapeC::get_displist( uint32 ui32Index )
{
	return m_rDispLists[ui32Index];
}


//
// Shape record
//

SWFShapeRecordC::SWFShapeRecordC() :
	m_f32MinScale( 1.0f ),
	m_f32MaxScale( 1.0f ),
	m_pRenderShape( 0 )
{
	// empty
}

SWFShapeRecordC::~SWFShapeRecordC()
{
	delete m_pRenderShape;
}

void
SWFShapeRecordC::add_fill( const SWFFillStyleC* pFill )
{
	m_rFills.push_back( *pFill );
}

uint32
SWFShapeRecordC::get_fill_count() const
{
	return m_rFills.size();
}

SWFFillStyleC*
SWFShapeRecordC::get_fill( uint32 ui32Idx )
{
	return &(m_rFills[ui32Idx]);
}

void
SWFShapeRecordC::add_line_style( const SWFLineStyleC* pOutline )
{
	m_rLineStyles.push_back( *pOutline );
}

uint32
SWFShapeRecordC::get_line_style_count() const
{
	return m_rLineStyles.size();
}

SWFLineStyleC*
SWFShapeRecordC::get_line_style( uint32 ui32Idx )
{
	return &(m_rLineStyles[ui32Idx]);
}

void
SWFShapeRecordC::add_edge( const SWFEdgeC* pEdge )
{
	m_rEdges.push_back( *pEdge );
}

uint32
SWFShapeRecordC::get_edge_count() const
{
	return m_rEdges.size();
}

const SWFEdgeC*
SWFShapeRecordC::get_edge( uint32 ui32Idx ) const
{
	return &(m_rEdges[ui32Idx]);
}

SWFRenderShapeC*
SWFShapeRecordC::get_render_shape()
{
	return m_pRenderShape;
}

float32
SWFShapeRecordC::get_min_scale() const
{
	return m_f32MinScale;
}

float32
SWFShapeRecordC::get_max_scale() const
{
	return m_f32MaxScale;
}

void
SWFShapeRecordC::set_min_scale( float32 f32Scale )
{
	m_f32MinScale = f32Scale;
}

void
SWFShapeRecordC::set_max_scale( float32 f32Scale )
{
	m_f32MaxScale = f32Scale;
}

float32
SWFShapeRecordC::get_scale() const
{
	return m_f32Scale;
}

void
SWFShapeRecordC::set_scale( float32 f32Scale )
{
	m_f32Scale = f32Scale;
}




struct EdgeS {
	PajaTypes::Vector2C	rPt[2];
	PajaTypes::int32	i32Idx[2];
	PajaTypes::int32	i32LineStyle;
	bool				bVisited;
};

struct PointS {
	std::vector<PajaTypes::uint32>	rEdges;
	PajaTypes::Vector2C				rPos;
};

struct PathS {
	std::vector<PajaTypes::uint32>	rPoints;
};

struct FillS {
	PajaTypes::uint32		ui32Fill;
	std::vector<EdgeS>		rEdges;
	std::vector<PointS>		rPoints;
	std::vector<PathS>		rPaths;
};



static bool					g_bIsGradientFill = false;
static bool					g_bIsBitmapFill = false;
static Matrix2C				g_rFillMatrix;
static float32					g_f32TexWidth;
static float32					g_f32TexHeight;



struct GarbListItemS {
	GLdouble		dVert[3];
	GarbListItemS*	pNext;
};

static	GarbListItemS*	g_pGarbagelist = NULL;
static	int				g_iGarbageCount = 0;


static
GLdouble*
add_garbage()
{
	// alloc memory for garbage
	// allocate mem for new list item
	GarbListItemS*	pTemp = new GarbListItemS;
	// add at head of list
	pTemp->pNext = g_pGarbagelist;
	g_pGarbagelist = pTemp;

	g_iGarbageCount++;

	return pTemp->dVert;
}

static
void
delete_garbage()
{
	if( g_pGarbagelist != NULL ) {
		GarbListItemS* pPunt = g_pGarbagelist;
		GarbListItemS* pTemp = g_pGarbagelist;
		// scan the list
		while( pPunt != NULL ) {
			// delete vertex
			pPunt = pPunt->pNext;
			// delete list item
			delete pTemp;
			pTemp = pPunt;
		}
		g_pGarbagelist = NULL;
	}
}


static
void CALLBACK
begin_callback( GLenum type )
{
	// issue corresponding GL call
	glBegin( type );
}

static
void CALLBACK
error_callback( GLenum errorCode )
{
//	TRACE( "Tesselation error: %s\n", gluErrorString( errorCode ) );
}

static
void CALLBACK
end_callback()
{
	// issue corresponding GL call
	glEnd();
}

static
void CALLBACK
vertex_callback( GLvoid *vertex )
{
	GLdouble*	ptr = (GLdouble*)vertex;

	if( g_bIsGradientFill ) {
		Vector2C	rPos( ptr[0], ptr[1] );
		rPos = rPos * g_rFillMatrix;
		float32	f32U = rPos[0] / 32768.0f * 20.0f + 0.5f;
		float32	f32V = rPos[1] / 32768.0f * 20.0f + 0.5f;
		glTexCoord2f( f32V, f32U );	// the gradient texture is build vertically, not horizontally as in flash -> change U/V
	}
	else if( g_bIsBitmapFill ) {
		Vector2C	rPos( ptr[0], ptr[1] );
		rPos = rPos * g_rFillMatrix;
		float32	f32U = rPos[0] / g_f32TexWidth * 20.0f;
		float32	f32V = rPos[1] / g_f32TexHeight * 20.0f;
		glTexCoord2f( f32U, f32V );
	}

	// issue corresponding GL call (double is used to get max precision)
	glVertex3dv( ptr );
}

static
void CALLBACK
combine_callback( GLdouble coords[3], GLdouble *data[4], GLfloat weight[4], GLdouble **dataOut )
{
	// allocate memory for a new vertex
	GLdouble *vertex = add_garbage();

	// store reported vertex
	vertex[0] = coords[0];
	vertex[1] = coords[1];
	vertex[2] = coords[2];
	// return vertex to OGL
	*dataOut = vertex;
}


static
void
calc_bezier( const Vector2C& rV0, const Vector2C& rV1, const Vector2C& rV2, std::vector<Vector2C>& rPoints, float32 f32Flatness )
{
	Vector2C	rV20;
	float32		f32Z20Dot;
	float32		f32ZPerp;
	float32		f32MaxPerpSq;

	Vector2C	rC1, rC2, rRes;

	rV20 = rV2 - rV0;

	// z3_0_dot is dist z0-z2 squared
	f32Z20Dot = rV20[0] * rV20[0] + rV20[1] * rV20[1];

	if( f32Z20Dot > (3 * 3) ) {
		// perp is distance from line, multiplied by dist z0-z3
		f32MaxPerpSq = f32Flatness * f32Flatness * f32Z20Dot;

		f32ZPerp = (rV1[1] - rV0[1]) * rV20[0] - (rV1[0] - rV0[0]) * rV20[1];

		if( f32ZPerp * f32ZPerp > f32MaxPerpSq ) {
			// subdivide
			rC1 = (rV0 + rV1) * 0.5;
			rC2 = (rV2 + rV1) * 0.5;
			rRes = (rC1 + rC2) * 0.5;

			calc_bezier( rV0, rC1, rRes, rPoints, f32Flatness );
			calc_bezier( rRes, rC2, rV2, rPoints, f32Flatness );

			return;
		}
	}

	// don't subdivide
	rPoints.push_back( rV2 );
}


#define		EPSILON		(1.0f / 20.0f)
#define		FLATNESS		1.0f

inline
bool
points_equal( const Vector2C& rA, const Vector2C& rB )
{
	if( fabs( rA[0] - rB[0] ) < EPSILON	&&
		fabs( rA[1] - rB[1] ) < EPSILON )
		return true;
	return false;
}


void
SWFShapeRecordC::prepare( bool bIsGlyph, SWFDictionaryC* pDict )
{
	uint32		i, j, k;
	EdgeS		rE;

	if( !m_pRenderShape )
		m_pRenderShape = new SWFRenderShapeC;

	SWFRenderShapeC*	pRenderShape = m_pRenderShape; //get_render_shape();

	if( !pRenderShape->get_displist_count() ) {

		GLUtesselator*	tobj = gluNewTess();

		gluTessCallback( tobj, GLU_TESS_BEGIN, (void (CALLBACK*)())&begin_callback ); 
		gluTessCallback( tobj, GLU_TESS_VERTEX, (void (CALLBACK*)())&vertex_callback ); 
		gluTessCallback( tobj, GLU_TESS_END, (void (CALLBACK*)())&end_callback );
		gluTessCallback( tobj, GLU_TESS_COMBINE, (void (CALLBACK*)())&combine_callback );
		gluTessCallback( tobj, GLU_TESS_ERROR, (void (CALLBACK*)())&error_callback );

		std::vector<FillS>	rFills;

		rE.i32Idx[0] = -1;
		rE.i32Idx[1] = -1;
		rE.bVisited = false;

		// Calculate scale for tesselation
		float32		f32Scale = (get_min_scale() + get_max_scale()) * 0.5f;
		set_scale( f32Scale );

		// Make scale matrix
		Matrix2C	rTM;
		rTM.set_scale( Vector2C( f32Scale, f32Scale ) );

		//
		// First tesselate and collect edges of same fill under same arrays
		//

		for( i = 0; i < get_edge_count(); i++ ) {
			const SWFEdgeC*	pEdge = get_edge( i );

			if( bIsGlyph ) {

				// Add dummy fill for the glyph
				if( !rFills.size() ) {
					FillS	rFill;
					rFill.ui32Fill = -1;
					rFills.push_back( rFill );
				}

				// Add edge
				if( pEdge->is_curve() ) {

					Vector2C	rV1 = pEdge->get_start_point() * rTM;
					Vector2C	rV2 = pEdge->get_control_point() * rTM;
					Vector2C	rV3 = pEdge->get_end_point() * rTM;
					Vector2C	rPrev, rCur;

					rPrev = rV1;

					std::vector<Vector2C>	rPoints;
					calc_bezier( rV1, rV2, rV3, rPoints, FLATNESS );

					for( j = 0; j < rPoints.size(); j++ ) {
						rE.rPt[0] = rPrev;
						rE.rPt[1] = rPoints[j];
						rE.i32LineStyle = 0;
						rFills[0].rEdges.push_back( rE );
						rPrev = rPoints[j];
					}
				}
				else {
					rE.rPt[0] = pEdge->get_start_point() * rTM;
					rE.rPt[1] = pEdge->get_end_point() * rTM;
					rE.i32LineStyle = 0;
					rFills[0].rEdges.push_back( rE );
				}

			}
			else {
				int32	i32Fill0Idx = -1;
				int32	i32Fill1Idx = -1;

				// Find fill 0.
				for( j = 0; j < rFills.size(); j++ ) {
					if( rFills[j].ui32Fill == pEdge->get_fill_style0() ) {
						i32Fill0Idx = j;
						break;
					}
				}
				// Used fill didnt found from list, add it there.
				if( i32Fill0Idx == -1 ) {
					FillS	rFill;
					rFill.ui32Fill = pEdge->get_fill_style0();
					rFills.push_back( rFill );
					i32Fill0Idx = rFills.size() - 1;
				}


				// Find fill 1.
				for( j = 0; j < rFills.size(); j++ ) {
					if( rFills[j].ui32Fill == pEdge->get_fill_style1() ) {
						i32Fill1Idx = j;
						break;
					}
				}
				// Used fill didnt found from list, add it there.
				if( i32Fill1Idx == -1 ) {
					FillS	rFill;
					rFill.ui32Fill = pEdge->get_fill_style1();
					rFills.push_back( rFill );
					i32Fill1Idx = rFills.size() - 1;
				}

//				ASSERT( i32Fill0Idx >= 0 && i32Fill1Idx >= 0 );

				// Add the edge
				if( pEdge->is_curve() ) {

					// Tesselate bezier edge.
					Vector2C	rV1 = pEdge->get_start_point() * rTM;
					Vector2C	rV2 = pEdge->get_control_point() * rTM;
					Vector2C	rV3 = pEdge->get_end_point() * rTM;
					Vector2C	rPrev, rCur;

					rPrev = rV1;

					std::vector<Vector2C>	rPoints;
					calc_bezier( rV1, rV2, rV3, rPoints, FLATNESS );

					// Add tesselated points.
					for( j = 0; j < rPoints.size(); j++ ) {
						rE.rPt[0] = rPrev;
						rE.rPt[1] = rPoints[j];
						rE.i32LineStyle = pEdge->get_line_style();

						if( !rFills[i32Fill0Idx].ui32Fill && !rFills[i32Fill1Idx].ui32Fill )	// The edge is outline only.
							rFills[i32Fill0Idx].rEdges.push_back( rE );
						else {
							// add the outline to the fill which is draw later so that the outline is fully visible.
							if( i32Fill0Idx > i32Fill1Idx ) {
								rFills[i32Fill0Idx].rEdges.push_back( rE );
								rE.i32LineStyle = 0;			// Reset line style. Only one edge will draw it's outline.
								rFills[i32Fill1Idx].rEdges.push_back( rE );
							}
							else {
								rFills[i32Fill1Idx].rEdges.push_back( rE );
								rE.i32LineStyle = 0;			// Reset line style. Only one edge will draw it's outline.
								rFills[i32Fill0Idx].rEdges.push_back( rE );
							}
						}
						
						rPrev = rPoints[j];
					}
				}
				else {
					// Add straight edge.
					rE.rPt[0] = pEdge->get_start_point() * rTM;
					rE.rPt[1] = pEdge->get_end_point() * rTM;
					rE.i32LineStyle = pEdge->get_line_style();

					if( !rFills[i32Fill0Idx].ui32Fill && !rFills[i32Fill1Idx].ui32Fill )		// The edge is outline only.
						rFills[i32Fill0Idx].rEdges.push_back( rE );
					else {
						// add the outline to the fill which is draw later so that the outline is fully visible.
						if( i32Fill0Idx > i32Fill1Idx ) {
							rFills[i32Fill0Idx].rEdges.push_back( rE );
							rE.i32LineStyle = 0;			// Reset line style. Only one edge will draw it's outline.
							rFills[i32Fill1Idx].rEdges.push_back( rE );
						}
						else {
							rFills[i32Fill1Idx].rEdges.push_back( rE );
							rE.i32LineStyle = 0;			// Reset line style. Only one edge will draw it's outline.
							rFills[i32Fill0Idx].rEdges.push_back( rE );
						}
					}
				}
			}
		}

		//
		// Built shared point list and also
		// save the sharing edge to the point.
		//

		for( i = 0; i < rFills.size(); i++ ) {

			// Fill 0 is just edges.
			if( !rFills[i].ui32Fill )
				continue;

			for( j = 0; j < rFills[i].rEdges.size(); j++ ) {

				PointS	rPt;
				bool	bFound;

				// Find first point
				rPt.rPos = rFills[i].rEdges[j].rPt[0];
				bFound = false;
				for( k = 0; k < rFills[i].rPoints.size(); k++ ) {
					if( points_equal( rPt.rPos, rFills[i].rPoints[k].rPos ) ) {
						rFills[i].rEdges[j].i32Idx[0] = k;
						rFills[i].rPoints[k].rEdges.push_back( j );
						bFound = true;
						break;
					}
				}
				if( !bFound ) {
					rFills[i].rPoints.push_back( rPt );
					rFills[i].rEdges[j].i32Idx[0] = rFills[i].rPoints.size() - 1;
					rFills[i].rPoints[rFills[i].rPoints.size() - 1].rEdges.push_back( j );
				}

				// Find second point
				rPt.rPos = rFills[i].rEdges[j].rPt[1];
				bFound = false;
				for( k = 0; k < rFills[i].rPoints.size(); k++ ) {
					if( points_equal( rPt.rPos, rFills[i].rPoints[k].rPos ) ) {
						rFills[i].rEdges[j].i32Idx[1] = k;
						rFills[i].rPoints[k].rEdges.push_back( j );
						bFound = true;
						break;
					}
				}
				if( !bFound ) {
					rFills[i].rPoints.push_back( rPt );
					rFills[i].rEdges[j].i32Idx[1] = rFills[i].rPoints.size() - 1;
					rFills[i].rPoints[rFills[i].rPoints.size() - 1].rEdges.push_back( j );
				}
			}
		}


		//
		// Generate polygons
		//
		// Algorithm:
		//		The task of the algorithm is to convert a list of separate edges to
		//		one possibly closed contour.
		//
		//		The algorithm first finds a starting edge. The edge is simply first edge
		//		which has not been processed (visited) yet. Usually the first edge in the list.
		//		The selected edge is set as current edge.
		//
		//		Secondly the algorithm finds first not processed edge which one end of the current
		//		edge has and set the newly found edge as current edge. The same procedure is continued
		//		until there is no more edges where the algorithm could proceed.
		//
		//		It is possible that the elgorithm misses some edges from the input data, but the
		//		input data should be organized in a way that it's a contour in the first place.
		//

		for( i = 0; i < rFills.size(); i++ ) {

			// Fill 0 is just edges.
			if( !rFills[i].ui32Fill )
				continue;

			PathS	rPath;
			bool	bDone = false;
			int32	i32Edge = -1;
			int32	i32FirstEdge;

			while( 1 ) {
				// find first not visited edge
				i32Edge = -1;
				for( j = 0; j < rFills[i].rEdges.size(); j++ ) {
					if( !rFills[i].rEdges[j].bVisited ) {
						i32Edge = j;
						break;
					}
				}

				if( i32Edge == -1 )
					break;		// all edges done, next fill

				i32FirstEdge = i32Edge;

				// add first point
				rPath.rPoints.push_back( rFills[i].rEdges[i32Edge].i32Idx[0] );
				rPath.rPoints.push_back( rFills[i].rEdges[i32Edge].i32Idx[1] );

				int32	i32Vert = rFills[i].rEdges[i32Edge].i32Idx[1];

				rFills[i].rEdges[i32Edge].bVisited = true;

				while( 1 ) {

					// Selected next edge which shares the vertex 'i32Vert' and is not yet visited.
					int32	i32NewEdge = -1;
					for( j = 0; j < rFills[i].rPoints[i32Vert].rEdges.size(); j++ ) {
						int32	i32EdgeIdx = rFills[i].rPoints[i32Vert].rEdges[j];
						if( !rFills[i].rEdges[i32EdgeIdx].bVisited ) {
							i32NewEdge = i32EdgeIdx;
							break;
						}
					}

					if( i32NewEdge == -1 )
						break;	// no where to go.

					i32Edge = i32NewEdge;

					// Mark the edge visited
					rFills[i].rEdges[i32Edge].bVisited = true;

					if( rFills[i].rEdges[i32Edge].i32Idx[0] == i32Vert ) {
						// Move from start to end.

						// Add end point.
						rPath.rPoints.push_back( rFills[i].rEdges[i32Edge].i32Idx[1] );

						// Select next vertex.
						i32Vert = rFills[i].rEdges[i32Edge].i32Idx[1];
					}
					else if( rFills[i].rEdges[i32Edge].i32Idx[1] == i32Vert ) {
						// Move from end to start.

						// Add start point.
						rPath.rPoints.push_back( rFills[i].rEdges[i32Edge].i32Idx[0] );

						// Select next vertex.
						i32Vert = rFills[i].rEdges[i32Edge].i32Idx[0];
					}
					else {
						// Something really weird happen.
//						TRACE( "polygonitsator fucked up!\n" );
						break;
					}

				}

				// Add the converted path.
				rFills[i].rPaths.push_back( rPath );

				// Clear the temp path for next run.
				rPath.rPoints.clear();
			}
		}


		//
		// Tesselate polygons
		//

		for( i = 0; i < rFills.size(); i++ ) {

			int32	i32PtCount = 0;


			SWFRenderDispListC*	pList = new SWFRenderDispListC;

			if( rFills[i].ui32Fill ) {

				ColorC	rCol( 1, 1, 1 );

				g_bIsGradientFill = false;
				g_bIsBitmapFill = false;

				if( !bIsGlyph ) {
					if( rFills[i].ui32Fill <= get_fill_count() ) {
						SWFFillStyleC*	pFill = get_fill( rFills[i].ui32Fill - 1 );

						if( pFill->get_type() == FILLTYPE_SOLID ) {
							rCol = pFill->get_color();
						}
						else if( pFill->get_type() == FILLTYPE_BITMAP ) {
							rCol = ColorC( 1, 1, 1, 1 );

							g_bIsBitmapFill = true;
							g_rFillMatrix = (pFill->get_matrix() * rTM).invert();

							SWFBitmapC*	pBitmap = (SWFBitmapC*)pDict->get_char_by_id( pFill->get_bitmap_id() );
							if( pBitmap ) {
								g_f32TexWidth = pBitmap->get_width();
								g_f32TexHeight = pBitmap->get_height();
								pList->set_texture( pBitmap->get_texture_id() );
							}
						}
						else if( pFill->get_type() == FILLTYPE_GRADIENT ) {
							rCol = ColorC( 1, 1, 1, 1 );

							g_bIsGradientFill = true;
							g_rFillMatrix = (pFill->get_matrix() * rTM).invert();

							SWFGradientC*	pGrad = pFill->get_gradient();
							pGrad->build();
						
							pList->set_texture( pGrad->get_texture_id() );
						}
					}
				}
				pList->set_color( rCol );

				uint32	ui32ListBase = glGenLists( 1 );
				pList->set_displist( ui32ListBase );

//				char	szPlaa[256];
//				_snprintf( szPlaa, 255, "list: %d\n", ui32ListBase );
//				OutputDebugString( szPlaa );

				glNewList( ui32ListBase, GL_COMPILE );

				gluTessBeginPolygon( tobj, NULL );

				for( j = 0; j < rFills[i].rPaths.size(); j++ ) {

					if( rFills[i].rPaths[j].rPoints.size() > 2 ) {

						gluTessBeginContour( tobj );

						for( k = 0; k < rFills[i].rPaths[j].rPoints.size(); k++ ) {
							GLdouble *pVert = add_garbage();

							int32	i32PtIdx = rFills[i].rPaths[j].rPoints[k];
							Vector2C	rPos = rFills[i].rPoints[i32PtIdx].rPos;
							pVert[0] = rPos[0];
							pVert[1] = rPos[1];
							pVert[2] = 0;
							gluTessVertex( tobj, pVert, pVert );
						}
						gluTessEndContour( tobj );
					}
				}
				gluTessEndPolygon( tobj );

				glEndList();
			}

			//
			// Draw outlines
			//

			if( !bIsGlyph ) {

				for( j = 0; j < rFills[i].rEdges.size(); j++ ) {

					if( !rFills[i].rEdges[j].i32LineStyle )
						continue;

					SWFRenderEdgeS	rRendEdge;

					rRendEdge.rVec[0] = rFills[i].rEdges[j].rPt[0];
					rRendEdge.rVec[1] = rFills[i].rEdges[j].rPt[1];
					rRendEdge.ui32LineStyle = rFills[i].rEdges[j].i32LineStyle;

					pList->add_edge( &rRendEdge );
				}
			}

			pRenderShape->add_displist( pList );
		}

		// Clean up
		delete_garbage();
		gluDeleteTess( tobj );
	}
}

void
SWFShapeRecordC::unprepare()
{
	delete m_pRenderShape;
	m_pRenderShape = 0;
}


//
// Shape character
//

SWFShapeC::SWFShapeC()
{
	// empty
}

SWFShapeC::~SWFShapeC()
{
	// empty
}

uint32
SWFShapeC::get_type() const
{
	return SWF_CHAR_SHAPE;
}

SWFShapeRecordC*
SWFShapeC::get_shape()
{
	return &m_rShape;
}

