File: PolygonHelper.h

package info (click to toggle)
veroroute 2.38-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 6,044 kB
  • sloc: cpp: 21,512; xml: 89; sh: 65; lisp: 20; makefile: 5
file content (185 lines) | stat: -rw-r--r-- 7,136 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/*
	VeroRoute - Qt based Veroboard/Perfboard/PCB layout & routing application.

	Copyright (C) 2017  Alex Lawrow    ( dralx@users.sourceforge.net )

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "Common.h"
#include <QPolygonF>
#include "CurveList.h"	// For GPEN

// A helper for calculating separations between tracks

struct MyPointF : public QPointF		// A point + the pen radius for drawing it
{
	MyPointF(qreal x = 0, qreal y = 0, qreal radius = 0) : QPointF(x,y), m_radius(radius) {}
	~MyPointF() {}
	MyPointF(const QPointF& p, qreal radius) : QPointF(p), m_radius(radius) {}
	MyPointF(const MyPointF& o) : QPointF(o), m_radius(o.m_radius) {}
	MyPointF& operator=(const MyPointF& o)	{ QPointF::operator=(o); m_radius = o.m_radius; return *this; }
	qreal	m_radius	= 0;		// Pen radius
};

struct MyPolygonF : public QPolygonF	// A polygon + the pen radii for drawing it
{
	MyPolygonF() {}
	~MyPolygonF() {}
	MyPolygonF(const QPolygonF& p, GPEN eTrkPen, GPEN ePadPen, qreal radiusTrk, qreal radiusPad, bool bClosed)
		: QPolygonF(p), m_eTrkPen(eTrkPen), m_ePadPen(ePadPen), m_radiusTrk(radiusTrk), m_radiusPad(radiusPad), m_bClosed(bClosed)
	{
		Process();
	}
	MyPolygonF(const MyPolygonF& o)
		: QPolygonF(o), m_eTrkPen(o.m_eTrkPen), m_ePadPen(o.m_ePadPen), m_radiusTrk(o.m_radiusTrk), m_radiusPad(o.m_radiusPad), m_bClosed(o.m_bClosed)
	{
		Process();
	}
	MyPolygonF& operator=(const MyPolygonF& o)
	{
		QPolygonF::operator=(o);
		m_eTrkPen	= o.m_eTrkPen;
		m_ePadPen	= o.m_ePadPen;
		m_radiusTrk	= o.m_radiusTrk;
		m_radiusPad	= o.m_radiusPad;
		m_bClosed	= o.m_bClosed;
		Process();
		return *this;
	}
	void flipV(const QPointF& pC) // Flip vertically keeping pC fixed
	{
		const qreal y0 = 2.0 * pC.y();
		for (auto p = begin(); p != end(); ++p)
			(*p).setY( y0 - (*p).y() );
	}
	void flipH(const QPointF& pC) // Flip horizontally keeping pC fixed
	{
		const qreal x0 = 2.0 * pC.x();
		for (auto p = begin(); p != end(); ++p)
			(*p).setX( x0 - (*p).x() );
	}
	void rotateCW(const QPointF& pC)	// Rotate CW about pC
	{
		for (auto p = begin(); p != end(); ++p)
		{
			const QPointF d = (*p) - pC;
			(*p) = pC + QPointF(-d.y(), d.x());
		}
	}
	bool HaveVariTracks() const { return QPolygonF::size() > 1 && m_radiusPad > m_radiusTrk && m_radiusTrk > 0; }
	void Process(bool bForce = false) const
	{
		// Set flags to indicate if points and edges are fat or thin
		const int iSize = QPolygonF::size();
		if ( !bForce && m_bFatPoint.size() == static_cast<size_t>(iSize) ) return;
		m_bFatPoint.clear();	m_bFatPoint.resize(static_cast<size_t>(iSize), false);
		m_bFatEdge.clear();		m_bFatEdge.resize(static_cast<size_t>(iSize), false);
		if ( !HaveVariTracks() ) return;
		for (int i = 0, j = 1, iEnd = m_bClosed ? iSize : (iSize-1); i < iEnd; i++, j++)
		{
			if ( j == iSize ) j = 0;
			const QPointF edge(operator[](i) - operator[](j));
			if ( edge.x() == 0.0 || edge.y() == 0.0 )	// If edge is V or H ...
			{
				const size_t I(static_cast<size_t>(i)), J(static_cast<size_t>(j));
				m_bFatPoint[I] = m_bFatPoint[J] = m_bFatEdge[I] = true;
			}
		}
	}
	// If both pens are set, then it means we are in fat tracks mode
	// and using the pad pen for the HV sections instead of the track pen
	GPEN	m_eTrkPen	= GPEN::NONE;	// TRK, TRK_GAP, or NONE
	GPEN	m_ePadPen	= GPEN::NONE;	// PAD, PAD_GAP, or NONE
	qreal	m_radiusTrk	= 0;			// Trk radius
	qreal	m_radiusPad	= 0;			// Pen radius
	bool	m_bClosed	= false;		// Flag to indicate closed polygon
	// A cache indicating the points and edges that are fat (i.e. have pad radius)
	mutable std::vector<bool> m_bFatPoint;	//
	mutable std::vector<bool> m_bFatEdge;	// [i] means edge from point with index i to point with index i+1
};

struct PolygonHelper
{
	PolygonHelper() {}
	~PolygonHelper() {}
	QPolygonF	m_pWarn;			// Set of warning points
	qreal		m_Dmin = DBL_MAX;	// The closest separation found (units of grid squares)

	static inline qreal Length(const QPointF& p)
	{
		return (p.x() == 0.0) ? fabs(p.y()) : (p.y() == 0.0) ? fabs(p.x()) : sqrt( QPointF::dotProduct(p,p) );
	}
	inline void CalcSeparation(const MyPointF& X, const MyPointF& Y)
	{
		const qreal sum		= ( X.m_radius + Y.m_radius );
		const qreal semi	= ( X.m_radius - Y.m_radius ) * 0.5;
		Update(X, Y, sum, semi);
	}
	inline void CalcSeparation(const MyPointF& X, const MyPolygonF& P)
	{
		if ( P.empty() ) return;
		//P.Process();	// Not needed since m_bFatEdge already populated
		const bool  bVariTracks	= P.HaveVariTracks();
		const qreal sum			= ( X.m_radius + P.m_radiusTrk );
		const qreal semi		= ( X.m_radius - P.m_radiusTrk ) * 0.5;
		const qreal sumHV		= bVariTracks ? (   X.m_radius + P.m_radiusPad         ) : sum;
		const qreal semiHV		= bVariTracks ? ( ( X.m_radius - P.m_radiusPad ) * 0.5 ) : semi;

		const int iSize = P.size();
		if ( iSize == 1 ) return Update(X, P[0], sum, semi);

		for (int i = 0, j = 1, iEnd = P.m_bClosed ? iSize : (iSize-1); i < iEnd; i++, j++)
		{
			const size_t I = static_cast<size_t>(i);
			if ( j == iSize ) j = 0;
			if ( P.m_bFatEdge[I] )	Update(X, Closest(X, P[i], P[j]), sumHV, semiHV);
			else					Update(X, Closest(X, P[i], P[j]), sum, semi);
		}
	}
	inline void CalcSeparation(const MyPolygonF& P, const MyPolygonF& Q)
	{
		if ( P.empty() || Q.empty() ) return;
		//P.Process();	Q.Process();	// Not needed since m_bFatPoint already populated
		size_t i(0), j(0);
		for (const auto& p : P) CalcSeparation(MyPointF(p, P.m_bFatPoint[i++] ? P.m_radiusPad : P.m_radiusTrk), Q);
		for (const auto& q : Q) CalcSeparation(MyPointF(q, Q.m_bFatPoint[j++] ? Q.m_radiusPad : Q.m_radiusTrk), P);
	}
private:
	static inline QPointF Closest(const QPointF& X, const QPointF& A, const QPointF& B)	// Closest point to X on line segment A-B
	{
		if ( A == B ) return A;
		const QPointF	AB(B - A), AX(X - A);
		const qreal		lambda = std::max(0.0, std::min(1.0, QPointF::dotProduct(AB,AX) / QPointF::dotProduct(AB,AB)));
		return A + (AB * lambda);
	}
	inline void Update(const QPointF& X, const QPointF& Y, qreal sum, qreal semi)	// Sum of radii, and semi-diff of radii
	{
		const QPointF	L(Y - X);
		const qreal		l = Length(L);
		const qreal		D = round( std::max(0.0, l - sum) * 1000 );	// Units of 0.1 mil
		if ( DBL_MAX - m_Dmin != 0.0 )
		{
			const int iDelta = static_cast<int>( D - m_Dmin * 1000 );	// Units of 0.1 mil
			if ( iDelta > 0 ) return;
			if ( iDelta < 0 ) m_pWarn.clear();
		}
		m_Dmin = D * 0.001;	// Units of grid squares (100 mil)
		QPointF mid( (X + Y) * 0.5 );
		if ( semi != 0.0 && l != 0.0 ) mid += L * ( semi / l );
		m_pWarn.push_back( mid );
	}
};