-- File: SVMatrix2dImpl.mesa
-- Last edited by Bier on December 18, 1982 1:10 am
-- Author: Eric Bier on November 18, 1982 8:40 pm
-- Contents: My own package for manipulating Homogenous 3 x 3 matrices. Parallels Matrix3d in many places

DIRECTORY
 RealFns,
 SV2d,
 SVMatrix2d,
 SVVector2d;

SVMatrix2dImpl: PROGRAM
IMPORTS RealFns, SVVector2d
EXPORTS SVMatrix2d =
BEGIN

Matrix3by3: TYPE = ARRAY [1..3] OF ARRAY [1..3] OF REAL;
Matrix2by2: TYPE = ARRAY [1..2] OF ARRAY [1..2] OF REAL;
Point2d: TYPE = SV2d.Point2d;
Vector2d: TYPE = SVVector2d.Vector2d;

Identity: PUBLIC PROC [] RETURNS [identityMat: Matrix3by3] = {
FOR i: NAT IN [1..3] DO
  FOR j: NAT IN [1..3] DO
   identityMat[i][j] ← IF i = j THEN 1 ELSE 0;
  ENDLOOP;
ENDLOOP;
 };

Update: PUBLIC PROC [mat: Matrix3by3, point: Point2d] RETURNS [newPoint: Point2d] = {
--  | x' |  | (1,1) (1,2) (1,3) /  / x /
--  | y' | = | (2,1) (2,2) (2,3) /  / y /
--  | 1 |  | 0 0 1 /  / 1 /
FOR i: NAT IN [1..2] DO
  newPoint[i] ← mat[i][1]*point[1] + mat[i][2]*point[2] + mat[i][3];
ENDLOOP;
 };

UpdateVector: PUBLIC PROC [mat: Matrix3by3, vec: Vector2d] RETURNS [newVec: Vector2d] = {
-- vectors can be rotated and scaled, but not translated
FOR i: NAT IN [1..2] DO
  newVec[i] ← mat[i][1]*vec[1] + mat[i][2]*vec[2];
ENDLOOP;
 };

-- The next three procedures perform an efficient matrix multiplication as though mat were being multiplied to its left (a transform in reference coordinates) by a matrix which translates or rotates respectively (See Paul).
Scale: PUBLIC PROC [mat: Matrix3by3, sx, sy: REAL] RETURNS [transMat: Matrix3by3] = {
FOR j: NAT IN[1..3] DO
  transMat[1][j] ← mat[1][j]*sx;
ENDLOOP;
FOR j: NAT IN[1..3] DO
  transMat[2][j] ← mat[2][j]*sy;
ENDLOOP;
 transMat[3][1] ← transMat[3][2] ← 0.0;
 transMat[3][3] ← 1.0;
 };

Translate: PUBLIC PROC [mat: Matrix3by3, dx, dy: REAL] RETURNS [transMat: Matrix3by3] = {

i, j: CARDINAL;
FOR i IN [1..3] DO-- in the first two columns, the elements are copied
FOR j IN [1..2] DO
  transMat[i][j] ← mat[i][j];
ENDLOOP;
ENDLOOP;
transMat[1][3] ← mat[1][3] + dx; -- add the translation offset to the last row
transMat[2][3] ← mat[2][3] + dy;
transMat[3][3] ← 1.0;
};

RotateCCW: PUBLIC PROC [mat: Matrix3by3, degrees: REAL] RETURNS [transMat: Matrix3by3] = {
 sind: REAL ← RealFns.SinDeg[degrees];
 cosd: REAL ← RealFns.CosDeg[degrees];
 rotMat: Matrix3by3 ← Identity[];
 rotMat[1][1] ← rotMat[2][2] ← cosd;
 rotMat[1][2] ← -sind; rotMat[2][1] ← sind;
 transMat ← MatMult[rotMat, mat];
 };

-- The next three procedures perform an efficient matrix multiplication as though mat were being multiplied on its right (a transform in local coordinates) by a matrix which translates or rotates respectively (See Paul).
LocalScale: PUBLIC PROC [mat: Matrix3by3, sx, sy: REAL] RETURNS [transMat: Matrix3by3] = {
FOR i: NAT IN[1..2] DO
  transMat[i][1] ← mat[i][1]*sx;
ENDLOOP;
FOR i: NAT IN[1..2] DO
  transMat[i][2] ← mat[i][2]*sy;
ENDLOOP;
FOR i: NAT IN[1..2] DO
  transMat[i][3] ← mat[i][3];
ENDLOOP;
 transMat[3][1] ← transMat[3][2] ← 0.0;
 transMat[3][3] ← 1.0;
 };

