Blob Blame Raw


//Toonz components includes
#include "tcurveutil.h"
#include "tinterval.h"

#include "tellipticbrushP.h"
#include "tstrokeoutline.h"

using namespace tellipticbrush;

//********************************************************************************
//    EXPLANATION
//********************************************************************************

/*! \file tellipticbrush.cpp
This code deals with the "outlinization" process of a TStroke instance.

The process of extracing the outline of a thick stroke can be resumed in 2 main steps:

  1. Discretize the stroke centerline in the most appropriate centerline points,
     extracting infos about position and left/right derivatives at each.

  2. Build the outline points associated to each individual centerline point;
     eventually including additional junction points and caps.

The first major step has some sub-routines worth noting:

  1.1 Isolate regions of the stroke where the thickness speed is greater
      than the gemoetrical speed of the centerline. These points are 'self-covered'
      by their immediate neighbourhood, and thus cannot be seen - or build outline directions.
  1.2 Some procedural style need to sample the centerline at a given length step.
  1.3 The centerline should be sampled so that the resulting polygonal outline
      approximation is tightly close to the theoretical outline, up to an error bound.
      The recursive approach is the simplest to deal with this issue.

The second step implements different outline styles to extrude the centerline points.
*/

//********************************************************************************
//    Geometric Helper Functions
//********************************************************************************

//! Returns the distance between two points
double tellipticbrush::dist(const TPointD &P1, const TPointD &P2)
{
	return norm(P2 - P1);
}

//------------------------------------------------------------

//! Returns the distance between two points
double tellipticbrush::dist(const TThickPoint &P1, const TThickPoint &P2)
{
	return norm(P2 - P1);
}

//------------------------------------------------------------

//!Returns the angle between (unnormalized) vectors v1 and v2
double tellipticbrush::angle(const TPointD &v1, const TPointD &v2)
{
	TPointD d1(v1 * (1.0 / norm(v1))), d2(v2 * (1.0 / norm(v2)));
	return atan2(cross(v1, v2), v1 * v2);
}

//------------------------------------------------------------

/*!
  Returns the intersection between two lines in the form of \b coordinates
  from a pair of the lines' starting points. Passed directions must have norm 1.

  If the system's determinant modulus is under the specified tolerance parameter, 
  TConsts::napd is returned.
*/
TPointD tellipticbrush::intersectionCoords(
	const TPointD &P0, const TPointD &d0, const TPointD &P1, const TPointD &d1,
	double detTol)
{
	//Solve P0 + x * d0 == P1 + y * d1

	double det = d0.y * d1.x - d0.x * d1.y;
	if (fabs(det) < detTol)
		return TConsts::napd;

	TPointD P1_P0(P1 - P0);
	return TPointD(
		(d1.x * P1_P0.y - d1.y * P1_P0.x) / det,
		(d0.x * P1_P0.y - d0.y * P1_P0.x) / det);
}

//------------------------------------------------------------

/*!
  Returns the left or right envelope direction of centerline point p against thick direction d.
*/
void tellipticbrush::buildEnvelopeDirection(
	const TThickPoint &p, const TThickPoint &d, bool left, TPointD &res)
{
	double dNorm2 = sq(d.x) + sq(d.y);

	double a = -d.thick / dNorm2;
	double b = sqrt(dNorm2 - sq(d.thick)) / dNorm2;

	TPointD n(left ? TPointD(-d.y, d.x) : TPointD(d.y, -d.x));
	res = a * TPointD(d.x, d.y) + b * n;
}

//------------------------------------------------------------

void tellipticbrush::buildEnvelopeDirections(
	const TThickPoint &p, const TThickPoint &d, TPointD &resL, TPointD &resR)
{
	double dNorm2 = sq(d.x) + sq(d.y);

	double a = -d.thick / dNorm2;
	double b = sqrt(dNorm2 - sq(d.thick)) / dNorm2;

	TPointD n(-d.y, d.x);
	resL = a * TPointD(d.x, d.y) + b * n;
	resR = a * TPointD(d.x, d.y) - b * n;
}

//------------------------------------------------------------

/*!
  Extrudes centerline point p against thick direction d, returning its left or right
  envelope displacement vector.
*/
void tellipticbrush::buildEnvelopeVector(
	const TThickPoint &p, const TThickPoint &d, bool left, TPointD &res)
{
	buildEnvelopeDirection(p, d, left, res);
	res.x = p.thick * res.x;
	res.y = p.thick * res.y;
}

//------------------------------------------------------------

void tellipticbrush::buildEnvelopeVectors(
	const TThickPoint &p, const TThickPoint &d, TPointD &resL, TPointD &resR)
{
	buildEnvelopeDirections(p, d, resL, resR);
	resL.x = p.thick * resL.x;
	resL.y = p.thick * resL.y;
	resR.x = p.thick * resR.x;
	resR.y = p.thick * resR.y;
}

//------------------------------------------------------------

/*!
  Builds the angle that supports a *quality* discretization of the circle
  with maximal error < m_pixSize.
*/
void tellipticbrush::buildAngularSubdivision(
	double radius, double angle, double err, int &nAngles)
{
	/*
    See "Graphic Gems", page 600.

    NOTE: maxAngle is not multiplied by 2.0 as the naive pythagorical
    argument would pretend. The 2.0 holds if we want to find the angle
    at which the distance of the circle from its approximation is always < error.

    But we want MORE. We want that to happen against the distance from EVERY
    TANGENT LINE of the arc - not the arc itself.
    This is coherent with the assumption that pixels orientation is not known.

    It's easy to see that maxAngle just has to be not multiplied by 2.
  */

	double maxAngle = acos(1.0 - err / radius); //* 2.0;
	nAngles = tceil(fabs(angle) / maxAngle);
}

//------------------------------------------------------------

TRectD tellipticbrush::computeBBox(const TStroke &stroke)
{
	TRectD bbox;

	int i, n = stroke.getChunkCount();
	for (i = 0; i < n; i++)
		bbox += stroke.getChunk(i)->getBBox();

	return bbox;
}

//********************************************************************************
//    CenterlinePoint implementation
//********************************************************************************

void tellipticbrush::CenterlinePoint::buildPos(const TStroke &stroke)
{
	if (m_posBuilt)
		return;

	m_p = stroke.getChunk(m_chunkIdx)->getThickPoint(m_t);
	m_posBuilt = true;
}

//------------------------------------------------------------

