1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "heightfieldgeometry_p.h"
5
6/*!
7 \qmltype HeightFieldGeometry
8 \inqmlmodule QtQuick3D.Helpers
9 \inherits Geometry
10 \since 6.4
11 \brief A height field geometry.
12
13 This helper implements a height-field geometry. It defines a surface built from a grayscale image.
14 The y-coordinate of the surface at a given point in the horizontal plane is determined by the
15 pixel value at the corresponding point in the image. The image's x-axis and y-axis will go along
16 the geometry's x-axis and z-axis respectively.
17*/
18
19/*!
20 \qmlproperty vector3d HeightFieldGeometry::extents
21 This property defines the extents of the height-field, that is
22 the dimensions of a box large enough to always contain the geometry.
23 The default value is (100, 100, 100) when the image is square.
24*/
25
26/*!
27 \qmlproperty QUrl HeightFieldGeometry::heightMap
28 \obsolete
29
30 This property defines the URL of the height map image.
31
32 Use \l HeightFieldGeometry::source instead.
33*/
34
35/*!
36 \qmlproperty QUrl HeightFieldGeometry::source
37 This property defines the URL of the height map image.
38*/
39
40/*!
41 \qmlproperty bool HeightFieldGeometry::smoothShading
42 This property defines whether the height map is shown with smooth shading
43 or with hard angles between the squares of the map.
44
45 The default value is \c true, meaning smooth scaling is turned on.
46*/
47
48
49HeightFieldGeometry::HeightFieldGeometry()
50{
51 updateData();
52}
53
54const QUrl &HeightFieldGeometry::source() const
55{
56 return m_heightMapSource;
57}
58
59void HeightFieldGeometry::setSource(const QUrl &newSource)
60{
61 if (m_heightMapSource == newSource)
62 return;
63 m_heightMapSource = newSource;
64
65 updateData();
66 update();
67
68 emit sourceChanged();
69}
70
71bool HeightFieldGeometry::smoothShading() const
72{
73 return m_smoothShading;
74}
75
76void HeightFieldGeometry::setSmoothShading(bool smooth)
77{
78 if (m_smoothShading == smooth)
79 return;
80 m_smoothShading = smooth;
81
82 updateData();
83 update();
84
85 emit smoothShadingChanged();
86}
87
88const QVector3D &HeightFieldGeometry::extents() const
89{
90 return m_extents;
91}
92
93void HeightFieldGeometry::setExtents(const QVector3D &newExtents)
94{
95 m_extentsSetExplicitly = true;
96 if (m_extents == newExtents)
97 return;
98 m_extents = newExtents;
99
100 updateData();
101 update();
102 emit extentsChanged();
103}
104
105struct HeightFieldVertex
106{
107 QVector3D position;
108 QVector3D normal;
109 QVector2D uv;
110};
111
112void HeightFieldGeometry::updateData()
113{
114 const QQmlContext *context = qmlContext(this);
115
116 const auto resolvedUrl = context ? context->resolvedUrl(m_heightMapSource) : m_heightMapSource;
117 if (!resolvedUrl.isValid())
118 return;
119
120 clear();
121 const auto qmlSource = QQmlFile::urlToLocalFileOrQrc(resolvedUrl);
122
123 QImage heightMap(qmlSource);
124 int numRows = heightMap.height();
125 int numCols = heightMap.width();
126
127 if (numRows < 2 || numCols < 2)
128 return;
129
130 const int numVertices = numRows * numCols;
131
132 if (!m_extentsSetExplicitly) {
133 auto prevExt = m_extents;
134 if (numRows == numCols) {
135 m_extents = {100, 100, 100};
136 } else if (numRows < numCols) {
137 float f = float(numRows) / float(numCols);
138 m_extents = {100.f, 100.f, 100.f * f};
139 } else {
140 float f = float(numCols) / float(numRows);
141 m_extents = {100.f * f, 100.f, 100.f};
142 }
143 if (m_extents != prevExt) {
144 emit extentsChanged();
145 }
146 }
147
148 QVector<HeightFieldVertex> vertices;
149 vertices.reserve(asize: numVertices);
150
151 const float rowF = m_extents.z() / (numRows - 1);
152 const float rowOffs = -m_extents.z() / 2;
153 const float colF = m_extents.x() / (numCols - 1);
154 const float colOffs = -m_extents.x() / 2;
155 for (int x = 0; x < numCols; x++) {
156 for (int y = 0; y < numRows; y++) {
157 float f = heightMap.pixelColor(x, y).valueF() - 0.5;
158 HeightFieldVertex vertex;
159 vertex.position = QVector3D(x * colF + colOffs, f * m_extents.y(), y * rowF + rowOffs);
160 vertex.normal = QVector3D(0, 0, 0);
161 vertex.uv = QVector2D(float(x) / (numCols - 1), 1.f - float(y) / (numRows - 1));
162 vertices.push_back(t: vertex);
163 }
164 }
165
166 QVector<quint32> indices;
167 for (int ix = 0; ix < numCols - 1; ++ix) {
168 for (int iy = 0; iy < numRows - 1; ++iy) {
169 const int idx = iy + ix * numRows;
170
171 const auto tri0 = std::array<int, 3> { idx + numRows + 1, idx + numRows, idx };
172 const auto tri1 = std::array<int, 3> { idx + 1, idx + numRows + 1, idx };
173
174 for (const auto [i0, i1, i2] : { tri0, tri1 }) {
175 indices.push_back(t: i0);
176 indices.push_back(t: i1);
177 indices.push_back(t: i2);
178
179 if (m_smoothShading) {
180 // Calculate face normal
181 const QVector3D e0 = vertices[i1].position - vertices[i0].position;
182 const QVector3D e1 = vertices[i2].position - vertices[i0].position;
183 QVector3D normal = QVector3D::crossProduct(v1: e0, v2: e1).normalized();
184
185 // Add normal to vertex, will normalize later
186 vertices[i0].normal += normal;
187 vertices[i1].normal += normal;
188 vertices[i2].normal += normal;
189 }
190 }
191 }
192 }
193
194 if (m_smoothShading) {
195 // Normalize
196 for (auto &vertex : vertices)
197 vertex.normal.normalize();
198 }
199
200 // Calculate bounds
201 QVector3D boundsMin = vertices[0].position;
202 QVector3D boundsMax = vertices[0].position;
203
204 for (const auto &vertex : vertices) {
205 const auto &p = vertex.position;
206 boundsMin = QVector3D(qMin(a: boundsMin.x(), b: p.x()), qMin(a: boundsMin.y(), b: p.y()), qMin(a: boundsMin.z(), b: p.z()));
207 boundsMax = QVector3D(qMax(a: boundsMax.x(), b: p.x()), qMax(a: boundsMax.y(), b: p.y()), qMax(a: boundsMax.z(), b: p.z()));
208 }
209
210 addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic, offset: 0, componentType: QQuick3DGeometry::Attribute::F32Type);
211 addAttribute(semantic: QQuick3DGeometry::Attribute::TexCoord0Semantic, offset: sizeof(QVector3D) * 2, componentType: QQuick3DGeometry::Attribute::F32Type);
212
213 if (m_smoothShading)
214 addAttribute(semantic: QQuick3DGeometry::Attribute::NormalSemantic, offset: sizeof(QVector3D), componentType: QQuick3DGeometry::Attribute::F32Type);
215
216 addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic, offset: 0, componentType: QQuick3DGeometry::Attribute::ComponentType::U32Type);
217
218 setStride(sizeof(HeightFieldVertex));
219 QByteArray vertexBuffer(reinterpret_cast<char *>(vertices.data()), vertices.size() * sizeof(HeightFieldVertex));
220 setVertexData(vertexBuffer);
221 setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
222 setBounds(min: boundsMin, max: boundsMax);
223
224 QByteArray indexBuffer(reinterpret_cast<char *>(indices.data()), indices.size() * sizeof(quint32));
225 setIndexData(indexBuffer);
226}
227

source code of qtquick3d/src/helpers/heightfieldgeometry.cpp