Ja, das einfache Multiplizieren war natürlich Unfug, bzw. funktioniert nur in Spezialfällen. (Notiz an mich: Nach Mitternacht keine Gremlins füttern UND keine Fragen mehr beantworten :o ). Dabei wird ja z.B. die Masse gar nicht berücksichtigt (im Gegensatz zu dem, was auch auf der verlinkten Seite steht). Eine Bleikugel wird weniger durch die („Luft“) Dämpfung beeinflusst, als eine Plastikkugel.
Richtig wäre also, eine Kraft auf das Objekt anzuwenden, die von der aktuellen Geschwindigkeit abhängt (und die dann - abhängig von der Masse des Objekts - die Beschleunigung und damit die zukünftige Geschwindigkeit verändert).
Das wichtige dabei ist: Das sind alles rein physikalische Berechnungen. Eine „Framerate“ gibt es gar nicht (d.h. man kann das auch ablaufen lassen, ohne überhaupt was auf dem Bildschirm anzuzeigen). Aaaaaber: Natürlich gibt es eine Zeitschrittgröße, die (bei einem „Echtzeitspiel“) von der Framerate abhängt. Je größer der Zeitschritt ist, desto „ungenauer“ werden die Berechnungen. Aber das ist ein allgemeines Problem, und gilt auch ohne Dämpfung. Die Dämpfung an sich schmiegt sich in die übrigen Berechnungen ein (und hängt damit genauso viel oder wenig von der Framerate ab, wie alle anderen).
Hab’ da mal schnell ein altes Beispiel so umgehackt, dass es eine „DampingForce“ gibt, die man mit einem Slider ein bißchen verändern kann.
ACHTUNG: Das ist wirklich nur ein altes, umgebautes Beispiel, und einiges davon ist inzwischen schon recht häßlich.
Aber dass die Dämpfung (Frameratenunabhängig) direkt in die physikalische Berechnung einfließt ist eben DerSpringendePunkt
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
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.Path2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class DerSpringendePunkt
{
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.getContentPane().setLayout(new BorderLayout());
PointPhysicsRunner pointPhysicsRunner = new PointPhysicsRunner();
f.getContentPane().add(
pointPhysicsRunner.getPointPhysicsPanel(), BorderLayout.CENTER);
JPanel controlPanel = createControlPanel(pointPhysicsRunner);
f.getContentPane().add(controlPanel, BorderLayout.NORTH);
f.setSize(800,800);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static JPanel createControlPanel(
final PointPhysicsRunner pointPhysicsRunner)
{
JPanel controlPanel = new JPanel(new GridLayout(0,1));
JButton emitButton = new JButton("Emit");
emitButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
PointPhysics pointPhysics =
pointPhysicsRunner.getPointPhysics();
pointPhysics.emitPoint(0.0, 0.9, 0.25, 0.0);
}
});
JPanel p0 = new JPanel();
p0.add(emitButton);
controlPanel.add(p0);
final JSlider updateDelayMsSlider = new JSlider(1, 250, 10);
final JLabel updateDelayMsLabel = new JLabel("Delay: 10");
updateDelayMsLabel.setPreferredSize(
new Dimension(150, 30));
updateDelayMsSlider.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent e)
{
int updateDelayMs = updateDelayMsSlider.getValue();
updateDelayMsLabel.setText("Delay: "+updateDelayMs);
pointPhysicsRunner.setUpdateDelayMs(updateDelayMs);
}
});
JPanel p1 = new JPanel(new BorderLayout());
p1.add(updateDelayMsLabel, BorderLayout.WEST);
p1.add(updateDelayMsSlider, BorderLayout.CENTER);
controlPanel.add(p1);
final JSlider dampingSlider = new JSlider(0, 100, 0);
final JLabel dampingLabel = new JLabel("Damping: 0");
dampingLabel.setPreferredSize(
new Dimension(150, 30));
dampingSlider.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent e)
{
double damping = dampingSlider.getValue() / 100.0;
dampingLabel.setText("Damping: "+damping);
PointPhysics pointPhysics =
pointPhysicsRunner.getPointPhysics();
pointPhysics.setDamping(damping);
}
});
JPanel p2 = new JPanel(new BorderLayout());
p2.add(dampingLabel, BorderLayout.WEST);
p2.add(dampingSlider, BorderLayout.CENTER);
controlPanel.add(p2);
return controlPanel;
}
}
class PointPhysicsRunner
{
private final PointPhysics pointPhysics;
private final PointPhysicsPanel pointPhysicsPanel;
private int updateDelayMs = 10;
PointPhysicsRunner()
{
pointPhysics = new PointPhysics();
pointPhysicsPanel = new PointPhysicsPanel(pointPhysics);
Thread thread = new Thread(new Runnable()
{
@Override
public void run()
{
runPhysics();
}
}, "PointPhysicsRunner");
thread.setDaemon(true);
thread.start();
}
private void runPhysics()
{
long previousNS = System.nanoTime();
while (true)
{
long currentNS = System.nanoTime();
double seconds = (currentNS - previousNS) / 1e9;
previousNS = currentNS;
pointPhysics.doStep(seconds);
pointPhysicsPanel.repaint();
try
{
Thread.sleep(updateDelayMs);
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
return;
}
}
}
public void setUpdateDelayMs(int updateDelayMs)
{
this.updateDelayMs = updateDelayMs;
}
public PointPhysics getPointPhysics()
{
return pointPhysics;
}
public PointPhysicsPanel getPointPhysicsPanel()
{
return pointPhysicsPanel;
}
}
class PointPhysicsPanel extends JPanel
{
private final PointPhysics pointPhysics;
private Path2D currentPath;
private Path2D previousPath;
private PhysicalPoint previousPhysicalPoint;
public PointPhysicsPanel(PointPhysics pointPhysics)
{
this.pointPhysics = pointPhysics;
}
@Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
List<PhysicalPoint> physicalPoints = pointPhysics.getPhysicalPoints();
if (!physicalPoints.isEmpty())
{
PhysicalPoint pp = physicalPoints.get(0);
if (pp != previousPhysicalPoint)
{
previousPhysicalPoint = pp;
previousPath = currentPath;
currentPath = new Path2D.Double();
Point2D position = pp.getPosition();
int x = (int)(position.getX()*getWidth());
int y = getHeight() - (int)(position.getY()*getHeight());
currentPath.moveTo(x, y);
}
else
{
Point2D position = pp.getPosition();
int x = (int)(position.getX()*getWidth());
int y = getHeight() - (int)(position.getY()*getHeight());
currentPath.lineTo(x, y);
}
g.setColor(Color.RED);
for (PhysicalPoint physicalPoint : physicalPoints)
{
Point2D position = physicalPoint.getPosition();
int x = (int)(position.getX()*getWidth());
int y = getHeight() - (int)(position.getY()*getHeight());
g.fillOval(x-5, y-5, 10, 10);
}
}
g.setColor(Color.BLACK);
int y = getHeight() - (int)(pointPhysics.getFloorHeight()*getHeight());
g.drawLine(0, y, getWidth(), y);
if (previousPath != null)
{
g.setColor(Color.LIGHT_GRAY);
g.draw(previousPath);
}
if (currentPath != null)
{
g.setColor(Color.DARK_GRAY);
g.draw(currentPath);
}
}
}
interface Force
{
Point2D computeAcceleration(PhysicalPoint physicalPoint);
}
class Gravity implements Force
{
@Override
public Point2D computeAcceleration(PhysicalPoint physicalPoint)
{
return new Point2D.Double(0, -9.81*0.1);
}
}
class DampingForce implements Force
{
private double coefficient = 0.0;
public void setCoefficient(double coefficient)
{
this.coefficient = coefficient;
}
@Override
public Point2D computeAcceleration(PhysicalPoint physicalPoint)
{
Point2D velocity = physicalPoint.getVelocity();
double ax = - coefficient * velocity.getX() / physicalPoint.getMass();
double ay = - coefficient * velocity.getY() / physicalPoint.getMass();
return new Point2D.Double(ax, ay);
}
}
class PointPhysics
{
private final List<PhysicalPoint> physicalPoints;
private double totalTime = 0;
private final List<Force> forces;
private DampingForce dampingForce;
private double floorHeight = 0.1;
public PointPhysics()
{
physicalPoints = new CopyOnWriteArrayList<PhysicalPoint>();
forces = new ArrayList<Force>();
forces.add(new Gravity());
dampingForce = new DampingForce();
forces.add(dampingForce);
}
public void setDamping(double damping)
{
dampingForce.setCoefficient(damping);
}
double getFloorHeight()
{
return floorHeight;
}
public List<PhysicalPoint> getPhysicalPoints()
{
return Collections.unmodifiableList(physicalPoints);
}
public void doStep(double t)
{
removeOld();
updateAccelerations();
updateVelocities(t);
updatePositions(t);
handleFloorCollisions();
totalTime += t;
//System.out.println("After "+totalTime+": "+physicalPoints);
}
public void emitPoint(double px, double py, double vx, double vy)
{
physicalPoints.clear();
PhysicalPoint physicalPoint = new PhysicalPoint();
physicalPoint.setPosition(new Point2D.Double(px, py));
physicalPoint.setVelocity(new Point2D.Double(vx, vy));
physicalPoints.add(physicalPoint);
}
private void removeOld()
{
List<PhysicalPoint> toRemove = new ArrayList<PhysicalPoint>();
for (PhysicalPoint physicalPoint : physicalPoints)
{
Point2D position = physicalPoint.getPosition();
if (position.getX() < 0 || position.getX() > 1)
{
toRemove.add(physicalPoint);
}
}
physicalPoints.removeAll(toRemove);
}
private void updateAccelerations()
{
for (PhysicalPoint physicalPoint : physicalPoints)
{
Point2D totalAcceleration = new Point2D.Double();
for (Force force : forces)
{
Point2D acceleration = force.computeAcceleration(physicalPoint);
addAssign(totalAcceleration, acceleration);
}
physicalPoint.setAcceleration(totalAcceleration);
//System.out.println("acc "+physicalPoint.getAcceleration());
}
}
private void updateVelocities(double t)
{
for (PhysicalPoint physicalPoint : physicalPoints)
{
Point2D velocity = physicalPoint.getVelocity();
Point2D acceleration = physicalPoint.getAcceleration();
scaleAddAssign(velocity, t, acceleration);
physicalPoint.setVelocity(velocity);
//System.out.println("vel "+physicalPoint.getVelocity());
}
}
private void updatePositions(double t)
{
for (PhysicalPoint physicalPoint : physicalPoints)
{
Point2D position = physicalPoint.getPosition();
Point2D velocity = physicalPoint.getVelocity();
scaleAddAssign(position, t, velocity);
physicalPoint.setPosition(position);
//System.out.println("pos "+physicalPoint.getPosition());
}
}
private void handleFloorCollisions()
{
for (PhysicalPoint physicalPoint : physicalPoints)
{
Point2D position = physicalPoint.getPosition();
if (position.getY() < floorHeight)
{
position.setLocation(position.getX(),
floorHeight + (floorHeight - position.getY()));
physicalPoint.setPosition(position);
Point2D velocity = physicalPoint.getVelocity();
physicalPoint.setVelocity(
new Point2D.Double(velocity.getX(), -velocity.getY()));
}
//System.out.println("pos "+physicalPoint.getPosition());
}
}
private static void addAssign(Point2D result, Point2D addend)
{
double x = result.getX() + addend.getX();
double y = result.getY() + addend.getY();
result.setLocation(x, y);
}
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 PhysicalPoint
{
private final Point2D position;
private final Point2D velocity;
private final Point2D acceleration;
private final double mass;
public PhysicalPoint()
{
position = new Point2D.Double();
velocity = new Point2D.Double();
acceleration = new Point2D.Double();
mass = 1;
}
public Point2D getPosition()
{
return new Point2D.Double(position.getX(), position.getY());
}
public void setPosition(Point2D point)
{
position.setLocation(point);
}
public Point2D getVelocity()
{
return new Point2D.Double(velocity.getX(), velocity.getY());
}
public void setVelocity(Point2D point)
{
velocity.setLocation(point);
}
public Point2D getAcceleration()
{
return new Point2D.Double(acceleration.getX(), acceleration.getY());
}
public void setAcceleration(Point2D point)
{
acceleration.setLocation(point);
}
public double getMass()
{
return mass;
}
@Override
public String toString()
{
return String.format("PhysicalPoint[p=(%.2f,%.2f),v=(%.2f,%.2f),a=(%.2f,%.2f)]",
position.getX(), position.getY(),
velocity.getY(), velocity.getY(),
acceleration.getX(), acceleration.getY());
}
}