Sonntag, 25. Mai 2014

JavaFX 3D Editor - Update 2

Da ist schon wieder eine Woche vorbei. Und damit wird es auch Zeit für ein neues Update.
Hier ein Überblick:


Was ist diese Woche alles neu dazugekommen?
  1.  Ausbau des Manipulations-Menü
    Hier sind jetzt die gezeichneten Komponenten zu sehen und auszuwählen. Weiterhin kann die Position der Komponente manipuliert werden. Dabei können zum einen die (+) - und (-)-Schaltflächen verwendet werden und zum anderen kann die Position auch direkt in die Felder eingegeben werden.

    Für die Leute, die auch in den Code gucken:
  2. Strukturierung
    Die Struktur hat sich etwas verändert. Sämtliche Menüs befinden sich nun in einem extra Package. Alles in einem GUI-Package war zu unübersichtlich.
  3. Graphic-Objekte
    Die Graphic-Objekt erben jetzt von einer eigenen Superklasse. Damit sind die Objekte keine Instanzen von der Klasse javafx.scene.Node. In den aktuellen Klassen kann mittels der Methode "asNode()" eine javafx.scene.Node erzeugt werden, die dann auf anderen scenes gerendert werden kann. Dadurch wurde ein weitaus höherer Abstraktionsgrad erreicht. Ihr könnt die Klasse Rectangle3D ja mal mit den vorherigen Revisionen vergleichen. Sollten jetzt neue Graphic-Klassen hinzukommen, müssen sie von Object3D erben. Den Großteil der benötigten Methoden bekommen sie damit schon mitgeliefert. Ein Manko ist immer noch, dass zu jeder Graphic-Klasse eine javafx.scene.Node angegeben werden muss.
  4. Shape-Builder
    Der Shape-Builder ist eine neue allgemeine Klasse, die es erlaubt Objekte einer Graphic-Klasse zu erzeugen. Sie ist so komzipiert, dass die entsprechende Graphic-Klasse mit angegeben werden muss.

Code ist immer noch hier im master zu finden: https://github.com/gundermann/JFX3D.git
Und Fehler und wünsche gehen weiterhin an: niels.gundermann.ng@googlemail.com

Sonntag, 18. Mai 2014

JavaFX 3D Editor - Update 1

Was gibt es neues?

Diese Woche habe ich nicht viel Zeit gehabt. Außerdem habe ich Probleme bei der Rotation des gesamten Grafik oder einzelnen Grafiken.

Von daher gibt es nur kleine Neuerungen in dieser Woche.


  1. Erweiterung des Dimensions Menüs

    Hier gibt es jetzt eine Infobox, in der die aktuellen Rotationswinkel einzusehen sind. Desweiteren kann man hier sämtliche Rotationen zurücksetzen.
    Weiterhin kann man sich die Achsen anzeigen lassen. Diese rotieren zwar mit - entsprechen jedoch nicht den Rotationsachsen. (Hier muss ich noch etwas machen)
  2. Manipulation der Komponenten im Manipulations Menü

    Das Manipulations Menü bietet die Möglichkeit, die einzelnen Komponenten zu verändern. Derzeit können die Komponenten nur in drei Dimensionen verschoben werden. Die Komponente, die verändert wird, wird durch die Auswahl im Zeichnen Menü bestimmt. Dies wird sich mit dem nächsten Update ändern, da das Zeichnen Menü nichts mit den schon bestehenden Komponenten zu tun hat, sondern nur für die Erzeugung neuer Komponenten benötigt wird.
  3. Neue Positionen für die Menüs


Hier noch einmal der Link: https://github.com/gundermann/JFX3D.git

Und meine E-Mail Adresse für Fehlermeldung und Wünsche ;-)
gundermann.niels.ng@googlemail.com

Sonntag, 11. Mai 2014

JavaFX 3D Editor

Mein JavaFX-3D-Editor. Was kann der eigentlich?
Momentan handelt es sich dabei um ein minimalistisches Paint, das in der Lage sein soll, 3D-Grafiken zu visualisieren.