LocalTranslate: PUBLIC PROC [mat: Matrix3by3, dx, dy: REAL] RETURNS [transMat: Matrix3by3] = {
 i, j: CARDINAL;
FOR i IN [1..3] DO-- in the first two columns, the elements are copied
  FOR j IN [1..2] DO
   transMat[i][j] ← mat[i][j];
  ENDLOOP;
ENDLOOP;
 transMat[1][3] ← mat[1][1]*dx + mat[1][2]*dy + mat[1][3];
 transMat[2][3] ← mat[2][1]*dx + mat[2][2]*dy + mat[2][3];
 transMat[3][3] ← 1.0;
 };

LocalRotateCCW: PUBLIC PROC [mat: Matrix3by3, degrees: REAL] RETURNS [transMat: Matrix3by3] = {
 sind: REAL ← RealFns.SinDeg[degrees];
 cosd: REAL ← RealFns.CosDeg[degrees];
 rotMat: Matrix3by3 ← Identity[];
 rotMat[1][1] ← rotMat[2][2] ← cosd;
 rotMat[1][2] ← -sind; rotMat[2][1] ← sind;
 transMat ← MatMult[mat, rotMat];
 };


MatMult: PUBLIC PROC [left, right: Matrix3by3] RETURNS [transMat: Matrix3by3] = {
-- MatMult is almost a general 3 by 3 matrix operation. However, it assumes that the last row of each matrix is [0 0 1].

 i, j: CARDINAL;
FOR i IN [1..2] DO  -- The last row is always [0 0 1]
  FOR j IN [1..2] DO-- In the first two columns, the last element is always zero
   transMat[i][j] ← left[i][1]*right[1][j] + left[i][2]*right[2][j];
  ENDLOOP;
ENDLOOP;
FOR i IN [1..2] DO-- calculate the last column
  transMat[i][3] ← left[i][1]*right[1][3] + left[i][2]*right[2][3] + left[i][3];
ENDLOOP;
 transMat[3][1] ← transMat[3][2] ← 0; -- Last row
 transMat[3][3] ← 1.0;
 };

MakeScaleMat: PUBLIC PROC [sx, sy: REAL] RETURNS [scale: Matrix3by3] = {
 scale ← Identity[];
 scale[1][1] ← sx; scale[2][2] ← sy;
 };

MakeTranslateMat: PUBLIC PROC [dx, dy: REAL] RETURNS [trans: Matrix3by3] = {
 trans ← Identity[];
 trans[1][3] ← dx; trans[2][3] ← dy;
 };

MakeRotateCCWMat: PUBLIC PROC [degrees: REAL] RETURNS [rot: Matrix3by3] = {
 sind: REAL ← RealFns.SinDeg[degrees];
 cosd: REAL ← RealFns.CosDeg[degrees];
 rot ← Identity[];
 rot[1][1] ← rot[2][2] ← cosd;
 rot[1][2] ← -sind; rot[2][1] ← sind;
 };

MakeMatFromAxes: PUBLIC PROC [xAxis, yAxis: Vector2d, origin: Point2d] RETURNS [mat: Matrix3by3] = {
 FOR i: NAT IN[1..2] DO
  mat[i][1] ← xAxis[i];
  mat[i][2] ← yAxis[i];
  mat[i][3] ← origin[i];
 ENDLOOP;
 mat[3][1] ← mat[3][2] ← 0; mat[3][3] ← 1;
 };


ScaleFromMatrix: PUBLIC PROC [mat: Matrix3by3] RETURNS [sx, sy: REAL] = {
 sx ← SVVector2d.Magnitude[[mat[1][1],mat[2][1]]];
 sy ← SVVector2d.Magnitude[[mat[1][2],mat[2][2]]];
-- finds out how much this matrix will scale an object it is applied to
 };

OriginOfMatrix: PUBLIC PROC [mat: Matrix3by3] RETURNS [origin: Point2d] = {
 origin[1] ← mat[1][3];
 origin[2] ← mat[2][3];
 };

XAxisOfMatrix: PUBLIC PROC [mat: Matrix3by3] RETURNS [xAxis: Vector2d] = {
 xAxis[1] ← mat[1][1];
 xAxis[2] ← mat[2][1];
 };

