Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
heightfieldgeometry.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
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 url 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 url 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 shading 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
105namespace {
106struct HeightFieldVertex
107{
108 QVector3D position;
109 QVector3D normal;
110 QVector2D uv;
111};
112}
113
114void HeightFieldGeometry::updateData()
115{
116 const QQmlContext *context = qmlContext(this);
117
118 const auto resolvedUrl = context ? context->resolvedUrl(m_heightMapSource) : m_heightMapSource;
119 if (!resolvedUrl.isValid())
120 return;
121
122 clear();
123 const auto qmlSource = QQmlFile::urlToLocalFileOrQrc(resolvedUrl);
124
125 QImage heightMap(qmlSource);
126 int numRows = heightMap.height();
127 int numCols = heightMap.width();
128
129 if (numRows < 2 || numCols < 2)
130 return;
131
132 const int numVertices = numRows * numCols;
133
134 if (!m_extentsSetExplicitly) {
135 auto prevExt = m_extents;
136 if (numRows == numCols) {
137 m_extents = {100, 100, 100};
138 } else if (numRows < numCols) {
139 float f = float(numRows) / float(numCols);
140 m_extents = {100.f, 100.f, 100.f * f};
141 } else {
142 float f = float(numCols) / float(numRows);
143 m_extents = {100.f * f, 100.f, 100.f};
144 }
145 if (m_extents != prevExt) {
146 emit extentsChanged();
147 }
148 }
149
150 QVector<HeightFieldVertex> vertices;
151 vertices.reserve(numVertices);
152
153 const float rowF = m_extents.z() / (numRows - 1);
154 const float rowOffs = -m_extents.z() / 2;
155 const float colF = m_extents.x() / (numCols - 1);
156 const float colOffs = -m_extents.x() / 2;
157 for (int x = 0; x < numCols; x++) {
158 for (int y = 0; y < numRows; y++) {
159 float f = heightMap.pixelColor(x, y).valueF() - 0.5;
160 HeightFieldVertex vertex;
161 vertex.position = QVector3D(x * colF + colOffs, f * m_extents.y(), y * rowF + rowOffs);
162 vertex.normal = QVector3D(0, 0, 0);
163 vertex.uv = QVector2D(float(x) / (numCols - 1), 1.f - float(y) / (numRows - 1));
164 vertices.push_back(vertex);
165 }
166 }
167
168 QVector<quint32> indices;
169 for (int ix = 0; ix < numCols - 1; ++ix) {
170 for (int iy = 0; iy < numRows - 1; ++iy) {
171 const int idx = iy + ix * numRows;
172
173 const auto tri0 = std::array<int, 3> { idx + numRows + 1, idx + numRows, idx };
174 const auto tri1 = std::array<int, 3> { idx + 1, idx + numRows + 1, idx };
175
176 for (const auto [i0, i1, i2] : { tri0, tri1 }) {
177 indices.push_back(i0);
178 indices.push_back(i1);
179 indices.push_back(i2);
180
181 if (m_smoothShading) {
182 // Calculate face normal
183 const QVector3D e0 = vertices[i1].position - vertices[i0].position;
184 const QVector3D e1 = vertices[i2].position - vertices[i0].position;
185 QVector3D normal = QVector3D::crossProduct(e0, e1).normalized();
186
187 // Add normal to vertex, will normalize later
188 vertices[i0].normal += normal;
189 vertices[i1].normal += normal;
190 vertices[i2].normal += normal;
191 }
192 }
193 }
194 }
195
196 if (m_smoothShading) {
197 // Normalize
198 for (auto &vertex : vertices)
199 vertex.normal.normalize();
200 }
201
202 // Calculate bounds
203 QVector3D boundsMin = vertices[0].position;
204 QVector3D boundsMax = vertices[0].position;
205
206 for (const auto &vertex : vertices) {
207 const auto &p = vertex.position;
208 boundsMin = QVector3D(qMin(boundsMin.x(), p.x()), qMin(boundsMin.y(), p.y()), qMin(boundsMin.z(), p.z()));
209 boundsMax = QVector3D(qMax(boundsMax.x(), p.x()), qMax(boundsMax.y(), p.y()), qMax(boundsMax.z(), p.z()));
210 }
211
212 addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0, QQuick3DGeometry::Attribute::F32Type);
213 addAttribute(QQuick3DGeometry::Attribute::TexCoord0Semantic, sizeof(QVector3D) * 2, QQuick3DGeometry::Attribute::F32Type);
214
215 if (m_smoothShading)
216 addAttribute(QQuick3DGeometry::Attribute::NormalSemantic, sizeof(QVector3D), QQuick3DGeometry::Attribute::F32Type);
217
218 addAttribute(QQuick3DGeometry::Attribute::IndexSemantic, 0, QQuick3DGeometry::Attribute::ComponentType::U32Type);
219
220 setStride(sizeof(HeightFieldVertex));
221 QByteArray vertexBuffer(reinterpret_cast<char *>(vertices.data()), vertices.size() * sizeof(HeightFieldVertex));
222 setVertexData(vertexBuffer);
223 setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
224 setBounds(boundsMin, boundsMax);
225
226 QByteArray indexBuffer(reinterpret_cast<char *>(indices.data()), indices.size() * sizeof(quint32));
227 setIndexData(indexBuffer);
228}