Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to draw text on a sphere in javafx

I want to draw a text onto a sphere in javafx.

I try to keep it purely javafx. I'm using a PerspectiveCamera.

One option would be to just draw 3D-text (if that exists) touching the sphere and being parallel to the viewer for good readability. The better option would be to draw a curved text right onto the sphere, also facing the viewer.

like image 876
jackjuni Avatar asked Apr 17 '19 14:04

jackjuni


2 Answers

Solution 1. Text3D

There is no built-in JavaFX 3D Text node. However you can use a Text3DMesh node from the FXyz library.

Something like this:

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

Text3D

Solution 2. Image

Another approach can be done by adding an image as diffuse map of the sphere. Again, this can be done with FXyz.

First, you need to render a regular Text 2D node on a scene, and do a snapshot of it to generate the image.

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

}

Image3D

EDIT

Solution 3

While this is not part of FXyz, you can also apply some transforms to wrap the Text3DMesh around the sphere.

Each letter basically is a TriangleMesh with a translate transform in X (offset).

So we take all the points for each letter, remove the transform, and apply the offset, so we have absolute coordinates:

 x = x + offset;

Then we calculate the bending angle based on a given arc length we want and the actual width w of the text node.

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

Then, for each point, its new coordinates will be:

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

}

Curved text 3d

like image 87
José Pereda Avatar answered Nov 10 '22 01:11

José Pereda


One idea that comes to my mind is to render the text normally in an appropriate node like (TextField/Area/Flow) and then take a snapshot of this node. The resulting image could then be used as a texture to wrap around your sphere.

like image 30
mipa Avatar answered Nov 10 '22 01:11

mipa