1

How could i calculate the "furthest out" edges of a sprite to create a rectangular outline around a transformed sprite with an origin?

I want to achieve something like this http://oi43.tinypic.com/14l39k0.jpg http://i42.tinypic.com/2m62v41.png where the red box is the "outline" and the black box is the transformed sprite. The box needs to expand based on the corner - just a bounding box really.

I've tried various equations liked this to find the coordinates of a transformed sprite:

Transformed.X = pos.X * (float)Math.Cos(angle) - pos.Y * (float)Math.Sin(angle);
Transformed.Y = pos.X * (float)Math.Sin(angle) + pos.Y * (float)Math.Cos(angle);

but i can't seem to make it work. Any ideas how i can achieve this?

Any help would be appreciated.

Juan

With thanks to Zenchovey i was able to solve my problem. Here's the code i used:

Initiating variables

Vector2 TransformPos = Vector2.Zero;
Vector2 TransformPos2 = Vector2.Zero;
float[] px = new float[2];
float[] py = new float[2];
float[] pxl = new float[2];
float[] pyl = new float[2];
float ox;
float oy;

Update Method

    // Vars
    ox = pos.X;
    oy = pos.Y;

    // top left
    pxl[0] = pos.X - Origin.X;
    pyl[0] = pos.Y - Origin.Y;
    // bottom left
    pxl[1] = pos.X - Origin.X;
    pyl[1] = pos.Y + Origin.Y;
    // top right
    px[0] = pos.X + Origin.X;
    py[0] = pos.Y - Origin.Y;
    // bottom right
    px[1] = pos.X + Origin.X;
    py[1] = pos.Y + Origin.Y;

    if (rot <= MathHelper.ToRadians(90) && rot >= MathHelper.ToRadians(0))
    {
        TransformPos.X = (float)Math.Cos(rot) * (pxl.Min() - ox) - (float)Math.Sin(rot) * (pyl.Max() - oy) + ox;
        TransformPos.Y = (float)Math.Sin(rot) * (pxl.Min() - ox) + (float)Math.Cos(rot) * (pyl.Min() - oy) + oy;
        TransformPos2.X = (float)Math.Cos(rot) * (px.Max() - ox) - (float)Math.Sin(rot) * (py.Min() - oy) + ox;
        TransformPos2.Y = (float)Math.Sin(rot) * (px.Max() - ox) + (float)Math.Cos(rot) * (py.Max() - oy) + oy;
    }
    else
    if (rot <= MathHelper.ToRadians(270) && rot >= MathHelper.ToRadians(180))
    {
        TransformPos2.X = (float)Math.Cos(rot) * (pxl.Min() - ox) - (float)Math.Sin(rot) * (pyl.Max() - oy) + ox;
        TransformPos2.Y = (float)Math.Sin(rot) * (pxl.Min() - ox) + (float)Math.Cos(rot) * (pyl.Min() - oy) + oy;
        TransformPos.X = (float)Math.Cos(rot) * (px.Max() - ox) - (float)Math.Sin(rot) * (py.Min() - oy) + ox;
        TransformPos.Y = (float)Math.Sin(rot) * (px.Max() - ox) + (float)Math.Cos(rot) * (py.Max() - oy) + oy;
    }
    else
    if (rot <= MathHelper.ToRadians(180) && rot >= MathHelper.ToRadians(90))
    {
        TransformPos2.X = (float)Math.Cos(rot) * (pxl.Max() - ox) - (float)Math.Sin(rot) * (pyl.Min() - oy) + ox;
        TransformPos.Y = (float)Math.Sin(rot) * (pxl.Max() - ox) + (float)Math.Cos(rot) * (pyl.Max() - oy) + oy;

        TransformPos.X = (float)Math.Cos(rot) * (px.Min() - ox) - (float)Math.Sin(rot) * (py.Max() - oy) + ox;
        TransformPos2.Y = (float)Math.Sin(rot) * (px.Min() - ox) + (float)Math.Cos(rot) * (py.Min() - oy) + oy;
    }
    else
    if (rot <= MathHelper.ToRadians(360) && rot >= MathHelper.ToRadians(270))
    {
        TransformPos.X = (float)Math.Cos(rot) * (pxl.Max() - ox) - (float)Math.Sin(rot) * (pyl.Min() - oy) + ox;
        TransformPos2.Y = (float)Math.Sin(rot) * (pxl.Max() - ox) + (float)Math.Cos(rot) * (pyl.Max() - oy) + oy;

        TransformPos2.X = (float)Math.Cos(rot) * (px.Min() - ox) - (float)Math.Sin(rot) * (py.Max() - oy) + ox;
        TransformPos.Y = (float)Math.Sin(rot) * (px.Min() - ox) + (float)Math.Cos(rot) * (py.Min() - oy) + oy; 
    }


Transform = new Rectangle((int)TransformPos.X, (int)TransformPos.Y, (int)TransformPos2.X - (int)TransformPos.X, (int)TransformPos2.Y - (int)TransformPos.Y);

It looks for the max and min values of the sprite's corner based on it's rotation to make the bounding box. The code is assuming that the origin is the middle of the sprite, you will have to change the code based on the origin

