414 lines
12 KiB
Objective-C
Executable File
414 lines
12 KiB
Objective-C
Executable File
/**
|
|
**
|
|
** @RDE_HEADER@
|
|
** 2D matrix operations
|
|
** $Id$
|
|
** $HeadURL$
|
|
** $Author$
|
|
**
|
|
** -----------------------------------------------------------------------------------------------
|
|
**
|
|
** Hardware-Environment: Intel-PC
|
|
** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5
|
|
**
|
|
** Language ........: C++ 11
|
|
**
|
|
** -----------------------------------------------------------------------------------------------
|
|
**
|
|
** Author ...........: Jörg Turla, SPD G, Tel. 3132
|
|
**
|
|
** -----------------------------------------------------------------------------------------------
|
|
**
|
|
** Notes .........:
|
|
**
|
|
** 22.06.2016, JT : First edition for use with Qt5
|
|
**
|
|
** -----------------------------------------------------------------------------------------------
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <QtCore/QPointF>
|
|
|
|
#include <QtGui/QGenericMatrix>
|
|
|
|
#include <situation_map_export.h>
|
|
|
|
#include <ostream>
|
|
|
|
|
|
namespace SituationMap { namespace MathUtils
|
|
{
|
|
|
|
/**
|
|
@class Matrix2D matrix2d.h
|
|
@brief The class implements a 3x3 matrix for calculating 2D coordinate transformation with homogeneouse coordinates.
|
|
|
|
One of the most common and important tasks in computer graphics is to transform the coordinates ( position,
|
|
orientation, and size ) of either objects within the graphical scene or the camera that is viewing the scene.
|
|
It is also frequently necessary to transform coordinates from one coordinate system to another, ( e.g. world
|
|
coordinates to viewpoint coordinates to screen coordinates. )
|
|
|
|
All of these transformations can be efficiently and succintly handled using some simple matrix representations,
|
|
which we will see can be particularly useful for combining multiple transformations into a single composite
|
|
transform matrix. The base transformations for translating, scaling and rotating coordinates can be combined in
|
|
an arbitrary manner.
|
|
|
|
<b>Translation in 2D</b>
|
|
|
|
Point (X,Y) is to be translated by amount Dx and Dy to a new location (X',Y').
|
|
|
|
\code
|
|
X' = Dx + X
|
|
Y' = Dy + Y
|
|
\endcode
|
|
|
|
or P' = T + P where
|
|
|
|
\code
|
|
P' = | X' |
|
|
| Y' |
|
|
|
|
T = | Dx |
|
|
| Dy |
|
|
|
|
P = | X |
|
|
| Y |
|
|
\endcode
|
|
|
|
<b>Scaling in 2D</b>
|
|
|
|
Point (X,Y) is to be scaled by amount Sx and Sy to location (X',Y').
|
|
|
|
\code
|
|
X' = Sx * X
|
|
Y' = Sy * Y
|
|
\endcode
|
|
|
|
or P' = S * P where
|
|
|
|
\code
|
|
P' = | X' |
|
|
| Y' |
|
|
|
|
S = | Sx 0 |
|
|
| 0 Sy |
|
|
|
|
P = | X |
|
|
| Y |
|
|
\endcode
|
|
|
|
Scaling is performed about the origin (0,0) not about the center of the line/polygon/whatever.
|
|
|
|
- Scale > 1 enlarge the object and move it away from the origin.
|
|
- Scale = 1 leave the object alone
|
|
- Scale < 1 shrink the object and move it towards the origin.
|
|
- uniform scaling: Sx = Sy
|
|
- differential scaling Sx != Sy -> alters proportions
|
|
|
|
|
|
<b>Rotation in 2D</b>
|
|
|
|
point (X,Y) is to be rotated about the origin by angle theta to location (X',Y')
|
|
|
|
\code{.unparsed}
|
|
X' = X * cos(theta) - Y * sin(theta)
|
|
Y' = X * sin(theta) + Y * cos(theta)
|
|
\endcode
|
|
|
|
or P' = R * P where
|
|
|
|
\code
|
|
P' = | X' |
|
|
| Y' |
|
|
|
|
R = | cos(theta) -sin(theta) |
|
|
| sin(theta) cos(theta) |
|
|
|
|
P = | X |
|
|
| Y |
|
|
\endcode
|
|
|
|
Note that this does involve sin and cos which are much more costly than addition or multiplication.
|
|
|
|
Rotation is performed about the origin \f$(0,0)\f$ not about the center of the line/polygon/whatever.
|
|
|
|
|
|
<b>Derivation of the 2D Rotation Equations</b>
|
|
|
|
Where does this matrix come from?
|
|
|
|
(X,Y) is located r away from (0,0) at a CCW angle of phi from the X axis.
|
|
(X',Y') is located r away from (0,0) at a CCW angle of theta+phi from the X axis.
|
|
|
|
Since rotation is about the origin, (X',Y') must be the same distance from the origin as (X,Y).
|
|
|
|
From trigonometry we have:
|
|
|
|
\code
|
|
X = r * cos(phi)
|
|
Y = r * sin(phi)
|
|
\endcode
|
|
|
|
and
|
|
|
|
\code
|
|
X' = r * cos(theta+phi)
|
|
Y' = r * sin(theta+phi)
|
|
\endcode
|
|
|
|
Now making use of the following trigonometric identities:
|
|
|
|
\code
|
|
cos(a+b) = cos(a) * cos(b) - sin(a) * sin(b)
|
|
sin(a+b) = sin(a) * cos(b) + cos(a) * sin(b)
|
|
\endcode
|
|
|
|
and substituting in for the above equations for X' and Y', we get:
|
|
|
|
\code
|
|
X' = r * cos(theta) * cos(phi) - r * sin(theta) * sin(phi)
|
|
Y' = r * sin(theta) * cos(phi) + r * cos(theta) * sin(phi)
|
|
\endcode
|
|
|
|
Then we substitute in X and Y from their definitions above, and the final result simplifies to:
|
|
|
|
\code
|
|
X' = X * cos(theta) - Y * sin(theta)
|
|
Y' = X * sin(theta) + Y * cos(theta)
|
|
\endcode
|
|
|
|
<b>Homogeneous Coordinates in 2 Dimensions</b>
|
|
|
|
Scaling and rotations are both handled using matrix multiplication, which can be combined as we
|
|
will see shortly. The translations cause a difficulty, however, since they use addition instead
|
|
of multiplication. We want to be able to treat all 3 transformations (translation, scaling,
|
|
rotation) in the same way - as multiplications. The solution is to give each point a third
|
|
coordinate (X,Y,W), which will allow translations to be handled as a multiplication also. (Note
|
|
that we are not really moving into the third dimension yet. The third coordinate is being added to
|
|
the mathematics solely in order to combine the addition and multiplication of 2-D coordinates.)
|
|
|
|
Two triples (X,Y,W) and (X',Y',W') represent the same point if they are multiples of each other e.g.
|
|
(1,2,3) and (2,4,6).
|
|
|
|
At least one of the three coordinates must be nonzero. If W is 0 then the point is at infinity. This
|
|
situation will rarely occur in practice in computer graphics. If W is nonzero we can divide the triple
|
|
by W to get the cartesian coordinates of X and Y which will be identical for triples representing the
|
|
same point (X/W,Y/W,1). This step can be considered as mapping the point from 3-D space onto the
|
|
plane W=1. Conversely, if the 2-D cartesian coordinates of a point are known as (X,Y), then the
|
|
homogenous coordinates can be given as (X,Y,1)
|
|
|
|
So, how does this apply to translation, scaling, and rotation of 2D coordinates?
|
|
|
|
|
|
<b>Translation of 2D Homogenous Coordinates</b>
|
|
|
|
point (X,Y) is to be translated by amount Dx and Dy to location (X',Y')
|
|
|
|
\code
|
|
X' = Dx + X
|
|
Y' = Dy + Y
|
|
\endcode
|
|
|
|
or P' = T * P where
|
|
|
|
\code
|
|
| X' |
|
|
P' = | Y' |
|
|
| 1 |
|
|
|
|
| 1 0 Dx |
|
|
T = | 0 1 Dy | = T(Dx,Dy)
|
|
| 0 0 1 |
|
|
|
|
| X |
|
|
P = | Y |
|
|
| 1 |
|
|
\endcode
|
|
|
|
Hey Look! Translation is now a multiplication instead of an addition!
|
|
|
|
<b>Scaling of 2D Homogenous Coordinates</b>
|
|
|
|
P' = S * P where
|
|
|
|
\code
|
|
P' = | X' |
|
|
| Y' |
|
|
| 1 |
|
|
|
|
| Sx 0 0 |
|
|
S = | 0 Sy 0 | = S(Sx,Sy)
|
|
| 0 0 1 |
|
|
|
|
| X |
|
|
P = | Y |
|
|
| 1 |
|
|
\endcode
|
|
|
|
<b>Rotation of 2D Homogenous Coordinates</b>
|
|
|
|
P' = R * P where
|
|
|
|
\code
|
|
| X' |
|
|
P' = | Y' |
|
|
| 1 |
|
|
|
|
| cos(theta) -sin(theta) 0 |
|
|
R = | sin(theta) cos(theta) 0 | = R(theta)
|
|
| 0 0 1 |
|
|
|
|
| X |
|
|
P = | Y |
|
|
| 1 |
|
|
\endcode
|
|
|
|
<b>Composition of 2D Transformations</b>
|
|
|
|
There are many situations in which the final transformation of a point is a combination of several
|
|
(often many) individual transformations. For example, the position of the finger of a robot might be
|
|
a function of the rotation of the robots hand, arm, and torso, as well as the position of the robot
|
|
on the railroad train and the position of the train in the world, and the rotation of the planet
|
|
around the sun, and . . .
|
|
|
|
Applying each transformation individually to all points in a model would take a lot of time. Instead
|
|
of applying several transformations matrices to each point we want to combine the transformations to
|
|
produce 1 matrix which can be applied to each point.
|
|
|
|
In the simplest case we want to apply the same type of transformation (translation, rotation, scaling)
|
|
more than once.
|
|
|
|
- translation is additive as expected
|
|
- scaling is multiplicative as expected
|
|
- rotation is additive as expected
|
|
|
|
But what if we want to combine different types of transformations?
|
|
|
|
A very common reason for doing this is to rotate a polygon about an arbitrary point (e.g. the center of
|
|
the polygon) rather than around the origin.
|
|
|
|
- Translate so that P1 is at the origin T(-Dx,-Dy)
|
|
- Rotate R(theta)
|
|
- Translate so that the point at the origin is at P1 T(Dx,Dy)
|
|
|
|
Note the order of operations here is right to left:
|
|
|
|
\code
|
|
P' = T(Dx,Dy) * R(theta) * T(-Dx,-Dy) * P
|
|
\endcode
|
|
|
|
is equal to
|
|
|
|
\code
|
|
P' = T(Dx,Dy) * { R(theta) * [ T(-Dx,-Dy) * P ] }
|
|
\endcode
|
|
|
|
is equal to
|
|
|
|
\code
|
|
P' = [ T(Dx,Dy) * R(theta) * T(-Dx,-Dy) ] * P
|
|
\endcode
|
|
|
|
The matrix that results from these 3 steps can then be applied to all of the points in the polygon. Another
|
|
common reason for doing this is to scale a polygon about an arbitrary point (e.g. the center of the polygon)
|
|
rather than around the origin.
|
|
|
|
- Translate so that P1 is at the origin
|
|
- Scale
|
|
- Translate so that the point at the origin is at P1
|
|
|
|
A detailed description can be also found in J.D.Foley, A.Van Dam "Fundamentals of Interactive Computer Graphics"
|
|
|
|
*/
|
|
class SITUATIONMAP_EXPORT Matrix2D
|
|
{
|
|
public:
|
|
|
|
/**
|
|
@name Construction, Destruction and Assign
|
|
@{
|
|
*/
|
|
Matrix2D() = default;
|
|
explicit Matrix2D(const QGenericMatrix<3, 3, qreal> & matrix) : m_matrix(matrix) {;}
|
|
|
|
Matrix2D & operator = ( const QGenericMatrix<3, 3, qreal> & matrix) { m_matrix = matrix; return *this; }
|
|
|
|
/** @} */
|
|
|
|
/**
|
|
Set matrix to identity.
|
|
*/
|
|
void setToIdentity() { m_matrix.setToIdentity(); }
|
|
|
|
/**
|
|
Returns true if this matrix is the identity; false otherwise.
|
|
*/
|
|
bool isIdentity() const { return m_matrix.isIdentity(); }
|
|
|
|
/**
|
|
Returns true if a translation is set, otherwise false
|
|
*/
|
|
bool isTranslation() const { return (!qFuzzyCompare( m_matrix(0,2),0.0) || !qFuzzyCompare(m_matrix(1,2),0.0)); }
|
|
|
|
/**
|
|
Returns true if a rotation is set, otherwise false
|
|
*/
|
|
bool isRotation() const { return (!qFuzzyCompare( m_matrix(0,1),0.0) || !qFuzzyCompare(m_matrix(1,0),0.0)); }
|
|
/**
|
|
Apply the coordinate transformation to one coordinate and returns the result.
|
|
*/
|
|
QPointF apply( const QPointF & ) const;
|
|
|
|
/**
|
|
Add translation to the current matrix
|
|
*/
|
|
void putTranslation( const QPointF & );
|
|
|
|
/**
|
|
Add scaling to the current matrix.
|
|
*/
|
|
void putScale( const QPointF & );
|
|
|
|
|
|
/**
|
|
Add scaling to the current matrix.
|
|
*/
|
|
void putScale(const QPointF & s, const QPointF & oVec) { putScale(s, oVec.y(), oVec.x() ); }
|
|
void putScale( const QPointF & s, qreal sinPhi, qreal cosPhi );
|
|
|
|
/**
|
|
Add rotation to the current matrix.
|
|
@param oVec vector defining the rotation regarding to (1,0). The length of \a oVec has to be 1 (unit vector)
|
|
*/
|
|
void putRotate( const QPointF & oVec ) { putRotate(oVec.y(),oVec.x()); }
|
|
void putRotate( qreal sinPhi, qreal cosPhi );
|
|
|
|
/**
|
|
Returns underlying QGenericMatrix.
|
|
*/
|
|
QGenericMatrix<3,3,qreal> & operator()() { return m_matrix; }
|
|
const QGenericMatrix<3,3,qreal> & operator()() const { return m_matrix; }
|
|
|
|
qreal operator()(int row, int col) const { return m_matrix(row,col); }
|
|
qreal & operator()(int row, int col) { return m_matrix(row,col); }
|
|
|
|
protected:
|
|
|
|
QGenericMatrix<3,3,qreal> m_matrix;
|
|
|
|
}; // Matrix2D
|
|
|
|
/**
|
|
Returns the product of N*M
|
|
*/
|
|
inline Matrix2D operator* ( const Matrix2D & N, const Matrix2D & M ) { return Matrix2D( N()*M() ); }
|
|
|
|
|
|
/**
|
|
Output operator
|
|
*/
|
|
SITUATIONMAP_EXPORT std::ostream & operator<< (std::ostream & stream, const Matrix2D & mat );
|
|
|
|
} } // namespace SituationMap::MathUtils
|