Jump to content

[SOLVED] Updating landscapes from code


photo

Recommended Posts

Hello,

We have a landscape object that we want to paint some of its mask from C++. I used the example from here https://developer.unigine.com/en/docs/2.13/api/library/objects/landscape_terrain/class.landscape?rlang=cpp to paint directly into textures via GPU.

All is fine except two main issues:

- The paint operations seems to be applied at a certain pace. It is not immediate and since I am doing it relatively often (one-two every second or so) and with small brush applications (3x3 pixels), they start to lag behind our simulation (it is used to paint burnt ground underneath spreading fire). At one point it lags way behind and doesn't seem to be able to catch up. So first question: is there a way to force the brush updates? I can see the editor still having some lags (when manually applying the brush) but its response is much better than what I witness via C++ code.

- Is there a way to have object clutters spread react to the paint mask via GPU update? I know editor can have the clutters mask linked to one of the landscape masks and I know we have some cluster update (on a bound box) call in C++, but (question 2), I am right to assume that clutters will not react to textures, but to mask images updates? If this is the case, can you indicate how can I update landscape images instead of textures (documentation is lacking in this aspect, I can find only sparse references that CPU based updates need to go into landscape images, and not textures). Also another question: how slow (performance impact) is to update landscape images and have the clutters react to it? Our updates, as indicated above, are like 1-2 / second and in very small areas (3x3 pixels).

Kind Regards,
Adrian L.

Link to comment

Hi Adrian,

As far as I know the only way to affect the already generated clutter object is to use decals and cutout intersection option. You can spawn decal in a shape of the fire and move it forward to remove the damaged trees. It's not possible to keep all the already generated trees position after you will re-set your mask via setMaskImage (for example, if you would find a performance friendly way to update the CPU mask according to the GPU mask changes).

If you still have an access to the 2.7.2 or 2.7 SDKs you can check how we implement the logic of trees destruction in the Tank demo (but, in that case we were using clusters instead of clutters).

Regarding the GPU mask update lags - that's unavoidable, I'm afraid. I think we can do a better mask update algorithm a bit later and you can check if it would be better in your case or not.

Thanks!

How to submit a good bug report
---
FTP server for test scenes and user uploads:

Link to comment

Hi,

1. Unfortunately, there is no way to wait for async terrain modifications. But you can try the following:

  • Verify that you only pass mask that you want to modify as the last argument to asyncTextureDraw. 
    Landscape::asyncTextureDraw(guid, coord, resolution, Landscape::FLAGS_DATA_MASK_0);
  •  If you are setting your mask to some fixed value, you can just clear texture to this value in the callback without using editor brush
    auto mask = buffer->getMask(0);
    mask->clearBuffer(vec4_one);
  • You can play with the following world settings to try if they make any difference for you (Settings->Runtime->World->Render->Landscape)
    • GPU Cache Size (render_landscape_cache_gpu_size)
    • GPU Cache Life Time (render_landscape_cache_gpu_life_time)
    • Tiles Reload Per Frame (render_landscape_terrain_vt_tiles_reload_per_frame)

2. First, CPU terrain modification should not be used. Second, Mesh Clutter will respond to the changes made, if you invalidate its tiles by calling ObjectMeshClutter::invalidate. It will regenerate all or some cells and will refetch mask values from the terrain. However, it can read old values, because it takes time to apply changes made to the terrain.

Link to comment

What is not clear as well from documentation - is how to modify the terrain without using mesh nodes, is there a way to modify the heightmap? Directly or through layers, etc. ?

Link to comment

Thanks fullconfession, will check tweaking those params you've mentioned. And good news that clutters would react to mask textures (I knew about invalidate function, didn't know if it can read textures also, or only CPU images)!

Link to comment

Hello,

Seems that those GPU cache parameters didn't helped. Anyway, debugging the source code, seems that the main reason for slow update pace is the fact that only one async operation is checked per frame in Landscape::update_operation_pool().

Forcing a bigger number of steps per frame in the update_operation_pool, something like:

	int steps = 50;
	int step = 0;
	do {

		if (operation_pool.first()->update())
		{
			engine.async_tasks->asyncDestroy(operation_pool.first());
			operation_pool.removeFirst();
		}

		step ++;
	} while (!operation_pool.empty() && step<steps);

