Blob Blame Raw


#include "tl2lautocloser.h"

#include "tgl.h"

#include "tvectorimage.h"
#include "tstroke.h"

#include <QDebug>

//=============================================================================
#ifdef _WIN32

class MyTimer
{
	bool m_enabled;
	LARGE_INTEGER m_freq;
	LARGE_INTEGER m_startTime, m_overhead;

public:
	MyTimer() : m_enabled(false)
	{
		if (QueryPerformanceFrequency(&m_freq)) {
			m_enabled = true;
			LARGE_INTEGER curTime;
			QueryPerformanceCounter(&m_startTime);
			QueryPerformanceCounter(&curTime);
			m_overhead.QuadPart = curTime.QuadPart - m_startTime.QuadPart;
		}
	}

	void start()
	{
		if (!m_enabled)
			return;
		QueryPerformanceCounter(&m_startTime);
	}

	double elapsedSeconds()
	{
		LARGE_INTEGER curTime;
		QueryPerformanceCounter(&curTime);
		LONGLONG microseconds = 1000000 *
								(curTime.QuadPart - m_startTime.QuadPart - m_overhead.QuadPart) / m_freq.QuadPart;
		return 0.000001 * (double)microseconds;
	}
};
#endif

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

namespace
{

// find the stroke curvature at w
// cfr. http://en.wikipedia.org/wiki/Curvature
TPointD getCurvature(TStroke *stroke, double w)
{
	const double h = 0.0001;
	double w0 = std::max(0.0, w - h);
	double w1 = std::min(1.0, w + h);
	TPointD p0 = stroke->getPoint(w0);
	TPointD p1 = stroke->getPoint(w1);
	double ds = norm(p0 - p1);
	double f = (w1 - w0) / ds;

	TPointD d = stroke->getSpeed(w) * f;
	TPointD d0 = stroke->getSpeed(w0) * f;
	TPointD d1 = stroke->getSpeed(w1) * f;
	TPointD dd = (d1 - d0) * (1.0 / ds);

	double c = (d.x * dd.y - d.y * dd.x) / pow(d.x * d.x + d.y * d.y, 1.5);
	TPointD crv = normalize(rotate90(d)) * c;

	return crv;
}

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

//
// A point along a stroke: ths stroke itself, pos, w,s, crv
//
struct StrokePoint {
	double w, s;
	TPointD pos, crv, crvdir; // crvdir is crv normalized (or 0)
	TPointD tgdir;			  // tgdir is the normalized tangent
	TStroke *stroke;
	StrokePoint(TStroke *stroke_, double s_)
		: stroke(stroke_), w(stroke_->getParameterAtLength(s_)), s(s_)
	{
		pos = stroke->getPoint(w);
		crv = getCurvature(stroke, w);
		double c = norm(crv);
		if (c > 0)
			crvdir = crv * (1.0 / c);
		else
			crvdir = TPointD();
		tgdir = stroke->getSpeed(w);
		double tgdirNorm = norm(tgdir);
		if (tgdirNorm > 0.000001)
			tgdir = tgdir * (1.0 / tgdirNorm);
		else
			tgdir = TPointD(0, 0);
	}
	StrokePoint()
		: w(0), s(0), pos(), crv(), crvdir(), stroke(0)
	{
	}
};

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

// a set of StrokePoint evenly spaced along the whole stroke
// (curvilinear distance between two adjacent point is "inc")
struct StrokePointSet {
	TStroke *stroke;
	std::vector<StrokePoint> points;
	StrokePointSet(TStroke *stroke_ = 0)
		: stroke(stroke_)
	{
		const double inc = 5;
		if (stroke_) {
			double length = stroke->getLength();
			double s = 0;
			if (stroke->isSelfLoop())
				length -= inc;
			while (s < length) {
				points.push_back(StrokePoint(stroke, s));
				s += inc;
			}
		}
	}
};

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

class StrokesIntersection
{
public:
	std::vector<double> m_ida, m_idb; // distances to the closest intersection
									  // (referred to StrokePointSet)

	StrokesIntersection() {}
	StrokesIntersection(
		const StrokePointSet &psa,
		const StrokePointSet &psb,
		const std::vector<DoublePair> *intersection);

	~StrokesIntersection()
	{
	}

