Отображение текста на лицевой стороне блока в зависимости от видимой области при увеличении/уменьшении масштаба

У меня есть пример 3D-приложения (созданного на основе примера Javafx 3DViewer), в котором есть таблица, созданная путем размещения блоков и панелей: (изображение таблицы)

Таблица центрируется по координатам (0,0,0), а камера изначально находится в позиции -z.

Он имеет увеличение / уменьшение в зависимости от положения камеры по оси z от объекта. При увеличении/уменьшении масштаба объекта boundsInParent увеличивается/уменьшается, т.е. площадь лица увеличивается/уменьшается. Таким образом, идея состоит в том, чтобы поместить больше текста, когда у нас есть большая область (всегда ограниченная лицом), и меньше текста или вообще не размещать текст, когда область лица слишком мала. Я могу сделать это, используя эту иерархию узлов:

эта иерархия узлов

и изменение размера панели (и управление vBox и количеством текстов в нем) в соответствии с полем при каждом увеличении / уменьшении масштаба.

Теперь проблема заключается в том, что table boundsInParent дает неверные результаты (изображение таблицы, показывающее, что ограничивающая рамка отключена вверху) всякий раз, когда текст добавляется в vBox только в первый раз. При дальнейшем увеличении/уменьшении выдает правильный 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. BoundsInParent table3D не обновляется должным образом, когда тексты добавляются в первый раз.

  2. Как правильно размещать тексты на 3D-узлах? Мне приходится много манипулировать, чтобы привести тексты в соответствие с требованиями.

Поделитесь кодом здесь.


person Itachi    schedule 06.02.2018    source источник
comment
Возможно, будет лучше, если вы поделитесь кодом, хотя бы чем-то, что мы можем использовать для воспроизведения вашей проблемы?   -  person José Pereda    schedule 10.02.2018
comment
Ссылка на общий код @JoséPeda, основной класс: JFXApp.java   -  person Itachi    schedule 12.02.2018


Ответы (1)


По первому вопросу, по поводу "скачка", который можно заметить именно тогда, когда после прокрутки выкладывается новый текстовый элемент:

движущиеся границы

Покопавшись в коде, я заметил, что UIPane3D имеет VBox textPane, который содержит различные узлы Text. Каждый раз, когда вызывается 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-узлов.

Именно в предыдущем вопросе Я «записал» изображение, которое позже использовал в качестве диффузной карты материала 3D-формы.

Встроенное 3D Box помещает одно и то же изображение на каждое лицо, так что это не сработает. Мы можем реализовать трехмерную призму или использовать узел CuboidMesh из библиотеки FXyz3D.

Замена 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, с размером шрифта и со стратегией предотвращения создания изображений при каждой прокрутке: это следует делать только при изменении текста. Таким образом, текущий подход довольно медленный, но его можно заметно улучшить, поскольку для каждого поля есть только три возможных изображения.

person José Pereda    schedule 12.02.2018
comment
понял, макет проходит до следующего импульса, спасибо. - person Itachi; 14.02.2018
comment
Что касается второго вопроса, затенение 3D-объекта изображением (это слишком разные тексты на разных лицах с использованием библиотеки FXyz3D и подхода с сеткой) является хорошим вариантом для отображения текстов и избегания 2D. Но позже я хотел бы, чтобы эти 3D-боксы были интерактивными (с кнопкой и т. Д.), Для которых я выбрал Панели вместо них. Не уверены, что смешение 2D с 3D — это хороший дизайн? - person Itachi; 14.02.2018
comment
Я не люблю смешивать 2D и 3D в одной подсцене. Но это зависит от вашего варианта использования, конечно. Если вы хотите добавить взаимодействие, возможно, вы даже можете попробовать 3D (сочетание pickResult и анимации мелкой сетки...) и, возможно, 2D-панель/всплывающее окно/наложение (если вам нужно TextField или что-то в этом роде, это не нужно находиться в той же подсцене, см. это). Этот проект отличается от вашего, но может дать вам подсказку о взаимодействии с сеткой. - person José Pereda; 14.02.2018