Juan
  • 25
  • 4

3 Answers3

0

If you find the position of each corner on the unrotated sprite and then rotate them about point you did your rotation to find the each corner of the rotated sprite. (How to do this is described here)

Then you can just find the max and min x and y values of these points. The minX and minY will be the top left of your bounding rect and the maxX and maxY will be the bottom right of your bounding rect.

Community
  • 1
  • 1
Zenchovey
  • 121
  • 7
  • Thank you for your reply it helped me find he position of each corner :) I'm having trouble making the rectangle though.. I would like something like this http://i42.tinypic.com/2m62v41.png Where the rectangle expands based on the corners, how would i be able to do this? – Juan Jul 04 '13 at 17:22
0

I looked into trying to put a collider on a rotated rectangular object (a car) a year or so ago and came up empty. While you can rotate a sprite you cannot rotate a rectangle around it for collision purposes.

I ended up using a circle on my object that "did the job" but was not perfect. Other solutions I read involved putting three circles over a rectangular object (one in front, one in the middle and one at the rear). The coverage was very good but the maths was more than I wanted.

As a last resort, is there an unrotated rectangle that would do over all possible rotations of your sprite?

None of these are perfect but they might get close enough.

Cheers, A.

Andrew H
  • 466
  • 10
  • 22
0

I know this question is a bit old, but if anyone interested in a working simple MonoGame solution (that also take texture size into consideration, since in MonoGame origin is based on texture size rather then dest rect), here's one:

    /// <summary>
    /// Rotate a vector around pivot.
    /// </summary>
    /// <param name="vec">Vector to rotate.</param>
    /// <param name="pivot">Point to rotate around.</param>
    /// <param name="theta">Rotation angle.</param>
    /// <returns>Rotated vector.</returns>
    public static Vector2 RotateAround(Vector2 vec, Vector2 pivot, float theta)
    {
        return new Vector2(
            (float)(System.Math.Cos(theta) * (vec.X - pivot.X) - System.Math.Sin(theta) * (vec.Y - pivot.Y) + pivot.X),
            (float)(System.Math.Sin(theta) * (vec.X - pivot.X) + System.Math.Cos(theta) * (vec.Y - pivot.Y) + pivot.Y));
    }

    /// <summary>
    /// Get rectangle and rotation (angle + origin) and calculate bounding box containing the rotated rect.
    /// </summary>
    /// <param name="rect">Rectangle to rotate.</param>
    /// <param name="rotation">Rotation angle.</param>
    /// <param name="origin">Rotation origin.</param>
    /// <param name="textureSize">In MonoGame origin is relative to source texture size, not dest rect. 
    /// So this param specify source texture size.</param>
    /// <returns>Rotated rectangle bounding box.</returns>
    public static Rectangle GetRotatedBoundingBox(Rectangle rect, float rotation, Vector2 origin, Rectangle textureSize)
    {
        // fix origin to be relative to rect location + fix it to be relative to rect size rather then texture size
        var originSize = ((origin / textureSize.Size.ToVector2()) * rect.Size.ToVector2());
        var convertedOrigin = rect.Location.ToVector2() + originSize;

        // calculate top-left rotated corner
        var topLeft = RotateAround(rect.Location.ToVector2(), convertedOrigin, rotation);

        // calculate rest of rotated corners
        Vector2[] corners = new Vector2[] {
            RotateAround(new Vector2(rect.Left, rect.Bottom), convertedOrigin, rotation),
            RotateAround(new Vector2(rect.Right, rect.Bottom), convertedOrigin, rotation),
            RotateAround(new Vector2(rect.Right, rect.Top), convertedOrigin, rotation)
        };

        // find min and max points
        Vector2 min, max;
        min = max = topLeft;
        foreach (var corner in corners)
        {
            if (corner.X < min.X) min.X = corner.X;
            if (corner.Y < min.Y) min.Y = corner.Y;
            if (corner.X > max.X) max.X = corner.X;
            if (corner.Y > max.Y) max.Y = corner.Y;
        }

        // create rectangle from min-max and return it
        return new Rectangle(min.ToPoint() - originSize.ToPoint(), (max - min).ToPoint());
    }

Just add these two static functions to some class and call GetRotatedBoundingBox() with your Draw params.

Usage example:

        // update rotation and start batch
        rotation += 0.01f;
        spriteBatch.Begin();

        // dest and origin values (play with these to see different results)
        var dest = new Rectangle(new Point(200, 200), new Point(100, 200));
        var origin = new Vector2(15, 200);

        // draw sprite
        spriteBatch.Draw(rectTexture, dest, null, Color.White, rotation, origin, SpriteEffects.None, 0f);

        // draw sprite bounding box (in my case I put the functions under Source.Graphics.Utils static class)
        var boundingBox = Source.Graphics.Utils.GetRotatedBoundingBox(dest, rotation, origin, rectTexture.Bounds);
        spriteBatch.Draw(rectTexture, boundingBox, Color.Red);

        // end batch
        spriteBatch.End();
Ronen Ness
  • 9,923
  • 4
  • 33
  • 50