
# HC2Mat.py
# Andrew Davison, ad@coe.psu.ac.th, April 2025
# https://en.wikipedia.org/wiki/Homogeneous_coordinates

'''
Support for 2D Homogenous coordinates.

See HC3Mat.py for 3D Homo. Coords.

Homogeneous coordinates are ubiquitous in computer graphics because they allow common vector operations such as translation, rotation, scaling and perspective projection to be represented as a matrix by which the vector is multiplied.

Operations:
  * identity
  * translation
  * scaling
  * shearing
  * rotation
  * reflection

Others:
  * matrix multiplication
  * matrix print
  * apply a matrix to a (x,y) coordinate, returning a new coord
  * plot a list of coordinates as a polygon in matplotlib
'''

import math
from matplotlib.patches import Polygon


def identMat():
  return [ [1, 0, 0],
           [0, 1, 0],
           [0, 0, 1] ]


def transMat(a, b):
  # translate by 'a' along the x-axis, 'b' along y-axis
  return [ [1, 0, a],
           [0, 1, b],
           [0, 0, 1] ]

def scaleMat(a, b):
  # scale by 'a' along the x-axis, 'b' along y-axis
  return [ [a, 0, 0],
           [0, b, 0],
           [0, 0, 1] ]


def shearMat(a, b):
  # shear by 'a' along the x-axis, 'b' along the y-axis
  return [ [1, b, 0],
           [a, 1, 0],
           [0, 0, 1] ]


def rotMat(angle):
  # rotate by angle degrees counter-clockwise around origin
  theta = math.radians(angle)
  return [ [math.cos(theta), -math.sin(theta), 0],
           [math.sin(theta),  math.cos(theta), 0],
           [             0,                0,  1]  ]

def reflectMat(m):
  # reflect about line y = mx
  den = 1 + m*m
  return [ [(1-m*m)/den,     2*m/den,  0],
           [    2*m/den, (m*m-1)/den,  0],
           [          0,           0,  1] ]

def reflectYMat():
  # reflect in y-axis 
  return [ [-1, 0, 0],
           [ 0, 1, 0],
           [ 0, 0, 1] ]

def reflectXMat():
  # reflect in x-axis
  return reflectMat(0)
      
# --------------------------------------

def multMats(matrixA, matrixB):
  # multiply matrix A to B, returning a new matrix
  result = [[0 for _ in range(3)] for _ in range(3)]
  for i in range(3):
    for j in range(3):
      for k in range(3):
        result[i][j] += matrixA[i][k] * matrixB[k][j]
  return result


def multMatsList(mats):
  # multiply the matrices in the list together
  # in left-to-right order, returning the result
  result = identMat()
  for mat in mats:
    result = multMats(result, mat)
  return result


def printMat(matrix):
  for row in matrix:
    print("[" + "  ".join(f"{val:7.3f}" for val in row) + " ]")


def applyMat(matrix, coord):
  # apply the matrix to a (x,y) coord, returning
  # a new (x,y) coord
  vec = [coord[0], coord[1], 1]  # homogenous
  nVec = [0, 0, 0]
  for i in range(3):
    for k in range(3):
      nVec[i] += matrix[i][k] * vec[k]
  return (nVec[0]/nVec[2], nVec[1]/nVec[2])


def plotPoly(ax, coords, color):
  ''' plot the coordinates as a closed polygon
      and label the vertices as v0, ...
  '''
  poly = Polygon(coords, closed=True, 
                    color=color, alpha=0.7)
  ax.add_patch(poly)
  # Label each vertex
  for i, (x, y) in enumerate(coords):
    ax.plot(x, y, 'o', color='red', ms=4)  # small red dot
    ax.text(x + 0.05, y + 0.05, f'v{i}', 
                     fontsize=8, color='black')



# --------------------------------------
if __name__ == "__main__":
  m1 = [ [1, 2, 3],
         [4, 5, 6],
         [7, 8, 9] ]
  m2 = [ [9, 8, 7],
         [6, 5, 4],
         [3, 2, 1] ]
  printMat( multMats(m1, m2))

  print("Example 2")
  coords = [(8,6),(8,8),(6,10),(8,12),(10,10),(10,6)]
  m = multMats( transMat(12,6), 
                multMats( rotMat(90), transMat(-12,-6)))
  printMat(m)
  nCoords = [applyMat(m, c) for c in coords]
  for x, y in nCoords:
    print(f"({x:.2f}, {y:.2f})", end =' ')
  print()
