/* RUDL - a C library wrapping SDL for use in Ruby. Copyright (C) 2001, 2002, 2003 Danny van Bruggen $Log: rudl_video_rect.c,v $ Revision 1.13 2004/10/23 19:54:22 rennex Fixed docs Revision 1.12 2004/08/04 23:03:46 tsuihark Updated all documentation to Dokumentat format. Revision 1.11 2004/01/06 18:01:39 tsuihark Removed a few warnings Revision 1.10 2003/09/26 23:06:13 tsuihark Fixed damn EOLs and added some headers Revision 1.9 2003/09/26 23:04:29 tsuihark Fixed damn EOLs and added some headers */ #include "rudl_events.h" #include "rudl_video.h" static VALUE rb_array_overlaps(VALUE self, VALUE otherRect); ///////////////////////////////// ARRAY __inline__ static bool intersect(SDL_Rect *a, SDL_Rect *b) { if(!((a->x >= b->x) && (a->x < (b->x+b->w))) && !((b->x >= a->x) && (b->x < (a->x+a->w))) && !(((a->x+a->w) > b->x) && ((a->x+a->w) <= (b->x+b->w))) && !(((b->x+b->w) > a->x) && ((b->x+b->w) <= (a->x+a->w)))) return false; if(!((a->y >= b->y) && (a->y < (b->y+b->h))) && !((b->y >= a->y) && (b->y < (a->y+a->h))) && !(((a->y+a->h) > b->y) && ((a->y+a->h) <= (b->y+b->h))) && !(((b->y+b->h) > a->y) && ((b->y+b->h) <= (a->y+a->h)))) return false; return true; } #define GET_X() double x=array_get_x(self) #define GET_Y() double y=array_get_y(self) #define GET_W() double w=array_get_w(self) #define GET_H() double h=array_get_h(self) #define SET_X(x) array_set_x(self, (x)) #define SET_Y(x) array_set_y(self, (x)) #define SET_W(x) array_set_w(self, (x)) #define SET_H(x) array_set_h(self, (x)) __inline__ static VALUE array_get_x_value(VALUE array) {return rb_ary_entry(array, 0);} __inline__ static VALUE array_get_y_value(VALUE array) {return rb_ary_entry(array, 1);} __inline__ static VALUE array_get_w_value(VALUE array) {return rb_ary_entry(array, 2);} __inline__ static VALUE array_get_h_value(VALUE array) {return rb_ary_entry(array, 3);} __inline__ static double array_get_x(VALUE array) {return NUM2DBL(rb_ary_entry(array, 0));} __inline__ static double array_get_y(VALUE array) {return NUM2DBL(rb_ary_entry(array, 1));} __inline__ static double array_get_w(VALUE array) {return NUM2DBL(rb_ary_entry(array, 2));} __inline__ static double array_get_h(VALUE array) {return NUM2DBL(rb_ary_entry(array, 3));} __inline__ static void array_set_x_value(VALUE array, VALUE x) {rb_ary_store(array, 0, x);} __inline__ static void array_set_y_value(VALUE array, VALUE y) {rb_ary_store(array, 1, y);} __inline__ static void array_set_w_value(VALUE array, VALUE w) {rb_ary_store(array, 2, w);} __inline__ static void array_set_h_value(VALUE array, VALUE h) {rb_ary_store(array, 3, h);} __inline__ static void array_set_x(VALUE array, double x) {rb_ary_store(array, 0, DBL2NUM(x));} __inline__ static void array_set_y(VALUE array, double y) {rb_ary_store(array, 1, DBL2NUM(y));} __inline__ static void array_set_w(VALUE array, double w) {rb_ary_store(array, 2, DBL2NUM(w));} __inline__ static void array_set_h(VALUE array, double h) {rb_ary_store(array, 3, DBL2NUM(h));} __inline__ void PARAMETER2CRECT(VALUE arg1, SDL_Rect* rect) { VALUE tmp; Check_Type(arg1, T_ARRAY); tmp=rb_ary_entry(arg1, 0); rect->x=NUM2Sint16(tmp); tmp=rb_ary_entry(arg1, 1); rect->y=NUM2Sint16(tmp); tmp=rb_ary_entry(arg1, 2); rect->w=NUM2Uint16(tmp); tmp=rb_ary_entry(arg1, 3); rect->h=NUM2Uint16(tmp); } __inline__ void RECT2CRECT(VALUE source, SDL_Rect* destination) { destination->x=(Sint16)array_get_x(source); destination->y=(Sint16)array_get_y(source); destination->w=(Uint16)array_get_w(source); destination->h=(Uint16)array_get_h(source); } __inline__ VALUE clone_array(VALUE array) { return rb_funcall(array, id_clone, 0); } __inline__ VALUE new_rect(double x, double y, double w, double h) { return rb_ary_new3(4, DBL2NUM(x), DBL2NUM(y), DBL2NUM(w), DBL2NUM(h)); } __inline__ VALUE new_rect_from_SDL_Rect(SDL_Rect* source) { return new_rect(source->x, source->y, source->w, source->h); } __inline__ static void normalize(VALUE self) { GET_W(); GET_H(); if(w<0){ GET_X(); x=x+w; w=-w; SET_X(x); SET_W(w); } if(h<0){ GET_Y(); y=y+h; h=-h; SET_Y(y); SET_H(h); } } /** @file Misc @class Rect Rect has been discarded. Its methods have moved to the standard Ruby @Array. All these methods are now written in C. */ /** @class Array The standard Ruby array class has been extended to provide methods for using it as a rectangle. Arrays like these have entries 0, 1, 2 and 3 set to floating point values for x, y, width and height. [10,15,20,25] would be a 20x25 rectangle at position 10,15. So far these methods are mostly untested. */ /** @section Class Methods */ struct ExtRect{ SDL_Rect crect; VALUE rect; VALUE sprite; }; /** @method collide_lists( l1, l2 ) This method looks through list @l1, checking collisions with every object in list @l2. It does this by calling "rect" on all objects, expecting an array of [x,y,w,h] back, defining the area this object is in. It yields (object_from_l1, object_from_l2) for every collision it detects. A more advanced collision detection method can be found in CollisionMap. */ static VALUE rb_array_collide_lists(VALUE self, VALUE list1Value, VALUE list2Value) { struct ExtRect* list2=NULL; SDL_Rect rect1; int i1, i2; int list1len, list2len; VALUE yieldValue=rb_ary_new2(2); VALUE sprite; Check_Type(list1Value, T_ARRAY); Check_Type(list2Value, T_ARRAY); list1len=RARRAY(list1Value)->len; list2len=RARRAY(list2Value)->len; if(list1len==0 || list2len==0) return self; list2=(struct ExtRect*)malloc(list2len*sizeof(struct ExtRect)); for(i2=0; i2len==0){ return Qnil; } rect=rb_ary_entry(other_rects, 0); left=array_get_x(rect); right=array_get_w(rect)+left; top=array_get_y(rect); bottom=array_get_h(rect)+top; for(i=1; ilen; i++){ rect=rb_ary_entry(other_rects, i); l=array_get_x(rect); r=array_get_w(rect)+l; t=array_get_y(rect); b=array_get_h(rect)+t; left=RUDL_MIN(left, l); right=RUDL_MAX(right, r); top=RUDL_MIN(top, t); bottom=RUDL_MAX(bottom, b); } return new_rect(left, top, right-left, bottom-top); } /* accessors */ /** @method x @method x=( x ) @method y @method y=( y ) @method w @method w=( w ) @method h @method h=( h ) These can be set and read at will. @w and @h have @width and @height as aliases. */ static VALUE rb_array_get_x(VALUE self) {return array_get_x_value(self);} static VALUE rb_array_get_y(VALUE self) {return array_get_y_value(self);} static VALUE rb_array_get_w(VALUE self) {return array_get_w_value(self);} static VALUE rb_array_get_h(VALUE self) {return array_get_h_value(self);} static VALUE rb_array_set_x(VALUE self, VALUE new_x) {array_set_x_value(self, new_x); return self;} static VALUE rb_array_set_y(VALUE self, VALUE new_y) {array_set_y_value(self, new_y); return self;} static VALUE rb_array_set_w(VALUE self, VALUE new_w) {array_set_w_value(self, new_w); return self;} static VALUE rb_array_set_h(VALUE self, VALUE new_h) {array_set_h_value(self, new_h); return self;} /** @section Instance methods @method left @method left=( left ) @method right @method right=( right ) @method top @method top=( top ) @method bottom @method bottom=( bottom ) These can be set and read at will. Differences with x, y, w and h are: * @right and @bottom are screen coordinates. @right is @x + @w and @bottom is @y + @h . */ static VALUE rb_array_set_right(VALUE self, VALUE new_right) { SET_X(NUM2DBL(new_right)-array_get_w(self)); return self; } static VALUE rb_array_set_bottom(VALUE self, VALUE new_bottom) { SET_Y(NUM2DBL(new_bottom)-array_get_h(self)); return self; } static VALUE rb_array_get_right(VALUE self) { return DBL2NUM(array_get_x(self)+array_get_w(self)); } static VALUE rb_array_get_bottom(VALUE self) { return DBL2NUM(array_get_y(self)+array_get_h(self)); } /* functions */ /** @method normalize -> self @method normalize! -> [x, y, w, h] If w and h aren't positive, this will change them to positive and keep the rectangle covering the same space. */ static VALUE rb_array_normalize_bang(VALUE self) { normalize(self); return self; } static VALUE rb_array_normalize(VALUE self) { VALUE retval=clone_array(self); normalize(retval); return retval; } /** @method move( delta ) @method move!( delta ) Returns a new rectangle which is the base rectangle moved by the given amount. */ static VALUE rb_array_move_bang(VALUE self, VALUE delta) { array_set_x(self, array_get_x(self)+array_get_x(delta)); array_set_y(self, array_get_y(self)+array_get_y(delta)); return self; } static VALUE rb_array_move(VALUE self, VALUE delta) { return rb_array_move_bang(clone_array(self), delta); } /** @method inflate( sizes ) -> [x, y, w, h] @method inflate!( sizes ) -> self Returns a rectangle which has the sizes changed by the given amounts. @sizes is an array of [dx, dy]. The rectangle shrinks and expands around the rectangle's center. Negative values will shrink the rectangle. */ static VALUE rb_array_inflate_bang(VALUE self, VALUE size) { normalize(self); { GET_X(); GET_Y(); GET_W(); GET_H(); double dx=array_get_x(size); double dy=array_get_y(size); SET_X(x-dx/2); SET_Y(y-dy/2); SET_W(w+dx); SET_H(h+dy); } return self; } static VALUE rb_array_inflate(VALUE self, VALUE size) { return rb_array_inflate_bang(clone_array(self), size); } /** @method union( rect ) -> [x, y, w, h] @method union!( rect ) -> self Returns a new rectangle that completely covers the given rectangle(s). There may be an area inside the new rectangle that is not covered by the inputs. Rectangles are pre-normalized. */ static VALUE rb_array_union_bang(VALUE self, VALUE other_rect) { normalize(self); normalize(other_rect); { GET_X(); GET_Y(); GET_W(); GET_H(); double other_x=array_get_x(other_rect); double other_y=array_get_y(other_rect); double other_w=array_get_w(other_rect); double other_h=array_get_h(other_rect); double new_x=RUDL_MIN(x, other_x); double new_y=RUDL_MIN(y, other_y); SET_X( new_x ); SET_Y( new_y ); SET_W( RUDL_MAX(x+w, other_x+other_w)-new_x ); SET_H( RUDL_MAX(y+h, other_y+other_h)-new_y ); } return self; } static VALUE rb_array_union(VALUE self, VALUE other_rect) { return rb_array_union_bang(clone_array(self), other_rect); } /** @method contains?( thing ) -> boolean Returns whether thing ([x, y, w, h] or [x, y]) fits completely within the rectangle. */ static VALUE rb_array_contains(VALUE self, VALUE thing) { GET_X(); GET_Y(); GET_W(); GET_H(); double x2,y2,w2,h2; Check_Type(thing, T_ARRAY); x2=array_get_x(thing); y2=array_get_y(thing); if(RARRAY(thing)->len>3){ // It's a rectangle w2=array_get_w(thing); h2=array_get_h(thing); return INT2BOOL((x <= x2) && (y <= y2) && (x + w >= x2 + w2) && (y + h >= y2 + h2) && (x + w > x2) && (y + h > y2)); }else{ // It's a point return INT2BOOL((x2>=x && x2=y && y2 self Sets @self to the same position and size as @rect. This is meant for optimizations. Returns @self. */ static VALUE rb_array_copy_from(VALUE self, VALUE rect) { if(self==rect){ return Qtrue; } Check_Type(rect, T_ARRAY); SET_X(array_get_x(rect)); SET_Y(array_get_y(rect)); SET_W(array_get_w(rect)); SET_H(array_get_h(rect)); return self; } /** @method set_coordinates( x, y, w, h ) -> self Sets all properties at once. This is meant for optimizations. */ static VALUE rb_array_set_coordinates(VALUE self, VALUE x, VALUE y, VALUE w, VALUE h) { array_set_x_value(self, x); array_set_y_value(self, y); array_set_w_value(self, w); array_set_h_value(self, h); return self; } /* @method find_overlapping_rect( rects ) -> [x, y, w, h] or false Returns the first rectangle in the @rects array to overlap the base rectangle. Once an overlap is found, this will stop checking the remaining array. If no overlap is found, it will return false. */ static VALUE rb_array_find_overlapping_rect(VALUE self, VALUE rects) { int i; Check_Type(rects, T_ARRAY); for(i=0; ilen; i++){ if(rb_array_overlaps(self, rb_ary_entry(rects, i))==Qtrue){ return rb_ary_entry(rects, i); } } return Qfalse; } /** @method find_overlapping_rects( rects ) -> [ [x, y, w, h], ... ] Returns an array with the rectangles in the list to overlaps the base rectangle. If no overlaps is found, it will return []. */ static VALUE rb_array_find_overlapping_rects(VALUE self, VALUE rects) { int i; VALUE retval=rb_ary_new(); Check_Type(rects, T_ARRAY); for(i=0; ilen; i++){ if(rb_array_overlaps(self, rb_ary_entry(rects, i))==Qtrue){ rb_ary_push(retval, rb_ary_entry(rects, i)); } } return retval; } /* @method clip( rect ) -> [x, y, w, h] @method clip!( rect ) -> self Returns a new rectangle that is the given rectangle cropped to the inside of the base rectangle. If the two rectangles do not overlaps to begin with, you will get a rectangle with 0 size. */ static VALUE rb_array_clip_bang(VALUE self, VALUE rect) { SDL_Rect a, b; double x,y,w,h; RECT2CRECT(self, &a); RECT2CRECT(rect, &b); /* Left */ if((a.x >= b.x) && (a.x < (b.x+b.w))) x = a.x; else if((b.x >= a.x) && (b.x < (a.x+a.w))) x = b.x; else goto nointersect; /* Right */ if(((a.x+a.w) > b.x) && ((a.x+a.w) <= (b.x+b.w))) w = (a.x+a.w) - x; else if(((b.x+b.w) > a.x) && ((b.x+b.w) <= (a.x+a.w))) w = (b.x+b.w) - x; else goto nointersect; /* Top */ if((a.y >= b.y) && (a.y < (b.y+b.h))) y = a.y; else if((b.y >= a.y) && (b.y < (a.y+a.h))) y = b.y; else goto nointersect; /* Bottom */ if (((a.y+a.h) > b.y) && ((a.y+a.h) <= (b.y+b.h))) h = (a.y+a.h) - y; else if(((b.y+b.h) > a.y) && ((b.y+b.h) <= (a.y+a.h))) h = (b.y+b.h) - y; else goto nointersect; SET_X(x); SET_Y(y); SET_W(w); SET_H(h); return self; nointersect: SET_X(a.x); SET_Y(a.y); SET_W(0); SET_H(0); return self; } static VALUE rb_array_clip(VALUE self, VALUE rect) { return rb_array_clip_bang(clone_array(self), rect); } /** @method clamp( rect ) -> [x, y, w, h] @method clamp!( rect ) -> nil Returns a new rectangle that is moved to be completely inside the base rectangle. If the given rectangle is too large for the base rectangle in an axis, it will be centered on that axis. */ static VALUE rb_array_clamp_bang(VALUE self, VALUE rect) { SDL_Rect a, b; double x, y; RECT2CRECT(self, &a); RECT2CRECT(rect, &b); if(a.w >= b.w) x = b.x + b.w / 2 - a.w / 2; else if(a.x < b.x) x = b.x; else if(a.x + a.w > b.x + b.w) x = b.x + b.w - a.w; else x = a.x; if(a.h >= b.h) y = b.y + b.h / 2 - a.h / 2; else if(a.y < b.y) y = b.y; else if(a.y + a.h > b.y + b.h) y = b.y + b.h - a.h; else y = a.y; SET_X(x); SET_Y(y); return self; } static VALUE rb_array_clamp(VALUE self, VALUE rect) { return rb_array_clamp_bang(clone_array(self), rect); } /** @method overlaps?( rect ) -> boolean Returns true if any area of the two rectangles overlapss. */ static VALUE rb_array_overlaps(VALUE self, VALUE otherRect) { SDL_Rect a, b; RECT2CRECT(self, &a); RECT2CRECT(otherRect, &b); return INT2BOOL(intersect(&a, &b)); } /* @class Pit @method cross_lines( a, b, c, d ) -> lots Returns the coordinate where lines @a to @b and @c to @d cross, an array of [x,y,x2,y2] if they are parallel and overlapping, an array of [x,y] if they are parallel and touch at only one point, or false if they don't cross. @a, @b, @c and @d are coordinates as always: [x,y]

