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
7QT_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 */
186QCustom3DItem::QCustom3DItem(QObject *parent)
187 : QObject(*(new QCustom3DItemPrivate()), parent)
188{
189 setTextureImage(QImage());
190}
191
192/*!
193 * \internal
194 */
195QCustom3DItem::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 */
205QCustom3DItem::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 */
219QCustom3DItem::~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 */
230void 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
246QString 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 */
270void 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
281QVector3D 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 */
298void 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
309bool 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 */
329void 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
340QVector3D 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 */
369void 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
383bool 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 */
395void 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
406QQuaternion 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 */
418void 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
429bool 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 */
443void 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
454bool 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 */
466void 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 */
478void 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 */
509void 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
526QString QCustom3DItem::textureFile() const
527{
528 Q_D(const QCustom3DItem);
529 return d->m_textureFile;
530}
531
532QCustom3DItemPrivate::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
545QCustom3DItemPrivate::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
562QCustom3DItemPrivate::~QCustom3DItemPrivate() {}
563
564QImage QCustom3DItemPrivate::textureImage()
565{
566 return m_textureImage;
567}
568
569void QCustom3DItemPrivate::clearTextureImage()
570{
571 m_textureImage = QImage();
572 m_textureFile.clear();
573}
574
575void 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
586QT_END_NAMESPACE
587

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtgraphs/src/graphs3d/data/qcustom3ditem.cpp