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