void tellipticbrush::CenterlinePoint::buildDirs(const TStroke &stroke)
{
	if (m_dirsBuilt)
		return;

	int chunkPrev, chunkNext;
	double tPrev, tNext;
	bool coveredPrev, coveredNext;

	//Discriminate the boundary cases
	bool quadBoundary;
	if (m_t == 0.0) {
		quadBoundary = true;
		chunkPrev = m_chunkIdx - 1, chunkNext = m_chunkIdx;
		tPrev = 1.0, tNext = 0.0;
	} else if (m_t == 1.0) {
		quadBoundary = true;
		chunkPrev = m_chunkIdx, chunkNext = m_chunkIdx + 1;
		tPrev = 1.0, tNext = 0.0;
	} else {
		quadBoundary = false;
		chunkPrev = chunkNext = m_chunkIdx;
		tPrev = tNext = m_t;
	}

	//Build the backward direction
	if (chunkPrev >= 0) {
		const TThickQuadratic *ttqPrev = stroke.getChunk(chunkPrev);

		const TThickPoint &P0 = ttqPrev->getThickP0();
		const TThickPoint &P1 = ttqPrev->getThickP1();
		const TThickPoint &P2 = ttqPrev->getThickP2();

		if (quadBoundary && (P1 == P2))
			m_prevD = P2 - P0; //Toonz 'Linear' CPs. Eliminating a perilous singularity this way.
		else {
			m_prevD.x = 2.0 * ((P1.x - P0.x) + tPrev * (P0.x - 2.0 * P1.x + P2.x));
			m_prevD.y = 2.0 * ((P1.y - P0.y) + tPrev * (P0.y - 2.0 * P1.y + P2.y));
			m_prevD.thick = 2.0 * ((P1.thick - P0.thick) + tPrev * (P0.thick - 2.0 * P1.thick + P2.thick));
		}

		//Points whose thickness derivative does exceeds the point speed
		//cannot project envelope directions for that direction. This needs to be known.
		coveredPrev = (sq(m_prevD.x) + sq(m_prevD.y) < sq(m_prevD.thick) + tolPar);

		//Accept only uncovered derivatives
		m_hasPrevD = !coveredPrev;
	} else {
		m_hasPrevD = false;
		coveredPrev = true; //ie prev coverage must not affect next coverage
		m_prevD = TConsts::natp;
	}

	//Build the forward direction
	if (chunkPrev == chunkNext) {
		//If the quadratic is the same, no need to derive it twice
		m_hasNextD = m_hasPrevD;
		m_nextD = m_prevD;
		coveredNext = coveredPrev;
	} else if (chunkNext < stroke.getChunkCount()) {
		const TThickQuadratic *ttqNext = stroke.getChunk(chunkNext);

		const TThickPoint &P0 = ttqNext->getThickP0();
		const TThickPoint &P1 = ttqNext->getThickP1();
		const TThickPoint &P2 = ttqNext->getThickP2();

		if (quadBoundary && (P0 == P1))
			m_nextD = P2 - P0;
		else {
			m_nextD.x = 2.0 * ((P1.x - P0.x) + tNext * (P0.x - 2.0 * P1.x + P2.x));
			m_nextD.y = 2.0 * ((P1.y - P0.y) + tNext * (P0.y - 2.0 * P1.y + P2.y));
			m_nextD.thick = 2.0 * ((P1.thick - P0.thick) + tNext * (P0.thick - 2.0 * P1.thick + P2.thick));
		}

		coveredNext = (sq(m_nextD.x) + sq(m_nextD.y) < sq(m_nextD.thick) + tolPar);
		m_hasNextD = !coveredNext;
	} else {
		m_hasNextD = false;
		coveredNext = true; //ie prev coverage must not affect next coverage
		m_nextD = TConsts::natp;
	}

	m_covered = (coveredPrev && coveredNext);

	m_dirsBuilt = true;
}

//********************************************************************************
//    Specialized Linearizator for common stroke drawing
//********************************************************************************

namespace
{

class LengthLinearizator : public tellipticbrush::StrokeLinearizator
{
	double m_lengthStep;
	int m_countIdx;

public:
	LengthLinearizator(const TStroke *stroke, double lengthStep)
		: StrokeLinearizator(stroke), m_lengthStep(lengthStep), m_countIdx(0) {}

	void linearize(std::vector<CenterlinePoint> &cPoints, int chunk);
};

//--------------------------------------------------------------------------------------------

void LengthLinearizator::linearize(std::vector<CenterlinePoint> &cPoints, int chunk)
{
	if (m_lengthStep == 0.0)
		return;

	//Retrieve the stroke length at stroke start
	double startW = this->m_stroke->getW(chunk, 0.0);
	double startLength = this->m_stroke->getLength(startW);

	//Retrieve the quadratic's end length
	const TThickQuadratic *ttq = this->m_stroke->getChunk(chunk);
	double endLength = startLength + ttq->getLength();

	//Build the step-length inside the chunk
	int n = tceil(startLength / m_lengthStep);
	double length;
	double t, w;
	int chk;

	for (length = n * m_lengthStep; length < endLength; length += m_lengthStep) {
		//Retrieve the new params at length. Need to use the sloppy TStroke interface,
		//unfortunately...
		w = this->m_stroke->getParameterAtLength(length);

		//WARNING: TStroke's interface is COMPLETELY WRONG about what gets returned
		//by the following function. This is just *CRAZY* - however, let's take it all right...
		bool ok = !this->m_stroke->getChunkAndT(w, chk, t);

		//In case something goes wrong, skip
		if (!ok || chk != chunk)
			continue;

		//Store the param, that NEEDS TO BE INCREMENTALLY COUNTED - as length linearization
		//is typically used for special procedural vector styles that need this info.
		CenterlinePoint cPoint(chk, t);
		cPoint.m_countIdx = m_countIdx += 2; //++m_countIdx;
		cPoints.push_back(cPoint);
	}
}

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

class RecursiveLinearizator : public tellipticbrush::StrokeLinearizator
{
	double m_pixSize;

public:
	RecursiveLinearizator(const TStroke *stroke, double pixSize)
		: StrokeLinearizator(stroke), m_pixSize(pixSize) {}

