Showing posts with label javaFX. Show all posts
Showing posts with label javaFX. Show all posts

Wednesday, August 19, 2015

Aplikasi 17 Agustus (Aplikasi pemutar musik 3D di javaFX)

Kebetulan kemarin tanggal 17 Agustus yang merupakan hari ulang tahun kemerdekaan Indonesia maka saya mencoba membuat sebuah aplikasi yang ada kaitannya dengan 17 Agustus. Dan kebetulan saya akhir-akhir ini lagi tertarik bermain-main dengan library JavaFX maka saya akhirnya membuat aplikasi ini dengan JavaFX.

Sebenarnya saya bukan fanatikus JavaFX. Aplikasi yang saya buat ini adalah aplikasi pemutar musik. Dan untuk membuat aplikasi pemutar musik, maka kita harus membuat codec. Sayangnya untuk membuat codec tersebut, kita harus paham digital signal processing, paham bahasa c++, belajar dikit format-format codec yang sudah ada, antara lain mp3, flac, dll. Dan di luar sana, sudah banyak aplikasi pemutar musik yang tersedia yang menggunakan codec-codec yang sudah umum di pasaran. JavaFX kemudian hanya merangkai codec-codec tersebut ke dalam library pemutar media buatan mereka. Jadi yang saya lakukan bisa dibilang hanya sebatas memanggil library tersebut atau sekedar "building a car" bukan me-"reinventing the wheel".

Pada program kali ini, saya hanya menunjukkan beberapa teknik baku dalam implementasi element UI, misalnya bagaimana drag n drop file ke listview. Atau bagaimana memodifikasi item-item yang terdapat pada listview.

Perhatikan video berikut:



Source code:
https://github.com/gunungloli666/3dEqualizer

Sekedar referensi:
https://en.wikipedia.org/wiki/Audio_coding_format
https://en.wikipedia.org/wiki/Codec

Tuesday, August 18, 2015

Tutorial Data Binding di JavaFX

Dalam tutorial kali ini saya akan memberikan  contoh bagaimana menggunakan konsep data binding di JavaFX. Sebenarnya ini terinspirasi dari sebuah kursus yang saya ikuti kemarin mengenai penggunaaan WPF (Windows Presentation Foundation) yang konsepnya juga hampir mirip dengan JavaFX.

Defenisi data binding sendiri adalah bagaimana perubahan yang dialami oleh sebuah element UI (misalnya button) langsung direfleksikan pada elemen UI yang lain (misalnya panel atau window). Jadi ketika kita merubah lebar button, maka lebar panel secara serta merta ikutan berubah. Dalam konsep  UI jadul, misalnya yang dijumpai pada Jawa Swing, hal ini amat rumit untuk diaplikasikan. Bisa sih, namun kode yang kita buat agak kotor. Kita mungkin bisa mendeteksi event click mouse yang dilakukan pada sebuah button atau event mouse drag. Namun bagaimana kalau sudah hal-hal semisal perubahan property lebar atau tinggi button tersebut, tentu cukup sulit untuk mendeteksinya. Salah satu cara kotor untuk menempuhnya antara lain dengan membuat sebuah thread yang secara periodik mengecek perubahan pada ukuran button tersebut untuk kemudian merefleksikannya pada panel.

Pada WPF, teknik data binding biasanya dilakukan secara langsung pada XAML. Namun kemudian saya menyadari bahwa cara ini kurang tepat. Hal ini karena  antara property-property yang di-binding biasanya tidak proporsional nilainya. Misalnya jika kita mem-binding lebar button dengan lebar panel (atau window), tentu itu kurang tepat karena secara umum lebar button jauh lebih kecil dari lebar panel. Jadi kita harus melakukan operasi aritmatika di situ. Misalnya dengan mengeset lebar button setengah atau seperempat dari lebar window.

Masalahnya adalah kita tidak bisa menyertakan operasi aritmatika ke dalam file XAML (atau sebut saja XML). Jadi mau tidak mau kita harus letakkan pengesetan tersebut pada code behind-nya. Dan ini sudah diantisipasi oleh JavaFX.

Jadi dalam JavaFX tersebut sebuah UI element (misalnya window) properti propertynya sudah mengantisipasi adanya event yang terjadi. Dalam Java Swing, sebuah window (atau dalam terminologinya disebut sebagai JFrame) hanya memiliki width atau height saja. Dalam JavaFX, sebuah window (atau dalam terminologinya disebut sebagai Stage) width dan height nya itu sudah berubah menjadi widthProperty dan heightProperty yang bisa diberikan listener jika terjadi sebuah event. Perhatikan video berikut.



Dalam video di atas, jelas bahwa lebar button tidak mungkin di binding secara langsung dengan lebar window, akan tetapi harus dikalikan sebuah rasio tertentu.

Video berikut ini sebuah contoh bagaimana di JavaFX terdapat objek khusus yakni objek text yang tidak dijumpai pada WPF.

package fjr.test.binding;

import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class TestBindingButton extends Application{

 Stage prStage; 
 
 Button testButton; 
 
 @Override
 public void start( Stage primaryStage) throws Exception {
  
  AnchorPane pane = new AnchorPane();
  
  Scene sc  = new Scene(pane, 500, 400) ; 
  
  primaryStage.setScene(sc); 
  
  primaryStage.show();
  
  this.prStage = primaryStage; 
  
  getChildren(pane);
  
  primaryStage.widthProperty().addListener(new InvalidationListener() {
   
   @Override
   public void invalidated(Observable arg0) {
    testButton.setPrefWidth(prStage.getWidth()/3.0) ;
    testButton.setPrefHeight(prStage.getHeight()/10.0);
   }
  });
   
 }
 
 
 private void getChildren(Pane p){
  
  VBox box1 = new VBox(); 
  box1.setSpacing(5);
  box1.setTranslateX(10);
  box1.setTranslateY(10);
  
  
  final Button b1 = new Button("COBA"); 
  b1.setPrefWidth(prStage.getWidth()/3.0) ;
  b1.setPrefHeight(prStage.getHeight()/10.0);  
  box1.getChildren().add(b1); 
  
  
  final Button b2 = new Button("ACUAN"); 
  b2.setPrefWidth(prStage.getWidth()/3.0) ;
  b2.setPrefHeight(prStage.getHeight()/10.0);  
  box1.getChildren().add(b2); 
  
  this.testButton = b1; 
  
  p.getChildren().addAll(box1); 
 }
 
 public static void main(String[] args){
  launch(args);
 }
}
package fjr.test.binding;

//import java.awt.Button;
import java.util.Random;

import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Orientation;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;


public class TestBindingFont extends Application{
 
    private static final Random RANDOM = new Random();
    private static final Interpolator INTERPOLATOR = Interpolator.SPLINE(0.295,0.800,0.305,1.000);
    Pane p; 
    
    Font font = new Font (Font.getDefault().getFamily(), 60 ) ; 

 @Override
 public void start(Stage primaryStage) throws Exception {
  // TODO Auto-generated method stub
  
  final AnchorPane pane = new AnchorPane(); 
  Scene sc = new Scene(pane, 500,500 ); 
  
  primaryStage.setScene(sc);
  primaryStage.show(); 
  
  pane.setFocusTraversable(true); 
  pane.setOnMousePressed(new EventHandler() {
   @Override
   public void handle(MouseEvent me) {
    pane.requestFocus();
    me.consume();
   }
  });
  
  pane.setOnKeyPressed(new EventHandler() {
   @Override
   public void handle(KeyEvent ke) {
    createLetter(ke.getText()); 
    ke.consume();
   }
  });
  
  p = pane; 
  
  final Slider s = new Slider(); 
  s.setTranslateX(10); 
  s.setTranslateY(10); 
  s.setPrefWidth(150); 
  s.setOrientation(Orientation.HORIZONTAL);
  s.setMin(1); 
  s.setMax(10); 
  s.valueProperty().addListener(new InvalidationListener() {   
   @Override
   public void invalidated(Observable arg0) {
    double m = s.getValue(); 
    Font f2  = new Font(Font.getDefault().getFamily(), 60 * m); 
    font = f2; 
    for( Node  t : p.getChildren()){
     if(t instanceof Text){
      Text tt = (Text) t; 
      tt.setFont(f2);
     }
     
    }
   }
  });
  
  VBox box = new VBox(); 
  box.setSpacing(5); 
  
  pane.getChildren().add(box);
  box.getChildren().add(s); 
  
  Text t  = new Text("Ketikan sesuatu di Keyboard!!"); 
  t.setTranslateX(10);
  t.setTranslateY(20); 
  Font f = Font.font(Font.getDefault().getFamily(), FontWeight.BOLD , FontPosture.ITALIC , 20); 
  
  t.setFont(f); 
  
  box.getChildren().add(t);  
  
  t.setFill( new LinearGradient(0f,1f,1f,0f,true, CycleMethod.NO_CYCLE, 
      new Stop(0,Color.web("#f8bd55")),
                        new Stop(0.14f,Color.web("#c0fe56")),
                        new Stop(0.28f,Color.web("#5dfbc1")),
                        new Stop(0.43f,Color.web("#64c2f8")),
                        new Stop(0.57f,Color.web("#be4af7")),
                        new Stop(0.71f,Color.web("#ed5fc2")),
                        new Stop(0.85f,Color.web("#ef504c")),
                        new Stop(1,Color.web("#f2660f"))));

 }
 
