

= jMonkeyEngine 3 Урок (10) - Знакомство с местностью

Предыдущий: <<документация/jme3_ru/начальная/знакомство_с_столкновениями#,Знакомство со столкновением>>,
Следующий: <<документация/jme3_ru/начальная/знакомство_со_звуком#,Знакомство с аудио>>
Английская версия <<jme3/beginner/hello_terrain#,Hello Terrain>>


Один из способов создания 3D ландшафта - слепить огромную модель местности. Это дает вам много творческой свободы – но сделать такую ​​огромную модель может быть довольно медленным. Это руководство объясняет как быстро создать местность использую карту высот и как использовать splat-текстуры чтобы местность хорошо выглядела.



image::jme3/beginner/beginner-terrain.png[beginner-terrain.png,with="360",height="291",align="center"]



Примечание: Если вы получили сообщение об ошибке при попытке создать объект ImageBasedHeightMap то вам может понадобиться обновления SDK, нажмите “Help / “Check for updates


““““



== Пример кода

[source,java]

----
package jme3test.helloworld;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.renderer.Camera;
import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.heightmap.AbstractHeightMap;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
import com.jme3.terrain.heightmap.HillHeightMap; // for exercise 2
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import java.util.ArrayList;
import java.util.List;

/** Образец 10 - Как создать быстрый рендеринг территорий по карте высот,
и как использовать splat-текстуры, чтобы местность хорошо выглядела.  */
public class HelloTerrain extends SimpleApplication {

  private TerrainQuad terrain;
  Material mat_terrain;

  public static void main(String[] args) {
    HelloTerrain app = new HelloTerrain();
    app.start();
  }

  @Override
  public void simpleInitApp() {
    flyCam.setMoveSpeed(50);

    /** 1. Создать материал местности и загрузить четыре текстуры в него. */
    mat_terrain = new Material(assetManager, 
            "Common/MatDefs/Terrain/Terrain.j3md");

    /** 1.1) Добавить альфа-карту (для красно-сине-зеленые кодированных текстур) */
    mat_terrain.setTexture("Alpha", assetManager.loadTexture(
            "Textures/Terrain/splat/alphamap.png"));

    /** 1.2) Добавить текстуру травы в красный слой(Tex1). */
    Texture grass = assetManager.loadTexture(
            "Textures/Terrain/splat/grass.jpg");
    grass.setWrap(WrapMode.Repeat);
    mat_terrain.setTexture("Tex1", grass);
    mat_terrain.setFloat("Tex1Scale", 64f);

    /** 1.3) Добавить DIRT текстуру в зеленый слой (Tex2) */
    Texture dirt = assetManager.loadTexture(
            "Textures/Terrain/splat/dirt.jpg");
    dirt.setWrap(WrapMode.Repeat);
    mat_terrain.setTexture("Tex2", dirt);
    mat_terrain.setFloat("Tex2Scale", 32f);

    /** 1.4) Добавить текстуру дороги в синий слой (Tex3) */
    Texture rock = assetManager.loadTexture(
            "Textures/Terrain/splat/road.jpg");
    rock.setWrap(WrapMode.Repeat);
    mat_terrain.setTexture("Tex3", rock);
    mat_terrain.setFloat("Tex3Scale", 128f);

    /** 2. Создайте карту высоты */
    AbstractHeightMap heightmap = null;
    Texture heightMapImage = assetManager.loadTexture(
            "Textures/Terrain/splat/mountains512.png");
    heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
    heightmap.load();

    /** 3. Мы подготовили материал и карты высот. 
     * Теперь мы создадим местность:
     * 3.1) Создайте TerrainQuad и назовите его "my terrain".
     * 3.2) Хорошее соотношение значения для тайлов местности 64x64 -- поэтому мы поставим 64+1=65.
     * 3.3) Мы подготовили карту высот размером 512x512 -- поэтому поставим 512+1=513.
     * 3.4) В масштабах шаг LOD мы поставим Vector3f(1,1,1).
     * 3.5) Мы используем подготовленную карту высот.
     */
    int patchSize = 65;
    terrain = new TerrainQuad("my terrain", patchSize, 513, heightmap.getHeightMap());

    /** 4. Мы даем материал местности, распологаем и масштабируем его и крепим. */
    terrain.setMaterial(mat_terrain);
    terrain.setLocalTranslation(0, -100, 0);
    terrain.setLocalScale(2f, 1f, 2f);
    rootNode.attachChild(terrain);

    /** 5. LOD (уровень детализации) зависит от того где камера: */
    TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
    terrain.addControl(control);
  }
}
----

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



== Что такое карта высот?