	void linearize(std::vector<CenterlinePoint> &cPoints, int chunk);
	void subdivide(std::vector<CenterlinePoint> &cPoints,
				   CenterlinePoint &cp0, CenterlinePoint &cp1);
};

//--------------------------------------------------------------------------------------------

void RecursiveLinearizator::linearize(std::vector<CenterlinePoint> &cPoints, int chunk)
{
	/*
    Recursively linearizes the centerline, in the following way:

    Take one point, together with the next. Add a point in the middle interval, until
    the next thick point is included (up to pixSize) in the 'forward-cast' envelope of
    current one. If the midpoint was added, repeat on the 2 sub-intervals.
  */

	const TStroke &stroke = *this->m_stroke;
	const TThickQuadratic &ttq = *stroke.getChunk(chunk);

	//Sort the interval (SHOULD BE DONE OUTSIDE?)
	std::sort(cPoints.begin(), cPoints.end());

	std::vector<CenterlinePoint> addedPoints;

	unsigned int i, size_1 = cPoints.size() - 1;
	for (i = 0; i < size_1; ++i) {
		cPoints[i].buildPos(stroke);
		cPoints[i].buildDirs(stroke);

		cPoints[i + 1].buildPos(stroke);
		cPoints[i + 1].buildDirs(stroke);

		subdivide(addedPoints, cPoints[i], cPoints[i + 1]);
	}

	cPoints[size_1].buildPos(stroke);
	cPoints[size_1].buildDirs(stroke);

	CenterlinePoint cpEnd(chunk, 1.0);
	{
		const TThickPoint &P1(ttq.getThickP1());
		cpEnd.m_p = ttq.getThickP2();
		cpEnd.m_prevD = TThickPoint(
			2.0 * (cpEnd.m_p.x - P1.x),
			2.0 * (cpEnd.m_p.y - P1.y),
			2.0 * (cpEnd.m_p.thick - P1.thick));
		cpEnd.m_hasPrevD = true; //The effective false case should already be dealt by sqrt...
	}

	subdivide(addedPoints, cPoints[size_1], cpEnd);

	cPoints.insert(cPoints.end(), addedPoints.begin(), addedPoints.end());
}

//--------------------------------------------------------------------------------------------

void RecursiveLinearizator::subdivide(std::vector<CenterlinePoint> &cPoints,
									  CenterlinePoint &cp0, CenterlinePoint &cp1)
{
	if (!(cp0.m_hasNextD && cp1.m_hasPrevD))
		return;

	//Build the distance of next from the outline of cp's 'envelope extension'

	TPointD envDirL0, envDirR0, envDirL1, envDirR1;
	buildEnvelopeDirections(cp0.m_p, cp0.m_nextD, envDirL0, envDirR0);
	buildEnvelopeDirections(cp1.m_p, cp1.m_prevD, envDirL1, envDirR1);

	TPointD diff(convert(cp1.m_p) - convert(cp0.m_p));
	double d = tmax(
		fabs(envDirL0 * (diff + cp1.m_p.thick * envDirL1 - cp0.m_p.thick * envDirL0)),
		fabs(envDirR0 * (diff + cp1.m_p.thick * envDirR1 - cp0.m_p.thick * envDirR0)));

	if (d > m_pixSize &&
		cp1.m_t - cp0.m_t > 1e-4) {
		double midT = 0.5 * (cp0.m_t + cp1.m_t);
		CenterlinePoint midPoint(cp0.m_chunkIdx, midT);

		midPoint.buildPos(*this->m_stroke);
		midPoint.buildDirs(*this->m_stroke);

		subdivide(cPoints, cp0, midPoint);
		subdivide(cPoints, midPoint, cp1);

		cPoints.push_back(midPoint);
	}
}

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

class CoverageLinearizator : public tellipticbrush::StrokeLinearizator
{
public:
	CoverageLinearizator(const TStroke *stroke)
		: StrokeLinearizator(stroke) {}

	void linearize(std::vector<CenterlinePoint> &cPoints, int chunk);
};

//--------------------------------------------------------------------------------------------

void CoverageLinearizator::linearize(std::vector<CenterlinePoint> &cPoints, int chunk)
{
	//Retrieve the at max 2 parameters for which:
	//    sq(d.x) + sq(d.y) == sq(d.thick) + tolPar(*)     (ie, "self-coverage" critical points)

	//It can be rewritten in the canonical form:    at^2 + bt + c == 0

	const TThickQuadratic &ttq(*this->m_stroke->getChunk(chunk));

	TThickPoint P0(ttq.getThickP0()), P1(ttq.getThickP1()), P2(ttq.getThickP2());
	if ((P0 == P1) || (P1 == P2))
		return; //Linear speed out/in case. Straighted up in the buildDirs()

	//Remember that d = 2 [P1 - P0 + t (P0 + P2 - 2 P1)]

	T3DPointD u(P1.x - P0.x, P1.y - P0.y, P1.thick - P0.thick);
	T3DPointD v(P0.x + P2.x - 2.0 * P1.x, P0.y + P2.y - 2.0 * P1.y, P0.thick + P2.thick - 2.0 * P1.thick);

	double a = sq(v.x) + sq(v.y) - sq(v.z);
	if (fabs(a) < 1e-4)
		return; //Little (acceleration) quadratics case

	//(*) Build tolerance - 2.0 since tolPar is already used to discriminate 'good' dirs. Ours must be.
	const double twiceTolPar = 2.0 * tolPar;

	double b = 2.0 * (u.x * v.x + u.y * v.y - u.z * v.z);
	double c = sq(u.x) + sq(u.y) - sq(u.z) - twiceTolPar;

	double delta = sq(b) - 4.0 * a * c;
	if (delta < 0)
		return;

	double sqrtDelta = sqrt(delta);
	double t0 = (-b - sqrtDelta) / (2.0 * a);
	double t1 = (-b + sqrtDelta) / (2.0 * a);

	if (t0 > 0 && t0 < 1) {
		CenterlinePoint cp(chunk, t0);
		cp.buildPos(*this->m_stroke);
		cp.buildDirs(*this->m_stroke);
		cp.m_hasNextD = false;
		cPoints.push_back(cp);
	}

	if (t1 > 0 && t1 < 1) {
		CenterlinePoint cp(chunk, t1);
		cp.buildPos(*this->m_stroke);
		cp.buildDirs(*this->m_stroke);
		cp.m_hasPrevD = false;
		cPoints.push_back(cp);
	}
}

} //namespace

//********************************************************************************
//    Outline Builder implementation
//********************************************************************************

