繁体   English   中英

为什么 Java Math.toRadians() 不准确以及如何解决?

[英]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来更新penXpenY时,您必须考虑笔的大小。

这仅适用于奇数笔尺寸!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM