1285 lines
42 KiB
C++
1285 lines
42 KiB
C++
|
|
|
|
//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 ¢er, 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;
|
|
}
|