1 | // Copyright (C) 2023 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include <QtCore/qdebug.h> |
5 | #include <QtCore/qfileinfo.h> |
6 | #include "qheightmapsurfacedataproxy_p.h" |
7 | #include "qsurface3dseries_p.h" |
8 | |
9 | QT_BEGIN_NAMESPACE |
10 | |
11 | // Default ranges correspond value axis defaults |
12 | const float defaultMinValue = 0.0f; |
13 | const float defaultMaxValue = 10.0f; |
14 | |
15 | /*! |
16 | * \class QHeightMapSurfaceDataProxy |
17 | * \inmodule QtGraphs |
18 | * \ingroup graphs_3D |
19 | * \brief Base proxy class for Q3DSurfaceWidgetItem. |
20 | * |
21 | * QHeightMapSurfaceDataProxy takes care of the processing of height map data |
22 | * related to surfaces. It provides the visualization of a height map as a |
23 | * surface plot. |
24 | * |
25 | * Since height maps do not contain values for X or Z axes, those values need to |
26 | * be given separately using the minXValue, maxXValue, minZValue, and maxZValue |
27 | * properties. The X-value corresponds to image horizontal direction and the Z-value |
28 | * to the vertical. Setting any of these properties triggers an asynchronous |
29 | * re-resolution of any existing height map. |
30 | * |
31 | * \sa QSurfaceDataProxy, {Qt Graphs Data Handling with 3D} |
32 | */ |
33 | |
34 | /*! |
35 | * \qmltype HeightMapSurfaceDataProxy |
36 | * \inqmlmodule QtGraphs |
37 | * \ingroup graphs_qml_3D |
38 | * \nativetype QHeightMapSurfaceDataProxy |
39 | * \inherits SurfaceDataProxy |
40 | * \brief Base proxy type for Surface3D. |
41 | * |
42 | * QHeightMapSurfaceDataProxy takes care of the processing of height map data |
43 | * related to surfaces. It provides the visualization of a height map as a |
44 | * surface plot. |
45 | * |
46 | * For more complete description, see QHeightMapSurfaceDataProxy. |
47 | * |
48 | * \sa {Qt Graphs Data Handling with 3D} |
49 | */ |
50 | |
51 | /*! |
52 | * \qmlproperty string HeightMapSurfaceDataProxy::heightMapFile |
53 | * |
54 | * A file with a height map image to be visualized. Setting this property |
55 | * replaces current data with height map data. |
56 | * |
57 | * There are several formats the image file can be given in, but if it is not in |
58 | * a directly usable format, a conversion is made. |
59 | * |
60 | * \note If the result seems wrong, the automatic conversion failed |
61 | * and you should try converting the image yourself before setting it. Preferred |
62 | * format is QImage::Format_RGB32 in grayscale. |
63 | * |
64 | * The height of the image is read from the red component of the pixels if the |
65 | * image is in grayscale. Otherwise, it is an average calculated from the red, green, |
66 | * and blue components of the pixels. Using grayscale images may improve data |
67 | * conversion speed for large images. |
68 | * |
69 | * Since height maps do not contain values for X or Z axes, these values need to |
70 | * be given separately using the minXValue, maxXValue, minZValue, and maxZValue |
71 | * properties. The X-value corresponds to the image's horizontal direction, |
72 | * and the Z-value to the vertical. Setting any of these properties triggers |
73 | * an asynchronous re-resolution of any existing height map. |
74 | * |
75 | * Not recommended formats: all mono formats (for example QImage::Format_Mono). |
76 | */ |
77 | |
78 | /*! |
79 | * \qmlproperty real HeightMapSurfaceDataProxy::minXValue |
80 | * |
81 | * The minimum X value for the generated surface points. Defaults to \c{0.0}. |
82 | * When setting this property the corresponding maximum value is adjusted if |
83 | * necessary, to ensure that the range remains valid. |
84 | */ |
85 | |
86 | /*! |
87 | * \qmlproperty real HeightMapSurfaceDataProxy::maxXValue |
88 | * |
89 | * The maximum X value for the generated surface points. Defaults to \c{10.0}. |
90 | * When setting this property the corresponding minimum value is adjusted if |
91 | * necessary, to ensure that the range remains valid. |
92 | */ |
93 | |
94 | /*! |
95 | * \qmlproperty real HeightMapSurfaceDataProxy::minZValue |
96 | * |
97 | * The minimum Z value for the generated surface points. Defaults to \c{0.0}. |
98 | * When setting this property the corresponding maximum value is adjusted if |
99 | * necessary, to ensure that the range remains valid. |
100 | */ |
101 | |
102 | /*! |
103 | * \qmlproperty real HeightMapSurfaceDataProxy::maxZValue |
104 | * |
105 | * The maximum Z value for the generated surface points. Defaults to \c{10.0}. |
106 | * When setting this property the corresponding minimum value is adjusted if |
107 | * necessary, to ensure that the range remains valid. |
108 | */ |
109 | |
110 | /*! |
111 | * \qmlproperty real HeightMapSurfaceDataProxy::minYValue |
112 | * |
113 | * The minimum Y value for the generated surface points. Defaults to \c{0.0}. |
114 | * When setting this property the corresponding maximum value is adjusted if |
115 | * necessary, to ensure that the range remains valid. |
116 | */ |
117 | |
118 | /*! |
119 | * \qmlproperty real HeightMapSurfaceDataProxy::maxYValue |
120 | * |
121 | * The maximum Y value for the generated surface points. Defaults to \c{10.0}. |
122 | * When setting this property the corresponding minimum value is adjusted if |
123 | * necessary, to ensure that the range remains valid. |
124 | */ |
125 | |
126 | /*! |
127 | * \qmlproperty real HeightMapSurfaceDataProxy::autoScaleY |
128 | * |
129 | * Scale height values to Y-axis. Defaults to \c{false}. When this property is |
130 | * set to \c{true}, the height values are scaled to fit on the Y-axis between |
131 | * \c{minYValue} and \c{maxYValue}. |
132 | */ |
133 | |
134 | /*! |
135 | \qmlsignal HeightMapSurfaceDataProxy::heightMapFileChanged(string filename) |
136 | |
137 | This signal is emitted when heightMapFile changes to \a filename. |
138 | */ |
139 | |
140 | /*! |
141 | \qmlsignal HeightMapSurfaceDataProxy::minXValueChanged(real value) |
142 | |
143 | This signal is emitted when minXValue changes to \a value. |
144 | */ |
145 | |
146 | /*! |
147 | \qmlsignal HeightMapSurfaceDataProxy::maxXValueChanged(real value) |
148 | |
149 | This signal is emitted when maxXValue changes to \a value. |
150 | */ |
151 | |
152 | /*! |
153 | \qmlsignal HeightMapSurfaceDataProxy::minZValueChanged(real value) |
154 | |
155 | This signal is emitted when minZValue changes to \a value. |
156 | */ |
157 | |
158 | /*! |
159 | \qmlsignal HeightMapSurfaceDataProxy::maxZValueChanged(real value) |
160 | |
161 | This signal is emitted when maxZValue changes to \a value. |
162 | */ |
163 | |
164 | /*! |
165 | \qmlsignal HeightMapSurfaceDataProxy::minYValueChanged(real value) |
166 | |
167 | This signal is emitted when minYValue changes to \a value. |
168 | */ |
169 | |
170 | /*! |
171 | \qmlsignal HeightMapSurfaceDataProxy::maxYValueChanged(real value) |
172 | |
173 | This signal is emitted when maxYValue changes to \a value. |
174 | */ |
175 | |
176 | /*! |
177 | \qmlsignal HeightMapSurfaceDataProxy::autoScaleYChanged(bool enabled) |
178 | |
179 | This signal is emitted when autoScaleY changes to \a enabled. |
180 | */ |
181 | |
182 | /*! |
183 | * Constructs QHeightMapSurfaceDataProxy with the given \a parent. |
184 | */ |
185 | QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(QObject *parent) |
186 | : QSurfaceDataProxy(*(new QHeightMapSurfaceDataProxyPrivate()), parent) |
187 | { |
188 | Q_D(QHeightMapSurfaceDataProxy); |
189 | QObject::connect(sender: &d->m_resolveTimer, |
190 | signal: &QTimer::timeout, |
191 | context: this, |
192 | slot: &QHeightMapSurfaceDataProxy::handlePendingResolve); |
193 | } |
194 | |
195 | /*! |
196 | * Constructs QHeightMapSurfaceDataProxy with the given \a image and \a parent. |
197 | * Height map is set by calling setHeightMap() with \a image. |
198 | * |
199 | * \sa heightMap |
200 | */ |
201 | QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(const QImage &image, QObject *parent) |
202 | : QSurfaceDataProxy(*(new QHeightMapSurfaceDataProxyPrivate()), parent) |
203 | { |
204 | Q_D(QHeightMapSurfaceDataProxy); |
205 | QObject::connect(sender: &d->m_resolveTimer, |
206 | signal: &QTimer::timeout, |
207 | context: this, |
208 | slot: &QHeightMapSurfaceDataProxy::handlePendingResolve); |
209 | setHeightMap(image); |
210 | } |
211 | |
212 | /*! |
213 | * Constructs QHeightMapSurfaceDataProxy from the given image \a filename and \a |
214 | * parent. Height map is set by calling setHeightMapFile() with \a filename. |
215 | * |
216 | * \sa heightMapFile |
217 | */ |
218 | QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(const QString &filename, QObject *parent) |
219 | : QSurfaceDataProxy(*(new QHeightMapSurfaceDataProxyPrivate()), parent) |
220 | { |
221 | Q_D(QHeightMapSurfaceDataProxy); |
222 | QObject::connect(sender: &d->m_resolveTimer, |
223 | signal: &QTimer::timeout, |
224 | context: this, |
225 | slot: &QHeightMapSurfaceDataProxy::handlePendingResolve); |
226 | setHeightMapFile(filename); |
227 | } |
228 | |
229 | /*! |
230 | * \internal |
231 | */ |
232 | QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(QHeightMapSurfaceDataProxyPrivate &d, |
233 | QObject *parent) |
234 | : QSurfaceDataProxy(d, parent) |
235 | {} |
236 | |
237 | /*! |
238 | * Destroys QHeightMapSurfaceDataProxy. |
239 | */ |
240 | QHeightMapSurfaceDataProxy::~QHeightMapSurfaceDataProxy() {} |
241 | |
242 | /*! |
243 | * \property QHeightMapSurfaceDataProxy::heightMap |
244 | * |
245 | * \brief The height map image to be visualized. |
246 | */ |
247 | |
248 | /*! |
249 | * Replaces current data with the height map data specified by \a image. |
250 | * |
251 | * There are several formats the \a image can be given in, but if it is not in a |
252 | * directly usable format, a conversion is made. |
253 | * |
254 | * \note If the result seems wrong, the automatic conversion failed |
255 | * and you should try converting the \a image yourself before setting it. |
256 | * Preferred format is QImage::Format_RGB32 in grayscale. |
257 | * |
258 | * The height of the \a image is read from the red component of the pixels if |
259 | * the \a image is in grayscale. Otherwise it is an average calculated from the red, |
260 | * green, and blue components of the pixels. Using grayscale images may improve |
261 | * data conversion speed for large images. |
262 | * |
263 | * Not recommended formats: all mono formats (for example QImage::Format_Mono). |
264 | * |
265 | * The height map is resolved asynchronously. QSurfaceDataProxy::arrayReset() is |
266 | * emitted when the data has been resolved. |
267 | */ |
268 | void QHeightMapSurfaceDataProxy::setHeightMap(const QImage &image) |
269 | { |
270 | Q_D(QHeightMapSurfaceDataProxy); |
271 | d->m_heightMap = image; |
272 | |
273 | // We do resolving asynchronously to make qml onArrayReset handlers actually |
274 | // get the initial reset |
275 | if (!d->m_resolveTimer.isActive()) |
276 | d->m_resolveTimer.start(msec: 0); |
277 | } |
278 | |
279 | QImage QHeightMapSurfaceDataProxy::heightMap() const |
280 | { |
281 | Q_D(const QHeightMapSurfaceDataProxy); |
282 | return d->m_heightMap; |
283 | } |
284 | |
285 | /*! |
286 | * \property QHeightMapSurfaceDataProxy::heightMapFile |
287 | * |
288 | * \brief The name of the file with a height map image to be visualized. |
289 | */ |
290 | |
291 | /*! |
292 | * Replaces current data with height map data from the file specified by |
293 | * \a filename. |
294 | * |
295 | * \sa heightMap |
296 | */ |
297 | void QHeightMapSurfaceDataProxy::setHeightMapFile(const QString &filename) |
298 | { |
299 | Q_D(QHeightMapSurfaceDataProxy); |
300 | QFileInfo validfile(filename); |
301 | // Check if the filename is empty, in which case we should clear the height map, |
302 | // or if not, it's an actual file that can be found |
303 | if (!filename.isEmpty() && (!validfile.exists() || !validfile.isFile())) { |
304 | qWarning(msg: "Height map file %ls does not exist." , qUtf16Printable(filename)); |
305 | return; |
306 | } |
307 | if (d->m_heightMapFile != filename) { |
308 | d->m_heightMapFile = filename; |
309 | setHeightMap(QImage(filename)); |
310 | emit heightMapFileChanged(filename); |
311 | } |
312 | } |
313 | |
314 | QString QHeightMapSurfaceDataProxy::heightMapFile() const |
315 | { |
316 | Q_D(const QHeightMapSurfaceDataProxy); |
317 | return d->m_heightMapFile; |
318 | } |
319 | |
320 | /*! |
321 | * A convenience function for setting all minimum (\a minX and \a minZ) and |
322 | * maximum |
323 | * (\a maxX and \a maxZ) values at the same time. The minimum values must be |
324 | * smaller than the corresponding maximum value. Otherwise the values get |
325 | * adjusted so that they are valid. |
326 | */ |
327 | void QHeightMapSurfaceDataProxy::setValueRanges(float minX, float maxX, float minZ, float maxZ) |
328 | { |
329 | Q_D(QHeightMapSurfaceDataProxy); |
330 | d->setValueRanges(minX, maxX, minZ, maxZ); |
331 | } |
332 | |
333 | /*! |
334 | * \property QHeightMapSurfaceDataProxy::minXValue |
335 | * |
336 | * \brief The minimum X value for the generated surface points. |
337 | * |
338 | * Defaults to \c{0.0}. |
339 | * |
340 | * When setting this property the corresponding maximum value is adjusted if |
341 | * necessary, to ensure that the range remains valid. |
342 | */ |
343 | void QHeightMapSurfaceDataProxy::setMinXValue(float min) |
344 | { |
345 | Q_D(QHeightMapSurfaceDataProxy); |
346 | d->setMinXValue(min); |
347 | } |
348 | |
349 | float QHeightMapSurfaceDataProxy::minXValue() const |
350 | { |
351 | Q_D(const QHeightMapSurfaceDataProxy); |
352 | return d->m_minXValue; |
353 | } |
354 | |
355 | /*! |
356 | * \property QHeightMapSurfaceDataProxy::maxXValue |
357 | * |
358 | * \brief The maximum X value for the generated surface points. |
359 | * |
360 | * Defaults to \c{10.0}. |
361 | * |
362 | * When setting this property the corresponding minimum value is adjusted if |
363 | * necessary, to ensure that the range remains valid. |
364 | */ |
365 | void QHeightMapSurfaceDataProxy::setMaxXValue(float max) |
366 | { |
367 | Q_D(QHeightMapSurfaceDataProxy); |
368 | d->setMaxXValue(max); |
369 | } |
370 | |
371 | float QHeightMapSurfaceDataProxy::maxXValue() const |
372 | { |
373 | Q_D(const QHeightMapSurfaceDataProxy); |
374 | return d->m_maxXValue; |
375 | } |
376 | |
377 | /*! |
378 | * \property QHeightMapSurfaceDataProxy::minZValue |
379 | * |
380 | * \brief The minimum Z value for the generated surface points. |
381 | * |
382 | * Defaults to \c{0.0}. |
383 | * |
384 | * When setting this property the corresponding maximum value is adjusted if |
385 | * necessary, to ensure that the range remains valid. |
386 | */ |
387 | void QHeightMapSurfaceDataProxy::setMinZValue(float min) |
388 | { |
389 | Q_D(QHeightMapSurfaceDataProxy); |
390 | d->setMinZValue(min); |
391 | } |
392 | |
393 | float QHeightMapSurfaceDataProxy::minZValue() const |
394 | { |
395 | Q_D(const QHeightMapSurfaceDataProxy); |
396 | return d->m_minZValue; |
397 | } |
398 | |
399 | /*! |
400 | * \property QHeightMapSurfaceDataProxy::maxZValue |
401 | * |
402 | * \brief The maximum Z value for the generated surface points. |
403 | * |
404 | * Defaults to \c{10.0}. |
405 | * |
406 | * When setting this property the corresponding minimum value is adjusted if |
407 | * necessary, to ensure that the range remains valid. |
408 | */ |
409 | void QHeightMapSurfaceDataProxy::setMaxZValue(float max) |
410 | { |
411 | Q_D(QHeightMapSurfaceDataProxy); |
412 | d->setMaxZValue(max); |
413 | } |
414 | |
415 | float QHeightMapSurfaceDataProxy::maxZValue() const |
416 | { |
417 | Q_D(const QHeightMapSurfaceDataProxy); |
418 | return d->m_maxZValue; |
419 | } |
420 | |
421 | /*! |
422 | * \property QHeightMapSurfaceDataProxy::minYValue |
423 | * |
424 | * \brief The minimum Y value for the generated surface points. |
425 | * |
426 | * Defaults to \c{0.0}. |
427 | * |
428 | * When setting this property the corresponding maximum value is adjusted if |
429 | * necessary, to ensure that the range remains valid. |
430 | * |
431 | * \sa autoScaleY |
432 | */ |
433 | void QHeightMapSurfaceDataProxy::setMinYValue(float min) |
434 | { |
435 | Q_D(QHeightMapSurfaceDataProxy); |
436 | d->setMinYValue(min); |
437 | } |
438 | |
439 | float QHeightMapSurfaceDataProxy::minYValue() const |
440 | { |
441 | Q_D(const QHeightMapSurfaceDataProxy); |
442 | return d->m_minYValue; |
443 | } |
444 | |
445 | /*! |
446 | * \property QHeightMapSurfaceDataProxy::maxYValue |
447 | * |
448 | * \brief The maximum Y value for the generated surface points. |
449 | * |
450 | * Defaults to \c{10.0}. |
451 | * |
452 | * When setting this property the corresponding minimum value is adjusted if |
453 | * necessary, to ensure that the range remains valid. |
454 | * |
455 | * \sa autoScaleY |
456 | */ |
457 | void QHeightMapSurfaceDataProxy::setMaxYValue(float max) |
458 | { |
459 | Q_D(QHeightMapSurfaceDataProxy); |
460 | d->setMaxYValue(max); |
461 | } |
462 | |
463 | float QHeightMapSurfaceDataProxy::maxYValue() const |
464 | { |
465 | Q_D(const QHeightMapSurfaceDataProxy); |
466 | return d->m_maxYValue; |
467 | } |
468 | |
469 | /*! |
470 | * \property QHeightMapSurfaceDataProxy::autoScaleY |
471 | * |
472 | * \brief Scale height values to Y-axis. |
473 | * |
474 | * Defaults to \c{false}. |
475 | * |
476 | * When this property is set to \c{true}, |
477 | * the height values are scaled to fit on the Y-axis between minYValue and |
478 | * maxYValue. |
479 | * |
480 | * \sa minYValue, maxYValue |
481 | */ |
482 | void QHeightMapSurfaceDataProxy::setAutoScaleY(bool enabled) |
483 | { |
484 | Q_D(QHeightMapSurfaceDataProxy); |
485 | d->setAutoScaleY(enabled); |
486 | } |
487 | |
488 | bool QHeightMapSurfaceDataProxy::autoScaleY() const |
489 | { |
490 | Q_D(const QHeightMapSurfaceDataProxy); |
491 | return d->m_autoScaleY; |
492 | } |
493 | |
494 | /*! |
495 | * \internal |
496 | */ |
497 | void QHeightMapSurfaceDataProxy::handlePendingResolve() |
498 | { |
499 | Q_D(QHeightMapSurfaceDataProxy); |
500 | d->handlePendingResolve(); |
501 | } |
502 | |
503 | // QHeightMapSurfaceDataProxyPrivate |
504 | |
505 | QHeightMapSurfaceDataProxyPrivate::QHeightMapSurfaceDataProxyPrivate() |
506 | : m_minXValue(defaultMinValue) |
507 | , m_maxXValue(defaultMaxValue) |
508 | , m_minZValue(defaultMinValue) |
509 | , m_maxZValue(defaultMaxValue) |
510 | , m_minYValue(defaultMinValue) |
511 | , m_maxYValue(defaultMaxValue) |
512 | , m_autoScaleY(false) |
513 | { |
514 | m_resolveTimer.setSingleShot(true); |
515 | } |
516 | |
517 | QHeightMapSurfaceDataProxyPrivate::~QHeightMapSurfaceDataProxyPrivate() {} |
518 | |
519 | void QHeightMapSurfaceDataProxyPrivate::setValueRanges(float minX, |
520 | float maxX, |
521 | float minZ, |
522 | float maxZ) |
523 | { |
524 | Q_Q(QHeightMapSurfaceDataProxy); |
525 | bool minXChanged = false; |
526 | bool maxXChanged = false; |
527 | bool minZChanged = false; |
528 | bool maxZChanged = false; |
529 | if (m_minXValue != minX) { |
530 | m_minXValue = minX; |
531 | minXChanged = true; |
532 | } |
533 | if (m_minZValue != minZ) { |
534 | m_minZValue = minZ; |
535 | minZChanged = true; |
536 | } |
537 | if (m_maxXValue != maxX || minX >= maxX) { |
538 | if (minX >= maxX) { |
539 | m_maxXValue = minX + 1.0f; |
540 | qWarning(msg: "Warning: Tried to set invalid range for X value range. Range automatically " |
541 | "adjusted to a valid one: %f - %f --> %f - %f" , |
542 | minX, |
543 | maxX, |
544 | m_minXValue, |
545 | m_maxXValue); |
546 | } else { |
547 | m_maxXValue = maxX; |
548 | } |
549 | maxXChanged = true; |
550 | } |
551 | if (m_maxZValue != maxZ || minZ >= maxZ) { |
552 | if (minZ >= maxZ) { |
553 | m_maxZValue = minZ + 1.0f; |
554 | qWarning(msg: "Warning: Tried to set invalid range for Z value range." |
555 | " Range automatically adjusted to a valid one: %f - %f --> %f - %f" , |
556 | minZ, |
557 | maxZ, |
558 | m_minZValue, |
559 | m_maxZValue); |
560 | } else { |
561 | m_maxZValue = maxZ; |
562 | } |
563 | maxZChanged = true; |
564 | } |
565 | |
566 | if (minXChanged) |
567 | emit q->minXValueChanged(value: m_minXValue); |
568 | if (minZChanged) |
569 | emit q->minZValueChanged(value: m_minZValue); |
570 | if (maxXChanged) |
571 | emit q->maxXValueChanged(value: m_maxXValue); |
572 | if (maxZChanged) |
573 | emit q->maxZValueChanged(value: m_maxZValue); |
574 | |
575 | if ((minXChanged || minZChanged || maxXChanged || maxZChanged) && !m_resolveTimer.isActive()) |
576 | m_resolveTimer.start(msec: 0); |
577 | } |
578 | |
579 | void QHeightMapSurfaceDataProxyPrivate::setMinXValue(float min) |
580 | { |
581 | Q_Q(QHeightMapSurfaceDataProxy); |
582 | if (min != m_minXValue) { |
583 | bool maxChanged = false; |
584 | if (min >= m_maxXValue) { |
585 | float oldMax = m_maxXValue; |
586 | m_maxXValue = min + 1.0f; |
587 | qWarning(msg: "Warning: Tried to set minimum X to equal or larger than maximum X for value " |
588 | "range. Maximum automatically adjusted to a valid one: %f --> %f" , |
589 | oldMax, |
590 | m_maxXValue); |
591 | maxChanged = true; |
592 | } |
593 | m_minXValue = min; |
594 | emit q->minXValueChanged(value: m_minXValue); |
595 | if (maxChanged) |
596 | emit q->maxXValueChanged(value: m_maxXValue); |
597 | |
598 | if (!m_resolveTimer.isActive()) |
599 | m_resolveTimer.start(msec: 0); |
600 | } |
601 | } |
602 | |
603 | void QHeightMapSurfaceDataProxyPrivate::setMaxXValue(float max) |
604 | { |
605 | Q_Q(QHeightMapSurfaceDataProxy); |
606 | if (m_maxXValue != max) { |
607 | bool minChanged = false; |
608 | if (max <= m_minXValue) { |
609 | float oldMin = m_minXValue; |
610 | m_minXValue = max - 1.0f; |
611 | qWarning(msg: "Warning: Tried to set maximum X to equal or smaller than minimum X for value " |
612 | "range. Minimum automatically adjusted to a valid one: %f --> %f" , |
613 | oldMin, |
614 | m_minXValue); |
615 | minChanged = true; |
616 | } |
617 | m_maxXValue = max; |
618 | emit q->maxXValueChanged(value: m_maxXValue); |
619 | if (minChanged) |
620 | emit q->minXValueChanged(value: m_minXValue); |
621 | |
622 | if (!m_resolveTimer.isActive()) |
623 | m_resolveTimer.start(msec: 0); |
624 | } |
625 | } |
626 | |
627 | void QHeightMapSurfaceDataProxyPrivate::setMinZValue(float min) |
628 | { |
629 | Q_Q(QHeightMapSurfaceDataProxy); |
630 | if (min != m_minZValue) { |
631 | bool maxChanged = false; |
632 | if (min >= m_maxZValue) { |
633 | float oldMax = m_maxZValue; |
634 | m_maxZValue = min + 1.0f; |
635 | qWarning(msg: "Warning: Tried to set minimum Z to equal or larger than maximum Z for value " |
636 | "range. Maximum automatically adjusted to a valid one: %f --> %f" , |
637 | oldMax, |
638 | m_maxZValue); |
639 | maxChanged = true; |
640 | } |
641 | m_minZValue = min; |
642 | emit q->minZValueChanged(value: m_minZValue); |
643 | if (maxChanged) |
644 | emit q->maxZValueChanged(value: m_maxZValue); |
645 | |
646 | if (!m_resolveTimer.isActive()) |
647 | m_resolveTimer.start(msec: 0); |
648 | } |
649 | } |
650 | |
651 | void QHeightMapSurfaceDataProxyPrivate::setMaxZValue(float max) |
652 | { |
653 | Q_Q(QHeightMapSurfaceDataProxy); |
654 | if (m_maxZValue != max) { |
655 | bool minChanged = false; |
656 | if (max <= m_minZValue) { |
657 | float oldMin = m_minZValue; |
658 | m_minZValue = max - 1.0f; |
659 | qWarning(msg: "Warning: Tried to set maximum Z to equal or smaller than minimum Z for value " |
660 | "range. Minimum automatically adjusted to a valid one: %f --> %f" , |
661 | oldMin, |
662 | m_minZValue); |
663 | minChanged = true; |
664 | } |
665 | m_maxZValue = max; |
666 | emit q->maxZValueChanged(value: m_maxZValue); |
667 | if (minChanged) |
668 | emit q->minZValueChanged(value: m_minZValue); |
669 | |
670 | if (!m_resolveTimer.isActive()) |
671 | m_resolveTimer.start(msec: 0); |
672 | } |
673 | } |
674 | |
675 | void QHeightMapSurfaceDataProxyPrivate::setMinYValue(float min) |
676 | { |
677 | Q_Q(QHeightMapSurfaceDataProxy); |
678 | if (m_minYValue != min) { |
679 | bool maxChanged = false; |
680 | if (min >= m_maxYValue) { |
681 | float oldMax = m_maxYValue; |
682 | m_maxYValue = min + 1.0f; |
683 | qWarning(msg: "Warning: Tried to set minimum Y to equal or larger than maximum Y for value " |
684 | "range. Maximum automatically adjusted to a valid one: %f --> %f" , |
685 | oldMax, |
686 | m_maxYValue); |
687 | maxChanged = true; |
688 | } |
689 | m_minYValue = min; |
690 | emit q->minYValueChanged(value: m_minYValue); |
691 | if (maxChanged) |
692 | emit q->maxYValueChanged(value: m_maxYValue); |
693 | |
694 | if (!m_resolveTimer.isActive()) |
695 | m_resolveTimer.start(msec: 0); |
696 | } |
697 | } |
698 | |
699 | void QHeightMapSurfaceDataProxyPrivate::setMaxYValue(float max) |
700 | { |
701 | Q_Q(QHeightMapSurfaceDataProxy); |
702 | if (m_maxYValue != max) { |
703 | bool minChanged = false; |
704 | if (max <= m_minYValue) { |
705 | float oldMin = m_minYValue; |
706 | m_minYValue = max - 1.0f; |
707 | qWarning(msg: "Warning: Tried to set maximum Y to equal or smaller than minimum Y for value " |
708 | "range. Minimum automatically adjusted to a valid one: %f --> %f" , |
709 | oldMin, |
710 | m_minYValue); |
711 | minChanged = true; |
712 | } |
713 | m_maxYValue = max; |
714 | emit q->maxYValueChanged(value: m_maxYValue); |
715 | if (minChanged) |
716 | emit q->minYValueChanged(value: m_minYValue); |
717 | |
718 | if (!m_resolveTimer.isActive()) |
719 | m_resolveTimer.start(msec: 0); |
720 | } |
721 | } |
722 | |
723 | void QHeightMapSurfaceDataProxyPrivate::setAutoScaleY(bool enabled) |
724 | { |
725 | Q_Q(QHeightMapSurfaceDataProxy); |
726 | if (enabled != m_autoScaleY) { |
727 | m_autoScaleY = enabled; |
728 | emit q->autoScaleYChanged(enabled: m_autoScaleY); |
729 | |
730 | if (!m_resolveTimer.isActive()) |
731 | m_resolveTimer.start(msec: 0); |
732 | } |
733 | } |
734 | |
735 | void QHeightMapSurfaceDataProxyPrivate::handlePendingResolve() |
736 | { |
737 | Q_Q(QHeightMapSurfaceDataProxy); |
738 | QImage heightImage = m_heightMap; |
739 | int bytesInChannel = 1; |
740 | float yMul = 1.0f / UINT8_MAX; |
741 | |
742 | bool is16bit = (heightImage.format() == QImage::Format_RGBX64 |
743 | || heightImage.format() == QImage::Format_RGBA64 |
744 | || heightImage.format() == QImage::Format_RGBA64_Premultiplied |
745 | || heightImage.format() == QImage::Format_Grayscale16); |
746 | |
747 | // Convert to RGB32 to be sure we're reading the right bytes |
748 | if (is16bit) { |
749 | if (heightImage.format() != QImage::Format_RGBX64) |
750 | heightImage = heightImage.convertToFormat(f: QImage::Format_RGBX64); |
751 | |
752 | bytesInChannel = 2; |
753 | yMul = 1.0f / UINT16_MAX; |
754 | } else if (heightImage.format() != QImage::Format_RGB32) { |
755 | heightImage = heightImage.convertToFormat(f: QImage::Format_RGB32); |
756 | } |
757 | |
758 | uchar *bits = heightImage.bits(); |
759 | |
760 | int imageHeight = heightImage.height(); |
761 | int imageWidth = heightImage.width(); |
762 | int bitCount = imageWidth * 4 * (imageHeight - 1) * bytesInChannel; |
763 | int widthBits = imageWidth * 4 * bytesInChannel; |
764 | float height = 0; |
765 | |
766 | // Do not recreate array if dimensions have not changed |
767 | QSurfaceDataArray dataArray = q->series()->dataArray(); |
768 | if (imageWidth != q->columnCount() || imageHeight != dataArray.size()) { |
769 | dataArray.clear(); |
770 | dataArray.reserve(asize: imageHeight); |
771 | for (int i = 0; i < imageHeight; i++) { |
772 | QSurfaceDataRow newProxyRow(imageWidth); |
773 | dataArray.append(t: newProxyRow); |
774 | } |
775 | } |
776 | yMul *= m_maxYValue - m_minYValue; |
777 | float xMul = (m_maxXValue - m_minXValue) / float(imageWidth - 1); |
778 | float zMul = (m_maxZValue - m_minZValue) / float(imageHeight - 1); |
779 | |
780 | // Last row and column are explicitly set to max values, as relying |
781 | // on multiplier can cause rounding errors, resulting in the value being |
782 | // slightly over the specified maximum, which in turn can lead to it not |
783 | // getting rendered. |
784 | int lastRow = imageHeight - 1; |
785 | int lastCol = imageWidth - 1; |
786 | if (heightImage.isGrayscale()) { |
787 | // Grayscale, it's enough to read Red byte |
788 | for (int i = 0; i < imageHeight; i++, bitCount -= widthBits) { |
789 | QSurfaceDataRow &newRow = dataArray[i]; |
790 | float zVal; |
791 | if (i == lastRow) |
792 | zVal = m_maxZValue; |
793 | else |
794 | zVal = (float(i) * zMul) + m_minZValue; |
795 | int j = 0; |
796 | float yVal = 0; |
797 | uchar *pixelptr; |
798 | for (; j < lastCol; j++) { |
799 | pixelptr = (uchar *) (bits + bitCount + (j * 4 * bytesInChannel)); |
800 | if (!m_autoScaleY) |
801 | yVal = *pixelptr; |
802 | else |
803 | yVal = float(*pixelptr) * yMul + m_minYValue; |
804 | newRow[j].setPosition(QVector3D((float(j) * xMul) + m_minXValue, yVal, zVal)); |
805 | } |
806 | newRow[j].setPosition(QVector3D(m_maxXValue, yVal, zVal)); |
807 | } |
808 | } else { |
809 | // Not grayscale, we'll need to calculate height from RGB |
810 | for (int i = 0; i < imageHeight; i++, bitCount -= widthBits) { |
811 | QSurfaceDataRow &newRow = dataArray[i]; |
812 | float zVal; |
813 | if (i == lastRow) |
814 | zVal = m_maxZValue; |
815 | else |
816 | zVal = (float(i) * zMul) + m_minZValue; |
817 | int j = 0; |
818 | float yVal = 0; |
819 | for (; j < lastCol; j++) { |
820 | int nextpixel = j * 4 * bytesInChannel; |
821 | uchar *pixelptr = (uchar *) (bits + bitCount + nextpixel); |
822 | if (is16bit) { |
823 | height = (float(*((ushort *) pixelptr)) + float(*(((ushort *) pixelptr) + 1)) |
824 | + float(*(((ushort *) pixelptr) + 2))); |
825 | } else { |
826 | height = (float(*pixelptr) + float(*(pixelptr + 1)) + float(*(pixelptr + 2))); |
827 | } |
828 | if (!m_autoScaleY) |
829 | yVal = height / 3.0f; |
830 | else |
831 | yVal = (height / 3.0f * yMul) + m_minYValue; |
832 | |
833 | newRow[j].setPosition(QVector3D((float(j) * xMul) + m_minXValue, yVal, zVal)); |
834 | } |
835 | newRow[j].setPosition(QVector3D(m_maxXValue, yVal, zVal)); |
836 | } |
837 | } |
838 | |
839 | q->resetArray(newArray: dataArray); |
840 | emit q->heightMapChanged(image: m_heightMap); |
841 | } |
842 | |
843 | QT_END_NAMESPACE |
844 | |