tahoma2d/toonz/sources/common/tvrender/tvectorbrush.cpp

993 lines
33 KiB
C++
Raw Normal View History

2016-03-19 06:57:51 +13:00
#include "tregion.h"
#include "tregionoutline.h"
#include "tellipticbrushP.h"
#include "tstrokeoutline.h"
using namespace tellipticbrush;
#define USE_LENGTH
//#define DEBUG_DRAW_TANGENTS
//********************************************************************************
// EXPLANATION
//********************************************************************************
/*! \file tellipticbrush.cpp
2016-06-15 18:43:10 +12:00
\brief This code performs the outlinization of a \a brush stroke with respect to
a
2016-03-19 06:57:51 +13:00
secondary \a path stroke. This is used to draw Adobe Illustrator-like
2016-06-15 18:43:10 +12:00
vectors whose brush is itself a custom vector (and by extension, a
complex
2016-03-19 06:57:51 +13:00
vector image).
2016-06-15 18:43:10 +12:00
Generalization: Introduce a repeat % and a superposition % in the
algorithm
2016-03-19 06:57:51 +13:00
*/
/* TECHNICAL:
We have two strokes: one is the 'guideline', the other is the 'brush', and the
2016-06-15 18:43:10 +12:00
purpose of this algorithm is that of bending the brush to follow the
guideline.
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
The brush is supposed to be lying horizontally, inside a rectangle
corresponding
2016-03-19 06:57:51 +13:00
to the bounding box of the image containing the brush.
2016-06-15 18:43:10 +12:00
The line segment connecting the midpoints of the vertical image bbox edges
maps
2016-03-19 06:57:51 +13:00
to the guideline's centerline.
Such mapping makes [bbox.x0, bbox.x1] -> [0, 1] in a linear fashion, where the
image interval represents the parametric space of the guideline.
2016-06-15 18:43:10 +12:00
Each vertical scanline of the bbox is further mapped to the segment extruding
from
2016-03-19 06:57:51 +13:00
the guideline's centerline along its normal (multiplied in length by the
guideline's thickness).
*/
//********************************************************************************
// Geometric helpers
//********************************************************************************
2016-06-15 18:43:10 +12:00
namespace {
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
double getX(const TThickPoint &P0, const TThickPoint &P1, const TThickPoint &P2,
double t) {
double one_t = 1.0 - t;
return P0.x * sq(one_t) + 2.0 * P1.x * t * one_t + P2.x * sq(t);
2016-03-19 06:57:51 +13:00
}
//--------------------------------------------------------------------------------------------
2016-06-15 18:43:10 +12:00
TPointD normal(const TPointD &p, bool left) {
TPointD n(-p.y, p.x);
return (1.0 / norm(n)) * (left ? n : -n);
2016-03-19 06:57:51 +13:00
}
//--------------------------------------------------------------------------------------------
2016-06-15 18:43:10 +12:00
TPointD normal(const TPointD &p) {
return (1.0 / norm(p)) * TPointD(-p.y, p.x);
2016-03-19 06:57:51 +13:00
}
//--------------------------------------------------------------------------------------------
2016-06-15 18:43:10 +12:00
void getHRange(const TThickQuadratic &ttq, double &x0, double &x1) {
const TPointD &P0 = ttq.getP0();
const TPointD &P1 = ttq.getP1();
const TPointD &P2 = ttq.getP2();
// Get the horizontal range of the chunk
x0 = std::min({x0, P0.x, P2.x}), x1 = std::max({x1, P0.x, P2.x});
double t = (P0.x - P1.x) / (P0.x + P2.x - 2.0 * P1.x);
if (t > 0.0 && t < 1.0) {
double x = getX(P0, P1, P2, t);
x0 = std::min(x0, x), x1 = std::max(x1, x);
}
2016-03-19 06:57:51 +13:00
}
2016-06-15 18:43:10 +12:00
void getHRange(const TThickQuadratic &ttq, double t0, double t1, double &x0,
double &x1) {
const TPointD &P0 = ttq.getP0();
const TPointD &P1 = ttq.getP1();
const TPointD &P2 = ttq.getP2();
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
double x0_ = getX(P0, P1, P2, t0);
double x1_ = getX(P0, P1, P2, t1);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// Get the horizontal range of the chunk
x0 = std::min({x0, x0_, x1_}), x1 = std::max({x1, x0_, x1_});
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
double t = (P0.x - P1.x) / (P0.x + P2.x - 2.0 * P1.x);
if (t > t0 && t < t1) {
double x = getX(P0, P1, P2, t);
x0 = std::min(x0, x), x1 = std::max(x1, x);
}
2016-03-19 06:57:51 +13:00
}
2016-06-15 18:43:10 +12:00
void getHRange(const TStroke &stroke, double &x0, double &x1) {
int i, nChunks = stroke.getChunkCount();
for (i = 0; i < nChunks; ++i) getHRange(*stroke.getChunk(i), x0, x1);
2016-03-19 06:57:51 +13:00
}
//********************************************************************************
// Outlinization Data
//********************************************************************************
struct StrokeOutlinizationData final
: public tellipticbrush::OutlinizationData {
2016-06-15 18:43:10 +12:00
double m_x0, m_x1, m_xRange;
double m_y0, m_yScale;
2016-03-19 06:57:51 +13:00
public:
2016-06-15 18:43:10 +12:00
StrokeOutlinizationData() : OutlinizationData() {}
StrokeOutlinizationData(const TStroke &stroke, const TRectD &strokeBox,
const TOutlineUtil::OutlineParameter &options)
: OutlinizationData(options)
, m_x0(strokeBox.x0)
, m_x1(strokeBox.x1)
, m_xRange(m_x1 - m_x0)
, m_y0(0.5 * (strokeBox.y0 + strokeBox.y1))
, m_yScale(1.0 / (strokeBox.y1 - strokeBox.y0)) {}
void buildPoint(const CenterlinePoint &p, bool isNextD, CenterlinePoint &ref,
bool isRefNextD, CenterlinePoint &out);
int buildPoints(const CenterlinePoint &p, CenterlinePoint &ref,
CenterlinePoint *out);
int buildPoints(const TStroke &stroke, const TStroke &path,
CenterlinePoint &cp, CenterlinePoint *out);
bool getChunkAndT_param(const TStroke &path, double x, int &chunk, double &t);
bool getChunkAndT_length(const TStroke &path, double x, int &chunk,
double &t);
double toW(double x);
2016-03-19 06:57:51 +13:00
};
//--------------------------------------------------------------------------------------------
2016-06-15 18:43:10 +12:00
double StrokeOutlinizationData::toW(double x) {
return tcrop((x - m_x0) / m_xRange, 0.0, 1.0);
2016-03-19 06:57:51 +13:00
}
//--------------------------------------------------------------------------------------------
2016-06-15 18:43:10 +12:00
bool StrokeOutlinizationData::getChunkAndT_param(const TStroke &path, double x,
int &chunk, double &t) {
double w = toW(x);
return !path.getChunkAndT(w, chunk, t);
2016-03-19 06:57:51 +13:00
}
//--------------------------------------------------------------------------------------------
2016-06-15 18:43:10 +12:00
bool StrokeOutlinizationData::getChunkAndT_length(const TStroke &path, double x,
int &chunk, double &t) {
double s = toW(x) * path.getLength();
return !path.getChunkAndTAtLength(s, chunk, t);
2016-03-19 06:57:51 +13:00
}
//--------------------------------------------------------------------------------------------
void StrokeOutlinizationData::buildPoint(const CenterlinePoint &p, bool pNextD,
2016-06-15 18:43:10 +12:00
CenterlinePoint &ref, bool refNextD,
CenterlinePoint &out) {
TThickPoint &refD = refNextD ? ref.m_nextD : ref.m_prevD;
const TThickPoint *pD;
TThickPoint *outD;
bool *outHasD;
if (pNextD) {
pD = &p.m_nextD;
outD = &out.m_nextD;
outHasD = &out.m_hasNextD;
} else {
pD = &p.m_prevD;
outD = &out.m_prevD;
outHasD = &out.m_hasPrevD;
}
// Build position
refD = (1.0 / norm(refD)) * refD;
TPointD normalDirection(-refD.y, refD.x);
double yPercentage = (p.m_p.y - m_y0) * m_yScale;
double yRelative = yPercentage * ref.m_p.thick;
double yFactor = ref.m_p.thick * m_yScale;
out.m_p = TThickPoint(ref.m_p.x + yRelative * normalDirection.x,
ref.m_p.y + yRelative * normalDirection.y,
p.m_p.thick * yFactor);
// Build direction
double stretchedDY = pD->x * yPercentage * refD.thick + pD->y * yFactor;
*outD = TThickPoint(refD.x * pD->x - refD.y * stretchedDY,
refD.y * pD->x + refD.x * stretchedDY,
pD->thick * (1.0 + refD.thick));
bool covered = (sq(outD->x) + sq(outD->y) < sq(outD->thick) + tolPar);
out.m_covered = out.m_covered && covered;
*outHasD = *outHasD && !covered;
2016-03-19 06:57:51 +13:00
}
//--------------------------------------------------------------------------------------------
/* EXPLANATION:
\ _ \ The path stroke with centerline point C has 2 outward
\ \ \ directions (prevD and nextD), which may be different.
2016-06-15 18:43:10 +12:00
\ |* \ Therefore, there may also be 2 different envelope
directions
2016-03-19 06:57:51 +13:00
\ * . \ from C (the *s in the drawing).
C . | When the brush stroke 'hits' one envelope direction, it
/ * . / transfers on the other e.d., and continues on that side.
/ _ / * /
/ / /
| /
*/
2016-06-15 18:43:10 +12:00
//! Build points resulting from the association between p (must have pos and
//! dirs
//! already built) and ref, returning the number of output points stored in
//! out[] (at max 2).
int StrokeOutlinizationData::buildPoints(const CenterlinePoint &p,
CenterlinePoint &ref,
CenterlinePoint *out) {
2016-06-20 14:23:05 +12:00
out[0] = out[1] = p;
2016-06-15 18:43:10 +12:00
out[0].m_covered = out[1].m_covered = true; // Coverage is rebuilt
bool refSymmetric =
ref.m_hasPrevD && ref.m_hasNextD && ref.m_nextD == ref.m_prevD;
bool pSymmetric = p.m_hasPrevD && p.m_hasNextD && p.m_nextD == p.m_prevD;
// Build prev
bool prevSideIsNext =
(p.m_prevD.x < 0) ? true : (p.m_prevD.x > 0) ? false : ref.m_hasNextD;
bool hasPrev =
p.m_hasPrevD && (prevSideIsNext ? ref.m_hasNextD : ref.m_hasPrevD);
int prevIdx = hasPrev ? 0 : -1;
if (hasPrev) {
CenterlinePoint &outPoint = out[prevIdx];
buildPoint(p, false, ref, prevSideIsNext, outPoint);
}
if (refSymmetric && pSymmetric) {
// Copy prev to next
if (hasPrev) {
CenterlinePoint &outPoint = out[prevIdx];
outPoint.m_hasNextD = outPoint.m_hasPrevD;
outPoint.m_nextD = outPoint.m_prevD;
return 1;
}
return 0;
}
// Build next
bool nextSideIsNext =
(p.m_nextD.x > 0) ? true : (p.m_nextD.x < 0) ? false : ref.m_hasNextD;
bool hasNext =
p.m_hasNextD && (nextSideIsNext ? ref.m_hasNextD : ref.m_hasPrevD);
int nextIdx =
hasNext ? hasPrev ? ((int)prevSideIsNext != nextSideIsNext) : 0 : -1;
if (hasNext) {
CenterlinePoint &outPoint = out[nextIdx];
buildPoint(p, true, ref, nextSideIsNext, outPoint);
}
// Fill in unbuilt directions if necessary
if (hasPrev && hasNext && prevIdx != nextIdx) {
CenterlinePoint &outPrev = out[prevIdx];
CenterlinePoint &outNext = out[nextIdx];
if (dist(outPrev.m_p, outNext.m_p) > 1e-4) {
// If there are 2 full output points, make their unbuilt directions match
outPrev.m_nextD = outNext.m_prevD = 0.5 * (outNext.m_p - outPrev.m_p);
bool covered = (sq(outPrev.m_nextD.x) + sq(outPrev.m_nextD.y) <
sq(outPrev.m_nextD.thick) + tolPar);
outPrev.m_hasNextD = outNext.m_hasPrevD = !covered;
outPrev.m_covered = outPrev.m_covered && covered;
outNext.m_covered = outNext.m_covered && covered;
} else {
// Merge the 2 existing ones
nextIdx = prevIdx;
outPrev.m_nextD = outNext.m_nextD;
outPrev.m_hasNextD = outNext.m_hasPrevD;
outPrev.m_covered = outPrev.m_covered && outNext.m_covered;
}
}
return std::max(prevIdx, nextIdx) + 1;
2016-03-19 06:57:51 +13:00
}
//--------------------------------------------------------------------------------------------
2016-06-15 18:43:10 +12:00
int StrokeOutlinizationData::buildPoints(const TStroke &stroke,
const TStroke &path,
CenterlinePoint &cp,
CenterlinePoint *out) {
const TThickQuadratic &ttq = *stroke.getChunk(cp.m_chunkIdx);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
const TThickPoint &P0 = ttq.getP0();
const TThickPoint &P1 = ttq.getP1();
const TThickPoint &P2 = ttq.getP2();
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
double x = getX(P0, P1, P2, cp.m_t);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
double pathT;
int pathChunk;
2016-03-19 06:57:51 +13:00
#ifdef USE_LENGTH
2016-06-15 18:43:10 +12:00
bool ok = getChunkAndT_length(path, x, pathChunk, pathT);
2016-03-19 06:57:51 +13:00
#else
2016-06-15 18:43:10 +12:00
bool ok = getChunkAndT_param(path, x, pathChunk, pathT);
2016-03-19 06:57:51 +13:00
#endif
2016-06-15 18:43:10 +12:00
assert(ok);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
CenterlinePoint pathCp(pathChunk, pathT);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
cp.buildPos(stroke);
cp.buildDirs(stroke);
pathCp.buildPos(path);
pathCp.buildDirs(path);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
return buildPoints(cp, pathCp, out);
2016-03-19 06:57:51 +13:00
}
//********************************************************************************
// Path-Altered Brush Linearizator (base class)
//********************************************************************************
2016-06-15 18:43:10 +12:00
class ReferenceLinearizator : public tellipticbrush::StrokeLinearizator {
2016-03-19 06:57:51 +13:00
protected:
2016-06-15 18:43:10 +12:00
const TStroke *m_path;
StrokeOutlinizationData m_data;
2016-03-19 06:57:51 +13:00
public:
2016-06-15 18:43:10 +12:00
ReferenceLinearizator(const TStroke *stroke, const TStroke *path,
const StrokeOutlinizationData &data);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
virtual void linearize(std::vector<CenterlinePoint> &cPoints, int chunk,
double t1) = 0;
2016-03-19 06:57:51 +13:00
};
//--------------------------------------------------------------------------------------------
2016-06-15 18:43:10 +12:00
ReferenceLinearizator::ReferenceLinearizator(
const TStroke *stroke, const TStroke *path,
const StrokeOutlinizationData &data)
: StrokeLinearizator(stroke), m_path(path), m_data(data) {}
2016-03-19 06:57:51 +13:00
//********************************************************************************
// Brush Linearizator on Path inter-chunk points
//********************************************************************************
class ReferenceChunksLinearizator final : public ReferenceLinearizator {
2016-06-15 18:43:10 +12:00
double m_w0, m_w1;
2016-03-19 06:57:51 +13:00
public:
2016-06-15 18:43:10 +12:00
ReferenceChunksLinearizator(const TStroke *stroke, const TStroke *path,
const StrokeOutlinizationData &data)
: ReferenceLinearizator(stroke, path, data) {}
2016-03-19 06:57:51 +13:00
2016-06-19 20:06:29 +12:00
void linearize(std::vector<CenterlinePoint> &cPoints, int chunk) override;
2016-06-20 14:23:05 +12:00
void linearize(std::vector<CenterlinePoint> &cPoints, int chunk,
double t1) override;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
void addCenterlinePoints(std::vector<CenterlinePoint> &cPoints,
int brushChunk, double x0, double x1);
void addCenterlinePoints(std::vector<CenterlinePoint> &cPoints,
int strokeChunk, double strokeT, int refChunk);
2016-03-19 06:57:51 +13:00
};
//--------------------------------------------------------------------------------------------
2016-06-15 18:43:10 +12:00
void ReferenceChunksLinearizator::linearize(
std::vector<CenterlinePoint> &cPoints, int chunk) {
// Get the stroke chunk
const TThickQuadratic &ttq = *this->m_stroke->getChunk(chunk);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// Get the chunk's horizontal range
double x0 = (std::numeric_limits<double>::max)(), x1 = -x0;
getHRange(ttq, x0, x1);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// Now, we have to add all points corresponding to the intersections between
// the relative
// vertical projection of path's chunk endpoints, and the stroke
addCenterlinePoints(cPoints, chunk, x0, x1);
2016-03-19 06:57:51 +13:00
}
//--------------------------------------------------------------------------------------------
2016-06-15 18:43:10 +12:00
void ReferenceChunksLinearizator::linearize(
std::vector<CenterlinePoint> &cPoints, int chunk, double t1) {
if (cPoints.empty()) return;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// Get the stroke chunk
const TThickQuadratic &ttq = *this->m_stroke->getChunk(chunk);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// Get the chunk's horizontal range
double x0 = (std::numeric_limits<double>::max)(), x1 = -x0;
getHRange(ttq, cPoints[0].m_t, t1, x0, x1);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// Now, we have to add all points corresponding to the intersections between
// the relative
// vertical projection of path's chunk endpoints, and the stroke
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
addCenterlinePoints(cPoints, chunk, x0, x1);
2016-03-19 06:57:51 +13:00
}
//--------------------------------------------------------------------------------------------
2016-06-15 18:43:10 +12:00
void ReferenceChunksLinearizator::addCenterlinePoints(
std::vector<CenterlinePoint> &cPoints, int chunk, double x0, double x1) {
const TThickQuadratic &ttq = *this->m_stroke->getChunk(chunk);
const TStroke &path = *this->m_path;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
int chunk0, chunk1;
double t0, t1;
2016-03-19 06:57:51 +13:00
#ifdef USE_LENGTH
2016-06-15 18:43:10 +12:00
bool ok0 = m_data.getChunkAndT_length(path, x0, chunk0, t0);
bool ok1 = m_data.getChunkAndT_length(path, x1, chunk1, t1);
2016-03-19 06:57:51 +13:00
#else
2016-06-15 18:43:10 +12:00
bool ok0 = m_data.getChunkAndT_param(path, x0, chunk0, t0);
bool ok1 = m_data.getChunkAndT_param(path, x1, chunk1, t1);
2016-03-19 06:57:51 +13:00
#endif
2016-06-15 18:43:10 +12:00
assert(ok0 && ok1);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
const TPointD &P0 = ttq.getP0();
const TPointD &P1 = ttq.getP1();
const TPointD &P2 = ttq.getP2();
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
double A = P0.x + P2.x - 2.0 * P1.x;
double B = P1.x - P0.x;
double delta_ = sq(B) - P0.x * A; // actual delta = delta_ + x * A;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
int i, initialSize = cPoints.size();
for (i = chunk0; i < chunk1; ++i) {
2016-03-19 06:57:51 +13:00
#ifdef USE_LENGTH
2016-06-15 18:43:10 +12:00
double s = std::min(path.getLength(i, 1.0) / path.getLength(), 1.0);
double x = m_data.m_x0 + m_data.m_xRange * s;
2016-03-19 06:57:51 +13:00
#else
2016-06-15 18:43:10 +12:00
double w = path.getW(i, 1.0);
double x = m_data.m_x0 + m_data.m_xRange * w;
2016-03-19 06:57:51 +13:00
#endif
2016-06-15 18:43:10 +12:00
double delta = delta_ + x * A;
if (delta < 0) continue;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// Add first solution
double t = (sqrt(delta) - B) / A;
if (t > 0.0 && t < 1.0) // 0 and 1 are dealt outside
addCenterlinePoints(cPoints, chunk, t, i);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
if (delta < tolPar) continue;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// Add second solution
t = -(sqrt(delta) + B) / A;
if (t > 0.0 && t < 1.0) addCenterlinePoints(cPoints, chunk, t, i);
}
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// As points may be mixed (by parameter), sort them.
std::sort(cPoints.begin() + initialSize, cPoints.end());
2016-03-19 06:57:51 +13:00
}
//--------------------------------------------------------------------------------------------
void ReferenceChunksLinearizator::addCenterlinePoints(
2016-06-15 18:43:10 +12:00
std::vector<CenterlinePoint> &cPoints, int strokeChunk, double strokeT,
int refChunk) {
CenterlinePoint p(strokeChunk, strokeT);
CenterlinePoint ref(refChunk, 1.0);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
CenterlinePoint newPoints[2];
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
p.buildPos(*m_stroke);
p.buildDirs(*m_stroke);
ref.buildPos(*m_path);
ref.buildDirs(*m_path);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
int i, count = m_data.buildPoints(p, ref, newPoints);
for (i = 0; i < count; ++i) cPoints.push_back(newPoints[i]);
2016-03-19 06:57:51 +13:00
}
//********************************************************************************
// Recursive (regular) Reference Stroke Linearizator
//********************************************************************************
class RecursiveReferenceLinearizator final : public ReferenceLinearizator {
2016-03-19 06:57:51 +13:00
public:
2016-06-15 18:43:10 +12:00
typedef void (RecursiveReferenceLinearizator::*SubdivisorFuncPtr)(
std::vector<CenterlinePoint> &cPoints, CenterlinePoint &cp0,
CenterlinePoint &cp1);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
SubdivisorFuncPtr m_subdivisor;
2016-03-19 06:57:51 +13:00
public:
2016-06-19 20:06:29 +12:00
void linearize(std::vector<CenterlinePoint> &cPoints, int chunk) override;
2016-06-20 14:23:05 +12:00
void linearize(std::vector<CenterlinePoint> &cPoints, int chunk,
double t1) override;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
void subdivide(std::vector<CenterlinePoint> &cPoints, CenterlinePoint &cp0,
CenterlinePoint &cp1);
void subdivideCenterline(std::vector<CenterlinePoint> &cPoints,
CenterlinePoint &cp0, CenterlinePoint &cp1);
2016-03-19 06:57:51 +13:00
public:
2016-06-15 18:43:10 +12:00
RecursiveReferenceLinearizator(const TStroke *stroke, const TStroke *path,
const StrokeOutlinizationData &data)
: ReferenceLinearizator(stroke, path, data)
, m_subdivisor(&RecursiveReferenceLinearizator::subdivide) {}
2016-03-19 06:57:51 +13:00
};
//--------------------------------------------------------------------------------------------
void RecursiveReferenceLinearizator::linearize(
2016-06-15 18:43:10 +12:00
std::vector<CenterlinePoint> &cPoints, int chunk) {
linearize(cPoints, chunk, 1.0);
2016-03-19 06:57:51 +13:00
}
//--------------------------------------------------------------------------------------------
void RecursiveReferenceLinearizator::linearize(
2016-06-15 18:43:10 +12:00
std::vector<CenterlinePoint> &cPoints, int chunk, double t1) {
if (cPoints.empty()) return;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
const TStroke &stroke = *this->m_stroke;
const TThickQuadratic &ttq = *stroke.getChunk(chunk);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
const TStroke &path = *this->m_path;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// Sort the interval (SHOULD BE DONE OUTSIDE?)
std::stable_sort(cPoints.begin(), cPoints.end());
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
std::vector<CenterlinePoint> addedPoints;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
unsigned int i, size_1 = cPoints.size() - 1;
for (i = 0; i < size_1; ++i) {
CenterlinePoint &cp1 = cPoints[i], cp2 = cPoints[i + 1];
if (cp2.m_t - cp1.m_t > 1e-4)
(this->*m_subdivisor)(addedPoints, cPoints[i], cPoints[i + 1]);
}
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
if (cPoints[size_1].m_t < t1) {
double t, x = (t1 == 1.0) ? ttq.getP2().x
: getX(ttq.getP0(), ttq.getP1(), ttq.getP2(), t1);
int refChunk;
2016-03-19 06:57:51 +13:00
#ifdef USE_LENGTH
2016-06-15 18:43:10 +12:00
bool ok = m_data.getChunkAndT_length(path, x, refChunk, t);
2016-03-19 06:57:51 +13:00
#else
2016-06-15 18:43:10 +12:00
bool ok = m_data.getChunkAndT_param(path, x, refChunk, t);
2016-03-19 06:57:51 +13:00
#endif
2016-06-15 18:43:10 +12:00
CenterlinePoint strokeCpEnd(chunk, t1);
CenterlinePoint refCp(refChunk, t);
CenterlinePoint newPoints[2];
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
strokeCpEnd.buildPos(*m_stroke);
strokeCpEnd.buildDirs(*m_stroke);
refCp.buildPos(*m_path);
refCp.buildDirs(*m_path);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
int count = m_data.buildPoints(strokeCpEnd, refCp, newPoints);
if (count == 1) // Otherwise, this is either impossible, or already covered
(this->*m_subdivisor)(addedPoints, cPoints[size_1], newPoints[0]);
}
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
cPoints.insert(cPoints.end(), addedPoints.begin(), addedPoints.end());
2016-03-19 06:57:51 +13:00
}
//--------------------------------------------------------------------------------------------
void RecursiveReferenceLinearizator::subdivide(
2016-06-15 18:43:10 +12:00
std::vector<CenterlinePoint> &cPoints, CenterlinePoint &cp0,
CenterlinePoint &cp1) {
if (!(cp0.m_hasNextD && cp1.m_hasPrevD)) return;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
const TStroke &stroke = *this->m_stroke;
const TThickQuadratic &ttq = *stroke.getChunk(cp0.m_chunkIdx);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
const TStroke &path = *this->m_path;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// Build the distance of next from the outline of cp's 'envelope extension'
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
TPointD envDirL0, envDirR0, envDirL1, envDirR1;
buildEnvelopeDirections(cp0.m_p, cp0.m_nextD, envDirL0, envDirR0);
buildEnvelopeDirections(cp1.m_p, cp1.m_prevD, envDirL1, envDirR1);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
TPointD diff(convert(cp1.m_p) - convert(cp0.m_p));
double d = std::max(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)));
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
if (d > m_data.m_pixSize && cp1.m_t - cp0.m_t > 1e-4) {
CenterlinePoint strokeMidPoint(cp0.m_chunkIdx, 0.5 * (cp0.m_t + cp1.m_t));
CenterlinePoint newPoints[2];
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
int count = m_data.buildPoints(*this->m_stroke, *this->m_path,
strokeMidPoint, newPoints);
if (count == 1) // Otherwise, this is either impossible, or already covered
{
subdivide(cPoints, cp0,
newPoints[0]); // should I use strokeMidPoint(Prev) here ?
subdivide(cPoints, newPoints[0], cp1);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
cPoints.push_back(newPoints[0]);
}
}
2016-03-19 06:57:51 +13:00
}
//--------------------------------------------------------------------------------------------
void RecursiveReferenceLinearizator::subdivideCenterline(
2016-06-15 18:43:10 +12:00
std::vector<CenterlinePoint> &cPoints, CenterlinePoint &cp0,
CenterlinePoint &cp1) {
if (cp0.m_covered || !cp0.m_hasNextD) return;
// Build the distance of next from cp's 'direction extension'
TPointD dir((1.0 / norm(cp0.m_nextD)) * cp0.m_nextD);
TPointD diff(convert(cp1.m_p) - convert(cp0.m_p));
double d = fabs(dir.x * diff.y - dir.y * diff.x);
if (d > m_data.m_pixSize && cp1.m_t - cp0.m_t > 1e-4) {
CenterlinePoint strokeMidPoint(cp0.m_chunkIdx, 0.5 * (cp0.m_t + cp1.m_t));
CenterlinePoint newPoints[2];
int count = m_data.buildPoints(*this->m_stroke, *this->m_path,
strokeMidPoint, newPoints);
if (count == 1) // Otherwise, this is either impossible, or already covered
{
subdivide(cPoints, cp0,
newPoints[0]); // should I use strokeMidPoint(Prev) here ?
subdivide(cPoints, newPoints[0], cp1);
cPoints.push_back(newPoints[0]);
}
}
2016-03-19 06:57:51 +13:00
}
//********************************************************************************
// Make Outline Implementation (stroke version)
//********************************************************************************
//********************************************************************************
// Make Outline Implementation (stroke version)
//********************************************************************************
/*
Quick container to store all the linearization features to be supported.
\note The set should be appropriately ordered so that linearizator dependence
2016-03-19 06:57:51 +13:00
can be supported (linearizators may work depending on knowledge of the other
linearized points)
*/
struct LinearizatorsSet {
2016-06-15 18:43:10 +12:00
static const int nLinearizators = 2;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
ReferenceChunksLinearizator m_refChunksLinearizator;
RecursiveReferenceLinearizator m_recursiveRefLinearizator;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
ReferenceLinearizator *m_linearizatorPtrs[nLinearizators];
2016-03-19 06:57:51 +13:00
public:
2016-06-15 18:43:10 +12:00
LinearizatorsSet(const TStroke &stroke, const TStroke &path,
const StrokeOutlinizationData &data)
: m_refChunksLinearizator(&stroke, &path, data)
, m_recursiveRefLinearizator(&stroke, &path, data) {
m_linearizatorPtrs[0] = &m_refChunksLinearizator;
m_linearizatorPtrs[1] = &m_recursiveRefLinearizator;
}
ReferenceLinearizator *operator[](int i) { return m_linearizatorPtrs[i]; }
const int size() const { return nLinearizators; }
2016-03-19 06:57:51 +13:00
};
2016-06-15 18:43:10 +12:00
} // namespace
2016-03-19 06:57:51 +13:00
//============================================================================================
void TOutlineUtil::makeOutline(const TStroke &path, const TStroke &stroke,
2016-06-15 18:43:10 +12:00
const TRectD &strokeBox, TStrokeOutline &outline,
const TOutlineUtil::OutlineParameter &options) {
// Build outlinization data
StrokeOutlinizationData data(stroke, strokeBox, options);
// Build a set of linearizators for the specified stroke
LinearizatorsSet linearizators(stroke, path, data);
CenterlinePoint newPoints[2];
std::vector<CenterlinePoint> cPoints, chunkPoints;
int i, chunksCount = stroke.getChunkCount();
for (i = 0; i < chunksCount; ++i) {
chunkPoints.clear();
CenterlinePoint cp(i, 0.0);
int j, count = data.buildPoints(stroke, path, cp, newPoints);
for (j = 0; j < count; ++j) chunkPoints.push_back(newPoints[j]);
int 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::stable_sort(chunkPoints.begin(), chunkPoints.end());
cPoints.insert(cPoints.end(), chunkPoints.begin(), chunkPoints.end());
}
// Build the final point.
CenterlinePoint cPoint(chunksCount - 1, 1.0);
int count = data.buildPoints(stroke, path, cPoint, newPoints);
for (i = 0; i < count; ++i) cPoints.push_back(newPoints[i]);
// If no centerline point was built, no outline point can, too.
// This specifically happens when the supplied path is a point.
if (cPoints.empty()) return;
// In the selfLoop case, use its info to modify the initial point.
if (stroke.isSelfLoop()) {
CenterlinePoint &lastCp = cPoints[cPoints.size() - 1];
cPoints[0].m_prevD = cPoint.m_prevD;
cPoints[0].m_hasPrevD = true;
lastCp.m_nextD = cPoint.m_prevD;
lastCp.m_hasNextD = true;
}
2016-03-19 06:57:51 +13:00
#ifdef DEBUG_DRAW_TANGENTS
2016-06-15 18:43:10 +12:00
{
// Debug - draw centerline directions (derivatives)
glBegin(GL_LINES);
glColor3d(1.0, 0.0, 0.0);
unsigned int i, size = cPoints.size();
for (i = 0; i < size; ++i) {
glVertex2d(cPoints[i].m_p.x, cPoints[i].m_p.y);
glVertex2d(
cPoints[i].m_p.x + cPoints[i].m_nextD.x * cPoints[i].m_p.thick,
cPoints[i].m_p.y + cPoints[i].m_nextD.y * cPoints[i].m_p.thick);
}
glEnd();
}
2016-03-19 06:57:51 +13:00
#endif
2016-06-15 18:43:10 +12:00
// Now, build the outline associated to the linearized centerline
buildOutline(stroke, cPoints, outline, data);
2016-03-19 06:57:51 +13:00
}
//============================================================================================
/*
TRectD TOutlineUtil::computeBBox(const TStroke &stroke, const TStroke& path)
{
typedef TStroke::OutlineOptions OOpts;
//First, calculate the usual stroke bbox
TRectD roundBBox(::computeBBox(stroke));
const OOpts& oOptions(stroke.outlineOptions());
2016-06-15 18:43:10 +12:00
if(oOptions.m_capStyle != OOpts::PROJECTING_CAP && oOptions.m_joinStyle !=
OOpts::MITER_JOIN)
2016-03-19 06:57:51 +13:00
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 cPoint(chunksCount-1, 1.0);
cPoint.buildPos(stroke);
cPoint.buildDirs(stroke);
//In the selfLoop case, use its info to modify the initial point.
if(stroke.isSelfLoop())
{
cPoints[0].m_prevD = cPoint.m_prevD;
cPoints[0].m_hasPrevD = true;
cPoint.m_nextD = cPoint.m_prevD;
cPoint.m_hasNextD = true;
}
cPoints.push_back(cPoint);
//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;
}
*/
//============================================================================================
2016-06-15 18:43:10 +12:00
namespace {
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
void makeCenterline(const TStroke &path, const TEdge &edge,
const TRectD &regionBox, std::vector<T3DPointD> &outline) {
int initialOutlineSize = outline.size();
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
static const TOutlineUtil::OutlineParameter options;
const TStroke &stroke = *edge.m_s;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
double w0 = edge.m_w0, w1 = edge.m_w1;
bool reversed = w1 < w0;
if (reversed) w0 = edge.m_w1, w1 = edge.m_w0;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// Build outlinization data
StrokeOutlinizationData data(stroke, regionBox, options);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// Build a set of linearizators for the specified stroke
LinearizatorsSet linearizators(stroke, path, data);
CenterlinePoint newPoints[2];
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
linearizators.m_recursiveRefLinearizator.m_subdivisor =
&RecursiveReferenceLinearizator::subdivideCenterline;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
std::vector<CenterlinePoint> chunkPoints;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
int i, chunk0, chunk1;
double t0, t1;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
bool ok0 = !edge.m_s->getChunkAndT(w0, chunk0, t0);
bool ok1 = !edge.m_s->getChunkAndT(w1, chunk1, t1);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
assert(ok0 && ok1);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
double tStart = t0;
for (i = chunk0; i < chunk1; ++i, tStart = 0.0) {
chunkPoints.clear();
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
CenterlinePoint cp(i, tStart);
int j, count = data.buildPoints(stroke, path, cp, newPoints);
for (j = 0; j < count; ++j) chunkPoints.push_back(newPoints[j]);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
int linearsCount = linearizators.size();
for (j = 0; j < linearsCount; ++j) {
ReferenceLinearizator *linearizator = linearizators[j];
linearizator->linearize(chunkPoints, i, 1.0);
}
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// 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::stable_sort(chunkPoints.begin(), chunkPoints.end());
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
int size = chunkPoints.size();
outline.reserve(outline.size() + size);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
for (j = 0; j < size; ++j) {
const TPointD &point = chunkPoints[j].m_p;
outline.push_back(T3DPointD(point.x, point.y, 0.0));
}
}
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// The last one with t1 as endpoint
{
chunkPoints.clear();
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
CenterlinePoint cp(chunk1, tStart);
int j, count = data.buildPoints(stroke, path, cp, newPoints);
for (j = 0; j < count; ++j) chunkPoints.push_back(newPoints[j]);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
int linearsCount = linearizators.size();
for (j = 0; j < linearsCount; ++j) {
ReferenceLinearizator *linearizator = linearizators[j];
linearizator->linearize(chunkPoints, chunk1, t1);
}
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
std::stable_sort(chunkPoints.begin(), chunkPoints.end());
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
int size = chunkPoints.size();
outline.reserve(outline.size() + size);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
for (j = 0; j < size; ++j) {
const TPointD &point = chunkPoints[j].m_p;
outline.push_back(T3DPointD(point.x, point.y, 0.0));
}
}
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// Finally, add the endpoint
CenterlinePoint cp(chunk1, t1);
int j, count = data.buildPoints(stroke, path, cp, newPoints);
for (j = 0; j < count; ++j) {
const TPointD &point = newPoints[j].m_p;
outline.push_back(T3DPointD(point.x, point.y, 0.0));
}
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// Eventually, reverse the output
if (reversed)
std::reverse(outline.begin() + initialOutlineSize, outline.end());
2016-03-19 06:57:51 +13:00
}
//--------------------------------------------------------------------------------------------
2016-06-15 18:43:10 +12:00
void makeOutlineRaw(const TStroke &path, const TRegion &region,
const TRectD &regionBox, std::vector<T3DPointD> &outline) {
// Deal with each edge independently
int e, edgesCount = region.getEdgeCount();
for (e = 0; e < edgesCount; ++e)
makeCenterline(path, *region.getEdge(e), regionBox, outline);
2016-03-19 06:57:51 +13:00
}
2016-06-15 18:43:10 +12:00
} // namespace
2016-03-19 06:57:51 +13:00
//--------------------------------------------------------------------------------------------
2016-06-15 18:43:10 +12:00
void TOutlineUtil::makeOutline(const TStroke &path, const TRegion &region,
const TRectD &regionBox,
TRegionOutline &outline) {
outline.m_doAntialiasing = true;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// Build the external boundary
{
outline.m_exterior.resize(1);
outline.m_exterior[0].clear();
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
makeOutlineRaw(path, region, regionBox, outline.m_exterior[0]);
}
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// Build internal boundaries
{
outline.m_interior.clear();
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
int i, subRegionNumber = region.getSubregionCount();
outline.m_interior.resize(subRegionNumber);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
for (i = 0; i < subRegionNumber; i++)
makeOutlineRaw(path, *region.getSubregion(i), regionBox,
outline.m_interior[i]);
}
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
outline.m_bbox = region.getBBox();
2016-03-19 06:57:51 +13:00
}