  private void createLetter(String c) {

         final Text letter = new Text(c);
         letter.setFill(Color.BLACK);
         letter.setFont(font);
         letter.setTextOrigin(VPos.TOP);
         letter.setTranslateX((p.getWidth() - letter.getBoundsInLocal().getWidth()) / 2);
         letter.setTranslateY((p.getHeight() - letter.getBoundsInLocal().getHeight()) / 2);

         p.getChildren().add(letter);

         final Timeline timeline = new Timeline();
         timeline.getKeyFrames().add(
                 new KeyFrame(Duration.seconds(6), new EventHandler() {
                     @Override public void handle(ActionEvent event) {
                         p.getChildren().remove(letter);
                     }
                 },
                 new KeyValue(letter.translateXProperty(), getRandom(0.0f, p.getWidth() - 
                   letter.getBoundsInLocal().getWidth()),INTERPOLATOR),
                 new KeyValue(letter.translateYProperty(), getRandom(0.0f, p.getHeight() - 
                   letter.getBoundsInLocal().getHeight()),INTERPOLATOR),
                 new KeyValue(letter.opacityProperty(), 0f)
         ));
         timeline.play();
     }
 
  private static float getRandom(double min, double max) {
         return (float)(RANDOM.nextFloat() * (max - min) + min);
     }
  
 public static void main(String[] args){
  launch(args);
 }

}

Saturday, August 23, 2014

Cross di dalam Lingkaran

qewqewq
Dalam postingan kali ini sebenarnya saya ingin berbagi permasalahan sekaligus ilmu yang mungkin bermanfaat bagi pembaca sekalian. Gambar di atas dikutip dari bukunya Edwin J.Purcell pada bab aplikasi turunan/derivatif, spesifiknya bagaimana menggunakan turunan dalam memecahkan persoalan optimisasi. Saya kemarin sudah mencoba menjawab soal ini, namun saya merasa kurang confidence dengan jawaban saya. Entah di mana kesalahan penurunannya yang menggiring saya pada kesimpulan bahwa sudut yang dimaksud adalah nol.
Akhirnya saya buat sebuah aplikasi yang mengkonfirmasi dugaan saya itu. Dan ternyata dugaan saya salah. Nilai maksimumnya bukan terletak ketika sudutnya sama dengan nol seperti yang terlihat pada gambar di bawah ini. Animasinya saya buat dengan JavaFX, sebagaimana animasi lainnya di blog ini. Dan Anda bisa melihat source-nya di Github. Dan saya sampai sekarang belum menemukan kesalahan penurunannya, atau jawaban yang benar untuk membuktikan nilai sudut ketika luas cross di dalam lingkaran tersebut bernilai maksimum.
Mudah-mudahan ada pembaca yang tertarik menyelesaikan persoalan di atas untuk mendapatkan jawaban analitisnya.
1

Sunday, May 11, 2014

Gerak Pada Bidang Miring: Kesalahan Terbesar Microsoft Excel VBA

Dalam tulisan kali ini saya sedikit berbagi pengalaman dalam menggunakan Microsoft Excel. Kebetulan kemarin saya mencoba membuat sebuah animasi mengenai bidang miring dengan menggunakan VBA ala Microsoft Excel. Masalahnya adalah rumusan saya tentang posisi kotak pada bidang miring itu sudah benar 100 persen. Yang bikin heran kok hasil penggambaran di Microsoft Excel tidak sesuai yang saya harapkan?

Gambar 1. Geometri Bidang Miring


Pada gambar 1, dengan mengingat bahwa koordinat monitor menggunakan titik asal pada TOP-LEFT, maka dapat diberlakukan persamaan: \begin{eqnarray} x_1 = x_2 - w \cdot \sin(\alpha) \\ y_1 = y_2 + w \cdot \cos(\alpha) \end{eqnarray} yang mengakibatkan \begin{eqnarray} x_2 = x_1 + w \cdot \sin (\alpha) \\ y_2 = y_1 - w \cdot \cos(\alpha) \end{eqnarray} Nah, rumusan ini berhasil saya terapkan dengan sempurna di JavaFX. Tapi entah kenapa ketika saya ganti ke VBA dari Microsoft Excel, semuanya kacau dan tidak sesuai harapan. Yang saya heran di Microsoft Excel, kok menghitung fungsi matematika yang berkaitan dengan trigonometri, dia menggunakan sudut radian. Ketika dia menentukan rotasi suatu objek, dia menggunakan sudut derajat. Mungkin saja ini penyebabnya atau satu dan lain hal, saya juga g tau.

Berikut ini adalah cuplikan video hasil implementasi dari rumus di atas di JavaFX:



Sementara source code untuk implementasi tersebut dapat anda lihat di github.

Adapun video dari keanehan Microsoft Excel yang dimaksud adalah:



di mana contoh implementasinya adalah
Sub TestAnimasi()
    total_iterasi = 0
    
    Dim shape As shape
    namaBenda = "Kotak"
    
    Set shape = Sheet1.shapes(namaBenda)
    Dim segitiga As shape
    Set segitiga = Sheet1.shapes("Bidang Miring")
    
    lokasiSegitigaX = segitiga.Left
    lokasiSegitigaY = segitiga.Top
    
    panjang = segitiga.Width
    lebar = segitiga.Height
    
    ' Hitung kemiringan bidang
    Dim sudutDerajat As Double
    sudut = Math.Atn(lebar / panjang)
    sudutDerajat = sudut / WorksheetFunction.Pi * 180
    shape.Rotation = sudutDerajat
    
    'Hitung default posisi
    defaultX = segitiga.Left + shape.Height * Sin(sudut)
    defaultY = segitiga.Top - shape.Height * Cos(sudut)
    
    'Letakkan kotak pada bagian awal dari bidang miring
    shape.Left = defaultX
    shape.Top = defaultY
    
    n = Sheet1.Cells(12, 13)
  
    Sheet1.Cells(22, 16) = sudutDerajat
    
    panjangLintasan = Math.Sqr(lebar * lebar + panjang * panjang)
    spasiLintasan = panjangLintasan / n
    lintasanAwal = 0
     Do
        DoEvents
        shape.Left = shape.Left + spasiLintasan * Cos(sudut)
        shape.Top = shape.Top + spasiLintasan * Sin(sudut)
        total_iterasi = total_iterasi + 1
        lintasanAwal = lintasanAwal + spasiLintasan
        timeout (0.2)
    Loop Until total_iterasi = n - 1
End Sub

Sub timeout(waktu As Double)
    StartTime = Timer
    Do
    DoEvents
    Loop Until (Timer - StartTime) >= waktu
End Sub

Monday, April 21, 2014

Penggunaan FXML dalam membentuk GUI dengan javaFX

