From c3a714fabe5cd23b328cf1e7fdde9411a7f45bfb Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Tue, 30 Jan 2024 12:04:11 +0200 Subject: [PATCH 01/34] fixed acceleration --- src/Character.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Character.java b/src/Character.java index 0b51a4d..b584da1 100644 --- a/src/Character.java +++ b/src/Character.java @@ -50,9 +50,9 @@ public void update() { if (Math.abs(error) < I_ZONE) errorSum += error; int moveValue = (int) (error * kP + errorSum * kI + errorRate * kD); moveValue = (int) normalizeSpeed(moveValue); - if (Math.abs(moveValue - lastSpeed * Math.signum(lastSpeed)) > MAX_ACCELERATION) { - if (moveValue > 0) moveValue = (int) (lastSpeed + MAX_ACCELERATION); - if (moveValue < 0) moveValue = (int) (lastSpeed - MAX_ACCELERATION); + if (Math.abs(moveValue - lastSpeed) > MAX_ACCELERATION) { + if (lastSpeed > moveValue) moveValue = (int) (lastSpeed - MAX_ACCELERATION); + if (lastSpeed < moveValue) moveValue = (int) (lastSpeed + MAX_ACCELERATION); } this.translate(moveValue, 0); lastError = error; From 07a397a54a5a0b436c93a94248fd2be2be2a4c75 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Tue, 30 Jan 2024 11:48:52 +0200 Subject: [PATCH 02/34] saving --- .idea/uiDesigner.xml | 124 +++++++++++++++++++++++++++++++++++++++++++ src/Character.java | 4 ++ src/RerunButton.java | 2 - src/Status.java | 38 +++++++++++++ src/Window.java | 4 ++ 5 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 .idea/uiDesigner.xml create mode 100644 src/Status.java diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Character.java b/src/Character.java index b584da1..5cf8179 100644 --- a/src/Character.java +++ b/src/Character.java @@ -99,4 +99,8 @@ public void setPID(double kP, double kI, double kD) { setI(kI); setD(kD); } + + public int getError() { + return Setpoint.getInstance().x - this.x; + } } diff --git a/src/RerunButton.java b/src/RerunButton.java index 221462f..e3f3bfe 100644 --- a/src/RerunButton.java +++ b/src/RerunButton.java @@ -1,7 +1,5 @@ import javax.swing.*; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; public class RerunButton extends JButton { diff --git a/src/Status.java b/src/Status.java new file mode 100644 index 0000000..10d495e --- /dev/null +++ b/src/Status.java @@ -0,0 +1,38 @@ +import javax.swing.*; +import java.awt.*; + +public class Status extends JPanel { + + private static final int WIDTH = 640; + private static final int HEIGHT = 640; + private static final int X = 800; + private static final int Y = 480; + private static final int FONT_SIZE = 20; + private final JTextField characterError; + + private static Status instance; + + public static Status getInstance() { + if (instance == null) { + instance = new Status(); + } + return instance; + } + + private Status() { + characterError = new JTextField("Error: " + Character.getInstance().getError()); + characterError.setBounds(1, 1, 200, 100); + characterError.setEnabled(false); + characterError.setFont(new Font(Font.MONOSPACED, Font.PLAIN, FONT_SIZE)); + characterError.setLayout(null); + this.add(characterError); + this.setLayout(null); + this.setVisible(true); + this.setBounds(X, Y, WIDTH, HEIGHT); + this.setBackground(Color.GRAY); + } + + public void update() { + characterError.setText("Error: " + Character.getInstance().getError()); + } +} diff --git a/src/Window.java b/src/Window.java index 3b719d0..4efff0b 100644 --- a/src/Window.java +++ b/src/Window.java @@ -17,6 +17,7 @@ public class Window extends JPanel { private final BaseTextField kPField; private final BaseTextField kIField; private final BaseTextField kDField; + private final Status status; private Window() { super(IS_DOUBLE_BUFFERED); @@ -27,6 +28,7 @@ private Window() { character = Character.getInstance(); setpoint = Setpoint.getInstance(); rerunButton = RerunButton.getInstance(); + status = Status.getInstance(); kPField = new BaseTextField("kP", 300, 30); kIField = new BaseTextField("kI", 600, 30); kDField = new BaseTextField("kD", 900, 30); @@ -34,6 +36,7 @@ private Window() { this.add(kPField); this.add(kIField); this.add(kDField); + this.add(status); } @Override @@ -59,6 +62,7 @@ private void update() { delay(0.02); character.update(); character.setPID(kPField.getValue(), kIField.getValue(), kDField.getValue()); + status.update(); repaint(); } From a193706bc9fc925402abdcd4190684f776bb0c4f Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Tue, 30 Jan 2024 18:22:02 +0200 Subject: [PATCH 03/34] displaying information now also moved everything to a package --- src/Status.java | 38 ----------- src/{ => simulator}/Character.java | 20 ++++-- src/{ => simulator}/Main.java | 4 ++ src/{ => simulator}/RerunButton.java | 2 + src/{ => simulator}/Setpoint.java | 2 + src/{ => simulator}/Window.java | 6 +- src/simulator/information/Status.java | 63 +++++++++++++++++++ .../textfields/BaseTextField.java | 2 +- 8 files changed, 91 insertions(+), 46 deletions(-) delete mode 100644 src/Status.java rename src/{ => simulator}/Character.java (88%) rename src/{ => simulator}/Main.java (68%) rename src/{ => simulator}/RerunButton.java (97%) rename src/{ => simulator}/Setpoint.java (96%) rename src/{ => simulator}/Window.java (95%) create mode 100644 src/simulator/information/Status.java rename src/{ => simulator}/textfields/BaseTextField.java (95%) diff --git a/src/Status.java b/src/Status.java deleted file mode 100644 index 10d495e..0000000 --- a/src/Status.java +++ /dev/null @@ -1,38 +0,0 @@ -import javax.swing.*; -import java.awt.*; - -public class Status extends JPanel { - - private static final int WIDTH = 640; - private static final int HEIGHT = 640; - private static final int X = 800; - private static final int Y = 480; - private static final int FONT_SIZE = 20; - private final JTextField characterError; - - private static Status instance; - - public static Status getInstance() { - if (instance == null) { - instance = new Status(); - } - return instance; - } - - private Status() { - characterError = new JTextField("Error: " + Character.getInstance().getError()); - characterError.setBounds(1, 1, 200, 100); - characterError.setEnabled(false); - characterError.setFont(new Font(Font.MONOSPACED, Font.PLAIN, FONT_SIZE)); - characterError.setLayout(null); - this.add(characterError); - this.setLayout(null); - this.setVisible(true); - this.setBounds(X, Y, WIDTH, HEIGHT); - this.setBackground(Color.GRAY); - } - - public void update() { - characterError.setText("Error: " + Character.getInstance().getError()); - } -} diff --git a/src/Character.java b/src/simulator/Character.java similarity index 88% rename from src/Character.java rename to src/simulator/Character.java index 5cf8179..829c149 100644 --- a/src/Character.java +++ b/src/simulator/Character.java @@ -1,3 +1,5 @@ +package simulator; + import java.awt.*; public class Character extends Rectangle { @@ -24,6 +26,9 @@ public class Character extends Rectangle { private double lastTimestamp; private double lastError; private double lastSpeed; + private double error; + private double dt; + private double errorRate; public static Character getInstance() { if (instance == null) { @@ -44,9 +49,9 @@ private Character(int width, int height, int sourceX, int sourceY) { } public void update() { - double error = Setpoint.getInstance().x - this.x; - double dt = System.currentTimeMillis() - lastTimestamp; - double errorRate = (error - lastError) / dt; + error = Setpoint.getInstance().x - this.x; + dt = System.currentTimeMillis() - lastTimestamp; + errorRate = (error - lastError) / dt; if (Math.abs(error) < I_ZONE) errorSum += error; int moveValue = (int) (error * kP + errorSum * kI + errorRate * kD); moveValue = (int) normalizeSpeed(moveValue); @@ -100,7 +105,12 @@ public void setPID(double kP, double kI, double kD) { setD(kD); } - public int getError() { - return Setpoint.getInstance().x - this.x; + public double getError() { + return error; + } + + public double getRate() { + return errorRate; } + } diff --git a/src/Main.java b/src/simulator/Main.java similarity index 68% rename from src/Main.java rename to src/simulator/Main.java index ea7f478..1c85d3e 100644 --- a/src/Main.java +++ b/src/simulator/Main.java @@ -1,3 +1,7 @@ +package simulator; + +import simulator.Window; + public class Main { public static void main(String[] args) { diff --git a/src/RerunButton.java b/src/simulator/RerunButton.java similarity index 97% rename from src/RerunButton.java rename to src/simulator/RerunButton.java index e3f3bfe..96cc9b5 100644 --- a/src/RerunButton.java +++ b/src/simulator/RerunButton.java @@ -1,3 +1,5 @@ +package simulator; + import javax.swing.*; import java.awt.*; diff --git a/src/Setpoint.java b/src/simulator/Setpoint.java similarity index 96% rename from src/Setpoint.java rename to src/simulator/Setpoint.java index 1308468..e3d7051 100644 --- a/src/Setpoint.java +++ b/src/simulator/Setpoint.java @@ -1,3 +1,5 @@ +package simulator; + import java.awt.*; public class Setpoint extends Rectangle { diff --git a/src/Window.java b/src/simulator/Window.java similarity index 95% rename from src/Window.java rename to src/simulator/Window.java index 4efff0b..15db388 100644 --- a/src/Window.java +++ b/src/simulator/Window.java @@ -1,8 +1,10 @@ -import textfields.BaseTextField; +package simulator; + +import simulator.information.Status; +import simulator.textfields.BaseTextField; import javax.swing.*; import java.awt.*; -import java.util.Set; public class Window extends JPanel { diff --git a/src/simulator/information/Status.java b/src/simulator/information/Status.java new file mode 100644 index 0000000..a58d172 --- /dev/null +++ b/src/simulator/information/Status.java @@ -0,0 +1,63 @@ +package simulator.information; + +import simulator.Character; + +import javax.swing.*; +import javax.swing.border.LineBorder; +import java.awt.*; + +public class Status extends JPanel { + + private static final int WIDTH = 640; + private static final int HEIGHT = 640; + private static final int X = 800; + private static final int Y = 480; + private static final int FONT_SIZE = 20; + private JTextField characterError; + private JTextField errorRate; + + private static Status instance; + + public static Status getInstance() { + if (instance == null) { + instance = new Status(); + } + return instance; + } + + private Status() { + configureCharacterError(); + configureErrorRate(); + this.add(characterError); + this.add(errorRate); + this.setLayout(null); + this.setVisible(true); + this.setBounds(X, Y, WIDTH, HEIGHT); + this.setBackground(Color.GRAY); + } + + public void configureCharacterError() { + characterError = new JTextField("Error: " + Character.getInstance().getError()); + characterError.setSize(200, 50); + characterError.setEditable(false); + characterError.setFont(new Font(Font.MONOSPACED, Font.PLAIN, FONT_SIZE)); + characterError.setLayout(null); + characterError.setBackground(Color.GRAY); + characterError.setBorder(new LineBorder(Color.GRAY)); + } + + public void configureErrorRate() { + errorRate = new JTextField("Error rate: " + Character.getInstance().getRate()); + errorRate.setBounds(0, 50, 400, 50); + errorRate.setEditable(false); + errorRate.setFont(new Font(Font.MONOSPACED, Font.PLAIN, FONT_SIZE)); + errorRate.setLayout(null); + errorRate.setBackground(Color.GRAY); + errorRate.setBorder(new LineBorder(Color.GRAY)); + } + + public void update() { + characterError.setText("Error: " + Character.getInstance().getError()); + errorRate.setText("Error rate: " + Character.getInstance().getRate()); + } +} diff --git a/src/textfields/BaseTextField.java b/src/simulator/textfields/BaseTextField.java similarity index 95% rename from src/textfields/BaseTextField.java rename to src/simulator/textfields/BaseTextField.java index 33fe81f..5fbc2d2 100644 --- a/src/textfields/BaseTextField.java +++ b/src/simulator/textfields/BaseTextField.java @@ -1,4 +1,4 @@ -package textfields; +package simulator.textfields; import javax.swing.*; import java.awt.*; From 3c576b688ed411a58e9433870e801dbb61bba41f Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Sat, 3 Feb 2024 20:24:31 +0200 Subject: [PATCH 04/34] created several classes related to PID control and FF control to make the code way nicer, implemented FF into the movement --- src/simulator/Character.java | 81 +++++++------------ src/simulator/Window.java | 12 ++- .../control/FeedForwardController.java | 69 ++++++++++++++++ .../control/FeedForwardSettings.java | 44 ++++++++++ src/simulator/control/PIDController.java | 72 +++++++++++++++++ src/simulator/control/PIDSettings.java | 44 ++++++++++ src/simulator/information/Status.java | 6 +- 7 files changed, 269 insertions(+), 59 deletions(-) create mode 100644 src/simulator/control/FeedForwardController.java create mode 100644 src/simulator/control/FeedForwardSettings.java create mode 100644 src/simulator/control/PIDController.java create mode 100644 src/simulator/control/PIDSettings.java diff --git a/src/simulator/Character.java b/src/simulator/Character.java index 829c149..a50e872 100644 --- a/src/simulator/Character.java +++ b/src/simulator/Character.java @@ -1,5 +1,10 @@ package simulator; +import simulator.control.FeedForwardController; +import simulator.control.FeedForwardSettings; +import simulator.control.PIDController; +import simulator.control.PIDSettings; + import java.awt.*; public class Character extends Rectangle { @@ -14,21 +19,14 @@ public class Character extends Rectangle { private static final int MAX_ACCELERATION = 2; private static final int FRICTION = 3; - private double kP; - private double kI; - private double kD; - - private static final int I_ZONE = 200; + private final PIDController pidController; + private final FeedForwardController feedForwardController; + private final PIDSettings pidSettings; + private final FeedForwardSettings feedForwardSettings; private static Character instance; - private double errorSum; - private double lastTimestamp; - private double lastError; private double lastSpeed; - private double error; - private double dt; - private double errorRate; public static Character getInstance() { if (instance == null) { @@ -39,37 +37,32 @@ public static Character getInstance() { private Character(int width, int height, int sourceX, int sourceY) { super(sourceX, sourceY, width, height); - errorSum = 0; - lastTimestamp = System.currentTimeMillis(); - lastError = Setpoint.getInstance().x - this.x; lastSpeed = 0; - kP = 0; - kI = 0; - kD = 0; + pidSettings = new PIDSettings(0, 0, 0); + feedForwardSettings = new FeedForwardSettings(0, 0, 0); + pidController = new PIDController(pidSettings); + feedForwardController = new FeedForwardController(feedForwardSettings); } public void update() { - error = Setpoint.getInstance().x - this.x; - dt = System.currentTimeMillis() - lastTimestamp; - errorRate = (error - lastError) / dt; - if (Math.abs(error) < I_ZONE) errorSum += error; - int moveValue = (int) (error * kP + errorSum * kI + errorRate * kD); + int moveValue = pidController.calculate(this.x, Setpoint.getInstance().x) + + feedForwardController.calculate(this.x, Setpoint.getInstance().x); moveValue = (int) normalizeSpeed(moveValue); if (Math.abs(moveValue - lastSpeed) > MAX_ACCELERATION) { if (lastSpeed > moveValue) moveValue = (int) (lastSpeed - MAX_ACCELERATION); if (lastSpeed < moveValue) moveValue = (int) (lastSpeed + MAX_ACCELERATION); } this.translate(moveValue, 0); - lastError = error; - lastTimestamp = System.currentTimeMillis(); - lastSpeed = normalizeNoFriction(moveValue); + lastSpeed = moveValue; } private double normalizeSpeed(double speed) { - if (speed > 0) speed -= FRICTION; - if (speed < 0) speed += FRICTION; - if (Math.abs(speed) > MAX_SPEED) speed = (int) (MAX_SPEED * Math.signum(speed)); - if (Math.abs(speed) < FRICTION) speed = 0; + if (speed != 0) { + if (speed > 0) speed -= FRICTION; + if (speed < 0) speed += FRICTION; + if (Math.abs(speed) > MAX_SPEED) speed = (int) (MAX_SPEED * Math.signum(speed)); + } + else if (Math.abs(speed) < FRICTION) speed = 0; return speed; } @@ -80,37 +73,17 @@ private double normalizeNoFriction(double speed) { public static void reset() { Character character = getInstance(); - character.errorSum = 0; - character.lastTimestamp = System.currentTimeMillis(); - character.lastError = Setpoint.getInstance().x - character.x; character.lastSpeed = 0; character.setLocation(SOURCE_X, SOURCE_Y); - } - - public void setP(double kP) { - this.kP = kP; - } - - public void setI(double kI) { - this.kI = kI; - } - - public void setD(double kD) { - this.kD = kD; + character.pidController.reset(); + character.feedForwardController.reset(); } public void setPID(double kP, double kI, double kD) { - setP(kP); - setI(kI); - setD(kD); + pidController.setPID(kP, kI, kD); } - public double getError() { - return error; + public void setFF(double kS, double kV, double kA) { + feedForwardController.setGains(kS, kV, kA); } - - public double getRate() { - return errorRate; - } - } diff --git a/src/simulator/Window.java b/src/simulator/Window.java index 15db388..733c663 100644 --- a/src/simulator/Window.java +++ b/src/simulator/Window.java @@ -19,6 +19,9 @@ public class Window extends JPanel { private final BaseTextField kPField; private final BaseTextField kIField; private final BaseTextField kDField; + private final BaseTextField kSField; + private final BaseTextField kVField; + private final BaseTextField kAField; private final Status status; private Window() { @@ -34,10 +37,16 @@ private Window() { kPField = new BaseTextField("kP", 300, 30); kIField = new BaseTextField("kI", 600, 30); kDField = new BaseTextField("kD", 900, 30); + kSField = new BaseTextField("kS", 300, 100); + kVField = new BaseTextField("kV", 600, 100); + kAField = new BaseTextField("kA", 900, 100); this.add(rerunButton); this.add(kPField); this.add(kIField); this.add(kDField); + this.add(kSField); + this.add(kVField); + this.add(kAField); this.add(status); } @@ -62,8 +71,9 @@ private void delay(double seconds) { private void update() { delay(0.02); - character.update(); character.setPID(kPField.getValue(), kIField.getValue(), kDField.getValue()); + character.setFF(kSField.getValue(), kVField.getValue(), kAField.getValue()); + character.update(); status.update(); repaint(); } diff --git a/src/simulator/control/FeedForwardController.java b/src/simulator/control/FeedForwardController.java new file mode 100644 index 0000000..164095d --- /dev/null +++ b/src/simulator/control/FeedForwardController.java @@ -0,0 +1,69 @@ +package simulator.control; + +public class FeedForwardController { + + private double kS; + + private double kV; + + private double kA; + + private double previousTarget; + + public FeedForwardController(double kS, double kV, double kA) { + this.kS = kS; + this.kV = kV; + this.kA = kA; + this.previousTarget = 0; + } + + public FeedForwardController(FeedForwardSettings feedForwardSettings) { + this(feedForwardSettings.getS(), feedForwardSettings.getV(), feedForwardSettings.getA()); + } + + public void setGains(double kS, double kV, double kA) { + this.kS = kS; + this.kV = kV; + this.kA = kA; + } + + public void setGains(FeedForwardSettings feedForwardSettings) { + setGains(feedForwardSettings.getS(), feedForwardSettings.getV(), feedForwardSettings.getA()); + } + + public double getS() { + return kS; + } + + public void setS(double kS) { + this.kS = kS; + } + + public double getV() { + return kV; + } + + public void setV(double kV) { + this.kV = kV; + } + + public double getA() { + return kA; + } + + public void setA(double kA) { + this.kA = kA; + } + + + public void reset() { + this.previousTarget = 0; + } + + public int calculate(double source, double setpoint) { + double error = setpoint - source; + double targetDerivative = (setpoint - previousTarget) / 0.02; + previousTarget = setpoint; + return (int) (kS * Math.signum(error) + kV * setpoint + kA * targetDerivative); + } +} diff --git a/src/simulator/control/FeedForwardSettings.java b/src/simulator/control/FeedForwardSettings.java new file mode 100644 index 0000000..8331868 --- /dev/null +++ b/src/simulator/control/FeedForwardSettings.java @@ -0,0 +1,44 @@ +package simulator.control; + +public class FeedForwardSettings { + + private double kS; + private double kV; + private double kA; + + public FeedForwardSettings(double kS, double kV, double kA) { + this.kS = kS; + this.kV = kV; + this.kA = kA; + } + + public double getS() { + return kS; + } + + public void setS(double kS) { + this.kS = kS; + } + + public double getV() { + return kV; + } + + public void setV(double kV) { + this.kV = kV; + } + + public double getA() { + return kA; + } + + public void setA(double kA) { + this.kA = kA; + } + + public void setFF(double kS, double kV, double kA) { + setS(kS); + setV(kV); + setA(kA); + } +} diff --git a/src/simulator/control/PIDController.java b/src/simulator/control/PIDController.java new file mode 100644 index 0000000..203b291 --- /dev/null +++ b/src/simulator/control/PIDController.java @@ -0,0 +1,72 @@ +package simulator.control; + +import simulator.Setpoint; + +public class PIDController { + + private static final int I_ZONE = 200; + + private double kP; + private double kI; + private double kD; + + private double errorSum; + private double lastTimestamp; + private double lastError; + private double lastSpeed; + private double error; + private double dt; + private double errorRate; + + public PIDController(PIDSettings pidSettings) { + this.kP = pidSettings.getP(); + this.kI = pidSettings.getI(); + this.kD = pidSettings.getD(); + errorSum = 0; + lastTimestamp = System.currentTimeMillis(); + lastError = 0; + } + + public PIDController(double kP, double kI, double kD) { + this(new PIDSettings(kP, kI, kD)); + } + + public void setP(double kP) { + this.kP = kP; + } + + public void setI(double kI) { + this.kI = kI; + } + + public void setD(double kD) { + this.kD = kD; + } + + public void setPID(double kP, double kI, double kD) { + setP(kP); + setI(kI); + setD(kD); + } + + public void setPID(PIDSettings pidSettings) { + setPID(pidSettings.getP(), pidSettings.getI(), pidSettings.getD()); + } + + public int calculate(double source, double setpoint) { + error = setpoint - source; + dt = System.currentTimeMillis() - lastTimestamp; + errorRate = (error - lastError) / dt; + if (Math.abs(error) < I_ZONE) errorSum += error; + int moveValue = (int) (error * kP + errorSum * kI + errorRate * kD); + lastError = error; + lastTimestamp = System.currentTimeMillis(); + return moveValue; + } + + public void reset() { + errorSum = 0; + lastTimestamp = System.currentTimeMillis(); + lastError = 0; + } +} diff --git a/src/simulator/control/PIDSettings.java b/src/simulator/control/PIDSettings.java new file mode 100644 index 0000000..bcc4765 --- /dev/null +++ b/src/simulator/control/PIDSettings.java @@ -0,0 +1,44 @@ +package simulator.control; + +public class PIDSettings { + + private double kP; + private double kI; + private double kD; + + public PIDSettings(double kP, double kI, double kD) { + this.kP = kP; + this.kI = kI; + this.kD = kD; + } + + public double getP() { + return kP; + } + + public double getI() { + return kI; + } + + public double getD() { + return kD; + } + + public void setP(double kP) { + this.kP = kP; + } + + public void setI(double kI) { + this.kI = kI; + } + + public void setD(double kD) { + this.kD = kD; + } + + public void setPID(double kP, double kI, double kD) { + setP(kP); + setI(kI); + setD(kD); + } +} diff --git a/src/simulator/information/Status.java b/src/simulator/information/Status.java index a58d172..43d2cd3 100644 --- a/src/simulator/information/Status.java +++ b/src/simulator/information/Status.java @@ -37,7 +37,7 @@ private Status() { } public void configureCharacterError() { - characterError = new JTextField("Error: " + Character.getInstance().getError()); + characterError = new JTextField(); characterError.setSize(200, 50); characterError.setEditable(false); characterError.setFont(new Font(Font.MONOSPACED, Font.PLAIN, FONT_SIZE)); @@ -47,7 +47,7 @@ public void configureCharacterError() { } public void configureErrorRate() { - errorRate = new JTextField("Error rate: " + Character.getInstance().getRate()); + errorRate = new JTextField(); errorRate.setBounds(0, 50, 400, 50); errorRate.setEditable(false); errorRate.setFont(new Font(Font.MONOSPACED, Font.PLAIN, FONT_SIZE)); @@ -57,7 +57,5 @@ public void configureErrorRate() { } public void update() { - characterError.setText("Error: " + Character.getInstance().getError()); - errorRate.setText("Error rate: " + Character.getInstance().getRate()); } } From 46c07036d7995702d89bc4902547608ab9565d04 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Thu, 8 Feb 2024 13:45:21 +0200 Subject: [PATCH 05/34] added wait time and tolerance --- src/simulator/Character.java | 45 ++++++++++++++------ src/simulator/Window.java | 27 ++++++++---- src/simulator/control/PIDController.java | 28 +++++++++++- src/simulator/control/PIDSettings.java | 22 +++++++++- src/simulator/information/BaseInfoField.java | 22 ++++++++++ src/simulator/information/Status.java | 33 ++++---------- 6 files changed, 130 insertions(+), 47 deletions(-) create mode 100644 src/simulator/information/BaseInfoField.java diff --git a/src/simulator/Character.java b/src/simulator/Character.java index a50e872..2858d09 100644 --- a/src/simulator/Character.java +++ b/src/simulator/Character.java @@ -26,7 +26,9 @@ public class Character extends Rectangle { private static Character instance; + private double lastTimeNotOnTarget; private double lastSpeed; + private boolean commandFinished; public static Character getInstance() { if (instance == null) { @@ -38,22 +40,30 @@ public static Character getInstance() { private Character(int width, int height, int sourceX, int sourceY) { super(sourceX, sourceY, width, height); lastSpeed = 0; - pidSettings = new PIDSettings(0, 0, 0); + pidSettings = new PIDSettings(0, 0, 0, 10, 1); feedForwardSettings = new FeedForwardSettings(0, 0, 0); pidController = new PIDController(pidSettings); feedForwardController = new FeedForwardController(feedForwardSettings); + lastTimeNotOnTarget = System.currentTimeMillis(); } public void update() { - int moveValue = pidController.calculate(this.x, Setpoint.getInstance().x) + - feedForwardController.calculate(this.x, Setpoint.getInstance().x); - moveValue = (int) normalizeSpeed(moveValue); - if (Math.abs(moveValue - lastSpeed) > MAX_ACCELERATION) { - if (lastSpeed > moveValue) moveValue = (int) (lastSpeed - MAX_ACCELERATION); - if (lastSpeed < moveValue) moveValue = (int) (lastSpeed + MAX_ACCELERATION); + if (!commandFinished) { + commandFinished = (System.currentTimeMillis() - lastTimeNotOnTarget >= pidSettings.getWaitTime() * 1000 && + pidController.isOnTarget()); + int moveValue = pidController.calculate(this.x, Setpoint.getInstance().x) + + feedForwardController.calculate(this.x, Setpoint.getInstance().x); + moveValue = (int) normalizeSpeed(moveValue); + if (Math.abs(moveValue - lastSpeed) > MAX_ACCELERATION) { + if (lastSpeed > moveValue) moveValue = (int) (lastSpeed - MAX_ACCELERATION); + if (lastSpeed < moveValue) moveValue = (int) (lastSpeed + MAX_ACCELERATION); + } + this.translate(moveValue, 0); + lastSpeed = moveValue; + } + if (!pidController.isOnTarget()) { + lastTimeNotOnTarget = System.currentTimeMillis(); } - this.translate(moveValue, 0); - lastSpeed = moveValue; } private double normalizeSpeed(double speed) { @@ -61,8 +71,7 @@ private double normalizeSpeed(double speed) { if (speed > 0) speed -= FRICTION; if (speed < 0) speed += FRICTION; if (Math.abs(speed) > MAX_SPEED) speed = (int) (MAX_SPEED * Math.signum(speed)); - } - else if (Math.abs(speed) < FRICTION) speed = 0; + } else if (Math.abs(speed) < FRICTION) speed = 0; return speed; } @@ -75,12 +84,24 @@ public static void reset() { Character character = getInstance(); character.lastSpeed = 0; character.setLocation(SOURCE_X, SOURCE_Y); + character.commandFinished = false; + character.lastTimeNotOnTarget = System.currentTimeMillis(); character.pidController.reset(); character.feedForwardController.reset(); } - public void setPID(double kP, double kI, double kD) { + public void setPID(double kP, double kI, double kD, double tolerance, double waitTime) { pidController.setPID(kP, kI, kD); + pidController.setTolerance(tolerance); + pidSettings.setWaitTime(waitTime); + } + + public PIDController getPIDController() { + return pidController; + } + + public double getError() { + return Setpoint.getInstance().x - this.x; } public void setFF(double kS, double kV, double kA) { diff --git a/src/simulator/Window.java b/src/simulator/Window.java index 733c663..d2e4318 100644 --- a/src/simulator/Window.java +++ b/src/simulator/Window.java @@ -19,6 +19,8 @@ public class Window extends JPanel { private final BaseTextField kPField; private final BaseTextField kIField; private final BaseTextField kDField; + private final BaseTextField toleranceField; + private final BaseTextField waitTimeField; private final BaseTextField kSField; private final BaseTextField kVField; private final BaseTextField kAField; @@ -37,17 +39,13 @@ private Window() { kPField = new BaseTextField("kP", 300, 30); kIField = new BaseTextField("kI", 600, 30); kDField = new BaseTextField("kD", 900, 30); + toleranceField = new BaseTextField("tolerance", 450, 65); + waitTimeField = new BaseTextField("wait time", 750, 65); kSField = new BaseTextField("kS", 300, 100); kVField = new BaseTextField("kV", 600, 100); kAField = new BaseTextField("kA", 900, 100); + configureTextFields(); this.add(rerunButton); - this.add(kPField); - this.add(kIField); - this.add(kDField); - this.add(kSField); - this.add(kVField); - this.add(kAField); - this.add(status); } @Override @@ -71,13 +69,26 @@ private void delay(double seconds) { private void update() { delay(0.02); - character.setPID(kPField.getValue(), kIField.getValue(), kDField.getValue()); + character.setPID(kPField.getValue(), kIField.getValue(), kDField.getValue(), toleranceField.getValue(), + waitTimeField.getValue()); character.setFF(kSField.getValue(), kVField.getValue(), kAField.getValue()); character.update(); status.update(); repaint(); } + private void configureTextFields() { + this.add(kPField); + this.add(kIField); + this.add(kDField); + this.add(toleranceField); + this.add(waitTimeField); + this.add(kSField); + this.add(kVField); + this.add(kAField); + this.add(status); + } + public static void initGame() { JFrame frame = new JFrame(); Window window = new Window(); diff --git a/src/simulator/control/PIDController.java b/src/simulator/control/PIDController.java index 203b291..568d0c8 100644 --- a/src/simulator/control/PIDController.java +++ b/src/simulator/control/PIDController.java @@ -9,6 +9,8 @@ public class PIDController { private double kP; private double kI; private double kD; + private double tolerance; + private double waitTime; private double errorSum; private double lastTimestamp; @@ -17,18 +19,22 @@ public class PIDController { private double error; private double dt; private double errorRate; + private boolean onTarget; public PIDController(PIDSettings pidSettings) { this.kP = pidSettings.getP(); this.kI = pidSettings.getI(); this.kD = pidSettings.getD(); + this.tolerance = pidSettings.getTolerance(); + this.waitTime = pidSettings.getWaitTime(); errorSum = 0; lastTimestamp = System.currentTimeMillis(); lastError = 0; + errorRate = 0; } - public PIDController(double kP, double kI, double kD) { - this(new PIDSettings(kP, kI, kD)); + public PIDController(double kP, double kI, double kD, double tolerance, double waitTime) { + this(new PIDSettings(kP, kI, kD, tolerance, waitTime)); } public void setP(double kP) { @@ -49,12 +55,25 @@ public void setPID(double kP, double kI, double kD) { setD(kD); } + public void setTolerance(double tolerance) { + this.tolerance = tolerance; + } + + public void setWaitTime(double waitTime) { + this.waitTime = waitTime; + } + + public boolean isOnTarget() { + return onTarget; + } + public void setPID(PIDSettings pidSettings) { setPID(pidSettings.getP(), pidSettings.getI(), pidSettings.getD()); } public int calculate(double source, double setpoint) { error = setpoint - source; + onTarget = (Math.abs(error) <= tolerance); dt = System.currentTimeMillis() - lastTimestamp; errorRate = (error - lastError) / dt; if (Math.abs(error) < I_ZONE) errorSum += error; @@ -68,5 +87,10 @@ public void reset() { errorSum = 0; lastTimestamp = System.currentTimeMillis(); lastError = 0; + onTarget = false; + } + + public double getErrorRate() { + return errorRate; } } diff --git a/src/simulator/control/PIDSettings.java b/src/simulator/control/PIDSettings.java index bcc4765..5671d6a 100644 --- a/src/simulator/control/PIDSettings.java +++ b/src/simulator/control/PIDSettings.java @@ -5,11 +5,15 @@ public class PIDSettings { private double kP; private double kI; private double kD; + private double tolerance; + private double waitTime; - public PIDSettings(double kP, double kI, double kD) { + public PIDSettings(double kP, double kI, double kD, double tolerance, double waitTime) { this.kP = kP; this.kI = kI; this.kD = kD; + this.tolerance = tolerance; + this.waitTime = waitTime; } public double getP() { @@ -36,6 +40,22 @@ public void setD(double kD) { this.kD = kD; } + public double getTolerance() { + return this.tolerance; + } + + public double getWaitTime() { + return this.waitTime; + } + + public void setTolerance(double tolerance) { + this.tolerance = tolerance; + } + + public void setWaitTime(double waitTime) { + this.waitTime = waitTime; + } + public void setPID(double kP, double kI, double kD) { setP(kP); setI(kI); diff --git a/src/simulator/information/BaseInfoField.java b/src/simulator/information/BaseInfoField.java new file mode 100644 index 0000000..5ac7df9 --- /dev/null +++ b/src/simulator/information/BaseInfoField.java @@ -0,0 +1,22 @@ +package simulator.information; + +import simulator.Character; + +import javax.swing.*; +import javax.swing.border.LineBorder; +import java.awt.*; + +public class BaseInfoField extends JTextField { + + private static final int FONT_SIZE = 20; + + public BaseInfoField(int x, int y, int width, int height, String text) { + this.setText(text); + this.setBounds(x, y, width, height); + this.setEditable(false); + this.setFont(new Font(Font.MONOSPACED, Font.PLAIN, FONT_SIZE)); + this.setLayout(null); + this.setBackground(Color.GRAY); + this.setBorder(new LineBorder(Color.GRAY)); + } +} diff --git a/src/simulator/information/Status.java b/src/simulator/information/Status.java index 43d2cd3..6f1264e 100644 --- a/src/simulator/information/Status.java +++ b/src/simulator/information/Status.java @@ -1,6 +1,7 @@ package simulator.information; import simulator.Character; +import simulator.Setpoint; import javax.swing.*; import javax.swing.border.LineBorder; @@ -13,8 +14,8 @@ public class Status extends JPanel { private static final int X = 800; private static final int Y = 480; private static final int FONT_SIZE = 20; - private JTextField characterError; - private JTextField errorRate; + private BaseInfoField characterError; + private BaseInfoField errorRate; private static Status instance; @@ -26,8 +27,10 @@ public static Status getInstance() { } private Status() { - configureCharacterError(); - configureErrorRate(); + characterError = new BaseInfoField(0, 0, 400, 50, + "Error: " + Character.getInstance().getError()); + errorRate = new BaseInfoField(0, 50, 400, 50, + "Error rate: " + Character.getInstance().getPIDController().getErrorRate()); this.add(characterError); this.add(errorRate); this.setLayout(null); @@ -36,26 +39,8 @@ private Status() { this.setBackground(Color.GRAY); } - public void configureCharacterError() { - characterError = new JTextField(); - characterError.setSize(200, 50); - characterError.setEditable(false); - characterError.setFont(new Font(Font.MONOSPACED, Font.PLAIN, FONT_SIZE)); - characterError.setLayout(null); - characterError.setBackground(Color.GRAY); - characterError.setBorder(new LineBorder(Color.GRAY)); - } - - public void configureErrorRate() { - errorRate = new JTextField(); - errorRate.setBounds(0, 50, 400, 50); - errorRate.setEditable(false); - errorRate.setFont(new Font(Font.MONOSPACED, Font.PLAIN, FONT_SIZE)); - errorRate.setLayout(null); - errorRate.setBackground(Color.GRAY); - errorRate.setBorder(new LineBorder(Color.GRAY)); - } - public void update() { + characterError.setText("Error: " + (Setpoint.getInstance().x - Character.getInstance().x)); + errorRate.setText("Error rate: " + (Character.getInstance().getPIDController().getErrorRate())); } } From 175b2e5ab35c5a8137524f5b956bc5821cf5e247 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Thu, 8 Feb 2024 13:50:59 +0200 Subject: [PATCH 06/34] fixed ordering in Character --- src/simulator/Character.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/simulator/Character.java b/src/simulator/Character.java index 2858d09..bb67a58 100644 --- a/src/simulator/Character.java +++ b/src/simulator/Character.java @@ -19,10 +19,10 @@ public class Character extends Rectangle { private static final int MAX_ACCELERATION = 2; private static final int FRICTION = 3; - private final PIDController pidController; - private final FeedForwardController feedForwardController; private final PIDSettings pidSettings; + private final PIDController pidController; private final FeedForwardSettings feedForwardSettings; + private final FeedForwardController feedForwardController; private static Character instance; From cea0973ed1097b98a040a92ed6ea66f29c782880 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Thu, 8 Feb 2024 13:51:19 +0200 Subject: [PATCH 07/34] fixed ordering in Character --- src/simulator/Character.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/simulator/Character.java b/src/simulator/Character.java index bb67a58..05aaede 100644 --- a/src/simulator/Character.java +++ b/src/simulator/Character.java @@ -24,12 +24,13 @@ public class Character extends Rectangle { private final FeedForwardSettings feedForwardSettings; private final FeedForwardController feedForwardController; - private static Character instance; private double lastTimeNotOnTarget; private double lastSpeed; private boolean commandFinished; + private static Character instance; + public static Character getInstance() { if (instance == null) { instance = new Character(WIDTH, HEIGHT, SOURCE_X, SOURCE_Y); From 0dd7a02f701ae394286e80088b48361f14c089b6 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Thu, 8 Feb 2024 13:52:29 +0200 Subject: [PATCH 08/34] removed magic --- src/simulator/Character.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/simulator/Character.java b/src/simulator/Character.java index 05aaede..0e0a6b9 100644 --- a/src/simulator/Character.java +++ b/src/simulator/Character.java @@ -9,6 +9,7 @@ public class Character extends Rectangle { + private static final int MILLISECONDS_IN_SECOND = 1000; private static final int WIDTH = 32; private static final int HEIGHT = 32; @@ -50,8 +51,8 @@ private Character(int width, int height, int sourceX, int sourceY) { public void update() { if (!commandFinished) { - commandFinished = (System.currentTimeMillis() - lastTimeNotOnTarget >= pidSettings.getWaitTime() * 1000 && - pidController.isOnTarget()); + commandFinished = (System.currentTimeMillis() - lastTimeNotOnTarget >= + pidSettings.getWaitTime() * MILLISECONDS_IN_SECOND && pidController.isOnTarget()); int moveValue = pidController.calculate(this.x, Setpoint.getInstance().x) + feedForwardController.calculate(this.x, Setpoint.getInstance().x); moveValue = (int) normalizeSpeed(moveValue); From f95ec10fa34cbd3e2121e0e6d0730eb86f79cee1 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Thu, 8 Feb 2024 13:53:06 +0200 Subject: [PATCH 09/34] removed magic --- src/simulator/Window.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/simulator/Window.java b/src/simulator/Window.java index d2e4318..223b4bb 100644 --- a/src/simulator/Window.java +++ b/src/simulator/Window.java @@ -11,6 +11,8 @@ public class Window extends JPanel { private static final int WINDOW_WIDTH = 1280; private static final int WINDOW_HEIGHT = 792; + private static final double PERIODIC_FRAME = 0.02; + private static final boolean IS_DOUBLE_BUFFERED = true; private final Character character; @@ -68,7 +70,7 @@ private void delay(double seconds) { } private void update() { - delay(0.02); + delay(PERIODIC_FRAME); character.setPID(kPField.getValue(), kIField.getValue(), kDField.getValue(), toleranceField.getValue(), waitTimeField.getValue()); character.setFF(kSField.getValue(), kVField.getValue(), kAField.getValue()); From 1768bf1c0c9edb153106d91858fcadd38dbca78f Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Thu, 8 Feb 2024 13:54:23 +0200 Subject: [PATCH 10/34] removed spaces and fixed magic --- src/simulator/Window.java | 5 ++--- src/simulator/control/FeedForwardController.java | 7 +++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/simulator/Window.java b/src/simulator/Window.java index 223b4bb..b033854 100644 --- a/src/simulator/Window.java +++ b/src/simulator/Window.java @@ -8,11 +8,10 @@ public class Window extends JPanel { + public static final double PERIODIC_FRAME = 0.02; + private static final int WINDOW_WIDTH = 1280; private static final int WINDOW_HEIGHT = 792; - - private static final double PERIODIC_FRAME = 0.02; - private static final boolean IS_DOUBLE_BUFFERED = true; private final Character character; diff --git a/src/simulator/control/FeedForwardController.java b/src/simulator/control/FeedForwardController.java index 164095d..920ea35 100644 --- a/src/simulator/control/FeedForwardController.java +++ b/src/simulator/control/FeedForwardController.java @@ -1,13 +1,12 @@ package simulator.control; +import simulator.Window; + public class FeedForwardController { private double kS; - private double kV; - private double kA; - private double previousTarget; public FeedForwardController(double kS, double kV, double kA) { @@ -62,7 +61,7 @@ public void reset() { public int calculate(double source, double setpoint) { double error = setpoint - source; - double targetDerivative = (setpoint - previousTarget) / 0.02; + double targetDerivative = (setpoint - previousTarget) / Window.PERIODIC_FRAME; previousTarget = setpoint; return (int) (kS * Math.signum(error) + kV * setpoint + kA * targetDerivative); } From be04f063d2136970e5debaf856833f396f2bce05 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Sun, 11 Feb 2024 10:07:56 +0200 Subject: [PATCH 11/34] added the option to change i zone --- src/simulator/Character.java | 4 ++++ src/simulator/Window.java | 4 ++++ src/simulator/control/PIDController.java | 12 ++++++++---- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/simulator/Character.java b/src/simulator/Character.java index 0e0a6b9..27ce108 100644 --- a/src/simulator/Character.java +++ b/src/simulator/Character.java @@ -98,6 +98,10 @@ public void setPID(double kP, double kI, double kD, double tolerance, double wai pidSettings.setWaitTime(waitTime); } + public void setIZone(int iZone) { + pidController.setIZone(iZone); + } + public PIDController getPIDController() { return pidController; } diff --git a/src/simulator/Window.java b/src/simulator/Window.java index b033854..0803db4 100644 --- a/src/simulator/Window.java +++ b/src/simulator/Window.java @@ -20,6 +20,7 @@ public class Window extends JPanel { private final BaseTextField kPField; private final BaseTextField kIField; private final BaseTextField kDField; + private final BaseTextField iZoneField; private final BaseTextField toleranceField; private final BaseTextField waitTimeField; private final BaseTextField kSField; @@ -40,6 +41,7 @@ private Window() { kPField = new BaseTextField("kP", 300, 30); kIField = new BaseTextField("kI", 600, 30); kDField = new BaseTextField("kD", 900, 30); + iZoneField = new BaseTextField("i zone", 30, 30); toleranceField = new BaseTextField("tolerance", 450, 65); waitTimeField = new BaseTextField("wait time", 750, 65); kSField = new BaseTextField("kS", 300, 100); @@ -72,6 +74,7 @@ private void update() { delay(PERIODIC_FRAME); character.setPID(kPField.getValue(), kIField.getValue(), kDField.getValue(), toleranceField.getValue(), waitTimeField.getValue()); + character.setIZone((int) iZoneField.getValue()); character.setFF(kSField.getValue(), kVField.getValue(), kAField.getValue()); character.update(); status.update(); @@ -84,6 +87,7 @@ private void configureTextFields() { this.add(kDField); this.add(toleranceField); this.add(waitTimeField); + this.add(iZoneField); this.add(kSField); this.add(kVField); this.add(kAField); diff --git a/src/simulator/control/PIDController.java b/src/simulator/control/PIDController.java index 568d0c8..903dac0 100644 --- a/src/simulator/control/PIDController.java +++ b/src/simulator/control/PIDController.java @@ -1,14 +1,13 @@ package simulator.control; -import simulator.Setpoint; - public class PIDController { - private static final int I_ZONE = 200; + private static final int DEFAULT_I_ZONE = 200; private double kP; private double kI; private double kD; + private double iZone; private double tolerance; private double waitTime; @@ -25,6 +24,7 @@ public PIDController(PIDSettings pidSettings) { this.kP = pidSettings.getP(); this.kI = pidSettings.getI(); this.kD = pidSettings.getD(); + this.iZone = DEFAULT_I_ZONE; this.tolerance = pidSettings.getTolerance(); this.waitTime = pidSettings.getWaitTime(); errorSum = 0; @@ -71,12 +71,16 @@ public void setPID(PIDSettings pidSettings) { setPID(pidSettings.getP(), pidSettings.getI(), pidSettings.getD()); } + public void setIZone(int iZone) { + this.iZone = iZone; + } + public int calculate(double source, double setpoint) { error = setpoint - source; onTarget = (Math.abs(error) <= tolerance); dt = System.currentTimeMillis() - lastTimestamp; errorRate = (error - lastError) / dt; - if (Math.abs(error) < I_ZONE) errorSum += error; + if (Math.abs(error) < iZone) errorSum += error; int moveValue = (int) (error * kP + errorSum * kI + errorRate * kD); lastError = error; lastTimestamp = System.currentTimeMillis(); From f7eab81c70d01eeeb4adabb951749a7332bced27 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Sun, 11 Feb 2024 10:26:52 +0200 Subject: [PATCH 12/34] added the option to change setpoint location --- src/simulator/Setpoint.java | 4 ++++ src/simulator/Window.java | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/simulator/Setpoint.java b/src/simulator/Setpoint.java index e3d7051..a058335 100644 --- a/src/simulator/Setpoint.java +++ b/src/simulator/Setpoint.java @@ -22,4 +22,8 @@ public static Setpoint getInstance() { private Setpoint(int width, int height, int sourceX, int sourceY) { super(sourceX, sourceY, width, height); } + + public void setPosition(int position) { + this.x = position; + } } diff --git a/src/simulator/Window.java b/src/simulator/Window.java index 0803db4..a0a2f9a 100644 --- a/src/simulator/Window.java +++ b/src/simulator/Window.java @@ -26,6 +26,7 @@ public class Window extends JPanel { private final BaseTextField kSField; private final BaseTextField kVField; private final BaseTextField kAField; + private final BaseTextField setpointField; private final Status status; private Window() { @@ -47,6 +48,7 @@ private Window() { kSField = new BaseTextField("kS", 300, 100); kVField = new BaseTextField("kV", 600, 100); kAField = new BaseTextField("kA", 900, 100); + setpointField = new BaseTextField("setpoint", 30, 722); configureTextFields(); this.add(rerunButton); } @@ -72,6 +74,7 @@ private void delay(double seconds) { private void update() { delay(PERIODIC_FRAME); + setpoint.setPosition((int) setpointField.getValue()); character.setPID(kPField.getValue(), kIField.getValue(), kDField.getValue(), toleranceField.getValue(), waitTimeField.getValue()); character.setIZone((int) iZoneField.getValue()); @@ -92,6 +95,7 @@ private void configureTextFields() { this.add(kVField); this.add(kAField); this.add(status); + this.add(setpointField); } public static void initGame() { From bea25eefa65b1cd4c3b7e9c384cf0c6327032499 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Sat, 15 Jun 2024 21:58:00 +0300 Subject: [PATCH 13/34] added velocity control --- src/simulator/Character.java | 59 +++++++++++++------ src/simulator/Setpoint.java | 9 ++- src/simulator/Window.java | 9 ++- src/simulator/control/ControlType.java | 37 ++++++++++++ .../control/FeedForwardController.java | 7 ++- src/simulator/information/Status.java | 12 +++- 6 files changed, 109 insertions(+), 24 deletions(-) create mode 100644 src/simulator/control/ControlType.java diff --git a/src/simulator/Character.java b/src/simulator/Character.java index 27ce108..d090935 100644 --- a/src/simulator/Character.java +++ b/src/simulator/Character.java @@ -1,9 +1,6 @@ package simulator; -import simulator.control.FeedForwardController; -import simulator.control.FeedForwardSettings; -import simulator.control.PIDController; -import simulator.control.PIDSettings; +import simulator.control.*; import java.awt.*; @@ -13,8 +10,11 @@ public class Character extends Rectangle { private static final int WIDTH = 32; private static final int HEIGHT = 32; - private static final int SOURCE_X = 0; - private static final int SOURCE_Y = 320; + private static final int POSITION_SOURCE_X = 0; + private static final int POSITION_SOURCE_Y = 320; + + private static final int VELOCITY_SOURCE_X = 640; + private static final int VELOCITY_SOURCE_Y = 320; private static final int MAX_SPEED = 120; private static final int MAX_ACCELERATION = 2; @@ -25,7 +25,6 @@ public class Character extends Rectangle { private final FeedForwardSettings feedForwardSettings; private final FeedForwardController feedForwardController; - private double lastTimeNotOnTarget; private double lastSpeed; private boolean commandFinished; @@ -34,7 +33,7 @@ public class Character extends Rectangle { public static Character getInstance() { if (instance == null) { - instance = new Character(WIDTH, HEIGHT, SOURCE_X, SOURCE_Y); + instance = new Character(WIDTH, HEIGHT, POSITION_SOURCE_X, POSITION_SOURCE_Y); } return instance; } @@ -51,17 +50,29 @@ private Character(int width, int height, int sourceX, int sourceY) { public void update() { if (!commandFinished) { - commandFinished = (System.currentTimeMillis() - lastTimeNotOnTarget >= - pidSettings.getWaitTime() * MILLISECONDS_IN_SECOND && pidController.isOnTarget()); - int moveValue = pidController.calculate(this.x, Setpoint.getInstance().x) + - feedForwardController.calculate(this.x, Setpoint.getInstance().x); - moveValue = (int) normalizeSpeed(moveValue); - if (Math.abs(moveValue - lastSpeed) > MAX_ACCELERATION) { - if (lastSpeed > moveValue) moveValue = (int) (lastSpeed - MAX_ACCELERATION); - if (lastSpeed < moveValue) moveValue = (int) (lastSpeed + MAX_ACCELERATION); + if (ControlType.getInstance().getSelectedIndex() == 0) { + commandFinished = (System.currentTimeMillis() - lastTimeNotOnTarget >= + pidSettings.getWaitTime() * MILLISECONDS_IN_SECOND && pidController.isOnTarget()); + int moveValue = pidController.calculate(this.x, Setpoint.getInstance().x) + + feedForwardController.calculate(this.x, Setpoint.getInstance().x); + moveValue = (int) normalizeSpeed(moveValue); + if (Math.abs(moveValue - lastSpeed) > MAX_ACCELERATION) { + if (lastSpeed > moveValue) moveValue = (int) (lastSpeed - MAX_ACCELERATION); + if (lastSpeed < moveValue) moveValue = (int) (lastSpeed + MAX_ACCELERATION); + } + this.translate(moveValue, 0); + lastSpeed = moveValue; + } else if (ControlType.getInstance().getSelectedIndex() == 1) { + int moveValue = pidController.calculate(lastSpeed, Setpoint.getInstance().x) + + feedForwardController.calculate(lastSpeed, Setpoint.getInstance().x); + moveValue = (int) normalizeSpeed(moveValue); + if (Math.abs(moveValue - lastSpeed) > MAX_ACCELERATION) { + if (lastSpeed > moveValue) moveValue = (int) (lastSpeed - MAX_ACCELERATION); + if (lastSpeed < moveValue) moveValue = (int) (lastSpeed + MAX_ACCELERATION); + } + lastSpeed = moveValue; + System.out.println(lastSpeed); } - this.translate(moveValue, 0); - lastSpeed = moveValue; } if (!pidController.isOnTarget()) { lastTimeNotOnTarget = System.currentTimeMillis(); @@ -85,7 +96,13 @@ private double normalizeNoFriction(double speed) { public static void reset() { Character character = getInstance(); character.lastSpeed = 0; - character.setLocation(SOURCE_X, SOURCE_Y); + if (ControlType.getInstance().getSelectedIndex() == 0) { + character.setLocation(POSITION_SOURCE_X, POSITION_SOURCE_Y); + } else { + if (ControlType.getInstance().getSelectedIndex() == 1) { + character.setLocation(VELOCITY_SOURCE_X, VELOCITY_SOURCE_Y); + } + } character.commandFinished = false; character.lastTimeNotOnTarget = System.currentTimeMillis(); character.pidController.reset(); @@ -113,4 +130,8 @@ public double getError() { public void setFF(double kS, double kV, double kA) { feedForwardController.setGains(kS, kV, kA); } + + public double getLastSpeed() { + return lastSpeed; + } } diff --git a/src/simulator/Setpoint.java b/src/simulator/Setpoint.java index a058335..ae2acf4 100644 --- a/src/simulator/Setpoint.java +++ b/src/simulator/Setpoint.java @@ -1,5 +1,7 @@ package simulator; +import simulator.control.ControlType; + import java.awt.*; public class Setpoint extends Rectangle { @@ -23,7 +25,12 @@ private Setpoint(int width, int height, int sourceX, int sourceY) { super(sourceX, sourceY, width, height); } - public void setPosition(int position) { + public void setSetpoint(int position) { this.x = position; + if (ControlType.getInstance().getSelectedIndex() != 0) { + this.setSize(-1, -1); + } else { + this.setSize(WIDTH, HEIGHT); + } } } diff --git a/src/simulator/Window.java b/src/simulator/Window.java index a0a2f9a..78ce489 100644 --- a/src/simulator/Window.java +++ b/src/simulator/Window.java @@ -1,5 +1,6 @@ package simulator; +import simulator.control.ControlType; import simulator.information.Status; import simulator.textfields.BaseTextField; @@ -27,6 +28,7 @@ public class Window extends JPanel { private final BaseTextField kVField; private final BaseTextField kAField; private final BaseTextField setpointField; + private final ControlType controlType; private final Status status; private Window() { @@ -49,8 +51,10 @@ private Window() { kVField = new BaseTextField("kV", 600, 100); kAField = new BaseTextField("kA", 900, 100); setpointField = new BaseTextField("setpoint", 30, 722); + controlType = ControlType.getInstance(); configureTextFields(); this.add(rerunButton); + this.add(controlType); } @Override @@ -59,6 +63,9 @@ public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g; g2.setColor(Color.WHITE); g2.fill(character); + if (ControlType.getInstance().getSelectedIndex() == 1) { + g2.drawString("Velocity: " + character.getLastSpeed(), character.x, character.y); + } g2.setColor(Color.RED); g2.fill(setpoint); } @@ -74,7 +81,7 @@ private void delay(double seconds) { private void update() { delay(PERIODIC_FRAME); - setpoint.setPosition((int) setpointField.getValue()); + setpoint.setSetpoint((int) setpointField.getValue()); character.setPID(kPField.getValue(), kIField.getValue(), kDField.getValue(), toleranceField.getValue(), waitTimeField.getValue()); character.setIZone((int) iZoneField.getValue()); diff --git a/src/simulator/control/ControlType.java b/src/simulator/control/ControlType.java new file mode 100644 index 0000000..1373a76 --- /dev/null +++ b/src/simulator/control/ControlType.java @@ -0,0 +1,37 @@ +package simulator.control; + +import simulator.Character; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class ControlType extends JComboBox { + + private static final int WIDTH = 140; + private static final int HEIGHT = 65; + + private static final int X = 1100; + private static final int Y = 65; + private static final int FONT_SIZE = 20; + + private static final String[] OPTIONS = new String[]{"Position", "Velocity"}; + + private static ControlType instance; + + public static ControlType getInstance() { + if (instance == null) { + instance = new ControlType(); + } + return instance; + } + + private ControlType() { + super(OPTIONS); + this.setBounds(X, Y, WIDTH, HEIGHT); + this.setBackground(Color.GRAY); + this.setFont(new Font(Font.MONOSPACED, Font.PLAIN, FONT_SIZE)); + addActionListener(e -> Character.reset()); + } +} diff --git a/src/simulator/control/FeedForwardController.java b/src/simulator/control/FeedForwardController.java index 920ea35..dc72769 100644 --- a/src/simulator/control/FeedForwardController.java +++ b/src/simulator/control/FeedForwardController.java @@ -63,6 +63,11 @@ public int calculate(double source, double setpoint) { double error = setpoint - source; double targetDerivative = (setpoint - previousTarget) / Window.PERIODIC_FRAME; previousTarget = setpoint; - return (int) (kS * Math.signum(error) + kV * setpoint + kA * targetDerivative); + double staticVal; + if (ControlType.getInstance().getSelectedIndex() == 1) { + return (int) (kS + kV * setpoint + kA * targetDerivative); + } else { + return (int) (kS * Math.signum(error) + kV * setpoint + kA * targetDerivative); + } } } diff --git a/src/simulator/information/Status.java b/src/simulator/information/Status.java index 6f1264e..7a104dc 100644 --- a/src/simulator/information/Status.java +++ b/src/simulator/information/Status.java @@ -2,6 +2,7 @@ import simulator.Character; import simulator.Setpoint; +import simulator.control.ControlType; import javax.swing.*; import javax.swing.border.LineBorder; @@ -40,7 +41,14 @@ private Status() { } public void update() { - characterError.setText("Error: " + (Setpoint.getInstance().x - Character.getInstance().x)); - errorRate.setText("Error rate: " + (Character.getInstance().getPIDController().getErrorRate())); + if (ControlType.getInstance().getSelectedIndex() == 0) { + characterError.setText("Error: " + (Setpoint.getInstance().x - Character.getInstance().x)); + errorRate.setText("Error rate: " + (Character.getInstance().getPIDController().getErrorRate())); + } else { + if (ControlType.getInstance().getSelectedIndex() == 1) { + characterError.setText("Error: " + (Setpoint.getInstance().x - Character.getInstance().getLastSpeed())); + errorRate.setText("Error rate: " + (Character.getInstance().getPIDController().getErrorRate())); + } + } } } From 9a9452edff8416da107c38523efd6b2e35abbde1 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Sat, 15 Jun 2024 22:04:17 +0300 Subject: [PATCH 14/34] removed debug sout --- src/simulator/Character.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/simulator/Character.java b/src/simulator/Character.java index d090935..52476f0 100644 --- a/src/simulator/Character.java +++ b/src/simulator/Character.java @@ -71,7 +71,6 @@ public void update() { if (lastSpeed < moveValue) moveValue = (int) (lastSpeed + MAX_ACCELERATION); } lastSpeed = moveValue; - System.out.println(lastSpeed); } } if (!pidController.isOnTarget()) { From fdee782695b5037eccb48de1efa1b290c856b781 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Sat, 15 Jun 2024 22:06:17 +0300 Subject: [PATCH 15/34] organized update() in Character to different functions --- src/simulator/Character.java | 46 +++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/src/simulator/Character.java b/src/simulator/Character.java index 52476f0..3c71cc7 100644 --- a/src/simulator/Character.java +++ b/src/simulator/Character.java @@ -51,26 +51,9 @@ private Character(int width, int height, int sourceX, int sourceY) { public void update() { if (!commandFinished) { if (ControlType.getInstance().getSelectedIndex() == 0) { - commandFinished = (System.currentTimeMillis() - lastTimeNotOnTarget >= - pidSettings.getWaitTime() * MILLISECONDS_IN_SECOND && pidController.isOnTarget()); - int moveValue = pidController.calculate(this.x, Setpoint.getInstance().x) + - feedForwardController.calculate(this.x, Setpoint.getInstance().x); - moveValue = (int) normalizeSpeed(moveValue); - if (Math.abs(moveValue - lastSpeed) > MAX_ACCELERATION) { - if (lastSpeed > moveValue) moveValue = (int) (lastSpeed - MAX_ACCELERATION); - if (lastSpeed < moveValue) moveValue = (int) (lastSpeed + MAX_ACCELERATION); - } - this.translate(moveValue, 0); - lastSpeed = moveValue; + runPosition(); } else if (ControlType.getInstance().getSelectedIndex() == 1) { - int moveValue = pidController.calculate(lastSpeed, Setpoint.getInstance().x) + - feedForwardController.calculate(lastSpeed, Setpoint.getInstance().x); - moveValue = (int) normalizeSpeed(moveValue); - if (Math.abs(moveValue - lastSpeed) > MAX_ACCELERATION) { - if (lastSpeed > moveValue) moveValue = (int) (lastSpeed - MAX_ACCELERATION); - if (lastSpeed < moveValue) moveValue = (int) (lastSpeed + MAX_ACCELERATION); - } - lastSpeed = moveValue; + runVelocity(); } } if (!pidController.isOnTarget()) { @@ -78,6 +61,31 @@ public void update() { } } + private void runPosition() { + commandFinished = (System.currentTimeMillis() - lastTimeNotOnTarget >= + pidSettings.getWaitTime() * MILLISECONDS_IN_SECOND && pidController.isOnTarget()); + int moveValue = pidController.calculate(this.x, Setpoint.getInstance().x) + + feedForwardController.calculate(this.x, Setpoint.getInstance().x); + moveValue = (int) normalizeSpeed(moveValue); + if (Math.abs(moveValue - lastSpeed) > MAX_ACCELERATION) { + if (lastSpeed > moveValue) moveValue = (int) (lastSpeed - MAX_ACCELERATION); + if (lastSpeed < moveValue) moveValue = (int) (lastSpeed + MAX_ACCELERATION); + } + this.translate(moveValue, 0); + lastSpeed = moveValue; + } + + private void runVelocity() { + int moveValue = pidController.calculate(lastSpeed, Setpoint.getInstance().x) + + feedForwardController.calculate(lastSpeed, Setpoint.getInstance().x); + moveValue = (int) normalizeSpeed(moveValue); + if (Math.abs(moveValue - lastSpeed) > MAX_ACCELERATION) { + if (lastSpeed > moveValue) moveValue = (int) (lastSpeed - MAX_ACCELERATION); + if (lastSpeed < moveValue) moveValue = (int) (lastSpeed + MAX_ACCELERATION); + } + lastSpeed = moveValue; + } + private double normalizeSpeed(double speed) { if (speed != 0) { if (speed > 0) speed -= FRICTION; From e0d683b52339409cb18691cac9cf9bb789583f80 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Sat, 15 Jun 2024 22:09:02 +0300 Subject: [PATCH 16/34] fixed ordering --- src/simulator/Character.java | 84 ++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/src/simulator/Character.java b/src/simulator/Character.java index 3c71cc7..baef0d6 100644 --- a/src/simulator/Character.java +++ b/src/simulator/Character.java @@ -48,6 +48,22 @@ private Character(int width, int height, int sourceX, int sourceY) { lastTimeNotOnTarget = System.currentTimeMillis(); } + public static void reset() { + Character character = getInstance(); + character.lastSpeed = 0; + if (ControlType.getInstance().getSelectedIndex() == 0) { + character.setLocation(POSITION_SOURCE_X, POSITION_SOURCE_Y); + } else { + if (ControlType.getInstance().getSelectedIndex() == 1) { + character.setLocation(VELOCITY_SOURCE_X, VELOCITY_SOURCE_Y); + } + } + character.commandFinished = false; + character.lastTimeNotOnTarget = System.currentTimeMillis(); + character.pidController.reset(); + character.feedForwardController.reset(); + } + public void update() { if (!commandFinished) { if (ControlType.getInstance().getSelectedIndex() == 0) { @@ -61,6 +77,32 @@ public void update() { } } + public void setPID(double kP, double kI, double kD, double tolerance, double waitTime) { + pidController.setPID(kP, kI, kD); + pidController.setTolerance(tolerance); + pidSettings.setWaitTime(waitTime); + } + + public void setIZone(int iZone) { + pidController.setIZone(iZone); + } + + public PIDController getPIDController() { + return pidController; + } + + public double getError() { + return Setpoint.getInstance().x - this.x; + } + + public void setFF(double kS, double kV, double kA) { + feedForwardController.setGains(kS, kV, kA); + } + + public double getLastSpeed() { + return lastSpeed; + } + private void runPosition() { commandFinished = (System.currentTimeMillis() - lastTimeNotOnTarget >= pidSettings.getWaitTime() * MILLISECONDS_IN_SECOND && pidController.isOnTarget()); @@ -99,46 +141,4 @@ private double normalizeNoFriction(double speed) { if (Math.abs(speed) > MAX_SPEED) speed = (int) (MAX_SPEED * Math.signum(speed)); return speed; } - - public static void reset() { - Character character = getInstance(); - character.lastSpeed = 0; - if (ControlType.getInstance().getSelectedIndex() == 0) { - character.setLocation(POSITION_SOURCE_X, POSITION_SOURCE_Y); - } else { - if (ControlType.getInstance().getSelectedIndex() == 1) { - character.setLocation(VELOCITY_SOURCE_X, VELOCITY_SOURCE_Y); - } - } - character.commandFinished = false; - character.lastTimeNotOnTarget = System.currentTimeMillis(); - character.pidController.reset(); - character.feedForwardController.reset(); - } - - public void setPID(double kP, double kI, double kD, double tolerance, double waitTime) { - pidController.setPID(kP, kI, kD); - pidController.setTolerance(tolerance); - pidSettings.setWaitTime(waitTime); - } - - public void setIZone(int iZone) { - pidController.setIZone(iZone); - } - - public PIDController getPIDController() { - return pidController; - } - - public double getError() { - return Setpoint.getInstance().x - this.x; - } - - public void setFF(double kS, double kV, double kA) { - feedForwardController.setGains(kS, kV, kA); - } - - public double getLastSpeed() { - return lastSpeed; - } } From 49839e9ac5de41ea868013d889e296448f00782f Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Sat, 15 Jun 2024 22:11:16 +0300 Subject: [PATCH 17/34] fixed ordering --- src/simulator/Window.java | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/simulator/Window.java b/src/simulator/Window.java index 78ce489..1c0a97d 100644 --- a/src/simulator/Window.java +++ b/src/simulator/Window.java @@ -57,6 +57,18 @@ private Window() { this.add(controlType); } + public static void initGame() { + JFrame frame = new JFrame(); + Window window = new Window(); + frame.add(window); + frame.pack(); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.setVisible(true); + while (true) { + window.update(); + } + } + @Override public void paintComponent(Graphics g) { super.paintComponent(g); @@ -104,16 +116,4 @@ private void configureTextFields() { this.add(status); this.add(setpointField); } - - public static void initGame() { - JFrame frame = new JFrame(); - Window window = new Window(); - frame.add(window); - frame.pack(); - frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - frame.setVisible(true); - while (true) { - window.update(); - } - } } From 00bdff9992c69a4b9b3066f28fc3703c015ab621 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Sun, 16 Jun 2024 16:39:04 +0300 Subject: [PATCH 18/34] function --- src/simulator/Window.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/simulator/Window.java b/src/simulator/Window.java index 1c0a97d..e7c9b1d 100644 --- a/src/simulator/Window.java +++ b/src/simulator/Window.java @@ -93,14 +93,18 @@ private void delay(double seconds) { private void update() { delay(PERIODIC_FRAME); + configurePIDF(); + character.update(); + status.update(); + repaint(); + } + + private void configurePIDF() { setpoint.setSetpoint((int) setpointField.getValue()); character.setPID(kPField.getValue(), kIField.getValue(), kDField.getValue(), toleranceField.getValue(), waitTimeField.getValue()); character.setIZone((int) iZoneField.getValue()); character.setFF(kSField.getValue(), kVField.getValue(), kAField.getValue()); - character.update(); - status.update(); - repaint(); } private void configureTextFields() { From 97befb41dd1efa59aae7f4bdc84a65e2acf97bf1 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Sun, 16 Jun 2024 16:57:44 +0300 Subject: [PATCH 19/34] removed magic and unnecessary function --- src/simulator/Character.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/simulator/Character.java b/src/simulator/Character.java index baef0d6..6a1bfea 100644 --- a/src/simulator/Character.java +++ b/src/simulator/Character.java @@ -41,7 +41,7 @@ public static Character getInstance() { private Character(int width, int height, int sourceX, int sourceY) { super(sourceX, sourceY, width, height); lastSpeed = 0; - pidSettings = new PIDSettings(0, 0, 0, 10, 1); + pidSettings = new PIDSettings(0, 0, 0, 0, 0); feedForwardSettings = new FeedForwardSettings(0, 0, 0); pidController = new PIDController(pidSettings); feedForwardController = new FeedForwardController(feedForwardSettings); @@ -136,9 +136,4 @@ private double normalizeSpeed(double speed) { } else if (Math.abs(speed) < FRICTION) speed = 0; return speed; } - - private double normalizeNoFriction(double speed) { - if (Math.abs(speed) > MAX_SPEED) speed = (int) (MAX_SPEED * Math.signum(speed)); - return speed; - } } From 386b882835100b23494f51ac4252865cf93cf055 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Sun, 16 Jun 2024 17:04:46 +0300 Subject: [PATCH 20/34] removed a shitton of magic --- src/simulator/Window.java | 34 ++++++++++++++------- src/simulator/textfields/BaseTextField.java | 4 +++ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/simulator/Window.java b/src/simulator/Window.java index e7c9b1d..4f0f7a1 100644 --- a/src/simulator/Window.java +++ b/src/simulator/Window.java @@ -9,12 +9,24 @@ public class Window extends JPanel { + private static final int MILLISECONDS_IN_SECOND = 1000; public static final double PERIODIC_FRAME = 0.02; private static final int WINDOW_WIDTH = 1280; private static final int WINDOW_HEIGHT = 792; private static final boolean IS_DOUBLE_BUFFERED = true; + private static final Point P_FIELD_LOCATION = new Point(300, 30); + private static final Point I_FIELD_LOCATION = new Point(600, 30); + private static final Point D_FIELD_LOCATION = new Point(900, 30); + private static final Point I_ZONE_FIELD_LOCATION = new Point(30, 30); + private static final Point TOLERANCE_FIELD_LOCATION = new Point(450, 65); + private static final Point WAIT_TIME_FIELD_LOCATION = new Point(750, 65); + private static final Point S_FIELD_LOCATION = new Point(300, 100); + private static final Point V_FIELD_LOCATION = new Point(600, 100); + private static final Point A_FIELD_LOCATION = new Point(900, 100); + private static final Point SETPOINT_FIELD_LOCATION = new Point(30, 722); + private final Character character; private final Setpoint setpoint; private final RerunButton rerunButton; @@ -41,16 +53,16 @@ private Window() { setpoint = Setpoint.getInstance(); rerunButton = RerunButton.getInstance(); status = Status.getInstance(); - kPField = new BaseTextField("kP", 300, 30); - kIField = new BaseTextField("kI", 600, 30); - kDField = new BaseTextField("kD", 900, 30); - iZoneField = new BaseTextField("i zone", 30, 30); - toleranceField = new BaseTextField("tolerance", 450, 65); - waitTimeField = new BaseTextField("wait time", 750, 65); - kSField = new BaseTextField("kS", 300, 100); - kVField = new BaseTextField("kV", 600, 100); - kAField = new BaseTextField("kA", 900, 100); - setpointField = new BaseTextField("setpoint", 30, 722); + kPField = new BaseTextField("kP", P_FIELD_LOCATION); + kIField = new BaseTextField("kI", I_FIELD_LOCATION); + kDField = new BaseTextField("kD", D_FIELD_LOCATION); + iZoneField = new BaseTextField("i zone", I_ZONE_FIELD_LOCATION); + toleranceField = new BaseTextField("tolerance", TOLERANCE_FIELD_LOCATION); + waitTimeField = new BaseTextField("wait time", WAIT_TIME_FIELD_LOCATION); + kSField = new BaseTextField("kS", S_FIELD_LOCATION); + kVField = new BaseTextField("kV", V_FIELD_LOCATION); + kAField = new BaseTextField("kA", A_FIELD_LOCATION); + setpointField = new BaseTextField("setpoint", SETPOINT_FIELD_LOCATION); controlType = ControlType.getInstance(); configureTextFields(); this.add(rerunButton); @@ -84,7 +96,7 @@ public void paintComponent(Graphics g) { private void delay(double seconds) { long currentTime = System.currentTimeMillis(); - long targetTime = (long) (System.currentTimeMillis() + seconds * 1000); + long targetTime = (long) (System.currentTimeMillis() + seconds * MILLISECONDS_IN_SECOND); while (targetTime > currentTime) { currentTime = System.currentTimeMillis(); repaint(character); diff --git a/src/simulator/textfields/BaseTextField.java b/src/simulator/textfields/BaseTextField.java index 5fbc2d2..99d690f 100644 --- a/src/simulator/textfields/BaseTextField.java +++ b/src/simulator/textfields/BaseTextField.java @@ -17,6 +17,10 @@ public BaseTextField(String initialText, int x, int y) { this.setFont(new Font(Font.MONOSPACED, Font.PLAIN, FONT_SIZE)); } + public BaseTextField(String initialText, Point position) { + this(initialText, position.x, position.y); + } + public double getValue() { try { return Double.parseDouble(this.getText()); From 0c4c3c33f3345c067e200cc62b9f73fa3ef39a2a Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Sun, 16 Jun 2024 17:19:24 +0300 Subject: [PATCH 21/34] made an enum which represents the control types --- src/simulator/Character.java | 8 ++++---- src/simulator/Main.java | 2 -- src/simulator/Setpoint.java | 2 +- src/simulator/Window.java | 2 +- src/simulator/control/ControlType.java | 13 +++++++++++-- src/simulator/control/FeedForwardController.java | 2 +- src/simulator/information/BaseInfoField.java | 2 -- src/simulator/information/Status.java | 5 ++--- src/simulator/textfields/BaseTextField.java | 2 +- 9 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/simulator/Character.java b/src/simulator/Character.java index 6a1bfea..269feb1 100644 --- a/src/simulator/Character.java +++ b/src/simulator/Character.java @@ -51,10 +51,10 @@ private Character(int width, int height, int sourceX, int sourceY) { public static void reset() { Character character = getInstance(); character.lastSpeed = 0; - if (ControlType.getInstance().getSelectedIndex() == 0) { + if (ControlType.getInstance().getSelectedIndex() == ControlType.Types.POSITION.index) { character.setLocation(POSITION_SOURCE_X, POSITION_SOURCE_Y); } else { - if (ControlType.getInstance().getSelectedIndex() == 1) { + if (ControlType.getInstance().getSelectedIndex() == ControlType.Types.VELOCITY.index) { character.setLocation(VELOCITY_SOURCE_X, VELOCITY_SOURCE_Y); } } @@ -66,9 +66,9 @@ public static void reset() { public void update() { if (!commandFinished) { - if (ControlType.getInstance().getSelectedIndex() == 0) { + if (ControlType.getInstance().getSelectedIndex() == ControlType.Types.POSITION.index) { runPosition(); - } else if (ControlType.getInstance().getSelectedIndex() == 1) { + } else if (ControlType.getInstance().getSelectedIndex() == ControlType.Types.VELOCITY.index) { runVelocity(); } } diff --git a/src/simulator/Main.java b/src/simulator/Main.java index 1c85d3e..6c99465 100644 --- a/src/simulator/Main.java +++ b/src/simulator/Main.java @@ -1,7 +1,5 @@ package simulator; -import simulator.Window; - public class Main { public static void main(String[] args) { diff --git a/src/simulator/Setpoint.java b/src/simulator/Setpoint.java index ae2acf4..610a2e3 100644 --- a/src/simulator/Setpoint.java +++ b/src/simulator/Setpoint.java @@ -27,7 +27,7 @@ private Setpoint(int width, int height, int sourceX, int sourceY) { public void setSetpoint(int position) { this.x = position; - if (ControlType.getInstance().getSelectedIndex() != 0) { + if (ControlType.getInstance().getSelectedIndex() != ControlType.Types.POSITION.index) { this.setSize(-1, -1); } else { this.setSize(WIDTH, HEIGHT); diff --git a/src/simulator/Window.java b/src/simulator/Window.java index 4f0f7a1..9141871 100644 --- a/src/simulator/Window.java +++ b/src/simulator/Window.java @@ -87,7 +87,7 @@ public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g; g2.setColor(Color.WHITE); g2.fill(character); - if (ControlType.getInstance().getSelectedIndex() == 1) { + if (ControlType.getInstance().getSelectedIndex() == ControlType.Types.VELOCITY.index) { g2.drawString("Velocity: " + character.getLastSpeed(), character.x, character.y); } g2.setColor(Color.RED); diff --git a/src/simulator/control/ControlType.java b/src/simulator/control/ControlType.java index 1373a76..4e724ad 100644 --- a/src/simulator/control/ControlType.java +++ b/src/simulator/control/ControlType.java @@ -4,11 +4,20 @@ import javax.swing.*; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; public class ControlType extends JComboBox { + public enum Types { + + POSITION(0), VELOCITY(1); + + public int index; + + Types(int index) { + this.index = index; + } + } + private static final int WIDTH = 140; private static final int HEIGHT = 65; diff --git a/src/simulator/control/FeedForwardController.java b/src/simulator/control/FeedForwardController.java index dc72769..2a602f3 100644 --- a/src/simulator/control/FeedForwardController.java +++ b/src/simulator/control/FeedForwardController.java @@ -64,7 +64,7 @@ public int calculate(double source, double setpoint) { double targetDerivative = (setpoint - previousTarget) / Window.PERIODIC_FRAME; previousTarget = setpoint; double staticVal; - if (ControlType.getInstance().getSelectedIndex() == 1) { + if (ControlType.getInstance().getSelectedIndex() == ControlType.Types.VELOCITY.index) { return (int) (kS + kV * setpoint + kA * targetDerivative); } else { return (int) (kS * Math.signum(error) + kV * setpoint + kA * targetDerivative); diff --git a/src/simulator/information/BaseInfoField.java b/src/simulator/information/BaseInfoField.java index 5ac7df9..cd9b7a9 100644 --- a/src/simulator/information/BaseInfoField.java +++ b/src/simulator/information/BaseInfoField.java @@ -1,7 +1,5 @@ package simulator.information; -import simulator.Character; - import javax.swing.*; import javax.swing.border.LineBorder; import java.awt.*; diff --git a/src/simulator/information/Status.java b/src/simulator/information/Status.java index 7a104dc..4c95fd5 100644 --- a/src/simulator/information/Status.java +++ b/src/simulator/information/Status.java @@ -5,7 +5,6 @@ import simulator.control.ControlType; import javax.swing.*; -import javax.swing.border.LineBorder; import java.awt.*; public class Status extends JPanel { @@ -41,11 +40,11 @@ private Status() { } public void update() { - if (ControlType.getInstance().getSelectedIndex() == 0) { + if (ControlType.getInstance().getSelectedIndex() == ControlType.Types.POSITION.index) { characterError.setText("Error: " + (Setpoint.getInstance().x - Character.getInstance().x)); errorRate.setText("Error rate: " + (Character.getInstance().getPIDController().getErrorRate())); } else { - if (ControlType.getInstance().getSelectedIndex() == 1) { + if (ControlType.getInstance().getSelectedIndex() == ControlType.Types.VELOCITY.index) { characterError.setText("Error: " + (Setpoint.getInstance().x - Character.getInstance().getLastSpeed())); errorRate.setText("Error rate: " + (Character.getInstance().getPIDController().getErrorRate())); } diff --git a/src/simulator/textfields/BaseTextField.java b/src/simulator/textfields/BaseTextField.java index 99d690f..85e150a 100644 --- a/src/simulator/textfields/BaseTextField.java +++ b/src/simulator/textfields/BaseTextField.java @@ -24,7 +24,7 @@ public BaseTextField(String initialText, Point position) { public double getValue() { try { return Double.parseDouble(this.getText()); - } catch(NumberFormatException e) { + } catch (NumberFormatException e) { return 0; } } From e53dbb6fbf4780a9380f199e509da69ae2178858 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Sun, 16 Jun 2024 17:22:18 +0300 Subject: [PATCH 22/34] removed wait time in PIDController --- src/simulator/control/PIDController.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/simulator/control/PIDController.java b/src/simulator/control/PIDController.java index 903dac0..1df0277 100644 --- a/src/simulator/control/PIDController.java +++ b/src/simulator/control/PIDController.java @@ -9,7 +9,6 @@ public class PIDController { private double kD; private double iZone; private double tolerance; - private double waitTime; private double errorSum; private double lastTimestamp; @@ -26,7 +25,6 @@ public PIDController(PIDSettings pidSettings) { this.kD = pidSettings.getD(); this.iZone = DEFAULT_I_ZONE; this.tolerance = pidSettings.getTolerance(); - this.waitTime = pidSettings.getWaitTime(); errorSum = 0; lastTimestamp = System.currentTimeMillis(); lastError = 0; @@ -59,10 +57,6 @@ public void setTolerance(double tolerance) { this.tolerance = tolerance; } - public void setWaitTime(double waitTime) { - this.waitTime = waitTime; - } - public boolean isOnTarget() { return onTarget; } From a38658b8aefcd5cfb28f1242dad9042c0a68715a Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Sun, 16 Jun 2024 17:29:14 +0300 Subject: [PATCH 23/34] added a status which shows if the command ended --- src/simulator/Character.java | 4 ++++ src/simulator/information/Status.java | 10 ++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/simulator/Character.java b/src/simulator/Character.java index 269feb1..5917d35 100644 --- a/src/simulator/Character.java +++ b/src/simulator/Character.java @@ -103,6 +103,10 @@ public double getLastSpeed() { return lastSpeed; } + public boolean commandEnded() { + return commandFinished; + } + private void runPosition() { commandFinished = (System.currentTimeMillis() - lastTimeNotOnTarget >= pidSettings.getWaitTime() * MILLISECONDS_IN_SECOND && pidController.isOnTarget()); diff --git a/src/simulator/information/Status.java b/src/simulator/information/Status.java index 4c95fd5..808a194 100644 --- a/src/simulator/information/Status.java +++ b/src/simulator/information/Status.java @@ -16,6 +16,7 @@ public class Status extends JPanel { private static final int FONT_SIZE = 20; private BaseInfoField characterError; private BaseInfoField errorRate; + private BaseInfoField commandEnded; private static Status instance; @@ -31,8 +32,11 @@ private Status() { "Error: " + Character.getInstance().getError()); errorRate = new BaseInfoField(0, 50, 400, 50, "Error rate: " + Character.getInstance().getPIDController().getErrorRate()); + commandEnded = new BaseInfoField(0, 100, 400, 50, "Command ended: " + + Character.getInstance().commandEnded()); this.add(characterError); this.add(errorRate); + this.add(commandEnded); this.setLayout(null); this.setVisible(true); this.setBounds(X, Y, WIDTH, HEIGHT); @@ -42,11 +46,13 @@ private Status() { public void update() { if (ControlType.getInstance().getSelectedIndex() == ControlType.Types.POSITION.index) { characterError.setText("Error: " + (Setpoint.getInstance().x - Character.getInstance().x)); - errorRate.setText("Error rate: " + (Character.getInstance().getPIDController().getErrorRate())); + errorRate.setText("Error rate: " + Character.getInstance().getPIDController().getErrorRate()); + commandEnded.setText("Command ended: " + Character.getInstance().commandEnded()); } else { if (ControlType.getInstance().getSelectedIndex() == ControlType.Types.VELOCITY.index) { characterError.setText("Error: " + (Setpoint.getInstance().x - Character.getInstance().getLastSpeed())); - errorRate.setText("Error rate: " + (Character.getInstance().getPIDController().getErrorRate())); + errorRate.setText("Error rate: " + Character.getInstance().getPIDController().getErrorRate()); + commandEnded.setText("Command ended: " + Character.getInstance().commandEnded()); } } } From 4f58e4270e9a287db53cfd8e7427e3b6f839c7b5 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Sun, 16 Jun 2024 17:34:12 +0300 Subject: [PATCH 24/34] removed more magic --- src/simulator/information/BaseInfoField.java | 4 ++++ src/simulator/information/Status.java | 14 +++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/simulator/information/BaseInfoField.java b/src/simulator/information/BaseInfoField.java index cd9b7a9..c158b53 100644 --- a/src/simulator/information/BaseInfoField.java +++ b/src/simulator/information/BaseInfoField.java @@ -17,4 +17,8 @@ public BaseInfoField(int x, int y, int width, int height, String text) { this.setBackground(Color.GRAY); this.setBorder(new LineBorder(Color.GRAY)); } + + public BaseInfoField(Point position, int width, int height, String text) { + this(position.x, position.y, width, height, text); + } } diff --git a/src/simulator/information/Status.java b/src/simulator/information/Status.java index 808a194..c7c2b2f 100644 --- a/src/simulator/information/Status.java +++ b/src/simulator/information/Status.java @@ -14,6 +14,14 @@ public class Status extends JPanel { private static final int X = 800; private static final int Y = 480; private static final int FONT_SIZE = 20; + + private static final int INFO_TEXT_WIDTH = 400; + private static final int INFO_TEXT_HEIGHT = 50; + + private static final Point ERROR_LOCATION = new Point(0, 0); + private static final Point ERROR_RATE_LOCATION = new Point(0, 50); + private static final Point COMMAND_ENDED_LOCATION = new Point(0, 100); + private BaseInfoField characterError; private BaseInfoField errorRate; private BaseInfoField commandEnded; @@ -28,11 +36,11 @@ public static Status getInstance() { } private Status() { - characterError = new BaseInfoField(0, 0, 400, 50, + characterError = new BaseInfoField(ERROR_LOCATION, INFO_TEXT_WIDTH, INFO_TEXT_HEIGHT, "Error: " + Character.getInstance().getError()); - errorRate = new BaseInfoField(0, 50, 400, 50, + errorRate = new BaseInfoField(ERROR_RATE_LOCATION, INFO_TEXT_WIDTH, INFO_TEXT_HEIGHT, "Error rate: " + Character.getInstance().getPIDController().getErrorRate()); - commandEnded = new BaseInfoField(0, 100, 400, 50, "Command ended: " + + commandEnded = new BaseInfoField(COMMAND_ENDED_LOCATION, INFO_TEXT_WIDTH, INFO_TEXT_HEIGHT, "Command ended: " + Character.getInstance().commandEnded()); this.add(characterError); this.add(errorRate); From 13d7cf96c39957e9b02d18cbe6d8b9ef6f02a721 Mon Sep 17 00:00:00 2001 From: TurtleMinecraft <110167837+TurtleMinecraft@users.noreply.github.com> Date: Sun, 16 Jun 2024 17:38:02 +0300 Subject: [PATCH 25/34] Delete .idea/uiDesigner.xml --- .idea/uiDesigner.xml | 124 ------------------------------------------- 1 file changed, 124 deletions(-) delete mode 100644 .idea/uiDesigner.xml diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml deleted file mode 100644 index 2b63946..0000000 --- a/.idea/uiDesigner.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 0fb97d56f8d47a3e3a6f3051b48b4dd5c54474f7 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Sun, 16 Jun 2024 18:07:59 +0300 Subject: [PATCH 26/34] changed param ordering --- src/simulator/Window.java | 20 ++++++++++---------- src/simulator/textfields/BaseTextField.java | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/simulator/Window.java b/src/simulator/Window.java index 9141871..bbbce92 100644 --- a/src/simulator/Window.java +++ b/src/simulator/Window.java @@ -53,16 +53,16 @@ private Window() { setpoint = Setpoint.getInstance(); rerunButton = RerunButton.getInstance(); status = Status.getInstance(); - kPField = new BaseTextField("kP", P_FIELD_LOCATION); - kIField = new BaseTextField("kI", I_FIELD_LOCATION); - kDField = new BaseTextField("kD", D_FIELD_LOCATION); - iZoneField = new BaseTextField("i zone", I_ZONE_FIELD_LOCATION); - toleranceField = new BaseTextField("tolerance", TOLERANCE_FIELD_LOCATION); - waitTimeField = new BaseTextField("wait time", WAIT_TIME_FIELD_LOCATION); - kSField = new BaseTextField("kS", S_FIELD_LOCATION); - kVField = new BaseTextField("kV", V_FIELD_LOCATION); - kAField = new BaseTextField("kA", A_FIELD_LOCATION); - setpointField = new BaseTextField("setpoint", SETPOINT_FIELD_LOCATION); + kPField = new BaseTextField(P_FIELD_LOCATION, "kP"); + kIField = new BaseTextField(I_FIELD_LOCATION, "kI"); + kDField = new BaseTextField(D_FIELD_LOCATION, "kD"); + iZoneField = new BaseTextField(I_ZONE_FIELD_LOCATION, "i zone"); + toleranceField = new BaseTextField(TOLERANCE_FIELD_LOCATION, "tolerance"); + waitTimeField = new BaseTextField(WAIT_TIME_FIELD_LOCATION, "wait time"); + kSField = new BaseTextField(S_FIELD_LOCATION, "kS"); + kVField = new BaseTextField(V_FIELD_LOCATION, "kV"); + kAField = new BaseTextField(A_FIELD_LOCATION, "kA"); + setpointField = new BaseTextField(SETPOINT_FIELD_LOCATION, "setpoint"); controlType = ControlType.getInstance(); configureTextFields(); this.add(rerunButton); diff --git a/src/simulator/textfields/BaseTextField.java b/src/simulator/textfields/BaseTextField.java index 85e150a..1cd576d 100644 --- a/src/simulator/textfields/BaseTextField.java +++ b/src/simulator/textfields/BaseTextField.java @@ -10,15 +10,15 @@ public class BaseTextField extends JTextField { private static final int FONT_SIZE = 40; - public BaseTextField(String initialText, int x, int y) { + public BaseTextField(int x, int y, String initialText) { super(initialText); this.setBounds(x, y, FIELD_WIDTH, FIELD_HEIGHT); this.setBackground(Color.GRAY); this.setFont(new Font(Font.MONOSPACED, Font.PLAIN, FONT_SIZE)); } - public BaseTextField(String initialText, Point position) { - this(initialText, position.x, position.y); + public BaseTextField(Point position, String initialText) { + this(position.x, position.y, initialText); } public double getValue() { From 4117c77501d77430cff0e99b3934eb78a8223561 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Mon, 17 Jun 2024 14:15:16 +0300 Subject: [PATCH 27/34] added an error graph --- src/simulator/Character.java | 10 ++- src/simulator/RerunButton.java | 3 + src/simulator/information/ErrorGraph.java | 80 +++++++++++++++++++++++ src/simulator/information/Status.java | 5 ++ 4 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 src/simulator/information/ErrorGraph.java diff --git a/src/simulator/Character.java b/src/simulator/Character.java index 5917d35..5e34411 100644 --- a/src/simulator/Character.java +++ b/src/simulator/Character.java @@ -92,7 +92,15 @@ public PIDController getPIDController() { } public double getError() { - return Setpoint.getInstance().x - this.x; + if (ControlType.getInstance().getSelectedIndex() == ControlType.Types.POSITION.index) { + return Setpoint.getInstance().x - this.x; + } else { + if (ControlType.getInstance().getSelectedIndex() == ControlType.Types.VELOCITY.index) { + return Setpoint.getInstance().x - lastSpeed; + } else { + return 0; + } + } } public void setFF(double kS, double kV, double kA) { diff --git a/src/simulator/RerunButton.java b/src/simulator/RerunButton.java index 96cc9b5..b3446e4 100644 --- a/src/simulator/RerunButton.java +++ b/src/simulator/RerunButton.java @@ -1,5 +1,7 @@ package simulator; +import simulator.information.ErrorGraph; + import javax.swing.*; import java.awt.*; @@ -28,6 +30,7 @@ private RerunButton() { this.setBounds(X, Y, WIDTH, HEIGHT); this.addActionListener(e -> { Character.reset(); + ErrorGraph.reset(); }); } } diff --git a/src/simulator/information/ErrorGraph.java b/src/simulator/information/ErrorGraph.java new file mode 100644 index 0000000..c5ebb82 --- /dev/null +++ b/src/simulator/information/ErrorGraph.java @@ -0,0 +1,80 @@ +package simulator.information; + +import simulator.Setpoint; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; + +public class ErrorGraph extends JPanel { + + private static final int WIDTH = 640; + private static final int HEIGHT = 390; + private static final int X = 0; + private static final int Y = 150; + + private static final int POINT_WIDTH = 2; + private static final int POINT_HEIGHT = 2; + private static final int DISTANCE_BETWEEN_POINTS = 2; + + private final ArrayList errors; + private int currentErrorLocation; + private double proportion; + + private static ErrorGraph instance; + + public static ErrorGraph getInstance() { + if (instance == null) { + instance = new ErrorGraph(); + } + return instance; + } + + private ErrorGraph() { + this.setLayout(null); + this.setVisible(true); + this.setBounds(X, Y, WIDTH, HEIGHT); + this.setBackground(Color.WHITE); + errors = new ArrayList<>(); + currentErrorLocation = 0; + proportion = 0; + } + + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g; + g2.setColor(Color.RED); + ArrayList rectangles = createRectangles(); + rectangles.forEach(g2::fill); + g2.setColor(Color.BLACK); + g2.fill(new Rectangle(0, HEIGHT / 2, WIDTH, POINT_HEIGHT)); + } + + public static void reset() { + ErrorGraph errorGraph = getInstance(); + errorGraph.errors.clear(); + errorGraph.currentErrorLocation = 0; + errorGraph.setProportions(Setpoint.getInstance().x); + } + + public void addPoint(int error) { + errors.add(error); + } + + public void setProportions(int setpoint) { + if (setpoint == 0) proportion = 0; + else proportion = (double) HEIGHT / setpoint; + } + + private ArrayList createRectangles() { + ArrayList rectangles = new ArrayList<>(); + currentErrorLocation = 0; + errors.forEach(error -> { + rectangles.add(new Rectangle(currentErrorLocation, (int) (error * proportion) / 2 + HEIGHT / 2, + POINT_WIDTH, POINT_HEIGHT)); + currentErrorLocation += DISTANCE_BETWEEN_POINTS; + }); + return rectangles; + } +} diff --git a/src/simulator/information/Status.java b/src/simulator/information/Status.java index c7c2b2f..ee209db 100644 --- a/src/simulator/information/Status.java +++ b/src/simulator/information/Status.java @@ -6,6 +6,7 @@ import javax.swing.*; import java.awt.*; +import java.util.Set; public class Status extends JPanel { @@ -25,6 +26,7 @@ public class Status extends JPanel { private BaseInfoField characterError; private BaseInfoField errorRate; private BaseInfoField commandEnded; + private ErrorGraph errorGraph; private static Status instance; @@ -42,9 +44,11 @@ private Status() { "Error rate: " + Character.getInstance().getPIDController().getErrorRate()); commandEnded = new BaseInfoField(COMMAND_ENDED_LOCATION, INFO_TEXT_WIDTH, INFO_TEXT_HEIGHT, "Command ended: " + Character.getInstance().commandEnded()); + errorGraph = ErrorGraph.getInstance(); this.add(characterError); this.add(errorRate); this.add(commandEnded); + this.add(errorGraph); this.setLayout(null); this.setVisible(true); this.setBounds(X, Y, WIDTH, HEIGHT); @@ -63,5 +67,6 @@ public void update() { commandEnded.setText("Command ended: " + Character.getInstance().commandEnded()); } } + errorGraph.addPoint((int) Character.getInstance().getError()); } } From 705169e13e3a6a919c30d2b9c590c56a2afbb4d9 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Mon, 17 Jun 2024 15:53:40 +0300 Subject: [PATCH 28/34] made GUI fit into most computer screens --- src/simulator/Character.java | 5 ++-- src/simulator/RerunButton.java | 9 +++--- src/simulator/Window.java | 33 +++++++++++++-------- src/simulator/control/ControlType.java | 6 ++-- src/simulator/information/ErrorGraph.java | 4 +-- src/simulator/information/Status.java | 10 +++---- src/simulator/textfields/BaseTextField.java | 4 +-- 7 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/simulator/Character.java b/src/simulator/Character.java index 5e34411..a8f1c21 100644 --- a/src/simulator/Character.java +++ b/src/simulator/Character.java @@ -6,9 +6,10 @@ public class Character extends Rectangle { + public static final int WIDTH = 32; + public static final int HEIGHT = 32; + private static final int MILLISECONDS_IN_SECOND = 1000; - private static final int WIDTH = 32; - private static final int HEIGHT = 32; private static final int POSITION_SOURCE_X = 0; private static final int POSITION_SOURCE_Y = 320; diff --git a/src/simulator/RerunButton.java b/src/simulator/RerunButton.java index b3446e4..4ba3510 100644 --- a/src/simulator/RerunButton.java +++ b/src/simulator/RerunButton.java @@ -1,17 +1,18 @@ package simulator; import simulator.information.ErrorGraph; +import simulator.textfields.BaseTextField; import javax.swing.*; import java.awt.*; public class RerunButton extends JButton { - private static final int WIDTH = 100; - private static final int HEIGHT = 45; + public static final int WIDTH = 100; + public static final int HEIGHT = 45; - private static final int X = 620; - private static final int Y = 700; + private static final int X = Window.WINDOW_WIDTH / 2 - WIDTH / 2; + private static final int Y = 200; private static RerunButton instance; diff --git a/src/simulator/Window.java b/src/simulator/Window.java index bbbce92..b8536fd 100644 --- a/src/simulator/Window.java +++ b/src/simulator/Window.java @@ -5,27 +5,34 @@ import simulator.textfields.BaseTextField; import javax.swing.*; +import javax.tools.Tool; import java.awt.*; public class Window extends JPanel { - private static final int MILLISECONDS_IN_SECOND = 1000; public static final double PERIODIC_FRAME = 0.02; + public static final int WINDOW_WIDTH = Toolkit.getDefaultToolkit().getScreenSize().width; + public static final int WINDOW_HEIGHT = Toolkit.getDefaultToolkit().getScreenSize().height; + private static final int MILLISECONDS_IN_SECOND = 1000; - private static final int WINDOW_WIDTH = 1280; - private static final int WINDOW_HEIGHT = 792; - private static final boolean IS_DOUBLE_BUFFERED = true; + public static final boolean IS_DOUBLE_BUFFERED = true; - private static final Point P_FIELD_LOCATION = new Point(300, 30); - private static final Point I_FIELD_LOCATION = new Point(600, 30); - private static final Point D_FIELD_LOCATION = new Point(900, 30); + private static final Point P_FIELD_LOCATION = + new Point((int) (WINDOW_WIDTH / 2 - BaseTextField.FIELD_WIDTH * 2.5 - 30), 30); + private static final Point I_FIELD_LOCATION = new Point(WINDOW_WIDTH / 2 - BaseTextField.FIELD_WIDTH / 2, 30); + private static final Point D_FIELD_LOCATION = + new Point((int) (WINDOW_WIDTH / 2 + BaseTextField.FIELD_WIDTH * 1.5 + 30), 30); private static final Point I_ZONE_FIELD_LOCATION = new Point(30, 30); - private static final Point TOLERANCE_FIELD_LOCATION = new Point(450, 65); - private static final Point WAIT_TIME_FIELD_LOCATION = new Point(750, 65); - private static final Point S_FIELD_LOCATION = new Point(300, 100); - private static final Point V_FIELD_LOCATION = new Point(600, 100); - private static final Point A_FIELD_LOCATION = new Point(900, 100); - private static final Point SETPOINT_FIELD_LOCATION = new Point(30, 722); + private static final Point TOLERANCE_FIELD_LOCATION = + new Point((int) (WINDOW_WIDTH / 2 - BaseTextField.FIELD_WIDTH * 1.5 - 15), 65); + private static final Point WAIT_TIME_FIELD_LOCATION = + new Point(WINDOW_WIDTH / 2 + BaseTextField.FIELD_WIDTH / 2 + 15, 65); + private static final Point S_FIELD_LOCATION = + new Point((int) (WINDOW_WIDTH / 2 - BaseTextField.FIELD_WIDTH * 2.5 - 30), 100); + private static final Point V_FIELD_LOCATION = new Point(WINDOW_WIDTH / 2 - BaseTextField.FIELD_WIDTH / 2, 100); + private static final Point A_FIELD_LOCATION = + new Point((int) (WINDOW_WIDTH / 2 + BaseTextField.FIELD_WIDTH * 1.5 + 30), 100); + private static final Point SETPOINT_FIELD_LOCATION = new Point(WINDOW_WIDTH - BaseTextField.FIELD_WIDTH - 30, 30); private final Character character; private final Setpoint setpoint; diff --git a/src/simulator/control/ControlType.java b/src/simulator/control/ControlType.java index 4e724ad..368c25f 100644 --- a/src/simulator/control/ControlType.java +++ b/src/simulator/control/ControlType.java @@ -1,6 +1,8 @@ package simulator.control; import simulator.Character; +import simulator.Window; +import simulator.textfields.BaseTextField; import javax.swing.*; import java.awt.*; @@ -21,8 +23,8 @@ public enum Types { private static final int WIDTH = 140; private static final int HEIGHT = 65; - private static final int X = 1100; - private static final int Y = 65; + private static final int X = Window.WINDOW_WIDTH - WIDTH - 30; + private static final int Y = 60 + HEIGHT; private static final int FONT_SIZE = 20; private static final String[] OPTIONS = new String[]{"Position", "Velocity"}; diff --git a/src/simulator/information/ErrorGraph.java b/src/simulator/information/ErrorGraph.java index c5ebb82..27cfa71 100644 --- a/src/simulator/information/ErrorGraph.java +++ b/src/simulator/information/ErrorGraph.java @@ -8,8 +8,8 @@ public class ErrorGraph extends JPanel { - private static final int WIDTH = 640; - private static final int HEIGHT = 390; + public static final int WIDTH = Status.WIDTH; + public static final int HEIGHT = 390; private static final int X = 0; private static final int Y = 150; diff --git a/src/simulator/information/Status.java b/src/simulator/information/Status.java index ee209db..02cf38b 100644 --- a/src/simulator/information/Status.java +++ b/src/simulator/information/Status.java @@ -2,18 +2,18 @@ import simulator.Character; import simulator.Setpoint; +import simulator.Window; import simulator.control.ControlType; import javax.swing.*; import java.awt.*; -import java.util.Set; public class Status extends JPanel { - private static final int WIDTH = 640; - private static final int HEIGHT = 640; - private static final int X = 800; - private static final int Y = 480; + public static final int WIDTH = Window.WINDOW_WIDTH; + public static final int HEIGHT = 640; + private static final int X = 0; + private static final int Y = Window.WINDOW_HEIGHT - HEIGHT; private static final int FONT_SIZE = 20; private static final int INFO_TEXT_WIDTH = 400; diff --git a/src/simulator/textfields/BaseTextField.java b/src/simulator/textfields/BaseTextField.java index 1cd576d..06563da 100644 --- a/src/simulator/textfields/BaseTextField.java +++ b/src/simulator/textfields/BaseTextField.java @@ -5,8 +5,8 @@ public class BaseTextField extends JTextField { - private static final int FIELD_WIDTH = 140; - private static final int FIELD_HEIGHT = 65; + public static final int FIELD_WIDTH = 140; + public static final int FIELD_HEIGHT = 65; private static final int FONT_SIZE = 40; From 19cc66b4f9991d0adcc6caa939dac19ae505b972 Mon Sep 17 00:00:00 2001 From: turtleminecraft Date: Wed, 26 Jun 2024 12:39:05 +0300 Subject: [PATCH 29/34] sizes shall now fit to screen size --- src/simulator/Character.java | 4 ++-- src/simulator/Setpoint.java | 2 +- src/simulator/information/ErrorGraph.java | 4 ++-- src/simulator/information/Status.java | 12 ++++++------ src/simulator/textfields/BaseTextField.java | 6 ++++-- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/simulator/Character.java b/src/simulator/Character.java index a8f1c21..1a38cd5 100644 --- a/src/simulator/Character.java +++ b/src/simulator/Character.java @@ -12,9 +12,9 @@ public class Character extends Rectangle { private static final int MILLISECONDS_IN_SECOND = 1000; private static final int POSITION_SOURCE_X = 0; - private static final int POSITION_SOURCE_Y = 320; + private static final int POSITION_SOURCE_Y = Window.WINDOW_HEIGHT / 3; - private static final int VELOCITY_SOURCE_X = 640; + private static final int VELOCITY_SOURCE_X = Window.WINDOW_WIDTH / 2; private static final int VELOCITY_SOURCE_Y = 320; private static final int MAX_SPEED = 120; diff --git a/src/simulator/Setpoint.java b/src/simulator/Setpoint.java index 610a2e3..8b61faf 100644 --- a/src/simulator/Setpoint.java +++ b/src/simulator/Setpoint.java @@ -10,7 +10,7 @@ public class Setpoint extends Rectangle { private static final int HEIGHT = 32; private static final int SOURCE_X = 1000; - private static final int SOURCE_Y = 320; + private static final int SOURCE_Y = Window.WINDOW_HEIGHT / 3; private static Setpoint instance; diff --git a/src/simulator/information/ErrorGraph.java b/src/simulator/information/ErrorGraph.java index 27cfa71..cac6f1c 100644 --- a/src/simulator/information/ErrorGraph.java +++ b/src/simulator/information/ErrorGraph.java @@ -9,9 +9,9 @@ public class ErrorGraph extends JPanel { public static final int WIDTH = Status.WIDTH; - public static final int HEIGHT = 390; + public static final int HEIGHT = Status.HEIGHT - Status.INFO_TEXT_HEIGHT * 3; private static final int X = 0; - private static final int Y = 150; + private static final int Y = Status.INFO_TEXT_HEIGHT * 3; private static final int POINT_WIDTH = 2; private static final int POINT_HEIGHT = 2; diff --git a/src/simulator/information/Status.java b/src/simulator/information/Status.java index 02cf38b..afe6d40 100644 --- a/src/simulator/information/Status.java +++ b/src/simulator/information/Status.java @@ -11,17 +11,17 @@ public class Status extends JPanel { public static final int WIDTH = Window.WINDOW_WIDTH; - public static final int HEIGHT = 640; + public static final int HEIGHT = Window.WINDOW_HEIGHT / 2; + public static final int INFO_TEXT_WIDTH = WIDTH; + public static final int INFO_TEXT_HEIGHT = HEIGHT / 15; + private static final int X = 0; private static final int Y = Window.WINDOW_HEIGHT - HEIGHT; private static final int FONT_SIZE = 20; - private static final int INFO_TEXT_WIDTH = 400; - private static final int INFO_TEXT_HEIGHT = 50; - private static final Point ERROR_LOCATION = new Point(0, 0); - private static final Point ERROR_RATE_LOCATION = new Point(0, 50); - private static final Point COMMAND_ENDED_LOCATION = new Point(0, 100); + private static final Point ERROR_RATE_LOCATION = new Point(0, INFO_TEXT_HEIGHT); + private static final Point COMMAND_ENDED_LOCATION = new Point(0, INFO_TEXT_HEIGHT * 2); private BaseInfoField characterError; private BaseInfoField errorRate; diff --git a/src/simulator/textfields/BaseTextField.java b/src/simulator/textfields/BaseTextField.java index 06563da..784c56c 100644 --- a/src/simulator/textfields/BaseTextField.java +++ b/src/simulator/textfields/BaseTextField.java @@ -1,12 +1,14 @@ package simulator.textfields; +import simulator.Window; + import javax.swing.*; import java.awt.*; public class BaseTextField extends JTextField { - public static final int FIELD_WIDTH = 140; - public static final int FIELD_HEIGHT = 65; + public static final int FIELD_WIDTH = Window.WINDOW_WIDTH / 15; + public static final int FIELD_HEIGHT = Window.WINDOW_HEIGHT / 20; private static final int FONT_SIZE = 40; From c20ee9fd23d606c84f16c9c4b91287e383ca85b0 Mon Sep 17 00:00:00 2001 From: TurtleMinecraft Date: Mon, 12 Aug 2024 19:56:04 +0300 Subject: [PATCH 30/34] created the randomness factor --- src/simulator/Character.java | 8 ++++++-- src/simulator/information/ErrorGraph.java | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/simulator/Character.java b/src/simulator/Character.java index 1a38cd5..eb05292 100644 --- a/src/simulator/Character.java +++ b/src/simulator/Character.java @@ -18,8 +18,10 @@ public class Character extends Rectangle { private static final int VELOCITY_SOURCE_Y = 320; private static final int MAX_SPEED = 120; - private static final int MAX_ACCELERATION = 2; + private static final int MAX_ACCELERATION = 6; private static final int FRICTION = 3; + private static final double POSITION_RANDOM_FACTOR = 3.6; + private static final double VELOCITY_RANDOM_FACTOR = 0.12; private final PIDSettings pidSettings; private final PIDController pidController; @@ -121,11 +123,12 @@ private void runPosition() { pidSettings.getWaitTime() * MILLISECONDS_IN_SECOND && pidController.isOnTarget()); int moveValue = pidController.calculate(this.x, Setpoint.getInstance().x) + feedForwardController.calculate(this.x, Setpoint.getInstance().x); - moveValue = (int) normalizeSpeed(moveValue); + moveValue += (Math.random() * 1.33 - 0.33) * POSITION_RANDOM_FACTOR * Math.pow(moveValue, 2) / MAX_SPEED; if (Math.abs(moveValue - lastSpeed) > MAX_ACCELERATION) { if (lastSpeed > moveValue) moveValue = (int) (lastSpeed - MAX_ACCELERATION); if (lastSpeed < moveValue) moveValue = (int) (lastSpeed + MAX_ACCELERATION); } + moveValue = (int) normalizeSpeed(moveValue); this.translate(moveValue, 0); lastSpeed = moveValue; } @@ -133,6 +136,7 @@ private void runPosition() { private void runVelocity() { int moveValue = pidController.calculate(lastSpeed, Setpoint.getInstance().x) + feedForwardController.calculate(lastSpeed, Setpoint.getInstance().x); + moveValue += (Math.random() * 1.33 - 0.33) * VELOCITY_RANDOM_FACTOR * Math.pow(moveValue, 2) / MAX_SPEED; moveValue = (int) normalizeSpeed(moveValue); if (Math.abs(moveValue - lastSpeed) > MAX_ACCELERATION) { if (lastSpeed > moveValue) moveValue = (int) (lastSpeed - MAX_ACCELERATION); diff --git a/src/simulator/information/ErrorGraph.java b/src/simulator/information/ErrorGraph.java index cac6f1c..ad1994b 100644 --- a/src/simulator/information/ErrorGraph.java +++ b/src/simulator/information/ErrorGraph.java @@ -4,6 +4,7 @@ import javax.swing.*; import java.awt.*; +import java.awt.geom.Line2D; import java.util.ArrayList; public class ErrorGraph extends JPanel { From 782d9264dd8846b0329f60bcb983b5629e0649f5 Mon Sep 17 00:00:00 2001 From: TurtleMinecraft Date: Tue, 10 Sep 2024 01:40:22 +0300 Subject: [PATCH 31/34] remove unneeded repaint --- src/simulator/Window.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/simulator/Window.java b/src/simulator/Window.java index b8536fd..beb69bf 100644 --- a/src/simulator/Window.java +++ b/src/simulator/Window.java @@ -106,7 +106,6 @@ private void delay(double seconds) { long targetTime = (long) (System.currentTimeMillis() + seconds * MILLISECONDS_IN_SECOND); while (targetTime > currentTime) { currentTime = System.currentTimeMillis(); - repaint(character); } } From 00713248cadb83b7c236a8be1b52d10b6900eea1 Mon Sep 17 00:00:00 2001 From: TurtleMinecraft Date: Mon, 30 Sep 2024 22:46:19 +0300 Subject: [PATCH 32/34] fucking hate this normalizeSpeed() method --- src/simulator/Character.java | 7 ++++--- src/simulator/control/FeedForwardController.java | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/simulator/Character.java b/src/simulator/Character.java index eb05292..542b3fc 100644 --- a/src/simulator/Character.java +++ b/src/simulator/Character.java @@ -147,10 +147,11 @@ private void runVelocity() { private double normalizeSpeed(double speed) { if (speed != 0) { - if (speed > 0) speed -= FRICTION; - if (speed < 0) speed += FRICTION; + if (speed > FRICTION) speed -= FRICTION; + else if (speed < -FRICTION) speed += FRICTION; + else return 0; if (Math.abs(speed) > MAX_SPEED) speed = (int) (MAX_SPEED * Math.signum(speed)); - } else if (Math.abs(speed) < FRICTION) speed = 0; + } return speed; } } diff --git a/src/simulator/control/FeedForwardController.java b/src/simulator/control/FeedForwardController.java index 2a602f3..610b9b6 100644 --- a/src/simulator/control/FeedForwardController.java +++ b/src/simulator/control/FeedForwardController.java @@ -63,7 +63,6 @@ public int calculate(double source, double setpoint) { double error = setpoint - source; double targetDerivative = (setpoint - previousTarget) / Window.PERIODIC_FRAME; previousTarget = setpoint; - double staticVal; if (ControlType.getInstance().getSelectedIndex() == ControlType.Types.VELOCITY.index) { return (int) (kS + kV * setpoint + kA * targetDerivative); } else { From ae1e68ceff9ea3255fd1a5bff47d4a71dbc9dfda Mon Sep 17 00:00:00 2001 From: TurtleMinecraft Date: Tue, 1 Oct 2024 00:22:43 +0300 Subject: [PATCH 33/34] velocity random factor is not so random anymore :3 (thanks tuval) --- src/simulator/Character.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/simulator/Character.java b/src/simulator/Character.java index 542b3fc..b1376ba 100644 --- a/src/simulator/Character.java +++ b/src/simulator/Character.java @@ -21,7 +21,6 @@ public class Character extends Rectangle { private static final int MAX_ACCELERATION = 6; private static final int FRICTION = 3; private static final double POSITION_RANDOM_FACTOR = 3.6; - private static final double VELOCITY_RANDOM_FACTOR = 0.12; private final PIDSettings pidSettings; private final PIDController pidController; @@ -123,7 +122,7 @@ private void runPosition() { pidSettings.getWaitTime() * MILLISECONDS_IN_SECOND && pidController.isOnTarget()); int moveValue = pidController.calculate(this.x, Setpoint.getInstance().x) + feedForwardController.calculate(this.x, Setpoint.getInstance().x); - moveValue += (Math.random() * 1.33 - 0.33) * POSITION_RANDOM_FACTOR * Math.pow(moveValue, 2) / MAX_SPEED; + moveValue += (Math.random() - 0.5) * POSITION_RANDOM_FACTOR * Math.pow(moveValue, 2) / MAX_SPEED; if (Math.abs(moveValue - lastSpeed) > MAX_ACCELERATION) { if (lastSpeed > moveValue) moveValue = (int) (lastSpeed - MAX_ACCELERATION); if (lastSpeed < moveValue) moveValue = (int) (lastSpeed + MAX_ACCELERATION); @@ -134,9 +133,9 @@ private void runPosition() { } private void runVelocity() { - int moveValue = pidController.calculate(lastSpeed, Setpoint.getInstance().x) + - feedForwardController.calculate(lastSpeed, Setpoint.getInstance().x); - moveValue += (Math.random() * 1.33 - 0.33) * VELOCITY_RANDOM_FACTOR * Math.pow(moveValue, 2) / MAX_SPEED; + int moveValue = pidController.calculate(lastSpeed, Setpoint.getInstance().x); + double feedForward = feedForwardController.calculate(lastSpeed, Setpoint.getInstance().x); + moveValue += feedForward * Math.pow(Math.E, -(Math.pow(feedForward / MAX_SPEED, 2) / Math.pow(MAX_SPEED, 0.25))); moveValue = (int) normalizeSpeed(moveValue); if (Math.abs(moveValue - lastSpeed) > MAX_ACCELERATION) { if (lastSpeed > moveValue) moveValue = (int) (lastSpeed - MAX_ACCELERATION); From 944c2e3e18461f17fdf08dde847074f8e35dab42 Mon Sep 17 00:00:00 2001 From: TurtleMinecraft Date: Tue, 1 Oct 2024 05:42:47 +0300 Subject: [PATCH 34/34] changed how the random factor works --- src/simulator/Character.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/simulator/Character.java b/src/simulator/Character.java index b1376ba..73a7d92 100644 --- a/src/simulator/Character.java +++ b/src/simulator/Character.java @@ -20,7 +20,6 @@ public class Character extends Rectangle { private static final int MAX_SPEED = 120; private static final int MAX_ACCELERATION = 6; private static final int FRICTION = 3; - private static final double POSITION_RANDOM_FACTOR = 3.6; private final PIDSettings pidSettings; private final PIDController pidController; @@ -122,12 +121,12 @@ private void runPosition() { pidSettings.getWaitTime() * MILLISECONDS_IN_SECOND && pidController.isOnTarget()); int moveValue = pidController.calculate(this.x, Setpoint.getInstance().x) + feedForwardController.calculate(this.x, Setpoint.getInstance().x); - moveValue += (Math.random() - 0.5) * POSITION_RANDOM_FACTOR * Math.pow(moveValue, 2) / MAX_SPEED; if (Math.abs(moveValue - lastSpeed) > MAX_ACCELERATION) { if (lastSpeed > moveValue) moveValue = (int) (lastSpeed - MAX_ACCELERATION); if (lastSpeed < moveValue) moveValue = (int) (lastSpeed + MAX_ACCELERATION); } moveValue = (int) normalizeSpeed(moveValue); + moveValue += (Math.random() - 0.5) * Math.pow(moveValue, 2) / MAX_SPEED; this.translate(moveValue, 0); lastSpeed = moveValue; }