Engauge Digitizer 2
Loading...
Searching...
No Matches
CentipedeEndpointsPolar.cpp
Go to the documentation of this file.
1/******************************************************************************************************
2 * (C) 2020 markummitchell@github.com. This file is part of Engauge Digitizer, which is released *
3 * under GNU General Public License version 2 (GPLv2) or (at your option) any later version. See file *
4 * LICENSE or go to gnu.org/licenses for details. Distribution requires prior written permission. *
5 ******************************************************************************************************/
6
9#include "CoordsType.h"
10#include "Logger.h"
11#include "mmsubs.h"
12#include <qdebug.h>
13#include <qmath.h>
14#include <QPointF>
15#include "QtToString.h"
16#include "Transformation.h"
17
18const int NUM_CIRCLE_POINTS = 400; // Use many points so complicated (linear, log, high dynamic range) interpolation is not needed
19const int NUM_LINE_POINTS = 400; // Use many points so complicated (linear, log, high dynamic range) interpolation is not needed
20
24 const QPointF &posClickScreen,
25 const QPointF &posOriginScreen) :
29 m_modelCoords (modelCoords),
30 m_posOriginScreen (posOriginScreen)
31{
32}
33
37
38double CentipedeEndpointsPolar::closestAngleToCentralAngle (double angleCenter,
39 double angleOld) const
40{
41 // Loop to find closest angle to angleCenter
42 bool isFirst = true;
43 double angleNew = angleOld;
44 for (int delta = -360; delta <= 360; delta += 360) {
45 double angleNext = angleOld + qDegreesToRadians ((double) delta);
46 if (isFirst || (qAbs (angleNext - angleCenter) < qAbs (angleNew - angleCenter))) {
47 isFirst = false;
48 angleNew = angleNext;
49 }
50 }
51
52 return angleNew;
53}
54
56 const QPointF &posClickScreen,
57 double &angleRotation,
58 QRectF &rectBounding,
59 CentipedeDebugPolar &debugPolar)
60{
61 // LOG4CPP is below
62
63 QPointF posClickGraph;
64 transformation.transformScreenToRawGraph (posClickScreen,
65 posClickGraph);
66 double rGraph = posClickGraph.y();
67
68 // Points at origin, then 0 degrees at range rGraph
69 QPointF posScreenOrigin, posScreen0, posScreen90, posScreen180;
70 transformation.transformRawGraphToScreen (QPointF (tAtOrigin (), rAtOrigin ()),
71 posScreenOrigin);
72 transformation.transformRawGraphToScreen (QPointF (0, rGraph),
73 posScreen0);
74 transformation.transformRawGraphToScreen (QPointF (90.0, rGraph),
75 posScreen90);
76 transformation.transformRawGraphToScreen (QPointF (180.0, rGraph),
77 posScreen180);
78
79 QPointF centerTo90 = posScreen90 - posScreenOrigin;
80
81 // Corners of parallelogram circumscribing the ellipse
82 QPointF posScreenTL = posScreen180 + centerTo90;
83 QPointF posScreenTR = posScreen0 + centerTo90;
84 QPointF posScreenBL = posScreen180 - centerTo90;
85 QPointF posScreenBR = posScreen0 - centerTo90;
86
87 double aAligned = 0, bAligned = 0;
88 ellipseFromParallelogram (posScreenTL.x() - posScreenOrigin.x(),
89 posScreenTL.y() - posScreenOrigin.y(),
90 posScreenTR.x() - posScreenOrigin.x(),
91 posScreenTR.y() - posScreenOrigin.y(),
92 posScreenBR.x() - posScreenOrigin.x(),
93 posScreenBR.y() - posScreenOrigin.y(),
94 angleRotation,
95 aAligned,
96 bAligned);
97
98 double angleGraphAxisFromScreenAxis = qAtan2 (posScreen0.y() - posScreenOrigin.y(),
99 posScreen0.x() - posScreenOrigin.x());
100
101 debugPolar = CentipedeDebugPolar (posScreenTL,
102 posScreenTR,
103 posScreenBL,
104 posScreenBR,
105 angleGraphAxisFromScreenAxis,
106 angleRotation,
107 aAligned,
108 bAligned,
109 rGraph);
110
111 // Bounding rectangle before rotation. We make sure the rectangle is normalized which at one point
112 // seemed to prevent drawing artifacts
113 rectBounding = QRectF (-1.0 * aAligned,
114 -1.0 * bAligned,
115 2.0 * aAligned,
116 2.0 * bAligned);
117
118 LOG4CPP_INFO_S ((*mainCat)) << "CentipedeEndpointsPolar::ellipseScreenConstantRForTHighLowAngles angleRotation="
119 << qRadiansToDegrees (angleRotation) << " posScreen0=" << QPointFToString (posScreen0).toLatin1().data()
120 << " posScreen90=" << QPointFToString (posScreen90).toLatin1().data()
121 << " a=" << aAligned
122 << " b=" << bAligned;
123}
124
125void CentipedeEndpointsPolar::generatePreviousAndNextPointsConstantR (double radiusAboutClick,
126 int iPrevious,
127 int iNext,
128 QPointF &posGraphPrevious,
129 QPointF &posGraphNext,
130 QPointF &posScreenPrevious) const
131{
132 // Always make sure our initial angle (i = 0) is along the radial vector from the origin to the
133 // click point. Given this, we know that no matter where the click point is around the origin, we
134 // will have to go some nonzero distance to get to the high point, and some nonzero distance
135 // to get to the low point. This prevent i=0 being chosen as both the high and low point
136 //
137 // In other words the polar case has to worry about 360 degree wraparound unlike the cartesian case
138
139 QPointF basisRadial = (posClickScreen () - m_posOriginScreen) / magnitude (posClickScreen() - m_posOriginScreen);
140 QPointF basisTangential (basisRadial.y(),
141 -1.0 * basisRadial.x()); // Right-handed or left-handed direction is not important
142
143 double angleBefore = 2.0 * M_PI * (double) iPrevious / (double) NUM_CIRCLE_POINTS;
144 double angleAfter = 2.0 * M_PI * (double) iNext / (double) NUM_CIRCLE_POINTS;
145
146 posScreenPrevious = posClickScreen () + radiusAboutClick * (qCos (angleBefore) * basisRadial + qSin (angleBefore) * basisTangential);
147 QPointF posScreenNext = posClickScreen () + radiusAboutClick * (qCos (angleAfter) * basisRadial + qSin (angleAfter) * basisTangential);
148
149 transformation().transformScreenToRawGraph (posScreenPrevious,
150 posGraphPrevious);
152 posGraphNext);
153}
154
155QPointF CentipedeEndpointsPolar::posScreenConstantRCommon (double radius,
156 CentipedeIntersectionType intersectionType) const
157{
158 QPointF posScreenBest;
159 double xBest = 0;
160
161 // Click point
162 QPointF posClickGraph;
164 posClickGraph);
165 double yClick = posClickGraph.y();
166
167 // Iterate points around the circle
168 bool isFirst = true;
169 for (int i = 0; i < NUM_CIRCLE_POINTS; i++) {
170 QPointF posGraphPrevious, posGraphNext, posScreenPrevious;
171 generatePreviousAndNextPointsConstantR (radius,
172 i,
173 i + 1,
174 posGraphPrevious,
175 posGraphNext,
176 posScreenPrevious);
177
178 double xGraphPrevious = posGraphPrevious.x();
179 double yGraphPrevious = posGraphPrevious.y();
180 double yGraphNext = posGraphNext.y();
181 double epsilon = qAbs (yGraphPrevious - yGraphNext) / 10.0; // Allow for roundoff
182
183 bool save = false;
184
185 // CENTIPEDE_INTERSECTION_HIGH or CENTIPEDE_INTERSECTION_LOW
186 bool transitionUp = (yGraphPrevious - epsilon <= yClick) && (yClick < yGraphNext + epsilon);
187 bool transitionDown = (yGraphNext - epsilon <= yClick) && (yClick < yGraphPrevious + epsilon);
188
189 if (transitionDown || transitionUp) {
190
191 // Transition occurred so save if best so far
192 if (isFirst ||
193 (intersectionType == CENTIPEDE_INTERSECTION_HIGH && xGraphPrevious > xBest) ||
194 (intersectionType == CENTIPEDE_INTERSECTION_LOW && xGraphPrevious < xBest)) {
195
196 save = true;
197 }
198 }
199
200 if (save) {
201
202 // Best so far so save
203 isFirst = false;
204 posScreenBest = posScreenPrevious;
205 xBest = xGraphPrevious;
206 }
207 }
208
209 return posScreenBest;
210}
211
213{
214 return posScreenConstantRCommon (radius,
216}
217
219{
220 return posScreenConstantRCommon (radius,
222}
223
225 QPointF &posLow,
226 QPointF &posHigh) const
227{
228 // Click point
229 QPointF posClickGraph;
231 posClickGraph);
232 double yClick = posClickGraph.y();
233
234 // Iterate points around the circle starting at angleCenter, and going in two different
235 // directions (clockwise for angleHigh and counterclockwise for angleLow)
236 bool isFirstLow = true, isFirstHigh = true;
237 QPointF posLowGraph, posHighGraph;
238 for (int i = 0; i < NUM_CIRCLE_POINTS; i++) {
239 QPointF posGraphPreviousLow, posGraphNextLow, posScreenPreviousLow;
240 QPointF posGraphPreviousHigh, posGraphNextHigh, posScreenPreviousHigh;
241 generatePreviousAndNextPointsConstantR (radiusAboutClick,
242 i,
243 i + 1,
244 posGraphPreviousLow,
245 posGraphNextLow,
246 posScreenPreviousLow);
247 generatePreviousAndNextPointsConstantR (radiusAboutClick,
248 - i,
249 - i - 1,
250 posGraphPreviousHigh,
251 posGraphNextHigh,
252 posScreenPreviousHigh);
253
254 double epsilon = qAbs (posGraphPreviousLow.y() - posGraphNextLow.y()) / 10.0; // Allow for roundoff
255
256 bool transitionUpLow = (posGraphPreviousLow.y() - epsilon <= yClick) && (yClick < posGraphNextLow.y() + epsilon);
257 bool transitionDownLow = (posGraphNextLow.y() - epsilon <= yClick) && (yClick < posGraphPreviousLow.y() + epsilon);
258 bool transitionUpHigh = (posGraphPreviousHigh.y() - epsilon <= yClick) && (yClick < posGraphNextHigh.y() + epsilon);
259 bool transitionDownHigh = (posGraphNextHigh.y() - epsilon <= yClick) && (yClick < posGraphPreviousHigh.y() + epsilon);
260
261 if (isFirstLow && (transitionDownLow || transitionUpLow)) {
262 // Found the first (=best) low value
263 isFirstLow = false;
264 posLowGraph = (posGraphPreviousLow + posGraphNextLow) / 2.0; // Average
265 if (!isFirstHigh) {
266 break; // Done
267 }
268 }
269
270 if (isFirstHigh && (transitionDownHigh || transitionUpHigh)) {
271 // Found the first (=best) high value
272 isFirstHigh = false;
273 posHighGraph = (posGraphPreviousHigh + posGraphNextHigh) / 2.0;
274 if (!isFirstLow) {
275 break; // Done
276 }
277 }
278 }
279
280 // Convert from graph to screen coordinates
282 posLow);
284 posHigh);
285}
286
288 QPointF &posLow,
289 QPointF &posHigh) const
290{
291 // This replaces CentipedeSegmentAbstract::posScreenConstantXTCommon since the polar coordinate radial vector
292 // can be on the other side of the origin if the ellipse center is within radius of the origin. This routine
293 // uses a different strategy of iterating on a line rather than a circle (since circle has tough issues with quadrants
294 // and 360 rollover)
295
296 // Origin and screen vector to center
297 QPointF posOriginScreen;
298 transformation().transformRawGraphToScreen (QPointF (tAtOrigin (), rAtOrigin ()),
299 posOriginScreen);
300 QPointF vecCenter = posClickScreen() - posOriginScreen;
301
302 // Number of solutions found
303 int numberFound = 0;
304
305 // Iterate points along the line from -2*vecCenterMagnitude to +2*vecCenterMagnitude
306 const double DOUBLE_PLUS_EXTRA = 2.1;
307 double maxRadius = DOUBLE_PLUS_EXTRA * (magnitude (vecCenter) + radius);
308 QPointF posStart = posOriginScreen - 2 * maxRadius * normalize (vecCenter);
309 QPointF posStop = posOriginScreen + 2 * maxRadius * normalize (vecCenter);
310 for (int i = 0; i < NUM_LINE_POINTS; i++) {
311 double sPrevious = (double) i / (double) NUM_LINE_POINTS;
312 double sNext = (double) (i + 1) / (double) NUM_LINE_POINTS;
313
314 QPointF posPrevious = (1.0 - sPrevious) * posStart + sPrevious * posStop;
315 QPointF posNext = (1.0 - sNext) * posStart + sNext * posStop;
316
317 double distancePrevious = magnitude (posPrevious - posClickScreen());
318 double distanceNext = magnitude (posNext - posClickScreen());
319
320 if ((distancePrevious < radius && radius <= distanceNext) ||
321 (distancePrevious > radius && radius >= distanceNext)) {
322
323 if (numberFound == 0) {
324 posLow = (posPrevious + posNext) / 2.0; // Average for accuracy
325 } else if (numberFound == 1) {
326 posHigh = (posPrevious + posNext) / 2.0; // Average for accuracy
327 break; // Done
328 }
329
330 ++numberFound;
331 }
332 }
333}
334
335double CentipedeEndpointsPolar::rAtOrigin () const
336{
337 // Values that work for both linear and log
338 if (m_modelCoords.coordScaleYRadius() == COORD_SCALE_LOG) {
339 return m_modelCoords.originRadius();
340 } else {
341 return 0;
342 }
343}
344
345double CentipedeEndpointsPolar::tAtOrigin () const
346{
347 // Value that works for all units (radians, degrees, gradians)
348 return 0.0;
349}
const int NUM_CIRCLE_POINTS
const int NUM_LINE_POINTS
CentipedeIntersectionType
Intersect with one of the following XT or YT coordinates for constant YR or XT respectively.
@ CENTIPEDE_INTERSECTION_HIGH
Intersection along circle perimeter with lowest value of XT or YR.
@ CENTIPEDE_INTERSECTION_LOW
@ COORD_SCALE_LOG
Definition CoordScale.h:14
log4cpp::Category * mainCat
Definition Logger.cpp:14
QString QPointFToString(const QPointF &pos)
Class for collecting and then displaying debug information computed during constant R ellipse calcula...
QPointF posClickScreen() const
Center of circle in screen coordinates.
const DocumentModelGuideline & modelGuideline() const
Settings.
const Transformation & transformation() const
Transformation which is static through the entire lifetime of the Centipede class instances.
CentipedeEndpointsAbstract(const DocumentModelGuideline &modelGuideline, const Transformation &transformation, const QPointF &posClickScreen)
Constructor with individual coordinates.
CentipedeEndpointsPolar(const DocumentModelCoords &modelCoords, const DocumentModelGuideline &modelGuideline, const Transformation &transformation, const QPointF &posClickScreen, const QPointF &posOriginScreen)
Constructor with individual coordinates.
QPointF posScreenConstantRForLowT(double radius) const
Screen point for R value of circle/coordinate intersection in the decreasing T direction.
void ellipseScreenConstantRForTHighLowAngles(const Transformation &transformation, const QPointF &posClickScreen, double &angleRotation, QRectF &rectBounding, CentipedeDebugPolar &DebugPolar)
Ellipse for R value of circle/coordinate intersection. Start/span angles are calculated separately.
void posScreenConstantRHighLow(double radiusAboutClick, QPointF &posLow, QPointF &posHigh) const
Return two points (posLow and posHigh) where circle around posClickScreen intersects constant-radiusA...
void posScreenConstantTForRHighLow(double radius, QPointF &posLow, QPointF &posHigh) const
Endpoints for radial line segmentin polar coordinates.
QPointF posScreenConstantRForHighT(double radius) const
Screen point for R value of circle/coordinate intersection in the increasing T direction.
Model for DlgSettingsCoords and CmdSettingsCoords.
CoordScale coordScaleYRadius() const
Get method for linear/log scale on y/radius.
double originRadius() const
Get method for origin radius in polar mode.
Model for managing the coordinate values corresponding Guidelines.
Affine transformation between screen and graph coordinates, based on digitized axis points.
void transformRawGraphToScreen(const QPointF &pointRaw, QPointF &pointScreen) const
Transform from raw graph coordinates to linear cartesian graph coordinates, then to screen coordinate...
void transformScreenToRawGraph(const QPointF &coordScreen, QPointF &coordGraph) const
Transform from cartesian pixel screen coordinates to cartesian/polar graph coordinates.
#define LOG4CPP_INFO_S(logger)
Definition convenience.h:18
double magnitude(const QPointF &vec)
Norm of vector.
Definition mmsubs.cpp:193
void ellipseFromParallelogram(double xTL, double yTL, double xTR, double yTR, double xBR, double yBR, double &angleRadians, double &aAligned, double &bAligned)
Calculate ellipse parameters that is incribed in a parallelogram centered at the origin,...
Definition mmsubs.cpp:70
QPointF normalize(const QPointF &vec)
Return normalized vector.
Definition mmsubs.cpp:198