【问题标题】: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
相关资源
最近更新 更多