	void update(
		const StrokePointSet &psa,
		const StrokePointSet &psb,
		const std::vector<DoublePair> &intersections);

	static void wrap(std::vector<double> &is, TStroke *stroke);

	static void computeIntersectionDistances(
		std::vector<double> &id,
		const StrokePointSet &ps,
		const std::vector<double> &is);

	StrokesIntersection *swapped() const
	{
		StrokesIntersection *s = new StrokesIntersection();
		s->m_ida = m_idb;
		s->m_idb = m_ida;
		return s;
	}
};

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

StrokesIntersection::StrokesIntersection(const StrokePointSet &psa, const StrokePointSet &psb,
										 const std::vector<DoublePair> *intersections)
{
	if (!psa.stroke || !psb.stroke)
		return; // it should never happen
	std::vector<DoublePair> myIntersections;
	if (!intersections) {
		intersections = &myIntersections;
		intersect(psa.stroke, psb.stroke, myIntersections);
	}
	update(psa, psb, *intersections);
}

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

void StrokesIntersection::update(
	const StrokePointSet &psa,
	const StrokePointSet &psb,
	const std::vector<DoublePair> &intersections)
{
	TStroke *strokea = psa.stroke;
	TStroke *strokeb = psb.stroke;
	m_ida.clear();
	m_idb.clear();

	if (!strokea || !strokeb)
		return; // it should never happen

	m_ida.resize(psa.points.size(), -1);
	m_idb.resize(psb.points.size(), -1);

	int n = (int)intersections.size();
	if (n <= 0)
		return;

	// compute intersection arclengths
	std::vector<double> isa(n), isb(n);
	for (int i = 0; i < n; i++) {
		isa[i] = strokea->getLength(intersections[i].first);
		isb[i] = strokeb->getLength(intersections[i].second);
	}

	if (strokea == strokeb) {
		// una sola stroke. ogni intersezione deve valere per due
		isa.insert(isa.end(), isb.begin(), isb.end());
		std::sort(isa.begin(), isa.end());
		isb = isa;
	} else {
		// due stroke. ordino
		std::sort(isa.begin(), isa.end());
		std::sort(isb.begin(), isb.end());
	}
	if (strokea->isSelfLoop() && !isa.empty())
		wrap(isa, strokea);
	if (strokeb->isSelfLoop() && !isb.empty())
		wrap(isb, strokeb);
	computeIntersectionDistances(m_ida, psa, isa);
	computeIntersectionDistances(m_idb, psb, isb);
}

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

// the stroke is a self loop. the last intersection is mirrored before s=0
// the first intersection is mirroed after s=length
void StrokesIntersection::wrap(std::vector<double> &is, TStroke *stroke)
{
	assert(stroke->isSelfLoop());
	if (!is.empty()) {
		double length = stroke->getLength();
		is.insert(is.begin(), is.back() - length);
		is.push_back(is[1] + length);
	}
}

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

// for each StrokePoint computes the related intersection distance (i.e. the distance to
// the closest intersection)
void StrokesIntersection::computeIntersectionDistances(
	std::vector<double> &id,
	const StrokePointSet &ps,
	const std::vector<double> &is)
{
	id.clear();
	id.resize(ps.points.size(), -1);
	int isn = (int)is.size();
	int k = 0;
	for (int i = 0; i < (int)ps.points.size(); i++) {
		double d = -1;
		if (k < isn) {
			double s = ps.points[i].s;
			while (k + 1 < isn && is[k + 1] < s)
				k++;
			if (is[k] > s)
				d = is[k] - s;
			else if (k + 1 < isn)
				d = std::min(is[k + 1] - s, s - is[k]);
			else
				d = s - is[k];
		}
		id[i] = d;
	}
}
//---------------------------------------------------------------------------

} // namespace

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

class TL2LAutocloser::Imp
{
public:
	double m_maxDist2;
	std::map<TStroke *, StrokePointSet *> m_strokes;
	std::map<std::pair<TStroke *, TStroke *>, StrokesIntersection *> m_intersections;

	// debug
	std::pair<StrokePointSet *, StrokePointSet *> m_lastStrokePair;
	std::vector<TL2LAutocloser::Segment> m_segments;

	Imp() : m_maxDist2(50 * 50), m_lastStrokePair((StrokePointSet *)0, (StrokePointSet *)0) {}
	~Imp();

