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