Карта высот представляет собой эффективный способ представления формы холмистого ландшафта. Не каждый пиксель ландшафта хранится в карте, вместо этого сетки выборочных значений используются что бы наметить высоту местности в определенных точках. Высоты между выборками интерполируются. 


В Java карта высот это вещественное массив, содержащий значения высот между 0f и 255f. Вот очень простой пример местности полученной от высоты 5x5 = 25 значений высоты.


image:jme2/terrain-from-float-array.png[terrain-from-float-array.png,with="",height=""]


Отметим важное:


*  Низкие значения (например, 0 или 50) являются долинами.
*  Высокие значения (например, 200, 255) являются холмами.
*  Карта высот определяет только несколько моментов а движок интерполирует остальное. Интерполяция является более эффективным способом чем создание модели с несколькими миллионами вершин.

При взгляде на Java типы данных для хранения массива плавающих значений от 0 до 255, класс Image приходит на ум. Хранение значений высоты местности как черно-белого изображения имеет одно большое преимущество: результат очень удобный, как топографическая карта:


*  Низкие значения (например, 0 или 50) темно-серые - это долины.
*  Высокие значения (например, 200, 255) светло-серые - это холмы.

Посмотрите на следующий скриншот: В левом верхнем углу вы видите 128x128 черно-белое изображение (карта высот), которое было использовано в качестве основы для создания изображения местности. Чтобы холмистой формы были лучше видны, горные вершины окрашены в белый цвет, углубления в коричневый и направления между ними в зеленым:


image:jme2/terrain-from-heightmap.png[terrain-from-heightmap.png,with="",height=""]}


В реальной игре вам нужно будет использовать более сложные и гладкие местности чем простая карта высот показаная здесь. Карты высот обычно имеют квадратные размеры 512x512 или 1024x1024, и содержат от сотни тысяч до 1 млн. значений высоты. Независимо от размера концепция остаётся таже самая.



=== Взгляните на код с картой высот


image::http///jmonkeyengine.googlecode.com/svn/trunk/engine/test-data/Textures/Terrain/splat/mountains512.png[mountains512.png,with="128",height="128",align="right"]



Первым шагом является создание ландшафта по карте высот. Вы можете создать ее самостоятельно в любом стандартном графическом редакторе. Убедитесь что она обладает следующими свойствами::


*  Размер должен быть квадратным, равным степени двойки.
**  Пример: 128x128, 256x256, 512x512, 1024x1024

*  Цветовой режим должен быть 255 градаций серого.
**  Не используйте цветное изображение оно будет интерпретировано как оттенки серого с возможно странными результатами.

*  Сохраните как .jpg или .png файл.

Файл “mountains512.png что вы видите здесь является типичным примером изображения карты высот.


Вот как вы создаете объект карты высот в коде jME:


.  Создать текстуру объекта.
.  Загрузите подготовленный образ карты высот в текстуру объекта.
.  Создать объект из AbstractHeightmap ImageBasedHeightMap. +
Она требует изображения с jME текстурой.
.  Загрузите карту высот.

[source,java]

----
    AbstractHeightMap heightmap = null;
    Texture heightMapImage = assetManager.loadTexture(
            "Textures/Terrain/splat/mountains512.png");
    heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
    heightmap.load();

----


== Что такое splat-текстура?

Ранее вы узнали как создать материал для простой формы, такой как куб. Все стороны куба имеют тот же цвет. Вы можете применить тот же материал в местности но тогда у вас будет большой луг, одна большая пустыня, и т.д.. Это не всегда то чего вы хотите.


Splat-текстура позволяет создать пользовательский материал окрашевая текстуру как кисточкой. Это очень полезно для ландшафтов: как вы видите в данном примере можно рисовать текстуры травы в долине, грязь на горах и в свободной форме промежуточные дороги.


<<sdk/terrain_editor_ru#,плагином редактора местности>><<sdk/terrain_editor#,TerrainEditor plugin>>


Splat текстуры на основе “Terrain.j3md материала. Если открыть файл Terrain.j3md и посмотреть в разделе параметров материала вы видите что у вас есть несколько слоев текстур для рисования: “Tex1, “Tex2, “Tex3, и так далее. 


Перед тем как начать рисовать вы должны определить несколько решений:


.  Выберите три текстуры. К примеру grass.jpg, dirt.jpg, и road.jpg. 
image::http///jmonkeyengine.googlecode.com/svn/trunk/engine/test-data/Textures/Terrain/splat/road.jpg[road.jpg,with="64",height="64",align="right"]
  
image::http///jmonkeyengine.googlecode.com/svn/trunk/engine/test-data/Textures/Terrain/splat/dirt.jpg[dirt.jpg,with="64",height="64",align="right"]
 
