1 /* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
2  * Qwt Widget Library
3  * Copyright (C) 1997   Josef Wilgen
4  * Copyright (C) 2002   Uwe Rathmann
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the Qwt License, Version 1.0
8  *****************************************************************************/
9 
10 #include "qwt_raster_data.h"
11 #include "qwt_point_3d.h"
12 #include <qnumeric.h>
13 
14 class QwtRasterData::ContourPlane
15 {
16 public:
ContourPlane(double z)17     inline ContourPlane( double z ):
18         d_z( z )
19     {
20     }
21 
22     inline bool intersect( const QwtPoint3D vertex[3],
23         QPointF line[2], bool ignoreOnPlane ) const;
24 
z() const25     inline double z() const { return d_z; }
26 
27 private:
28     inline int compare( double z ) const;
29     inline QPointF intersection(
30         const QwtPoint3D& p1, const QwtPoint3D &p2 ) const;
31 
32     double d_z;
33 };
34 
intersect(const QwtPoint3D vertex[3],QPointF line[2],bool ignoreOnPlane) const35 inline bool QwtRasterData::ContourPlane::intersect(
36     const QwtPoint3D vertex[3], QPointF line[2],
37     bool ignoreOnPlane ) const
38 {
39     bool found = true;
40 
41     // Are the vertices below (-1), on (0) or above (1) the plan ?
42     const int eq1 = compare( vertex[0].z() );
43     const int eq2 = compare( vertex[1].z() );
44     const int eq3 = compare( vertex[2].z() );
45 
46     /*
47         (a) All the vertices lie below the contour level.
48         (b) Two vertices lie below and one on the contour level.
49         (c) Two vertices lie below and one above the contour level.
50         (d) One vertex lies below and two on the contour level.
51         (e) One vertex lies below, one on and one above the contour level.
52         (f) One vertex lies below and two above the contour level.
53         (g) Three vertices lie on the contour level.
54         (h) Two vertices lie on and one above the contour level.
55         (i) One vertex lies on and two above the contour level.
56         (j) All the vertices lie above the contour level.
57      */
58 
59     static const int tab[3][3][3] =
60     {
61         // jump table to avoid nested case statements
62         { { 0, 0, 8 }, { 0, 2, 5 }, { 7, 6, 9 } },
63         { { 0, 3, 4 }, { 1, 10, 1 }, { 4, 3, 0 } },
64         { { 9, 6, 7 }, { 5, 2, 0 }, { 8, 0, 0 } }
65     };
66 
67     const int edgeType = tab[eq1+1][eq2+1][eq3+1];
68     switch ( edgeType )
69     {
70         case 1:
71             // d(0,0,-1), h(0,0,1)
72             line[0] = vertex[0].toPoint();
73             line[1] = vertex[1].toPoint();
74             break;
75         case 2:
76             // d(-1,0,0), h(1,0,0)
77             line[0] = vertex[1].toPoint();
78             line[1] = vertex[2].toPoint();
79             break;
80         case 3:
81             // d(0,-1,0), h(0,1,0)
82             line[0] = vertex[2].toPoint();
83             line[1] = vertex[0].toPoint();
84             break;
85         case 4:
86             // e(0,-1,1), e(0,1,-1)
87             line[0] = vertex[0].toPoint();
88             line[1] = intersection( vertex[1], vertex[2] );
89             break;
90         case 5:
91             // e(-1,0,1), e(1,0,-1)
92             line[0] = vertex[1].toPoint();
93             line[1] = intersection( vertex[2], vertex[0] );
94             break;
95         case 6:
96             // e(-1,1,0), e(1,0,-1)
97             line[0] = vertex[2].toPoint();
98             line[1] = intersection( vertex[0], vertex[1] );
99             break;
100         case 7:
101             // c(-1,1,-1), f(1,1,-1)
102             line[0] = intersection( vertex[0], vertex[1] );
103             line[1] = intersection( vertex[1], vertex[2] );
104             break;
105         case 8:
106             // c(-1,-1,1), f(1,1,-1)
107             line[0] = intersection( vertex[1], vertex[2] );
108             line[1] = intersection( vertex[2], vertex[0] );
109             break;
110         case 9:
111             // f(-1,1,1), c(1,-1,-1)
112             line[0] = intersection( vertex[2], vertex[0] );
113             line[1] = intersection( vertex[0], vertex[1] );
114             break;
115         case 10:
116             // g(0,0,0)
117             // The CONREC algorithm has no satisfying solution for
118             // what to do, when all vertices are on the plane.
119 
120             if ( ignoreOnPlane )
121                 found = false;
122             else
123             {
124                 line[0] = vertex[2].toPoint();
125                 line[1] = vertex[0].toPoint();
126             }
127             break;
128         default:
129             found = false;
130     }
131 
132     return found;
133 }
134 
compare(double z) const135 inline int QwtRasterData::ContourPlane::compare( double z ) const
136 {
137     if ( z > d_z )
138         return 1;
139 
140     if ( z < d_z )
141         return -1;
142 
143     return 0;
144 }
145 
intersection(const QwtPoint3D & p1,const QwtPoint3D & p2) const146 inline QPointF QwtRasterData::ContourPlane::intersection(
147     const QwtPoint3D& p1, const QwtPoint3D &p2 ) const
148 {
149     const double h1 = p1.z() - d_z;
150     const double h2 = p2.z() - d_z;
151 
152     const double x = ( h2 * p1.x() - h1 * p2.x() ) / ( h2 - h1 );
153     const double y = ( h2 * p1.y() - h1 * p2.y() ) / ( h2 - h1 );
154 
155     return QPointF( x, y );
156 }
157 
158 //! Constructor
QwtRasterData()159 QwtRasterData::QwtRasterData()
160 {
161 }
162 
163 //! Destructor
~QwtRasterData()164 QwtRasterData::~QwtRasterData()
165 {
166 }
167 
168 /*!
169    Set the bounding interval for the x, y or z coordinates.
170 
171    \param axis Axis
172    \param interval Bounding interval
173 
174    \sa interval()
175 */
setInterval(Qt::Axis axis,const QwtInterval & interval)176 void QwtRasterData::setInterval( Qt::Axis axis, const QwtInterval &interval )
177 {
178     d_intervals[axis] = interval;
179 }
180 
181 /*!
182   \brief Initialize a raster
183 
184   Before the composition of an image QwtPlotSpectrogram calls initRaster(),
185   announcing the area and its resolution that will be requested.
186 
187   The default implementation does nothing, but for data sets that
188   are stored in files, it might be good idea to reimplement initRaster(),
189   where the data is resampled and loaded into memory.
190 
191   \param area Area of the raster
192   \param raster Number of horizontal and vertical pixels
193 
194   \sa initRaster(), value()
195 */
initRaster(const QRectF & area,const QSize & raster)196 void QwtRasterData::initRaster( const QRectF &area, const QSize &raster )
197 {
198     Q_UNUSED( area );
199     Q_UNUSED( raster );
200 }
201 
202 /*!
203   \brief Discard a raster
204 
205   After the composition of an image QwtPlotSpectrogram calls discardRaster().
206 
207   The default implementation does nothing, but if data has been loaded
208   in initRaster(), it could deleted now.
209 
210   \sa initRaster(), value()
211 */
discardRaster()212 void QwtRasterData::discardRaster()
213 {
214 }
215 
216 /*!
217    \brief Pixel hint
218 
219    pixelHint() returns the geometry of a pixel, that can be used
220    to calculate the resolution and alignment of the plot item, that is
221    representing the data.
222 
223    Width and height of the hint need to be the horizontal
224    and vertical distances between 2 neighbored points.
225    The center of the hint has to be the position of any point
226    ( it doesn't matter which one ).
227 
228    An empty hint indicates, that there are values for any detail level.
229 
230    Limiting the resolution of the image might significantly improve
231    the performance and heavily reduce the amount of memory when rendering
232    a QImage from the raster data.
233 
234    The default implementation returns an empty rectangle recommending
235    to render in target device ( f.e. screen ) resolution.
236 
237    \param area In most implementations the resolution of the data doesn't
238                depend on the requested area.
239 
240    \return Bounding rectangle of a pixel
241 */
pixelHint(const QRectF & area) const242 QRectF QwtRasterData::pixelHint( const QRectF &area ) const
243 {
244     Q_UNUSED( area );
245     return QRectF();
246 }
247 
248 /*!
249    Calculate contour lines
250 
251    \param rect Bounding rectangle for the contour lines
252    \param raster Number of data pixels of the raster data
253    \param levels List of limits, where to insert contour lines
254    \param flags Flags to customize the contouring algorithm
255 
256    \return Calculated contour lines
257 
258    An adaption of CONREC, a simple contouring algorithm.
259    http://local.wasp.uwa.edu.au/~pbourke/papers/conrec/
260 */
contourLines(const QRectF & rect,const QSize & raster,const QList<double> & levels,ConrecFlags flags) const261 QwtRasterData::ContourLines QwtRasterData::contourLines(
262     const QRectF &rect, const QSize &raster,
263     const QList<double> &levels, ConrecFlags flags ) const
264 {
265     ContourLines contourLines;
266 
267     if ( levels.size() == 0 || !rect.isValid() || !raster.isValid() )
268         return contourLines;
269 
270     const double dx = rect.width() / raster.width();
271     const double dy = rect.height() / raster.height();
272 
273     const bool ignoreOnPlane =
274         flags & QwtRasterData::IgnoreAllVerticesOnLevel;
275 
276     const QwtInterval range = interval( Qt::ZAxis );
277     bool ignoreOutOfRange = false;
278     if ( range.isValid() )
279         ignoreOutOfRange = flags & IgnoreOutOfRange;
280 
281     QwtRasterData *that = const_cast<QwtRasterData *>( this );
282     that->initRaster( rect, raster );
283 
284 #if __GNUC__ >= 9
285 #pragma GCC diagnostic push
286 #pragma GCC diagnostic ignored "-Wdeprecated-copy"
287 #endif
288 
289     for ( int y = 0; y < raster.height() - 1; y++ )
290     {
291         enum Position
292         {
293             Center,
294 
295             TopLeft,
296             TopRight,
297             BottomRight,
298             BottomLeft,
299 
300             NumPositions
301         };
302 
303         QwtPoint3D xy[NumPositions];
304 
305         for ( int x = 0; x < raster.width() - 1; x++ )
306         {
307             const QPointF pos( rect.x() + x * dx, rect.y() + y * dy );
308 
309             if ( x == 0 )
310             {
311                 xy[TopRight].setX( pos.x() );
312                 xy[TopRight].setY( pos.y() );
313                 xy[TopRight].setZ(
314                     value( xy[TopRight].x(), xy[TopRight].y() )
315                 );
316 
317                 xy[BottomRight].setX( pos.x() );
318                 xy[BottomRight].setY( pos.y() + dy );
319                 xy[BottomRight].setZ(
320                     value( xy[BottomRight].x(), xy[BottomRight].y() )
321                 );
322             }
323 
324             xy[TopLeft] = xy[TopRight];
325             xy[BottomLeft] = xy[BottomRight];
326 
327             xy[TopRight].setX( pos.x() + dx );
328             xy[TopRight].setY( pos.y() );
329             xy[BottomRight].setX( pos.x() + dx );
330             xy[BottomRight].setY( pos.y() + dy );
331 
332             xy[TopRight].setZ(
333                 value( xy[TopRight].x(), xy[TopRight].y() )
334             );
335             xy[BottomRight].setZ(
336                 value( xy[BottomRight].x(), xy[BottomRight].y() )
337             );
338 
339             double zMin = xy[TopLeft].z();
340             double zMax = zMin;
341             double zSum = zMin;
342 
343             for ( int i = TopRight; i <= BottomLeft; i++ )
344             {
345                 const double z = xy[i].z();
346 
347                 zSum += z;
348                 if ( z < zMin )
349                     zMin = z;
350                 if ( z > zMax )
351                     zMax = z;
352             }
353 
354             if ( qIsNaN( zSum ) )
355             {
356                 // one of the points is NaN
357                 continue;
358             }
359 
360             if ( ignoreOutOfRange )
361             {
362                 if ( !range.contains( zMin ) || !range.contains( zMax ) )
363                     continue;
364             }
365 
366             if ( zMax < levels[0] ||
367                 zMin > levels[levels.size() - 1] )
368             {
369                 continue;
370             }
371 
372             xy[Center].setX( pos.x() + 0.5 * dx );
373             xy[Center].setY( pos.y() + 0.5 * dy );
374             xy[Center].setZ( 0.25 * zSum );
375 
376             const int numLevels = levels.size();
377             for ( int l = 0; l < numLevels; l++ )
378             {
379                 const double level = levels[l];
380                 if ( level < zMin || level > zMax )
381                     continue;
382                 QPolygonF &lines = contourLines[level];
383                 const ContourPlane plane( level );
384 
385                 QPointF line[2];
386                 QwtPoint3D vertex[3];
387 
388                 for ( int m = TopLeft; m < NumPositions; m++ )
389                 {
390                     vertex[0] = xy[m];
391                     vertex[1] = xy[0];
392                     vertex[2] = xy[m != BottomLeft ? m + 1 : TopLeft];
393 
394                     const bool intersects =
395                         plane.intersect( vertex, line, ignoreOnPlane );
396                     if ( intersects )
397                     {
398                         lines += line[0];
399                         lines += line[1];
400                     }
401                 }
402             }
403         }
404     }
405 
406 #if __GNUC__ >= 9
407 #pragma GCC diagnostic pop
408 #endif
409 
410     that->discardRaster();
411 
412     return contourLines;
413 }
414