Как нарисовать текст на сфере в javafx

Я хочу нарисовать текст на сфере в javafx.

Я стараюсь, чтобы это было чисто javafx. Я использую PerspectiveCamera.

Одним из вариантов было бы просто нарисовать 3D-текст (если он существует), касающийся сферы и параллельный зрителю для хорошей читаемости. Лучшим вариантом было бы нарисовать изогнутый текст прямо на сфере, также лицом к зрителю.


person jackjuni    schedule 17.04.2019    source источник


Ответы (2)


Решение 1. Text3D

Нет встроенного узла JavaFX 3D Text. Однако вы можете использовать узел Text3DMesh из FXyz.

Что-то вроде этого:

build.gradle

plugins {
  id 'application'
  id 'org.openjfx.javafxplugin' version '0.0.7'
}

repositories {
    jcenter()
    mavenCentral()
}

dependencies {
    implementation 'org.fxyz3d:fxyz3d:0.4.0'
}

javafx {
    version = "12"
    modules = [ 'javafx.controls' ]
}

mainClassName = 'text3d.Text3D'

text3d.Text3D.java

public class Text3D extends Application {

    private final Rotate rotateX = new Rotate(10, Rotate.X_AXIS);
    private final Rotate rotateY = new Rotate(-10, Rotate.Y_AXIS);
    private double mousePosX, mousePosY, mouseOldX, mouseOldY;

    @Override
    public void start(Stage stage) throws Exception {
        PerspectiveCamera camera = new PerspectiveCamera(true);
        camera.setNearClip(0.1);
        camera.setFarClip(10000.0);
        camera.getTransforms().addAll (rotateX, rotateY, new Translate(-100, 100, -1000));

        Text3DMesh text3D = new Text3DMesh("Text3D");
        text3D.setFont(Font.getFamilies().get(new Random().nextInt(Font.getFamilies().size())));
        text3D.setFontSize(200);
        text3D.setTextureModeVertices3D(1530, p -> p.y);
        Bounds bounds = text3D.getBoundsInParent();
        text3D.setTranslateX(- bounds.getWidth() / 2d);
        text3D.setTranslateY(bounds.getHeight() / 2d);
        text3D.setTranslateZ(-bounds.getDepth());

        double radius = Math.max(bounds.getWidth(), bounds.getHeight()) * 1.2;
        SpheroidMesh spheroid = new SpheroidMesh(radius);
        spheroid.setTextureModeVertices3D(1530, p -> p.x * p.y);
        spheroid.setTranslateZ(radius);

        Group group = new Group(spheroid, text3D);

        Scene scene = new Scene(group, 400, 300, true, SceneAntialiasing.BALANCED);
        scene.setFill(Color.BISQUE);
        scene.setCamera(camera);

        scene.setOnMousePressed(event -> {
            mousePosX = event.getSceneX();
            mousePosY = event.getSceneY();
        });

        scene.setOnMouseDragged(event -> {
            mousePosX = event.getSceneX();
            mousePosY = event.getSceneY();
            rotateX.setAngle(rotateX.getAngle() - (mousePosY - mouseOldY));
            rotateY.setAngle(rotateY.getAngle() + (mousePosX - mouseOldX));
            mouseOldX = mousePosX;
            mouseOldY = mousePosY;
        });

        stage.setScene(scene);
        stage.setTitle("FXyz3D Sample");
        stage.show();
    }
}

Текст3D

Решение 2. Изображение

Другой подход можно реализовать, добавив изображение в виде диффузной карты сферы. Опять же, это можно сделать с помощью FXyz.

Во-первых, вам нужно отрендерить обычный Text 2D-узел на сцене и сделать его снимок, чтобы сгенерировать изображение.

text3d.Image3D

public class Image3D extends Application {

    private final Rotate rotateX = new Rotate(10, Rotate.X_AXIS);
    private final Rotate rotateY = new Rotate(-10, Rotate.Y_AXIS);

    private double mousePosX;
    private double mousePosY;
    private double mouseOldX;
    private double mouseOldY;

    @Override
    public void start(Stage stage) throws Exception {
        PerspectiveCamera camera = new PerspectiveCamera(true);
        camera.setNearClip(0.1);
        camera.setFarClip(10000.0);
        camera.setFieldOfView(20);
        camera.getTransforms().addAll (rotateX, rotateY, new Translate(-100, 100, -2000));

        Text text = new Text(" Text3D ");
        text.setStroke(Color.DARKGOLDENROD);
        text.setFill(Color.DARKGOLDENROD);
        text.setFont(Font.font(Font.getFamilies().get(new Random().nextInt(Font.getFamilies().size())), 30));
        Group root = new Group(text);
        Scene sceneAux = new Scene(root, root.getBoundsInLocal().getWidth(), root.getBoundsInLocal().getHeight());
        SnapshotParameters sp = new SnapshotParameters();
        double s = Screen.getPrimary().getOutputScaleX();
        sp.setTransform(new Scale(s, s));
        sp.setFill(Color.DARKMAGENTA);
        Image image = root.snapshot(sp, null);

        double radius = 400;
        SpheroidMesh spheroid = new SpheroidMesh(radius);
        ((PhongMaterial) spheroid.getMaterial()).setDiffuseMap(image);
        Group group = new Group(spheroid);

        Scene scene = new Scene(group, 400, 300, true, SceneAntialiasing.BALANCED);
        scene.setFill(Color.BISQUE);
        scene.setCamera(camera);

        scene.setOnMousePressed(event -> {
            mousePosX = event.getSceneX();
            mousePosY = event.getSceneY();
        });

        scene.setOnMouseDragged(event -> {
            mousePosX = event.getSceneX();
            mousePosY = event.getSceneY();
            rotateX.setAngle(rotateX.getAngle() - (mousePosY - mouseOldY));
            rotateY.setAngle(rotateY.getAngle() + (mousePosX - mouseOldX));
            mouseOldX = mousePosX;
            mouseOldY = mousePosY;
        });

        stage.setScene(scene);
        stage.setTitle("FXyz3D Sample");
        stage.show();
    }

}

