This page has been translated automatically.
Video Tutorials
Interface
Essentials
Advanced
How To
Professional (SIM)
UnigineEditor
Interface Overview
Assets Workflow
Settings and Preferences
Working With Projects
Adjusting Node Parameters
Setting Up Materials
Setting Up Properties
Lighting
Sandworm
Using Editor Tools for Specific Tasks
Extending Editor Functionality
Built-in Node Types
Nodes
Objects
Effects
Decals
Light Sources
Geodetics
World Nodes
Sound Objects
Pathfinding Objects
Players
Programming
Fundamentals
Setting Up Development Environment
C++
C#
UnigineScript
UUSL (Unified UNIGINE Shader Language)
Plugins
File Formats
Materials and Shaders
Rebuilding the Engine Tools
GUI
Double Precision Coordinates
API
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
Content Creation
Content Optimization
Materials
Material Nodes Library
Miscellaneous
Input
Math
Matrix
Textures
Art Samples
Tutorials

Importing Models Directly to Memory

Warning
The functionality described in this article is not available in the Community SDK edition.
You should upgrade to Engineering / Sim SDK edition to use it.

UNIGINE Import System allows you to import models and scenes in various external formats (FBX, DAE, OBJ, etc.). By default (i.e., when DefaultProcessor is used) for each imported scene or model a set of corresponding files in UNIGINE file formats (.mesh, .dds, .node, etc.) is created on disk.

However, in some cases it might be necessary to import models directly into your current scene on-the-fly, without creating any unnecessary files on disk (e.g. loading models of buildings for a smart city application, etc.). Moreover, this approach can speed up the whole importing process due to a reduced number of slow disk I/O operations.

For this purpose we should create a custom import processor.

This example demonstrates how to:

  • Create your own custom import processor for the FbxImporter plugin.
  • Use your custom import processor to bring a scene stored in an FBX file to the currently loaded UNIGINE scene.
Notice
It is recommended that you've read the Import System article and familiarized with basic import functionality classes.

Before we get started, a little bit of theory (as key points).

  • UNIGINE API offers us a set of classes for implementing customized model import.
  • All meta information about your imported scene should be stored in an instance of the ImportScene class. Basically a scene may contain meshes (ImportMesh), lights (ImportLight), cameras (ImportCamera), and other elements (see the complete list here).
  • A custom import processor should be inherited from the ImportProcessor class.

Creating a Custom Import Processor#

So, let's start with the processor. We call it MemoryProcessor and implement creation of objects in the current UNIGINE world from the contents of the FBX-file skipping unnecessary saving of data to disk. So we shall simply run through the components of the imported FBX scene (cameras, lights and meshes), build the corresponding node hierarchy to be added to the world and create all necessary materials and textures. The code below is supplemented with comments explaining the basic aspects of the process.

Direct Memory Import Processor

Source code (C++)
// including necessary libraries
#include <UnigineImport.h>
#include <UnigineMaterials.h>

using namespace Unigine;

// our custom ImportProcessor to be used for direct memory import of scenes
class MemoryProcessor : public ImportProcessor
{
public:

