Files
qt5-legatus-legacy/Lts/Viewer/SituationMap/MathUtils/matrix2d.h
T
2026-08-13 18:33:10 +02:00

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