I have written the following function for projectile motion of different force and angle, but it doesn\'t work properly. Where have I gone wrong? I want something like the A
The formulae you created for x- and y-displacement are both missing a time
factor in the first term. Below, I placed your code above and the correct code below, for comparison, so you can see exactly what you left out.
For X Displacement
(ball.getX()+10)*Math.cos(radians)+ ...
(ball.getX()+10)*time*Math.cos(radians)+ ...
For Y Displacement
(ball.getY()+10)*Math.sin(radians)+ ...
(ball.getY()+10)*time*Math.sin(radians)+ ...
I referenced Wikipedia's equation for displacement as a function of radians to answer your question.
As pointed out in the comment (and in the answer https://stackoverflow.com/a/21785385 ) : In order to achieve a "realistic" ballistic trajectory for the projectile, it is important to take the velocity into account - as well as the change of velocity for the given acceleration (based on the gravity force). Admittedly, I did not entirely understand what you wanted to achive with the sin/cos computation in your current position update. But I already had some SSCE here that was close to what you want to achieve, so I adapted it a little bit. Most of this is q&d-boilerplate code, but you might want to have a look at the Projectile
class and how the velocity and position are updated in its performTimeStep
method.
BTW: This approach has the nice advantage that it can easily be extended to model something like wind: Just use a different acceleration. For example, not (0,-9.81) but (1,-9.81) to simulate a light wind from the left.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
public class ProjectileShooterTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(600,600);
final ProjectileShooter projectileShooter =
new ProjectileShooter();
ProjectileShooterPanel projectileShooterPanel =
new ProjectileShooterPanel(projectileShooter);
projectileShooter.setPaintingComponent(projectileShooterPanel);
JPanel controlPanel = new JPanel(new GridLayout(1,0));
controlPanel.add(new JLabel("Angle"));
final JSlider angleSlider = new JSlider(0, 90, 45);
controlPanel.add(angleSlider);
controlPanel.add(new JLabel("Power"));
final JSlider powerSlider = new JSlider(0, 100, 50);
controlPanel.add(powerSlider);
JButton shootButton = new JButton("Shoot");
shootButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
int angleDeg = angleSlider.getValue();
int power = powerSlider.getValue();
projectileShooter.setAngle(Math.toRadians(angleDeg));
projectileShooter.setPower(power);
projectileShooter.shoot();
}
});
controlPanel.add(shootButton);
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(controlPanel, BorderLayout.NORTH);
f.getContentPane().add(projectileShooterPanel, BorderLayout.CENTER);
f.setVisible(true);
}
}
class ProjectileShooter
{
private double angleRad = Math.toRadians(45);
private double power = 50;
private Projectile projectile;
private JComponent paintingComponent;
void setPaintingComponent(JComponent paintingComponent)
{
this.paintingComponent = paintingComponent;
}
void setAngle(double angleRad)
{
this.angleRad = angleRad;
}
void setPower(double power)
{
this.power = power;
}
void shoot()
{
Thread t = new Thread(new Runnable()
{
@Override
public void run()
{
executeShot();
}
});
t.setDaemon(true);
t.start();
}
private void executeShot()
{
if (projectile != null)
{
return;
}
projectile = new Projectile();
Point2D velocity =
AffineTransform.getRotateInstance(angleRad).
transform(new Point2D.Double(1,0), null);
velocity.setLocation(
velocity.getX() * power * 0.5,
velocity.getY() * power * 0.5);
projectile.setVelocity(velocity);
//System.out.println("Initial "+velocity);
long prevTime = System.nanoTime();
while (projectile.getPosition().getY() >= 0)
{
long currentTime = System.nanoTime();
double dt = 3 * (currentTime - prevTime) / 1e8;
projectile.performTimeStep(dt);
prevTime = currentTime;
paintingComponent.repaint();
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
return;
}
}
projectile = null;
paintingComponent.repaint();
}
Projectile getProjectile()
{
return projectile;
}
}
class Projectile
{
private final Point2D ACCELERATION = new Point2D.Double(0, -9.81 * 0.1);
private final Point2D position = new Point2D.Double();
private final Point2D velocity = new Point2D.Double();
public Point2D getPosition()
{
return new Point2D.Double(position.getX(), position.getY());
}
public void setPosition(Point2D point)
{
position.setLocation(point);
}
public void setVelocity(Point2D point)
{
velocity.setLocation(point);
}
void performTimeStep(double dt)
{
scaleAddAssign(velocity, dt, ACCELERATION);
scaleAddAssign(position, dt, velocity);
//System.out.println("Now at "+position+" with "+velocity);
}
private static void scaleAddAssign(
Point2D result, double factor, Point2D addend)
{
double x = result.getX() + factor * addend.getX();
double y = result.getY() + factor * addend.getY();
result.setLocation(x, y);
}
}
class ProjectileShooterPanel extends JPanel
{
private final ProjectileShooter projectileShooter;
public ProjectileShooterPanel(ProjectileShooter projectileShooter)
{
this.projectileShooter = projectileShooter;
}
@Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
Projectile projectile = projectileShooter.getProjectile();
if (projectile != null)
{
g.setColor(Color.RED);
Point2D position = projectile.getPosition();
int x = (int)position.getX();
int y = getHeight() - (int)position.getY();
g.fillOval(x-01, y-10, 20, 20);
}
}
}
Quite important to notice: in Java (like in many other languages) position (0, 0) in your image is the top left corner. So to simulate your object "fall down" you actually need to increase its Y coordinate (and check if it already "hit the ground" -> if (position.Y == GROUND) stop();
). Also the starting position is not (0, 0) but (0, startingY).
Apart from this, my recommendation is:
move
method, which would change current position according to velocity.Note that your x coordinate changes in a constant maner, so your velocity.X
would be constant and could be calculated at the start. Then, your velocity.Y
should change with time: you calculate it's initial value and then substract a gravity-related amount (also constant) from it in every iteration. The move
method could look like this:
public void move(Position position, Velocity velocity) {
position.X += velocity.X;
position.Y += velocity.Y;
velocity.Y -= ACCELERATION*TIME; //TIME is time between calls of move(), approximate.
}
Of course this is a very simple example, but I guess it gives the idea. Note that both TIME and ACCELERATION are constant inside the simulation, as TIME is not time elapsed from the start, but time elapsed from the previous move
call. Remember the notice from the top of this answer. Also: initialize your velocity
and position
correctly, like this:
position.X = startingX; //the leftmost pixel of the screen is 0, of course.
position.Y = startingY; //the "ground level" of your simulation is probably NOT 0.
velocity.X = speed*Math.cos(throwAngle);
velocity.Y = speed*Math.sin(throwAngle);
Where speed
is just an int (the length of your velocity vector at the start.)