UnigineEditor
Interface Overview
Assets Workflow
Settings and Preferences
Adjusting Node Parameters
Setting Up Materials
Setting Up Properties
Landscape Tool
Using Editor Tools for Specific Tasks
FAQ
Programming
Fundamentals
Setting Up Development Environment
UnigineScript
C++
C#
UUSL (Unified UNIGINE Shader Language)
File Formats
Rebuilding the Engine and 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
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
CIGI Client Plugin
Rendering-Related Classes

Importing Models Directly to Memory

UNIGINE's Import System allows you to import models and scenes in various external formats (FBX, DAE, OBJ, etc.). By default (i.e., when the DefaultProcessor is used) for each imported scene or model a set of corresponding files in UNIGINE's 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 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's 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 <UnigineHashMap.h>
#include <UnigineMaterials.h>

using namespace Unigine;

// our custom ImportProcessor to be used for direct memory import of FBX-scenes
class MemoryProcessor : public ImportProcessor
{
protected:
	// method to be called on mesh processing
	virtual bool onProcessMesh(MeshPtr &mesh, ImportMesh *import_mesh) override
	{
		UGUID guid;
		guid.generate();
		import_mesh->filepath = guid.getString();
		meshes.append(import_mesh->filepath, mesh);
		return true;
	}
	
	// method to be called on mesh animation processing
	bool onProcessAnimation(MeshPtr &animation, ImportMesh *import_mesh, ImportAnimation *import_animation)
	{
		return true;
	}
	
	// method to be called on texture processing
	bool onProcessTexture(ImportTexture *import_texture)
	{
		import_texture->filepath = import_texture->original_filepath;
		return true;
	}
	
	// method to be called on material processing
	bool onProcessMaterial(MaterialPtr &material, ImportMaterial *import_material)
	{
		UGUID guid;
		guid.generate();
		materials.append(import_material->name, material);
		return true;
	}
	
	// method performing nodeto be called on mesh processing
	void convert_node(NodePtr &node, ImportNode *import_node)
	{
		using namespace Unigine::Math;
		
		// checking node's type and performing the corresponding import operations
		if (import_node->camera != nullptr)
		{
			// importing a camera
			PlayerPtr player;
			getImporter()->importCamera(this, player, import_node->camera);
			node = player->getNode();
			node->setWorldTransform(Mat4(import_node->transform));
		}
		else if (import_node->light != nullptr)
		{
			// importing a camera
			LightPtr light;
			getImporter()->importLight(this, light, import_node->light);
			node = light->getNode();
			node->setWorldTransform(Mat4(import_node->transform));
		}
		else if (import_node->mesh != nullptr)
		{
			// importing a mesh
			ImportMesh *import_mesh = import_node->mesh;
			ObjectMeshStaticPtr object = ObjectMeshStatic::create(meshes[import_mesh->filepath]);
			object->setWorldTransform(Mat4(import_node->transform));

			for (const ImportGeometry &geometry : import_mesh->geometries)
			{
				for (const ImportSurface &surface : geometry.surfaces)
				{
					int surface_index = surface.target_surface;
					if (surface_index == -1)
					{
						Log::error("Can't find surface \"%s\".\n", surface.name.get());
						continue;
					}
					if (surface.material == nullptr)
						continue;
					if (object->getMaterialName(surface_index) != String("mesh_base"))
						continue;
					object->setMaterial(materials[surface.material->name], surface_index);
				}
			}

			object->release();
			node = object->getNode();
		}
		else
		{
			// or importing a dummy node
			NodeDummyPtr dummy = NodeDummy::create();
			dummy->release();
			node = dummy->getNode();
			node->setWorldTransform(Mat4(import_node->transform));
		}

		node->setName(import_node->name);
		
		// recursively processing all node's children and adding them to the hierarchy in the world
		for (ImportNode *import_child : import_node->children)
		{
			NodePtr child;
			convert_node(child, import_child);
			node->addWorldChild(child);
		}
	}
	
