# # File:: bbox.rb # Description:: Bounding Box Class # # Author:: David Muir # Date:: 28 February 2008 # #----------------------------------------------------------------------------- # Uses #----------------------------------------------------------------------------- require 'pipeline/math/vector2' require 'pipeline/math/vector3' #----------------------------------------------------------------------------- # Implementation #----------------------------------------------------------------------------- module Pipeline module Math # # == Description # Class representing a 3D Bounding Box. # class BoundingBox3 class Invalid < Exception; end XML_TAG = 'boundingbox3' attr_accessor :min attr_accessor :max def initialize( min = nil, max = nil ) throw Invalid.new( "BoundingBox3 min must be Vector3 type (#{min.class})." ) \ unless ( min.nil? or min.is_a?( Vector3 ) ) throw Invalid.new( "BoundingBox3 max must be Vector3 type (#{max.class}).") \ unless ( max.nil? or max.is_a?( Vector3 ) ) @min = Vector3.new( Float::MAX, Float::MAX, Float::MAX ) if ( min.nil? ) @min = min unless ( min.nil? ) @max = Vector3.new( Float::MIN, Float::MIN, Float::MIN ) if ( max.nil? ) @max = max unless ( max.nil? ) end # # Return true if vector p is within the bounding box, false otherwise. # def contains( p ) raise ArgumentError.new( "p must be a Vector3 or BoundingBox3 object" ) \ unless ( p.is_a?( Vector3 ) or ( p.is_a?( BoundingBox3 ) ) ) # Vector3 Test return (( p.x >= @min.x ) and ( p.y >= @min.y ) and ( p.z >= @min.z ) and ( p.x <= @max.x ) and ( p.y <= @max.y ) and ( p.z <= @max.z )) if ( p.is_a?( Vector3 ) ) # BoundingBox Test return ( ( p.min >= self.min ) and ( p.max <= self.max ) ) if ( p.is_a?( BoundingBox3 ) ) end # # Expand the bounding box to include the specified Vector3 or BoundingBox3. # def expand( v ) raise ArgumentError.new( "v must be a Vector3 or BoundingBox3 object (#{v.class})." ) \ unless ( v.is_a?( Vector3 ) or v.is_a?( BoundingBox3 ) ) if ( v.is_a?( Vector3 ) ) then @min.x = v.x if v.x < @min.x @min.y = v.y if v.y < @min.y @min.z = v.z if v.z < @min.z @max.x = v.x if v.x > @max.x @max.y = v.y if v.y > @max.y @max.z = v.z if v.z > @max.z elsif ( v.is_a?( BoundingBox3 ) ) then expand( v.min ) expand( v.max ) end end # # Alias for contains. # alias include? contains #-------------------------------------------------------------------- # Class Methods #-------------------------------------------------------------------- # # Used by the SceneXml module in pipeline/fileformats, to read the # bounding box representation from an XML file. # def BoundingBox3::from_xml( xml_node ) raise ArgumentError.new( "Invalid BoundingBox3 node, incorrect name: #{xml_node.name}." ) \ unless ( XML_TAG == xml_node.name ) min = nil max = nil if ( xml_node.is_a?( LibXML::XML::Node ) ) then min = Vector3::from_xml( xml_node.find_first( 'min' ) ) max = Vector3::from_xml( xml_node.find_first( 'max' ) ) elsif ( xml_node.is_a?( REXML::Node ) ) then min = Vector3::from_xml( xml_node.elements['min'] ) max = Vector3::from_xml( xml_node.elements['max'] ) end BoundingBox3::new( min, max ) end end # # == Description # Class representing a 2D Bounding Box. # class BoundingBox2 class Invalid < Exception; end XML_TAG = 'boundingbox2' attr_accessor :min attr_accessor :max def initialize( min = nil, max = nil ) throw Invalid.new( "BoundingBox2 min must be Vector2 type (#{min.class})." ) \ unless ( min.nil? or min.is_a?( Vector2 ) ) throw Invalid.new( "BoundingBox2 max must be Vector2 type (#{max.class}).") \ unless ( max.nil? or max.is_a?( Vector2 ) ) @min = Vector2.new( Float::MAX, Float::MAX ) if ( min.nil? ) @min = min unless ( min.nil? ) @max = Vector2.new( Float::MIN, Float::MIN ) if ( max.nil? ) @max = max unless ( max.nil? ) end # # Return true if vector p is within the bounding box, false otherwise. # def contains( p ) raise ArgumentError.new( "p must be a Vector2 or BoundingBox2 object" ) \ unless ( p.is_a?( Vector2 ) or ( p.is_a?( BoundingBox2 ) ) ) # Vector3 Test return ( ( p.x >= @min.x ) and ( p.y >= @min.y ) and ( p.x <= @max.x ) and ( p.y <= @max.y ) ) if ( p.is_a?( Vector3 ) ) # BoundingBox2 Test return ( ( p.min >= self.min ) and ( p.max <= self.max ) ) if ( p.is_a?( BoundingBox2 ) ) end # # Expand the bounding box to include the specified Vector2 or BoundingBox2. # def expand( v ) raise ArgumentError.new( "v must be a Vector2 or BoundingBox2 object (#{v.class})." ) \ unless ( v.is_a?( Vector2 ) or v.is_a?( BoundingBox2 ) ) if ( v.is_a?( Vector3 ) ) then @min.x = v.x if v.x < @min.x @min.y = v.y if v.y < @min.y @max.x = v.x if v.x > @max.x @max.y = v.y if v.y > @max.y elsif ( v.is_a?( BoundingBox2 ) ) then expand( v.min ) expand( v.max ) end end # # Alias for contains. # alias include? contains #-------------------------------------------------------------------- # Class Methods #-------------------------------------------------------------------- # # Used by the SceneXml module in pipeline/fileformats, to read the # bounding box representation from an XML file. # def BoundingBox2::from_xml( xml_node ) raise ArgumentError.new( "Invalid boundingbox2 node, incorrect name: #{xml_node.name}." ) \ unless ( XML_TAG == xml_node.name ) min = nil max = nil if ( xml_node.is_a?( LibXML::XML::Node ) ) then min = Vector2::from_xml( xml_node.find_first( 'min' ) ) max = Vector2::from_xml( xml_node.find_first( 'max' ) ) elsif ( xml_node.is_a?( REXML::Node ) ) then min = Vector2::from_xml( xml_node.elements['min'] ) max = Vector2::from_xml( xml_node.elements['max'] ) end BoundingBox2::new( min, max ) end end end # Math module end # Pipeline module # End of bbox.rb