Rotation and radians

When a user clicks on a unit and the unit needs to change formation from column to line, based on what direction the unit is being ordered to go, I need to invert the rotation of the angle. Here’s an example:

The user right clicks on this unit in line formation:
Screenshot 2023-12-17 093701

Indicating it should switch to column formation:
Screenshot 2023-12-17 093740

The the user right-clicks again (somewhere to the west in this example and the unit ‘inverts its rotation’ to the correct angle:
Screenshot 2023-12-17 093812
(Yes, I know when the unit ‘flips’ it’s location is off by a little. I have to sort this out. The problem is that the center of the unit is not the center of the sprite.

This is the code I use to flip the rotation of the unit:

 tFacing = (tFacing + Math.PI) % (2 * Math.PI);

This seems to work fine EXCEPT when the unit is facing due south:
Screenshot 2023-12-17 094432
Note that the unit should flip and now be facing north, but it remains facing south.

What I’ve noticed is that when the unit is facing south, the tFacing value is PI:

3.1415963267948968

After the rotation, above, it’s value is:

3.6732051036381108E-06

And it still points south (instead of north)

Can somebody please tell me what I’m doing wrong and how to solve this? Many thanks!

Creating a spreadsheet with the same function gives these results which look fine:

Column A	Column B Formula			Column B Output
0			=MOD((A1 + 180), 360)		180
30			=MOD((A2 + 180), 360)		210
60			=MOD((A3 + 180), 360)		240
90			=MOD((A4 + 180), 360)		270
120			=MOD((A5 + 180), 360)		300
150			=MOD((A6 + 180), 360)		330
180			=MOD((A7 + 180), 360)		0
210			=MOD((A8 + 180), 360)		30
240			=MOD((A9 + 180), 360)		60
270			=MOD((A10 + 180), 360)		90
300			=MOD((A11 + 180), 360)		120
330			=MOD((A12 + 180), 360)		150
360			=MOD((A13 + 180), 360)		180

Are you not expecting 3.1415963267948968 to become 0 (well 0.0000036732051036381108 with floating point errors)? Does the unit not visually rotate?

The unit does not rotate (see the last image, above). The unit should be pointing north. It isn’t. And when that unit’s facing (PI) is pumped into the equation, the result isn’t 0 is a weird small number.

Okay, found the solution when replying to Squarebanas above. Look at the precision of the value stored for the new facing. It’s a really small number but it’s not 0. So I solved the problem changing tFacing from a double to a float and, yup, that solved the problem. I hadn’t seen a precision problem like this in a long time.

1 Like

By weird negative you’re refering to this:

That’s 3.673 x 10⁻⁶ which shouldn’t be negative but is actually 0.000003673. That shouldn’t make it face south though. How are you using the rotation in the drawing code? Changing from double to float might fix it this time but there is something else wrong which may cause issues down the line.

Edit: Think you noticed it’s positive as you updated your reply while I was typing.

              Vector3 R = GAME.RENDER_QUEUE[0].Rotation;
              R += new Vector3(0, 0.1f, 0);

                if (R.Y > MathF.PI)
                    R.Y = -MathF.PI;
                if (R.Y < -MathF.PI)
                    R.Y = MathF.PI;

                if (R.X > MathF.PI)
                    R.X = -MathF.PI;
                if (R.X < -MathF.PI)
                    R.X = MathF.PI;

                if (R.Z > MathF.PI)
                    R.Z = -MathF.PI;
                if (R.Z < -MathF.PI)
                    R.Z = MathF.PI;

I get consistent and accurate results with this. In a Matrix mind you.

The fact you are over Pi and not wrapping is alien to me. So is everything in the technique squareBannas showed. Looks advanced.

I have not encounted a single floating error. But it is very likely to happen using doubles.


was the Pi you are using a double, and was the rotation a double, or could it be a Pi vs Double comparison.

Anyways seeems you had resolved it. If it is not resolved try the above wrap.

If you wanted euler, which i often do. You are asking about what percentage of 180* that your pi is at. And to find out if it was less than 180 or greater than depends on the negative.

I don’t know if it is much help but i will take the opportunity to talk about it.

(obviously above wrap can be more accurate but depends if you care about that accuracy)

What I posted was his line of code tFacing = (tFacing + Math.PI) % (2 * Math.PI); but converted it to a spreadsheet formula instead. This gives a print out in Excel/LibreCalc/etc that proves his line of code correctly gives an angle that is 180° in the opposite direction for every angle.

I usually keep angles in the -180° to 180° range as well. By the way the code you posted will clamp to 180° not wrap. You typically want 190° to wrap around to -170° but it depends on context.

The important thing is 0°, -0.001°, 0.001°, 360°, 36,000° should all face north regardless of floating point issues. Switching from double to float must be masking a more serious bug if a close to zero value points it south.

I agree with you guys. I’m back to the drawing board here.

I really need another set of eyes on this problem. I’m going around in circles and I really appreciate everybody’s help. I’m going to lay out all the steps and methods. Sorry for the long post.

First, because of a decision made by my coding partner who isn’t using MonoGame, but WPF, the center of our sprites aren’t where they should be. The ‘point’ of a sprite, the location, is actually the tip of the arrowhead. This has caused a ton of problems.

Consequently, I have to go through a bunch of steps to find the true rectangle of a unit. So, as we can see from this screen cap that outlines the true rects of a unit, we can determine the actual location of the unit:
Screenshot 2023-12-19 104525

So, given the location of the unit (and the unit type) we have this code:

  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
              // 24 x 55 including arrows
              // 24 x 44 without arrows

              ActualCorners = new Rectangle(Location.X - (int)(12 * PieceSize), (int)(Location.Y + (UnscaledArrowheads * PieceSize)), (int)(24 * PieceSize), (int)(55 * PieceSize) - (int)(UnscaledArrowheads * PieceSize));

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

              ActualCorners = new Rectangle(Location.X - (int)((44 / 2) * PieceSize), Location.Y + (int)(UnscaledArrowheads * PieceSize), (int)(44 * PieceSize), (int)(38 * PieceSize) - (int)(UnscaledArrowheads * PieceSize));
          }
      } // small unit
      else // large unit
      {
          if (TheUnit.Formation == Formations.Column ||
              TheUnit.Formation == Formations.MeleeColumn)
          {
              // 27 x 74 including arrows
              // 27 x 62 without arrows

              ActualCorners = new Rectangle(Location.X - (int)((27 / 2) * PieceSize), (int)(Location.Y + (UnscaledArrowheads * PieceSize)), (int)(27 * PieceSize), (int)((74 * PieceSize) - (UnscaledArrowheads * PieceSize)));

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

              ActualCorners = new Rectangle(Location.X - (int)(31 * PieceSize), Location.Y + (int)(UnscaledArrowheads * PieceSize), (int)(62 * PieceSize), (int)(38 * PieceSize) - (int)(UnscaledArrowheads * 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 - (float)(UnscaledArrowheads * PieceSize));


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

     return corners;
 }

So, from the image, above, we know that these calculations are correct. Now, we need to flip the unit/sprite 180 degrees and we need to know the new location of the unit and rotation of the unit after the flip.

However, the unit isn’t flipping. These points are the same:

 PointI PointA = BlueArmy[DisplayUnitIndex].Location;
 PointI PointB = FlipUnit180Degrees(true, DisplayUnitIndex);

 DistanceA = (int)UtilityMethods.EuclideanDistance(PointA, DestinationGoal);
 DistanceB = (int)UtilityMethods.EuclideanDistance(PointB, DestinationGoal);

And here’s the (current) code for the flipping method (which is returning the same Point that it started off with):

  public PointI FlipUnit180Degrees(bool side, int UnitID)
  {
      Vector2 point;
      Point center;
      Rectangle rec = new Rectangle();
      Vector2 RPoint;
      float tFacing;
      
      if (side)
      {
          point = new Vector2((float) BlueArmy[UnitID].Location.X, (float)BlueArmy[UnitID].Location.Y);
          rec = CalculateActualRect(BlueArmy[UnitID], BlueArmy[UnitID].Location);
          center = new Point(rec.Center.X, rec.Center.Y - 6);
          tFacing = (float)BlueArmy[UnitID].Facing;
      }
      else
      {
          point = new Vector2((float)RedArmy[UnitID].Location.X, (float)RedArmy[UnitID].Location.Y);
          rec = CalculateActualRect(RedArmy[UnitID], RedArmy[UnitID].Location);
          center = new Point(rec.Center.X, rec.Center.Y);
          tFacing = (float)RedArmy[UnitID].Facing;
      }

   
      tFacing = (float)((tFacing + Math.PI) % (2 * Math.PI));
      
      RPoint = RotatePoint(point,new Vector2(center.X, center.Y), tFacing);
      PointI RPointI = new PointI(RPoint.X, RPoint.Y);

      return RPointI;
  }

Sorry for the long post. If anybody’s eyes can see something that I’m missing, it would be greatly appreciated.

SquareBananas solved the problem in this thread.
Thank you SquareBananas!

1 Like