	// method to be called on node processing
	virtual bool onProcessNode(NodePtr &node, ImportNode *import_node)
	{
		convert_node(node, import_node);
		return true;
	}

private:
	// HashMaps to be used for imported meshes and materials
	HashMap<String, MeshPtr> meshes;		
	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++)
NodePtr import(const char *path)
{
	// creating an importer for our fbx file
	Importer *importer = Import::get()->createImporterByFileName(path);
	if (!importer)
		return NodePtr();

	if (!importer->init(path))
	{
		delete importer;
		return NodePtr();
	}
	// creating our custom MemoryProcessor and using it to import meshes, textures, materials, and nodes
	// without saving data to files on disk
	MemoryProcessor memory_processor;
	ImportScene *scene = importer->getScene();
	for (ImportMesh *mesh : scene->getMeshes())
	{
		MeshPtr m = Mesh::create();
		importer->importMesh(&memory_processor, m, mesh);
	}

	for (ImportTexture *texture : scene->getTextures())
	{
		importer->importTexture(&memory_processor, texture);
	}
	MaterialPtr mesh_base = Materials::get()->findBaseMaterial("mesh_base");
	for (ImportMaterial *material : scene->getMaterials())
	{
		MaterialPtr m = mesh_base->inherit();
		importer->importMaterial(&memory_processor, m, material);
	}

	for (ImportNode *node : scene->getNodes())
	{
		if (node->parent == nullptr)
		{
			NodePtr n;
			importer->importNode(&memory_processor, n, node);
			delete importer;
			return n;
		}
	}

	delete importer;
	return NodePtr();
}

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>
#include <UnigineEditor.h>

// ...

// loading the FbxImporter plugin
Console::get()->run("plugin_load FbxImporter");
Console::get()->flush();
	
// importing an FBX-model directly to the scene
Editor::get()->addNode(import("test.fbx"));

Example Code#

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

AppSystemLogic.cpp

Source code (C++)
// AppSystemLogic.cpp

#include "AppSystemLogic.h"

// including necessary libraries
#include <UnigineConsole.h>
#include <UnigineImport.h>
#include <UnigineHashMap.h>
#include <UnigineEditor.h>
#include <UnigineMaterials.h>

using namespace Unigine;

// our custom ImportProcessor to be used for direct memory import of FBX-scenes
class MemoryProcessor : public ImportProcessor
{
protected:
	// method to be called on mesh processing
	virtual bool onProcessMesh(MeshPtr &mesh, ImportMesh *import_mesh) override
	{
		UGUID guid;
		guid.generate();
		import_mesh->filepath = guid.getString();
		meshes.append(import_mesh->filepath, mesh);
		return true;
	}
	
	// method to be called on mesh animation processing
	bool onProcessAnimation(MeshPtr &animation, ImportMesh *import_mesh, ImportAnimation *import_animation)
	{
		return true;
	}
	
	// method to be called on texture processing
	bool onProcessTexture(ImportTexture *import_texture)
	{
		import_texture->filepath = import_texture->original_filepath;
		return true;
	}
	
	// method to be called on material processing
	bool onProcessMaterial(MaterialPtr &material, ImportMaterial *import_material)
	{
		UGUID guid;
		guid.generate();
		materials.append(import_material->name, material);
		return true;
	}
	
