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

207 lines
6.6 KiB
Ruby
Executable File

#
# File:: bbox.rb
# Description:: Bounding Box Class
#
# Author:: David Muir <david.muir@rockstarnorth.com>
# 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