tellipticbrush::OutlineBuilder::OutlineBuilder(const OutlinizationData &data, const TStroke &stroke)
	: m_pixSize(data.m_pixSize), m_oOptions(stroke.outlineOptions()), m_lastChunk(stroke.getChunkCount() - 1)
{
	typedef TStroke::OutlineOptions OutlineOptions;

	switch (m_oOptions.m_capStyle) {
	case OutlineOptions::PROJECTING_CAP: {
		m_addBeginCap = &OutlineBuilder::addProjectingBeginCap<std::vector<TOutlinePoint>>;
		m_addEndCap = &OutlineBuilder::addProjectingEndCap<std::vector<TOutlinePoint>>;
		m_addBeginCap_ext = &OutlineBuilder::addProjectingBeginCap<TRectD>;
		m_addEndCap_ext = &OutlineBuilder::addProjectingEndCap<TRectD>;
		break;
	}
	case OutlineOptions::BUTT_CAP: {
		m_addBeginCap = &OutlineBuilder::addButtBeginCap;
		m_addEndCap = &OutlineBuilder::addButtEndCap;
		m_addBeginCap_ext = 0;
		m_addEndCap_ext = 0;
		break;
	}
	case OutlineOptions::ROUND_CAP:
	default:
		m_addBeginCap = &OutlineBuilder::addRoundBeginCap;
		m_addEndCap = &OutlineBuilder::addRoundEndCap;
		m_addBeginCap_ext = 0;
		m_addEndCap_ext = 0;
	};

	switch (m_oOptions.m_joinStyle) {
	case OutlineOptions::MITER_JOIN: {
		m_addSideCaps = &OutlineBuilder::addMiterSideCaps<std::vector<TOutlinePoint>>;
		m_addSideCaps_ext = &OutlineBuilder::addMiterSideCaps<TRectD>;
		break;
	}

	case OutlineOptions::BEVEL_JOIN: {
		m_addSideCaps = &OutlineBuilder::addBevelSideCaps;
		m_addSideCaps_ext = 0;
		break;
	}
	case OutlineOptions::ROUND_JOIN:
	default:
		m_addSideCaps = &OutlineBuilder::addRoundSideCaps;
		m_addSideCaps_ext = 0;
	};
}

//------------------------------------------------------------

/*!
  Translates a CenterlinePoint instance into OutlinePoints, and
  adds them to the supplied vector container.
*/
void tellipticbrush::OutlineBuilder::buildOutlinePoints(
	std::vector<TOutlinePoint> &oPoints, const CenterlinePoint &cPoint)
{
	//If the centerline directions exist and match, just add their envelope
	//displacement directly
	if (cPoint.m_hasPrevD && cPoint.m_hasNextD &&
		cPoint.m_prevD == cPoint.m_nextD) {
		TPointD leftD, rightD;
		buildEnvelopeVector(cPoint.m_p, cPoint.m_prevD, true, leftD);
		buildEnvelopeVector(cPoint.m_p, cPoint.m_prevD, false, rightD);

		oPoints.push_back(TOutlinePoint(convert(cPoint.m_p) + rightD, cPoint.m_countIdx));
		oPoints.push_back(TOutlinePoint(convert(cPoint.m_p) + leftD, cPoint.m_countIdx));
	} else {
		//We have to add caps/joins together with the envelope displacements
		//Caps which are not at stroke ends are always imposed to be round.

		if (cPoint.m_hasPrevD) {
			if (cPoint.m_hasNextD)
				(this->*m_addSideCaps)(oPoints, cPoint);
			else if (cPoint.m_chunkIdx == m_lastChunk && cPoint.m_t == 1.0)
				(this->*m_addEndCap)(oPoints, cPoint);
			else
				addRoundEndCap(oPoints, cPoint);
		} else {
			if (cPoint.m_hasNextD)
				if (cPoint.m_chunkIdx == 0 && cPoint.m_t == 0.0)
					(this->*m_addBeginCap)(oPoints, cPoint);
				else
					addRoundBeginCap(oPoints, cPoint);
			else
				addCircle(oPoints, cPoint);
		}
	}
}

//------------------------------------------------------------

/*!
  Translates a CenterlinePoint instance into bounding box points,
  and adds them to the supplied (bbox) rect.
*/
void tellipticbrush::OutlineBuilder::buildOutlineExtensions(
	TRectD &bbox, const CenterlinePoint &cPoint)
{
	if (!(cPoint.m_hasPrevD && cPoint.m_hasNextD &&
		  cPoint.m_prevD == cPoint.m_nextD)) {
		//Only non-envelope points are interesting to the bbox builder procedure

		if (cPoint.m_hasPrevD) {
			if (cPoint.m_hasNextD &&
				m_addSideCaps_ext)
				(this->*m_addSideCaps_ext)(bbox, cPoint);
			else if (cPoint.m_chunkIdx == m_lastChunk && cPoint.m_t == 1.0 &&
					 m_addEndCap_ext)
				(this->*m_addEndCap_ext)(bbox, cPoint);
		} else {
			if (cPoint.m_hasNextD)
				if (cPoint.m_chunkIdx == 0 && cPoint.m_t == 0.0 &&
					m_addBeginCap_ext)
					(this->*m_addBeginCap_ext)(bbox, cPoint);
		}
	}
}

//------------------------------------------------------------

void tellipticbrush::OutlineBuilder::addCircularArcPoints(
	int idx, std::vector<TOutlinePoint> &outPoints,
	const TPointD &center, const TPointD &ray, double angle, int nAngles,
	int countIdx)
{
	TPointD rotRay(ray);

	//Push the initial point without rotation
	outPoints[idx] = TOutlinePoint(center + ray, countIdx);
	idx += 2;

	//Build the rotation
	double sin_a = sin(angle); //NOTE: The 'angle' input parameter CANNOT be substituted with just cos,
	double cos_a = cos(angle); //while sin = sqrt(1.0 - sq(cos)), BECAUSE this way sin is ALWAYS > 0

	int i;
	for (i = 1; i <= nAngles; ++i, idx += 2) {
		rotRay = TPointD(
			rotRay.x * cos_a - rotRay.y * sin_a,
			rotRay.x * sin_a + rotRay.y * cos_a);
		outPoints[idx] = center + rotRay;
	}
}

//------------------------------------------------------------

