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 if (m_asynchronous) {
224 m_geometryDataFuture = QtConcurrent::run(generatePlaneGeometryAsync,
225 m_width,
226 m_height,
227 m_meshResolution,
228 m_plane,
229 m_reversed,
230 m_mirrored);
231 m_geometryDataWatcher.setFuture(m_geometryDataFuture);
232 m_status = Status::Loading;
233 Q_EMIT statusChanged();
234 } else {
235#else
236 {
237
238#endif // QT_CONFIG(concurrent)
239 updateGeometry(generatePlaneGeometry(m_width, m_height, m_meshResolution, m_plane, m_reversed, m_mirrored));
240 }
241}
242
243void PlaneGeometry::requestFinished()
244{
245#if QT_CONFIG(concurrent)
246 const auto output = m_geometryDataFuture.takeResult();
247 updateGeometry(output);
248#endif
249}
250
251void PlaneGeometry::scheduleGeometryUpdate()
252{
253 if (!m_geometryUpdateRequested) {
254 QMetaObject::invokeMethod(this, "doUpdateGeometry", Qt::QueuedConnection);
255 m_geometryUpdateRequested = true;
256 }
257}
258
259void PlaneGeometry::updateGeometry(const GeometryData &geometryData)
260{
261 setStride(sizeof(float) * 8); // 3 for position, 2 for uv0, 3 for normal
262 setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
263 addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
264 0,
265 QQuick3DGeometry::Attribute::F32Type);
266 addAttribute(QQuick3DGeometry::Attribute::TexCoord0Semantic,
267 3 * sizeof(float),
268 QQuick3DGeometry::Attribute::F32Type);
269 addAttribute(QQuick3DGeometry::Attribute::NormalSemantic,
270 5 * sizeof(float),
271 QQuick3DGeometry::Attribute::F32Type);
272 addAttribute(QQuick3DGeometry::Attribute::IndexSemantic,
273 0,
274 QQuick3DGeometry::Attribute::U16Type);
275
276 setBounds(geometryData.boundsMin, geometryData.boundsMax);
277 setVertexData(geometryData.vertexData);
278 setIndexData(geometryData.indexData);
279
280 // If the geometry update was requested while the geometry was being generated asynchronously,
281 // we need to schedule another geometry update now that the geometry is ready.
282 if (m_pendingAsyncUpdate) {
283 m_pendingAsyncUpdate = false;
284 scheduleGeometryUpdate();
285 } else {
286 m_status = Status::Ready;
287 Q_EMIT statusChanged();
288 }
289 update();
290 emit geometryChanged();
291}
292
293PlaneGeometry::GeometryData PlaneGeometry::generatePlaneGeometry(float width, float height, QSize meshResolution, Plane plane, bool reversed, bool mirrored)
294{
295 GeometryData geometryData;
296
297 int quadsX = meshResolution.width();
298 int quadsY = meshResolution.height();
299
300 const int numVertices = (quadsX + 1) * (quadsY + 1);
301 const int numIndices = quadsX * quadsY * 6; // Two triangles per quad
302
303 const int vertexStride = sizeof(float) * (3 + 2 + 3); // vec3 (position), vec2 (uv), vec3 (normal)
304 const int indexStride = sizeof(uint16_t); // 16-bit index
305
306 geometryData.vertexData.resize(numVertices * vertexStride);
307 geometryData.indexData.resize(numIndices * indexStride);
308
309 QVector3D boundsMin(std::numeric_limits<float>::max(),
310 std::numeric_limits<float>::max(),
311 std::numeric_limits<float>::max());
312
313 QVector3D boundsMax(std::numeric_limits<float>::lowest(),
314 std::numeric_limits<float>::lowest(),
315 std::numeric_limits<float>::lowest());
316
317 float* vertexPtr = reinterpret_cast<float*>(geometryData.vertexData.data());
318 uint16_t* indexPtr = reinterpret_cast<uint16_t*>(geometryData.indexData.data());
319
320 QVector3D normal;
321 switch (plane) {
322 case Plane::XY:
323 normal = QVector3D(0, 0, 1);
324 break;
325 case Plane::XZ:
326 normal = QVector3D(0, 1, 0);
327 break;
328 case Plane::ZY:
329 normal = QVector3D(1, 0, 0);
330 break;
331 }
332
333 // Flip normal if the plane is reversed
334 if (reversed)
335 normal = -normal;
336
337 for (int y = 0; y <= quadsY; ++y) {
338 for (int x = 0; x <= quadsX; ++x) {
339 // Normalized UV coordinates
340 float u = static_cast<float>(x) / quadsX;
341 float v = static_cast<float>(y) / quadsY;
342
343 // Position in local space based on plane orientation
344 float posX = width * (u - 0.5f);
345 float posY = height * (v - 0.5f);
346
347 if (mirrored)
348 v = 1.0f - v;
349
350 if (reversed)
351 u = 1.0f - u;
352
353
354 QVector3D position;
355 switch (plane) {
356 case Plane::XY:
357 position = QVector3D(posX, posY, 0.0f);
358 break;
359 case Plane::XZ:
360 position = QVector3D(posX, 0.0f, -posY);
361 break;
362 case Plane::ZY:
363 position = QVector3D(0.0f, posY, -posX);
364 break;
365 }
366
367 // Write position
368 *vertexPtr++ = position.x();
369 *vertexPtr++ = position.y();
370 *vertexPtr++ = position.z();
371
372 // Write UV coordinates
373 *vertexPtr++ = u;
374 *vertexPtr++ = v;
375
376 // Write normal
377 *vertexPtr++ = normal.x();
378 *vertexPtr++ = normal.y();
379 *vertexPtr++ = normal.z();
380
381 // Update bounds
382 boundsMin.setX(std::min(boundsMin.x(), position.x()));
383 boundsMin.setY(std::min(boundsMin.y(), position.y()));
384 boundsMin.setZ(std::min(boundsMin.z(), position.z()));
385
386 boundsMax.setX(std::max(boundsMax.x(), position.x()));
387 boundsMax.setY(std::max(boundsMax.y(), position.y()));
388 boundsMax.setZ(std::max(boundsMax.z(), position.z()));
389 }
390 }
391
392 // Generate indices
393 for (int y = 0; y < quadsY; ++y) {
394 for (int x = 0; x < quadsX; ++x) {
395 uint16_t a = static_cast<uint16_t>(y * (quadsX + 1) + x);
396 uint16_t b = static_cast<uint16_t>(a + quadsX + 1);
397 uint16_t c = static_cast<uint16_t>(b + 1);
398 uint16_t d = static_cast<uint16_t>(a + 1);
399
400 if (reversed) {
401 // Reverse the triangle winding order
402 *indexPtr++ = a;
403 *indexPtr++ = b;
404 *indexPtr++ = d;
405
406 *indexPtr++ = b;
407 *indexPtr++ = c;
408 *indexPtr++ = d;
409 } else {
410 // Normal winding order
411 *indexPtr++ = a;
412 *indexPtr++ = d;
413 *indexPtr++ = b;
414
415 *indexPtr++ = b;
416 *indexPtr++ = d;
417 *indexPtr++ = c;
418 }
419 }
420 }
421
422 // Return the geometry data
423 geometryData.boundsMax = boundsMax;
424 geometryData.boundsMin = boundsMin;
425
426 return geometryData;
427}
428
429#if QT_CONFIG(concurrent)
430void PlaneGeometry::generatePlaneGeometryAsync(QPromise<PlaneGeometry::GeometryData> &promise,
431 float width,
432 float height,
433 QSize meshResolution,
434 Plane plane,
435 bool reversed,
436 bool mirrored)
437{
438 auto output = generatePlaneGeometry(width, height, meshResolution, plane, reversed, mirrored);
439 promise.addResult(output);
440}
441#endif
442
443QT_END_NAMESPACE