Jump to content


Photo
- - - - -

Bounding Box Offset Error Correction


  • Please log in to reply
4 replies to this topic

#1 TheouAegis

TheouAegis

    GMC Member

  • GMC Member
  • 4762 posts
  • Version:GM8

Posted 08 April 2012 - 06:52 PM

  • Title: Bounding Box Offset Error Correction for Mirrored or Flipped Sprites
  • Description: This script will return the correct bounding box values in the room for instances with mirrored or flipped sprites.
  • GM Version: :GM81: :GM8: :GM7: :GM6: :GM5:
  • Registered: No
  • File Type: .gml (Use "Import Scripts..." to add to your game then rename to sprite_get_bbox)
  • File Size: 3kb
  • File Link: http://www.mediafire...1iysds8yc5m8ibi , http://sharesend.com/xj9pw
Summary
Game Maker has a nasty little bug when you mirror or flip sprites using the image_xscale or image_yscale variables. This isn't anything new and was even present on the Nintendo Entertainment System (don't worry if you're too young to know what that is). When you mirror or flip a sprite using these variables, the bounding box dimensions in the room -- as defined by the variables bbox_left, bbox_top, bbox_right, and bbox_bottom -- gets stretched and/or shifted. Why does it do this? My guess it is has to do with how bitwise negation works, but you don't need to concern yourself with that.

If you ever want to use any of these four variables with a mirrored or flipped sprite, you will need to compensate for the shift in values. I have taken the guesswork out of it and devised a script which will handle the values for you. You could just as easily set image_xscale and image_yscale to positive, retrieve the bounding box values, then set image_xscale and image_yscale back to where they were via a temporary variable, but this script is my preferred alternative.

Note that only negation affects the bounding box adversely; all negative scale values will yield incorrect bounding box values. Further scaling (setting to anything other than 1 or -1) does affect the bounding box, but there are no errors in the shift beyond what is already caused by using a negative value.

Remember, the argument is passed as a string.

//Corrects bounding box offsets in room when using negative sprite scaling
//Use: sprite_get_bbox(bbox)


if (argument=='bbox_left' || argument=='bbox_right')
{
    var true_L,true_R;
    //Store bounding box coordinates in the room for manipulation
    true_L = bbox_left;
    true_R = bbox_right;    
    
    if image_xscale<0
    {
        var diff_L,diff_R;
        //Calculate distances bounding box edges are from sprite border
        diff_L = sprite_get_bbox_left(sprite_index);
        diff_R = sprite_get_width(sprite_index)-1-sprite_get_bbox_right(sprite_index);
    
        //Check and compensate for symmetrical bounding box stretching
        if diff_L = diff_R
        {
            true_L += 1;
            true_R -= 1;
        }
        else
        //Check and compensate for leftward stretch
        if diff_L > diff_R
        {
            //Check and compensate for bounding box shift to the left
            true_L += 1 + diff_L - diff_R;
            true_R += diff_L - diff_R - 1;
        }
        else
        //Check and compensate for rightward stretch
        if diff_R > diff_L
        {
            //Check and compensate for bounding box shift to the right
            true_L -= diff_R - diff_L - 1;
            true_R -= diff_R - diff_L + 1;
        }
    }
    
    //Return the corrected bbox_ value
    switch argument
    {
        case "bbox_left":     return true_L; exit;
        case "bbox_right":    return true_R; exit;
    }
}    

else

if (argument=='bbox_top' || argument='bbox_bottom')
{
    var true_T,true_B;
    
    //Store bounding box coordinates in the room for manipulation
    true_T = bbox_top;
    true_B = bbox_bottom;
    
    if image_yscale<0
    {
        var diff_T,diff_B;
        //Calculate distances bounding box edges are from sprite border
        diff_T = sprite_get_bbox_top(sprite_index);
        diff_B = sprite_get_height(sprite_index)-1-sprite_get_bbox_bottom(sprite_index);
    
        //Check and compensate for symmetrical bounding box stretching
        if diff_T = diff_B
        {
            true_T += 1;
            true_B -= 1;
        }
        else
        //Check and compensate for upward stretch
        if diff_T > diff_B
        {
            //Check and compensate for bounding box shift up
            true_T += 1 + diff_T - diff_B;
            true_B += diff_T - diff_B - 1;
        }
        else
        //Check and compensate for downward stretch
        if diff_B > diff_T
        {
            //Check and compensate for bounding box shift down
            true_T -= diff_B - diff_T - 1;
            true_B -= diff_B - diff_T + 1;
        }
    }
    
    //Return the corrected bbox_ value
    switch argument
    {
        case 'bbox_top':      return true_T; exit;
        case 'bbox_bottom':   return true_B; exit;
    }
    
}