	StrokePointSet *getPointSet(TStroke *stroke)
	{
		std::map<TStroke *, StrokePointSet *>::iterator it = m_strokes.find(stroke);
		if (it != m_strokes.end())
			return it->second;
		StrokePointSet *ps = new StrokePointSet(stroke);
		m_strokes[stroke] = ps;
		return ps;
	}

	StrokesIntersection *getIntersection(TStroke *strokea, TStroke *strokeb,
										 const std::vector<DoublePair> *intersection)
	{
		std::map<std::pair<TStroke *, TStroke *>, StrokesIntersection *>::iterator
			it = m_intersections.find(std::make_pair(strokea, strokeb));
		if (it != m_intersections.end())
			return it->second;
		StrokesIntersection *si = new StrokesIntersection(strokea, strokeb, intersection);
		m_intersections[std::make_pair(strokea, strokeb)] = si;
		m_intersections[std::make_pair(strokeb, strokea)] = si->swapped();
		return si;
	}

	void search(std::vector<TL2LAutocloser::Segment> &segments,
				TStroke *strokea, TStroke *strokeb,
				const std::vector<DoublePair> *intersection);

	void drawLinks();
	void drawStroke(StrokePointSet *);
	void drawStrokes();
};

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

TL2LAutocloser::Imp::~Imp()
{
	std::map<TStroke *, StrokePointSet *>::iterator i;
	for (i = m_strokes.begin(); i != m_strokes.end(); ++i)
		delete i->second;
	std::map<std::pair<TStroke *, TStroke *>, StrokesIntersection *>::iterator j;
	for (j = m_intersections.begin(); j != m_intersections.end(); ++j)
		delete j->second;
	m_strokes.clear();
	m_intersections.clear();
}

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

void TL2LAutocloser::Imp::drawLinks()
{
	glColor3d(0, 0, 1);
	glBegin(GL_LINES);
	for (int i = 0; i < (int)m_segments.size(); i++) {
		tglVertex(m_segments[i].p0);
		tglVertex(m_segments[i].p1);
	}
	glEnd();
}

void TL2LAutocloser::Imp::drawStroke(StrokePointSet *ps)
{
	if (ps && ps->points.size() >= 2) {
		glBegin(GL_LINES);
		for (int i = 0; i < (int)ps->points.size(); i++)
			tglVertex(ps->points[i].pos);
		glEnd();
	}
}

void TL2LAutocloser::Imp::drawStrokes()
{
	if (m_lastStrokePair.first) {
		if (m_lastStrokePair.first == m_lastStrokePair.second) {
			glColor3d(1, 0, 1);
			drawStroke(m_lastStrokePair.first);
		} else {
			glColor3d(1, 0, 0);
			drawStroke(m_lastStrokePair.first);
			glColor3d(0, 1, 0);
			drawStroke(m_lastStrokePair.second);
		}
	}
}

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