will remove the delay (especially since GPU draw operation have around 4 states and only 1 was done per frame), but of course this may create stalls. Is there a way to expose this max number of steps per frame so each can tweak it (even if default is 1) ? Or can we have some sort of a more generic approach like checking how many milliseconds took for an update and continue with another as long, as we are under a certain budged (e.g. 1-2 ms / frame)? Or maybe this is not feasible as it would introduce stalls on GPU driver side of things?

Also, not only that there is a delay, but if I have many small, sparse updates, with this default slow update rate (1 step / frame) some of the updates are lost, they are not applied, while with the above modified code everything is being updated. This seems like a bug (maybe cause by multiple textures being updated and re-updated at once?), so while I can leave with a delayed update, I would really like to be applied in the end.

Also, am I right, to assume that moving the modified detail on top of other details will help with performance (especially with the feedback phase, when reading back to images) since I would be avoiding clearing the details on top to make this show through (with mask_override as 1) ?

Kind Regards,
Adrian L.

 

Link to comment

Your suggested approach is not really fixing anything, it just slow down the rendering. The same you can emulate with a simple sleep(1sec) in your rendering of 3x3 pixel code. Right now we don't have plans to change the API somehow.

When you working with GPU brushes and LandscapeTerrain modifications you always will have delays. Depending on a different hardware numbers can be different, but you will need to spend some time uploading the textures to GPU (doesn't matter if it's 3x3 px or 300x300) - 8 frames (in the best scenarios) and to download textures - 8 frames. That's how hardware works and we can't change it (but you can implement a better brushes behavior on your side).

We didn't see your exact drawing code yet, so our guess is that it can be optimized (there is really no point to upload 3x3 texture each frame with 8 frame delay - you queue will increase forever). We will show some basic algorithms from the Editor code that you can try to implement and see how it goes. Our guys from the Editor team has some interesting code to show (I guess tomorrow).

If you don't need a very detailed terrains you also can decrease the Target Resolution in Windows -> Settings -> Landscape, increase Texel Size to something like 10 and disable the Texture Filtering. That will help to increase bandwidth a bit, but will not change thing dramatically.

 

Quote

moving the modified detail on top of other details will help with performance 

That should help a bit, yes.

 

When modifying the textures it's important to keep the queue of modifications as low as possible. Keep an eye on the Terrain Reload Tiles and Terrain Reload Bounds in profiler and try to minimize the queue length and amount of tiles that needs to be redrawn simultaneosly:
image.png

How to submit a good bug report
---
FTP server for test scenes and user uploads:

Link to comment

Hi and thank you for the explanations,

Looking forward for the editor code since it seems to be able to update much quicker than what I do. What I do is basically from your examples in the docs:

The trigger of painting is:
Unigine::Landscape::asyncTextureDraw(id, layerMapGuid, pos, size, ~0);
where size is around 3x3 pixels.

And then the callback is something like:

void VisualGrid::_burntGroundCallback(Unigine::UGUID guid, int id, Unigine::LandscapeTexturesPtr buffer, Unigine::Math::ivec2 coord, int data_mask)
{
	Unigine::MaterialPtr mat = Unigine::Materials::findMaterial("circle_hard");

	if(mat) 
	{
		mat->setParameterFloat("size", 500);

		mat->setParameterFloat("mask_override", 1); // If one, it will clear the other masks that are on top, to make this visible.

		mat->setParameterFloat4("color", Unigine::Math::vec4::ONE); // Is this set needed? One is the default anyway.
		mat->setParameterFloat4("opacity", Unigine::Math::vec4::ONE);

		const int burntGroundMaskIndex = 11;
		const int burntGroundMaskFlag = (Unigine::Landscape::FLAGS_DATA_MASK_0 << burntGroundMaskIndex);

		// Data masks controls what we are affecting...

		int burnedGroundMask = burntGroundMaskFlag;

		// Question: what is the difference between affecting mask and affecting its opacity?
		// For now I write on both.
		mat->setParameterInt("data_mask", burnedGroundMask);
		mat->setParameterInt("data_mask_opacity", burnedGroundMask);

		// Setting necessary textures...
		
		mat->setTexture("terrain_mask_2", buffer->getMask(2));
		mat->setTexture("terrain_opacity_mask_2", buffer->getOpacityMask(2));
		
		// Running material's "brush" expression
		mat->runExpression("brush", buffer->getResolution().x, buffer->getResolution().y);
		
		// Resetting material textures
		mat->setTexture("terrain_mask_2", nullptr);
		mat->setTexture("terrain_opacity_mask_2", nullptr);
	}
}

So there are quite a few detail masks (currently painting on 11th, but planned to move this up as first, on the second lmap).

