egl/sfopenvg/riPath.cpp
author Jose Thachil<jose.thachil@cell-telecom.com>
Wed, 12 May 2010 11:20:41 +0100
branchEGL_MERGE
changeset 57 2bf8a359aa2f
child 88 a5a3a8cb368e
permissions -rw-r--r--
Create EGL 1.4 implementation by merging eglrefimpl and EGL from sfopenvg Subject to some fixes in uiresources and uiaccelerator, this version removes the EGL 1 panics. A quick attempt to use the compositor renderstage wasn't successful, but not apparently due to EGL issues. There are a lot of RDebug::Printf statements still in this code, which will be removed in the next commit.

/*------------------------------------------------------------------------
 *
 * OpenVG 1.1 Reference Implementation
 * -----------------------------------
 *
 * Copyright (c) 2007 The Khronos Group Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and /or associated documentation files
 * (the "Materials "), to deal in the Materials without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Materials,
 * and to permit persons to whom the Materials are furnished to do so,
 * subject to the following conditions: 
 *
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Materials. 
 *
 * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR
 * THE USE OR OTHER DEALINGS IN THE MATERIALS.
 *
 *//**
 * \file
 * \brief	Implementation of Path functions.
 * \note	
 *//*-------------------------------------------------------------------*/

#include "riPath.h"

using namespace OpenVGRI;
//==============================================================================================


//==============================================================================================

