JBox2D:http://www.jbox2d.org/
以下のプログラムは,ある閉じた空間(2D)内に複数のボールを用意し,物理演算を行ってボールが跳ねる様子を見ることができます.
また,自由に新しいボールや壁を追加することができます.
[PhysicalWorld.java]
import org.jbox2d.collision.AABB; import org.jbox2d.collision.CircleDef; import org.jbox2d.collision.PolygonDef; import org.jbox2d.collision.Shape; import org.jbox2d.common.Vec2; import org.jbox2d.dynamics.Body; import org.jbox2d.dynamics.BodyDef; import org.jbox2d.dynamics.World; import org.jbox2d.dynamics.joints.MouseJoint; import org.jbox2d.dynamics.joints.MouseJointDef; import org.jbox2d.collision.*; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.*; import java.util.*; public class PhysicalWorld extends JFrame { public static void main(String[] args) { new PhysicalWorld(); } public PhysicalWorld() { // タイトルを設定 setTitle("MyFrame"); // 閉じるボタンを押した際,プログラムを終了 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 起動時のロケーション設定 // setLocation(0, 0); // メインパネルを作成してフレームに追加 MainPanel panel = new MainPanel(); Container contentPane = getContentPane(); contentPane.add(panel); setVisible(true); // パネルサイズに合わせてフレームサイズを自動設定 pack(); } } class MainPanel extends JPanel implements Runnable { // パネルサイズ private static int width = 600; private static int height = 400; // 1m = 10px private static final int WORLD_SCALE = 10; private World mWorld; // 壁格納用リスト private java.util.List groundBodyList = new ArrayList(); // ボール格納用リスト private java.util.List ballBodyList = new ArrayList(); // マウスジョイント private MouseJointDef mouseJointDef; private MouseJoint mouseJoint; // アニメーション用スレッド private Thread thread; Body ballBody; public MainPanel() { initMainPanel(); initWorld(); initGround(); initBall(); // スレッドを起動 thread = new Thread(this); thread.start(); } private void initMainPanel() { // パネルの推奨サイズを設定 // pack()するときに必要 setPreferredSize(new Dimension(width, height)); setBackground(Color.BLACK); // マウスリスナを登録 this.addMouseListener(new MyListener()); this.addMouseMotionListener(new MyListener()); } private void initWorld() { Vec2 minV = new Vec2(-100,-100); Vec2 maxV = new Vec2(100,100); AABB aabb = new AABB(minV,maxV); mWorld = new World(aabb, new Vec2(0.0f,9.8f),true); } private void initGround() { // 上壁 createGroundBody(0.0f, 0.0f, (width * 0.9f) / WORLD_SCALE, 0.0f); // 下壁 createGroundBody(0.0f, (height * 0.9f) / WORLD_SCALE, (width * 0.9f) / WORLD_SCALE, (height * 0.9f) / WORLD_SCALE); // 左壁 createGroundBody(0.0f, 0.0f, 0.0f, (height * 0.9f) / WORLD_SCALE); // 右壁 createGroundBody((width * 0.9f) / WORLD_SCALE, 0.0f, (width * 0.9f) / WORLD_SCALE, (height * 0.9f) / WORLD_SCALE); } private void initBall() { // ボールの作成 for(int i = 15; i < 30; i++){ createBallBody((float)i, 10.0f); } // ジョイント用ボールの作成 BodyDef ballBodyDef = new BodyDef(); ballBodyDef.position.set(15.0f, 5.0f); CircleDef ballShapeDef = new CircleDef(); ballShapeDef.radius = 1.0f; ballShapeDef.density = 1.0f; ballShapeDef.restitution = 0.7f; ballBody = mWorld.createBody(ballBodyDef); ballBody.createShape(ballShapeDef); ballBody.setMassFromShapes(); ballBodyList.add(ballBody); mouseJointDef = new MouseJointDef(); mouseJointDef.body1 = mWorld.getGroundBody(); mouseJointDef.body2 = ballBody; mouseJointDef.target = ballBody.getWorldCenter(); mouseJointDef.maxForce = 1000; } private void createGroundBody(float sx, float sy, float ex, float ey){ BodyDef groundBodyDef = new BodyDef(); groundBodyDef.position.set((sx + ex)/2.0f, (sy + ey)/2.0f); PolygonDef groundShapeDef = new PolygonDef(); int angle = (int)(Math.atan((double)(ey - sy) / (double)(ex - sx)) * 180 / Math.PI); float dis = (float)Math.sqrt(Math.pow((sx-ex), 2) + Math.pow((sy-ey), 2)) / 2.0f; groundShapeDef.setAsBox(dis, 0.1f, new Vec2(0,0),(float)(Math.PI * angle / 180.0)); Body groundBody = mWorld.createBody(groundBodyDef); groundBody.createShape(groundShapeDef); groundBodyList.add(groundBody); } private void createBallBody(float x, float y){ BodyDef ballBodyDef = new BodyDef(); ballBodyDef.position.set(x, y); CircleDef ballShapeDef = new CircleDef(); ballShapeDef.radius = 1.0f; ballShapeDef.density = 1.0f; ballShapeDef.restitution = 0.7f; Body ballBody = mWorld.createBody(ballBodyDef); ballBody.createShape(ballShapeDef); ballBody.setMassFromShapes(); ballBodyList.add(ballBody); } public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.GREEN); for(Body groundBody : groundBodyList){ Shape shape = groundBody.getShapeList(); Vec2[] vPts = ((PolygonShape)shape).getVertices(); int minX = (int)Math.min(Math.min(vPts[0].x, vPts[1].x), Math.min(vPts[2].x, vPts[3].x)); int minY = (int)Math.min(Math.min(vPts[0].y, vPts[1].y), Math.min(vPts[2].y, vPts[3].y)); int maxX = (int)Math.min(Math.max(vPts[0].x, vPts[1].x), Math.max(vPts[2].x, vPts[3].x)); int maxY = (int)Math.min(Math.max(vPts[0].y, vPts[1].y), Math.max(vPts[2].y, vPts[3].y)); minX = (int)(groundBody.getWorldCenter().x + minX) * WORLD_SCALE; minY = (int)(groundBody.getWorldCenter().y + minY) * WORLD_SCALE; maxX = (int)(groundBody.getWorldCenter().x + maxX) * WORLD_SCALE; maxY = (int)(groundBody.getWorldCenter().y + maxY) * WORLD_SCALE; if(vPts[0].y <= vPts[1].y){ g.drawLine(minX, minY, maxX, maxY); } else { g.drawLine(minX, maxY, maxX, minY); } } g.setColor(Color.RED); int r = 2; Vec2 pos; for(Body ballBody : ballBodyList){ pos = ballBody.getWorldCenter(); g.fillOval((int)((pos.x-r/2) * WORLD_SCALE), (int)((pos.y-r/2) * WORLD_SCALE), r*WORLD_SCALE, r*WORLD_SCALE); } } // メインループ public void run() { // プログラムが終了するまでフレーム処理を繰り返す while (true) { mWorld.step(1/20f, 8); // ボールを再描画 repaint(); // 20ミリ秒だけ休止 try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } } public class MyListener extends MouseAdapter { Point startP,endP; // ボタンプレス時 public void mousePressed(java.awt.event.MouseEvent e){ if((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0){ // 左クリック // ジョイントの作成 mouseJoint = (MouseJoint)mWorld.createJoint(mouseJointDef); mouseJoint.m_localAnchor = ballBody.getLocalCenter(); } else if((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0){ // 右クリック // クリックポイントの作成 startP = e.getPoint(); } } // ボタンリリース時 public void mouseReleased(java.awt.event.MouseEvent e){ if((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0){ // ジョイントの削除 mWorld.destroyJoint(mouseJoint); mouseJoint = null; } else if((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0){ // リリース場所の格納 endP = e.getPoint(); if(Math.abs(startP.x - endP.x) < 3 && Math.abs(startP.y - endP.y) < 3){ // プレス時とリリース時にあまり差が無い場合 // 新しいボールを作成 createBallBody(startP.x / WORLD_SCALE, startP.y / WORLD_SCALE); } else { // プレス場所からリリース場所まで壁を作成する createGroundBody(startP.x / WORLD_SCALE, startP.y / WORLD_SCALE, endP.x / WORLD_SCALE, endP.y / WORLD_SCALE); } } } // マウスドラッグ時 public void mouseDragged(java.awt.event.MouseEvent e){ if((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0){ if(mouseJoint != null){ // ボールの移動 int x = e.getPoint().x / WORLD_SCALE; int y = e.getPoint().y / WORLD_SCALE; mouseJoint.setTarget(new Vec2(x, y)); } } } } }