This is for burning ground simulation so, as fire spreads, you will have bigger and bigger circles, only with perimeter being updated. So a lot of 3x3 pixels updates. I can try to optimize on my end do some sort of partitioning to group multiple burning cells, so I do less updates but bigger, but it would just complicate the code on my end a lot (so need to be sure that it really helps with the speed).

What I am most concern of though is that I am loosing updates (they are dropped somehow, never to end up in mask texture) if I am NOT forcing multiple update passes per frame (if I do the code with 50 passes I never loose those). So any idea what this may happen?

Another question: what is actually the size in the buffer (I just set it as 500 which I think is the max value)? It is not obvious from the brush shader what that parameter does.

Kind Regards,
Adrian L.

Link to comment

Hi Adrian.

Here is the our internal code for the brush editor (easier to start reading from BrushTool::update):

Spoiler


#include "BrushTool.h"


#include <UnigineWorld.h>
#include <UnigineRender.h>
#include <UnigineFfp.h>
#include <UnigineMaterials.h>

#define DEFAULT_SPACING ((1.0f / 3.0f) / 10.0f)

using namespace Editor;
using namespace Unigine;
using namespace Unigine::Math;

namespace
{

struct BrushTextures
{
	const char *name;
	int flag;
	TexturePtr texture;
};

int get_state(const MaterialPtr &m, const char *name)
{
	int index_state = m->findState(name);
	if (index_state == -1)
		return -1;
	return m->getState(index_state);
}

void set_parameter(const MaterialPtr &m, const char *name, float p)
{
	int index_parameter = m->findParameter(name);
	if (index_parameter == -1)
		return;
	m->setParameterFloat(index_parameter, p);
}

float get_parameter(const MaterialPtr &m, const char *name, float def = 0.0f)
{
	int index_parameter = m->findParameter(name);
	if (index_parameter != -1)
		def = m->getParameterFloat(index_parameter);
	return def;
}

void set_parameter_reset(const MaterialPtr &m, const char *name, float p)
{
	MaterialPtr base_material = m->getBaseMaterial();
	int index_parameter = m->findParameter(name);
	if (index_parameter != -1)
	{
		if (base_material &&
			Unigine::Math::compare(get_parameter(base_material, name, 0.0f), p))
		{
			m->resetParameter(index_parameter);
		}
		else
			m->setParameterFloat(index_parameter, p);
	}
}

void set_parameter(const MaterialPtr &m, const char *name, const vec2 &v)
{
	int index_parameter = m->findParameter(name);
	if (index_parameter == -1)
		return;

	m->setParameterFloat2(index_parameter, v);
}

void set_parameter(const MaterialPtr &m, const char *name, int p)
{
	m->setParameterInt(name, p);
	m->setState(name, p);
}

int get_parameter_int(const MaterialPtr &m, const char *name, int def = 0)
{
	int id = m->findParameter(name);
	if (id != -1)
		def = m->getParameterInt(id);

	id = m->findState(name);
	if (id != -1)
		def = m->getState(id);

	return def;
}

ivec2 to_coord(const LandscapeLayerMapPtr &map, const Vec3 &point)
{
	Vec3 p = map->getIWorldTransform() * point;
	Vec2 texel_size = map->getTexelSize();
	int x = Unigine::Math::ceil(p.x / texel_size.x);
	int y = Unigine::Math::ceil(p.y / texel_size.y);
	return { x, y };
}

int get_material_required_data(MaterialPtr mat)
{

	BrushTextures textures[] =
	{
		{ "terrain_albedo", Landscape::FLAGS_FILE_DATA_ALBEDO, nullptr },
		{ "terrain_height", Landscape::FLAGS_FILE_DATA_HEIGHT, nullptr },
		{ "terrain_mask_0", Landscape::FLAGS_FILE_DATA_MASK_0, nullptr },
		{ "terrain_mask_1", Landscape::FLAGS_FILE_DATA_MASK_1, nullptr },
		{ "terrain_mask_2", Landscape::FLAGS_FILE_DATA_MASK_2, nullptr },
		{ "terrain_mask_3", Landscape::FLAGS_FILE_DATA_MASK_3, nullptr },
		{ "terrain_mask_4", Landscape::FLAGS_FILE_DATA_MASK_4, nullptr },

		{ "terrain_opacity_height", Landscape::FLAGS_FILE_DATA_OPACITY_HEIGHT, nullptr },
		{ "terrain_opacity_mask_0", Landscape::FLAGS_FILE_DATA_OPACITY_MASK_0, nullptr},
		{ "terrain_opacity_mask_1", Landscape::FLAGS_FILE_DATA_OPACITY_MASK_1, nullptr},
		{ "terrain_opacity_mask_2", Landscape::FLAGS_FILE_DATA_OPACITY_MASK_2, nullptr},
		{ "terrain_opacity_mask_3", Landscape::FLAGS_FILE_DATA_OPACITY_MASK_3, nullptr},
		{ "terrain_opacity_mask_4", Landscape::FLAGS_FILE_DATA_OPACITY_MASK_4, nullptr},
	};

	auto check_need_texture = [mat](const char *name) {
		int index = mat->findTexture(name);
		return index != -1 ? mat->checkTextureConditions(index) : false;
	};

	int ret = 0;
	for (const auto &bt : textures)
	{
		if (check_need_texture(bt.name))
			ret |= bt.flag;
	}

	return ret;
}

int convert_masks_flags(int masks)
{
	int ret = 0;
	if (masks & Landscape::FLAGS_DATA_HEIGHT)
		ret |= Landscape::FLAGS_FILE_DATA_HEIGHT;

	if (masks & Landscape::FLAGS_DATA_ALBEDO)
		ret |= Landscape::FLAGS_FILE_DATA_ALBEDO;

	if ((masks & Landscape::FLAGS_DATA_MASK_0) ||
		(masks & Landscape::FLAGS_DATA_MASK_1) ||
		(masks & Landscape::FLAGS_DATA_MASK_2) ||
		(masks & Landscape::FLAGS_DATA_MASK_3))
	{
		ret |= Landscape::FLAGS_FILE_DATA_MASK_0;
	}

	if ((masks & Landscape::FLAGS_DATA_MASK_4) ||
		(masks & Landscape::FLAGS_DATA_MASK_5) ||
		(masks & Landscape::FLAGS_DATA_MASK_6) ||
		(masks & Landscape::FLAGS_DATA_MASK_7))
	{
		ret |= Landscape::FLAGS_FILE_DATA_MASK_1;
	}

	if ((masks & Landscape::FLAGS_DATA_MASK_8) ||
		(masks & Landscape::FLAGS_DATA_MASK_9) ||
		(masks & Landscape::FLAGS_DATA_MASK_10) ||
		(masks & Landscape::FLAGS_DATA_MASK_11))
	{
		ret |= Landscape::FLAGS_FILE_DATA_MASK_2;
	}

	if ((masks & Landscape::FLAGS_DATA_MASK_12) ||
		(masks & Landscape::FLAGS_DATA_MASK_13) ||
		(masks & Landscape::FLAGS_DATA_MASK_14) ||
		(masks & Landscape::FLAGS_DATA_MASK_15))
	{
		ret |= Landscape::FLAGS_FILE_DATA_MASK_3;
	}

	if ((masks & Landscape::FLAGS_DATA_MASK_16) ||
		(masks & Landscape::FLAGS_DATA_MASK_17) ||
		(masks & Landscape::FLAGS_DATA_MASK_18) ||
		(masks & Landscape::FLAGS_DATA_MASK_19))
	{
		ret |= Landscape::FLAGS_FILE_DATA_MASK_4;
	}

	return ret;
}

int convert_masks_opacity_flags(int masks)
{
	int ret = 0;
	if (masks & Landscape::FLAGS_DATA_HEIGHT)
		ret |= Landscape::FLAGS_FILE_DATA_OPACITY_HEIGHT;

	if ((masks & Landscape::FLAGS_DATA_MASK_0) ||
		(masks & Landscape::FLAGS_DATA_MASK_1) ||
		(masks & Landscape::FLAGS_DATA_MASK_2) ||
		(masks & Landscape::FLAGS_DATA_MASK_3))
	{
		ret |= Landscape::FLAGS_FILE_DATA_OPACITY_MASK_0;
	}

	if ((masks & Landscape::FLAGS_DATA_MASK_4) ||
		(masks & Landscape::FLAGS_DATA_MASK_5) ||
		(masks & Landscape::FLAGS_DATA_MASK_6) ||
		(masks & Landscape::FLAGS_DATA_MASK_7))
	{
		ret |= Landscape::FLAGS_FILE_DATA_OPACITY_MASK_1;
	}

	if ((masks & Landscape::FLAGS_DATA_MASK_8) ||
		(masks & Landscape::FLAGS_DATA_MASK_9) ||
		(masks & Landscape::FLAGS_DATA_MASK_10) ||
		(masks & Landscape::FLAGS_DATA_MASK_11))
	{
		ret |= Landscape::FLAGS_FILE_DATA_OPACITY_MASK_2;
	}

	if ((masks & Landscape::FLAGS_DATA_MASK_12) ||
		(masks & Landscape::FLAGS_DATA_MASK_13) ||
		(masks & Landscape::FLAGS_DATA_MASK_14) ||
		(masks & Landscape::FLAGS_DATA_MASK_15))
	{
		ret |= Landscape::FLAGS_FILE_DATA_OPACITY_MASK_3;
	}

	if ((masks & Landscape::FLAGS_DATA_MASK_16) ||
		(masks & Landscape::FLAGS_DATA_MASK_17) ||
		(masks & Landscape::FLAGS_DATA_MASK_18) ||
		(masks & Landscape::FLAGS_DATA_MASK_19))
	{
		ret |= Landscape::FLAGS_FILE_DATA_OPACITY_MASK_4;
	}

	return ret;
}

void clear_masks_flags(const LandscapeMapFileSettingsPtr &settings, int mask,
		int &data_mask, int &data_opacity_mask)
{
	data_mask = mask;
	if (!settings->isEnabledAlbedo())
		data_mask &= ~Landscape::FLAGS_DATA_ALBEDO;
	if (!settings->isEnabledHeight())
		data_mask &= ~Landscape::FLAGS_DATA_HEIGHT;

	for (int i = 0; i < terrain::NUM_MASKS; ++i)
	{
		if (!settings->isEnabledMask(i))
			data_mask &= ~(Landscape::FLAGS_DATA_MASK_0 << i);
	}

	data_opacity_mask = data_mask;

	if (!settings->isEnabledOpacityHeight())
		data_opacity_mask &= ~Landscape::FLAGS_DATA_HEIGHT;

	for (int i = 0; i < terrain::NUM_MASKS; ++i)
	{
		if (!settings->isEnabledOpacityMask(i))
			data_opacity_mask &= ~(Landscape::FLAGS_DATA_MASK_0 << i);
	}
}

}

