# -*- coding: utf-8 -*-
"""
UTc! module: src.matrix

Purpose
=======
 Provides a centralised location for matrix-related operations.
 
Notes
=====
 This module attempts to map functions from a C module, matrix_c, for the sake
 of performance. If matrix_c cannot be imported, then native Python versions of
 the functions it defines will be used instead.
 
 In either case, this module will offer functions with signatures identical to
 those described here. 
 
Legal
=====
 All code, unless otherwise indicated, is original, and subject to the
 terms of the GPLv3, which is provided in COPYING.
 
 (C) Neil Tallim, 2009
"""
buildIdentityRenderMatrix = None #: A function placeholder. Points to a C function or Python function depending on availability.
buildRenderMatrix = None #: A function placeholder. Points to a C function or Python function depending on availability.
buildRotationMatrix = None #: A function placeholder. Points to a C function or Python function depending on availability.
computePosition = None #: A function placeholder. Points to a C function or Python function depending on availability.
decomposeRotationMatrix = None #: A function placeholder. Points to a C function or Python function depending on availability.
multiply3x3Matrices = None #: A function placeholder. Points to a C function or Python function depending on availability.
multiply4x4Matrices = None #: A function placeholder. Points to a C function or Python function depending on availability.

try: #Attempt to load the C versions of these functions.
	import matrix_c
	
	buildIdentityRenderMatrix = matrix_c.buildIdentityRenderMatrix
	buildRenderMatrix = matrix_c.buildRenderMatrix
	buildRotationMatrix = matrix_c.buildRotationMatrix
	computePosition = matrix_c.computePosition
	decomposeRotationMatrix = matrix_c.decomposeRotationMatrix
	multiply3x3Matrices = matrix_c.multiply3x3Matrices
	multiply4x4Matrices = matrix_c.multiply4x4Matrices