Dalam kesempatan kali ini saya memberikan sedikit contoh penggunaan FXML dalam membentuk GUI di javaFX. FXML sendiri merupakan penarapan konsep MVC di java yakni dengan menggunakan bantuan java reflection. Terdapat beberapa tag yang cukup penting dalam javaFX. Yakni fx:include dan fx:root. Tutorial ini mencoba memberikan proof of concept mengenai tag yang kedua. Tag fx:root digunakan untuk melompati pendefenisian controller pada  file fxml. Jadi dengan tag ini maka class controller yang digunakan di-inisialisasi selama runtime.  Untuk maksud ini, maka terdapat  menu option pada javaFX scenebuilder yang bisa digunakan  yang bisa dilihat pada gambar berikut

:

Sehingga file fxml yang  sebelumnya berbentuk: <!--xml version="1.0" encoding="UTF-8"?--> <!--import java.lang.*?--> <!--import java.util.*?--> <!--import javafx.scene.*?--> <!--import javafx.scene.control.*?--> <!--import javafx.scene.layout.*?--> <AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="fjr.example.mediaplayer.playlist.PlayerController"> <children> <StackPane layoutX="49.0" layoutY="14.0" prefHeight="150.0" prefWidth="200.0" /> </children> </AnchorPane> akan berubah menjadi <!--xml version="1.0" encoding="UTF-8"?--> <!--import java.lang.*?--> <!--import java.util.*?--> <!--import javafx.scene.*?--> <!--import javafx.scene.control.*?--> <!--import javafx.scene.layout.*?--> <fx:root type="javafx.scene.layout.AnchorPane" id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2"> <children> <StackPane layoutX="49.0" layoutY="14.0" prefHeight="150.0" prefWidth="200.0" /> </children> </fx:root> Nah agar properti dari file fxml tersebut bisa diakses, maka dalam kelas controller kita harus defenisikan root-nya yang contohnya bisa dilihat pada potongan berikut:
    public PlayerController(Group root) throws IOException {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("Player.fxml"));
        loader.setController(this);
        loader.setRoot(this);
        loader.load();
        root.getChildren().add(this);
        this.root = root;
        this.mainStage = (Stage) root.getScene().getWindow();
    }
Untuk melengkapi tutorial kali ini, saya memberikan sebuah proyek kecil yakni membuat sebuah media player dengan playlist dengan menggunakan javaFX. Playlist yang dimaksud dapat digunakan layaknya playlist di media player yang biasa kita kenal (semacam winamp, jet audio atau lain-lain) yakni dapat di-repeat kembali ke daftar pertama ketika lagu terakhir selesai. Bagi pembaca yang tertarik mencoba, dapat meng-clone source code-nya di github: https://github.com/gunungloli666/example-media-player-with-playlist/tree/master/src/fjr/example/mediaplayer/playlist

Sunday, March 16, 2014

Clock follow Mouse di javaFX

Dalam demo ini saya sebenarnya ingin menunjukkan beberapa konsep pada javaFX yang sebenarnya kurang sempurna, atau mungkin saya saja yang belum tahu persis teknik yang paling bagus. Yakni bagaimana menjalankan beberapa animasi yang mengalami delay satu sama lain. Misalnya TranslateTransition di javaFx itu mau tidak mau harus dibikin secara bersamaan mulainya. Tidak bisa kita memulai secara terpisah. Ketika kita panggil perintah animation play, maka otomatis kesemuanya instance dari TranslateTransition itu jalan secara serta merta. Padahal saya menginginkan suatu kasus di mana antara masing-masing instance terdapat delay sehingga satu instance akan lebih dulu jalan ketimbang yang lain, dan pada masing-masing instance terdapat delay waktu. Saya mendapati suatu teknik yang bisa dipake, hanya saja teknik ini bisa dianggap kotor (dirty) dalam pemrograman javaFX, dan memang tidak sesuai dengan yang saya harapkan. Kotor dalam artian bukan pendekatan yang seharusnya diambil. Yakni dengan menggunakan java default Thread. Hasil yang diperoleh adalah Scenegraph dalam Thread berjalan dengan berat dan lambat. Demonya bisa dilihat di video berikut:



Makanya saya tidak bisa mewujudkan keinginan saya untuk merespon koordinat mouse saat ini bukan sekedar posisi klik dari mouse. Karena memang berat.

Concurrent programming untuk javaFX menggunakan Task dan Service class. Masalahnya adalah kedua kelas ini tidak bisa digunakan untuk memanipulasi Scenegraph. Hanya untuk keperluan-keperluan lain misalnya mendownload video, mengextract file atau lain-lain di luar manipulasi scenegraph. Sementara kita tidak mungkin menginterupsi main Thread dari program untuk memberi delay dari animasi.

Untuk source dari demo di atas Anda bisa lihat di sini:
https://github.com/gunungloli666/jam_follow_mouse/blob/master/src/fjr/mouse/follower/MouseFollowerPlugin.java

Saturday, February 8, 2014

Animasi Convex Hull

Convex hull didefenisikan sebagai sutau objek di mana garis yang menghubungkan titik-titik di dalam objek tersebut juga berada di dalam objek tersebut. Untuk menentukan convex hull dari kumpulan titik, terdapat beberapa algoritma yang digunakan, salah satunya adalah monotone chain. Animasinya dapat dilihat pada video berikut



Sementara kode untuk menggenerate-nya di javaFX dapat dilihat pada
https://gist.github.com/gunungloli666/8859178

Thursday, January 30, 2014

Trammel Archimedes di JavaFX

Berikut ini saya berikan contoh animasi javaFX yakni dalam membuat trammel Archimedes.



Adapun source code nya adalah
package fjr.ellipse;

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.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;


// @author moh_fajar 
// @date: 31-1-2014

public class EllipseExp extends Application {

 public static void main(String[] args) {
  launch(args);
 }

 Timeline animation;
 GraphicsContext gc; 
 Canvas canvas; 
 Trammel tr ;
 
 double angle; 

 double canvasWidth = 450 , canvasHeight = 350 ; 
 @Override
 public void start(Stage primaryStage) throws Exception {
  Group root = new Group();
  Group child = new Group(); 
  primaryStage.setScene(new Scene(root,500, 500));
  angle = 45; 
  tr = new Trammel(200, 150 , 100, 60, angle);
  canvas = new Canvas(canvasWidth, canvasHeight); 
  gc = canvas.getGraphicsContext2D(); 
  root.getChildren().add(child); 
  child.getChildren().addAll(canvas);
  animation = new Timeline();
  animation.getKeyFrames().addAll(
    new KeyFrame(Duration.millis(10),
      new EventHandler() {
       @Override
       public void handle(ActionEvent arg0) {
        angle-= Math.PI/200 ; 
        tr.setAngle(angle);
        tr.calculatePosition();
        draw(gc);
       }
      }));

  primaryStage.show();
  
  animation.setCycleCount(Timeline.INDEFINITE);
  animation.setAutoReverse(false);
  animation.play(); 
 }
 
 void draw(GraphicsContext gc){
  gc.setFill(Color.WHITE);
  gc.fillRect(0, 0, canvasWidth, canvasHeight);
  gc.setStroke(Color.BLACK);
  gc.strokeRect(0, 0, canvasWidth, canvasHeight);
  gc.setFill(Color.RED);
  gc.fillRect(tr.x, tr.y, 5, 5);
  gc.setFill(Color.BLUE);
  gc.fillRect(tr.x1, tr.y1, 5, 5);
  
  gc.setFill(Color.GREENYELLOW);
  gc.fillRect(tr.x2, tr.y2, 5, 5);
  
  gc.setStroke(Color.RED);
  gc.strokeLine(tr.x, tr.y, tr.x2, tr.y2);
  
  
  gc.setStroke(Color.BLACK);
  gc.strokeLine(tr.x2, tr.y1 - 110 , tr.x2, tr.y1 + 110);
  gc.strokeLine(tr.x2- 200, tr.y1  , tr.x2 + 200, tr.y1 );
  
  gc.setStroke(Color.BLACK);
  gc.strokeOval(tr.getXCorrection() -   tr.getA(), tr.getYCorrection() -  
      tr.getB(), 2 * tr.getA() , 2 * tr.getB());
 }

 class Trammel {

  double angle;
  double p, q;