Auch wenn der zündende Gedanke zur Auswahl dieses Projektes "PAINT ist doch toll" war, habe ich mich bei der Aufmachung für einen gimp-ähnlichen Stil entschieden. Von daher sieht das Programm nach dem Start nach nichts aus, wie man im folgenden Bild sieht.


Von den Menüpunkten "Edit" und "Help" sollte man sich vorerst nicht beeindrucken lassen - unter denen passiert noch gar nichts ;-)

Auch das Schließen via File->Close ist noch nicht umgesetzt - ja ich weiß: "Was geht da überhaupt?!".
Also mache ich mal mit dem weiter, was überhaupt geht.

Die Schaltfläche "New Object" sieht aus, als könnte sie schon funktionieren. Wenn man darauf klickt passiert sogar etwas. Dabei wird eine neue Arbeitsoberfläche angelegt, auf dem die Grafiken erstellt werden können. Das sieht erstmal unspektakulär aus:

Was aber im Hauptmenü auffällt ist, dass die Schaltflächen "Dimension Menu" und "Painting Menu" sowie die Auswahlbox aktiv wurden. Beim Klick auf die beiden Menu-Schaltflächen öffnet sich das jeweilige Menu. Die Beiden Menüs werden ich im Anschluss erklären. die Auswahlbox bestimmt auf welche Arbeitsoberfläche sich die Menüs beziehen. Insofern ist es auch möglich mit mehreren Arbeitsflächen gleichzeitig zu arbeiten.

Mit Hilfe des Dimensions Menü kann man die 3D-Objekte in den entsprechenden Achsen drehen, um sich sein Kunstwerk mal von allen Seiten aus zu betrachten. Die Rotation funktioniert bei aktivierter Rotation über das Ziehen mit der Maus über die entsprechende Arbeitsfläche bei gedrückter linker Maustaste (Die Drag-Funktion).


Mit Hilfe des Zeichen Menü werden erstens die Komponenten in der ausgewählter Arbeitsfläche aufgelistet und zweiten kann man hier die Tools zum Zeichnen anzeigen lassen. Wobei derzeit nur das Rechteck funktioniert.


Wo ich schon wieder bei nicht funktionierenden Dingen bin - Speichern kann man die Grafiken auch noch nicht ;-)

Ich würde mich freuen, wenn ihr ein bisschen mit dem Editor herumspielt und mir eure Meinung schreibt.
Auch über Fehlermeldungen, Verbesserungsvorschläge und Verbesserungswünsche würdeich mich sehr freuen.

Follow on twitter: @NGJFXaA

Email: gundermann.niels.ng@googlemail.com

Wer möchte, kann sich den Code auch auf GitHub angucken:
https://github.com/gundermann/JFX3D/tree/peter

Donnerstag, 8. August 2013

Shobu-Ippon Desktop

Aufgrund einiger Nachfragen dahingehend, ob es nicht eine Windows-Version für eine von mir entwickelte Android-App geben könnte, habe ich diese Anregung erfreut aufgenommen und umgesetzt.
Informationen zu der Android-App sind hier zu finden:
http://androidappexperience.blogspot.com/2013/06/mobile-wettkampfverwaltung-1-modul.html

Größere Erklärungen zu diesem Programm will ich nicht doppelt ausführen. Wer Erläuterungen benötigt, kann sich gerne den Beitrag zu der App durchlesen. Die Bedienung und das Verhalten sind gleich - nur die User-Interface und die Plattform hab sich geändert.
Leider gibt noch keine Möglichkeit das Reglement abzuändern. Darum werde ich mich in den nächsten Wochen kümmern.

Solange ich noch keinen Download-Link veröffentlicht habe, kann mir jeder, der an diesem Programm interessiert ist eine Mail schreiben (gundermann.niels.ng@googlemail.com).
Ich werde ihm dann spätestens nach zwei Tagen die notwendigen Dateien zuschicken.

Hier noch ein kleiner Einblick:
Bei Anmerkungen und Fragen immer an mich wenden ;-)

Montag, 3. Juni 2013

