如何为陀螺制作动画?

时间:2017-12-14 18:24:40

标签: java animation javafx javafx-8 javafx-3d

它的光明节和我试图制作旋转陀螺(dreidel)的动画:

spinning top

我可以让它在自己的轴上旋转。这是我的代码:

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

我创建了一个简单的模型,围绕其轴旋转,并将其翻译为其尖端在(0, 0, 0)上。结果如下:

enter image description here

如何在旋转轴周围旋转的顶部图片上实现类似的效果?

2 个答案:

答案 0 :(得分:8)

对象旋转的轴的旋转称为Precession。旋转顶部运动需要2次旋转:

  1. 物体绕其内轴旋转(与红色手柄平行)。
  2. 围绕静态轴旋转其中一个内部轴(在本例中为 z )。
  3. 从表面上看,您需要2 Animation个实例。但是,两个旋转实际上是相同的。两者的轴心点都是(0, 0, 0)(在zTrans之后)并且它们都在 z 轴周围,只有一个轴倾斜一个角度。

    以下是修改后的代码:

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

    我添加轴表示以方便查看。请注意,getTransforms()列表不要求其对象是唯一的(与getChildren()不同),这允许我们重用相同的动画。如下所述,动画的顺序也很重要。

    倾斜是围绕 x y 轴的简单旋转。
    如果我们tilt然后spingetTransforms().addAll(tilt, spin, zTrans),我们会得到内部轮换(上面列出的1),只是倾斜:

    spin

    如果我们spin然后tiltgetTransforms().addAll(spin, tilt, zTrans),我们会得到进动(上面列出的2):

    precession

    在完整代码中组合2将得到所需的结果:

    full

答案 1 :(得分:3)

这是另一个可能的答案,非常基于@ user1803551方法,但使用可以使用纹理图像的3D网格,以及不同的进动周期。

这就是它的样子:

dreidel

为了应用纹理,我会对dreidel的主体使用net概念,这个图像:

基于此image

最后,我将为手柄添加一个常规圆柱体。

我不会详细介绍如何为身体创建TriangleMesh,但我们定义了9个顶点(3D坐标),16个纹理坐标(2D)和14个包含顶点的三角形面指数和纹理指数。多维数据集由其边width定义,金字塔由其height定义。净尺寸为L = 4 * width, H = 2 * width + height

例如,face 0的顶点为0 - 2 - 1,纹理索引为8 - 3 - 7,其中顶点0的坐标为{width / 2, width / 2, width / 2},纹理索引8的坐标为{width, 2 * width},在[0,1]之间归一化:{width / L, 2 * width / H}

在这种情况下,为了样本,值是硬编码的:

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

这是3D形状类:

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

最后,这个形状被添加到场景中,我将提供两个动画(而不是一个),一个用于旋转,另一个用于进动:

@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();
}

运行应用程序将显示上面显示的动画。