  public double x, y; // posisi dari rod
  public double x1, y1 ; // posisi dari pivot 1 
  public double x2, y2 ; // posisi dari pivot 2

  
  double xCorrection; 
  double yCorrection; 
  /*
   * Dalam Trammel Archimedes, terdapat dua titik pivot yakni A dan B.
   * Sementara yang men-trace elipse adalah titik C pada ujung trammel.
   * Jarak A ke B adalah p, sementara jarak B ke C adalah q, dengan
   * demikian lintasan titik C ditentukan oleh x = (p + q) cos \theta y =
   * q sin theta.  lihat: http://en.wikipedia.org/wiki/Trammel_of_Archimedes
   */

  public Trammel(double x, double y, double p, double q, double angle) {
   this.xCorrection = x; 
   this.yCorrection = y; 
   
   this.p = p;
   this.q = q;
   this.angle = angle; 
  }

  
  public void setAngle(double angle) {
   this.angle = angle;
  }

  public void calculatePosition() {
   x = xCorrection+  (p + q) * Math.cos(angle);
   y = yCorrection + (q) * Math.sin(angle);
   
   x1 = x - q * Math.cos(angle); 
   y1 = y -  q * Math.sin(angle);
   
   x2 = x1 -   p * Math.cos(angle); 
   y2 = y1 -   p * Math.sin(angle); 
  }
  
  public double getA(){return (p+q);}
  public double getB(){return q ;}
  public double getXCorrection() {return xCorrection;}
  public double getYCorrection() {return yCorrection; }
 }

}

Wednesday, January 8, 2014

Menyimpan gambar di canvas ke file di javaFX

Berikut ini saya berikan contoh bagaimana menyimpan hasil penggambaran free-form kita dalam javaFx canvas ke dalam file.
package fjr.test.testIO;
   import java.io.File;

import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.*;
import javafx.scene.image.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.stage.*;

import javax.imageio.ImageIO;

import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.paint.Color;


public class PrintCanvas extends Application {

 GraphicsContext gc; 
 Canvas canvas; 
 double canvaswidth  = 300 , canvasheight = 350 ; 
 
    @Override
    public void start(Stage primaryStage) {
        Group root = new Group();
        Scene scene = new Scene(root, 400, 450);
        canvas = new Canvas(canvaswidth, canvasheight);
        canvas.setTranslateX(10);
        canvas.setTranslateY(50);
        gc = canvas.getGraphicsContext2D();
        gc.setLineWidth(23);
        gc.setFill(Color.GREEN);
        gc.setStroke(Color.BLUE);
        gc.setLineWidth(6);
        
        gc.strokeRect(0, 0, canvaswidth, canvasheight);
                
        gc.setStroke(Color.BLACK);
        
        final WritableImage wim = new WritableImage((int)canvaswidth, (int)canvasheight);
        root.getChildren().add(canvas); 
        primaryStage.setScene(scene);
        primaryStage.show();
        
        final  Button simpan = new Button();
        simpan.setTranslateX(10);
        simpan.setTranslateY(10);
        simpan.setPrefSize(100, 30);
        simpan.setText("SIMPAN");
  simpan.setOnAction(new EventHandler() {
   @Override
   public void handle(ActionEvent arg0) {
    canvas.snapshot(null, wim);
    File file = new File("E:/CanvasImage.png");
    try {
     ImageIO.write(SwingFXUtils.fromFXImage(wim, null), "png",
       file);
     System.out.println("File sudah disimpan"); 
    } catch (Exception s) {
    }
   }
  });

  root.getChildren().add(new HBox(){{
   getChildren().add(simpan);
  }}); 
  

  canvas.addEventHandler(MouseEvent.MOUSE_DRAGGED, new EventHandler() {
            @Override
            public void handle(MouseEvent e) {
             gc.lineTo(e.getX(), e.getY());
             gc.stroke(); 
            }
        });
  
  canvas.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler() {
            @Override
            public void handle(MouseEvent e) {
             gc.beginPath();
             gc.moveTo(e.getX(), e.getY());
             gc.stroke();
            }
        });

    }

    public static void main(String[] args) {
        launch(args);
    }

}
Hasilnya adalah


Tuesday, January 7, 2014

Cara menggabungkan javaFX dengan java Swing

Tanpa perlu berbasa-basi berikut saya berikan contoh menggabungkan teknologi javaFX dengan java Swing. Berdasarkan informasi yang saya peroleh javaFX mungkin kedepannya dipake untuk menggantikan Swing.
package fjr.test.javafxpanel;

import java.awt.Dimension;

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class TestJavaFXPanel {

 private XYChart.Series hourDataSeries;
    private XYChart.Series minuteDataSeries;
    private NumberAxis xAxis;
    private Timeline animation;

    private double hours = 0;
    private double minutes = 0;
    private double timeInHours = 0;
    private double prevY = 10;
    private double y = 10;
    
 public TestJavaFXPanel() {
  initAndShowGUI();
 }

 private void initAndShowGUI() {
  JFrame frame = new JFrame("Test Swing");
  frame.setPreferredSize(new Dimension(600, 400));
  frame.pack();
  final JFXPanel fxPanel = new JFXPanel();
  frame.add(fxPanel);
  frame.setVisible(true);

  Platform.runLater(new Runnable() {
   @Override
   public void run() {
    initFX(fxPanel);
   }
  });
 }

 private void initFX(JFXPanel fxPanel) {
  Scene scene = createScene();
  fxPanel.setScene(scene);
  
   animation = new Timeline();
         animation.getKeyFrames().add(new KeyFrame(Duration.millis(1000/60), new EventHandler() {
             @Override public void handle(ActionEvent actionEvent) {
                 for(int count=0; count < 6; count++) {
                     nextTime();
                     plotTime();
                 }
             }
         }));

         animation.setCycleCount(Animation.INDEFINITE);
 }

 private Scene createScene() {
  StackPane root = new StackPane(){{
   setTranslateX(0);
   setTranslateY(0);
  }};
  
  Button button = new Button("PLAY"){{
   setMinSize(100, 30);
   setOnAction(new EventHandler() {
    
    @Override
    public void handle(ActionEvent arg0) {
      play();
    }
   });
  }};
  
  
  Button button2 = new Button("STOP"){{
   setMinSize(100, 30);
   setOnAction(new EventHandler() {
    
    @Override
    public void handle(ActionEvent arg0) {
     // TODO Auto-generated method stub
      stop();
    }
   } );
  }};
  
  HBox box = new HBox(){{
   setSpacing(10);
   setTranslateX(10);
   setTranslateY(10);
  }}; 
  
  box.getChildren().addAll(button, button2);
  
  root.getChildren().addAll( new BorderPane(){{
   setLeft(createChart());
  }} , box); 
  
  Scene sc = new Scene(root);
  return sc;
 }

 public static void main(String[] args) {
  SwingUtilities.invokeLater(new Runnable() {
   @Override
   public void run() {
    new TestJavaFXPanel();
   }
  });
 }
    protected LineChart createChart() {
         xAxis = new NumberAxis(0,24,3);
         final NumberAxis yAxis = new NumberAxis(0,100,10);
         final LineChart lc = new LineChart(xAxis,yAxis);
         lc.setCreateSymbols(false);
         lc.setAnimated(false);
         lc.setLegendVisible(false);
         lc.setTitle("PLOT GRAFIK");
         xAxis.setForceZeroInRange(false);
         yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis,"$",null));
         hourDataSeries = new XYChart.Series();
         minuteDataSeries = new XYChart.Series();
         hourDataSeries.getData().add(new XYChart.Data(timeInHours,prevY));
         minuteDataSeries.getData().add(new XYChart.Data(timeInHours,prevY));
         for (double m=0; m<(60); m++) {
             nextTime();
             plotTime();
         }
         lc.getData().add(minuteDataSeries);
         lc.getData().add(hourDataSeries);
         
         lc.setTranslateX(10);
         lc.setTranslateY(40);
         lc.setMaxWidth(400);
         lc.setMaxHeight(330);
         return lc;
     }

     private void nextTime() {
         if (minutes == 59) {
             hours ++;
             minutes = 0;
         } else {
             minutes ++;
         }
         timeInHours = hours + ((1d/60d)*minutes);
     }
     private void plotTime() {
         if ((timeInHours % 1) == 0) {
             double oldY = y;
             y = prevY - 10 + (Math.random()*20);
             prevY = oldY;
             while (y < 10 || y > 90) y = y - 10 + (Math.random()*20);
             hourDataSeries.getData().add(new XYChart.Data(timeInHours, prevY));
             if (timeInHours > 25) hourDataSeries.getData().remove(0);
             if (timeInHours > 24) {
                 xAxis.setLowerBound(xAxis.getLowerBound()+1);
                 xAxis.setUpperBound(xAxis.getUpperBound()+1);
             }
         }
         double min = (timeInHours % 1);
         double randomPickVariance = Math.random();
         if (randomPickVariance < 0.3) {
             double minY = prevY + ((y-prevY) * min) - 4 + (Math.random()*8);
             minuteDataSeries.getData().add(new XYChart.Data(timeInHours,minY));
         } else if (randomPickVariance < 0.7) {
             double minY = prevY + ((y-prevY) * min) - 6 + (Math.random()*12);
             minuteDataSeries.getData().add(new XYChart.Data(timeInHours,minY));
         } else if (randomPickVariance < 0.95) {
             double minY = prevY + ((y-prevY) * min) - 10 + (Math.random()*20);
             minuteDataSeries.getData().add(new XYChart.Data(timeInHours,minY));
         } else {
             double minY = prevY + ((y-prevY) * min) - 15 + (Math.random()*30);
             minuteDataSeries.getData().add(new XYChart.Data(timeInHours,minY));
         }
         if (timeInHours > 25) minuteDataSeries.getData().remove(0);
     }
    public void play() {
         animation.play();

     }
    
      public void stop() {
         animation.pause();
     }    

  
}
Potongan hasil eksekusinya adalah sebagai berikut