void tellipticbrush::OutlineBuilder::addCircle(
	std::vector<TOutlinePoint> &oPoints, const CenterlinePoint &cPoint)
{
	//Build the angle step for (0, pi)
	int nAngles;
	double stepAngle, totAngle = angle(TPointD(1.0, 0.0), TPointD(-1.0, 0.0));

	buildAngularSubdivision(cPoint.m_p.thick, totAngle, m_pixSize, nAngles);
	stepAngle = totAngle / (double)nAngles;

	//Resize the vector to store the required points
	int idx = oPoints.size();
	oPoints.resize(oPoints.size() + 2 * (nAngles + 1), TOutlinePoint(TPointD()));

	//Add the circle points from each semi-circle
	addCircularArcPoints(idx, oPoints,
						 convert(cPoint.m_p), TPointD(cPoint.m_p.thick, 0.0),
						 -stepAngle, nAngles, cPoint.m_countIdx);
	addCircularArcPoints(idx + 1, oPoints,
						 convert(cPoint.m_p), TPointD(cPoint.m_p.thick, 0.0),
						 stepAngle, nAngles, cPoint.m_countIdx);
}

//------------------------------------------------------------

void tellipticbrush::OutlineBuilder::addRoundBeginCap(
	std::vector<TOutlinePoint> &oPoints, const CenterlinePoint &cPoint)
{
	TPointD rightD;
	buildEnvelopeVector(cPoint.m_p, cPoint.m_nextD, false, rightD);

	TPointD beginD(-convert(cPoint.m_nextD));
	beginD = (cPoint.m_p.thick / norm(beginD)) * beginD;

	int nAngles;
	double stepAngle, totAngle = angle(beginD, rightD);

	buildAngularSubdivision(cPoint.m_p.thick, totAngle, m_pixSize, nAngles);
	stepAngle = totAngle / (double)nAngles;

	int idx = oPoints.size();
	oPoints.resize(oPoints.size() + 2 * (nAngles + 1), TOutlinePoint(TPointD()));

	addCircularArcPoints(idx, oPoints,
						 convert(cPoint.m_p), beginD,
						 stepAngle, nAngles, cPoint.m_countIdx);
	addCircularArcPoints(idx + 1, oPoints,
						 convert(cPoint.m_p), beginD,
						 -stepAngle, nAngles, cPoint.m_countIdx); //we just need to take the opposite angle to deal with left side
}

//------------------------------------------------------------

void tellipticbrush::OutlineBuilder::addRoundEndCap(
	std::vector<TOutlinePoint> &oPoints, const CenterlinePoint &cPoint)
{
	//Build the backward envelope directions
	//Note that the situation is specular on the left and right side...
	TPointD leftD, rightD;
	buildEnvelopeVector(cPoint.m_p, cPoint.m_prevD, true, leftD);
	buildEnvelopeVector(cPoint.m_p, cPoint.m_prevD, false, rightD);

	int nAngles;
	double stepAngle, totAngle = angle(rightD, convert(cPoint.m_prevD));

	buildAngularSubdivision(cPoint.m_p.thick, totAngle, m_pixSize, nAngles);
	stepAngle = totAngle / (double)nAngles;

	int idx = oPoints.size();
	oPoints.resize(oPoints.size() + 2 * (nAngles + 1), TOutlinePoint(TPointD()));

	addCircularArcPoints(idx, oPoints,
						 convert(cPoint.m_p), rightD,
						 stepAngle, nAngles, cPoint.m_countIdx);
	addCircularArcPoints(idx + 1, oPoints,
						 convert(cPoint.m_p), leftD,
						 -stepAngle, nAngles, cPoint.m_countIdx); //we just need to take the opposite angle to deal with left side
}

//------------------------------------------------------------

void tellipticbrush::OutlineBuilder::addButtBeginCap(
	std::vector<TOutlinePoint> &oPoints, const CenterlinePoint &cPoint)
{
	//Just add the 2 basic envelope points
	TPointD leftDNext, rightDNext;
	buildEnvelopeVectors(cPoint.m_p, cPoint.m_nextD, leftDNext, rightDNext);

	//PLUS, add their midpoint, since it generates this part of stroke antialias...
	TPointD leftP(convert(cPoint.m_p) + leftDNext), rightP(convert(cPoint.m_p) + rightDNext);
	TPointD midP(0.5 * (leftP + rightP));

	oPoints.push_back(midP);
	oPoints.push_back(midP);

	oPoints.push_back(TOutlinePoint(rightP, cPoint.m_countIdx));
	oPoints.push_back(TOutlinePoint(leftP, cPoint.m_countIdx));
}

//------------------------------------------------------------

void tellipticbrush::OutlineBuilder::addButtEndCap(
	std::vector<TOutlinePoint> &oPoints, const CenterlinePoint &cPoint)
{
	TPointD leftDPrev, rightDPrev;
	buildEnvelopeVectors(cPoint.m_p, cPoint.m_prevD, leftDPrev, rightDPrev);

	TPointD leftP(convert(cPoint.m_p) + leftDPrev), rightP(convert(cPoint.m_p) + rightDPrev);
	TPointD midP(0.5 * (leftP + rightP));

	oPoints.push_back(TOutlinePoint(convert(cPoint.m_p) + rightDPrev, cPoint.m_countIdx));
	oPoints.push_back(TOutlinePoint(convert(cPoint.m_p) + leftDPrev, cPoint.m_countIdx));

	oPoints.push_back(midP);
	oPoints.push_back(midP);
}

//------------------------------------------------------------

template <typename T>
void tellipticbrush::OutlineBuilder::addProjectingBeginCap(
	T &oPoints, const CenterlinePoint &cPoint)
{
	double thick = cPoint.m_p.thick;

	//Find the base points
	TPointD leftDNext, rightDNext;
	buildEnvelopeDirections(cPoint.m_p, cPoint.m_nextD, leftDNext, rightDNext);

	TPointD leftP(convert(cPoint.m_p) + thick * leftDNext);
	TPointD rightP(convert(cPoint.m_p) + thick * rightDNext);

	//Add the intersections between the envelope directions' orthogonals and the
	//direction orthogonals
	TPointD dir(normalize(-cPoint.m_nextD));
	TPointD dirP(convert(cPoint.m_p) + thick * dir);

	TPointD cornerLCoords = intersectionCoords(
		dirP, TPointD(dir.y, -dir.x), leftP, TPointD(-leftDNext.y, leftDNext.x));

	TPointD cornerRCoords = intersectionCoords(
		dirP, TPointD(-dir.y, dir.x), rightP, TPointD(rightDNext.y, -rightDNext.x));

	if (cornerLCoords.x < 0 || cornerRCoords.y < 0)
		return;

	//As before, midPoints must be added due to antialias
	TPointD cornerL(dirP + cornerLCoords.x * TPointD(dir.y, -dir.x));
	TPointD cornerR(dirP + cornerRCoords.x * TPointD(-dir.y, dir.x));
	TPointD midP(0.5 * (cornerL + cornerR));

	addEnvelopePoint(oPoints, midP);
	addEnvelopePoint(oPoints, midP);

	addExtensionPoint(oPoints, cornerR);
	addExtensionPoint(oPoints, cornerL);

	//Initial points must be added later, in the begin case
	addEnvelopePoint(oPoints, rightP, cPoint.m_countIdx);
	addEnvelopePoint(oPoints, leftP, cPoint.m_countIdx);
}