image::http///jmonkeyengine.googlecode.com/svn/trunk/engine/test-data/Textures/Terrain/splat/grass.jpg[grass.jpg,with="64",height="64",align="right"]

.  Вы краски для трех слоев текстуры с помощью трех цветов: Red, blue и green. Решите произволно…
..  Красный   это трава – красный слой “Tex1, так что ставте текстуру травы в Tex1.
..  Зеленый это земля  – зеленый слой “Tex2, так что ставте текстуру земли в Tex2.
..  Синий  это дороги – синий слой “Tex3, так что ставте текстуру дорог в Tex3.


Теперь начните рисовать текстуры:


.  Сделайте копию вашей карты высот местности, “mountains512.png. Вы хотите использовать ее в качестве справочника для формы ландшафта.
.  Назовите копию “alphamap.png.
.  Откройте “alphamap.png в графическом редакторе и переключите режим изображения в цветное изображение.
..  Закрасте черные участки красным цветом – здесь будет трава.
..  Закрасте белые участки зеленым цветом – это будет грязь в горах.
..  Синими линиями нарисуйте дороги между гор.

.  Конечный результат должен выглядеть примерно так:

image:http///jmonkeyengine.googlecode.com/svn/trunk/engine/test-data/Textures/Terrain/splat/mountains512.png[mountains512.png,with="64",height="64"] ⇒ image:http///jmonkeyengine.googlecode.com/svn/trunk/engine/test-data/Textures/Terrain/splat/alphamap.png[alphamap.png,with="64",height="64"]



=== Посмотрим на код текстурирования

Как обычно вы создаете материал объекта. Ее на основе определеного материала “Terrain.j3md который включен в jME3.


[source,java]

----
Material mat_terrain = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
----

Загрузите четыре текстуры в этот материал. Первым “Alpha, который только что создали.


[source,java]