Binding

Das Problem beim Binding ist in der Regel, dass man nur Properties binden kann. Wohingegen Strings, Integer, oder andere Datentypen nicht direkt an die gewünschten Elemente gekoppelt werden können. Die eigentlich benötigten Datentypen können aber glücklicherweise an Properties gesetzt werden.

Wie funktioniert das Ganz also:

Egal ob man dabei mit Tabellen arbeitet, oder mit anderen Nodes aus JavaFX - die Vorgehensweise ist immer dieselbe.
Zuerst wird ein Property-Objekt benötigt. Je nachdem, was genau gebunden werden soll, kann man schon hier zswichen gewissen Datentypen unterscheiden.
Zum Beispiel:

  • SimpleStringProperty
  • SimpleIntegerProperty
  • SimpleBooleanProperty
  • SimpleObjectProperty
  • ...
Diese Klassen SimpleObjectProperty ist die mächtigste dieser Propertyklassen, was nicht heißt, dass es sich dabei um eine Oberklasse handelt. Sollte man mit einem Datentyp arbeiten, den keine Propertyklasse speziell verarbeiten kann, schafft SimpleObjectProperty immer Abhilfe für die Bindung.
Nun zur Implementation - das Property-Objekt wird instantiiert und der Wert dessen auf die entsprechende Referenz gesetzt. Im Beispiel handelt es sich um einen einfachen String:

public StringProperty labelString = new SimpleStringProperty("hallo");

Der Wert der StringProperty entspricht nun dem String "hallo". Dieses Property-Objekt kann nun an eine beliebige Node gehängt werden (Binding). Hierzu ein Beispiel mit einem TextField:

TextField tf =  new TextField();
  tf.textProperty().bind(labelString);

Es gibt viele andere Properties der Nodes, wie z.B. alignmentProperty() oder visibleProperty(). Es ist möglich an alle dieser Properties bestimmte Objekte, oder Inhalt von Objekten zu binden.

Was bringt das Ganze nun?
Es macht die Implementation etwas schöner, da beim Verändern des Inhaltes einer Node, diese nicht mehr direkt verändert werden muss. Die geschieht über das Property-Objekt, welches an die Node gebunden wurde. Vereinfacht gesagt, wurde an die Node eine Art Listener angehängt, der die Änderung bestimmter Inhalte überwacht. Zum Ändern der Node, muss also nur noch die Property geändert werden, welche man in einer separaten Klasse implementieren könnte. Das sorgt für eine klarere Struktur.

Die Änderung der Property erfolgt über die Methode "set()":

labelString.set("wie geht's?");

Die Verwendung von anderen Datentypen in den Properties wird z.B. in folgendem Beitrag gezeigt:
Nodes in Tabellen

Mittwoch, 29. Mai 2013

3D-Rotation mit der Maus

Ich fand es mal interessant zu sehen, wie einfach es ist, einen Würfel mit JavaFX3D zu zeichnen und mit der Maus zu drehen. Die einzige Schwierigkeit dabei war, dass man drauf achten muss, die Würfel direkt in der Mitte zu platzieren, er sonst durch den Rotationspunkt von seiner ursprünglichen Position abweicht. Der Folgende Code realisiert das drehen des Würfels bei gedrückter Maustaste.

public class MouseRotation extends Application{
 
 private DoubleProperty side2Angle = new SimpleDoubleProperty();
    private DoubleProperty side3Angle = new SimpleDoubleProperty();
    private DoubleProperty side4Angle = new SimpleDoubleProperty();
    private DoubleProperty side5Angle = new SimpleDoubleProperty();
    private DoubleProperty side6TranslateZ = new SimpleDoubleProperty();
     private DoubleProperty rootAngleX = new SimpleDoubleProperty();
     private DoubleProperty rootAngleY = new SimpleDoubleProperty();
     
     public static void main(String[] args) {
         launch(args);
     }

     @Override
     public void start(Stage stage) {
         stage.setTitle("Cube");
         stage.setScene(makeScene());
         stage.show();
         animate();
     }