// search autoclose segments
void TL2LAutocloser::Imp::search(
	std::vector<TL2LAutocloser::Segment> &segments,
	TStroke *strokea, TStroke *strokeb,
	const std::vector<DoublePair> *intersections)
{
	m_lastStrokePair.first = m_lastStrokePair.second = 0;
	if (strokea == 0 || strokeb == 0)
		return;
	/*
#ifdef _WIN32
  MyTimer timer;
  qDebug() << "search started";
  timer.start();
#endif
*/
	bool sameStroke = strokea == strokeb;

	StrokePointSet *psa = getPointSet(strokea);
	StrokePointSet *psb = sameStroke ? psa : getPointSet(strokeb);
	m_lastStrokePair.first = psa;
	m_lastStrokePair.second = psb;
	StrokesIntersection *si = getIntersection(strokea, strokeb, intersections);

	// find links (i.e. best matching point in psb for each point in psa)

	std::vector<std::pair<int, int>> links;
	int na = (int)psa->points.size();
	int nb = (int)psb->points.size();
	int i;
	for (i = 0; i < na; i++) {
		double minDist2 = 0;
		int k = -1;

		int j0 = 0;
		if (sameStroke)
			j0 = i + 1;
		for (int j = 0; j < nb; j++) {
			TPointD delta = psb->points[j].pos - psa->points[i].pos;
			double dist2 = norm2(delta);

			if (dist2 > m_maxDist2)
				continue;
			if (dist2 < 0.000001)
				continue;

			double dist = sqrt(dist2);
			delta = delta * (1.0 / dist);
			double cs = 0.005;

			if ((psa->points[i].crvdir * delta) > -cs)
				continue;
			if ((psb->points[j].crvdir * delta) < cs)
				continue;

			if (sameStroke) {
				double ds = fabs(psa->points[i].s - psb->points[j].s);
				if (ds < dist * 1.5)
					continue;
			}
			if ((si->m_ida[i] > 0 && si->m_ida[i] < dist) || (si->m_idb[j] > 0 && si->m_idb[j] < dist))
				continue;
			if (k < 0 || dist2 < minDist2) {
				k = j;
				minDist2 = dist2;
			}
		}
		if (k >= 0 && (!sameStroke || k > i) && (0 < i && i < na - 1) && (0 < k && k < nb - 1)) {
			links.push_back(std::make_pair(i, k));
		}
	}

	/*
  for(i=0;i<(int)links.size();i++)
  {
    int ia = links[i].first;
    int ib = links[i].second;
    double mind2 = norm2(psa->points[ia].pos - psb->points[ib].pos);
    TL2LAutocloser::Segment segment;
    segment.stroke0 = strokea;
    segment.stroke1 = strokeb;
    segment.w0 = psa->points[ia].w;
    segment.w1 = psb->points[ib].w;
    segment.p0 = strokea->getThickPoint(segment.w0);
    segment.p1 = strokeb->getThickPoint(segment.w1);
    segment.dist2 = mind2;
    segments.push_back(segment);
  }
  */

	// select best links
	for (i = 0; i < (int)links.size(); i++) {
		int ia = links[i].first;
		int ib = links[i].second;
		double d2 = norm2(psa->points[ia].pos - psb->points[ib].pos);
		double mind2 = d2;
		int k = i;
		while (i + 1 < (int)links.size()) {
			int ia2 = links[i + 1].first;
			int ib2 = links[i + 1].second;
			double d22 = norm2(psa->points[ia2].pos - psb->points[ib2].pos);
			double d2m = (d2 + d22) * 0.5;
			double dsa = psa->points[ia2].s - psa->points[ia].s;
			double dsb = psb->points[ib2].s - psb->points[ib].s;
			if (dsa * dsa > d2m || dsb * dsb > d2m)
				break;
			if (d22 < mind2) {
				mind2 = d22;
				k = i + 1;
			}
			ia = ia2;
			ib = ib2;
			d2 = d22;
			i++;
		}
		ia = links[k].first;
		ib = links[k].second;
		TL2LAutocloser::Segment segment;
		segment.stroke0 = strokea;
		segment.stroke1 = strokeb;
		segment.w0 = psa->points[ia].w;
		segment.w1 = psb->points[ib].w;
		segment.p0 = strokea->getThickPoint(segment.w0);
		segment.p1 = strokeb->getThickPoint(segment.w1);
		segment.dist2 = mind2;
		segments.push_back(segment);
	}
	/*
#ifdef _WIN32
  double elapsed = timer.elapsedSeconds();
  qDebug() << "search completed. time=" << elapsed << "s";
#endif
 */
}

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

TL2LAutocloser::TL2LAutocloser()
	: m_imp(new Imp())
{
}

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

TL2LAutocloser::~TL2LAutocloser()
{
}

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

void TL2LAutocloser::setMaxDistance2(double dist2)
{
	m_imp->m_maxDist2 = dist2;
}

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

double TL2LAutocloser::getMaxDistance2() const
{
	return m_imp->m_maxDist2;
}

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

void TL2LAutocloser::search(std::vector<Segment> &segments, TStroke *stroke0, TStroke *stroke1)
{
	if (stroke0 && stroke1)
		m_imp->search(segments, stroke0, stroke1, 0);
}

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

void TL2LAutocloser::search(
	std::vector<Segment> &segments,
	TStroke *stroke0, TStroke *stroke1,
	const std::vector<DoublePair> &intersection)
{
	if (stroke0 && stroke1)
		m_imp->search(segments, stroke0, stroke1, &intersection);
}

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

void TL2LAutocloser::draw()
{
	m_imp->drawStrokes();
	m_imp->drawLinks();
}