This page has been translated automatically.
Видеоуроки
Интерфейс
Основы
Продвинутый уровень
Подсказки и советы
Основы
Программирование на C#
Рендеринг
Профессиональный уровень (SIM)
Принципы работы
Свойства (properties)
Компонентная Система
Рендер
Физика
Редактор UnigineEditor
Обзор интерфейса
Работа с ассетами
Контроль версий
Настройки и предпочтения
Работа с проектами
Настройка параметров ноды
Setting Up Materials
Настройка свойств
Освещение
Sandworm
Использование инструментов редактора для конкретных задач
Расширение функционала редактора
Встроенные объекты
Ноды (Nodes)
Объекты (Objects)
Эффекты
Декали
Источники света
Geodetics
World-ноды
Звуковые объекты
Объекты поиска пути
Player-ноды
Программирование
Основы
Настройка среды разработки
C++
C#
UnigineScript
UUSL (Unified UNIGINE Shader Language)
Плагины
Форматы файлов
Материалы и шейдеры
Rebuilding the Engine Tools
Интерфейс пользователя (GUI)
VR Development
Двойная точность координат
API
Animations-Related Classes
Containers
Common Functionality
Controls-Related Classes
Engine-Related Classes
Filesystem Functionality
GUI-Related Classes
Math Functionality
Node-Related Classes
Objects-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
IG Plugin
CIGIConnector Plugin
Rendering-Related Classes
VR-Related Classes
Работа с контентом
Оптимизация контента
Материалы
Визуальный редактор материалов
Сэмплы материалов
Material Nodes Library
Miscellaneous
Input
Math
Matrix
Textures
Art Samples
Учебные материалы

Работа с Landscape Terrain из кода

This article describes how to create and modify the Landscape Terrain object via code. But before we get down to coding, let's start with a bit of theory.В этой статье описывается, как создать и изменить объект Landscape Terrain с помощью кода. Но прежде чем мы перейдем к программированию, немного теории.

The surface of Landscape Terrain (ObjectLandscapeTerrain class) is represented by a single or multiple rectangular layers called Landscape Layer Maps (LandscapeLayerMap class). By creating and arranging layer maps you define the look and functionality of the terrain.Поверхность Landscape Terrain (класс ObjectLandscapeTerrain) представлена одним или несколькими прямоугольными слоями, называемыми Картами слоев ландшафта (класс LandscapeLayerMap). Создавая и упорядочивая карты слоев, вы определяете внешний вид и функциональность ландшафта.

To see a terrain, at least one Landscape Layer Map is required. Data of each Landscape Layer Map (heights, albedo, and masks) is stored in an .lmap file. To create such a file the LandscapeMapFileCreator class is used. If you want to load, modify, and apply settings stored in this file later, use the LandscapeMapFileSettings class.Чтобы увидеть ландшафт, требуется по крайней мере один Landscape Layer Map. Данные каждой карты слоя ландшафта (высоты, альбедо и маски) хранятся в файле .lmap. Для создания такого файла используется класс LandscapeMapFileCreator. Если вы хотите загрузить, изменить и применить настройки, сохраненные в этом файле позже, используйте класс LandscapeMapFileSettings.

Landscape Terrain rendering and modification is managed via the Landscape class.Управление рендерингом и модификацией Landscape Terrain осуществляется с помощью класса Landscape.

There is a set of API classes used to manage the Landscape Terrain object:Существует набор классов API, используемых для управления объектом Landscape Terrain:

  • ObjectLandscapeTerrain — managing general Landscape Terrain object parameters.ObjectLandscapeTerrain — управление общими параметрами объекта Landscape Terrain.
  • TerrainDetail — managing terrain details that define its appearance. Details are organized into a hierarchy, each of them can have an unlimited number of children. Details are attached to detail masks and are drawn in accordance with their rendering order (the one with the highest order shall be rendered above all others).TerrainDetail — управление деталями ландшафта, которые определяют его внешний вид. Детали организованы в иерархию, каждая из них может иметь неограниченное количество дочерних элементов. Детали привязаны к маскам деталей и рисуются в соответствии с порядком их отображения (деталь с самым высоким порядком отображается поверх всех остальных).
  • TerrainDetailMask — managing terrain detail masks. Each detail mask can have an unlimited number of details.TerrainDetailMask — управление масками деталей ландшафта. Каждая маска деталей может содержать неограниченное количество деталей.
  • LandscapeFetch — getting terrain data at a certain point (e.g. a height request) or check for an intersection with a traced line.LandscapeFetch — получение данных о ландшафте в определенной точке (например, запрос высоты) или проверка пересечения с линией трассировки.
  • LandscapeImages — to edit landscape terrain via API.LandscapeImages — для редактирования ландшафта через API.
  • LandscapeTextures — to edit landscape terrain via API.LandscapeTextures — для редактирования ландшафта через API.

See AlsoСмотрите также#

  • C++ Sample set demonstrating various Landscape Terrain features and use casesНабор примеров C++ Samples, демонстрирующий функционал и способы примения Landscape Terrain

Creating a TerrainСоздание ландшафта#

To create a Landscape Terrain based on arbitrary height and albedo maps, the following workflow is used:Для создания ландшафта на основе произвольных карт высоты и альбедо нужно выполнить следующие операции:

  1. Create a LandscapeMapFileCreator instance, set necessary parameters (grid size, resolution, etc.) and generate a new .lmap file based on specified albedo and height images (tiles) via the Run() method. Here you can subscribe for events at different stages of creation.Создайте экземпляр LandscapeMapFileCreator, задайте необходимые параметры (размер сетки, разрешение и т. д.) и сгенерируйте новый файл .lmap на основе заданных изображений альбедо и высоты (тайлов) с помощью метода Run(). Здесь вы можете добавить обратные вызовы для разных этапов создания.
  2. Create a LandscapeMapFileSettings instance, load target .lmap file for settings, set necessary parameters (opacity and blending for height and albedo data) and apply them.Создайте экземпляр LandscapeMapFileSettings, загрузите целевой файл .lmap для настроек, установите необходимые параметры (непрозрачность и смешивание для данных высоты и альбедо) и примените их.
  3. Create a new ObjectLandscapeTerrain object.Создайте новый объект ObjectLandscapeTerrain.
  4. Create a LandscapeLayerMap based on the previously created .lmap file.Создайте LandscapeLayerMap на основе ранее созданного файла .lmap.

Preparing a ProjectПодготовка проекта#

Before we get to code, perform the following:Прежде чем мы перейдем к программированию, выполните следующее:

  1. Open SDK Browser and create a new C# (.NET) or C++ project depending on the programming language selected.Откройте SDK Browser и создайте новый проект C# (.NET) или C++ в зависимости от выбранного языка программирования.
  2. Open your project in UnigineEditor via the Open Editor button in SDK Browser.Откройте свой проект в UnigineEditor с помощью кнопки Open Editor в SDK Browser.
  3. Save the following images to your computer:Сохраните следующие изображения на свой компьютер:

    Карта альбедо Карта высот
    Карта альбедо Карта высот
  4. Drag the height.png file directly to the Asset Browser window to add it to your project. In the Import Dialog for your height map, set Image Format to R32F and click Yes.Перетащите файл height.png непосредственно в окно Asset Browser, чтобы добавить его в свой проект. В диалоговом окне импорта для вашей карты высот установите для Image Format значение R32F и нажмите Yes.
  5. Drag the albedo.png file directly to the Asset Browser window too. In the Import Dialog for your height map, set Texture Preset to Albedo (RGB — color, A — opacity) and click Yes.Перетащите файл albedo.png также непосредственно в окно Asset Browser. В диалоговом окне импорта для вашей карты высот установите для Texture Preset значение Albedo (RGB — color, A — opacity) и нажмите Yes.

CodeКод#

Copy the source code below and paste it to the corresponding source files of your project.Скопируйте приведенный ниже исходный код и вставьте его в соответствующие исходные файлы вашего проекта.

In the AppWorldLogic.h file, define all parameters required for terrain creation, as well as the terrain itself with a layer map.В файле AppWorldLogic.h определите все параметры, необходимые для создания ландшафта, а также сам ландшафт с картой слоя.

AppWorldLogic.h

Исходный код (C++)
#ifndef __APP_WORLD_LOGIC_H__
#define __APP_WORLD_LOGIC_H__

#include <UnigineLogic.h>
#include <UnigineStreams.h>
#include <UnigineObjects.h>
#include <UnigineImage.h>


class AppWorldLogic : public Unigine::WorldLogic
{
	// Layer map parameters
	// position, rotation and scaling factor along the Z axis 
	Unigine::Math::Vec3 lmapPosition = Unigine::Math::Vec3::ZERO;
	float lmapRotation = 45.0f;
	float lmapHeightScale = 1.0f;

	// size of the layer map (in units)
	Unigine::Math::Vec2 lmapSize = Unigine::Math::Vec2(20.0f, 20.0f);
	
	// landscape layer map grid size (number of cells along X and Y axes)
	int lmapGridSizeX = 2;
	int lmapGridSizeY = 2;

	// resolution of a single tile of the layer map 
	// (tile images to be used must have the same resolution)
	int lmapTileResolutionX = 512;
	int lmapTileResolutionY = 512;
	
	// layer map name
	Unigine::String lmapName = "map";
	
	// set of images for a single tile
	struct TileImages
	{
		Unigine::String albedoImagePath;
		Unigine::String heightImagePath;
	};

	// vector for the tileset data
	Unigine::Vector<TileImages> tiles;

	// pointers to the terrain and landscape map (at least one is required)
	Unigine::ObjectLandscapeTerrainPtr terrain;
	Unigine::LandscapeLayerMapPtr lmap;

public:
	AppWorldLogic();
	virtual ~AppWorldLogic();

	int init() override;

	int update() override;
	int postUpdate() override;
	int updatePhysics() override;

	int shutdown() override;

	int save(const Unigine::StreamPtr &stream) override;
	int restore(const Unigine::StreamPtr &stream) override;

	void makeTileset();
	void applySettings();
	void createTerrain();

	// callbacks for different stages of landscape layer map creation 
	void OnCreatorCreate(Unigine::LandscapeMapFileCreatorPtr creator, Unigine::LandscapeImagesPtr images, int x, int y);
	void OnCreatorBegin(Unigine::LandscapeMapFileCreatorPtr creator);
	void OnCreatorProgress(Unigine::LandscapeMapFileCreatorPtr creator);
	void OnCreatorEnd(Unigine::LandscapeMapFileCreatorPtr creator);
};

#endif // __APP_WORLD_LOGIC_H__

Insert the following code implementing terrain creation into the AppWorldLogic.cpp file.Вставьте следующий код, реализующий создание ландшафта, в файл AppWorldLogic.cpp.

Примечание
Unchanged methods of the AppWorldLogic class are not listed here, so leave them as they are.Неизмененные методы класса AppWorldLogic здесь не перечислены, поэтому оставьте их такими, какие они есть.

AppWorldLogic.cpp

Исходный код (C++)
#include "AppWorldLogic.h"
#include <UnigineConsole.h>
#include <UnigineFileSystem.h>
#include <UnigineInput.h>
#include <UnigineGame.h>
#include <UnigineWorld.h>
using namespace Unigine;
using namespace Math;



AppWorldLogic::AppWorldLogic()
{
}

AppWorldLogic::~AppWorldLogic()
{
}