The results may have small precision errors, so don't use it to compare directly with other numbers. */ static VALUE rb_pit_cross_lines_retval(int cross, int parallel, double x, double y, double x2, double y2) { if(cross){ if(parallel){ if(x==x2 && y==y2){ return rb_ary_new3(2, DBL2NUM(x), DBL2NUM(y)); }else{ return rb_ary_new3(4, DBL2NUM(x), DBL2NUM(y), DBL2NUM(x2), DBL2NUM(y2)); } }else{ return rb_ary_new3(2, DBL2NUM(x), DBL2NUM(y)); } }else{ return Qfalse; } } static VALUE rb_pit_cross_lines(VALUE self, VALUE pa, VALUE pb, VALUE pc, VALUE pd) { double pax=array_get_x(pa); double pbx=array_get_x(pb); double pcx=array_get_x(pc); double pdx=array_get_x(pd); double pay=array_get_y(pa); double pby=array_get_y(pb); double pcy=array_get_y(pc); double pdy=array_get_y(pd); double xk, yk, a, b, c, d; double tmp; if(pax!=pbx && pcx!=pdx){ // check eerst even of geen van de lijnen vertikaal is // De lijnpunten sorteren op x-coordinaat. if(pax>pbx){ tmp=pax; pax=pbx; pbx=tmp; tmp=pay; pay=pby; pby=tmp; } if(pcx>pdx){ tmp=pcx; pcx=pdx; pdx=tmp; tmp=pcy; pcy=pdy; pdy=tmp; } a=(pby-pay)/(pbx-pax); // de helling van de eerste lijn b=pay-a*pax; // het snijpunt van de eerste lijn met x=0 c=(pdy-pcy)/(pdx-pcx); // de helling van de tweede lijn d=pcy-c*pcx; // het snijpunt van de tweede lijn met x=0 if(a==c){ // Are they parallel? if(b==d){ // Are they the same line? if(!(pbxpdx)){ // Are the sections the same? // Calculate overlapping line section double cx1, cy1, cx2, cy2; DEBUG_S("overlap"); if(pax=pax && xk<=pdx && xk>=pcx, false, xk, yk, 0, 0); }else{ // wat als een van de lijnen vertikaal is? // De lijnpunten sorteren op y-coordinaat. if(pay>pby){ tmp=pax; pax=pbx; pbx=tmp; tmp=pay; pay=pby; pby=tmp; } if(pcy>pdy){ tmp=pcx; pcx=pdx; pdx=tmp; tmp=pcy; pcy=pdy; pdy=tmp; } if(pax==pbx){ if(pcx==pdx){ // Beide lijnen lopen verticaal return rb_pit_cross_lines_retval((pax==pcx)&&!(pbypdy), true, pax, RUDL_MAX(pay, pcy), pax, RUDL_MIN(pby, pdy)); }else{ // alleen de eerste lijn loopt vertikaal if((pcxpax && pdx>pax)){ return rb_pit_cross_lines_retval(false, false, 0,0,0,0); } c=(pdy-pcy)/(pdx-pcx); // de helling van de tweede lijn berkenen d=pcy-c*pcx; // het snijpunt van de tweede lijn met x=0 berekenen yk=c*pax+d; // de y-coord van het snijpunt is dan return rb_pit_cross_lines_retval(yk>=pay && yk<=pby, false, pax, yk, 0, 0); } }else{ // Alleen de tweede lijn loopt verticaal if((paxpcx && pbx>pcx)){ return rb_pit_cross_lines_retval(false, false, 0,0,0,0); } c=(pby-pay)/(pbx-pax); // de helling van de eerste lijn berkenen d=pay-c*pax; // het snijpunt van de tweede lijn met x=0 berekenen yk=c*pcx+d; // de y-coord van het snijpunt is dan return rb_pit_cross_lines_retval(yk>=pcy && yk<=pdy, false, pcx, yk, 0, 0); } } } ///////////////////////////////// INIT void initVideoRectClasses() { rb_define_singleton_method(rb_cArray, "collide_lists", rb_array_collide_lists, 2); rb_define_singleton_method(rb_cArray, "union_list", rb_array_union_list, 1); rb_define_method(rb_cArray, "x", rb_array_get_x, 0); rb_define_method(rb_cArray, "y", rb_array_get_y, 0); rb_define_method(rb_cArray, "w", rb_array_get_w, 0); rb_define_method(rb_cArray, "h", rb_array_get_h, 0); rb_define_method(rb_cArray, "x=", rb_array_set_x, 1); rb_define_method(rb_cArray, "y=", rb_array_set_y, 1); rb_define_method(rb_cArray, "w=", rb_array_set_w, 1); rb_define_method(rb_cArray, "h=", rb_array_set_h, 1); rb_alias(rb_cArray, rb_intern("left"), rb_intern("x")); rb_alias(rb_cArray, rb_intern("top"), rb_intern("y")); rb_define_method(rb_cArray, "right", rb_array_get_right, 0); rb_define_method(rb_cArray, "bottom", rb_array_get_bottom, 0); rb_alias(rb_cArray, rb_intern("left="), rb_intern("x=")); rb_alias(rb_cArray, rb_intern("top="), rb_intern("y=")); rb_define_method(rb_cArray, "right=", rb_array_set_right, 1); rb_define_method(rb_cArray, "bottom=", rb_array_set_bottom, 1); rb_define_method(rb_cArray, "normalize", rb_array_normalize, 0); rb_define_method(rb_cArray, "normalize!", rb_array_normalize_bang, 0); rb_define_method(rb_cArray, "move", rb_array_move, 1); rb_define_method(rb_cArray, "move!", rb_array_move_bang, 1); rb_define_method(rb_cArray, "inflate", rb_array_inflate, 1); rb_define_method(rb_cArray, "inflate!", rb_array_inflate_bang, 1); rb_define_method(rb_cArray, "union", rb_array_union, 1); rb_define_method(rb_cArray, "union!", rb_array_union_bang, 1); rb_define_method(rb_cArray, "contains?", rb_array_contains, 1); rb_define_method(rb_cArray, "same_size?", rb_array_same_size, 1); rb_define_method(rb_cArray, "overlaps?", rb_array_overlaps, 1); rb_define_method(rb_cArray, "set_coordinates", rb_array_set_coordinates, 4); rb_define_method(rb_cArray, "copy_from", rb_array_copy_from, 1); rb_define_method(rb_cArray, "find_overlapping_rect", rb_array_find_overlapping_rect, 1); rb_define_method(rb_cArray, "find_overlapping_rects", rb_array_find_overlapping_rects, 1); rb_define_method(rb_cArray, "clip", rb_array_clip, 1); rb_define_method(rb_cArray, "clip!", rb_array_clip_bang, 1); rb_define_method(rb_cArray, "clamp", rb_array_clamp, 1); rb_define_method(rb_cArray, "clamp!", rb_array_clamp_bang, 1); rb_define_singleton_method(classPit, "cross_lines", rb_pit_cross_lines, 4); // Backward compatability: (crap) rb_define_class_under(moduleRUDL, "Rect", rb_cArray); }