namespace lbrush
{

class BrushTool
{

public:
	BrushTool(const Unigine::LandscapeLayerMapPtr &map);
	~BrushTool() override;

	void setBrushMaterial(const Unigine::MaterialPtr &material);
	void setSelectedMasks(int masks);

protected:
	void update();

private:
	using vec2 = Unigine::Math::vec2;
	using Vec2 = Unigine::Math::Vec2;
	using Vec3 = Unigine::Math::Vec3;
	using ivec2 = Unigine::Math::ivec2;

	void draw(const Vec3 &point);

	void handle_texture_draw(Unigine::UGUID guid, int id,
			Unigine::LandscapeTexturesPtr buffer, ivec2 coord,
			int data_mask);

	void update_masks();

	bool update_spacing(const ivec2 &coord,
			const Vec2 &size);

	Vec2 get_decal_size(vec2 &border) const;

	vec2 get_angle_border(const Vec2 &size) const;

	bool get_direction(Vec3 &p0, Vec3 &p1);

	bool get_intersection(Vec3 &world_point);

	void get_intersection_async();
	bool update_intersection(Vec3 &pos);
	Unigine::LandscapeFetchPtr get_fetch();
	bool get_intersection_plane(const Vec3 &p0, const Vec3 &p1, Vec3 &pos);

private:
	//spacing
	ivec2 spacing_coord;
	bool spacing_inited{false};

