将模型直接导入内存
UNIGINE的 Import System允许您以各种外部格式(FBX, DAE, OBJ等)导入模型和场景。默认情况下(即当使用DefaultProcessor时),对于每个导入的场景或模型,都会在磁盘上创建一组UNIGINE文件格式的对应文件(.mesh, .texture, .node,等)。
然而,在某些情况下,可能需要直接将模型导入到当前场景中,而不需要在磁盘上创建任何不必要的文件(例如,为智能城市应用程序加载建筑物模型等)。此外,由于减少了缓慢的磁盘I/O操作,这种方法可以加快整个导入过程。
为此,我们应该创建一个自定义的导入处理器。
这个例子演示了如何:
- 为FbxImporter插件创建自己的自定义导入处理器。
- 使用您的自定义导入处理器将存储在FBX文件中的场景带到当前加载的UNIGINE场景。
在我们开始之前,先讲一点理论(作为重点):
- UNIGINE API为我们提供了一组的类,用于实现自定义模型导入。
- 关于导入场景的所有元信息都应该存储在ImportScene类的一个实例中。基本上,场景可能包含网格(ImportMesh)、灯光(ImportLight)、摄像机(ImportCamera)和其他元素(参见完整列表 here).
- 自定义导入处理器应该从ImportProcessor类继承。
Creating a Custom Import Processor创建自定义导入处理器#
让我们从处理器开始。我们称它为MemoryProcessor,并实现在当前UNIGINE世界中从fbx文件的内容创建对象,跳过不必要的数据保存到磁盘。因此,我们将简单地运行导入的FBX场景的组件(摄像机、灯光和网格),构建相应的节点层次结构,添加到世界中,并创建所有必要的材质和纹理。下面的代码补充了解释该过程基本方面的注释。
Direct Memory Import Processor
// including necessary libraries
#include <UnigineImport.h>
#include <UnigineMaterials.h>
#include <UnigineFileSystem.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();
mesh_skinned->setMeshProceduralMode(true);
mesh_skinned->applyMeshProcedural(meshes[import_mesh->getFilepath()]);
if (!String::isEmpty(import_mesh->getAnimationFilepath()))
mesh_skinned->setAnimPath(import_mesh->getAnimationFilepath());
mesh_skinned->setSpeed(fps);
object = mesh_skinned;
} else
{
ObjectMeshStaticPtr 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.makeString().get());
meshes.append(import_mesh->getFilepath(), mesh);
return true;
}
// method to be called on mesh animation processing
bool onProcessAnimation(const MeshAnimationPtr &animation, const ImportMeshPtr &import_mesh,
const ImportAnimationPtr &import_animation) override
{
StringStack<> animation_file_path = "import_animation_temp_blob_";
animation_file_path += generate_unique_guid().makeString();
animation_file_path += ".anim";
const bool added = FileSystem::addBlobFile(animation_file_path);
if (!added)
{
Log::error("Can't add a blob file: %s\n", animation_file_path.get());
return false;
}
import_animation->setFilepath(animation_file_path);
if (!animation->save(import_animation->getFilepath()))
return false;
import_mesh->setAnimationFilepath(import_animation->getFilepath());
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.makeString().get());
materials.append(guid.makeString().get(), 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, MaterialPtr> materials;
};
Using Our Import Processor使用导入处理器#
我们有定制导入处理器,让我们写一个函数使用导入的内容指定的FBX当前加载UNIGINE世界。
导入函数
/// 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 mesh_index = 0; mesh_index < num_meshes; ++mesh_index)
{
MeshPtr m = Mesh::create();
importer->importMesh(memory_processor.getImportProcessor(), m, scene->getMesh(mesh_index));
if (m->getNumBones())
{
int num_animations = scene->getNumAnimations();
for (int anim_index = 0; anim_index < num_animations; ++anim_index)
{
MeshAnimationPtr animation_mesh = MeshAnimation::create();
importer->importAnimation(memory_processor.getImportProcessor(), animation_mesh,
scene->getMesh(mesh_index), scene->getAnimation(anim_index));
}
}
}
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.setImporter(importer);
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示例代码#
为了测试我们的自定义导入处理器,我们可以在SDK Browser中创建一个新的c++项目,并将下面列出的所有代码放到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::WidgetDialogFilePtr dialog);
void dialog_file_cancel_clicked(Unigine::WidgetDialogFilePtr dialog);
void request_import(const 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 <UnigineFileSystem.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();
mesh_skinned->setMeshProceduralMode(true);
mesh_skinned->applyMeshProcedural(meshes[import_mesh->getFilepath()]);
if (!String::isEmpty(import_mesh->getAnimationFilepath()))
mesh_skinned->setAnimPath(import_mesh->getAnimationFilepath());
mesh_skinned->setSpeed(fps);
object = mesh_skinned;
} else
{
ObjectMeshStaticPtr 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.makeString().get());
meshes.append(import_mesh->getFilepath(), mesh);
return true;
}
// method to be called on mesh animation processing
bool onProcessAnimation(const MeshAnimationPtr &animation, const ImportMeshPtr &import_mesh,
const ImportAnimationPtr &import_animation) override
{
StringStack<> animation_file_path = "import_animation_temp_blob_";
animation_file_path += generate_unique_guid().makeString();
animation_file_path += ".anim";
const bool added = FileSystem::addBlobFile(animation_file_path);
if (!added)
{
Log::error("Can't add a blob file: %s\n", animation_file_path.get());
return false;
}
import_animation->setFilepath(animation_file_path);
if (!animation->save(import_animation->getFilepath()))
return false;
import_mesh->setAnimationFilepath(import_animation->getFilepath());
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.makeString().get());
materials.append(guid.makeString().get(), 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, 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 mesh_index = 0; mesh_index < num_meshes; ++mesh_index)
{
MeshPtr m = Mesh::create();
importer->importMesh(memory_processor.getImportProcessor(), m, scene->getMesh(mesh_index));
if (m->getNumBones())
{
int num_animations = scene->getNumAnimations();
for (int anim_index = 0; anim_index < num_animations; ++anim_index)
{
MeshAnimationPtr animation_mesh = MeshAnimation::create();
importer->importAnimation(memory_processor.getImportProcessor(), animation_mesh,
scene->getMesh(mesh_index), scene->getAnimation(anim_index));
}
}
}
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.setImporter(importer);
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->getEventClicked().connect(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(WidgetDialogFilePtr dialog)
{
const String filepath = dialog->getFile();
default_dialog_path = filepath.pathname();
import_file(filepath);
dialog.deleteLater();
}
void AppWorldLogic::dialog_file_cancel_clicked(WidgetDialogFilePtr dialog)
{
dialog.deleteLater();
}
void AppWorldLogic::request_import(const 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()->getEventClicked().connect(this, &AppWorldLogic::dialog_file_ok_clicked, dialog_file);
dialog_file->getCancelButton()->getEventClicked().connect(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;
}
最新更新:
2024-12-13
Help improve this article
Was this article helpful?
(or select a word/phrase and press Ctrl+Enter)