2017-12-14 183 views
6

Za Chanuka i Próbuję ożywić szczyt przędzenia (Bączek):Jak animować bączek?

spinning top

mogę dostać się kręcić wokół własnej osi. Tu jest mój kodu:

import static javafx.scene.paint.Color.*; 

import javafx.animation.KeyFrame; 
import javafx.animation.KeyValue; 
import javafx.animation.Timeline; 
import javafx.application.Application; 
import javafx.beans.property.DoubleProperty; 
import javafx.beans.property.SimpleDoubleProperty; 
import javafx.geometry.Point3D; 
import javafx.scene.Camera; 
import javafx.scene.Group; 
import javafx.scene.PerspectiveCamera; 
import javafx.scene.Scene; 
import javafx.scene.SceneAntialiasing; 
import javafx.scene.paint.PhongMaterial; 
import javafx.scene.shape.Box; 
import javafx.scene.shape.Cylinder; 
import javafx.scene.shape.Sphere; 
import javafx.scene.transform.Rotate; 
import javafx.scene.transform.Translate; 
import javafx.stage.Stage; 
import javafx.util.Duration; 

public class DreidelAnim extends Application { 

    private double bodyBase = 30; 
    private double bodyHeight = bodyBase * 3/2; 
    private double baseRadius = bodyBase/2; 

    @Override 
    public void start(Stage stage) throws Exception { 
     DoubleProperty spinAngle = new SimpleDoubleProperty(); 
     Rotate spin = new Rotate(0, Rotate.Z_AXIS); 
     spin.angleProperty().bind(spinAngle); 

     Timeline spinAnim = new Timeline(new KeyFrame(Duration.seconds(2), new KeyValue(spinAngle, 360))); 
     spinAnim.setCycleCount(Timeline.INDEFINITE); 
     spinAnim.play(); 

     Group dreidel = createDreidel(); 
     Translate zTrans = new Translate(0, 0, -(bodyHeight/2 + baseRadius)); 
     dreidel.getTransforms().addAll(spin, zTrans); 

     Scene scene = new Scene(dreidel, 200, 200, true, SceneAntialiasing.BALANCED); 
     scene.setFill(SKYBLUE); 
     scene.setCamera(createCamera()); 

     stage.setScene(scene); 
     stage.show(); 
    } 

    private Group createDreidel() { 
     double handleHeight = bodyBase * 3/4; 
     Cylinder handle = new Cylinder(bodyBase/6, handleHeight); 
     handle.setTranslateZ(-(bodyHeight + handleHeight)/2); 
     handle.setRotationAxis(Rotate.X_AXIS); 
     handle.setRotate(90); 
     handle.setMaterial(new PhongMaterial(RED)); 

     Box body = new Box(bodyBase, bodyBase, bodyHeight); 
     body.setMaterial(new PhongMaterial(BLUE)); 

     Sphere base = new Sphere(baseRadius); 
     base.setTranslateZ(bodyHeight/2); 
     base.setMaterial(new PhongMaterial(GREEN)); 

     return new Group(handle, body, base); 
    } 

    private Camera createCamera() { 
     PerspectiveCamera camera = new PerspectiveCamera(true); 
     camera.setFarClip(1000); 

     int xy = 150; 
     Translate trans = new Translate(-xy, xy, -120); 
     Rotate rotXY = new Rotate(70, new Point3D(1, 1, 0)); 
     Rotate rotZ = new Rotate(45, new Point3D(0, 0, 1)); 
     camera.getTransforms().addAll(trans, rotXY, rotZ); 

     return camera; 
    } 

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

stworzyłem prosty model, wirując wokół własnej osi, a tłumaczone tak, że jego końcówka jest (0, 0, 0). Oto wynik:

enter image description here

Jak mogę osiągnąć coś podobnego do obrazu na górze, gdzie jest również wiruje wokół osi obracającej?

Odpowiedz

8

Rotacja osi, wokół której obraca się obiekt, nazywa się Precession. Ruch wirowania wymaga 2 obrotów:

