Files
2025-09-29 00:52:08 +02:00

329 lines
9.1 KiB
Ruby
Executable File

#
# File:: matrix.rb
# Description:: Matrix classes.
#
# Author:: David Muir <david.muir@rockstarnorth.com>
# Date:: 10 December 2008
#
#----------------------------------------------------------------------------
# Uses
#----------------------------------------------------------------------------
require 'pipeline/math/constants'
require 'pipeline/math/vector3'
#----------------------------------------------------------------------------
# Implementation
#----------------------------------------------------------------------------
module Pipeline
module Math
#
# == Description
# The Matrix34 class represents a matrix with 3 columns and 4 rows.
#
# Ref: X:\jimmy\src\dev\rage\base\src\vector\matrix34_default.h.
#
class Matrix34
#--------------------------------------------------------------------
# Attributes
#--------------------------------------------------------------------
attr_accessor :a
attr_accessor :b
attr_accessor :c
attr_accessor :d
#--------------------------------------------------------------------
# Virtual Attributes
#--------------------------------------------------------------------
#
# Return translation component of Matrix34.
#
def translation
@d
end
#
# Set translation component of Matrix34.
#
def translation=( trans )
throw ArgumentError.new( "Invalid Vector3 object (#{trans.class})." ) \
unless ( trans.is_a?( Vector3 ) )
@d = trans
end
#--------------------------------------------------------------------
# Methods
#--------------------------------------------------------------------
#
# Constructor, default arguments create an identity matrix.
#
def initialize( a = Vector3.new( 1.0, 0.0, 0.0 ),
b = Vector3.new( 0.0, 1.0, 0.0 ),
c = Vector3.new( 0.0, 0.0, 1.0 ),
d = Vector3.new( 0.0, 0.0, 0.0 ) )
@a = a
@b = b
@c = c
@d = d
end
#
# Set matrix to the identity matrix.
#
def identity!( )
@a = Vector3.new( 1.0, 0.0, 0.0 )
@b = Vector3.new( 0.0, 1.0, 0.0 )
@c = Vector3.new( 0.0, 0.0, 1.0 )
@d = Vector3.new( 0.0, 0.0, 0.0 )
end
#
# Set matrix to zero.
#
def zero!( )
@a = Vector3.new( 0.0, 0.0, 0.0 )
@b = Vector3.new( 0.0, 0.0, 0.0 )
@c = Vector3.new( 0.0, 0.0, 0.0 )
@d = Vector3.new( 0.0, 0.0, 0.0 )
end
#
# Add a matrix to the current matrix.
#
def +( m )
throw ArgumentError.new( "Invalid matrix specified (#{m.class})." ) \
unless ( m.is_a?( Matrix34 ) )
@a += m.a
@b += m.b
@c += m.c
@d += m.d
end
#
# Subtract a matrix from the current matrix.
#
def -( m )
throw ArgumentError.new( "Invalid matrix specified (#{m.class})." ) \
unless ( m.is_a?( Matrix34 ) )
@a -= m.a
@b -= m.b
@c -= m.c
@d -= m.d
end
#
# Transform a Matrix34 or Vector3 by this Matrix34, returning a new
# Matrix34 or Vector3 object.
#
def *( v )
throw ArgumentError.new( "Invalid Matrix34 or Vector3 specified (#{v.class})." ) \
unless ( v.is_a?( Matrix34 ) or v.is_a?( Vector3 ) )
if ( v.is_a?( Vector3 ) ) then
newX = v.x * a.x + v.y * b.x + v.z * c.x + d.x;
newY = v.x * a.y + v.y * b.y + v.z * c.y + d.y;
newZ = v.x * a.z + v.y * b.z + v.z * c.z + d.z;
Vector3.new( newX, newY, newZ )
elsif ( v.is_a?( Matrix34 ) ) then
ma = Vector3.new
mb = Vector3.new
mc = Vector3.new
md = Vector3.new
ma.x = @a.x*v.a.x + @a.y*v.b.x + @a.z*v.c.x;
ma.y = @a.x*v.a.y + @a.y*v.b.y + @a.z*v.c.y;
ma.z = @a.x*v.a.z + @a.y*v.b.z + @a.z*v.c.z;
mb.x = @b.x*v.a.x + @b.y*v.b.x + @b.z*v.c.x;
mb.y = @b.x*v.a.y + @b.y*v.b.y + @b.z*v.c.y;
mb.z = @b.x*v.a.z + @b.y*v.b.z + @b.z*v.c.z;
mc.x = @c.x*v.a.x + @c.y*v.b.x + @c.z*v.c.x;
mc.y = @c.x*v.a.y + @c.y*v.b.y + @c.z*v.c.y;
mc.z = @c.x*v.a.z + @c.y*v.b.z + @c.z*v.c.z;
md.x = @d.x*v.a.x + @d.y*v.b.x + @d.z*v.c.x + v.d.x;
md.y = @d.x*v.a.y + @d.y*v.b.y + @d.z*v.c.y + v.d.y;
md.z = @d.x*v.a.z + @d.y*v.b.z + @d.z*v.c.z + v.d.z;
Matrix34.new( ma, mb, mc, md )
end
end
#
# Return inverse of this matrix.
#
def inverse( )
Matrix34::inverse( self )
end
#
# Return whether the matrix can be inverted.
#
def invertable?( )
# Get three of the subdeterminants.
subDetX = Math::Determinant22( m.b.y, m.b.z, m.c.y, m.c.z );
subDetY = Math::Determinant22( m.b.x, m.b.z, m.c.x, m.c.z );
subDetZ = Math::Determinant22( m.b.x, m.b.y, m.c.x ,m.c.y );
# Find the largest absolute value element.
bigElement = [ [ m.a.x.abs, m.a.y.abs, m.a.z.abs ].max,
[ m.b.x.abs, m.b.y.abs, m.b.z.abs ].max,
[ m.c.x.abs, m.c.y.abs, m.c.z.abs ].max ].max
# Get the inverse of the determinant.
invDet = ( m.a.x * subDetX ) - ( m.a.y * subDetY ) + ( m.a.z * subDetZ );
return ( invDet.abs > ( 3.6e-7 * bigElement ) )
end
#
# Serialise to String.
#
def to_s( )
"#{@a}\n#{@b}\n#{@c}\n#{@d}"
end
#--------------------------------------------------------------------
# Class Methods
#--------------------------------------------------------------------
#
# Return the identity Matrix34.
#
def Matrix34::identity( )
Matrix34.new( Vector3.new( 1.0, 0.0, 0.0 ),
Vector3.new( 0.0, 1.0, 0.0 ),
Vector3.new( 0.0, 0.0, 1.0 ) )
end
#
# Return the inverse of a Matrix34.
#
def Matrix34::inverse( m )
# Get three of the subdeterminants.
subDetX = Math::Determinant22( m.b.y, m.b.z, m.c.y, m.c.z );
subDetY = Math::Determinant22( m.b.x, m.b.z, m.c.x, m.c.z );
subDetZ = Math::Determinant22( m.b.x, m.b.y, m.c.x ,m.c.y );
# Find the largest absolute value element.
bigElement = [ [ m.a.x.abs, m.a.y.abs, m.a.z.abs ].max,
[ m.b.x.abs, m.b.y.abs, m.b.z.abs ].max,
[ m.c.x.abs, m.c.y.abs, m.c.z.abs ].max ].max
# Get the inverse of the determinant.
invDet = ( m.a.x * subDetX ) - ( m.a.y * subDetY ) + ( m.a.z * subDetZ );
if ( invDet.abs > ( 3.6e-7 * bigElement ) ) then
invm = Matrix34.new( )
invDet = 1.0 / invDet;
# Start making the inverse matrix.
invm.a.x = subDetX * invDet;
invm.b.x = -subDetY * invDet;
invm.c.x = subDetZ * invDet;
invm.d.x = - (m.d.x*invm.a.x + m.d.y*invm.b.x + m.d.z*invm.c.x);
# Get three more subdeterminants.
subDetX = Math::Determinant22( m.a.y,m.a.z,m.c.y,m.c.z );
subDetY = Math::Determinant22( m.a.x,m.a.z,m.c.x,m.c.z );
subDetZ = Math::Determinant22( m.a.x,m.a.y,m.c.x,m.c.y );
# Add more terms to the inverse matrix.
invm.a.y = -subDetX*invDet;
invm.b.y = subDetY*invDet;
invm.c.y = -subDetZ*invDet;
invm.d.y = - (m.d.x*invm.a.y + m.d.y*invm.b.y + m.d.z*invm.c.y);
# Get the last three subdeterminants.
subDetX = Math::Determinant22( m.a.y,m.a.z,m.b.y,m.b.z );
subDetY = Math::Determinant22( m.a.x,m.a.z,m.b.x,m.b.z );
subDetZ = Math::Determinant22( m.a.x,m.a.y,m.b.x,m.b.y );
# Finish making the inverse matrix.
invm.a.z = subDetX*invDet;
invm.b.z = -subDetY*invDet;
invm.c.z = subDetZ*invDet;
invm.d.z = -(m.d.x*invm.a.z + m.d.y*invm.b.z + m.d.z*invm.c.z);
# Return inverted matrix (the determinant was not too close to zero).
return ( invm )
end
# The determinant of this matrix is too close to zero to do an accurate inverse.
Matrix34::identity( )
end
#
# Construct a Matrix34 object from a XML node. This should be coded
# to handle both libXML and REXML XML node types, which it does
# provided the Vector3::from_xml handles both libraries.
#
def Matrix34::from_xml( xml_node )
a = Vector3::from_xml( xml_node.find_first( 'a' ) )
b = Vector3::from_xml( xml_node.find_first( 'b' ) )
c = Vector3::from_xml( xml_node.find_first( 'c' ) )
d = Vector3::from_xml( xml_node.find_first( 'd' ) )
Matrix34.new( a, b, c, d )
end
#
# Construct a Matrix34 object from a Quaternion (Quat) object,
# representing a rotation.
#
def Matrix34::from_quat( quat )
tx = quat.x * M_SQRT2
ty = quat.y * M_SQRT2
tz = quat.z * M_SQRT2
tw = quat.w * M_SQRT2
ma = Vector3.new
mb = Vector3.new
mc = Vector3.new
md = Vector3.new
ma.y = tx*ty + tz*tw;
mb.x = tx*ty - tz*tw;
ma.z = tx*tz - ty*tw;
mc.x = tx*tz + ty*tw;
mb.z = ty*tz + tx*tw;
mc.y = ty*tz - tx*tw;
ty *= ty; ## need squares along diagonal
tz *= tz;
tx *= tx;
ma.x = 1.0 - (ty + tz)
mb.y = 1.0 - (tz + tx)
mc.z = 1.0 - (ty + tx)
Matrix34.new( ma, mb, mc, md )
end
end
#
# Return the 2x2 matrix determinant.
#
def Math::Determinant22( a, b, c, d )
( ( a * d ) - ( b * c ) )
end
#
# Return the 3x3 matrix determinant.
#
def Math::Determinant33( ax, ay, az, bx, by, bz, cx, cy, cz )
( ax*by*cz ) + ( ay*bz*cx ) + ( az*bx*cy ) - ( ax*bz*cy ) - ( ay*bx*cz ) - ( az*by*cx )
end
end # Math module
end # Pipeline module
# matrix.rb