     private Scene makeScene() {
         return SceneBuilder.create()
             .width(500)
             .height(500)
             .root(createRoot())
             .onMouseDragged(new MouseEventHandler())
             .camera(PerspectiveCameraBuilder.create()
                 .build())
             .depthBuffer(true)
             .build();
     }

     private Parent createRoot() {
         final Rectangle side1 = RectangleBuilder.create()
             .x(-100)
             .y(-100)
             .width(200)
             .height(200)
             .fill(Color.RED)
             .build();
         
         final Rotate side2Rotate = RotateBuilder.create()
                 .pivotX(-100)
                 .pivotY(-100)
                 .pivotZ(0)
                 .axis(Rotate.Y_AXIS)
                 .build();
             side2Rotate.angleProperty().bind(side2Angle);

             final Rectangle side2 = RectangleBuilder.create()
                 .x(-100)
                 .y(-100)
                 .width(200)
                 .height(200)
                 .fill(Color.GREEN)
                 .transforms(side2Rotate)
                 .build();

             final Rotate side3Rotate = RotateBuilder.create()
                 .pivotX(100)
                 .pivotY(-100)
                 .pivotZ(0)
                 .axis(new Point3D(-1, 0, 0))
                 .build();
             side3Rotate.angleProperty().bind(side3Angle);

             final Rectangle side3 = RectangleBuilder.create()
                 .x(-100)
                 .y(-100)
                 .width(200)
                 .height(200)
                 .fill(Color.BLUE)
                 .transforms(side3Rotate)
                 .build();

             final Rotate side4Rotate = RotateBuilder.create()
                 .pivotX(100)
                 .pivotY(100)
                 .pivotZ(0)
                 .axis(new Point3D(0, -1, 0))
                 .build();
             side4Rotate.angleProperty().bind(side4Angle);

             final Rectangle side4 = RectangleBuilder.create()
                 .x(-100)
                 .y(-100)
                 .width(200)
                 .height(200)
                 .fill(Color.CYAN)
                 .transforms(side4Rotate)
                 .build();

             final Rotate side5Rotate = RotateBuilder.create()
                 .pivotX(-100)
                 .pivotY(100)
                 .pivotZ(0)
                 .axis(Rotate.X_AXIS)
                 .build();
             side5Rotate.angleProperty().bind(side5Angle);

             final Rectangle side5 = RectangleBuilder.create()
                 .x(-100)
                 .y(-100)
                 .width(200)
                 .height(200)
                 .fill(Color.MAGENTA)
                 .transforms(side5Rotate)
                 .build();

             final Rectangle side6 = RectangleBuilder.create()
                 .x(-100)
                 .y(-100)
                 .width(200)
                 .height(200)
                 .fill(Color.YELLOW)
                 .build();

             side1.translateZProperty().set(100);
             side2.translateZProperty().set(100);
             side3.translateZProperty().set(100);
             side4.translateZProperty().set(100);
             side5.translateZProperty().set(100);
             side6.translateZProperty().bind(side6TranslateZ.add(100));

         
         final Rotate rootRotateX = RotateBuilder.create()
              .pivotX(0)
              .pivotY(0)
              .pivotZ(0)
              .axis(Rotate.X_AXIS)
              .build();
          rootRotateX.angleProperty().bind(rootAngleX);
          
         final Rotate rootRotateY = RotateBuilder.create()
            .pivotX(0)
            .pivotY(0)
            .pivotZ(0)
            .axis(Rotate.Y_AXIS)
            .build();
          rootRotateY.angleProperty().bind(rootAngleY);

         return GroupBuilder.create()
             .children(side1, side6, side2, side3, side4, side5)
             .translateX(250)
             .translateY(250)
             .transforms(rootRotateX, rootRotateY)
             .build();
     }
     
