1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: b2dhommatrix.cxx,v $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
31 // MARKER(update_precomp.py): autogen include statement, do not remove
32 #include "precompiled_basegfx.hxx"
33 #include <osl/diagnose.h>
34 #include <rtl/instance.hxx>
35 #include <basegfx/matrix/b2dhommatrix.hxx>
36 #include <hommatrixtemplate.hxx>
37 #include <basegfx/tuple/b2dtuple.hxx>
38 #include <basegfx/vector/b2dvector.hxx>
42 class Impl2DHomMatrix
: public ::basegfx::internal::ImplHomMatrixTemplate
< 3 >
46 namespace { struct IdentityMatrix
: public rtl::Static
< B2DHomMatrix::ImplType
,
47 IdentityMatrix
> {}; }
49 B2DHomMatrix::B2DHomMatrix() :
50 mpImpl( IdentityMatrix::get() ) // use common identity matrix
54 B2DHomMatrix::B2DHomMatrix(const B2DHomMatrix
& rMat
) :
59 B2DHomMatrix::~B2DHomMatrix()
63 B2DHomMatrix
& B2DHomMatrix::operator=(const B2DHomMatrix
& rMat
)
69 void B2DHomMatrix::makeUnique()
74 double B2DHomMatrix::get(sal_uInt16 nRow
, sal_uInt16 nColumn
) const
76 return mpImpl
->get(nRow
, nColumn
);
79 void B2DHomMatrix::set(sal_uInt16 nRow
, sal_uInt16 nColumn
, double fValue
)
81 mpImpl
->set(nRow
, nColumn
, fValue
);
84 bool B2DHomMatrix::isLastLineDefault() const
86 return mpImpl
->isLastLineDefault();
89 bool B2DHomMatrix::isIdentity() const
91 if(mpImpl
.same_object(IdentityMatrix::get()))
94 return mpImpl
->isIdentity();
97 void B2DHomMatrix::identity()
99 mpImpl
= IdentityMatrix::get();
102 bool B2DHomMatrix::isInvertible() const
104 return mpImpl
->isInvertible();
107 bool B2DHomMatrix::invert()
109 Impl2DHomMatrix
aWork(*mpImpl
);
110 sal_uInt16
* pIndex
= new sal_uInt16
[mpImpl
->getEdgeLength()];
113 if(aWork
.ludcmp(pIndex
, nParity
))
115 mpImpl
->doInvert(aWork
, pIndex
);
125 bool B2DHomMatrix::isNormalized() const
127 return mpImpl
->isNormalized();
130 void B2DHomMatrix::normalize()
132 if(!const_cast<const B2DHomMatrix
*>(this)->mpImpl
->isNormalized())
133 mpImpl
->doNormalize();
136 double B2DHomMatrix::determinant() const
138 return mpImpl
->doDeterminant();
141 double B2DHomMatrix::trace() const
143 return mpImpl
->doTrace();
146 void B2DHomMatrix::transpose()
148 mpImpl
->doTranspose();
151 B2DHomMatrix
& B2DHomMatrix::operator+=(const B2DHomMatrix
& rMat
)
153 mpImpl
->doAddMatrix(*rMat
.mpImpl
);
157 B2DHomMatrix
& B2DHomMatrix::operator-=(const B2DHomMatrix
& rMat
)
159 mpImpl
->doSubMatrix(*rMat
.mpImpl
);
163 B2DHomMatrix
& B2DHomMatrix::operator*=(double fValue
)
165 const double fOne(1.0);
167 if(!fTools::equal(fOne
, fValue
))
168 mpImpl
->doMulMatrix(fValue
);
173 B2DHomMatrix
& B2DHomMatrix::operator/=(double fValue
)
175 const double fOne(1.0);
177 if(!fTools::equal(fOne
, fValue
))
178 mpImpl
->doMulMatrix(1.0 / fValue
);
183 B2DHomMatrix
& B2DHomMatrix::operator*=(const B2DHomMatrix
& rMat
)
185 if(!rMat
.isIdentity())
186 mpImpl
->doMulMatrix(*rMat
.mpImpl
);
191 bool B2DHomMatrix::operator==(const B2DHomMatrix
& rMat
) const
193 if(mpImpl
.same_object(rMat
.mpImpl
))
196 return mpImpl
->isEqual(*rMat
.mpImpl
);
199 bool B2DHomMatrix::operator!=(const B2DHomMatrix
& rMat
) const
201 return !(*this == rMat
);
204 void B2DHomMatrix::rotate(double fRadiant
)
206 if(!fTools::equalZero(fRadiant
))
211 // is the rotation angle an approximate multiple of pi/2?
212 // If yes, force fSin/fCos to -1/0/1, to maintain
213 // orthogonality (which might also be advantageous for the
214 // other cases, but: for multiples of pi/2, the exact
215 // values _can_ be attained. It would be largely
216 // unintuitive, if a 180 degrees rotation would introduce
217 // slight roundoff errors, instead of exactly mirroring
218 // the coordinate system).
219 if( fTools::equalZero( fmod( fRadiant
, F_PI2
) ) )
221 // determine quadrant
222 const sal_Int32
nQuad(
223 (4 + fround( 4/F_2PI
*fmod( fRadiant
, F_2PI
) )) % 4 );
226 case 0: // -2pi,0,2pi
231 case 1: // -3/2pi,1/2pi
241 case 3: // -1/2pi,3/2pi
248 "B2DHomMatrix::rotate(): Impossible case reached" );
253 // TODO(P1): Maybe use glibc's sincos here (though
254 // that's kinda non-portable...)
255 fSin
= sin(fRadiant
);
256 fCos
= cos(fRadiant
);
259 Impl2DHomMatrix aRotMat
;
261 aRotMat
.set(0, 0, fCos
);
262 aRotMat
.set(1, 1, fCos
);
263 aRotMat
.set(1, 0, fSin
);
264 aRotMat
.set(0, 1, -fSin
);
266 mpImpl
->doMulMatrix(aRotMat
);
270 void B2DHomMatrix::translate(double fX
, double fY
)
272 if(!fTools::equalZero(fX
) || !fTools::equalZero(fY
))
274 Impl2DHomMatrix aTransMat
;
276 aTransMat
.set(0, 2, fX
);
277 aTransMat
.set(1, 2, fY
);
279 mpImpl
->doMulMatrix(aTransMat
);
283 void B2DHomMatrix::scale(double fX
, double fY
)
285 const double fOne(1.0);
287 if(!fTools::equal(fOne
, fX
) || !fTools::equal(fOne
, fY
))
289 Impl2DHomMatrix aScaleMat
;
291 aScaleMat
.set(0, 0, fX
);
292 aScaleMat
.set(1, 1, fY
);
294 mpImpl
->doMulMatrix(aScaleMat
);
298 void B2DHomMatrix::shearX(double fSx
)
300 // #i76239# do not test againt 1.0, but against 0.0. We are talking about a value not on the diagonal (!)
301 if(!fTools::equalZero(fSx
))
303 Impl2DHomMatrix aShearXMat
;
305 aShearXMat
.set(0, 1, fSx
);
307 mpImpl
->doMulMatrix(aShearXMat
);
311 void B2DHomMatrix::shearY(double fSy
)
313 // #i76239# do not test againt 1.0, but against 0.0. We are talking about a value not on the diagonal (!)
314 if(!fTools::equalZero(fSy
))
316 Impl2DHomMatrix aShearYMat
;
318 aShearYMat
.set(1, 0, fSy
);
320 mpImpl
->doMulMatrix(aShearYMat
);
326 New, optimized version with local shearX detection. Old version (keeping
327 below, is working well, too) used the 3D matrix decomposition when
328 shear was used. Keeping old version as comment below since it may get
329 necessary to add the determinant() test from there here, too.
331 bool B2DHomMatrix::decompose(B2DTuple
& rScale
, B2DTuple
& rTranslate
, double& rRotate
, double& rShearX
) const
333 // when perspective is used, decompose is not made here
334 if(!mpImpl
->isLastLineDefault())
339 // reset rotate and shear and copy translation values in every case
340 rRotate
= rShearX
= 0.0;
341 rTranslate
.setX(get(0, 2));
342 rTranslate
.setY(get(1, 2));
344 // test for rotation and shear
345 if(fTools::equalZero(get(0, 1)) && fTools::equalZero(get(1, 0)))
347 // no rotation and shear, copy scale values
348 rScale
.setX(get(0, 0));
349 rScale
.setY(get(1, 1));
353 // get the unit vectors of the transformation -> the perpendicular vectors
354 B2DVector
aUnitVecX(get(0, 0), get(1, 0));
355 B2DVector
aUnitVecY(get(0, 1), get(1, 1));
356 const double fScalarXY(aUnitVecX
.scalar(aUnitVecY
));
358 // Test if shear is zero. That's the case if the unit vectors in the matrix
359 // are perpendicular -> scalar is zero. This is also the case when one of
360 // the unit vectors is zero.
361 if(fTools::equalZero(fScalarXY
))
363 // calculate unsigned scale values
364 rScale
.setX(aUnitVecX
.getLength());
365 rScale
.setY(aUnitVecY
.getLength());
367 // check unit vectors for zero lengths
368 const bool bXIsZero(fTools::equalZero(rScale
.getX()));
369 const bool bYIsZero(fTools::equalZero(rScale
.getY()));
371 if(bXIsZero
|| bYIsZero
)
373 // still extract as much as possible. Scalings are already set
376 // get rotation of X-Axis
377 rRotate
= atan2(aUnitVecX
.getY(), aUnitVecX
.getX());
381 // get rotation of X-Axis. When assuming X and Y perpendicular
382 // and correct rotation, it's the Y-Axis rotation minus 90 degrees
383 rRotate
= atan2(aUnitVecY
.getY(), aUnitVecY
.getX()) - M_PI_2
;
386 // one or both unit vectors do not extist, determinant is zero, no decomposition possible.
387 // Eventually used rotations or shears are lost
393 // calculate rotation of X unit vector relative to (1, 0)
394 rRotate
= atan2(aUnitVecX
.getY(), aUnitVecX
.getX());
396 // use orientation to evtl. correct sign of Y-Scale
397 const double fCrossXY(aUnitVecX
.cross(aUnitVecY
));
401 rScale
.setY(-rScale
.getY());
407 // fScalarXY is not zero, thus both unit vectors exist. No need to handle that here
409 double fCrossXY(aUnitVecX
.cross(aUnitVecY
));
411 // get rotation by calculating angle of X unit vector relative to (1, 0).
412 // This is before the parallell test following the motto to extract
413 // as much as possible
414 rRotate
= atan2(aUnitVecX
.getY(), aUnitVecX
.getX());
416 // get unsigned scale value for X. It will not change and is useful
417 // for further corrections
418 rScale
.setX(aUnitVecX
.getLength());
420 if(fTools::equalZero(fCrossXY
))
422 // extract as much as possible
423 rScale
.setY(aUnitVecY
.getLength());
425 // unit vectors are parallel, thus not linear independent. No
426 // useful decomposition possible. This should not happen since
427 // the only way to get the unit vectors nearly parallell is
428 // a very big shearing. Anyways, be prepared for hand-filled
430 // Eventually used rotations or shears are lost
435 // calculate the contained shear
436 rShearX
= fScalarXY
/ fCrossXY
;
438 if(!fTools::equalZero(rRotate
))
440 // To be able to correct the shear for aUnitVecY, rotation needs to be
441 // removed first. Correction of aUnitVecX is easy, it will be rotated back to (1, 0).
442 aUnitVecX
.setX(rScale
.getX());
445 // for Y correction we rotate the UnitVecY back about -rRotate
446 const double fNegRotate(-rRotate
);
447 const double fSin(sin(fNegRotate
));
448 const double fCos(cos(fNegRotate
));
450 const double fNewX(aUnitVecY
.getX() * fCos
- aUnitVecY
.getY() * fSin
);
451 const double fNewY(aUnitVecY
.getX() * fSin
+ aUnitVecY
.getY() * fCos
);
453 aUnitVecY
.setX(fNewX
);
454 aUnitVecY
.setY(fNewY
);
457 // Correct aUnitVecY and fCrossXY to fShear=0. Rotation is already removed.
458 // Shear correction can only work with removed rotation
459 aUnitVecY
.setX(aUnitVecY
.getX() - (aUnitVecY
.getY() * rShearX
));
460 fCrossXY
= aUnitVecX
.cross(aUnitVecY
);
462 // calculate unsigned scale value for Y, after the corrections since
463 // the shear correction WILL change the length of aUnitVecY
464 rScale
.setY(aUnitVecY
.getLength());
466 // use orientation to set sign of Y-Scale
469 rScale
.setY(-rScale
.getY());
478 /* Old version: Used 3D decompose when shaer was involved and also a determinant test
479 (but only in that case). Keeping as comment since it also worked and to allow a
480 fallback in case the new version makes trouble somehow. Definitely missing in the 2nd
481 case is the sign correction for Y-Scale, this would need to be added following the above
484 bool B2DHomMatrix::decompose(B2DTuple& rScale, B2DTuple& rTranslate, double& rRotate, double& rShearX) const
486 // when perspective is used, decompose is not made here
487 if(!mpImpl->isLastLineDefault())
490 // test for rotation and shear
491 if(fTools::equalZero(get(0, 1))
492 && fTools::equalZero(get(1, 0)))
494 // no rotation and shear, direct value extraction
495 rRotate = rShearX = 0.0;
498 rScale.setX(get(0, 0));
499 rScale.setY(get(1, 1));
501 // copy translation values
502 rTranslate.setX(get(0, 2));
503 rTranslate.setY(get(1, 2));
509 // test if shear is zero. That's the case, if the unit vectors in the matrix
510 // are perpendicular -> scalar is zero
511 const ::basegfx::B2DVector aUnitVecX(get(0, 0), get(1, 0));
512 const ::basegfx::B2DVector aUnitVecY(get(0, 1), get(1, 1));
514 if(fTools::equalZero(aUnitVecX.scalar(aUnitVecY)))
516 // no shear, direct value extraction
519 // calculate rotation
521 rRotate = atan2(aUnitVecX.getY(), aUnitVecX.getX());
523 // calculate scale values
524 rScale.setX(aUnitVecX.getLength());
525 rScale.setY(aUnitVecY.getLength());
527 // copy translation values
528 rTranslate.setX(get(0, 2));
529 rTranslate.setY(get(1, 2));
535 // If determinant is zero, decomposition is not possible
536 if(0.0 == determinant())
539 // copy 2x2 matrix and translate vector to 3x3 matrix
540 ::basegfx::B3DHomMatrix a3DHomMat;
542 a3DHomMat.set(0, 0, get(0, 0));
543 a3DHomMat.set(0, 1, get(0, 1));
544 a3DHomMat.set(1, 0, get(1, 0));
545 a3DHomMat.set(1, 1, get(1, 1));
546 a3DHomMat.set(0, 3, get(0, 2));
547 a3DHomMat.set(1, 3, get(1, 2));
549 ::basegfx::B3DTuple r3DScale, r3DTranslate, r3DRotate, r3DShear;
551 if(a3DHomMat.decompose(r3DScale, r3DTranslate, r3DRotate, r3DShear))
554 rScale.setX(r3DScale.getX());
555 rScale.setY(r3DScale.getY());
558 rShearX = r3DShear.getX();
561 rRotate = r3DRotate.getZ();
564 rTranslate.setX(r3DTranslate.getX());
565 rTranslate.setY(r3DTranslate.getY());
575 } // end of namespace basegfx