Saturday, January 4, 2014

Menggunakan radio button dengan benar di JavaFX

Berikut ini adalah contoh source cara menggunakan radio button yang benar di JavaFX:
package fjr.test.toggle;

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.RadioButton;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class TestToggleA extends Application {
 
 static enum Cuaca{
  CERAH , BERAWAN
 }

 Cuaca cuaca_now = Cuaca.CERAH; 
 
 public static void main(String[] args){
  Application.launch(args);
 }

 ToggleGroup groupToggle ; 
 
 RadioButton radio1 , radio2 ; 

 @Override
 public void start(Stage stage) throws Exception {
  Group root = new Group(); 
  
  groupToggle = new ToggleGroup(); 

  HBox box = new HBox(){{
   setTranslateX(30);
   setTranslateY(30);
   setSpacing(10);
   
   getChildren().addAll(
     radio1 = new RadioButton("BERSINAR"){{
      setToggleGroup(groupToggle);
      setUserData(Cuaca.CERAH);
     }}, 
     
     radio2 = new RadioButton("GELAP"){{
      setToggleGroup(groupToggle);
      setUserData(Cuaca.BERAWAN);
     }}); 
  }}; 
  
  
  switch(cuaca_now){
  case CERAH:
   radio1.setSelected(true);
   break; 
  case BERAWAN:
   radio2.setSelected(true);
   break; 
  }
  
  root.getChildren().add(box); 
  stage.setScene(new Scene(root, 200,200));
  stage.show(); 
  
  
  System.out.println("Type cuaca saat inisialisasi: "+ cuaca_now); 
  
  groupToggle.selectedToggleProperty().addListener(new ChangeListener() {
   @Override
   public void changed(ObservableValue arg0,
     Toggle arg1, Toggle toggle) {
    // TODO Auto-generated method stub
    RadioButton radio = (RadioButton) toggle; 
    Cuaca c = (Cuaca) radio.getUserData(); 
    cuaca_now = c; 
    System.out.println("Type cuaca saat pergantian button: "+cuaca_now);
   }
  });
  
 }
}

Tuesday, September 3, 2013

Barnes-hut tree in JavaFX

Pada kesempatan kali ini saya akan membagikan sedikit pengalaman saya dalam javaFX kepada pembaca semua yang mungkin bisa mengambil manfaat. Sekaligus mempopulerkan blog saya ini.
Yang saya akan ungkapkan kali ini adalah algoritma barnes-hut yang merupakan algoritma yang biasa digunakan dalam software-software simulasi fisika (semisal molekular dinamik dan semisalnya) [1].

Kali ini saya belum membahas tentang bagaimana algoritma tersebut digunakan untuk menghitung interaksi dalam simulasi. Akan tetapi yang saya lakukan adalah memberi gambaran dasar bagaimana algoritma tersebut bekerja. Tepatnya saya akan melakukan penggambaran animasi barnes-hut tree yang merupakan elemen inti dari algoritma ini.

Mengapa algoritma ini digunakan, adalah karena efisiensinya yang lebih bagus ketimbang skema brute-force yang biasa kita gunakan. Di mana kalo pake brute-force itu kompleksitasnya sampe O(N^2) maka dalam barnes-hut ini, kompleksitasnya adalah O(N log N).



Untuk source-nya dapat anda download di link berikut:
https://docs.google.com/file/d/0B1irLqfPwjq0YXA0emt2WWMtNkU/edit?usp=sharing


Referensi:
1. http://arborjs.org/docs/barnes-hut

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; 
  }
 }
}

Monday, August 19, 2013

Deteksi tumbukan dengan javaFX

Pada tutorial ini saya akan memberikan  bagaimana cara mendeteksti tumbukan dengan javaFX yang previewnya dapat dilihat pada gambar berikut:


Adapaun source codenya adalah:
package fjr.collision;

import java.util.Random;

import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
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;

public class TestCollision extends Application {

 double r = 14.0;
 Random rand = new Random();

 double xwidth = 400;
 double ywidth = 400;

 int numberParticle = 100;

 // uji tumbukan
 public boolean testCol(Circle p1, Circle p2) {
  double dx = p1.getCenterX() - p2.getCenterX();
  double dy = p1.getCenterY() - p2.getCenterY();
  if (dx * dx + dy * dy < 4 * r * r) {
   return true;
  }
  return false;
 }

 public void removeOverlap(ObservableList list) {
  for (int i = 0 ; i< list.size() ; i++ ) {
   if (list.get(i) instanceof Circle) {
    Circle p1 = (Circle) list.get(i);
    for (int j = list.size()- 1 ; j   > i ; j-- ) {
     if (list.get(j) instanceof Circle) {
      Circle p2 = (Circle) list.get(j);
      if (testCol(p1, p2)) {
       list.remove(j);
      }
     }
    }
   }
  }
 }

 public static void main(String[] args) {
  launch(args);
 }

 Button button, button1;
 boolean state = true;

 @Override
 public void start(Stage primaryStage) throws Exception {

  final Group root = new Group();
  primaryStage.setScene(new Scene(root, xwidth, ywidth));

  for (int i = 0; i < numberParticle; i++) {
   root.getChildren().add(
     CircleBuilder.create().centerX(rand.nextDouble() * xwidth)
       .centerY(rand.nextDouble() * ywidth).radius(r)
       .build());
  }

  button = ButtonBuilder.create().text("ORGANIZE").translateX(10)
    .translateY(10).build();
  button1 = ButtonBuilder.create().text("RESET").translateX(100)
    .translateY(10).build();

  root.getChildren().addAll(button, button1);

  button.setOnAction(new EventHandler() {

   @Override
   public void handle(ActionEvent arg0) {
    if (state) {
     removeOverlap(root.getChildren());
    }
    state = false;
   }
  });

  button1.setOnAction(new EventHandler() {

   @Override
   public void handle(ActionEvent arg0) {
    for (int i = root.getChildren().size() - 1; i >= 0; i--) {
     root.getChildren().remove(i);
    }

    for (int i = 0; i < numberParticle; i++) {
     root.getChildren().add(
       CircleBuilder.create()
         .centerX(rand.nextDouble() * xwidth)
         .centerY(rand.nextDouble() * ywidth)
         .radius(r).build());
    }
    root.getChildren().addAll(button, button1);
    state = true;
   }
  });

  primaryStage.show();

 }
}

Sunday, July 14, 2013

Rut-etra-izer versi JavaFX

