如何像 JavaFX 2.0 演示中那样创建视频墙?

How to create a Video Wall like in the JavaFX 2.0 demo?

如何像此处的 JavaFX 2.0 演示那样创建视频墙:

https://www.youtube.com/watch?v=UXSmJYFrulY#t=411

首先,它不一定是视频,也可以是图片。我想要的只是像视频中那样放置节点,i。 e.呈圆柱体或球体内部的弯曲形状。

或者在某处可以找到该演示的源代码?

非常感谢。

对我来说,这似乎是创建网格的问题,或者 "Mesh" 从 ImageView 中创建。 然后将所有视口映射到要显示的图像。

例如,

Here 是使用这种方法实现 3D 的天空盒。 请注意,这只是一个简单的立方体。 图片的设置类似于this,虽然我把它分成了6张单独的图片。

如果你想用视频,我推荐你用VLCJ, 他们有 JavaFX 设置示例 Here 为此,您将对 WritableImage(s)

应用相同的原则

////////////////////////////////////////// //////////////////////////////////////////////// //////////////////////////////////////////////// /////////

我添加了一些东西供你玩...

/**
* A self initializing First Person Shooter camera
*
* @author Jason Pollastrini aka jdub1581
*/
public class SimpleFPSCamera extends Parent {

public SimpleFPSCamera() {
    initialize();
}

private void update() {
    updateControls();
}

private void updateControls() {
    if (fwd && !back) {
        moveForward();
    }
    if (strafeL) {
        strafeLeft();
    }
    if (strafeR) {
        strafeRight();
    }
    if (back && !fwd) {
        moveBack();
    }
    if (up && !down) {
        moveUp();
    }
    if (down && !up) {
        moveDown();
    }
}
/*==========================================================================
 Initialization
 */
private final Group root = new Group();
private final Affine affine = new Affine();
private final Translate t = new Translate(0, 0, 0);
private final Rotate rotateX = new Rotate(0, Rotate.X_AXIS),
        rotateY = new Rotate(0, Rotate.Y_AXIS),
        rotateZ = new Rotate(0, Rotate.Z_AXIS);

private boolean fwd, strafeL, strafeR, back, up, down, shift;

private double mouseSpeed = 1.0, mouseModifier = 0.1;
private double moveSpeed = 10.0;
private double mousePosX;
private double mousePosY;
private double mouseOldX;
private double mouseOldY;
private double mouseDeltaX;
private double mouseDeltaY;

private void initialize() {
    getChildren().add(root);
    getTransforms().addAll(affine);
    initializeCamera();
    startUpdateThread();
}

public void loadControlsForSubScene(SubScene scene) {
    sceneProperty().addListener(l -> {
        if (getScene() != null) {
            getScene().addEventHandler(KeyEvent.ANY, ke -> {
                if (ke.getEventType() == KeyEvent.KEY_PRESSED) {
                    switch (ke.getCode()) {
                        case Q:
                            up = true;
                            break;
                        case E:
                            down = true;
                            break;
                        case W:
                            fwd = true;
                            break;
                        case S:
                            back = true;
                            break;
                        case A:
                            strafeL = true;
                            break;
                        case D:
                            strafeR = true;
                            break;
                        case SHIFT:
                            shift = true;
                            moveSpeed = 20;
                            break;
                    }
                } else if (ke.getEventType() == KeyEvent.KEY_RELEASED) {
                    switch (ke.getCode()) {
                        case Q:
                            up = false;
                            break;
                        case E:
                            down = false;
                            break;
                        case W:
                            fwd = false;
                            break;
                        case S:
                            back = false;
                            break;
                        case A:
                            strafeL = false;
                            break;
                        case D:
                            strafeR = false;
                            break;
                        case SHIFT:
                            moveSpeed = 10;
                            shift = false;
                            break;
                    }
                }
                ke.consume();
            });
        }
    });
    scene.addEventHandler(MouseEvent.ANY, me -> {
        if (me.getEventType().equals(MouseEvent.MOUSE_PRESSED)) {
            mousePosX = me.getSceneX();
            mousePosY = me.getSceneY();
            mouseOldX = me.getSceneX();
            mouseOldY = me.getSceneY();

        } else if (me.getEventType().equals(MouseEvent.MOUSE_DRAGGED)) {
            mouseOldX = mousePosX;
            mouseOldY = mousePosY;
            mousePosX = me.getSceneX();
            mousePosY = me.getSceneY();
            mouseDeltaX = (mousePosX - mouseOldX);
            mouseDeltaY = (mousePosY - mouseOldY);

            mouseSpeed = 1.0;
            mouseModifier = 0.1;

            if (me.isPrimaryButtonDown()) {
                if (me.isControlDown()) {
                    mouseSpeed = 0.1;
                }
                if (me.isShiftDown()) {
                    mouseSpeed = 1.0;
                }
                t.setX(getPosition().getX());
                t.setY(getPosition().getY());
                t.setZ(getPosition().getZ());

                affine.setToIdentity();

                rotateY.setAngle(
                        Utils.clamp(-360, ((rotateY.getAngle() + mouseDeltaX * (mouseSpeed * mouseModifier)) % 360 + 540) % 360 - 180, 360)
                ); // horizontal                
                rotateX.setAngle(
                        Utils.clamp(-45, ((rotateX.getAngle() - mouseDeltaY * (mouseSpeed * mouseModifier)) % 360 + 540) % 360 - 180, 35)
                ); // vertical
                affine.prepend(t.createConcatenation(rotateY.createConcatenation(rotateX)));

            } else if (me.isSecondaryButtonDown()) {
                /*
                 init zoom?
                 */
            } else if (me.isMiddleButtonDown()) {
                /*
                 init panning?
                 */
            }
        }
    });

    scene.addEventHandler(ScrollEvent.ANY, se -> {

        if (se.getEventType().equals(ScrollEvent.SCROLL_STARTED)) {

        } else if (se.getEventType().equals(ScrollEvent.SCROLL)) {

        } else if (se.getEventType().equals(ScrollEvent.SCROLL_FINISHED)) {

        }
    });
}

public void loadControlsForScene(Scene scene) {
    scene.addEventHandler(KeyEvent.ANY, ke -> {
        if (ke.getEventType() == KeyEvent.KEY_PRESSED) {
            switch (ke.getCode()) {
                case Q:
                    up = true;
                    break;
                case E:
                    down = true;
                    break;
                case W:
                    fwd = true;
                    break;
                case S:
                    back = true;
                    break;
                case A:
                    strafeL = true;
                    break;
                case D:
                    strafeR = true;
                    break;
                case SHIFT:
                    shift = true;
                    moveSpeed = 20;
                    break;
            }
        } else if (ke.getEventType() == KeyEvent.KEY_RELEASED) {
            switch (ke.getCode()) {
                case Q:
                    up = false;
                    break;
                case E:
                    down = false;
                    break;
                case W:
                    fwd = false;
                    break;
                case S:
                    back = false;
                    break;
                case A:
                    strafeL = false;
                    break;
                case D:
                    strafeR = false;
                    break;
                case SHIFT:
                    moveSpeed = 10;
                    shift = false;
                    break;
            }
        }
        ke.consume();
    });
    scene.addEventHandler(MouseEvent.ANY, me -> {
        if (me.getEventType().equals(MouseEvent.MOUSE_PRESSED)) {
            mousePosX = me.getSceneX();
            mousePosY = me.getSceneY();
            mouseOldX = me.getSceneX();
            mouseOldY = me.getSceneY();

        } else if (me.getEventType().equals(MouseEvent.MOUSE_DRAGGED)) {
            mouseOldX = mousePosX;
            mouseOldY = mousePosY;
            mousePosX = me.getSceneX();
            mousePosY = me.getSceneY();
            mouseDeltaX = (mousePosX - mouseOldX);
            mouseDeltaY = (mousePosY - mouseOldY);

            mouseSpeed = 1.0;
            mouseModifier = 0.1;

            if (me.isPrimaryButtonDown()) {
                if (me.isControlDown()) {
                    mouseSpeed = 0.1;
                }
                if (me.isShiftDown()) {
                    mouseSpeed = 1.0;
                }
                t.setX(getPosition().getX());
                t.setY(getPosition().getY());
                t.setZ(getPosition().getZ());

                affine.setToIdentity();

                rotateY.setAngle(
                        Utils.clamp(-360, ((rotateY.getAngle() + mouseDeltaX * (mouseSpeed * mouseModifier)) % 360 + 540) % 360 - 180, 360)
                ); // horizontal                
                rotateX.setAngle(
                        Utils.clamp(-45, ((rotateX.getAngle() - mouseDeltaY * (mouseSpeed * mouseModifier)) % 360 + 540) % 360 - 180, 35)
                ); // vertical
                affine.prepend(t.createConcatenation(rotateY.createConcatenation(rotateX)));

            } else if (me.isSecondaryButtonDown()) {
                /*
                 init zoom?
                 */
            } else if (me.isMiddleButtonDown()) {
                /*
                 init panning?
                 */
            }
        }
    });

    scene.addEventHandler(ScrollEvent.ANY, se -> {

        if (se.getEventType().equals(ScrollEvent.SCROLL_STARTED)) {

        } else if (se.getEventType().equals(ScrollEvent.SCROLL)) {

        } else if (se.getEventType().equals(ScrollEvent.SCROLL_FINISHED)) {

        }
    });
}

private void initializeCamera() {
    getCamera().setNearClip(0.1);
    getCamera().setFarClip(100000);
    getCamera().setFieldOfView(42);
    getCamera().setVerticalFieldOfView(true);
    //getCamera().getTransforms().add(new Rotate(180, Rotate.Z_AXIS));
    root.getChildren().add(getCamera());
}

private void startUpdateThread() {
    new AnimationTimer() {
        @Override
        public void handle(long now) {
            update();
        }
    }.start();
}
/*==========================================================================
 Movement
 */

private void moveForward() {
    affine.setTx(getPosition().getX() + moveSpeed * getN().getX());
    affine.setTy(getPosition().getY() + moveSpeed * getN().getY());
    affine.setTz(getPosition().getZ() + moveSpeed * getN().getZ());
}

private void strafeLeft() {
    affine.setTx(getPosition().getX() + moveSpeed * -getU().getX());
    affine.setTy(getPosition().getY() + moveSpeed * -getU().getY());
    affine.setTz(getPosition().getZ() + moveSpeed * -getU().getZ());
}

private void strafeRight() {
    affine.setTx(getPosition().getX() + moveSpeed * getU().getX());
    affine.setTy(getPosition().getY() + moveSpeed * getU().getY());
    affine.setTz(getPosition().getZ() + moveSpeed * getU().getZ());
}

private void moveBack() {
    affine.setTx(getPosition().getX() + moveSpeed * -getN().getX());
    affine.setTy(getPosition().getY() + moveSpeed * -getN().getY());
    affine.setTz(getPosition().getZ() + moveSpeed * -getN().getZ());
}

private void moveUp() {
    affine.setTx(getPosition().getX() + moveSpeed * -getV().getX());
    affine.setTy(getPosition().getY() + moveSpeed * -getV().getY());
    affine.setTz(getPosition().getZ() + moveSpeed * -getV().getZ());
}

private void moveDown() {
    affine.setTx(getPosition().getX() + moveSpeed * getV().getX());
    affine.setTy(getPosition().getY() + moveSpeed * getV().getY());
    affine.setTz(getPosition().getZ() + moveSpeed * getV().getZ());
}

/*==========================================================================
 Properties
 */
private final ReadOnlyObjectWrapper<PerspectiveCamera> camera = new ReadOnlyObjectWrapper<>(this, "camera", new PerspectiveCamera(true));

public final PerspectiveCamera getCamera() {
    return camera.get();
}

public ReadOnlyObjectProperty cameraProperty() {
    return camera.getReadOnlyProperty();
}

/*==========================================================================
 Callbacks    
 | R | Up| F |  | P|
 U |mxx|mxy|mxz|  |tx|
 V |myx|myy|myz|  |ty|
 N |mzx|mzy|mzz|  |tz|

 */
//Forward / look direction    
private final Callback<Transform, Point3D> F = (a) -> {
    return new Point3D(a.getMzx(), a.getMzy(), a.getMzz());
};
private final Callback<Transform, Point3D> N = (a) -> {
    return new Point3D(a.getMxz(), a.getMyz(), a.getMzz());
};
// up direction
private final Callback<Transform, Point3D> UP = (a) -> {
    return new Point3D(a.getMyx(), a.getMyy(), a.getMyz());
};
private final Callback<Transform, Point3D> V = (a) -> {
    return new Point3D(a.getMxy(), a.getMyy(), a.getMzy());
};
// right direction
private final Callback<Transform, Point3D> R = (a) -> {
    return new Point3D(a.getMxx(), a.getMxy(), a.getMxz());
};
private final Callback<Transform, Point3D> U = (a) -> {
    return new Point3D(a.getMxx(), a.getMyx(), a.getMzx());
};
//position
private final Callback<Transform, Point3D> P = (a) -> {
    return new Point3D(a.getTx(), a.getTy(), a.getTz());
};

private Point3D getF() {
    return F.call(getLocalToSceneTransform());
}

public Point3D getLookDirection() {
    return getF();
}

private Point3D getN() {
    return N.call(getLocalToSceneTransform());
}

public Point3D getLookNormal() {
    return getN();
}

private Point3D getR() {
    return R.call(getLocalToSceneTransform());
}

private Point3D getU() {
    return U.call(getLocalToSceneTransform());
}

private Point3D getUp() {
    return UP.call(getLocalToSceneTransform());
}

private Point3D getV() {
    return V.call(getLocalToSceneTransform());
}

public final Point3D getPosition() {
    return P.call(getLocalToSceneTransform());
}
}