// callback to be fired on creating a tile
void AppWorldLogic::OnCreatorCreate(LandscapeMapFileCreatorPtr creator, LandscapeImagesPtr images, int x, int y)
{
	// get number of the current tile
	int tileNumber = x * lmapGridSizeY + y;
	Log::message("Create tile %d\n", tileNumber);

	// set albedo for current tile
	if (FileSystem::isFileExist(tiles[tileNumber].albedoImagePath))
	{
		ImagePtr albedoImage = Image::create(tiles[tileNumber].albedoImagePath);
		if (albedoImage && (albedoImage->getWidth() == lmapTileResolutionX) && (albedoImage->getHeight() == lmapTileResolutionY))
		{
			ImagePtr albedo = images->getAlbedo();
			albedo->create2D(albedoImage->getWidth(), albedoImage->getHeight(), albedoImage->getFormat(), albedoImage->getNumMipmaps());
			albedo->copy(albedoImage, 0, 0, 0, 0, albedoImage->getWidth(), albedoImage->getHeight());
		}
		else
			Log::error("The albedo image cannot be loaded, or its resolution does not match the resolution of tile.\n");
	}
	else
		Log::error("Albedo file does not exist.\n");

	// set height for current tile
	if (FileSystem::isFileExist(tiles[tileNumber].heightImagePath))
	{
		ImagePtr heightImage = Image::create(tiles[tileNumber].heightImagePath);
		if (heightImage && (heightImage->getWidth() == lmapTileResolutionX) && (heightImage->getHeight() == lmapTileResolutionY))
		{
			ImagePtr height = images->getHeight();
			height->create2D(heightImage->getWidth(), heightImage->getHeight(), heightImage->getFormat(), heightImage->getNumMipmaps());
			height->copy(heightImage, 0, 0, 0, 0, heightImage->getWidth(), height->getHeight());
		}
		else
			Log::error("The height image cannot be loaded, or its resolution does not match the resolution of tile.\n");
	}
	else
		Log::error("Height file does not exist.\n");
}

// callback to be fired on beginning the landscape map file generation process
void AppWorldLogic::OnCreatorBegin(LandscapeMapFileCreatorPtr creator)
{
	Log::message("--------------------\n");
	Log::message("--- %s creation started ---\n", creator->getPath());
	Log::message("lmap creator begin\n");
}

// callback to be used for visualizing landscape map file generation progress
void AppWorldLogic::OnCreatorProgress(LandscapeMapFileCreatorPtr creator)
{
	Log::message("lmap creator progress: %d\n", creator->getProgress());
}

// callback to be fired on beginning the landscape map generation process
void AppWorldLogic::OnCreatorEnd(LandscapeMapFileCreatorPtr creator)
{
	Log::message("lmap creator end\n");
	Log::message("--- %s created ---\n", creator->getPath());
	Log::message("--------------------\n");

	// after creating .lmap file apply settings
	applySettings();

	// and create terrain
	createTerrain();
}

// function applying .lmap file settings
void AppWorldLogic::applySettings()
{
	// load target .lmap file for settings
	LandscapeMapFileSettingsPtr settings = LandscapeMapFileSettings::create();
	settings->load(FileSystem::getGUID(String::format("%s.lmap",lmapName.get())));

	// set parameters and apply them
	if (settings->isLoaded())
	{
		// set alpha blend for height and albedo
		settings->setHeightBlending(Landscape::BLENDING_MODE::ALPHA_BLEND); 
		settings->setAlbedoBlending(Landscape::BLENDING_MODE::ALPHA_BLEND);

		settings->setEnabledHeight(true);
		settings->setEnabledAlbedo(true);

		// disable opacity for height and albedo
		settings->setEnabledOpacityAlbedo(false);
		settings->setEnabledOpacityHeight(false);

		settings->apply();
	}
}
// function creating the Landscape Terrain object using the generated .lmap file
void AppWorldLogic::createTerrain()
{
	// create new terrain
	terrain = ObjectLandscapeTerrain::create();
	terrain->setActiveTerrain(true);
	terrain->setCollision(true, 0);

	// create layer map based on created .lmap file
	lmap = LandscapeLayerMap::create();
	lmap->setParent(Landscape::getActiveTerrain());
	lmap->setPath(String::format("%s.lmap", lmapName.get()));
	lmap->setName(lmapName.get());
	lmap->setSize(lmapSize);
	lmap->setHeightScale(lmapHeightScale);
	lmap->setWorldPosition(lmapPosition);
	lmap->setWorldRotation(quat(vec3::UP, lmapRotation));
}

// function creating the Landscape Terrain object using the generated .lmap file
void AppWorldLogic::makeTileset()
{
	// filling
	TileImages timg;
	timg.albedoImagePath = "albedo.png";
	timg.heightImagePath = "height.png";
	int n_tiles = lmapGridSizeX * lmapGridSizeY;
	while (tiles.size() < n_tiles)
		tiles.append(timg);
}

int AppWorldLogic::init()
{
	// set the camera
	Game::getPlayer()->setPosition(Vec3(-15.0f, 3.0f, 8.0f));
	Game::getPlayer()->worldLookAt(Vec3(-1.0f, 15.0f, 0.0f));

	// disable nodes created in the template world by default
	NodePtr content_root = World::getNodeByName("content");
	if (content_root)
		content_root->setEnabled(false);

	//enable displaying console messages
	Console::run("console_onscreen 1");

	// collect the tileset
	makeTileset();
	
	// create .lmap file based on tiles with albedo and height images
	LandscapeMapFileCreatorPtr lmapCreator = LandscapeMapFileCreator::create();
	lmapCreator->setGrid(ivec2(lmapGridSizeX, lmapGridSizeY));
	lmapCreator->setResolution(ivec2(lmapTileResolutionX * lmapGridSizeX, lmapTileResolutionY * lmapGridSizeY));

	lmapCreator->setPath(String::format("%s.lmap", lmapName.get()));

	// add callbacks for different stages of *.lmap file creation
	lmapCreator->addCreateCallback(MakeCallback(this, &AppWorldLogic::OnCreatorCreate));
	lmapCreator->addBeginCallback(MakeCallback(this, &AppWorldLogic::OnCreatorBegin));
	lmapCreator->addProgressCallback(MakeCallback(this, &AppWorldLogic::OnCreatorProgress));
	lmapCreator->addEndCallback(MakeCallback(this, &AppWorldLogic::OnCreatorEnd));

	// start the creation process
	lmapCreator->run();

	return 1;
}