Имидж3Д

ИЗМЕНИТЬ

Решение 3

Хотя это не является частью FXyz, вы также можете применить некоторые преобразования, чтобы обернуть Text3DMesh вокруг сферы.

Каждая буква в основном представляет собой TriangleMesh с преобразованием перевода в X (offset).

Итак, мы берем все точки для каждой буквы, удаляем преобразование и применяем смещение, так что у нас есть абсолютные координаты:

 x = x + offset;

Затем мы вычисляем угол изгиба на основе заданной длины дуги, которую мы хотим, и фактической ширины w текстового узла.

 t = (x - w/2) / (w/2) * Pi/2;

Тогда для каждой точки ее новые координаты будут:

 x = (R + z) * cos(t);
 z = (R + z) * Sin(t);

text3d.CurvedText3D.java

public class CurvedText3D extends Application {

    private final Rotate rotateX = new Rotate(10, Rotate.X_AXIS);
    private final Rotate rotateY = new Rotate(-10, Rotate.Y_AXIS);

    private double mousePosX, mousePosY, mouseOldX, mouseOldY;

    @Override
    public void start(Stage stage) throws Exception {
        PerspectiveCamera camera = new PerspectiveCamera(true);
        camera.setNearClip(0.1);
        camera.setFarClip(10000.0);
        camera.setFieldOfView(40);
        camera.getTransforms().addAll (rotateX, rotateY, new Translate(-100, 100, -2000));

        Text3DMesh text3D = new Text3DMesh("Round Text3D",
                Font.getFamilies().get(new Random().nextInt(Font.getFamilies().size())), 100, true);
        text3D.setHeight(30);
        text3D.setTextureModeNone(Color.CRIMSON);
        Bounds bounds = text3D.getBoundsInParent();
        double width = bounds.getWidth();

        double radius = Math.max(width, bounds.getHeight());

        text3D.getMeshes().stream()
                .forEach(m -> {
                    ObservableFloatArray points = ((TriangleMesh) m.getMesh()).getPoints();
                    float[] v = new float[points.size()];
                    points.toArray(v);
                    double offset = m.getTransforms().get(0).getTx();
                    m.getTransforms().clear();
                    for (int i = 0; i < v.length; i += 3) {
                        v[i] += offset;
                        double t0 =  (v[i] - width / 2d) / (width / 2d) *  Math.PI / 2;
                        double t1 = (radius + v[i + 2]) * Math.cos(t0);
                        double t2 = (radius + v[i + 2]) * Math.sin(t0);
                        v[i] = (float) t1;
                        v[i + 2] = (float) t2;
                    }
                    ((TriangleMesh) m.getMesh()).getPoints().setAll(v);
                });

        SpheroidMesh spheroid = new SpheroidMesh(radius * 0.9);
        spheroid.setTextureModeVertices3D(1530, p -> p.x * p.y);

        Group group = new Group(spheroid, text3D);

        Scene scene = new Scene(group, 400, 300, true, SceneAntialiasing.BALANCED);
        scene.setFill(Color.BISQUE);
        scene.setCamera(camera);

        scene.setOnMousePressed(event -> {
            mousePosX = event.getSceneX();
            mousePosY = event.getSceneY();
        });

        scene.setOnMouseDragged(event -> {
            mousePosX = event.getSceneX();
            mousePosY = event.getSceneY();
            rotateX.setAngle(rotateX.getAngle() - (mousePosY - mouseOldY));
            rotateY.setAngle(rotateY.getAngle() + (mousePosX - mouseOldX));
            mouseOldX = mousePosX;
            mouseOldY = mousePosY;
        });

        stage.setScene(scene);
        stage.setTitle("FXyz3D Sample");
        stage.show();
    }

}

Изогнутый текст 3d

person José Pereda    schedule 17.04.2019
comment
Выглядит очень хорошо, но неужели нельзя использовать только javafx? Даже очень хакерским способом? - person jackjuni; 18.04.2019
comment
Или, может быть, даже с awt/swing? - person jackjuni; 18.04.2019
comment
Что вы имеете в виду под «только JavaFX»? FXyz — это чистая библиотека JavaFX. Он не является частью встроенных модулей JavaFX, поэтому вы должны добавить его в качестве сторонней зависимости (через Maven/Gradle или добавив банку) в свой проект. - person José Pereda; 18.04.2019
comment
@jackjuni Когда вы знаете, что делаете, все это можно делать без какой-либо дополнительной библиотеки. Вам нужно только понять, как работает TriangleMesh, а затем применить некоторые математические вычисления, чтобы получить правильные преобразования. Но почему вы настаиваете на том, чтобы делать все это, если библиотека может сделать вашу жизнь намного проще. - person mipa; 18.04.2019

Одна идея, которая приходит мне на ум, состоит в том, чтобы нормально отобразить текст в соответствующем узле, например (TextField/Area/Flow), а затем сделать снимок этого узла. Полученное изображение затем можно использовать в качестве текстуры для обертывания вашей сферы.

person mipa    schedule 17.04.2019