Pada tutorial kali ini saya akan mendemonstrasikan salah satu kegunaan dari JavaFX yakni dalam melakukan translate terhadap salah satu aplikasi berbasis javascript yakni  Rut-etra-izer  kedalam bahasa java. Kebetulan saya agak penasaran dengan kecanggihan aplikasi ini. Saya baru tahu soalnya  dan saya harap pembaca bisa mengambil manfaat dari tutorial ini. Adapun hasil mentahnya dapat dilihat pada gambar berikut:

package fjr.main.rutte.traizer;

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelReader;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathBuilder;
import javafx.scene.shape.QuadCurve;
import javafx.scene.transform.Affine;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;

public class Rutte extends Application {

  static String main = Rutte.class.getResource("mami.jpg").toExternalForm();
 int step = 5;
 int stepY = 50; 
 double depth = 56;
 double width = 0 ;
 double  height = 0; 

 @Override
 public void start(Stage primaryStage) throws Exception {
  Group root = getEsembel();
  primaryStage.setScene(new Scene(root));
  primaryStage.setTitle("TUT WURI HANDAYANI");
  primaryStage.show();
 }

 public Group getEsembel() {
  Group root = new Group();
  Image image = new Image(main);
  width = image.getWidth();
  height = image.getHeight();

  PixelReader pixel = image.getPixelReader();

  double originX = 0;
  double destinyX = 0;
  double originY = 100;
  double destinyY = 0.0;

  Canvas canvas = new Canvas(width+50,height+50); 
  GraphicsContext gc = canvas.getGraphicsContext2D(); 
  double shifty  =0; 
  for(int y = 0 ; y < height; y+= step){
   for(int x = 0; x < width-step ;x+= step){
    Color color = pixel.getColor(x, y);
    Color color1 = pixel.getColor(x+step, y);
    double brightness = color.getBrightness();
    double brightness1 = color1.getBrightness(); 
    originX = x ; 
    destinyX = x+step; 
    originY = -  brightness*depth+brightness/2 + shifty;
    destinyY = -  brightness1*depth+brightness1/2+ shifty;
    gc.setStroke(color);
    this.rotateX(gc, -30);
    gc.strokeLine(originX,originY, destinyX, destinyY );
    originX  = destinyX ; 
    originY = destinyY; 
   }
   shifty+= 5;
  }
  root.setTranslateX(20); 
  root.setTranslateY(20);
  root.getChildren().add(canvas);
  
  return root;
 }
 
 /*
  * I'm not found the way to rotate GraphicContext with simple way like other node
  */
 static void rotateX(GraphicsContext gc , double angle){
  Affine affine = new Affine(); 
  double angleRadi = Math.toRadians(angle); 
  double sinus = Math.sin(angleRadi);
  double cosinus = Math.cos(angleRadi);
  affine.setMxx(1); 
  affine.setMxy(0); 
  affine.setMxz(0); 
  affine.setTx(0); 
  affine.setMyx(0); 
  affine.setMyy(cosinus);
  affine.setMyz(-sinus); 
  affine.setTy(0); 
  affine.setMzx(0); 
  affine.setMzy(sinus);
  affine.setMzz(cosinus);
  affine.setTz(0); 
  
  gc.setTransform(affine);
  
 }
 
 static void rotateY(GraphicsContext gc , double angle){
  Affine affine = new Affine(); 
  double angleRadi = Math.toRadians(angle); 
  double sinus = Math.sin(angleRadi);
  double cosinus = Math.cos(angleRadi);
  affine.setMxx(cosinus); 
  affine.setMxy(0); 
  affine.setMxz(sinus); 
  affine.setTx(0); 
  affine.setMyx(0); 
  affine.setMyy(1);
  affine.setMyz(0); 
  affine.setTy(0); 
  affine.setMzx(-sinus); 
  affine.setMzy(0);
  affine.setMzz(-cosinus);
  affine.setTz(0); 
  
  gc.setTransform(affine);
  
 }
 
 static void rotateZ(GraphicsContext gc , double angle){
  Affine affine = new Affine(); 
  double angleRadi = Math.toRadians(angle); 
  double sinus = Math.sin(angleRadi);
  double cosinus = Math.cos(angleRadi);
  affine.setMxx(cosinus); 
  affine.setMxy(-sinus); 
  affine.setMxz(0); 
  affine.setTx(0); 
  affine.setMyx(sinus); 
  affine.setMyy(cosinus);
  affine.setMyz(0); 
  affine.setTy(0); 
  affine.setMzx(0); 
  affine.setMzy(0);
  affine.setMzz(1);
  affine.setTz(0); 
  
  gc.setTransform(affine);
  
 }

 public Group getLine(double y) {
  Group root = new Group();

  return root;
 }

 public static void main(String[] args) {
  launch(args);
 }
}
Sebenarnya masih ada beberapa bagian yang ingin saya sempurnakan, misalnya respon terhadap mouse, drag n drop terhadap foto serta menyimpan hasil ke dalam gambar JPEG. Tapi  biar g terlalu basi, mendingan dipublish aja versi BETA-nya. Itupun kalo layak disebut BETA.

Maksud saya adalah saya pengen berbagi tutorial aja. Semoga bermanfaat!

Thursday, May 30, 2013

Tutorial kubus dengan javaFX

Berikut ini adalah sambungan tutorial tentang efek kompiz dengan javaFX. Kebetulan saya masih bingung bagaimana membuat ritasinya secara acak, saya harap pembaca bisa melanjutkannya. Soalnya kalo cuma rotasi satu arah, mungkin agak mudah, tapi katika rotasinya sudah dicampur-campur vertikal-horizontal maka si kubusnya akan kebingungan menentukan arah dan koordinat.

sampai detik ini saya masih berfikir bagaimana cara menggeneralisirnya. Saya harap sedikit tutorial ini bisa membantu pembaca dalam memahami bahasa pemrograman java.



adapun source codenya anda bisa download di sini:

https://docs.google.com/file/d/0B1irLqfPwjq0THZoODdpT0tpQzQ/edit?usp=sharing

Wednesday, May 29, 2013

Animasi bola dengan JavaFX

Nah ini adalah kelanjutan dari tutorial sebelumnya tentang rotasi pada JavaFX:



Anda tinggal menambahkan sedikit sentuhan untuk memberi efek rotasi dengan trigger mouse...
selamat mencoba..!
package fjr.booksample;

import org.omg.CORBA.Environment;

import javafx.scene.transform.Rotate;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Orientation;
import javafx.scene.Group;
import javafx.scene.GroupBuilder;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.control.Slider;
import javafx.scene.control.SliderBuilder;
import javafx.scene.image.Image;
import javafx.scene.image.ImageViewBuilder;
import javafx.scene.layout.StackPane;


/**
 * cari gambar sebanyak 46 buah (bisa juga sejumlah lain tapi biar mudahnya pake 46).. 
 * kemudian namakan dengan pic1.jpg, pic2.jpg dst.... liat method getArrayImage di bawah.. :D 
 * 
 * @author meta alias trisna utami yang pacarnya suka ama lev landau... :D
 */
public class MakeABall extends Application {

 static final int NUM_ARRAY = 46;

