Нужна помощь в создании треугольной сетки из карты высот (JavaFX)

У меня есть программа, которая генерирует карту высот (2D-массив целых чисел от 0 до 255) и строит 3D-представление, используя объект Shape3D «Box» для каждого «пикселя» с высотой, пропорциональной его значению на карте высоты. Это создает квадратную местность, которая выглядит круто. Моя программа также создает соответствующую «карту цветов», чтобы указать, какого цвета должен быть каждый прямоугольник на местности.

Я хочу иметь возможность также превратить эту карту высот в сетку, которую можно текстурировать с помощью карты цветов.

2D-карта высот и цветов 2D-карта высот и цветов

Цветная треугольная сетка, созданная из карты высот и цветовой карты Желаемая сетка с цветамиp

(Это изображения, которые я взял из Google)


person Antonio Ferreras    schedule 08.07.2019    source источник
comment
А в чем именно ваш вопрос?   -  person mipa    schedule 09.07.2019


Ответы (1)


Если я правильно понял, вы хотите построить HeightMapMesh на основе сетки 2D-точек {x, y} с заданной высотой или значением z для каждой точки. Это значение будет напрямую связано с цветом пикселя в том же месте данного 2D-изображения.

Получить вершины относительно просто: вы создаете 2D-сетку и получаете цвет с помощью PixelReader.

Построить сетку не так просто, но вы можете просто построить обычную сетку на основе прямоугольного 2D-изображения.

Есть и другой вариант: по заданному количеству вершин можно сгенерировать сетку с триангуляцией Делоне.

Это уже реализовано в библиотеке FXyz: Surface3DMesh.

Чтобы использовать его, просто добавьте зависимость в свой проект:

dependencies {
    implementation "org.fxyz3d:fxyz3d:0.5.0"
}

Следующее приложение сделает грубую аппроксимацию HeighMapMesh, которую вы ищете.

Он использует изображение, которое вы опубликовали, для создания List<Point3D> data на основе PixelReader каждые 5 пикселей на x и y, с небольшой выборкой цветов этого изображения.

С помощью этого списка создаются две поверхности, одна из которых будет заполнена и визуализирована картой текстуры на основе высоты каждой вершины с использованием одного и того же списка цветов. Другой будет использоваться в качестве каркаса для рендеринга поверх.

public class HeighMapMeshTest extends Application {

    private static final int PIXEL_SIZE = 5;

    private static final List<Color> COLOR_LIST = Arrays.asList(Color.web("#3b6eca"),
            Color.web("#d7d588"), Color.web("#60a318"), Color.web("#457517"), Color.web("#467610"),
            Color.web("#654f44"), Color.web("#56453d"), Color.web("#fdfefc"), Color.web("#ffffff"));

    private final Rotate rotateX = new Rotate(-10, Rotate.X_AXIS);
    private final Rotate rotateY = new Rotate(5, Rotate.Y_AXIS);

    private double mousePosX;
    private double mousePosY;
    private double mouseOldX;
    private double mouseOldY;

    @Override
    public void start(Stage primaryStage) {
        Group sceneRoot = new Group();

        PerspectiveCamera camera = new PerspectiveCamera(true);
        camera.setNearClip(0.1);
        camera.setFarClip(10000.0);
        camera.getTransforms().addAll (rotateX, rotateY, new Translate(0, 0, -800));

        Scene scene = new Scene(sceneRoot, 1000, 600, true, SceneAntialiasing.BALANCED);
        scene.setCamera(camera);

        List<Point3D> data = processImage();

        Surface3DMesh heightMapMesh = new Surface3DMesh(data);
        heightMapMesh.setDrawMode(DrawMode.FILL);
        heightMapMesh.setTextureModeVertices3D(new Palette.ListColorPalette(COLOR_LIST), p -> -p.y);

        Surface3DMesh wireframe = new Surface3DMesh(data);
        wireframe.setTextureModeNone(Color.BLACK);

        Group mapGroup = new Group(heightMapMesh, wireframe);
        mapGroup.getTransforms().add(new Translate(-500, 100, 0));
        sceneRoot.getChildren().addAll(mapGroup, new AmbientLight());

        scene.setOnMousePressed(event -> {
            mousePosX = event.getSceneX();
            mousePosY = event.getSceneY();
        });

        scene.setOnMouseDragged(event -> {
            mousePosX = event.getSceneX();
            mousePosY = event.getSceneY();
            rotateX.setAngle(rotateX.getAngle() - (mousePosY - mouseOldY));
            rotateY.setAngle(rotateY.getAngle() + (mousePosX - mouseOldX));
            mouseOldX = mousePosX;
            mouseOldY = mousePosY;
        });

        primaryStage.setTitle("F(X)yz - HeightMapMesh");
        primaryStage.setScene(scene);
        primaryStage.show();

    }

