// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #include "qheightmapsurfacedataproxy_p.h" #include "qsurface3dseries_p.h" QT_BEGIN_NAMESPACE // Default ranges correspond value axis defaults const float defaultMinValue = 0.0f; const float defaultMaxValue = 10.0f; /*! * \class QHeightMapSurfaceDataProxy * \inmodule QtGraphs * \ingroup graphs_3D * \brief Base proxy class for Q3DSurfaceWidgetItem. * * QHeightMapSurfaceDataProxy takes care of the processing of height map data * related to surfaces. It provides the visualization of a height map as a * surface plot. * * Since height maps do not contain values for X or Z axes, those values need to * be given separately using the minXValue, maxXValue, minZValue, and maxZValue * properties. The X-value corresponds to image horizontal direction and the Z-value * to the vertical. Setting any of these properties triggers an asynchronous * re-resolution of any existing height map. * * \sa QSurfaceDataProxy, {Qt Graphs Data Handling with 3D} */ /*! * \qmltype HeightMapSurfaceDataProxy * \inqmlmodule QtGraphs * \ingroup graphs_qml_3D * \nativetype QHeightMapSurfaceDataProxy * \inherits SurfaceDataProxy * \brief Base proxy type for Surface3D. * * QHeightMapSurfaceDataProxy takes care of the processing of height map data * related to surfaces. It provides the visualization of a height map as a * surface plot. * * For more complete description, see QHeightMapSurfaceDataProxy. * * \sa {Qt Graphs Data Handling with 3D} */ /*! * \qmlproperty string HeightMapSurfaceDataProxy::heightMapFile * * A file with a height map image to be visualized. Setting this property * replaces current data with height map data. * * There are several formats the image file can be given in, but if it is not in * a directly usable format, a conversion is made. * * \note If the result seems wrong, the automatic conversion failed * and you should try converting the image yourself before setting it. Preferred * format is QImage::Format_RGB32 in grayscale. * * The height of the image is read from the red component of the pixels if the * image is in grayscale. Otherwise, it is an average calculated from the red, green, * and blue components of the pixels. Using grayscale images may improve data * conversion speed for large images. * * Since height maps do not contain values for X or Z axes, these values need to * be given separately using the minXValue, maxXValue, minZValue, and maxZValue * properties. The X-value corresponds to the image's horizontal direction, * and the Z-value to the vertical. Setting any of these properties triggers * an asynchronous re-resolution of any existing height map. * * Not recommended formats: all mono formats (for example QImage::Format_Mono). */ /*! * \qmlproperty real HeightMapSurfaceDataProxy::minXValue * * The minimum X value for the generated surface points. Defaults to \c{0.0}. * When setting this property the corresponding maximum value is adjusted if * necessary, to ensure that the range remains valid. */ /*! * \qmlproperty real HeightMapSurfaceDataProxy::maxXValue * * The maximum X value for the generated surface points. Defaults to \c{10.0}. * When setting this property the corresponding minimum value is adjusted if * necessary, to ensure that the range remains valid. */ /*! * \qmlproperty real HeightMapSurfaceDataProxy::minZValue * * The minimum Z value for the generated surface points. Defaults to \c{0.0}. * When setting this property the corresponding maximum value is adjusted if * necessary, to ensure that the range remains valid. */ /*! * \qmlproperty real HeightMapSurfaceDataProxy::maxZValue * * The maximum Z value for the generated surface points. Defaults to \c{10.0}. * When setting this property the corresponding minimum value is adjusted if * necessary, to ensure that the range remains valid. */ /*! * \qmlproperty real HeightMapSurfaceDataProxy::minYValue * * The minimum Y value for the generated surface points. Defaults to \c{0.0}. * When setting this property the corresponding maximum value is adjusted if * necessary, to ensure that the range remains valid. */ /*! * \qmlproperty real HeightMapSurfaceDataProxy::maxYValue * * The maximum Y value for the generated surface points. Defaults to \c{10.0}. * When setting this property the corresponding minimum value is adjusted if * necessary, to ensure that the range remains valid. */ /*! * \qmlproperty real HeightMapSurfaceDataProxy::autoScaleY * * Scale height values to Y-axis. Defaults to \c{false}. When this property is * set to \c{true}, the height values are scaled to fit on the Y-axis between * \c{minYValue} and \c{maxYValue}. */ /*! \qmlsignal HeightMapSurfaceDataProxy::heightMapFileChanged(string filename) This signal is emitted when heightMapFile changes to \a filename. */ /*! \qmlsignal HeightMapSurfaceDataProxy::minXValueChanged(real value) This signal is emitted when minXValue changes to \a value. */ /*! \qmlsignal HeightMapSurfaceDataProxy::maxXValueChanged(real value) This signal is emitted when maxXValue changes to \a value. */ /*! \qmlsignal HeightMapSurfaceDataProxy::minZValueChanged(real value) This signal is emitted when minZValue changes to \a value. */ /*! \qmlsignal HeightMapSurfaceDataProxy::maxZValueChanged(real value) This signal is emitted when maxZValue changes to \a value. */ /*! \qmlsignal HeightMapSurfaceDataProxy::minYValueChanged(real value) This signal is emitted when minYValue changes to \a value. */ /*! \qmlsignal HeightMapSurfaceDataProxy::maxYValueChanged(real value) This signal is emitted when maxYValue changes to \a value. */ /*! \qmlsignal HeightMapSurfaceDataProxy::autoScaleYChanged(bool enabled) This signal is emitted when autoScaleY changes to \a enabled. */ /*! * Constructs QHeightMapSurfaceDataProxy with the given \a parent. */ QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(QObject *parent) : QSurfaceDataProxy(*(new QHeightMapSurfaceDataProxyPrivate()), parent) { Q_D(QHeightMapSurfaceDataProxy); QObject::connect(&d->m_resolveTimer, &QTimer::timeout, this, &QHeightMapSurfaceDataProxy::handlePendingResolve); } /*! * Constructs QHeightMapSurfaceDataProxy with the given \a image and \a parent. * Height map is set by calling setHeightMap() with \a image. * * \sa heightMap */ QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(const QImage &image, QObject *parent) : QSurfaceDataProxy(*(new QHeightMapSurfaceDataProxyPrivate()), parent) { Q_D(QHeightMapSurfaceDataProxy); QObject::connect(&d->m_resolveTimer, &QTimer::timeout, this, &QHeightMapSurfaceDataProxy::handlePendingResolve); setHeightMap(image); } /*! * Constructs QHeightMapSurfaceDataProxy from the given image \a filename and \a * parent. Height map is set by calling setHeightMapFile() with \a filename. * * \sa heightMapFile */ QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(const QString &filename, QObject *parent) : QSurfaceDataProxy(*(new QHeightMapSurfaceDataProxyPrivate()), parent) { Q_D(QHeightMapSurfaceDataProxy); QObject::connect(&d->m_resolveTimer, &QTimer::timeout, this, &QHeightMapSurfaceDataProxy::handlePendingResolve); setHeightMapFile(filename); } /*! * \internal */ QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(QHeightMapSurfaceDataProxyPrivate &d, QObject *parent) : QSurfaceDataProxy(d, parent) {} /*! * Destroys QHeightMapSurfaceDataProxy. */ QHeightMapSurfaceDataProxy::~QHeightMapSurfaceDataProxy() {} /*! * \property QHeightMapSurfaceDataProxy::heightMap * * \brief The height map image to be visualized. */ /*! * Replaces current data with the height map data specified by \a image. * * There are several formats the \a image can be given in, but if it is not in a * directly usable format, a conversion is made. * * \note If the result seems wrong, the automatic conversion failed * and you should try converting the \a image yourself before setting it. * Preferred format is QImage::Format_RGB32 in grayscale. * * The height of the \a image is read from the red component of the pixels if * the \a image is in grayscale. Otherwise it is an average calculated from the red, * green, and blue components of the pixels. Using grayscale images may improve * data conversion speed for large images. * * Not recommended formats: all mono formats (for example QImage::Format_Mono). * * The height map is resolved asynchronously. QSurfaceDataProxy::arrayReset() is * emitted when the data has been resolved. */ void QHeightMapSurfaceDataProxy::setHeightMap(const QImage &image) { Q_D(QHeightMapSurfaceDataProxy); d->m_heightMap = image; // We do resolving asynchronously to make qml onArrayReset handlers actually // get the initial reset if (!d->m_resolveTimer.isActive()) d->m_resolveTimer.start(0); } QImage QHeightMapSurfaceDataProxy::heightMap() const { Q_D(const QHeightMapSurfaceDataProxy); return d->m_heightMap; } /*! * \property QHeightMapSurfaceDataProxy::heightMapFile * * \brief The name of the file with a height map image to be visualized. */ /*! * Replaces current data with height map data from the file specified by * \a filename. * * \sa heightMap */ void QHeightMapSurfaceDataProxy::setHeightMapFile(const QString &filename) { Q_D(QHeightMapSurfaceDataProxy); QFileInfo validfile(filename); // Check if the filename is empty, in which case we should clear the height map, // or if not, it's an actual file that can be found if (!filename.isEmpty() && (!validfile.exists() || !validfile.isFile())) { qWarning("Height map file %ls does not exist.", qUtf16Printable(filename)); return; } if (d->m_heightMapFile != filename) { d->m_heightMapFile = filename; setHeightMap(QImage(filename)); emit heightMapFileChanged(filename); } } QString QHeightMapSurfaceDataProxy::heightMapFile() const { Q_D(const QHeightMapSurfaceDataProxy); return d->m_heightMapFile; } /*! * A convenience function for setting all minimum (\a minX and \a minZ) and * maximum * (\a maxX and \a maxZ) values at the same time. The minimum values must be * smaller than the corresponding maximum value. Otherwise the values get * adjusted so that they are valid. */ void QHeightMapSurfaceDataProxy::setValueRanges(float minX, float maxX, float minZ, float maxZ) { Q_D(QHeightMapSurfaceDataProxy); d->setValueRanges(minX, maxX, minZ, maxZ); } /*! * \property QHeightMapSurfaceDataProxy::minXValue * * \brief The minimum X value for the generated surface points. * * Defaults to \c{0.0}. * * When setting this property the corresponding maximum value is adjusted if * necessary, to ensure that the range remains valid. */ void QHeightMapSurfaceDataProxy::setMinXValue(float min) { Q_D(QHeightMapSurfaceDataProxy); d->setMinXValue(min); } float QHeightMapSurfaceDataProxy::minXValue() const { Q_D(const QHeightMapSurfaceDataProxy); return d->m_minXValue; } /*! * \property QHeightMapSurfaceDataProxy::maxXValue * * \brief The maximum X value for the generated surface points. * * Defaults to \c{10.0}. * * When setting this property the corresponding minimum value is adjusted if * necessary, to ensure that the range remains valid. */ void QHeightMapSurfaceDataProxy::setMaxXValue(float max) { Q_D(QHeightMapSurfaceDataProxy); d->setMaxXValue(max); } float QHeightMapSurfaceDataProxy::maxXValue() const { Q_D(const QHeightMapSurfaceDataProxy); return d->m_maxXValue; } /*! * \property QHeightMapSurfaceDataProxy::minZValue * * \brief The minimum Z value for the generated surface points. * * Defaults to \c{0.0}. * * When setting this property the corresponding maximum value is adjusted if * necessary, to ensure that the range remains valid. */ void QHeightMapSurfaceDataProxy::setMinZValue(float min) { Q_D(QHeightMapSurfaceDataProxy); d->setMinZValue(min); } float QHeightMapSurfaceDataProxy::minZValue() const { Q_D(const QHeightMapSurfaceDataProxy); return d->m_minZValue; } /*! * \property QHeightMapSurfaceDataProxy::maxZValue * * \brief The maximum Z value for the generated surface points. * * Defaults to \c{10.0}. * * When setting this property the corresponding minimum value is adjusted if * necessary, to ensure that the range remains valid. */ void QHeightMapSurfaceDataProxy::setMaxZValue(float max) { Q_D(QHeightMapSurfaceDataProxy); d->setMaxZValue(max); } float QHeightMapSurfaceDataProxy::maxZValue() const { Q_D(const QHeightMapSurfaceDataProxy); return d->m_maxZValue; } /*! * \property QHeightMapSurfaceDataProxy::minYValue * * \brief The minimum Y value for the generated surface points. * * Defaults to \c{0.0}. * * When setting this property the corresponding maximum value is adjusted if * necessary, to ensure that the range remains valid. * * \sa autoScaleY */ void QHeightMapSurfaceDataProxy::setMinYValue(float min) { Q_D(QHeightMapSurfaceDataProxy); d->setMinYValue(min); } float QHeightMapSurfaceDataProxy::minYValue() const { Q_D(const QHeightMapSurfaceDataProxy); return d->m_minYValue; } /*! * \property QHeightMapSurfaceDataProxy::maxYValue * * \brief The maximum Y value for the generated surface points. * * Defaults to \c{10.0}. * * When setting this property the corresponding minimum value is adjusted if * necessary, to ensure that the range remains valid. * * \sa autoScaleY */ void QHeightMapSurfaceDataProxy::setMaxYValue(float max) { Q_D(QHeightMapSurfaceDataProxy); d->setMaxYValue(max); } float QHeightMapSurfaceDataProxy::maxYValue() const { Q_D(const QHeightMapSurfaceDataProxy); return d->m_maxYValue; } /*! * \property QHeightMapSurfaceDataProxy::autoScaleY * * \brief Scale height values to Y-axis. * * Defaults to \c{false}. * * When this property is set to \c{true}, * the height values are scaled to fit on the Y-axis between minYValue and * maxYValue. * * \sa minYValue, maxYValue */ void QHeightMapSurfaceDataProxy::setAutoScaleY(bool enabled) { Q_D(QHeightMapSurfaceDataProxy); d->setAutoScaleY(enabled); } bool QHeightMapSurfaceDataProxy::autoScaleY() const { Q_D(const QHeightMapSurfaceDataProxy); return d->m_autoScaleY; } /*! * \internal */ void QHeightMapSurfaceDataProxy::handlePendingResolve() { Q_D(QHeightMapSurfaceDataProxy); d->handlePendingResolve(); } // QHeightMapSurfaceDataProxyPrivate QHeightMapSurfaceDataProxyPrivate::QHeightMapSurfaceDataProxyPrivate() : m_minXValue(defaultMinValue) , m_maxXValue(defaultMaxValue) , m_minZValue(defaultMinValue) , m_maxZValue(defaultMaxValue) , m_minYValue(defaultMinValue) , m_maxYValue(defaultMaxValue) , m_autoScaleY(false) { m_resolveTimer.setSingleShot(true); } QHeightMapSurfaceDataProxyPrivate::~QHeightMapSurfaceDataProxyPrivate() {} void QHeightMapSurfaceDataProxyPrivate::setValueRanges(float minX, float maxX, float minZ, float maxZ) { Q_Q(QHeightMapSurfaceDataProxy); bool minXChanged = false; bool maxXChanged = false; bool minZChanged = false; bool maxZChanged = false; if (m_minXValue != minX) { m_minXValue = minX; minXChanged = true; } if (m_minZValue != minZ) { m_minZValue = minZ; minZChanged = true; } if (m_maxXValue != maxX || minX >= maxX) { if (minX >= maxX) { m_maxXValue = minX + 1.0f; qWarning("Warning: Tried to set invalid range for X value range. Range automatically " "adjusted to a valid one: %f - %f --> %f - %f", minX, maxX, m_minXValue, m_maxXValue); } else { m_maxXValue = maxX; } maxXChanged = true; } if (m_maxZValue != maxZ || minZ >= maxZ) { if (minZ >= maxZ) { m_maxZValue = minZ + 1.0f; qWarning("Warning: Tried to set invalid range for Z value range." " Range automatically adjusted to a valid one: %f - %f --> %f - %f", minZ, maxZ, m_minZValue, m_maxZValue); } else { m_maxZValue = maxZ; } maxZChanged = true; } if (minXChanged) emit q->minXValueChanged(m_minXValue); if (minZChanged) emit q->minZValueChanged(m_minZValue); if (maxXChanged) emit q->maxXValueChanged(m_maxXValue); if (maxZChanged) emit q->maxZValueChanged(m_maxZValue); if ((minXChanged || minZChanged || maxXChanged || maxZChanged) && !m_resolveTimer.isActive()) m_resolveTimer.start(0); } void QHeightMapSurfaceDataProxyPrivate::setMinXValue(float min) { Q_Q(QHeightMapSurfaceDataProxy); if (min != m_minXValue) { bool maxChanged = false; if (min >= m_maxXValue) { float oldMax = m_maxXValue; m_maxXValue = min + 1.0f; qWarning("Warning: Tried to set minimum X to equal or larger than maximum X for value " "range. Maximum automatically adjusted to a valid one: %f --> %f", oldMax, m_maxXValue); maxChanged = true; } m_minXValue = min; emit q->minXValueChanged(m_minXValue); if (maxChanged) emit q->maxXValueChanged(m_maxXValue); if (!m_resolveTimer.isActive()) m_resolveTimer.start(0); } } void QHeightMapSurfaceDataProxyPrivate::setMaxXValue(float max) { Q_Q(QHeightMapSurfaceDataProxy); if (m_maxXValue != max) { bool minChanged = false; if (max <= m_minXValue) { float oldMin = m_minXValue; m_minXValue = max - 1.0f; qWarning("Warning: Tried to set maximum X to equal or smaller than minimum X for value " "range. Minimum automatically adjusted to a valid one: %f --> %f", oldMin, m_minXValue); minChanged = true; } m_maxXValue = max; emit q->maxXValueChanged(m_maxXValue); if (minChanged) emit q->minXValueChanged(m_minXValue); if (!m_resolveTimer.isActive()) m_resolveTimer.start(0); } } void QHeightMapSurfaceDataProxyPrivate::setMinZValue(float min) { Q_Q(QHeightMapSurfaceDataProxy); if (min != m_minZValue) { bool maxChanged = false; if (min >= m_maxZValue) { float oldMax = m_maxZValue; m_maxZValue = min + 1.0f; qWarning("Warning: Tried to set minimum Z to equal or larger than maximum Z for value " "range. Maximum automatically adjusted to a valid one: %f --> %f", oldMax, m_maxZValue); maxChanged = true; } m_minZValue = min; emit q->minZValueChanged(m_minZValue); if (maxChanged) emit q->maxZValueChanged(m_maxZValue); if (!m_resolveTimer.isActive()) m_resolveTimer.start(0); } } void QHeightMapSurfaceDataProxyPrivate::setMaxZValue(float max) { Q_Q(QHeightMapSurfaceDataProxy); if (m_maxZValue != max) { bool minChanged = false; if (max <= m_minZValue) { float oldMin = m_minZValue; m_minZValue = max - 1.0f; qWarning("Warning: Tried to set maximum Z to equal or smaller than minimum Z for value " "range. Minimum automatically adjusted to a valid one: %f --> %f", oldMin, m_minZValue); minChanged = true; } m_maxZValue = max; emit q->maxZValueChanged(m_maxZValue); if (minChanged) emit q->minZValueChanged(m_minZValue); if (!m_resolveTimer.isActive()) m_resolveTimer.start(0); } } void QHeightMapSurfaceDataProxyPrivate::setMinYValue(float min) { Q_Q(QHeightMapSurfaceDataProxy); if (m_minYValue != min) { bool maxChanged = false; if (min >= m_maxYValue) { float oldMax = m_maxYValue; m_maxYValue = min + 1.0f; qWarning("Warning: Tried to set minimum Y to equal or larger than maximum Y for value " "range. Maximum automatically adjusted to a valid one: %f --> %f", oldMax, m_maxYValue); maxChanged = true; } m_minYValue = min; emit q->minYValueChanged(m_minYValue); if (maxChanged) emit q->maxYValueChanged(m_maxYValue); if (!m_resolveTimer.isActive()) m_resolveTimer.start(0); } } void QHeightMapSurfaceDataProxyPrivate::setMaxYValue(float max) { Q_Q(QHeightMapSurfaceDataProxy); if (m_maxYValue != max) { bool minChanged = false; if (max <= m_minYValue) { float oldMin = m_minYValue; m_minYValue = max - 1.0f; qWarning("Warning: Tried to set maximum Y to equal or smaller than minimum Y for value " "range. Minimum automatically adjusted to a valid one: %f --> %f", oldMin, m_minYValue); minChanged = true; } m_maxYValue = max; emit q->maxYValueChanged(m_maxYValue); if (minChanged) emit q->minYValueChanged(m_minYValue); if (!m_resolveTimer.isActive()) m_resolveTimer.start(0); } } void QHeightMapSurfaceDataProxyPrivate::setAutoScaleY(bool enabled) { Q_Q(QHeightMapSurfaceDataProxy); if (enabled != m_autoScaleY) { m_autoScaleY = enabled; emit q->autoScaleYChanged(m_autoScaleY); if (!m_resolveTimer.isActive()) m_resolveTimer.start(0); } } void QHeightMapSurfaceDataProxyPrivate::handlePendingResolve() { Q_Q(QHeightMapSurfaceDataProxy); QImage heightImage = m_heightMap; int bytesInChannel = 1; float yMul = 1.0f / UINT8_MAX; bool is16bit = (heightImage.format() == QImage::Format_RGBX64 || heightImage.format() == QImage::Format_RGBA64 || heightImage.format() == QImage::Format_RGBA64_Premultiplied || heightImage.format() == QImage::Format_Grayscale16); // Convert to RGB32 to be sure we're reading the right bytes if (is16bit) { if (heightImage.format() != QImage::Format_RGBX64) heightImage = heightImage.convertToFormat(QImage::Format_RGBX64); bytesInChannel = 2; yMul = 1.0f / UINT16_MAX; } else if (heightImage.format() != QImage::Format_RGB32) { heightImage = heightImage.convertToFormat(QImage::Format_RGB32); } uchar *bits = heightImage.bits(); int imageHeight = heightImage.height(); int imageWidth = heightImage.width(); int bitCount = imageWidth * 4 * (imageHeight - 1) * bytesInChannel; int widthBits = imageWidth * 4 * bytesInChannel; float height = 0; // Do not recreate array if dimensions have not changed QSurfaceDataArray dataArray = q->series()->dataArray(); if (imageWidth != q->columnCount() || imageHeight != dataArray.size()) { dataArray.clear(); dataArray.reserve(imageHeight); for (int i = 0; i < imageHeight; i++) { QSurfaceDataRow newProxyRow(imageWidth); dataArray.append(newProxyRow); } } yMul *= m_maxYValue - m_minYValue; float xMul = (m_maxXValue - m_minXValue) / float(imageWidth - 1); float zMul = (m_maxZValue - m_minZValue) / float(imageHeight - 1); // Last row and column are explicitly set to max values, as relying // on multiplier can cause rounding errors, resulting in the value being // slightly over the specified maximum, which in turn can lead to it not // getting rendered. int lastRow = imageHeight - 1; int lastCol = imageWidth - 1; if (heightImage.isGrayscale()) { // Grayscale, it's enough to read Red byte for (int i = 0; i < imageHeight; i++, bitCount -= widthBits) { QSurfaceDataRow &newRow = dataArray[i]; float zVal; if (i == lastRow) zVal = m_maxZValue; else zVal = (float(i) * zMul) + m_minZValue; int j = 0; float yVal = 0; uchar *pixelptr; for (; j < lastCol; j++) { pixelptr = (uchar *) (bits + bitCount + (j * 4 * bytesInChannel)); if (!m_autoScaleY) yVal = *pixelptr; else yVal = float(*pixelptr) * yMul + m_minYValue; newRow[j].setPosition(QVector3D((float(j) * xMul) + m_minXValue, yVal, zVal)); } newRow[j].setPosition(QVector3D(m_maxXValue, yVal, zVal)); } } else { // Not grayscale, we'll need to calculate height from RGB for (int i = 0; i < imageHeight; i++, bitCount -= widthBits) { QSurfaceDataRow &newRow = dataArray[i]; float zVal; if (i == lastRow) zVal = m_maxZValue; else zVal = (float(i) * zMul) + m_minZValue; int j = 0; float yVal = 0; for (; j < lastCol; j++) { int nextpixel = j * 4 * bytesInChannel; uchar *pixelptr = (uchar *) (bits + bitCount + nextpixel); if (is16bit) { height = (float(*((ushort *) pixelptr)) + float(*(((ushort *) pixelptr) + 1)) + float(*(((ushort *) pixelptr) + 2))); } else { height = (float(*pixelptr) + float(*(pixelptr + 1)) + float(*(pixelptr + 2))); } if (!m_autoScaleY) yVal = height / 3.0f; else yVal = (height / 3.0f * yMul) + m_minYValue; newRow[j].setPosition(QVector3D((float(j) * xMul) + m_minXValue, yVal, zVal)); } newRow[j].setPosition(QVector3D(m_maxXValue, yVal, zVal)); } } q->resetArray(dataArray); emit q->heightMapChanged(m_heightMap); } QT_END_NAMESPACE