     private void animate() {
         TimelineBuilder.create()
             .keyFrames(
                 new KeyFrame(
                     Duration.seconds(0),
                     new KeyValue(side2Angle, 0),
                     new KeyValue(side3Angle, 0),
                     new KeyValue(side4Angle, 0),
                     new KeyValue(side5Angle, 0),
                     new KeyValue(side6TranslateZ, 0)
                 ),
                 new KeyFrame(
                     Duration.seconds(1),
                     new KeyValue(side2Angle, 0),
                     new KeyValue(side3Angle, 0),
                     new KeyValue(side4Angle, 0),
                     new KeyValue(side5Angle, 90),
                     new KeyValue(side6TranslateZ, 0)
                 ),
                 new KeyFrame(
                     Duration.seconds(2),
                     new KeyValue(side2Angle, 0),
                     new KeyValue(side3Angle, 0),
                     new KeyValue(side4Angle, 90),
                     new KeyValue(side5Angle, 90),
                     new KeyValue(side6TranslateZ, 0)
                 ),
                 new KeyFrame(
                     Duration.seconds(3),
                     new KeyValue(side2Angle, 0),
                     new KeyValue(side3Angle, 90),
                     new KeyValue(side4Angle, 90),
                     new KeyValue(side5Angle, 90),
                     new KeyValue(side6TranslateZ, 0)
                 ),
                 new KeyFrame(
                     Duration.seconds(4),
                     new KeyValue(side2Angle, 90),
                     new KeyValue(side3Angle, 90),
                     new KeyValue(side4Angle, 90),
                     new KeyValue(side5Angle, 90),
                     new KeyValue(side6TranslateZ, 0)
                 ),
                 new KeyFrame(
                     Duration.seconds(5),
                     new KeyValue(side2Angle, 90),
                     new KeyValue(side3Angle, 90),
                     new KeyValue(side4Angle, 90),
                     new KeyValue(side5Angle, 90),
                     new KeyValue(side6TranslateZ, -200)
                 )
             )
             .build().play();
     }

     public class MouseEventHandler implements EventHandler<MouseEvent>{

   @Override
   public void handle(MouseEvent mouseEvent) {
    double mouseX = mouseEvent.getSceneX();
    double mouseY = mouseEvent.getSceneY();
    
    rootAngleX.set(mouseY);
    rootAngleY.set(mouseX);
   }
      
     }

}
Durch das Ändern, der Methode "onMouseDragged" in "makeScene" kann man sicherlich das Eingabegerät noch variieren - z.B. könnte man hier mit Touchevents arbeiten, oder die ganze Sache über die Tastatur steuern.

Die Animation kann man natürlich weglassen. Die Werte der Seiten, die die Position angeben müssen dann mit den entsprechenden Werten aus der Animation aufsummiert werden. Sonst liegen alle Seiten übereinander. 

Nodes in Tabellen (Combobox, Textfield, ToggleButton)

Ich hatte vor einigen Tagen Probleme mit dem Einfügen von TextFields und Comboboxen in einer Tabelle. Man findet dazu einiges im Netz, was jedoch schnell unübersichtlich wird.

Hier ein kleines einfaches Beispiel, wie es ganz einfach realisiert werden kann:

Zuerst wird Datenmodell für die Tabelle entworfen:


public class Datamodel {
 
 private SimpleObjectProperty<ComboBox<String>> combobox;
 
 private SimpleObjectProperty<TextField> textfield;
 
 private SimpleObjectProperty<ToggleButton> toggleButton;
 
 public Datamodel(){
  combobox = new SimpleObjectProperty<ComboBox<String>>();
  textfield = new SimpleObjectProperty<TextField>();
  toggleButton = new SimpleObjectProperty<ToggleButton>();
 }

 public ComboBox<String&gt getCombobox() {
  return combobox.get();
 }

 public void setCombobox(ComboBox<String&gt combobox) {
  this.combobox.set(combobox);
 }

 public TextField getTextfield() {
  return textfield.get();
 }

 public void setTextfield(TextField textfield) {
  this.textfield.set(textfield);
 }

 public ToggleButton getToggleButton() {
  return toggleButton.get();
 }

 public void setToggleButton(ToggleButton togglebutton) {
  this.toggleButton.set(togglebutton);
 }
 
}