	void convertNode(const NodePtr &node_parent, const ImportNodePtr &import_node_parent,
		NodePtr &node, const ImportNodePtr &import_node)
	{
		using namespace Unigine::Math;
		if (ImportCameraPtr import_camera = import_node->getCamera())
		{
			node = getImporter()->importCamera(getImportProcessor(), import_camera);
			if (node)
				node->setWorldTransform(Mat4(import_node->getTransform()));
		} else if (ImportLightPtr import_light = import_node->getLight())
		{
			node = getImporter()->importLight(getImportProcessor(), import_light);
			if (node)
				node->setWorldTransform(Mat4(import_node->getTransform()));
		} else if (ImportMeshPtr import_mesh = import_node->getMesh())
		{
			ObjectPtr object;
			if (import_mesh->isHasAnimations())
			{
				float fps = getImporter()->getParameterFloat("fps");
				auto mesh_skinned = ObjectMeshSkinned::create(meshes[import_mesh->getFilepath()]);
				if (MeshPtr animation = meshes_animations.value(import_mesh->getFilepath()))
				{
					int animation_id = mesh_skinned->addAnimation(animation);
					mesh_skinned->setNumLayers(1);
					mesh_skinned->setAnimation(0, animation_id);
				}
				mesh_skinned->setSpeed(fps);
				object = mesh_skinned;
			} else
			{
				auto mesh_static = ObjectMeshStatic::create(meshes[import_mesh->getFilepath()]);
				object = mesh_static;
				object->setWorldTransform(Mat4(import_node->getTransform()));
			}

			int num_geometries = import_mesh->getNumGeometries();
			for (int i = 0; i < num_geometries; ++i)
			{
				ImportGeometryPtr geometry = import_mesh->getGeometry(i);
				int num_surfaces = geometry->getNumSurfaces();
				for (int s = 0; s < num_surfaces; ++s)
				{
					ImportSurfacePtr surface = geometry->getSurface(s);
					const int surface_index = surface->getTargetSurface();
					if (surface_index == -1 || surface_index >= object->getNumSurfaces())
					{
						Log::error("MemoryProcessor: can't find surface \"%s\".\n", surface->getName());
						continue;
					}

					if (!compare(surface->getMinVisibleDistance(), -Consts::INF))
					{
						object->setMinVisibleDistance(surface->getMinVisibleDistance(),
							surface_index);
					}
					if (!compare(surface->getMaxVisibleDistance(), Consts::INF))
					{
						object->setMaxVisibleDistance(surface->getMaxVisibleDistance(),
							surface_index);
					}

					object->setMinFadeDistance(surface->getMinFadeDistance(), surface_index);
					object->setMaxFadeDistance(surface->getMaxFadeDistance(), surface_index);

					if (ImportMaterialPtr import_material = surface->getMaterial())
					{
						object->setMaterial(materials[import_material->getFilepath()],
							surface_index);
					}
				}
			}

			node = object;
		} else
		{
			node = NodeDummy::create();
			node->setWorldTransform(Mat4(import_node->getTransform()));
		}

		node->setName(import_node->getName());

		getImporter()->importNodeChild(getImportProcessor(), node_parent, import_node_parent,
			node, import_node);

		int num_children = import_node->getNumChildren();
		for (int i = 0; i < num_children; ++i)
		{
			NodePtr child;
			convertNode(node, import_node, child, import_node->getChild(i));
			node->addWorldChild(child);
		}
	}

protected:
	// method to be called on mesh processing
	bool onProcessMesh(const MeshPtr &mesh, const ImportMeshPtr &import_mesh) override
	{
		UGUID guid = generate_unique_guid();
		import_mesh->setFilepath(guid.getString());
		meshes.append(import_mesh->getFilepath(), mesh);
		return true;
	}

	// method to be called on mesh animation processing
	bool onProcessAnimation(const MeshPtr &animation, const ImportMeshPtr &import_mesh,
		const ImportAnimationPtr &import_animation) override
	{
		meshes_animations.append(import_mesh->getFilepath(), animation);
		return true;
	}

	// method to be called on texture processing
	bool onProcessTexture(const ImportTexturePtr &import_texture) override
	{
		import_texture->setFilepath(import_texture->getOriginalFilepath());
		return true;
	}

	// method to be called on material processing
	bool onProcessMaterial(const MaterialPtr &material,
		const ImportMaterialPtr &import_material) override
	{
		UGUID guid = generate_unique_guid();
		import_material->setFilepath(guid.getString());
		materials.append(guid.getString(), material);
		return true;
	}

private:
	UGUID generate_unique_guid()
	{
		UGUID guid;
		guid.generate();

		int attempt = 100;
		while(guids.contains(guid) && attempt-- > 0)
			guid.generate();

		guids.append(guid);

		return guid;
	}

private:
	HashSet<UGUID> guids;
	// HashMaps to be used for imported meshes and materials
	HashMap<String, MeshPtr> meshes;
	HashMap<String, MeshPtr> meshes_animations;
	HashMap<String, MaterialPtr> materials;

};