 private void init(Stage primaryStage) {
  final Group root = new Group();
  StackPane mainPane = new StackPane();
  mainPane.setMinSize(800, 600);

  primaryStage.setScene(new Scene(mainPane));

  Image[] images = MakeABall.getArrayImage();
  final  Group mainEnsembel = new Group();

  int a = 0;
  Image[] imageA = new Image[12];
  for (int i = 0; i < imageA.length; i++) {
   imageA[i] = images[a];
   a++;
  }
  ImageEnsembel ensembelA = new ImageEnsembel(imageA, 0, 153);
  ensembelA.setTranslateX(200);
  ensembelA.setTranslateY(200);
//  ensembelA.ry.setAngle(12);
  mainEnsembel.getChildren().add(ensembelA);

  Image[] imageB = new Image[10];
  for (int i = 0; i < imageB.length; i++) {
   imageB[i] = images[a];
   a++;
  }

  Group ensembelB = new ImageEnsembel(imageB, 30, 140);
  ensembelB.setTranslateX(200);
  ensembelB.setTranslateY(290);
  mainEnsembel.getChildren().add(ensembelB);

  Image[] imageC = new Image[10];
  for (int i = 0; i < imageC.length; i++) {
   imageC[i] = images[a];
   a++;
  }

  ImageEnsembel ensembelC = new ImageEnsembel(imageC, 30, 140);
  ensembelC.setTranslateX(200);
  ensembelC.setTranslateY(150);
  ensembelC.rz.setAngle(180);
  mainEnsembel.getChildren().add(ensembelC);
  
  
  Image[] imageD = new Image[6];
  for(int i =0 ;i < imageD.length; i++){
   imageD[i] = images[a]; 
   a++; 
  }
  
  ImageEnsembel ensembelD = new ImageEnsembel(imageD, 65, 110);
  ensembelD.setTranslateX(200);
  ensembelD.setTranslateY(355);
  mainEnsembel.getChildren().add(ensembelD);
 
  
  
  Image[] imageE  = new Image[6]; 
  for(int i =0 ; i< imageE.length ; i++){
   imageE[i] = images[a] ;
   a++; 
  }
//  
  ImageEnsembel ensembelE = new ImageEnsembel(imageE, 65, 110); 
  ensembelE.setTranslateX(200);
  ensembelE.setTranslateY(85);
  ensembelE.rz.setAngle(180);
  mainEnsembel.getChildren().add(ensembelE);
  
  Image[] imageF = new Image[2]; 
  for(int i =0 ; i< imageF.length ; i++){
   imageF[i] = images[a] ;
   a++; 
  }
  
//  ImageEnsembel ensembelF = new ImageEnsembel(imageA, 0, 180);
//  ensembelF.setTranslateX(210);
//  ensembelF.setTranslateY(210);
//  ensembelF.rz.setAngle(90);
  
//  mainEnsembel.getChildren().add(ensembelF);
  
  
  mainEnsembel.setRotationAxis(Rotate.Y_AXIS);
//  mainEnsembel.setRotate(90);
  root.getChildren().add(mainEnsembel);

  final Group xGroup = new Group();
  xGroup.getChildren().add(root);
  final Slider slider = SliderBuilder.create()
    .orientation(Orientation.VERTICAL).minWidth(50).minHeight(50)
    .value(50).maxHeight(70).translateX(-230).translateY(-200)
    .build();
  slider.valueProperty().addListener(new ChangeListener() {
   @Override
   public void changed(ObservableValue arg0,
     Number arg1, Number arg2) {
    root.setRotationAxis(Rotate.X_AXIS);
    if (slider.getValue() < 50) {
     root.setRotate(slider.getValue() - 50);
    } else {
     root.setRotate(slider.getValue() - 50);
    }
   }
  });

  final Slider slider2 = SliderBuilder.create()
    .orientation(Orientation.VERTICAL).minWidth(50).minHeight(50)
    .maxHeight(70).translateX(-200).translateY(-200).build();

  slider2.valueProperty().addListener(new ChangeListener() {
   @Override
   public void changed(ObservableValue arg0,
     Number arg1, Number arg2) {
    mainEnsembel.setRotationAxis(Rotate.Y_AXIS);
    mainEnsembel.setRotate(slider2.getValue() * 2);
   }
  });

  root.setRotationAxis(Rotate.X_AXIS);
  mainPane.getChildren().add(xGroup);
  

  mainPane.getChildren().addAll(slider, slider2);
  primaryStage.setResizable(true);
 }




 class ImageEnsembel extends Group implements Cloneable
 {
  
  final Rotate rx = new Rotate(0, Rotate.X_AXIS);
  final Rotate ry = new Rotate(0, Rotate.Y_AXIS);
  final Rotate rz = new Rotate(0, Rotate.Z_AXIS);
  
  public ImageEnsembel(Image[] images, double angles, double space) 
  {
   super();
   double angle = 0;
   double jarak = 360 / images.length;
   getTransforms().addAll(rx,ry,rz);
   for (int i = 0; i < images.length; i++) {
    Xform xform = new Xform();
    xform.getChildren()
      .add(ImageViewBuilder.create().image(images[i])
        .fitWidth(60).fitHeight(50)
        .preserveRatio(false)
        .rotationAxis(Rotate.Y_AXIS).rotate(90).build());
    xform.rz.setAngle(angles);
    xform.setTranslateX(space);
    Xform xform1 = new Xform();
    xform1.getChildren().add(xform);
    xform1.ry.setAngle(angle);
    getChildren().add(xform1);
    angle += jarak;
   }
  }
  
  public Object clone() throws CloneNotSupportedException{
   return super.clone();
  }
 }

 static String[] getArrayString() {
  String[] array = new String[NUM_ARRAY];
  String a = "pic";
  for (int i = 0; i < array.length; i++) {
   array[i] = a.concat(Integer.toString(i + 1).concat(".jpg"));
  }
  return array;
 }

 static Image[] getArrayImage() {
  Image[] array = new Image[NUM_ARRAY];
  String[] arrStr = getArrayString();
  for (int i = 0; i < array.length; i++) {
   array[i] = new Image(MakeABall.class.getResource(arrStr[i])
     .toExternalForm());
  }
  return array;
 }

 class Xform extends Group {
  final Rotate rx = new Rotate(0, Rotate.X_AXIS);
  final Rotate ry = new Rotate(0, Rotate.Y_AXIS);
  final Rotate rz = new Rotate(0, Rotate.Z_AXIS);

  public Xform() {
   super();
   getTransforms().addAll(rz, ry, rx);
  }
 }

 @Override
 public void start(Stage primaryStage) throws Exception {
  init(primaryStage);
  primaryStage.show();
 }

 public static void main(String[] args) {
  launch(args);
 }
}

Monday, May 27, 2013

Bikin Animasi Compiz di JavaFX

Berikut ini akan saya berikan bagaimana caranya membuat "semacam piring" dengan javaFX. Jika anda sudah bisa memahami tutorial kali ini, saya yakin anda bisa membuat banyak hal yang menarik.



adapun sourcenya:
package fjr.booksample;
 
import javafx.scene.transform.Rotate;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Orientation;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.control.Slider;
import javafx.scene.control.SliderBuilder;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.RectangleBuilder;
 
//author fajar
public class MakeABall extends Application {
 
  private void init(Stage primaryStage) {
  final Group root = new Group();
  StackPane mainPane = new StackPane();
  mainPane.setMinSize(500, 500);
 
  primaryStage.setScene(new Scene(mainPane));
 
  Group ensembel = new Group();
  double angle = 0;
  for (int i = 0; i < 10; i++) {
   Rectangle rect = RectangleBuilder.create().width(100).height(100)
     .rotationAxis(Rotate.Y_AXIS).rotate(90).fill(getColor()[i])
     .build();
   Xform xform = new Xform();
   xform.getChildren().add(rect);
   xform.rz.setAngle(45);
   xform.setTranslateX(200);
   Xform xform1 = new Xform();
   xform1.getChildren().add(xform);
   xform1.ry.setAngle(angle);
   ensembel.getChildren().add(xform1);
   angle += 36;
  }
 
  ensembel.setTranslateX(200);
  ensembel.setTranslateY(200);
  ensembel.setRotationAxis(Rotate.Z_AXIS);
  root.getChildren().add(ensembel);
 
  final Slider slider = SliderBuilder.create()
    .orientation(Orientation.VERTICAL).minWidth(50).minHeight(50)
    .maxHeight(70).translateX(-230).translateY(-200).build();
  slider.valueProperty().addListener(new ChangeListener() {
   @Override
   public void changed(ObservableValue arg0,
     Number arg1, Number arg2) {
    root.setRotationAxis(Rotate.X_AXIS);
    root.setRotate(slider.getValue());
   }
  });
 
  final Slider slider2 = SliderBuilder.create()
    .orientation(Orientation.VERTICAL).minWidth(50).minHeight(50)
    .maxHeight(70).translateX(-190).translateY(-200).build();
 
  slider2.valueProperty().addListener(new ChangeListener() {
   @Override
   public void changed(ObservableValue arg0,
     Number arg1, Number arg2) {
    root.setRotationAxis(Rotate.Y_AXIS);
    root.setRotate(slider2.getValue());
   }
  });
 
  mainPane.getChildren().add(root);
  mainPane.getChildren().addAll(slider, slider2);
  primaryStage.setResizable(true);
 }
 