YAxisOfMatrix: PUBLIC PROC [mat: Matrix3by3] RETURNS [yAxis: Vector2d] = {
 yAxis[1] ← mat[1][2];
 yAxis[2] ← mat[2][2];
 };

RotationOfMatrix: PUBLIC PROC [mat: Matrix3by3] RETURNS [degrees: REAL] = {
-- take the dot product of normalized xAxis with [1,0]. This should be cosine.
-- take the dot product of normalized yAxis with [1,0]. This should be - sine.
-- Find the arctan.
 cos, sin: REAL;
 xAxis, yAxis: Vector2d;
 xAxis ← XAxisOfMatrix[mat];
 yAxis ← YAxisOfMatrix[mat];
 xAxis ← SVVector2d.Normalize[xAxis];
 yAxis ← SVVector2d.Normalize[yAxis];
 cos ← SVVector2d.DotProduct[xAxis, [1,0]];
 sin ← - SVVector2d.DotProduct[yAxis, [1,0]];
 degrees ← RealFns.ArcTanDeg[sin, cos];
 };

Determinant: PUBLIC PROC [mat: Matrix3by3] RETURNS [det: REAL] = {
 det ← mat[1][1]*mat[2][2]*mat[3][3] +
   mat[2][1]*mat[3][2]*mat[1][3] +
   mat[3][1]*mat[1][2]*mat[2][3] -
   (mat[3][1]*mat[2][2]*mat[1][3] +
   mat[2][1]*mat[1][2]*mat[3][3] +
   mat[1][1]*mat[3][2]*mat[2][3]);
 };

Determinant2by2: PRIVATE PROC [mat: Matrix2by2] RETURNS [det: REAL] = {
 det ← mat[1][1]*mat[2][2] - mat[2][1]*mat[1][2];
 };

Transpose: PUBLIC PROC [mat: Matrix3by3] RETURNS [matT: Matrix3by3] = {
-- interchange rows and columns
FOR i: NAT IN[1..3] DO
  FOR j: NAT IN[1..3] DO
   matT[i][j] ← mat[j][i];
  ENDLOOP;
ENDLOOP;
 };


Cofactor: PUBLIC PROC [mat: Matrix3by3, row, col: NAT] RETURNS [cof: REAL] = {
-- returns the determinant of the 2 by 2 matrix which results by crossing off row row and column col in mat.
 rowCount, colCount: NAT;
 exponent: NAT;
 m2: Matrix2by2;
 rowCount ← 0;
FOR i: NAT IN[1..3] DO
  IF i = row THEN LOOP;
  rowCount ← rowCount + 1;
  colCount ← 0;
  FOR j: NAT IN[1..3] DO
   IF j = col THEN LOOP;
   colCount ← colCount + 1;
   m2[rowCount][colCount] ← mat[i][j];
  ENDLOOP;
ENDLOOP;
 cof ← Determinant2by2[m2];
 exponent ← row+col;
IF (exponent/2)*2 # exponent THEN cof ← -cof;
 };


Inverse: PUBLIC PROC [mat: Matrix3by3] RETURNS [inverse: Matrix3by3] = {
-- inverts any matrix of the given form (even with scaling). Find the matrix of Cofactors, transpose it and divide by the determinant
 det: REAL ← Determinant[mat];
IF det = 0 THEN SIGNAL DegenerateInverse;
FOR i: NAT IN[1..2] DO
  FOR j: NAT IN[1..3] DO
   inverse[i][j] ← Cofactor[mat,j,i]/det; -- transpose as you do it
  ENDLOOP;
ENDLOOP;
 inverse[3][1] ← inverse[3][2] ← 0.0;
 inverse[3][3] ← 1.0; -- these would also be the calculated values if we did them in the loop
 };
DegenerateInverse: PUBLIC SIGNAL = CODE;

WorldToLocal: PUBLIC PROC [AinWorld,BinWorld: Matrix3by3] RETURNS [BinTermsOfA: Matrix3by3] = {
-- clearly (AinWorld)<matmult>(BinTermsOfA) = BinWorld;
-- Hence BinTermsOfA = (Inverse[AinWorld])(BinWorld);
 worldAInverse: Matrix3by3 ← Inverse[AinWorld];
 BinTermsOfA ← MatMult[worldAInverse, BinWorld];
 };

END.