基于放大/缩小的可见区域在Box的表面上显示文本 [英] Showing texts over the face of a Box based on the visible area on zooming in/out

查看:110
本文介绍了基于放大/缩小的可见区域在Box的表面上显示文本的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个示例3D应用程序(通过参考Javafx示例3DViewer构建),它有一个通过布局Boxes和Panes创建的表:



表格居中于wrt(0,0,0)坐标,相机位于-z最初的位置。



它根据物体的相机z位置进行放大/缩小。
放大/缩小对象的boundsInParent增加/减少,即面部区域增加/减少。因此,当面部区域太小时,我们的想法是在我们有更多区域(总是限制在脸部内)和较小文本或没有文本时放置更多文本。我能够使用这个节点层次结构来做到这一点:





并在每次放大/缩小时按照Box调整窗格大小(以及管理vBox及其中的文本数量)。



现在问题是表boundsInParent给出的结果不正确(



深入研究



我还没玩过那些文本节点的数量可以添加到VBox,字体大小以及避免在每个滚动上生成图像的策略:只有在文本更改时才应该这样做。因此,目前的方法非常缓慢,但可以显着改善,因为每个盒子只有三个可能的图像。


I have a sample 3D application (built by taking reference from the Javafx sample 3DViewer) which has a table created by laying out Boxes and Panes:

The table is centered wrt (0,0,0) coordinates and camera is at -z position initially.

It has the zoom-in/out based on the camera z position from the object. On zooming in/out the object's boundsInParent increases/decreases i.e. area of the face increases/decreases. So the idea is to put more text when we have more area (always confining within the face) and lesser text or no text when the face area is too less. I am able to to do that using this node hierarchy:

and resizing the Pane (and managing the vBox and number of texts in it) as per Box on each zoom-in/out.

Now the issue is that table boundsInParent is giving incorrect results (table image showing the boundingBox off at the top) whenever a text is added to the vBox for the first time only. On further zooming-in/out gives correct boundingBox and does not go off.

Below is the UIpane3D class:

public class UIPane3D extends Pane
{
VBox textPane;

ArrayList<String> infoTextKeys = new ArrayList<>();
ArrayList<Text> infoTextValues = new ArrayList<>();

Rectangle bgCanvasRect = null;

final double fontSize = 16.0;   

public UIPane3D() {
    setMouseTransparent(true);
    textPane = new VBox(2.0)
}

public void updateContent() {
    textPane.getChildren().clear();
    getChildren().clear();

    for (Text textNode : infoTextValues) {
        textPane.getChildren().add(textNode);
        textPane.autosize();
        if (textPane.getHeight() > getHeight()) {
            textPane.getChildren().remove(textNode);
            textPane.autosize();

            break;
        }
    }

    textPane.setTranslateY(getHeight() / 2 - textPane.getHeight() / 2.0);

    bgCanvasRect = new Rectangle(getWidth(), getHeight());
    bgCanvasRect.setFill(Color.web(Color.BURLYWOOD.toString(), 0.10));
    bgCanvasRect.setVisible(true);

    getChildren().addAll(bgCanvasRect, textPane);
}


public void resetInfoTextMap()
{
    if (infoTextKeys != null || infoTextValues != null) 
    {
        try 
        {
            infoTextKeys.clear();
            infoTextValues.clear();     
        } catch (Exception e){e.printStackTrace();}
    }
}


public void updateInfoTextMap(String pKey, String pValue)
{
    int index = -1;
    boolean objectFound = false;

    for (String string : infoTextKeys) 
    {
        index++;
        if(string.equals(pKey))
        {
            objectFound = true;
            break;
        }
    }

    if(objectFound)
    {
        infoTextValues.get(index).setText(pValue.toUpperCase());
    }
    else
    {
        if (pValue != null) 
        {   
            Text textNode = new Text(pValue.toUpperCase());
            textNode.setFont(Font.font("Consolas", FontWeight.BLACK, FontPosture.REGULAR, fontSize));
            textNode.wrappingWidthProperty().bind(widthProperty());
            textNode.setTextAlignment(TextAlignment.CENTER);
            infoTextKeys.add(pKey);
            infoTextValues.add(textNode);
        }
    }
}

}

The code which get called at the last after all the manipulations:

public void refreshBoundingBox()
{
    if(boundingBox != null)
    {
        root3D.getChildren().remove(boundingBox);
    }

    PhongMaterial blueMaterial = new PhongMaterial();
    blueMaterial.setDiffuseColor(Color.web(Color.CRIMSON.toString(), 0.25));

    Bounds tableBounds = table.getBoundsInParent();
    boundingBox = new Box(tableBounds.getWidth(), tableBounds.getHeight(), tableBounds.getDepth());
    boundingBox.setMaterial(blueMaterial);
    boundingBox.setTranslateX(tableBounds.getMinX() + tableBounds.getWidth()/2.0);
    boundingBox.setTranslateY(tableBounds.getMinY() + tableBounds.getHeight()/2.0);
    boundingBox.setTranslateZ(tableBounds.getMinZ() + tableBounds.getDepth()/2.0);
    boundingBox.setMouseTransparent(true);

    root3D.getChildren().add(boundingBox);
}

Two things:

  1. The table3D's boundsInParent is not updated properly when texts are added for the first time.

  2. What would be the right way of putting texts on 3D nodes? I am having to manipulate a whole lot to bring the texts as required.

Sharing code here.

解决方案

For the first question, about the "jump" that can be noticed just when after scrolling a new text item is laid out:

After digging into the code, I noticed that the UIPane3D has a VBox textPane that contains the different Text nodes. Every time updateContent is called, it tries to add a text node, but it checks that the vbox's height is always lower than the pane's height, or else the node will be removed:

    for (Text textNode : infoTextValues) {
        textPane.getChildren().add(textNode);
        textPane.autosize();
        if (textPane.getHeight() > getHeight()) {
            textPane.getChildren().remove(textNode);
            textPane.autosize();
            break;
        }
    }

While this is basically correct, when you add a node to the scene, you can't get textPane.getHeight() immediately, as it hasn't been laid out yet, and you have to wait until the next pulse. This is why the next time you scroll, the height is correct and the bounding box is well placed.

One way to force the layout and get the correct height of the textNode is by forcing css and a layout pass:

    for (Text textNode : infoTextValues) {
        textPane.getChildren().add(textNode);

        // force css and layout
        textPane.applyCss();
        textPane.layout();

        textPane.autosize();
        if (textPane.getHeight() > getHeight()) {
            textPane.getChildren().remove(textNode);
            textPane.autosize();
            break;
        }
    }

Note that:

This method [applyCss] does not normally need to be invoked directly but may be used in conjunction with Parent.layout() to size a Node before the next pulse, or if the Scene is not in a Stage.

For the second question, about a different solution to add Text to 3D Shape.

Indeed, placing a (2D) text on top of a 3D shape is quite difficult, and requires complex maths (that are done quite nicely in the project, by the way).

There is an alternative avoiding the use of 2D nodes directly.

Precisely in a previous question, I "wrote" into an image, that later on I used as the material diffuse map of a 3D shape.

The built-in 3D Box places the same image into every face, so that wouldn't work. We can implement a 3D prism, or we can make use of the CuboidMesh node from the FXyz3D library.

Replacing the Box in UIPaneBoxGroup:

final CuboidMesh contentShape;
UIPane3D displaypane = null;
PhongMaterial shader = new PhongMaterial();
final Color pColor;

public UIPaneBoxGroup(final double pWidth, final double pHeight, final double pDepth, final Color pColor) { 
    contentShape = new CuboidMesh(pWidth, pHeight, pDepth);
    this.pColor = pColor;
    contentShape.setMaterial(shader);
    getChildren().add(contentShape);
    addInfoUIPane();
}

and adding the generateNet method:

private Image generateNet(String string) {

    GridPane grid = new GridPane();
    grid.setAlignment(Pos.CENTER);

    Label label5 = new Label(string);
    label5.setFont(Font.font("Consolas", FontWeight.BLACK, FontPosture.REGULAR, 40));
    GridPane.setHalignment(label5, HPos.CENTER);

    grid.add(label5, 3, 1);

    double w = contentShape.getWidth() * 10; // more resolution
    double h = contentShape.getHeight() * 10;
    double d = contentShape.getDepth() * 10;
    final double W = 2 * d + 2 * w;
    final double H = 2 * d + h;

    ColumnConstraints col1 = new ColumnConstraints();
    col1.setPercentWidth(d * 100 / W);
    ColumnConstraints col2 = new ColumnConstraints();
    col2.setPercentWidth(w * 100 / W);
    ColumnConstraints col3 = new ColumnConstraints();
    col3.setPercentWidth(d * 100 / W);
    ColumnConstraints col4 = new ColumnConstraints();
    col4.setPercentWidth(w * 100 / W);
    grid.getColumnConstraints().addAll(col1, col2, col3, col4);

    RowConstraints row1 = new RowConstraints();
    row1.setPercentHeight(d * 100 / H);
    RowConstraints row2 = new RowConstraints();
    row2.setPercentHeight(h * 100 / H);
    RowConstraints row3 = new RowConstraints();
    row3.setPercentHeight(d * 100 / H);
    grid.getRowConstraints().addAll(row1, row2, row3);
    grid.setPrefSize(W, H);
    grid.setBackground(new Background(new BackgroundFill(pColor, CornerRadii.EMPTY, Insets.EMPTY)));
    new Scene(grid);
    return grid.snapshot(null, null);
}

Now all the 2D related code can be removed (including displaypane), and after a scrolling event get the image:

public void refreshBomUIPane() {        
    Image net = generateNet(displaypane.getText());
    shader.setDiffuseMap(net);
}

where in UIPane3D:

public String getText() {
    return infoTextKeys.stream().collect(Collectors.joining("\n"));
}

I've also removed the bounding box to get this picture:

I haven't played around with the number of text nodes that can be added to the VBox, the font size nor with an strategy to avoid generating images on every scroll: only when the text changes this should be done. So with the current approach is quite slow, but it can be improved notably as there are only three possible images for each box.

这篇关于基于放大/缩小的可见区域在Box的表面上显示文本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