  1. Obrót obiektu wokół jego osi wewnętrznej (równolegle do czerwonego uchwytu).
  2. Obrót jednej z osi wewnętrznych wokół osi statycznej (z w tym przypadku).

Na pierwszy rzut oka potrzeba 2 Animation. Jednak obie rotacje są w rzeczywistości takie same. Punktem obrotu obu jest (0, 0, 0) (po zTrans) i oba są wokół osi z, tylko jeden z nich jest pochylony pod kątem.

Oto zmodyfikowany kod:

import static javafx.scene.paint.Color.*; 

import javafx.animation.KeyFrame; 
import javafx.animation.KeyValue; 
import javafx.animation.Timeline; 
import javafx.application.Application; 
import javafx.beans.property.DoubleProperty; 
import javafx.beans.property.SimpleDoubleProperty; 
import javafx.geometry.Point3D; 
import javafx.scene.Camera; 
import javafx.scene.Group; 
import javafx.scene.PerspectiveCamera; 
import javafx.scene.Scene; 
import javafx.scene.SceneAntialiasing; 
import javafx.scene.paint.PhongMaterial; 
import javafx.scene.shape.Box; 
import javafx.scene.shape.Cylinder; 
import javafx.scene.shape.Sphere; 
import javafx.scene.transform.Rotate; 
import javafx.scene.transform.Translate; 
import javafx.stage.Stage; 
import javafx.util.Duration; 

public class FinalDreidelSpin extends Application { 

    private double bodyBase = 30; 
    private double bodyHeight = bodyBase * 3/2; 
    private double baseRadius = bodyBase/2; 

    @Override 
    public void start(Stage stage) throws Exception { 
     double tiltAngle = 40; 
     DoubleProperty spinAngle = new SimpleDoubleProperty(); 

     Rotate spin = new Rotate(0, Rotate.Z_AXIS); 
     Rotate tilt = new Rotate(tiltAngle, Rotate.X_AXIS); 

     spin.angleProperty().bind(spinAngle); 

     Timeline spinAnim = new Timeline(); 
     spinAnim.getKeyFrames().add(new KeyFrame(Duration.seconds(2), new KeyValue(spinAngle, 360))); 
     spinAnim.setCycleCount(Timeline.INDEFINITE); 
     spinAnim.play(); 

     Group dreidel = createDreidel(); 
     Translate zTrans = new Translate(0, 0, -(bodyHeight/2 + baseRadius)); 
     dreidel.getTransforms().addAll(spin, tilt, spin, zTrans); 

     Scene scene = new Scene(new Group(dreidel, createAxes()), 200, 200, true, SceneAntialiasing.BALANCED); 
     scene.setFill(SKYBLUE); 
     scene.setCamera(createCamera()); 

     stage.setScene(scene); 
     stage.show(); 
    } 

    private Group createDreidel() { 
     double handleHeight = bodyBase * 3/4; 
     Cylinder handle = new Cylinder(bodyBase/6, handleHeight); 
     handle.setTranslateZ(-(bodyHeight + handleHeight)/2); 
     handle.setRotationAxis(Rotate.X_AXIS); 
     handle.setRotate(90); 
     handle.setMaterial(new PhongMaterial(RED)); 

     Box body = new Box(bodyBase, bodyBase, bodyHeight); 
     body.setMaterial(new PhongMaterial(BLUE)); 

     Sphere base = new Sphere(baseRadius); 
     base.setTranslateZ(bodyHeight/2); 
     base.setMaterial(new PhongMaterial(GREEN)); 

     return new Group(handle, body, base); 
    } 

    private Camera createCamera() { 
     PerspectiveCamera camera = new PerspectiveCamera(true); 
     camera.setFarClip(1000); 

     int xy = 150; 
     Translate trans = new Translate(-xy, xy, -100); 
     Rotate rotXY = new Rotate(70, new Point3D(1, 1, 0)); 
     Rotate rotZ = new Rotate(45, new Point3D(0, 0, 1)); 
     camera.getTransforms().addAll(trans, rotXY, rotZ); 

     return camera; 
    } 

