Jump to content


Photo
- - - - -

Bounding Box Offset Error Correction *New Findings


  • Please log in to reply
5 replies to this topic

#1 TheouAegis

TheouAegis

    GMC Member

  • GMC Member
  • 9947 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_new: :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, 31 October 2013 - 06:34 AM.

  • 0

#2 fredcobain

fredcobain

    GMC Member

  • GMC Member
  • 165 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

  • GMC Member
  • 15886 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
  • 9947 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

  • GMC Member
  • 15886 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

#6 TheouAegis

TheouAegis

    GMC Member

  • GMC Member
  • 9947 posts
  • Version:GM8

Posted 31 October 2013 - 06:30 AM

I think I cracked it. I need feedback from one of GM's actual programmers to know if my findings are correct or not.

 

 

If they fixed this in Studio, then this would probably belong in the Legacy Discussion forum (or whatever it's called).

 

I'm trying to figure out what's possibly going wrong under GM's hood that causes image_xscale and image_yscale to yield such funky results.

 

For starters, upscaling by positive factors. Setting image_xscale to a positive integer doesn't yield the results one would typically expect.

 

image_xscale = 1

sprite_width = 32

sprite_xoffset = 16

bbox_left = 4

bbox_right = 24

 

From here, if we were to double image_xscale, it's reasonable to expect the following results.

 

image_xscale = 2

sprite_width = 64

sprite_xoffset = 32

bbox_left = 8

bbox_right = 48

 

We would expect the distance between the xoffset and bbox_left to double; likewise, we would expect the distance between bbox_right and the xoffset to double. Instead, what we get is the following.

 

image_xscale = 2

sprite_width = 64

sprite_xoffset = 32

bbox_left = 8

bbox_right = 49

 

Everything follows simple logic up until the bbox_right value, which mysteriously shifts right one extra pixel. If we increase the xscale factor further, a pattern emerges.

 

image_xscale = 3

bbox_right = 74

 

image_xscale = 4

bbox_right = 99

 

Thus the formula for scaling so far seems to be

 

sprite_width = sprite_get_width() * image_xscale

sprite_xoffset = sprite_get_xoffset() * image_xscale

bbox_left = sprite_get_bbox_left() * image_xscale

bbox_right = sprite_get_bbox_right() * image_xscale + image_xscale - 1

 

 

First question: Why is this? What could be causing bbox_right to undergo such a transformation?

 

 

Now for negative scale factors. To recap, we'll be using the following sprite.

 

image_xscale = 1

sprite_width = 32

sprite_xoffset = 16

bbox_left = 4

bbox_right = 24

 

If we negate the xscale, we'd reasonably expect the following.

 

image_xscale = -1

sprite_width = -32

sprite_xoffset = -16

bbox_left = -4

bbox_right = -24

 

Of course this is nonsensical and impractical to work with. The whole sprite undergoes a translation when placed in the room. The result is of course that bbox_left and bbox_right should essentially be swapped such that the distance between either point and the xoffset should too be swapped like so.

 

image_xscale = 1

xoffset - bbox_left = 12

bbox_right - xoffset = 8

 

image_xscale = -1

xoffset - bbox_left = 8

bbox_right - xoffset = 12

 

The actual results, as many of you should by now know, are skewed.

 

image_xscale = -1

xoffset - bbox_left = 10

bbox_right - xoffset = 12

 

Again, the bounding box dimensions undergo a transformation beyond a simple mirroring.

 

bbox_right = sprite_get_bbox_right() * image_xscale + image_xscale - 1

image_xscale = -1

bbox_right = 24 * -1 + -1 - 1

bbox_right = -26

 

Since this is a mirror, bbox_right is now bbox_left.

 

xoffset - bbox_left = -16 - -26 = 10

 

So far so good. Now let's take it up a notch and scale as well. As you can predict now, all values will scale properly except bbox_right (which becomes bbox_left after the mirror).

 

image_xscale = -2

sprite_xoffset = -32

bbox_left = -8

bbox_right = -49     [ from previous discussion ]

 

Of course that ideal bbox_right is incorrect, so we'll use the formula to get a better prediction.

 

image_xscale = -2

bbox_right = 24 * -2 + -2 - 1

bbox_right = -51

 

A simple test shows that this is indeed the case. One more test further confirms it.

 

image_xscale = -3

bbox_right = 24 * -3 + -3 -1

bbox_right = -76

 

Simple math shows the bounding box of our example when xscale is -2 is 43 pixels. Thus the formula for finding the bounding box width would be as follows.

 

bbox_width = abs(bbox_right - bbox_left)

bbox_width = abs(bbox_right * image_xscale + image_xscale - 1 - bbox_left * image_xscale)

 

 

Thus the negative scaling bug in GM has to some degree been solved, although not resolved. To that end, again, I pose the question: Why is image_xscale-1 even in the equation at all? I think I found the answer. (I've been typing this post for two hours or so, but took a nap so really it's been nearly five hours).

 

I will simplify things a bit by working in hexadecimal with a singular byte values. This should hopefully make everything clear and should shed light on the negative scaling bug.

 

To negate a signed value, you XOR it by $FF then add 1. This can be "simplified" as F(x)=(x^255)-255 since to convert a byte to a negative integer, you subtract 256, and -256+1 is -255. Whatever. It may or may not matter in the grand scheme of things. What does matter is there seems to be an incorrect order of operations in GM's code (assuming it's GM's fault and not Windows).

 

bbox_left' = ( (sprite_get_bbox_left() ^ $FF) + 1 ) * image_xscale

bbox_right' = ( (sprite_get_bbox_right() ^ $FF) - 1 ) * image_xscale

 

Here, bbox_left' and bbox_right' are the values added to the x-coordinate in the room to establish the actual bbox_left and bbox_right variables respectively.

 

Mixing terms around just makes things more confusing, I guess, and that last part only really has any relevance to Game Maker's developers. In the end, finding the correct bbox positions is really just as simple as:

 

bbox_left = x - sprite_get_bbox_right(sprite_index) * image_xscale + sprite_get_xoffset(sprite_index)

bbox_right = x + sprite_get_bbox_left(sprite_index) * image_xscale - sprite_get_xoffset(sprite_index)

 

Is it faster to calculate the bounding box values in the room algorithmically or with a series of branches like in my previous posts? I don't know; I haven't tested it. Regardless, I think it's safe to say the negative scaling bug has been solved. I'll send this to the bug reports now.


Edited by TheouAegis, 09 November 2013 - 07:14 PM.

  • 0




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users