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