----
mat_terrain.setTexture("Alpha",
    assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
----

Три других текстур слоя которые вы ранее решили покрасить: grass, dirt, и road. Вы создаете текстуру объекта и загрузили три текстуры как обычно. Обратите внимание как вы назначаете их в соответствующие слои текстур (Tex1, Tex2, and Tex3) внутри материала!


[source,java]

----
    /** 1.2) Добавим GRASS текстуру в красный слой (Tex1). */
    Texture grass = assetManager.loadTexture(
            "Textures/Terrain/splat/grass.jpg");
    grass.setWrap(WrapMode.Repeat);
    mat_terrain.setTexture("Tex1", grass);
    mat_terrain.setFloat("Tex1Scale", 64f);

    /** 1.3) Добавим DIRT текстуру в зеленый слой (Tex2) */
    Texture dirt = assetManager.loadTexture(
            "Textures/Terrain/splat/dirt.jpg");
    dirt.setWrap(WrapMode.Repeat);
    mat_terrain.setTexture("Tex2", dirt);
    mat_terrain.setFloat("Tex2Scale", 32f);

    /** 1.4) Добавим ROAD текстуру в синий слой (Tex3) */
    Texture rock = assetManager.loadTexture(
            "Textures/Terrain/splat/road.jpg");
    rock.setWrap(WrapMode.Repeat);
    mat_terrain.setTexture("Tex3", rock);
    mat_terrain.setFloat("Tex3Scale", 128f);

----

Отдельно масштаб текстур (например “mat_terrain.setFloat(“Tex3Scale, 128f);) зависит от размера текстуры при использовании.


*  Вы можете сказать что вы выбрали слишком маленький масштаб если например ваша плитка дороги появляется как крошечные песчинки. 
*  Вы можете сказать что вы выбрали слишком большой масштаб если например травинки выглядят как ветки.

Используйте “setWrap(WrapMode.Repeat) чтобы небольшой текстурой заполнить широкую область. Если повторения слишком заметны попробуйте отрегулировать соответствующие значения “Tex*Scale.



== Что такое местность?

Внутренне сгенерированная сетка местности разбита на плитки и блоки. Это оптимизация чтобы сделать легче выбраковку. Вам не нужно беспокоиться о “плитках и блоках, просто используйте рекомендованные значения на данный момент - 64 является хорошим началом.


Предположим, что вы хотите создать местность 512x512. Вы уже создали карту высот. Вот шаги которые вы выполняете каждый раз при создании новой местности.


Создать TerrainQuad со следующими аргументами:


.  Имя: Например “my terrain.
.  Укажите размер плитки: Вы хотите местность с размером плитки 64x64, так что ставте 64 +1 = 65.
**  В общем 64 является хорошим начальным значением для плитки местности.

.  Укажите размер блока: Так как вы подготовили карту высот размером 512x512, нужно указать 512 +1 = 513.
**  Если указать размер блока 2x размер карты высоты (1024 +1 = 1025), вы получите растянутую, широкую и более плоскую поверхность.
**  Если указать размер блока 1/2 размера карты высот (256 +1 = 257), вы получите меньшую, более подробную местность.

.  Используйте 512x512 карту высот созданую вами.


=== Посмотрим на код местности

Вот код:


[source]

----
terrain = new TerrainQuad(
  "my terrain",               // имя
  65,                         // размер плитки
  513,                        // размер блока
  heightmap.getHeightMap());  // карта высот

----

Вы создали объект местности.


.  Не забудьте применить созданный материал: 
[source,java]

----
terrain.setMaterial(mat_terrain);
----

.  Не забудьте прикрепить местность к RootNode.
[source,java]

----
rootNode.attachChild(terrain);

----

.  При необходимости масштабируйте и переносите местност как и любой другой объект.

*Совет:* Terrain.j3md является незатененым материалом поэтому вам не нужен источник света. Вы также можете использовать TerrainLighting.j3md плюс свет, если вы хотите затененную местность.



== Что такое LOD (Уровень детализации)?

JME3 включает в себя оптимизацию которая регулирует уровень детализации (LOD) местности в зависимости от того насколько близко или далеко камера.


[source,java]

----

    TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
    terrain.addControl(control);

----

Закрывая часть местности показывая остальное во всех деталях. Части местности которые дальше не видны отсекаются - JME3 повышает производительность. Таким образом вы можете себе позволить загрузить огромные местности без страха снизить производительность.



== Упражнения


=== Упражнение 1: Слои текстур

Что происходит когда нужно поменять местами два слоя, например “Tex1 и “Tex2?


[source,java]

----

...
mat_terrain.setTexture("Tex2", grass);
...
mat_terrain.setTexture("Tex1", dirt);

----

Вы видите что легче поменять слои в коде чем изменить цвета в alphamap.



=== Упражнение 2: Случайная местность

Следующие три строки создают объект карты высот на основе ваших пользовательских изображений:


[source,java]

----
    AbstractHeightMap heightmap = null;
    Texture heightMapImage = assetManager.loadTexture(
        "Textures/Terrain/splat/mountains512.png");
    heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
----

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


.  Какой результат вы получаете когда вы замените три строки выше на следующие строки и запустите?
[source,java]

----

HillHeightMap heightmap = null;
HillHeightMap.NORMALIZE_RANGE = 100; // необязательно
try {
    heightmap = new HillHeightMap(513, 1000, 50, 100, (byte) 3); // 3 байта представляют собой случайное начальное число
} catch (Exception ex) {
    ex.printStackTrace();
}
----

.  Изменените один параметр за один раз и запустите образец снова. Обратите внимание на различия. Можете ли вы выяснить какие из значений имеют действие на генерацию местности (посмотрите на документацию)?
**  Какое значение управляет размером?
***  Что произойдет если размер не является квадратом числа + 1 ?

**  Какое значение управляет генерацией количества холмов?
**  Какое значение управлять размером и крутизной холмов?
***  Что произойдет, если установить меньше или больше? 
***  Что произойдет, если оба минимальных и максимальных значения буду малыми (например, 10/20)?
***  Что произойдет, если оба минимальных и максимальных значения будут больщими (например, 1000/1500)?
***  Что произойдет, если минимальные и максимальные значения очень близки (например, 1000/1001, 20/21)? Очень далеки друг от друга (например, 10/1000)?



Вы видите различные холмистые ландшафты которые могут быть получены с помощью этого метода.






=== Упражнение 3: Твердая местность

Можно ли соединить то что вы узнали здесь и в <<hello_collision_ru#,Столкновениях>> и <<jme3/advanced/terrain_collision#,создании твердой местности>>?



== Заключение

Вы узнали как создать территорию что более эффективно чем загрузка одной гигантской модели. Вы знаете как генерировать случайную или создать вручную карту высот. Вы можете добавить элемент управления LOD что бы показывать больше местности. Вы знаете как вы можете объединить то что вы узнали об обнаружения столкновений чтобы сделать твердую местность для игрока. Вы также можете создать текстуры местности используя слоистые материалы и splat-текстуры. Вы знаете что jMonkeyEngine SDK предоставляет TerrainEditor который помогает с большинством задач руководства.


Хотите услышать как ваши игроки говорят “ой! когда они врезаются в стену или падают с пропасти? Продолжайте обучение <<hello_audio_ru#,как добавить звук>> в вашу игру.

'''

См. также:


*  <<jme3/advanced/terrain_collision#,Terrain Collision>>
<tags><tag target="beginner" /><tag target="heightmap" /><tag target="documentation" /><tag target="terrain" /><tag target="texture" /></tags>