//------------------------------------------------------------

template <typename T>
void tellipticbrush::OutlineBuilder::addProjectingEndCap(
	T &oPoints, const CenterlinePoint &cPoint)
{
	double thick = cPoint.m_p.thick;

	//Add the base points
	TPointD leftDPrev, rightDPrev;
	buildEnvelopeDirections(cPoint.m_p, cPoint.m_prevD, leftDPrev, rightDPrev);

	TPointD leftP(convert(cPoint.m_p) + thick * leftDPrev);
	TPointD rightP(convert(cPoint.m_p) + thick * rightDPrev);

	addEnvelopePoint(oPoints, rightP, cPoint.m_countIdx);
	addEnvelopePoint(oPoints, leftP, cPoint.m_countIdx);

	//Add the intersections between the envelope directions' orthogonals and the
	//direction orthogonals
	TPointD dir(normalize(cPoint.m_prevD));
	TPointD dirP(convert(cPoint.m_p) + thick * dir);

	TPointD cornerLCoords = intersectionCoords(
		dirP, TPointD(-dir.y, dir.x), leftP, TPointD(leftDPrev.y, -leftDPrev.x));

	TPointD cornerRCoords = intersectionCoords(
		dirP, TPointD(dir.y, -dir.x), rightP, TPointD(-rightDPrev.y, rightDPrev.x));

	if (cornerLCoords.x < 0 || cornerRCoords.y < 0)
		return;

	TPointD cornerL(dirP + cornerLCoords.x * TPointD(-dir.y, dir.x));
	TPointD cornerR(dirP + cornerRCoords.x * TPointD(dir.y, -dir.x));
	TPointD midP(0.5 * (cornerL + cornerR));

	addExtensionPoint(oPoints, cornerR);
	addExtensionPoint(oPoints, cornerL);

	addEnvelopePoint(oPoints, midP);
	addEnvelopePoint(oPoints, midP);
}

//------------------------------------------------------------

void tellipticbrush::OutlineBuilder::addRoundSideCaps(
	std::vector<TOutlinePoint> &oPoints, const CenterlinePoint &cPoint)
{
	//Side caps - this has only sense when the backward and forward direction-derivatives
	//are different. This means that thay build different envelope directions. So, we add
	//side caps to cover the 'elbow fractures'

	TPointD leftDPrev, leftDNext, rightDPrev, rightDNext;
	buildEnvelopeVector(cPoint.m_p, cPoint.m_prevD, true, leftDPrev);
	buildEnvelopeVector(cPoint.m_p, cPoint.m_nextD, true, leftDNext);
	buildEnvelopeVector(cPoint.m_p, cPoint.m_prevD, false, rightDPrev);
	buildEnvelopeVector(cPoint.m_p, cPoint.m_nextD, false, rightDNext);

	//This time, angle step is NOT specular
	int nAnglesL, nAnglesR;
	double totAngleL = angle(leftDPrev, leftDNext);
	double totAngleR = angle(rightDPrev, rightDNext);

	//The common case is that these angles have the same sign - thus building
	//opposites arcs of a circle
	if (tsign(totAngleL) != tsign(totAngleR)) {
		//However, there may be exceptions. We must still impose
		//the constraint about 'covering opposite arcs of a circle' -
		//it is necessary to make the outline look consistently filled.

		TPointD prevD(convert(cPoint.m_prevD)), nextD(convert(cPoint.m_nextD));

		//The only dangerous case is when the directions are near-opposed
		if (prevD * nextD < 0) {
			const double twice_pi = 2 * TConsts::pi;

			//Here, we must make one angle its (sign-opposite) 2*pi complement.
			//Keep the angle with the least fabs (smallest 'butterfly intersection')
			if (fabs(totAngleL) < fabs(totAngleR))
				totAngleR = (totAngleR > 0) ? totAngleR - twice_pi : totAngleR + twice_pi;
			else
				totAngleL = (totAngleL > 0) ? totAngleL - twice_pi : totAngleL + twice_pi;
		}
	}

	buildAngularSubdivision(cPoint.m_p.thick, totAngleL, m_pixSize, nAnglesL);
	buildAngularSubdivision(cPoint.m_p.thick, totAngleR, m_pixSize, nAnglesR);

	int nAngles = tmax(nAnglesL, nAnglesR);
	double stepAngleL = totAngleL / (double)nAngles;
	double stepAngleR = totAngleR / (double)nAngles;

	if (nAnglesL == 1 && nAnglesR == 1 &&
		fabs(totAngleL) < 0.525 && fabs(totAngleR) < 0.525) //angle < 30 degrees
	{
		//Simple case
		oPoints.push_back(TOutlinePoint(convert(cPoint.m_p) + rightDPrev, cPoint.m_countIdx));
		oPoints.push_back(TOutlinePoint(convert(cPoint.m_p) + leftDPrev, cPoint.m_countIdx));
	} else {
		int idx = oPoints.size();
		oPoints.resize(oPoints.size() + 2 * (nAngles + 1), TOutlinePoint(TPointD()));

		addCircularArcPoints(idx, oPoints,
							 convert(cPoint.m_p), rightDPrev,
							 stepAngleR, nAngles, cPoint.m_countIdx);
		addCircularArcPoints(idx + 1, oPoints,
							 convert(cPoint.m_p), leftDPrev,
							 stepAngleL, nAngles, cPoint.m_countIdx); //same angle here, as this is just a stroke direction rotation
	}
}

//------------------------------------------------------------

