2fc36cb841
* #mingw #bug: place explicit instantiations on templates before first use * #mingw #bug: remove API attributes from inline functions * #mingw #bug: add 'static' attribute for local functions * #mingw #bug: fix API attributes for splitSpeedInOutSegment * #mingw #bug: replace strstream to stringstream * #mingw #bug: remove cross references to plasticskeleton from tnzcore * #mingw #bug: fix bug with order of initializaition of static variables * #mingw #bug: fix glutInit
3790 lines
113 KiB
C++
3790 lines
113 KiB
C++
|
|
|
|
#include "tmachine.h"
|
|
#include "tmathutil.h"
|
|
#include "tstrokeutil.h"
|
|
#include "tstrokeoutline.h"
|
|
#include "tcurves.h"
|
|
#include "tbezier.h"
|
|
#include "tzerofinder.h"
|
|
#include "tcurveutil.h"
|
|
#include "cornerdetector.h"
|
|
|
|
#include <limits>
|
|
|
|
#include "tstroke.h"
|
|
|
|
//=============================================================================
|
|
|
|
#define USE_NEW_3D_ERROR_COMPUTE 1
|
|
|
|
//=============================================================================
|
|
|
|
// Some using declaration
|
|
|
|
using namespace TConsts;
|
|
using namespace std;
|
|
|
|
// Some useful typedefs
|
|
|
|
typedef std::vector<double> DoubleArray;
|
|
typedef DoubleArray::iterator DoubleIt;
|
|
typedef std::vector<TThickQuadratic *> QuadStrokeChunkArray;
|
|
|
|
static int numSaved = 0;
|
|
|
|
namespace {
|
|
//---------------------------------------------------------------------------
|
|
|
|
void extractStrokeControlPoints(const QuadStrokeChunkArray &curves,
|
|
vector<TThickPoint> &ctrlPnts) {
|
|
const TThickQuadratic *prev = curves[0];
|
|
assert(prev);
|
|
const TThickQuadratic *curr;
|
|
|
|
ctrlPnts.push_back(prev->getThickP0());
|
|
ctrlPnts.push_back(prev->getThickP1());
|
|
|
|
for (UINT i = 1; i < curves.size(); ++i) {
|
|
curr = curves[i];
|
|
assert(curr);
|
|
TThickPoint middlePnt = (prev->getThickP2() + curr->getThickP0()) * 0.5;
|
|
ctrlPnts.push_back(middlePnt);
|
|
ctrlPnts.push_back(curr->getThickP1());
|
|
prev = curr;
|
|
}
|
|
|
|
ctrlPnts.push_back(prev->getThickP2());
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
inline TThickPoint adapter(const TThickPoint &tp) { return tp; }
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
inline TThickPoint adapter(const TPointD &p) { return TThickPoint(p); }
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
template <typename T>
|
|
void buildChunksFromControlPoints(QuadStrokeChunkArray &tq,
|
|
const vector<T> &v) {
|
|
TThickQuadratic *chunk;
|
|
T temp;
|
|
switch (v.size()) {
|
|
case 0:
|
|
tq.push_back(new TThickQuadratic);
|
|
break;
|
|
case 1:
|
|
temp = adapter(v.front());
|
|
chunk = new TThickQuadratic(temp, temp, temp);
|
|
tq.push_back(chunk);
|
|
break;
|
|
case 2: {
|
|
TThickSegment s(adapter(v.front()), adapter(v.back()));
|
|
chunk = new TThickQuadratic(s.getThickP0(), s.getThickPoint(0.5),
|
|
s.getThickP1());
|
|
tq.push_back(chunk);
|
|
} break;
|
|
default:
|
|
assert(v.size() & 1); // v.size() == 2 * chunk + 1
|
|
for (UINT i = 0; i < v.size() - 1; i += 2) {
|
|
chunk = new TThickQuadratic(adapter(v[i]), adapter(v[i + 1]),
|
|
adapter(v[i + 2]));
|
|
tq.push_back(chunk);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
// WARNING duplicata in tellipticbrush
|
|
// dovrebbe essere eliminata da tellipticbrush perche' qui
|
|
// viene usata per eliminare le strokes a thickness negativa
|
|
// evitandone la gestione in tellip...
|
|
inline bool pairValuesAreEqual(const DoublePair &p) {
|
|
return p.first == p.second;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// WARNING duplicata in tellipticbrush
|
|
// dovrebbe essere eliminata da tellipticbrush perche' qui
|
|
// viene usata per eliminare le strokes a thickness negativa
|
|
// evitandone la gestione in tellip...
|
|
void analyzeSolution(const vector<double> &coeff,
|
|
vector<DoublePair> &interval) {
|
|
// risolve la disequazione coeff[2]*t^2 + coeff[1]*t + coeff[0] >= 0 in [0,
|
|
// 1] ritornando le soluzioni
|
|
// come sotto-intervalli chiusi di [0, 1] (gli intervalli degeneri [s, s]
|
|
// isolati vengono eliminati)
|
|
vector<double> sol;
|
|
// int numberOfIntervalSolution = 0;
|
|
|
|
rootFinding(coeff, sol);
|
|
|
|
if (isAlmostZero(coeff[2])) {
|
|
// disequazione di 1^ grado
|
|
if (isAlmostZero(coeff[1])) {
|
|
if (coeff[0] >= 0) interval.push_back(DoublePair(0.0, 1.0));
|
|
return;
|
|
}
|
|
|
|
double singleSol = -coeff[0] / coeff[1];
|
|
|
|
if (coeff[1] > 0) {
|
|
if (singleSol < 1)
|
|
interval.push_back(DoublePair(std::max(0.0, singleSol), 1.0));
|
|
} else {
|
|
if (singleSol > 0)
|
|
interval.push_back(DoublePair(0.0, std::min(1.0, singleSol)));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// disequazione di 2^ grado effettivo
|
|
// double delta = sq(coeff[1]) - 4*coeff[2]*coeff[0];
|
|
|
|
sort(sol.begin(), sol.end());
|
|
|
|
if (coeff[2] > 0) {
|
|
switch (sol.size()) {
|
|
case 0:
|
|
interval.push_back(DoublePair(0.0, 1.0));
|
|
break;
|
|
|
|
case 1:
|
|
interval.push_back(DoublePair(0.0, 1.0));
|
|
break;
|
|
|
|
case 2:
|
|
interval.push_back(DoublePair(0.0, std::min(std::max(sol[0], 0.0), 1.0)));
|
|
interval.push_back(DoublePair(std::max(std::min(sol[1], 1.0), 0.0), 1.0));
|
|
break;
|
|
}
|
|
} else if (coeff[2] < 0 && sol.size() == 2)
|
|
interval.push_back(DoublePair(std::min(std::max(sol[0], 0.0), 1.0),
|
|
std::max(std::min(sol[1], 1.0), 0.0)));
|
|
|
|
// eat not valid interval
|
|
std::vector<DoublePair>::iterator it =
|
|
std::remove_if(interval.begin(), interval.end(), pairValuesAreEqual);
|
|
|
|
interval.erase(it, interval.end());
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
void floorNegativeThickness(TThickQuadratic *quad) {
|
|
assert(quad);
|
|
double val = quad->getThickP0().thick;
|
|
|
|
if (val < 0 && isAlmostZero(val))
|
|
quad->setThickP0(TThickPoint(quad->getP0(), 0.0));
|
|
|
|
val = quad->getThickP1().thick;
|
|
if (val < 0 && isAlmostZero(val))
|
|
quad->setThickP1(TThickPoint(quad->getP1(), 0.0));
|
|
|
|
val = quad->getThickP2().thick;
|
|
if (val < 0 && isAlmostZero(val))
|
|
quad->setThickP2(TThickPoint(quad->getP2(), 0.0));
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// potrebbe essere realizzata come unary function da usare in una transform
|
|
void roundNegativeThickess(QuadStrokeChunkArray &v) {
|
|
QuadStrokeChunkArray protoStroke, tempVectTQ;
|
|
|
|
TThickQuadratic *tempTQ = 0, *tempTQ_1 = 0;
|
|
|
|
int chunkCount = v.size();
|
|
|
|
vector<double> coeff;
|
|
double alpha, beta, gamma;
|
|
|
|
vector<DoublePair> positiveIntervals;
|
|
|
|
for (int i = 0; i < chunkCount; ++i) {
|
|
const TThickQuadratic &ttq = *v[i];
|
|
|
|
alpha = ttq.getThickP0().thick - 2 * ttq.getThickP1().thick +
|
|
ttq.getThickP2().thick;
|
|
beta = 2.0 * (ttq.getThickP1().thick - ttq.getThickP0().thick);
|
|
gamma = ttq.getThickP0().thick;
|
|
|
|
coeff.push_back(gamma);
|
|
coeff.push_back(beta);
|
|
coeff.push_back(alpha);
|
|
|
|
// return sotto-intervalli non degeneri di [0, 1] in cui coeff[2]*t^2 +
|
|
// coeff[1]*t + coeff[0] >= 0
|
|
analyzeSolution(coeff, positiveIntervals);
|
|
|
|
// il caso isAlmostZero(r(t)) per t in [0, 1] e' gestito direttamente da
|
|
// computeOutline
|
|
switch (positiveIntervals.size()) {
|
|
case 0: // r(t) <= 0 per t in [0, 1]
|
|
tempTQ = new TThickQuadratic(ttq);
|
|
tempTQ->setThickP0(TThickPoint(tempTQ->getP0(), 0.0));
|
|
tempTQ->setThickP1(TThickPoint(tempTQ->getP1(), 0.0));
|
|
tempTQ->setThickP2(TThickPoint(tempTQ->getP2(), 0.0));
|
|
protoStroke.push_back(tempTQ);
|
|
break;
|
|
case 1:
|
|
if (positiveIntervals[0].first == 0.0 &&
|
|
positiveIntervals[0].second == 1.0)
|
|
protoStroke.push_back(new TThickQuadratic(ttq));
|
|
else if (positiveIntervals[0].first == 0.0) {
|
|
tempTQ = new TThickQuadratic;
|
|
tempTQ_1 = new TThickQuadratic;
|
|
ttq.split(positiveIntervals[0].first, *tempTQ, *tempTQ_1);
|
|
tempTQ_1->setThickP0(TThickPoint(tempTQ_1->getP0(), 0.0));
|
|
tempTQ_1->setThickP1(TThickPoint(tempTQ_1->getP1(), 0.0));
|
|
tempTQ_1->setThickP2(TThickPoint(tempTQ_1->getP2(), 0.0));
|
|
|
|
floorNegativeThickness(tempTQ);
|
|
protoStroke.push_back(tempTQ);
|
|
protoStroke.push_back(tempTQ_1);
|
|
} else if (positiveIntervals[0].second == 1.0) {
|
|
tempTQ = new TThickQuadratic;
|
|
tempTQ_1 = new TThickQuadratic;
|
|
ttq.split(positiveIntervals[0].first, *tempTQ, *tempTQ_1);
|
|
tempTQ->setThickP0(TThickPoint(tempTQ->getP0(), 0.0));
|
|
tempTQ->setThickP1(TThickPoint(tempTQ->getP1(), 0.0));
|
|
tempTQ->setThickP2(TThickPoint(tempTQ->getP2(), 0.0));
|
|
|
|
protoStroke.push_back(tempTQ);
|
|
floorNegativeThickness(tempTQ_1);
|
|
protoStroke.push_back(tempTQ_1);
|
|
} else {
|
|
coeff.clear();
|
|
coeff.push_back(positiveIntervals[0].first);
|
|
coeff.push_back(positiveIntervals[0].second);
|
|
split<TThickQuadratic>(ttq, coeff, tempVectTQ);
|
|
assert(tempVectTQ.size() == 3);
|
|
|
|
tempVectTQ[0]->setThickP0(TThickPoint(tempVectTQ[0]->getP0(), 0.0));
|
|
tempVectTQ[0]->setThickP1(TThickPoint(tempVectTQ[0]->getP1(), 0.0));
|
|
tempVectTQ[0]->setThickP2(TThickPoint(tempVectTQ[0]->getP2(), 0.0));
|
|
|
|
// controllo che i valori prossimi a zero siano in ogni caso positivi
|
|
floorNegativeThickness(tempVectTQ[1]);
|
|
|
|
tempVectTQ[2]->setThickP0(TThickPoint(tempVectTQ[2]->getP0(), 0.0));
|
|
tempVectTQ[2]->setThickP1(TThickPoint(tempVectTQ[2]->getP1(), 0.0));
|
|
tempVectTQ[2]->setThickP2(TThickPoint(tempVectTQ[2]->getP2(), 0.0));
|
|
|
|
copy(tempVectTQ.begin(), tempVectTQ.end(), back_inserter(protoStroke));
|
|
tempVectTQ
|
|
.clear(); // non serve una clearPointerArray perchè il possesso
|
|
// va alla protoStroke
|
|
}
|
|
break;
|
|
case 2:
|
|
assert(positiveIntervals[0].first == 0.0);
|
|
assert(positiveIntervals[1].second == 1.0);
|
|
|
|
coeff.clear();
|
|
coeff.push_back(positiveIntervals[0].second);
|
|
coeff.push_back(positiveIntervals[1].first);
|
|
split<TThickQuadratic>(ttq, coeff, tempVectTQ);
|
|
assert(tempVectTQ.size() == 3);
|
|
|
|
floorNegativeThickness(tempVectTQ[0]);
|
|
|
|
tempVectTQ[1]->setThickP0(TThickPoint(tempVectTQ[1]->getP0(), 0.0));
|
|
tempVectTQ[1]->setThickP1(TThickPoint(tempVectTQ[1]->getP1(), 0.0));
|
|
tempVectTQ[1]->setThickP2(TThickPoint(tempVectTQ[1]->getP2(), 0.0));
|
|
|
|
floorNegativeThickness(tempVectTQ[2]);
|
|
|
|
copy(tempVectTQ.begin(), tempVectTQ.end(), back_inserter(protoStroke));
|
|
tempVectTQ.clear(); // non serve una clearPointerArray perchè il possesso
|
|
|
|
break;
|
|
}
|
|
|
|
positiveIntervals.clear();
|
|
coeff.clear();
|
|
}
|
|
|
|
swap(protoStroke, v);
|
|
clearPointerContainer(protoStroke);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// Some usefuf constant
|
|
// used in printContainer to set number of row to print
|
|
const int MAX_ROW = 10;
|
|
|
|
inline void changeTQDirection(TThickQuadratic *tq) {
|
|
TThickPoint p = tq->getThickP2();
|
|
tq->setThickP2(tq->getThickP0());
|
|
tq->setThickP0(p);
|
|
}
|
|
|
|
/*
|
|
//---------------------------------------------------------------------------
|
|
|
|
double getTQDistance2(const TThickQuadratic& tq,
|
|
const TPointD& p,
|
|
double maxDistance2,
|
|
double& currT)
|
|
{
|
|
TRectD rect = tq.getBBox();
|
|
if (!rect.contains(p))
|
|
{
|
|
double dist21 = tdistance2(p, rect.getP00());
|
|
double dist22 = tdistance2(p, rect.getP01());
|
|
double dist23 = tdistance2(p, rect.getP10());
|
|
double dist24 = tdistance2(p, rect.getP11());
|
|
|
|
if (std::min(dist21, dist22, dist23, dist24)>=maxDistance2)
|
|
return maxDistance2;
|
|
}
|
|
currT = tq.getT(p);
|
|
double dist2 = tdistance2(tq.getPoint(currT), p);
|
|
return (dist2<maxDistance2)?dist2:maxDistance2;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
template <class K, class T>
|
|
void clearMap(std::map<K,T>& v)
|
|
{
|
|
typedef std::map<K,T>::iterator TypeIt;
|
|
|
|
TypeIt it = v.begin();
|
|
|
|
for( ;it!=v.end(); ++it)
|
|
delete it->second;
|
|
|
|
v.clear();
|
|
}
|
|
*/
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
/*
|
|
Local binary function to find approx values in a vector.
|
|
*/
|
|
bool bfAreAlmostEqual(double x, double y) {
|
|
double x_y = (x > y) ? x - y : y - x;
|
|
return x_y < TConsts::epsilon;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
/*
|
|
Sends values of a container in standard input.
|
|
*/
|
|
template <class T>
|
|
void printContainer(const T &c, int maxRow = MAX_ROW) {
|
|
/* DA DECOMMENTARE SE NECESSARIO!!!!!
|
|
//(commentato per non avere dipendenze da tsystem.lib 7/1/2004)
|
|
if (maxRow <=0 ) maxRow = MAX_ROW;
|
|
|
|
typename T::const_iterator cit;
|
|
cit = c.begin();
|
|
stringstream oss1;
|
|
oss1<<'['<<c.size()<<']'<<"=\n";
|
|
TSystem::outputDebug( oss1.str() );
|
|
|
|
int counter = 0;
|
|
for( ; cit != c.end(); ++cit)
|
|
{
|
|
stringstream oss;
|
|
if( ++counter == maxRow-1)
|
|
{
|
|
oss<<'\n';
|
|
counter = 0;
|
|
}
|
|
oss<<(*cit)<<'\n'<<'\0';
|
|
TSystem::outputDebug( oss.str() );
|
|
}
|
|
*/
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
template <class T>
|
|
void printContainer(ostream &os, const T &c, int maxRow = MAX_ROW) {
|
|
if (maxRow <= 0) maxRow = MAX_ROW;
|
|
typename T::const_iterator cit;
|
|
cit = c.begin();
|
|
os << '[' << c.size() << ']' << "=\n";
|
|
int counter = 0;
|
|
for (; cit != c.end(); ++cit) {
|
|
if (++counter == maxRow - 1) {
|
|
os << '\n';
|
|
counter = 0;
|
|
}
|
|
os << (*cit) << ' ';
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
template <class T>
|
|
void printContainerOfPointer(ostream &os, const T &c, int maxRow = MAX_ROW) {
|
|
if (maxRow <= 0) maxRow = MAX_ROW;
|
|
typename T::const_iterator cit;
|
|
cit = c.begin();
|
|
os << '[' << c.size() << ']' << " - ";
|
|
int counter = 0;
|
|
for (; cit != c.end(); ++cit) {
|
|
if (++counter == maxRow - 1) {
|
|
os << '\n';
|
|
counter = 0;
|
|
}
|
|
os << **cit << ' ';
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
/*
|
|
Compute a proportion of type x:a=b:c
|
|
*/
|
|
template <class T>
|
|
T proportion(T a, T b, T c) {
|
|
assert(c != T(0));
|
|
return a * b / c;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
/*
|
|
Compute a proportion of type x-off:a-off=b:c
|
|
*/
|
|
template <class T>
|
|
T proportion(T a, T b, T c, T offset) {
|
|
assert(c != T(0));
|
|
return (a - offset) * b / c + offset;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
/*!
|
|
backinserter
|
|
*/
|
|
template <typename type_, typename container_>
|
|
class TBackInserterPointer {
|
|
container_ &m_c;
|
|
|
|
public:
|
|
explicit TBackInserterPointer(container_ &c) : m_c(c){};
|
|
|
|
TBackInserterPointer &operator=(const type_ *value) {
|
|
m_c.push_back(new type_(*value));
|
|
return *this;
|
|
}
|
|
|
|
TBackInserterPointer &operator=(const type_ &value) {
|
|
m_c.push_back(new type_(value));
|
|
return *this;
|
|
};
|
|
#ifdef MACOSX
|
|
typedef type_ value_type;
|
|
typedef type_ &reference;
|
|
typedef type_ *pointer;
|
|
typedef output_iterator_tag iterator_category;
|
|
typedef ptrdiff_t difference_type;
|
|
#endif
|
|
/* SIC */
|
|
TBackInserterPointer &operator*() { return *this; }
|
|
TBackInserterPointer &operator++() { return *this; }
|
|
TBackInserterPointer operator++(int val) { return *this; }
|
|
};
|
|
|
|
typedef TBackInserterPointer<TThickQuadratic, QuadStrokeChunkArray>
|
|
TThickQuadraticArrayInsertIterator;
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
/*!
|
|
simple adapter for find zero algorithm
|
|
*/
|
|
struct computeOffset_ {
|
|
TQuadraticLengthEvaluator m_lengthEval;
|
|
double m_offset;
|
|
|
|
computeOffset_(const TThickQuadratic *ttq, double offset)
|
|
: m_lengthEval(*ttq), m_offset(offset) {}
|
|
|
|
double operator()(double par) {
|
|
return m_lengthEval.getLengthAt(par) - m_offset;
|
|
}
|
|
};
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
/*!
|
|
simple adapter for find zero algorithm
|
|
*/
|
|
struct computeSpeed_ {
|
|
const TThickQuadratic *ref_;
|
|
|
|
computeSpeed_(const TThickQuadratic *ref) : ref_(ref) {}
|
|
|
|
double operator()(double par) { return norm(ref_->getSpeed(par)); }
|
|
};
|
|
|
|
//---------------------------------------------------------------------------
|
|
} // end of unnamed namespace
|
|
|
|
//=============================================================================
|
|
|
|
const BYTE TStroke::c_selected_flag = 0x1;
|
|
const BYTE TStroke::c_changed_region_flag = 0x2;
|
|
const BYTE TStroke::c_dirty_flag = 0x4;
|
|
|
|
//=============================================================================
|
|
//
|
|
// TStroke::Imp
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
struct TStroke::Imp {
|
|
// Geometry-related infos
|
|
|
|
BYTE m_flag;
|
|
|
|
//! This flag checks if changes occurs and if it is neccessary to update
|
|
//! length.
|
|
bool m_isValidLength;
|
|
|
|
//! This flag checks if changes occurs and if it is neccessary to update
|
|
//! outline.
|
|
bool m_isOutlineValid;
|
|
|
|
//! Control calculus of cache vector.
|
|
bool m_areDisabledComputeOfCaches;
|
|
|
|
//! Bounding Box of a stroke
|
|
TRectD m_bBox;
|
|
|
|
//! This vector contains length computed for each control point of stroke.
|
|
DoubleArray m_partialLengthArray;
|
|
|
|
//! This vector contains parameter computed for each control point of stroke.
|
|
DoubleArray m_parameterValueAtControlPoint;
|
|
|
|
//! This vector contains outline of stroke.
|
|
QuadStrokeChunkArray m_centerLineArray;
|
|
|
|
bool m_selfLoop;
|
|
|
|
int m_negativeThicknessPoints;
|
|
double m_averageThickness;
|
|
double m_maxThickness;
|
|
|
|
//-------------------------------
|
|
|
|
// Not-geometrical vars (style infos)
|
|
|
|
int m_id;
|
|
|
|
int m_styleId;
|
|
TStrokeProp *m_prop;
|
|
|
|
TStroke::OutlineOptions m_outlineOptions;
|
|
|
|
//-------------------------------
|
|
|
|
Imp();
|
|
|
|
Imp(const vector<TPointD> &v);
|
|
|
|
Imp(const vector<TThickPoint> &v);
|
|
|
|
~Imp() {
|
|
delete m_prop;
|
|
clearPointerContainer(m_centerLineArray);
|
|
// delete m_style;
|
|
}
|
|
|
|
void init();
|
|
|
|
// void computeOutlines( double pixelSize );
|
|
|
|
inline double getW(int index) {
|
|
return ((int)m_parameterValueAtControlPoint.size() > index)
|
|
? m_parameterValueAtControlPoint[index]
|
|
: m_parameterValueAtControlPoint.back();
|
|
}
|
|
|
|
TRectD computeCenterlineBBox();
|
|
/*!
|
|
It computes the bounding box of the subStroke in the parameter range w0-w1.
|
|
*/
|
|
|
|
void computeMaxThickness();
|
|
|
|
TRectD computeSubBBox(double w0, double w1) const;
|
|
|
|
inline QuadStrokeChunkArray &getTQArray() { return m_centerLineArray; }
|
|
|
|
/*!
|
|
Swaps the geometrical infos only
|
|
*/
|
|
void swapGeometry(TStroke::Imp &other) throw();
|
|
|
|
//! compute cache vector
|
|
void computeCacheVector();
|
|
|
|
/*!
|
|
Set value in m_parameterValueAtControlPoint
|
|
*/
|
|
void computeParameterInControlPoint();
|
|
|
|
/*!
|
|
Update parameter in m_parameterValueAtControlPoint
|
|
after insert of control point in stroke.
|
|
*/
|
|
void updateParameterValue(double w, UINT chunk, TThickQuadratic *tq1,
|
|
TThickQuadratic *tq2);
|
|
|
|
/*!
|
|
From parameter w retrieves chunk and its parameter ( t in [0,1] ).
|
|
Return
|
|
true -> error (parameter w out of range, etc)
|
|
false -> ok
|
|
*/
|
|
bool retrieveChunkAndItsParamameter(double w, int &chunk, double &t);
|
|
|
|
/*!
|
|
From length s retrieves chunk and its parameter ( t in [0,1] ).
|
|
Return
|
|
true -> error (parameter w out of range, etc)
|
|
false -> ok
|
|
*/
|
|
bool retrieveChunkAndItsParamameterAtLength(double s, int &chunk, double &t);
|
|
|
|
/*!
|
|
Retrieve chunk which contains the n-th control point of stroke.
|
|
If control point is between two chunks return the left point.
|
|
*/
|
|
int retrieveChunkFromControlPointIndex(int n) {
|
|
assert(0 <= n && n < getControlPointCount());
|
|
|
|
if (n & 1) ++n;
|
|
|
|
n >>= 1;
|
|
|
|
return n ? n - 1 : n;
|
|
};
|
|
|
|
/*!
|
|
Retrieve range for a chunk.
|
|
*/
|
|
DoublePair retrieveParametersFromChunk(UINT chunk) {
|
|
DoublePair outPar;
|
|
|
|
int nFirst, nSecond;
|
|
|
|
nFirst = chunk * 2;
|
|
nSecond = (chunk + 1) * 2;
|
|
|
|
outPar.first = getW(nFirst);
|
|
outPar.second = getW(nSecond);
|
|
|
|
return outPar;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
void print(ostream &os);
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
inline int getChunkCount() const { return m_centerLineArray.size(); }
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
inline int getControlPointCount() const {
|
|
UINT out = 2 * getChunkCount() + 1;
|
|
return out;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TThickQuadratic *getChunk(int index) {
|
|
if (0 <= index && index < getChunkCount()) return m_centerLineArray[index];
|
|
|
|
return 0;
|
|
}
|
|
|
|
private:
|
|
// Declared but not defined.
|
|
Imp(const Imp &other);
|
|
Imp &operator=(const Imp &other);
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
namespace {
|
|
int maxStrokeId = 0;
|
|
}
|
|
|
|
/*! init() is required to initialize all variable
|
|
Call init after centerline initialization, because
|
|
it's necessary to compute BBox
|
|
*/
|
|
void TStroke::Imp::init() {
|
|
m_flag = c_dirty_flag;
|
|
|
|
m_styleId = 1; // DefaultStrokeStyle;
|
|
m_prop = 0;
|
|
|
|
m_id = ++maxStrokeId;
|
|
m_isValidLength = false;
|
|
m_isOutlineValid = false;
|
|
m_areDisabledComputeOfCaches = false;
|
|
m_selfLoop = false;
|
|
m_averageThickness = 0;
|
|
m_maxThickness = -1;
|
|
m_negativeThicknessPoints = 0;
|
|
for (UINT j = 0; j < m_centerLineArray.size(); j++) {
|
|
if (m_centerLineArray[j]->getThickP0().thick <= 0)
|
|
m_negativeThicknessPoints++;
|
|
if (m_centerLineArray[j]->getThickP1().thick <= 0)
|
|
m_negativeThicknessPoints++;
|
|
}
|
|
if (!m_centerLineArray.empty() &&
|
|
m_centerLineArray.back()->getThickP2().thick <= 0)
|
|
m_negativeThicknessPoints++;
|
|
|
|
computeParameterInControlPoint();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TStroke::Imp::Imp() { init(); }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TStroke::Imp::Imp(const std::vector<TThickPoint> &v) {
|
|
buildChunksFromControlPoints(m_centerLineArray, v);
|
|
roundNegativeThickess(m_centerLineArray);
|
|
|
|
init();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TStroke::Imp::Imp(const std::vector<TPointD> &v) {
|
|
buildChunksFromControlPoints(m_centerLineArray, v);
|
|
roundNegativeThickess(m_centerLineArray);
|
|
|
|
init();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::Imp::swapGeometry(Imp &other) throw() {
|
|
std::swap(m_flag, other.m_flag);
|
|
std::swap(m_isValidLength, other.m_isValidLength);
|
|
std::swap(m_isOutlineValid, other.m_isOutlineValid);
|
|
std::swap(m_areDisabledComputeOfCaches, other.m_areDisabledComputeOfCaches);
|
|
std::swap(m_bBox, other.m_bBox);
|
|
std::swap(m_partialLengthArray, other.m_partialLengthArray);
|
|
std::swap(m_parameterValueAtControlPoint,
|
|
other.m_parameterValueAtControlPoint);
|
|
std::swap(m_centerLineArray, other.m_centerLineArray);
|
|
std::swap(m_selfLoop, other.m_selfLoop);
|
|
std::swap(m_negativeThicknessPoints, other.m_negativeThicknessPoints);
|
|
std::swap(m_averageThickness, other.m_averageThickness);
|
|
std::swap(m_maxThickness, other.m_maxThickness);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::Imp::computeMaxThickness() {
|
|
m_maxThickness = m_centerLineArray[0]->getThickP0().thick;
|
|
for (UINT i = 0; i < m_centerLineArray.size(); i++)
|
|
m_maxThickness =
|
|
std::max({m_maxThickness, m_centerLineArray[i]->getThickP1().thick,
|
|
m_centerLineArray[i]->getThickP2().thick});
|
|
}
|
|
|
|
void TStroke::Imp::computeCacheVector() {
|
|
// se la stroke e' stata invalidata a causa dell'inserimento di punti
|
|
// di controllo o dal ricampionamento
|
|
if (!m_areDisabledComputeOfCaches && !m_isValidLength) {
|
|
if (getChunkCount() > 0) // se ci sono cionchi
|
|
{
|
|
// (re)inizializzo un vettore
|
|
m_partialLengthArray.resize(getControlPointCount(),
|
|
(std::numeric_limits<double>::max)());
|
|
|
|
m_partialLengthArray[0] = 0.0;
|
|
|
|
double length = 0.0;
|
|
int j = 0;
|
|
const TThickQuadratic *tq;
|
|
|
|
TQuadraticLengthEvaluator lengthEvaluator;
|
|
|
|
for (int i = 0; i < getChunkCount(); ++i) {
|
|
assert(j <= getControlPointCount());
|
|
tq = getChunk(i);
|
|
lengthEvaluator.setQuad(*tq);
|
|
m_partialLengthArray[j++] = length;
|
|
m_partialLengthArray[j++] = length + lengthEvaluator.getLengthAt(0.5);
|
|
length += lengthEvaluator.getLengthAt(1.0);
|
|
}
|
|
|
|
m_partialLengthArray[j++] = length;
|
|
assert(j == getControlPointCount());
|
|
// assert( m_parameterValueAtControlPoint.size() ==
|
|
// m_partialLengthArray.size() );
|
|
}
|
|
m_isValidLength = true;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::Imp::computeParameterInControlPoint() {
|
|
if (!m_areDisabledComputeOfCaches) {
|
|
// questa funzione ricalcola i valori dei parametri nei cionchi
|
|
// N.B. deve essere richiamata quando si effettuano inserimenti
|
|
// di punti di controllo che cambiano la lunghezza della curva
|
|
// insert, push e costruttore
|
|
if (!getChunkCount()) {
|
|
m_parameterValueAtControlPoint.clear();
|
|
return;
|
|
}
|
|
|
|
int controlPointCount = getControlPointCount();
|
|
|
|
m_parameterValueAtControlPoint.resize(controlPointCount, 0);
|
|
|
|
// N.B. number of control point is reduced of 1
|
|
--controlPointCount;
|
|
|
|
double val = 0.0;
|
|
|
|
assert(controlPointCount >= 0.0);
|
|
|
|
for (int i = 0; i <= controlPointCount; ++i) {
|
|
val = i / (double)controlPointCount;
|
|
m_parameterValueAtControlPoint[i] = val;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::Imp::updateParameterValue(double w, UINT chunk,
|
|
TThickQuadratic *tq1,
|
|
TThickQuadratic *tq2) {
|
|
DoublePair p = retrieveParametersFromChunk(chunk);
|
|
|
|
UINT controlPointToErase = 2 * chunk + 1;
|
|
DoubleIt it = m_parameterValueAtControlPoint.begin();
|
|
std::advance(it, controlPointToErase);
|
|
m_parameterValueAtControlPoint.erase(it);
|
|
|
|
double normalizedParam = tq2->getT(tq2->getP1());
|
|
|
|
std::vector<double>::iterator first;
|
|
|
|
normalizedParam = proportion(p.second, normalizedParam, 1.0, w);
|
|
|
|
first =
|
|
std::upper_bound(m_parameterValueAtControlPoint.begin(),
|
|
m_parameterValueAtControlPoint.end(), normalizedParam);
|
|
|
|
if (first != m_parameterValueAtControlPoint.end()) {
|
|
first = m_parameterValueAtControlPoint.insert(first, normalizedParam);
|
|
|
|
first = m_parameterValueAtControlPoint.insert(first, w);
|
|
|
|
normalizedParam = tq1->getT(tq1->getP1());
|
|
normalizedParam = proportion(w, normalizedParam, 1.0, p.first);
|
|
|
|
m_parameterValueAtControlPoint.insert(first, normalizedParam);
|
|
}
|
|
|
|
/* FAB
|
|
assert( getControlPointCount() <=
|
|
(int)m_parameterValueAtControlPoint.size() );
|
|
//*/
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool TStroke::getChunkAndTAtLength(double s, int &chunk, double &t) const {
|
|
return m_imp->retrieveChunkAndItsParamameterAtLength(s, chunk, t);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool TStroke::Imp::retrieveChunkAndItsParamameterAtLength(double s, int &chunk,
|
|
double &t) {
|
|
vector<double>::iterator first;
|
|
|
|
// cerco nella cache la posizione che compete alla lunghezza s
|
|
first = std::upper_bound(m_partialLengthArray.begin(),
|
|
m_partialLengthArray.end(), s);
|
|
|
|
// se s e' interna al vettore di cache
|
|
if (first != m_partialLengthArray.end()) {
|
|
// individuo il punto di controllo della stroke...
|
|
int controlPointOffset = distance(m_partialLengthArray.begin(), first);
|
|
|
|
// ...e da questo il cionco relativo.
|
|
chunk = retrieveChunkFromControlPointIndex(controlPointOffset);
|
|
|
|
if (first != m_partialLengthArray.begin() && s == *(first - 1)) {
|
|
controlPointOffset--;
|
|
if (controlPointOffset & 1) {
|
|
const DoublePair &p = retrieveParametersFromChunk(chunk);
|
|
t = proportion(1.0, getW(controlPointOffset) - p.first,
|
|
p.second - p.first);
|
|
} else
|
|
t = 0.0;
|
|
|
|
return false;
|
|
}
|
|
|
|
// fisso un offset per l'algoritmo di bisezione
|
|
double offset = (first == m_partialLengthArray.begin())
|
|
? s
|
|
: s - m_partialLengthArray[chunk * 2];
|
|
|
|
// cerco il parametro minimo a meno di una tolleranza epsilon
|
|
|
|
const double tol = TConsts::epsilon * 0.1;
|
|
int err;
|
|
|
|
computeOffset_ op(getChunk(chunk), offset);
|
|
computeSpeed_ op2(getChunk(chunk));
|
|
|
|
if (!findZero_Newton(0.0, 1.0, op, op2, tol, tol, 100, t, err))
|
|
t = -1; // if can not find a good value set parameter to error value
|
|
|
|
// se l'algoritmo di ricerca ha fallito fissa il valore ad uno dei due
|
|
// estremi
|
|
if (t == -1) {
|
|
if (s <= m_partialLengthArray[controlPointOffset]) t = 0.0;
|
|
t = 1.0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (s <= 0.0) {
|
|
chunk = 0;
|
|
t = 0.0;
|
|
} else if (s >= m_partialLengthArray.back()) {
|
|
chunk = getChunkCount() - 1;
|
|
t = 1.0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool TStroke::getChunkAndT(double w, int &chunk, double &t) const {
|
|
return m_imp->retrieveChunkAndItsParamameter(w, chunk, t);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool TStroke::Imp::retrieveChunkAndItsParamameter(double w, int &chunk,
|
|
double &t) {
|
|
vector<double>::iterator first;
|
|
|
|
// trova l'iteratore alla prima posizione che risulta maggiore o uguale a w
|
|
first = std::lower_bound(m_parameterValueAtControlPoint.begin(),
|
|
m_parameterValueAtControlPoint.end(), w);
|
|
|
|
// se non e' stato possibile trovare w nel vettore ritorna errore
|
|
if (first == m_parameterValueAtControlPoint.end()) return true;
|
|
/* FAB
|
|
double
|
|
found = *first;
|
|
assert(found <= *first);
|
|
//*/
|
|
// individuo il punto di controllo che compete alla posizione nel vettore
|
|
int controlPointOffset =
|
|
distance(m_parameterValueAtControlPoint.begin(), first);
|
|
|
|
// individuo il cionco relativo al punto di controllo
|
|
chunk = retrieveChunkFromControlPointIndex(controlPointOffset);
|
|
|
|
// calcolo il parametro relativo al cionco
|
|
DoublePair p = retrieveParametersFromChunk(chunk);
|
|
/* FAB
|
|
assert( p.first <= w &&
|
|
w <= p.second );
|
|
|
|
#ifdef _DEBUG
|
|
chunk = retrieveChunkFromControlPointIndex( controlPointOffset );
|
|
p = retrieveParametersFromChunk( chunk );
|
|
#endif
|
|
//*/
|
|
|
|
if (w < p.first || w > p.second) {
|
|
t = (p.first + p.second) * 0.5;
|
|
} else
|
|
t = proportion(1.0, w - p.first, p.second - p.first);
|
|
|
|
/* FAB
|
|
assert( 0.0 <= t && t <= 1.0 );
|
|
//*/
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TRectD TStroke::Imp::computeCenterlineBBox() {
|
|
UINT n = m_centerLineArray.size();
|
|
if (m_centerLineArray.empty()) return TRectD();
|
|
TQuadratic q(m_centerLineArray[0]->getP0(), m_centerLineArray[0]->getP1(),
|
|
m_centerLineArray[0]->getP2());
|
|
TRectD bbox = q.getBBox();
|
|
for (UINT i = 1; i < n; i++) {
|
|
q = TQuadratic(m_centerLineArray[i]->getP0(), m_centerLineArray[i]->getP1(),
|
|
m_centerLineArray[i]->getP2());
|
|
bbox += q.getBBox();
|
|
}
|
|
return bbox;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TRectD TStroke::Imp::computeSubBBox(double w0, double w1) const {
|
|
if (m_centerLineArray.empty()) return TRectD();
|
|
|
|
int n = m_centerLineArray.size();
|
|
|
|
TRectD bBox;
|
|
const double eps = 0.000000001;
|
|
int i;
|
|
|
|
if (w0 > w1) tswap(w0, w1);
|
|
|
|
double nw0 = w0 * n;
|
|
double nw1 = w1 * n;
|
|
|
|
int i0 = (int)nw0; // indice della quadrica che contiene w0
|
|
int i1 = (int)nw1; // idem per w1
|
|
|
|
double t0 =
|
|
nw0 -
|
|
(double)i0; // parametro di w0 rispetto alla quadrica che lo contiene
|
|
double t1 = nw1 - (double)i1; // idem per w1
|
|
|
|
if (t0 < eps) // se t0 e' quasi uguale a zero, evito di fare lo split e
|
|
// considero tutta la quadrica i0
|
|
{
|
|
i0--;
|
|
t0 = 1.0;
|
|
}
|
|
if (t1 > (1 - eps)) // se t1 e' quasi uguale a uno, evito di fare lo split e
|
|
// considero tutta la quadrica i1
|
|
{
|
|
i1++;
|
|
t1 = 0.0;
|
|
}
|
|
|
|
TThickQuadratic quadratic1, quadratic2, quadratic3;
|
|
|
|
if (i0 == i1) // i due punti di taglio capitano nella stessa quadratica
|
|
{
|
|
if (t0 < eps && t1 > (1 - eps)) return m_centerLineArray[i0]->getBBox();
|
|
|
|
if (t0 < eps) {
|
|
m_centerLineArray[i0]->split(t1, quadratic1, quadratic2);
|
|
return quadratic1.getBBox();
|
|
}
|
|
|
|
if (t1 > (1 - eps)) {
|
|
m_centerLineArray[i0]->split(t0, quadratic1, quadratic2);
|
|
return quadratic2.getBBox();
|
|
}
|
|
|
|
// quadratic1 e' la quadratica risultante dallo split tra t0 e t1 di
|
|
// m_centerLineArray[i0]
|
|
m_centerLineArray[i0]->split(t0, quadratic1, quadratic2);
|
|
quadratic2.split((t1 - t0) / (1 - t0), quadratic1, quadratic3);
|
|
|
|
return quadratic1.getBBox();
|
|
}
|
|
|
|
// se i due punti di taglio capitano in quadratiche diverse
|
|
|
|
// sommo le bbox di quelle interne
|
|
for (i = i0 + 1; i < i1; i++) bBox += m_centerLineArray[i]->getBBox();
|
|
|
|
// e sommo le bbox delle quadratiche splittate agli estremi se non sono
|
|
// irrilevanti
|
|
if (i0 >= 0 && t0 < (1 - eps)) {
|
|
m_centerLineArray[i0]->split(t0, quadratic1, quadratic2);
|
|
bBox += quadratic2.getBBox();
|
|
}
|
|
|
|
if (i1 < n && t1 > eps) {
|
|
m_centerLineArray[i1]->split(t1, quadratic1, quadratic2);
|
|
bBox += quadratic1.getBBox();
|
|
}
|
|
|
|
return bBox;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::Imp::print(ostream &os) {
|
|
#if defined(_DEBUG) || defined(DEBUG)
|
|
|
|
os << "m_isValidLength:" << m_isValidLength << endl;
|
|
|
|
os << "m_isOutlineValid:" << m_isOutlineValid << endl;
|
|
|
|
os << "m_areDisabledComputeOfCaches:" << m_areDisabledComputeOfCaches << endl;
|
|
|
|
os << "m_bBox:" << m_bBox << endl;
|
|
|
|
os << "m_partialLengthArray";
|
|
printContainer(os, m_partialLengthArray);
|
|
os << endl;
|
|
|
|
os << "m_parameterValueAtControlPoint";
|
|
printContainer(os, m_parameterValueAtControlPoint);
|
|
os << endl;
|
|
|
|
os << "m_centerLineArray";
|
|
// os.setf(myIOFlags::scientific);
|
|
printContainerOfPointer(os, m_centerLineArray);
|
|
os << endl;
|
|
|
|
/*
|
|
vector<TPixel> m_outlineColorArray;
|
|
|
|
vector<TPointD> m_texArray;
|
|
|
|
TFilePath m_filePath;
|
|
TRasterP m_texture;
|
|
*/
|
|
// TSystem::outputDebug( os.str());
|
|
#else
|
|
#endif
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
// Needed to DEBUG
|
|
DEFINE_CLASS_CODE(TStroke, 15)
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Costructor
|
|
TStroke::TStroke() : TSmartObject(m_classCode) {
|
|
vector<TThickPoint> p(3);
|
|
p[0] = TThickPoint(0, 0, 0);
|
|
p[1] = p[0];
|
|
p[2] = p[1];
|
|
|
|
m_imp.reset(new TStroke::Imp(p));
|
|
|
|
/*
|
|
// da fissare deve trovarsi prima della init
|
|
m_imp->m_centerLineArray.push_back ( new TThickQuadratic );
|
|
*/
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Build a stroke from a set of ThickPoint
|
|
TStroke::TStroke(const vector<TThickPoint> &v)
|
|
: TSmartObject(m_classCode), m_imp(new TStroke::Imp(v)) {}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TStroke::TStroke(const vector<TPointD> &v)
|
|
: TSmartObject(m_classCode), m_imp(new TStroke::Imp(v)) {}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TStroke::~TStroke() {}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TStroke::TStroke(const TStroke &other)
|
|
: TSmartObject(m_classCode), m_imp(new TStroke::Imp()) {
|
|
m_imp->m_bBox = other.getBBox();
|
|
m_imp->m_isValidLength = other.m_imp->m_isValidLength;
|
|
m_imp->m_isOutlineValid = other.m_imp->m_isOutlineValid;
|
|
m_imp->m_areDisabledComputeOfCaches =
|
|
other.m_imp->m_areDisabledComputeOfCaches;
|
|
m_imp->m_flag = other.m_imp->m_flag;
|
|
m_imp->m_outlineOptions = other.m_imp->m_outlineOptions;
|
|
|
|
// Are they sure as regards exceptions ?
|
|
m_imp->m_centerLineArray.resize(other.m_imp->m_centerLineArray.size());
|
|
int i;
|
|
for (i = 0; i < (int)other.m_imp->m_centerLineArray.size(); i++)
|
|
m_imp->m_centerLineArray[i] =
|
|
new TThickQuadratic(*other.m_imp->m_centerLineArray[i]);
|
|
|
|
// copy( other.m_imp->m_centerLineArray.begin(),
|
|
// other.m_imp->m_centerLineArray.end(),
|
|
// TThickQuadraticArrayInsertIterator(m_imp->m_centerLineArray));
|
|
|
|
copy(other.m_imp->m_partialLengthArray.begin(),
|
|
other.m_imp->m_partialLengthArray.end(),
|
|
back_inserter<DoubleArray>(m_imp->m_partialLengthArray));
|
|
copy(other.m_imp->m_parameterValueAtControlPoint.begin(),
|
|
other.m_imp->m_parameterValueAtControlPoint.end(),
|
|
back_inserter<DoubleArray>(m_imp->m_parameterValueAtControlPoint));
|
|
|
|
m_imp->m_styleId = other.m_imp->m_styleId;
|
|
m_imp->m_prop =
|
|
0; // other.m_imp->m_prop ? other.m_imp->m_prop->clone(this) : 0;
|
|
m_imp->m_selfLoop = other.m_imp->m_selfLoop;
|
|
m_imp->m_negativeThicknessPoints = other.m_imp->m_negativeThicknessPoints;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TStroke &TStroke::operator=(const TStroke &other) {
|
|
TStroke temp(other);
|
|
swap(temp);
|
|
return *this;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool TStroke::getNearestW(const TPointD &p, double &outW, double &dist2,
|
|
bool checkBBox) const {
|
|
double outT;
|
|
int chunkIndex;
|
|
bool ret = getNearestChunk(p, outT, chunkIndex, dist2, checkBBox);
|
|
if (ret) outW = getW(chunkIndex, outT);
|
|
return ret;
|
|
}
|
|
|
|
bool TStroke::getNearestChunk(const TPointD &p, double &outT, int &chunkIndex,
|
|
double &dist2, bool checkBBox) const {
|
|
dist2 = (numeric_limits<double>::max)();
|
|
|
|
for (UINT i = 0; i < m_imp->m_centerLineArray.size(); i++) {
|
|
if (checkBBox &&
|
|
!m_imp->m_centerLineArray[i]->getBBox().enlarge(30).contains(p))
|
|
continue;
|
|
|
|
double t = (m_imp->m_centerLineArray)[i]->getT(p);
|
|
double dist = tdistance2((m_imp->m_centerLineArray)[i]->getPoint(t), p);
|
|
|
|
if (dist < dist2) {
|
|
dist2 = dist;
|
|
chunkIndex = i;
|
|
outT = t;
|
|
}
|
|
}
|
|
|
|
return dist2 < (numeric_limits<double>::max)();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// finds all points on stroke which are "enough" close to point p. return the
|
|
// number of such points.
|
|
|
|
int TStroke::getNearChunks(const TThickPoint &p,
|
|
vector<TThickPoint> &pointsOnStroke,
|
|
bool checkBBox) const {
|
|
int currIndex = -100;
|
|
double currDist2 = 100000;
|
|
|
|
for (UINT i = 0; i < m_imp->m_centerLineArray.size(); i++) {
|
|
TThickQuadratic *q = m_imp->m_centerLineArray[i];
|
|
|
|
if (checkBBox && !q->getBBox().enlarge(30).contains(p)) continue;
|
|
|
|
double t = q->getT(p);
|
|
TThickPoint p1 = q->getThickPoint(t);
|
|
double dist2 = tdistance2(p1, p);
|
|
|
|
if (dist2 < (p1.thick + p.thick + 5) * (p1.thick + p.thick + 5)) {
|
|
if (!pointsOnStroke.empty() &&
|
|
areAlmostEqual(p1, pointsOnStroke.back(), 1e-3))
|
|
continue;
|
|
|
|
if (currIndex == i - 1) {
|
|
if (dist2 < currDist2)
|
|
pointsOnStroke.pop_back();
|
|
else
|
|
continue;
|
|
}
|
|
|
|
currIndex = i;
|
|
currDist2 = dist2;
|
|
pointsOnStroke.push_back(p1);
|
|
}
|
|
}
|
|
|
|
return pointsOnStroke.size(); // dist2 < (numeric_limits<double>::max)();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::getControlPoints(vector<TThickPoint> &v) const {
|
|
assert(v.empty());
|
|
v.resize(m_imp->m_centerLineArray.size() * 2 + 1);
|
|
|
|
v[0] = m_imp->m_centerLineArray[0]->getThickP0();
|
|
|
|
for (UINT i = 0; i < m_imp->m_centerLineArray.size(); i++) {
|
|
TThickQuadratic *q = m_imp->m_centerLineArray[i];
|
|
v[2 * i + 1] = q->getThickP1();
|
|
v[2 * i + 2] = q->getThickP2();
|
|
}
|
|
}
|
|
|
|
TThickPoint TStroke::getControlPoint(int n) const {
|
|
if (n <= 0) return m_imp->m_centerLineArray.front()->getThickP0();
|
|
|
|
if (n >= getControlPointCount())
|
|
return m_imp->m_centerLineArray.back()->getThickP2();
|
|
|
|
// calcolo l'offset del chunk risolvendo l'equazione
|
|
// 2 * chunkNumber + 1 = n
|
|
// chunkNumber = tceil((n - 1) / 2)
|
|
int chunkNumber = tceil((n - 1) * 0.5);
|
|
assert(chunkNumber <= getChunkCount());
|
|
|
|
int pointOffset = n - chunkNumber * 2;
|
|
|
|
if (chunkNumber == getChunkCount()) // e' l'ultimo punto della stroke
|
|
return getChunk(chunkNumber - 1)->getThickP2();
|
|
|
|
switch (pointOffset) {
|
|
case 0:
|
|
return getChunk(chunkNumber)->getThickP0();
|
|
case 1:
|
|
return getChunk(chunkNumber)->getThickP1();
|
|
case 2:
|
|
return getChunk(chunkNumber)->getThickP2();
|
|
}
|
|
|
|
assert("Not yet finished" && false);
|
|
return getControlPoint(0);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TThickPoint TStroke::getControlPointAtParameter(double w) const {
|
|
if (w <= 0) return m_imp->m_centerLineArray.front()->getThickP0();
|
|
|
|
if (w >= 1.0) return m_imp->m_centerLineArray.back()->getThickP2();
|
|
|
|
vector<double>::iterator it_begin =
|
|
m_imp->m_parameterValueAtControlPoint.begin(),
|
|
first,
|
|
it_end = m_imp->m_parameterValueAtControlPoint.end();
|
|
|
|
// find iterator at position greater or equal to w
|
|
first = std::lower_bound(it_begin, it_end, w);
|
|
|
|
assert(first != it_end);
|
|
|
|
// now is possible to get control point
|
|
// if( areAlmostEqual(*first, w, 0.1) )
|
|
// return getControlPoint( distance(it_begin, first) );
|
|
if (first == it_begin)
|
|
return getControlPoint(0);
|
|
else if ((*first - w) <= w - *(first - 1))
|
|
return getControlPoint(distance(it_begin, first));
|
|
else
|
|
return getControlPoint(distance(it_begin, first - 1));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int TStroke::getControlPointIndexAfterParameter(double w) const {
|
|
const vector<double>::const_iterator
|
|
begin = m_imp->m_parameterValueAtControlPoint.begin(),
|
|
end = m_imp->m_parameterValueAtControlPoint.end();
|
|
|
|
vector<double>::const_iterator it = std::upper_bound(begin, end, w);
|
|
|
|
if (it == end)
|
|
return getControlPointCount();
|
|
else
|
|
return std::distance(begin, it);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::setControlPoint(int n, const TThickPoint &pos) {
|
|
assert(n >= 0);
|
|
assert(n < getControlPointCount());
|
|
if (n < 0 || n >= getControlPointCount()) return;
|
|
|
|
invalidate();
|
|
|
|
QuadStrokeChunkArray &chunkArray = m_imp->m_centerLineArray;
|
|
|
|
if (getControlPoint(n).thick <= 0 && pos.thick > 0)
|
|
m_imp->m_negativeThicknessPoints--;
|
|
else if (getControlPoint(n).thick > 0 && pos.thick <= 0)
|
|
m_imp->m_negativeThicknessPoints++;
|
|
|
|
if (n == 0) {
|
|
chunkArray[0]->setThickP0(pos);
|
|
// m_imp->computeBBox();
|
|
return;
|
|
}
|
|
|
|
int chunkNumber = tceil((n - 1) * 0.5);
|
|
assert(chunkNumber <= getChunkCount());
|
|
|
|
int pointOffset = n - chunkNumber * 2;
|
|
|
|
if (chunkNumber == getChunkCount()) // e' l'ultimo punto della stroke
|
|
{
|
|
chunkArray[chunkNumber - 1]->setThickP2(pos);
|
|
// m_imp->computeBBox();
|
|
return;
|
|
}
|
|
|
|
if (0 == pointOffset) {
|
|
chunkArray[chunkNumber]->setThickP0(pos);
|
|
|
|
if (chunkNumber >= 1) {
|
|
chunkNumber--;
|
|
chunkArray[chunkNumber]->setThickP2(pos);
|
|
}
|
|
} else if (1 == pointOffset)
|
|
chunkArray[chunkNumber]->setThickP1(pos);
|
|
else if (2 == pointOffset) {
|
|
chunkArray[chunkNumber]->setThickP2(pos);
|
|
|
|
if (chunkNumber < getChunkCount() - 1) {
|
|
chunkNumber++;
|
|
chunkArray[chunkNumber]->setThickP0(pos);
|
|
}
|
|
}
|
|
// m_imp->computeBBox();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//! Ridisegna lo stroke
|
|
void TStroke::reshape(const TThickPoint pos[], int count) {
|
|
// count deve essere dispari e maggiore o uguale a tre
|
|
assert(count >= 3);
|
|
assert(count & 1);
|
|
QuadStrokeChunkArray &chunkArray = m_imp->m_centerLineArray;
|
|
clearPointerContainer(chunkArray);
|
|
|
|
m_imp->m_negativeThicknessPoints = 0;
|
|
for (int i = 0; i < count - 1; i += 2) {
|
|
chunkArray.push_back(new TThickQuadratic(pos[i], pos[i + 1], pos[i + 2]));
|
|
if (pos[i].thick <= 0) m_imp->m_negativeThicknessPoints++;
|
|
if (pos[i + 1].thick <= 0) m_imp->m_negativeThicknessPoints++;
|
|
}
|
|
if (pos[count - 1].thick <= 0) m_imp->m_negativeThicknessPoints++;
|
|
|
|
invalidate();
|
|
// m_imp->computeBBox();
|
|
m_imp->computeParameterInControlPoint();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
double TStroke::getApproximateLength(double w0, double w1, double error) const {
|
|
m_imp->computeCacheVector();
|
|
|
|
assert((int)m_imp->m_partialLengthArray.size() == getControlPointCount());
|
|
|
|
if (w0 == w1) return 0.0;
|
|
|
|
w0 = min(max(0.0, w0), 1.0);
|
|
w1 = min(max(0.0, w1), 1.0);
|
|
|
|
if (w0 > w1) std::swap(w0, w1);
|
|
|
|
// vede se la lunghezza e' individuabile nella cache
|
|
if (0.0 == w0) {
|
|
vector<double>::iterator first;
|
|
|
|
// trova l'iteratore alla prima posizione che risulta maggiore di w
|
|
first = std::upper_bound(m_imp->m_parameterValueAtControlPoint.begin(),
|
|
m_imp->m_parameterValueAtControlPoint.end(),
|
|
w1 - TConsts::epsilon);
|
|
|
|
if (first != m_imp->m_parameterValueAtControlPoint.end() &&
|
|
*first < w1 + TConsts::epsilon) {
|
|
int offset =
|
|
distance(m_imp->m_parameterValueAtControlPoint.begin(), first);
|
|
return m_imp->m_partialLengthArray[offset];
|
|
}
|
|
}
|
|
|
|
int firstChunk, secondChunk;
|
|
double firstT, secondT;
|
|
|
|
// calcolo i chunk interessati ed i valori del parametro t
|
|
bool val1 = m_imp->retrieveChunkAndItsParamameter(w0, firstChunk, firstT);
|
|
assert(val1);
|
|
|
|
bool val2 = m_imp->retrieveChunkAndItsParamameter(w1, secondChunk, secondT);
|
|
assert(val2);
|
|
|
|
if (firstChunk == secondChunk)
|
|
return getChunk(firstChunk)->getApproximateLength(firstT, secondT, error);
|
|
|
|
double totalLength = 0;
|
|
|
|
totalLength += getChunk(firstChunk)->getApproximateLength(firstT, 1, error);
|
|
|
|
// lunghezza dei pezzi intermedi
|
|
for (int i = firstChunk + 1; i != secondChunk; i++)
|
|
totalLength += getChunk(i)->getApproximateLength(0.0, 1.0, error);
|
|
|
|
totalLength +=
|
|
getChunk(secondChunk)->getApproximateLength(0.0, secondT, error);
|
|
|
|
return totalLength;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
double TStroke::getLength(double w0, double w1) const {
|
|
if (w0 == w1) return 0.0;
|
|
|
|
// If necessary, swap values
|
|
w0 = min(max(0.0, w0), 1.0);
|
|
w1 = min(max(0.0, w1), 1.0);
|
|
|
|
if (w0 > w1) std::swap(w0, w1);
|
|
|
|
// Retrieve s1
|
|
int chunk;
|
|
double t;
|
|
|
|
bool ok = !m_imp->retrieveChunkAndItsParamameter(w1, chunk, t);
|
|
assert(ok);
|
|
|
|
double s1 = getLength(chunk, t);
|
|
|
|
if (w0 == 0.0) return s1;
|
|
|
|
// Retrieve s0
|
|
ok = !m_imp->retrieveChunkAndItsParamameter(w0, chunk, t);
|
|
assert(ok);
|
|
|
|
return s1 - getLength(chunk, t);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
double TStroke::getLength(int chunk, double t) const {
|
|
// Compute length caches
|
|
m_imp->computeCacheVector();
|
|
assert((int)m_imp->m_partialLengthArray.size() == getControlPointCount());
|
|
|
|
if (t == 1.0) ++chunk, t = 0.0;
|
|
|
|
double s = m_imp->m_partialLengthArray[chunk << 1];
|
|
if (t > 0.0) s += getChunk(chunk)->getLength(t);
|
|
|
|
return s;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::invalidate() {
|
|
m_imp->m_maxThickness = -1;
|
|
m_imp->m_isOutlineValid = false;
|
|
m_imp->m_isValidLength = false;
|
|
m_imp->m_flag = m_imp->m_flag | c_dirty_flag;
|
|
if (m_imp->m_prop) m_imp->m_prop->notifyStrokeChange();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/*!
|
|
N.B. Questa funzione e' piu' lenta rispetto alla insertCP
|
|
perche' ricerca la posizione di s con un algoritmo di bisezione.
|
|
*/
|
|
void TStroke::insertControlPointsAtLength(double s) {
|
|
if (0 > s || s > getLength()) return;
|
|
|
|
int chunk;
|
|
double t;
|
|
|
|
// cerca il cionco ed il parametro alla lunghezza s
|
|
if (!m_imp->retrieveChunkAndItsParamameterAtLength(s, chunk, t)) {
|
|
if (isAlmostZero(t) || areAlmostEqual(t, 1)) return;
|
|
|
|
// calcolo i due "cionchi"
|
|
TThickQuadratic *tqfirst = new TThickQuadratic,
|
|
*tqsecond = new TThickQuadratic;
|
|
|
|
getChunk(chunk)->split(t, *tqfirst, *tqsecond);
|
|
|
|
double parameterInStroke;
|
|
|
|
if (0 == chunk)
|
|
parameterInStroke = m_imp->getW(2) * t;
|
|
else
|
|
parameterInStroke =
|
|
t * m_imp->getW((chunk + 1) * 2) + (1 - t) * m_imp->getW(chunk * 2);
|
|
|
|
m_imp->updateParameterValue(parameterInStroke, chunk, tqfirst, tqsecond);
|
|
|
|
// recupero la posizione nella lista delle curve
|
|
QuadStrokeChunkArray::iterator it = m_imp->m_centerLineArray.begin();
|
|
|
|
// elimino la curva vecchia
|
|
advance(it, chunk);
|
|
delete *it;
|
|
it = m_imp->m_centerLineArray.erase(it);
|
|
|
|
// ed aggiungo le nuove
|
|
it = m_imp->m_centerLineArray.insert(it, tqsecond);
|
|
it = m_imp->m_centerLineArray.insert(it, tqfirst);
|
|
}
|
|
/* FAB
|
|
#ifdef _DEBUG
|
|
{
|
|
const int
|
|
size = m_imp->m_parameterValueAtControlPoint.size();
|
|
double
|
|
prev = m_imp->m_parameterValueAtControlPoint[0];
|
|
for( int i = 1;
|
|
i < size;
|
|
++i )
|
|
{
|
|
assert( prev <= m_imp->m_parameterValueAtControlPoint[i] );
|
|
prev = m_imp->m_parameterValueAtControlPoint[i];
|
|
}
|
|
}
|
|
#endif
|
|
//*/
|
|
invalidate();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::insertControlPoints(double w) {
|
|
if (0.0 > w || w > 1.0) return;
|
|
|
|
int chunk;
|
|
double tOfDivision = -1;
|
|
|
|
if (m_imp->retrieveChunkAndItsParamameter(w, chunk, tOfDivision)) return;
|
|
|
|
if (isAlmostZero(tOfDivision) || areAlmostEqual(tOfDivision, 1)) return;
|
|
|
|
assert(0 <= chunk && chunk < getChunkCount());
|
|
assert(0 <= tOfDivision && tOfDivision <= 1.0);
|
|
|
|
// calcolo i due "cionchi"
|
|
TThickQuadratic *tqfirst = new TThickQuadratic,
|
|
*tqsecond = new TThickQuadratic;
|
|
|
|
getChunk(chunk)->split(tOfDivision, *tqfirst, *tqsecond);
|
|
|
|
m_imp->updateParameterValue(w, chunk, tqfirst, tqsecond);
|
|
|
|
// recupero la posizione nella lista delle curve
|
|
QuadStrokeChunkArray::iterator it = m_imp->m_centerLineArray.begin();
|
|
|
|
// elimino la curva vecchia
|
|
advance(it, chunk);
|
|
delete *it;
|
|
it = m_imp->m_centerLineArray.erase(it);
|
|
|
|
// ed aggiungo le nuove
|
|
it = m_imp->m_centerLineArray.insert(it, tqsecond);
|
|
m_imp->m_centerLineArray.insert(it, tqfirst);
|
|
|
|
invalidate();
|
|
m_imp->computeCacheVector();
|
|
|
|
m_imp->m_negativeThicknessPoints = 0;
|
|
for (UINT j = 0; j < m_imp->m_centerLineArray.size(); j++) {
|
|
if (m_imp->m_centerLineArray[j]->getThickP0().thick <= 0)
|
|
m_imp->m_negativeThicknessPoints++;
|
|
if (m_imp->m_centerLineArray[j]->getThickP1().thick <= 0)
|
|
m_imp->m_negativeThicknessPoints++;
|
|
}
|
|
if (!m_imp->m_centerLineArray.empty() &&
|
|
m_imp->m_centerLineArray.back()->getThickP2().thick <= 0)
|
|
m_imp->m_negativeThicknessPoints++;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::reduceControlPoints(double error, vector<int> corners) {
|
|
double step, quadLen;
|
|
vector<TThickPoint> tempVect, controlPoints;
|
|
TStroke *tempStroke = 0;
|
|
double missedLen = 0;
|
|
UINT cp, nextQuad, quadI, cornI, cpSize, size;
|
|
|
|
const TThickQuadratic *quad = m_imp->m_centerLineArray.front();
|
|
|
|
size = corners.size();
|
|
assert(size > 1);
|
|
if (size < 2) {
|
|
// Have at least the first and last stroke points as corners
|
|
corners.resize(2);
|
|
corners[0] = 0;
|
|
corners[1] = m_imp->m_centerLineArray.size();
|
|
}
|
|
|
|
// For every corners interval
|
|
for (cornI = 0; cornI < size - 1; ++cornI) {
|
|
tempVect.clear();
|
|
|
|
nextQuad = corners[cornI + 1];
|
|
if (nextQuad > m_imp->m_centerLineArray.size()) {
|
|
assert(!"bad quadric index");
|
|
return;
|
|
}
|
|
if (corners[cornI] >= (int)m_imp->m_centerLineArray.size()) {
|
|
assert(!"bad quadric index");
|
|
return;
|
|
}
|
|
|
|
for (quadI = corners[cornI]; quadI < nextQuad; quadI++) {
|
|
quad = getChunk(quadI);
|
|
quadLen = quad->getLength();
|
|
|
|
missedLen += quadLen;
|
|
if (quadLen && (missedLen > 1 || quadI == 0 ||
|
|
quadI == nextQuad - 1)) // err instead of 1?
|
|
{
|
|
missedLen = 0;
|
|
step = 1.0 / quadLen; // err instead of 1.0?
|
|
|
|
// si, lo so che t non e' lineare sulla lunghezza, ma secondo me
|
|
// funziona benissimo
|
|
// cosi'. tanto devo interpolare dei punto e non e' richiesto che siano
|
|
// a distanze
|
|
// simili. e poi difficilmete il punto p1 di una quadratica e' cosi'
|
|
// asimmetrico
|
|
for (double t = 0; t < 1.0; t += step)
|
|
tempVect.push_back(quad->getThickPoint(t));
|
|
}
|
|
}
|
|
tempVect.push_back(quad->getThickP2());
|
|
tempStroke = TStroke::interpolate(tempVect, error, false);
|
|
|
|
cpSize = tempStroke->getControlPointCount();
|
|
for (cp = 0; cp < cpSize - 1; cp++) {
|
|
controlPoints.push_back(tempStroke->getControlPoint(cp));
|
|
}
|
|
delete tempStroke;
|
|
tempStroke = 0;
|
|
}
|
|
controlPoints.push_back(m_imp->m_centerLineArray.back()->getThickP2());
|
|
|
|
#ifdef _DEBUG
|
|
cpSize = controlPoints.size();
|
|
for (cp = 1; cp < cpSize; cp++)
|
|
assert(!(controlPoints[cp - 1] == controlPoints[cp]));
|
|
#endif
|
|
|
|
reshape(&(controlPoints[0]), controlPoints.size());
|
|
invalidate();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::reduceControlPoints(double error) {
|
|
vector<int> corners;
|
|
corners.push_back(0);
|
|
detectCorners(this, 10, corners);
|
|
corners.push_back(getChunkCount());
|
|
reduceControlPoints(error, corners);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
double TStroke::getAverageThickness() const {
|
|
return m_imp->m_averageThickness;
|
|
}
|
|
|
|
double TStroke::getMaxThickness() {
|
|
if (m_imp->m_maxThickness == -1) m_imp->computeMaxThickness();
|
|
return m_imp->m_maxThickness;
|
|
}
|
|
|
|
void TStroke::setAverageThickness(double thickness) {
|
|
m_imp->m_averageThickness = thickness;
|
|
}
|
|
|
|
bool TStroke::operator==(const TStroke &s) const {
|
|
if (getChunkCount() != s.getChunkCount()) return false;
|
|
int i;
|
|
for (i = 0; i < getChunkCount(); i++) {
|
|
const TThickQuadratic *chanck = getChunk(i);
|
|
const TThickQuadratic *sChanck = s.getChunk(i);
|
|
if (chanck->getThickP0() != sChanck->getThickP0() ||
|
|
chanck->getThickP1() != sChanck->getThickP1() ||
|
|
chanck->getThickP2() != sChanck->getThickP2())
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int TStroke::getChunkCount() const { return m_imp->getChunkCount(); }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int TStroke::getControlPointCount() const {
|
|
return m_imp->getControlPointCount();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const TThickQuadratic *TStroke::getChunk(int index) const {
|
|
return m_imp->getChunk(index);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TRectD TStroke::getBBox(double w0, double w1) const {
|
|
if (w0 > w1) tswap(w0, w1);
|
|
|
|
if (w0 != 0.0 || w1 != 1.0) return m_imp->computeSubBBox(w0, w1);
|
|
|
|
if (m_imp->m_flag & c_dirty_flag) ((TStroke *)this)->computeBBox();
|
|
|
|
return m_imp->m_bBox;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::computeBBox() {
|
|
m_imp->m_bBox = TOutlineUtil::computeBBox(*this);
|
|
m_imp->m_flag &= ~c_dirty_flag;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TRectD TStroke::getCenterlineBBox() const {
|
|
return m_imp->computeCenterlineBBox();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::disableComputeOfCaches() {
|
|
m_imp->m_areDisabledComputeOfCaches = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::enableComputeOfCaches() {
|
|
m_imp->m_areDisabledComputeOfCaches = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// DEL double TStroke::getDistance2(const TPointD &p,
|
|
// DEL double maxDistance2,
|
|
// DEL UINT &chunkIndex,
|
|
// DEL double &currT)
|
|
// DEL {
|
|
// DEL TRectD rect = getBBox();
|
|
// DEL
|
|
// DEL if (!rect.contains(p))
|
|
// DEL {
|
|
// DEL double dist21 = tdistance2(p, rect.getP00());
|
|
// DEL double dist22 = tdistance2(p, rect.getP01());
|
|
// DEL double dist23 = tdistance2(p, rect.getP10());
|
|
// DEL double dist24 = tdistance2(p, rect.getP11());
|
|
// DEL
|
|
// DEL if (std::min(dist21, dist22, dist23, dist24)>=maxDistance2)
|
|
// DEL return maxDistance2;
|
|
// DEL }
|
|
// DEL
|
|
// DEL double distance2, curMaxDistance2=maxDistance2;
|
|
// DEL
|
|
// DEL for (UINT i=0; i < m_imp->m_centerLineArray.size(); i++)
|
|
// DEL {
|
|
// DEL distance2 = getTQDistance2(*(m_imp->m_centerLineArray)[i], p,
|
|
// maxDistance2, currT);
|
|
// DEL if (distance2 < curMaxDistance2)
|
|
// DEL {
|
|
// DEL curMaxDistance2 = distance2;
|
|
// DEL chunkIndex = i;
|
|
// DEL }
|
|
// DEL }
|
|
// DEL
|
|
// DEL return curMaxDistance2;
|
|
// DEL }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TThickPoint TStroke::getThickPointAtLength(double s) const {
|
|
assert(!m_imp->m_centerLineArray.empty());
|
|
|
|
if (s <= 0) return getControlPoint(0);
|
|
|
|
if (s >= getLength()) return getControlPoint(getControlPointCount() - 1);
|
|
|
|
int chunk;
|
|
double tOfDivision;
|
|
|
|
bool error =
|
|
m_imp->retrieveChunkAndItsParamameterAtLength(s, chunk, tOfDivision);
|
|
|
|
if (error)
|
|
error =
|
|
m_imp->retrieveChunkAndItsParamameterAtLength(s, chunk, tOfDivision);
|
|
|
|
assert(!error);
|
|
|
|
if (error) return getControlPoint(0);
|
|
|
|
return getChunk(chunk)->getThickPoint(tOfDivision);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TThickPoint TStroke::getThickPoint(double w) const {
|
|
assert(!m_imp->m_centerLineArray.empty());
|
|
|
|
if (w < 0) return getControlPoint(0);
|
|
|
|
if (w > 1.0) return getControlPoint(getControlPointCount() - 1);
|
|
|
|
int chunk = 0;
|
|
double t = 0;
|
|
|
|
bool error = m_imp->retrieveChunkAndItsParamameter(w, chunk, t);
|
|
|
|
assert(!error);
|
|
|
|
if (error) return getControlPoint(0);
|
|
|
|
return getChunk(chunk)->getThickPoint(t);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
double TStroke::getParameterAtLength(double s) const {
|
|
if (s <= 0)
|
|
return 0;
|
|
else if (s >= getLength())
|
|
return 1;
|
|
|
|
int chunk;
|
|
double t;
|
|
|
|
if (!m_imp->retrieveChunkAndItsParamameterAtLength(s, chunk, t)) {
|
|
DoublePair p = m_imp->retrieveParametersFromChunk(chunk);
|
|
|
|
return proportion(p.second, t, 1.0, p.first);
|
|
} else if (chunk < (int)getChunkCount() && t == -1)
|
|
return getParameterAtControlPoint(2 * chunk);
|
|
|
|
return 1.0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
double TStroke::getParameterAtControlPoint(int n) const {
|
|
double out = -1;
|
|
|
|
if (0 <= n && n < getControlPointCount()) out = m_imp->getW(n);
|
|
/* FAB
|
|
assert( 0.0 <= out &&
|
|
out <= 1.0 );
|
|
*/
|
|
if (0.0 > out) return 0.0;
|
|
if (out > 1.0) return 1.0;
|
|
return out;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
double TStroke::getW(int chunkIndex, double t) const {
|
|
DoublePair parRange = m_imp->retrieveParametersFromChunk(chunkIndex);
|
|
|
|
double w = proportion(parRange.second, t, 1.0, parRange.first);
|
|
|
|
assert(0 <= w && w <= 1.0);
|
|
|
|
return w;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
double TStroke::getW(const TPointD &p) const {
|
|
int chunkIndex;
|
|
|
|
double tOfchunk, distance2 = (numeric_limits<double>::max)();
|
|
|
|
// cerca il chunk piu' vicino senza testare la BBox
|
|
getNearestChunk(p, tOfchunk, chunkIndex, distance2, false);
|
|
|
|
assert(0 <= chunkIndex && chunkIndex <= getChunkCount());
|
|
|
|
DoublePair parRange = m_imp->retrieveParametersFromChunk(chunkIndex);
|
|
|
|
double t = proportion(parRange.second, tOfchunk, 1.0, parRange.first);
|
|
|
|
assert(0 <= t && t <= 1.0);
|
|
|
|
return t;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool TStroke::getSpeedTwoValues(double w, TPointD &speed0,
|
|
TPointD &speed1) const {
|
|
bool ret = false;
|
|
|
|
assert(!m_imp->m_centerLineArray.empty());
|
|
|
|
if (w < 0) {
|
|
speed0 = m_imp->m_centerLineArray.front()->getSpeed(0.0);
|
|
return ret;
|
|
}
|
|
|
|
if (w > 1.0) {
|
|
speed0 = m_imp->m_centerLineArray.back()->getSpeed(1.0);
|
|
return ret;
|
|
}
|
|
|
|
int chunk;
|
|
double t;
|
|
|
|
bool error = m_imp->retrieveChunkAndItsParamameter(w, chunk, t);
|
|
|
|
assert(!error);
|
|
|
|
if (error) {
|
|
speed0 = m_imp->m_centerLineArray.front()->getSpeed(0.0);
|
|
speed1 = -speed0;
|
|
return ret;
|
|
}
|
|
|
|
speed0 = getChunk(chunk)->getSpeed(t);
|
|
speed1 = -speed0;
|
|
|
|
if (areAlmostEqual(t, 0.0, 1e-9) && chunk > 0 &&
|
|
(speed1 = -getChunk(chunk - 1)->getSpeed(1.0)) != -speed0)
|
|
ret = true;
|
|
else if (areAlmostEqual(t, 1.0, 1e-9) && chunk < getChunkCount() - 1 &&
|
|
(speed1 = -getChunk(chunk + 1)->getSpeed(0.0)) != -speed0) {
|
|
TPointD aux = -speed0;
|
|
speed0 = -speed1;
|
|
speed1 = aux;
|
|
ret = true;
|
|
}
|
|
|
|
if (speed0 == TPointD()) // la quadratica e' degenere!!!
|
|
{
|
|
while (speed0 == TPointD()) {
|
|
speed0 = getChunk(chunk--)->getSpeed(1.0);
|
|
if (chunk <= 0) break;
|
|
}
|
|
chunk = 0;
|
|
while (speed0 == TPointD()) {
|
|
speed0 = getChunk(chunk++)->getSpeed(0.0);
|
|
if (chunk >= getChunkCount() - 1) break;
|
|
}
|
|
|
|
if (speed0 == TPointD()) {
|
|
if (getChunkCount() == 1) {
|
|
const TThickQuadratic *q = getChunk(0);
|
|
|
|
if (q->getP0() == q->getP1() && q->getP1() != q->getP2())
|
|
speed0 = TSegment(q->getP1(), q->getP2()).getSpeed(t);
|
|
else if (q->getP1() == q->getP2() && q->getP0() != q->getP1())
|
|
speed0 = TSegment(q->getP0(), q->getP1()).getSpeed(t);
|
|
else
|
|
assert(speed0 != TPointD());
|
|
} else
|
|
assert(speed0 != TPointD());
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
TPointD TStroke::getSpeed(double w, bool outSpeed) const {
|
|
assert(!m_imp->m_centerLineArray.empty());
|
|
|
|
if (w < 0) return m_imp->m_centerLineArray.front()->getSpeed(0.0);
|
|
|
|
if (w > 1.0) return m_imp->m_centerLineArray.back()->getSpeed(1.0);
|
|
|
|
int chunk;
|
|
double t;
|
|
|
|
bool error = m_imp->retrieveChunkAndItsParamameter(w, chunk, t);
|
|
if (t == 1 && outSpeed && chunk < getChunkCount() - 1) {
|
|
chunk++;
|
|
t = 0;
|
|
}
|
|
assert(!error);
|
|
|
|
if (error) return m_imp->m_centerLineArray.front()->getSpeed(0.0);
|
|
|
|
TPointD speed = getChunk(chunk)->getSpeed(t);
|
|
|
|
if (speed == TPointD()) // la quadratica e' degenere!!!
|
|
{
|
|
while (speed == TPointD()) {
|
|
speed = getChunk(chunk--)->getSpeed(1.0);
|
|
if (chunk <= 0) break;
|
|
}
|
|
chunk = 0;
|
|
while (speed == TPointD()) {
|
|
speed = getChunk(chunk++)->getSpeed(0.0);
|
|
if (chunk >= getChunkCount() - 1) break;
|
|
}
|
|
if (speed == TPointD()) {
|
|
if (getChunkCount() == 1) {
|
|
const TThickQuadratic *q = getChunk(0);
|
|
|
|
if (q->getP0() == q->getP1() && q->getP1() != q->getP2())
|
|
return TSegment(q->getP1(), q->getP2()).getSpeed(t);
|
|
else if (q->getP1() == q->getP2() && q->getP0() != q->getP1())
|
|
return TSegment(q->getP0(), q->getP1()).getSpeed(t);
|
|
else
|
|
assert(speed != TPointD());
|
|
} else
|
|
assert(speed != TPointD());
|
|
}
|
|
}
|
|
return speed;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TPointD TStroke::getSpeedAtLength(double s) const {
|
|
double t = getParameterAtLength(s);
|
|
return getSpeed(t);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
double TStroke::getLengthAtControlPoint(int n) const {
|
|
m_imp->computeCacheVector();
|
|
|
|
if (n >= getControlPointCount()) return m_imp->m_partialLengthArray.back();
|
|
|
|
if (n <= 0) return m_imp->m_partialLengthArray.front();
|
|
|
|
return m_imp->m_partialLengthArray[n];
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::split(double w, TStroke &f, TStroke &s) const {
|
|
int chunk;
|
|
double t;
|
|
f.m_imp->m_maxThickness = -1;
|
|
s.m_imp->m_maxThickness = -1;
|
|
if (!m_imp->retrieveChunkAndItsParamameter(w, chunk, t)) {
|
|
assert(0 <= chunk && chunk < getChunkCount());
|
|
assert(0 <= w && w <= 1.0);
|
|
assert(0 <= t && t <= 1.0);
|
|
|
|
QuadStrokeChunkArray &chunkArray = m_imp->m_centerLineArray;
|
|
|
|
// build two temporary quadratic
|
|
TThickQuadratic *tq1 = new TThickQuadratic, *tq2 = new TThickQuadratic;
|
|
// make split
|
|
chunkArray[chunk]->split(t, *tq1, *tq2);
|
|
|
|
// a temporary vector of ThickQuadratic
|
|
QuadStrokeChunkArray vTQ;
|
|
|
|
// copy all chunk of stroke in a vTQ
|
|
int i;
|
|
for (i = 0; i < chunk; ++i) vTQ.push_back(chunkArray[i]);
|
|
|
|
// not insert null length chunk (unless vTQ is empty....)
|
|
if (tq1->getLength() != 0.0 || w == 0.0 || vTQ.empty()) vTQ.push_back(tq1);
|
|
|
|
// build a temp and swap
|
|
TStroke *ts1 = TStroke::create(vTQ);
|
|
if (!ts1) ts1 = new TStroke;
|
|
ts1->swapGeometry(f);
|
|
|
|
// clear vector (chunks are now under TStroke control)
|
|
vTQ.clear();
|
|
|
|
// idem...
|
|
if (tq2->getLength() != 0.0 || w == 1.0 || getChunkCount() == 0)
|
|
vTQ.push_back(tq2);
|
|
|
|
for (i = chunk + 1; i < getChunkCount(); ++i) vTQ.push_back(chunkArray[i]);
|
|
|
|
TStroke *ts2 = TStroke::create(vTQ);
|
|
if (!ts2) ts2 = new TStroke;
|
|
ts2->swapGeometry(s);
|
|
|
|
// Copy style infos
|
|
f.setStyle(getStyle());
|
|
s.setStyle(getStyle());
|
|
f.outlineOptions() = s.outlineOptions() = outlineOptions();
|
|
|
|
delete ts2;
|
|
delete ts1;
|
|
// delete temporary quadratic
|
|
delete tq1;
|
|
delete tq2;
|
|
if (f.getControlPointCount() == 3 &&
|
|
f.getControlPoint(0) !=
|
|
f.getControlPoint(2)) // gli stroke con solo 1 chunk vengono fatti
|
|
// dal tape tool...e devono venir
|
|
// riconosciuti come speciali di autoclose
|
|
// proprio dal fatto che hanno 1 solo chunk.
|
|
f.insertControlPoints(0.5);
|
|
if (s.getControlPointCount() == 3 &&
|
|
s.getControlPoint(0) !=
|
|
s.getControlPoint(2)) // gli stroke con solo 1 chunk vengono fatti
|
|
// dal tape tool...e devono venir
|
|
// riconosciuti come speciali di autoclose
|
|
// proprio dal fatto che hanno 1 solo chunk.
|
|
s.insertControlPoints(0.5);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::print(ostream &os) const {
|
|
// m_imp->print(os);
|
|
const TThickQuadratic *q;
|
|
|
|
os << "Punti di controllo\n";
|
|
for (int i = 0; i < getChunkCount(); ++i) {
|
|
os << "quad #" << i << ":" << endl;
|
|
q = getChunk(i);
|
|
|
|
os << " P0:" << q->getThickP0().x << ", " << q->getThickP0().y << ", "
|
|
<< q->getThickP0().thick << endl;
|
|
os << " P1:" << q->getThickP1().x << ", " << q->getThickP1().y << ", "
|
|
<< q->getThickP1().thick << endl;
|
|
assert(i == getChunkCount() - 1 ||
|
|
(getChunk(i)->getThickP2() == getChunk(i + 1)->getThickP0()));
|
|
}
|
|
|
|
q = getChunk(getChunkCount() - 1);
|
|
|
|
os << " P2:" << q->getThickP2().x << ", " << q->getThickP2().y << ", "
|
|
<< q->getThickP2().thick << endl;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::transform(const TAffine &aff, bool doChangeThickness) {
|
|
for (UINT i = 0; i < m_imp->m_centerLineArray.size(); ++i) {
|
|
TThickQuadratic &ref = *m_imp->m_centerLineArray[i];
|
|
ref = transformQuad(aff, ref, doChangeThickness);
|
|
|
|
if (doChangeThickness) {
|
|
double det = aff.det();
|
|
if (det == 0) m_imp->m_negativeThicknessPoints = getControlPointCount();
|
|
if (m_imp->m_maxThickness != -1) m_imp->m_maxThickness *= sqrt(fabs(det));
|
|
// else if(det<0)
|
|
// m_imp->m_negativeThicknessPoints=getControlPointCount()-m_imp->m_negativeThicknessPoints;
|
|
}
|
|
}
|
|
|
|
invalidate();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/*
|
|
void TStroke::draw(const TVectorRenderData &rd) const
|
|
{
|
|
if(! m_imp->m_styleId)
|
|
return;
|
|
|
|
TColorStyle * style = rd.m_palette->getStyle(m_imp->m_styleId);
|
|
|
|
if( !style->isStrokeStyle() || style->isEnabled() == false )
|
|
return;
|
|
|
|
|
|
|
|
if( !m_imp->m_prop || style != m_imp->m_prop->getColorStyle() )
|
|
{
|
|
delete m_imp->m_prop;
|
|
m_imp->m_prop = style->makeStrokeProp(this);
|
|
}
|
|
|
|
m_imp->m_prop->draw(rd);
|
|
}
|
|
|
|
*/
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TStrokeProp *TStroke::getProp() const {
|
|
#if !defined(DISEGNO_OUTLINE)
|
|
if (!m_imp->m_styleId) return 0;
|
|
#endif
|
|
|
|
/*
|
|
TColorStyle * style = palette->getStyle(m_imp->m_styleId);
|
|
if( !style->isStrokeStyle() || style->isEnabled() == false )
|
|
return 0;
|
|
|
|
if( !m_imp->m_prop || style != m_imp->m_prop->getColorStyle() )
|
|
{
|
|
delete m_imp->m_prop;
|
|
m_imp->m_prop = style->makeStrokeProp(this);
|
|
}
|
|
*/
|
|
return m_imp->m_prop;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::setProp(TStrokeProp *prop) {
|
|
assert(prop);
|
|
delete m_imp->m_prop;
|
|
m_imp->m_prop = prop;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int TStroke::getStyle() const { return m_imp->m_styleId; }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::setStyle(int styleId) {
|
|
m_imp->m_styleId = styleId;
|
|
|
|
/*
|
|
if (!colorStyle || (colorStyle && colorStyle->isStrokeStyle()) )
|
|
{
|
|
m_imp->m_colorStyle = colorStyle;
|
|
delete m_imp->m_prop;
|
|
m_imp->m_prop = 0;
|
|
}
|
|
*/
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TStroke &TStroke::changeDirection() {
|
|
UINT chunkCount = getChunkCount();
|
|
UINT to = tfloor(chunkCount * 0.5);
|
|
UINT i;
|
|
|
|
if (chunkCount & 1) changeTQDirection(m_imp->m_centerLineArray[to]);
|
|
|
|
--chunkCount;
|
|
|
|
for (i = 0; i < to; ++i) {
|
|
changeTQDirection(m_imp->m_centerLineArray[i]);
|
|
changeTQDirection(m_imp->m_centerLineArray[chunkCount - i]);
|
|
TThickQuadratic *q1 = m_imp->m_centerLineArray[i];
|
|
m_imp->m_centerLineArray[i] = m_imp->m_centerLineArray[chunkCount - i];
|
|
m_imp->m_centerLineArray[chunkCount - i] = q1;
|
|
}
|
|
invalidate();
|
|
|
|
return *this;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::setFlag(TStrokeFlag flag, bool status) {
|
|
if (status)
|
|
m_imp->m_flag |= flag;
|
|
else
|
|
m_imp->m_flag &= ~flag;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool TStroke::getFlag(TStrokeFlag flag) const {
|
|
return (m_imp->m_flag & flag) != 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::swap(TStroke &ref) {
|
|
std::swap(m_imp, ref.m_imp);
|
|
|
|
// Stroke props need to update their stroke owners
|
|
if (m_imp->m_prop) m_imp->m_prop->setStroke(this);
|
|
if (ref.m_imp->m_prop) ref.m_imp->m_prop->setStroke(&ref);
|
|
|
|
// The id is retained. This is coherent as the stroke id is supposedly
|
|
// not an exchangeable info.
|
|
std::swap(m_imp->m_id, ref.m_imp->m_id);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::swapGeometry(TStroke &ref) { m_imp->swapGeometry(*ref.m_imp); }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int TStroke::getId() const { return m_imp->m_id; }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::setId(int id) { m_imp->m_id = id; }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// magari poi la sposto in un altro file
|
|
TThickPoint TStroke::getCentroid() const {
|
|
double totalLen = getLength();
|
|
|
|
if (totalLen == 0) return getControlPoint(0);
|
|
|
|
double step = totalLen * 0.1;
|
|
double len = 0;
|
|
if (step > 10.0) step = 10.0;
|
|
int count = 0;
|
|
TThickPoint point;
|
|
for (; len <= totalLen; len += step) {
|
|
count++;
|
|
point += getThickPointAtLength(len);
|
|
}
|
|
return point * (1.0 / (double)count);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TStroke::setSelfLoop(bool loop) {
|
|
if (loop) {
|
|
// assert that a self loop is a stroke where first and last control points
|
|
// are the same
|
|
const int cpCount = this->getControlPointCount();
|
|
|
|
TThickPoint p, p0 = this->getControlPoint(0),
|
|
pn = this->getControlPoint(cpCount - 1);
|
|
|
|
assert(areAlmostEqual(p0, pn, 2));
|
|
p = (p0 + pn) * 0.5;
|
|
|
|
this->setControlPoint(0, p);
|
|
this->setControlPoint(cpCount - 1, p);
|
|
}
|
|
m_imp->m_selfLoop = loop;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool TStroke::isSelfLoop() const { return m_imp->m_selfLoop; }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool TStroke::isCenterLine() const {
|
|
assert(m_imp->m_negativeThicknessPoints <= getControlPointCount());
|
|
|
|
return m_imp->m_negativeThicknessPoints == getControlPointCount();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TStroke::OutlineOptions &TStroke::outlineOptions() {
|
|
return m_imp->m_outlineOptions;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const TStroke::OutlineOptions &TStroke::outlineOptions() const {
|
|
return m_imp->m_outlineOptions;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// End Of File
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TStroke *joinStrokes(const TStroke *s0, const TStroke *s1) {
|
|
TStroke *newStroke;
|
|
|
|
if (s0 == s1) {
|
|
newStroke = new TStroke(*s0);
|
|
newStroke->setSelfLoop();
|
|
return newStroke;
|
|
}
|
|
int i;
|
|
vector<TThickPoint> v;
|
|
for (i = 0; i < s0->getControlPointCount(); i++)
|
|
v.push_back(s0->getControlPoint(i));
|
|
if (areAlmostEqual(v.back(), s1->getControlPoint(0), 1e-3))
|
|
for (i = 1; i < s1->getControlPointCount(); i++)
|
|
v.push_back(s1->getControlPoint(i));
|
|
else if (areAlmostEqual(v.back(),
|
|
s1->getControlPoint(s1->getControlPointCount() - 1),
|
|
1e-3))
|
|
for (i = s1->getControlPointCount() - 2; i >= 0; i--)
|
|
v.push_back(s1->getControlPoint(i));
|
|
else
|
|
assert(false);
|
|
|
|
newStroke = new TStroke(v);
|
|
newStroke->setStyle(s0->getStyle());
|
|
newStroke->outlineOptions() = s0->outlineOptions();
|
|
|
|
return newStroke;
|
|
|
|
/*
|
|
int cpCount1 = stroke1->getControlPointCount();
|
|
int cpCount2 = stroke2->getControlPointCount();
|
|
|
|
int qCount1 = stroke1->getChunkCount();
|
|
const TThickQuadratic* q1=stroke1->getChunk(qCount1-1 );
|
|
const TThickQuadratic* q2=stroke2->getChunk(0);
|
|
|
|
TThickPoint extreme1 = q1->getThickP2() ;
|
|
TThickPoint extreme2 = q2->getThickP0();
|
|
|
|
vector<TThickPoint> points;
|
|
points.reserve(cpCount1+cpCount2-1);
|
|
|
|
int i = 0;
|
|
for(; i!=cpCount1-1; i++)
|
|
points.push_back( stroke1->getControlPoint(i));
|
|
|
|
if(extreme1==extreme2)
|
|
{
|
|
points.push_back(extreme1);
|
|
}
|
|
else
|
|
{
|
|
double len1=q1->getLength();
|
|
assert(len1>=0);
|
|
if(len1<=0)
|
|
len1=0;
|
|
double w1= exp(-len1*0.01);
|
|
|
|
double len2=q2->getLength();
|
|
assert(len2>=0);
|
|
if(len2<=0)
|
|
len2=0;
|
|
double w2= exp(-len2*0.01);
|
|
|
|
|
|
TThickPoint m1=q1->getThickP1();
|
|
TThickPoint m2=q2->getThickP1();
|
|
|
|
TThickPoint p1= extreme1*(1-w1)+m1*w1;
|
|
TThickPoint p2= extreme2*(1-w2)+m2*w2;
|
|
|
|
TThickPoint middleP= (p1 + p2)*0.5;
|
|
|
|
double angle=fabs(cross(normalize(m1-middleP),normalize(m2-middleP)));
|
|
if( angle< 0.05)
|
|
middleP=(m1+m2)*0.5;
|
|
|
|
points.push_back(middleP);
|
|
}
|
|
|
|
for(i = 1; i!=cpCount2; i++)
|
|
points.push_back( stroke2->getControlPoint(i));
|
|
|
|
newStroke = new TStroke(points);
|
|
newStroke->setStyle(stroke1->getStyle());
|
|
return newStroke;
|
|
*/
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
namespace {
|
|
|
|
int local_intersect(const TStroke &stroke, const TSegment &segment,
|
|
std::vector<DoublePair> &intersections,
|
|
bool strokeIsFirst) {
|
|
const TThickQuadratic *chunk;
|
|
for (int i = 0; i < stroke.getChunkCount(); i++) {
|
|
std::vector<DoublePair> localIntersections;
|
|
chunk = stroke.getChunk(i);
|
|
|
|
if (intersect(*chunk, segment, localIntersections)) {
|
|
for (UINT j = 0; j < localIntersections.size(); j++) {
|
|
double strokePar = localIntersections[j].first;
|
|
|
|
strokePar = stroke.getW(chunk->getPoint(strokePar));
|
|
|
|
// check for multiple solution
|
|
DoublePair sol(
|
|
strokeIsFirst ? strokePar : localIntersections[j].second,
|
|
strokeIsFirst ? localIntersections[j].second : strokePar);
|
|
|
|
std::vector<DoublePair>::iterator it_end = intersections.end();
|
|
|
|
if (it_end == std::find(intersections.begin(), it_end, sol))
|
|
intersections.push_back(sol);
|
|
}
|
|
}
|
|
}
|
|
return intersections.size();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool almostOverlaps(const TRectD &r0, const TRectD &r1) {
|
|
if (r0.overlaps(r1)) return true;
|
|
|
|
if ((r0.x1 < r1.x0) && (areAlmostEqual(r0.x1, r1.x0, 1e-5)))
|
|
return true;
|
|
else if ((r1.x1 < r0.x0) && (areAlmostEqual(r1.x1, r0.x0, 1e-5)))
|
|
return true;
|
|
if ((r0.y1 < r1.y0) && (areAlmostEqual(r0.y1, r1.y0, 1e-5)))
|
|
return true;
|
|
else if ((r1.y1 < r0.y0) && (areAlmostEqual(r1.y1, r0.y0, 1e-5)))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool greaterThanOneAndLesserThanZero(double val) {
|
|
if (val > 1.0 || val < 0.0) return true;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
int intersect(const TStroke *s1, const TStroke *s2,
|
|
std::vector<DoublePair> &intersections, bool checkBBox) {
|
|
UINT k = 0;
|
|
|
|
intersections.clear();
|
|
|
|
if (checkBBox && !s1->getBBox().overlaps(s2->getBBox())) return 0;
|
|
|
|
for (int i = 0; i < s1->getChunkCount(); i++) {
|
|
const TQuadratic *q1 = s1->getChunk(i);
|
|
if (q1->getP0() == q1->getP2() && q1->getP1() == q1->getP2()) continue;
|
|
int j = 0;
|
|
if (s1 ==
|
|
s2) // this 'if': if after i-th stroke there are degenere strokes,
|
|
// I do not consider them; so, instead of starting from j = i+2
|
|
// I start from j = i+2+numDegeneres
|
|
{
|
|
j = i + 2;
|
|
|
|
while (j <= s2->getChunkCount()) {
|
|
const TQuadratic *qAux = s2->getChunk(j - 1);
|
|
if (qAux->getP0() != qAux->getP2() || qAux->getP1() != qAux->getP2())
|
|
break;
|
|
j++;
|
|
}
|
|
}
|
|
|
|
for (; j < s2->getChunkCount(); j++) {
|
|
const TQuadratic *q2 = s2->getChunk(j);
|
|
if (q2->getP0() == q2->getP2() && q2->getP1() == q2->getP2()) continue;
|
|
|
|
if (!almostOverlaps(q1->getBBox(), q2->getBBox())) continue;
|
|
#ifdef CHECK_DEGLI_ESTREMI
|
|
vector<DoublePair> quadIntersections1;
|
|
if (i == 0 || i == s1->getChunkCount() - 1) && (j==0 || j==s2->getChunkCount()-1))
|
|
{
|
|
TPointD pp1 = (i == 0 ? q1->getP0() : q1->getP2());
|
|
TPointD pp2 = (j == 0 ? q2->getP0() : q2->getP2());
|
|
if (areAlmostEqual(pp1, pp2)) {
|
|
intersections.push_back(
|
|
DoublePair(i == 0 ? 0.0 : 1.0, j == 0 ? 0.0 : 1.0));
|
|
k++;
|
|
continue;
|
|
}
|
|
if (s1->getChunkCount() == 1 && s2->getChunkCount() == 1) {
|
|
TPointD pp1 = q1->getP2();
|
|
TPointD pp2 = q2->getP2();
|
|
if (areAlmostEqual(pp1, pp2)) {
|
|
intersections.push_back(DoublePair(1.0, 1.0));
|
|
k++;
|
|
continue;
|
|
}
|
|
}
|
|
if (s1->getChunkCount() == 1) {
|
|
TPointD pp1 = q1->getP2();
|
|
TPointD pp2 = (j == 0 ? q2->getP0() : q2->getP2());
|
|
if (areAlmostEqual(pp1, pp2)) {
|
|
intersections.push_back(DoublePair(1.0, j == 0 ? 0.0 : 1.0));
|
|
k++;
|
|
continue;
|
|
}
|
|
}
|
|
if (s2->getChunkCount() == 1) {
|
|
TPointD pp1 = (i == 0 ? q1->getP0() : q1->getP2());
|
|
TPointD pp2 = q2->getP2();
|
|
if (areAlmostEqual(pp1, pp2)) {
|
|
intersections.push_back(DoublePair(i == 0 ? 0.0 : 1.0, 1.0));
|
|
k++;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// if (j<s2->getChunkCount()-1)
|
|
// {
|
|
// const TQuadratic *q3= s2->getChunk(j==0?0:j+1);
|
|
// evito di intersecare se le quadratiche sono uguali
|
|
if (*q1 == *q2 ||
|
|
(q1->getP0() == q2->getP2() && q1->getP1() == q2->getP1() &&
|
|
q1->getP2() == q2->getP0())) {
|
|
// j+=((j==0)?1:2);
|
|
continue;
|
|
}
|
|
// }
|
|
|
|
vector<DoublePair> quadIntersections;
|
|
if (intersect(*q1, *q2, quadIntersections))
|
|
for (int h = 0; h < (int)quadIntersections.size(); h++) {
|
|
DoublePair res(getWfromChunkAndT(s1, i, quadIntersections[h].first),
|
|
getWfromChunkAndT(s2, j, quadIntersections[h].second));
|
|
|
|
if (areAlmostEqual(quadIntersections[h].first, 0) ||
|
|
areAlmostEqual(quadIntersections[h].first, 1) ||
|
|
areAlmostEqual(quadIntersections[h].second, 0) ||
|
|
areAlmostEqual(quadIntersections[h].second, 1)) {
|
|
int q = 0;
|
|
for (q = 0; q < (int)intersections.size(); q++)
|
|
if (areAlmostEqual(intersections[q].first, res.first, 1e-8) &&
|
|
areAlmostEqual(intersections[q].second, res.second, 1e-8))
|
|
break;
|
|
if (q < (int)intersections.size()) continue;
|
|
}
|
|
intersections.push_back(res);
|
|
// if (k==0 || intersections[k].first!=intersections[k-1].first ||
|
|
// intersections[k].second!=intersections[k-1].second)
|
|
k++;
|
|
}
|
|
}
|
|
}
|
|
if (s1 == s2 &&
|
|
(s1->isSelfLoop() || s1->getPoint(0.0) == s1->getPoint(1.0))) {
|
|
int i;
|
|
for (i = 0; i < (int)intersections.size(); i++) {
|
|
assert(!(areAlmostEqual(intersections[i].first, 1.0, 1e-1) &&
|
|
areAlmostEqual(intersections[i].second, 0.0, 1e-1)));
|
|
if (areAlmostEqual(intersections[i].first, 0.0, 1e-1) &&
|
|
areAlmostEqual(intersections[i].second, 1.0, 1e-1))
|
|
break;
|
|
}
|
|
if (i == (int)intersections.size()) {
|
|
intersections.push_back(DoublePair(0.0, 1.0));
|
|
k++;
|
|
}
|
|
}
|
|
|
|
return k;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int intersect(const TSegment &segment, const TStroke &stroke,
|
|
std::vector<DoublePair> &intersections) {
|
|
return local_intersect(stroke, segment, intersections, false);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int intersect(const TStroke &stroke, const TSegment &segment,
|
|
std::vector<DoublePair> &intersections) {
|
|
return local_intersect(stroke, segment, intersections, true);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int intersect(const TStroke &stroke, const TPointD ¢er, double radius,
|
|
vector<double> &intersections) {
|
|
// 2 2 2
|
|
// riconduco x + y = r con x(t) y(t) alla forma
|
|
// 2 2 2 2 2
|
|
// ( a_x t + b_x t + c_x ) + ( a_y t + b_y t + c_y ) = r
|
|
|
|
// 2
|
|
// a x + b x + c
|
|
|
|
const int a = 2;
|
|
const int b = 1;
|
|
const int c = 0;
|
|
|
|
vector<TPointD> bez(3);
|
|
vector<TPointD> pol(3);
|
|
vector<double> coeff(5);
|
|
|
|
for (int chunk = 0; chunk < stroke.getChunkCount(); ++chunk) {
|
|
const TThickQuadratic *tq = stroke.getChunk(chunk);
|
|
|
|
bez[0] = tq->getP0();
|
|
bez[1] = tq->getP1();
|
|
bez[2] = tq->getP2();
|
|
|
|
bezier2poly(bez, pol);
|
|
|
|
pol[c] -= center;
|
|
|
|
coeff[4] = sq(pol[a].x) + sq(pol[a].y);
|
|
coeff[3] = 2.0 * (pol[a].x * pol[b].x + pol[a].y * pol[b].y);
|
|
coeff[2] = 2.0 * (pol[a].x * pol[c].x + pol[a].y * pol[c].y) +
|
|
sq(pol[b].x) + sq(pol[b].y);
|
|
coeff[1] = 2.0 * (pol[b].x * pol[c].x + pol[b].y * pol[c].y);
|
|
coeff[0] = sq(pol[c].x) + sq(pol[c].y) - sq(radius);
|
|
|
|
vector<double> sol;
|
|
rootFinding(coeff, sol);
|
|
|
|
sol.erase(
|
|
remove_if(sol.begin(), sol.end(), greaterThanOneAndLesserThanZero),
|
|
sol.end());
|
|
|
|
for (UINT j = 0; j < sol.size(); ++j)
|
|
intersections.push_back(getWfromChunkAndT(&stroke, chunk, sol[j]));
|
|
}
|
|
|
|
#if defined(DEBUG) || defined(_DEBUG)
|
|
/*
|
|
cout<<"interesections:";
|
|
copy( intersections.begin(), intersections.end(), ostream_iterator<double>(
|
|
cout, " " ) );
|
|
cout<<endl;
|
|
*/
|
|
// assert to test intersections are not decrescent
|
|
vector<double> test;
|
|
|
|
adjacent_difference(intersections.begin(), intersections.end(),
|
|
back_inserter(test));
|
|
|
|
while (!test.empty()) {
|
|
assert(test.back() >= 0);
|
|
test.pop_back();
|
|
}
|
|
|
|
#endif
|
|
|
|
return intersections.size();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void splitStroke(const TStroke &tq, const vector<double> &pars,
|
|
std::vector<TStroke *> &v) {
|
|
if (pars.empty()) return;
|
|
|
|
UINT i, vSize = pars.size();
|
|
|
|
vector<double> length(vSize);
|
|
|
|
// primo passo estraggo i parametri di lunghezza
|
|
for (i = 0; i < vSize; ++i) length[i] = tq.getLength(pars[i]);
|
|
|
|
std::adjacent_difference(length.begin(), length.end(), length.begin());
|
|
|
|
TStroke *q1, q2, q3;
|
|
|
|
q1 = new TStroke();
|
|
tq.split(pars[0], *q1, q2);
|
|
|
|
assert(areAlmostEqual(q1->getLength(), length[0], 1e-4));
|
|
v.push_back(q1);
|
|
|
|
for (i = 1; i < vSize; ++i) {
|
|
q1 = new TStroke();
|
|
double par = q2.getParameterAtLength(length[i]);
|
|
assert(0 <= par && par <= 1.0);
|
|
q2.split(par, *q1, q3);
|
|
|
|
assert(areAlmostEqual(q1->getLength(), length[i], 1e-4));
|
|
v.push_back(q1);
|
|
q2 = q3;
|
|
}
|
|
|
|
v.push_back(new TStroke(q2));
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------
|
|
|
|
void detectCorners(const TStroke *stroke, double minDegree,
|
|
std::vector<int> &corners) {
|
|
const double minSin = fabs(sin(minDegree * M_PI_180));
|
|
|
|
const TThickQuadratic *quad1 = 0;
|
|
const TThickQuadratic *quad2 = 0;
|
|
UINT quadCount1 = stroke->getChunkCount();
|
|
TPointD speed1, speed2;
|
|
|
|
TPointD tan1, tan2;
|
|
quad1 = stroke->getChunk(0);
|
|
for (UINT j = 1; j < quadCount1; j++) {
|
|
quad2 = stroke->getChunk(j);
|
|
|
|
speed1 = quad1->getSpeed(1);
|
|
speed2 = quad2->getSpeed(0);
|
|
if (!(speed1 == TPointD() || speed2 == TPointD())) {
|
|
tan1 = normalize(speed1);
|
|
tan2 = normalize(speed2);
|
|
if (tan1 * tan2 < 0 || fabs(cross(tan1, tan2)) >= minSin)
|
|
corners.push_back(j);
|
|
}
|
|
quad1 = quad2;
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------
|
|
|
|
//---------------------------------------------------------------------------
|
|
namespace {
|
|
|
|
//! Rimuove le quadratiche nulle, cioe' i chunk in cui i TThickPoint sono quasi
|
|
//! uguali
|
|
bool removeNullQuadratic(TStroke *stroke, bool checkThickness = true) {
|
|
vector<TThickPoint> points;
|
|
UINT i, qCount = stroke->getChunkCount();
|
|
const TThickQuadratic *q;
|
|
TThickPoint p1, p2, p3;
|
|
|
|
// se i punti coincidono e' una stroke puntiforme ed e' ammessa
|
|
if (qCount == 1) return false;
|
|
|
|
bool check = false;
|
|
|
|
for (i = 0; i != qCount; i++) {
|
|
q = stroke->getChunk(i);
|
|
p1 = q->getThickP0();
|
|
p2 = q->getThickP1();
|
|
p3 = q->getThickP2();
|
|
|
|
if (areAlmostEqual(p1.x, p2.x) && areAlmostEqual(p2.x, p3.x) &&
|
|
areAlmostEqual(p1.y, p2.y) && areAlmostEqual(p2.y, p3.y) &&
|
|
(!checkThickness || (areAlmostEqual(p1.thick, p2.thick) &&
|
|
areAlmostEqual(p2.thick, p3.thick)))) {
|
|
// assert(!"null quadratic");
|
|
check = true;
|
|
} else {
|
|
points.push_back(p1);
|
|
points.push_back(p2);
|
|
}
|
|
}
|
|
|
|
if (check) {
|
|
points.push_back(p3);
|
|
stroke->reshape(&(points[0]), points.size());
|
|
}
|
|
|
|
return check;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
//! Converte un TThickPoint p0 in un T3DPoint
|
|
inline T3DPointD thickPntTo3DPnt(const TThickPoint &p0) {
|
|
return T3DPointD(p0.x, p0.y, p0.thick);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//! Converte un vettore di TThickPoint from in un vettore di T3DPointD to
|
|
void convert(const vector<TThickPoint> &from, vector<T3DPointD> &to) {
|
|
to.resize(from.size());
|
|
transform(from.begin(), from.end(), to.begin(), thickPntTo3DPnt);
|
|
}
|
|
|
|
typedef vector<TThickCubic *> TThickCubicArray;
|
|
typedef vector<TThickQuadratic *> QuadStrokeChunkArray;
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
/*!
|
|
Restituisce un puntatore ad un array di double che contiene la
|
|
distanza tra
|
|
il primo punto e i successivi diviso la distanza tra il primo
|
|
punto e l'ultimo
|
|
*/
|
|
double *chordLengthParameterize3D(const T3DPointD *pointsArrayBegin, int size) {
|
|
double *u = new double[size];
|
|
u[0] = 0.0;
|
|
int i;
|
|
for (i = 1; i < size; i++)
|
|
u[i] = u[i - 1] +
|
|
tdistance(*(pointsArrayBegin + i), *(pointsArrayBegin + i - 1));
|
|
for (i = 1; i < size; i++) {
|
|
assert(!isAlmostZero(u[size - 1]));
|
|
u[i] = u[i] / u[size - 1];
|
|
}
|
|
return u;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
/*!
|
|
Returns a \a measure of the maximal discrepancy of the points specified
|
|
by pointsArray from the corresponding cubic(u[]) points.
|
|
*/
|
|
double computeMaxError3D(const TThickCubic &cubic,
|
|
const T3DPointD *pointsArrayBegin, int size, double *u,
|
|
int &splitPoint) {
|
|
double err, maxErr = 0;
|
|
splitPoint = 0;
|
|
|
|
for (int i = 1; i < size - 1; i++) {
|
|
#ifdef USE_NEW_3D_ERROR_COMPUTE
|
|
|
|
// Being cubic a THICK cubic, we assume that the supplied points' z
|
|
// refers to thicknesses as well.
|
|
|
|
// So, given 2 thick points in the plane, we use the maximal distance
|
|
// of 'corresponding' outline points. Correspondence refers to the
|
|
// same relative position from centers. It's easily verifiable that
|
|
// such maximal distance is found when the relative positions both lie
|
|
// along the line connecting the two centers.
|
|
|
|
const TThickPoint &A(cubic.getThickPoint(u[i]));
|
|
const T3DPointD &B(pointsArrayBegin[i]);
|
|
|
|
err = sqrt(sq(B.x - A.x) + sq(B.y - A.y)) + fabs(B.z - A.thick);
|
|
|
|
#else
|
|
|
|
// Old version, less intuitive. Similar to the above, except that the
|
|
// 2d-norm is
|
|
// roughly multiplied by norm((TPointD) B-A) / A.thick ... I wonder
|
|
// why ....
|
|
|
|
T3DPointD delta =
|
|
thickPntTo3DPnt(cubic.getThickPoint(u[i])) - *(pointsArrayBegin + i);
|
|
|
|
double thick = cubic.getThickPoint(u[i]).thick;
|
|
if (thick <= 2.0) thick = 2.0;
|
|
|
|
err = norm2(TPointD(delta.x, delta.y)) / thick;
|
|
|
|
if (fabs(delta.z) > 2.0) err = err + fabs(delta.z);
|
|
|
|
#endif
|
|
|
|
if (err >= maxErr) {
|
|
maxErr = err;
|
|
splitPoint = i;
|
|
}
|
|
}
|
|
|
|
return maxErr;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
double NewtonRaphsonRootFind3D(const TThickCubic &cubic, const T3DPointD &p3D,
|
|
double u) {
|
|
TPointD qU = cubic.getPoint(u);
|
|
TPointD q1U = cubic.getSpeed(u);
|
|
TPointD q2U = cubic.getAcceleration(u);
|
|
|
|
TPointD p(p3D.x, p3D.y);
|
|
|
|
return u - ((qU - p) * q1U) / (norm2(q1U) + (qU - p) * q2U);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
int compareDouble(const void *e1, const void *e2) {
|
|
return (*(double *)e1 < *(double *)e2)
|
|
? -1
|
|
: (*(double *)e1 == *(double *)e2) ? 0 : 1;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
//! Ricalcola i valori di u[] sulla base del metodo di Newton Raphson
|
|
double *reparameterize3D(const TThickCubic &cubic,
|
|
const T3DPointD *pointsArrayBegin, int size,
|
|
double *u) {
|
|
double *uPrime = new double[size];
|
|
|
|
for (int i = 0; i < size; i++) {
|
|
uPrime[i] = NewtonRaphsonRootFind3D(cubic, *(pointsArrayBegin + i), u[i]);
|
|
if (!_finite(uPrime[i])) {
|
|
delete[] uPrime;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
qsort(uPrime, size, sizeof(double), compareDouble);
|
|
// std::sort( uPrime, uPrime+size );
|
|
|
|
if (uPrime[0] < 0.0 || uPrime[size - 1] > 1.0) {
|
|
delete[] uPrime;
|
|
return NULL;
|
|
}
|
|
|
|
assert(uPrime[0] >= 0.0);
|
|
assert(uPrime[size - 1] <= 1.0);
|
|
|
|
return uPrime;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
inline double B1(double u) {
|
|
double tmp = 1.0 - u;
|
|
return 3 * u * tmp * tmp;
|
|
}
|
|
inline double B2(double u) { return 3 * u * u * (1 - u); }
|
|
inline double B0plusB1(double u) {
|
|
double tmp = 1.0 - u;
|
|
return tmp * tmp * (1.0 + 2.0 * u);
|
|
}
|
|
inline double B2plusB3(double u) { return u * u * (3.0 - 2.0 * u); }
|
|
|
|
} // end of unnamed namespace
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//! Classe che definisce uno stroke cubico
|
|
|
|
class TCubicStroke {
|
|
private:
|
|
//! Genera un TThickCubic
|
|
TThickCubic *generateCubic3D(const T3DPointD pointsArrayBegin[],
|
|
const double uPrime[], int size,
|
|
const T3DPointD &tangentLeft,
|
|
const T3DPointD &tangentRight);
|
|
|
|
//! Genera una cubica in modo ricorsivo
|
|
void fitCubic3D(const T3DPointD pointsArrayBegin[], int size,
|
|
const T3DPointD &tangentLeft, const T3DPointD &tangentRight,
|
|
double error);
|
|
|
|
//! Rettangolo che contiene il TCubicStroke
|
|
TRectD m_bBox;
|
|
|
|
public:
|
|
//! Puntatore a vettore di puntatori a TThickCubic
|
|
vector<TThickCubic *> *m_cubicChunkArray;
|
|
|
|
//! Costruttore
|
|
TCubicStroke();
|
|
//! Costruttore
|
|
/*!
|
|
Costruisce un TCubicStroke uguale al TCubicStroke datogli
|
|
*/
|
|
TCubicStroke(const TCubicStroke &stroke);
|
|
//! Costruttore
|
|
/*!
|
|
Costruisce un TCubicStroke da un array di T3DPointD,
|
|
in funzione dei due parametri error e doDetectCorners
|
|
*/
|
|
TCubicStroke(const vector<T3DPointD> &pointsArray3D, double error,
|
|
bool doDetectCorners = true);
|
|
/*
|
|
TCubicStroke( vector<TPointD> &pointsArray,
|
|
double error,
|
|
const TPointD &tangentLeft,
|
|
const TPointD &tangentRight);
|
|
*/
|
|
//! Distruttore
|
|
~TCubicStroke();
|
|
};
|
|
|
|
namespace {
|
|
|
|
// It implements the degree reduction algorithm, returning the times
|
|
// that the cubic must be splitted (in the half), in order to approximate
|
|
// it with a sequence of quadratic bezier curves.
|
|
// This method doesn't take into count the thickness of the control points,
|
|
// so it just operates on the center line of the fat cubic curve
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
/*! It splits a cubic bezier curve 'splits' times in the half. Then each
|
|
subcurve is converted into quadratic curve, computing the middle control
|
|
circle as the weighted averege of the four control circles of the
|
|
original cubic subcurve.
|
|
*/
|
|
void doComputeQuadraticsFromCubic(const TThickCubic &cubic, int splits,
|
|
vector<TThickQuadratic *> &chunkArray) {
|
|
TThickPoint p0r, p1r, p2r, p3r, p0l, p1l, p2l, p3l;
|
|
|
|
p0l = cubic.getThickP0();
|
|
p1l = cubic.getThickP1();
|
|
p2l = cubic.getThickP2();
|
|
p3l = cubic.getThickP3();
|
|
|
|
p3l = 0.5 * (p3l + p2l);
|
|
p2l = 0.5 * (p2l + p1l);
|
|
p1l = 0.5 * (p1l + p0l);
|
|
p3l = 0.5 * (p3l + p2l);
|
|
p2l = 0.5 * (p2l + p1l);
|
|
p3l = 0.5 * (p3l + p2l);
|
|
|
|
// cubic.getControlPoints(p0r, p1r, p2r, p3r);
|
|
p0r = cubic.getThickP0();
|
|
p1r = cubic.getThickP1();
|
|
p2r = cubic.getThickP2();
|
|
p3r = cubic.getThickP3();
|
|
|
|
p0r = 0.5 * (p0r + p1r);
|
|
p1r = 0.5 * (p1r + p2r);
|
|
p2r = 0.5 * (p2r + p3r);
|
|
p0r = 0.5 * (p0r + p1r);
|
|
p1r = 0.5 * (p1r + p2r);
|
|
p0r = 0.5 * (p0r + p1r);
|
|
|
|
if (splits > 0) {
|
|
TThickCubic cubic1(cubic);
|
|
TThickCubic tmp_1(p0l, p1l, p2l, p3l);
|
|
std::swap(cubic1, tmp_1);
|
|
doComputeQuadraticsFromCubic(cubic1, splits - 1, chunkArray);
|
|
TThickCubic tmp_2(p0r, p1r, p2r, p3r);
|
|
std::swap(cubic1, tmp_2);
|
|
doComputeQuadraticsFromCubic(cubic1, splits - 1, chunkArray);
|
|
} else {
|
|
TThickQuadratic *chunkL =
|
|
new TThickQuadratic(p0l, 0.25 * (3 * (p1l + p2l) - (p0l + p3l)), p3l);
|
|
TThickQuadratic *chunkR =
|
|
new TThickQuadratic(p0r, 0.25 * (3 * (p1r + p2r) - (p0r + p3r)), p3r);
|
|
|
|
// int size = chunkArray.size();
|
|
chunkArray.push_back(chunkL);
|
|
chunkArray.push_back(chunkR);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
/*!
|
|
Genera una o piu' quadratiche da una cubica.
|
|
Pone una serie di condizioni , se sono verificate crea una unica quadratica,
|
|
altrimenti richiama doComputeQuadraticsFromCubic
|
|
*/
|
|
void computeQuadraticsFromCubic(const TThickCubic &cubic, double error,
|
|
vector<TThickQuadratic *> &chunkArray) {
|
|
const double T = 0.21132486540518711775; /* 1/2 - sqrt(3)/6 */
|
|
const double S = (1 - T);
|
|
int splits;
|
|
double dist2 = tdistance2(cubic.getP1(), cubic.getP2());
|
|
if (dist2 < 2) {
|
|
chunkArray.push_back(new TThickQuadratic(
|
|
cubic.getThickP0(), 0.5 * (cubic.getThickP1() + cubic.getThickP2()),
|
|
cubic.getThickP3()));
|
|
return;
|
|
}
|
|
|
|
TPointD dp = ((S * S * S) - (S * S - S * T / 2)) * cubic.getP0() +
|
|
((3 * S * S * T) - (S * T * 3 / 2)) * cubic.getP1() +
|
|
((3 * S * T * T) - (S * T * 3 / 2)) * cubic.getP2() +
|
|
((T * T * T) - (T * T - S * T / 2)) * cubic.getP3();
|
|
|
|
double dist_sq = norm2(dp);
|
|
|
|
for (splits = 0; dist_sq > error; splits++) dist_sq *= 0.125 * 0.125;
|
|
|
|
if (splits == 0) {
|
|
TPointD p0 = cubic.getP0();
|
|
TPointD p1 = cubic.getP1();
|
|
TPointD p2 = cubic.getP2();
|
|
TPointD p3 = cubic.getP3();
|
|
|
|
TPointD side01 = p1 - p0;
|
|
TPointD side32 = p2 - p3;
|
|
// se sono verificate TUTTE le condizioni dei seguenti if nidificati,
|
|
// allora viene
|
|
// generata un'unica quadratica per approssimare cubic. Altrimenti,
|
|
// se NON viene verificata ALMENO una delle condizioni dei seguenti if
|
|
// nidificati, allora
|
|
// cubic viene splittata ulteriormente e trattata da
|
|
// doComputeQuadraticsFromCubic
|
|
double det =
|
|
-side01.x * side32.y + side01.y * side32.x; // -cross(side01, side32)
|
|
if (!isAlmostZero(det)) // side01 incidente side32 (verra' calcolata
|
|
// l'intersezione...)
|
|
{
|
|
TPointD side03 = p3 - p0;
|
|
double det01 = -side03.x * side32.y + side03.y * side32.x;
|
|
double det32 = side01.x * side03.y - side01.y * side03.x;
|
|
double t01 = det01 / det;
|
|
double t32 = det32 / det;
|
|
// unused variable
|
|
#if 0
|
|
TPointD p01 = p0 + t01*(p1 - p0); // debug
|
|
TPointD p32 = p3 + t32*(p2 - p3); // debug
|
|
#endif
|
|
// TPointD intersection = p0 + t01*(p1 - p0) = p3 + t32*(p2 - p3)
|
|
// assert (areAlmostEqual(p0 + t01*(p1 - p0), p3 + t32*(p2 - p3)));
|
|
if (t01 >= 1 && t32 >= 1) { // poligonale p0_p1_p2_p3 NON e' a "zig-zag"
|
|
double norm2_side0p = norm2(t01 * side01);
|
|
double norm2_side3p = norm2(t32 * side32);
|
|
if (!isAlmostZero(norm2_side0p, 1e-20) &&
|
|
!isAlmostZero(norm2_side3p,
|
|
1e-20)) { // la condizione puo' essere violata anche
|
|
// nel caso !isAlmostZero(det) == true
|
|
double norm2_side03 = norm2(side03);
|
|
double tmp = norm2_side0p + norm2_side3p - norm2_side03;
|
|
// double cs = tmp/(2*sqrt(norm2_side0p)*sqrt(norm2_side3p)); //
|
|
// debug
|
|
double cs_sign =
|
|
tmp >= 0 ? 1 : -1; // cs2 "perde" il segno (cs2 = cs*cs)
|
|
// th coseno: gli assert del tipo acos(sqrt(cs2)) sono commentati
|
|
// perche' puo' essere cs2 > 1 per errori
|
|
// di approssimazione: tuttavia la cosa non costituisce problema
|
|
// (acos non viene mai eseguta...)
|
|
double cs2 = sq(tmp) / (4 * norm2_side0p * norm2_side3p);
|
|
// assert (0 <= cs2 && cs2 <= 1 + TConsts::epsilon);
|
|
assert(areAlmostEqual(
|
|
tsign(cs_sign) * sqrt(cs2),
|
|
tmp / (2 * sqrt(norm2_side0p) * sqrt(norm2_side3p))));
|
|
if (cs_sign < 0 || cs2 < 0.969846) // cos(10°)^2 = 0.969846
|
|
{ // limita distanza di intersection: elimina quadratiche "cappio"
|
|
// (con p1 "lontano")
|
|
// assert (acos(tsign(cs_sign)*sqrt(cs2)) > 10*M_PI_180);
|
|
assert(tsign(cs_sign) * sqrt(cs2) < cos(10 * M_PI_180));
|
|
TPointD intersection =
|
|
p0 + t01 * (p1 - p0); // = p2 + t32*(p2 - p3)
|
|
TThickPoint p(
|
|
intersection.x, intersection.y,
|
|
0.5 * (cubic.getThickP1().thick +
|
|
cubic.getThickP2()
|
|
.thick)); // compatibilita' precedente funzione
|
|
chunkArray.push_back(
|
|
new TThickQuadratic(cubic.getThickP0(), p, cubic.getThickP3()));
|
|
|
|
#ifdef _DEBUG
|
|
TThickQuadratic *lastTq = chunkArray.back();
|
|
TThickPoint pDeb = lastTq->getThickP0();
|
|
assert(_finite(pDeb.x));
|
|
assert(_finite(pDeb.y));
|
|
assert(_finite(pDeb.thick));
|
|
pDeb = lastTq->getThickP1();
|
|
assert(_finite(pDeb.x));
|
|
assert(_finite(pDeb.y));
|
|
assert(_finite(pDeb.thick));
|
|
pDeb = lastTq->getThickP2();
|
|
assert(_finite(pDeb.x));
|
|
assert(_finite(pDeb.y));
|
|
assert(_finite(pDeb.thick));
|
|
#endif
|
|
numSaved++; // variabile debug: compatibilita' precedente funzione
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
TPointD side03 = p3 - p0;
|
|
double det01 = -side03.x * side32.y + side03.y * side32.x;
|
|
if (isAlmostZero(det01)) {
|
|
// e' una retta!, crea unica quadratica con p in mezzo a p1 e p2
|
|
chunkArray.push_back(new TThickQuadratic(
|
|
cubic.getThickP0(), (cubic.getThickP1() + cubic.getThickP2()) * 0.5,
|
|
cubic.getThickP3()));
|
|
numSaved++; // variabile debug: compatibilita' precedente funzione
|
|
return;
|
|
}
|
|
}
|
|
// else: se arriva qui, almeno una delle condizioni negli if nidificati
|
|
// precedenti e' falsa
|
|
splits++;
|
|
}
|
|
doComputeQuadraticsFromCubic(cubic, splits - 1, chunkArray);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// TStroke *computeQuadStroke(const TCubicStroke& cubic, double error)
|
|
|
|
//! Ricava uno stroke quadratico da uno stroke cubico
|
|
TStroke *computeQuadStroke(const TCubicStroke &cubic) {
|
|
vector<TThickQuadratic *> chunkArray;
|
|
|
|
for (UINT i = 0; i < cubic.m_cubicChunkArray->size(); i++) {
|
|
TThickCubic tmp(*(*cubic.m_cubicChunkArray)[i]);
|
|
|
|
#ifdef _DEBUG
|
|
{
|
|
TThickPoint p = tmp.getThickP0();
|
|
assert(_finite(p.x));
|
|
assert(_finite(p.y));
|
|
assert(_finite(p.thick));
|
|
p = tmp.getThickP1();
|
|
assert(_finite(p.x));
|
|
assert(_finite(p.y));
|
|
assert(_finite(p.thick));
|
|
p = tmp.getThickP2();
|
|
assert(_finite(p.x));
|
|
assert(_finite(p.y));
|
|
assert(_finite(p.thick));
|
|
p = tmp.getThickP3();
|
|
assert(_finite(p.x));
|
|
assert(_finite(p.y));
|
|
assert(_finite(p.thick));
|
|
}
|
|
#endif
|
|
|
|
// this code use a Chebychev version of degree reduction
|
|
|
|
// This code is for old algorithm only:
|
|
computeQuadraticsFromCubic(tmp, 2.0 /*0.5*/, chunkArray);
|
|
}
|
|
|
|
TStroke *outStroke = TStroke::create(chunkArray);
|
|
|
|
clearPointerContainer(chunkArray);
|
|
|
|
return outStroke;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// helper function (defined in tstroke.h)
|
|
|
|
void computeQuadraticsFromCubic(const TThickPoint &p0, const TThickPoint &p1,
|
|
const TThickPoint &p2, const TThickPoint &p3,
|
|
double error,
|
|
vector<TThickQuadratic *> &chunkArray) {
|
|
computeQuadraticsFromCubic(TThickCubic(p0, p1, p2, p3), error, chunkArray);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TCubicStroke::TCubicStroke() : m_bBox() {
|
|
m_cubicChunkArray = new TThickCubicArray();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TCubicStroke::TCubicStroke(const TCubicStroke &stroke)
|
|
: m_bBox(stroke.m_bBox), m_cubicChunkArray(stroke.m_cubicChunkArray) {
|
|
m_cubicChunkArray = new TThickCubicArray(*stroke.m_cubicChunkArray);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TCubicStroke::~TCubicStroke() {
|
|
if (m_cubicChunkArray) {
|
|
while (!m_cubicChunkArray->empty()) {
|
|
delete m_cubicChunkArray->back();
|
|
m_cubicChunkArray->pop_back();
|
|
}
|
|
|
|
delete m_cubicChunkArray;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TCubicStroke::TCubicStroke(const vector<T3DPointD> &pointsArray3D, double error,
|
|
bool doDetectCorners) {
|
|
vector<int> corners;
|
|
corners.push_back(0);
|
|
if (doDetectCorners) detectCorners(pointsArray3D, 3, 3, 15, 100, corners);
|
|
corners.push_back(pointsArray3D.size() - 1);
|
|
|
|
#ifndef USE_NEW_3D_ERROR_COMPUTE
|
|
error *= error;
|
|
#endif
|
|
|
|
m_cubicChunkArray = new vector<TThickCubic *>();
|
|
|
|
for (int i = 1; i < (int)corners.size(); i++) {
|
|
int size = corners[i] - corners[i - 1] + 1;
|
|
int firstPoint = corners[i - 1];
|
|
T3DPointD tanLeft, tanRigth;
|
|
assert(size > 0);
|
|
if (size > 1) // capita che corners[i] = corners[i - 1] ("clic" senza drag
|
|
// oppure bug (noto!!!) del cornerDetector)
|
|
{
|
|
tanLeft = -pointsArray3D[firstPoint + 1] + pointsArray3D[firstPoint];
|
|
tanRigth = pointsArray3D[firstPoint + size - 2] -
|
|
pointsArray3D[firstPoint + size - 1];
|
|
|
|
if (norm2(tanLeft) > 0) tanLeft = normalize(tanLeft);
|
|
|
|
if (norm2(tanRigth) > 0) tanRigth = normalize(tanRigth);
|
|
|
|
fitCubic3D(&pointsArray3D[firstPoint], size, tanLeft, tanRigth, error);
|
|
} else if (pointsArray3D.size() == 1) {
|
|
// caso in cui i non calcola nessun corner a meno di quello iniziale
|
|
// e finale coincidenti: 1 solo punto campionato ("clic" senza drag)
|
|
assert(size == 1);
|
|
assert(corners.size() == 2);
|
|
assert(corners[0] == 0);
|
|
assert(corners[1] == 0);
|
|
m_cubicChunkArray->push_back(
|
|
new TThickCubic(pointsArray3D[0], pointsArray3D[0], pointsArray3D[0],
|
|
pointsArray3D[0]));
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TCubicStroke::fitCubic3D(const T3DPointD pointsArrayBegin[], int size,
|
|
const T3DPointD &tangentLeft,
|
|
const T3DPointD &tangentRight, double error) {
|
|
int maxIterations = 4;
|
|
|
|
if (size == 2) {
|
|
double dist = tdistance(*pointsArrayBegin, *(pointsArrayBegin + 1)) / 3.0;
|
|
TThickCubic *strokeCubicChunk = new TThickCubic(
|
|
*pointsArrayBegin, *pointsArrayBegin - dist * tangentLeft,
|
|
*(pointsArrayBegin + 1) + dist * tangentRight, *(pointsArrayBegin + 1));
|
|
|
|
m_cubicChunkArray->push_back(strokeCubicChunk);
|
|
return;
|
|
}
|
|
|
|
double *u = chordLengthParameterize3D(pointsArrayBegin, size);
|
|
TThickCubic *cubic =
|
|
generateCubic3D(pointsArrayBegin, u, size, tangentLeft, tangentRight);
|
|
|
|
int splitPoint;
|
|
double maxError =
|
|
computeMaxError3D(*cubic, pointsArrayBegin, size, u, splitPoint);
|
|
|
|
if (maxError < error) {
|
|
delete[] u;
|
|
m_cubicChunkArray->push_back(cubic);
|
|
return;
|
|
}
|
|
|
|
// if (maxError<error)
|
|
{
|
|
double *uPrime = NULL;
|
|
for (int i = 0; i < maxIterations; i++) {
|
|
// delete uPrime;
|
|
uPrime = reparameterize3D(*cubic, pointsArrayBegin, size, u);
|
|
if (!uPrime) break;
|
|
|
|
delete cubic;
|
|
cubic = generateCubic3D(pointsArrayBegin, uPrime, size, tangentLeft,
|
|
tangentRight);
|
|
maxError =
|
|
computeMaxError3D(*cubic, pointsArrayBegin, size, uPrime, splitPoint);
|
|
if (maxError < error) {
|
|
delete[] uPrime;
|
|
delete[] u;
|
|
m_cubicChunkArray->push_back(cubic);
|
|
return;
|
|
}
|
|
delete[] u;
|
|
u = uPrime;
|
|
}
|
|
}
|
|
|
|
delete[] u;
|
|
delete cubic;
|
|
|
|
T3DPointD centralTangent;
|
|
if (*(pointsArrayBegin + splitPoint - 1) ==
|
|
*(pointsArrayBegin + splitPoint + 1))
|
|
centralTangent = normalize(*(pointsArrayBegin + splitPoint) -
|
|
*(pointsArrayBegin + splitPoint + 1));
|
|
else
|
|
centralTangent = normalize(*(pointsArrayBegin + splitPoint - 1) -
|
|
*(pointsArrayBegin + splitPoint + 1));
|
|
|
|
fitCubic3D(pointsArrayBegin, splitPoint + 1, tangentLeft, centralTangent,
|
|
error);
|
|
|
|
fitCubic3D(pointsArrayBegin + splitPoint, size - splitPoint, centralTangent,
|
|
tangentRight, error);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TThickCubic *TCubicStroke::generateCubic3D(const T3DPointD pointsArrayBegin[],
|
|
const double uPrime[], int size,
|
|
const T3DPointD &tangentLeft,
|
|
const T3DPointD &tangentRight) {
|
|
double X[2], C[2][2];
|
|
int i;
|
|
|
|
T3DPointD p0 = *pointsArrayBegin;
|
|
T3DPointD p3 = *(pointsArrayBegin + size - 1);
|
|
|
|
C[0][0] = C[0][1] = X[0] = 0;
|
|
C[1][0] = C[1][1] = X[1] = 0;
|
|
|
|
for (i = 0; i < size; i++) {
|
|
const T3DPointD A[2] = {
|
|
tangentLeft * B1(uPrime[i]), tangentRight * B2(uPrime[i]),
|
|
};
|
|
|
|
C[0][0] += A[0] * A[0];
|
|
C[0][1] += A[0] * A[1];
|
|
C[1][1] += A[1] * A[1];
|
|
|
|
C[1][0] = C[0][1];
|
|
|
|
T3DPointD tmp = *(pointsArrayBegin + i) - (B0plusB1(uPrime[i]) * p0) +
|
|
B2plusB3(uPrime[i]) * p3;
|
|
X[0] += A[0] * tmp;
|
|
X[1] += A[1] * tmp;
|
|
}
|
|
|
|
double detC0C1 = C[0][0] * C[1][1] - C[0][1] * C[1][0];
|
|
double detC0X = X[1] * C[0][0] - X[0] * C[0][1];
|
|
double detXC1 = X[0] * C[1][1] - X[1] * C[0][1];
|
|
|
|
if (isAlmostZero(detC0C1)) detC0C1 = C[0][0] * C[1][1] * 10e-12;
|
|
|
|
double alphaL = detXC1 / detC0C1;
|
|
double alphaR = detC0X / detC0C1;
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// il problema e' che i valori stupidi per alpha non adrebbere messi solo
|
|
// quando ci si accorge
|
|
// che il segno e' sbagliato, ma anche se i valori sono troppo alti. Ma come
|
|
// fare a valutarlo?
|
|
|
|
// Intanto bisognerebbe accertarsi che 'sti valori alcune volte sono cosi'
|
|
// assurdi solo per problemi
|
|
// di approssimazione e non per bachi nel codice
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
double xmin = (numeric_limits<double>::max)();
|
|
double ymin = (numeric_limits<double>::max)();
|
|
double xmax = -(numeric_limits<double>::max)();
|
|
double ymax = -(numeric_limits<double>::max)();
|
|
double thickmin = (numeric_limits<double>::max)();
|
|
double thickmax = -(numeric_limits<double>::max)();
|
|
|
|
for (i = 0; i < size; i++) {
|
|
if (pointsArrayBegin[i].x < xmin) xmin = pointsArrayBegin[i].x;
|
|
if (pointsArrayBegin[i].x > xmax) xmax = pointsArrayBegin[i].x;
|
|
if (pointsArrayBegin[i].y < ymin) ymin = pointsArrayBegin[i].y;
|
|
if (pointsArrayBegin[i].y > ymax) ymax = pointsArrayBegin[i].y;
|
|
if (pointsArrayBegin[i].z < thickmin) thickmin = pointsArrayBegin[i].z;
|
|
if (pointsArrayBegin[i].z > thickmax) thickmax = pointsArrayBegin[i].z;
|
|
}
|
|
|
|
double lx = xmax - xmin;
|
|
assert(lx >= 0);
|
|
double ly = ymax - ymin;
|
|
assert(ly >= 0);
|
|
double lt = thickmax - thickmin;
|
|
assert(lt >= 0);
|
|
|
|
xmin -= lx;
|
|
xmax += lx;
|
|
ymin -= ly;
|
|
ymax += ly;
|
|
thickmin -= lt;
|
|
thickmax += lt;
|
|
|
|
TRectD bbox(xmin, ymin, xmax, ymax);
|
|
|
|
if (alphaL < 0.0 || alphaR < 0.0)
|
|
alphaL = alphaR = sqrt(tdistance2(p0, p3)) / 3.0;
|
|
|
|
T3DPointD p1 = p0 - tangentLeft * alphaL;
|
|
T3DPointD p2 = p3 + tangentRight * alphaR;
|
|
|
|
if (!bbox.contains(TPointD(p1.x, p1.y)) ||
|
|
!bbox.contains(TPointD(p2.x, p2.y))) {
|
|
alphaL = alphaR = sqrt(tdistance2(p0, p3)) / 3.0;
|
|
p1 = p0 - tangentLeft * alphaL;
|
|
p2 = p3 + tangentRight * alphaR;
|
|
}
|
|
|
|
if (p1.z < thickmin)
|
|
p1.z = thickmin;
|
|
else if (p1.z > thickmax)
|
|
p1.z = thickmax;
|
|
|
|
if (p2.z < thickmin)
|
|
p2.z = thickmin;
|
|
else if (p2.z > thickmax)
|
|
p2.z = thickmax;
|
|
|
|
TThickCubic *thickCubic = new TThickCubic(
|
|
TThickPoint(p0.x, p0.y, p0.z), TThickPoint(p1.x, p1.y, p1.z),
|
|
TThickPoint(p2.x, p2.y, p2.z), TThickPoint(p3.x, p3.y, p3.z));
|
|
|
|
return thickCubic;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//! Restituisce uno stroke da un vettore di TThickPoint
|
|
/*!
|
|
Trasforma un vettore di TThickPoint in un vettore di T3DPointD che usa per
|
|
trovare un
|
|
TCubicStroke, cioe' uno stroke cubico. Da questo trova lo stroke
|
|
quadratico.
|
|
*/
|
|
TStroke *TStroke::interpolate(const vector<TThickPoint> &points, double error,
|
|
bool findCorners) {
|
|
vector<T3DPointD> pointsArray3D;
|
|
convert(points, pointsArray3D);
|
|
|
|
TCubicStroke cubicStroke(pointsArray3D, error, findCorners);
|
|
numSaved = 0;
|
|
|
|
// TStroke *stroke = computeQuadStroke(cubicStroke,error);
|
|
TStroke *stroke = computeQuadStroke(cubicStroke);
|
|
|
|
#ifdef _DEBUG
|
|
|
|
UINT cpIndex = 0;
|
|
TThickPoint p;
|
|
for (; cpIndex != (UINT)stroke->getControlPointCount(); cpIndex++) {
|
|
p = stroke->getControlPoint(cpIndex);
|
|
assert(_finite(p.x));
|
|
assert(_finite(p.y));
|
|
assert(_finite(p.thick));
|
|
}
|
|
#endif
|
|
|
|
removeNullQuadratic(stroke);
|
|
stroke->invalidate();
|
|
|
|
return stroke;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
//! Restituisce uno stroke da un vettore di TThickQuadratic
|
|
/*!
|
|
Estrae dalle curve i punti di controllo e crea con questi un nuovo stroke
|
|
*/
|
|
TStroke *TStroke::create(const vector<TThickQuadratic *> &curves) {
|
|
if (curves.empty()) return 0;
|
|
|
|
// make a vector of control points
|
|
vector<TThickPoint> ctrlPnts;
|
|
|
|
extractStrokeControlPoints(curves, ctrlPnts);
|
|
|
|
TStroke *stroke = new TStroke(ctrlPnts);
|
|
|
|
stroke->invalidate();
|
|
|
|
return stroke;
|
|
}
|
|
|
|
//============================================================================
|
|
|
|
TStrokeProp::TStrokeProp(const TStroke *stroke)
|
|
: m_stroke(stroke), m_strokeChanged(true), m_mutex() {}
|
|
|
|
//============================================================================
|
|
|
|
TStroke::OutlineOptions::OutlineOptions()
|
|
: m_capStyle(ROUND_CAP)
|
|
, m_joinStyle(ROUND_JOIN)
|
|
, m_miterLower(0.0)
|
|
, m_miterUpper(4.0) {}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TStroke::OutlineOptions::OutlineOptions(UCHAR capStyle, UCHAR joinStyle,
|
|
double lower, double upper)
|
|
: m_capStyle(capStyle)
|
|
, m_joinStyle(joinStyle)
|
|
, m_miterLower(lower)
|
|
, m_miterUpper(upper) {}
|