Using Our Import Processor#

We have got our custom import processor, let's write a function using it to import the contents of the specified FBX to the currently loaded UNIGINE world.

Import Function

Source code (C++)
/// function importing the contents of the specified FBX to the currently loaded scene
NodePtr import(const char *filepath)
{
	// creating an importer for our fbx file
	ImporterPtr importer = Import::createImporterByFileName(filepath);
	if (!importer)
		return nullptr;

	if (!importer->init(filepath))
		return nullptr;

	// creating our custom MemoryProcessor and using it to import meshes, textures, materials, and nodes without saving data to files on disk
	MemoryProcessor memory_processor;
	ImportScenePtr scene = importer->getScene();
	int num_meshes = scene->getNumMeshes();
	for (int i = 0; i < num_meshes; ++i)
	{
		MeshPtr m = Mesh::create();
		importer->importMesh(memory_processor.getImportProcessor(), m, scene->getMesh(i));
		if (m->getNumBones())
		{
			int num_animations = scene->getNumAnimations();
			for (int i = 0; i < num_animations; ++i)
			{
				MeshPtr animation_mesh = Mesh::create();
				importer->importAnimation(memory_processor.getImportProcessor(), animation_mesh,
					scene->getMesh(i), scene->getAnimation(i));
			}
		}
	}

	int num_textures = scene->getNumTextures();
	for (int i = 0; i < num_textures; ++i)
	{
		importer->importTexture(memory_processor.getImportProcessor(), scene->getTexture(i));
	}
	
	MaterialPtr mesh_base = Materials::findManualMaterial("Unigine::mesh_base");
	int num_materials = scene->getNumMaterials();
	for (int i = 0; i < num_materials; ++i)
	{
		MaterialPtr m = mesh_base->inherit();
		importer->importMaterial(memory_processor.getImportProcessor(), m, scene->getMaterial(i));
	}

	int num_nodes = scene->getNumNodes();
	ImportNodePtr root_node;
	for (int i = 0; i < num_nodes; ++i)
	{
		const ImportNodePtr &node = scene->getNode(i);
		if (node->getParent() == nullptr)
		{
			root_node = node;
			break;
		}
	}

	if (root_node)
	{
		NodePtr node;
		memory_processor.convertNode(nullptr, nullptr, node, root_node);
		return node;
	}

	return nullptr;
}

Now we can just load the FbxImporter plugin and use our import() function to add the desired model.

Source code (C++)
// including necessary libraries
#include <UnigineConsole.h>

// ...

// loading the FbxImporter plugin
Console::run("plugin_load FbxImporter");
Console::flush();
	
// importing an FBX-model directly to the scene
NodePtr imported_node = import("material_ball.fbx");

Example Code#

To test our custom import processor, we can create a new C++ project in SDK Browser and put all the code listed below in the AppWorldLogic.h and AppWorldLogic.cpp files.

AppWorldLogic.h

Source code (C++)
#ifndef __APP_WORLD_LOGIC_H__
#define __APP_WORLD_LOGIC_H__

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

class AppWorldLogic : public Unigine::WorldLogic
{

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;

private:
	void run_animation(const Unigine::NodePtr &node);
	void setup_gui();
	void setup_player(const Unigine::NodePtr &node);
	void import_file(const Unigine::String &filepath);
	void dialog_file_ok_clicked(Unigine::WidgetPtr, Unigine::WidgetPtr, int, Unigine::WidgetDialogFilePtr dialog);
	void dialog_file_cancel_clicked(Unigine::WidgetPtr, Unigine::WidgetPtr, int, Unigine::WidgetDialogFilePtr dialog);
	void request_import(Unigine::WidgetPtr sender);

	Unigine::NodePtr node_;
	Unigine::String default_dialog_path{"./"};
};

#endif // __APP_WORLD_LOGIC_H__

AppWorldLogic.cpp

Source code (C++)
#include "AppWorldLogic.h"
#include <UnigineMathLib.h>
// including necessary libraries
#include <UnigineImport.h>
#include <UnigineMaterials.h>
#include <UnigineGame.h>
#include <UnigineWindowManager.h>

