7

enter image description hereI'm trying to rotate a rectangle by rotating its points , using this code

  var
 dx,dy:real;
 rotp:Tpoint;
begin
  dx := (CenterPoint.Y * Sin(angle)) - (CenterPoint.X * Cos(angle)) + CenterPoint.X;
  dy := -(CenterPoint.X * Sin(angle)) - (CenterPoint.Y * Cos(angle)) + CenterPoint.Y;
  rotP.X := round((point.X * Cos(angle)) - (point.Y * Sin(angle)) + dx);
  rotP.Y := round((point.X * Sin(angle)) + (point.Y * Cos(angle)) + dy);
  result:= rotP;
end;

but the round function makes the rectangle distorted , has anyone any idea how to overcome this?

I attached the image, the white points are the points i rotate about the center point, i'm sure that the image is rotated well thus, the white points should be identical to the corners of the image.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Sara S.
  • 1,365
  • 1
  • 15
  • 33
  • You are attempting to rotate the rectangle about its centre of mass, right? – Andreas Rejbrand Nov 20 '11 at 13:48
  • 1
    In addition, `round` is a function, not an operator. – Andreas Rejbrand Nov 20 '11 at 13:50
  • 1
    yes Andreas i rotate it about its center, Thanks for your note i edited the question :) – Sara S. Nov 20 '11 at 13:57
  • 1
    working with integer coords, you can't do better than round. Could you post images to show what's happening. I trust you are just rotating the 4 corners and not every single pixel on the perimeter. – David Heffernan Nov 20 '11 at 13:57
  • 1
    Note that the Real type is deprecated, use Single for these kind of rounding calculations. Makes no difference in output though... – NGLN Nov 20 '11 at 14:05
  • The white center dot clearly isn't the center of the image. How do you get/calculate the center? – NGLN Nov 20 '11 at 14:07
  • note that the height is not equal to the width , the height is 5 cm and the width is 4 cm it's deviated about 2 pixels, isn't it ? – Sara S. Nov 20 '11 at 14:11
  • 1
    @NGLN `Real` is not deprecated. It is a generic type currently aliased to `Double`. You are thinking of `Real48`. – David Heffernan Nov 20 '11 at 21:14

3 Answers3

7

The only way I can see that this approach would fail is if you are transforming every point on the perimeter. If you are doing that, don't. Transform the corners and draw lines between each corner using graphics primitives.

Update: Your comment gives the game away. You are rotating repeatedly and accumulating errors every time you digitise by converting to integer. Deal with that by storing your coordinates as double precision values and just convert to integer on demand when you need to draw.

In fact, if I were you I would treat your master data to be a position and an angle, both stored to double precision. I would not store the coordinates of the corners at all. I would store a position (center or one of the corners) and an orientation angle (relative to a fixed global axis system). That way you will always draw a true rectangle. At each integration step increment position and orientation as necessary and then calculate the position of the corners from the master data. Do it like this and you will never suffer from distortion of your shape.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I do this David but the distortion is from accumalating wrong points due to the round function – Sara S. Nov 20 '11 at 14:05
  • 4
    @Sara Saeed Concering accumulation - what coordinates (point.X,point.Y) are you using to rotate? Initial or rotated at previous step? – MBo Nov 20 '11 at 14:27
  • unfortunately, i rotated the previous step point, but i'll update my code and test it , Really Thanks for ur help :) – Sara S. Nov 21 '11 at 08:44
  • Yes David well done, i tried it and it works great, as i have the center,the width , and the height of the rectangle. but because i rotate it with mouse move i had to save the total angle, rotate with negative the angle then adjusting the rectangle sides to be perpendicular to eachother , then rotating it again with the total rotation angle. I just made this because i have another modes like resize and pan so i dont want change the original points in multiple places – Sara S. Nov 21 '11 at 09:18
4

Floating point calculations, especially with trigonometric functions, are always error prone due to the limited resolution of float variables. You could enhance the precision of the calculation when you multiply the coordinate differences with the trigonometric function instead of multiplying the coordinates and subtracting the results. You can try this code (assuming angle is in radians and math.pas is used):

var
  dx,dy,ca,sa:Extended;
  rotp:Tpoint;
begin
  SinCos(angle, sa, ca);
  dx := point.x - CenterPoint.X;
  dy := point.y - CenterPoint.Y;
  result.X := CenterPoint.X + round(dx*ca - dy*sa);
  result.Y := CenterPoint.Y + round(dx*sa + dy*ca);
end;

Update: And according to David's edited answer, you shouldn't use incremental rotations as this will increase the rounding error.

Uwe Raabe
  • 45,288
  • 3
  • 82
  • 130
  • I can't see how this could make any difference. What am I missing? – David Heffernan Nov 20 '11 at 14:38
  • @David, in the original formula the interim results depend on the absolute position of the CenterPoint and the point, while in my formula they only depend on the difference of them. Thus the calculation precision is only depended on the size of the rectangle and not on its position. In floating point computation (A*C - B*C) can be slightly different from (A-B)*C depending on the individual values. This might manifest when you Round() the results. – Uwe Raabe Nov 20 '11 at 14:49
  • AC-BC is just a prone to round off as (A-B)*C. The subtraction is exactly the same, subject to scaling. – David Heffernan Nov 20 '11 at 14:58
  • @David, I have found a couple of numbers that produce a significant difference (sorry for the non-formatting): `type Float = Extended; var A: Float; B: Float; C: Float; D: Float; diff: Integer; begin A := 100000.0; B := A - 1.5; C := 0.1; D := 10.0; diff := Round(D*(A*C - B*C)) - Round(D*(A - B)*C); Assert(diff = 0); end;` – Uwe Raabe Nov 20 '11 at 15:24
  • That does not demonstrate that your code is superior in any way. Sure you will be able to construct examples where the results differ. But not in a significant way. The actual issue must surely be accumulation of rounding errors. It was just a shame that the fact that the question did not state that sequences of rotations were performed. – David Heffernan Nov 20 '11 at 15:30
  • @David, I agree with you on the accumulation issue, which I wasn't aware of when I wrote my answer. Nonetheless does the issue I mentioned exist. I have encountered cases where data extracted from a CAD system produced different results when the geometry was placed far away from (0,0). This could be solved by extracting only coordinate differences instead of absolute coordinates. – Uwe Raabe Nov 20 '11 at 15:46
  • The point you make about calculations far away from the origin is very valid. I'm very familiar with that in my work writing FE codes. But it seems like it's not the issue here. Once you bring `Round` into play then the distance from the origin issue rather recedes into the background. – David Heffernan Nov 20 '11 at 15:51
1
type
  TRectangle = record
    A, B, C, D: TPoint;
  end;

var
  Rectangle, // master rect
  TurnedRectangle: TRectangle; // turned rect

...

procedure RotateRectangle;
begin
  TurnedRectangle.A := RotatePoint(Rectangle.A);
  ...
  DrawRectangle
end

function RotatePoint(Point: TPoint): TPoint;
var
  dx, dy: Real;
  rotp: TPoint;
begin
  dx := (CenterPoint.Y * Sin(angle)) - (CenterPoint.X * Cos(angle)) + CenterPoint.X;
  dy := -(CenterPoint.X * Sin(angle)) - (CenterPoint.Y * Cos(angle)) + CenterPoint.Y;
  rotP.X := Round((Point.X * Cos(angle)) - (point.Y * Sin(angle)) + dx);
  rotP.Y := Round((Point.X * Sin(angle)) + (point.Y * Cos(angle)) + dy);
  result:= rotP;
end;

procedure DrawRectangle;
begin
  Canvas.Polygon([TurnedRectangle.A, TurnedRectangle.B, TurnedRectangle.C, TurnedRectangle.D]);
end;