% Copyright (C) 2003-2019 Olivier Boudeville % % This file is part of the Ceylan-Myriad library. % % This library is free software: you can redistribute it and/or modify % it under the terms of the GNU Lesser General Public License or % the GNU General Public License, as they are published by the Free Software % Foundation, either version 3 of these Licenses, or (at your option) % any later version. % You can also redistribute it and/or modify it under the terms of the % Mozilla Public License, version 1.1 or later. % % This library is 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 Lesser General Public License and the GNU General Public License % for more details. % % You should have received a copy of the GNU Lesser General Public % License, of the GNU General Public License and of the Mozilla Public License % along with this library. % If not, see and % . % % Author: Olivier Boudeville [olivier (dot) boudeville (at) esperide (dot) com] % Creation date: Monday, February 15, 2010. % Gathering of various four dimensional linear facilities, mostly dealing with % homogeneous matrices for 3D. % % See linear_4D_test.erl for the corresponding test. % -module(linear_4D). % Relatively aggressive inlining for basic operations: -compile( inline ). -compile( { inline_size, 48 } ). % For the mat4 record: -include("linear_4D.hrl"). % For the mat3 record: -include("linear_3D.hrl"). % Shorthands: -type coordinate() :: linear:coordinate(). -type factor() :: linear:factor(). % A 4D vector, with floating-point coordinates. % % They are typically referenced as [ X, Y, Z, W ]. % -type vector() :: { coordinate(), coordinate(), coordinate(), coordinate() }. % Alias for 4x4 canonical matrices: -type mat4() :: #mat4{}. -type canonical_matrix() :: mat4(). % Aliases for 4x4 compact matrices: -type cpt_mat4() :: #cpt_mat4{}. -type compact_matrix() :: cpt_mat4(). -type matrix() :: 'identity_4' | canonical_matrix() | compact_matrix(). -export_type([ mat4/0, canonical_matrix/0, cpt_mat4/0, compact_matrix/0, matrix/0 ]). % Operations common to vectors and matrices: % -export([ scale/2 ]). % Vector-related operations: % -export([ null_vector/0, add/2, add/1 ]). % Matrix-related operations: % -export([ null_matrix/0, identity/0, from_columns/4, from_rows/4, from_coordinates/16, from_coordinates/12, from_3D/2, to_canonical/1, to_compact/1, mult/2, are_equal/2, to_string/1 ]). -import( math_utils, [ is_null/1, are_close/2 ] ). -import( linear, [ coord_to_string/1 ]). % Implementation of vector-related operations. % Returns the null (4D) vector. % -spec null_vector() -> vector(). null_vector() -> { 0.0, 0.0, 0.0, 0.0 }. % Adds the two specified (4D) vectors. % -spec add( vector(), vector() ) -> vector(). add( _Va={Xa,Ya,Za,Wa}, _Vb={Xb,Yb,Zb,Wb} ) -> { Xa+Xb, Ya+Yb, Za+Zb, Wa+Wb }. % Adds the specified (non-empty) list of (4D) vectors. % -spec add( [ vector() ] ) -> vector(). add( _Vectors=[ V | T ] ) -> add_vec_list( T, _Acc=V ); add( _Vectors=[] ) -> throw( cannot_add_empty_list ). % (helper) add_vec_list( _Vec=[], AccV ) -> AccV; add_vec_list( _Vec=[ V | T ], AccV ) -> NewAccV = add( V, AccV ), add_vec_list( T, NewAccV ). % Implementation of matrix-related operations. % Returns the null (4x4) matrix. % -spec null_matrix() -> canonical_matrix(). null_matrix() -> #mat4{ m11=0.0, m12=0.0, m13=0.0, m14=0.0, m21=0.0, m22=0.0, m23=0.0, m24=0.0, m31=0.0, m32=0.0, m33=0.0, m34=0.0, m41=0.0, m42=0.0, m43=0.0, m44=0.0 }. % Returns the identity (4x4) matrix. % % -spec identity() -> matrix(). identity() -> identity_4. % Scales specified (4D) vector or matrix of specified factor. % -spec scale( vector(), factor() ) -> vector(); ( matrix(), factor() ) -> matrix(). scale( _V={X,Y,Z,W}, Factor ) -> { Factor*X, Factor*Y, Factor*Z, Factor*W }; scale( #cpt_mat4{ m11=M11, m12=M12, m13=M13, tx=Tx, m21=M21, m22=M22, m23=M23, ty=Ty, m31=M31, m32=M32, m33=M33, tz=Tz }, Factor ) -> #cpt_mat4{ m11=Factor*M11, m12=Factor*M12, m13=Factor*M13, tx=Factor*Tx, m21=Factor*M21, m22=Factor*M22, m23=Factor*M23, ty=Factor*Ty, m31=Factor*M31, m32=Factor*M32, m33=Factor*M33, tz=Factor*Tz }; scale( #mat4{ m11=M11, m12=M12, m13=M13, m14=Tx, m21=M21, m22=M22, m23=M23, m24=Ty, m31=M31, m32=M32, m33=M33, m34=Tz, m41=M41, m42=M42, m43=M43, m44=Tw }, Factor ) -> #mat4{ m11=Factor*M11, m12=Factor*M12, m13=Factor*M13, m14=Factor*Tx, m21=Factor*M21, m22=Factor*M22, m23=Factor*M23, m24=Factor*Ty, m31=Factor*M31, m32=Factor*M32, m33=Factor*M33, m34=Factor*Tz, m41=Factor*M41, m42=Factor*M42, m43=Factor*M43, m44=Factor*Tw }; scale( identity_4, Factor ) -> scale( to_canonical( identity_4 ), Factor ). % Returns the (4x4) matrix whose columns correspond to the specified 4 vectors: % [ Va Vb Vc Vd ] % [ | | | | ] % [ | | | | ] % [ | | | | ] % -spec from_columns( vector(), vector(), vector(), vector() ) -> canonical_matrix(). from_columns( _Va={Xa,Ya,Za,Wa}, _Vb={Xb,Yb,Zb,Wb}, _Vc={Xc,Yc,Zc,Wc}, _Vd={Xd,Yd,Zd,Wd} ) -> #mat4{ m11=Xa, m12=Xb, m13=Xc, m14=Xd, m21=Ya, m22=Yb, m23=Yc, m24=Yd, m31=Za, m32=Zb, m33=Zc, m34=Zd, m41=Wa, m42=Wb, m43=Wc, m44=Wd }. % Returns the (4x4) matrix whose columns correspond to the specified 4 vectors: % [ Va - - - ] % [ Vb - - - ] % [ Vc - - - ] % [ Vd - - - ] % -spec from_rows( vector(), vector(), vector(), vector() ) -> canonical_matrix(). from_rows( _Va={Xa,Ya,Za,Wa}, _Vb={Xb,Yb,Zb,Wb}, _Vc={Xc,Yc,Zc,Wc}, _Vd={Xd,Yd,Zd,Wd} ) -> #mat4{ m11=Xa, m12=Ya, m13=Za, m14=Wa, m21=Xb, m22=Yb, m23=Zb, m24=Wb, m31=Xc, m32=Yc, m33=Zc, m34=Wc, m41=Xd, m42=Yd, m43=Zd, m44=Wd }. % Returns the (4x4, canonical) matrix whose (16) coordinates are the specified % ones, specified rows after rows. % -spec from_coordinates( coordinate(), coordinate(), coordinate(), coordinate(), coordinate(), coordinate(), coordinate(), coordinate(), coordinate(), coordinate(), coordinate(), coordinate(), coordinate(), coordinate(), coordinate(), coordinate() ) -> canonical_matrix(). from_coordinates( A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16 ) -> #mat4{ m11=A1, m12=A2, m13=A3, m14=A4, m21=A5, m22=A6, m23=A7, m24=A8, m31=A9, m32=A10, m33=A11, m34=A12, m41=A13, m42=A14, m43=A15, m44=A16 }. % Returns the (4x4, compact) matrix whose (12) coordinates are the specified % ones, specified rows after rows. % -spec from_coordinates( coordinate(), coordinate(), coordinate(), coordinate(), coordinate(), coordinate(), coordinate(), coordinate(), coordinate(), coordinate(), coordinate(), coordinate() ) -> compact_matrix(). from_coordinates( A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12 ) -> #cpt_mat4{ m11=A1, m12=A2, m13=A3, tx=A4, m21=A5, m22=A6, m23=A7, ty=A8, m31=A9, m32=A10, m33=A11, tz=A12 }. % Returns the (4x4) compact matrix obtained from specified 3x3 matrix and vector. % -spec from_3D( linear_3D:matrix(), linear_3D:vector() ) -> compact_matrix(). from_3D( #mat3{ m11=M11, m12=M12, m13=M13, m21=M21, m22=M22, m23=M23, m31=M31, m32=M32, m33=M33 }, _Vec3={ X, Y, Z } ) -> #cpt_mat4{ m11=M11, m12=M12, m13=M13, tx=X, m21=M21, m22=M22, m23=M23, ty=Y, m31=M31, m32=M32, m33=M33, tz=Z }. % Returns the canonical form of specified (4x4) matrix. % -spec to_canonical( matrix() ) -> canonical_matrix(). to_canonical( #cpt_mat4{ m11=M11, m12=M12, m13=M13, tx=Tx, m21=M21, m22=M22, m23=M23, ty=Ty, m31=M31, m32=M32, m33=M33, tz=Tz } ) -> #mat4{ m11=M11, m12=M12, m13=M13, m14=Tx, m21=M21, m22=M22, m23=M23, m24=Ty, m31=M31, m32=M32, m33=M33, m34=Tz, m41=0.0, m42=0.0, m43=0.0, m44=1.0 }; to_canonical( identity_4 ) -> #mat4{ m11=1.0, m12=0.0, m13=0.0, m14=0.0, m21=0.0, m22=1.0, m23=0.0, m24=0.0, m31=0.0, m32=0.0, m33=1.0, m34=0.0, m41=0.0, m42=0.0, m43=0.0, m44=1.0 }; to_canonical( M ) when is_record( M, mat4 ) -> M. % Returns the compact form of specified (4x4) matrix. % % Throws an exception if the specified matrix cannot be expressed as a compact % one. % -spec to_compact( matrix() ) -> compact_matrix(). to_compact( M=#mat4{ m11=M11, m12=M12, m13=M13, m14=M14, m21=M21, m22=M22, m23=M23, m24=M24, m31=M31, m32=M32, m33=M33, m34=M34, m41=M41, m42=M42, m43=M43, m44=M44 }) -> case is_null( M41 ) andalso is_null( M42 ) andalso is_null( M43 ) andalso are_close( M44, 1.0 ) of true -> % Just drop the last row: #cpt_mat4{ m11=M11, m12=M12, m13=M13, tx=M14, m21=M21, m22=M22, m23=M23, ty=M24, m31=M31, m32=M32, m33=M33, tz=M34 }; false -> trace_utils:error_fmt( "Canonical matrix~n~s~ncannot be expressed " "as a compact one.", [ to_string( M ) ] ), throw( { not_compactable, M } ) end; to_compact( identity_4 ) -> #cpt_mat4{ m11=1.0, m12=0.0, m13=0.0, tx=0.0, m21=0.0, m22=1.0, m23=0.0, ty=0.0, m31=0.0, m32=0.0, m33=1.0, tz=0.0 }; to_compact( M ) when is_record( M, cpt_mat4 ) -> M. % Multiplies the first matrix by the second one: returns Mc = Ma.Mb. % -spec mult( matrix(), matrix() ) -> matrix(). mult( identity_4, M ) -> M; mult( M, identity_4 ) -> M; mult( _Ma=#mat4{ m11=A11, m12=A12, m13=A13, m14=A14, m21=A21, m22=A22, m23=A23, m24=A24, m31=A31, m32=A32, m33=A33, m34=A34, m41=A41, m42=A42, m43=A43, m44=A44 }, _Mb=#mat4{ m11=B11, m12=B12, m13=B13, m14=B14, m21=B21, m22=B22, m23=B23, m24=B24, m31=B31, m32=B32, m33=B33, m34=B34, m41=B41, m42=B42, m43=B43, m44=B44 } ) -> C11 = A11*B11 + A12*B21 + A13*B31 + A14*B41, C12 = A11*B12 + A12*B22 + A13*B32 + A14*B42, C13 = A11*B13 + A12*B23 + A13*B33 + A14*B43, C14 = A11*B14 + A12*B24 + A13*B34 + A14*B44, C21 = A21*B11 + A22*B21 + A23*B31 + A24*B41, C22 = A21*B12 + A22*B22 + A23*B32 + A24*B42, C23 = A21*B13 + A22*B23 + A23*B33 + A24*B43, C24 = A21*B14 + A22*B24 + A23*B34 + A24*B44, C31 = A31*B11 + A32*B21 + A33*B31 + A34*B41, C32 = A31*B12 + A32*B22 + A33*B32 + A34*B42, C33 = A31*B13 + A32*B23 + A33*B33 + A34*B43, C34 = A31*B14 + A32*B24 + A33*B34 + A34*B44, C41 = A41*B11 + A42*B21 + A43*B31 + A44*B41, C42 = A41*B12 + A42*B22 + A43*B32 + A44*B42, C43 = A41*B13 + A42*B23 + A43*B33 + A44*B43, C44 = A41*B14 + A42*B24 + A43*B34 + A44*B44, #mat4{ m11=C11, m12=C12, m13=C13, m14=C14, m21=C21, m22=C22, m23=C23, m24=C24, m31=C31, m32=C32, m33=C33, m34=C34, m41=C41, m42=C42, m43=C43, m44=C44 }; mult( _Ma=#cpt_mat4{ m11=A11, m12=A12, m13=A13, tx=Tx, m21=A21, m22=A22, m23=A23, ty=Ty, m31=A31, m32=A32, m33=A33, tz=Tz }, _Mb=#mat4{ m11=B11, m12=B12, m13=B13, m14=B14, m21=B21, m22=B22, m23=B23, m24=B24, m31=B31, m32=B32, m33=B33, m34=B34, m41=B41, m42=B42, m43=B43, m44=B44 } ) -> C11 = A11*B11 + A12*B21 + A13*B31 + Tx*B41, C12 = A11*B12 + A12*B22 + A13*B32 + Tx*B42, C13 = A11*B13 + A12*B23 + A13*B33 + Tx*B43, C14 = A11*B14 + A12*B24 + A13*B34 + Tx*B44, C21 = A21*B11 + A22*B21 + A23*B31 + Ty*B41, C22 = A21*B12 + A22*B22 + A23*B32 + Ty*B42, C23 = A21*B13 + A22*B23 + A23*B33 + Ty*B43, C24 = A21*B14 + A22*B24 + A23*B34 + Ty*B44, C31 = A31*B11 + A32*B21 + A33*B31 + Tz*B41, C32 = A31*B12 + A32*B22 + A33*B32 + Tz*B42, C33 = A31*B13 + A32*B23 + A33*B33 + Tz*B43, C34 = A31*B14 + A32*B24 + A33*B34 + Tz*B44, C41 = B41, C42 = B42, C43 = B43, C44 = B44, #mat4{ m11=C11, m12=C12, m13=C13, m14=C14, m21=C21, m22=C22, m23=C23, m24=C24, m31=C31, m32=C32, m33=C33, m34=C34, m41=C41, m42=C42, m43=C43, m44=C44 }; mult( _Ma=#mat4{ m11=A11, m12=A12, m13=A13, m14=A14, m21=A21, m22=A22, m23=A23, m24=A24, m31=A31, m32=A32, m33=A33, m34=A34, m41=A41, m42=A42, m43=A43, m44=A44 }, _Mb=#cpt_mat4{ m11=B11, m12=B12, m13=B13, tx=Tx, m21=B21, m22=B22, m23=B23, ty=Ty, m31=B31, m32=B32, m33=B33, tz=Tz } ) -> C11 = A11*B11 + A12*B21 + A13*B31, C12 = A11*B12 + A12*B22 + A13*B32, C13 = A11*B13 + A12*B23 + A13*B33, C14 = A11*Tx + A12*Ty + A13*Tz + A14, C21 = A21*B11 + A22*B21 + A23*B31, C22 = A21*B12 + A22*B22 + A23*B32, C23 = A21*B13 + A22*B23 + A23*B33, C24 = A21*Tx + A22*Ty + A23*Tz + A24, C31 = A31*B11 + A32*B21 + A33*B31, C32 = A31*B12 + A32*B22 + A33*B32, C33 = A31*B13 + A32*B23 + A33*B33, C34 = A31*Tx + A32*Ty + A33*Tz + A34, C41 = A41*B11 + A42*B21 + A43*B31, C42 = A41*B12 + A42*B22 + A43*B32, C43 = A41*B13 + A42*B23 + A43*B33, C44 = A41*Tx + A42*Ty + A43*Tz + A44, #mat4{ m11=C11, m12=C12, m13=C13, m14=C14, m21=C21, m22=C22, m23=C23, m24=C24, m31=C31, m32=C32, m33=C33, m34=C34, m41=C41, m42=C42, m43=C43, m44=C44 }; mult( _Ma=#cpt_mat4{ m11=A11, m12=A12, m13=A13, tx=Ax, m21=A21, m22=A22, m23=A23, ty=Ay, m31=A31, m32=A32, m33=A33, tz=Az }, _Mb=#cpt_mat4{ m11=B11, m12=B12, m13=B13, tx=Bx, m21=B21, m22=B22, m23=B23, ty=By, m31=B31, m32=B32, m33=B33, tz=Bz } ) -> C11 = A11*B11 + A12*B21 + A13*B31, C12 = A11*B12 + A12*B22 + A13*B32, C13 = A11*B13 + A12*B23 + A13*B33, Cx = A11*Bx + A12*By + A13*Bz + Ax, C21 = A21*B11 + A22*B21 + A23*B31, C22 = A21*B12 + A22*B22 + A23*B32, C23 = A21*B13 + A22*B23 + A23*B33, Cy = A21*Bx + A22*By + A23*Bz + Ay, C31 = A31*B11 + A32*B21 + A33*B31, C32 = A31*B12 + A32*B22 + A33*B32, C33 = A31*B13 + A32*B23 + A33*B33, Cz = A31*Bx + A32*By + A33*Bz + Az, % C4{1,2,3} are 0.0, C44 is 1.0. #cpt_mat4{ m11=C11, m12=C12, m13=C13, tx=Cx, m21=C21, m22=C22, m23=C23, ty=Cy, m31=C31, m32=C32, m33=C33, tz=Cz }. % Tells whether the two specified (4x4) matrices are equal. % -spec are_equal( matrix(), matrix() ) -> boolean(). are_equal( _Ma=identity_4, _Mb=identity_4 ) -> true; are_equal( _Ma=#mat4{ m11=A11, m12=A12, m13=A13, m14=A14, m21=A21, m22=A22, m23=A23, m24=A24, m31=A31, m32=A32, m33=A33, m34=A34, m41=A41, m42=A42, m43=A43, m44=A44 }, _Mb=#mat4{ m11=B11, m12=B12, m13=B13, m14=B14, m21=B21, m22=B22, m23=B23, m24=B24, m31=B31, m32=B32, m33=B33, m34=B34, m41=B41, m42=B42, m43=B43, m44=B44 } ) -> are_close( A11, B11 ) andalso are_close( A12, B12 ) andalso are_close( A13, B13 ) andalso are_close( A14, B14 ) andalso are_close( A21, B21 ) andalso are_close( A22, B22 ) andalso are_close( A23, B23 ) andalso are_close( A24, B24 ) andalso are_close( A31, B31 ) andalso are_close( A32, B32 ) andalso are_close( A33, B33 ) andalso are_close( A34, B34 ) andalso are_close( A41, B41 ) andalso are_close( A42, B42 ) andalso are_close( A43, B43 ) andalso are_close( A44, B44 ); are_equal( _Ma=#cpt_mat4{ m11=A11, m12=A12, m13=A13, tx=Ax, m21=A21, m22=A22, m23=A23, ty=Ay, m31=A31, m32=A32, m33=A33, tz=Az }, _Mb=#cpt_mat4{ m11=B11, m12=B12, m13=B13, tx=Bx, m21=B21, m22=B22, m23=B23, ty=By, m31=B31, m32=B32, m33=B33, tz=Bz } ) -> are_close( A11, B11 ) andalso are_close( A12, B12 ) andalso are_close( A13, B13 ) andalso are_close( Ax, Bx ) andalso are_close( A21, B21 ) andalso are_close( A22, B22 ) andalso are_close( A23, B23 ) andalso are_close( Ay, By ) andalso are_close( A31, B31 ) andalso are_close( A32, B32 ) andalso are_close( A33, B33 ) andalso are_close( Az, Bz ); are_equal( _Ma=#mat4{ m11=A11, m12=A12, m13=A13, m14=A14, m21=A21, m22=A22, m23=A23, m24=A24, m31=A31, m32=A32, m33=A33, m34=A34, m41=A41, m42=A42, m43=A43, m44=A44 }, _Mb=#cpt_mat4{ m11=B11, m12=B12, m13=B13, tx=Bx, m21=B21, m22=B22, m23=B23, ty=By, m31=B31, m32=B32, m33=B33, tz=Bz } ) -> are_close( A11, B11 ) andalso are_close( A12, B12 ) andalso are_close( A13, B13 ) andalso are_close( A14, Bx ) andalso are_close( A21, B21 ) andalso are_close( A22, B22 ) andalso are_close( A23, B23 ) andalso are_close( A24, By ) andalso are_close( A31, B31 ) andalso are_close( A32, B32 ) andalso are_close( A33, B33 ) andalso are_close( A34, Bz ) andalso is_null( A41 ) andalso is_null( A42 ) andalso is_null( A43 ) andalso are_close( A44, 1.0 ); are_equal( Ma=#cpt_mat4{}, Mb=#mat4{} ) -> are_equal( Mb, Ma ); are_equal( Ma, _Mb=identity_4 ) -> are_equal( Ma, to_compact( identity_4 ) ); are_equal( _Ma=identity_4, Mb ) -> are_equal( Mb, identity_4 ). % Returns a textual representation of specified (4x4) vector or matrix. % -spec to_string( vector() | matrix() ) -> string(). to_string( _Vector={X,Y,Z,W} ) -> text_utils:format( "[ ~s, ~s, ~s, ~s ]", [ coord_to_string( X ), coord_to_string( Y ), coord_to_string( Z ), coord_to_string( W ) ] ); to_string( _Matrix=identity_4 ) -> "4x4 identity"; to_string( _Matrix=#mat4{ m11=M11, m12=M12, m13=M13, m14=M14, m21=M21, m22=M22, m23=M23, m24=M24, m31=M31, m32=M32, m33=M33, m34=M34, m41=M41, m42=M42, m43=M43, m44=M44 } ) -> Elements = [ M11, M12, M13, M14, M21, M22, M23, M24, M31, M32, M33, M34, M41, M42, M43, M44 ], ElemStrings = [ coord_to_string( E ) || E <- Elements ], text_utils:format( "[ ~s, ~s, ~s, ~s ]~n" "[ ~s, ~s, ~s, ~s ]~n" "[ ~s, ~s, ~s, ~s ]~n" "[ ~s, ~s, ~s, ~s ]~n", ElemStrings ); to_string( _Matrix=#cpt_mat4{ m11=M11, m12=M12, m13=M13, tx=Tx, m21=M21, m22=M22, m23=M23, ty=Ty, m31=M31, m32=M32, m33=M33, tz=Tz } ) -> Elements = [ M11, M12, M13, Tx, M21, M22, M23, Ty, M31, M32, M33, Tz ], ElemStrings = [ coord_to_string( E ) || E <- Elements ], text_utils:format( "[ ~s, ~s, ~s, ~s ]~n" "[ ~s, ~s, ~s, ~s ]~n" "[ ~s, ~s, ~s, ~s ]~n", ElemStrings ).