Rotating rects and calculating a new point

Here is a unit in my game moving in column formation:
Screenshot 2023-08-22 145951

Now, when a user wants to change the unit from column to line formation it should look like this:
Screenshot 2023-08-22 150727

The key things to remember here are the stored location of the unit is where the arrowhead is. That is to say, the unit location X,Y for the unit in column formation (top photo) is at the base of the arrowhead. The unit location X,Y for the bottom unit in line formation is between the two arrowheads where they connect with the unit.

Previously, you guys helped me out with rotating these units and calculating their new corners and using this for unit collision detection.

This code returns the 4 corners of a rotated unit:

    UnitRect = CalculateActualRect(TheUnit, TheUnit.Location);
    Vector2[] corners = CalculateCorners(UnitRect, (float)TheUnit.Facing);

  public static Rectangle CalculateActualRect(MATEUnit TheUnit, PointI Location)
        {
            Rectangle ActualCorners = new Rectangle();

            // We have to calculate the actual X1,Y1 X2,Y2 of the unit rect
            // Taking into consideration unit type
            // and unit formation
            // Also, scale by PieceSize

            if (TheUnit.Type == (int)UnitTypes.Supplies ||
                TheUnit.Type == (int)UnitTypes.HeadQuarters ||
                TheUnit.Type == (int)UnitTypes.Artillery ||
                TheUnit.Type == (int)UnitTypes.HorseArtillery)
            {
                if (TheUnit.Formation == Formations.Column ||
                    TheUnit.Formation == Formations.MeleeColumn)
                {
                    // These are UNSCALED icon sizes
                    // 26 x 44 including arrows
                    // 26 x 33 without arrows

                    ActualCorners = new Rectangle(TheUnit.Location.X - (int)(13 * PieceSize), TheUnit.Location.Y, (int)(26 * PieceSize), (int)(44 * PieceSize));

                } // column formation
                else // line formation
                {
                    // 44 x 36 including arrows
                    // 44 x 25 without arrows

                    ActualCorners = new Rectangle(TheUnit.Location.X - (int)(22 * PieceSize), TheUnit.Location.Y, (int)(44 * PieceSize), (int)(36 * PieceSize));
                }
            } // small unit
            else // large unit
            {
                if (TheUnit.Formation == Formations.Column ||
                    TheUnit.Formation == Formations.MeleeColumn)
                {
                    // 26 x 71 including arrows
                    // 26 x 60 without arrows

                    ActualCorners = new Rectangle(TheUnit.Location.X - (int)(13 * PieceSize), TheUnit.Location.Y, (int)(36 * PieceSize), (int)(71 * PieceSize));

                } // column formation
                else // line formation
                {
                    // 62 x 38 including arrows
                    // 62 x 27 without arrows

                    ActualCorners = new Rectangle(TheUnit.Location.X - (int)(31 * PieceSize), TheUnit.Location.Y, (int)(62 * PieceSize), (int)(38 * PieceSize));

                }
            } // large unit

            return ActualCorners;

        } // CalculateActualRect


        private static Vector2[] CalculateCorners(Rectangle rect, float angle)
        {
            // Calculate the four corners of the rectangle
            Vector2[] corners = new Vector2[4];
            corners[0] = new Vector2(rect.Left, rect.Top);
            corners[1] = new Vector2(rect.Right, rect.Top);
            corners[2] = new Vector2(rect.Right, rect.Bottom);
            corners[3] = new Vector2(rect.Left, rect.Bottom);

            // Rotate the corners around the center of the rectangle
            // AHA! Our units don't rotate around the center!
            //Vector2 center = new Vector2(rect.Center.X, rect.Center.Y);

            Vector2 center = new Vector2(rect.Center.X, rect.Top + 5);


            for (int i = 0; i < corners.Length; i++)
            {
                corners[i] = RotatePoint(corners[i], center, angle);
            }

            return corners;
        }

        private static Vector2 RotatePoint(Vector2 point, Vector2 center, float angle)
        {
            // Rotate a point around another point by a given angle
            float sin = (float)Math.Sin(angle);
            float cos = (float)Math.Cos(angle);
            Vector2 translated = new Vector2(point.X - center.X, point.Y - center.Y);
            Vector2 rotated = new Vector2(
                translated.X * cos - translated.Y * sin,
                translated.X * sin + translated.Y * cos);
            return new Vector2(rotated.X + center.X, rotated.Y + center.Y);
        }

