根据缩放时的可见区域在 Box 的表面显示文本 in/out

Showing texts over the face of a Box based on the visible area on zooming in/out

我有一个示例 3D 应用程序(通过参考 Javafx 示例 3DViewer 构建),它有一个 table 通过布置框和窗格创建的:

table 以 (0,0,0) 坐标为中心,相机最初位于 -z 位置。

它有 zoom-in/out 基于物体的相机 z 位置。 缩放 in/out 对象的 boundsInParent increases/decreases 即面部区域 increases/decreases。所以我们的想法是当我们有更多的区域(总是限制在面部)时放置更多的文本,当面部区域太小时放置更少的文本或不放置文本。我可以使用此节点层次结构来做到这一点:

并根据每个 zoom-in/out 上的框调整窗格大小(并管理 vBox 和其中的文本数量)zoom-in/out。

现在的问题是,仅在第一次将文本添加到 vBox 时,table boundsInParent 会给出不正确的结果(table image 在顶部显示 boundingBox)。在进一步缩放时-in/out 给出正确的 boundingBox 并且不会关闭。

下面是 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);
        }
    }
}

}

所有操作后最后调用的代码:

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

两件事:

  1. 首次添加文本时,table3D 的 boundsInParent 未正确更新。

  2. 在 3D 节点上放置文本的正确方法是什么?我不得不进行大量操作才能根据需要提供文本。

分享代码here.

第一个问题,关于在滚动一个新的文本项后刚好布局时可以注意到的"jump":

深入研究 code 后,我注意到 UIPane3D 有一个包含不同文本节点的 VBox textPane。每次调用 updateContent 时,它都会尝试添加一个文本节点,但它会检查 vbox 的高度是否始终低于窗格的高度,否则该节点将被删除:

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

虽然这基本上是正确的,但是当你在场景中添加一个节点时,你不能立即得到textPane.getHeight(),因为它还没有布局,你必须等到下一个脉冲。这就是为什么下次滚动时,高度正确且边界框位置正确的原因。

强制布局并获得 textNode 正确高度的一种方法是强制 css 和布局传递:

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

注意:

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.

关于第二个问题,关于将文本添加到 3D 形状的不同解决方案。

事实上,将 (2D) 文本放置在 3D 形状之上非常困难,并且需要复杂的数学运算(顺便说一句,在项目中做得很好)。

有一种替代方法可以避免直接使用 2D 节点。

正是在之前的 中,我 "wrote" 制作了一张图像,后来我将其用作 3D 形状的 material 漫反射贴图。

内置的 3D Box 将相同的图像放入每张脸上,所以这是行不通的。我们可以实现 3D 棱镜,或者我们可以使用 FXyz3D library.

中的 CuboidMesh 节点

替换 UIPaneBoxGroup 中的 Box:

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

并添加 generateNet 方法:

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

现在可以去掉所有二维相关的代码(包括displaypane),滚动事件后获取图片:

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

UIPane3D 中的位置:

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

为了得到这张照片,我还删除了边界框:

我没有研究过可以添加到 VBox 的文本节点的数量、字体大小,也没有研究过避免在每次滚动时生成图像的策略:只有当文本发生变化时才应该这样做。所以目前的方法很慢,但它可以显着改进,因为每个框只有三个可能的图像。