An alternative as proposed by icuurd12b42, setting new variables to use.
//Corrects bounding box offsets in room when using negative sprite scaling
//This version creates 4 variables to be used instead of the bbox_D family.
//Use: sprite_get_bboxes()

//Store bounding box coordinates in the room for manipulation
true_left   =   bbox_left;
true_right  =   bbox_right;   
true_top    =   bbox_top;
true_bottom =   bbox_bottom; 
    
    if image_xscale<0
    {
        var diff_L,diff_R;
        //Calculate distances bounding box edges are from sprite border
        diff_L = sprite_get_bbox_left(sprite_index);
        diff_R = sprite_get_width(sprite_index)-1-sprite_get_bbox_right(sprite_index);

    
        //Check and compensate for symmetrical bounding box stretching
        if diff_L = diff_R
        {
            true_left += 1;
            true_right -= 1;
        }
        else
        //Check and compensate for leftward stretch
        if diff_L > diff_R
        {
            //Check and compensate for bounding box shift to the left
            true_left += 1 + diff_L - diff_R;
            true_right += diff_L - diff_R - 1;
        }
        else
        //Check and compensate for rightward stretch
        if diff_R > diff_L
        {
            //Check and compensate for bounding box shift to the right
            true_left -= diff_R - diff_L - 1;
            true_right -= diff_R - diff_L + 1;
        }
    }
    
    if image_yscale<0
    {
        var diff_T,diff_B;
        //Calculate distances bounding box edges are from sprite border
        diff_T = sprite_get_bbox_top(sprite_index);
        diff_B = sprite_get_height(sprite_index)-1-sprite_get_bbox_bottom(sprite_index);

    
        //Check and compensate for symmetrical bounding box stretching
        if diff_T = diff_B
        {
            true_top += 1;
            true_bottom -= 1;
        }
        else
        //Check and compensate for upward stretch
        if diff_T > diff_B
        {
            //Check and compensate for bounding box shift up
            true_top += 1 + diff_T - diff_B;
            true_bottom += diff_T - diff_B - 1;
        }
        else
        //Check and compensate for downward stretch
        if diff_B > diff_T
        {
            //Check and compensate for bounding box shift down
            true_top -= diff_B - diff_T - 1;
            true_bottom -= diff_B - diff_T + 1;
        }
    }


One more alternative, which should be called only when the sign of image_xscale or image_yscale is changed.

//Corrects bounding box offsets in room when using negative sprite scaling
//This version creates 4 variables you should add to each bbox_Dir reference.
//Ideally, only call this when the sign of image_xscale or image_yscale is changed.
//Use:  sprite_get_bbox_offset()
//
//Example Usage:    draw_rectangle(bbox_left+t_left,bbox_top+t_top,bbox_right+t_right,bbox_bottom+t_bot,0)

//Create the four new variables
t_left   =   0;
t_right  =   0;   
t_top    =   0;
t_bot    =   0; 
    
    if image_xscale<0
    {
        var diff_L,diff_R;
        //Calculate distances bounding box edges are from sprite border
        diff_L = sprite_get_bbox_left(sprite_index);
        diff_R = sprite_get_width(sprite_index)-1-sprite_get_bbox_right(sprite_index);

    
        //Check and compensate for symmetrical bounding box stretching
        if diff_L = diff_R
        {
            t_left += 1;
            t_right -= 1;
        }
        else
        //Check and compensate for leftward stretch
        if diff_L > diff_R
        {
            //Check and compensate for bounding box shift to the left
            t_left += 1 + diff_L - diff_R;
            t_right += diff_L - diff_R - 1;
        }
        else
        //Check and compensate for rightward stretch
        if diff_R > diff_L
        {
            //Check and compensate for bounding box shift to the right
            t_left -= diff_R - diff_L - 1;
            t_right -= diff_R - diff_L + 1;
        }
    }
    
    if image_yscale<0
    {
        var diff_T,diff_B;
        //Calculate distances bounding box edges are from sprite border
        diff_T = sprite_get_bbox_top(sprite_index);
        diff_B = sprite_get_height(sprite_index)-1-sprite_get_bbox_bottom(sprite_index);

    
        //Check and compensate for symmetrical bounding box stretching
        if diff_T = diff_B
        {
            t_top += 1;
            t_bot -= 1;
        }
        else
        //Check and compensate for upward stretch
        if diff_T > diff_B
        {
            //Check and compensate for bounding box shift up
            t_top += 1 + diff_T - diff_B;
            t_bot += diff_T - diff_B - 1;
        }
        else
        //Check and compensate for downward stretch
        if diff_B > diff_T
        {
            //Check and compensate for bounding box shift down
            t_top -= diff_B - diff_T - 1;
            t_bot -= diff_B - diff_T + 1;
        }
    }

