在乌龟程序中画一个圆圈
Drawing a circle in a turtle program
我目前正在处理由 Turtle 逻辑驱动的 Processing(如语言)草图(参见 https://en.wikipedia.org/wiki/Turtle_graphics)。这意味着我从当前坐标到提供的坐标画了一条线。这个提供的坐标将成为新的当前坐标。我想近似一个圆并使用三角函数编写了一段简单的代码。代码如下所示:
void drawCircle(int radius){
// The circle center is radius amount to the left of the current xpos
int steps = 16;
double step = TWO_PI /steps;
for(double theta = step; theta <= TWO_PI; theta += step){
float deltaX = cos((float)theta) - cos((float)(theta - step));
float deltaY = sin((float)theta) - sin((float)(theta - step));
moveXY(deltaX*radius, deltaY*radius);
}
}
程序逻辑简单。它将使用变量 theta
循环遍历一个圆中的所有弧度。步骤的数量将指示每个 theta
块的大小。然后它将计算由 theta 控制的圆中特定点的 x,y 值。然后它将减去前一个循环的 x,y 值(因此 theta-step
)以获得它必须从该位置移动的量才能达到所需的 x,y 位置。它最终会将这些增量值提供给 moveXY 函数,该函数从当前点到提供的值画一条线,并将它们作为新的当前位置。
当使用有限的步骤时,该程序似乎运行良好。然而,当步数增加时,圆圈变得越来越像斐波那契螺旋线。我的猜测是,这是由于浮点数以及正弦和余弦计算不精确,并且每次迭代都会累加。
我是不是理解错了什么?我希望最终将此代码移植到 Javascript,因此我正在设计中寻找解决方案。使用 BigDecimal 可能不起作用,特别是因为它不包含自己的余弦和正弦函数。我提供了一些图片来详细说明问题。非常感谢任何帮助!
步数 16:
步数 32:
步数 64:
步数 128:
Float 和 sine/cosine 应该足够精确。问题是:你在飞机上的位置有多精确?如果此位置以像素为单位测量,则每个浮点值在每一步后都会四舍五入为整数。然后精度损失加起来。
在循环的每次迭代中,您都在计算增量而不考虑当前坐标是什么。如此有效,你是 "dead-reckoning",这总是不准确的,因为每一步都会累积错误。
既然你知道你想要一个圆,另一种方法是在每次迭代时,首先确定你想要到达的圆上的实际点,然后计算到达那里的增量 - 比如以下(但我必须承认我还没有测试过!):
void drawCircle(int radius){
// The circle center is radius amount to the left of the current xpos
int steps = 16;
double step = TWO_PI /steps;
float previousX = 0;
float previousY = radius;
for(double theta = step; theta <= TWO_PI; theta += step){
float thisX = radius * sin((float)theta);
float thisY = radius * cos((float)theta);
moveXY(thisX - previousX, thisY - previousY);
previousX = thisX;
previousY = thisY;
}
}
我目前正在处理由 Turtle 逻辑驱动的 Processing(如语言)草图(参见 https://en.wikipedia.org/wiki/Turtle_graphics)。这意味着我从当前坐标到提供的坐标画了一条线。这个提供的坐标将成为新的当前坐标。我想近似一个圆并使用三角函数编写了一段简单的代码。代码如下所示:
void drawCircle(int radius){
// The circle center is radius amount to the left of the current xpos
int steps = 16;
double step = TWO_PI /steps;
for(double theta = step; theta <= TWO_PI; theta += step){
float deltaX = cos((float)theta) - cos((float)(theta - step));
float deltaY = sin((float)theta) - sin((float)(theta - step));
moveXY(deltaX*radius, deltaY*radius);
}
}
程序逻辑简单。它将使用变量 theta
循环遍历一个圆中的所有弧度。步骤的数量将指示每个 theta
块的大小。然后它将计算由 theta 控制的圆中特定点的 x,y 值。然后它将减去前一个循环的 x,y 值(因此 theta-step
)以获得它必须从该位置移动的量才能达到所需的 x,y 位置。它最终会将这些增量值提供给 moveXY 函数,该函数从当前点到提供的值画一条线,并将它们作为新的当前位置。
当使用有限的步骤时,该程序似乎运行良好。然而,当步数增加时,圆圈变得越来越像斐波那契螺旋线。我的猜测是,这是由于浮点数以及正弦和余弦计算不精确,并且每次迭代都会累加。
我是不是理解错了什么?我希望最终将此代码移植到 Javascript,因此我正在设计中寻找解决方案。使用 BigDecimal 可能不起作用,特别是因为它不包含自己的余弦和正弦函数。我提供了一些图片来详细说明问题。非常感谢任何帮助!
步数 16:
步数 32:
步数 64:
步数 128:
Float 和 sine/cosine 应该足够精确。问题是:你在飞机上的位置有多精确?如果此位置以像素为单位测量,则每个浮点值在每一步后都会四舍五入为整数。然后精度损失加起来。
在循环的每次迭代中,您都在计算增量而不考虑当前坐标是什么。如此有效,你是 "dead-reckoning",这总是不准确的,因为每一步都会累积错误。
既然你知道你想要一个圆,另一种方法是在每次迭代时,首先确定你想要到达的圆上的实际点,然后计算到达那里的增量 - 比如以下(但我必须承认我还没有测试过!):
void drawCircle(int radius){
// The circle center is radius amount to the left of the current xpos
int steps = 16;
double step = TWO_PI /steps;
float previousX = 0;
float previousY = radius;
for(double theta = step; theta <= TWO_PI; theta += step){
float thisX = radius * sin((float)theta);
float thisY = radius * cos((float)theta);
moveXY(thisX - previousX, thisY - previousY);
previousX = thisX;
previousY = thisY;
}
}