	Unigine::LandscapeLayerMapPtr map_;

	void *texture_draw_handle_{nullptr};
	bool drawing_{false};

	int prev_has_intersection{0};

	int selected_masks{0};

	Unigine::Vector<Unigine::LandscapeFetchPtr> fetch_queue;
	Unigine::Vector<Unigine::LandscapeFetchPtr> fetch_pool;

	Unigine::Vector<Unigine::Pair<Vec3, Vec3>> rays_pool;

	Unigine::MaterialPtr brush_material_;

};

BrushTool::BrushTool(const LandscapeLayerMapPtr &map)
	: map_(map)
{
	texture_draw_handle_ = Landscape::addTextureDrawCallback(
			MakeCallback(this, &BrushTool::handle_texture_draw));
}

BrushTool::~BrushTool()
{
	Landscape::removeTextureDrawCallback(texture_draw_handle_);
}

void BrushTool::setBrushMaterial(const MaterialPtr &material)
{
	brush_material_ = material;
	update_masks();
}

void BrushTool::setSelectedMasks(int masks)
{
	selected_masks = masks;
	update_masks();
}

void BrushTool::update()
{
	Vec3 world_point;

	bool map_enabled = map_ && map_->isEnabled();
	bool has_intersection = map_enabled && get_intersection(world_point);

	prev_has_intersection = has_intersection || fetch_queue.size();

	if (!brush_material_)
		return;

	if (drawing_ && has_intersection)
		draw(world_point);
}

void BrushTool::draw(const Vec3 &point)
{
	if (!brush_material_)
		return;

	ivec2 coord = to_coord(map_, point);

	Vec2 texel_size = map_->getTexelSize();
	float brush_size = get_parameter(brush_material_, "size", 1.0f);

	if (brush_size < UNIGINE_EPSILON)
		return;

	Vec2 size = Vec2(brush_size) / texel_size;

	coord.x -= size.x * 0.5;
	coord.y -= size.y * 0.5;

	if (!update_spacing(coord, size))
		return;

	update_direction(coord);

	set_parameter(brush_material_, "height_scale", map_->getHeightScale());

	vec2 border;
	border.x = get_parameter_int(brush_material_, "border", 0);
	border.y = border.x;

	border += get_angle_border(size);

	coord -= ivec2(Unigine::Math::round(border.x),
			Unigine::Math::round(border.y));
	size += Vec2(border * 2.0f);

	ivec2 resolution(Unigine::Math::ceil(size.x), Unigine::Math::ceil(size.y));

	int mask = get_parameter_int(brush_material_, "data_mask");
	int opacity_mask = get_parameter_int(brush_material_, "data_mask_opacity");

	int data_mask =
			convert_masks_flags(mask) |
			convert_masks_opacity_flags(opacity_mask) |
			get_material_required_data(brush_material_);

	if (data_mask != 0)
	{
		Landscape::asyncTextureDraw(map_->getGUID(), coord,
				resolution, data_mask);
	}
}

void BrushTool::handle_texture_draw(UGUID guid, int /*id*/,
		LandscapeTexturesPtr buffer, ivec2 coord, int /*data_mask*/)
{
	if (guid != map_->getGUID())
		return;

	if (!brush_material_)
		return;

	BrushTextures textures[] =
	{
		{ "terrain_albedo", Landscape::FLAGS_FILE_DATA_ALBEDO, buffer->getAlbedo() },
		{ "terrain_height", Landscape::FLAGS_FILE_DATA_HEIGHT, buffer->getHeight() },
		{ "terrain_mask_0", Landscape::FLAGS_FILE_DATA_MASK_0, buffer->getMask(0) },
		{ "terrain_mask_1", Landscape::FLAGS_FILE_DATA_MASK_1, buffer->getMask(1) },
		{ "terrain_mask_2", Landscape::FLAGS_FILE_DATA_MASK_2, buffer->getMask(2) },
		{ "terrain_mask_3", Landscape::FLAGS_FILE_DATA_MASK_3, buffer->getMask(3) },
		{ "terrain_mask_4", Landscape::FLAGS_FILE_DATA_MASK_4, buffer->getMask(4) },

		{ "terrain_opacity_height", Landscape::FLAGS_FILE_DATA_OPACITY_HEIGHT, buffer->getOpacityHeight() },
		{ "terrain_opacity_mask_0", Landscape::FLAGS_FILE_DATA_OPACITY_MASK_0, buffer->getOpacityMask(0) },
		{ "terrain_opacity_mask_1", Landscape::FLAGS_FILE_DATA_OPACITY_MASK_1, buffer->getOpacityMask(1) },
		{ "terrain_opacity_mask_2", Landscape::FLAGS_FILE_DATA_OPACITY_MASK_2, buffer->getOpacityMask(2) },
		{ "terrain_opacity_mask_3", Landscape::FLAGS_FILE_DATA_OPACITY_MASK_3, buffer->getOpacityMask(3) },
		{ "terrain_opacity_mask_4", Landscape::FLAGS_FILE_DATA_OPACITY_MASK_4, buffer->getOpacityMask(4) },
	};

	int indices[ARRAY_LENGTH(textures)];
	for (int i = 0; i < ARRAY_LENGTH(textures); ++i)
	{
		indices[i] = brush_material_->findTexture(textures[i].name);
		if (indices[i] >= 0 && brush_material_->checkTextureConditions(indices[i]))
			brush_material_->setTexture(indices[i], textures[i].texture);
	}

	const ivec2 &brush_resolution = buffer->getResolution();
	const ivec2 &map_resolution = map_->getResolution();

	ivec2 min_coord = max(ivec2::ZERO, -coord);
	ivec2 max_coord = min(brush_resolution - ivec2::ONE, map_resolution - ivec2::ONE - coord);
	ivec4 enabled_rect(min_coord, max_coord);
	brush_material_->setParameterInt4("enabled_rect", enabled_rect);

	brush_material_->runExpression("brush", brush_resolution.x, brush_resolution.y);

	for (int i = 0; i < ARRAY_LENGTH(textures); ++i)
	{
		if (indices[i] >= 0 && brush_material_->checkTextureConditions(indices[i]))
			brush_material_->setTexture(indices[i], nullptr);
	}
}

void BrushTool::update_masks()
{
	if (!brush_material_)
		return;

	int data_mask;
	int data_opacity_mask;
	clear_masks_flags(map_settings_, selected_masks,
			data_mask, data_opacity_mask);

	set_parameter(brush_material_, "data_mask", data_mask);
	set_parameter(brush_material_, "data_mask_opacity", data_opacity_mask);
}

bool BrushTool::update_spacing(const ivec2 &coord, const Vec2 &size)
{
	if (spacing_inited && compare(depr::g.controls->getMouseDX(), 0.0f)
			&& compare(depr::g.controls->getMouseDY(), 0.0f))
		return false;

	float spacing = get_parameter(brush_material_, "spacing", 1.0f);
	spacing *= DEFAULT_SPACING * size.x;
	spacing += UNIGINE_EPSILON;
	float distance2 = length2(spacing_coord - coord);
	if (spacing_inited && distance2 < (spacing * spacing))
		return false;

	spacing_coord = coord;
	spacing_inited = true;
	return true;
}

vec2 BrushTool::get_angle_border(const Vec2 &size) const
{
	float angle = get_parameter(brush_material_, "angle");

	float s;
	float c;
	Unigine::Math::sincos(angle * UNIGINE_DEG2RAD, s, c);

	float scale = Unigine::Math::max(Unigine::Math::abs(c - s),
			Unigine::Math::abs(s + c));

	vec2 border;
	border.x += (size.x * scale - size.x) * 0.5f;
	border.y += (size.y * scale - size.y) * 0.5f;
	return border;
}

bool BrushTool::get_direction(Vec3 &p0, Vec3 &p1)
{
	auto viewport = depr::g.viewports->getLastHovered();
	if (!viewport)
		return false;

	PlayerPtr player = viewport->getInternalPlayer();
	if (!player)
		return false;

	player->getDirectionFromScreen(p0, p1,
			viewport->getMouseX(), viewport->getMouseY(),
			viewport->width(), viewport->height());

	return true;
}

bool BrushTool::get_intersection(Vec3 &world_point)
{
	get_intersection_async();
	return update_intersection(world_point);
}

void BrushTool::get_intersection_async()
{
	if (!drawing_ && fetch_queue.size())
		return;

	Vec3 p0;
	Vec3 p1;
	if (!get_direction(p0, p1))
		return;

	rays_pool.emplace_back(p0, p1);

	const auto &fetch = get_fetch();
	fetch->intersectionAsync(p0, p1, true);
	fetch_queue.append(fetch);
}

bool BrushTool::update_intersection(Vec3 &pos)
{
	if (fetch_queue.empty())
		return false;

	if (!fetch_queue.first()->isAsyncCompleted())
		return false;

	auto fetch = fetch_queue.takeFirst();
	pos = fetch->getPosition();

	fetch_pool.append(fetch);

	const auto &ray = rays_pool.takeFirst();

	bool ret = fetch->isIntersection();
	ret &= !Unigine::Math::compare(fetch->getHeight(), -UNIGINE_INFINITY);
	ret &= !Unigine::Math::compare(fetch->getHeight(), 0.0f);

	if (!ret)
	{
		ret = get_intersection_plane(ray.first, ray.second, pos);
	}

	return ret;
}

LandscapeFetchPtr BrushTool::get_fetch()
{
	if (fetch_pool.empty())
	{
		auto fetch = LandscapeFetch::create();
		return fetch;
	}
	return fetch_pool.takeLast();
}

bool BrushTool::get_intersection_plane(const Vec3 &p0, const Vec3 &p1, Vec3 &pos)
{
	auto plane = ::Math::buildPlane(Vec3_up, Vec3_zero);
	auto transform = map_->getWorldTransform();
	auto translate = transform.getColumn3(3);
	translate.z = 0.0;
	transform.setColumn3(3, translate);
	plane = ::Math::transformPlane(transform, plane);

	bool ret = Geometry::linePlaneIntersection(p0, p1, plane, pos);
	if (ret)
		ret = map_->getBoundBox().inside(map_->toLocal(pos));

	return ret;
}

}

 