using namespace Unigine;
// our custom ImportProcessor to be used for direct memory import of scenes
class MemoryProcessor : public ImportProcessor
{
public:

	void convertNode(const NodePtr &node_parent, const ImportNodePtr &import_node_parent,
		NodePtr &node, const ImportNodePtr &import_node)
	{
		using namespace Unigine::Math;
		if (ImportCameraPtr import_camera = import_node->getCamera())
		{
			node = getImporter()->importCamera(getImportProcessor(), import_camera);
			if (node)
				node->setWorldTransform(Mat4(import_node->getTransform()));
		} else if (ImportLightPtr import_light = import_node->getLight())
		{
			node = getImporter()->importLight(getImportProcessor(), import_light);
			if (node)
				node->setWorldTransform(Mat4(import_node->getTransform()));
		} else if (ImportMeshPtr import_mesh = import_node->getMesh())
		{
			ObjectPtr object;
			if (import_mesh->isHasAnimations())
			{
				float fps = getImporter()->getParameterFloat("fps");
				auto mesh_skinned = ObjectMeshSkinned::create(meshes[import_mesh->getFilepath()]);
				if (MeshPtr animation = meshes_animations.value(import_mesh->getFilepath()))
				{
					int animation_id = mesh_skinned->addAnimation(animation);
					mesh_skinned->setNumLayers(1);
					mesh_skinned->setAnimation(0, animation_id);
				}
				mesh_skinned->setSpeed(fps);
				object = mesh_skinned;
			} else
			{
				auto mesh_static = ObjectMeshStatic::create(meshes[import_mesh->getFilepath()]);
				object = mesh_static;
				object->setWorldTransform(Mat4(import_node->getTransform()));
			}

			int num_geometries = import_mesh->getNumGeometries();
			for (int i = 0; i < num_geometries; ++i)
			{
				ImportGeometryPtr geometry = import_mesh->getGeometry(i);
				int num_surfaces = geometry->getNumSurfaces();
				for (int s = 0; s < num_surfaces; ++s)
				{
					ImportSurfacePtr surface = geometry->getSurface(s);
					const int surface_index = surface->getTargetSurface();
					if (surface_index == -1 || surface_index >= object->getNumSurfaces())
					{
						Log::error("MemoryProcessor: can't find surface \"%s\".\n", surface->getName());
						continue;
					}

					if (!compare(surface->getMinVisibleDistance(), -Consts::INF))
					{
						object->setMinVisibleDistance(surface->getMinVisibleDistance(),
							surface_index);
					}
					if (!compare(surface->getMaxVisibleDistance(), Consts::INF))
					{
						object->setMaxVisibleDistance(surface->getMaxVisibleDistance(),
							surface_index);
					}

					object->setMinFadeDistance(surface->getMinFadeDistance(), surface_index);
					object->setMaxFadeDistance(surface->getMaxFadeDistance(), surface_index);

					if (ImportMaterialPtr import_material = surface->getMaterial())
					{
						object->setMaterial(materials[import_material->getFilepath()],
							surface_index);
					}
				}
			}

			node = object;
		} else
		{
			node = NodeDummy::create();
			node->setWorldTransform(Mat4(import_node->getTransform()));
		}

		node->setName(import_node->getName());

		getImporter()->importNodeChild(getImportProcessor(), node_parent, import_node_parent,
			node, import_node);

		int num_children = import_node->getNumChildren();
		for (int i = 0; i < num_children; ++i)
		{
			NodePtr child;
			convertNode(node, import_node, child, import_node->getChild(i));
			node->addWorldChild(child);
		}
	}

protected:
	// method to be called on mesh processing
	bool onProcessMesh(const MeshPtr &mesh, const ImportMeshPtr &import_mesh) override
	{
		UGUID guid = generate_unique_guid();
		import_mesh->setFilepath(guid.getString());
		meshes.append(import_mesh->getFilepath(), mesh);
		return true;
	}

