1 | // Copyright (C) 2023 The Qt Company Ltd. |
---|---|
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include <QtCore/qfileinfo.h> |
5 | #include "qcustom3ditem_p.h" |
6 | |
7 | QT_BEGIN_NAMESPACE |
8 | |
9 | /*! |
10 | * \class QCustom3DItem |
11 | * \inmodule QtGraphs |
12 | * \ingroup graphs_3D |
13 | * \brief The QCustom3DItem class adds a custom item to a graph. |
14 | * |
15 | * A custom item has a custom mesh, position, scaling, rotation, and an optional |
16 | * texture. |
17 | * |
18 | * \sa Q3DGraphsWidgetItem::addCustomItem() |
19 | */ |
20 | |
21 | /*! |
22 | * \qmltype Custom3DItem |
23 | * \inqmlmodule QtGraphs |
24 | * \ingroup graphs_qml_3D |
25 | * \nativetype QCustom3DItem |
26 | * \brief Adds a custom item to a graph. |
27 | * |
28 | * A custom item has a custom mesh, position, scaling, rotation, and an optional |
29 | * texture. |
30 | */ |
31 | |
32 | /*! \qmlproperty string Custom3DItem::meshFile |
33 | * |
34 | * The item mesh file name. The item in the file must be mesh format. |
35 | * The mesh files are recommended to include vertices, normals, and UVs. |
36 | */ |
37 | |
38 | /*! \qmlproperty string Custom3DItem::textureFile |
39 | * |
40 | * The texture file name for the item. If left unset, a solid gray texture will |
41 | * be used. |
42 | * |
43 | * \note To conserve memory, the QImage loaded from the file is cleared after a |
44 | * texture is created. |
45 | */ |
46 | |
47 | /*! \qmlproperty vector3d Custom3DItem::position |
48 | * |
49 | * The item position as a \l [QtQuick] vector3d. Defaults to \c {vector3d(0.0, |
50 | * 0.0, 0.0)}. |
51 | * |
52 | * Item position is specified either in data coordinates or in absolute |
53 | * coordinates, depending on the value of the positionAbsolute property. When |
54 | * using absolute coordinates, values between \c{-1.0...1.0} are |
55 | * within axis ranges. |
56 | * |
57 | * \note Items positioned outside any axis range are not rendered if |
58 | * positionAbsolute is \c{false}, unless the item is a Custom3DVolume that would |
59 | * be partially visible and scalingAbsolute is also \c{false}. In that case, the |
60 | * visible portion of the volume will be rendered. |
61 | * |
62 | * \sa positionAbsolute, scalingAbsolute |
63 | */ |
64 | |
65 | /*! \qmlproperty bool Custom3DItem::positionAbsolute |
66 | * |
67 | * Defines whether item position is to be handled in data coordinates or in |
68 | * absolute coordinates. Defaults to \c{false}. Items with absolute coordinates |
69 | * will always be rendered, whereas items with data coordinates are only |
70 | * rendered if they are within axis ranges. |
71 | * |
72 | * \sa position |
73 | */ |
74 | |
75 | /*! \qmlproperty vector3d Custom3DItem::scaling |
76 | * |
77 | * The item scaling as a \l [QtQuick] vector3d type. Defaults to |
78 | * \c {vector3d(0.1, 0.1, 0.1)}. |
79 | * |
80 | * Item scaling is specified either in data values or in absolute values, |
81 | * depending on the value of the scalingAbsolute property. The default vector |
82 | * interpreted as absolute values sets the item to |
83 | * 10% of the height of the graph, provided the item mesh is normalized and the |
84 | * graph aspect ratios have not been changed from the defaults. |
85 | * |
86 | * \sa scalingAbsolute |
87 | */ |
88 | |
89 | /*! \qmlproperty bool Custom3DItem::scalingAbsolute |
90 | * |
91 | * Defines whether item scaling is to be handled in data values or in absolute |
92 | * values. Defaults to \c{true}. Items with absolute scaling will be rendered at |
93 | * the same size, regardless of axis ranges. Items with data scaling will change |
94 | * their apparent size according to the axis ranges. If positionAbsolute is |
95 | * \c{true}, this property is ignored and scaling is interpreted as an absolute |
96 | * value. If the item has rotation, the data scaling is calculated on the |
97 | * unrotated item. Similarly, for Custom3DVolume items, the range clipping is |
98 | * calculated on the unrotated item. |
99 | * |
100 | * \note Only absolute scaling is supported for Custom3DLabel items or for |
101 | * custom items used in \l{GraphsItem3D::polar}{polar} graphs. |
102 | * |
103 | * \note The custom item's mesh must be normalized to the range \c{[-1 ,1]}, or |
104 | * the data scaling will not be accurate. |
105 | * |
106 | * \sa scaling, positionAbsolute |
107 | */ |
108 | |
109 | /*! \qmlproperty quaternion Custom3DItem::rotation |
110 | * |
111 | * The item rotation as a \l [QtQuick] quaternion. Defaults to |
112 | * \c {quaternion(0.0, 0.0, 0.0, 0.0)}. |
113 | */ |
114 | |
115 | /*! \qmlproperty bool Custom3DItem::visible |
116 | * |
117 | * The visibility of the item. Defaults to \c{true}. |
118 | */ |
119 | |
120 | /*! \qmlproperty bool Custom3DItem::shadowCasting |
121 | * |
122 | * Defines whether shadow casting for the item is enabled. Defaults to \c{true}. |
123 | * If \c{false}, the item does not cast shadows regardless of |
124 | * \l{QtGraphs3D::ShadowQuality}{ShadowQuality}. |
125 | */ |
126 | |
127 | /*! |
128 | * \qmlmethod void Custom3DItem::setRotationAxisAndAngle(vector3d axis, real |
129 | * angle) |
130 | * |
131 | * A convenience function to construct the rotation quaternion from \a axis and |
132 | * \a angle. |
133 | * |
134 | * \sa rotation |
135 | */ |
136 | |
137 | /*! |
138 | \qmlsignal Custom3DItem::meshFileChanged(string meshFile) |
139 | |
140 | This signal is emitted when meshFile changes to \a meshFile. |
141 | */ |
142 | /*! |
143 | \qmlsignal Custom3DItem::textureFileChanged(string textureFile) |
144 | |
145 | This signal is emitted when textureFile changes to \a textureFile. |
146 | */ |
147 | /*! |
148 | \qmlsignal Custom3DItem::positionChanged(vector3d position) |
149 | |
150 | This signal is emitted when item \l position changes to \a position. |
151 | */ |
152 | /*! |
153 | \qmlsignal Custom3DItem::positionAbsoluteChanged(bool positionAbsolute) |
154 | |
155 | This signal is emitted when positionAbsolute changes to \a positionAbsolute. |
156 | */ |
157 | /*! |
158 | \qmlsignal Custom3DItem::scalingChanged(vector3d scaling) |
159 | |
160 | This signal is emitted when \l scaling changes to \a scaling. |
161 | */ |
162 | /*! |
163 | \qmlsignal Custom3DItem::rotationChanged(quaternion rotation) |
164 | |
165 | This signal is emitted when \l rotation changes to \a rotation. |
166 | */ |
167 | /*! |
168 | \qmlsignal Custom3DItem::visibleChanged(bool visible) |
169 | |
170 | This signal is emitted when \l visible changes to \a visible. |
171 | */ |
172 | /*! |
173 | \qmlsignal Custom3DItem::shadowCastingChanged(bool shadowCasting) |
174 | |
175 | This signal is emitted when shadowCasting changes to \a shadowCasting. |
176 | */ |
177 | /*! |
178 | \qmlsignal Custom3DItem::scalingAbsoluteChanged(bool scalingAbsolute) |
179 | |
180 | This signal is emitted when scalingAbsolute changes to \a scalingAbsolute. |
181 | */ |
182 | |
183 | /*! |
184 | * Constructs a custom 3D item with the specified \a parent. |
185 | */ |
186 | QCustom3DItem::QCustom3DItem(QObject *parent) |
187 | : QObject(*(new QCustom3DItemPrivate()), parent) |
188 | { |
189 | setTextureImage(QImage()); |
190 | } |
191 | |
192 | /*! |
193 | * \internal |
194 | */ |
195 | QCustom3DItem::QCustom3DItem(QCustom3DItemPrivate &d, QObject *parent) |
196 | : QObject(d, parent) |
197 | { |
198 | setTextureImage(QImage()); |
199 | } |
200 | |
201 | /*! |
202 | * Constructs a custom 3D item with the specified \a meshFile, \a position, \a |
203 | * scaling, \a rotation, \a texture image, and optional \a parent. |
204 | */ |
205 | QCustom3DItem::QCustom3DItem(const QString &meshFile, |
206 | QVector3D position, |
207 | QVector3D scaling, |
208 | const QQuaternion &rotation, |
209 | const QImage &texture, |
210 | QObject *parent) |
211 | : QObject(*(new QCustom3DItemPrivate(meshFile, position, scaling, rotation)), parent) |
212 | { |
213 | setTextureImage(texture); |
214 | } |
215 | |
216 | /*! |
217 | * Deletes the custom 3D item. |
218 | */ |
219 | QCustom3DItem::~QCustom3DItem() {} |
220 | |
221 | /*! \property QCustom3DItem::meshFile |
222 | * |
223 | * \brief The item mesh file name. |
224 | * |
225 | * The item in the file must be in mesh format. The other types |
226 | * can be converted by \l {Balsam Asset Import Tool}{Balsam} |
227 | * asset import tool. The mesh files are recommended to include |
228 | * vertices, normals, and UVs. |
229 | */ |
230 | void QCustom3DItem::setMeshFile(const QString &meshFile) |
231 | { |
232 | Q_D(QCustom3DItem); |
233 | QFileInfo validfile(meshFile); |
234 | if (!validfile.exists() || !validfile.isFile()) { |
235 | qWarning(msg: "Mesh file %ls does not exist.", qUtf16Printable(meshFile)); |
236 | return; |
237 | } |
238 | if (d->m_meshFile != meshFile) { |
239 | d->m_meshFile = meshFile; |
240 | d->m_dirtyBits.meshDirty = true; |
241 | emit meshFileChanged(meshFile); |
242 | emit needUpdate(); |
243 | } |
244 | } |
245 | |
246 | QString QCustom3DItem::meshFile() const |
247 | { |
248 | Q_D(const QCustom3DItem); |
249 | return d->m_meshFile; |
250 | } |
251 | |
252 | /*! \property QCustom3DItem::position |
253 | * |
254 | * \brief The item position as a QVector3D. |
255 | * |
256 | * Defaults to \c {QVector3D(0.0, 0.0, 0.0)}. |
257 | * |
258 | * Item position is specified either in data coordinates or in absolute |
259 | * coordinates, depending on the |
260 | * positionAbsolute property. When using absolute coordinates, values between |
261 | * \c{-1.0...1.0} are within axis ranges. |
262 | * |
263 | * \note Items positioned outside any axis range are not rendered if |
264 | * positionAbsolute is \c{false}, unless the item is a QCustom3DVolume that |
265 | * would be partially visible and scalingAbsolute is also \c{false}. In that |
266 | * case, the visible portion of the volume will be rendered. |
267 | * |
268 | * \sa positionAbsolute |
269 | */ |
270 | void QCustom3DItem::setPosition(QVector3D position) |
271 | { |
272 | Q_D(QCustom3DItem); |
273 | if (d->m_position != position) { |
274 | d->m_position = position; |
275 | d->m_dirtyBits.positionDirty = true; |
276 | emit positionChanged(position); |
277 | emit needUpdate(); |
278 | } |
279 | } |
280 | |
281 | QVector3D QCustom3DItem::position() const |
282 | { |
283 | Q_D(const QCustom3DItem); |
284 | return d->m_position; |
285 | } |
286 | |
287 | /*! \property QCustom3DItem::positionAbsolute |
288 | * |
289 | * \brief Whether item position is to be handled in data coordinates or in |
290 | * absolute coordinates. |
291 | * |
292 | * Defaults to \c{false}. Items with absolute coordinates will always be |
293 | * rendered, whereas items with data coordinates are only rendered if they are |
294 | * within axis ranges. |
295 | * |
296 | * \sa position |
297 | */ |
298 | void QCustom3DItem::setPositionAbsolute(bool positionAbsolute) |
299 | { |
300 | Q_D(QCustom3DItem); |
301 | if (d->m_positionAbsolute != positionAbsolute) { |
302 | d->m_positionAbsolute = positionAbsolute; |
303 | d->m_dirtyBits.positionDirty = true; |
304 | emit positionAbsoluteChanged(positionAbsolute); |
305 | emit needUpdate(); |
306 | } |
307 | } |
308 | |
309 | bool QCustom3DItem::isPositionAbsolute() const |
310 | { |
311 | Q_D(const QCustom3DItem); |
312 | return d->m_positionAbsolute; |
313 | } |
314 | |
315 | /*! \property QCustom3DItem::scaling |
316 | * |
317 | * \brief The item scaling as a QVector3D. |
318 | * |
319 | * Defaults to \c {QVector3D(0.1, 0.1, 0.1)}. |
320 | * |
321 | * Item scaling is either in data values or in absolute values, depending on the |
322 | * scalingAbsolute property. The default vector interpreted as absolute values |
323 | * sets the item to 10% of the height of the graph, provided the item mesh is |
324 | * normalized and the graph aspect ratios have not been changed from the |
325 | * defaults. |
326 | * |
327 | * \sa scalingAbsolute |
328 | */ |
329 | void QCustom3DItem::setScaling(QVector3D scaling) |
330 | { |
331 | Q_D(QCustom3DItem); |
332 | if (d->m_scaling != scaling) { |
333 | d->m_scaling = scaling; |
334 | d->m_dirtyBits.scalingDirty = true; |
335 | emit scalingChanged(scaling); |
336 | emit needUpdate(); |
337 | } |
338 | } |
339 | |
340 | QVector3D QCustom3DItem::scaling() const |
341 | { |
342 | Q_D(const QCustom3DItem); |
343 | return d->m_scaling; |
344 | } |
345 | |
346 | /*! \property QCustom3DItem::scalingAbsolute |
347 | * |
348 | * \brief Whether item scaling is to be handled in data values or in absolute |
349 | * values. |
350 | * |
351 | * Defaults to \c{true}. |
352 | * |
353 | * Items with absolute scaling will be rendered at the same |
354 | * size, regardless of axis ranges. Items with data scaling will change their |
355 | * apparent size according to the axis ranges. If positionAbsolute is \c{true}, |
356 | * this property is ignored and scaling is interpreted as an absolute value. If |
357 | * the item has rotation, the data scaling is calculated on the unrotated item. |
358 | * Similarly, for QCustom3DVolume items, the range clipping is calculated on the |
359 | * unrotated item. |
360 | * |
361 | * \note Only absolute scaling is supported for QCustom3DLabel items or for |
362 | * custom items used in \l{Q3DGraphsWidgetItem::polar}{polar} graphs. |
363 | * |
364 | * \note The custom item's mesh must be normalized to the range \c{[-1 ,1]}, or |
365 | * the data scaling will not be accurate. |
366 | * |
367 | * \sa scaling, positionAbsolute |
368 | */ |
369 | void QCustom3DItem::setScalingAbsolute(bool scalingAbsolute) |
370 | { |
371 | Q_D(QCustom3DItem); |
372 | if (d->m_isLabelItem && !scalingAbsolute) { |
373 | qWarning(msg: "%ls Data bounds are not supported for label items.", |
374 | qUtf16Printable(QString::fromUtf8(__func__))); |
375 | } else if (d->m_scalingAbsolute != scalingAbsolute) { |
376 | d->m_scalingAbsolute = scalingAbsolute; |
377 | d->m_dirtyBits.scalingDirty = true; |
378 | emit scalingAbsoluteChanged(scalingAbsolute); |
379 | emit needUpdate(); |
380 | } |
381 | } |
382 | |
383 | bool QCustom3DItem::isScalingAbsolute() const |
384 | { |
385 | Q_D(const QCustom3DItem); |
386 | return d->m_scalingAbsolute; |
387 | } |
388 | |
389 | /*! \property QCustom3DItem::rotation |
390 | * |
391 | * \brief The item rotation as a QQuaternion. |
392 | * |
393 | * Defaults to \c {QQuaternion(0.0, 0.0, 0.0, 0.0)}. |
394 | */ |
395 | void QCustom3DItem::setRotation(const QQuaternion &rotation) |
396 | { |
397 | Q_D(QCustom3DItem); |
398 | if (d->m_rotation != rotation) { |
399 | d->m_rotation = rotation; |
400 | d->m_dirtyBits.rotationDirty = true; |
401 | emit rotationChanged(rotation); |
402 | emit needUpdate(); |
403 | } |
404 | } |
405 | |
406 | QQuaternion QCustom3DItem::rotation() |
407 | { |
408 | Q_D(const QCustom3DItem); |
409 | return d->m_rotation; |
410 | } |
411 | |
412 | /*! \property QCustom3DItem::visible |
413 | * |
414 | * \brief The visibility of the item. |
415 | * |
416 | * Defaults to \c{true}. |
417 | */ |
418 | void QCustom3DItem::setVisible(bool visible) |
419 | { |
420 | Q_D(QCustom3DItem); |
421 | if (d->m_visible != visible) { |
422 | d->m_visible = visible; |
423 | d->m_dirtyBits.visibleDirty = true; |
424 | emit visibleChanged(visible); |
425 | emit needUpdate(); |
426 | } |
427 | } |
428 | |
429 | bool QCustom3DItem::isVisible() const |
430 | { |
431 | Q_D(const QCustom3DItem); |
432 | return d->m_visible; |
433 | } |
434 | |
435 | /*! \property QCustom3DItem::shadowCasting |
436 | * |
437 | * \brief Whether shadow casting for the item is enabled. |
438 | * |
439 | * Defaults to \c{true}. |
440 | * If \c{false}, the item does not cast shadows regardless of |
441 | * Q3DGraphsWidgetItem::ShadowQuality. |
442 | */ |
443 | void QCustom3DItem::setShadowCasting(bool enabled) |
444 | { |
445 | Q_D(QCustom3DItem); |
446 | if (d->m_shadowCasting != enabled) { |
447 | d->m_shadowCasting = enabled; |
448 | d->m_dirtyBits.shadowCastingDirty = true; |
449 | emit shadowCastingChanged(shadowCasting: enabled); |
450 | emit needUpdate(); |
451 | } |
452 | } |
453 | |
454 | bool QCustom3DItem::isShadowCasting() const |
455 | { |
456 | Q_D(const QCustom3DItem); |
457 | return d->m_shadowCasting; |
458 | } |
459 | |
460 | /*! |
461 | * A convenience function to construct the rotation quaternion from \a axis and |
462 | * \a angle. |
463 | * |
464 | * \sa rotation |
465 | */ |
466 | void QCustom3DItem::setRotationAxisAndAngle(QVector3D axis, float angle) |
467 | { |
468 | setRotation(QQuaternion::fromAxisAndAngle(axis, angle)); |
469 | } |
470 | |
471 | /*! |
472 | * Sets the value of \a textureImage as a QImage for the item. The texture |
473 | * defaults to solid gray. |
474 | * |
475 | * \note To conserve memory, the given QImage is cleared after a texture is |
476 | * created. |
477 | */ |
478 | void QCustom3DItem::setTextureImage(const QImage &textureImage) |
479 | { |
480 | Q_D(QCustom3DItem); |
481 | if (textureImage != d->m_textureImage) { |
482 | if (textureImage.isNull()) { |
483 | // Make a solid gray texture |
484 | d->m_textureImage = QImage(2, 2, QImage::Format_RGB32); |
485 | d->m_textureImage.fill(color: Qt::gray); |
486 | } else { |
487 | d->m_textureImage = textureImage; |
488 | } |
489 | |
490 | if (!d->m_textureFile.isEmpty()) { |
491 | d->m_textureFile.clear(); |
492 | emit textureFileChanged(textureFile: d->m_textureFile); |
493 | } |
494 | d->m_dirtyBits.textureDirty = true; |
495 | emit needUpdate(); |
496 | } |
497 | } |
498 | |
499 | /*! \property QCustom3DItem::textureFile |
500 | * |
501 | * \brief The texture file name for the item. |
502 | * |
503 | * If both this property and the texture image are unset, a solid |
504 | * gray texture will be used. |
505 | * |
506 | * \note To conserve memory, the QImage loaded from the file is cleared after a |
507 | * texture is created. |
508 | */ |
509 | void QCustom3DItem::setTextureFile(const QString &textureFile) |
510 | { |
511 | Q_D(QCustom3DItem); |
512 | if (d->m_textureFile != textureFile) { |
513 | d->m_textureFile = textureFile; |
514 | if (!textureFile.isEmpty()) { |
515 | d->m_textureImage = QImage(textureFile); |
516 | } else { |
517 | d->m_textureImage = QImage(2, 2, QImage::Format_RGB32); |
518 | d->m_textureImage.fill(color: Qt::gray); |
519 | } |
520 | emit textureFileChanged(textureFile); |
521 | d->m_dirtyBits.textureDirty = true; |
522 | emit needUpdate(); |
523 | } |
524 | } |
525 | |
526 | QString QCustom3DItem::textureFile() const |
527 | { |
528 | Q_D(const QCustom3DItem); |
529 | return d->m_textureFile; |
530 | } |
531 | |
532 | QCustom3DItemPrivate::QCustom3DItemPrivate() |
533 | : m_textureImage(QImage(1, 1, QImage::Format_ARGB32)) |
534 | , m_position(QVector3D(0.0f, 0.0f, 0.0f)) |
535 | , m_positionAbsolute(false) |
536 | , m_scaling(QVector3D(0.1f, 0.1f, 0.1f)) |
537 | , m_scalingAbsolute(true) |
538 | , m_rotation(QQuaternion()) |
539 | , m_visible(true) |
540 | , m_shadowCasting(true) |
541 | , m_isLabelItem(false) |
542 | , m_isVolumeItem(false) |
543 | {} |
544 | |
545 | QCustom3DItemPrivate::QCustom3DItemPrivate(const QString &meshFile, |
546 | QVector3D position, |
547 | QVector3D scaling, |
548 | const QQuaternion &rotation) |
549 | : m_textureImage(QImage(1, 1, QImage::Format_ARGB32)) |
550 | , m_meshFile(meshFile) |
551 | , m_position(position) |
552 | , m_positionAbsolute(false) |
553 | , m_scaling(scaling) |
554 | , m_scalingAbsolute(true) |
555 | , m_rotation(rotation) |
556 | , m_visible(true) |
557 | , m_shadowCasting(true) |
558 | , m_isLabelItem(false) |
559 | , m_isVolumeItem(false) |
560 | {} |
561 | |
562 | QCustom3DItemPrivate::~QCustom3DItemPrivate() {} |
563 | |
564 | QImage QCustom3DItemPrivate::textureImage() |
565 | { |
566 | return m_textureImage; |
567 | } |
568 | |
569 | void QCustom3DItemPrivate::clearTextureImage() |
570 | { |
571 | m_textureImage = QImage(); |
572 | m_textureFile.clear(); |
573 | } |
574 | |
575 | void QCustom3DItemPrivate::resetDirtyBits() |
576 | { |
577 | m_dirtyBits.textureDirty = false; |
578 | m_dirtyBits.meshDirty = false; |
579 | m_dirtyBits.positionDirty = false; |
580 | m_dirtyBits.scalingDirty = false; |
581 | m_dirtyBits.rotationDirty = false; |
582 | m_dirtyBits.visibleDirty = false; |
583 | m_dirtyBits.shadowCastingDirty = false; |
584 | } |
585 | |
586 | QT_END_NAMESPACE |
587 |
Definitions
- QCustom3DItem
- QCustom3DItem
- QCustom3DItem
- ~QCustom3DItem
- setMeshFile
- meshFile
- setPosition
- position
- setPositionAbsolute
- isPositionAbsolute
- setScaling
- scaling
- setScalingAbsolute
- isScalingAbsolute
- setRotation
- rotation
- setVisible
- isVisible
- setShadowCasting
- isShadowCasting
- setRotationAxisAndAngle
- setTextureImage
- setTextureFile
- textureFile
- QCustom3DItemPrivate
- QCustom3DItemPrivate
- ~QCustom3DItemPrivate
- textureImage
- clearTextureImage
Start learning QML with our Intro Training
Find out more