I'm afraid you will have to write a complex logic, since the object itself is quite complex and it's almost impossible to write a really simple API for it (we tried couple of times and the best option is already available).

Now, regarding your code - we see a suspicios code here:

  1. In Unigine::Landscape::asyncTextureDraw(id, layerMapGuid, pos, size, ~0);  // it's better to specify the exact data you want (not ~0), not the all textures at once to reduce the amount of data that needs to be transferred

Our brush implementation has a specific spacing parameter that allow us to not send the data each frame, but only when the brush is moved to a specific distance.

Hope that somehow helps :)

How to submit a good bug report
---
FTP server for test scenes and user uploads:

Link to comment

Hello and thank you for the updated code. Just wanted to let you know the outcome of my issue:

- setting the asyncTextureDraw file flags did helped a little but...

- the most important performance factor was an error in our propagation that accumulated too many state changes (that resulted in many redundant paint operations). 

So in the end, the brushstrokes did applied, was just that it took too much time to be seen visually as we had many overlapping paint operations. So thank you again for your support, it did help our code go faster and also to find where in our code to apply optimizations.

Regards,
Adrian L.

  • Like 1
Link to comment
  • silent changed the title to [SOLVED] Updating landscapes from code
  • 4 months later...

Hello,

I am returning with the same issue for the Landscape::update_operation_pool updates. Currently you do one single update (and possible one state update) per frame which seems a very hardcoded way to update the landscape. Unfortunately for our wildfire simulation we can't really batch the paints that much (since most of the time fire is spreading in a circle bigger and bigger and basically we need to paint the landscape along the perimeter of the circle, not really much of neighbor pixels being painted at the same time) so I do want to request to expose a parameter to let me setup how many updates to do per frame or, to make it more general, give me a certain number of milliseconds the update can loop before bailing out, something like the following pseudocode:

    interval dt = 1ms;
    time total_time = 0;
    do {

        if (operation_pool.first()->update())
        {
            engine.async_tasks->asyncDestroy(operation_pool.first());
            operation_pool.removeFirst();
        }

        total_time += time_spent_in_loop;
    } while (!operation_pool.empty() && total_time < dt);