您修改后的代码:

public class VideoWall extends Application {

Random rand = new Random();

Group root = new Group();
PerspectiveCamera camera;
SimpleFPSCamera fpsCam;

private static final double CAMERA_INITIAL_DISTANCE = -10000;
private static final double CAMERA_NEAR_CLIP = 0.1;
private static final double CAMERA_FAR_CLIP = 100000.0;

Image[] images = new Image[]{
    new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Siberischer_tiger_de_edit02.jpg/320px-Siberischer_tiger_de_edit02.jpg"),
    new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/White_Lion.jpg/320px-White_Lion.jpg"),
    new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Lion_female.jpg/319px-Lion_female.jpg")

};

List<ParallelTransition> transitionList = new ArrayList<>();
List<ImageView> imageList = new ArrayList<>();
public VideoWall() {

}

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

/**
 * Create ImageView with random Image.
 *
 * @return
 */
private ImageView createImageView() {

    Image image = images[rand.nextInt(images.length)];

    ImageView c = new ImageView(image);

    c.setFitWidth(140);
    c.setFitWidth(100);
    c.setPreserveRatio(true);

    return c;
}

private BillboardImage createBillboardImage() {
    Image image = images[rand.nextInt(images.length)];

    BillboardImage c = new BillboardImage(image);

    c.setFitWidth(140);
    c.setFitWidth(100);
    c.setPreserveRatio(true);

    return c;
}

@Override
public void start(Stage primaryStage) {

    // build camera
    //camera = new PerspectiveCamera(true);
    //camera.setNearClip(CAMERA_NEAR_CLIP);
    //camera.setFarClip(CAMERA_FAR_CLIP);
    //camera.setTranslateZ(CAMERA_INITIAL_DISTANCE);
    fpsCam = new SimpleFPSCamera();

    // we display any node (imageview, webview, etc)
    Node node;

    // wall. the degrees depend on the distance, image size, translate start points, etc. so these values were just as they fit
    double ringBeginDeg = -30;
    double ringEndDeg = 38;

    double r = 1300;
    double yOffset = 80; // offset per image row
    double yOffsetInitial = 120; // initial y offset from "floor"

    int min = -3;
    int max = 3;
    /*
     for (double angle1 = Math.toRadians(ringBeginDeg); angle1 < Math.toRadians(ringEndDeg); angle1 += 0.08) {

     double angle2 = Math.PI;

     for (int i = min; i <= max; i++) {

     double x = r * Math.sin(angle1) * Math.cos(angle2);
     // double y = r * Math.sin(angle1) * Math.sin(angle2);
     double z = r * Math.cos(angle1);

     node = createImageView();

     node.setTranslateX(x);
     node.setTranslateY(yOffset * i - yOffsetInitial);
     node.setTranslateZ(z);

     // rotate towards viewer position
     Rotate rx = new Rotate();
     rx.setAxis(Rotate.Y_AXIS);
     rx.setAngle(Math.toDegrees(-angle1));

     node.getTransforms().addAll(rx);

     // reflection on bottom row
     if (i == max) {
     Reflection refl = new Reflection();
     refl.setFraction(0.8f);
     node.setEffect(refl);
     }

     // build the wall using a transition 
     node.setVisible(false);
     transitionList.add(createTransition(node));

     root.getChildren().add(node);

     }

     }*/

    // full sphere
    for (double angle1 = -Math.PI; angle1 <= Math.PI; angle1 += 0.48) {
        for (double angle2 = -Math.PI; angle2 <= Math.PI; angle2 += 0.48) {

            double x = r * Math.sin(angle1) * Math.cos(angle2);
            double y = r * Math.sin(angle1) * Math.sin(angle2);
            double z = r * Math.cos(angle1);

            BillboardImage c = createBillboardImage();

            c.setTranslateX(x);
            c.setTranslateY(y);
            c.setTranslateZ(z);


            imageList.add(c);
        }
    }
    root.getChildren().add(fpsCam);
    root.getChildren().addAll(imageList);
    Scene scene = new Scene(root, 1600, 900, true, SceneAntialiasing.BALANCED);
    scene.setFill(Color.BLACK);
    scene.setCamera(fpsCam.getCamera());
    fpsCam.loadControlsForScene(scene);

    primaryStage.setScene(scene);
    primaryStage.show();

    AnimationTimer timer = createBillboardTimer();
    timer.start();

}

private AnimationTimer createBillboardTimer() {
    return new AnimationTimer() {
        @Override
        public void handle(long now) {
            if(!imageList.isEmpty()){
                imageList.stream().forEach(bbi ->{
                    ((BillboardImage)bbi).updateMatrix(bbi, fpsCam);
                });
            }
        }
    };
}

private AnimationTimer createAnimation() {

    Collections.sort(transitionList, new Comparator<ParallelTransition>() {

        @Override
        public int compare(ParallelTransition arg0, ParallelTransition arg1) {

            // bottom right to top left
            Point2D ref = new Point2D(1000, 1000);
            Point2D pt0 = new Point2D(arg0.getNode().getTranslateX(), arg0.getNode().getTranslateY());
            Point2D pt1 = new Point2D(arg1.getNode().getTranslateX(), arg1.getNode().getTranslateY());

            return Double.compare(ref.distance(pt0), ref.distance(pt1));

            // bottom row first
            // return -Double.compare( arg0.getNode().getTranslateY(), arg1.getNode().getTranslateY());
        }

    });

    AnimationTimer timer = new AnimationTimer() {

        long last = 0;

        @Override
        public void handle(long now) {

            //if( (now - last) > 1_000_000_000) 
            if ((now - last) > 40_000_000) {
                if (transitionList.size() > 0) {

                    ParallelTransition t = transitionList.remove(0);
                    t.getNode().setVisible(true);
                    t.play();

                }
                last = now;

            }

            if (transitionList.size() == 0) {
                stop();
            }
        }

    };

    return timer;
}

private ParallelTransition createTransition(final Node node) {

    Path path = new Path();
    path.getElements().add(new MoveToAbs(node, node.getTranslateX() - 1000, node.getTranslateY() - 900));
    path.getElements().add(new LineToAbs(node, node.getTranslateX(), node.getTranslateY()));

    Duration duration = Duration.millis(1500);

    PathTransition pt = new PathTransition(duration, path, node);

    RotateTransition rt = new RotateTransition(duration, node);
    rt.setByAngle(720);
    rt.setAutoReverse(true);

    ParallelTransition parallelTransition = new ParallelTransition();
    parallelTransition.setNode(node);
    parallelTransition.getChildren().addAll(pt, rt);

    return parallelTransition;

}

public static class MoveToAbs extends MoveTo {

