【问题标题】:Showing texts over the face of a Box based on the visible area on zooming in/out根据放大/缩小时的可见区域在框的表面上显示文本
【发布时间】:2018-02-06 17:37:09
【问题描述】:

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

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

它具有基于对象的相机 z 位置的放大/缩小。 放大/缩小对象的 boundsInParent 增加/减少,即面部区域增加/减少。所以我们的想法是当我们有更多的区域(总是限制在面部)时放置更多的文本,而当面部区域太少时放置更少的文本或没有文本。我可以使用此节点层次结构来做到这一点:

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

现在的问题是,每当第一次将文本添加到 vBox 时,表 boundsInParent 就会给出不正确的结果(table image 在顶部显示 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);
}

两件事:

  1. 第一次添加文本时,table3D 的 boundsInParent 没有正确更新。

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

分享码here.

【问题讨论】:

  • 您最好分享代码,至少我们可以用来重现您的问题?
  • @JoséPereda 共享代码链接,主类:JFXApp.java

标签: javafx javafx-8 javafx-3d


【解决方案1】:

对于第一个问题,关于在滚动新文本项后可以注意到的“跳跃”:

深入了解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;
        }
    }

注意:

此方法 [applyCss] 通常不需要直接调用,但可以与 Parent.layout() 结合使用,以在下一个脉冲之前或场景不在舞台中时调整节点大小。

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

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

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

正是在之前的question 中,我“写入”了一张图像,后来我将其用作 3D 形状的材质漫反射贴图。

内置的 3D Box 将相同的图像放置到每张脸上,这样就行不通了。我们可以实现一个 3D 棱镜,或者我们可以利用 FXyz3D library 中的 CuboidMesh 节点。

替换Box 中的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相关代码都可以去掉(包括displaypane),在滚动事件后获取图片:

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

UIPane3D:

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

我还删除了边界框以获得这张图片:

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

【讨论】:

  • 知道了,布局在下一个脉冲之前通过,谢谢。
  • 对于第二个问题,使用图像对 3D 对象进行着色(使用 FXyz3D 库和网格方法在不同的面上显示太多不同的文本)是显示文本并避免 2D 的好选择。但后来我想让那些 3D 盒子交互(有一个按钮等),我选择了窗格而不是它们。不确定在 3D 中混合 2D 是否是一个好的设计?
  • 我不喜欢在同一个子场景中混合 2D 和 3D。但这当然取决于您的用例。如果你想添加交互,也许你甚至可以尝试 3D(结合 pickResult 和小网格动画......),也许是 2D 面板/弹出/覆盖(如果你需要一个 TextField 左右​​,那不需要在同一个子场景中,请参阅this)。这个project 和你的不一样,但是可以给你一个关于网格交互的提示。
猜你喜欢
  • 1970-01-01
  • 2012-03-08
  • 2011-11-02
  • 2022-01-08
  • 2013-03-24
  • 2022-11-21
  • 2013-06-28
  • 2011-06-21
  • 1970-01-01
相关资源
最近更新 更多