 Color[] getColor() {
  Color[] colors = new Color[10];
  colors[0] = Color.ALICEBLUE;
  colors[1] = Color.RED;
  colors[2] = Color.YELLOW;
  colors[3] = Color.YELLOWGREEN;
  colors[4] = Color.ALICEBLUE;
  colors[5] = Color.AQUAMARINE;
  colors[6] = Color.BISQUE;
  colors[7] = Color.BLUEVIOLET;
  colors[8] = Color.BROWN;
  colors[9] = Color.BURLYWOOD;
  return colors;
 }
 
 class Xform extends Group {
  final Rotate rx = new Rotate(0, Rotate.X_AXIS);
  final Rotate ry = new Rotate(0, Rotate.Y_AXIS);
  final Rotate rz = new Rotate(0, Rotate.Z_AXIS);
 
  public Xform() {
   super();
   getTransforms().addAll(rz, ry, rx);
  }
 }
 
 @Override
 public void start(Stage primaryStage) throws Exception {
  init(primaryStage);
  primaryStage.show();
 }
 
 public static void main(String[] args) {
  launch(args);
 }
}

Sunday, February 10, 2013

Belajar animasi 3D dengan javaFX

Awalnya mau buat simulasi gerak planet, tapi kebetulan kemarin mata kuliah fisika galaksi belum lulus, akhirnya g jadi deh. Yang ingin saya tunjukkan dalam program ini adalah bagaimana teknologi 3D diserap ke dalam javaFX. Sebenarnya program ini sudah ada dalam sample bawaan dari JavaFX, hanya saya modifikasi dikit. Tapi itupun saya mesti repot-repot buka buku sana-sini untuk pahami konsep semisal affine transformation, spline curve (cubic dan quad) atau hal-hal non-teknis lainnya. tapi g masalah, sekali mendayung 2,3 pulau terlampaui. Pada program ini saya gunakan kubus (seperti contohnya). Sebenarnya bisa juga digunakan bola. Anda tinggal buat circle (lingkaran) yang di-fill dengan LinearGradient. Cuma buat menampakkan efek 3D, makanya saya gunakan kubus. Hikmahnya adalah saya tahu beberapa hal yang sebelumnya saya salah pahami dalam konsep 3D. Jangan lupa, sebelum menjalankan program ini, anda mesti pastikan kartu grafis anda up-to-date. Kebetulan saya kemarin, programnya g jalan karena drivernya sudah eskpire, padahal sudah di download dari versi terbaru di situs resminya. Ternyata yang dipasang di situ sudah ekspire, dan saya mesti mendownload dari update ala microsoft windows.
package fjr.physics.animate;

import javafx.animation.KeyValue;
import javafx.animation.PathTransition;
import javafx.animation.PathTransition.OrientationType;
import javafx.animation.KeyFrame;
import javafx.animation.PathTransitionBuilder;
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.ButtonBuilder;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.AnchorPaneBuilder;
import javafx.scene.paint.Color;
import javafx.scene.shape.ArcToBuilder;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathBuilder;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.RectangleBuilder;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;

public class GerakPlanet extends Application {

  public static void main(String[] args) {
  launch(args);
 }

 @Override
 public void start(Stage primaryStage) throws Exception {
  Group root = new Group();
  Planet planet1 = new Planet();
  planet1.setTranslateX(60);
  planet1.setTranslateY(320);

  PathTransition pathTransition = PathTransitionBuilder.create()
    .duration(Duration.seconds(4))
    .orientation(OrientationType.ORTHOGONAL_TO_TANGENT)
    .cycleCount(Timeline.INDEFINITE).autoReverse(false).path(planet1.getPath())
    .node(planet1.getCube()).build();
  pathTransition.play();
  
  final Timeline animasi = new Timeline();
  animasi.getKeyFrames().addAll(
    new KeyFrame(Duration.ZERO,
      new KeyValue(planet1.rx.angleProperty(), 0d)),
    new KeyFrame(Duration.seconds(5), new KeyValue(planet1.rx
      .angleProperty(), 720d)));

  animasi.setCycleCount(Timeline.INDEFINITE);
  animasi.setAutoReverse(false);
  animasi.play();
  
  //add tombol control
  AnchorPane pane = AnchorPaneBuilder.create()
    .children(ButtonBuilder.create()
      .translateX(10)
      .translateY(10)
      .minWidth(60)
      .text("play")
      .onAction(new EventHandler() {
       @Override
       public void handle(ActionEvent arg0) {
        animasi.play();
       }
      })
      .build(), 
      ButtonBuilder.create()
      .text("pause")
      .translateX(10)
      .minWidth(60)
      .translateY(40)
      .onAction(new EventHandler() {
       @Override
       public void handle(ActionEvent arg0) {
        animasi.pause();
       }
      })
      .build()
      )
    .build();

  root.getChildren().addAll(planet1 ,pane);
  primaryStage.setScene(new Scene(root, 500, 700));
  primaryStage.show();
 }

 class Planet extends Group {

  Path path;
  Rectangle rect;
  Group cube; 
  private int size = 40;
  private Color color = Color.ORANGE;
  private double shade = 1; 

  final Rotate rx = new Rotate(0, Rotate.X_AXIS);
  final Rotate ry = new Rotate(0, Rotate.Y_AXIS);
  final Rotate rz = new Rotate(0, Rotate.Z_AXIS);

  Planet() {
   getTransforms().addAll(rx,ry,rz);
   path = PathBuilder
     .create()
     .elements(
       new MoveTo(50, 200),
       ArcToBuilder.create().radiusX(50).radiusY(40)
         .x(400).y(200).build(),
       ArcToBuilder.create().radiusX(50).radiusY(40).x(50)
         .y(200).build()).build();
   path.setStroke(Color.DODGERBLUE);
   rect = new Rectangle(0, 0, 40, 40);
   rect.setArcHeight(10);
   rect.setArcWidth(10);
   rect.setFill(Color.ORANGE);
   
   //sebenarnya tadi mau pasang rectangle, hanya g keliatan efek 3D 
   this.getChildren().addAll(path, createCube());
   
  }
  
  private  Group createCube(){
   cube = new Group();
   cube.getChildren().addAll(
       RectangleBuilder.create() // back face
             .width(size).height(size)
             .fill(color.deriveColor(0.0, 1.0, (1 - 0.5*shade), 1.0))
             .translateX(-0.5*size)
             .translateY(-0.5*size)
             .translateZ(0.5*size)
             .build(),
         RectangleBuilder.create() // bottom face
             .width(size).height(size)
             .fill(color.deriveColor(0.0, 1.0, (1 - 0.4*shade), 1.0))
             .translateX(-0.5*size)
             .translateY(0)
             .rotationAxis(Rotate.X_AXIS)
             .rotate(90)
             .build(),
         RectangleBuilder.create() // right face
             .width(size).height(size)
             .fill(color.deriveColor(0.0, 1.0, (1 - 0.3*shade), 1.0))
             .translateX(-1*size)
             .translateY(-0.5*size)
             .rotationAxis(Rotate.Y_AXIS)
             .rotate(90)
             .build(),
         RectangleBuilder.create() // left face
             .width(size).height(size)
             .fill(color.deriveColor(0.0, 1.0, (1 - 0.2*shade), 1.0))
             .translateX(0)
             .translateY(-0.5*size)
             .rotationAxis(Rotate.Y_AXIS)
             .rotate(90)
             .build(),
         RectangleBuilder.create() // top face
             .width(size).height(size)
             .fill(color.deriveColor(0.0, 1.0, (1 - 0.1*shade), 1.0))
             .translateX(-0.5*size)
             .translateY(-1*size)
             .rotationAxis(Rotate.X_AXIS)
             .rotate(90)
             .build()
             ,
         RectangleBuilder.create() // top face
             .width(size).height(size)
             .fill(color)
             .translateX(-0.5*size)
             .translateY(-0.5*size)
             .translateZ(-0.5*size)
             .build()
             );
   return cube;
  }
  
  public Group getCube(){return cube;}
 
  public Path getPath(){return path;}
  
  public Rectangle getRectangle(){return rect;}
 }
}
Hasil eksekusinya dapat di liat di video berikut (kebetulan saya g tau meng-embed applet di blog):