void tellipticbrush::OutlineBuilder::addBevelSideCaps(
	std::vector<TOutlinePoint> &oPoints, const CenterlinePoint &cPoint)
{
	//Build the envelope directions
	TPointD leftDPrev, leftDNext, rightDPrev, rightDNext;
	buildEnvelopeDirections(cPoint.m_p, cPoint.m_prevD, leftDPrev, rightDPrev);
	buildEnvelopeDirections(cPoint.m_p, cPoint.m_nextD, leftDNext, rightDNext);

	//Add at least 2 outline points (the prevs)
	oPoints.push_back(TOutlinePoint(convert(cPoint.m_p) + cPoint.m_p.thick * rightDPrev, cPoint.m_countIdx));
	oPoints.push_back(TOutlinePoint(convert(cPoint.m_p) + cPoint.m_p.thick * leftDPrev, cPoint.m_countIdx));

	//Only add the additional points when at least one of the envelope differences
	//passing from prev to next is above the pixel size
	if (2.0 * cPoint.m_p.thick < m_pixSize)
		return;

	double threshold = sq(m_pixSize / cPoint.m_p.thick);

	double bevelSizeL = norm2(leftDNext - leftDPrev);
	double bevelSizeR = norm2(rightDNext - rightDPrev);

	if (bevelSizeL > threshold || bevelSizeR > threshold) {
		oPoints.push_back(convert(cPoint.m_p) + cPoint.m_p.thick * rightDNext);
		oPoints.push_back(convert(cPoint.m_p) + cPoint.m_p.thick * leftDNext);
	}
}

//------------------------------------------------------------

template <typename T>
void tellipticbrush::OutlineBuilder::addMiterSideCaps(
	T &oPoints, const CenterlinePoint &cPoint)
{
	//Build the elbow side

	TPointD prevD(cPoint.m_prevD);
	prevD = (1.0 / norm(prevD)) * prevD;
	TPointD nextD(cPoint.m_nextD);
	nextD = (1.0 / norm(nextD)) * nextD;

	double cross = prevD.x * nextD.y - prevD.y * nextD.x;
	bool leftSide = (cross < 0); //ie elbow on the left side when turning right

	//Add the intersection point of the outline's tangential extensions
	//'Tangential extensions' are just the orthogonals to envelope directions

	//Build envelope directions
	TPointD envPrevSide, envNextSide;
	buildEnvelopeDirection(cPoint.m_p, cPoint.m_prevD, leftSide, envPrevSide);
	buildEnvelopeDirection(cPoint.m_p, cPoint.m_nextD, leftSide, envNextSide);

	//Build tangential directions
	TPointD prevTangentialD, nextTangentialD;
	if (leftSide) {
		prevTangentialD = TPointD(envPrevSide.y, -envPrevSide.x);
		nextTangentialD = TPointD(-envNextSide.y, envNextSide.x);
	} else {
		prevTangentialD = TPointD(-envPrevSide.y, envPrevSide.x);
		nextTangentialD = TPointD(envNextSide.y, -envNextSide.x);
	}

	//Build the outline points in the envelope directions
	envPrevSide = cPoint.m_p.thick * envPrevSide;
	envNextSide = cPoint.m_p.thick * envNextSide;

	TPointD p0(convert(cPoint.m_p) + envPrevSide);
	TPointD p1(convert(cPoint.m_p) + envNextSide);

	//Set coordinates bounds
	double lowerBound = tmax(cPoint.m_p.thick * m_oOptions.m_miterLower, m_pixSize);
	double upperBound = cPoint.m_p.thick * m_oOptions.m_miterUpper;

	//Build the intersection between the 2 lines
	TPointD cornerCoords(intersectionCoords(p0, prevTangentialD, p1, nextTangentialD));
	if (cornerCoords == TConsts::napd ||
		cornerCoords.x < lowerBound || cornerCoords.y > upperBound ||
		cornerCoords.y < lowerBound || cornerCoords.y > upperBound) {
		//Bevel caps
		addOutlineBuilderFunc(&OutlineBuilder::addBevelSideCaps, oPoints, cPoint);
		return;
	}

	TPointD corner(p0 + cornerCoords.x * prevTangentialD);

	TPointD envPrevNotSide, envNextNotSide;
	buildEnvelopeVector(cPoint.m_p, cPoint.m_prevD, !leftSide, envPrevNotSide);
	buildEnvelopeVector(cPoint.m_p, cPoint.m_nextD, !leftSide, envNextNotSide);

	TPointD notSidePrev(convert(cPoint.m_p) + envPrevNotSide);
	TPointD notSideNext(convert(cPoint.m_p) + envNextNotSide);
	if (leftSide) {
		addEnvelopePoint(oPoints, notSidePrev, cPoint.m_countIdx);
		addEnvelopePoint(oPoints, convert(cPoint.m_p) + envPrevSide, cPoint.m_countIdx);

		addExtensionPoint(oPoints, 0.5 * (notSidePrev + notSideNext));
		addExtensionPoint(oPoints, corner);

		addEnvelopePoint(oPoints, notSideNext);
		addEnvelopePoint(oPoints, convert(cPoint.m_p) + envNextSide);
	} else {
		addEnvelopePoint(oPoints, convert(cPoint.m_p) + envPrevSide, cPoint.m_countIdx);
		addEnvelopePoint(oPoints, notSidePrev, cPoint.m_countIdx);

		addExtensionPoint(oPoints, corner);
		addExtensionPoint(oPoints, 0.5 * (notSidePrev + notSideNext));

		addEnvelopePoint(oPoints, convert(cPoint.m_p) + envNextSide);
		addEnvelopePoint(oPoints, notSideNext);
	}
}

//********************************************************************************
//    Outline Builder Function
//********************************************************************************

namespace
{

int buildCPointsData(const TStroke &stroke, std::vector<CenterlinePoint> &cPoints)
{
	//Build point positions
	unsigned int i, pointsCount = cPoints.size();
	int validPointsCount = 0;
	for (i = 0; i < pointsCount; ++i) {
		CenterlinePoint &cPoint = cPoints[i];

		cPoint.buildPos(stroke);
		cPoint.buildDirs(stroke);

		if (!cPoint.m_covered)
			//Covered points simply cannot build envelope directions (forward nor backward).
			//So, don't consider them
			++validPointsCount;
	}

	if (!validPointsCount) {
		//Only single points may end up here. We just solve the problem
		//uncovering the first point.
		cPoints[0].m_covered = false;
		validPointsCount = 1;
	}

	return validPointsCount;
}

} //namespace

//------------------------------------------------------------