Das Datenmodell enthält hier SimpleObjectProperties in denen die jeweiligen Nodes gespeichert werden.
Wichtig hierbei ist, dass die Variablenbezeichnungen in der Bezeichnungen der Getter- und Setter-Methoden auftreten - also diese am besten generieren lassen. Nachdem die Zugriffsmethoden automatisch erzeugt wurden, müssen die noch etwas umgeschrieben werden, damit auch die entsprechenden Nodes gesetzt bzw. zurückgegeben werden und nicht die Properties.

Da das Datenmodell implementiert ist, kann es nun an eine Tabelle gebunden werden. Hierzu wird erst einmal eine Tabelle mit den entsprechenden Spalten erstellt werden:


public class TableExample extends Application{
 
 private TableView<Datamodel> table;
 private TableColumn<Datamodel, ComboBox<String>> columnWithCombobox;
 private TableColumn<Datamodel, TextField> columnWithTextfield;
 private TableColumn<Datamodel, ToggleButton> columnWithTogglebutton;
 private Button bt;

 @Override
 public void start(Stage primeStage) throws Exception {
  Group group = new Group();
  Scene scene = new Scene(group, 500, 430);
  
  BorderPane pane = new BorderPane();
  pane.setCenter(initTable());
  pane.setBottom(initButton());
  group.getChildren().add(pane);
  
  primeStage.setScene(scene);
  primeStage.show();
 }
 
 private Node initTable() {
  table = new TableView<Datamodel>();
  columnWithCombobox = new TableColumn<Datamodel, ComboBox&ltString>>("1");
  columnWithCombobox.setCellValueFactory(new PropertyValueFactory<Datamodel, ComboBox<String>>("combobox"));
  columnWithCombobox.setMinWidth(200);
  columnWithTextfield = new TableColumn<Datamodel, TextField>("2");
  columnWithTextfield.setCellValueFactory(new PropertyValueFactory<Datamodel, TextField>("textfield"));
  columnWithTogglebutton = new TableColumn<Datamodel, ToggleButton>("3");
  columnWithTogglebutton.setCellValueFactory(new PropertyValueFactory<Datamodel, ToggleButton>("toggleButton"));
  columnWithTogglebutton.setMaxWidth(50);
  
  table.getColumns().addAll(columnWithCombobox, columnWithTextfield, columnWithTogglebutton);
  
  return table;
 }

In der Methode "initTable" wird die Tabelle dementsprechend konfiguriert. Wichtig dabei ist das setzten der CellValueFactory. Die dazugehörige PropertyValueFactory wird durch das Datenmodell und dem Typ bestimme, den die Zelle verkörpern soll - also ComboBox, TextField, oder ToggleButton. Weiterhin muss die Variablenbezeichnung aus dem Datenmodel angegeben werden, um mit den Getter- und Setter-Methoden arbeiten zu können.


Um dann eine neue Zeile in die Tabelle einfügen zu können habe ich einen Button zu Hilfe genommen:

private Node initButton() {
  bt = new Button("Zeile hinzufügen");
  
  bt.setOnAction(new EventHandler<ActionEvent>() {
   
   @Override
   public void handle(ActionEvent paramT) {
    addNewLineInTable();
   }

  });
  
  return bt;
 }



 private void addNewLineInTable() {
  ComboBox<String> combobox = new ComboBox<String>();
  combobox.setMinWidth(190);
  combobox.setPromptText("PromtText");
  TextField textfield = new TextField();
  ToggleButton togglebutton = new ToggleButton();
  
  Datamodel datamodel = new Datamodel();
  datamodel.setCombobox(combobox);
  datamodel.setTextfield(textfield);
  datamodel.setToggleButton(togglebutton);
  
  table.getItems().add(datamodel);
 }
Der Button ruft beim Klick "addNewLineInTable" auf. In dieser Methode werden die gewünschten Nodes instantiiert und an den entsprechenden Variablen des Datenmodells gesetzt. Zum Schluss wird das neue Exemplar des Datenmodells als neues Item in der Tabelle hinzugefügt.