namespace OpenVGRI
{

RIfloat inputFloat(VGfloat f);	//defined in riApi.cpp

/*-------------------------------------------------------------------*//*!
* \brief	Form a reliable normalized average of the two unit input vectors.
*           The average always lies to the given direction from the first
*			vector.
* \param	u0, u1 Unit input vectors.
* \param	cw True if the average should be clockwise from u0, false if
*              counterclockwise.
* \return	Average of the two input vectors.
* \note		
*//*-------------------------------------------------------------------*/

static const Vector2 unitAverage(const Vector2& u0, const Vector2& u1, bool cw)
{
	Vector2 u = 0.5f * (u0 + u1);
	Vector2 n0 = perpendicularCCW(u0);

	if( dot(u, u) > 0.25f )
	{	//the average is long enough and thus reliable
		if( dot(n0, u1) < 0.0f )
			u = -u;	//choose the larger angle
	}
	else
	{	// the average is too short, use the average of the normals to the vectors instead
		Vector2 n1 = perpendicularCW(u1);
		u = 0.5f * (n0 + n1);
	}
	if( cw )
		u = -u;

	return normalize(u);
}

/*-------------------------------------------------------------------*//*!
* \brief	Form a reliable normalized average of the two unit input vectors.
*			The average lies on the side where the angle between the input
*			vectors is less than 180 degrees.
* \param	u0, u1 Unit input vectors.
* \return	Average of the two input vectors.
* \note		
*//*-------------------------------------------------------------------*/

static const Vector2 unitAverage(const Vector2& u0, const Vector2& u1)
{
	Vector2 u = 0.5f * (u0 + u1);

	if( dot(u, u) < 0.25f )
	{	// the average is unreliable, use the average of the normals to the vectors instead
		Vector2 n0 = perpendicularCCW(u0);
		Vector2 n1 = perpendicularCW(u1);
		u = 0.5f * (n0 + n1);
		if( dot(n1, u0) < 0.0f )
			u = -u;
	}

	return normalize(u);
}

/*-------------------------------------------------------------------*//*!
* \brief	Interpolate the given unit tangent vectors to the given
*			direction on a unit circle.
* \param	
* \return	
* \note		
*//*-------------------------------------------------------------------*/

static const Vector2 circularLerp(const Vector2& t0, const Vector2& t1, RIfloat ratio, bool cw)
{
	Vector2 u0 = t0, u1 = t1;
	RIfloat l0 = 0.0f, l1 = 1.0f;
	for(int i=0;i<18;i++)
	{
		Vector2 n = unitAverage(u0, u1, cw);
		RIfloat l = 0.5f * (l0 + l1);
		if( ratio < l )
		{
			u1 = n;
			l1 = l;
		}
		else
		{
			u0 = n;
			l0 = l;
		}
	}
	return u0;
}

/*-------------------------------------------------------------------*//*!
* \brief	Interpolate the given unit tangent vectors on a unit circle.
*			Smaller angle between the vectors is used.
* \param	
* \return	
* \note		
*//*-------------------------------------------------------------------*/

static const Vector2 circularLerp(const Vector2& t0, const Vector2& t1, RIfloat ratio)
{
	Vector2 u0 = t0, u1 = t1;
	RIfloat l0 = 0.0f, l1 = 1.0f;
	for(int i=0;i<18;i++)
	{
		Vector2 n = unitAverage(u0, u1);
		RIfloat l = 0.5f * (l0 + l1);
		if( ratio < l )
		{
			u1 = n;
			l1 = l;
		}
		else
		{
			u0 = n;
			l0 = l;
		}
	}
	return u0;
}

/*-------------------------------------------------------------------*//*!
* \brief	Path constructor.
* \param	
* \return	
* \note		
*//*-------------------------------------------------------------------*/

Path::Path(VGint format, VGPathDatatype datatype, RIfloat scale, RIfloat bias, int segmentCapacityHint, int coordCapacityHint, VGbitfield caps) :
	m_format(format),
	m_datatype(datatype),
	m_scale(scale),
	m_bias(bias),
	m_capabilities(caps),
	m_referenceCount(0),
	m_segments(),
	m_data(),
	m_vertices(),
	m_segmentToVertex(),
	m_userMinx(0.0f),
	m_userMiny(0.0f),
	m_userMaxx(0.0f),
	m_userMaxy(0.0f)
{
	RI_ASSERT(format == VG_PATH_FORMAT_STANDARD);
	RI_ASSERT(datatype >= VG_PATH_DATATYPE_S_8 && datatype <= VG_PATH_DATATYPE_F);
	if(segmentCapacityHint > 0)
		m_segments.reserve(RI_INT_MIN(segmentCapacityHint, 65536));
	if(coordCapacityHint > 0)
		m_data.reserve(RI_INT_MIN(coordCapacityHint, 65536) * getBytesPerCoordinate(datatype));
}

/*-------------------------------------------------------------------*//*!
* \brief	Path destructor.
* \param	
* \return	
* \note		
*//*-------------------------------------------------------------------*/

Path::~Path()
{
	RI_ASSERT(m_referenceCount == 0);
}

/*-------------------------------------------------------------------*//*!
* \brief	Reads a coordinate and applies scale and bias.
* \param	
* \return	
*//*-------------------------------------------------------------------*/

RIfloat Path::getCoordinate(int i) const
{
	RI_ASSERT(i >= 0 && i < m_data.size() / getBytesPerCoordinate(m_datatype));
	RI_ASSERT(m_scale != 0.0f);

	const RIuint8* ptr = &m_data[0];
	switch(m_datatype)
	{
	case VG_PATH_DATATYPE_S_8:
		return (RIfloat)(((const RIint8*)ptr)[i]) * m_scale + m_bias;

	case VG_PATH_DATATYPE_S_16:
		return (RIfloat)(((const RIint16*)ptr)[i]) * m_scale + m_bias;

	case VG_PATH_DATATYPE_S_32:
		return (RIfloat)(((const RIint32*)ptr)[i]) * m_scale + m_bias;

	default:
		RI_ASSERT(m_datatype == VG_PATH_DATATYPE_F);
		return (RIfloat)(((const RIfloat32*)ptr)[i]) * m_scale + m_bias;
	}
}

/*-------------------------------------------------------------------*//*!
* \brief	Writes a coordinate, subtracting bias and dividing out scale.
* \param	
* \return	
* \note		If the coordinates do not fit into path datatype range, they
*			will overflow silently.
*//*-------------------------------------------------------------------*/

void Path::setCoordinate(Array<RIuint8>& data, VGPathDatatype datatype, RIfloat scale, RIfloat bias, int i, RIfloat c)
{
	RI_ASSERT(i >= 0 && i < data.size()/getBytesPerCoordinate(datatype));
	RI_ASSERT(scale != 0.0f);

	c -= bias;
	c /= scale;

	RIuint8* ptr = &data[0];
	switch(datatype)
	{
	case VG_PATH_DATATYPE_S_8:
		((RIint8*)ptr)[i] = (RIint8)floor(c + 0.5f);	//add 0.5 for correct rounding
		break;

	case VG_PATH_DATATYPE_S_16:
		((RIint16*)ptr)[i] = (RIint16)floor(c + 0.5f);	//add 0.5 for correct rounding
		break;

	case VG_PATH_DATATYPE_S_32:
		((RIint32*)ptr)[i] = (RIint32)floor(c + 0.5f);	//add 0.5 for correct rounding
		break;

	default:
		RI_ASSERT(datatype == VG_PATH_DATATYPE_F);
		((RIfloat32*)ptr)[i] = (RIfloat32)c;
		break;
	}
}

/*-------------------------------------------------------------------*//*!
* \brief	Given a datatype, returns the number of bytes per coordinate.
* \param	
* \return	
* \note		
*//*-------------------------------------------------------------------*/

int Path::getBytesPerCoordinate(VGPathDatatype datatype)
{
	if(datatype == VG_PATH_DATATYPE_S_8)
		return 1;
	if(datatype == VG_PATH_DATATYPE_S_16)
		return 2;
	RI_ASSERT(datatype == VG_PATH_DATATYPE_S_32 || datatype == VG_PATH_DATATYPE_F);
	return 4;
}

/*-------------------------------------------------------------------*//*!
* \brief	Given a path segment type, returns the number of coordinates
*			it uses.
* \param	
* \return	
* \note		
*//*-------------------------------------------------------------------*/

int Path::segmentToNumCoordinates(VGPathSegment segment)
{
	RI_ASSERT(((int)segment >> 1) >= 0 && ((int)segment >> 1) <= 12);
	static const int coords[13] = {0,2,2,1,1,4,6,2,4,5,5,5,5};
	return coords[(int)segment >> 1];
}

/*-------------------------------------------------------------------*//*!
* \brief	Computes the number of coordinates a segment sequence uses.
* \param	
* \return	
* \note		
*//*-------------------------------------------------------------------*/

int Path::countNumCoordinates(const RIuint8* segments, int numSegments)
{
	RI_ASSERT(segments);
	RI_ASSERT(numSegments >= 0);

	int coordinates = 0;
	for(int i=0;i<numSegments;i++)
		coordinates += segmentToNumCoordinates(getPathSegment(segments[i]));
	return coordinates;
}

/*-------------------------------------------------------------------*//*!
* \brief	Clears path segments and data, and resets capabilities.
* \param	
* \return	
* \note		
*//*-------------------------------------------------------------------*/

void Path::clear(VGbitfield capabilities)
{
	m_segments.clear();
	m_data.clear();
	m_capabilities = capabilities;
}

/*-------------------------------------------------------------------*//*!
* \brief	Appends user segments and data.
* \param	
* \return	
* \note		if runs out of memory, throws bad_alloc and leaves the path as it was
*//*-------------------------------------------------------------------*/

void Path::appendData(const RIuint8* segments, int numSegments, const RIuint8* data)
{
	RI_ASSERT(numSegments > 0);
	RI_ASSERT(segments && data);
	RI_ASSERT(m_referenceCount > 0);

	//allocate new arrays
	int oldSegmentsSize = m_segments.size();
	int newSegmentsSize = oldSegmentsSize + numSegments;
	Array<RIuint8> newSegments;
	newSegments.resize(newSegmentsSize);	//throws bad_alloc

	int newCoords = countNumCoordinates(segments, numSegments);
	int bytesPerCoordinate = getBytesPerCoordinate(m_datatype);
	int newDataSize = m_data.size() + newCoords * bytesPerCoordinate;
	Array<RIuint8> newData;
	newData.resize(newDataSize);	//throws bad_alloc
	//if we get here, the memory allocations have succeeded

	//copy old segments and append new ones
	if(m_segments.size())
		memcpy(&newSegments[0], &m_segments[0], m_segments.size());
	memcpy(&newSegments[0] + m_segments.size(), segments, numSegments);

	//copy old data and append new ones
	if(newData.size())
	{
		if(m_data.size())
			memcpy(&newData[0], &m_data[0], m_data.size());
		if(m_datatype == VG_PATH_DATATYPE_F)
		{
			RIfloat32* d = (RIfloat32*)(&newData[0] + m_data.size());
			const RIfloat32* s = (const RIfloat32*)data;
			for(int i=0;i<newCoords;i++)
				*d++ = (RIfloat32)inputFloat(*s++);
		}
		else
		{
			memcpy(&newData[0] + m_data.size(), data, newCoords * bytesPerCoordinate);
		}
	}

	RI_ASSERT(newData.size() == countNumCoordinates(&newSegments[0],newSegments.size()) * getBytesPerCoordinate(m_datatype));

	//replace old arrays
	m_segments.swap(newSegments);
	m_data.swap(newData);

	int c = 0;
	for(int i=0;i<m_segments.size();i++)
	{
		VGPathSegment segment = getPathSegment(m_segments[i]);
		int coords = segmentToNumCoordinates(segment);
		c += coords;
	}
}

/*-------------------------------------------------------------------*//*!
* \brief	Appends a path.
* \param	
* \return	
* \note		if runs out of memory, throws bad_alloc and leaves the path as it was
*//*-------------------------------------------------------------------*/

void Path::append(const Path* srcPath)
{
	RI_ASSERT(srcPath);
	RI_ASSERT(m_referenceCount > 0 && srcPath->m_referenceCount > 0);

	if(srcPath->m_segments.size())
	{
		//allocate new arrays
		int newSegmentsSize = m_segments.size() + srcPath->m_segments.size();
		Array<RIuint8> newSegments;
		newSegments.resize(newSegmentsSize);	//throws bad_alloc

		int newDataSize = m_data.size() + srcPath->getNumCoordinates() * getBytesPerCoordinate(m_datatype);
		Array<RIuint8> newData;
		newData.resize(newDataSize);	//throws bad_alloc
		//if we get here, the memory allocations have succeeded

		//copy old segments and append new ones
		if(m_segments.size())
			memcpy(&newSegments[0], &m_segments[0], m_segments.size());
		if(srcPath->m_segments.size())
			memcpy(&newSegments[0] + m_segments.size(), &srcPath->m_segments[0], srcPath->m_segments.size());

		//copy old data and append new ones
		if(m_data.size())
			memcpy(&newData[0], &m_data[0], m_data.size());
		for(int i=0;i<srcPath->getNumCoordinates();i++)
			setCoordinate(newData, m_datatype, m_scale, m_bias, i + getNumCoordinates(), srcPath->getCoordinate(i));

		RI_ASSERT(newData.size() == countNumCoordinates(&newSegments[0],newSegments.size()) * getBytesPerCoordinate(m_datatype));

		//replace old arrays
		m_segments.swap(newSegments);
		m_data.swap(newData);
	}
}

/*-------------------------------------------------------------------*//*!
* \brief	Modifies existing coordinate data.
* \param	
* \return	
* \note		
*//*-------------------------------------------------------------------*/

void Path::modifyCoords(int startIndex, int numSegments, const RIuint8* data)
{
	RI_ASSERT(numSegments > 0);
	RI_ASSERT(startIndex >= 0 && startIndex + numSegments <= m_segments.size());
	RI_ASSERT(data);
	RI_ASSERT(m_referenceCount > 0);

	int startCoord = countNumCoordinates(&m_segments[0], startIndex);
	int numCoords = countNumCoordinates(&m_segments[startIndex], numSegments);
	if(!numCoords)
		return;
	int bytesPerCoordinate = getBytesPerCoordinate(m_datatype);
	RIuint8* dst = &m_data[startCoord * bytesPerCoordinate];
	if(m_datatype == VG_PATH_DATATYPE_F)
	{
		RIfloat32* d = (RIfloat32*)dst;
		const RIfloat32* s = (const RIfloat32*)data;
		for(int i=0;i<numCoords;i++)
			*d++ = (RIfloat32)inputFloat(*s++);
	}
	else
	{
		memcpy(dst, data, numCoords*bytesPerCoordinate);
	}
}

/*-------------------------------------------------------------------*//*!
* \brief	Appends a transformed copy of the source path.
* \param	
* \return	
* \note		if runs out of memory, throws bad_alloc and leaves the path as it was
*//*-------------------------------------------------------------------*/

void Path::transform(const Path* srcPath, const Matrix3x3& matrix)
{
	RI_ASSERT(srcPath);
	RI_ASSERT(m_referenceCount > 0 && srcPath->m_referenceCount > 0);
	RI_ASSERT(matrix.isAffine());

	if(!srcPath->m_segments.size())
		return;

	//count the number of resulting coordinates
	int numSrcCoords = 0;
	int numDstCoords = 0;
	for(int i=0;i<srcPath->m_segments.size();i++)
	{
		VGPathSegment segment = getPathSegment(srcPath->m_segments[i]);
		int coords = segmentToNumCoordinates(segment);
		numSrcCoords += coords;
		if(segment == VG_HLINE_TO || segment == VG_VLINE_TO)
			coords = 2;	//convert hline and vline to lines
		numDstCoords += coords;
	}

	//allocate new arrays
	Array<RIuint8> newSegments;
	newSegments.resize(m_segments.size() + srcPath->m_segments.size());	//throws bad_alloc
	Array<RIuint8> newData;
	newData.resize(m_data.size() + numDstCoords * getBytesPerCoordinate(m_datatype));	//throws bad_alloc
	//if we get here, the memory allocations have succeeded

	//copy old segments
	if(m_segments.size())
		memcpy(&newSegments[0], &m_segments[0], m_segments.size());

	//copy old data
	if(m_data.size())
		memcpy(&newData[0], &m_data[0], m_data.size());

	int srcCoord = 0;
	int dstCoord = getNumCoordinates();
	Vector2 s(0,0);		//the beginning of the current subpath
	Vector2 o(0,0);		//the last point of the previous segment
	for(int i=0;i<srcPath->m_segments.size();i++)
	{
		VGPathSegment segment = getPathSegment(srcPath->m_segments[i]);
		VGPathAbsRel absRel = getPathAbsRel(srcPath->m_segments[i]);
		int coords = segmentToNumCoordinates(segment);

		switch(segment)
		{
		case VG_CLOSE_PATH:
		{
			RI_ASSERT(coords == 0);
			o = s;
			break;
		}

		case VG_MOVE_TO:
		{
			RI_ASSERT(coords == 2);
			Vector2 c(srcPath->getCoordinate(srcCoord+0), srcPath->getCoordinate(srcCoord+1));
            Vector2 tc;

			if (absRel == VG_ABSOLUTE)
                tc = affineTransform(matrix, c);
            else
            {
                tc = affineTangentTransform(matrix, c);
				c += o;
            }
			
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc.x);
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc.y);
			s = c;
			o = c;
			break;
		}

		case VG_LINE_TO:
		{
			RI_ASSERT(coords == 2);
			Vector2 c(srcPath->getCoordinate(srcCoord+0), srcPath->getCoordinate(srcCoord+1));
            Vector2 tc;

			if (absRel == VG_ABSOLUTE)
                tc = affineTransform(matrix, c);
            else
            {
                tc = affineTangentTransform(matrix, c);
				c += o;
            }

			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc.x);
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc.y);
			o = c;
			break;
		}

		case VG_HLINE_TO:
		{
			RI_ASSERT(coords == 1);
			Vector2 c(srcPath->getCoordinate(srcCoord+0), 0);
            Vector2 tc;

			if (absRel == VG_ABSOLUTE)
            {
                c.y = o.y;
                tc = affineTransform(matrix, c);
            }
            else
            {
                tc = affineTangentTransform(matrix, c);
				c += o;
            }

			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc.x);
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc.y);
			o = c;
			segment = VG_LINE_TO;
			break;
		}

		case VG_VLINE_TO:
		{
			RI_ASSERT(coords == 1);
			Vector2 c(0, srcPath->getCoordinate(srcCoord+0));
            Vector2 tc;

			if (absRel == VG_ABSOLUTE)
            {
                c.x = o.x;
                tc = affineTransform(matrix, c);
            }
            else
            {
                tc = affineTangentTransform(matrix, c);
				c += o;
            }

			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc.x);
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc.y);
			o = c;
			segment = VG_LINE_TO;
			break;
		}

		case VG_QUAD_TO:
		{
			RI_ASSERT(coords == 4);
			Vector2 c0(srcPath->getCoordinate(srcCoord+0), srcPath->getCoordinate(srcCoord+1));
			Vector2 c1(srcPath->getCoordinate(srcCoord+2), srcPath->getCoordinate(srcCoord+3));
            Vector2 tc0, tc1;

			if (absRel == VG_ABSOLUTE)
            {
                tc0 = affineTransform(matrix, c0);
                tc1 = affineTransform(matrix, c1);
            }
            else
            {
                tc0 = affineTangentTransform(matrix, c0);
                tc1 = affineTangentTransform(matrix, c1);
                c1 += o;
            }

			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc0.x);
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc0.y);
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc1.x);
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc1.y);
			o = c1;
			break;
		}

		case VG_CUBIC_TO:
		{
			RI_ASSERT(coords == 6);
			Vector2 c0(srcPath->getCoordinate(srcCoord+0), srcPath->getCoordinate(srcCoord+1));
			Vector2 c1(srcPath->getCoordinate(srcCoord+2), srcPath->getCoordinate(srcCoord+3));
			Vector2 c2(srcPath->getCoordinate(srcCoord+4), srcPath->getCoordinate(srcCoord+5));
            Vector2 tc0, tc1, tc2;

			if (absRel == VG_ABSOLUTE)
            {
                tc0 = affineTransform(matrix, c0);
                tc1 = affineTransform(matrix, c1);
                tc2 = affineTransform(matrix, c2);
            }
            else
            {
                tc0 = affineTangentTransform(matrix, c0);
                tc1 = affineTangentTransform(matrix, c1);
                tc2 = affineTangentTransform(matrix, c2);
                c2 += o;
            }

			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc0.x);
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc0.y);
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc1.x);
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc1.y);
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc2.x);
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc2.y);
			o = c2;
			break;
		}

		case VG_SQUAD_TO:
		{
			RI_ASSERT(coords == 2);
			Vector2 c1(srcPath->getCoordinate(srcCoord+0), srcPath->getCoordinate(srcCoord+1));
            Vector2 tc1;

			if (absRel == VG_ABSOLUTE)
                tc1 = affineTransform(matrix, c1);
            else
            {
                tc1 = affineTangentTransform(matrix, c1);
                c1 += o;
            }

			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc1.x);
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc1.y);
			o = c1;
			break;
		}

		case VG_SCUBIC_TO:
		{
			RI_ASSERT(coords == 4);
			Vector2 c1(srcPath->getCoordinate(srcCoord+0), srcPath->getCoordinate(srcCoord+1));
			Vector2 c2(srcPath->getCoordinate(srcCoord+2), srcPath->getCoordinate(srcCoord+3));
            Vector2 tc1, tc2;

			if (absRel == VG_ABSOLUTE)
            {
                tc1 = affineTransform(matrix, c1);
                tc2 = affineTransform(matrix, c2);
            }
            else
            {
                tc1 = affineTangentTransform(matrix, c1);
                tc2 = affineTangentTransform(matrix, c2);
                c2 += o;
            }

			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc1.x);
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc1.y);
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc2.x);
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc2.y);
			o = c2;
			break;
		}

		default:
		{
			RI_ASSERT(segment == VG_SCCWARC_TO || segment == VG_SCWARC_TO ||
					  segment == VG_LCCWARC_TO || segment == VG_LCWARC_TO);
			RI_ASSERT(coords == 5);
			RIfloat rh = srcPath->getCoordinate(srcCoord+0);
			RIfloat rv = srcPath->getCoordinate(srcCoord+1);
			RIfloat rot = srcPath->getCoordinate(srcCoord+2);
			Vector2 c(srcPath->getCoordinate(srcCoord+3), srcPath->getCoordinate(srcCoord+4));

			rot = RI_DEG_TO_RAD(rot);
			Matrix3x3 u((RIfloat)cos(rot)*rh, -(RIfloat)sin(rot)*rv,  0,
						(RIfloat)sin(rot)*rh,  (RIfloat)cos(rot)*rv,  0,
						0,                   0,                   1);
			u = matrix * u;
			u[2].set(0,0,1);		//force affinity
			//u maps from the unit circle to transformed ellipse

			//compute new rh, rv and rot
			Vector2	p(u[0][0], u[1][0]);
			Vector2	q(u[1][1], -u[0][1]);
			bool swapped = false;
			if(dot(p,p) < dot(q,q))
			{
				RI_SWAP(p.x,q.x);
				RI_SWAP(p.y,q.y);
				swapped = true;
			}
			Vector2 h = (p+q) * 0.5f;
			Vector2 hp = (p-q) * 0.5f;
			RIfloat hlen = h.length();
			RIfloat hplen = hp.length();
			rh = hlen + hplen;
			rv = hlen - hplen;
			h = hplen * h + hlen * hp;
			hlen = dot(h,h);
			if(hlen == 0.0f)
				rot = 0.0f;
			else
			{
				h.normalize();
				rot = (RIfloat)acos(h.x);
				if(h.y < 0.0f)
					rot = 2.0f*PI - rot;
			}
			if(swapped)
				rot += PI*0.5f;

            Vector2 tc;
			if (absRel == VG_ABSOLUTE)
                tc = affineTransform(matrix, c);
            else
            {
                tc = affineTangentTransform(matrix, c);
                c += o;
            }

			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, rh);
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, rv);
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, RI_RAD_TO_DEG(rot));
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc.x);
			setCoordinate(newData, m_datatype, m_scale, m_bias, dstCoord++, tc.y);
			o = c;

            //flip winding if the determinant is negative
            if (matrix.det() < 0)
            {
                switch (segment)
                {
                case VG_SCCWARC_TO: segment = VG_SCWARC_TO;     break;
                case VG_SCWARC_TO:  segment = VG_SCCWARC_TO;    break;
                case VG_LCCWARC_TO: segment = VG_LCWARC_TO;     break;
                case VG_LCWARC_TO:  segment = VG_LCCWARC_TO;    break;
                default:                                        break;
                }
            }
            break;
		}
		}

		newSegments[m_segments.size() + i] = (RIuint8)(segment | absRel);
		srcCoord += coords;
	}
	RI_ASSERT(srcCoord == numSrcCoords);
	RI_ASSERT(dstCoord == getNumCoordinates() + numDstCoords);

	RI_ASSERT(newData.size() == countNumCoordinates(&newSegments[0],newSegments.size()) * getBytesPerCoordinate(m_datatype));

	//replace old arrays
	m_segments.swap(newSegments);
	m_data.swap(newData);
}