// ...

Modifying Terrain By Adding New Layer MapsИзменение ландшафта путем добавления новых карт слоев#

Spawn new Landscape Layer Maps (LandscapeLayerMap) to modify terrain surface, these layer maps can represent vehicle tracks, chunks of trenches, or pits. This way is similar to using Decals: each layer is a separate node, so you can control each modification separately. Moreover, using Landscape Layer Map implies no data density limits, enabling you to achieve realistic results with high-quality insets.Создайте новые Landscape Layer Map (LandscapeLayerMap) для изменения поверхности ландшафта, эти карты слоев могут представлять следы транспортных средств, участки траншей или ям. Этот способ аналогичен использованию декалей: каждый слой является отдельной нодой, поэтому вы можете управлять каждой модификацией отдельно. Более того, использование Landscape Layer Map не подразумевает ограничений по плотности данных, что позволяет достигать реалистичных результатов с высококачественными вставками.

Adding AssetsДобавление ассетов#

Let's create a new layer map for a crater, to do so, perform the following actions:Давайте создадим новую карту слоя для кратера. Для этого выполните следующие действия:

  1. Save the following images to be used for the crater to your computer:Сохраните на свой компьютер следующие изображения, которые будут использоваться для кратера:

    Карта альбедо для кратера Карта высот для кратера
    Карта альбедо для кратера Карта высот для кратера
  2. Switch to UnigineEditor and Drag the crater_height.png file directly to the Asset Browser window to add it to your project (just like you did before for the terrain's Height map). In the Import Dialog for your height map, set Image Format to R32F and click Yes.В UnigineEditor перетащите файл crater_height.png непосредственно в окно Asset Browser, чтобы добавить его в свой проект (точно так же, как вы делали раньше с картой высот ландшафта). В диалоговом окне импорта для вашей карты высот установите для Image Format значение R32F и нажмите Yes.
  3. Drag the crater_albedo.png file directly to the Asset Browser window (just like you did before for the terrain's Albedo map). In the Import Dialog for your albedo map set Texture Preset to Albedo (RGB — color, A — opacity) and click Yes.Перетащите файл crater_albedo.png непосредственно в окно Asset Browser (точно так же, как вы делали раньше с картой альбедо ландшафта). В диалоговом окне импорта для карты альбедо установите для Texture Preset значение Albedo (RGB — color, A — opacity) и нажмите Yes.
  4. Select Create -> Create Landscape Layer Map in the Asset Browser.В Asset Browser выберите Create -> Create Landscape Layer Map.

  5. Enter a name for the layer map: crater.Введите имя для карты слоя: crater.
  6. Select your new crater.lmap asset and adjust its settings as shown below (assign our images, select blending mode and adjust heights range for the crater).Выберите свой новый ассет crater.lmap и измените его настройки, как показано ниже (назначьте наши изображения, выберите режим наложения и отрегулируйте диапазон высот для кратера).

  7. Click Reimport and process to the Code section below.Нажмите Reimport и переходите к разделу Код далее.

CodeКод#

So, we have a layer map file representing a crater (crater.lmap). To add a crater to the landscape surface at the desired location we simply create a new LandscapeLayerMap using our file and set its size and transformation.Итак, у нас есть файл карты слоя, представляющий кратер (crater.lmap). Чтобы добавить кратер на поверхность ландшафта в нужном месте, мы просто создаем новый LandscapeLayerMap с помощью нашего файла и задаем его размер и трансформации.

Let's implement a method spawning a crater at a specified location (AddCrater()):Давайте реализуем метод, создающий кратер в указанном месте (AddCrater()):

Исходный код (C++)
LandscapeLayerMapPtr AddCrater(float x, float y) {
	// create a new layer map
	LandscapeLayerMapPtr crater_lmap = LandscapeLayerMap::create();

	// add the layer map as a child to the active terrain
	crater_lmap->setParent(Landscape::getActiveTerrain());

	// set a path to the crater.lmap file representing a crater
	crater_lmap->setPath(String::format("crater.lmap"));
	crater_lmap->setName("crater");

	// set the size of the crater layer to 5x5 units
	crater_lmap->setSize(Vec2(5.0f, 5.0f));

	// set the height scale multiplier 
	crater_lmap->setHeightScale(0.5f);

	// set the position and rotation of the new layer
	crater_lmap->setWorldPosition(Vec3(x, y, 0.0f));
	crater_lmap->setWorldRotation(quat(vec3::UP, 35.0f));

	// set the order of the new layer to place is above the first one (basic)
	crater_lmap->setOrder(2);
	return crater_lmap;
}
Примечание
Although layer maps are relatively light and fast, genereating too many of them may drop performance.Хотя карты слоев относительно легкие и быстрые, генерация слишком большого их числа может снизить производительность.

This approach is non-destructive (i.e., it does not irreversibly change initial terrain data). To see the underlaying layer again in its initial state simply disable an overlaying layer map:Этот подход является неразрушающим (т.е. он не изменяет необратимо исходные данные о ландшафте). Чтобы снова увидеть нижележащий слой в его исходном состоянии, просто отключите карту наложенного слоя:

Исходный код (C++)
crater_lmap->setEnabled(false);

For example, after disabling a layer map representing a crater, you'll have the surface looking just the way it was before the explosion.Например, после отключения карты слоев, представляющей кратер, поверхность будет выглядеть точно так же, как до взрыва.

To illustrate that let's add the AddCrater() method described above to our AppWorldLogic.cpp file, declare a vector to store all spawned craters, and add handlers for Enter key (spawn a new crater) and Backspace key (show/hide all craters) in the AppWorldLogic::update() method:Чтобы проиллюстрировать это, давайте добавим метод AddCrater(), описанный выше, в наш файл AppWorldLogic.cpp, объявим вектор для хранения всех созданных кратеров и добавим обработчики для клавиши Enter (создать новый кратер) и клавиши Backspace (показать/скрыть все кратеры) в методе AppWorldLogic::update():

Исходный код (C++)
// ...
// declare a vector to store craters
Unigine::Vector<Unigine::LandscapeLayerMapPtr> craters;

// ...
int AppWorldLogic::update()
{
	// ...
	// spawn a new crater at a random point on hitting Backspace
	if (Input::isKeyDown(Input::KEY_BACKSPACE))
		craters.append(AddCrater(Game::getRandomFloat(0.0f, lmapSize.x), Game::getRandomFloat(0.0f, lmapSize.y)));
	
	// toggle visibility for all spawned craters on hitting Enter to show initial landscape state
	else if (Input::isKeyDown(Input::KEY_RETURN))
		for (Vector<LandscapeLayerMapPtr>::Iterator it = craters.begin(); it != craters.end(); ++it)
			it->get()->setEnabled(!it->get()->isEnabled());
	return 1;
}
// ...
int AppWorldLogic::shutdown()
{
	// ...
	// delete all spawned craters
	for (Vector<LandscapeLayerMapPtr>::Iterator it = craters.begin(); it != craters.end(); ++it)
		it->get()->deleteLater();
	return 1;
}

GPU-Based Terrain ModificationМодификация ландшафта на стороне графического процессора#

Terrain modification is performed in asynchronous mode on GPU side by calling the asyncTextureDraw method of the Landscape class, that commences a drawing operation. The operation itself is to be implemented inside an event handler.Модификация ландшафта выполняется в асинхронном режиме на стороне графического процессора путем вызова метода asyncTextureDraw класса Landscape, который запускает операцию отрисовки. Сама операция должна быть реализована внутри обработчика обратного вызова.

The workflow here is as follows:Рабочий процесс здесь выглядит следующим образом:

  1. Implement your GPU-based terrain modification logic in a function.Реализуйте свою логику изменения ландшафта на стороне графического процессора в функции.
  2. Set this handler function when subscribing for the Texture Draw event (when GPU-based terrain modification operation is performed) via getEventTextureDraw().Установите эту функцию, подписываясь на событие Texture Draw (модификация ландшафта на стороне графического процессора) через getEventTextureDraw().
  3. Commence a GPU drawing operation by calling the asyncTextureDraw() method. Here you should specify the GUID of an .lmap file of the landscape layer map to be modified, the coordinates of the top-left corner and the resolution of the segment of data to be modified, you should also define which data layers are to be affected (heights, albedo, masks) via a set of flags.Запустите операцию отрисовки на графическом процессоре, вызвав метод asyncTextureDraw(). Здесь вы должны указать идентификатор GUID файла .lmap карты слоя ландшафта, который необходимо изменить, координаты верхнего левого угла и разрешение сегмента данных, который необходимо изменить. Вы также должны определить с помощью набора флагов, какие слои данных должны быть затронуты (высоты, альбедо, маски).

    Примечание
    If your modification requires additional data beyond the specified area as well as data of other landscape layer maps (e.g. a copy brush), you can enable force loading of required data. In this case you should use this overload of the asyncTextureDraw() method.Если для вашей модификации требуются дополнительные данные за пределами указанной области, а также данные других карт слоев ландшафта (например, кисть для копирования), вы можете включить принудительную загрузку необходимых данных. В этом случае вы должны использовать эту перегрузку метода asyncTextureDraw().

Adding AssetsДобавление ассетов#

Let us modify the Heights and Albedo data of the terrain, so we need two custom maps for that. Save the following images to your computer and drag them to the Asset Browser window to add them to your project (just like you did before for the terrain's Albedo and Height maps):Давайте изменим данные Высоты и Альбедо ландшафта, для этого нам понадобятся две пользовательские карты. Сохраните следующие изображения на свой компьютер и перетащите их в окно Asset Browser, чтобы добавить их в свой проект (точно так же, как вы делали раньше для карт Альбедо и Высота ландшафта):

Custom Albedo Map Custom Height Map
Custom Albedo Map Custom Height Map

Don't forget to set Image Format to R32F for your height map and set Texture Preset to Albedo (RGB — color, A — opacity) for your albedo map and reimport them with new settings.Не забудьте установить для Image Format значение R32F для вашей карты высот и выбрать Texture Preset Albedo (RGB — color, A — opacity) для вашей карты альбедо и повторно импортировать их с новыми настройками.

CodeКод#

Insert the following code into the AppWorldLogic.cpp file.Вставьте следующий код в файл AppWorldLogic.cpp.

Примечание
Unchanged methods of the AppWorldLogic class are not listed here, so leave them as they are.Неизмененные методы класса AppWorldLogic здесь не перечислены, поэтому оставьте их такими, какие они есть.
Исходный код (C++)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
// 												(1)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

// images to be used for terrain modification
Unigine::ImagePtr custom_albedo_image1;
Unigine::ImagePtr custom_height_image1;

// callback to be fired on commencing a texture draw operation
void my_texture_draw(UGUID guid, int id, LandscapeTexturesPtr buffer, ivec2 coord, int data_mask)
{
	// preparing images for terrain modification 
	// Note: image preparation block can be moved outside the callback, not to repeat these operations every time,
	// e.g. you can move this block to a place before calling the asyncTextureDraw() method 
	{
		// create a new image to load a custom albedo map to
		custom_albedo_image1 = Image::create("custom_albedo.png");

		// set the format required for the albedo map — RGBA8
		custom_albedo_image1->convertToFormat(Image::FORMAT_RGBA8);

		// create a new image to load a custom height map to
		custom_height_image1 = Image::create("custom_height.png");

		// set the format required for the heightmap — R32F
		custom_height_image1->convertToFormat(Image::FORMAT_R32F);
	}

	// resize albedo and height images to fit the area to be modified
	custom_albedo_image1->resize(buffer->getResolution().x, buffer->getResolution().y);
	custom_height_image1->resize(buffer->getResolution().x, buffer->getResolution().y);

	// setting our custom image to the albedo buffer
	buffer->getAlbedo()->setImage(custom_albedo_image1);

	// setting our custom image to the height buffer
	buffer->getHeight()->setImage(custom_height_image1);

}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
// 												(2)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
int AppWorldLogic::init()
{
	// ...
	// add a callback to be fired on a Texture Draw operation
	Landscape::addTextureDrawCallback(MakeCallback(my_texture_draw));
	return 1;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
// 												(3)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
int AppWorldLogic::update()
{
	// ...
	if (Input::isKeyPressed(Input::KEY_SPACE)) {
		// get the first layermap that we're going to modify
		LandscapeLayerMapPtr lmap = checked_ptr_cast <LandscapeLayerMap> (terrain->getChild(0));

		// generate a new ID for the draw operation
		int id = Landscape::generateOperationID();

		// commence a Texture Draw operation for the selected landscape map at (10, 10) with the size of [512 x 512]
		Landscape::asyncTextureDraw(id, lmap->getGUID(), ivec2(10, 10), ivec2(512, 512), 
									(int)(Landscape::FLAGS_FILE_DATA_HEIGHT | Landscape::FLAGS_FILE_DATA_ALBEDO));
	}
	return 1;
}

int AppWorldLogic::shutdown()
{
	// ...
	// clear TextureDraw callbacks
	Landscape::clearTextureDrawCallbacks();
	return 1;
}

Resulting Demonstration CodeИтоговый демонстрационный код#

Copy the source code below and paste it to the corresponding source files of your project.Скопируйте приведенный ниже исходный код и вставьте его в соответствующие исходные файлы вашего проекта.

AppWorldLogic.h

Исходный код (C++)
#ifndef __APP_WORLD_LOGIC_H__
#define __APP_WORLD_LOGIC_H__

#include <UnigineLogic.h>
#include <UnigineStreams.h>
#include <UnigineObjects.h>
#include <UnigineImage.h>


class AppWorldLogic : public Unigine::WorldLogic
{
	// Layer map parameters
	// position, rotation and scaling factor along the Z axis 
	Unigine::Math::Vec3 lmapPosition = Unigine::Math::Vec3::ZERO;
	float lmapRotation = 0.0f;
	float lmapHeightScale = 1.0f;

	// size of the layer map (in units)
	Unigine::Math::Vec2 lmapSize = Unigine::Math::Vec2(20.0f, 20.0f);

	// landscape layer map grid size (number of cells along X and Y axes)
	int lmapGridSizeX = 2;
	int lmapGridSizeY = 2;

	// resolution of a single tile of the layer map 
	// (tile images to be used must have the same resolution)
	int lmapTileResolutionX = 512;
	int lmapTileResolutionY = 512;

	// layer map name
	Unigine::String lmapName = "map";

	// set of images for a single tile
	struct TileImages
	{
		Unigine::String albedoImagePath;
		Unigine::String heightImagePath;
	};

	// vector for the tileset data
	Unigine::Vector<TileImages> tiles;

	// pointers to the terrain and landscape map (at least one is required)
	Unigine::ObjectLandscapeTerrainPtr terrain;
	Unigine::LandscapeLayerMapPtr lmap;



public:
	AppWorldLogic();
	virtual ~AppWorldLogic();

	int init() override;

	int update() override;
	int postUpdate() override;
	int updatePhysics() override;

	int shutdown() override;

	int save(const Unigine::StreamPtr &stream) override;
	int restore(const Unigine::StreamPtr &stream) override;

	void makeTileset();
	void applySettings();
	void createTerrain();

	// callbacks for different stages of landscape layer map creation 
	void OnCreatorCreate(Unigine::LandscapeMapFileCreatorPtr creator, Unigine::LandscapeImagesPtr images, int x, int y);
	void OnCreatorBegin(Unigine::LandscapeMapFileCreatorPtr creator);
	void OnCreatorProgress(Unigine::LandscapeMapFileCreatorPtr creator);
	void OnCreatorEnd(Unigine::LandscapeMapFileCreatorPtr creator);
};

#endif // __APP_WORLD_LOGIC_H__

AppWorldLogic.cpp

Исходный код (C++)
#include "AppWorldLogic.h"
#include <UnigineConsole.h>
#include <UnigineFileSystem.h>
#include <UnigineInput.h>
#include <UnigineGame.h>
#include <UnigineWorld.h>
using namespace Unigine;
using namespace Math;


// images to be used for terrain modification
Unigine::ImagePtr custom_albedo_image1;
Unigine::ImagePtr custom_height_image1;

// array of craters
Unigine::Vector<Unigine::LandscapeLayerMapPtr> craters;

// callback to be fired on commencing a texture draw operation
void my_texture_draw(UGUID guid, int id, LandscapeTexturesPtr buffer, ivec2 coord, int data_mask)
{
	// resize albedo and height images to fit the area to be modified
	custom_albedo_image1->resize(buffer->getResolution().x, buffer->getResolution().y);
	custom_height_image1->resize(buffer->getResolution().x, buffer->getResolution().y);

	// setting our custom image to the albedo buffer
	buffer->getAlbedo()->setImage(custom_albedo_image1);

	// setting our custom image to the height buffer
	buffer->getHeight()->setImage(custom_height_image1);
}

// method spawning a new crater at the specified location
LandscapeLayerMapPtr AddCrater(float x, float y) {
	// create a new layer map
	LandscapeLayerMapPtr crater_lmap = LandscapeLayerMap::create();

	// add the layer map as a child to the active terrain
	crater_lmap->setParent(Landscape::getActiveTerrain());

	// set a path to the crater.lmap file representing a crater
	crater_lmap->setPath(String::format("crater.lmap"));
	crater_lmap->setName("crater");

	// set the size of the crater layer to 5x5 units
	crater_lmap->setSize(Vec2(5.0f, 5.0f));

	// set the height scale multiplier 
	crater_lmap->setHeightScale(0.5f);

	// set the position and rotation of the new layer
	crater_lmap->setWorldPosition(Vec3(x, y, 0.0f));
	crater_lmap->setWorldRotation(quat(vec3::UP, 35.0f));

	// set the order of the new layer to place is above the first one (basic)
	crater_lmap->setOrder(2);
	return crater_lmap;
}

// World logic, it takes effect only when the world is loaded.
// These methods are called right after corresponding world script's (UnigineScript) methods.
// callback to be fired on creating a tile
void AppWorldLogic::OnCreatorCreate(LandscapeMapFileCreatorPtr creator, LandscapeImagesPtr images, int x, int y)
{
	// get number of the current tile
	int tileNumber = x * lmapGridSizeY + y;
	Log::message("Create tile %d\n", tileNumber);

	// set albedo for current tile
	if (FileSystem::isFileExist(tiles[tileNumber].albedoImagePath))
	{
		ImagePtr albedoImage = Image::create(tiles[tileNumber].albedoImagePath);
		if (albedoImage && (albedoImage->getWidth() == lmapTileResolutionX) && (albedoImage->getHeight() == lmapTileResolutionY))
		{
			ImagePtr albedo = images->getAlbedo();
			albedo->create2D(albedoImage->getWidth(), albedoImage->getHeight(), albedoImage->getFormat(), albedoImage->getNumMipmaps());
			albedo->copy(albedoImage, 0, 0, 0, 0, albedoImage->getWidth(), albedoImage->getHeight());
		}
		else
			Log::error("The albedo image cannot be loaded, or its resolution does not match the resolution of tile.\n");
	}
	else
		Log::error("Albedo file does not exist.\n");

	// set height for current tile
	if (FileSystem::isFileExist(tiles[tileNumber].heightImagePath))
	{
		ImagePtr heightImage = Image::create(tiles[tileNumber].heightImagePath);
		if (heightImage && (heightImage->getWidth() == lmapTileResolutionX) && (heightImage->getHeight() == lmapTileResolutionY))
		{
			ImagePtr height = images->getHeight();
			height->create2D(heightImage->getWidth(), heightImage->getHeight(), heightImage->getFormat(), heightImage->getNumMipmaps());
			height->copy(heightImage, 0, 0, 0, 0, heightImage->getWidth(), height->getHeight());
		}
		else
			Log::error("The height image cannot be loaded, or its resolution does not match the resolution of tile.\n");
	}
	else
		Log::error("Height file does not exist.\n");
}

// callback to be fired on beginning the landscape map file generation process
void AppWorldLogic::OnCreatorBegin(LandscapeMapFileCreatorPtr creator)
{
	Log::message("--------------------\n");
	Log::message("--- %s creation started ---\n", creator->getPath());
	Log::message("lmap creator begin\n");
}

// callback to be used for visualizing landscape map file generation progress
void AppWorldLogic::OnCreatorProgress(LandscapeMapFileCreatorPtr creator)
{
	Log::message("lmap creator progress: %d\n", creator->getProgress());
}

// callback to be fired on beginning the landscape map generation process
void AppWorldLogic::OnCreatorEnd(LandscapeMapFileCreatorPtr creator)
{
	Log::message("lmap creator end\n");
	Log::message("--- %s created ---\n", creator->getPath());
	Log::message("--------------------\n");

	// after creating .lmap file apply settings
	applySettings();

	// and create terrain
	createTerrain();
}

// function applying .lmap file settings
void AppWorldLogic::applySettings()
{
	// load target .lmap file for settings
	LandscapeMapFileSettingsPtr settings = LandscapeMapFileSettings::create();
	settings->load(FileSystem::getGUID(String::format("%s.lmap", lmapName.get())));

	// set parameters and apply them
	if (settings->isLoaded())
	{
		// set alpha blend for height and albedo
		settings->setHeightBlending(Landscape::BLENDING_MODE::ALPHA_BLEND);
		settings->setAlbedoBlending(Landscape::BLENDING_MODE::ALPHA_BLEND);

		settings->setEnabledHeight(true);
		settings->setEnabledAlbedo(true);

		// disable opacity for height and albedo
		settings->setEnabledOpacityAlbedo(false);
		settings->setEnabledOpacityHeight(false);

		settings->apply();
	}
}
// function creating the Landscape Terrain object using the generated .lmap file
void AppWorldLogic::createTerrain()
{
	// create new terrain
	terrain = ObjectLandscapeTerrain::create();
	terrain->setActiveTerrain(true);
	terrain->setCollision(true, 0);

	// create layer map based on created .lmap file
	lmap = LandscapeLayerMap::create();
	lmap->setParent(Landscape::getActiveTerrain());
	lmap->setPath(String::format("%s.lmap", lmapName.get()));
	lmap->setName(lmapName.get());
	lmap->setSize(lmapSize);
	lmap->setHeightScale(lmapHeightScale);
	lmap->setWorldPosition(lmapPosition);
	lmap->setWorldRotation(quat(vec3::UP, lmapRotation));
}

// function creating the Landscape Terrain object using the generated .lmap file
void AppWorldLogic::makeTileset()
{
	// filling
	TileImages timg;
	timg.albedoImagePath = "albedo.png";
	timg.heightImagePath = "height.png";
	int n_tiles = lmapGridSizeX * lmapGridSizeY;
	while (tiles.size() < n_tiles)
		tiles.append(timg);
}
AppWorldLogic::AppWorldLogic()
{
}

AppWorldLogic::~AppWorldLogic()
{
}

int AppWorldLogic::init()
{
	// set the camera
	Game::getPlayer()->setPosition(Vec3(0.0f, lmapSize.y, 8.0f));
	Game::getPlayer()->worldLookAt(Vec3(lmapSize.x/2, lmapSize.y/2, 0.0f));
	
	// disable nodes created in the template world by default
	NodePtr content_root = World::getNodeByName("content");
	if (content_root)
		content_root->setEnabled(false);
	//enable displaying console messages
	Console::run("console_onscreen 1");

	// collect the tileset
	makeTileset();

	// create .lmap file based on tiles with albedo and height images
	LandscapeMapFileCreatorPtr lmapCreator = LandscapeMapFileCreator::create();
	lmapCreator->setGrid(ivec2(lmapGridSizeX, lmapGridSizeY));
	lmapCreator->setResolution(ivec2(lmapTileResolutionX * lmapGridSizeX, lmapTileResolutionY * lmapGridSizeY));

	lmapCreator->setPath(String::format("%s.lmap", lmapName.get()));

	// add callbacks for different stages of *.lmap file creation
	lmapCreator->addCreateCallback(MakeCallback(this, &AppWorldLogic::OnCreatorCreate));
	lmapCreator->addBeginCallback(MakeCallback(this, &AppWorldLogic::OnCreatorBegin));
	lmapCreator->addProgressCallback(MakeCallback(this, &AppWorldLogic::OnCreatorProgress));
	lmapCreator->addEndCallback(MakeCallback(this, &AppWorldLogic::OnCreatorEnd));

	// start the creation process
	lmapCreator->run();

	// preparing images for terrain modification 
	// create a new image to load a custom albedo map to
	custom_albedo_image1 = Image::create("custom_albedo.png");

	// set the format required for the albedo map — RGBA8
	custom_albedo_image1->convertToFormat(Image::FORMAT_RGBA8);

	// create a new image to load a custom height map to
	custom_height_image1 = Image::create("custom_height.png");

	// set the format required for the heightmap — R32F
	custom_height_image1->convertToFormat(Image::FORMAT_R32F);

	// add a callback to be fired on a Texture Draw operation
	Landscape::addTextureDrawCallback(MakeCallback(my_texture_draw));
	return 1;
}

////////////////////////////////////////////////////////////////////////////////
// start of the main loop
////////////////////////////////////////////////////////////////////////////////

int AppWorldLogic::update()
{
	if (Input::isKeyDown(Input::KEY_BACKSPACE))// spawning a new crater at a random point
		craters.append(AddCrater(Game::getRandomFloat(0.0f, lmapSize.x), Game::getRandomFloat(0.0f, lmapSize.y)));
	else if (Input::isKeyDown(Input::KEY_RETURN))// hiding all craters to show initial landscape state
		for (Vector<LandscapeLayerMapPtr>::Iterator it = craters.begin(); it != craters.end(); ++it)
			it->get()->setEnabled(!it->get()->isEnabled());

	if (Input::isKeyPressed(Input::KEY_SPACE)) {
		// get the first layermap that we're going to modify
		LandscapeLayerMapPtr lmap = checked_ptr_cast <LandscapeLayerMap> (terrain->getChild(0));

		// generate new ID for the draw operation
		int id = Landscape::generateOperationID();
		
		// user's code (bounding to ID)
		// commence a Texture Draw operation for the selected landscape map at (10, 10) with the size of [512 x 512]
		Landscape::asyncTextureDraw(id, lmap->getGUID(), ivec2(10, 10), ivec2(512, 512), (int)(Landscape::FLAGS_FILE_DATA_HEIGHT | Landscape::FLAGS_FILE_DATA_ALBEDO));
	}
	return 1;
}

int AppWorldLogic::postUpdate()
{
	// The engine calls this function after updating each render frame: correct behavior after the state of the node has been updated.
	return 1;
}

int AppWorldLogic::updatePhysics()
{
	// Write here code to be called before updating each physics frame: control physics in your application and put non-rendering calculations.
	// The engine calls updatePhysics() with the fixed rate (60 times per second by default) regardless of the FPS value.
	// WARNING: do not create, delete or change transformations of nodes here, because rendering is already in progress.
	return 1;
}

////////////////////////////////////////////////////////////////////////////////
// end of the main loop
////////////////////////////////////////////////////////////////////////////////

int AppWorldLogic::shutdown()
{
	// delete all spawned craters
	for (Vector<LandscapeLayerMapPtr>::Iterator it = craters.begin(); it != craters.end(); ++it)
		it->get()->deleteLater();

	// clear TextureDraw callbacks on shutdown
	Landscape::clearTextureDrawCallbacks();
	return 1;
}

int AppWorldLogic::save(const Unigine::StreamPtr &stream)
{
	// Write here code to be called when the world is saving its state: save custom user data to a file.
	UNIGINE_UNUSED(stream);
	return 1;
}

int AppWorldLogic::restore(const Unigine::StreamPtr &stream)
{
	// Write here code to be called when the world is restoring its state: restore custom user data to a file here.
	UNIGINE_UNUSED(stream);
	return 1;
}
Последнее обновление: 19.12.2023
Build: ()