# # File:: vector.rb # Description:: Vector classes (2, 3 and 4 component). # # Author:: David Muir # Date:: 28 February 2008 # #---------------------------------------------------------------------------- # Uses #---------------------------------------------------------------------------- require 'pipeline/math/angles' require 'pipeline/util/float' require 'mathn' #---------------------------------------------------------------------------- # Implementation #---------------------------------------------------------------------------- module Pipeline module Math # # == Description # 4D vector class. # class Vector4 attr_reader :x attr_reader :y attr_reader :z attr_reader :w # Virtual methods for R, G, B, A access alias r x alias g y alias b z alias a w # # == Description # Vector class constructor, taking three optional arguments. All # vector components must be of the same class otherwise an # ArgumentError exception is raised. # def initialize( x = 0.0, y = 0.0, z = 0.0, w = 0.0 ) @x = x @y = y @z = z @w = w end # # Addition with another vector object or scalar. # def +( v ) if ( v.is_a?( Vector4 ) ) Vector4.new( @x + v.x, @y + v.y, @z + v.z, @w + v.w ) elsif ( v.is_a?( Numeric ) ) Vector4.new( @x + v, @y + v, @z + v, @w + v ) else throw ArgumentError.new( "Addition of Vector4 and #{v.class} not supported." ) end end # # Subtraction of another vector object or scalar. # def -( v ) if ( v.is_a?( Vector4 ) ) Vector4.new( @x - v.x, @y - v.y, @z - v.z, @w - v.w ) elsif ( v.is_a?( Numeric ) ) Vector4.new( @x - v, @y - v, @z - v, @w - v ) else throw ArgumentError.new( "Subtraction of #{v.class} from Vector4 not supported." ) end end # # Multiplication by a scalar. # def *( s ) if ( s.is_a?( Numeric ) ) Vector4.new( @x * s, @y * s, @z * s, @w * s ) else throw ArgumentError.new( "Multiplication by #{s.class} is not supported." ) end end # # == Description # Return magnitude of vector. # def magnitude ::Math::sqrt( @x*@x + @y*@y + @z*@z + @w*@w ) end # # == Description # Alias for vector magnitude. # alias length magnitude # # == Description # Return the inverse magnitude of this vector. # def inverse_magnitude ::Math::rsqrt( dot( self ) ) end # # == Description # Normalise this vector. # def normalise! scale!( inverse_magnitude ) end # # == Description # Return this vector normalised # def normalise scale( inverse_magnitude ) end alias normalize! normalise! alias normalize normalise # # == Description # Scale this vector. # def scale!( f ) @x *= f @y *= f @z *= f @w *= f end # # == Description # Return this vector scaled by multiplier f. # def scale( f ) Vector.new( @x * f, @y * f, @z * f, @w * f ) end # # == Description # Return this vector scaled by inverse of f. # def inverse_scale( f ) inv_f = 1.0 / f scale( inf_f ) end # # == Description # Scale this vector by the inverse of a value. def inverse_scale!( f ) inv_f = 1.0 / f scale!( inv_f ) end # # == Description # Find and return dot product of this vector and v2. # def dot( v2 ) ( (@x * v2.x) + (@y * v2.y) + (@z * v2.z) + (@w * v2.w) ) end #--------------------------------------------------------------------- # Operators #--------------------------------------------------------------------- # # [] operator for readonly access to components # def [](index) case index when 0 return @x when 1 return @y when 2 return @z when 3 return @w else raise ArgumentError.new( "0 <= index <= 3 for xyzw vector" ) end end #--------------------------------------------------------------------- # Overloaded Methods #--------------------------------------------------------------------- # # # def to_a( ) [ @x, @y, @z, @w ] end # # String conversion function for vectors. Optional precision flag # can be used if x, y and z components are all Float. # def to_s( prec = nil ) all_float = ( Float == @x.class and Float == @y.class and Float == @z.class and Float == @w.class ) if ( all_float and !prec.nil? ) return "[ #{@x.to_s(prec)}, #{@y.to_s(prec)}, #{@z.to_s(prec)}, #{@w.to_s(prec)} ]" else return "[ #{@x}, #{@y}, #{@z}, #{@w} ]" end end #--------------------------------------------------------------------- # Class Methods #--------------------------------------------------------------------- # # Construct a Vector4 object from a XML node. This should be coded # to handle both libXML and REXML XML node types. # def Vector4::from_xml( xml_node ) if ( xml_node.is_a?( LibXML::XML::Node ) ) then if ( xml_node['x'] and xml_node['y'] and xml_node['z'] and xml_node['w'] ) then Vector4.new( xml_node['x'].to_f, xml_node['y'].to_f, xml_node['z'].to_f, xml_node['w'].to_f ) elsif ( xml_node['r'] and xml_node['g'] and xml_node['b'] and xml_node['a'] ) then Vector4.new( xml_node['r'].to_f, xml_node['g'].to_f, xml_node['b'].to_f, xml_node['a'].to_f ) end elsif ( xml_node.is_a?( REXML::Node ) ) then if ( xml_node.attributes['x'] and xml_node.attributes['y'] and xml_node.attributes['z'] and xml_node.attributes['w'] ) then Vector4.new( xml_node.attributes['x'].to_f, xml_node.attributes['y'].to_f, xml_node.attributes['z'].to_f, xml_node.attributes['w'].to_f ) elsif ( xml_node.attributes['r'] and xml_node.attributes['g'] and xml_node.attributes['b'] and xml_node.attributes['a'] ) then Vector4.new( xml_node.attributes['r'].to_f, xml_node.attributes['g'].to_f, xml_node.attributes['b'].to_f, xml_node.attributes['a'].to_f ) end end end end end # Math module end # Pipeline module # End of vector.rb