    public MoveToAbs(Node node, double x, double y) {
        super(x - node.getLayoutX() + node.getLayoutBounds().getWidth() / 2, y - node.getLayoutY() + node.getLayoutBounds().getHeight() / 2);
    }

}

public static class LineToAbs extends LineTo {

    public LineToAbs(Node node, double x, double y) {
        super(x - node.getLayoutX() + node.getLayoutBounds().getWidth() / 2, y - node.getLayoutY() + node.getLayoutBounds().getHeight() / 2);
    }

}

/*

 */
public enum BillboardMode {
    SPHERICAL,
    CYLINDRICAL;
}

private class BillboardImage extends ImageView{

// Add transform to Node that needs to look at target..
    public Affine affine = new Affine();

    public BillboardImage() {
        this.getTransforms().add(affine);
    }

    public BillboardImage(String url) {
        super(url);
        this.getTransforms().add(affine);
    }

    public BillboardImage(Image image) {
        super(image);
        this.getTransforms().add(affine);
    }


// set up to look at camera, can change to any other Node

    protected void updateMatrix(Node billBoardNode, Node other) {
        Transform self = billBoardNode.getLocalToSceneTransform(),
                oth = other.getLocalToSceneTransform();
        Bounds b;
        double cX, cY, cZ;
        if (!(billBoardNode instanceof Shape3D)) {
            b = billBoardNode.getBoundsInLocal();
            cX = b.getWidth() / 2;
            cY = b.getHeight() / 2;
            cZ = b.getDepth() / 2;
        } else {
            cX = self.getTx();
            cY = self.getTy();
            cZ = self.getTz();
        }
        Point3D otherPos = Point3D.ZERO.add(oth.getTx(), oth.getTy(), oth.getTz());
        Point3D selfPos = new Point3D(cX, cY, cZ);
        Point3D up = Point3D.ZERO.add(0, -1, 0),
                forward = new Point3D(
                        (selfPos.getX()) - otherPos.getX(),
                        (selfPos.getY()) - otherPos.getY(),
                        (selfPos.getZ()) - otherPos.getZ()
                ).normalize(),
                right = up.crossProduct(forward).normalize();
        up = forward.crossProduct(right).normalize();
        switch (getBillboardMode()) {
            case SPHERICAL:
                affine.setMxx(right.getX()); affine.setMxy(up.getX());affine.setMzx(forward.getX());
                affine.setMyx(right.getY());affine.setMyy(up.getY()); affine.setMzy(forward.getY());
                affine.setMzx(right.getZ());affine.setMzy(up.getZ());affine.setMzz(forward.getZ());
                affine.setTx(cX * (1 - affine.getMxx()) - cY * affine.getMxy() - cZ * affine.getMxz());
                affine.setTy(cY * (1 - affine.getMyy()) - cX * affine.getMyx() - cZ * affine.getMyz());
                affine.setTz(cZ * (1 - affine.getMzz()) - cX * affine.getMzx() - cY * affine.getMzy());
                break;
            case CYLINDRICAL:
                affine.setMxx(right.getX());affine.setMxy(0);affine.setMzx(forward.getX());
                affine.setMyx(0);affine.setMyy(1);affine.setMzy(0);
                affine.setMzx(right.getZ()); affine.setMzy(0);affine.setMzz(forward.getZ());
                affine.setTx(cX * (1 - affine.getMxx()) - cY * affine.getMxy() - cZ * affine.getMxz());
                affine.setTy(cY * (1 - affine.getMyy()) - cX * affine.getMyx() - cZ * affine.getMyz());
                affine.setTz(cZ * (1 - affine.getMzz()) - cX * affine.getMzx() - cY * affine.getMzy());
                break;
        }
    }