/*-------------------------------------------------------------------*//*!
* \brief	Normalizes a path for interpolation.
* \param	
* \return	
* \note		
*//*-------------------------------------------------------------------*/

void Path::normalizeForInterpolation(const Path* srcPath)
{
	RI_ASSERT(srcPath);
	RI_ASSERT(srcPath != this);
	RI_ASSERT(srcPath->m_referenceCount > 0);

	//count the number of resulting coordinates
	int numSrcCoords = 0;
	int numDstCoords = 0;
	for(int i=0;i<srcPath->m_segments.size();i++)
	{
		VGPathSegment segment = getPathSegment(srcPath->m_segments[i]);
		int coords = segmentToNumCoordinates(segment);
		numSrcCoords += coords;
		switch(segment)
		{
		case VG_CLOSE_PATH:
		case VG_MOVE_TO:
		case VG_LINE_TO:
			break;

		case VG_HLINE_TO:
		case VG_VLINE_TO:
			coords = 2;
			break;

		case VG_QUAD_TO:
		case VG_CUBIC_TO:
		case VG_SQUAD_TO:
		case VG_SCUBIC_TO:
			coords = 6;
			break;

		default:
			RI_ASSERT(segment == VG_SCCWARC_TO || segment == VG_SCWARC_TO ||
					  segment == VG_LCCWARC_TO || segment == VG_LCWARC_TO);
			break;
		}
		numDstCoords += coords;
	}

	m_segments.resize(srcPath->m_segments.size());	//throws bad_alloc
	m_data.resize(numDstCoords * getBytesPerCoordinate(VG_PATH_DATATYPE_F));	//throws bad_alloc

	int srcCoord = 0;
	int dstCoord = 0;
	Vector2 s(0,0);		//the beginning of the current subpath
	Vector2 o(0,0);		//the last point of the previous segment

	// the last internal control point of the previous segment, if the
	//segment was a (regular or smooth) quadratic or cubic
	//Bezier, or else the last point of the previous segment
	Vector2 p(0,0);		
	for(int i=0;i<srcPath->m_segments.size();i++)
	{
		VGPathSegment segment = getPathSegment(srcPath->m_segments[i]);
		VGPathAbsRel absRel = getPathAbsRel(srcPath->m_segments[i]);
		int coords = segmentToNumCoordinates(segment);

		switch(segment)
		{
		case VG_CLOSE_PATH:
		{
			RI_ASSERT(coords == 0);
			p = s;
			o = s;
			break;
		}

		case VG_MOVE_TO:
		{
			RI_ASSERT(coords == 2);
			Vector2 c(srcPath->getCoordinate(srcCoord+0), srcPath->getCoordinate(srcCoord+1));
			if(absRel == VG_RELATIVE)
				c += o;
			setCoordinate(dstCoord++, c.x);
			setCoordinate(dstCoord++, c.y);
			s = c;
			p = c;
			o = c;
			break;
		}

		case VG_LINE_TO:
		{
			RI_ASSERT(coords == 2);
			Vector2 c(srcPath->getCoordinate(srcCoord+0), srcPath->getCoordinate(srcCoord+1));
			if(absRel == VG_RELATIVE)
				c += o;
			setCoordinate(dstCoord++, c.x);
			setCoordinate(dstCoord++, c.y);
			p = c;
			o = c;
			break;
		}

		case VG_HLINE_TO:
		{
			RI_ASSERT(coords == 1);
			Vector2 c(srcPath->getCoordinate(srcCoord+0), o.y);
			if(absRel == VG_RELATIVE)
				c.x += o.x;
			setCoordinate(dstCoord++, c.x);
			setCoordinate(dstCoord++, c.y);
			p = c;
			o = c;
			segment = VG_LINE_TO;
			break;
		}

		case VG_VLINE_TO:
		{
			RI_ASSERT(coords == 1);
			Vector2 c(o.x, srcPath->getCoordinate(srcCoord+0));
			if(absRel == VG_RELATIVE)
				c.y += o.y;
			setCoordinate(dstCoord++, c.x);
			setCoordinate(dstCoord++, c.y);
			p = c;
			o = c;
			segment = VG_LINE_TO;
			break;
		}

		case VG_QUAD_TO:
		{
			RI_ASSERT(coords == 4);
			Vector2 c0(srcPath->getCoordinate(srcCoord+0), srcPath->getCoordinate(srcCoord+1));
			Vector2 c1(srcPath->getCoordinate(srcCoord+2), srcPath->getCoordinate(srcCoord+3));
			if(absRel == VG_RELATIVE)
			{
				c0 += o;
				c1 += o;
			}
			Vector2 d0 = (1.0f/3.0f) * (o + 2.0f * c0);
			Vector2 d1 = (1.0f/3.0f) * (c1 + 2.0f * c0);
			setCoordinate(dstCoord++, d0.x);
			setCoordinate(dstCoord++, d0.y);
			setCoordinate(dstCoord++, d1.x);
			setCoordinate(dstCoord++, d1.y);
			setCoordinate(dstCoord++, c1.x);
			setCoordinate(dstCoord++, c1.y);
			p = c0;
			o = c1;
			segment = VG_CUBIC_TO;
			break;
		}

		case VG_CUBIC_TO:
		{
			RI_ASSERT(coords == 6);
			Vector2 c0(srcPath->getCoordinate(srcCoord+0), srcPath->getCoordinate(srcCoord+1));
			Vector2 c1(srcPath->getCoordinate(srcCoord+2), srcPath->getCoordinate(srcCoord+3));
			Vector2 c2(srcPath->getCoordinate(srcCoord+4), srcPath->getCoordinate(srcCoord+5));
			if(absRel == VG_RELATIVE)
			{
				c0 += o;
				c1 += o;
				c2 += o;
			}
			setCoordinate(dstCoord++, c0.x);
			setCoordinate(dstCoord++, c0.y);
			setCoordinate(dstCoord++, c1.x);
			setCoordinate(dstCoord++, c1.y);
			setCoordinate(dstCoord++, c2.x);
			setCoordinate(dstCoord++, c2.y);
			p = c1;
			o = c2;
			break;
		}

		case VG_SQUAD_TO:
		{
			RI_ASSERT(coords == 2);
			Vector2 c0 = 2.0f * o - p;
			Vector2 c1(srcPath->getCoordinate(srcCoord+0), srcPath->getCoordinate(srcCoord+1));
			if(absRel == VG_RELATIVE)
				c1 += o;
			Vector2 d0 = (1.0f/3.0f) * (o + 2.0f * c0);
			Vector2 d1 = (1.0f/3.0f) * (c1 + 2.0f * c0);
			setCoordinate(dstCoord++, d0.x);
			setCoordinate(dstCoord++, d0.y);
			setCoordinate(dstCoord++, d1.x);
			setCoordinate(dstCoord++, d1.y);
			setCoordinate(dstCoord++, c1.x);
			setCoordinate(dstCoord++, c1.y);
			p = c0;
			o = c1;
			segment = VG_CUBIC_TO;
			break;
		}

		case VG_SCUBIC_TO:
		{
			RI_ASSERT(coords == 4);
			Vector2 c0 = 2.0f * o - p;
			Vector2 c1(srcPath->getCoordinate(srcCoord+0), srcPath->getCoordinate(srcCoord+1));
			Vector2 c2(srcPath->getCoordinate(srcCoord+2), srcPath->getCoordinate(srcCoord+3));
			if(absRel == VG_RELATIVE)
			{
				c1 += o;
				c2 += o;
			}
			setCoordinate(dstCoord++, c0.x);
			setCoordinate(dstCoord++, c0.y);
			setCoordinate(dstCoord++, c1.x);
			setCoordinate(dstCoord++, c1.y);
			setCoordinate(dstCoord++, c2.x);
			setCoordinate(dstCoord++, c2.y);
			p = c1;
			o = c2;
			segment = VG_CUBIC_TO;
			break;
		}

		default:
		{
			RI_ASSERT(segment == VG_SCCWARC_TO || segment == VG_SCWARC_TO ||
					  segment == VG_LCCWARC_TO || segment == VG_LCWARC_TO);
			RI_ASSERT(coords == 5);
			RIfloat rh = srcPath->getCoordinate(srcCoord+0);
			RIfloat rv = srcPath->getCoordinate(srcCoord+1);
			RIfloat rot = srcPath->getCoordinate(srcCoord+2);
			Vector2 c(srcPath->getCoordinate(srcCoord+3), srcPath->getCoordinate(srcCoord+4));
			if(absRel == VG_RELATIVE)
				c += o;
			setCoordinate(dstCoord++, rh);
			setCoordinate(dstCoord++, rv);
			setCoordinate(dstCoord++, rot);
			setCoordinate(dstCoord++, c.x);
			setCoordinate(dstCoord++, c.y);
			p = c;
			o = c;
			break;
		}
		}

		m_segments[i] = (RIuint8)(segment | VG_ABSOLUTE);
		srcCoord += coords;
	}
	RI_ASSERT(srcCoord == numSrcCoords);
	RI_ASSERT(dstCoord == numDstCoords);
}