I know you said "Your suggested approach is not really fixing anything, it just slow down the rendering. The same you can emulate with a simple sleep(1sec) in your rendering of 3x3 pixel code." but the above code actually works in my case and does not slow things considerably. It is just forcing small texture copies and landscape paints and somehow the GPU can deal with this just fine.

Kind Regards,
Adrian L.

Link to comment
5 hours ago, adrian.licuriceanu said:

I do want to request to expose a parameter to let me setup how many updates to do per frame or, to make it more general, give me a certain number of milliseconds the update can loop before bailing out

Hello, Adrian!

I will add your suggestion as a feature request to might be added in following SDK releases. I can't promise that such an functionality will certainly added, but the request is guaranteed to be considered by the lead developer.

Thanks!

  • Like 1
Link to comment
  • 2 months later...

Hello!

Adrian, we have discussed an issue and from now I want to clarify one thing.

In actual 2.14 release we have added the following methods in terms to solve exactly your problem:

https://developer.unigine.com/en/docs/2.14.1/api/library/rendering/class.render?rlang=cpp#setLandscapeOperationsPerFrame_int_void

https://developer.unigine.com/en/docs/2.14.1/api/library/rendering/class.render?rlang=cpp#getLandscapeOperationsPerFrame_int

console command has also added:

изображение.png

Since I did not find any mention of this here, I would like to find out - have you tried using them already? if not, might this become a solution for you?

Thanks!

Link to comment

Hi,

That's good news, probably is what we need. We are currently using Unigine 2.13.0.1, but once we upgrade to 2.14 for sure we will try that (probably in around 1 month time) and let you know if we have any issue (probably we should be fine as from the description this is exactly what we need). 

Many thanks,
Adrian

  • Like 1
  • Thanks 1
Link to comment
×
×
  • Create New...