Tuesday, August 20, 2013

Animasi Bola Biliard dengan JavaFX

Kali ini saya akan memberikan sebuah tutorial mengenai bagaimana membuat animasi bola billiard dengan javaFX yang preview-nya dapat dilihat pada gambar berikut:

 

Adapun source codenya adalah sebagai berikut:

package fjr.collision;

import java.util.ArrayList;
import java.util.Random;

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBuilder;
import javafx.scene.shape.Circle;
import javafx.scene.shape.CircleBuilder;
import javafx.stage.Stage;
import javafx.util.Duration;

public class BilliardBallDetection extends Application {

  ArrayList listParticle;
 ArrayList listCircle; 

 double xwidth = 400;
 double ywidth = 400;

 double timeStep = 2.5;
 double numberParticle = 9;
 Random rand = new Random();

 double dmin = 30.0;

 double xmax = xwidth;
 double xmin = 0.0;
 double ymax = ywidth;
 double ymin = 0.0;
 double size = 0.0;

 double dmax = 0.0;
 
 double vmax = 3.0; 


 Timeline animation; 
 Button buttonPause, buttonPlay; 
 
 @Override
 public void start(Stage primarySrage) throws Exception {
  // TODO Auto-generated method stub
  generateParticle();
  removeOverlap(listParticle);
  generateCircle();
  
  Group root = new Group(); 
  for(int i =0; i< listCircle.size(); i++){
   Circle c = listCircle.get(i); 
   root.getChildren().add(c); 
  }
  
  animation = new Timeline(); 
  animation.setCycleCount(Timeline.INDEFINITE);
  animation.setAutoReverse(false);
  
  KeyFrame kf = new KeyFrame(Duration.millis(20), new EventHandler(){

   @Override
   public void handle(ActionEvent event) {
    // TODO Auto-generated method stub
    checkBoundary();
    calculateCollision();
    moveParticle(); 
    redrawCircle();
   }
   
  }); 

  animation.getKeyFrames().add(kf); 
  
  root.getChildren().addAll(buttonPlay = ButtonBuilder.create()
    .text("PLAY").onAction(new EventHandler() {

     @Override
     public void handle(ActionEvent arg0) {
      // TODO Auto-generated method stub
      animation.play();
     }
    }).translateX(10).translateY(10)
    .build(), 
    buttonPause = ButtonBuilder.create()
    
    .text("PAUSE").translateX(80).translateY(10)
    .onAction(new EventHandler() {

     @Override
     public void handle(ActionEvent arg0) {
      // TODO Auto-generated method stub
      animation.pause();
     }
    })
    .build() 

    ); 
  primarySrage.setScene(new Scene(root, xwidth, ywidth));
  primarySrage.show();
 }
 
 private void redrawCircle(){
  for(int i=0; i< listCircle.size(); i++){
   Circle c = listCircle.get(i); 
   Particle  p  = listParticle.get(i); 
   c.setCenterX(p.x);
   c.setCenterY(p.y);
  }
 }
 
 
 private void generateCircle(){
  listCircle = new ArrayList<>(); 
  for(int i=0; i< listParticle.size(); i++){
   Particle p = listParticle.get(i); 
   Circle c = CircleBuilder.create()
     .centerX(p.x).centerY(p.y).radius(dmin)
     .build(); 
   listCircle.add(c);
  }
 }

 private void generateParticle() {
  listParticle = new ArrayList<>();
  for (int i = 0; i < numberParticle; i++) {
   Particle particle = new Particle(rand.nextDouble() * xwidth,
     rand.nextDouble() * ywidth, rand.nextDouble() * vmax,
     rand.nextDouble() * vmax);
   listParticle.add(particle);
  }
 }


 private void calculateCollision(){
  for(int i=0; i< listParticle.size(); i++){
   Particle p1  = listParticle.get(i); 
   for(int j= i+1; j< listParticle.size(); j++ ){
    Particle p2 = listParticle.get(j); 
    chekCollision(p1, p2);
   }
  }
 }
 
 public void chekCollision(Particle p1, Particle p2) {
  
  if (isOverlap(p1, p2)) {
   
   double deltaX = p1.x - p2.x;
   double deltaY = p1.y - p2.y;
   double distance =  Math.sqrt(deltaX * deltaX + deltaY * deltaY);
   deltaX = deltaX/ distance;
   deltaY = deltaY/distance; 
   
   double aci = p1.vx * deltaX + p1.vy * deltaY; 
   double bci = p2.vx * deltaX + p2.vy * deltaY; 
   
   double acf = bci; 
   double bcf = aci; 
   
   p1.vx = p1.vx + (acf - aci) * deltaX; 
   p1.vy = p1.vy + (acf - aci) * deltaY; 
   p2.vx = p2.vx + (bcf - bci) * deltaX; 
   p2.vy = p2.vy + (bcf - bci) * deltaY;
  }
 }

 private void moveParticle() {
  for (int i = 0; i < listParticle.size(); i++) {
   Particle p = listParticle.get(i);
   p.x += p.vx * timeStep;
   p.y += p.vy * timeStep;
  }
 }


 public void removeOverlap(ArrayList list) {
  for (int i = 0; i < list.size(); i++) {
   Particle p1 = list.get(i);
   for (int j = list.size() - 1; j > i; j--) {
    Particle p2 = list.get(j);
    if(isOverlap(p1, p2))
     list.remove(j); 
   }
  }
 }
 
 public void checkBoundary(){
  for(int i =0; i< listParticle.size() ;i++){
   Particle p = listParticle.get(i); 
   if(p.x > xmax - dmin)
    p.vx = -Math.abs(p.vx) ;
   if(p.x < xmin + dmin)
    p.vx = Math.abs(p.vx); 
   if(p.y > ymax - dmin)
    p.vy = - Math.abs(p.vy);
   if(p.y < ymin + dmin)
    p.vy = Math.abs(p.vy); 
  }
 }
 
 private boolean isOverlap(Particle p1, Particle p2){
  double deltax = p1.x - p2.x; 
  double deltay = p1.y - p2.y; 
  if(deltax * deltax + deltay * deltay < 4 * dmin * dmin)
   return true; 
  return false; 
 }
 
 public static void main(String[] args) {
  launch(args);
 }

 private class Particle {
  double x = 0.0;
  double y = 0.0;
  double vx = 0.0;
  double vy = 0.0;
  public Particle(double x, double y , double vx , double vy ) {
   this.x = x;
   this.y = y;
   this.vx  = vx ;
   this.vy = vy; 
  }
 }
}

No comments: