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.
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
// 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
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.
// 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
// 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;
}