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