What I need to do now is calculate the new Location Point (X,Y) of the unit after it has been changed from line to column formation or from column to line formation. Note that the unit occupies the exact same space, it’s just that the Point from where the unit is drawn is changed.

Hopefully, these two images will explain:
Screenshot 2023-08-22 145951
Screenshot 2023-08-22 150727

So, the question is: given the above methods and info, how do I calculate the new Location (X,Y) point after a unit is changed from column to line formation (or vice versa)?

Or, to put another way, what is the new point (X,Y) that will cause the unit to be drawn occupying the exact same space on the map (arrowheads don’t count) when the unit changes from column to line formation?

It’s probably a bit late to suggest this, but since you’re dealing with both forward and lateral motion I would suggest the unit’s Location Point should be the rectangle’s center of mass. That would simplify a lot of movement operations.

For the immediate problem, your goal is to move the Location Point from sprite1’s origin to sprite2’s origin when you switch from forward to lateral motion?

In theory, you already know what both origins are when the sprites are in a neutral rotation. Subtract one from the other, and you’ve got a vector to translate the Location Point by. Transform that vector with RotatePoint, and you should be good to go.

Ah, yes, as you can see from the attached code:

// Rotate the corners around the center of the rectangle
// AHA! Our units don't rotate around the center!
Vector2 center = new Vector2(rect.Center.X, rect.Top + 5);

It would definitely have been easier if we were using center mass for unit X,Y. But, we’re too far along to redo this now so…

I don’t know the origin point for the second unit in relation to the first unit. Note, there is also a facing issue, resolved like this:

 RedArmy[DisplayUnitIndex].Facing += 1.5708; // 90 degrees

So, I would be most obliged if you would rough out an algorithm or pseudocode or code of what you’re thinking.

Okay, soooo… if you lack the origin information for both sprites, we can’t really move the unit location based on that. You’re trying to solve Y=f(x) when you don’t know what x is.

The proper solution here is to replace your Type enum with the Type Object pattern.

https://gameprogrammingpatterns.com/type-object.html

public class UnitType
{
    UnitTypeEnum Key;
    
    Texture2D ColumnSprite { get; private set; }
    Texture2D LineSprite { get; private set; }
    Vector2 ColumnSpriteOrigin { get; private set; }
    Vector2 LineSpriteOrigin { get; private set; }

    //The dimensions of this unit minus the arrows.
    Rectangle CollisionRect { get; private set; } 

    //You can get the dimensions plus the arrows from ColumnSprite.Width and Height
}

public Dictionary<UnitTypeEnum, UnitType> UnitTypes = new Dictionary<UnitTypeEnum, UnitType>();

Fill out the dictionary at startup and give each unit a reference to one of these. That’ll give you somewhere to store all the things that make unit types unique, and you won’t need to spam if/else statements elsewhere in your code (in CalculateActualRect).

More importantly to what we’re talking about, this allows you to set and get the origin points of your sprites for each type of Unit. You can use those for this sort of origin math.

Personally, I would try and invest some time in learning math for games to resolve these kinds of situations:

When it comes to game development, don’t skip on the basic theories and rely on others to figure it out for you, it’s best to get the fundamentals done so you are better equipped for the road ahead.

The GPT engines are also a good source of enlightenment for looking where to understand what is needed rather than just being given the answer.

My 5 pence worth, but good luck on your game dev journey @Ezra_Sidran

2 Likes

I woke up this morning realizing the problem was actually pretty simple. We already knew the 4 corners of the rotated rect. We just needed to find the mid point of one of the lines. This is the solution:

            NewUnitLocation.X = (int)(corners[1].X + corners[2].X) / 2;
            NewUnitLocation.Y = (int)(corners[1].Y + corners[2].Y) / 2;

Or, if we want to flip the unit the other way it would be corners 0,3.

And converting from line to column will simply use the opposite corners 0,1 and and 2,3.