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 
96 QT_BEGIN_NAMESPACE
97 
~QGraphicsTransformPrivate()98 QGraphicsTransformPrivate::~QGraphicsTransformPrivate()
99 {
100 }
101 
setItem(QGraphicsItem * i)102 void 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 
updateItem(QGraphicsItem * item)121 void 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 */
QGraphicsTransform(QObject * parent)130 QGraphicsTransform::QGraphicsTransform(QObject *parent)
131     : QObject(*new QGraphicsTransformPrivate, parent)
132 {
133 }
134 
135 /*!
136     Destroys the graphics transform.
137 */
~QGraphicsTransform()138 QGraphicsTransform::~QGraphicsTransform()
139 {
140     Q_D(QGraphicsTransform);
141     d->setItem(nullptr);
142 }
143 
144 /*!
145     \internal
146 */
QGraphicsTransform(QGraphicsTransformPrivate & p,QObject * parent)147 QGraphicsTransform::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 */
update()172 void 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 
202 class QGraphicsScalePrivate : public QGraphicsTransformPrivate
203 {
204 public:
QGraphicsScalePrivate()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 */
QGraphicsScale(QObject * parent)216 QGraphicsScale::QGraphicsScale(QObject *parent)
217     : QGraphicsTransform(*new QGraphicsScalePrivate, parent)
218 {
219 }
220 
221 /*!
222     Destroys the graphics scale.
223 */
~QGraphicsScale()224 QGraphicsScale::~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 */
origin() const237 QVector3D QGraphicsScale::origin() const
238 {
239     Q_D(const QGraphicsScale);
240     return d->origin;
241 }
setOrigin(const QVector3D & point)242 void 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 */
xScale() const263 qreal QGraphicsScale::xScale() const
264 {
265     Q_D(const QGraphicsScale);
266     return d->xScale;
267 }
setXScale(qreal scale)268 void 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 */
yScale() const290 qreal QGraphicsScale::yScale() const
291 {
292     Q_D(const QGraphicsScale);
293     return d->yScale;
294 }
setYScale(qreal scale)295 void 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 */
zScale() const317 qreal QGraphicsScale::zScale() const
318 {
319     Q_D(const QGraphicsScale);
320     return d->zScale;
321 }
setZScale(qreal scale)322 void 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 */
applyTo(QMatrix4x4 * matrix) const336 void 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 
418 class QGraphicsRotationPrivate : public QGraphicsTransformPrivate
419 {
420 public:
QGraphicsRotationPrivate()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 */
QGraphicsRotation(QObject * parent)431 QGraphicsRotation::QGraphicsRotation(QObject *parent)
432     : QGraphicsTransform(*new QGraphicsRotationPrivate, parent)
433 {
434 }
435 
436 /*!
437     Destroys the graphics rotation.
438 */
~QGraphicsRotation()439 QGraphicsRotation::~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 */
origin() const452 QVector3D QGraphicsRotation::origin() const
453 {
454     Q_D(const QGraphicsRotation);
455     return d->origin;
456 }
setOrigin(const QVector3D & point)457 void 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 */
angle() const480 qreal QGraphicsRotation::angle() const
481 {
482     Q_D(const QGraphicsRotation);
483     return d->angle;
484 }
setAngle(qreal angle)485 void 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 */
axis() const523 QVector3D QGraphicsRotation::axis() const
524 {
525     Q_D(const QGraphicsRotation);
526     return d->axis;
527 }
setAxis(const QVector3D & axis)528 void 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 */
setAxis(Qt::Axis axis)557 void 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 */
applyTo(QMatrix4x4 * matrix) const576 void 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 
598 QT_END_NAMESPACE
599