/*-------------------------------------------------------------------*//*!
* \brief	Appends a linearly interpolated copy of the two source paths.
* \param	
* \return	
* \note		if runs out of memory, throws bad_alloc and leaves the path as it was
*//*-------------------------------------------------------------------*/

bool Path::interpolate(const Path* startPath, const Path* endPath, RIfloat amount)
{
	RI_ASSERT(startPath && endPath);
	RI_ASSERT(m_referenceCount > 0 && startPath->m_referenceCount > 0 && endPath->m_referenceCount > 0);

	if(!startPath->m_segments.size() || startPath->m_segments.size() != endPath->m_segments.size())
		return false;	//start and end paths are incompatible or zero length

	Path start(VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F, 1.0f, 0.0f, 0, 0, 0);
	start.normalizeForInterpolation(startPath);	//throws bad_alloc

	Path end(VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F, 1.0f, 0.0f, 0, 0, 0);
	end.normalizeForInterpolation(endPath);	//throws bad_alloc

	//check that start and end paths are compatible
	if(start.m_data.size() != end.m_data.size() || start.m_segments.size() != end.m_segments.size())
		return false;	//start and end paths are incompatible

	//allocate new arrays
	Array<RIuint8> newSegments;
	newSegments.resize(m_segments.size() + start.m_segments.size());	//throws bad_alloc
	Array<RIuint8> newData;
	newData.resize(m_data.size() + start.m_data.size() * getBytesPerCoordinate(m_datatype) / getBytesPerCoordinate(start.m_datatype));	//throws bad_alloc
	//if we get here, the memory allocations have succeeded

	//copy old segments
	if(m_segments.size())
		memcpy(&newSegments[0], &m_segments[0], m_segments.size());

	//copy old data
	if(m_data.size())
		memcpy(&newData[0], &m_data[0], m_data.size());

	//copy segments
	for(int i=0;i<start.m_segments.size();i++)
	{
		VGPathSegment s = getPathSegment(start.m_segments[i]);
		VGPathSegment e = getPathSegment(end.m_segments[i]);

		if(s == VG_SCCWARC_TO || s == VG_SCWARC_TO || s == VG_LCCWARC_TO || s == VG_LCWARC_TO)
		{
			if(e != VG_SCCWARC_TO && e != VG_SCWARC_TO && e != VG_LCCWARC_TO && e != VG_LCWARC_TO)
				return false;	//start and end paths are incompatible
			if(amount < 0.5f)
				newSegments[m_segments.size() + i] = start.m_segments[i];
			else
				newSegments[m_segments.size() + i] = end.m_segments[i];
		}
		else
		{
			if(s != e)
				return false;	//start and end paths are incompatible
			newSegments[m_segments.size() + i] = start.m_segments[i];
		}
	}

	//interpolate data
	int oldNumCoords = getNumCoordinates();
	for(int i=0;i<start.getNumCoordinates();i++)
		setCoordinate(newData, m_datatype, m_scale, m_bias, oldNumCoords + i, start.getCoordinate(i) * (1.0f - amount) + end.getCoordinate(i) * amount);

	RI_ASSERT(newData.size() == countNumCoordinates(&newSegments[0],newSegments.size()) * getBytesPerCoordinate(m_datatype));

	//replace old arrays
	m_segments.swap(newSegments);
	m_data.swap(newData);

	return true;
}

/*-------------------------------------------------------------------*//*!
* \brief	Tessellates a path for filling and appends resulting edges
*			to a rasterizer.
* \param	
* \return	
* \note		if runs out of memory, throws bad_alloc and leaves the path as it was
*//*-------------------------------------------------------------------*/

void Path::fill(const Matrix3x3& pathToSurface, Rasterizer& rasterizer)
{
	RI_ASSERT(m_referenceCount > 0);
	RI_ASSERT(pathToSurface.isAffine());

	tessellate(pathToSurface, 0.0f);	//throws bad_alloc

	try
	{
		Vector2 p0(0,0), p1(0,0);
		for(int i=0;i<m_vertices.size();i++)
		{
			p1 = affineTransform(pathToSurface, m_vertices[i].userPosition);

			if(!(m_vertices[i].flags & START_SEGMENT))
			{	//in the middle of a segment
				rasterizer.addEdge(p0, p1);	//throws bad_alloc
			}

			p0 = p1;
		}
	}
	catch(std::bad_alloc)
	{
		rasterizer.clear();	//remove the unfinished path
		throw;
	}
}

/*-------------------------------------------------------------------*//*!
* \brief	Smoothly interpolates between two StrokeVertices. Positions
*			are interpolated linearly, while tangents are interpolated
*			on a unit circle. Stroking is implemented so that overlapping
*			geometry doesnt cancel itself when filled with nonzero rule.
*			The resulting polygons are closed.
* \param	
* \return	
* \note		
*//*-------------------------------------------------------------------*/

void Path::interpolateStroke(const Matrix3x3& pathToSurface, Rasterizer& rasterizer, const StrokeVertex& v0, const StrokeVertex& v1, RIfloat strokeWidth) const
{
	Vector2 pccw = affineTransform(pathToSurface, v0.ccw);
	Vector2 pcw = affineTransform(pathToSurface, v0.cw);
	Vector2 p = affineTransform(pathToSurface, v0.p);
	Vector2 endccw = affineTransform(pathToSurface, v1.ccw);
	Vector2 endcw = affineTransform(pathToSurface, v1.cw);
	Vector2 endp = affineTransform(pathToSurface, v1.p);

	const RIfloat tessellationAngle = 5.0f;

	RIfloat angle = RI_RAD_TO_DEG((RIfloat)acos(RI_CLAMP(dot(v0.t, v1.t), -1.0f, 1.0f))) / tessellationAngle;
	int samples = RI_INT_MAX((int)ceil(angle), 1);

	for(int j=0;j<samples-1;j++)
	{
		RIfloat t = (RIfloat)(j+1) / (RIfloat)samples;
		Vector2 position = v0.p * (1.0f - t) + v1.p * t;
		Vector2 tangent = circularLerp(v0.t, v1.t, t);
		Vector2 normal = normalize(perpendicularCCW(tangent)) * strokeWidth * 0.5f;

		Vector2 nccw = affineTransform(pathToSurface, position + normal);
		Vector2 ncw = affineTransform(pathToSurface, position - normal);
		Vector2 n = affineTransform(pathToSurface, position);

        rasterizer.clear();
		rasterizer.addEdge(p, pccw);	//throws bad_alloc
		rasterizer.addEdge(pccw, nccw);	//throws bad_alloc
		rasterizer.addEdge(nccw, n);	//throws bad_alloc
		rasterizer.addEdge(n, ncw);     //throws bad_alloc
		rasterizer.addEdge(ncw, pcw);	//throws bad_alloc
		rasterizer.addEdge(pcw, p);	    //throws bad_alloc
        rasterizer.fill();

		pccw = nccw;
		pcw = ncw;
        p = n;
	}

	//connect the last segment to the end coordinates
    rasterizer.clear();
	rasterizer.addEdge(p, pccw);	    //throws bad_alloc
	rasterizer.addEdge(pccw, endccw);	//throws bad_alloc
	rasterizer.addEdge(endccw, endp);	//throws bad_alloc
	rasterizer.addEdge(endp, endcw);	//throws bad_alloc
	rasterizer.addEdge(endcw, pcw);     //throws bad_alloc
	rasterizer.addEdge(pcw, p);         //throws bad_alloc
    rasterizer.fill();
}

/*-------------------------------------------------------------------*//*!
* \brief	Generate edges for stroke caps. Resulting polygons are closed.
* \param	
* \return	
* \note		
*//*-------------------------------------------------------------------*/

void Path::doCap(const Matrix3x3& pathToSurface, Rasterizer& rasterizer, const StrokeVertex& v, RIfloat strokeWidth, VGCapStyle capStyle) const
{
	Vector2 ccwt = affineTransform(pathToSurface, v.ccw);
	Vector2 cwt = affineTransform(pathToSurface, v.cw);
	Vector2 p = affineTransform(pathToSurface, v.p);

    rasterizer.clear();
	switch(capStyle)
	{
	case VG_CAP_BUTT:
		break;

	case VG_CAP_ROUND:
	{
        const RIfloat tessellationAngle = 5.0f;

		RIfloat angle = 180.0f / tessellationAngle;

		int samples = (int)ceil(angle);
		RIfloat step = 1.0f / samples;
		RIfloat t = step;
		Vector2 u0 = normalize(v.ccw - v.p);
		Vector2 u1 = normalize(v.cw - v.p);
		Vector2 prev = ccwt;
		rasterizer.addEdge(p, ccwt);	//throws bad_alloc
		for(int j=1;j<samples;j++)
		{
			Vector2 next = v.p + circularLerp(u0, u1, t, true) * strokeWidth * 0.5f;
			next = affineTransform(pathToSurface, next);

			rasterizer.addEdge(prev, next);	//throws bad_alloc
			prev = next;
			t += step;
		}
		rasterizer.addEdge(prev, cwt);	//throws bad_alloc
		rasterizer.addEdge(cwt, p);     //throws bad_alloc
		break;
	}

	default:
	{
		RI_ASSERT(capStyle == VG_CAP_SQUARE);
		Vector2 t = v.t;
		t.normalize();
		Vector2 ccws = affineTransform(pathToSurface, v.ccw + t * strokeWidth * 0.5f);
		Vector2 cws = affineTransform(pathToSurface, v.cw + t * strokeWidth * 0.5f);
		rasterizer.addEdge(p, ccwt);	//throws bad_alloc
		rasterizer.addEdge(ccwt, ccws);	//throws bad_alloc
		rasterizer.addEdge(ccws, cws);	//throws bad_alloc
		rasterizer.addEdge(cws, cwt);	//throws bad_alloc
		rasterizer.addEdge(cwt, p);     //throws bad_alloc
		break;
	}
	}
    rasterizer.fill();
}

/*-------------------------------------------------------------------*//*!
* \brief	Generate edges for stroke joins. Resulting polygons are closed.
* \param	
* \return	
* \note		
*//*-------------------------------------------------------------------*/

