One of commonly used ways of representing affine transformations such as rotations and translations in geometric software is utilizing of 4×4 matrices. The main problem with matrices is that they are often confusing both to newbie and even to experienced programmers. And there is an objective reason behind this. A matrix is a subject of analytical algebra, while geometric software deals with geometric scope. If you use low level constructs such as matrices math in your high level logic do not be surprised your code becomes complicated.
I call this situation a scope violation. In my opinion a good code should look like a SADT/IDEF0 diagram. Every subroutine like an IDEF0 block generates some output data from an input data based on its business logic. There is a rule in IDEF0 that the decomposition of a block can not have more than 7 other blocks. This restriction guarantees that every diagram is consistent in respect to its scope, i.e. every activity is described on the same level of detail. Using algebra matrices in your geometric code is like trying to insert a low level block into a top level IDEF0 diagram. Obviously this is wrong.
So, what geometric sense does a 4×4 transformation matrix have? From the geometric point of view a 4×4 transformation matrix is just a local coordinate system with its origin, X, Y, and Z axes. You might be surprised but a transformation matrix can be easily represented with the following structure.
T3DMatrix = array[1..4,1..4] of STFloat; THmgMatrix3d = record case byte of 0: (vX : T3DPoint; A : STFloat; vY : T3DPoint; B : STFloat; vZ : T3DPoint; C : STFloat; vT : T3DPoint; D : STFloat); 1: (M : T3DMatrix); end;
Here vX, vY, vZ are directions of the local coordinate system axes, vT is the origin of the LCS. A, B, C are some stupid perspective transformation coefficients. They are used mainly in computer graphics, in CAD/CAM applications they are always equal to zero. The D coefficient is the scale coefficient. In geometric software it always equals to 1. Furthermore in geometric software in 99.99% of cases only orthogonal coordinate systems are used. This is enough to represent such transformations as translations, rotations, mirroring and the combinations.
All operations involving points, vectors and matrices also have a simple geometric sense. By multiplying a point onto a matrix we just compute coordinates of a point defined in a local coordinate system, in the global coordinate system. So we should replace the function Point_x_Matrix(const p: T3DPoint; const M: T3DMatrix) with the method TransformPoint of our THmgMatrix3d record.
function THmgMatrix3d.TransformPoint(const lp: T3DPoint): T3DPoint; begin result := lp.x*self.vX+ lp.y*self.vY+ lp.z*self.vZ+ self.vT; end;
Analogously multiplication of a vector onto a matrix is just computing of a global vector.
function THmgMatrix3d.TransformVector(const lv: T3DPoint): T3DPoint; begin result := lv.x*self.vX+ lv.y*self.vY+ lv.z*self.vZ; end;
Multiplication of two matrices is the most confusing operation, because the result of multiplication varies of the order of multiplication and it’s easy to get matrices multiplied a wrong order. However from the geometric point of view multiplication of two matrices is just computation of a LCS2 defined in the LCS1 in the global coordinate system. So by replacing the function Matr_x_Matr(const M1, M2: T3DMatrix): T3DMatrix with the method THmgMatrix3d.TransformMatrix we once for all eliminate all misunderstandings.
function THmgMatrix3d.TransformMatrix(const lm: THmgMatrix3d): THmgMatrix3d; begin result.Init; reuslt.vX := self.TransformVector(lm.vX); result.vY := self.TransformVector(lm.vY); result.vZ := self.TransformVector(lm.vZ); result.vT := self.TransformPoint(lm.vT); end;
Even more misunderstandings introduce inverse matrices. Whilst a matrix itself has a simple geometric sense, an inverse matrix has no geometric sense at all. From the linear algebra point of view this is just a solution of a system of 4 linear equations. However the use of inverse matrices has an easily understandable geometric sense. Multiplication of a point, a vector or another matrix onto an inverse matrix results into computation of a point, a vector, or an lcs defined in the global coordinate system in the local coordinate system, the matrix of which we have inverted.
Fortunately the 99.99% of matrices used in geometric software are orthogonal so we can easily compute local vectors, points, and coordinate systems without computation of an inverse matrix.
function THmgMatrix3d.GetLocalVector(const gv: T3DPoint): T3DPoint; begin result.x := Vec_mul_Vec(gv, self.vX); result.y := Vec_mul_Vec(gv, self.vY); result.z := Vec_mul_Vec(gv, self.vZ); end; function THmgMatrix3d.GetLocalPoint(const gp: T3DPoint): T3DPoint; begin result := GetLocalVector(gp-self.vT); end; function THmgMatrix3d.GetLocalMatrix(const gm: THmgMatrix3d): THmgMatrix3d; begin result.Init; reuslt.vX := self.GetLocalVector(gm.vX); result.vY := self.GetLocalVector(gm.vY); result.vZ := self.GetLocalVector(gm.vZ); result.vT := self.GetLocalPoint(gm.vT); end;
function Vec_mul_Vec(const v1, v2: T3DPoint): T3DPoint; begin result := v1.x*v2.x+v1.y*v2.y+v1.z*v2.z; end;
So, by replacing all those messy T3DMatrix, Matr_x_Matr and InverseMatrix functions with THmgMatrix3d you can avoid scope violations and thus make your code much more easy to read and understand.