[英]Why Java Math.toRadians() is not accurate and how to solve it?
我正在创建自己的 Turtle 应用程序,类似于 Java 中的 python。 说明中采用的角度以度为单位。 在绘制它时,我必须使用 sin 和 cos,这需要角度为弧度。 所以,我使用Math.toRadians(theta)
。
我的指示如下:
moveTo(250, 250);
for (int i = 0; i < 60; i++) {
forward(100);
right(60);
}
我循环了 60 次以检查错误。 在这里,我只是制作一个六边形并一次又一次地绘制它 10 次,以检查角度是否正常工作。 (如果我只找到一个六边形,这意味着六边形角度工作正常,否则会有一些错误)
正如您在图像中看到的, Math.toRadians()
中肯定有一些可疑的地方,因为在 6 轮之后,它没有回到相同的位置。
有没有办法解决这个问题?
一个循环的图像,在这里你可以清楚地看到它不是精确的 360 度
对于那些想要查看我的代码的人。 (这里的问题是关于为什么Math.toRadians()
不准确。我的代码没有问题)
这里是。
package com.example.jturtle;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.stream.IntStream;
public class JTurtle extends Application {
private final ArrayList<String> instructions;
private final ArrayList<String> previousInstructions;
GraphicsContext gc;
int i = 0;
private boolean penDown;
private int penX, penY;
private int penSize;
private int speed;
private double theta;
private Color penColor;
private Color backgroundColor;
public JTurtle() {
penX = penY = 0;
speed = 10;
penSize = 1;
penDown = true;
theta = 0;
penColor = Color.BLACK;
backgroundColor = Color.WHITE;
instructions = new ArrayList<>();
previousInstructions = new ArrayList<>();
}
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
stage.setTitle("JTurtle");
stage.setScene(new Scene(getPane()));
stage.setResizable(false);
stage.show();
}
private Parent getPane() {
var canvas = new Canvas(500, 500);
canvas.setLayoutX(0);
canvas.setLayoutY(0);
gc = canvas.getGraphicsContext2D();
penSize(5);
moveTo(250, 250);
for (int i = 0; i < 6; i++) {
forward(100);
right(60);
}
new AnimationTimer() {
@Override
public void handle(long now) {
update();
}
}.start();
var pane = new AnchorPane(canvas);
pane.setPrefSize(500, 500);
return pane;
}
private void update() {
try {
boolean flag = IntStream.range(0, instructions.size()).anyMatch(i -> !instructions.get(i).equals(previousInstructions.get(i)));
if (!flag) return;
} catch (Exception ignored) {
}
for (String instruction : instructions) {
var ins = instruction.split(" ");
switch (ins[0]) {
case "FWD" -> {
if (penDown)
gc.strokeLine(penX,
penY,
penX += Integer.parseInt(ins[1]) * Math.cos(Math.toRadians(theta)),
penY -= Integer.parseInt(ins[1]) * Math.sin(Math.toRadians(theta)));
else {
penX += Integer.parseInt(ins[1]) * Math.cos(Math.toRadians(theta));
penY -= Integer.parseInt(ins[1]) * Math.sin(Math.toRadians(theta));
}
}
case "BWD" -> {
if (penDown)
gc.strokeLine(penX,
penY,
penX += Integer.parseInt(ins[1]) * Math.cos(Math.toRadians(theta)),
penY += Integer.parseInt(ins[1]) * Math.sin(Math.toRadians(theta)));
else {
penX += Integer.parseInt(ins[1]) * Math.cos(Math.toRadians(theta));
penY += Integer.parseInt(ins[1]) * Math.sin(Math.toRadians(theta));
}
}
case "RGT" -> theta += Integer.parseInt(ins[1]);
case "LFT" -> theta -= Integer.parseInt(ins[1]);
case "PUP" -> penDown = false;
case "PDN" -> penDown = true;
case "PSZ" -> penSize = Integer.parseInt(ins[1]);
case "PC" -> penColor = Color.web(ins[1]);
case "BC" -> backgroundColor = Color.web(ins[1]);
case "SPD" -> speed = Integer.parseInt(ins[1]);
case "CLR" -> {
gc.setFill(backgroundColor);
gc.fillRect(0, 0, 500, 500);
}
case "MOV" -> {
penX = Integer.parseInt(ins[1]);
penY = Integer.parseInt(ins[2]);
}
}
}
previousInstructions.clear();
previousInstructions.addAll(instructions);
}
public void forward(int distance) {
instructions.add("FWD " + distance);
}
public void smoothForward(int distance) {
instructions.add("SFD " + distance);
}
public void backward(int distance) {
instructions.add("BWD " + distance);
}
public void smoothBackward(int distance) {
instructions.add("SBW " + distance);
}
public void left(int angle) {
instructions.add("LFT " + angle);
}
public void right(int angle) {
instructions.add("RGT " + angle);
}
public void moveTo(int x, int y) {
instructions.add("MOV " + x + " " + y);
}
public void penUp() {
instructions.add("PUP");
}
public void penDown() {
instructions.add("PDN");
}
public void penSize(int size) {
instructions.add("PSZ " + size);
}
public void speed(int s) {
instructions.add("SPD " + s);
}
public void penColor(Color c) {
instructions.add("PC " + c.getRed() + " " + c.getGreen() + " " + c.getBlue());
}
public void backgroundColor(Color c) {
instructions.add("BC " + c.getRed() + " " + c.getGreen() + " " + c.getBlue());
}
public void clear() {
instructions.add("CLR");
}
}
这里的问题是关于为什么
Math.toRadians()
不准确。 我的代码没有问题。
这里有些人认为这是一个大胆的说法。 所以我做了一些测试
class Scratch {
public static void main(String[] args) {
for (int i = 0; i < 360; i++) {
System.out.println(Math.toDegrees(Math.toRadians(i)));
}
}
}
和输出(打印的东西的随机段)
245.00000000000003
246.00000000000003
247.0
248.0
248.99999999999997
250.00000000000003
251.0
252.0
253.0
254.00000000000003
255.00000000000003
所以这清楚地证明了 Math.toRadians 没有正确保留实际角度,其细微的差异实际上会破坏整个绘图......
好吧,我刚刚使用Math.round()
解决了它
喜欢,
Math.round(Integer.parseInt(ins[1]) * Math.cos(Math.toRadians(theta)))
这可能是问题所在。 随着坐标的更新,对先前坐标的连续处理可能会引入浮点错误。 即使数学是正确的。 这就像制作副本一样。 这是一个例子。 三角形在其底部旋转。 它的坐标一次又一次地更新,并且由于浮点数学的限制而开始缩小。
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class JTurtle extends JPanel {
JFrame f = new JFrame();
int length = 150;
int size = 500;
int ctrX = size / 2;
int ctrY = size / 2;
double[] tri = { -length / 2, 0, length / 2, 0, 0,
-length * Math.sin(Math.PI / 3.), -length / 2, 0 };
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new JTurtle().start());
}
public void start() {
f.add(this);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
setBackground(Color.white);
f.setLocationRelativeTo(null);
f.setVisible(true);
Timer t = new Timer(0, (ae) -> rotate());
t.setDelay(15);
t.start();
}
public void rotate() {
double a = Math.PI / 120;
for (int i = 0; i < tri.length; i += 2) {;
tri[i] = (tri[i]) * Math.cos(a)
- (tri[i + 1]) * Math.sin(a);
tri[i + 1] = (tri[i]) * Math.sin(a)
+ (tri[i + 1]) * Math.cos(a);
repaint();
}
}
public Dimension getPreferredSize() {
return new Dimension(size, size);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(2));
for (int i = 0; i < tri.length - 3; i += 2) {
g2d.drawLine(ctrX + (int) tri[i], ctrY+(int) tri[i + 1],
ctrX+(int) (tri[i + 2]), ctrY+(int) tri[i + 3]);
}
g2d.setColor(Color.red);
g2d.fillOval(ctrX - 5, ctrY - 5, 10, 10);
}
}
如果是这种情况,对于每次移动,您要么需要将原始坐标更新到新位置,要么将图形上下文转换到新位置,然后将原始图形相对于其新位置移动。 由于我不精通或设置JavaFX
,我无法运行您的代码。
当您通过添加或减去penSize/2 + 1
来更新penX
和penY
时,您必须考虑笔的大小。
这仅适用于奇数笔尺寸!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.