	// method to be called on mesh animation processing
	bool onProcessAnimation(const MeshPtr &animation, const ImportMeshPtr &import_mesh,
		const ImportAnimationPtr &import_animation) override
	{
		meshes_animations.append(import_mesh->getFilepath(), animation);
		return true;
	}

	// method to be called on texture processing
	bool onProcessTexture(const ImportTexturePtr &import_texture) override
	{
		import_texture->setFilepath(import_texture->getOriginalFilepath());
		return true;
	}

	// method to be called on material processing
	bool onProcessMaterial(const MaterialPtr &material,
		const ImportMaterialPtr &import_material) override
	{
		UGUID guid = generate_unique_guid();
		import_material->setFilepath(guid.getString());
		materials.append(guid.getString(), material);
		return true;
	}

private:
	UGUID generate_unique_guid()
	{
		UGUID guid;
		guid.generate();

		int attempt = 100;
		while(guids.contains(guid) && attempt-- > 0)
			guid.generate();

		guids.append(guid);

		return guid;
	}

private:
	HashSet<UGUID< guids;
	// HashMaps to be used for imported meshes and materials
	HashMap<String, MeshPtr< meshes;
	HashMap<String, MeshPtr< meshes_animations;
	HashMap<String, MaterialPtr< materials;

};

/// function importing the contents of the specified FBX to the currently loaded scene
NodePtr import(const char *filepath)
{
	// creating an importer for our fbx file
	ImporterPtr importer = Import::createImporterByFileName(filepath);
	if (!importer)
		return nullptr;

	if (!importer->init(filepath))
		return nullptr;

	// creating our custom MemoryProcessor and using it to import meshes, textures, materials, and nodes without saving data to files on disk
	MemoryProcessor memory_processor;
	ImportScenePtr scene = importer->getScene();
	int num_meshes = scene->getNumMeshes();
	for (int i = 0; i < num_meshes; ++i)
	{
		MeshPtr m = Mesh::create();
		importer->importMesh(memory_processor.getImportProcessor(), m, scene->getMesh(i));
		if (m->getNumBones())
		{
			int num_animations = scene->getNumAnimations();
			for (int i = 0; i < num_animations; ++i)
			{
				MeshPtr animation_mesh = Mesh::create();
				importer->importAnimation(memory_processor.getImportProcessor(), animation_mesh,
					scene->getMesh(i), scene->getAnimation(i));
			}
		}
	}

	int num_textures = scene->getNumTextures();
	for (int i = 0; i < num_textures; ++i)
	{
		importer->importTexture(memory_processor.getImportProcessor(), scene->getTexture(i));
	}
	
	MaterialPtr mesh_base = Materials::findManualMaterial("Unigine::mesh_base");
	int num_materials = scene->getNumMaterials();
	for (int i = 0; i < num_materials; ++i)
	{
		MaterialPtr m = mesh_base->inherit();
		importer->importMaterial(memory_processor.getImportProcessor(), m, scene->getMaterial(i));
	}

	int num_nodes = scene->getNumNodes();
	ImportNodePtr root_node;
	for (int i = 0; i < num_nodes; ++i)
	{
		const ImportNodePtr &node = scene->getNode(i);
		if (node->getParent() == nullptr)
		{
			root_node = node;
			break;
		}
	}

	if (root_node)
	{
		NodePtr node;
		memory_processor.convertNode(nullptr, nullptr, node, root_node);
		return node;
	}

	return nullptr;
}

AppWorldLogic::AppWorldLogic(){}

AppWorldLogic::~AppWorldLogic(){}

int AppWorldLogic::init()
{
	setup_gui();
	return 1;
}

void AppWorldLogic::run_animation(const NodePtr &node)
{
	if (ObjectMeshSkinnedPtr skinned = checked_ptr_cast<ObjectMeshSkinned>(node))
	{
		skinned->setLoop(1);
		skinned->play();
	}
	
	int num_children = node->getNumChildren();
	for (int i = 0; i < num_children; ++i)
		run_animation(node->getChild(i));
}