void Path::doJoin(const Matrix3x3& pathToSurface, Rasterizer& rasterizer, const StrokeVertex& v0, const StrokeVertex& v1, RIfloat strokeWidth, VGJoinStyle joinStyle, RIfloat miterLimit) const
{
	Vector2 ccw0t = affineTransform(pathToSurface, v0.ccw);
	Vector2 cw0t = affineTransform(pathToSurface, v0.cw);
	Vector2 m0t = affineTransform(pathToSurface, v0.p);
	Vector2 ccw1t = affineTransform(pathToSurface, v1.ccw);
	Vector2 cw1t = affineTransform(pathToSurface, v1.cw);
	Vector2 m1t = affineTransform(pathToSurface, v1.p);

	Vector2 tccw = v1.ccw - v0.ccw;
	Vector2 s, e, m, st, et;
	bool cw;

    rasterizer.clear();

	if( dot(tccw, v0.t) > 0.0f )
	{	//draw ccw miter (draw from point 0 to 1)
		s = ccw0t;
		e = ccw1t;
		st = v0.t;
		et = v1.t;
		m = v0.ccw;
		cw = false;
		rasterizer.addEdge(m0t, ccw0t);	//throws bad_alloc
		rasterizer.addEdge(ccw1t, m1t);	//throws bad_alloc
		rasterizer.addEdge(m1t, m0t);	//throws bad_alloc
	}
	else
	{	//draw cw miter (draw from point 1 to 0)
		s = cw1t;
		e = cw0t;
		st = v1.t;
		et = v0.t;
		m = v0.cw;
		cw = true;
		rasterizer.addEdge(cw0t, m0t);	//throws bad_alloc
		rasterizer.addEdge(m1t, cw1t);	//throws bad_alloc
		rasterizer.addEdge(m0t, m1t);	//throws bad_alloc
	}

	switch(joinStyle)
	{
	case VG_JOIN_MITER:
	{
		RIfloat theta = (RIfloat)acos(RI_CLAMP(dot(v0.t, -v1.t), -1.0f, 1.0f));
		RIfloat miterLengthPerStrokeWidth = 1.0f / (RIfloat)sin(theta*0.5f);
		if( miterLengthPerStrokeWidth < miterLimit )
		{	//miter
			RIfloat l = (RIfloat)cos(theta*0.5f) * miterLengthPerStrokeWidth * (strokeWidth * 0.5f);
			l = RI_MIN(l, RI_FLOAT_MAX);	//force finite
			Vector2 c = m + v0.t * l;
			c = affineTransform(pathToSurface, c);
			rasterizer.addEdge(s, c);	//throws bad_alloc
			rasterizer.addEdge(c, e);	//throws bad_alloc
		}
		else
		{	//bevel
			rasterizer.addEdge(s, e);	//throws bad_alloc
		}
		break;
	}

	case VG_JOIN_ROUND:
	{
		const RIfloat tessellationAngle = 5.0f;

		Vector2 prev = s;
		RIfloat angle = RI_RAD_TO_DEG((RIfloat)acos(RI_CLAMP(dot(st, et), -1.0f, 1.0f))) / tessellationAngle;
		int samples = (int)ceil(angle);
		if( samples )
		{
			RIfloat step = 1.0f / samples;
			RIfloat t = step;
			for(int j=1;j<samples;j++)
			{
				Vector2 position = v0.p * (1.0f - t) + v1.p * t;
				Vector2 tangent = circularLerp(st, et, t, true);

				Vector2 next = position + normalize(perpendicular(tangent, cw)) * strokeWidth * 0.5f;
				next = affineTransform(pathToSurface, next);

				rasterizer.addEdge(prev, next);	//throws bad_alloc
				prev = next;
				t += step;
			}
		}
		rasterizer.addEdge(prev, e);	//throws bad_alloc
		break;
	}

	default:
		RI_ASSERT(joinStyle == VG_JOIN_BEVEL);
		if(!cw)
			rasterizer.addEdge(ccw0t, ccw1t);	//throws bad_alloc
		else
			rasterizer.addEdge(cw1t, cw0t);		//throws bad_alloc
		break;
	}
    rasterizer.fill();
}

/*-------------------------------------------------------------------*//*!
* \brief	Tessellate a path, apply stroking, dashing, caps and joins, and
*			append resulting edges to a rasterizer.
* \param	
* \return	
* \note		if runs out of memory, throws bad_alloc and leaves the path as it was
*//*-------------------------------------------------------------------*/

void Path::stroke(const Matrix3x3& pathToSurface, Rasterizer& rasterizer, const Array<RIfloat>& dashPattern, RIfloat dashPhase, bool dashPhaseReset, RIfloat strokeWidth, VGCapStyle capStyle, VGJoinStyle joinStyle, RIfloat miterLimit)
{
	RI_ASSERT(pathToSurface.isAffine());
	RI_ASSERT(m_referenceCount > 0);
	RI_ASSERT(strokeWidth >= 0.0f);
	RI_ASSERT(miterLimit >= 1.0f);

	tessellate(pathToSurface, strokeWidth);	//throws bad_alloc

	if(!m_vertices.size())
		return;

	bool dashing = true;
	int dashPatternSize = dashPattern.size();
	if( dashPattern.size() & 1 )
		dashPatternSize--;	//odd number of dash pattern entries, discard the last one
	RIfloat dashPatternLength = 0.0f;
	for(int i=0;i<dashPatternSize;i++)
		dashPatternLength += RI_MAX(dashPattern[i], 0.0f);
	if(!dashPatternSize || dashPatternLength == 0.0f )
		dashing = false;
	dashPatternLength = RI_MIN(dashPatternLength, RI_FLOAT_MAX);

	//walk along the path
	//stop at the next event which is either:
	//-path vertex
	//-dash stop
	//for robustness, decisions based on geometry are done only once.
	//inDash keeps track whether the last point was in dash or not

	//loop vertex events
	try
	{
		RIfloat nextDash = 0.0f;
		int d = 0;
		bool inDash = true;
		StrokeVertex v0, v1, vs;
		for(int i=0;i<m_vertices.size();i++)
		{
			//read the next vertex
			Vertex& v = m_vertices[i];
			v1.p = v.userPosition;
			v1.t = v.userTangent;
			RI_ASSERT(!isZero(v1.t));	//don't allow zero tangents
			v1.ccw = v1.p + normalize(perpendicularCCW(v1.t)) * strokeWidth * 0.5f;
			v1.cw = v1.p + normalize(perpendicularCW(v1.t)) * strokeWidth * 0.5f;
			v1.pathLength = v.pathLength;
			v1.flags = v.flags;
			v1.inDash = dashing ? inDash : true;	//NOTE: for other than START_SEGMENT vertices inDash will be updated after dashing

			//process the vertex event
			if(v.flags & START_SEGMENT)
			{
				if(v.flags & START_SUBPATH)
				{
					if( dashing )
					{	//initialize dashing by finding which dash or gap the first point of the path lies in
						if(dashPhaseReset || i == 0)
						{
							d = 0;
							inDash = true;
							nextDash = v1.pathLength - RI_MOD(dashPhase, dashPatternLength);
							for(;;)
							{
								RIfloat prevDash = nextDash;
								nextDash = prevDash + RI_MAX(dashPattern[d], 0.0f);
								if(nextDash >= v1.pathLength)
									break;

								if( d & 1 )
									inDash = true;
								else
									inDash = false;
								d = (d+1) % dashPatternSize;
							}
							v1.inDash = inDash;
							//the first point of the path lies between prevDash and nextDash
							//d in the index of the next dash stop
							//inDash is true if the first point is in a dash
						}
					}
					vs = v1;	//save the subpath start point
				}
				else
				{
					if( v.flags & IMPLICIT_CLOSE_SUBPATH )
					{	//do caps for the start and end of the current subpath
						if( v0.inDash )
							doCap(pathToSurface, rasterizer, v0, strokeWidth, capStyle);	//end cap	//throws bad_alloc
						if( vs.inDash )
						{
							StrokeVertex vi = vs;
							vi.t = -vi.t;
							RI_SWAP(vi.ccw.x, vi.cw.x);
							RI_SWAP(vi.ccw.y, vi.cw.y);
							doCap(pathToSurface, rasterizer, vi, strokeWidth, capStyle);	//start cap	//throws bad_alloc
						}
					}
					else
					{	//join two segments
						RI_ASSERT(v0.inDash == v1.inDash);
						if( v0.inDash )
							doJoin(pathToSurface, rasterizer, v0, v1, strokeWidth, joinStyle, miterLimit);	//throws bad_alloc
					}
				}
			}
			else
			{	//in the middle of a segment
				if( !(v.flags & IMPLICIT_CLOSE_SUBPATH) )
				{	//normal segment, do stroking
					if( dashing )
					{
						StrokeVertex prevDashVertex = v0;	//dashing of the segment starts from the previous vertex

						if(nextDash + 10000.0f * dashPatternLength < v1.pathLength)
							throw std::bad_alloc();		//too many dashes, throw bad_alloc

						//loop dash events until the next vertex event
						//zero length dashes are handled as a special case since if they hit the vertex,
						//we want to include their starting point to this segment already in order to generate a join
						int numDashStops = 0;
						while(nextDash < v1.pathLength || (nextDash <= v1.pathLength && dashPattern[(d+1) % dashPatternSize] == 0.0f))
						{
							RIfloat edgeLength = v1.pathLength - v0.pathLength;
							RIfloat ratio = 0.0f;
							if(edgeLength > 0.0f)
								ratio = (nextDash - v0.pathLength) / edgeLength;
							StrokeVertex nextDashVertex;
							nextDashVertex.p = v0.p * (1.0f - ratio) + v1.p * ratio;
							nextDashVertex.t = circularLerp(v0.t, v1.t, ratio);
							nextDashVertex.ccw = nextDashVertex.p + normalize(perpendicularCCW(nextDashVertex.t)) * strokeWidth * 0.5f;
							nextDashVertex.cw = nextDashVertex.p + normalize(perpendicularCW(nextDashVertex.t)) * strokeWidth * 0.5f;

							if( inDash )
							{	//stroke from prevDashVertex -> nextDashVertex
								if( numDashStops )
								{	//prevDashVertex is not the start vertex of the segment, cap it (start vertex has already been joined or capped)
									StrokeVertex vi = prevDashVertex;
									vi.t = -vi.t;
									RI_SWAP(vi.ccw.x, vi.cw.x);
									RI_SWAP(vi.ccw.y, vi.cw.y);
									doCap(pathToSurface, rasterizer, vi, strokeWidth, capStyle);	//throws bad_alloc
								}
								interpolateStroke(pathToSurface, rasterizer, prevDashVertex, nextDashVertex, strokeWidth);	//throws bad_alloc
								doCap(pathToSurface, rasterizer, nextDashVertex, strokeWidth, capStyle);	//end cap	//throws bad_alloc
							}
							prevDashVertex = nextDashVertex;

							if( d & 1 )
							{	//dash starts
								RI_ASSERT(!inDash);
								inDash = true;
							}
							else
							{	//dash ends
								RI_ASSERT(inDash);
								inDash = false;
							}
							d = (d+1) % dashPatternSize;
							nextDash += RI_MAX(dashPattern[d], 0.0f);
							numDashStops++;
						}
						
						if( inDash )
						{	//stroke prevDashVertex -> v1
							if( numDashStops )
							{	//prevDashVertex is not the start vertex of the segment, cap it (start vertex has already been joined or capped)
								StrokeVertex vi = prevDashVertex;
								vi.t = -vi.t;
								RI_SWAP(vi.ccw.x, vi.cw.x);
								RI_SWAP(vi.ccw.y, vi.cw.y);
								doCap(pathToSurface, rasterizer, vi, strokeWidth, capStyle);	//throws bad_alloc
							}
							interpolateStroke(pathToSurface, rasterizer, prevDashVertex, v1, strokeWidth);	//throws bad_alloc
							//no cap, leave path open
						}

						v1.inDash = inDash;	//update inDash status of the segment end point
					}
					else	//no dashing, just interpolate segment end points
						interpolateStroke(pathToSurface, rasterizer, v0, v1, strokeWidth);	//throws bad_alloc
				}
			}

			if((v.flags & END_SEGMENT) && (v.flags & CLOSE_SUBPATH))
			{	//join start and end of the current subpath
				if( v1.inDash && vs.inDash )
					doJoin(pathToSurface, rasterizer, v1, vs, strokeWidth, joinStyle, miterLimit);	//throws bad_alloc
				else
				{	//both start and end are not in dash, cap them
					if( v1.inDash )
						doCap(pathToSurface, rasterizer, v1, strokeWidth, capStyle);	//end cap	//throws bad_alloc
					if( vs.inDash )
					{
						StrokeVertex vi = vs;
						vi.t = -vi.t;
						RI_SWAP(vi.ccw.x, vi.cw.x);
						RI_SWAP(vi.ccw.y, vi.cw.y);
						doCap(pathToSurface, rasterizer, vi, strokeWidth, capStyle);	//start cap	//throws bad_alloc
					}
				}
			}

			v0 = v1;
		}
	}
	catch(std::bad_alloc)
	{
		rasterizer.clear();	//remove the unfinished path
		throw;
	}
}

/*-------------------------------------------------------------------*//*!
* \brief	Tessellates a path, and returns a position and a tangent on the path
*			given a distance along the path.
* \param	
* \return	
* \note		if runs out of memory, throws bad_alloc and leaves the path as it was
*//*-------------------------------------------------------------------*/