    public BillboardMode getBillboardMode() {
        return BillboardMode.SPHERICAL;
    }
}
}

至少现在图像被广告牌显示在相机上了.. 也可以随意使用我的 FPSCamera ..

控制就像任何标准的第一人称射击游戏

w = 前进,s = 后退, a = 向左扫射,d = 向右扫射, q & e 上下。

我研究并找到了一个非常棒的网站,其中包含相关信息:

http://paulbourke.net/geometry/transformationprojection/

相关部分是坐标系转换,特别是笛卡尔坐标和球坐标之间的转换方程。

double x = r * Math.sin(angle1) * Math.cos(angle2);
double y = r * Math.sin(angle1) * Math.sin(angle2);
double z = r * Math.cos(angle1);

在我下面的示例中,公式中没有使用 y,因为图像行是堆叠的。

注意:通过在从 -Math.PI 到 Math.PI 的 2 个嵌套 for 循环中使用这些公式,您可以围绕球体布置节点。关于整个球体的困难部分是将节点旋转到中心,那个我想不出来。

由于我不熟悉 Java3D,所以我也查看了 Building a 3D Sample Application

最后搞了个电视墙,代码缩减成这样:

public class VideoWall extends Application {

    Random rand = new Random();

    Group root = new Group();
    PerspectiveCamera camera;

