#-- # Rubygame -- Ruby code and bindings to SDL to facilitate game creation # Copyright (C) 2004-2007 John Croisant # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # 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 for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #++ #-- # Table of Contents: # # class Rect # GENERAL: # initialize # new_from_object # to_s # to_a, to_ary # [] # ATTRIBUTES: # x, y, w, h [<- accessors] # width, height, size # left, top, right, bottom # center, centerx, centery # topleft, topright # bottomleft, bottomright # midleft, midtop, midright, midbottom # UTILITY METHODS: # clamp, clamp! # clip, clip! # collide_hash, collide_hash_all # collide_array, collide_array_all # collide_point? # collide_rect? # contain? # inflate, inflate! # move, move! # normalize, normalize! # union, union! # union_all, union_all! # # class Surface # make_rect # #++ #module Rubygame # A Rect is a representation of a rectangle, with four core attributes # (x offset, y offset, width, and height) and a variety of functions # for manipulating and accessing these attributes. # # Like all coordinates in Rubygame (and its base library, SDL), x and y # offsets are measured from the top-left corner of the screen, with greater # y offsets being lower. Thus, specifying the x and y offsets of the Rect # is equivalent to setting the location of its top-left corner. # # In Rubygame, Rects are used for collision detection and describing # the area of a Surface to operate on. class Rect < Array include MinMaxHelpers #-- # GENERAL #++ # Create a new Rect, attempting to extract its own information from # the given arguments. The arguments must fall into one of these cases: # # - 4 integers +(x, y, w, h)+. # - 1 Rect or Array containing 4 integers +([x, y, w, h])+. # - 2 Arrays containing 2 integers each +([x,y], [w,h])+. # - 1 object with a +rect+ attribute which is a valid Rect object. # # All rect core attributes (x,y,w,h) must be integers. # def initialize(*argv) case argv.length when 1 if argv[0].kind_of? Array; super(argv[0]) elsif argv[0].respond_to? :rect; super(argv[0]) end when 2 super(argv[0].concat(argv[1])) when 4 super(argv) end return self end # Extract or generate a Rect from the given object, if possible, using the # following process: # # 1. If it's a Rect already, return a duplicate Rect. # 2. Elsif it's an Array with at least 4 values, make a Rect from it. # 3. Elsif it has a +rect+ attribute., perform (1) and (2) on that. # 4. Otherwise, raise TypeError. # # See also Surface#make_rect() def Rect.new_from_object(object) case(object) when Rect return object.dup when Array if object.length >= 4 return Rect.new(object) else raise(ArgumentError,"Array does not have enough indices to be made into a Rect (%d for 4)."%object.length ) end else begin case(object.rect) when Rect return object.rect.dup when Array if object.rect.length >= 4 return Rect.new(object.rect) else raise(ArgumentError,"Array does not have enough indices to be made into a Rect (%d for 4)."%object.rect.length ) end end # case object.rect rescue NoMethodError # if no rect.rect raise(TypeError,"Object must be a Rect or Array [x,y,w,h], or have an attribute called 'rect'. (Got %s instance.)"%object.class) end end # case object end # Print the Rect in the form "+#+" def to_s; "#"%self; end # Print the Rect in the form "+#+" def inspect; "#"%self; end #-- # ATTRIBUTES #++ # Returns self[0] def x; return self[0]; end # Sets self[0] to +val+ def x=(val); self[0] = val; end alias left x alias left= x=; alias l x alias l= x=; # Returns self[1] def y; return self[1]; end # Sets self[1] to +val+ def y=(val); self[1] = val; end alias top y alias top= y=; alias t y alias t= y=; # Returns self[2] def w; return self[2]; end # Sets self[2] to +val+ def w=(val); self[2] = val; end alias width w alias width= w=; # Returns self[3] def h; return self[3]; end # Sets self[3] to +val+ def h=(val); self[3] = val; end alias height h alias height= h=; # Return the width and height of the Rect. def size; return self[2,2]; end # Set the width and height of the Rect. def size=(size) raise ArgumentError, "Rect#size= takes an Array of form [width, height]." if size.size != 2 self[2,2] = size size end # Return the x coordinate of the right side of the Rect. def right; return self[0]+self[2]; end # Set the x coordinate of the right side of the Rect by translating the # Rect (adjusting the x offset). def right=(r); self[0] = r - self[2]; return r; end alias r right alias r= right=; # Return the y coordinate of the bottom side of the Rect. def bottom; return self[1]+self[3]; end # Set the y coordinate of the bottom side of the Rect by translating the # Rect (adjusting the y offset). def bottom=(b); self[1] = b - self[3]; return b; end alias b bottom alias b= bottom=; # Return the x and y coordinates of the center of the Rect. def center; return self.centerx, self.centery; end # Set the x and y coordinates of the center of the Rect by translating the # Rect (adjusting the x and y offsets). def center=(center) raise ArgumentError, "Rect#center= takes an Array of the form [x,y]." if center.size != 2 self.centerx, self.centery = center center end alias c center alias c= center=; # Return the x coordinate of the center of the Rect def centerx; return self[0]+(self[2].div(2)); end # Set the x coordinate of the center of the Rect by translating the # Rect (adjusting the x offset). def centerx=(x); self[0] = x - (self[2].div(2)); return x; end alias cx centerx alias cx= centerx=; # Return the y coordinate of the center of the Rect def centery; return self[1]+(self[3].div(2)); end # Set the y coordinate of the center of the Rect by translating the # Rect (adjusting the y offset). def centery=(y); self[1] = y- (self[3].div(2)); return y; end alias cy centery alias cy= centery=; # Return the x and y coordinates of the top-left corner of the Rect def topleft; return self[0,2].to_a; end # Set the x and y coordinates of the top-left corner of the Rect by # translating the Rect (adjusting the x and y offsets). def topleft=(topleft) raise ArgumentError, "Rect#topright= takes an Array of form [x, y]." if topleft.size != 2 self[0,2] = topleft return topleft end alias tl topleft alias tl= topleft=; # Return the x and y coordinates of the top-right corner of the Rect def topright; return self.right, self[1]; end # Set the x and y coordinates of the top-right corner of the Rect by # translating the Rect (adjusting the x and y offsets). def topright=(topright) raise ArgumentError, "Rect#topright= takes an Array of form [x, y]." if topright.size != 2 self.right, self[1] = topright return topright end alias tr topright alias tr= topright=; # Return the x and y coordinates of the bottom-left corner of the Rect def bottomleft; return self[0], self.bottom; end # Set the x and y coordinates of the bottom-left corner of the Rect by # translating the Rect (adjusting the x and y offsets). def bottomleft=(bottomleft) raise ArgumentError, "Rect#bottomleft= takes an Array of form [x, y]." if bottomleft.size != 2 self[0], self.bottom = bottomleft return bottomleft end alias bl bottomleft alias bl= bottomleft=; # Return the x and y coordinates of the bottom-right corner of the Rect def bottomright; return self.right, self.bottom; end # Set the x and y coordinates of the bottom-right corner of the Rect by # translating the Rect (adjusting the x and y offsets). def bottomright=(bottomright) raise ArgumentError, "Rect#bottomright= takes an Array of form [x, y]." if bottomright.size != 2 self.right, self.bottom = bottomright return bottomright end alias br bottomright alias br= bottomright=; # Return the x and y coordinates of the midpoint on the left side of the # Rect. def midleft; return self[0], self.centery; end # Set the x and y coordinates of the midpoint on the left side of the Rect # by translating the Rect (adjusting the x and y offsets). def midleft=(midleft) raise ArgumentError, "Rect#midleft= takes an Array of form [x, y]." if midleft.size != 2 self[0], self.centery = midleft return midleft end alias ml midleft alias ml= midleft=; # Return the x and y coordinates of the midpoint on the left side of the # Rect. def midtop; return self.centerx, self[1]; end # Set the x and y coordinates of the midpoint on the top side of the Rect # by translating the Rect (adjusting the x and y offsets). def midtop=(midtop) raise ArgumentError, "Rect#midtop= takes an Array of form [x, y]." if midtop.size != 2 self.centerx, self[1] = midtop return midtop end alias mt midtop alias mt= midtop=; # Return the x and y coordinates of the midpoint on the left side of the # Rect. def midright; return self.right, self.centery; end # Set the x and y coordinates of the midpoint on the right side of the Rect # by translating the Rect (adjusting the x and y offsets). def midright=(midright) raise ArgumentError, "Rect#midright= takes an Array of form [x, y]." if midright.size != 2 self.right, self.centery = midright return midright end alias mr midright alias mr= midright=; # Return the x and y coordinates of the midpoint on the left side of the # Rect. def midbottom; return self.centerx, self.bottom; end # Set the x and y coordinates of the midpoint on the bottom side of the # Rect by translating the Rect (adjusting the x and y offsets). def midbottom=(midbottom) raise ArgumentError, "Rect#midbottom= takes an Array of form [x, y]." if midbottom.size != 2 self.centerx, self.bottom = midbottom return midbottom end alias mb midbottom alias mb= midbottom=; #-- # UTILITY METHODS #++ # As #clamp!, but the original caller is not changed. def clamp(rect) self.dup.clamp!(rect) end # Translate the calling Rect to be entirely inside the given Rect. If # the caller is too large along either axis to fit in the given rect, # it is centered with respect to the given rect, along that axis. def clamp!(rect) nself = self.normalize rect = Rect.new_from_object(rect) #If self is inside given, there is no need to move self unless rect.contain?(nself) #If self is too wide: if nself[2] >= rect[2] self[0] = rect.centerx - nself[2].div(2) #Else self is not too wide else #If self is to the left of arg if nself[0] < rect[0] self[0] = rect[0] #If self is to the right of arg elsif nself.right > rect.right self[0] = rect.right - nself[2] #Otherwise, leave x alone end end #If self is too tall: if nself[3] >= rect[3] self[1] = rect.centery - nself[3].div(2) #Else self is not too tall else #If self is above arg if nself[1] < rect[1] self[1] = rect[1] #If self below arg elsif nself.bottom > rect.bottom self[1] = rect.bottom - nself[3] #Otherwise, leave y alone end end end return self end # As #clip!, but the original caller is not changed. def clip(rect) self.dup.clip!(rect) end # Crop the calling Rect to be entirely inside the given Rect. If the # caller does not intersect the given Rect at all, its width and height # are set to zero, but its x and y offsets are not changed. # # As a side effect, the Rect is normalized. def clip!(rect) nself = self.normalize other = Rect.new_from_object(rect).normalize! if self.collide_rect?(other) self[0] = [nself[0], other[0]].max self[1] = [nself[1], other[1]].max self[2] = [nself.right, other.right].min - self[0] self[3] = [nself.bottom, other.bottom].min - self[1] else #if they do not intersect at all: self[0], self[1] = nself.topleft self[2], self[3] = 0, 0 end return self end # Iterate through all key/value pairs in the given hash table, and # return the first pair whose value is a Rect that collides with the # caller. # # Because a hash table is unordered, you should not expect any # particular Rect to be returned first. def collide_hash(hash_rects) hash_rects.each { |key,value| if value.collide_rect?+(self); return [key,value]; end } return nil end # Iterate through all key/value pairs in the given hash table, and # return an Array of every pair whose value is a Rect that collides # the caller. # # Because a hash table is unordered, you should not expect the returned # pairs to be in any particular order. def collide_hash_all(hash_rects) hash_rects.select { |key,value| value.collide_rect?+(self) } end # Iterate through all elements in the given Array, and return # the *index* of the first element which is a Rect that collides with # the caller. def collide_array(array_rects) for i in (0...(array_rects.length)) if array_rects[i].collide_rect?(self) return i end end return nil end # Iterate through all elements in the given Array, and return # an Array containing the *indices* of every element that is a Rect # that collides with the caller. def collide_array_all(array_rects) indexes = [] for i in (0...(array_rects.length)) if array_rects[i].collide_rect?(self) indexes += [i] end end return indexes end # True if the point is inside (including on the border) of the caller. # If you have Array of coordinates, you can use collide_point?(*coords). def collide_point?(x,y) nself = normalize() x.between?(nself.left,nself.right) && y.between?(nself.top,nself.bottom) end # True if the caller and the given Rect overlap (or touch) at all. def collide_rect?(rect) nself = self #.normalize rect = Rect.new_from_object(rect)#.normalize! return ((nself.l >= rect.l && nself.l <= rect.r) or (rect.l >= nself.l && rect.l <= nself.r)) && ((nself.t >= rect.t && nself.t <= rect.b) or (rect.t >= nself.t && rect.t <= nself.b)) end # True if the given Rect is totally within the caller. Borders may # overlap. def contain?(rect) nself = self#.normalize rect = Rect.new_from_object(rect)#.normalize! return (nself.left <= rect.left and rect.right <= nself.right and nself.top <= rect.top and rect.bottom <= nself.bottom) end # As #inflate!, but the original caller is not changed. def inflate(x,y) return self.class.new(self[0] - x.div(2), self[1] - y.div(2), self[2] + x, self[3] + y) end # Increase the Rect's size is the x and y directions, while keeping the # same center point. For best results, expand by an even number. # X and y inflation can be given as an Array or as separate values. def inflate!(x,y) self[0] -= x.div(2) self[1] -= y.div(2) self[2] += x self[3] += y return self end # As #move!, but the original caller is not changed. def move(x,y) self.dup.move!(x,y) end # Translate the Rect by the given amounts in the x and y directions. # Positive values are rightward for x and downward for y. # X and y movement can be given as an Array or as separate values. def move!(x,y) self[0]+=x; self[1]+=y return self end # As #normalize!, but the original caller is not changed. def normalize self.dup.normalize!() end # Fix Rects that have negative width or height, without changing the # area it represents. Has no effect on Rects with non-negative width # and height. Some Rect methods will automatically normalize the Rect. def normalize! if self[2] < 0 self[0], self[2] = self[0]+self[2], -self[2] end if self[3] < 0 self[1], self[3] = self[1]+self[3], -self[3] end self end # As #union!, but the original caller is not changed. def union(rect) self.dup.union!(rect) end # Expand the caller to also cover the given Rect. The Rect is still a # rectangle, so it may also cover areas that neither of the original # Rects did, for example areas between the two Rects. def union!(rect) self.normalize! rleft, rtop = self.topleft rright, rbottom = self.bottomright r2 = Rect.new_from_object(rect).normalize! rleft = [rleft, r2.left].min rtop = [rtop, r2.top].min rright = [rright, r2.right].max rbottom = [rbottom, r2.bottom].max self[0,4] = rleft, rtop, rright - rleft, rbottom - rtop return self end # As #union_all!, but the original caller is not changed. def union_all(array_rects) self.dup.union_all!(array_rects) end # Expand the caller to cover all of the given Rects. See also #union! def union_all!(array_rects) array_rects.each do |r| self.union!(r) end return self end # calculate the area of the Rect def area w * h end def zero_out! self[0] = 0 self[1] = 0 self[2] = 0 self[3] = 0 end def union_area(rect) rleft = self.left rtop = self.top rright = self.right rbottom = self.bottom r2 = Rect.new_from_object(rect) rleft = min(rleft, r2.left) rtop = min(rtop, r2.top) rright = max(rright, r2.right) rbottom = max(rbottom, r2.bottom) (rright - rleft) * (rbottom - rtop) end def union_fast(rect) rleft = self.left rtop = self.top rright = self.right rbottom = self.bottom r2 = Rect.new_from_object(rect) rleft = min(rleft, r2.left) rtop = min(rtop, r2.top) rright = max(rright, r2.right) rbottom = max(rbottom, r2.bottom) Rect.new(rleft, rtop, rright - rleft, rbottom - rtop) end def expand_to_include!(rect) rleft = self.left rtop = self.top rright = self.right rbottom = self.bottom r2 = Rect.new_from_object(rect) rleft = min(rleft, r2.left) rtop = min(rtop, r2.top) rright = max(rright, r2.right) rbottom = max(rbottom, r2.bottom) self.x = rleft self.y = rtop self.w = rright - rleft self.h = rbottom - rtop end def refit_for!(rect_a, rect_b) self.zero_out! self.expand_to_include! rect_a self.expand_to_include! rect_b end end # class Rect #end # module Rubygame