except ImportError: #Unable to load C implementations; use Python versions to avoid brokenness.
	import math
	
	def buildIdentityRenderMatrix(position):
		"""
		Constructs a render matrix that represents an object without any scaling.
		
		@type position: sequence(3)
		@param position: The centre of the object to be positioned with this matrix.
		
		@rtype: tuple(12)
		@return: The requested 4x4 render matrix.
		"""
		(x, y, z) = position
		return (
		 1.0, 0.0, 0.0, 0.0,
		 0.0, 1.0, 0.0, 0.0,
		 0.0, 0.0, 1.0, 0.0,
		 x, y, z, 1.0
		)
		
	def buildRenderMatrix(rotation_matrix, position):
		"""
		Constructs a render matrix that represents an object with scaling based on
		the provided rotation matrix.
		
		@type rotation_matrix: sequence(9)
		@param rotation_matrix: The rotation matrix that specifies the scaling to
		    apply to the generated render matrix.
		@type position: sequence(3)
		@param position: The centre of the object to be positioned with this matrix.
		
		@rtype: tuple(12)
		@return: The requested 4x4 render matrix.
		"""
		(m_1_1, m_1_2, m_1_3, m_2_1, m_2_2, m_2_3, m_3_1, m_3_2, m_3_3) = rotation_matrix
		(x, y, z) = position
		return (
		 m_1_1, m_2_1, m_3_1, 0.0,
		 m_1_2, m_2_2, m_3_2, 0.0,
		 m_1_3, m_2_3, m_3_3, 0.0,
		 x, y, z, 1.0
		)
		
	def buildRotationMatrix(x_rot, y_rot, z_rot):
		"""
		Converts (x, y, z) Euler rotations into a rotation matrix.
		
		The order of application is roll (Z), yaw (Y), pitch (X).
		
		@type x_rot: float
		@param x_rot: The rotation around the X-axis, in degrees.
		@type y_rot: float
		@param y_rot: The rotation around the Y-axis, in degrees.
		@type z_rot: float
		@param z_rot: The rotation around the Z-axis, in degrees.
		
		@rtype: tuple(9)
		@return: The 3x3 rotation matrix that represents the given Euler rotations.
		"""
		radians = math.radians
		cos = math.cos
		sin = math.sin
		rx = radians(x_rot)
		ry = radians(y_rot)
		rz = radians(z_rot)
		cx = cos(rx)
		cy = cos(ry)
		cz = cos(rz)
		sx = sin(rx)
		sy = sin(ry)
		sz = sin(rz)
		sx_sy = sx * sy
		cx_sy = cx * sy
		return (
		 cz * cy, cz * sx_sy - sz * cx, cz * cx_sy + sz * sx,
		 sz * cy, sz * sx_sy + cz * cx, sz * cx_sy - cz * sx,
		 -sy, cy * sx, cy * cx
		)
		
	def computePosition(x, y, z, offset_x, offset_y, offset_z, rotation_matrix):
		"""
		Projects an (x, y, z) co-ordinate relative to a given position, based on a
		given rotation matrix.
		
		@type x: float
		@param x: The X-co-ordinate of this projection's origin.
		@type y: float
		@param y: The Y-co-ordinate of this projection's origin.
		@type z: float
		@param z: The Z-co-ordinate of this projection's origin.
		@type offset_x: float
		@param offset_x: The relative X-offset, based on the rotation matrix.
		@type offset_y: float
		@param offset_y: The relative Y-offset, based on the rotation matrix.
		@type offset_z: float
		@param offset_z: The relative Z-offset, based on the rotation matrix.
		@type rotation_matrix: sequence(9)
		@param rotation_matrix: The rotation matrix to use for the projection.
		
		@rtype: tuple(3)
		@return: The (x, y, z) co-ordinate of the projected point.
		"""
		(m_1_1, m_1_2, m_1_3, m_2_1, m_2_2, m_2_3, m_3_1, m_3_2, m_3_3) = rotation_matrix
		return (
		 x + (offset_x * m_1_1 + offset_y * m_1_2 + offset_z * m_1_3),
		 y + (offset_x * m_2_1 + offset_y * m_2_2 + offset_z * m_2_3),
		 z + (offset_x * m_3_1 + offset_y * m_3_2 + offset_z * m_3_3)
		)
		
	def decomposeRotationMatrix(rotation_matrix):
		"""
		Determines one possible Euler representation of a rotation matrix.
		
		Note: Gimbal lock is addressed by locking X. 
		
		@type rotation_matrix: sequence(9)
		@param rotation_matrix: The rotation matrix to be decomposed.
		
		@rtype: tuple(3)
		@return: The (x, y, z) rotations, in degrees, of the Euler representation.
		"""
		(m_1_1, m_1_2, m_1_3, m_2_1, m_2_2, m_2_3, m_3_1, m_3_2, m_3_3) = rotation_matrix
		atan2 = math.atan2
		degrees = math.degrees
		
		y_rot = m_3_1
		x_rot = z_rot = None
		if abs(y_rot) <= 1.0:
			x_rot = degrees(atan2(m_3_2, m_3_3)) % 360.0
			z_rot = degrees(atan2(m_2_1, m_1_1)) % 360.0
		else: #Gimbal lock; leave x_rot at 0.0.
			if y_rot > 0.0:
				y_rot = 1.0
			else:
				y_rot = -1.0
			x_rot = 0.0
			z_rot = degrees(atan2(m_1_2, m_2_2)) % 360.0
		y_rot = degrees(math.asin(-y_rot)) % 360.0
		
		return (x_rot, y_rot, z_rot)
		
	def multiply3x3Matrices(m_1, m_2):
		"""
		Multiplies one 3x3 matrix by another 3x3 matrix and returns the resulting
		3x3 matrix. All matrices are represented as a 1D sequence of nine elements.
		
		@type m_1: sequence(9)
		@param m_1: The first matrix for the multiplication operation.
		@type m_2: sequence(9)
		@param m_2: The second matrix for the multiplication operation.
		
		@rtype: tuple(9)
		@return: The product of the matrix multiplication.
		"""
		(m1_1_1, m1_1_2, m1_1_3, m1_2_1, m1_2_2, m1_2_3, m1_3_1, m1_3_2, m1_3_3) = m_1
		(m2_1_1, m2_1_2, m2_1_3, m2_2_1, m2_2_2, m2_2_3, m2_3_1, m2_3_2, m2_3_3) = m_2
		
		return (
		 m1_1_1 * m2_1_1 + m1_1_2 * m2_2_1 + m1_1_3 * m2_3_1,
		 m1_1_1 * m2_1_2 + m1_1_2 * m2_2_2 + m1_1_3 * m2_3_2,
		 m1_1_1 * m2_1_3 + m1_1_2 * m2_2_3 + m1_1_3 * m2_3_3,
		 
		 m1_2_1 * m2_1_1 + m1_2_2 * m2_2_1 + m1_2_3 * m2_3_1,
		 m1_2_1 * m2_1_2 + m1_2_2 * m2_2_2 + m1_2_3 * m2_3_2,
		 m1_2_1 * m2_1_3 + m1_2_2 * m2_2_3 + m1_2_3 * m2_3_3,
		 
		 m1_3_1 * m2_1_1 + m1_3_2 * m2_2_1 + m1_3_3 * m2_3_1,
		 m1_3_1 * m2_1_2 + m1_3_2 * m2_2_2 + m1_3_3 * m2_3_2,
		 m1_3_1 * m2_1_3 + m1_3_2 * m2_2_3 + m1_3_3 * m2_3_3
		)
		
	def multiply4x4Matrices(m_1, m_2):
		"""
		Multiplies one 4x4 matrix by another 4x4 matrix and returns the resulting
		4x4 matrix. All matrices are represented as a 1D sequence of twelve elements.
		
		@type m_1: sequence(12)
		@param m_1: The first matrix for the multiplication operation.
		@type m_2: sequence(12)
		@param m_2: The second matrix for the multiplication operation.
		
		@rtype: tuple(12)
		@return: The product of the matrix multiplication.
		"""
		(m1_1_1, m1_1_2, m1_1_3, m1_1_4, m1_2_1, m1_2_2, m1_2_3, m1_2_4, m1_3_1, m1_3_2, m1_3_3, m1_3_4, m1_4_1, m1_4_2, m1_4_3, m1_4_4) = m_1
		(m2_1_1, m2_1_2, m2_1_3, m2_1_4, m2_2_1, m2_2_2, m2_2_3, m2_2_4, m2_3_1, m2_3_2, m2_3_3, m2_3_4, m2_4_1, m2_4_2, m2_4_3, m2_4_4) = m_2
		
		return (
		 m1_1_1 * m2_1_1 + m1_1_2 * m2_2_1 + m1_1_3 * m2_3_1 + m1_1_4 * m2_4_1,
		 m1_1_1 * m2_1_2 + m1_1_2 * m2_2_2 + m1_1_3 * m2_3_2 + m1_1_4 * m2_4_2,
		 m1_1_1 * m2_1_3 + m1_1_2 * m2_2_3 + m1_1_3 * m2_3_3 + m1_1_4 * m2_4_3,
		 m1_1_1 * m2_1_4 + m1_1_2 * m2_2_4 + m1_1_3 * m2_3_4 + m1_1_4 * m2_4_4,
		 
		 m1_2_1 * m2_1_1 + m1_2_2 * m2_2_1 + m1_2_3 * m2_3_1 + m1_2_4 * m2_4_1,
		 m1_2_1 * m2_1_2 + m1_2_2 * m2_2_2 + m1_2_3 * m2_3_2 + m1_2_4 * m2_4_2,
		 m1_2_1 * m2_1_3 + m1_2_2 * m2_2_3 + m1_2_3 * m2_3_3 + m1_2_4 * m2_4_3,
		 m1_2_1 * m2_1_4 + m1_2_2 * m2_2_4 + m1_2_3 * m2_3_4 + m1_2_4 * m2_4_4,
		 
		 m1_3_1 * m2_1_1 + m1_3_2 * m2_2_1 + m1_3_3 * m2_3_1 + m1_3_4 * m2_4_1,
		 m1_3_1 * m2_1_2 + m1_3_2 * m2_2_2 + m1_3_3 * m2_3_2 + m1_3_4 * m2_4_2,
		 m1_3_1 * m2_1_3 + m1_3_2 * m2_2_3 + m1_3_3 * m2_3_3 + m1_3_4 * m2_4_3,
		 m1_3_1 * m2_1_4 + m1_3_2 * m2_2_4 + m1_3_3 * m2_3_4 + m1_3_4 * m2_4_4,
		 
		 m1_4_1 * m2_1_1 + m1_4_2 * m2_2_1 + m1_4_3 * m2_3_1 + m1_4_4 * m2_4_1,
		 m1_4_1 * m2_1_2 + m1_4_2 * m2_2_2 + m1_4_3 * m2_3_2 + m1_4_4 * m2_4_2,
		 m1_4_1 * m2_1_3 + m1_4_2 * m2_2_3 + m1_4_3 * m2_3_3 + m1_4_4 * m2_4_3,
		 m1_4_1 * m2_1_4 + m1_4_2 * m2_2_4 + m1_4_3 * m2_3_4 + m1_4_4 * m2_4_4
		)
		