void Path::getPointAlong(int startIndex, int numSegments, RIfloat distance, Vector2& p, Vector2& t)
{
	RI_ASSERT(m_referenceCount > 0);
	RI_ASSERT(startIndex >= 0 && startIndex + numSegments <= m_segments.size() && numSegments > 0);

	Matrix3x3 identity;
	identity.identity();
	tessellate(identity, 0.0f);	//throws bad_alloc

	RI_ASSERT(startIndex >= 0 && startIndex < m_segmentToVertex.size());
	RI_ASSERT(startIndex + numSegments >= 0 && startIndex + numSegments <= m_segmentToVertex.size());

    // ignore move segments at the start of the path
    while (numSegments && (m_segments[startIndex] & ~VG_RELATIVE) == VG_MOVE_TO)
    {
        startIndex++;
        numSegments--;
    }

    // ignore move segments at the end of the path
    while (numSegments && (m_segments[startIndex + numSegments - 1] & ~VG_RELATIVE) == VG_MOVE_TO)
        numSegments--;

    // empty path?
    if (!m_vertices.size() || !numSegments)
    {
		p.set(0,0);
		t.set(1,0);
		return;
    }

	int startVertex = m_segmentToVertex[startIndex].start;
	int endVertex = m_segmentToVertex[startIndex + numSegments - 1].end;

	if(startVertex == -1)
		startVertex = 0;

    // zero length?
    if (startVertex >= endVertex)
    {
		p = m_vertices[startVertex].userPosition;
		t.set(1,0);
		return;
    }

	RI_ASSERT(startVertex >= 0 && startVertex < m_vertices.size());
	RI_ASSERT(endVertex >= 0 && endVertex < m_vertices.size());

	distance += m_vertices[startVertex].pathLength;	//map distance to the range of the whole path

	if(distance <= m_vertices[startVertex].pathLength)
	{	//return the first point of the path
		p = m_vertices[startVertex].userPosition;
		t = m_vertices[startVertex].userTangent;
		return;
	}

	if(distance >= m_vertices[endVertex].pathLength)
	{	//return the last point of the path
		p = m_vertices[endVertex].userPosition;
		t = m_vertices[endVertex].userTangent;
		return;
	}

	//search for the segment containing the distance
	for(int s=startIndex;s<startIndex+numSegments;s++)
	{
		int start = m_segmentToVertex[s].start;
		int end = m_segmentToVertex[s].end;
		if(start < 0)
			start = 0;
		if(end < 0)
			end = 0;
		RI_ASSERT(start >= 0 && start < m_vertices.size());
		RI_ASSERT(end >= 0 && end < m_vertices.size());

		if(distance >= m_vertices[start].pathLength && distance < m_vertices[end].pathLength)
		{	//segment contains the queried distance
			for(int i=start;i<end;i++)
			{
				const Vertex& v0 = m_vertices[i];
				const Vertex& v1 = m_vertices[i+1];
				if(distance >= v0.pathLength && distance < v1.pathLength)
				{	//segment found, interpolate linearly between its end points
					RIfloat edgeLength = v1.pathLength - v0.pathLength;
					RI_ASSERT(edgeLength > 0.0f);
					RIfloat r = (distance - v0.pathLength) / edgeLength;
					p = (1.0f - r) * v0.userPosition + r * v1.userPosition;
					t = (1.0f - r) * v0.userTangent + r * v1.userTangent;
					return;
				}
			}
		}
	}

	RI_ASSERT(0);	//point not found (should never get here)
}

/*-------------------------------------------------------------------*//*!
* \brief	Tessellates a path, and computes its length.
* \param	
* \return	
* \note		if runs out of memory, throws bad_alloc and leaves the path as it was
*//*-------------------------------------------------------------------*/

RIfloat Path::getPathLength(int startIndex, int numSegments)
{
	RI_ASSERT(m_referenceCount > 0);
	RI_ASSERT(startIndex >= 0 && startIndex + numSegments <= m_segments.size() && numSegments > 0);

	Matrix3x3 identity;
	identity.identity();
	tessellate(identity, 0.0f);	//throws bad_alloc

	RI_ASSERT(startIndex >= 0 && startIndex < m_segmentToVertex.size());
	RI_ASSERT(startIndex + numSegments >= 0 && startIndex + numSegments <= m_segmentToVertex.size());

	int startVertex = m_segmentToVertex[startIndex].start;
	int endVertex = m_segmentToVertex[startIndex + numSegments - 1].end;

	if(!m_vertices.size())
		return 0.0f;

	RIfloat startPathLength = 0.0f;
	if(startVertex >= 0)
	{
		RI_ASSERT(startVertex >= 0 && startVertex < m_vertices.size());
		startPathLength = m_vertices[startVertex].pathLength;
	}
	RIfloat endPathLength = 0.0f;
	if(endVertex >= 0)
	{
		RI_ASSERT(endVertex >= 0 && endVertex < m_vertices.size());
		endPathLength = m_vertices[endVertex].pathLength;
	}

	return endPathLength - startPathLength;
}

/*-------------------------------------------------------------------*//*!
* \brief	Tessellates a path, and computes its bounding box in user space.
* \param	
* \return	
* \note		if runs out of memory, throws bad_alloc and leaves the path as it was
*//*-------------------------------------------------------------------*/

void Path::getPathBounds(RIfloat& minx, RIfloat& miny, RIfloat& maxx, RIfloat& maxy)
{
	RI_ASSERT(m_referenceCount > 0);

	Matrix3x3 identity;
	identity.identity();
	tessellate(identity, 0.0f);	//throws bad_alloc

	if(m_vertices.size())
	{
		minx = m_userMinx;
		miny = m_userMiny;
		maxx = m_userMaxx;
		maxy = m_userMaxy;
	}
	else
	{
		minx = miny = 0;
		maxx = maxy = -1;
	}
}

/*-------------------------------------------------------------------*//*!
* \brief	Tessellates a path, and computes its bounding box in surface space.
* \param	
* \return	
* \note		if runs out of memory, throws bad_alloc and leaves the path as it was
*//*-------------------------------------------------------------------*/

void Path::getPathTransformedBounds(const Matrix3x3& pathToSurface, RIfloat& minx, RIfloat& miny, RIfloat& maxx, RIfloat& maxy)
{
	RI_ASSERT(m_referenceCount > 0);
	RI_ASSERT(pathToSurface.isAffine());

	Matrix3x3 identity;
	identity.identity();
	tessellate(identity, 0.0f);	//throws bad_alloc

	if(m_vertices.size())
	{
		Vector3 p0(m_userMinx, m_userMiny, 1.0f);
		Vector3 p1(m_userMinx, m_userMaxy, 1.0f);
		Vector3 p2(m_userMaxx, m_userMaxy, 1.0f);
		Vector3 p3(m_userMaxx, m_userMiny, 1.0f);
		p0 = pathToSurface * p0;
		p1 = pathToSurface * p1;
		p2 = pathToSurface * p2;
		p3 = pathToSurface * p3;

		minx = RI_MIN(RI_MIN(RI_MIN(p0.x, p1.x), p2.x), p3.x);
		miny = RI_MIN(RI_MIN(RI_MIN(p0.y, p1.y), p2.y), p3.y);
		maxx = RI_MAX(RI_MAX(RI_MAX(p0.x, p1.x), p2.x), p3.x);
		maxy = RI_MAX(RI_MAX(RI_MAX(p0.y, p1.y), p2.y), p3.y);
	}
	else
	{
		minx = miny = 0;
		maxx = maxy = -1;
	}
}

/*-------------------------------------------------------------------*//*!
* \brief	Adds a vertex to a tessellated path.
* \param	
* \return	
* \note		
*//*-------------------------------------------------------------------*/

void Path::addVertex(const Vector2& p, const Vector2& t, RIfloat pathLength, unsigned int flags)
{
	RI_ASSERT(!isZero(t));

	Vertex v;
	v.pathLength = pathLength;
	v.userPosition = p;
	v.userTangent = t;
	v.flags = flags;
	m_vertices.push_back(v);	//throws bad_alloc
    m_numTessVertices++;

	m_userMinx = RI_MIN(m_userMinx, v.userPosition.x);
	m_userMiny = RI_MIN(m_userMiny, v.userPosition.y);
	m_userMaxx = RI_MAX(m_userMaxx, v.userPosition.x);
	m_userMaxy = RI_MAX(m_userMaxy, v.userPosition.y);
}

/*-------------------------------------------------------------------*//*!
* \brief	Adds an edge to a tessellated path.
* \param	
* \return	
* \note		
*//*-------------------------------------------------------------------*/

void Path::addEdge(const Vector2& p0, const Vector2& p1, const Vector2& t0, const Vector2& t1, unsigned int startFlags, unsigned int endFlags)
{
	Vertex v;
	RIfloat pathLength = 0.0f;

	RI_ASSERT(!isZero(t0) && !isZero(t1));

	//segment midpoints are shared between edges
	if(!m_numTessVertices)
	{
		if(m_vertices.size() > 0)
			pathLength = m_vertices[m_vertices.size()-1].pathLength;

		addVertex(p0, t0, pathLength, startFlags);	//throws bad_alloc
	}

	//other than implicit close paths (caused by a MOVE_TO) add to path length
	if( !(endFlags & IMPLICIT_CLOSE_SUBPATH) )
	{
		//NOTE: with extremely large coordinates the floating point path length is infinite
		RIfloat l = (p1 - p0).length();
		pathLength = m_vertices[m_vertices.size()-1].pathLength + l;
		pathLength = RI_MIN(pathLength, RI_FLOAT_MAX);
	}

	addVertex(p1, t1, pathLength, endFlags);	//throws bad_alloc
}

/*-------------------------------------------------------------------*//*!
* \brief	Tessellates a close-path segment.
* \param	
* \return	
* \note		
*//*-------------------------------------------------------------------*/

void Path::addEndPath(const Matrix3x3& pathToSurface, const Vector2& p0, const Vector2& p1, bool subpathHasGeometry, unsigned int flags)
{
	RI_UNREF(pathToSurface);
    m_numTessVertices = 0;
	if(!subpathHasGeometry)
	{	//single vertex
		Vector2 t(1.0f,0.0f);
		addEdge(p0, p1, t, t, START_SEGMENT | START_SUBPATH, END_SEGMENT | END_SUBPATH);	//throws bad_alloc
        m_numTessVertices = 0;
		addEdge(p0, p1, -t, -t, IMPLICIT_CLOSE_SUBPATH | START_SEGMENT, IMPLICIT_CLOSE_SUBPATH | END_SEGMENT);	//throws bad_alloc
		return;
	}
	//the subpath contains segment commands that have generated geometry

	//add a close path segment to the start point of the subpath
	RI_ASSERT(m_vertices.size() > 0);
	m_vertices[m_vertices.size()-1].flags |= END_SUBPATH;

	Vector2 t = normalize(p1 - p0);
	if(isZero(t))
		t = m_vertices[m_vertices.size()-1].userTangent;	//if the segment is zero-length, use the tangent of the last segment end point so that proper join will be generated
	RI_ASSERT(!isZero(t));

	addEdge(p0, p1, t, t, flags | START_SEGMENT, flags | END_SEGMENT);	//throws bad_alloc
}

/*-------------------------------------------------------------------*//*!
* \brief	Tessellates a line-to segment.
* \param	
* \return	
* \note		
*//*-------------------------------------------------------------------*/

bool Path::addLineTo(const Matrix3x3& pathToSurface, const Vector2& p0, const Vector2& p1, bool subpathHasGeometry)
{
	RI_UNREF(pathToSurface);
	if(p0 == p1)
		return false;	//discard zero-length segments

	//compute end point tangents
	Vector2 t = normalize(p1 - p0);
	RI_ASSERT(!isZero(t));

    m_numTessVertices = 0;
	unsigned int startFlags = START_SEGMENT;
	if(!subpathHasGeometry)
		startFlags |= START_SUBPATH;
	addEdge(p0, p1, t, t, startFlags, END_SEGMENT);	//throws bad_alloc
	return true;
}