void AppWorldLogic::setup_gui()
{
	auto gui = Gui::getCurrent();

	auto window = WidgetWindow::create(gui, "Import File", 4, 4);

	auto import_button = WidgetButton::create(gui, "Import");
	import_button->addCallback(Gui::CLICKED,
	MakeCallback(this, &AppWorldLogic::request_import));

	window->addChild(import_button, Gui::ALIGN_EXPAND);
	window->arrange();

	gui->addChild(window, Gui::ALIGN_OVERLAP | Gui::ALIGN_LEFT | Gui::ALIGN_TOP);
}

void AppWorldLogic::setup_player(const NodePtr &node)
{
	static const float CAMERA_PHI = 75.0f;
	static const float CAMERA_THETA = 140.0f;
	static const float CAMERA_DISTANCE = 2.0f;
	static const float FOV = 60.0f;

	using namespace Unigine::Math;

	ivec2 window_size = WindowManager::getMainWindow()->getClientSize();

	WorldBoundSphere bound_sphere = node->getHierarchyBoundSphere();

	Vec3 center = bound_sphere.center;
	Scalar radius = bound_sphere.radius * CAMERA_DISTANCE *
		max(1.0f, float(window_size.y) / window_size.y);
	quat rotation= quat(1.0f, 0.0f, 0.0f, -CAMERA_PHI) * quat(0.0f, 0.0f, 1.0f, CAMERA_THETA);

	Mat4 modelview{translate(Scalar(0.0f), Scalar(0.0f), -radius) *
		Mat4(rotation) * translate(-center)};

	mat4 projection = perspective(FOV, 1.0f, radius * 0.01f, radius * 2.0f);
	if (PlayerPtr player = Game::getPlayer())
	{
		player->setWorldTransform(inverse(modelview));
		player->setProjection(projection);
	} else
		Log::error("Main player not found.\n");
}

void AppWorldLogic::import_file(const String &filepath)
{
	if (node_)
	{
		node_.deleteLater();
		node_ = nullptr;
	}

	node_ = import(filepath);
	if (node_)
	{
		Log::message("Node loaded \"%s\" from filepath \"%s\".\n",
			node_->getName(), filepath.get());
		setup_player(node_);
		run_animation(node_);
	}
	else
		Log::error("Can't import file from filepath %s\n", filepath.get());
}

void AppWorldLogic::dialog_file_ok_clicked(WidgetPtr, WidgetPtr, int, WidgetDialogFilePtr dialog)
{
	const String filepath = dialog->getFile();
	default_dialog_path = filepath.pathname();

	import_file(filepath);

	dialog.deleteLater();
}

void AppWorldLogic::dialog_file_cancel_clicked(WidgetPtr, WidgetPtr, int, WidgetDialogFilePtr dialog)
{
	dialog.deleteLater();
}

void AppWorldLogic::request_import(WidgetPtr sender)
{
	auto dialog_file = WidgetDialogFile::create(Gui::getCurrent(), "DialogFile");
	dialog_file->setPath(default_dialog_path);

	dialog_file->getOkButton()->addCallback(Gui::CLICKED,
		MakeCallback(this, &AppWorldLogic::dialog_file_ok_clicked, dialog_file));
	dialog_file->getCancelButton()->addCallback(Gui::CLICKED,
		MakeCallback(this, &AppWorldLogic::dialog_file_cancel_clicked, dialog_file));

	Gui::getCurrent()->addChild(dialog_file, Gui::ALIGN_OVERLAP | Gui::ALIGN_CENTER);

	dialog_file->setPermanentFocus();
}

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

int AppWorldLogic::update()
{
	// Write here code to be called before updating each render frame: specify all graphics-related functions you want to be called every frame while your application executes.
	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()
{
	// Write here code to be called on world shutdown: delete resources that were created during world script execution to avoid memory leaks.
	return 1;
}

int AppWorldLogic::save(const Unigine::StreamPtr &stream)
{
	// Write here code to be called when the world is saving its state (i.e. state_save is called): 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 (i.e. state_restore is called): restore custom user data to a file here.
	UNIGINE_UNUSED(stream);
	return 1;
}
Last update: 2023-01-30
Build: ()