1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4/*!
5 \class QGraphicsTransform
6 \brief The QGraphicsTransform class is an abstract base class for building
7 advanced transformations on QGraphicsItems.
8 \since 4.6
9 \ingroup graphicsview-api
10 \inmodule QtWidgets
11
12 As an alternative to QGraphicsItem::transform, QGraphicsTransform lets you
13 create and control advanced transformations that can be configured
14 independently using specialized properties.
15
16 QGraphicsItem allows you to assign any number of QGraphicsTransform
17 instances to one QGraphicsItem. Each QGraphicsTransform is applied in
18 order, one at a time, to the QGraphicsItem it's assigned to.
19
20 QGraphicsTransform is particularly useful for animations. Whereas
21 QGraphicsItem::setTransform() lets you assign any transform directly to an
22 item, there is no direct way to interpolate between two different
23 transformations (e.g., when transitioning between two states, each for
24 which the item has a different arbitrary transform assigned). Using
25 QGraphicsTransform you can interpolate the property values of each
26 independent transformation. The resulting operation is then combined into a
27 single transform which is applied to QGraphicsItem.
28
29 Transformations are computed in true 3D space using QMatrix4x4.
30 When the transformation is applied to a QGraphicsItem, it will be
31 projected back to a 2D QTransform. When multiple QGraphicsTransform
32 objects are applied to a QGraphicsItem, all of the transformations
33 are computed in true 3D space, with the projection back to 2D
34 only occurring after the last QGraphicsTransform is applied.
35 The exception to this is QGraphicsRotation, which projects back to
36 2D after each rotation to preserve the perspective effect around
37 the X and Y axes.
38
39 If you want to create your own configurable transformation, you can create
40 a subclass of QGraphicsTransform (or any or the existing subclasses), and
41 reimplement the pure virtual applyTo() function, which takes a pointer to a
42 QMatrix4x4. Each operation you would like to apply should be exposed as
43 properties (e.g., customTransform->setVerticalShear(2.5)). Inside you
44 reimplementation of applyTo(), you can modify the provided transform
45 respectively.
46
47 QGraphicsTransform can be used together with QGraphicsItem::setTransform(),
48 QGraphicsItem::setRotation(), and QGraphicsItem::setScale().
49
50 \sa QGraphicsItem::transform(), QGraphicsScale, QGraphicsRotation
51*/
52
53#include "qgraphicstransform.h"
54#include "qgraphicsitem_p.h"
55#include "qgraphicstransform_p.h"
56#include <QDebug>
57#include <QtCore/qmath.h>
58#include <QtCore/qnumeric.h>
59
60QT_BEGIN_NAMESPACE
61
62QGraphicsTransformPrivate::~QGraphicsTransformPrivate()
63{
64}
65
66void QGraphicsTransformPrivate::setItem(QGraphicsItem *i)
67{
68 if (item == i)
69 return;
70
71 if (item) {
72 Q_Q(QGraphicsTransform);
73 QGraphicsItemPrivate *d_ptr = item->d_ptr.data();
74
75 item->prepareGeometryChange();
76 Q_ASSERT(d_ptr->transformData);
77 d_ptr->transformData->graphicsTransforms.removeAll(t: q);
78 d_ptr->dirtySceneTransform = 1;
79 item = nullptr;
80 }
81
82 item = i;
83}
84
85void QGraphicsTransformPrivate::updateItem(QGraphicsItem *item)
86{
87 item->prepareGeometryChange();
88 item->d_ptr->dirtySceneTransform = 1;
89}
90
91/*!
92 Constructs a new QGraphicsTransform with the given \a parent.
93*/
94QGraphicsTransform::QGraphicsTransform(QObject *parent)
95 : QObject(*new QGraphicsTransformPrivate, parent)
96{
97}
98
99/*!
100 Destroys the graphics transform.
101*/
102QGraphicsTransform::~QGraphicsTransform()
103{
104 Q_D(QGraphicsTransform);
105 d->setItem(nullptr);
106}
107
108/*!
109 \internal
110*/
111QGraphicsTransform::QGraphicsTransform(QGraphicsTransformPrivate &p, QObject *parent)
112 : QObject(p, parent)
113{
114}
115
116/*!
117 \fn void QGraphicsTransform::applyTo(QMatrix4x4 *matrix) const
118
119 This pure virtual method has to be reimplemented in derived classes.
120
121 It applies this transformation to \a matrix.
122
123 \sa QGraphicsItem::transform(), QMatrix4x4::toTransform()
124*/
125
126/*!
127 Notifies that this transform operation has changed its parameters in such a
128 way that applyTo() will return a different result than before.
129
130 When implementing you own custom graphics transform, you must call this
131 function every time you change a parameter, to let QGraphicsItem know that
132 its transformation needs to be updated.
133
134 \sa applyTo()
135*/
136void QGraphicsTransform::update()
137{
138 Q_D(QGraphicsTransform);
139 if (d->item)
140 d->updateItem(item: d->item);
141}
142
143/*!
144 \class QGraphicsScale
145 \brief The QGraphicsScale class provides a scale transformation.
146 \since 4.6
147 \inmodule QtWidgets
148
149 QGraphicsScene provides certain parameters to help control how the scale
150 should be applied.
151
152 The origin is the point that the item is scaled from (i.e., it stays fixed
153 relative to the parent as the rest of the item grows). By default the
154 origin is QPointF(0, 0).
155
156 The parameters xScale, yScale, and zScale describe the scale factors to
157 apply in horizontal, vertical, and depth directions. They can take on any
158 value, including 0 (to collapse the item to a point) or negative value.
159 A negative xScale value will mirror the item horizontally. A negative yScale
160 value will flip the item vertically. A negative zScale will flip the
161 item end for end.
162
163 \sa QGraphicsTransform, QGraphicsItem::setScale(), QTransform::scale()
164*/
165
166class QGraphicsScalePrivate : public QGraphicsTransformPrivate
167{
168public:
169 QGraphicsScalePrivate()
170 : xScale(1), yScale(1), zScale(1) {}
171 QVector3D origin;
172 qreal xScale;
173 qreal yScale;
174 qreal zScale;
175};
176
177/*!
178 Constructs an empty QGraphicsScale object with the given \a parent.
179*/
180QGraphicsScale::QGraphicsScale(QObject *parent)
181 : QGraphicsTransform(*new QGraphicsScalePrivate, parent)
182{
183}
184
185/*!
186 Destroys the graphics scale.
187*/
188QGraphicsScale::~QGraphicsScale()
189{
190}
191
192/*!
193 \property QGraphicsScale::origin
194 \brief the origin of the scale in 3D space.
195
196 All scaling will be done relative to this point (i.e., this point
197 will stay fixed, relative to the parent, when the item is scaled).
198
199 \sa xScale, yScale, zScale
200*/
201QVector3D QGraphicsScale::origin() const
202{
203 Q_D(const QGraphicsScale);
204 return d->origin;
205}
206void QGraphicsScale::setOrigin(const QVector3D &point)
207{
208 Q_D(QGraphicsScale);
209 if (d->origin == point)
210 return;
211 d->origin = point;
212 update();
213 emit originChanged();
214}
215
216/*!
217 \property QGraphicsScale::xScale
218 \brief the horizontal scale factor.
219
220 The scale factor can be any real number; the default value is 1.0. If you
221 set the factor to 0.0, the item will be collapsed to a single point. If you
222 provide a negative value, the item will be mirrored horizontally around its
223 origin.
224
225 \sa yScale, zScale, origin
226*/
227qreal QGraphicsScale::xScale() const
228{
229 Q_D(const QGraphicsScale);
230 return d->xScale;
231}
232void QGraphicsScale::setXScale(qreal scale)
233{
234 Q_D(QGraphicsScale);
235 if (d->xScale == scale)
236 return;
237 d->xScale = scale;
238 update();
239 emit xScaleChanged();
240 emit scaleChanged();
241}
242
243/*!
244 \property QGraphicsScale::yScale
245 \brief the vertical scale factor.
246
247 The scale factor can be any real number; the default value is 1.0. If you
248 set the factor to 0.0, the item will be collapsed to a single point. If you
249 provide a negative value, the item will be flipped vertically around its
250 origin.
251
252 \sa xScale, zScale, origin
253*/
254qreal QGraphicsScale::yScale() const
255{
256 Q_D(const QGraphicsScale);
257 return d->yScale;
258}
259void QGraphicsScale::setYScale(qreal scale)
260{
261 Q_D(QGraphicsScale);
262 if (d->yScale == scale)
263 return;
264 d->yScale = scale;
265 update();
266 emit yScaleChanged();
267 emit scaleChanged();
268}
269
270/*!
271 \property QGraphicsScale::zScale
272 \brief the depth scale factor.
273
274 The scale factor can be any real number; the default value is 1.0. If you
275 set the factor to 0.0, the item will be collapsed to a single point. If you
276 provide a negative value, the item will be flipped end for end around its
277 origin.
278
279 \sa xScale, yScale, origin
280*/
281qreal QGraphicsScale::zScale() const
282{
283 Q_D(const QGraphicsScale);
284 return d->zScale;
285}
286void QGraphicsScale::setZScale(qreal scale)
287{
288 Q_D(QGraphicsScale);
289 if (d->zScale == scale)
290 return;
291 d->zScale = scale;
292 update();
293 emit zScaleChanged();
294 emit scaleChanged();
295}
296
297/*!
298 \reimp
299*/
300void QGraphicsScale::applyTo(QMatrix4x4 *matrix) const
301{
302 Q_D(const QGraphicsScale);
303 matrix->translate(vector: d->origin);
304 matrix->scale(x: d->xScale, y: d->yScale, z: d->zScale);
305 matrix->translate(vector: -d->origin);
306}
307
308/*!
309 \fn QGraphicsScale::originChanged()
310
311 QGraphicsScale emits this signal when its origin changes.
312
313 \sa QGraphicsScale::origin
314*/
315
316/*!
317 \fn QGraphicsScale::xScaleChanged()
318 \since 4.7
319
320 This signal is emitted whenever the \l xScale property changes.
321*/
322
323/*!
324 \fn QGraphicsScale::yScaleChanged()
325 \since 4.7
326
327 This signal is emitted whenever the \l yScale property changes.
328*/
329
330/*!
331 \fn QGraphicsScale::zScaleChanged()
332 \since 4.7
333
334 This signal is emitted whenever the \l zScale property changes.
335*/
336
337/*!
338 \fn QGraphicsScale::scaleChanged()
339
340 This signal is emitted whenever the xScale, yScale, or zScale
341 of the object changes.
342
343 \sa QGraphicsScale::xScale, QGraphicsScale::yScale
344 \sa QGraphicsScale::zScale
345*/
346
347/*!
348 \class QGraphicsRotation
349 \brief The QGraphicsRotation class provides a rotation transformation around
350 a given axis.
351 \since 4.6
352 \inmodule QtWidgets
353
354 You can provide the desired axis by assigning a QVector3D to the axis property
355 or by passing a member if Qt::Axis to the setAxis convenience function.
356 By default the axis is (0, 0, 1) i.e., rotation around the Z axis.
357
358 The angle property, which is provided by QGraphicsRotation, now
359 describes the number of degrees to rotate around this axis.
360
361 QGraphicsRotation provides certain parameters to help control how the
362 rotation should be applied.
363
364 The origin is the point that the item is rotated around (i.e., it stays
365 fixed relative to the parent as the rest of the item is rotated). By
366 default the origin is QPointF(0, 0).
367
368 The angle property provides the number of degrees to rotate the item
369 clockwise around the origin. This value also be negative, indicating a
370 counter-clockwise rotation. For animation purposes it may also be useful to
371 provide rotation angles exceeding (-360, 360) degrees, for instance to
372 animate how an item rotates several times.
373
374 Note: the final rotation is the combined effect of a rotation in
375 3D space followed by a projection back to 2D. If several rotations
376 are performed in succession, they will not behave as expected unless
377 they were all around the Z axis.
378
379 \sa QGraphicsTransform, QGraphicsItem::setRotation(), QTransform::rotate()
380*/
381
382class QGraphicsRotationPrivate : public QGraphicsTransformPrivate
383{
384public:
385 QGraphicsRotationPrivate()
386 : angle(0), axis(0, 0, 1) {}
387 QVector3D origin;
388 qreal angle;
389 QVector3D axis;
390};
391
392/*!
393 Constructs a new QGraphicsRotation with the given \a parent.
394*/
395QGraphicsRotation::QGraphicsRotation(QObject *parent)
396 : QGraphicsTransform(*new QGraphicsRotationPrivate, parent)
397{
398}
399
400/*!
401 Destroys the graphics rotation.
402*/
403QGraphicsRotation::~QGraphicsRotation()
404{
405}
406
407/*!
408 \property QGraphicsRotation::origin
409 \brief the origin of the rotation in 3D space.
410
411 All rotations will be done relative to this point (i.e., this point
412 will stay fixed, relative to the parent, when the item is rotated).
413
414 \sa angle
415*/
416QVector3D QGraphicsRotation::origin() const
417{
418 Q_D(const QGraphicsRotation);
419 return d->origin;
420}
421void QGraphicsRotation::setOrigin(const QVector3D &point)
422{
423 Q_D(QGraphicsRotation);
424 if (d->origin == point)
425 return;
426 d->origin = point;
427 update();
428 emit originChanged();
429}
430
431/*!
432 \property QGraphicsRotation::angle
433 \brief the angle for clockwise rotation, in degrees.
434
435 The angle can be any real number; the default value is 0.0. A value of 180
436 will rotate 180 degrees, clockwise. If you provide a negative number, the
437 item will be rotated counter-clockwise. Normally the rotation angle will be
438 in the range (-360, 360), but you can also provide numbers outside of this
439 range (e.g., a angle of 370 degrees gives the same result as 10 degrees).
440 Setting the angle to NaN results in no rotation.
441
442 \sa origin
443*/
444qreal QGraphicsRotation::angle() const
445{
446 Q_D(const QGraphicsRotation);
447 return d->angle;
448}
449void QGraphicsRotation::setAngle(qreal angle)
450{
451 Q_D(QGraphicsRotation);
452 if (d->angle == angle)
453 return;
454 d->angle = angle;
455 update();
456 emit angleChanged();
457}
458
459/*!
460 \fn QGraphicsRotation::originChanged()
461
462 This signal is emitted whenever the origin has changed.
463
464 \sa QGraphicsRotation::origin
465*/
466
467/*!
468 \fn void QGraphicsRotation::angleChanged()
469
470 This signal is emitted whenever the angle has changed.
471
472 \sa QGraphicsRotation::angle
473*/
474
475/*!
476 \property QGraphicsRotation::axis
477 \brief a rotation axis, specified by a vector in 3D space.
478
479 This can be any axis in 3D space. By default the axis is (0, 0, 1),
480 which is aligned with the Z axis. If you provide another axis,
481 QGraphicsRotation will provide a transformation that rotates
482 around this axis. For example, if you would like to rotate an item
483 around its X axis, you could pass (1, 0, 0) as the axis.
484
485 \sa QTransform, QGraphicsRotation::angle
486*/
487QVector3D QGraphicsRotation::axis() const
488{
489 Q_D(const QGraphicsRotation);
490 return d->axis;
491}
492void QGraphicsRotation::setAxis(const QVector3D &axis)
493{
494 Q_D(QGraphicsRotation);
495 if (d->axis == axis)
496 return;
497 d->axis = axis;
498 update();
499 emit axisChanged();
500}
501
502/*!
503 \fn void QGraphicsRotation::setAxis(Qt::Axis axis)
504
505 Convenience function to set the axis to \a axis.
506
507 Note: the Qt::YAxis rotation for QTransform is inverted from the
508 correct mathematical rotation in 3D space. The QGraphicsRotation
509 class implements a correct mathematical rotation. The following
510 two sequences of code will perform the same transformation:
511
512 \code
513 QTransform t;
514 t.rotate(45, Qt::YAxis);
515
516 QGraphicsRotation r;
517 r.setAxis(Qt::YAxis);
518 r.setAngle(-45);
519 \endcode
520*/
521void QGraphicsRotation::setAxis(Qt::Axis axis)
522{
523 switch (axis)
524 {
525 case Qt::XAxis:
526 setAxis(QVector3D(1, 0, 0));
527 break;
528 case Qt::YAxis:
529 setAxis(QVector3D(0, 1, 0));
530 break;
531 case Qt::ZAxis:
532 setAxis(QVector3D(0, 0, 1));
533 break;
534 }
535}
536
537/*!
538 \reimp
539*/
540void QGraphicsRotation::applyTo(QMatrix4x4 *matrix) const
541{
542 Q_D(const QGraphicsRotation);
543
544 if (d->angle == 0. || d->axis.isNull() || qIsNaN(d: d->angle))
545 return;
546
547 matrix->translate(vector: d->origin);
548 matrix->projectedRotate(angle: d->angle, x: d->axis.x(), y: d->axis.y(), z: d->axis.z());
549 matrix->translate(vector: -d->origin);
550}
551
552/*!
553 \fn void QGraphicsRotation::axisChanged()
554
555 This signal is emitted whenever the axis of the object changes.
556
557 \sa QGraphicsRotation::axis
558*/
559
560QT_END_NAMESPACE
561
562#include "moc_qgraphicstransform.cpp"
563

source code of qtbase/src/widgets/graphicsview/qgraphicstransform.cpp