/*-------------------------------------------------------------------*//*!
* \brief	Tessellates a quad-to segment.
* \param	
* \return	
* \note		
*//*-------------------------------------------------------------------*/

bool Path::addQuadTo(const Matrix3x3& pathToSurface, const Vector2& p0, const Vector2& p1, const Vector2& p2, bool subpathHasGeometry, float strokeWidth)
{
	RI_UNREF(pathToSurface);
	RI_UNREF(strokeWidth);
	if(p0 == p1 && p0 == p2)
	{
		RI_ASSERT(p1 == p2);
		return false;	//discard zero-length segments
	}

	//compute end point tangents

	Vector2 incomingTangent = normalize(p1 - p0);
	Vector2 outgoingTangent = normalize(p2 - p1);
	if(p0 == p1)
		incomingTangent = normalize(p2 - p0);
	if(p1 == p2)
		outgoingTangent = normalize(p2 - p0);
	RI_ASSERT(!isZero(incomingTangent) && !isZero(outgoingTangent));

    m_numTessVertices = 0;
	unsigned int startFlags = START_SEGMENT;
	if(!subpathHasGeometry)
		startFlags |= START_SUBPATH;

	const int segments = RI_NUM_TESSELLATED_SEGMENTS;
	Vector2 pp = p0;
	Vector2 tp = incomingTangent;
	unsigned int prevFlags = startFlags;
	for(int i=1;i<segments;i++)
	{
		RIfloat t = (RIfloat)i / (RIfloat)segments;
		RIfloat u = 1.0f-t;
		Vector2 pn = u*u * p0 + 2.0f*t*u * p1 + t*t * p2;
		Vector2 tn = (-1.0f+t) * p0 + (1.0f-2.0f*t) * p1 + t * p2;
		tn = normalize(tn);
		if(isZero(tn))
			tn = tp;

		addEdge(pp, pn, tp, tn, prevFlags, 0);	//throws bad_alloc

		pp = pn;
		tp = tn;
		prevFlags = 0;
	}
	addEdge(pp, p2, tp, outgoingTangent, prevFlags, END_SEGMENT);	//throws bad_alloc
	return true;
}

/*-------------------------------------------------------------------*//*!
* \brief	Tessellates a cubic-to segment.
* \param	
* \return	
* \note		
*//*-------------------------------------------------------------------*/

bool Path::addCubicTo(const Matrix3x3& pathToSurface, const Vector2& p0, const Vector2& p1, const Vector2& p2, const Vector2& p3, bool subpathHasGeometry, float strokeWidth)
{
	RI_UNREF(pathToSurface);
	RI_UNREF(strokeWidth);

	if(p0 == p1 && p0 == p2 && p0 == p3)
	{
		RI_ASSERT(p1 == p2 && p1 == p3 && p2 == p3);
		return false;	//discard zero-length segments
	}

	//compute end point tangents
	Vector2 incomingTangent = normalize(p1 - p0);
	Vector2 outgoingTangent = normalize(p3 - p2);
	if(p0 == p1)
	{
		incomingTangent = normalize(p2 - p0);
		if(p1 == p2)
			incomingTangent = normalize(p3 - p0);
	}
	if(p2 == p3)
	{
		outgoingTangent = normalize(p3 - p1);
		if(p1 == p2)
			outgoingTangent = normalize(p3 - p0);
	}
	RI_ASSERT(!isZero(incomingTangent) && !isZero(outgoingTangent));

    m_numTessVertices = 0;
	unsigned int startFlags = START_SEGMENT;
	if(!subpathHasGeometry)
		startFlags |= START_SUBPATH;

	const int segments = RI_NUM_TESSELLATED_SEGMENTS;
	Vector2 pp = p0;
	Vector2 tp = incomingTangent;
	unsigned int prevFlags = startFlags;
	for(int i=1;i<segments;i++)
	{
		RIfloat t = (RIfloat)i / (RIfloat)segments;
		Vector2 pn = (1.0f - 3.0f*t + 3.0f*t*t - t*t*t) * p0 + (3.0f*t - 6.0f*t*t + 3.0f*t*t*t) * p1 + (3.0f*t*t - 3.0f*t*t*t) * p2 + t*t*t * p3;
		Vector2 tn = (-3.0f + 6.0f*t - 3.0f*t*t) * p0 + (3.0f - 12.0f*t + 9.0f*t*t) * p1 + (6.0f*t - 9.0f*t*t) * p2 + 3.0f*t*t * p3;

		tn = normalize(tn);
		if(isZero(tn))
			tn = tp;

		addEdge(pp, pn, tp, tn, prevFlags, 0);	//throws bad_alloc

		pp = pn;
		tp = tn;
		prevFlags = 0;
	}
	addEdge(pp, p3, tp, outgoingTangent, prevFlags, END_SEGMENT);	//throws bad_alloc
	return true;
}

/*-------------------------------------------------------------------*//*!
* \brief	Finds an ellipse center and transformation from the unit circle to
*			that ellipse.
* \param	rh Length of the horizontal axis
*			rv Length of the vertical axis
*			rot Rotation angle
*			p0,p1 User space end points of the arc
*			c0,c1 (Return value) Unit circle space center points of the two ellipses
*			u0,u1 (Return value) Unit circle space end points of the arc
*			unitCircleToEllipse (Return value) A matrix mapping from unit circle space to user space
* \return	true if ellipse exists, false if doesn't
* \note		
*//*-------------------------------------------------------------------*/

static bool findEllipses(RIfloat rh, RIfloat rv, RIfloat rot, const Vector2& p0, const Vector2& p1, VGPathSegment segment, Vector2& c0, Vector2& c1, Vector2& u0, Vector2& u1, Matrix3x3& unitCircleToEllipse, bool& cw)
{
	rh = RI_ABS(rh);
	rv = RI_ABS(rv);
	if(rh == 0.0f || rv == 0.0f || p0 == p1)
		return false;	//degenerate ellipse

	rot = RI_DEG_TO_RAD(rot);
	unitCircleToEllipse.set((RIfloat)cos(rot)*rh, -(RIfloat)sin(rot)*rv,  0,
							(RIfloat)sin(rot)*rh,  (RIfloat)cos(rot)*rv,  0,
							0,                   0,                   1);
	Matrix3x3 ellipseToUnitCircle = invert(unitCircleToEllipse);
	//force affinity
	ellipseToUnitCircle[2][0] = 0.0f;
	ellipseToUnitCircle[2][1] = 0.0f;
	ellipseToUnitCircle[2][2] = 1.0f;

	// Transform p0 and p1 into unit space
	u0 = affineTransform(ellipseToUnitCircle, p0);
	u1 = affineTransform(ellipseToUnitCircle, p1);

	Vector2 m = 0.5f * (u0 + u1);
	Vector2 d = u0 - u1;

	RIfloat lsq = (RIfloat)dot(d,d);
	if(lsq <= 0.0f)
		return false;	//the points are coincident

	RIfloat disc = (1.0f / lsq) - 0.25f;
	if(disc < 0.0f)
	{	//the points are too far apart for a solution to exist, scale the axes so that there is a solution
		RIfloat l = (RIfloat)sqrt(lsq);
		rh *= 0.5f * l;
		rv *= 0.5f * l;

		//redo the computation with scaled axes
		unitCircleToEllipse.set((RIfloat)cos(rot)*rh, -(RIfloat)sin(rot)*rv,  0,
								(RIfloat)sin(rot)*rh,  (RIfloat)cos(rot)*rv,  0,
								0,                   0,                   1);
		ellipseToUnitCircle = invert(unitCircleToEllipse);
		//force affinity
		ellipseToUnitCircle[2][0] = 0.0f;
		ellipseToUnitCircle[2][1] = 0.0f;
		ellipseToUnitCircle[2][2] = 1.0f;

		// Transform p0 and p1 into unit space
		u0 = affineTransform(ellipseToUnitCircle, p0);
		u1 = affineTransform(ellipseToUnitCircle, p1);

		// Solve for intersecting unit circles
		d = u0 - u1;
		m = 0.5f * (u0 + u1);

		lsq = dot(d,d);
		if(lsq <= 0.0f)
			return false;	//the points are coincident

		disc = RI_MAX(0.0f, 1.0f / lsq - 0.25f);
	}

	if(u0 == u1)
		return false;

	Vector2 sd = d * (RIfloat)sqrt(disc);
	Vector2 sp = perpendicularCW(sd);
	c0 = m + sp;
	c1 = m - sp;

	//choose the center point and direction
	Vector2 cp = c0;
	if(segment == VG_SCWARC_TO || segment == VG_LCCWARC_TO)
		cp = c1;
	cw = false;
	if(segment == VG_SCWARC_TO || segment == VG_LCWARC_TO)
		cw = true;

	//move the unit circle origin to the chosen center point
	u0 -= cp;
	u1 -= cp;

	if(u0 == u1 || isZero(u0) || isZero(u1))
		return false;

	//transform back to the original coordinate space
	cp = affineTransform(unitCircleToEllipse, cp);
	unitCircleToEllipse[0][2] = cp.x;
	unitCircleToEllipse[1][2] = cp.y;
	return true;
}

/*-------------------------------------------------------------------*//*!
* \brief	Tessellates an arc-to segment.
* \param	
* \return	
* \note		
*//*-------------------------------------------------------------------*/

bool Path::addArcTo(const Matrix3x3& pathToSurface, const Vector2& p0, RIfloat rh, RIfloat rv, RIfloat rot, const Vector2& p1, const Vector2& p1r, VGPathSegment segment, bool subpathHasGeometry, float strokeWidth)
{
	RI_UNREF(pathToSurface);
	RI_UNREF(strokeWidth);
	if(p0 == p1)
		return false;	//discard zero-length segments

	Vector2 c0, c1, u0, u1;
	Matrix3x3 unitCircleToEllipse;
	bool cw;

    m_numTessVertices = 0;
	unsigned int startFlags = START_SEGMENT;
	if(!subpathHasGeometry)
		startFlags |= START_SUBPATH;

	if(!findEllipses(rh, rv, rot, Vector2(), p1r, segment, c0, c1, u0, u1, unitCircleToEllipse, cw))
	{	//ellipses don't exist, add line instead
		Vector2 t = normalize(p1r);
		RI_ASSERT(!isZero(t));
		addEdge(p0, p1, t, t, startFlags, END_SEGMENT);	//throws bad_alloc
		return true;
	}

	//compute end point tangents
	Vector2 incomingTangent = perpendicular(u0, cw);
	incomingTangent = affineTangentTransform(unitCircleToEllipse, incomingTangent);
	incomingTangent = normalize(incomingTangent);
	Vector2 outgoingTangent = perpendicular(u1, cw);
	outgoingTangent = affineTangentTransform(unitCircleToEllipse, outgoingTangent);
	outgoingTangent = normalize(outgoingTangent);
	RI_ASSERT(!isZero(incomingTangent) && !isZero(outgoingTangent));

	const int segments = RI_NUM_TESSELLATED_SEGMENTS;
	Vector2 pp = p0;
	Vector2 tp = incomingTangent;
	unsigned int prevFlags = startFlags;
	for(int i=1;i<segments;i++)
	{
		RIfloat t = (RIfloat)i / (RIfloat)segments;
		Vector2 pn = circularLerp(u0, u1, t, cw);
		Vector2 tn = perpendicular(pn, cw);
		tn = affineTangentTransform(unitCircleToEllipse, tn);
		pn = affineTransform(unitCircleToEllipse, pn) + p0;
		tn = normalize(tn);
		if(isZero(tn))
			tn = tp;

		addEdge(pp, pn, tp, tn, prevFlags, 0);	//throws bad_alloc

		pp = pn;
		tp = tn;
		prevFlags = 0;
	}
	addEdge(pp, p1, tp, outgoingTangent, prevFlags, END_SEGMENT);	//throws bad_alloc
	return true;
}

