// $Id: matrix.cpp 1282 2006-06-09 09:46:49Z alex $ /* @@tag:xara-cn@@ DO NOT MODIFY THIS LINE ================================XARAHEADERSTART=========================== Xara LX, a vector drawing and manipulation program. Copyright (C) 1993-2006 Xara Group Ltd. Copyright on certain contributions may be held in joint with their respective authors. See AUTHORS file for details. LICENSE TO USE AND MODIFY SOFTWARE ---------------------------------- This file is part of Xara LX. Xara LX is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. Xara LX and its component source files are distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Xara LX (see the file GPL in the root directory of the distribution); if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ADDITIONAL RIGHTS ----------------- Conditional upon your continuing compliance with the GNU General Public License described above, Xara Group Ltd grants to you certain additional rights. The additional rights are to use, modify, and distribute the software together with the wxWidgets library, the wxXtra library, and the "CDraw" library and any other such library that any version of Xara LX relased by Xara Group Ltd requires in order to compile and execute, including the static linking of that library to XaraLX. In the case of the "CDraw" library, you may satisfy obligation under the GNU General Public License to provide source code by providing a binary copy of the library concerned and a copy of the license accompanying it. Nothing in this section restricts any of the rights you have under the GNU General Public License. SCOPE OF LICENSE ---------------- This license applies to this program (XaraLX) and its constituent source files only, and does not necessarily apply to other Xara products which may in part share the same code base, and are subject to their own licensing terms. This license does not apply to files in the wxXtra directory, which are built into a separate library, and are subject to the wxWindows license contained within that directory in the file "WXXTRA-LICENSE". This license does not apply to the binary libraries (if any) within the "libs" directory, which are subject to a separate license contained within that directory in the file "LIBS-LICENSE". ARRANGEMENTS FOR CONTRIBUTION OF MODIFICATIONS ---------------------------------------------- Subject to the terms of the GNU Public License (see above), you are free to do whatever you like with your modifications. However, you may (at your option) wish contribute them to Xara's source tree. You can find details of how to do this at: http://www.xaraxtreme.org/developers/ Prior to contributing your modifications, you will need to complete our contributor agreement. This can be found at: http://www.xaraxtreme.org/developers/contribute/ Please note that Xara will not accept modifications which modify any of the text between the start and end of this header (marked XARAHEADERSTART and XARAHEADEREND). MARKS ----- Xara, Xara LX, Xara X, Xara X/Xtreme, Xara Xtreme, the Xtreme and Xara designs are registered or unregistered trademarks, design-marks, and/or service marks of Xara Group Ltd. All rights in these marks are reserved. Xara Group Ltd, Gaddesden Place, Hemel Hempstead, HP2 6EX, UK. http://www.xara.com/ =================================XARAHEADEREND============================ */ #include "camtypes.h" // camconfig.h must be included immediately after camtypes.h //#include "camconfig.h" //#include "errors.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "ensure.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "matrix.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "docrect.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "xadrwold.h" #include "macros.h" //#include "grndrgn.h" DECLARE_SOURCE("$Revision: 1282 $"); #define TORADIANS(x) ((x)/180.0*PI) /******************************************************************************************** > Matrix::Matrix () Author: Mario_Shamtani (Xara Group Ltd) Created: 14/5/93 Inputs: None Outputs: None Returns: None. Purpose: Default initialisation - sets up the identity matrix. Errors: None. ********************************************************************************************/ /* Technical notes: ********************************************************************************************/ Matrix::Matrix() { a = (FIXED16)1; b = (FIXED16)0; c = (FIXED16)0; d = (FIXED16)1; e = 0; f = 0; Type = TRANS_IDENTITY; Angle = (ANGLE)0; } /******************************************************************************************** > Matrix::Matrix (const FIXED16& xScale, const FIXED16& yScale) Author: Mario_Shamtani (Xara Group Ltd) Created: 14/5/93 Inputs: Two fixed16s representing the x and y scale factors. Outputs: None Returns: None. Purpose: Default initialisation - sets up the scale matrix. Errors: None. ********************************************************************************************/ /* Technical notes: ********************************************************************************************/ Matrix::Matrix(const FIXED16& xScale, const FIXED16& yScale) { a = xScale; b = (FIXED16)0; c = (FIXED16)0; d = yScale; e = 0; f = 0; Type = TRANS_SCALE; Angle = (ANGLE)0; } /******************************************************************************************** > Matrix::Matrix (const ANGLE& theta) Author: Mario_Shamtani (Xara Group Ltd) Created: 14/5/93 Inputs: An angle representing the degree of rotation. Outputs: None Returns: None. Purpose: Default initialisation - sets up the rotation matrix. Errors: None. ********************************************************************************************/ /* Technical notes: ********************************************************************************************/ Matrix::Matrix(const ANGLE& theta) { #if 0 a = b = c = d = theta; a.Cos(); b.Sin(); c = -(c.Sin()); d.Cos(); #else double thetaradians = TORADIANS(theta.MakeDouble()); FIXED16 costheta = cos(thetaradians); FIXED16 sintheta = sin(thetaradians); a = d = costheta; b = sintheta; c = -sintheta; #endif e = 0; f = 0; Type = TRANS_ROTATION; Angle = theta; } /******************************************************************************************** > Matrix::Matrix (const Coord& disp) Author: Mario_Shamtani (Xara Group Ltd) Created: 14/5/93 Inputs: Displacement of translation. Outputs: None Returns: None. Purpose: Default initialisation - Sets up a translate matrix. Errors: None. ********************************************************************************************/ /* Technical notes: ********************************************************************************************/ Matrix::Matrix(const Coord& disp) { a = (FIXED16)1; b = (FIXED16)0; c = (FIXED16)0; d = (FIXED16)1; e = disp.x; f = disp.y; Type = TRANS_TRANSLATION; Angle = (ANGLE)0; } /******************************************************************************************** > Matrix::Matrix (const INT32 x, const INT32 y) Author: Andy_Pennell (Xara Group Ltd) Created: 30/5/93 Inputs: Displacement of translation. Outputs: None Returns: None. Purpose: Default initialisation - Sets up a translate matrix. Errors: None. ********************************************************************************************/ /* Technical notes: ********************************************************************************************/ Matrix::Matrix(const INT32 x, const INT32 y) { a = (FIXED16)1; b = (FIXED16)0; c = (FIXED16)0; d = (FIXED16)1; e = x; f = y; Type = TRANS_TRANSLATION; Angle = (ANGLE)0; } /******************************************************************************************** > Matrix::Matrix (const FIXED16& ca, const FIXED16& cb, const FIXED16& cc, const FIXED16& cd, const INT32 ce, const INT32 cf ) Author: Phil_Martin (Xara Group Ltd) Created: 21/7/93 Inputs: All six parameters which make up a matrix Outputs: None Returns: None. Purpose: Initialise a while matrix. Errors: None. ********************************************************************************************/ Matrix::Matrix( const FIXED16& ca, const FIXED16& cb, const FIXED16& cc, const FIXED16& cd, const INT32 ce, const INT32 cf ) { a = ca; b = cb; c = cc; d = cd; e = ce; f = cf; Type = TRANS_COMPLEX; Angle = (ANGLE)0; } /******************************************************************************************* > Matrix::Matrix(const DocRect& Source, const DocRect& Destin) Author: Mike_Kenny (Xara Group Ltd) Created: 13/12/93 Inputs: Source = source rectangle Destin = destination rectangle Returns: a matrix whose domain is the source rectangle and range is the destination rectangle. Purpose: Matrix constructor taking two document rectangles. Creates a matrix which can be used to transform coordinates within the source rectangle into coordinates in the second rectangle ********************************************************************************************/ Matrix::Matrix(const DocRect& Source, const DocRect& Destin) { double Sx0 = Source.lo.x; double Sy0 = Source.lo.y; double Sx1 = Source.hi.x; double Sy1 = Source.hi.y; double Dx0 = Destin.lo.x; double Dy0 = Destin.lo.y; double Dx1 = Destin.hi.x; double Dy1 = Destin.hi.y; double t0,t1; (Sx1==Sx0) ? t0=1 : t0=(Dx1-Dx0)/(Sx1-Sx0); (Sy1==Sy0) ? t1=1 : t1=(Dy1-Dy0)/(Sy1-Sy0); a = (FIXED16)t0; b = (FIXED16)0; c = (FIXED16)0; d = (FIXED16)t1; e = (INT32)(Dx0-Sx0*t0); f = (INT32)(Dy0-Sy0*t1); Type = TRANS_COMPLEX; Angle = (ANGLE)0; } /******************************************************************************************** > void Matrix::transform(Coord pts[], UINT32 count) Author: Mario_Shamtani (Xara Group Ltd) Created: 14/5/93 Inputs: result - points to be translated. count - number of points. Outputs: None Returns: None. Purpose: Transforms a list of points. Errors: None. ********************************************************************************************/ /* Technical notes: ********************************************************************************************/ void Matrix::transform(Coord pts[], UINT32 count ) const { if ( Type == TRANS_TRANSLATION ) { for (UINT32 i = 0; i < count; i++) { pts[i].x += e; pts[i].y += f; } } else { #ifdef OLD_MATRIX_TRANSFORMATIONS INT32 tx; // Holds INPUT value of pts[i].x for use in second MatrixCalc for (UINT32 i = 0; i < count; i++) { tx = pts[i].x; pts[i].x = MatrixCalc(a, tx, c, pts[i].y) + e; pts[i].y = MatrixCalc(b, tx, d, pts[i].y) + f; } #else // We use GDraw to transform the path // Alex asserts it is unimportant to switch contexts here as path transformations are not context // dependent; thus we call GDraw directly GMATRIX gmat; gmat.AX = a.GetShifted16(); gmat.AY = b.GetShifted16(); gmat.BX = c.GetShifted16(); gmat.BY = d.GetShifted16(); gmat.CX.SetHighLow(e>>16, e<<16); gmat.CY.SetHighLow(f>>16, f<<16); // Alex promises we don't need a context to do this op // XaDrawOld_TransformPath( (LPPOINT)pts, (LPPOINT)pts, count, &gmat ); GRenderRegion::GetStaticDrawContext()->TransformPath( (LPPOINT)pts, (LPPOINT)pts, count, &gmat ); #endif } } /******************************************************************************************** > void Matrix::transform(Coord pts[], const Coord input[], UINT32 count) Author: Mario_Shamtani (Xara Group Ltd) Created: 14/5/93 Inputs: input - points to be translated. count - number of points. Outputs: result points translated. Returns: None. Purpose: Transforms a list of points. Errors: None. ********************************************************************************************/ /* Technical notes: ********************************************************************************************/ void Matrix::transform(Coord pts[], const Coord input[], UINT32 count) const { if ( Type == TRANS_TRANSLATION ) { for (UINT32 i = 0; i < count; i++) { pts[i].x = input[i].x + e; pts[i].y = input[i].y + f; } } else { #ifdef OLD_MATRIX_TRANSFORMATIONS for (UINT32 i = 0; i < count; i++) { pts[i].x = MatrixCalc(a, input[i].x, c, input[i].y); pts[i].x += e; pts[i].y = MatrixCalc(b, input[i].x, d, input[i].y); pts[i].y += f; } #else // We use GDraw to transform the path // Alex asserts it is unimportant to switch contexts here as path transformations are not context // dependent; thus we call GDraw directly GMATRIX gmat; gmat.AX = a.GetShifted16(); gmat.AY = b.GetShifted16(); gmat.BX = c.GetShifted16(); gmat.BY = d.GetShifted16(); gmat.CX.SetHighLow(e>>16, e<<16); gmat.CY.SetHighLow(f>>16, f<<16); // Alex promises we don't need a context to do this op // XaDrawOld_TransformPath( (LPPOINT)input, (LPPOINT)pts, count, &gmat ); GRenderRegion::GetStaticDrawContext()->TransformPath( (LPPOINT)input, (LPPOINT)pts, count, &gmat ); #endif } } /******************************************************************************************** > void Matrix::transform(Coord* pt) Author: Mario_Shamtani (Xara Group Ltd) Created: 19/5/93 Inputs: Coordinate to be transformed. Outputs: Coordinate is overwritten with new values Returns: None. Purpose: Transforms a single point. Errors: None. ********************************************************************************************/ /* Technical notes: ********************************************************************************************/ void Matrix::transform(Coord* pt) const { INT32 tx; // Holds INPUT value of pt.x for use in second MatrixCalc! if ( Type == TRANS_TRANSLATION ) { pt->x += e; pt->y += f; } else { tx = pt->x; pt->x = MatrixCalc(a, tx, c, pt->y) + e; pt->y = MatrixCalc(b, tx, d, pt->y) + f; } } /******************************************************************************************** > Matrix& Matrix::operator=(const Matrix& rhs) Author: Mario_Shamtani (Xara Group Ltd) Created: 14/5/93 Inputs: rhs - matrix to be assigned. Outputs: None. Returns: None. Purpose: Matrix assignment. Errors: None. ********************************************************************************************/ Matrix& Matrix::operator=(const Matrix& rhs) { this->a = rhs.a; this->b = rhs.b; this->c = rhs.c; this->d = rhs.d; this->e = rhs.e; this->f = rhs.f; this->Type = rhs.Type; this->Angle = rhs.Angle; return *this; } /******************************************************************************************** > BOOL Matrix::operator==(const Matrix&) Author: Colin_Barfoot (Xara Group Ltd) Created: 26/01/97 Inputs: rhs: The right-hand side of the equality. Returns: TRUE if this Matrix is equal to rhs Purpose: Test for Matrix equality ********************************************************************************************/ BOOL Matrix::operator==(const Matrix& rhs) const { return (a == rhs.a && b == rhs.b && c == rhs.c && d == rhs.d && e == rhs.e && f == rhs.f); } /******************************************************************************************** > Matrix Matrix::operator*(const Matrix& operand1, const Matrix& operand2) Author: Ed_Cornes (Xara Group Ltd) Created: 5/2/96 Inputs: Two matrices to be multiplied. Returns: result of multiplication. Purpose: Matrix multiplication. ********************************************************************************************/ Matrix operator*(const Matrix& op1, const Matrix& op2) { static Matrix t; t = op1; t *= op2; // no point in duplicating code, just call *= operator return t; } /******************************************************************************************** > Matrix& Matrix::operator*=(const Matrix& op) Author: Ed_Cornes (Xara Group Ltd) Created: 5/2/96 Inputs: Matrix to be multiplied by. Returns: result of multiplication. Purpose: optimized *= operator for matrices Note: e,f are not optimised as MatrixCalc() has different round from explicit operations however, it does have optimisations for 0 an 1 ********************************************************************************************/ Matrix& Matrix::operator*=(const Matrix& op) { if (op.b==0 && op.c==0) { // it's just an x or y scaling ... if (op.a!=1) { a *= op.a; c *= op.a; } if (op.d!=1) { b *= op.d; d *= op.d; } } else { // it's the complex case ... FIXED16 t; t = a*op.a + b*op.c; b = a*op.b + b*op.d; a = t; t = c*op.a + d*op.c; d = c*op.b + d*op.d; c = t; } // either case requires these bits INT32 u; u = MatrixCalc(op.a, e, op.c, f) + op.e; f = MatrixCalc(op.b, e, op.d, f) + op.f; e = u; Type = TRANS_COMPLEX; Angle = (ANGLE)0; return *this; } /******************************************************************************************** > Matrix Matrix::Inverse() const Author: Rik_Heywood (Xara Group Ltd) Created: 2/11/93 Returns: The Inverse of this matrix Purpose: Inverts the 'this' matrix and returns the result. 'this' matrix is not effected ********************************************************************************************/ Matrix Matrix::Inverse() const { Matrix Temp; // Inverting a general matrix is quite an expensive operation, so we will try and // avoid having to do all the maths (esp as it can fail - some matrices do not have // an inverse! eg Scale by a factor of zero) switch ( Type ) { case TRANS_IDENTITY: // Inverse is the same as this ENSURE(a==FIXED16(1), "Matrix inconsistency!"); ENSURE(b==FIXED16(0), "Matrix inconsistency!"); ENSURE(c==FIXED16(0), "Matrix inconsistency!"); ENSURE(d==FIXED16(1), "Matrix inconsistency!"); ENSURE(e==0, "Matrix inconsistency!"); ENSURE(f==0, "Matrix inconsistency!"); return Temp; break; case TRANS_TRANSLATION : // Translation matrix - The inverse of this requires negating the x and y // components of the translation ENSURE(a==FIXED16(1), "Matrix inconsistency!"); ENSURE(b==FIXED16(0), "Matrix inconsistency!"); ENSURE(c==FIXED16(0), "Matrix inconsistency!"); ENSURE(d==FIXED16(1), "Matrix inconsistency!"); Temp.a = this->a; Temp.b = this->b; Temp.c = this->c; Temp.d = this->d; Temp.e = -this->e; Temp.f = -this->f; Temp.Type = this->Type; Temp.Angle = this->Angle; break; case TRANS_ROTATION: // The inverse of a rotation matrix is the Transpose of this matrix // ie components b and c are swapped ENSURE(e==0, "Matrix inconsistency!"); ENSURE(f==0, "Matrix inconsistency!"); Temp.a = this->a; Temp.b = this->c; Temp.c = this->b; Temp.d = this->d; Temp.e = this->e; Temp.f = this->f; Temp.Type = this->Type; Temp.Angle = -this->Angle; break; case TRANS_SCALE: // This can fail if one of the scale factors is 0. This will cause an ENSURE to fail // The inverse of a scale involves finding the inverse of the scale factors // ENSURE( this->a != 0, "Matrix Inversion failed - X scale factor was zero!" ); // ENSURE( this->d != 0, "Matrix Inversion failed - Y scale factor was zero!" ); if ((this->a==0) || (this->d==0)) { TRACE( _T("Matrix Inversion failed!\n")); // There is no inversion of this matrix // return the identity matrix return Temp; } ENSURE(e==0, "Matrix inconsistency!"); ENSURE(f==0, "Matrix inconsistency!"); Temp.a = 1/this->a; Temp.b = this->b; Temp.c = this->c; Temp.d = 1/this->d; Temp.e = this->e; Temp.f = this->f; Temp.Type = this->Type; Temp.Angle = this->Angle; break; case TRANS_SHEAR: case TRANS_COMPLEX: default: // This is the general case. It may be possible for this to fail. // I got the general idea for this from Graphics Gems page 766. // It did take 4 pages of c code in there, but it was a slightly // more complex solution. // Find the determinent of the Adjoint matrix - Luckily we can calculate // this before we calculate the Adjoint matrix as we know that the // right hand edge of the matrix is 0 0 1 double Det = a.MakeDouble()*d.MakeDouble() - b.MakeDouble()*c.MakeDouble(); if (Det==0.0) { // There is no inversion of this matrix // return the identity matrix // ENSURE( FALSE, "Matrix Inversion Failed - Tried to Invert a non-invertable matrix" ); TRACE( _T("Matrix Inversion failed!\n")); return Temp; } // this section calculates the inverse of the matrix. As it takes into // account that our 3x3 matrix always has 0,0,1 down its right hand side // it has been greatly simplified. The operations combine the calculation // of the adjoint matrix and scaling it by the Determinent with a matrix // transpose (the inverse is the transpose of the adjoint scaled by the // determinent) Temp.a = (FIXED16)(d.MakeDouble() / Det); Temp.b = (FIXED16)(-b.MakeDouble() / Det); Temp.c = (FIXED16)(-c.MakeDouble() / Det); Temp.d = (FIXED16)(a.MakeDouble() / Det); Temp.e = (INT32)( ((c.MakeDouble()*f)-(e*d.MakeDouble()))/Det ); Temp.f = (INT32)( -(((a.MakeDouble()*f) - (e*b.MakeDouble()))/Det)); Temp.Type = TRANS_COMPLEX; Temp.Angle = 0; break; } // return the inverted matrix back return Temp; } /******************************************************************************************** > void Matrix::GetComponents( FIXED16 *abcd, INT32 *ef ) const Author: Andy_Pennell (Xara Group Ltd) Created: 24/5/94 Inputs: - Outputs: abcd should point to FIXED16[4] which will get loaded with the a,b,c,d values from the matrix. Similarly ef should point to INT32[2] which will get loaded with the e and f values. If either pointer is NULL then nothing will get written for that type. Returns: - Purpose: Allows code to get to the elements in the matrix without having to be a friend of the class. Should be used very sparingly indeed. ********************************************************************************************/ void Matrix::GetComponents( FIXED16 *abcd, INT32 *ef) const { if (abcd) { abcd[0] = a; abcd[1] = b; abcd[2] = c; abcd[3] = d; } if (ef) { ef[0] = e; ef[1] = f; } } #ifdef _DEBUG void Matrix::Dump() const { // Display a matrix /*TRACE( _T("| %+5.3f %+5.3f %+5.3f |\n"), a.MakeDouble(), b.MakeDouble(), 0.0 ); TRACE( _T("| %+5.3f %+5.3f %+5.3f |\n"), c.MakeDouble(), d.MakeDouble(), 0.0 ); TRACE( _T("| %+5.3f %+5.3f %+5.3f |\n\n"), (double)e, (double)f, 1.0 );*/ TRACE( _T("| %f %f %f |\n"), a.MakeDouble(), b.MakeDouble(), 0.0 ); TRACE( _T("| %f %f %f |\n"), c.MakeDouble(), d.MakeDouble(), 0.0 ); TRACE( _T("| %f %f %f |\n\n"), (double)e, (double)f, 1.0 ); } #endif /******************************************************************************************** > void Matrix::translate(const INT32 TransX, const INT32 TransY); Author: Ed_Cornes (Xara Group Ltd) Created: 17/4/95 Inputs: TransX, TransY - translations to apply Purpose: QUICKLY add a translation to a matrix! ********************************************************************************************/ void Matrix::translate(const INT32 TransX, const INT32 TransY) { e+=TransX; f+=TransY; } void Matrix::GetTranslation(DocCoord& coord) const { coord.x = e; coord.y = f; } void Matrix::GetTranslation(INT32& xlate,INT32& ylate) const { xlate=e; ylate=f; } void Matrix::SetTranslation(const DocCoord& coord) { e = coord.x; f = coord.y; if (Type!=TRANS_TRANSLATION) Type = TRANS_COMPLEX; } void Matrix::SetTranslation(const INT32& xlate, const INT32& ylate) { e = xlate; f = ylate; if (Type!=TRANS_TRANSLATION) Type = TRANS_COMPLEX; } /******************************************************************************************** > virtual BOOL Matrix::Decompose(FIXED16* pScale=NULL, FIXED16* pAspect=NULL, ANGLE* pRotation=NULL, ANGLE* pShear=NULL, Coord* pTranslate=NULL, FIXED16* pScaleY=NULL); Author: Ed_Cornes (Xara Group Ltd) Created: 7/4/95 Outputs: pScale - if not NULL, outputs scale (-ve indicates mirror Y) pAspect - if not NULL, outputs aspect pRotation - if not NULL, outputs rotation (-PI..PI) pShear - if not NULL, outputs sheer (-PI/2..PI/2) pTranslate - if not NULL, outputs translation Returns: FALSE if falis Purpose: Decompose a matrix into its component transforms ********************************************************************************************/ BOOL Matrix::Decompose(FIXED16* pScale, FIXED16* pAspect, ANGLE* pRotation, ANGLE* pShear, Coord* pTranslate, FIXED16* pScaleY) { // unit square decomposition of matrix ... // A={a,c}, B={b,d}, Q=angle between vectors (ie PI/2-shear angle)! // |A|=sqrt(aa+cc) // |B|=sqrt(bb+dd) // AxB=|A||B|sinQ=ad-bc=(new area of unit square) // A.B=|A||B|cosQ=ab+cd // so ... // ScaleX = |A| // ScaleY = |B| // Scale = ScaleY*cos(PI/2-Q) = ScaleY*sinQ = AxB/|A| // AbsScale = abs(Scale) // effectively remove any mirror // Aspect = ScaleX/abs(Scale) // Shear = PI/2-Q = PI/2-acos((A.B)/(|A||B|)) // Rotate = RotateX = atan2(c,a) double a=this->a.MakeDouble(); double b=this->c.MakeDouble(); // 'cos I think of coords as column vectors not rows! double c=this->b.MakeDouble(); double d=this->d.MakeDouble(); // get cross product (determinant), modulus (length) of A and scale double AxB = a*d-b*c; double ModA = sqrt(a*a+c*c); double ModB = sqrt(b*b+d*d); double Scale = AxB/ModA; // set output values where required if (pTranslate) *pTranslate = Coord(this->e,this->f); if (pScale) *pScale = Scale; if (pAspect) if (Scale==0) *pAspect = (FIXED16)1; else *pAspect = ModA/fabs(Scale); if (pRotation) *pRotation = (ANGLE)atan2(c,a); if (pShear) { double AdotB = a*b+c*d; *pShear = (ANGLE)(PI/2-acos(AdotB/(ModB*ModA))); } if (pScaleY) *pScaleY = ModB; // TRACE( _T("\n")); // TRACE( _T("Scale = %f\n"),Scale); // TRACE( _T("Aspect = %f\n"),ModA/fabs(Scale)); // TRACE( _T("Rotate = %f\n"),atan2(c,a)/PI*180); // double AdotB = a*b+c*d; // double ModB = sqrt(b*b+d*d); // TRACE( _T("Shear = %f\n"),(PI/2-acos(AdotB/(ModB*ModA)))/PI*180); return TRUE; } /******************************************************************************************** > virtual BOOL Matrix::Compose(FIXED16 Scale=1.0, FIXED16 Aspect=1.0, ANGLE Rotation=0, ANGLE Shear=0, Coord Translation=Coord(0,0)); Author: Ed_Cornes (Xara Group Ltd) Created: 7/4/95 Inputs: Scale - Aspect - Rotation - Shear - Translation - Returns: FALSE if falis Purpose: Compose a matrix from its components ********************************************************************************************/ BOOL Matrix::Compose(FIXED16 Scale, FIXED16 Aspect, ANGLE Rotation, ANGLE Shear, Coord Translation) { // do scale, aspect and shear FIXED16 AbsScale = (Scale<0) ? -Scale : Scale; *this=Matrix(AbsScale*Aspect,0,Scale*tan(Shear.MakeDouble()),Scale,0,0); // do rotate *this*=Matrix((Rotation*180)/PI); // god damned amatuers using degrees! // translate this->translate(Translation.x,Translation.y); return TRUE; } /******************************************************************************************** > BOOL Matrix::TransformBounds(DocRect* pRect) const Author: Ed_Cornes (Xara Group Ltd) Created: 23/4/95 Inputs: pRect - rect to transform Outputs: pRect - transformed rect Returns: FALSE if fails Purpose: Given a rect, find its bounds when transformed ********************************************************************************************/ BOOL Matrix::TransformBounds(DocRect* pRect) const { ERROR2IF(pRect==NULL,FALSE,"Matrix::Transform() - pRect==NULL"); static DocCoord coords[4]; coords[0] = pRect->lo; coords[1] = DocCoord(pRect->hi.x,pRect->lo.y); coords[2] = pRect->hi; coords[3] = DocCoord(pRect->lo.x,pRect->hi.y); this->transform((Coord*)&coords,4); *pRect = DocRect(coords[0],coords[0]); pRect->IncludePoint(coords[1]); pRect->IncludePoint(coords[2]); pRect->IncludePoint(coords[3]); return TRUE; } /******************************************************************************************** > Matrix::Matrix(const Coord& CentreOfRotation, const ANGLE& Rotation) Author: Rik_Heywood (Xara Group Ltd) Created: 7/3/94 Inputs: CentreOfRot - The centre of rotation RotateBy - The angle of the rotation that this transform object will perform Purpose: Creates a Matrix transform object that will perform a rotation about the given point by the given number of degrees. ********************************************************************************************/ Matrix::Matrix(const Coord& CentreOfRotation, const ANGLE& Rotation) { // need to translate the centre of rotation to the origin *this = Matrix(-CentreOfRotation.x, -CentreOfRotation.y); // rotate about the origin *this *= Matrix(Rotation); // translate back to the centre of rotation this->translate(CentreOfRotation.x, CentreOfRotation.y); Type = TRANS_COMPLEX; Angle = (ANGLE)0; } /******************************************************************************************** > BOOL Matrix::IsTranslation(double epsilon=0.0) const Author: Mike_Kenny (Xara Group Ltd) Created: 11/01/96 Inputs: epsilon - an error threshold to use against the scaling components Returns: TRUE if this matrix is a translation matrix FALSE if not Purpose: Examine this matrix and determin whether it is a translation matrix or not. This function is needed as within the code there are areas where complex matricees are built which are really translation matricees. The function will return immediately with TRUE if the matrix type is Translation otherwise the function will actually check the matrix values for translation type See also: IsRotation(), IsReflection(). ********************************************************************************************/ BOOL Matrix::IsTranslation(const double epsilon) const { if (Type==TRANS_TRANSLATION) return TRUE; if (epsilon==0.0) { FIXED16 c0,c1,c2,c3; c0 = (FIXED16)1; c1 = (FIXED16)0; c2 = (FIXED16)0; c3 = (FIXED16)1; return ((a==c0) && (b==c1) && (c==c2) && (d==c3)); } double c0,c1,c2,c3; c0=a.MakeDouble(); c1=b.MakeDouble(); c2=c.MakeDouble(); c3=d.MakeDouble(); return ( (fabs(c0-1.0) BOOL Matrix::IsRotation(const double epsilon = 0.0) const Author: Karim_MacDonald (Xara Group Ltd) Created: 16/11/2000 Inputs: epsilon optional error threshold to use in our calculations. Returns: TRUE if we're a rotation matrix, FALSE otherwise. Purpose: Determine whether this matrix is a rotation matrix or not. See also: IsTranslation(), IsReflection(). ********************************************************************************************/ BOOL Matrix::IsRotation(const double epsilon) const { if (Type == TRANS_ROTATION) return TRUE; // Note: Camelot matrices swap the b & c components, so the form is: // // (a c) , instead of the more familiar (a b) . // (b d) (c d) // // So tests for rotation are: // 1. a = d // 2. b = -c // 3. |(a b)| = |(c d)| = 1. if (epsilon == 0.0) { if (a == d && b == -c) { FIXED16 aa = (a * a); FIXED16 bb = (b * b); FIXED16 ab = aa + bb; if (ab == FIXED16(1)) return TRUE; } } else { double c0,c1,c2,c3; c0 = a.MakeDouble(); c1 = b.MakeDouble(); c2 = c.MakeDouble(); c3 = d.MakeDouble(); if (fabs(c0 - c3) < epsilon && fabs(c1 + c2) < epsilon) { double ab = (c0 * c0) + (c1 * c1); if (fabs(ab - 1.0) < epsilon) return TRUE; } } return FALSE; } /******************************************************************************************** > BOOL Matrix::IsReflection(const double epsilon = 0.0) const Author: Karim_MacDonald (Xara Group Ltd) Created: 16/11/2000 Inputs: epsilon optional error threshold to use in our calculations. Returns: TRUE if we're a reflection matrix, FALSE otherwise. Purpose: Determine whether this matrix is a reflection matrix or not. Notes: In the interests of speed, this test is INCOMPLETE - we're *only* testing for x- or y- axis reflections (no, not both together either). Feel free to modify this method to test for all reflections, but first make sure you won't break any code which uses it (at time of writing, you're ok). See also: IsTranslation(), IsRotation(). ********************************************************************************************/ BOOL Matrix::IsReflection(const double epsilon) const { // A Camelot matrix of the form: (a c) , is an x- or y- reflection if: // (b d) // 1. b = c = 0. // 2. a = -d. // 3. |a| = 1. if (epsilon == 0.0) { if (b == c && c == FIXED16(0) && a == -d && (a == FIXED16(1) || a == FIXED16(-1)) ) return TRUE; } else { double c0,c1,c2,c3; c0 = a.MakeDouble(); c1 = b.MakeDouble(); c2 = c.MakeDouble(); c3 = d.MakeDouble(); if (fabs(c1) < epsilon && fabs(c2) < epsilon && fabs(c0 + c3) < epsilon && (fabs(c0 - 1.0) < epsilon || fabs(c0 + 1.0) < epsilon) ) return TRUE; } return FALSE; } /******************************************************************************************** > BOOL IsNear(const T& x, const T& value, const T& Tolerance) Author: Colin_Barfoot (Xara Group Ltd) Created: 28/08/96 Inputs: x1: The first of the two values to compare x2: The second of the two values to compare Tolerance: The value within which x1 is considered to be near to x2 Returns: TRUE if the two values have an absolute difference greater than or equal to the given Tolerance FALSE otherwise Purpose: Support function for IsIsometric Determines whether two values are near enough to each other to be considered equal. Notes: This is a templated function with ********************************************************************************************/ /* template inline BOOL IsNear(const T& x1, const T& x2, const T& Tolerance) { return (ABS(x1 - x2) <= Tolerance) ? TRUE : FALSE; } */ /******************************************************************************************** > BOOL Matrix::IsIdentity(DOUBLEPARAM epsilon) const Author: Colin_Barfoot (Xara Group Ltd) Created: 26/01/97 Returns: TRUE if this matrix is an identity matrix FALSE if not Purpose: Determines whether or not this matrix is an identity matrix. Used by the MOA interface, XMOAFhMtx::FHIMtxIsUnity. ********************************************************************************************/ BOOL Matrix::IsIdentity(/*DOUBLEPARAM epsilon*/) const { BOOL bIsIdentity = FALSE; if (Type == TRANS_IDENTITY) { bIsIdentity = TRUE; } if (Type != TRANS_IDENTITY) // && epsilon == 0.0) { const FIXED16 c0 = FIXED16(0); const FIXED16 c1 = FIXED16(1); bIsIdentity = ( (a == c1) && (b == c0) && (c == c0) && (d == c1) && (e == 0) && (f == 0)); } /* if (Type != TRANS_IDENTITY && epsilon != 0.0) { const double ca = a.MakeDouble(); const double cb = b.MakeDouble(); const double cc = c.MakeDouble(); const double cd = d.MakeDouble(); const double ce = e.MakeDouble(); const double cf = f.MakeDouble(); bIsIdentity = ( IsNear(ca, 1.0, epsilon) && IsNear(cb, 1.0, epsilon) && IsNear(cc, 1.0, epsilon) && IsNear(cd, 1.0, epsilon) && IsNear(ce, 1.0, epsilon) && IsNear(cf, 1.0, epsilon)) }*/ return bIsIdentity; } /******************************************************************************************** > static Matrix Matrix::CreateTransMatrix(const Coord& dcTrans) Author: Karim_MacDonald (Xara Group Ltd) Created: 23-07-2000 Inputs: dcTrans the translation to perform. Returns: A translation matrix. Purpose: Factory method - maps directly onto the translation Matrix constructor. ********************************************************************************************/ Matrix Matrix::CreateTransMatrix(const Coord& dcTrans) { return Matrix(dcTrans); } /******************************************************************************************** > static Matrix Matrix::CreateScaleMatrix(const double& xScale, const double& yScale) Author: Karim_MacDonald (Xara Group Ltd) Created: 23-07-2000 Inputs: xScale the x-scaling to perform. yScale the y-scaling to perform. Returns: A scaling matrix. Purpose: Factory method - maps directly onto the scaling Matrix constructor. ********************************************************************************************/ Matrix Matrix::CreateScaleMatrix(const double& xScale, const double& yScale) { return Matrix(FIXED16(xScale), FIXED16(yScale)); } /******************************************************************************************** > static Matrix Matrix::CreateScaleMatrix(const double& ScaleFactor) Author: Karim_MacDonald (Xara Group Ltd) Created: 23-07-2000 Inputs: ScaleFactor the uniform scaling to perform. Returns: A scaling matrix. Purpose: Factory method - maps directly onto the scaling Matrix constructor. ********************************************************************************************/ Matrix Matrix::CreateScaleMatrix(const double& ScaleFactor) { return Matrix(FIXED16(ScaleFactor), FIXED16(ScaleFactor)); } /******************************************************************************************** > static Matrix Matrix::CreateShearMatrix(const double& ShearAngle) Author: Karim_MacDonald (Xara Group Ltd) Created: 23-07-2000 Inputs: ShearAngle the angle clockwise from vertical to shear by, in radians. Returns: A shearing matrix. Purpose: Factory method - maps directly onto the 'complex' Matrix constructor. ********************************************************************************************/ Matrix Matrix::CreateShearMatrix(const double& ShearAngle) { FIXED16 one = FIXED16(1); FIXED16 zero = FIXED16(0); FIXED16 shear = FIXED16(tan(ShearAngle)); return Matrix( one, zero, shear, one, 0, 0 ); } /******************************************************************************************** > static Matrix Matrix::CreateRotateMatrix(const double& RotateAngle) Author: Karim_MacDonald (Xara Group Ltd) Created: 23-07-2000 Inputs: RotateAngle the angle to rotate anti-clockwise by, in radians. Returns: A rotation matrix. Purpose: Factory method - maps directly onto the rotation Matrix constructor. Notes: Karim 06/09/2001 Method now in use & fixed bug so input is processed as radians, not degrees. ********************************************************************************************/ Matrix Matrix::CreateRotateMatrix(const double& RotateAngle) { return Matrix(FIXED16(DDegrees(RotateAngle))); } /******************************************************************************************** > double Matrix::Trace() const Author: Karim_MacDonald (Xara Group Ltd) Created: 04 September 2000 Returns: The trace of this matrix (the sum of the elements on its leading diagonal). Notes: Trace(Rotation matrix) = 2cos(theta), where theta is the angle of rotation. (Usually 1+2cos(theta), but these are 3x2 matrices). ********************************************************************************************/ double Matrix::Trace() const { return (a.MakeDouble() + d.MakeDouble()); } /******************************************************************************************** > double Matrix::Det() const Author: Karim_MacDonald (Xara Group Ltd) Created: 04 September 2000 Returns: The determinant of this matrix: (ad - bc) if matrix is ((a b),(c d)). Notes: Det(Matrix) = area scale factor on application of the matrix. ********************************************************************************************/ double Matrix::Det() const { return ( (a.MakeDouble() * d.MakeDouble()) - (b.MakeDouble() * c.MakeDouble()) ); } /******************************************************************************************** > void Matrix::RatioMatrix (const double ratio) Author: Chris_Snook (Xara Group Ltd) Created: ???? Returns: This routine ratios *this* matrix. For example, if we have a scaling matrix and ratio is 0.5, then we get a scaling matrix that has half the effect of the original matrix. Do NOT go changing this routine !!!!!! It is fundamental to the entire animation system - and an error will break everything !!!!!!! You should get CGS to change this routine if needs be .... This routine cannot be used to ratio a rotation matrix. This is because the elements in a rotation matrix are cos & sin of the quantity which must be interpolated (the angle), and these are non-linear functions. ********************************************************************************************/ void Matrix::RatioMatrix (const double ratio) { static Matrix Identity; *this = Identity.Interpolate(*this, ratio); } /******************************************************************************************** > Matrix Matrix::Interpolate(const Matrix& op, const double ratio) const Author: Karim_MacDonald (Xara Group Ltd) Created: 25/09/2001 Inputs: op other matrix to interpolate to. ratio interpolation ratio. Returns: Interpolated matrix. Purpose: Interpolate between this and another matrix. ********************************************************************************************/ Matrix Matrix::Interpolate(const Matrix& op, const double ratio) const { // Essential: 0 <= ratio <= 1. if (ratio < 0 || ratio > 1 || Type == TRANS_ROTATION || op.Type == TRANS_ROTATION) { ERROR3("Interpolate() requires 0 <= ratio <= 1 and no rotation matrices."); return Matrix(); } else if (ratio == 0) return *this; else if (ratio == 1) return op; else { const FIXED16 f16Ratio (ratio); const FIXED16 f16InvRatio (1 - ratio); Matrix temp; temp.a = a * f16InvRatio + op.a * f16Ratio; temp.b = b * f16InvRatio + op.b * f16Ratio; temp.c = c * f16InvRatio + op.c * f16Ratio; temp.d = d * f16InvRatio + op.d * f16Ratio; temp.e = e * (INT32)((1 - ratio) + op.e * ratio); temp.f = f * (INT32)((1 - ratio) + op.f * ratio); temp.Type = TRANS_COMPLEX; temp.Angle = 0; return temp; } } /******************************************************************************************** > Matrix Matrix::PostRotate(DocCoord dcCentre, const ANGLE& Angle) const Author: Karim_MacDonald (Xara Group Ltd) Created: 04/10/2001 Inputs: dcCentre centre of rotation *before* it is transformed by this matrix. Angle angle of rotation, anti-clockwise in degrees. Returns: A matrix which will apply this transformation followed by a rotation of Angle around the transformed centre. Purpose: Allows you to transform an object, then rotate it about some point relative to its original self, which has now been transformed. eg: squash an object, then rotate it around its centre. You can't rotate-then-squash, as you will lose the right-angles. Nor should you rotate about its new centre, as this may be different to the transformed version of its old centre. ********************************************************************************************/ Matrix Matrix::PostRotate(DocCoord dcCentre, const ANGLE& Angle) const { transform(&dcCentre); return (*this) * Matrix(dcCentre, Angle); }