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

source code of qtdatavis3d/src/datavisualization/data/qcustom3ditem.cpp