Импорт моделей непосредственно в память
Чтобы использовать этот функционал вам необходимо получить лицензию на
Система импорта UNIGINE позволяет импортировать модели и сцены в различных внешних форматах (FBX, DAE, OBJ и т.д.). По умолчанию (т. е. при использовании DefaultProcessor) для каждой импортируемой сцены или модели на диске создается набор соответствующих файлов в форматах файлов UNIGINE (.mesh, .dds, .node, и т. д.).
Однако в некоторых случаях может потребоваться импортировать модели непосредственно в текущую сцену "на лету", не создавая никаких ненужных файлов на диске (например, загрузка моделей зданий для приложения "умный город" и т. д.). Более того, такой подход может ускорить весь процесс импорта за счет сокращения количества медленных операций ввода-вывода с диска.
Для этой цели необходимо создать пользовательский процессор импорта.
В этом примере показано, как:
- создать свой собственный пользовательский процессор импорта для плагина FbxImporter;
- использовать свой пользовательский процессор импорта, чтобы перенести сцену, сохраненную в файле FBX, в загруженную в данный момент сцену UNIGINE.
Прежде чем мы начнем, немного теории (ключевые моменты):
- UNIGINE API предлагает набор классов для реализации импорта настраиваемой модели.
- Вся метаинформация об импортированной вами сцене должна храниться в экземпляре класса ImportScene. В принципе, сцена может содержать меши (ImportMesh), источники света (ImportLight), камеры (ImportCamera) и другие элементы (смотрите полный список здесь).
- Пользовательский процессор импорта должен быть унаследован от класса ImportProcessor.
Creating a Custom Import ProcessorСоздание пользовательского процессора импорта#
Итак, давайте начнем с процессора. Назовем его MemoryProcessor и реализуем создание объектов в текущем мире UNIGINE из содержимого FBX-файла, пропуская ненужное сохранение данных на диск. Итак, мы просто пройдемся по компонентам импортированной сцены FBX (камеры, источники света и меши), построим соответствующую иерархию нод для добавления в мир и создадим все необходимые материалы и текстуры. Приведенный ниже код дополнен комментариями, объясняющими основные аспекты процесса.
Direct Memory Import Processor
// including necessary libraries
#include <UnigineImport.h>
#include <UnigineMaterials.h>
#include <UnigineMeshStatic.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();
mesh_static->setMeshProceduralMode(true);
mesh_static->applyMeshProcedural(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Использование процессора импорта#
У нас есть наш пользовательский процессор импорта, давайте напишем функцию, использующую его для импорта содержимого из указанного FBX в загруженный в данный момент UNIGINE world.
Функция импорта
/// 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;
}
Теперь мы можем просто загрузить плагин FbxImporter и использовать нашу функцию import() для добавления желаемой модели.
// 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Пример кода#
Чтобы протестировать наш пользовательский процессор импорта, можно создать новый проект C++ в SDK Browser и добавить весь приведенный ниже код в файлы AppWorldLogic.h и AppWorldLogic.cpp.
AppWorldLogic.h
#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
#include "AppWorldLogic.h"
#include <UnigineMathLib.h>
// including necessary libraries
#include <UnigineImport.h>
#include <UnigineMaterials.h>
#include <UnigineMeshStatic.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();
mesh_static->setMeshProceduralMode(true);
mesh_static->applyMeshProcedural(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)
{
const Vector<String> &supported_extensions = Import::getSupportedExtensions();
const String extensions_filter = "." + String::join(supported_extensions, ".");
auto dialog_file = WidgetDialogFile::create(Gui::getCurrent(), "DialogFile");
dialog_file->setPath(default_dialog_path);
dialog_file->setFilter(extensions_filter);
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;
}