    private Group createAxes() { 
     int axisWidth = 1; 
     int axisLength = 400; 

     Cylinder xAxis = new Cylinder(axisWidth, axisLength); 
     xAxis.setMaterial(new PhongMaterial(CYAN)); 

     Cylinder yAxis = new Cylinder(axisWidth, axisLength); 
     yAxis.setRotationAxis(Rotate.Z_AXIS); 
     yAxis.setRotate(90); 
     yAxis.setMaterial(new PhongMaterial(MAGENTA)); 

     Cylinder zAxis = new Cylinder(axisWidth, axisLength); 
     zAxis.setRotationAxis(Rotate.X_AXIS); 
     zAxis.setRotate(90); 
     zAxis.setMaterial(new PhongMaterial(YELLOW)); 

     return new Group(xAxis, yAxis, zAxis); 
    } 

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

Gdzie dodałem osie reprezentacje do oglądania wygodę. Zauważ, że lista getTransforms() nie wymaga, aby jej obiekty były unikalne (w przeciwieństwie do getChildren()), co pozwala nam na ponowne użycie tej samej animacji. Kolejność animacji jest również ważna, jak podano poniżej.

Przechylanie to prosty obrót wokół osi x lub y.
Jeśli tilt i spin, getTransforms().addAll(tilt, spin, zTrans), chcemy uzyskać obrót wewnętrznego (wymienionych 1 powyżej) tylko nachylone: ​​

spin

Jeśli spin i tilt, getTransforms().addAll(spin, tilt, zTrans), to otrzymujemy precesji (wymienione 2 powyżej)

precession

Połączenie 2 jak w pełnym kodem dałoby pożądane wyniki:

full

3

Jest to kolejna możliwa odpowiedź, bardzo dużo z siedzibą w @ user1803551 podejścia, ale przy użyciu siatki 3D, który można użyć tekstury obraz, a inny okres precesji.

Jak to wygląda:

dreidel

W celu zastosowania tekstury, użyję koncepcję net dla ciała Bączek, i ten obraz:

na podstawie tego image.

Wreszcie dodam zwykły cylinder do uchwytu.

Nie będę wdawać się w szczegóły dotyczące tworzenia TriangleMesh dla ciała, ale definiujemy 9 wierzchołków (współrzędne 3D), 16 współrzędnych tekstur (2D) i 14 trójkątnych ścian, w tym indeksy wierzchołków i indeksy tekstur . Kostka jest zdefiniowana przez jego stronę width, a piramida za jej height. Wymiary netto to L = 4 * width, H = 2 * width + height.

Na przykład, twarz 0 ma wierzchołki 0 - 2 - 1, a indeksy tekstury 8 - 3 - 7, gdzie wierzchołek 0 ma współrzędne {width/2, width/2, width/2}, a indeks tekstury 8 ma współrzędne {width, 2 * width}, które są znormalizowane między [0, 1] : {width/L, 2 * width/H}.

W tym przypadku, i ze względu na próbki, wartości są sztywno:

float width = 375f; 
float height = 351f; 

to 3D klasy kształt:

class DreidelMesh extends Group { 

    float width = 375f; 
    float height = 351f; 

    public DreidelMesh(){ 
     MeshView bodyMesh = new MeshView(createBodyMesh()); 
     PhongMaterial material = new PhongMaterial(); 
     material.setDiffuseMap(new Image(getClass().getResourceAsStream("3dreidel3d.png"))); 
     bodyMesh.setMaterial(material); 

     Cylinder handle = new Cylinder(45, 260); 
     handle.setTranslateY(-(handle.getHeight() + width)/2); 
     material = new PhongMaterial(Color.web("#daaf6d")); 
     handle.setMaterial(material); 

     getTransforms().add(new Rotate(90, Rotate.X_AXIS)); 
     getChildren().addAll(bodyMesh, handle); 
    } 

