I'm creating own Turtle app similar to python in Java. The angles taken in the instructions is in degrees. While drawing it, I have to use sin and cos which requires the angle to be in radians. So, I used Math.toRadians(theta)
.
My instructions are like:
moveTo(250, 250);
for (int i = 0; i < 60; i ) {
forward(100);
right(60);
}
I looped it for 60 times to check for errors. Here, I'm just making a hexagon and drawing over it again and again for 10 times, to check if the angles are working correctly. (If I find only one hexagon it means that the hexagon angles are working correctly, else there is some error)
As you can see in the image, there is surely something fishy going in Math.toRadians()
because after 6 rounds, it's not coming back to the same position.
Is there any way to solve this?
Image for one loop, here you can clearly see that its not making exactly 360 degrees
For those who want to see my code. (the question here is about why Math.toRadians()
is not accurate. there is no issue with my code)
Here it is.
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");
}
}
CodePudding user response:
This may be the problem. As the coordinates get updated, continuous processing of previous coordinates can introduce floating point errors. Even if the math is sound. It's like making a copy of a copy. Here is an example. The triangle is rotated on its base. It's coordinates get updated over and over again and it starts to shrink due to the limitations of floating point math.
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);
}
}
If this is the situation, for each move you either need to update the original coordinates to the new location, or translate the graphics context to the new location and just move the original figure relative to its new position. As I am not versed in nor setup for JavaFX
I could not run your code.
CodePudding user response:
You must account for the size of the pen when you update penX
and penY
by adding or subtracting penSize/2 1
.
This will only work for odd pen sizes!