单独旋转矩形的各个点会扭曲矩形。

7

在此输入图片描述我正在尝试通过旋转矩形的点来旋转矩形,使用以下代码:

  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;

但是 round 函数会使矩形变形,有没有人有什么想法来克服这个问题?

我附上了图片,白色点是我围绕中心点旋转的点,我确信图片已经被正确旋转,因此白色点应该与图片的角落相同。


1
此外,round是一个函数,而不是运算符。 - Andreas Rejbrand
1
是的,Andreas,我围绕它的中心旋转了它。谢谢您的留言,我编辑了问题 :) - Sara S.
1
处理整数坐标时,最好使用四舍五入。您能否发布图片以展示正在发生的情况?我相信您只是旋转了四个角而不是周边的每个像素。 - David Heffernan
1
请注意,Real类型已被弃用,请在进行此类舍入计算时使用Single。尽管输出没有任何区别... - NGLN
1
@NGLN Real并没有被弃用。它是一个通用类型,目前别名为Double。你可能想到的是Real48 - David Heffernan
显示剩余3条评论
3个回答

7

我能想到这种方法失败的唯一方式是你正在转换周长上的每个点。如果你是这样做的,请不要这样做。转换角落并使用图形原语在每个角之间绘制线条。

更新: 你的评论透露了问题所在。您正在重复旋转并在每次将其转换为整数时累积错误。通过将坐标存储为双精度值并在需要绘制时仅按需转换为整数来处理此问题。

实际上,如果我是你,我会将主数据视为位置和角度,两者均以双精度存储。我根本不会存储角落的坐标。我会存储位置(中心或一个角)和方向角(相对于固定的全局坐标系)。这样,您将始终绘制真实的矩形。在每个积分步骤中,根据需要增加位置和方向,然后从主数据计算角落的位置。采用这种方法,您将永远不会遭受形状失真的困扰。


我这样做,David,但扭曲是由于 round 函数积累错误点所致。 - Sara S.
4
关于累积问题 - 在旋转时,您使用哪些坐标(点.X,点.Y)进行旋转?是初始坐标还是上一步旋转后的坐标? - MBo
不幸的是,我旋转了之前的步骤点,但我会更新我的代码并测试它,非常感谢您的帮助 :) - Sara S.
是的,David做得很好,我试过了,它运行得非常好,因为我有矩形的中心、宽度和高度。但是因为我用鼠标移动旋转它,所以我必须保存总角度,用负角度旋转,然后调整矩形边使其相互垂直,然后再用总旋转角度旋转它。我之所以这样做,是因为我还有其他模式,比如调整大小和平移,所以我不想在多个地方更改原始点。 - Sara S.

4

浮点数计算,特别是涉及三角函数时,由于浮点变量的限制,总是容易出错。当您将坐标差乘以三角函数而不是将坐标相乘并减去结果时,您可以增强计算的精度。您可以尝试使用以下代码(假设角度以弧度表示且使用math.pas):

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;

更新: 根据David编辑的答案,您不应该使用增量旋转,因为这会增加舍入误差。


我看不出这会有任何区别。我错过了什么? - David Heffernan
@David,在原始公式中,中心点和点的临时结果依赖于其绝对位置,而在我的公式中,它们仅依赖于它们之间的差异。因此,计算精度仅取决于矩形的大小,而不取决于其位置。在浮点运算中,(AC - BC)可能与(A-B)*C略有不同,这取决于各自的值。当你使用Round()函数舍入结果时,这可能会显现出来。 - Uwe Raabe
AC-BC 和 (A-B)*C 一样容易出现舍入误差。减法是完全相同的,只是需要进行缩放。 - David Heffernan
@David,我发现了一些产生显著差异的数字(抱歉没有格式):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*(AC - BC)) - Round(D*(A - B)*C); Assert(diff = 0); end; - Uwe Raabe
这并不证明您的代码在任何方面都更优秀。当然,您将能够构建示例,其中结果有所不同。但是差异并不显著。实际问题肯定是舍入误差的累积。遗憾的是,问题没有说明旋转序列已被执行的事实。 - David Heffernan
显示剩余2条评论

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;

1
这并没有回答问题。你需要添加一些文字来解释1. 问题是什么,2. 你的代码如何解决它。 - David Heffernan
你是对的。没有必要存储和传递角落的坐标。 - Ivan Yuzafatau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接