    private TriangleMesh createBodyMesh() { 
     TriangleMesh m = new TriangleMesh(); 

     float L = 4f * width; 
     float H = 2f * width + height; 
     float w2 = width/2f; 

     // POINTS 
     m.getPoints().addAll(
      w2, w2, w2, 
      w2, w2, -w2, 
      w2, -w2, w2, 
      w2, -w2, -w2, 
      -w2, w2, w2, 
      -w2, w2, -w2, 
      -w2, -w2, w2, 
      -w2, -w2, -w2, 
      0f, w2 + height, 0f 
     ); 

     // TEXTURES 
     m.getTexCoords().addAll(
      width/L, 0f, 
      2f * width/ L, 0f, 
      0f, width/H, 
      width/L, width/H, 
      2f * width/ L, width/H, 
      3f * width/ L, width/H, 
      1f, width/H, 
      0f, 2f * width/H, 
      width/L, 2f * width/H, 
      2f * width/ L, 2f * width/H, 
      3f * width/ L, 2f * width/H, 
      1f, 2f * width/H, 
      width/2f/L, 1f, 
      3f * width/2f/L, 1f, 
      5f * width/2f/L, 1f, 
      7f * width/2f/L, 1f 
     ); 

     // FACES 
     m.getFaces().addAll(
      0, 8, 2, 3, 1, 7,   
      2, 3, 3, 2, 1, 7,   
      4, 9, 5, 10, 6, 4,   
      6, 4, 5, 10, 7, 5,   
      0, 8, 1, 7, 8, 12,   
      4, 9, 0, 8, 8, 13,   
      5, 10, 4, 9, 8, 14,   
      1, 11, 5, 10, 8, 15,    
      2, 3, 6, 4, 3, 0,    
      3, 0, 6, 4, 7, 1,    
      0, 8, 4, 9, 2, 3,   
      2, 3, 4, 9, 6, 4,   
      1, 11, 3, 6, 5, 10,   
      5, 10, 3, 6, 7, 5 
     ); 
     return m; 
    } 
} 

dreidel

Wreszcie, kształt ten jest dodawany do sceny, a ja zapewniają dwie animacje (zamiast jednego), po jednym dla spinu, a jeden wolniej precesji:

@Override 
public void start(Stage stage) { 
    double tiltAngle = 15; 
    DoubleProperty spinAngle = new SimpleDoubleProperty(); 
    DoubleProperty precessionAngle = new SimpleDoubleProperty(); 

    Rotate spin = new Rotate(0, Rotate.Z_AXIS); 
    Rotate precession = new Rotate(0, Rotate.Z_AXIS); 
    Rotate tilt = new Rotate(tiltAngle, Rotate.X_AXIS); 

    spin.angleProperty().bind(spinAngle); 
    precession.angleProperty().bind(precessionAngle); 

    Timeline spinAnim = new Timeline(); 
    spinAnim.getKeyFrames().add(new KeyFrame(Duration.seconds(1.5), new KeyValue(spinAngle, 360))); 
    spinAnim.setCycleCount(Timeline.INDEFINITE); 
    spinAnim.play(); 

    Timeline precessionAnim = new Timeline(); 
    precessionAnim.getKeyFrames().add(new KeyFrame(Duration.seconds(4), new KeyValue(precessionAngle, 360))); 
    precessionAnim.setCycleCount(Timeline.INDEFINITE); 
    precessionAnim.play(); 

    Group dreidel = new Group(new DreidelMesh()); 
    Translate zTrans = new Translate(0, 0, - dreidel.getBoundsInLocal().getMaxZ()); 
    dreidel.getTransforms().addAll(precession, tilt, spin, zTrans); 

    Scene scene = new Scene(new Group(dreidel), 300, 300, true, SceneAntialiasing.BALANCED); 
    scene.setFill(SKYBLUE); 
    scene.setCamera(createCamera()); 

    stage.setScene(scene); 
    stage.setTitle("JavaFX 3D - Dreidel"); 
    stage.show(); 
} 

uruchomioną aplikacją pokaże animację wyświetlane powyżej.

+0

No tak, myślę, że odpowiedziałeś na rozszerzone pytanie "Jak animować * dobrze wyglądający * bączek?" :) Moim zamiarem było skupienie się na matematyce/animacjach. Bardzo fajny efekt końcowy dzięki DIY! – user1803551

+0

Nawiasem mówiąc, animowany GIF zatrzymuje się dla mnie po 1 cyklu i obraz znika. Może być problem na moim końcu. – user1803551

+0

To działa dobrze tutaj (Mac/Safari i Windows/Firefox). –