/*-------------------------------------------------------------------*//*!
* \brief	Tessellates a path.
* \param	
* \return	
* \note		tessellation output format: A list of vertices describing the
*			path tessellated into line segments and relevant aspects of the
*			input data. Each path segment has a start vertex, a number of
*			internal vertices (possibly zero), and an end vertex. The start
*			and end of segments and subpaths have been flagged, as well as
*			implicit and explicit close subpath segments.
*//*-------------------------------------------------------------------*/

void Path::tessellate(const Matrix3x3& pathToSurface, float strokeWidth)
{
	m_vertices.clear();

	m_userMinx = RI_FLOAT_MAX;
	m_userMiny = RI_FLOAT_MAX;
	m_userMaxx = -RI_FLOAT_MAX;
	m_userMaxy = -RI_FLOAT_MAX;

	try
	{
		m_segmentToVertex.resize(m_segments.size());

		int coordIndex = 0;
		Vector2 s(0,0);		//the beginning of the current subpath
		Vector2 o(0,0);		//the last point of the previous segment
		Vector2 p(0,0);		//the last internal control point of the previous segment, if the segment was a (regular or smooth) quadratic or cubic Bezier, or else the last point of the previous segment

		//tessellate the path segments
		coordIndex = 0;
		s.set(0,0);
		o.set(0,0);
		p.set(0,0);
		bool subpathHasGeometry = false;
		VGPathSegment prevSegment = VG_MOVE_TO;
		for(int i=0;i<m_segments.size();i++)
		{
			VGPathSegment segment = getPathSegment(m_segments[i]);
			VGPathAbsRel absRel = getPathAbsRel(m_segments[i]);
			int coords = segmentToNumCoordinates(segment);
			m_segmentToVertex[i].start = m_vertices.size();

			switch(segment)
			{
			case VG_CLOSE_PATH:
			{
				RI_ASSERT(coords == 0);
				addEndPath(pathToSurface, o, s, subpathHasGeometry, CLOSE_SUBPATH);
				p = s;
				o = s;
				subpathHasGeometry = false;
				break;
			}

			case VG_MOVE_TO:
			{
				RI_ASSERT(coords == 2);
				Vector2 c(getCoordinate(coordIndex+0), getCoordinate(coordIndex+1));
				if(absRel == VG_RELATIVE)
					c += o;
				if(prevSegment != VG_MOVE_TO && prevSegment != VG_CLOSE_PATH)
					addEndPath(pathToSurface, o, s, subpathHasGeometry, IMPLICIT_CLOSE_SUBPATH);
				s = c;
				p = c;
				o = c;
				subpathHasGeometry = false;
				break;
			}

			case VG_LINE_TO:
			{
				RI_ASSERT(coords == 2);
				Vector2 c(getCoordinate(coordIndex+0), getCoordinate(coordIndex+1));
				if(absRel == VG_RELATIVE)
					c += o;
				if(addLineTo(pathToSurface, o, c, subpathHasGeometry))
					subpathHasGeometry = true;
				p = c;
				o = c;
				break;
			}

			case VG_HLINE_TO:
			{
				RI_ASSERT(coords == 1);
				Vector2 c(getCoordinate(coordIndex+0), o.y);
				if(absRel == VG_RELATIVE)
					c.x += o.x;
				if(addLineTo(pathToSurface, o, c, subpathHasGeometry))
					subpathHasGeometry = true;
				p = c;
				o = c;
				break;
			}

			case VG_VLINE_TO:
			{
				RI_ASSERT(coords == 1);
				Vector2 c(o.x, getCoordinate(coordIndex+0));
				if(absRel == VG_RELATIVE)
					c.y += o.y;
				if(addLineTo(pathToSurface, o, c, subpathHasGeometry))
					subpathHasGeometry = true;
				p = c;
				o = c;
				break;
			}

			case VG_QUAD_TO:
			{
				RI_ASSERT(coords == 4);
				Vector2 c0(getCoordinate(coordIndex+0), getCoordinate(coordIndex+1));
				Vector2 c1(getCoordinate(coordIndex+2), getCoordinate(coordIndex+3));
				if(absRel == VG_RELATIVE)
				{
					c0 += o;
					c1 += o;
				}
				if(addQuadTo(pathToSurface, o, c0, c1, subpathHasGeometry, strokeWidth))
					subpathHasGeometry = true;
				p = c0;
				o = c1;
				break;
			}

			case VG_SQUAD_TO:
			{
				RI_ASSERT(coords == 2);
				Vector2 c0 = 2.0f * o - p;
				Vector2 c1(getCoordinate(coordIndex+0), getCoordinate(coordIndex+1));
				if(absRel == VG_RELATIVE)
					c1 += o;
				if(addQuadTo(pathToSurface, o, c0, c1, subpathHasGeometry, strokeWidth))
					subpathHasGeometry = true;
				p = c0;
				o = c1;
				break;
			}

			case VG_CUBIC_TO:
			{
				RI_ASSERT(coords == 6);
				Vector2 c0(getCoordinate(coordIndex+0), getCoordinate(coordIndex+1));
				Vector2 c1(getCoordinate(coordIndex+2), getCoordinate(coordIndex+3));
				Vector2 c2(getCoordinate(coordIndex+4), getCoordinate(coordIndex+5));
				if(absRel == VG_RELATIVE)
				{
					c0 += o;
					c1 += o;
					c2 += o;
				}
				if(addCubicTo(pathToSurface, o, c0, c1, c2, subpathHasGeometry, strokeWidth))
					subpathHasGeometry = true;
				p = c1;
				o = c2;
				break;
			}

			case VG_SCUBIC_TO:
			{
				RI_ASSERT(coords == 4);
				Vector2 c0 = 2.0f * o - p;
				Vector2 c1(getCoordinate(coordIndex+0), getCoordinate(coordIndex+1));
				Vector2 c2(getCoordinate(coordIndex+2), getCoordinate(coordIndex+3));
				if(absRel == VG_RELATIVE)
				{
					c1 += o;
					c2 += o;
				}
				if(addCubicTo(pathToSurface, o, c0, c1, c2, subpathHasGeometry, strokeWidth))
					subpathHasGeometry = true;
				p = c1;
				o = c2;
				break;
			}

			default:
			{
				RI_ASSERT(segment == VG_SCCWARC_TO || segment == VG_SCWARC_TO ||
						  segment == VG_LCCWARC_TO || segment == VG_LCWARC_TO);
				RI_ASSERT(coords == 5);
				RIfloat rh = getCoordinate(coordIndex+0);
				RIfloat rv = getCoordinate(coordIndex+1);
				RIfloat rot = getCoordinate(coordIndex+2);
				Vector2 c(getCoordinate(coordIndex+3), getCoordinate(coordIndex+4));

                Vector2 cr = c;
				if(absRel == VG_ABSOLUTE)
                    cr -= o;
                else
					c += o;

				if(addArcTo(pathToSurface, o, rh, rv, rot, c, cr, segment, subpathHasGeometry, strokeWidth))
					subpathHasGeometry = true;
				p = c;
				o = c;
				break;
			}
			}

			if(m_vertices.size() > m_segmentToVertex[i].start)
			{	//segment produced vertices
				m_segmentToVertex[i].end = m_vertices.size() - 1;
			}
			else
			{	//segment didn't produce vertices (zero-length segment). Ignore it.
				m_segmentToVertex[i].start = m_segmentToVertex[i].end = m_vertices.size()-1;
			}
			prevSegment = segment;
			coordIndex += coords;
		}

		//add an implicit MOVE_TO to the end to close the last subpath.
		//if the subpath contained only zero-length segments, this produces the necessary geometry to get it stroked
		// and included in path bounds. The geometry won't be included in the pointAlongPath query.
		if(prevSegment != VG_MOVE_TO && prevSegment != VG_CLOSE_PATH)
			addEndPath(pathToSurface, o, s, subpathHasGeometry, IMPLICIT_CLOSE_SUBPATH);

		//check that the flags are correct
#ifdef RI_DEBUG
		int prev = -1;
		bool subpathStarted = false;
		bool segmentStarted = false;
		for(int i=0;i<m_vertices.size();i++)
		{
			Vertex& v = m_vertices[i];

			if(v.flags & START_SUBPATH)
			{
				RI_ASSERT(!subpathStarted);
				RI_ASSERT(v.flags & START_SEGMENT);
				RI_ASSERT(!(v.flags & END_SUBPATH));
				RI_ASSERT(!(v.flags & END_SEGMENT));
				RI_ASSERT(!(v.flags & CLOSE_SUBPATH));
				RI_ASSERT(!(v.flags & IMPLICIT_CLOSE_SUBPATH));
				subpathStarted = true;
			}
			
			if(v.flags & START_SEGMENT)
			{
				RI_ASSERT(subpathStarted || (v.flags & CLOSE_SUBPATH) || (v.flags & IMPLICIT_CLOSE_SUBPATH));
				RI_ASSERT(!segmentStarted);
				RI_ASSERT(!(v.flags & END_SUBPATH));
				RI_ASSERT(!(v.flags & END_SEGMENT));
				segmentStarted = true;
			}
			
			if( v.flags & CLOSE_SUBPATH )
			{
				RI_ASSERT(segmentStarted);
				RI_ASSERT(!subpathStarted);
				RI_ASSERT((v.flags & START_SEGMENT) || (v.flags & END_SEGMENT));
				RI_ASSERT(!(v.flags & IMPLICIT_CLOSE_SUBPATH));
				RI_ASSERT(!(v.flags & START_SUBPATH));
				RI_ASSERT(!(v.flags & END_SUBPATH));
			}
			if( v.flags & IMPLICIT_CLOSE_SUBPATH )
			{
				RI_ASSERT(segmentStarted);
				RI_ASSERT(!subpathStarted);
				RI_ASSERT((v.flags & START_SEGMENT) || (v.flags & END_SEGMENT));
				RI_ASSERT(!(v.flags & CLOSE_SUBPATH));
				RI_ASSERT(!(v.flags & START_SUBPATH));
				RI_ASSERT(!(v.flags & END_SUBPATH));
			}
			
			if( prev >= 0 )
			{
				RI_ASSERT(segmentStarted);
				RI_ASSERT(subpathStarted || ((m_vertices[prev].flags & CLOSE_SUBPATH) && (m_vertices[i].flags & CLOSE_SUBPATH)) ||
						  ((m_vertices[prev].flags & IMPLICIT_CLOSE_SUBPATH) && (m_vertices[i].flags & IMPLICIT_CLOSE_SUBPATH)));
			}

			prev = i;
			if(v.flags & END_SEGMENT)
			{
				RI_ASSERT(subpathStarted || (v.flags & CLOSE_SUBPATH) || (v.flags & IMPLICIT_CLOSE_SUBPATH));
				RI_ASSERT(segmentStarted);
				RI_ASSERT(!(v.flags & START_SUBPATH));
				RI_ASSERT(!(v.flags & START_SEGMENT));
				segmentStarted = false;
				prev = -1;
			}
			
			if(v.flags & END_SUBPATH)
			{
				RI_ASSERT(subpathStarted);
				RI_ASSERT(v.flags & END_SEGMENT);
				RI_ASSERT(!(v.flags & START_SUBPATH));
				RI_ASSERT(!(v.flags & START_SEGMENT));
				RI_ASSERT(!(v.flags & CLOSE_SUBPATH));
				RI_ASSERT(!(v.flags & IMPLICIT_CLOSE_SUBPATH));
				subpathStarted = false;
			}
		}
#endif	//RI_DEBUG
	}
	catch(std::bad_alloc)
	{
		m_vertices.clear();
		throw;
	}
}

//==============================================================================================

}		//namespace OpenVGRI

//==============================================================================================