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

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