    private static final double CAMERA_INITIAL_DISTANCE = -850;
    private static final double CAMERA_NEAR_CLIP = 0.1;
    private static final double CAMERA_FAR_CLIP = 10000.0;

    Image[] images = new Image[] {
            new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Siberischer_tiger_de_edit02.jpg/320px-Siberischer_tiger_de_edit02.jpg"),
            new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/White_Lion.jpg/320px-White_Lion.jpg"),
            new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Lion_female.jpg/319px-Lion_female.jpg")

    };

    public VideoWall(){

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

    /**
     * Create ImageView with random Image.
     * @return
     */
    private ImageView createImageView() {

        Image image = images[ rand.nextInt(images.length)];

        ImageView c = new ImageView( image);

        c.setFitWidth(140);
        c.setFitWidth(100);
        c.setPreserveRatio(true);

        return c;
    }

    @Override
    public void start(Stage primaryStage) {

        // build camera
        camera = new PerspectiveCamera(true);
        camera.setNearClip(CAMERA_NEAR_CLIP);
        camera.setFarClip(CAMERA_FAR_CLIP);
        camera.setTranslateZ(CAMERA_INITIAL_DISTANCE);

        // we display any node (imageview, webview, etc)
        Node node;

        // create a single webview; we only add it once because we don't want to flood youtube
        WebView webView = new WebView();
        webView.getEngine().load(
          "http://www.youtube.com/embed/utUPth77L_o?autoplay=1"
        );
        webView.setPrefSize(100, 70);

        // wall. the degrees depend on the distance, image size, translate start points, etc. so these values were just as they fit
        double ringBeginDeg = -30;
        double ringEndDeg = 38;

        double r = 1300; 
        double yOffset = 80; // offset per image row
        double yOffsetInitial = 120; // initial y offset from "floor"

        int count=0;

        for( double angle1=Math.toRadians(ringBeginDeg); angle1 <Math.toRadians(ringEndDeg); angle1+=0.08)
        {

            double angle2 = Math.PI;

            for( int i=-3; i <= 3; i++)
            {

                double x = r * Math.sin(angle1) * Math.cos(angle2);
                // double y = r * Math.sin(angle1) * Math.sin(angle2);
                double z = r * Math.cos(angle1);

                // add 1 webview, the rest imageviews 
                if( count == 16) {
                    node = webView;
                } else {
                    node = createImageView();
                }

                node.setTranslateX(x);
                node.setTranslateY(yOffset * i - yOffsetInitial);
                node.setTranslateZ(z);

                // rotate towards viewer position
                Rotate rx = new Rotate();
                rx.setAxis(Rotate.Y_AXIS);
                rx.setAngle(Math.toDegrees( -angle1));

                node.getTransforms().addAll(rx);

                root.getChildren().add( node);

                count++;
            }

        }

        Scene scene = new Scene(root, 1600, 900, Color.BLACK);

        primaryStage.setScene( scene);
        primaryStage.show();

        scene.setCamera(camera);
    }

}

您可以添加您喜欢的任何节点。我添加了一个 youtube webview 进行测试。它播放,但视频没有加载,所以你看到的只是静态噪音(屏幕截图中的灰色方块)。所以从理论上讲,您可以使用 youtube 视频使节点成为所有 webview,但这意味着充斥 youtube。最好使用一些离线视频。

截图如下:

我还玩弄了完整的 3d 示例并创建了一个戒指。这就是它从外部视图看起来的样子(始终具有相同的图像):

将相机放在中央,您可以很好地滚动环。

如果有人想玩,here's a quick & dirty gist with the navigable ring。使用 left/right/middle 鼠标按钮进行导航。

如果你想玩弄一个完整的球体,你可以使用这个:

// full sphere
for (double angle1 = -Math.PI; angle1 <= Math.PI; angle1 += 0.15) {
    for (double angle2 = -Math.PI; angle2 <= Math.PI; angle2 += 0.15) {

        double x = r * Math.sin(angle1) * Math.cos(angle2);
        double y = r * Math.sin(angle1) * Math.sin(angle2);
        double z = r * Math.cos(angle1);

        c = createImageView();

        c.setTranslateX(x);
        c.setTranslateY(y);
        c.setTranslateZ(z);

        Rotate rx = new Rotate();
        rx.setAxis(Rotate.Y_AXIS);
        rx.setAngle(Math.toDegrees(-angle1));

        c.getTransforms().addAll(rx);

        world.getChildren().add(c);
    }
}

看起来像这样:

但如前所述,我还没有想出如何旋转所有图块,使它们朝向中心。他们需要平均分配。但这只是为了好玩和题外话。


因为它是我问题中视频的一部分,所以只需要保留一个平行过渡列表来创建图块的 "build-up" 动画。底行现在有反射。

扩展代码:

public class VideoWall extends Application {

