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