Edit Notes 04/09/12: Optimized code.
[i]Edit Notes 04/19/12: Added icuurd12b42 variant code[/b]

Edited by TheouAegis, 20 April 2012 - 05:02 AM.

  • 0

#2 fredcobain

fredcobain

    GMC Member

  • GMC Member
  • 155 posts

Posted 16 April 2012 - 04:51 AM

Congrats for solving this issue! Very smart!

Thanks for sharing it!
  • 0

#3 icuurd12b42

icuurd12b42

    Self Formed Sentient

  • Global Moderators
  • 14398 posts
  • Version:GM:Studio

Posted 17 April 2012 - 07:58 AM

would it not be simpler to mirror the data into 4 instance variables and do one call

//calc_true_bbox()
tbbox_top = bbox_top;
tbbox_left = bbox_left;
tbbox_right = bbox_right;
tbbox_bottom = bbox_bottom;

//as an example, not same calcs determined above
if image_xscale<0
{
tbbox_left+=1;
tbbox_right-=1;
}
if image_yscale<0
{
tbbox_top+=1;
tbbox_bottom-=1;
}

to use
calc_true_bbox();
v1 = tbbox_left;
v2 = tbbox_right;
v3 = tbbox_top;
v4 = tbbox_bottom;
  • 0

#4 TheouAegis

TheouAegis

    GMC Member

  • GMC Member
  • 4762 posts
  • Version:GM8

Posted 20 April 2012 - 04:31 AM

Your code didn't account for the horizontal/vertical shifts. Part of the real headache in the Negative Scaling bug is when the differential is greater than 1 or 2 or whatever I said in the code, the bounding box gets shifted toward the higher differential. But it appears you just didn't want to copy all the code and left that out as a result.

And yes, that would be simpler but you'd be creating four more variables which would eat up RAM for each instance in the game that calls that script. My script just uses temp variables. Now, yes, if you need to call this script many times over within each instance, it may be better to just add the 4 extra variables. But if you are only needing to call the script one, two or three times each step per instance or have a lot of instances in the room, not tying up the RAM with the extra variables might be better. Of course, your script has the additional benefit of needing to only be called when image_xscale or image_yscale changes (e.g., within a keyboard_check_pressed() call.

Either way, if you want to get anal about the proper bbox values in your game, either of our scripts will eat up more memory. I think most programmers here wouldn't have much real use for this script until they get sick of using GM's built-in collision detection or want to use tile-based collision detection, which is what either script would be best suited for. But fine. I'll write up an alternate that's like yours.

Edit: I just noticed, I think your code may technically be worse than mine, unless I misread it (even in its abridged form). You take up additional RAM with 4 object-local variables and your script would have to be called each step (like mine) because bbox_Dir could change each step (e.g., an instance with hspeed=1 will have different bbox_Dir values each step.

Maybe what you were thinking and what I thought you were thinking would be to create 4 new variables that store the offsets and add those offsets with each reference to bbox_Dir. For example:

tleft=0;
tright=0;
if diff_L>diff_R
tleft+=1+diff_L-diff_R;
tright+=diff_L-diff_R-1;


/*Use Example: draw_rectangle(bbox_left+tleft,bbox_top+ttop,bbox_right+tright,bbox_bottom+tbottom,0)*/

That was just an example.

Edited by TheouAegis, 20 April 2012 - 04:55 AM.

  • 0

#5 icuurd12b42

icuurd12b42

    Self Formed Sentient

  • Global Moderators
  • 14398 posts
  • Version:GM:Studio

Posted 20 April 2012 - 04:55 AM

Like I said
//as an example, not same calcs determined above

I should have been more specific in meaning the calc are wrong, it's just for concept.

And yep... ram vs multiple call.
  • 0




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users