	// method performing nodeto be called on mesh processing
	void convert_node(NodePtr &node, ImportNode *import_node)
	{
		using namespace Unigine::Math;
		
		// checking node's type and performing the corresponding import operations
		if (import_node->camera != nullptr)
		{
			// importing a camera
			PlayerPtr player;
			getImporter()->importCamera(this, player, import_node->camera);
			node = player->getNode();
			node->setWorldTransform(Mat4(import_node->transform));
		}
		else if (import_node->light != nullptr)
		{
			// importing a camera
			LightPtr light;
			getImporter()->importLight(this, light, import_node->light);
			node = light->getNode();
			node->setWorldTransform(Mat4(import_node->transform));
		}
		else if (import_node->mesh != nullptr)
		{
			// importing a mesh
			ImportMesh *import_mesh = import_node->mesh;
			ObjectMeshStaticPtr object = ObjectMeshStatic::create(meshes[import_mesh->filepath]);
			object->setWorldTransform(Mat4(import_node->transform));

			for (const ImportGeometry &geometry : import_mesh->geometries)
			{
				for (const ImportSurface &surface : geometry.surfaces)
				{
					int surface_index = surface.target_surface;
					if (surface_index == -1)
					{
						Log::error("Can't find surface \"%s\".\n", surface.name.get());
						continue;
					}
					if (surface.material == nullptr)
						continue;
					if (object->getMaterialName(surface_index) != String("mesh_base"))
						continue;
					object->setMaterial(materials[surface.material->name], surface_index);
				}
			}

			object->release();
			node = object->getNode();
		}
		else
		{
			// or importing a dummy node
			NodeDummyPtr dummy = NodeDummy::create();
			dummy->release();
			node = dummy->getNode();
			node->setWorldTransform(Mat4(import_node->transform));
		}

		node->setName(import_node->name);
		
		// recursively processing all node's children and adding them to the hierarchy in the world
		for (ImportNode *import_child : import_node->children)
		{
			NodePtr child;
			convert_node(child, import_child);
			node->addWorldChild(child);
		}
	}
	
	// method to be called on node processing
	virtual bool onProcessNode(NodePtr &node, ImportNode *import_node)
	{
		convert_node(node, import_node);
		return true;
	}

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

};

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

	if (!importer->init(path))
	{
		delete importer;
		return NodePtr();
	}
	// creating our custom MemoryProcessor and using it to import meshes, textures, materials, and nodes without saving data to files on disk
	MemoryProcessor memory_processor;
	ImportScene *scene = importer->getScene();
	for (ImportMesh *mesh : scene->getMeshes())
	{
		MeshPtr m = Mesh::create();
		importer->importMesh(&memory_processor, m, mesh);
	}

	for (ImportTexture *texture : scene->getTextures())
	{
		importer->importTexture(&memory_processor, texture);
	}
	MaterialPtr mesh_base = Materials::get()->findBaseMaterial("mesh_base");
	for (ImportMaterial *material : scene->getMaterials())
	{
		MaterialPtr m = mesh_base->inherit();
		importer->importMaterial(&memory_processor, m, material);
	}

	for (ImportNode *node : scene->getNodes())
	{
		if (node->parent == nullptr)
		{
			NodePtr n;
			importer->importNode(&memory_processor, n, node);
			delete importer;
			return n;
		}
	}

	delete importer;
	return NodePtr();
}

AppSystemLogic::AppSystemLogic()
{
}

AppSystemLogic::~AppSystemLogic()
{
}

int AppSystemLogic::init() {
	// Write here code to be called on engine initialization.
	
	// loading the FbxImporter plugin
	Console::get()->run("plugin_load FbxImporter");
	Console::get()->flush();
	
	// importing an FBX-model directly to the scene
	Editor::get()->addNode(import("test.fbx"));
	return 1;
}

// start of the main loop
int AppSystemLogic::update() {
	// Write here code to be called before updating each render frame.
	
	return 1;
}

int AppSystemLogic::render() {
	// Write here code to be called before rendering each render frame.
	
	return 1;
}
// end of the main loop

int AppSystemLogic::shutdown() {
	// Write here code to be called on engine shutdown.
	
	return 1;
}

int AppSystemLogic::destroy() {
	// Write here code to be called when the video mode is changed or the application is restarted (i.e. video_restart is called). It is used to reinitialize the graphics context.
	
	return 1;
}
Last update: 2019-05-16