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
planegeometry.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
5#include <limits>
6
7#if QT_CONFIG(concurrent)
8#include <QtConcurrentRun>
9#endif
10
11QT_BEGIN_NAMESPACE
12
13/*!
14 \qmltype PlaneGeometry
15 \inqmlmodule QtQuick3D.Helpers
16 \inherits Geometry
17 \since 6.9
18 \brief Provides geometry for a plane.
19
20 PlaneGeometry is a geometry type that represents a plane. The plane's size is
21 defined by its height and width properties. The topology of the plane is defined by
22 the meshResolution property. The orientation of the plane is defined by the plane property.
23*/
24
25/*!
26 \qmlproperty real PlaneGeometry::width
27 The width of the plane. The default value is 100.0.
28*/
29
30/*!
31 \qmlproperty real PlaneGeometry::height
32 The height of the plane. The default value is 100.0.
33*/
34
35
36/*!
37 \qmlproperty size PlaneGeometry::meshResolution
38 The resolution of the plane. The default value is QSize(2, 2).
39*/
40
41/*!
42 \qmlproperty PlaneGeometry::Plane PlaneGeometry::plane
43 The orientation of the plane. The default value is PlaneGeometry.XY.
44 All geometry will be created along the selected plane, and the front
45 face and normal will point towards the remaining positive axis, unless
46 reversed is true.
47
48 \value PlaneGeometry.XY The plane is oriented along the XY plane.
49 \value PlaneGeometry.XZ The plane is oriented along the XZ plane.
50 \value PlaneGeometry.ZY The plane is oriented along the ZY plane.
51*/
52
53/*!
54 \qmlproperty bool PlaneGeometry::reversed
55 This property holds whether the plane is flipped. This changes both the normal as
56 well as the winding order of the plane. The default value is false, which means that
57 when a Plane is created with the XY orientation, the normal will point in the positive
58 Z direction and the winding order will be counter-clockwise. When reversed is true,
59 the normal will point in the negative Z direction and the winding order will be clockwise.
60*/
61
62/*!
63 \qmlproperty bool PlaneGeometry::mirrored
64 This property holds whether the UV coordinates of the plane are flipped vertically.
65*/
66
67/*!
68 \qmlproperty bool PlaneGeometry::asynchronous
69
70 This property holds whether the geometry generation should be asynchronous.
71*/
72
73/*!
74 \qmlproperty bool PlaneGeometry::status
75 \readonly
76
77 This property holds the status of the geometry generation when asynchronous is true.
78
79 \value PlaneGeometry.Null The geometry generation has not started
80 \value PlaneGeometry.Ready The geometry generation is complete.
81 \value PlaneGeometry.Loading The geometry generation is in progress.
82 \value PlaneGeometry.Error The geometry generation failed.
83*/
84
85PlaneGeometry::PlaneGeometry(QQuick3DObject *parent)
86 : QQuick3DGeometry(parent)
87{
88#if QT_CONFIG(concurrent)
89 connect(&m_geometryDataWatcher, &QFutureWatcher<GeometryData>::finished, this, &PlaneGeometry::requestFinished);
90#endif
91 scheduleGeometryUpdate();
92}
93
94PlaneGeometry::~PlaneGeometry()
95{
96
97}
98
99float PlaneGeometry::width() const
100{
101 return m_width;
102}
103
104void PlaneGeometry::setWidth(float newWidth)
105{
106 if (qFuzzyCompare(m_width, newWidth))
107 return;
108 m_width = newWidth;
109 emit widthChanged();
110 scheduleGeometryUpdate();
111}
112
113float PlaneGeometry::height() const
114{
115 return m_height;
116}
117
118void PlaneGeometry::setHeight(float newHeight)
119{
120 if (qFuzzyCompare(m_height, newHeight))
121 return;
122 m_height = newHeight;
123 emit heightChanged();
124 scheduleGeometryUpdate();
125}
126
127QSize PlaneGeometry::meshResolution() const
128{
129 return m_meshResolution;
130}
131
132void PlaneGeometry::setMeshResolution(const QSize &newMeshResolution)
133{
134 if (m_meshResolution == newMeshResolution)
135 return;
136 m_meshResolution = newMeshResolution;
137 emit meshResolutionChanged();
138 scheduleGeometryUpdate();
139}
140
141PlaneGeometry::Plane PlaneGeometry::plane() const
142{
143 return m_plane;
144}
145
146void PlaneGeometry::setPlane(Plane newPlane)
147{
148 if (m_plane == newPlane)
149 return;
150 m_plane = newPlane;
151 emit planeChanged();
152 scheduleGeometryUpdate();
153}
154
155bool PlaneGeometry::reversed() const
156{
157 return m_reversed;
158}
159
160void PlaneGeometry::setReversed(bool newReversed)
161{
162 if (m_reversed == newReversed)
163 return;
164 m_reversed = newReversed;
165 emit reversedChanged();
166 scheduleGeometryUpdate();
167}
168
169bool PlaneGeometry::mirrored() const
170{
171 return m_mirrored;
172}
173
174void PlaneGeometry::setMirrored(bool newMirrored)
175{
176 if (m_mirrored == newMirrored)
177 return;
178 m_mirrored = newMirrored;
179 emit mirroredChanged();
180 scheduleGeometryUpdate();
181}
182
183bool PlaneGeometry::asynchronous() const
184{
185 return m_asynchronous;
186}
187
188void PlaneGeometry::setAsynchronous(bool newAsynchronous)
189{
190 if (m_asynchronous == newAsynchronous)
191 return;
192 m_asynchronous = newAsynchronous;
193 emit asynchronousChanged();
194}
195
196PlaneGeometry::Status PlaneGeometry::status() const
197{
198 return m_status;
199}
200
201void PlaneGeometry::doUpdateGeometry()
202{
203 // reset the flag since we are processing the update
204 m_geometryUpdateRequested = false;
205
206#if QT_CONFIG(concurrent)
207 if (m_geometryDataFuture.isRunning()) {
208 m_pendingAsyncUpdate = true;
209 return;
210 }
211#endif
212
213 // Check validity of the geometry parameters
214 if (m_width <= 0 || m_height <= 0 || m_meshResolution.width() <= 0 || m_meshResolution.height() <= 0) {
215 clear();
216 update();
217 return;
218 }
219
220#if QT_CONFIG(concurrent)
221 if (m_asynchronous) {
222 m_geometryDataFuture = QtConcurrent::run(generatePlaneGeometryAsync,
223 m_width,
224 m_height,
225 m_meshResolution,
226 m_plane,
227 m_reversed,
228 m_mirrored);
229 m_geometryDataWatcher.setFuture(m_geometryDataFuture);
230 m_status = Status::Loading;
231 Q_EMIT statusChanged();
232 } else {
233#else
234 {
235
236#endif // QT_CONFIG(concurrent)
237 updateGeometry(generatePlaneGeometry(m_width, m_height, m_meshResolution, m_plane, m_reversed, m_mirrored));
238 }
239}
240
241void PlaneGeometry::requestFinished()
242{
243#if QT_CONFIG(concurrent)
244 const auto output = m_geometryDataFuture.takeResult();
245 updateGeometry(output);
246#endif
247}
248
249void PlaneGeometry::scheduleGeometryUpdate()
250{
251 if (!m_geometryUpdateRequested) {
252 QMetaObject::invokeMethod(this, "doUpdateGeometry", Qt::QueuedConnection);
253 m_geometryUpdateRequested = true;
254 }
255}
256
257void PlaneGeometry::updateGeometry(const GeometryData &geometryData)
258{
259 setStride(sizeof(float) * 8); // 3 for position, 2 for uv0, 3 for normal
260 setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
261 addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
262 0,
263 QQuick3DGeometry::Attribute::F32Type);
264 addAttribute(QQuick3DGeometry::Attribute::TexCoord0Semantic,
265 3 * sizeof(float),
266 QQuick3DGeometry::Attribute::F32Type);
267 addAttribute(QQuick3DGeometry::Attribute::NormalSemantic,
268 5 * sizeof(float),
269 QQuick3DGeometry::Attribute::F32Type);
270 addAttribute(QQuick3DGeometry::Attribute::IndexSemantic,
271 0,
272 QQuick3DGeometry::Attribute::U16Type);
273
274 setBounds(geometryData.boundsMin, geometryData.boundsMax);
275 setVertexData(geometryData.vertexData);
276 setIndexData(geometryData.indexData);
277
278 // If the geometry update was requested while the geometry was being generated asynchronously,
279 // we need to schedule another geometry update now that the geometry is ready.
280 if (m_pendingAsyncUpdate) {
281 m_pendingAsyncUpdate = false;
282 scheduleGeometryUpdate();
283 } else {
284 m_status = Status::Ready;
285 Q_EMIT statusChanged();
286 }
287 update();
288}
289
290PlaneGeometry::GeometryData PlaneGeometry::generatePlaneGeometry(float width, float height, QSize meshResolution, Plane plane, bool reversed, bool mirrored)
291{
292 GeometryData geometryData;
293
294 int quadsX = meshResolution.width();
295 int quadsY = meshResolution.height();
296
297 const int numVertices = (quadsX + 1) * (quadsY + 1);
298 const int numIndices = quadsX * quadsY * 6; // Two triangles per quad
299
300 const int vertexStride = sizeof(float) * (3 + 2 + 3); // vec3 (position), vec2 (uv), vec3 (normal)
301 const int indexStride = sizeof(uint16_t); // 16-bit index
302
303 geometryData.vertexData.resize(numVertices * vertexStride);
304 geometryData.indexData.resize(numIndices * indexStride);
305
306 QVector3D boundsMin(std::numeric_limits<float>::max(),
307 std::numeric_limits<float>::max(),
308 std::numeric_limits<float>::max());
309
310 QVector3D boundsMax(std::numeric_limits<float>::lowest(),
311 std::numeric_limits<float>::lowest(),
312 std::numeric_limits<float>::lowest());
313
314 float* vertexPtr = reinterpret_cast<float*>(geometryData.vertexData.data());
315 uint16_t* indexPtr = reinterpret_cast<uint16_t*>(geometryData.indexData.data());
316
317 QVector3D normal;
318 switch (plane) {
319 case Plane::XY:
320 normal = QVector3D(0, 0, 1);
321 break;
322 case Plane::XZ:
323 normal = QVector3D(0, 1, 0);
324 break;
325 case Plane::ZY:
326 normal = QVector3D(1, 0, 0);
327 break;
328 }
329
330 // Flip normal if the plane is reversed
331 if (reversed)
332 normal = -normal;
333
334 for (int y = 0; y <= quadsY; ++y) {
335 for (int x = 0; x <= quadsX; ++x) {
336 // Normalized UV coordinates
337 float u = static_cast<float>(x) / quadsX;
338 float v = static_cast<float>(y) / quadsY;
339
340 // Position in local space based on plane orientation
341 float posX = width * (u - 0.5f);
342 float posY = height * (v - 0.5f);
343
344 if (mirrored)
345 v = 1.0f - v;
346
347 if (reversed)
348 u = 1.0f - u;
349
350
351 QVector3D position;
352 switch (plane) {
353 case Plane::XY:
354 position = QVector3D(posX, posY, 0.0f);
355 break;
356 case Plane::XZ:
357 position = QVector3D(posX, 0.0f, -posY);
358 break;
359 case Plane::ZY:
360 position = QVector3D(0.0f, posY, -posX);
361 break;
362 }
363
364 // Write position
365 *vertexPtr++ = position.x();
366 *vertexPtr++ = position.y();
367 *vertexPtr++ = position.z();
368
369 // Write UV coordinates
370 *vertexPtr++ = u;
371 *vertexPtr++ = v;
372
373 // Write normal
374 *vertexPtr++ = normal.x();
375 *vertexPtr++ = normal.y();
376 *vertexPtr++ = normal.z();
377
378 // Update bounds
379 boundsMin.setX(std::min(boundsMin.x(), position.x()));
380 boundsMin.setY(std::min(boundsMin.y(), position.y()));
381 boundsMin.setZ(std::min(boundsMin.z(), position.z()));
382
383 boundsMax.setX(std::max(boundsMax.x(), position.x()));
384 boundsMax.setY(std::max(boundsMax.y(), position.y()));
385 boundsMax.setZ(std::max(boundsMax.z(), position.z()));
386 }
387 }
388
389 // Generate indices
390 for (int y = 0; y < quadsY; ++y) {
391 for (int x = 0; x < quadsX; ++x) {
392 uint16_t a = static_cast<uint16_t>(y * (quadsX + 1) + x);
393 uint16_t b = static_cast<uint16_t>(a + quadsX + 1);
394 uint16_t c = static_cast<uint16_t>(b + 1);
395 uint16_t d = static_cast<uint16_t>(a + 1);
396
397 if (reversed) {
398 // Reverse the triangle winding order
399 *indexPtr++ = a;
400 *indexPtr++ = b;
401 *indexPtr++ = d;
402
403 *indexPtr++ = b;
404 *indexPtr++ = c;
405 *indexPtr++ = d;
406 } else {
407 // Normal winding order
408 *indexPtr++ = a;
409 *indexPtr++ = d;
410 *indexPtr++ = b;
411
412 *indexPtr++ = b;
413 *indexPtr++ = d;
414 *indexPtr++ = c;
415 }
416 }
417 }
418
419 // Return the geometry data
420 geometryData.boundsMax = boundsMax;
421 geometryData.boundsMin = boundsMin;
422
423 return geometryData;
424}
425
426#if QT_CONFIG(concurrent)
427void PlaneGeometry::generatePlaneGeometryAsync(QPromise<PlaneGeometry::GeometryData> &promise,
428 float width,
429 float height,
430 QSize meshResolution,
431 Plane plane,
432 bool reversed,
433 bool mirrored)
434{
435 auto output = generatePlaneGeometry(width, height, meshResolution, plane, reversed, mirrored);
436 promise.addResult(output);
437}
438#endif
439
440QT_END_NAMESPACE