From 940b4d1848e8c70ab7642901a68594e8016caffc Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 27 Apr 2024 18:51:28 +0200 Subject: Adding upstream version 1:7.0.4. Signed-off-by: Daniel Baumann --- basegfx/source/matrix/b2dhommatrix.cxx | 428 ++++++++++++++++++++++ basegfx/source/matrix/b2dhommatrixtools.cxx | 457 +++++++++++++++++++++++ basegfx/source/matrix/b3dhommatrix.cxx | 546 ++++++++++++++++++++++++++++ basegfx/source/matrix/b3dhommatrixtools.cxx | 71 ++++ 4 files changed, 1502 insertions(+) create mode 100644 basegfx/source/matrix/b2dhommatrix.cxx create mode 100644 basegfx/source/matrix/b2dhommatrixtools.cxx create mode 100644 basegfx/source/matrix/b3dhommatrix.cxx create mode 100644 basegfx/source/matrix/b3dhommatrixtools.cxx (limited to 'basegfx/source/matrix') diff --git a/basegfx/source/matrix/b2dhommatrix.cxx b/basegfx/source/matrix/b2dhommatrix.cxx new file mode 100644 index 000000000..89088e8eb --- /dev/null +++ b/basegfx/source/matrix/b2dhommatrix.cxx @@ -0,0 +1,428 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include + +namespace basegfx +{ + typedef ::basegfx::internal::ImplHomMatrixTemplate< 3 > Impl2DHomMatrix_Base; + class Impl2DHomMatrix : public Impl2DHomMatrix_Base + { + }; + + B2DHomMatrix::B2DHomMatrix() = default; + + B2DHomMatrix::B2DHomMatrix(const B2DHomMatrix&) = default; + + B2DHomMatrix::B2DHomMatrix(B2DHomMatrix&&) = default; + + B2DHomMatrix::~B2DHomMatrix() = default; + + B2DHomMatrix::B2DHomMatrix(double f_0x0, double f_0x1, double f_0x2, double f_1x0, double f_1x1, double f_1x2) + : mpImpl() // identity + { + mpImpl->set(0, 0, f_0x0); + mpImpl->set(0, 1, f_0x1); + mpImpl->set(0, 2, f_0x2); + mpImpl->set(1, 0, f_1x0); + mpImpl->set(1, 1, f_1x1); + mpImpl->set(1, 2, f_1x2); + } + + B2DHomMatrix& B2DHomMatrix::operator=(const B2DHomMatrix&) = default; + + B2DHomMatrix& B2DHomMatrix::operator=(B2DHomMatrix&&) = default; + + double B2DHomMatrix::get(sal_uInt16 nRow, sal_uInt16 nColumn) const + { + return mpImpl->get(nRow, nColumn); + } + + void B2DHomMatrix::set(sal_uInt16 nRow, sal_uInt16 nColumn, double fValue) + { + mpImpl->set(nRow, nColumn, fValue); + } + + void B2DHomMatrix::set3x2(double f_0x0, double f_0x1, double f_0x2, double f_1x0, double f_1x1, double f_1x2) + { + mpImpl->set(0, 0, f_0x0); + mpImpl->set(0, 1, f_0x1); + mpImpl->set(0, 2, f_0x2); + mpImpl->set(1, 0, f_1x0); + mpImpl->set(1, 1, f_1x1); + mpImpl->set(1, 2, f_1x2); + } + + bool B2DHomMatrix::isLastLineDefault() const + { + return mpImpl->isLastLineDefault(); + } + + bool B2DHomMatrix::isIdentity() const + { + return mpImpl->isIdentity(); + } + + void B2DHomMatrix::identity() + { + *mpImpl = Impl2DHomMatrix(); + } + + bool B2DHomMatrix::isInvertible() const + { + return mpImpl->isInvertible(); + } + + bool B2DHomMatrix::invert() + { + if(isIdentity()) + { + return true; + } + + Impl2DHomMatrix aWork(*mpImpl); + std::unique_ptr pIndex( new sal_uInt16[Impl2DHomMatrix_Base::getEdgeLength()] ); + sal_Int16 nParity; + + if(aWork.ludcmp(pIndex.get(), nParity)) + { + mpImpl->doInvert(aWork, pIndex.get()); + return true; + } + + return false; + } + + B2DHomMatrix& B2DHomMatrix::operator+=(const B2DHomMatrix& rMat) + { + mpImpl->doAddMatrix(*rMat.mpImpl); + return *this; + } + + B2DHomMatrix& B2DHomMatrix::operator-=(const B2DHomMatrix& rMat) + { + mpImpl->doSubMatrix(*rMat.mpImpl); + return *this; + } + + B2DHomMatrix& B2DHomMatrix::operator*=(double fValue) + { + const double fOne(1.0); + + if(!fTools::equal(fOne, fValue)) + mpImpl->doMulMatrix(fValue); + + return *this; + } + + B2DHomMatrix& B2DHomMatrix::operator/=(double fValue) + { + const double fOne(1.0); + + if(!fTools::equal(fOne, fValue)) + mpImpl->doMulMatrix(1.0 / fValue); + + return *this; + } + + B2DHomMatrix& B2DHomMatrix::operator*=(const B2DHomMatrix& rMat) + { + if(rMat.isIdentity()) + { + // multiply with identity, no change -> nothing to do + } + else if(isIdentity()) + { + // we are identity, result will be rMat -> assign + *this = rMat; + } + else + { + // multiply + mpImpl->doMulMatrix(*rMat.mpImpl); + } + + return *this; + } + + bool B2DHomMatrix::operator==(const B2DHomMatrix& rMat) const + { + if(mpImpl.same_object(rMat.mpImpl)) + return true; + + return mpImpl->isEqual(*rMat.mpImpl); + } + + bool B2DHomMatrix::operator!=(const B2DHomMatrix& rMat) const + { + return !(*this == rMat); + } + + void B2DHomMatrix::rotate(double fRadiant) + { + if(fTools::equalZero(fRadiant)) + return; + + double fSin(0.0); + double fCos(1.0); + + utils::createSinCosOrthogonal(fSin, fCos, fRadiant); + Impl2DHomMatrix aRotMat; + + aRotMat.set(0, 0, fCos); + aRotMat.set(1, 1, fCos); + aRotMat.set(1, 0, fSin); + aRotMat.set(0, 1, -fSin); + + mpImpl->doMulMatrix(aRotMat); + } + + void B2DHomMatrix::translate(double fX, double fY) + { + if(!fTools::equalZero(fX) || !fTools::equalZero(fY)) + { + Impl2DHomMatrix aTransMat; + + aTransMat.set(0, 2, fX); + aTransMat.set(1, 2, fY); + + mpImpl->doMulMatrix(aTransMat); + } + } + + void B2DHomMatrix::translate(const B2DTuple& rTuple) + { + translate(rTuple.getX(), rTuple.getY()); + } + + void B2DHomMatrix::scale(double fX, double fY) + { + const double fOne(1.0); + + if(!fTools::equal(fOne, fX) || !fTools::equal(fOne, fY)) + { + Impl2DHomMatrix aScaleMat; + + aScaleMat.set(0, 0, fX); + aScaleMat.set(1, 1, fY); + + mpImpl->doMulMatrix(aScaleMat); + } + } + + void B2DHomMatrix::scale(const B2DTuple& rTuple) + { + scale(rTuple.getX(), rTuple.getY()); + } + + void B2DHomMatrix::shearX(double fSx) + { + // #i76239# do not test against 1.0, but against 0.0. We are talking about a value not on the diagonal (!) + if(!fTools::equalZero(fSx)) + { + Impl2DHomMatrix aShearXMat; + + aShearXMat.set(0, 1, fSx); + + mpImpl->doMulMatrix(aShearXMat); + } + } + + void B2DHomMatrix::shearY(double fSy) + { + // #i76239# do not test against 1.0, but against 0.0. We are talking about a value not on the diagonal (!) + if(!fTools::equalZero(fSy)) + { + Impl2DHomMatrix aShearYMat; + + aShearYMat.set(1, 0, fSy); + + mpImpl->doMulMatrix(aShearYMat); + } + } + + /** Decomposition + + New, optimized version with local shearX detection. Old version (keeping + below, is working well, too) used the 3D matrix decomposition when + shear was used. Keeping old version as comment below since it may get + necessary to add the determinant() test from there here, too. + */ + bool B2DHomMatrix::decompose(B2DTuple& rScale, B2DTuple& rTranslate, double& rRotate, double& rShearX) const + { + // when perspective is used, decompose is not made here + if(!mpImpl->isLastLineDefault()) + { + return false; + } + + // reset rotate and shear and copy translation values in every case + rRotate = rShearX = 0.0; + rTranslate.setX(get(0, 2)); + rTranslate.setY(get(1, 2)); + + // test for rotation and shear + if(fTools::equalZero(get(0, 1)) && fTools::equalZero(get(1, 0))) + { + // no rotation and shear, copy scale values + rScale.setX(get(0, 0)); + rScale.setY(get(1, 1)); + + // or is there? + if( rScale.getX() < 0 && rScale.getY() < 0 ) + { + // there is - 180 degree rotated + rScale *= -1; + rRotate = M_PI; + } + } + else + { + // get the unit vectors of the transformation -> the perpendicular vectors + B2DVector aUnitVecX(get(0, 0), get(1, 0)); + B2DVector aUnitVecY(get(0, 1), get(1, 1)); + const double fScalarXY(aUnitVecX.scalar(aUnitVecY)); + + // Test if shear is zero. That's the case if the unit vectors in the matrix + // are perpendicular -> scalar is zero. This is also the case when one of + // the unit vectors is zero. + if(fTools::equalZero(fScalarXY)) + { + // calculate unsigned scale values + rScale.setX(aUnitVecX.getLength()); + rScale.setY(aUnitVecY.getLength()); + + // check unit vectors for zero lengths + const bool bXIsZero(fTools::equalZero(rScale.getX())); + const bool bYIsZero(fTools::equalZero(rScale.getY())); + + if(bXIsZero || bYIsZero) + { + // still extract as much as possible. Scalings are already set + if(!bXIsZero) + { + // get rotation of X-Axis + rRotate = atan2(aUnitVecX.getY(), aUnitVecX.getX()); + } + else if(!bYIsZero) + { + // get rotation of X-Axis. When assuming X and Y perpendicular + // and correct rotation, it's the Y-Axis rotation minus 90 degrees + rRotate = atan2(aUnitVecY.getY(), aUnitVecY.getX()) - M_PI_2; + } + + // one or both unit vectors do not exist, determinant is zero, no decomposition possible. + // Eventually used rotations or shears are lost + return false; + } + else + { + // no shear + // calculate rotation of X unit vector relative to (1, 0) + rRotate = atan2(aUnitVecX.getY(), aUnitVecX.getX()); + + // use orientation to evtl. correct sign of Y-Scale + const double fCrossXY(aUnitVecX.cross(aUnitVecY)); + + if(fCrossXY < 0.0) + { + rScale.setY(-rScale.getY()); + } + } + } + else + { + // fScalarXY is not zero, thus both unit vectors exist. No need to handle that here + // shear, extract it + double fCrossXY(aUnitVecX.cross(aUnitVecY)); + + // get rotation by calculating angle of X unit vector relative to (1, 0). + // This is before the parallel test following the motto to extract + // as much as possible + rRotate = atan2(aUnitVecX.getY(), aUnitVecX.getX()); + + // get unsigned scale value for X. It will not change and is useful + // for further corrections + rScale.setX(aUnitVecX.getLength()); + + if(fTools::equalZero(fCrossXY)) + { + // extract as much as possible + rScale.setY(aUnitVecY.getLength()); + + // unit vectors are parallel, thus not linear independent. No + // useful decomposition possible. This should not happen since + // the only way to get the unit vectors nearly parallel is + // a very big shearing. Anyways, be prepared for hand-filled + // matrices + // Eventually used rotations or shears are lost + return false; + } + else + { + // calculate the contained shear + rShearX = fScalarXY / fCrossXY; + + if(!fTools::equalZero(rRotate)) + { + // To be able to correct the shear for aUnitVecY, rotation needs to be + // removed first. Correction of aUnitVecX is easy, it will be rotated back to (1, 0). + aUnitVecX.setX(rScale.getX()); + aUnitVecX.setY(0.0); + + // for Y correction we rotate the UnitVecY back about -rRotate + const double fNegRotate(-rRotate); + const double fSin(sin(fNegRotate)); + const double fCos(cos(fNegRotate)); + + const double fNewX(aUnitVecY.getX() * fCos - aUnitVecY.getY() * fSin); + const double fNewY(aUnitVecY.getX() * fSin + aUnitVecY.getY() * fCos); + + aUnitVecY.setX(fNewX); + aUnitVecY.setY(fNewY); + } + + // Correct aUnitVecY and fCrossXY to fShear=0. Rotation is already removed. + // Shear correction can only work with removed rotation + aUnitVecY.setX(aUnitVecY.getX() - (aUnitVecY.getY() * rShearX)); + fCrossXY = aUnitVecX.cross(aUnitVecY); + + // calculate unsigned scale value for Y, after the corrections since + // the shear correction WILL change the length of aUnitVecY + rScale.setY(aUnitVecY.getLength()); + + // use orientation to set sign of Y-Scale + if(fCrossXY < 0.0) + { + rScale.setY(-rScale.getY()); + } + } + } + } + + return true; + } +} // end of namespace basegfx + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/basegfx/source/matrix/b2dhommatrixtools.cxx b/basegfx/source/matrix/b2dhommatrixtools.cxx new file mode 100644 index 000000000..72256688c --- /dev/null +++ b/basegfx/source/matrix/b2dhommatrixtools.cxx @@ -0,0 +1,457 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include + +namespace basegfx::utils +{ + void createSinCosOrthogonal(double& o_rSin, double& o_rCos, double fRadiant) + { + if( fTools::equalZero( fmod( fRadiant, F_PI2 ) ) ) + { + // determine quadrant + const sal_Int32 nQuad( + (4 + fround( 4/F_2PI*fmod( fRadiant, F_2PI ) )) % 4 ); + switch( nQuad ) + { + case 0: // -2pi,0,2pi + o_rSin = 0.0; + o_rCos = 1.0; + break; + + case 1: // -3/2pi,1/2pi + o_rSin = 1.0; + o_rCos = 0.0; + break; + + case 2: // -pi,pi + o_rSin = 0.0; + o_rCos = -1.0; + break; + + case 3: // -1/2pi,3/2pi + o_rSin = -1.0; + o_rCos = 0.0; + break; + + default: + OSL_FAIL( "createSinCos: Impossible case reached" ); + } + } + else + { + // TODO(P1): Maybe use glibc's sincos here (though + // that's kinda non-portable...) + o_rSin = sin(fRadiant); + o_rCos = cos(fRadiant); + } + } + + B2DHomMatrix createScaleB2DHomMatrix(double fScaleX, double fScaleY) + { + B2DHomMatrix aRetval; + const double fOne(1.0); + + if(!fTools::equal(fScaleX, fOne)) + { + aRetval.set(0, 0, fScaleX); + } + + if(!fTools::equal(fScaleY, fOne)) + { + aRetval.set(1, 1, fScaleY); + } + + return aRetval; + } + + B2DHomMatrix createShearXB2DHomMatrix(double fShearX) + { + B2DHomMatrix aRetval; + + if(!fTools::equalZero(fShearX)) + { + aRetval.set(0, 1, fShearX); + } + + return aRetval; + } + + B2DHomMatrix createShearYB2DHomMatrix(double fShearY) + { + B2DHomMatrix aRetval; + + if(!fTools::equalZero(fShearY)) + { + aRetval.set(1, 0, fShearY); + } + + return aRetval; + } + + B2DHomMatrix createRotateB2DHomMatrix(double fRadiant) + { + B2DHomMatrix aRetval; + + if(!fTools::equalZero(fRadiant)) + { + double fSin(0.0); + double fCos(1.0); + + createSinCosOrthogonal(fSin, fCos, fRadiant); + aRetval.set(0, 0, fCos); + aRetval.set(1, 1, fCos); + aRetval.set(1, 0, fSin); + aRetval.set(0, 1, -fSin); + } + + return aRetval; + } + + B2DHomMatrix createTranslateB2DHomMatrix(double fTranslateX, double fTranslateY) + { + B2DHomMatrix aRetval; + + if(!(fTools::equalZero(fTranslateX) && fTools::equalZero(fTranslateY))) + { + aRetval.set(0, 2, fTranslateX); + aRetval.set(1, 2, fTranslateY); + } + + return aRetval; + } + + B2DHomMatrix createScaleShearXRotateTranslateB2DHomMatrix( + double fScaleX, double fScaleY, + double fShearX, + double fRadiant, + double fTranslateX, double fTranslateY) + { + const double fOne(1.0); + + if(fTools::equal(fScaleX, fOne) && fTools::equal(fScaleY, fOne)) + { + /// no scale, take shortcut + return createShearXRotateTranslateB2DHomMatrix(fShearX, fRadiant, fTranslateX, fTranslateY); + } + else + { + /// scale used + if(fTools::equalZero(fShearX)) + { + /// no shear + if(fTools::equalZero(fRadiant)) + { + /// no rotate, take shortcut + return createScaleTranslateB2DHomMatrix(fScaleX, fScaleY, fTranslateX, fTranslateY); + } + else + { + /// rotate and scale used, no shear + double fSin(0.0); + double fCos(1.0); + + createSinCosOrthogonal(fSin, fCos, fRadiant); + + B2DHomMatrix aRetval( + /* Row 0, Column 0 */ fCos * fScaleX, + /* Row 0, Column 1 */ fScaleY * -fSin, + /* Row 0, Column 2 */ fTranslateX, + /* Row 1, Column 0 */ fSin * fScaleX, + /* Row 1, Column 1 */ fScaleY * fCos, + /* Row 1, Column 2 */ fTranslateY); + + return aRetval; + } + } + else + { + /// scale and shear used + if(fTools::equalZero(fRadiant)) + { + /// scale and shear, but no rotate + B2DHomMatrix aRetval( + /* Row 0, Column 0 */ fScaleX, + /* Row 0, Column 1 */ fScaleY * fShearX, + /* Row 0, Column 2 */ fTranslateX, + /* Row 1, Column 0 */ 0.0, + /* Row 1, Column 1 */ fScaleY, + /* Row 1, Column 2 */ fTranslateY); + + return aRetval; + } + else + { + /// scale, shear and rotate used + double fSin(0.0); + double fCos(1.0); + + createSinCosOrthogonal(fSin, fCos, fRadiant); + + B2DHomMatrix aRetval( + /* Row 0, Column 0 */ fCos * fScaleX, + /* Row 0, Column 1 */ fScaleY * ((fCos * fShearX) - fSin), + /* Row 0, Column 2 */ fTranslateX, + /* Row 1, Column 0 */ fSin * fScaleX, + /* Row 1, Column 1 */ fScaleY * ((fSin * fShearX) + fCos), + /* Row 1, Column 2 */ fTranslateY); + + return aRetval; + } + } + } + } + + B2DHomMatrix createShearXRotateTranslateB2DHomMatrix( + double fShearX, + double fRadiant, + double fTranslateX, double fTranslateY) + { + if(fTools::equalZero(fShearX)) + { + /// no shear + if(fTools::equalZero(fRadiant)) + { + /// no shear, no rotate, take shortcut + return createTranslateB2DHomMatrix(fTranslateX, fTranslateY); + } + else + { + /// no shear, but rotate used + double fSin(0.0); + double fCos(1.0); + + createSinCosOrthogonal(fSin, fCos, fRadiant); + + B2DHomMatrix aRetval( + /* Row 0, Column 0 */ fCos, + /* Row 0, Column 1 */ -fSin, + /* Row 0, Column 2 */ fTranslateX, + /* Row 1, Column 0 */ fSin, + /* Row 1, Column 1 */ fCos, + /* Row 1, Column 2 */ fTranslateY); + + return aRetval; + } + } + else + { + /// shear used + if(fTools::equalZero(fRadiant)) + { + /// no rotate, but shear used + B2DHomMatrix aRetval( + /* Row 0, Column 0 */ 1.0, + /* Row 0, Column 1 */ fShearX, + /* Row 0, Column 2 */ fTranslateX, + /* Row 1, Column 0 */ 0.0, + /* Row 1, Column 1 */ 1.0, + /* Row 1, Column 2 */ fTranslateY); + + return aRetval; + } + else + { + /// shear and rotate used + double fSin(0.0); + double fCos(1.0); + + createSinCosOrthogonal(fSin, fCos, fRadiant); + + B2DHomMatrix aRetval( + /* Row 0, Column 0 */ fCos, + /* Row 0, Column 1 */ (fCos * fShearX) - fSin, + /* Row 0, Column 2 */ fTranslateX, + /* Row 1, Column 0 */ fSin, + /* Row 1, Column 1 */ (fSin * fShearX) + fCos, + /* Row 1, Column 2 */ fTranslateY); + + return aRetval; + } + } + } + + B2DHomMatrix createScaleTranslateB2DHomMatrix( + double fScaleX, double fScaleY, + double fTranslateX, double fTranslateY) + { + const double fOne(1.0); + + if(fTools::equal(fScaleX, fOne) && fTools::equal(fScaleY, fOne)) + { + /// no scale, take shortcut + return createTranslateB2DHomMatrix(fTranslateX, fTranslateY); + } + else + { + /// scale used + if(fTools::equalZero(fTranslateX) && fTools::equalZero(fTranslateY)) + { + /// no translate, but scale. + B2DHomMatrix aRetval; + + aRetval.set(0, 0, fScaleX); + aRetval.set(1, 1, fScaleY); + + return aRetval; + } + else + { + /// translate and scale + B2DHomMatrix aRetval( + /* Row 0, Column 0 */ fScaleX, + /* Row 0, Column 1 */ 0.0, + /* Row 0, Column 2 */ fTranslateX, + /* Row 1, Column 0 */ 0.0, + /* Row 1, Column 1 */ fScaleY, + /* Row 1, Column 2 */ fTranslateY); + + return aRetval; + } + } + } + + B2DHomMatrix createRotateAroundPoint( + double fPointX, double fPointY, + double fRadiant) + { + B2DHomMatrix aRetval; + + if(!fTools::equalZero(fRadiant)) + { + double fSin(0.0); + double fCos(1.0); + + createSinCosOrthogonal(fSin, fCos, fRadiant); + + aRetval.set3x2( + /* Row 0, Column 0 */ fCos, + /* Row 0, Column 1 */ -fSin, + /* Row 0, Column 2 */ (fPointX * (1.0 - fCos)) + (fSin * fPointY), + /* Row 1, Column 0 */ fSin, + /* Row 1, Column 1 */ fCos, + /* Row 1, Column 2 */ (fPointY * (1.0 - fCos)) - (fSin * fPointX)); + } + + return aRetval; + } + + BASEGFX_DLLPUBLIC B2DHomMatrix createRotateAroundCenterKeepAspectRatioStayInsideRange( + const basegfx::B2DRange& rTargetRange, + double fRotate) + { + basegfx::B2DHomMatrix aRetval; + + // RotGrfFlyFrame: Create a transformation that maps the range inside of itself + // so that it fits, takes as much space as possible and keeps the aspect ratio + if(0.0 != fRotate) + { + // Fit rotated graphic to center of available space, keeping page ratio: + // Adapt scaling ratio of unit object and rotate it + aRetval.scale(1.0, rTargetRange.getHeight() / rTargetRange.getWidth()); + aRetval.rotate(fRotate); + + // get the range to see where we are in unit coordinates + basegfx::B2DRange aFullRange(0.0, 0.0, 1.0, 1.0); + aFullRange.transform(aRetval); + + // detect needed scales in X/Y and choose the smallest for staying inside the + // available space while keeping aspect ratio of the source + const double fScaleX(rTargetRange.getWidth() / aFullRange.getWidth()); + const double fScaleY(rTargetRange.getHeight() / aFullRange.getHeight()); + const double fScaleMin(std::min(fScaleX, fScaleY)); + + // TopLeft to zero, then scale, then move to center of available space + aRetval.translate(-aFullRange.getMinX(), -aFullRange.getMinY()); + aRetval.scale(fScaleMin, fScaleMin); + aRetval.translate( + rTargetRange.getCenterX() - (0.5 * fScaleMin * aFullRange.getWidth()), + rTargetRange.getCenterY() - (0.5 * fScaleMin * aFullRange.getHeight())); + } + else + { + // just scale/translate needed + aRetval *= createScaleTranslateB2DHomMatrix( + rTargetRange.getRange(), + rTargetRange.getMinimum()); + } + + return aRetval; + } + + /// special for the case to map from source range to target range + B2DHomMatrix createSourceRangeTargetRangeTransform( + const B2DRange& rSourceRange, + const B2DRange& rTargetRange) + { + B2DHomMatrix aRetval; + + if(&rSourceRange == &rTargetRange) + { + return aRetval; + } + + if(!fTools::equalZero(rSourceRange.getMinX()) || !fTools::equalZero(rSourceRange.getMinY())) + { + aRetval.set(0, 2, -rSourceRange.getMinX()); + aRetval.set(1, 2, -rSourceRange.getMinY()); + } + + const double fSourceW(rSourceRange.getWidth()); + const double fSourceH(rSourceRange.getHeight()); + const bool bDivX(!fTools::equalZero(fSourceW) && !fTools::equal(fSourceW, 1.0)); + const bool bDivY(!fTools::equalZero(fSourceH) && !fTools::equal(fSourceH, 1.0)); + const double fScaleX(bDivX ? rTargetRange.getWidth() / fSourceW : rTargetRange.getWidth()); + const double fScaleY(bDivY ? rTargetRange.getHeight() / fSourceH : rTargetRange.getHeight()); + + if(!fTools::equalZero(fScaleX) || !fTools::equalZero(fScaleY)) + { + aRetval.scale(fScaleX, fScaleY); + } + + if(!fTools::equalZero(rTargetRange.getMinX()) || !fTools::equalZero(rTargetRange.getMinY())) + { + aRetval.translate( + rTargetRange.getMinX(), + rTargetRange.getMinY()); + } + + return aRetval; + } + + B2DHomMatrix createCoordinateSystemTransform( + const B2DPoint& rOrigin, + const B2DVector& rX, + const B2DVector& rY) + { + return basegfx::B2DHomMatrix( + rX.getX(), rY.getX(), rOrigin.getX(), + rX.getY(), rY.getY(), rOrigin.getY()); + } + + B2DTuple getColumn(const B2DHomMatrix& rMatrix, sal_uInt16 nCol) + { + return B2DTuple(rMatrix.get(0, nCol), rMatrix.get(1, nCol)); + } +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/basegfx/source/matrix/b3dhommatrix.cxx b/basegfx/source/matrix/b3dhommatrix.cxx new file mode 100644 index 000000000..2a56c7a28 --- /dev/null +++ b/basegfx/source/matrix/b3dhommatrix.cxx @@ -0,0 +1,546 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include + +namespace basegfx +{ + typedef ::basegfx::internal::ImplHomMatrixTemplate< 4 >Impl3DHomMatrix_Base; + class Impl3DHomMatrix : public Impl3DHomMatrix_Base + { + }; + + B3DHomMatrix::B3DHomMatrix() = default; + + B3DHomMatrix::B3DHomMatrix(const B3DHomMatrix&) = default; + + B3DHomMatrix::B3DHomMatrix(B3DHomMatrix&&) = default; + + B3DHomMatrix::~B3DHomMatrix() = default; + + B3DHomMatrix& B3DHomMatrix::operator=(const B3DHomMatrix&) = default; + + B3DHomMatrix& B3DHomMatrix::operator=(B3DHomMatrix&&) = default; + + double B3DHomMatrix::get(sal_uInt16 nRow, sal_uInt16 nColumn) const + { + return mpImpl->get(nRow, nColumn); + } + + void B3DHomMatrix::set(sal_uInt16 nRow, sal_uInt16 nColumn, double fValue) + { + mpImpl->set(nRow, nColumn, fValue); + } + + bool B3DHomMatrix::isLastLineDefault() const + { + return mpImpl->isLastLineDefault(); + } + + bool B3DHomMatrix::isIdentity() const + { + return mpImpl->isIdentity(); + } + + void B3DHomMatrix::identity() + { + *mpImpl = Impl3DHomMatrix(); + } + + void B3DHomMatrix::invert() + { + Impl3DHomMatrix aWork(*mpImpl); + std::unique_ptr pIndex( new sal_uInt16[Impl3DHomMatrix_Base::getEdgeLength()] ); + sal_Int16 nParity; + + if(aWork.ludcmp(pIndex.get(), nParity)) + { + mpImpl->doInvert(aWork, pIndex.get()); + } + } + + double B3DHomMatrix::determinant() const + { + return mpImpl->doDeterminant(); + } + + B3DHomMatrix& B3DHomMatrix::operator+=(const B3DHomMatrix& rMat) + { + mpImpl->doAddMatrix(*rMat.mpImpl); + return *this; + } + + B3DHomMatrix& B3DHomMatrix::operator-=(const B3DHomMatrix& rMat) + { + mpImpl->doSubMatrix(*rMat.mpImpl); + return *this; + } + + B3DHomMatrix& B3DHomMatrix::operator*=(double fValue) + { + const double fOne(1.0); + + if(!fTools::equal(fOne, fValue)) + mpImpl->doMulMatrix(fValue); + + return *this; + } + + B3DHomMatrix& B3DHomMatrix::operator/=(double fValue) + { + const double fOne(1.0); + + if(!fTools::equal(fOne, fValue)) + mpImpl->doMulMatrix(1.0 / fValue); + + return *this; + } + + B3DHomMatrix& B3DHomMatrix::operator*=(const B3DHomMatrix& rMat) + { + if(rMat.isIdentity()) + { + // multiply with identity, no change -> nothing to do + } + else if(isIdentity()) + { + // we are identity, result will be rMat -> assign + *this = rMat; + } + else + { + // multiply + mpImpl->doMulMatrix(*rMat.mpImpl); + } + return *this; + } + + bool B3DHomMatrix::operator==(const B3DHomMatrix& rMat) const + { + if(mpImpl.same_object(rMat.mpImpl)) + return true; + + return mpImpl->isEqual(*rMat.mpImpl); + } + + bool B3DHomMatrix::operator!=(const B3DHomMatrix& rMat) const + { + return !(*this == rMat); + } + + void B3DHomMatrix::rotate(double fAngleX,double fAngleY,double fAngleZ) + { + if(!(!fTools::equalZero(fAngleX) || !fTools::equalZero(fAngleY) || !fTools::equalZero(fAngleZ))) + return; + + if(!fTools::equalZero(fAngleX)) + { + Impl3DHomMatrix aRotMatX; + double fSin(sin(fAngleX)); + double fCos(cos(fAngleX)); + + aRotMatX.set(1, 1, fCos); + aRotMatX.set(2, 2, fCos); + aRotMatX.set(2, 1, fSin); + aRotMatX.set(1, 2, -fSin); + + mpImpl->doMulMatrix(aRotMatX); + } + + if(!fTools::equalZero(fAngleY)) + { + Impl3DHomMatrix aRotMatY; + double fSin(sin(fAngleY)); + double fCos(cos(fAngleY)); + + aRotMatY.set(0, 0, fCos); + aRotMatY.set(2, 2, fCos); + aRotMatY.set(0, 2, fSin); + aRotMatY.set(2, 0, -fSin); + + mpImpl->doMulMatrix(aRotMatY); + } + + if(fTools::equalZero(fAngleZ)) + return; + + Impl3DHomMatrix aRotMatZ; + double fSin(sin(fAngleZ)); + double fCos(cos(fAngleZ)); + + aRotMatZ.set(0, 0, fCos); + aRotMatZ.set(1, 1, fCos); + aRotMatZ.set(1, 0, fSin); + aRotMatZ.set(0, 1, -fSin); + + mpImpl->doMulMatrix(aRotMatZ); + } + + void B3DHomMatrix::rotate(const B3DTuple& rRotation) + { + rotate(rRotation.getX(), rRotation.getY(), rRotation.getZ()); + } + + void B3DHomMatrix::translate(double fX, double fY, double fZ) + { + if(!fTools::equalZero(fX) || !fTools::equalZero(fY) || !fTools::equalZero(fZ)) + { + Impl3DHomMatrix aTransMat; + + aTransMat.set(0, 3, fX); + aTransMat.set(1, 3, fY); + aTransMat.set(2, 3, fZ); + + mpImpl->doMulMatrix(aTransMat); + } + } + + void B3DHomMatrix::translate(const B3DTuple& rRotation) + { + translate(rRotation.getX(), rRotation.getY(), rRotation.getZ()); + } + + void B3DHomMatrix::scale(double fX, double fY, double fZ) + { + const double fOne(1.0); + + if(!fTools::equal(fOne, fX) || !fTools::equal(fOne, fY) ||!fTools::equal(fOne, fZ)) + { + Impl3DHomMatrix aScaleMat; + + aScaleMat.set(0, 0, fX); + aScaleMat.set(1, 1, fY); + aScaleMat.set(2, 2, fZ); + + mpImpl->doMulMatrix(aScaleMat); + } + } + + void B3DHomMatrix::scale(const B3DTuple& rRotation) + { + scale(rRotation.getX(), rRotation.getY(), rRotation.getZ()); + } + + void B3DHomMatrix::shearXY(double fSx, double fSy) + { + // #i76239# do not test against 1.0, but against 0.0. We are talking about a value not on the diagonal (!) + if(!fTools::equalZero(fSx) || !fTools::equalZero(fSy)) + { + Impl3DHomMatrix aShearXYMat; + + aShearXYMat.set(0, 2, fSx); + aShearXYMat.set(1, 2, fSy); + + mpImpl->doMulMatrix(aShearXYMat); + } + } + + void B3DHomMatrix::shearXZ(double fSx, double fSz) + { + // #i76239# do not test against 1.0, but against 0.0. We are talking about a value not on the diagonal (!) + if(!fTools::equalZero(fSx) || !fTools::equalZero(fSz)) + { + Impl3DHomMatrix aShearXZMat; + + aShearXZMat.set(0, 1, fSx); + aShearXZMat.set(2, 1, fSz); + + mpImpl->doMulMatrix(aShearXZMat); + } + } + void B3DHomMatrix::frustum(double fLeft, double fRight, double fBottom, double fTop, double fNear, double fFar) + { + const double fZero(0.0); + const double fOne(1.0); + + if(!fTools::more(fNear, fZero)) + { + fNear = 0.001; + } + + if(!fTools::more(fFar, fZero)) + { + fFar = fOne; + } + + if(fTools::equal(fNear, fFar)) + { + fFar = fNear + fOne; + } + + if(fTools::equal(fLeft, fRight)) + { + fLeft -= fOne; + fRight += fOne; + } + + if(fTools::equal(fTop, fBottom)) + { + fBottom -= fOne; + fTop += fOne; + } + + Impl3DHomMatrix aFrustumMat; + + aFrustumMat.set(0, 0, 2.0 * fNear / (fRight - fLeft)); + aFrustumMat.set(1, 1, 2.0 * fNear / (fTop - fBottom)); + aFrustumMat.set(0, 2, (fRight + fLeft) / (fRight - fLeft)); + aFrustumMat.set(1, 2, (fTop + fBottom) / (fTop - fBottom)); + aFrustumMat.set(2, 2, -fOne * ((fFar + fNear) / (fFar - fNear))); + aFrustumMat.set(3, 2, -fOne); + aFrustumMat.set(2, 3, -fOne * ((2.0 * fFar * fNear) / (fFar - fNear))); + aFrustumMat.set(3, 3, fZero); + + mpImpl->doMulMatrix(aFrustumMat); + } + + void B3DHomMatrix::ortho(double fLeft, double fRight, double fBottom, double fTop, double fNear, double fFar) + { + if(fTools::equal(fNear, fFar)) + { + fFar = fNear + 1.0; + } + + if(fTools::equal(fLeft, fRight)) + { + fLeft -= 1.0; + fRight += 1.0; + } + + if(fTools::equal(fTop, fBottom)) + { + fBottom -= 1.0; + fTop += 1.0; + } + + Impl3DHomMatrix aOrthoMat; + + aOrthoMat.set(0, 0, 2.0 / (fRight - fLeft)); + aOrthoMat.set(1, 1, 2.0 / (fTop - fBottom)); + aOrthoMat.set(2, 2, -1.0 * (2.0 / (fFar - fNear))); + aOrthoMat.set(0, 3, -1.0 * ((fRight + fLeft) / (fRight - fLeft))); + aOrthoMat.set(1, 3, -1.0 * ((fTop + fBottom) / (fTop - fBottom))); + aOrthoMat.set(2, 3, -1.0 * ((fFar + fNear) / (fFar - fNear))); + + mpImpl->doMulMatrix(aOrthoMat); + } + + void B3DHomMatrix::orientation(const B3DPoint& rVRP, B3DVector aVPN, B3DVector aVUV) + { + Impl3DHomMatrix aOrientationMat; + + // translate -VRP + aOrientationMat.set(0, 3, -rVRP.getX()); + aOrientationMat.set(1, 3, -rVRP.getY()); + aOrientationMat.set(2, 3, -rVRP.getZ()); + + // build rotation + aVUV.normalize(); + aVPN.normalize(); + + // build x-axis as perpendicular from aVUV and aVPN + B3DVector aRx(aVUV.getPerpendicular(aVPN)); + aRx.normalize(); + + // y-axis perpendicular to that + B3DVector aRy(aVPN.getPerpendicular(aRx)); + aRy.normalize(); + + // the calculated normals are the line vectors of the rotation matrix, + // set them to create rotation + aOrientationMat.set(0, 0, aRx.getX()); + aOrientationMat.set(0, 1, aRx.getY()); + aOrientationMat.set(0, 2, aRx.getZ()); + aOrientationMat.set(1, 0, aRy.getX()); + aOrientationMat.set(1, 1, aRy.getY()); + aOrientationMat.set(1, 2, aRy.getZ()); + aOrientationMat.set(2, 0, aVPN.getX()); + aOrientationMat.set(2, 1, aVPN.getY()); + aOrientationMat.set(2, 2, aVPN.getZ()); + + mpImpl->doMulMatrix(aOrientationMat); + } + + void B3DHomMatrix::decompose(B3DTuple& rScale, B3DTuple& rTranslate, B3DTuple& rRotate, B3DTuple& rShear) const + { + // when perspective is used, decompose is not made here + if(!mpImpl->isLastLineDefault()) + return; + + // If determinant is zero, decomposition is not possible + if(determinant() == 0.0) + return; + + // isolate translation + rTranslate.setX(mpImpl->get(0, 3)); + rTranslate.setY(mpImpl->get(1, 3)); + rTranslate.setZ(mpImpl->get(2, 3)); + + // correct translate values + rTranslate.correctValues(); + + // get scale and shear + B3DVector aCol0(mpImpl->get(0, 0), mpImpl->get(1, 0), mpImpl->get(2, 0)); + B3DVector aCol1(mpImpl->get(0, 1), mpImpl->get(1, 1), mpImpl->get(2, 1)); + B3DVector aCol2(mpImpl->get(0, 2), mpImpl->get(1, 2), mpImpl->get(2, 2)); + B3DVector aTemp; + + // get ScaleX + rScale.setX(aCol0.getLength()); + aCol0.normalize(); + + // get ShearXY + rShear.setX(aCol0.scalar(aCol1)); + + if(fTools::equalZero(rShear.getX())) + { + rShear.setX(0.0); + } + else + { + aTemp.setX(aCol1.getX() - rShear.getX() * aCol0.getX()); + aTemp.setY(aCol1.getY() - rShear.getX() * aCol0.getY()); + aTemp.setZ(aCol1.getZ() - rShear.getX() * aCol0.getZ()); + aCol1 = aTemp; + } + + // get ScaleY + rScale.setY(aCol1.getLength()); + aCol1.normalize(); + + const double fShearX(rShear.getX()); + + if(!fTools::equalZero(fShearX)) + { + rShear.setX(rShear.getX() / rScale.getY()); + } + + // get ShearXZ + rShear.setY(aCol0.scalar(aCol2)); + + if(fTools::equalZero(rShear.getY())) + { + rShear.setY(0.0); + } + else + { + aTemp.setX(aCol2.getX() - rShear.getY() * aCol0.getX()); + aTemp.setY(aCol2.getY() - rShear.getY() * aCol0.getY()); + aTemp.setZ(aCol2.getZ() - rShear.getY() * aCol0.getZ()); + aCol2 = aTemp; + } + + // get ShearYZ + rShear.setZ(aCol1.scalar(aCol2)); + + if(fTools::equalZero(rShear.getZ())) + { + rShear.setZ(0.0); + } + else + { + aTemp.setX(aCol2.getX() - rShear.getZ() * aCol1.getX()); + aTemp.setY(aCol2.getY() - rShear.getZ() * aCol1.getY()); + aTemp.setZ(aCol2.getZ() - rShear.getZ() * aCol1.getZ()); + aCol2 = aTemp; + } + + // get ScaleZ + rScale.setZ(aCol2.getLength()); + aCol2.normalize(); + + const double fShearY(rShear.getY()); + + if(!fTools::equalZero(fShearY)) + { + // coverity[copy_paste_error : FALSE] - this is correct getZ, not getY + rShear.setY(rShear.getY() / rScale.getZ()); + } + + const double fShearZ(rShear.getZ()); + + if(!fTools::equalZero(fShearZ)) + { + // coverity[original] - this is not an original copy-and-paste source for ^^^ + rShear.setZ(rShear.getZ() / rScale.getZ()); + } + + // correct shear values + rShear.correctValues(); + + // Coordinate system flip? + if(0.0 > aCol0.scalar(aCol1.getPerpendicular(aCol2))) + { + rScale = -rScale; + aCol0 = -aCol0; + aCol1 = -aCol1; + aCol2 = -aCol2; + } + + // correct scale values + rScale.correctValues(1.0); + + // Get rotations + { + double fy=0; + double cy=0; + + if( ::basegfx::fTools::equal( aCol0.getZ(), 1.0 ) + || aCol0.getZ() > 1.0 ) + { + fy = -F_PI/2.0; + cy = 0.0; + } + else if( ::basegfx::fTools::equal( aCol0.getZ(), -1.0 ) + || aCol0.getZ() < -1.0 ) + { + fy = F_PI/2.0; + cy = 0.0; + } + else + { + fy = asin( -aCol0.getZ() ); + cy = cos(fy); + } + + rRotate.setY(fy); + if( ::basegfx::fTools::equalZero( cy ) ) + { + if( aCol0.getZ() > 0.0 ) + rRotate.setX(atan2(-1.0*aCol1.getX(), aCol1.getY())); + else + rRotate.setX(atan2(aCol1.getX(), aCol1.getY())); + rRotate.setZ(0.0); + } + else + { + rRotate.setX(atan2(aCol1.getZ(), aCol2.getZ())); + rRotate.setZ(atan2(aCol0.getY(), aCol0.getX())); + } + + // correct rotate values + rRotate.correctValues(); + } + } +} // end of namespace basegfx + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/basegfx/source/matrix/b3dhommatrixtools.cxx b/basegfx/source/matrix/b3dhommatrixtools.cxx new file mode 100644 index 000000000..9125aca4e --- /dev/null +++ b/basegfx/source/matrix/b3dhommatrixtools.cxx @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +namespace basegfx::utils +{ +B3DHomMatrix UnoHomogenMatrixToB3DHomMatrix(const com::sun::star::drawing::HomogenMatrix& rMatrixIn) +{ + B3DHomMatrix aRetval; + + aRetval.set(0, 0, rMatrixIn.Line1.Column1); + aRetval.set(0, 1, rMatrixIn.Line1.Column2); + aRetval.set(0, 2, rMatrixIn.Line1.Column3); + aRetval.set(0, 3, rMatrixIn.Line1.Column4); + aRetval.set(1, 0, rMatrixIn.Line2.Column1); + aRetval.set(1, 1, rMatrixIn.Line2.Column2); + aRetval.set(1, 2, rMatrixIn.Line2.Column3); + aRetval.set(1, 3, rMatrixIn.Line2.Column4); + aRetval.set(2, 0, rMatrixIn.Line3.Column1); + aRetval.set(2, 1, rMatrixIn.Line3.Column2); + aRetval.set(2, 2, rMatrixIn.Line3.Column3); + aRetval.set(2, 3, rMatrixIn.Line3.Column4); + aRetval.set(3, 0, rMatrixIn.Line4.Column1); + aRetval.set(3, 1, rMatrixIn.Line4.Column2); + aRetval.set(3, 2, rMatrixIn.Line4.Column3); + aRetval.set(3, 3, rMatrixIn.Line4.Column4); + + return aRetval; +} + +void B3DHomMatrixToUnoHomogenMatrix(const B3DHomMatrix& rMatrixIn, + com::sun::star::drawing::HomogenMatrix& rMatrixOut) +{ + rMatrixOut.Line1.Column1 = rMatrixIn.get(0, 0); + rMatrixOut.Line1.Column2 = rMatrixIn.get(0, 1); + rMatrixOut.Line1.Column3 = rMatrixIn.get(0, 2); + rMatrixOut.Line1.Column4 = rMatrixIn.get(0, 3); + rMatrixOut.Line2.Column1 = rMatrixIn.get(1, 0); + rMatrixOut.Line2.Column2 = rMatrixIn.get(1, 1); + rMatrixOut.Line2.Column3 = rMatrixIn.get(1, 2); + rMatrixOut.Line2.Column4 = rMatrixIn.get(1, 3); + rMatrixOut.Line3.Column1 = rMatrixIn.get(2, 0); + rMatrixOut.Line3.Column2 = rMatrixIn.get(2, 1); + rMatrixOut.Line3.Column3 = rMatrixIn.get(2, 2); + rMatrixOut.Line3.Column4 = rMatrixIn.get(2, 3); + rMatrixOut.Line4.Column1 = rMatrixIn.get(3, 0); + rMatrixOut.Line4.Column2 = rMatrixIn.get(3, 1); + rMatrixOut.Line4.Column3 = rMatrixIn.get(3, 2); + rMatrixOut.Line4.Column4 = rMatrixIn.get(3, 3); +} +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit v1.2.3