Engauge Digitizer 2
Loading...
Searching...
No Matches
CallbackAxisPointsAbstract.cpp
1/******************************************************************************************************
2 * (C) 2014 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
7#include "CallbackAxisPointsAbstract.h"
8#include "EngaugeAssert.h"
9#include "Logger.h"
10#include "Point.h"
11#include <qmath.h>
12#include "QtToString.h"
13#include "Transformation.h"
14
16 DocumentAxesPointsRequired documentAxesPointsRequired) :
17 m_modelCoords (modelCoords),
18 m_isError (false),
19 m_documentAxesPointsRequired (documentAxesPointsRequired)
20{
21}
22
24 const QString pointIdentifierOverride,
25 const QPointF &posScreenOverride,
26 const QPointF &posGraphOverride,
27 DocumentAxesPointsRequired documentAxesPointsRequired) :
28 m_modelCoords (modelCoords),
29 m_pointIdentifierOverride (pointIdentifierOverride),
30 m_posScreenOverride (posScreenOverride),
31 m_posGraphOverride (posGraphOverride),
32 m_isError (false),
33 m_documentAxesPointsRequired (documentAxesPointsRequired)
34{
35}
36
37bool CallbackAxisPointsAbstract::anyPointsRepeatPair (const CoordPairVector &vector) const
38{
39 for (int pointLeft = 0; pointLeft < vector.count(); pointLeft++) {
40 for (int pointRight = pointLeft + 1; pointRight < vector.count(); pointRight++) {
41
42 if ((vector.at(pointLeft).x() == vector.at(pointRight).x()) &&
43 (vector.at(pointLeft).y() == vector.at(pointRight).y())) {
44
45 // Points pointLeft and pointRight repeat each other, which means matrix cannot be inverted
46 return true;
47 }
48 }
49 }
50
51 // No columns repeat
52 return false;
53}
54
55bool CallbackAxisPointsAbstract::anyPointsRepeatSingle (const CoordSingleVector &vector) const
56{
57 for (int pointLeft = 0; pointLeft < vector.count(); pointLeft++) {
58 for (int pointRight = pointLeft + 1; pointRight < vector.count(); pointRight++) {
59
60 if (vector.at(pointLeft) == vector.at(pointRight)) {
61
62 // Points pointLeft and pointRight repeat each other, which means matrix cannot be inverted
63 return true;
64 }
65 }
66 }
67
68 // No columns repeat
69 return false;
70}
71
73 const Point &point)
74{
75 QPointF posScreen = point.posScreen ();
76 QPointF posGraph = point.posGraph ();
77
78 if (m_pointIdentifierOverride == point.identifier ()) {
79
80 // Override the old point coordinates with its new (if all tests are passed) coordinates
81 posScreen = m_posScreenOverride;
82 posGraph = m_posGraphOverride;
83 }
84
85 // Try to compute transform
86 if (m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_3) {
87 return callbackRequire3AxisPoints (posScreen,
88 posGraph);
89 } else {
90 return callbackRequire4AxisPoints (point.isXOnly(),
91 posScreen,
92 posGraph);
93 }
94}
95
96CallbackSearchReturn CallbackAxisPointsAbstract::callbackRequire3AxisPoints (const QPointF &posScreen,
97 const QPointF &posGraph)
98{
100
101 // Update range variables
102 int numberPoints = m_screenInputs.count();
103 if ((numberPoints == 0) || (posGraph.x () < m_xGraphLow)) { m_xGraphLow = posGraph.x (); }
104 if ((numberPoints == 0) || (posGraph.y () < m_yGraphLow)) { m_yGraphLow = posGraph.y (); }
105 if ((numberPoints == 0) || (posGraph.x () > m_xGraphHigh)) { m_xGraphHigh = posGraph.x (); }
106 if ((numberPoints == 0) || (posGraph.y () > m_yGraphHigh)) { m_yGraphHigh = posGraph.y (); }
107
108 if (numberPoints < 3) {
109
110 // Append new point
111 m_screenInputs.push_back (posScreen);
112 m_graphOutputs.push_back (posGraph);
113 numberPoints = m_screenInputs.count();
114
115 if (numberPoints == 3) {
116 loadTransforms3 ();
117 }
118
119 // Error checking
120 if (anyPointsRepeatPair (m_screenInputs)) {
121
122 m_isError = true;
123 m_errorMessage = QObject::tr ("New axis point cannot be at the same screen position as an exisiting axis point");
125
126 } else if (anyPointsRepeatPair (m_graphOutputs)) {
127
128 m_isError = true;
129 m_errorMessage = QObject::tr ("New axis point cannot have the same graph coordinates as an existing axis point");
131
132 } else if ((numberPoints == 3) && threePointsAreCollinear (m_screenInputsTransform)) {
133
134 m_isError = true;
135 m_errorMessage = QObject::tr ("No more than two axis points can lie along the same line on the screen");
137
138 } else if ((numberPoints == 3) && threePointsAreCollinear (m_graphOutputsTransform)) {
139
140 m_isError = true;
141 m_errorMessage = QObject::tr ("No more than two axis points can lie along the same line in graph coordinates");
143
144 }
145 }
146
147 if (m_screenInputs.count() > 2) {
148
149 // There are enough axis points so quit
151
152 }
153
154 return rtn;
155}
156
157CallbackSearchReturn CallbackAxisPointsAbstract::callbackRequire4AxisPoints (bool isXOnly,
158 const QPointF &posScreen,
159 const QPointF &posGraph)
160{
162
163 // Update range variables
164 int numberPoints = m_screenInputsX.count() + m_screenInputsY.count();
165 if ((numberPoints == 0) || (posGraph.x () < m_xGraphLow)) { m_xGraphLow = posGraph.x (); }
166 if ((numberPoints == 0) || (posGraph.y () < m_yGraphLow)) { m_yGraphLow = posGraph.y (); }
167 if ((numberPoints == 0) || (posGraph.x () > m_xGraphHigh)) { m_xGraphHigh = posGraph.x (); }
168 if ((numberPoints == 0) || (posGraph.y () > m_yGraphHigh)) { m_yGraphHigh = posGraph.y (); }
169
170 if (numberPoints < 4) {
171
172 // Append the new point
173 if (isXOnly) {
174
175 m_screenInputsX.push_back (posScreen);
176 m_graphOutputsX.push_back (posGraph.x());
177
178 } else {
179
180 m_screenInputsY.push_back (posScreen);
181 m_graphOutputsY.push_back (posGraph.y());
182
183 }
184
185 numberPoints = m_screenInputsX.count() + m_screenInputsY.count();
186 if (numberPoints == 4) {
187 loadTransforms4 ();
188 }
189 }
190
191 if (m_screenInputsX.count() > 2) {
192
193 m_isError = true;
194 m_errorMessage = QObject::tr ("Too many x axis points. There should only be two");
196
197 } else if (m_screenInputsY.count() > 2) {
198
199 m_isError = true;
200 m_errorMessage = QObject::tr ("Too many y axis points. There should only be two");
202
203 } else {
204
205 if ((m_screenInputsX.count() == 2) &&
206 (m_screenInputsY.count() == 2)) {
207
208 // Done, although an error may intrude
210 }
211
212 // Error checking
213 if (anyPointsRepeatPair (m_screenInputsX) ||
214 anyPointsRepeatPair (m_screenInputsY)) {
215
216 m_isError = true;
217 m_errorMessage = QObject::tr ("New axis point cannot be at the same screen position as an exisiting axis point");
219
220 } else if (anyPointsRepeatSingle (m_graphOutputsX) ||
221 anyPointsRepeatSingle (m_graphOutputsY)) {
222
223 m_isError = true;
224 m_errorMessage = QObject::tr ("New axis point cannot have the same graph coordinates as an existing axis point");
226
227 } else if ((numberPoints == 4) && threePointsAreCollinear (m_screenInputsTransform)) {
228
229 m_isError = true;
230 m_errorMessage = QObject::tr ("No more than two axis points can lie along the same line on the screen");
232
233 } else if ((numberPoints == 4) && threePointsAreCollinear (m_graphOutputsTransform)) {
234
235 m_isError = true;
236 m_errorMessage = QObject::tr ("No more than two axis points can lie along the same line in graph coordinates");
238
239 }
240 }
241
242 return rtn;
243}
244
246{
247 return m_documentAxesPointsRequired;
248}
249
250void CallbackAxisPointsAbstract::loadTransforms3 ()
251{
252 // Screen coordinates
253 m_screenInputsTransform = QTransform (m_screenInputs.at(0).x(), m_screenInputs.at(1).x(), m_screenInputs.at(2).x(),
254 m_screenInputs.at(0).y(), m_screenInputs.at(1).y(), m_screenInputs.at(2).y(),
255 1.0 , 1.0 , 1.0 );
256
257 // Graph coordinates
258 m_graphOutputsTransform = QTransform (m_graphOutputs.at(0).x(), m_graphOutputs.at(1).x(), m_graphOutputs.at(2).x(),
259 m_graphOutputs.at(0).y(), m_graphOutputs.at(1).y(), m_graphOutputs.at(2).y(),
260 1.0 , 1.0 , 1.0 );
261}
262
263void CallbackAxisPointsAbstract::loadTransforms4 ()
264{
265 double x1Screen = m_screenInputsX.at(0).x();
266 double y1Screen = m_screenInputsX.at(0).y();
267 double x2Screen = m_screenInputsX.at(1).x();
268 double y2Screen = m_screenInputsX.at(1).y();
269 double x3Screen = m_screenInputsY.at(0).x();
270 double y3Screen = m_screenInputsY.at(0).y();
271 double x4Screen = m_screenInputsY.at(1).x();
272 double y4Screen = m_screenInputsY.at(1).y();
273
274 // Each of the four axes points has only one coordinate
275 double x1Graph = m_graphOutputsX.at(0);
276 double x2Graph = m_graphOutputsX.at(1);
277 double y3Graph = m_graphOutputsY.at(0);
278 double y4Graph = m_graphOutputsY.at(1);
279
280 // Intersect the two lines of the two axes. The lines are defined parametrically for the screen coordinates, with
281 // points 1 and 2 on the x axis and points 3 and 4 on the y axis, as:
282 // x = (1 - sx) * x1 + sx * x2
283 // y = (1 - sx) * y1 + sx * y2
284 // x = (1 - sy) * x3 + sy * x4
285 // y = (1 - sy) * y3 + sy * y4
286 // Intersection of the 2 lines is at (x,y). Solving for sx and sy using Cramer's rule where Ax=b
287 // (x1 - x3) (x1 - x2 x4 - x3) (sx)
288 // (y1 - y3) = (y1 - y2 y4 - y3) (sy)
289 double A00 = x1Screen - x2Screen;
290 double A01 = x4Screen - x3Screen;
291 double A10 = y1Screen - y2Screen;
292 double A11 = y4Screen - y3Screen;
293 double b0 = x1Screen - x3Screen;
294 double b1 = y1Screen - y3Screen;
295 double numeratorx = (b0 * A11 - A01 * b1);
296 double numeratory = (A00 * b1 - b0 * A10);
297 double denominator = (A00 * A11 - A01 * A10);
298 double sx = numeratorx / denominator;
299 double sy = numeratory / denominator;
300
301 // Intersection point. For the graph coordinates, the initial implementation assumes cartesian coordinates
302 double xIntScreen = (1.0 - sx) * x1Screen + sx * x2Screen;
303 double yIntScreen = (1.0 - sy) * y3Screen + sy * y4Screen;
304 double xIntGraph, yIntGraph;
305 if (m_modelCoords.coordScaleXTheta() == COORD_SCALE_LINEAR) {
306 xIntGraph = (1.0 - sx) * x1Graph + sx * x2Graph;
307 } else {
308 xIntGraph = qExp ((1.0 - sx) * qLn (x1Graph) + sx * qLn (x2Graph));
309 }
310 if (m_modelCoords.coordScaleYRadius() == COORD_SCALE_LINEAR) {
311 yIntGraph = (1.0 - sy) * y3Graph + sy * y4Graph;
312 } else {
313 yIntGraph = qExp ((1.0 - sy) * qLn (y3Graph) + sy * qLn (y4Graph));
314 }
315
316 // Distances of 4 axis points from interception
317 double distance1 = qSqrt ((x1Screen - xIntScreen) * (x1Screen - xIntScreen) +
318 (y1Screen - yIntScreen) * (y1Screen - yIntScreen));
319 double distance2 = qSqrt ((x2Screen - xIntScreen) * (x2Screen - xIntScreen) +
320 (y2Screen - yIntScreen) * (y2Screen - yIntScreen));
321 double distance3 = qSqrt ((x3Screen - xIntScreen) * (x3Screen - xIntScreen) +
322 (y3Screen - yIntScreen) * (y3Screen - yIntScreen));
323 double distance4 = qSqrt ((x4Screen - xIntScreen) * (x4Screen - xIntScreen) +
324 (y4Screen - yIntScreen) * (y4Screen - yIntScreen));
325
326 // We now have too many data points with both x and y coordinates:
327 // (xInt,yInt) (xInt,y3) (xInt,y4) (x1,yInt) (x2,yInt)
328 // so we pick just 3, making sure that those 3 are widely separated
329 // (xInt,yInt) (x axis point furthest from xInt,yInt) (y axis point furthest from xInt,yInt)
330 double xFurthestXAxisScreen, yFurthestXAxisScreen, xFurthestYAxisScreen, yFurthestYAxisScreen;
331 double xFurthestXAxisGraph, yFurthestXAxisGraph, xFurthestYAxisGraph, yFurthestYAxisGraph;
332 if (distance1 < distance2) {
333 xFurthestXAxisScreen = x2Screen;
334 yFurthestXAxisScreen = y2Screen;
335 xFurthestXAxisGraph = x2Graph;
336 yFurthestXAxisGraph = yIntGraph;
337 } else {
338 xFurthestXAxisScreen = x1Screen;
339 yFurthestXAxisScreen = y1Screen;
340 xFurthestXAxisGraph = x1Graph;
341 yFurthestXAxisGraph = yIntGraph;
342 }
343 if (distance3 < distance4) {
344 xFurthestYAxisScreen = x4Screen;
345 yFurthestYAxisScreen = y4Screen;
346 xFurthestYAxisGraph = xIntGraph;
347 yFurthestYAxisGraph = y4Graph;
348 } else {
349 xFurthestYAxisScreen = x3Screen;
350 yFurthestYAxisScreen = y3Screen;
351 xFurthestYAxisGraph = xIntGraph;
352 yFurthestYAxisGraph = y3Graph;
353 }
354
355 // Screen coordinates
356 m_screenInputsTransform = QTransform (xIntScreen, xFurthestXAxisScreen, xFurthestYAxisScreen,
357 yIntScreen, yFurthestXAxisScreen, yFurthestYAxisScreen,
358 1.0 , 1.0 , 1.0 );
359
360 // Graph coordinates
361 m_graphOutputsTransform = QTransform (xIntGraph, xFurthestXAxisGraph, xFurthestYAxisGraph,
362 yIntGraph, yFurthestXAxisGraph, yFurthestYAxisGraph,
363 1.0 , 1.0 , 1.0 );
364}
365
367{
368 return m_graphOutputsTransform;
369}
370
372{
373 return m_screenInputsTransform;
374}
375
377{
378 if (m_documentAxesPointsRequired == DOCUMENT_AXES_POINTS_REQUIRED_3) {
379 return m_screenInputs.count();
380 } else {
381 return m_screenInputsX.count() + m_screenInputsY.count();
382 }
383}
384
385bool CallbackAxisPointsAbstract::threePointsAreCollinear (const QTransform &transform)
386{
387 return (transform.determinant() == 0);
388}
CallbackSearchReturn
Return values for search callback methods.
@ CALLBACK_SEARCH_RETURN_CONTINUE
Continue normal execution of the search.
@ CALLBACK_SEARCH_RETURN_INTERRUPT
Immediately terminate the current search.
DocumentAxesPointsRequired documentAxesPointsRequired() const
Number of axes points required for the transformation.
QTransform matrixGraph() const
Returns graph coordinates matrix after transformIsDefined has already indicated success.
CallbackSearchReturn callback(const QString &curveName, const Point &point)
Callback method.
CallbackAxisPointsAbstract(const DocumentModelCoords &modelCoords, DocumentAxesPointsRequired documentAxesPointsRequired)
Constructor for when all of the existing axis points are to be processed as is.
unsigned int numberAxisPoints() const
Number of axis points which is less than 3 if the axes curve is incomplete.
QTransform matrixScreen() const
Returns screen coordinates matrix after transformIsDefined has already indicated success.
Model for DlgSettingsCoords and CmdSettingsCoords.
CoordScale coordScaleYRadius() const
Get method for linear/log scale on y/radius.
CoordScale coordScaleXTheta() const
Get method for linear/log scale on x/theta.
Class that represents one digitized point. The screen-to-graph coordinate transformation is always ex...
Definition Point.h:24
QPointF posGraph(ApplyHasCheck applyHasCheck=KEEP_HAS_CHECK) const
Accessor for graph position. Skip check if copying one instance to another.
Definition Point.cpp:383
QPointF posScreen() const
Accessor for screen position.
Definition Point.cpp:392
QString identifier() const
Unique identifier for a specific Point.
Definition Point.cpp:256
bool isXOnly() const
In DOCUMENT_AXES_POINTS_REQUIRED_4 modes, this is true/false if y/x coordinate is undefined.
Definition Point.cpp:274