如何为陀螺制作动画?

问题描述 投票:6回答:2

它是光明节,我正在尝试动画陀螺(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

如何才能实现与顶部图片类似的旋转轴旋转?

java animation javafx javafx-8 javafx-3d
2个回答
8
投票

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

  1. 物体围绕其内轴旋转(平行于红色手柄)。
  2. 围绕静态轴旋转其中一个内部轴(在这种情况下为z)。

从表面上看,你需要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


3
投票

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

这是它的样子:

dreidel

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

这是基于这个image

最后,我将为手柄添加一个普通的圆柱体。

我不会详细介绍如何为身体创建TriangleMesh,但我们定义了9个顶点(3D坐标),16个纹理坐标(2D)和14个三角形面,包括顶点索引和纹理索引。立方体由其侧面width定义,金字塔由其height定义。净尺寸是L = 4 * width, H = 2 * width + height

例如,面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();
}

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

© www.soinside.com 2019 - 2024. All rights reserved.