    Random rand = new Random();

    Group root = new Group();
    PerspectiveCamera camera;

    private static final double CAMERA_INITIAL_DISTANCE = -850;
    private static final double CAMERA_NEAR_CLIP = 0.1;
    private static final double CAMERA_FAR_CLIP = 10000.0;

    Image[] images = new Image[] {
            new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Siberischer_tiger_de_edit02.jpg/320px-Siberischer_tiger_de_edit02.jpg"),
            new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/White_Lion.jpg/320px-White_Lion.jpg"),
            new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Lion_female.jpg/319px-Lion_female.jpg")

    };

    List<ParallelTransition> transitionList = new ArrayList<>();

    public VideoWall(){

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

    /**
     * Create ImageView with random Image.
     * @return
     */
    private ImageView createImageView() {

        Image image = images[ rand.nextInt(images.length)];

        ImageView c = new ImageView( image);

        c.setFitWidth(140);
        c.setFitWidth(100);
        c.setPreserveRatio(true);

        return c;
    }

    @Override
    public void start(Stage primaryStage) {

        // build camera
        camera = new PerspectiveCamera(true);
        camera.setNearClip(CAMERA_NEAR_CLIP);
        camera.setFarClip(CAMERA_FAR_CLIP);
        camera.setTranslateZ(CAMERA_INITIAL_DISTANCE);

        // we display any node (imageview, webview, etc)
        Node node;

        // wall. the degrees depend on the distance, image size, translate start points, etc. so these values were just as they fit
        double ringBeginDeg = -30;
        double ringEndDeg = 38;

        double r = 1300; 
        double yOffset = 80; // offset per image row
        double yOffsetInitial = 120; // initial y offset from "floor"

        int min = -3;
        int max = 3;

        for( double angle1=Math.toRadians(ringBeginDeg); angle1 <Math.toRadians(ringEndDeg); angle1+=0.08)
        {

            double angle2 = Math.PI;

            for( int i=min; i <= max; i++)
            {

                double x = r * Math.sin(angle1) * Math.cos(angle2);
                // double y = r * Math.sin(angle1) * Math.sin(angle2);
                double z = r * Math.cos(angle1);

                node = createImageView();

                node.setTranslateX(x);
                node.setTranslateY(yOffset * i - yOffsetInitial);
                node.setTranslateZ(z);

                // rotate towards viewer position
                Rotate rx = new Rotate();
                rx.setAxis(Rotate.Y_AXIS);
                rx.setAngle(Math.toDegrees( -angle1));

                node.getTransforms().addAll(rx);

                // reflection on bottom row
                if( i==max) {
                    Reflection refl = new Reflection();
                    refl.setFraction(0.8f);
                    node.setEffect(refl);
                }

                // build the wall using a transition 
                node.setVisible(false);
                transitionList.add( createTransition( node));

                root.getChildren().add( node);

            }

        }

        Scene scene = new Scene(root, 1600, 900, Color.BLACK);

        primaryStage.setScene( scene);
        primaryStage.show();

        scene.setCamera(camera);

        AnimationTimer timer = createAnimation();
        timer.start();

    }

    private AnimationTimer createAnimation() {

        Collections.sort(transitionList, new Comparator<ParallelTransition>() {

            @Override
            public int compare(ParallelTransition arg0, ParallelTransition arg1) {

                // bottom right to top left
                Point2D ref = new Point2D(1000,1000);
                Point2D pt0 = new Point2D( arg0.getNode().getTranslateX(), arg0.getNode().getTranslateY());
                Point2D pt1 = new Point2D( arg1.getNode().getTranslateX(), arg1.getNode().getTranslateY());

                return Double.compare(ref.distance(pt0), ref.distance(pt1));

                // bottom row first
                // return -Double.compare( arg0.getNode().getTranslateY(), arg1.getNode().getTranslateY());

            }

        });


        AnimationTimer timer = new AnimationTimer() {

            long last = 0;

            @Override
            public void handle(long now) {

                //if( (now - last) > 1_000_000_000) 
                if( (now - last) >   40_000_000) 
                {
                    if( transitionList.size() > 0) {

                        ParallelTransition t = transitionList.remove(0);
                        t.getNode().setVisible(true);
                        t.play();

                    }
                    last = now;

                }

                if( transitionList.size() == 0) {
                    stop();
                }
            }

        };

        return timer;
    }

    private ParallelTransition createTransition( final Node node) {

        Path path = new Path();
        path.getElements().add(new MoveToAbs( node, node.getTranslateX() - 1000, node.getTranslateY() - 900));
        path.getElements().add(new LineToAbs( node, node.getTranslateX(), node.getTranslateY()));

        Duration duration = Duration.millis(1500);

        PathTransition pt = new PathTransition( duration, path, node);

        RotateTransition rt = new RotateTransition( duration, node);
        rt.setByAngle(720);
        rt.setAutoReverse(true);

        ParallelTransition parallelTransition = new ParallelTransition();
        parallelTransition.setNode(node);
        parallelTransition.getChildren().addAll(pt, rt);

        return parallelTransition;

    }

    public static class MoveToAbs extends MoveTo {

        public MoveToAbs( Node node, double x, double y) {
            super( x - node.getLayoutX() + node.getLayoutBounds().getWidth() / 2, y - node.getLayoutY() + node.getLayoutBounds().getHeight() / 2);
        }

    }

    public static class LineToAbs extends LineTo {

        public LineToAbs( Node node, double x, double y) {
            super( x - node.getLayoutX() + node.getLayoutBounds().getWidth() / 2, y - node.getLayoutY() + node.getLayoutBounds().getHeight() / 2);
        }

    }   

}