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