摆模拟只执行一个周期
Pendulum simulation only performing one period
我正在模拟一个钟摆,但它只执行一次摆动,然后发送接近于摆锤所在的随机位置。本质上,它不会倒退。
我已经尝试使用 goingForward 布尔值来改变方向,但它仍然不起作用。
public class AnimationPane extends JPanel {
// START CHANGEABLE VARIABLES
private double startAngle = -60.0; // degrees
private double mass = 1; // kilogrammes
private int radius = 10; // m
private double gravity = 9.80665; // m/s^2 // on earth: 9.80665
// END CHANGEABLE VARIABLEs
private BufferedImage ball;
private BufferedImage rope;
private int pointX = 180;
private int pointY = 50;
private double endAngle = Math.abs(startAngle); // absolute value of startAngle
private double angle = startAngle; // current angle
private double circum = (2 * Math.PI * radius); // m
private double distance = 0; // m
private double velocity = 0; // m/s
private double totalEnergy = ((radius) - (Math.cos(Math.toRadians(angle)) * radius)) * gravity * mass + 0.00001;
private double previousE;
private int xPos = 0; // for program
private int yPos = 0; // for program
private boolean goingForward = true;
private double height = 0;
public AnimationPane() {
try {
ball = ImageIO.read(new File("rsz_black-circle-mask-to-fill-compass-outline.png"));
Timer timer = new Timer(100, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
double angleRad = Math.toRadians(Math.abs(angle));
double potentialE = ((radius) - (Math.cos(angleRad) * radius)) * gravity * mass;
Double pE = new Double(potentialE);
height = (radius - (Math.cos(angleRad) * radius));
double kineticE = totalEnergy - pE;
if (kineticE <= 0 || angle >= endAngle) {
if (goingForward == true) {
goingForward = false;
}
else
{
goingForward = true;
}
kineticE = 0.1;
angle = 60;
}
velocity = Math.sqrt(2 * kineticE / mass);
double ratio = distance / circum;
if (goingForward == true) {
distance = distance + (velocity / 10);
angle = startAngle + (360 * ratio);
}
else {
distance = distance - (velocity / 10);
angle = startAngle - (360 * ratio);
}
double angles = Math.toRadians(angle);
double xDouble = Math.sin(angles) * (radius * 10);
Double x = new Double(xDouble);
xPos = x.intValue() + 150;
double yDouble = Math.cos(angles) * (radius * 10);
Double y = new Double(yDouble);
yPos = y.intValue() + 50;
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
} catch (IOException ex) {
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawLine(xPos + 20, yPos + 20, pointX, pointY);
g.drawImage(ball, xPos, yPos, this);
}
}
如果能帮我实现这个功能,我将不胜感激。
谢谢
我无法调试你的代码,它很难使用,有时也很难理解(你在代码中使用了很多整数文字,隐藏了它们的语义,我不知道你对某些语句的意图是什么).
因此,我用小振荡的微分方程的解重写了它。它有效,您可以将它作为一个干净的基础,以您想要的方式再次实现它。请注意,正如 Andy Turner 指出的那样,您不必担心前进或后退的事实。你有一个方程式,你求解它,它会随时为你提供球的位置。如果你想要一些对大角度来说准确的东西,我建议你去维基百科看看这种情况下的运动方程。最后一个选项,你可以数值求解微分方程,虽然我个人乍一看不知道如何去做。
package Whosebug;
public class AnimationPane extends JPanel {
private static final long serialVersionUID = 1L;
private static final double GRAVITY = 9.80665;
private BufferedImage ball;
private final Point fixedCordPoint;
private final int cordLength;
private final double startAngle;
private double currentAngle;
private final double pulsation;
private final Point ballPos = new Point();
private int time = 1;
public AnimationPane(Point fixedCordPoint, int cordLength, double startAngleRadians) {
this.fixedCordPoint = new Point(fixedCordPoint);
this.cordLength = cordLength;
this.pulsation = Math.sqrt(GRAVITY / cordLength);
this.startAngle = startAngleRadians;
this.currentAngle = startAngleRadians;
this.ball = loadImage(new File("ball.jpg"));
}
private BufferedImage loadImage(File file) {
try {
return ImageIO.read(file);
} catch (IOException e) {
throw new RuntimeException("Could not load file : " + file, e);
}
}
public void start() {
Timer timer = new Timer(100, event -> {
ballPos.x = fixedCordPoint.x + (int) Math.round(Math.sin(currentAngle) * cordLength);
ballPos.y = fixedCordPoint.y + (int) Math.round(Math.cos(currentAngle) * cordLength);
repaint();
currentAngle = startAngle * Math.cos(pulsation * time);
time++;
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawLine(ballPos.x, ballPos.y, fixedCordPoint.x, fixedCordPoint.y);
g.drawImage(ball, ballPos.x - ball.getWidth() / 2, ballPos.y - ball.getHeight() / 2, this);
}
public static void main(String[] args) {
JFrame frame = new JFrame();
AnimationPane pendulumAnimationPane = new AnimationPane(new Point(160, 25), 180, - Math.PI / 10);
frame.setContentPane(pendulumAnimationPane);
frame.setSize(400,400);
frame.setVisible(true);
pendulumAnimationPane.start();
}
}
我真的跟不上你的代码。如果我不得不说我认为问题出在哪里,我会说它是 distance
变量,它在 ratio
中的后续使用:这似乎被定义为钟摆开始时为零。当你改变钟摆的方向时,我认为它需要重新设置为零......不知何故。
您还应该将 GUI 代码与模拟分开。您可以在调试时让它打印出 x 和 y 坐标。例如,您可以将这些值写入 CSV 文件,这样您就可以在 Matlab(或其他软件)中可视化它们,以确保您的模拟随着时间的推移看起来是正确的。
我会提出两个建议来帮助您进行模拟:
- 不要根据能量对其进行建模,这是一个标量,这意味着您必须使用像 "direction flag" 这样的附加状态来对其进行建模;
- 您将需要一个额外的状态变量。存储 x 和 y 是多余的,因为它们可以直接从角度(和 R,尽管它是常数)导出。您正在为二阶系统建模,因此您需要第二个状态变量来模拟系统瞬间的运动及其位置。例如,您可以对随时间变化的角度和 angular 速度进行建模。
当然,不要乱用度数 - 坚持弧度:)
(关于速度,你那个名字的变量实际上是速度——你不知道它向哪个方向移动,而且这个信息与系统的动力学高度相关)。
使用角度和 angular 速度的方法的推导非常简单,尽管我的数学很生疏,不用仔细检查就可以谨慎使用!
rotational form of Newton's second law of motion是:
Torque = moment of inertia * angular acceleration
- 摆锤上的扭矩由
-mgR sin angle
给出(m
是质量,g
是重力,R
是摆的长度,angle
是摆从垂直方向的 angular 位移)。减号是因为力倾向于 return 摆锤垂直;
- 质点的惯性矩为
mR^2
- 如果摆与摆锤的半径相比非常长,则这是一个合理的近似值。
因此angular加速度为-g sin theta / R
。希望这应该看起来像简谐运动——加速度与平衡点的距离成正比(对于小的 theta,sin theta
大约是 theta
)并指向它。
您现在可以将其组合成一个数值方案来模拟钟摆。例如,使用 Euler's method:
New angle = old angle + dt * old angular velocity
New angular velocity = old angular velocity vel - dt * g * sin(angle) / R
其中 dt
是您的时间步长。
当然,您可以使用 Runge-Kutta 等高阶方法来减少模拟中的数值误差,但它(稍微)涉及更多。
我正在模拟一个钟摆,但它只执行一次摆动,然后发送接近于摆锤所在的随机位置。本质上,它不会倒退。
我已经尝试使用 goingForward 布尔值来改变方向,但它仍然不起作用。
public class AnimationPane extends JPanel {
// START CHANGEABLE VARIABLES
private double startAngle = -60.0; // degrees
private double mass = 1; // kilogrammes
private int radius = 10; // m
private double gravity = 9.80665; // m/s^2 // on earth: 9.80665
// END CHANGEABLE VARIABLEs
private BufferedImage ball;
private BufferedImage rope;
private int pointX = 180;
private int pointY = 50;
private double endAngle = Math.abs(startAngle); // absolute value of startAngle
private double angle = startAngle; // current angle
private double circum = (2 * Math.PI * radius); // m
private double distance = 0; // m
private double velocity = 0; // m/s
private double totalEnergy = ((radius) - (Math.cos(Math.toRadians(angle)) * radius)) * gravity * mass + 0.00001;
private double previousE;
private int xPos = 0; // for program
private int yPos = 0; // for program
private boolean goingForward = true;
private double height = 0;
public AnimationPane() {
try {
ball = ImageIO.read(new File("rsz_black-circle-mask-to-fill-compass-outline.png"));
Timer timer = new Timer(100, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
double angleRad = Math.toRadians(Math.abs(angle));
double potentialE = ((radius) - (Math.cos(angleRad) * radius)) * gravity * mass;
Double pE = new Double(potentialE);
height = (radius - (Math.cos(angleRad) * radius));
double kineticE = totalEnergy - pE;
if (kineticE <= 0 || angle >= endAngle) {
if (goingForward == true) {
goingForward = false;
}
else
{
goingForward = true;
}
kineticE = 0.1;
angle = 60;
}
velocity = Math.sqrt(2 * kineticE / mass);
double ratio = distance / circum;
if (goingForward == true) {
distance = distance + (velocity / 10);
angle = startAngle + (360 * ratio);
}
else {
distance = distance - (velocity / 10);
angle = startAngle - (360 * ratio);
}
double angles = Math.toRadians(angle);
double xDouble = Math.sin(angles) * (radius * 10);
Double x = new Double(xDouble);
xPos = x.intValue() + 150;
double yDouble = Math.cos(angles) * (radius * 10);
Double y = new Double(yDouble);
yPos = y.intValue() + 50;
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
} catch (IOException ex) {
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawLine(xPos + 20, yPos + 20, pointX, pointY);
g.drawImage(ball, xPos, yPos, this);
}
}
如果能帮我实现这个功能,我将不胜感激。 谢谢
我无法调试你的代码,它很难使用,有时也很难理解(你在代码中使用了很多整数文字,隐藏了它们的语义,我不知道你对某些语句的意图是什么).
因此,我用小振荡的微分方程的解重写了它。它有效,您可以将它作为一个干净的基础,以您想要的方式再次实现它。请注意,正如 Andy Turner 指出的那样,您不必担心前进或后退的事实。你有一个方程式,你求解它,它会随时为你提供球的位置。如果你想要一些对大角度来说准确的东西,我建议你去维基百科看看这种情况下的运动方程。最后一个选项,你可以数值求解微分方程,虽然我个人乍一看不知道如何去做。
package Whosebug;
public class AnimationPane extends JPanel {
private static final long serialVersionUID = 1L;
private static final double GRAVITY = 9.80665;
private BufferedImage ball;
private final Point fixedCordPoint;
private final int cordLength;
private final double startAngle;
private double currentAngle;
private final double pulsation;
private final Point ballPos = new Point();
private int time = 1;
public AnimationPane(Point fixedCordPoint, int cordLength, double startAngleRadians) {
this.fixedCordPoint = new Point(fixedCordPoint);
this.cordLength = cordLength;
this.pulsation = Math.sqrt(GRAVITY / cordLength);
this.startAngle = startAngleRadians;
this.currentAngle = startAngleRadians;
this.ball = loadImage(new File("ball.jpg"));
}
private BufferedImage loadImage(File file) {
try {
return ImageIO.read(file);
} catch (IOException e) {
throw new RuntimeException("Could not load file : " + file, e);
}
}
public void start() {
Timer timer = new Timer(100, event -> {
ballPos.x = fixedCordPoint.x + (int) Math.round(Math.sin(currentAngle) * cordLength);
ballPos.y = fixedCordPoint.y + (int) Math.round(Math.cos(currentAngle) * cordLength);
repaint();
currentAngle = startAngle * Math.cos(pulsation * time);
time++;
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawLine(ballPos.x, ballPos.y, fixedCordPoint.x, fixedCordPoint.y);
g.drawImage(ball, ballPos.x - ball.getWidth() / 2, ballPos.y - ball.getHeight() / 2, this);
}
public static void main(String[] args) {
JFrame frame = new JFrame();
AnimationPane pendulumAnimationPane = new AnimationPane(new Point(160, 25), 180, - Math.PI / 10);
frame.setContentPane(pendulumAnimationPane);
frame.setSize(400,400);
frame.setVisible(true);
pendulumAnimationPane.start();
}
}
我真的跟不上你的代码。如果我不得不说我认为问题出在哪里,我会说它是 distance
变量,它在 ratio
中的后续使用:这似乎被定义为钟摆开始时为零。当你改变钟摆的方向时,我认为它需要重新设置为零......不知何故。
您还应该将 GUI 代码与模拟分开。您可以在调试时让它打印出 x 和 y 坐标。例如,您可以将这些值写入 CSV 文件,这样您就可以在 Matlab(或其他软件)中可视化它们,以确保您的模拟随着时间的推移看起来是正确的。
我会提出两个建议来帮助您进行模拟:
- 不要根据能量对其进行建模,这是一个标量,这意味着您必须使用像 "direction flag" 这样的附加状态来对其进行建模;
- 您将需要一个额外的状态变量。存储 x 和 y 是多余的,因为它们可以直接从角度(和 R,尽管它是常数)导出。您正在为二阶系统建模,因此您需要第二个状态变量来模拟系统瞬间的运动及其位置。例如,您可以对随时间变化的角度和 angular 速度进行建模。
当然,不要乱用度数 - 坚持弧度:)
(关于速度,你那个名字的变量实际上是速度——你不知道它向哪个方向移动,而且这个信息与系统的动力学高度相关)。
使用角度和 angular 速度的方法的推导非常简单,尽管我的数学很生疏,不用仔细检查就可以谨慎使用!
rotational form of Newton's second law of motion是:
Torque = moment of inertia * angular acceleration
- 摆锤上的扭矩由
-mgR sin angle
给出(m
是质量,g
是重力,R
是摆的长度,angle
是摆从垂直方向的 angular 位移)。减号是因为力倾向于 return 摆锤垂直; - 质点的惯性矩为
mR^2
- 如果摆与摆锤的半径相比非常长,则这是一个合理的近似值。
因此angular加速度为-g sin theta / R
。希望这应该看起来像简谐运动——加速度与平衡点的距离成正比(对于小的 theta,sin theta
大约是 theta
)并指向它。
您现在可以将其组合成一个数值方案来模拟钟摆。例如,使用 Euler's method:
New angle = old angle + dt * old angular velocity
New angular velocity = old angular velocity vel - dt * g * sin(angle) / R
其中 dt
是您的时间步长。
当然,您可以使用 Runge-Kutta 等高阶方法来减少模拟中的数值误差,但它(稍微)涉及更多。