1 /******************************************************************************************************
2  * (C) 2018 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 "EngaugeAssert.h"
8 #include "LineStyle.h"
9 #include <qmath.h>
10 #include "Spline.h"
11 #include "SplineDrawer.h"
12 
SplineDrawer(const Transformation & transformation)13 SplineDrawer::SplineDrawer(const Transformation &transformation) :
14   m_transformation (transformation)
15 {
16 }
17 
bindToSpline(const LineStyle & lineStyle,int numSegments,const Spline & spline)18 void SplineDrawer::bindToSpline (const LineStyle &lineStyle,
19                                  int numSegments,
20                                  const Spline &spline)
21 {
22   m_segmentOperations.resize (numSegments);
23 
24   // Loop through segments to get move/draw choice. We do not need to worry about
25   // applying a move (versus a draw) for the first segment since that first point
26   // is handled by external code
27   for (int segment = 0; segment < numSegments; segment++) {
28 
29     bool itsAKeeper = true;
30     if (m_transformation.transformIsDefined()) {
31 
32       // We have the graph<->screen transformation so let's use it. Could there be an ambiguity issue?
33       if ((lineStyle.curveConnectAs() == CONNECT_AS_FUNCTION_SMOOTH) &&
34           segmentIsMultiValued (spline,
35                                 numSegments,
36                                 segment)) {
37         itsAKeeper = false;
38       }
39 
40       // Invisible or visible?
41       m_segmentOperations [segment] = (itsAKeeper ?
42                                        SPLINE_DRAWER_ENUM_VISIBLE_DRAW :
43                                        SPLINE_DRAWER_ENUM_INVISIBLE_MOVE);
44     }
45   }
46 }
47 
segmentIsMultiValued(const Spline & spline,int numSegments,int segment) const48 bool SplineDrawer::segmentIsMultiValued (const Spline &spline,
49                                          int numSegments,
50                                          int segment) const
51 {
52   ENGAUGE_ASSERT (m_transformation.transformIsDefined());
53 
54   if (segment < numSegments - 1) {
55 
56     // Not at very end
57     double tI = double (segment);
58     double tIp1 = double (segment + 1);
59 
60     // Compute number of pixels between endpoints
61     SplinePair posScreenStart  = spline.interpolateCoeff (tI);
62     SplinePair posScreenEnd = spline.interpolateCoeff (tIp1);
63 
64     int deltaX = qFloor (posScreenEnd.x() - posScreenStart.x());
65     int deltaY = qFloor (posScreenEnd.y() - posScreenStart.y());
66     double pixelDistance = qSqrt (deltaX * deltaX + deltaY * deltaY);
67     double numSteps = pixelDistance;
68 
69     // Search through a sufficiently large number of points to verify single-valuedness
70     double tIDelta = 1.0 / numSteps;
71     for (int itI = 1; itI < numSteps - 1; itI++) {
72 
73       double tIm1 = segment + (itI - 1) * tIDelta;
74       double tI   = segment + (itI    ) * tIDelta;
75       double tIp1 = segment + (itI + 1) * tIDelta;
76 
77       SplinePair spBefore = spline.interpolateCoeff (tIm1);
78       SplinePair spCurrent = spline.interpolateCoeff (tI);
79       SplinePair spAfter = spline.interpolateCoeff (tIp1);
80 
81       QPointF posScreenBefore (spBefore.x(), spBefore.y());
82       QPointF posScreenCurrent (spCurrent.x(), spCurrent.y());
83       QPointF posScreenAfter (spAfter.x(), spAfter.y());
84 
85       QPointF posGraphBefore, posGraphCurrent, posGraphAfter;
86       m_transformation.transformScreenToRawGraph (posScreenBefore,
87                                                   posGraphBefore);
88       m_transformation.transformScreenToRawGraph (posScreenCurrent,
89                                                   posGraphCurrent);
90       m_transformation.transformScreenToRawGraph (posScreenAfter,
91                                                   posGraphAfter);
92 
93       // In between the start and end points we look for deltaXBefore>0 and deltaXAfter<0,
94       // or deltaXBefore<0 and deltaXAfter>0, either of those two cases indicates multi-valued
95       double deltaXBefore = posGraphCurrent.x() - posGraphBefore.x();
96       double deltaXAfter = posGraphAfter.x() - posGraphCurrent.x();
97 
98       if ((deltaXBefore > 0 && deltaXAfter < 0) ||
99           (deltaXBefore < 0 && deltaXAfter > 0)) {
100 
101         // Multi-valued
102         return true;
103       }
104     }
105   }
106 
107   return false;
108 }
109 
segmentOperation(int segment) const110 SplineDrawerOperation SplineDrawer::segmentOperation (int segment) const
111 {
112   if (segment < m_segmentOperations.count()) {
113     return m_segmentOperations.at (segment);
114   } else {
115     return SPLINE_DRAWER_ENUM_INVISIBLE_MOVE;
116   }
117 }
118