1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qcustom3dvolume_p.h"
5#include "utils_p.h"
6
7QT_BEGIN_NAMESPACE
8
9/*!
10 * \class QCustom3DVolume
11 * \inmodule QtDataVisualization
12 * \brief The QCustom3DVolume class adds a volume rendered object to a graph.
13 * \since QtDataVisualization 1.2
14 *
15 * A volume rendered
16 * object is a box with a 3D texture. Three slice planes are supported for the volume, one along
17 * each main axis of the volume.
18 *
19 * Rendering volume objects is very performance intensive, especially when the volume is largely
20 * transparent, as the contents of the volume are ray-traced. The performance scales nearly linearly
21 * with the amount of pixels that the volume occupies on the screen, so showing the volume in a
22 * smaller view or limiting the zoom level of the graph are easy ways to improve performance.
23 * Similarly, the volume texture dimensions have a large impact on performance.
24 * If the frame rate is more important than pixel-perfect rendering of the volume contents, consider
25 * turning the high definition shader off by setting the useHighDefShader property to \c{false}.
26 *
27 * \note Volumetric objects are only supported with orthographic projection.
28 *
29 * \note Volumetric objects utilize 3D textures, which are not supported in OpenGL ES2 environments.
30 *
31 * \sa QAbstract3DGraph::addCustomItem(), QAbstract3DGraph::orthoProjection, useHighDefShader
32 */
33
34/*!
35 * \qmltype Custom3DVolume
36 * \inqmlmodule QtDataVisualization
37 * \since QtDataVisualization 1.2
38 * \ingroup datavisualization_qml
39 * \instantiates QCustom3DVolume
40 * \inherits Custom3DItem
41 * \brief Adds a volume rendered object to a graph.
42 *
43 * A volume rendered
44 * object is a box with a 3D texture. Three slice planes are supported for the volume, one along
45 * each main axis of the volume.
46 *
47 * Rendering volume objects is very performance intensive, especially when the volume is largely
48 * transparent, as the contents of the volume are ray-traced. The performance scales nearly linearly
49 * with the amount of pixels that the volume occupies on the screen, so showing the volume in a
50 * smaller view or limiting the zoom level of the graph are easy ways to improve performance.
51 * Similarly, the volume texture dimensions have a large impact on performance.
52 * If the frame rate is more important than pixel-perfect rendering of the volume contents, consider
53 * turning the high definition shader off by setting the useHighDefShader property to \c{false}.
54 *
55 * \note Filling in the volume data would not typically be efficient or practical from pure QML,
56 * so properties directly related to that are not fully supported from QML.
57 * Create a hybrid QML/C++ application if you want to use volume objects with a Qt Quick UI.
58 *
59 * \note Volumetric objects are only supported with orthographic projection.
60 *
61 * \note Volumetric objects utilize 3D textures, which are not supported in OpenGL ES2 environments.
62 *
63 * \sa AbstractGraph3D::orthoProjection, useHighDefShader
64 */
65
66/*! \qmlproperty int Custom3DVolume::textureWidth
67 *
68 * The width of the 3D texture defining the volume content in pixels. Defaults to \c{0}.
69 *
70 * \note Changing this property from QML is not supported, as the texture data cannot be resized
71 * accordingly.
72 */
73
74/*! \qmlproperty int Custom3DVolume::textureHeight
75 *
76 * The height of the 3D texture defining the volume content in pixels. Defaults to \c{0}.
77 *
78 * \note Changing this property from QML is not supported, as the texture data cannot be resized
79 * accordingly.
80 */
81
82/*! \qmlproperty int Custom3DVolume::textureDepth
83 *
84 * The depth of the 3D texture defining the volume content in pixels. Defaults to \c{0}.
85 *
86 * \note Changing this property from QML is not supported, as the texture data cannot be resized
87 * accordingly.
88 */
89
90/*! \qmlproperty int Custom3DVolume::sliceIndexX
91 *
92 * The x-dimension index into the texture data indicating which vertical slice to show.
93 * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
94 * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
95 * normally.
96 * Defaults to \c{-1}.
97 *
98 * \sa QCustom3DVolume::textureData, drawSlices, drawSliceFrames
99 */
100
101/*! \qmlproperty int Custom3DVolume::sliceIndexY
102 *
103 * The y-dimension index into the texture data indicating which horizontal slice to show.
104 * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
105 * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
106 * normally.
107 * Defaults to \c{-1}.
108 *
109 * \sa QCustom3DVolume::textureData, drawSlices, drawSliceFrames
110 */
111
112/*! \qmlproperty int Custom3DVolume::sliceIndexZ
113 *
114 * The z-dimension index into the texture data indicating which vertical slice to show.
115 * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
116 * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
117 * normally.
118 * Defaults to \c{-1}.
119 *
120 * \sa QCustom3DVolume::textureData, drawSlices, drawSliceFrames
121 */
122
123/*!
124 * \qmlproperty real Custom3DVolume::alphaMultiplier
125 *
126 * The alpha value of every texel of the volume texture is multiplied with this value at
127 * the render time. This can be used to introduce uniform transparency to the volume.
128 * If preserveOpacity is \c{true}, only texels with at least some transparency to begin with are
129 * affected, and fully opaque texels are not affected.
130 * The value must not be negative.
131 * Defaults to \c{1.0}.
132 *
133 * \sa preserveOpacity
134 */
135
136/*!
137 * \qmlproperty bool Custom3DVolume::preserveOpacity
138 *
139 * If this property value is \c{true}, alphaMultiplier is only applied to texels that already have
140 * some transparency. If it is \c{false}, the multiplier is applied to the alpha value of all
141 * texels.
142 * Defaults to \c{true}.
143 *
144 * \sa alphaMultiplier
145 */
146
147/*!
148 * \qmlproperty bool Custom3DVolume::useHighDefShader
149 *
150 * If this property value is \c{true}, a high definition shader is used to render the volume.
151 * If it is \c{false}, a low definition shader is used.
152 *
153 * The high definition shader guarantees that every visible texel of the volume texture is sampled
154 * when the volume is rendered.
155 * The low definition shader renders only a rough approximation of the volume contents,
156 * but at a much higher frame rate. The low definition shader does not guarantee every texel of the
157 * volume texture is sampled, so there may be flickering if the volume contains distinct thin
158 * features.
159 *
160 * \note This value does not affect the level of detail when rendering the
161 * slices of the volume.
162 *
163 * Defaults to \c{true}.
164 */
165
166/*!
167 * \qmlproperty bool Custom3DVolume::drawSlices
168 *
169 * If this property value is \c{true}, the slices indicated by slice index properties
170 * will be drawn instead of the full volume.
171 * If it is \c{false}, the full volume will always be drawn.
172 * Defaults to \c{false}.
173 *
174 * \note The slices are always drawn along the item axes, so if the item is rotated, the slices are
175 * rotated as well.
176 *
177 * \sa sliceIndexX, sliceIndexY, sliceIndexZ
178 */
179
180/*!
181 * \qmlproperty bool Custom3DVolume::drawSliceFrames
182 *
183 * If this property value is \c{true}, the frames of slices indicated by slice index properties
184 * will be drawn around the volume.
185 * If it is \c{false}, no slice frames will be drawn.
186 * Drawing slice frames is independent of drawing slices, so you can show the full volume and
187 * still draw the slice frames around it.
188 * Defaults to \c{false}.
189 *
190 * \sa sliceIndexX, sliceIndexY, sliceIndexZ, drawSlices
191 */
192
193/*!
194 * \qmlproperty color Custom3DVolume::sliceFrameColor
195 *
196 * The color of the slice frame. Transparent slice frame color is not supported.
197 *
198 * Defaults to black.
199 *
200 * \sa drawSliceFrames
201 */
202
203/*!
204 * \qmlproperty vector3d Custom3DVolume::sliceFrameWidths
205 *
206 * The widths of the slice frame. The width can be different on different dimensions,
207 * so you can for example omit drawing the frames on certain sides of the volume by setting the
208 * value for that dimension to zero. The values are fractions of the volume thickness in the same
209 * dimension. The values cannot be negative.
210 *
211 * Defaults to \c{vector3d(0.01, 0.01, 0.01)}.
212 *
213 * \sa drawSliceFrames
214 */
215
216/*!
217 * \qmlproperty vector3d Custom3DVolume::sliceFrameGaps
218 *
219 * The size of the air gap left between the volume itself and the frame in each dimension.
220 * The gap can be different on different dimensions. The values are fractions of the volume
221 * thickness in the same dimension. The values cannot be negative.
222 *
223 * Defaults to \c{vector3d(0.01, 0.01, 0.01)}.
224 *
225 * \sa drawSliceFrames
226 */
227
228/*!
229 * \qmlproperty vector3d Custom3DVolume::sliceFrameThicknesses
230 *
231 * The thickness of the slice frames for each dimension. The values are fractions of
232 * the volume thickness in the same dimension. The values cannot be negative.
233 *
234 * Defaults to \c{vector3d(0.01, 0.01, 0.01)}.
235 *
236 * \sa drawSliceFrames
237 */
238
239/*!
240 * Constructs a custom 3D volume with the given \a parent.
241 */
242QCustom3DVolume::QCustom3DVolume(QObject *parent) :
243 QCustom3DItem(new QCustom3DVolumePrivate(this), parent)
244{
245}
246
247/*!
248 * Constructs a custom 3D volume with the given \a position, \a scaling, \a rotation,
249 * \a textureWidth, \a textureHeight, \a textureDepth, \a textureData, \a textureFormat,
250 * \a colorTable, and optional \a parent.
251 *
252 * \sa textureData, setTextureFormat(), colorTable
253 */
254QCustom3DVolume::QCustom3DVolume(const QVector3D &position, const QVector3D &scaling,
255 const QQuaternion &rotation, int textureWidth, int textureHeight,
256 int textureDepth, QList<uchar> *textureData,
257 QImage::Format textureFormat, const QList<QRgb> &colorTable,
258 QObject *parent)
259 : QCustom3DItem(new QCustom3DVolumePrivate(this, position, scaling, rotation, textureWidth,
260 textureHeight, textureDepth, textureData,
261 textureFormat, colorTable),
262 parent)
263{
264}
265
266
267/*!
268 * Deletes the custom 3D volume.
269 */
270QCustom3DVolume::~QCustom3DVolume()
271{
272}
273
274/*! \property QCustom3DVolume::textureWidth
275 *
276 * \brief The width of the 3D texture defining the volume content in pixels.
277 *
278 * Defaults to \c{0}.
279 *
280 * \note The textureData value may need to be resized or recreated if this value
281 * is changed.
282 * Defaults to \c{0}.
283 *
284 * \sa textureData, textureHeight, textureDepth, setTextureFormat(), textureDataWidth()
285 */
286void QCustom3DVolume::setTextureWidth(int value)
287{
288 if (value >= 0) {
289 if (dptr()->m_textureWidth != value) {
290 dptr()->m_textureWidth = value;
291 dptr()->m_dirtyBitsVolume.textureDimensionsDirty = true;
292 emit textureWidthChanged(value);
293 emit dptr()->needUpdate();
294 }
295 } else {
296 qWarning() << __FUNCTION__ << "Cannot set negative value.";
297 }
298}
299
300int QCustom3DVolume::textureWidth() const
301{
302 return dptrc()->m_textureWidth;
303}
304
305/*! \property QCustom3DVolume::textureHeight
306 *
307 * \brief The height of the 3D texture defining the volume content in pixels.
308 *
309 * Defaults to \c{0}.
310 *
311 * \note The textureData value may need to be resized or recreated if this value
312 * is changed.
313 * Defaults to \c{0}.
314 *
315 * \sa textureData, textureWidth, textureDepth, setTextureFormat()
316 */
317void QCustom3DVolume::setTextureHeight(int value)
318{
319 if (value >= 0) {
320 if (dptr()->m_textureHeight != value) {
321 dptr()->m_textureHeight = value;
322 dptr()->m_dirtyBitsVolume.textureDimensionsDirty = true;
323 emit textureHeightChanged(value);
324 emit dptr()->needUpdate();
325 }
326 } else {
327 qWarning() << __FUNCTION__ << "Cannot set negative value.";
328 }
329
330}
331
332int QCustom3DVolume::textureHeight() const
333{
334 return dptrc()->m_textureHeight;
335}
336
337/*! \property QCustom3DVolume::textureDepth
338 *
339 * \brief The depth of the 3D texture defining the volume content in pixels.
340 *
341 * Defaults to \c{0}.
342 *
343 * \note The textureData value may need to be resized or recreated if this value
344 * is changed.
345 * Defaults to \c{0}.
346 *
347 * \sa textureData, textureWidth, textureHeight, setTextureFormat()
348 */
349void QCustom3DVolume::setTextureDepth(int value)
350{
351 if (value >= 0) {
352 if (dptr()->m_textureDepth != value) {
353 dptr()->m_textureDepth = value;
354 dptr()->m_dirtyBitsVolume.textureDimensionsDirty = true;
355 emit textureDepthChanged(value);
356 emit dptr()->needUpdate();
357 }
358 } else {
359 qWarning() << __FUNCTION__ << "Cannot set negative value.";
360 }
361}
362
363int QCustom3DVolume::textureDepth() const
364{
365 return dptrc()->m_textureDepth;
366}
367
368/*!
369 * A convenience function for setting all three texture dimensions
370 * (\a width, \a height, and \a depth) at once.
371 *
372 * \sa textureData
373 */
374void QCustom3DVolume::setTextureDimensions(int width, int height, int depth)
375{
376 setTextureWidth(width);
377 setTextureHeight(height);
378 setTextureDepth(depth);
379}
380
381/*!
382 * Returns the actual texture data width. When the texture format is QImage::Format_Indexed8,
383 * this value equals textureWidth aligned to a 32-bit boundary. Otherwise, this
384 * value equals four times textureWidth.
385 */
386int QCustom3DVolume::textureDataWidth() const
387{
388 int dataWidth = dptrc()->m_textureWidth;
389
390 if (dptrc()->m_textureFormat == QImage::Format_Indexed8)
391 dataWidth += dataWidth % 4;
392 else
393 dataWidth *= 4;
394
395 return dataWidth;
396}
397
398/*! \property QCustom3DVolume::sliceIndexX
399 *
400 * \brief The x-dimension index into the texture data indicating which vertical
401 * slice to show.
402 *
403 * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
404 * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
405 * normally.
406 *
407 * Defaults to \c{-1}.
408 *
409 * \sa textureData, drawSlices, drawSliceFrames
410 */
411void QCustom3DVolume::setSliceIndexX(int value)
412{
413 if (dptr()->m_sliceIndexX != value) {
414 dptr()->m_sliceIndexX = value;
415 dptr()->m_dirtyBitsVolume.slicesDirty = true;
416 emit sliceIndexXChanged(value);
417 emit dptr()->needUpdate();
418 }
419}
420
421int QCustom3DVolume::sliceIndexX() const
422{
423 return dptrc()->m_sliceIndexX;
424}
425
426/*! \property QCustom3DVolume::sliceIndexY
427 *
428 * \brief The y-dimension index into the texture data indicating which
429 * horizontal slice to show.
430 *
431 * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
432 * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
433 * normally.
434 *
435 * Defaults to \c{-1}.
436 *
437 * \sa textureData, drawSlices, drawSliceFrames
438 */
439void QCustom3DVolume::setSliceIndexY(int value)
440{
441 if (dptr()->m_sliceIndexY != value) {
442 dptr()->m_sliceIndexY = value;
443 dptr()->m_dirtyBitsVolume.slicesDirty = true;
444 emit sliceIndexYChanged(value);
445 emit dptr()->needUpdate();
446 }
447}
448
449int QCustom3DVolume::sliceIndexY() const
450{
451 return dptrc()->m_sliceIndexY;
452}
453
454/*! \property QCustom3DVolume::sliceIndexZ
455 *
456 * \brief The z-dimension index into the texture data indicating which vertical
457 * slice to show.
458 *
459 * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
460 * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
461 * normally.
462 *
463 * Defaults to \c{-1}.
464 *
465 * \sa textureData, drawSlices, drawSliceFrames
466 */
467void QCustom3DVolume::setSliceIndexZ(int value)
468{
469 if (dptr()->m_sliceIndexZ != value) {
470 dptr()->m_sliceIndexZ = value;
471 dptr()->m_dirtyBitsVolume.slicesDirty = true;
472 emit sliceIndexZChanged(value);
473 emit dptr()->needUpdate();
474 }
475}
476
477int QCustom3DVolume::sliceIndexZ() const
478{
479 return dptrc()->m_sliceIndexZ;
480}
481
482/*!
483 * A convenience function for setting all three slice indices (\a x, \a y, and \a z) at once.
484 *
485 * \sa textureData
486 */
487void QCustom3DVolume::setSliceIndices(int x, int y, int z)
488{
489 setSliceIndexX(x);
490 setSliceIndexY(y);
491 setSliceIndexZ(z);
492}
493
494/*! \property QCustom3DVolume::colorTable
495 *
496 * \brief The array containing the colors for indexed texture formats.
497 *
498 * If the texture format is not indexed, this array is not used and can be empty.
499 *
500 * Defaults to \c{0}.
501 *
502 * \sa textureData, setTextureFormat(), QImage::colorTable()
503 */
504void QCustom3DVolume::setColorTable(const QList<QRgb> &colors)
505{
506 if (dptr()->m_colorTable != colors) {
507 dptr()->m_colorTable = colors;
508 dptr()->m_dirtyBitsVolume.colorTableDirty = true;
509 emit colorTableChanged();
510 emit dptr()->needUpdate();
511 }
512}
513
514QList<QRgb> QCustom3DVolume::colorTable() const
515{
516 return dptrc()->m_colorTable;
517}
518
519/*! \property QCustom3DVolume::textureData
520 *
521 * \brief The array containing the texture data in the format specified by textureFormat.
522 *
523 * The size of this array must be at least
524 * (\c{textureDataWidth * textureHeight * textureDepth * texture format color depth in bytes}).
525 *
526 * A 3D texture is defined by a stack of 2D subtextures. Each subtexture must be of identical size
527 * (\c{textureDataWidth * textureHeight}), and the depth of the stack is defined
528 * by the textureDepth property. The data in each 2D texture is identical to a
529 * QImage data with the same format, so
530 * QImage::bits() can be used to supply the data for each subtexture.
531 *
532 * Ownership of the new array transfers to the QCustom3DVolume instance.
533 * If another array is set, the previous array is deleted.
534 * If the same array is set again, it is assumed that the array contents have been changed and the
535 * graph rendering is triggered.
536 *
537 * \note Each x-dimension line of the data needs to be 32-bit aligned.
538 * If textureFormat is QImage::Format_Indexed8 and the textureWidth value is not
539 * divisible by four, padding bytes might need to be added to each x-dimension
540 * line of the \a data. The textureDataWidth() function returns the padded byte
541 * count. The padding bytes should indicate a fully transparent color to avoid
542 * rendering artifacts.
543 *
544 * Defaults to \c{0}.
545 *
546 * \sa colorTable, setTextureFormat(), setSubTextureData(), textureDataWidth()
547 */
548void QCustom3DVolume::setTextureData(QList<uchar> *data)
549{
550 if (dptr()->m_textureData != data)
551 delete dptr()->m_textureData;
552
553 // Even if the pointer is same as previously, consider this property changed, as the values
554 // can be changed unbeknownst to us via the array pointer.
555 dptr()->m_textureData = data;
556 dptr()->m_dirtyBitsVolume.textureDataDirty = true;
557 emit textureDataChanged(data);
558 emit dptr()->needUpdate();
559}
560
561/*!
562 * Creates a new texture data array from an array of \a images and sets it as
563 * textureData for this volume object. The texture dimensions are also set according to image
564 * and array dimensions. All of the images in the array must be the same size. If the images are not
565 * all in the QImage::Format_Indexed8 format, all texture data will be converted into the
566 * QImage::Format_ARGB32 format. If the images are in the
567 * QImage::Format_Indexed8 format, the colorTable value
568 * for the entire volume will be taken from the first image.
569 *
570 * Returns a pointer to the newly created array.
571 *
572 * \sa textureData, textureWidth, textureHeight, textureDepth, setTextureFormat()
573 */
574QList<uchar> *QCustom3DVolume::createTextureData(const QList<QImage *> &images)
575{
576 int imageCount = images.size();
577 if (imageCount) {
578 QImage *currentImage = images.at(i: 0);
579 int imageWidth = currentImage->width();
580 int imageHeight = currentImage->height();
581 QImage::Format imageFormat = currentImage->format();
582 bool convert = false;
583 if (imageFormat != QImage::Format_Indexed8 && imageFormat != QImage::Format_ARGB32) {
584 convert = true;
585 imageFormat = QImage::Format_ARGB32;
586 } else {
587 for (int i = 0; i < imageCount; i++) {
588 currentImage = images.at(i);
589 if (imageWidth != currentImage->width() || imageHeight != currentImage->height()) {
590 qWarning() << __FUNCTION__ << "Not all images were of the same size.";
591 setTextureData(0);
592 setTextureWidth(0);
593 setTextureHeight(0);
594 setTextureDepth(0);
595 return 0;
596
597 }
598 if (currentImage->format() != imageFormat) {
599 convert = true;
600 imageFormat = QImage::Format_ARGB32;
601 break;
602 }
603 }
604 }
605 int colorBytes = (imageFormat == QImage::Format_Indexed8) ? 1 : 4;
606 int imageByteWidth = (imageFormat == QImage::Format_Indexed8)
607 ? currentImage->bytesPerLine() : imageWidth;
608 int frameSize = imageByteWidth * imageHeight * colorBytes;
609 QList<uchar> *newTextureData = new QList<uchar>;
610 newTextureData->resize(size: frameSize * imageCount);
611 uchar *texturePtr = newTextureData->data();
612 QImage convertedImage;
613
614 for (int i = 0; i < imageCount; i++) {
615 currentImage = images.at(i);
616 if (convert) {
617 convertedImage = currentImage->convertToFormat(f: imageFormat);
618 currentImage = &convertedImage;
619 }
620 memcpy(dest: texturePtr, src: static_cast<void *>(currentImage->bits()), n: frameSize);
621 texturePtr += frameSize;
622 }
623
624 if (imageFormat == QImage::Format_Indexed8)
625 setColorTable(images.at(i: 0)->colorTable());
626 setTextureData(newTextureData);
627 setTextureFormat(imageFormat);
628 setTextureWidth(imageWidth);
629 setTextureHeight(imageHeight);
630 setTextureDepth(imageCount);
631 } else {
632 setTextureData(0);
633 setTextureWidth(0);
634 setTextureHeight(0);
635 setTextureDepth(0);
636 }
637 return dptr()->m_textureData;
638}
639
640QList<uchar> *QCustom3DVolume::textureData() const
641{
642 return dptrc()->m_textureData;
643}
644
645/*!
646 * Sets a single 2D subtexture of the 3D texture along the specified
647 * \a axis of the volume.
648 * The \a index parameter specifies the subtexture to set.
649 * The texture \a data must be in the format specified by the textureFormat
650 * property and have the size of
651 * the cross-section of the volume texture along the specified axis multiplied by
652 * the texture format color depth in bytes.
653 * The \a data is expected to be ordered similarly to the data in images
654 * produced by the renderSlice() method along the same axis.
655 *
656 * \note Each x-dimension line of the data needs to be 32-bit aligned when
657 * targeting the y-axis or z-axis. If textureFormat is QImage::Format_Indexed8
658 * and the textureWidth value is not divisible by four, padding bytes might need
659 * to be added to each x-dimension line of the \a data to properly align it. The
660 * padding bytes should indicate a fully transparent color to avoid rendering
661 * artifacts.
662 *
663 * \sa textureData, renderSlice()
664 */
665void QCustom3DVolume::setSubTextureData(Qt::Axis axis, int index, const uchar *data)
666{
667 if (data) {
668 int lineSize = textureDataWidth();
669 int frameSize = lineSize * dptr()->m_textureHeight;
670 int dataSize = dptr()->m_textureData->size();
671 int pixelWidth = (dptr()->m_textureFormat == QImage::Format_Indexed8) ? 1 : 4;
672 int targetIndex;
673 uchar *dataPtr = dptr()->m_textureData->data();
674 bool invalid = (index < 0);
675 if (axis == Qt::XAxis) {
676 targetIndex = index * pixelWidth;
677 if (index >= dptr()->m_textureWidth
678 || (frameSize * (dptr()->m_textureDepth - 1) + targetIndex) > dataSize) {
679 invalid = true;
680 }
681 } else if (axis == Qt::YAxis) {
682 targetIndex = (index * lineSize) + (frameSize * (dptr()->m_textureDepth - 1));
683 if (index >= dptr()->m_textureHeight || (targetIndex + lineSize > dataSize))
684 invalid = true;
685 } else {
686 targetIndex = index * frameSize;
687 if (index >= dptr()->m_textureDepth || ((targetIndex + frameSize) > dataSize))
688 invalid = true;
689 }
690
691 if (invalid) {
692 qWarning() << __FUNCTION__ << "Attempted to set invalid subtexture.";
693 } else {
694 const uchar *sourcePtr = data;
695 uchar *targetPtr = dataPtr + targetIndex;
696 if (axis == Qt::XAxis) {
697 int targetWidth = dptr()->m_textureDepth;
698 int targetHeight = dptr()->m_textureHeight;
699 for (int i = 0; i < targetHeight; i++) {
700 targetPtr = dataPtr + targetIndex + (lineSize * i);
701 for (int j = 0; j < targetWidth; j++) {
702 for (int k = 0; k < pixelWidth; k++)
703 *targetPtr++ = *sourcePtr++;
704 targetPtr += (frameSize - pixelWidth);
705 }
706 }
707 } else if (axis == Qt::YAxis) {
708 int targetHeight = dptr()->m_textureDepth;
709 for (int i = 0; i < targetHeight; i++){
710 for (int j = 0; j < lineSize; j++)
711 *targetPtr++ = *sourcePtr++;
712 targetPtr -= (frameSize + lineSize);
713 }
714 } else {
715 void *subTexPtr = dataPtr + targetIndex;
716 memcpy(dest: subTexPtr, src: static_cast<const void *>(data), n: frameSize);
717 }
718 dptr()->m_dirtyBitsVolume.textureDataDirty = true;
719 emit textureDataChanged(data: dptr()->m_textureData);
720 emit dptr()->needUpdate();
721 }
722 } else {
723 qWarning() << __FUNCTION__ << "Tried to set null data.";
724 }
725}
726
727/*!
728 * Sets a single 2D subtexture of the 3D texture along the specified
729 * \a axis of the volume.
730 * The \a index parameter specifies the subtexture to set.
731 * The source \a image must be in the format specified by the textureFormat property if
732 * textureFormat is indexed. If textureFormat is QImage::Format_ARGB32, the image is converted
733 * to that format. The image must have the size of the cross-section of the volume texture along
734 * the specified axis. The orientation of the image should correspond to the orientation of
735 * the slice image produced by renderSlice() method along the same axis.
736 *
737 * \note Each x-dimension line of the data needs to be 32-bit aligned when
738 * targeting the y-axis or z-axis. If textureFormat is QImage::Format_Indexed8
739 * and the textureWidth value is not divisible by four, padding bytes might need
740 * to be added to each x-dimension line of the image to properly align it. The
741 * padding bytes should indicate a fully transparent color to avoid rendering
742 * artifacts. It is not guaranteed that QImage will do this automatically.
743 *
744 * \sa textureData, renderSlice()
745 */
746void QCustom3DVolume::setSubTextureData(Qt::Axis axis, int index, const QImage &image)
747{
748 int sourceWidth = image.width();
749 int sourceHeight = image.height();
750 int targetWidth;
751 int targetHeight;
752 if (axis == Qt::XAxis) {
753 targetWidth = dptr()->m_textureDepth;
754 targetHeight = dptr()->m_textureHeight;
755 } else if (axis == Qt::YAxis) {
756 targetWidth = dptr()->m_textureWidth;
757 targetHeight = dptr()->m_textureDepth;
758 } else {
759 targetWidth = dptr()->m_textureWidth;
760 targetHeight = dptr()->m_textureHeight;
761 }
762
763 if (sourceWidth == targetWidth
764 && sourceHeight == targetHeight
765 && (image.format() == dptr()->m_textureFormat
766 || dptr()->m_textureFormat == QImage::Format_ARGB32)) {
767 QImage convertedImage;
768 if (dptr()->m_textureFormat == QImage::Format_ARGB32
769 && image.format() != QImage::Format_ARGB32) {
770 convertedImage = image.convertToFormat(f: QImage::Format_ARGB32);
771 } else {
772 convertedImage = image;
773 }
774 setSubTextureData(axis, index, data: convertedImage.bits());
775 } else {
776 qWarning() << __FUNCTION__ << "Invalid image size or format.";
777 }
778}
779
780// Note: textureFormat is not a Q_PROPERTY to work around an issue in meta object system that
781// doesn't allow QImage::format to be a property type. Qt 5.2.1 at least has this problem.
782
783/*!
784 * Sets the format of the textureData property to \a format. Only two formats
785 * are supported currently:
786 * QImage::Format_Indexed8 and QImage::Format_ARGB32. If an indexed format is specified, colorTable
787 * must also be set.
788 * Defaults to QImage::Format_ARGB32.
789 *
790 * \sa colorTable, textureData
791 */
792void QCustom3DVolume::setTextureFormat(QImage::Format format)
793{
794 if (format == QImage::Format_ARGB32 || format == QImage::Format_Indexed8) {
795 if (dptr()->m_textureFormat != format) {
796 dptr()->m_textureFormat = format;
797 dptr()->m_dirtyBitsVolume.textureFormatDirty = true;
798 emit textureFormatChanged(format);
799 emit dptr()->needUpdate();
800 }
801 } else {
802 qWarning() << __FUNCTION__ << "Attempted to set invalid texture format.";
803 }
804}
805
806/*!
807 * Returns the format of the textureData property value.
808 *
809 * \sa setTextureFormat()
810 */
811QImage::Format QCustom3DVolume::textureFormat() const
812{
813 return dptrc()->m_textureFormat;
814}
815
816/*!
817 * \fn void QCustom3DVolume::textureFormatChanged(QImage::Format format)
818 *
819 * This signal is emitted when the \a format of the textureData value changes.
820 *
821 * \sa setTextureFormat()
822 */
823
824/*!
825 * \property QCustom3DVolume::alphaMultiplier
826 *
827 * \brief The value that the alpha value of every texel of the volume texture is multiplied with at
828 * the render time.
829 *
830 * This property can be used to introduce uniform transparency to the volume.
831 * If preserveOpacity is \c{true}, only texels with at least some transparency to begin with are
832 * affected, and fully opaque texels are not affected.
833 * The value must not be negative.
834 * Defaults to \c{1.0f}.
835 *
836 * \sa preserveOpacity, textureData
837 */
838void QCustom3DVolume::setAlphaMultiplier(float mult)
839{
840 if (mult >= 0.0f) {
841 if (dptr()->m_alphaMultiplier != mult) {
842 dptr()->m_alphaMultiplier = mult;
843 dptr()->m_dirtyBitsVolume.alphaDirty = true;
844 emit alphaMultiplierChanged(mult);
845 emit dptr()->needUpdate();
846 }
847 } else {
848 qWarning() << __FUNCTION__ << "Attempted to set negative multiplier.";
849 }
850}
851
852float QCustom3DVolume::alphaMultiplier() const
853{
854 return dptrc()->m_alphaMultiplier;
855}
856
857/*!
858 * \property QCustom3DVolume::preserveOpacity
859 *
860 * \brief Whether the alpha multiplier is applied to all texels.
861 *
862 * If this property value is \c{true}, alphaMultiplier is only applied to texels that already have
863 * some transparency. If it is \c{false}, the multiplier is applied to the alpha value of all
864 * texels.
865 * Defaults to \c{true}.
866 *
867 * \sa alphaMultiplier
868 */
869void QCustom3DVolume::setPreserveOpacity(bool enable)
870{
871 if (dptr()->m_preserveOpacity != enable) {
872 dptr()->m_preserveOpacity = enable;
873 dptr()->m_dirtyBitsVolume.alphaDirty = true;
874 emit preserveOpacityChanged(enabled: enable);
875 emit dptr()->needUpdate();
876 }
877}
878
879bool QCustom3DVolume::preserveOpacity() const
880{
881 return dptrc()->m_preserveOpacity;
882}
883
884/*!
885 * \property QCustom3DVolume::useHighDefShader
886 *
887 * \brief Whether a high or low definition shader is used to render the volume.
888 *
889 * If this property value is \c{true}, a high definition shader is used.
890 * If it is \c{false}, a low definition shader is used.
891 *
892 * The high definition shader guarantees that every visible texel of the volume texture is sampled
893 * when the volume is rendered.
894 * The low definition shader renders only a rough approximation of the volume contents,
895 * but at a much higher frame rate. The low definition shader does not guarantee
896 * that every texel of the
897 * volume texture is sampled, so there may be flickering if the volume contains distinct thin
898 * features.
899 *
900 * \note This value does not affect the level of detail when rendering the
901 * slices of the volume.
902 *
903 * Defaults to \c{true}.
904 *
905 * \sa renderSlice()
906 */
907void QCustom3DVolume::setUseHighDefShader(bool enable)
908{
909 if (dptr()->m_useHighDefShader != enable) {
910 dptr()->m_useHighDefShader = enable;
911 dptr()->m_dirtyBitsVolume.shaderDirty = true;
912 emit useHighDefShaderChanged(enabled: enable);
913 emit dptr()->needUpdate();
914 }
915}
916
917bool QCustom3DVolume::useHighDefShader() const
918{
919 return dptrc()->m_useHighDefShader;
920}
921
922/*!
923 * \property QCustom3DVolume::drawSlices
924 *
925 * \brief Whether the specified slices are drawn instead of the full volume.
926 *
927 * If this property value is \c{true}, the slices indicated by slice index properties
928 * will be drawn instead of the full volume.
929 * If it is \c{false}, the full volume will always be drawn.
930 * Defaults to \c{false}.
931 *
932 * \note The slices are always drawn along the item axes, so if the item is rotated, the slices are
933 * rotated as well.
934 *
935 * \sa sliceIndexX, sliceIndexY, sliceIndexZ
936 */
937void QCustom3DVolume::setDrawSlices(bool enable)
938{
939 if (dptr()->m_drawSlices != enable) {
940 dptr()->m_drawSlices = enable;
941 dptr()->m_dirtyBitsVolume.slicesDirty = true;
942 emit drawSlicesChanged(enabled: enable);
943 emit dptr()->needUpdate();
944 }
945}
946
947bool QCustom3DVolume::drawSlices() const
948{
949 return dptrc()->m_drawSlices;
950}
951
952/*!
953 * \property QCustom3DVolume::drawSliceFrames
954 *
955 * \brief Whether slice frames are drawn around the volume.
956 *
957 * If this property value is \c{true}, the frames of slices indicated by slice index properties
958 * will be drawn around the volume.
959 * If it is \c{false}, no slice frames will be drawn.
960 *
961 * Drawing slice frames is independent of drawing slices, so you can show the full volume and
962 * still draw the slice frames around it. This is useful when using renderSlice() to display the
963 * slices outside the graph itself.
964 *
965 * Defaults to \c{false}.
966 *
967 * \sa sliceIndexX, sliceIndexY, sliceIndexZ, drawSlices, renderSlice()
968 */
969void QCustom3DVolume::setDrawSliceFrames(bool enable)
970{
971 if (dptr()->m_drawSliceFrames != enable) {
972 dptr()->m_drawSliceFrames = enable;
973 dptr()->m_dirtyBitsVolume.slicesDirty = true;
974 emit drawSliceFramesChanged(enabled: enable);
975 emit dptr()->needUpdate();
976 }
977}
978
979bool QCustom3DVolume::drawSliceFrames() const
980{
981 return dptrc()->m_drawSliceFrames;
982}
983
984/*!
985 * \property QCustom3DVolume::sliceFrameColor
986 *
987 * \brief The color of the slice frame.
988 *
989 * Transparent slice frame color is not supported.
990 *
991 * Defaults to black.
992 *
993 * \sa drawSliceFrames
994 */
995void QCustom3DVolume::setSliceFrameColor(const QColor &color)
996{
997 if (dptr()->m_sliceFrameColor != color) {
998 dptr()->m_sliceFrameColor = color;
999 dptr()->m_dirtyBitsVolume.slicesDirty = true;
1000 emit sliceFrameColorChanged(color);
1001 emit dptr()->needUpdate();
1002 }
1003}
1004
1005QColor QCustom3DVolume::sliceFrameColor() const
1006{
1007 return dptrc()->m_sliceFrameColor;
1008}
1009
1010/*!
1011 * \property QCustom3DVolume::sliceFrameWidths
1012 *
1013 * \brief The width of the slice frame.
1014 *
1015 * The width can be different on different dimensions,
1016 * so you can for example omit drawing the frames on certain sides of the volume by setting the
1017 * value for that dimension to zero. The values are fractions of the volume thickness in the same
1018 * dimension. The values cannot be negative.
1019 *
1020 * Defaults to \c{QVector3D(0.01, 0.01, 0.01)}.
1021 *
1022 * \sa drawSliceFrames
1023 */
1024void QCustom3DVolume::setSliceFrameWidths(const QVector3D &values)
1025{
1026 if (values.x() < 0.0f || values.y() < 0.0f || values.z() < 0.0f) {
1027 qWarning() << __FUNCTION__ << "Attempted to set negative values.";
1028 } else if (dptr()->m_sliceFrameWidths != values) {
1029 dptr()->m_sliceFrameWidths = values;
1030 dptr()->m_dirtyBitsVolume.slicesDirty = true;
1031 emit sliceFrameWidthsChanged(values);
1032 emit dptr()->needUpdate();
1033 }
1034}
1035
1036QVector3D QCustom3DVolume::sliceFrameWidths() const
1037{
1038 return dptrc()->m_sliceFrameWidths;
1039}
1040
1041/*!
1042 * \property QCustom3DVolume::sliceFrameGaps
1043 *
1044 * \brief The size of the air gap left between the volume itself and the frame
1045 * in each dimension.
1046 *
1047 * The gap can be different on different dimensions. The values are fractions of the volume
1048 * thickness in the same dimension. The values cannot be negative.
1049 *
1050 * Defaults to \c{QVector3D(0.01, 0.01, 0.01)}.
1051 *
1052 * \sa drawSliceFrames
1053 */
1054void QCustom3DVolume::setSliceFrameGaps(const QVector3D &values)
1055{
1056 if (values.x() < 0.0f || values.y() < 0.0f || values.z() < 0.0f) {
1057 qWarning() << __FUNCTION__ << "Attempted to set negative values.";
1058 } else if (dptr()->m_sliceFrameGaps != values) {
1059 dptr()->m_sliceFrameGaps = values;
1060 dptr()->m_dirtyBitsVolume.slicesDirty = true;
1061 emit sliceFrameGapsChanged(values);
1062 emit dptr()->needUpdate();
1063 }
1064}
1065
1066QVector3D QCustom3DVolume::sliceFrameGaps() const
1067{
1068 return dptrc()->m_sliceFrameGaps;
1069}
1070
1071/*!
1072 * \property QCustom3DVolume::sliceFrameThicknesses
1073 *
1074 * \brief The thickness of the slice frames for each dimension.
1075 *
1076 * The values are fractions of
1077 * the volume thickness in the same dimension. The values cannot be negative.
1078 *
1079 * Defaults to \c{QVector3D(0.01, 0.01, 0.01)}.
1080 *
1081 * \sa drawSliceFrames
1082 */
1083void QCustom3DVolume::setSliceFrameThicknesses(const QVector3D &values)
1084{
1085 if (values.x() < 0.0f || values.y() < 0.0f || values.z() < 0.0f) {
1086 qWarning() << __FUNCTION__ << "Attempted to set negative values.";
1087 } else if (dptr()->m_sliceFrameThicknesses != values) {
1088 dptr()->m_sliceFrameThicknesses = values;
1089 dptr()->m_dirtyBitsVolume.slicesDirty = true;
1090 emit sliceFrameThicknessesChanged(values);
1091 emit dptr()->needUpdate();
1092 }
1093}
1094
1095QVector3D QCustom3DVolume::sliceFrameThicknesses() const
1096{
1097 return dptrc()->m_sliceFrameThicknesses;
1098}
1099
1100/*!
1101 * Renders the slice specified by \a index along the axis specified by \a axis
1102 * into an image.
1103 * The texture format of this object is used.
1104 *
1105 * Returns the rendered image of the slice, or a null image if an invalid index is
1106 * specified.
1107 *
1108 * \sa setTextureFormat()
1109 */
1110QImage QCustom3DVolume::renderSlice(Qt::Axis axis, int index)
1111{
1112 return dptr()->renderSlice(axis, index);
1113}
1114
1115/*!
1116 * \internal
1117 */
1118QCustom3DVolumePrivate *QCustom3DVolume::dptr()
1119{
1120 return static_cast<QCustom3DVolumePrivate *>(d_ptr.data());
1121}
1122
1123/*!
1124 * \internal
1125 */
1126const QCustom3DVolumePrivate *QCustom3DVolume::dptrc() const
1127{
1128 return static_cast<const QCustom3DVolumePrivate *>(d_ptr.data());
1129}
1130
1131QCustom3DVolumePrivate::QCustom3DVolumePrivate(QCustom3DVolume *q) :
1132 QCustom3DItemPrivate(q),
1133 m_textureWidth(0),
1134 m_textureHeight(0),
1135 m_textureDepth(0),
1136 m_sliceIndexX(-1),
1137 m_sliceIndexY(-1),
1138 m_sliceIndexZ(-1),
1139 m_textureFormat(QImage::Format_ARGB32),
1140 m_textureData(0),
1141 m_alphaMultiplier(1.0f),
1142 m_preserveOpacity(true),
1143 m_useHighDefShader(true),
1144 m_drawSlices(false),
1145 m_drawSliceFrames(false),
1146 m_sliceFrameColor(Qt::black),
1147 m_sliceFrameWidths(QVector3D(0.01f, 0.01f, 0.01f)),
1148 m_sliceFrameGaps(QVector3D(0.01f, 0.01f, 0.01f)),
1149 m_sliceFrameThicknesses(QVector3D(0.01f, 0.01f, 0.01f))
1150{
1151 m_isVolumeItem = true;
1152 m_meshFile = QStringLiteral(":/defaultMeshes/barFull");
1153}
1154
1155QCustom3DVolumePrivate::QCustom3DVolumePrivate(
1156 QCustom3DVolume *q, const QVector3D &position, const QVector3D &scaling,
1157 const QQuaternion &rotation, int textureWidth, int textureHeight, int textureDepth,
1158 QList<uchar> *textureData, QImage::Format textureFormat, const QList<QRgb> &colorTable)
1159 : QCustom3DItemPrivate(q, QStringLiteral(":/defaultMeshes/barFull"), position, scaling,
1160 rotation),
1161 m_textureWidth(textureWidth),
1162 m_textureHeight(textureHeight),
1163 m_textureDepth(textureDepth),
1164 m_sliceIndexX(-1),
1165 m_sliceIndexY(-1),
1166 m_sliceIndexZ(-1),
1167 m_textureFormat(textureFormat),
1168 m_colorTable(colorTable),
1169 m_textureData(textureData),
1170 m_alphaMultiplier(1.0f),
1171 m_preserveOpacity(true),
1172 m_useHighDefShader(true),
1173 m_drawSlices(false),
1174 m_drawSliceFrames(false),
1175 m_sliceFrameColor(Qt::black),
1176 m_sliceFrameWidths(QVector3D(0.01f, 0.01f, 0.01f)),
1177 m_sliceFrameGaps(QVector3D(0.01f, 0.01f, 0.01f)),
1178 m_sliceFrameThicknesses(QVector3D(0.01f, 0.01f, 0.01f))
1179{
1180 m_isVolumeItem = true;
1181 m_shadowCasting = false;
1182
1183 if (m_textureWidth < 0)
1184 m_textureWidth = 0;
1185 if (m_textureHeight < 0)
1186 m_textureHeight = 0;
1187 if (m_textureDepth < 0)
1188 m_textureDepth = 0;
1189
1190 if (m_textureFormat != QImage::Format_Indexed8)
1191 m_textureFormat = QImage::Format_ARGB32;
1192
1193}
1194
1195QCustom3DVolumePrivate::~QCustom3DVolumePrivate()
1196{
1197 delete m_textureData;
1198}
1199
1200void QCustom3DVolumePrivate::resetDirtyBits()
1201{
1202 QCustom3DItemPrivate::resetDirtyBits();
1203
1204 m_dirtyBitsVolume.textureDimensionsDirty = false;
1205 m_dirtyBitsVolume.slicesDirty = false;
1206 m_dirtyBitsVolume.colorTableDirty = false;
1207 m_dirtyBitsVolume.textureDataDirty = false;
1208 m_dirtyBitsVolume.textureFormatDirty = false;
1209 m_dirtyBitsVolume.alphaDirty = false;
1210 m_dirtyBitsVolume.shaderDirty = false;
1211}
1212
1213QImage QCustom3DVolumePrivate::renderSlice(Qt::Axis axis, int index)
1214{
1215 if (index < 0)
1216 return QImage();
1217
1218 int x;
1219 int y;
1220 if (axis == Qt::XAxis) {
1221 if (index >= m_textureWidth)
1222 return QImage();
1223 x = m_textureDepth;
1224 y = m_textureHeight;
1225 } else if (axis == Qt::YAxis) {
1226 if (index >= m_textureHeight)
1227 return QImage();
1228 x = m_textureWidth;
1229 y = m_textureDepth;
1230 } else {
1231 if (index >= m_textureDepth)
1232 return QImage();
1233 x = m_textureWidth;
1234 y = m_textureHeight;
1235 }
1236
1237 int padding = 0;
1238 int pixelWidth = 4;
1239 int dataWidth = qptr()->textureDataWidth();
1240 if (m_textureFormat == QImage::Format_Indexed8) {
1241 padding = x % 4;
1242 pixelWidth = 1;
1243 }
1244 QList<uchar> data((x + padding) * y * pixelWidth);
1245 int frameSize = qptr()->textureDataWidth() * m_textureHeight;
1246
1247 int dataIndex = 0;
1248 if (axis == Qt::XAxis) {
1249 for (int i = 0; i < y; i++) {
1250 const uchar *p = m_textureData->constData()
1251 + (index * pixelWidth) + (dataWidth * i);
1252 for (int j = 0; j < x; j++) {
1253 for (int k = 0; k < pixelWidth; k++)
1254 data[dataIndex++] = *(p + k);
1255 p += frameSize;
1256 }
1257 }
1258 } else if (axis == Qt::YAxis) {
1259 for (int i = y - 1; i >= 0; i--) {
1260 const uchar *p = m_textureData->constData() + (index * dataWidth)
1261 + (frameSize * i);
1262 for (int j = 0; j < (x * pixelWidth); j++) {
1263 data[dataIndex++] = *p;
1264 p++;
1265 }
1266 }
1267 } else {
1268 for (int i = 0; i < y; i++) {
1269 const uchar *p = m_textureData->constData() + (index * frameSize) + (dataWidth * i);
1270 for (int j = 0; j < (x * pixelWidth); j++) {
1271 data[dataIndex++] = *p;
1272 p++;
1273 }
1274 }
1275 }
1276
1277 if (m_textureFormat != QImage::Format_Indexed8 && m_alphaMultiplier != 1.0f) {
1278 for (int i = pixelWidth - 1; i < data.size(); i += pixelWidth)
1279 data[i] = static_cast<uchar>(multipliedAlphaValue(alpha: data.at(i)));
1280 }
1281
1282 QImage image(data.constData(), x, y, x * pixelWidth, m_textureFormat);
1283 image.bits(); // Call bits() to detach the new image from local data
1284 if (m_textureFormat == QImage::Format_Indexed8) {
1285 QList<QRgb> colorTable = m_colorTable;
1286 if (m_alphaMultiplier != 1.0f) {
1287 for (int i = 0; i < colorTable.size(); i++) {
1288 QRgb curCol = colorTable.at(i);
1289 int alpha = multipliedAlphaValue(alpha: qAlpha(rgb: curCol));
1290 if (alpha != qAlpha(rgb: curCol))
1291 colorTable[i] = qRgba(r: qRed(rgb: curCol), g: qGreen(rgb: curCol), b: qBlue(rgb: curCol), a: alpha);
1292 }
1293 }
1294 image.setColorTable(colorTable);
1295 }
1296
1297 return image;
1298}
1299
1300int QCustom3DVolumePrivate::multipliedAlphaValue(int alpha)
1301{
1302 int modifiedAlpha = alpha;
1303 if (!m_preserveOpacity || alpha != 255) {
1304 modifiedAlpha = int(m_alphaMultiplier * float(alpha));
1305 modifiedAlpha = qMin(a: modifiedAlpha, b: 255);
1306 }
1307 return modifiedAlpha;
1308}
1309
1310QCustom3DVolume *QCustom3DVolumePrivate::qptr()
1311{
1312 return static_cast<QCustom3DVolume *>(q_ptr);
1313}
1314
1315QT_END_NAMESPACE
1316

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