javafx-根据放大/缩小时的可见区域在Box的正面显示文本

我有一个示例3D应用程序(通过引用Javafx示例3DViewer进行构建),该应用程序具有通过布局Box和Panes创建的表:

该表位于wrt(0,0,0)坐标的中心,并且照相机最初位于-z位置.

它具有基于相机z相对于对象的位置的放大/缩小的功能.
放大/缩小对象的boundsInParent会增加/减少,即面部的面积会增加/减少.因此,我们的想法是,当我们拥有更多区域时(总是限制在脸部内)放置更多的文本,而当脸部区域太少时则放置更少的文本或不显示文本.我可以使用以下节点层次结构来做到这一点:

 

并根据每个放大/缩小的Box调整窗格的大小(并管理vBox和其中的文本数).

现在的问题是,仅当第一次将文本添加到vBox时,表boundsInParent给出的结果不正确(table image在顶部显示了boundingBox).进一步放大/缩小会给出正确的boundingBox并且不会消失.

下面是UIpane3D类:

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

两件事情:

>首次添加文本时,无法正确更新table3D的boundsInParent.
>在3D节点上放置文本的正确方法是什么?我必须操纵很多东西才能根据需要携带这些文字.

共享代码here.

最佳答案
对于第一个问题,是关于在滚动新文本项后才可以注意到的“跳转”:

moving bounds

深入研究code之后,我注意到UIPane3D具有包含不同Text节点的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节点.

恰好在上一个question中,我“写”了一个图像,之后又将其用作3D形状的材质扩散贴图.

内置的3D盒子将相同的图像放置在每个人的脸上,因此无法正常工作.我们可以实现3D棱镜,也可以使用FXyz3D library中的CuboidMesh节点.

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

并添加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);
}

现在可以删除所有2D相关代码(包括显示窗格),并在滚动事件后获取图像:

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

在UIPane3D中:

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

我还删除了边界框以获取以下图片:

Just CuboidMeshes

我没有考虑可以添加到VBox的文本节点的数量,字体大小,也没有解决避免在每次滚动时生成图像的策略:只有在文本更改时才这样做.因此,当前的方法速度很慢,但是由于每个框只有三个可能的图像,因此可以显着改进.

转载注明原文:javafx-根据放大/缩小时的可见区域在Box的正面显示文本 - 代码日志