    private List<Point3D> processImage() {
        Image image = new Image(VoxelTest.class.getResourceAsStream("/8rF9BXu.png"));
        PixelReader pixelReader = image.getPixelReader();
        int width = (int) image.getWidth();
        int height = (int) image.getHeight();

        List<Point3D> data = new ArrayList<>();
        for (int y = 0; y < height - PIXEL_SIZE / 2; y += PIXEL_SIZE){
            for (int x = 0; x < width - PIXEL_SIZE / 2; x += PIXEL_SIZE){
                Color color = pixelReader.getColor(x + PIXEL_SIZE / 2, y + PIXEL_SIZE / 2);
                float h = Math.max(COLOR_LIST.indexOf(color) * 10, 0);
                data.add(new Point3D((float) x, -h, (float) (height - y)));
            }
        }
        return data;
    }

    public static void main(String[] args) {
        launch(args);
    }

}

Как результат:

MapHeightMesh

Конечно, это можно улучшить разными способами.

ИЗМЕНИТЬ

После создания 3D-сетки ее можно экспортировать в файл .OBJ, включая примененную текстуру.

FXyz уже включает OBJWriter для этой цели.

Этот код:

OBJWriter writer = new OBJWriter((TriangleMesh) heightMapMesh.getMesh(), "mapHeight");
writer.setTextureColors(9);
writer.exportMesh();

сгенерирует mapHeight.obj и mapHeight.mtl, где используется диффузное изображение с именем palette_9.png.

Однако это изображение палитры не использует пользовательскую палитру, которую мы определили.

Чтобы экспортировать пользовательскую цветовую палитру, нам нужно создать Palette и сохранить ее на диск:

OBJWriter writer = new OBJWriter((TriangleMesh) heightMapMesh.getMesh(), "mapHeight");
writer.setTextureColors(9);
Palette.ListColorPalette colorPalette = 
        new Palette.ListColorPalette(COLOR_LIST);
Palette palette = new Palette(9, colorPalette);
palette.createPalette(true);
writer.exportMesh();

Убедитесь, что файл палитры представляет собой изображение 3x3 с цветами из COLOR_LIST.

Теперь вы можете открыть файл obj с помощью 3DViewer, чтобы проверьте правильность экспорта.

3DПросмотр

person José Pereda    schedule 09.07.2019
comment
Я думаю, что способ улучшить это было бы иметь отдельную карту высот, кроме карты цветов, которая показывает высоту каждого пикселя, чтобы придать ему большую точность. Предоставленная мной двухмерная карта не дает хорошего представления о высотах, а только о цветах. Спасибо - person Antonio Ferreras; 09.07.2019
comment
Конечно, как я уже сказал, я просто предлагаю грубый подход, чтобы вы могли начать, и вы можете улучшить его в соответствии со своими потребностями. - person José Pereda; 09.07.2019
comment
Извините, но я никогда не использовал такую ​​библиотеку Java. Я использовал только те, которые поставляются в виде файла jar. Можете ли вы помочь мне добавить FXyz в мой проект в Intellij IDEA? Спасибо. - person Antonio Ferreras; 10.07.2019
comment
Если вы используете Maven/Gradle, просто добавьте зависимость, как описано здесь. Если вы этого не сделаете, вам нужно будет вручную загрузить несколько jar-файлов из Maven Central, начиная с этого one и все не-JavaFX-зависимости, которые вы видите в его файле pom. Затем все эти банки для вашего проекта. - person José Pereda; 10.07.2019
comment
Я получил библиотеку и ее зависимости и использовал Surface3DMesh для создания ландшафта, и он работал очень хорошо при использовании отдельной карты для высот и другой только для цвета. Спасибо за этот ответ! - person Antonio Ferreras; 10.07.2019
comment
Как мне использовать FXyz для экспорта этой сетки в виде файла obj (с текстурированными цветами)? - person Antonio Ferreras; 13.07.2019
comment
Проверьте Объект записи - person José Pereda; 13.07.2019
comment
Я могу экспортировать Surface3DMesh как объект с сеткой. Как мне добавить текстуру к этому объекту? - person Antonio Ferreras; 13.07.2019