void tellipticbrush::buildOutline(
	const TStroke &stroke, std::vector<CenterlinePoint> &cPoints,
	TStrokeOutline &outline, const OutlinizationData &data)
{
	//Build the centerline points associated with passed stroke parameters
	int outlineSize = buildCPointsData(stroke, cPoints);

	//Reserve the lower bound known to the outline points
	std::vector<TOutlinePoint> &oPoints = outline.getArray();
	oPoints.reserve(2 * outlineSize);

	OutlineBuilder outBuilder(data, stroke);

	//Now, build the outline
	unsigned int i, cPointsCount = cPoints.size();
	for (i = 0;; ++i) {
		//Search the next uncovered point
		for (; i < cPointsCount && cPoints[i].m_covered; ++i)
			;

		if (i >= cPointsCount)
			break;

		//Build the associated outline points
		outBuilder.buildOutlinePoints(oPoints, cPoints[i]);
	}
}

//********************************************************************************
//    Make Outline Implementation
//********************************************************************************

namespace
{

/*
  Quick container to store all the linearization features to be supported.
  \note The set should be appropriately ordered so that linearizator dependance
  can be supported (linearizators may work depending on knowledge of the other
  linearized points)
*/
struct LinearizatorsSet {
	static const int nLinearizators = 3;

	LengthLinearizator m_lengthLinearizator;
	CoverageLinearizator m_coverageLinearizator;
	RecursiveLinearizator m_recursiveLinearizator;

	StrokeLinearizator *m_linearizatorPtrs[nLinearizators];

public:
	LinearizatorsSet(const TStroke &stroke, const OutlinizationData &data)
		: m_lengthLinearizator(&stroke, data.m_options.m_lengthStep), m_coverageLinearizator(&stroke), m_recursiveLinearizator(&stroke, data.m_pixSize)
	{
		m_linearizatorPtrs[0] = &m_lengthLinearizator;
		m_linearizatorPtrs[1] = &m_coverageLinearizator;
		m_linearizatorPtrs[2] = &m_recursiveLinearizator;
	}

	StrokeLinearizator *operator[](int i) { return m_linearizatorPtrs[i]; }
	const int size() const { return nLinearizators; }
};

} //namespace

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

void TOutlineUtil::makeOutline(const TStroke &stroke,
							   TStrokeOutline &outline,
							   const TOutlineUtil::OutlineParameter &options)
{
	//Build outlinization data
	OutlinizationData data(options);

	//Build a set of linearizators for the specified stroke
	LinearizatorsSet linearizators(stroke, data);

	std::vector<CenterlinePoint> cPoints, chunkPoints;
	int i, chunksCount = stroke.getChunkCount();
	for (i = 0; i < chunksCount; ++i) {
		chunkPoints.clear();
		chunkPoints.push_back(CenterlinePoint(i, 0.0));

		int j, linearsCount = linearizators.size();
		for (j = 0; j < linearsCount; ++j) {
			StrokeLinearizator *linearizator = linearizators[j];
			linearizator->linearize(chunkPoints, i);
		}

		//These points are just PUSH_BACK'd to the vector. A sorting must be performed
		//before storing them in the overall centerline points vector
		std::sort(chunkPoints.begin(), chunkPoints.end());

		cPoints.insert(cPoints.end(), chunkPoints.begin(), chunkPoints.end());
	}

	//Build the final point.
	CenterlinePoint last(chunksCount - 1, 1.0);

	//In the selfLoop case, use its info to modify the initial point.
	if (stroke.isSelfLoop()) {
		CenterlinePoint &first = cPoints[0];

		first.buildPos(stroke);
		first.buildDirs(stroke);
		last.buildPos(stroke);
		last.buildDirs(stroke);

		first.m_prevD = last.m_prevD;
		first.m_hasPrevD = last.m_hasPrevD;
		last.m_nextD = first.m_nextD;
		last.m_hasNextD = first.m_hasNextD;
		first.m_covered = last.m_covered = (first.m_covered && last.m_covered);
	}

	cPoints.push_back(last);

	//Now, build the outline associated to the linearized centerline

	//NOTE: It's NOT NECESSARY TO BUILD ALL THE CENTERLINE POINTS BEFORE THIS!
	//It's sufficient to build the outline TOGETHER with the centeraline, for each quadratic!
	buildOutline(stroke, cPoints, outline, data);
}

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

TRectD TOutlineUtil::computeBBox(const TStroke &stroke)
{
	typedef TStroke::OutlineOptions OOpts;

	//First, calculate the usual stroke bbox
	TRectD roundBBox(::computeBBox(stroke));
	const OOpts &oOptions(stroke.outlineOptions());

	if (oOptions.m_capStyle != OOpts::PROJECTING_CAP && oOptions.m_joinStyle != OOpts::MITER_JOIN)
		return roundBBox;

	//Build interesting centerline points (in this case, junction points)
	std::vector<CenterlinePoint> cPoints;
	int i, chunksCount = stroke.getChunkCount();
	for (i = 0; i < chunksCount; ++i) {
		CenterlinePoint cPoint(i, 0.0);

		cPoint.buildPos(stroke);
		cPoint.buildDirs(stroke);
		cPoints.push_back(cPoint);
	}

	//Build the final point.
	CenterlinePoint last(chunksCount - 1, 1.0);

	last.buildPos(stroke);
	last.buildDirs(stroke);

	//In the selfLoop case, use its info to modify the initial point.
	if (stroke.isSelfLoop()) {
		CenterlinePoint &first = cPoints[0];

		first.m_prevD = last.m_prevD;
		first.m_hasPrevD = last.m_hasPrevD;
		last.m_nextD = first.m_nextD;
		last.m_hasNextD = first.m_hasNextD;
		first.m_covered = last.m_covered = (first.m_covered && last.m_covered);
	}

	cPoints.push_back(last);

	//Now, add the associated 'extending' outline points
	OutlineBuilder outBuilder(OutlinizationData(), stroke);
	TRectD extensionBBox(
		(std::numeric_limits<double>::max)(),
		(std::numeric_limits<double>::max)(),
		-(std::numeric_limits<double>::max)(),
		-(std::numeric_limits<double>::max)());

	unsigned int j, cPointsCount = cPoints.size();
	for (j = 0;; ++j) {
		//Search the next uncovered point
		for (; j < cPointsCount && cPoints[j].m_covered; ++j)
			;

		if (j >= cPointsCount)
			break;

		//Build the associated outline points
		outBuilder.buildOutlineExtensions(extensionBBox, cPoints[j]);
	}

	//Finally, merge the 2 bboxes
	return roundBBox + extensionBBox;
}