egl/sfopenvg/riPath.cpp
author Faisal Memon <faisal.memon@nokia.com>
Fri, 25 Jun 2010 17:49:58 +0100
branchEGL_MERGE
changeset 105 158b2308cc08
parent 57 2bf8a359aa2f
child 88 a5a3a8cb368e
permissions -rw-r--r--
Fix def files so that the implementation agnostic interface definition has no non-standards defined entry points, and change the eglrefimpl specific implementation to place its private entry points high up in the ordinal order space in the implementation region, not the standards based entrypoints region.

/*------------------------------------------------------------------------
 *
 * 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

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