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
Программирование
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
Objects-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
CIGI Client Plugin
Rendering-Related Classes

Custom Import Plugin

UNIGINE's Import System allows you to import models and scenes in various external formats (FBX, DAE, OBJ, etc.). Although the basic set of file formats supported by UNIGINE out-of-the-box includes the most commonly used ones, in some cases you may need to import models or scenes in some specific file format. Fortunately the Import System can be extended to support any custom file format.

This example demonstrates how to:

  • Implement your own custom importer as a plugin.
  • Create your own import pre-processor.
  • Use your custom importer to bring a scene stored in a custom file format to UNIGINE.
Notice
It is recommended that you've read the Import System article and familiarized with basic import functionality classes.

So, in this example we are going create a custom importer for a custom file format. Let it be an xml-based format in which we declare our scene as a hierarchy of nodes that have a transformation and can have a mesh, light or camera attribute. Let our file format have a .myext exstension. An example of a scene in our custom format containing all available elements is given below.

myscene.myext

Source code (XML)
<?xml version="1.0"?>
<scene name="my_custom_scene">
	<node name="root">
		<node name="Mesh1">
			<attribute type="mesh">
				<surfaces>
					<surface name="cube_surface1" offset="0" size="12"/>
					<surface name="cube_surface2" offset="12" size="12"/>
					<surface name="cube_surface3" offset="24" size="12"/>
				</surfaces>
				<vertices>
					<vertex>0.0 0.0 0.0</vertex>
					<vertex>0.0 1.0 0.0</vertex>
					<vertex>1.0 1.0 0.0</vertex>
					<vertex>1.0 0.0 0.0</vertex>
					<vertex>0.0 0.0 1.0</vertex>
					<vertex>0.0 1.0 1.0</vertex>
					<vertex>1.0 1.0 1.0</vertex>
					<vertex>1.0 0.0 1.0</vertex>
				</vertices>
				<indices num_indices="36">0 1 2 2 3 0 4 7 6 6 5 4 3 2 6 6 7 3 4 5 1 1 0 4 4 0 3 3 7 4 5 6 2 2 1 5</indices>
			</attribute>
			<transform>1 0 0 0.0 0 1 0 0.0 0 0 1 0.0 -0.5 -1 0.0 1.0</transform>
		</node>
		<node name="OmniLight1">
			<attribute type="light">
				<light_type>LIGHT_OMNI</light_type>
				<color>0.0 1.0 0.0 1.0</color>
				<intensity>0.5f</intensity>
			</attribute>
			<transform>1 0 0 0.0 0 1 0 0.0 0 0 1 0.0 5.0 5.0 5.0 1.0</transform>
		</node>
		<node name="OmniLight2">
			<attribute type="light">
				<light_type>LIGHT_OMNI</light_type>
				<color>0.0 0.0 1.0 1.0</color>
				<intensity>0.5f</intensity>
			</attribute>
			<transform>1 0 0 0.0 0 1 0 0.0 0 0 1 0.0 -5.0 5.0 5.0 1.0</transform>
		</node>
		<node name="ProjectedLight1">
			<attribute type="light">
				<light_type>LIGHT_PROJ</light_type>
				<color>1.0 0.0 0.0 1.0</color>
				<intensity>1.0f</intensity>
			</attribute>
			<transform>1 0 0 0.0 0 1 0 0.0 0 0 1 5.0 0 0 3.0 1.0</transform>
		</node>
		<node name="my_camera">
			<attribute type="camera">
				<camera_type>TYPE_PERSPECTIVE</camera_type>
				<near>0.001f</near>
				<far>10000.0f</far>
				<fov>60.0f</fov>
				<target>10.0 10.0 10.0</target>
			</attribute>
			<transform>1 0 0 0.0 0 1 0 0.0 0 0 1 0.0 0.0 -2.0 2.0 1.0</transform>
		</node>
	</node>
</scene>

For our importer we're going to create and use a custom pre-processor to reset light colors to white when the corresponding flag is set. Thus, we're going to have the following import options and parameters:

  • scale - scale factor to be used for imported scene
  • vertex_cache - vertex cache optimization for meshes
  • make_lights_white - reset light colors flag for our pre-processor

At the end of this example we're going to have this model imported to UNIGINE's world and saved in UNIGINE's native file formats in the specified folder. But 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). You can specify which of these scene components are to be imported using the corresponding import flags (IMPORT_MESHES, IMPORT_LIGHTS, etc.)
  • A custom importer should be inherited from the Importer class.
  • A custom import processor should be inherited from the ImportProcessor class.

Enough words! Let's get to business! The code below is supplemented with comments explaining the basic aspects of the process.

Making a Plugin#

As you know each importer should be implemented as a plugin. So we're going to create a C++ plugin.

So, we inherit our import plugin from the Plugin class. We specify plugin name by overriding the get_name() method and also override init() and shutdown() methods to perform initialization and cleanup. We will have the following header file:

MyImportPlugin.h

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

#include <UniginePlugin.h>

class MyImportPlugin : public Unigine::Plugin
{
public:
	MyImportPlugin();
	virtual ~MyImportPlugin();

protected:
	// specifying our plugin's name
	virtual const char * get_name() override { return "MyImportPlugin"; }

	// overriding methods for initialization and cleanup on shutdown
	virtual int init() override;
	virtual int shutdown() override;

};

#endif /* MY_IMPORT_PLUGIN_H */

As for plugin implementation, it's also simple. Include UnigineImport.h for basic import functionality and our importer's header file (MyImporter.h).

MyImportPlugin.cpp

Source code (C++)
#include "MyImportPlugin.h"
#include <UnigineImport.h>	// basic import functionality
#include "MyImporter.h"				// our importer's header file (we also declared our import processor there)

using namespace Unigine;

MyImportPlugin::MyImportPlugin()
{

}

MyImportPlugin::~MyImportPlugin()
{
}

// plugin initialization
int MyImportPlugin::init()
{
	// getting a pointer to the Import Manager
	Import *import = Import::get();
	
	// registering our custom importer and import processor
	import->registerImporter<MyImporter>("MyImporter");
	import->registerImportProcessor<MyImportProcessor>("MyImportProcessor");
	
	return 1;
}

// plugin cleanup on shutdown
int MyImportPlugin::shutdown()
{
	// getting a pointer to the Import Manager
	Import *import = Import::get();
	
	// removing our custom importer and import processor from the registry 
	import->unregisterImporter("MyImporter");
	import->unregisterImportProcessor("MyImportProcessor");
	return 1;
}

extern "C" {

UNIGINE_EXPORT void *CreatePlugin()
{
	return new MyImportPlugin();
}

UNIGINE_EXPORT void ReleasePlugin(void *plugin)
{
	delete static_cast<MyImportPlugin *>(plugin);
}

}

To learn more about creating a C++ plugin please refer to this article.

Implementing Import Functionality#

First we should define some intermediate data structures to which all parameters of imported scene elements are to be extracted. And we should declare a common template for UserData to be able to assign data to the import structures.

Auxiliary Data Structures

Source code (C++)
using namespace Unigine;
// declaring a template for user data
template<typename Data>
struct TemplateUserData : public UserData
{
	TemplateUserData(Data d) 
		: data(d)
	{
	}
	Data data;
};

// camera data
struct MyCamera{
	String type;
	float near_plane;
	float far_plane;
	float fov;
	Math::vec3 target;
};

// light data
struct MyLight{
	String type;
	float intensity;
	Math::vec4 color;
};

// mesh data
struct MyMesh{
	String type;
	Unigine::Vector<String> surfaces;
	Unigine::Vector<Math::ivec3> polygons;
	Unigine::Vector<Math::vec3> vertices;
	Unigine::Vector<int> indices;
};

// declaring data types for imported scene components
using MyCameraData = TemplateUserData<MyCamera *>;
using MyLightData = TemplateUserData<MyLight *>;
using MyMeshData = TemplateUserData<MyMesh *>;

The general workflow will be as follows:

  1. Create an ImportScene instance in the override of Importer::onImport() method:

    MyImporter::process_node()

    Source code (C++)
    Unigine::ImportScene *MyImporter::onInit(const String &filepath)
    {
    	// getting all necessary import parameters and making necessary preparations for scene import
    	scale = getParameterFloat("scale");
    
    	String filesystem_filepath = FileSystem::get()->getAbsolutePath(filepath);
    	my_scene = Xml::create();
    	my_scene->load(filesystem_filepath);
    
    	Log::message("\n\nLAUNCHING IMPORTING PROCESS... \n");
    	
    	// parse input file and fill metadata structures
    	return import_scene();
    }
  2. Extract data from the input file, put it to the corresponding import structures (ImportMesh, ImportLight, etc.) and add them to the import scene.

    Our xml file parsing is implemented in MyImporter::import_scene() and MyImporter::process_node():

    MyImporter::process_node()

    Source code (C++)
    oid MyImporter::process_node(ImportScene *import_scene, ImportNode *parent, XmlPtr scene_node)
    {
    
    	ImportNode *node = import_scene->addNode(parent);
    	node->name = scene_node->getArg("name");
    	if (scene_node->getChild("transform"))
    	{	
    		node->transform.set(scene_node->getChild("transform")->getDMat4Data());
    		node->transform.m03 *= scale;
    		node->transform.m13 *= scale;
    		node->transform.m23 *= scale;
    	}
    	
    	// processing node attributes
    	XmlPtr attribute = scene_node->getChild("attribute");
    	if (attribute)
    	{
    		String attr_type = attribute->getArg("type");
    		if (attr_type == "light")
    		{
    			// creating and filling light metadata
    			MyLight *light = new MyLight();
    			light->type = attribute->getChildData("light_type");
    			light->color = attribute->getChild("color")->getVec4Data();
    			light->intensity = attribute->getChild("intensity")->getFloatData();
    
    			// processing light metadata and adding ImportLight to the scene
    			process_light(import_scene, node, light);
    
    			Log::message("Atribute light: named %s , type %s, color (%f, %f, %f, %f)\n", node->name.get(), light->type.get(), light->color.x, light->color.y, light->color.z, light->color.w);
    		}
    		else if (attr_type == "camera")
    		{
    			// creating and filling camera metadata
    			MyCamera *camera = new MyCamera();
    			camera->type = attribute->getChildData("player_type");
    			camera->fov = attribute->getChild("fov")->getFloatData();
    			camera->near_plane = attribute->getChild("near")->getFloatData();
    			camera->far_plane = attribute->getChild("far")->getFloatData();
    			camera->target = attribute->getChild("target")->getVec3Data();
    
    			// processing camera metadata and adding ImportCamera to the scene
    			process_camera(import_scene, node, camera);
    			Log::message("Atribute camera: named %s , type %s, target (%f, %f, %f)\n", node->name.get(), camera->type.get(), camera->target.x, camera->target.y, camera->target.z);
    		}
    		else if (attr_type == "mesh")
    		{
    			// creating and filling mesh metadata
    			MyMesh *mesh = new MyMesh();
    			int num_surfaces = attribute->getChild("surfaces")->getNumChildren();
    			int num_vertices = attribute->getChild("vertices")->getNumChildren();
    
    			Log::message("Atribute mesh: named %s , %d surfaces, %d vertices\n", node->name.get(), num_surfaces, num_vertices);
    
    			// getting all surfaces
    			for (int i = 0; i < num_surfaces; ++i){
    				XmlPtr xml_surf = attribute->getChild("surfaces")->getChild(i);
    				mesh->surfaces.append(xml_surf->getArg("name"));
    				//filling polygons data
    				mesh->polygons.append(Math::ivec3(i, xml_surf->getIntArg("offset"), xml_surf->getIntArg("size")));
    				Log::message("\nSurface [%d] named: %s\n", i, mesh->surfaces[i].get());
    			}
    			// getting all vertices
    			for (int i = 0; i < num_vertices; ++i){
    				mesh->vertices.append(attribute->getChild("vertices")->getChild(i)->getVec3Data());
    					
    				Log::message("Vertex [%d] coordinates:(%f, %f, %f)\n", i, mesh->vertices[i].x, mesh->vertices[i].y, mesh->vertices[i].z);
    			}
    			// getting all indices
    			XmlPtr indices_xml = attribute->getChild("indices");
    			int num_indices = indices_xml->getIntArg("num_indices");
    			mesh->indices.resize(num_indices);
    			indices_xml->getIntArrayData(mesh->indices.get(), num_indices);
    
    			// processing mesh metadata and adding ImportMesh to the scene
    			process_mesh(import_scene, node, mesh);
    		}
    	}
    	
    	int num_children = scene_node->getNumChildren();
    	Log::message("Node named %s , children count - %d\n", scene_node->getArg("name"), num_children);
    	
    	for (int i = 0; i < num_children; ++i){
    		XmlPtr child = scene_node->getChild(i);
    		const String &child_name = child->getName();
    		if (child_name == "node")
    			process_node(import_scene, node, child);
    	}
    }

    Scene components are added to the hierarchy in the corresponding MyImporter::process_*() methods, i.e. for lights we'll have:

    MyImporter::process_light()

    Source code (C++)
    void MyImporter::process_light(ImportScene *import_scene, ImportNode *node, MyLight *my_light)
    {
    	// checking if the light import flag is set
    	if ((flags & IMPORT_LIGHTS) == 0) return;
    
    	// adding a light source to the scene and setting lignt source data
    	ImportLight *light = import_scene->addLight(node);
    	Log::message("\n FUNCTION: process_light() REPORTING [%s]... \n", my_light->type.get());
    	light->data = new MyLightData(my_light);
    }
  3. Use pre-processor(s) to prepare scene metadata when necessary (e.g. merge all static meshes into a single one, optimize vertex cache, etc.). In our example we're going to strip off light color information if the corresponding option (make_lights_white) is set.

    For a pre-processor we should only override the ImportProcessor::onProcessScene() method

    Custom Pre-Processor

    Source code (C++)
    // inheriting a custom pre-processor from the ImportProcessor
    class MyImportProcessor : public Unigine::ImportProcessor
    {
    protected:
    	virtual bool onProcessScene(Unigine::ImportScene *scene) override;
    };
    
    // custom scene preprocessor stripping off color information from the light sources
    bool MyImportProcessor::onProcessScene(ImportScene *scene)
    {
    	// getting the number of lights in the scene
    	int num_lights = scene->getNumLights();
    	
    	// checking if the scene contains lights and the make_lights_white flag is set
    	if (num_lights && getImporter()->getParameterFloat("make_lights_white")){
    		for (int i = 0; i < num_lights; i++)
    		{
    			// getting light data from the ImportLight sctructure
    			MyLight *light = static_cast<MyLightData *>(scene->getLight(i)->data)->data;
    			// setting color to white
    			light->color = Math::vec4(1.0f);
    		}
    	}
    	
    	return true;
    }
  4. Process scene metadata corresponding to the type of imported scene element and create UNIGINE objects (nodes, meshes, lights, cameras, etc.). This is performed in overrides of Importer::onImport*() methods, i.e. for lights we'll have MyImporter::onImportlight() looking as follows:

    MyImporter::onImportlight()

    Source code (C++)
    bool MyImporter::onImportLight(ImportProcessor *processor, LightPtr &light, ImportLight *import_light) {
    
    	// trying to generate a Unigine light source using the ImportLight data
    	if (!create_light(light, import_light))
    	{
    		Log::error("MyImporter::onImportLight: can't create light.\n");
    		return false;
    	}
    	
    	// saving light data to the resulting *.node file
    	return processor->processLight(light, import_light);
    }

    UNIGINE objects creation is implemented in MyImporter::create_*() methods. For our lights we'll have create_lights() as follows:

    MyImporter::create_light()

    Source code (C++)
    bool create_light(LightPtr &light, ImportLight *import_light)
    {
    	using namespace Math;
    
    	MyLight *my_light = static_cast<MyLightData *>(import_light->data)->data;
    	// getting light parameters from the ImportLight structure
    	String type = my_light->type;
    	vec4 color = vec4(my_light->color);
    	float intensity = my_light->intensity;
    
    	// checking light source type and creating a corresponding light source
    	if (type == "LIGHT_OMNI")
    	{
    		Log::message("\n FUNCTION: create_light() creating an OMNI light... \n");
    		LightOmniPtr light_omni = LightOmni::create(color, 100.0f);
    		light_omni->release();
    		light_omni->setIntensity(intensity);
    		light_omni->setShapeType(Light::SHAPE_RECTANGLE);
    		light_omni->setTransform(Mat4(import_light->node->transform));
    		light = light_omni->getLight();
    		return true;
    	}
    	else if (type == "LIGHT_PROJ")
    	{
    		Log::message("\n FUNCTION: create_light() creating a PROJ light... \n");
    		LightProjPtr light_proj = LightProj::create(color, 100.0f, 60.0f);
    		light_proj->release();
    		light_proj->setTransform(Mat4(import_light->node->transform));
    		light = light_proj->getLight();
    		return true;
    	}
    
    	Log::error("create_light: unknown light type.\n");
    	return false;
    }
  5. Save generated UNIGINE objects to *.node and *.mesh files. This part is performad by the DefaultProcessor, so we do nothing here.

And there are a couple more things we should think of for our importer: initialization and cleanup operations, which are implemented in the constructor and destructor respectively. So, upon construction we should set default import parameters and add necessary processors. While in the cleanup section we should remove all added processors. In our case we have:

Initialization and Cleanup

Source code (C++)
MyImporter::MyImporter()
{
	// setting up necessary import parameters to be used by default
	setParameterInt("vertex_cache", 1);
	setParameterFloat("scale", 1.0f);
	
	// adding a pre-processor
	addPreProcessor("MyImportProcessor");
}

MyImporter::~MyImporter()
{
	removePreProcessor("MyImportProcessor");
}
Notice
These default values can be changed in your application when using the importer via the Importer::setParameter*() methods.

So, here is the resulting code for our importer (MyImporter)

MyImporter.h

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

#include <UnigineImport.h>
#include <UnigineMesh.h>
#include <UnigineHashMap.h>
#include <UnigineXml.h>

struct MyCamera;
struct MyLight;
struct MyMesh;

// declaring our custom import processor, as it is a pre-processor we should only override the onProcessScene() method
class MyImportProcessor : public Unigine::ImportProcessor
{
protected:
	virtual bool onProcessScene(Unigine::ImportScene *scene) override;
};

// declaring our custom importer
class MyImporter : public Unigine::Importer
{
	using ImportScene = Unigine::ImportScene;
	using ImportProcessor = Unigine::ImportProcessor;
	using ImportMesh = Unigine::ImportMesh;
	using ImportLight = Unigine::ImportLight;
	using ImportCamera = Unigine::ImportCamera;
	using ImportNode = Unigine::ImportNode;

public:
	MyImporter();
	virtual ~MyImporter();
	// here we define file extensions to be exported by our plugin, let it be a single .myext
	UNIGINE_INLINE static Unigine::Vector<Unigine::String> getExtensions() { return {"myext"}; }

protected:
	// overriding methods that we're going to use to import required scene components
	virtual Unigine::ImportScene *onInit(const Unigine::String &filepath) override;
	virtual bool onImport(const char *output_path) override;
	virtual bool onImportMesh(ImportProcessor *processor, Unigine::MeshPtr &mesh, ImportMesh *import_mesh) override;
	virtual bool onImportLight(ImportProcessor *processor, Unigine::LightPtr &light, ImportLight *import_light) override;
	virtual bool onImportCamera(ImportProcessor *processor, Unigine::PlayerPtr &camera, ImportCamera *import_camera) override;
	virtual bool onImportNode(ImportProcessor *processor, Unigine::NodePtr &node, ImportNode *import_node) override;

private:

	// method creating a new import scene, performing file format checks and parsing the input file to fill the hierarchy of metadata structures
	ImportScene *import_scene();
	
	// the methods below check required flags, perform necessary data preparations and add components to the hierarchy of scene metada structures (import_scene)
	void process_node(Unigine::ImportScene *import_scene, Unigine::ImportNode *parent, Unigine::XmlPtr scene_node);
	void process_mesh(ImportScene *import_scene, ImportNode *node, MyMesh *my_mesh);
	void process_light(ImportScene *import_scene, ImportNode *node, MyLight *my_light);
	void process_camera(ImportScene *import_scene, ImportNode *node, MyCamera *my_camera);
private:
	
	float scale { 1.0 };		// scale factor for the imported scene
	Unigine::XmlPtr my_scene;	// initial xml scene structure
};

#endif // MYIMPORTER_H

MyImporter.cpp

Source code (C++)
#include "MyImporter.h"
#include <UnigineThread.h>
#include <UnigineFileSystem.h>
#include <UnigineGeometry.h>
#include <UnigineNodes.h>
#include <UnigineWorld.h>
#include <UnigineHashSet.h>
#include <UnigineMaterials.h>
#include <UnigineDir.h>
#include <UnigineSet.h>

using namespace Unigine;

// template user data structures
template<typename Data>
struct TemplateUserData : public UserData
{
	TemplateUserData(Data d) 
		: data(d)
	{
	}
	Data data;
};

// camera metadata structure
struct MyCamera{
	String type;
	float near_plane;
	float far_plane;
	float fov;
	Math::vec3 target;
};

// light metadata structure
struct MyLight{
	String type;
	float intensity;
	Math::vec4 color;
};
// mesh metadata structure
struct MyMesh{
	String type;
	Unigine::Vector<String> surfaces;
	Unigine::Vector<Math::ivec3> polygons;
	Unigine::Vector<Math::vec3> vertices;
	Unigine::Vector<int> indices;
};

// declaring data types for imported scene components
using MyCameraData = TemplateUserData<MyCamera *>;
using MyLightData = TemplateUserData<MyLight *>;
using MyMeshData = TemplateUserData<MyMesh *>;

// auxiliary functions creating UNIGINE objects
namespace
{
	bool create_mesh(MeshPtr &mesh, ImportMesh *import_mesh, float scale);
	bool create_light(LightPtr &light, ImportLight *import_light);
	bool create_camera(PlayerPtr &camera, ImportCamera *import_camera);
}

MyImporter::MyImporter()
{
	// setting up necessary import parameters
	setParameterInt("vertex_cache", 1);
	setParameterFloat("scale", 1.0f);
	
	// adding a custom pre-processor
	addPreProcessor("MyImportProcessor");
}

MyImporter::~MyImporter()
{
	// removing our custom pre-processor
	removePreProcessor("MyImportProcessor");
}

Unigine::ImportScene *MyImporter::onInit(const String &filepath)
{
	// getting all necessary import parameters and making necessary preparations for scene import
	scale = getParameterFloat("scale");

	String filesystem_filepath = FileSystem::get()->getAbsolutePath(filepath);
	my_scene = Xml::create();
	my_scene->load(filesystem_filepath);

	Log::message("\n\nLAUNCHING IMPORTING PROCESS... \n");

	return import_scene();
}

bool MyImporter::onImport(const char *output_path)
{
	Log::message("\nOnImport() reporting... \n");
	// importing meshes
	{
		ImportProcessor *proc = nullptr;
		if (meshes_processor.size())
		{
			proc = Import::get()->createImportProcessor(meshes_processor);
			proc->setOutputPath(output_path);
			proc->setImporter(this);
		}

		Unigine::MeshPtr mesh = Unigine::Mesh::create();

		for (ImportMesh *import_mesh: scene->getMeshes())
		{
			mesh->clear();
			importMesh(proc, mesh, import_mesh);
		}
		
		delete proc;
	}
	// importing nodes
	{
		ImportProcessor *proc = nullptr;
		if (nodes_processor.size())
		{
			proc = Import::get()->createImportProcessor(nodes_processor);
			proc->setOutputPath(output_path);
			proc->setImporter(this);
		}

		for (ImportNode *node : scene->getNodes())
		{
			if (node->parent != nullptr) continue;
			NodePtr root_node;
			importNode(proc, root_node, node);
			output_filepath = node->filepath;
		}

		delete proc;
	}
	return true;
}

bool MyImporter::onImportMesh(ImportProcessor *processor, MeshPtr &mesh, ImportMesh *import_mesh)
{
	Log::message("\n onImportMesh reporting... \n");

	// trying to generate a Unigine Mesh using the ImportMesh data
	if (!create_mesh(mesh, import_mesh, scale))
	{
		Log::error("MyImporter::onImportMesh: can't create mesh.\n");
		return false;
	}

	// checking vertex_cache parameter and optimizing cache if necessary
	if (getParameterInt("vertex_cache"))
		mesh->optimizeIndices(Unigine::Mesh::VERTEX_CACHE);
	
	// calling a default processor to save the mesh to a file
	return processor->processMesh(mesh, import_mesh);
}

bool MyImporter::onImportCamera(ImportProcessor *processor, PlayerPtr &camera, ImportCamera *import_camera)
{
	Log::message("\n onImportCamera reporting... \n");
	// trying to generate a Unigine player using the ImportCamera data
	if (!create_camera(camera, import_camera))
	{
		Log::error("MyImporter::onImportCamera: can't create camera.\n");
		return false;
	}

	// saving player data to the resulting *.node file
	return processor->processCamera(camera, import_camera);
}

bool MyImporter::onImportLight(ImportProcessor *processor, LightPtr &light, ImportLight *import_light) 
{
	Log::message("\n onImportLight reporting... \n");
	// trying to generate a Unigine light source using the ImportLight data
	if (!create_light(light, import_light))
	{
		Log::error("MyImporter::onImportLight: can't create light.\n");
		return false;
	}

	// saving light data to the resulting *.node file
	return processor->processLight(light, import_light);
}

bool MyImporter::onImportNode(ImportProcessor *processor, NodePtr &node, ImportNode *import_node)
{
	Log::message("\n onImportNode reporting: importing %s node\n", import_node->name.get());

	return processor->processNode(node, import_node);
}

ImportScene * MyImporter::import_scene()
{
	// creating a new import scene
	ImportScene *new_scene = new ImportScene();

	// performing necessary checks on file format
	String n = my_scene->getName();
	if (n != "scene")
	{
		Log::error("Scene loader: bad my_scene format\n");
		return 0;
	}

	// checking scene name
	String name = my_scene->getArg("name");
	if (String::null == name)
	{
		Log::error("Scene loader: scene name can't be empty\n");
		return 0;
	}
	// parsing the input file and filling the ImportScene 
	auto size = my_scene->getNumChildren();
	Log::message("Importing [%s] scene num_nodes = %d\n", name.get(), size);
	for (int i = 0; i < size; i++)
	{
		const XmlPtr child_xml = my_scene->getChild(i);
		const String &child_name = child_xml->getName();

		if (child_name == "node")
		{
			process_node(new_scene, nullptr, child_xml);
		}
		else
			Log::error("Scene loader: unknown element \"%s\" in the \"%s\" scene\n", child_name.get(), name.get());

	}

	return new_scene;
}

void MyImporter::process_node(ImportScene *import_scene, ImportNode *parent, XmlPtr scene_node)
{

	ImportNode *node = import_scene->addNode(parent);
	node->name = scene_node->getArg("name");
	if (scene_node->getChild("transform"))
	{	
		node->transform.set(scene_node->getChild("transform")->getDMat4Data());
		node->transform.m03 *= scale;
		node->transform.m13 *= scale;
		node->transform.m23 *= scale;
	}
	
	// processing node attributes
	XmlPtr attribute = scene_node->getChild("attribute");
	if (attribute)
	{
		String attr_type = attribute->getArg("type");
		if (attr_type == "light")
		{
			// creating and filling light metadata
			MyLight *light = new MyLight();
			light->type = attribute->getChildData("light_type");
			light->color = attribute->getChild("color")->getVec4Data();
			light->intensity = attribute->getChild("intensity")->getFloatData();

			// processing light metadata and adding ImportLight to the scene
			process_light(import_scene, node, light);

			Log::message("Atribute light: named %s , type %s, color (%f, %f, %f, %f)\n", node->name.get(), light->type.get(), light->color.x, light->color.y, light->color.z, light->color.w);
		}
		else if (attr_type == "camera")
		{
			// creating and filling camera metadata
			MyCamera *camera = new MyCamera();
			camera->type = attribute->getChildData("player_type");
			camera->fov = attribute->getChild("fov")->getFloatData();
			camera->near_plane = attribute->getChild("near")->getFloatData();
			camera->far_plane = attribute->getChild("far")->getFloatData();
			camera->target = attribute->getChild("target")->getVec3Data();

			// processing camera metadata and adding ImportCamera to the scene
			process_camera(import_scene, node, camera);
			Log::message("Atribute camera: named %s , type %s, target (%f, %f, %f)\n", node->name.get(), camera->type.get(), camera->target.x, camera->target.y, camera->target.z);
		}
		else if (attr_type == "mesh")
		{
			// creating and filling mesh metadata
			MyMesh *mesh = new MyMesh();
			int num_surfaces = attribute->getChild("surfaces")->getNumChildren();
			int num_vertices = attribute->getChild("vertices")->getNumChildren();

			Log::message("Atribute mesh: named %s , %d surfaces, %d vertices\n", node->name.get(), num_surfaces, num_vertices);

			// getting all surfaces
			for (int i = 0; i < num_surfaces; ++i){
				XmlPtr xml_surf = attribute->getChild("surfaces")->getChild(i);
				mesh->surfaces.append(xml_surf->getArg("name"));
				// filling polygons data
				mesh->polygons.append(Math::ivec3(i, xml_surf->getIntArg("offset"), xml_surf->getIntArg("size")));
				Log::message("\nSurface [%d] named: %s\n", i, mesh->surfaces[i].get());
			}
			// getting all vertices
			for (int i = 0; i < num_vertices; ++i){
				mesh->vertices.append(attribute->getChild("vertices")->getChild(i)->getVec3Data());
					
				Log::message("Vertex [%d] coordinates:(%f, %f, %f)\n", i, mesh->vertices[i].x, mesh->vertices[i].y, mesh->vertices[i].z);
			}
			// getting all indices
			XmlPtr indices_xml = attribute->getChild("indices");
			int num_indices = indices_xml->getIntArg("num_indices");

			mesh->indices.resize(num_indices);
			indices_xml->getIntArrayData(mesh->indices.get(), num_indices);

			// processing mesh metadata and adding ImportMesh to the scene
			process_mesh(import_scene, node, mesh);
		}
	}
	
	// processing all node's children
	int num_children = scene_node->getNumChildren();
	for (int i = 0; i < num_children; ++i){
		XmlPtr child = scene_node->getChild(i);
		const String &child_name = child->getName();
		if (child_name == "node")
			process_node(import_scene, node, child);
	}
}
void MyImporter::process_mesh(ImportScene *import_scene, ImportNode *node, MyMesh *my_mesh/*, fbx::FbxNode *fbx_node, fbx::FbxMesh *fbx_mesh*/)
{
	Log::message("\n FUNCTION: process_mesh() reporting... \n");
	if ((flags & IMPORT_MESHES) == 0)
		return;
	
	ImportMesh *mesh = import_scene->addMesh(node);
	mesh->name = node->name;

	ImportGeometry &geometry = mesh->geometries.append();
	geometry.transform = Math::dmat4::IDENTITY;
	mesh->has_animations = 0;
	geometry.data = new MyMeshData(my_mesh);

	//adding surfaces to the mesh
	for (int i = 0; i < my_mesh->surfaces.size(); i++)
	{
		ImportSurface &s = geometry.surfaces.append();
		s.name = my_mesh->surfaces[i];
		s.material = nullptr;
		s.data = nullptr;
		s.target_surface = geometry.surfaces.size() - 1;
	}
}
void MyImporter::process_light(ImportScene *import_scene, ImportNode *node, MyLight *my_light)
{
	Log::message("\n FUNCTION: process_light() reporting... \n");
	// checking if the light import flag is set
	if ((flags & IMPORT_LIGHTS) == 0) return;

	// adding a light source to the scene and setting lignt source data
	ImportLight *light = import_scene->addLight(node);
	light->data = new MyLightData(my_light);
}


void MyImporter::process_camera(MyImporter::ImportScene *import_scene, MyImporter::ImportNode *node, MyCamera *my_camera)
{
	Log::message("\n FUNCTION: process_camera() reporting... \n");
	// checking if the camera import flag is set
	if ((flags & IMPORT_CAMERAS) == 0) return;

	// adding a camera to the scene and setting camera data
	ImportCamera *camera = import_scene->addCamera(node);
	camera->data = new MyCameraData(my_camera);
}

namespace
{
// auxiliary data structures to store mesh geometry information
struct SurfaceData
{
	int index;
	ImportSurface *surface;
	Unigine::Vector<int> cindices;
	Unigine::Vector<int> tindices;
};

struct GeometryData
{
	Unigine::Math::vec3 *vertices;
	Unigine::Math::dmat4 transform;
};

bool create_surfaces(MeshPtr &mesh, const Unigine::Vector<SurfaceData> &surfaces, const GeometryData &geometry_data, float scale) {

	Log::message("\n FUNCTION: create_surfaces() reporting... \n");
	using namespace Unigine;
	using namespace Unigine::Math;

	const dmat4 &transform = geometry_data.transform;
	const Unigine::Math::vec3 *vertices = geometry_data.vertices;

	mat3 rotation = mat3(transform);
	// creating mesh surfaces using metadata
	for (const SurfaceData &surface : surfaces)
	{
		if (surface.cindices.empty())
			continue;
		
		int s = surface.surface->target_surface;
		while(s >= mesh->getNumSurfaces())
			mesh->addSurface();

		mesh->setSurfaceName(s, surface.surface->name);
		
		for (int index : surface.cindices){
			mesh->addVertex(vec3(transform * vec3(vertices[index])), s);
			Log::message("\n FUNCTION: create_surfaces() reporting: adding vertex[%d] with coords (%f, %f, %f)... \n", index, vertices[index].x, vertices[index].y, vertices[index].z);
		}
		// applying scale
		mesh->setSurfaceTransform(Math::scale(vec3(scale)), s);
	}

	return true;
}

bool create_geometry(Unigine::MeshPtr &mesh, ImportGeometry &geometry, float scale)
{
	Log::message("\n FUNCTION: create_geometry() REPORTING... \n");
	using namespace Unigine;
	using namespace Unigine::Math;
	
	MyMeshData *mesh_data = static_cast<MyMeshData *>(geometry.data);
	MyMesh *my_mesh = mesh_data->data;

	const dmat4 &transform = geometry.transform;

	GeometryData geometry_data;
	geometry_data.vertices = &my_mesh->vertices[0];
	geometry_data.transform = transform;

	Unigine::Vector<SurfaceData> surfaces_data;

	for (int n_surf = 0; n_surf < my_mesh->surfaces.size();n_surf++)
	{
		// adding cindices and tindices and calling create surfaces
		SurfaceData &surface = surfaces_data.append();
		surface.index = n_surf;
		surface.surface = &geometry.surfaces[n_surf];

		int offset = my_mesh->polygons[n_surf].y;
		int size = my_mesh->polygons[n_surf].z;
		for (int i = 0; i < size; i++)
		{
			surface.cindices.append(my_mesh->indices[offset + i]);
			surface.tindices.append(my_mesh->indices[offset + i]);
		}
	}

	create_surfaces(mesh, surfaces_data, geometry_data, scale);
	return true;
}

bool create_mesh(Unigine::MeshPtr &mesh, ImportMesh *import_mesh, float scale)
{
	Log::message("\n FUNCTION: create_mesh() reporting... \n");
	for (ImportGeometry &geometry : import_mesh->geometries)
		create_geometry(mesh, geometry, scale);


	int num_surfaces = mesh->getNumSurfaces();
	
	// creating mesh indices
	mesh->createIndices();

	for (int s = 0; s < num_surfaces; ++s)
	{
		if (!mesh->getNumTangents(s, 0))
			mesh->createTangents(s, 0);

		for (int t = 1; t < mesh->getNumSurfaceTargets(s); ++t)
			mesh->createTangents(s, t);
	}
	// creating mesh bounds
	mesh->createBounds();

	return true;
}

bool create_light(LightPtr &light, ImportLight *import_light)
{
	using namespace Math;

	MyLight *my_light = static_cast<MyLightData *>(import_light->data)->data;
	
	// getting light parameters from the ImportLight structure
	String type = my_light->type;
	vec4 color = vec4(my_light->color);
	float intensity = my_light->intensity;

	// checking light source type and creating a corresponding light source
	if (type == "LIGHT_OMNI")
	{
		Log::message("\n FUNCTION: create_light() creating an OMNI light... \n");
		LightOmniPtr light_omni = LightOmni::create(color, 100.0f);
		light_omni->release();
		light_omni->setIntensity(intensity);
		light_omni->setShapeType(Light::SHAPE_RECTANGLE);
		light_omni->setTransform(Mat4(import_light->node->transform));
		light = light_omni->getLight();
		return true;
	}
	else if (type == "LIGHT_PROJ")
	{
		Log::message("\n FUNCTION: create_light() creating a PROJ light... \n");
		LightProjPtr light_proj = LightProj::create(color, 100.0f, 60.0f);
		light_proj->release();
		light_proj->setTransform(Mat4(import_light->node->transform));
		light = light_proj->getLight();
		return true;
	}

	Log::error("create_light: unknown light type.\n");
	return false;
}

bool create_camera(PlayerPtr &camera, ImportCamera *import_camera)
{
	Log::message("\n FUNCTION: create_camera() reporting... \n");

	using namespace Unigine::Math;
	MyCamera *my_camera = static_cast<MyCameraData *>(import_camera->data)->data;
	
	// getting camera parameters from the ImportCamera structure
	float fov = my_camera->fov;
	float znear = my_camera->near_plane;
	float zfar = my_camera->far_plane;
	
	PlayerDummyPtr player_dummy = PlayerDummy::create();
	player_dummy->release();
	player_dummy->setZNear(znear);
	player_dummy->setZFar(zfar);
	player_dummy->worldLookAt(my_camera->target,vec3::UP));
	
	// setting player projection
	if (my_camera->type == "TYPE_ORTHOGONAL")
		player_dummy->setProjection(ortho(-1.0f, 1.0f, -1.0f, 1.0f, znear, zfar));
	
	if (my_camera->type == "TYPE_PERSPECTIVE")
		player_dummy->setFov(fov);
	
	camera = player_dummy->getPlayer();
	return true;
}

}

// custom scene preprocessor stripping out color information from all scene light sources
bool MyImportProcessor::onProcessScene(ImportScene *scene)
{
	Log::message("\n MyImportProcessor reporting... \n");
	
	// getting the number of lights in the scene
	int num_lights = scene->getNumLights();
	
	// checking if the scene contains lights and the make_lights_white flag is set
	if (num_lights && getImporter()->getParameterFloat("make_lights_white")){
		for (int i = 0; i < num_lights; i++)
		{	
			// getting light data from the ImportLight sctructure
			MyLight *light = static_cast<MyLightData *>(scene->getLight(i)->data)->data;
			// setting color to white
			light->color = Math::vec4(1.0f);
		}
	}
	
	return true;
}

Using a Plugin#

Now that our library is ready we should perform the following steps to use it in our project:

  1. First, let us create a new project named MyProject, that will use our library. Let it be a simple project using UnigineScript only (the idea for C++ is the same).
  2. Put our plugin library module (a *.dll, *.so or *.dylib file) to our project's bin directory.
    Notice
    Make sure your project's binary and plugin library have the same bitness and version.
  3. Provide plugin loading in one of the following ways:
    • Pass the plugin as a command-line argument extern_plugin . Once passed, it is written into the configuration file.
      Shell commands
      MyProject.exe -extern_plugin MyImporter
    • Specify the plugin directly in the configuration file (unigine.cfg, extern_plugin string).
    • Add and use the plugin in the world script via engine.addPlugin(). The same can be done in the project's world logic via Engine::addPlugin() on the C++ side or viaEngine.addPlugin() on the C# side of the project.
    • Add and use the plugin in the system script (unigine.cpp):
      • Add the plugin via engine.addPlugin() and use it in the world script. You cannot initialize the plugin in the system script and call plugin functions from it at the same time.
      • Use the plugin in the system script after initializing it via the command-line argument extern_plugin .
      The same can be done in the project's system logic via Engine::addPlugin() on the C++ side or via Engine.addPlugin() on the C# side of the project.
  4. Use the plugin in our project. Let's add some lines of code to the init() method of the world script (MyProject.cpp file) to demonstrate the idea.

    When it's necessary to set up import parameters:

    Source code (C++)
    // MyProject.cpp
    int init() {
    
    	/* ... */
    	
    	// string to store the path to the resulting .node file with our imported scene
    	string filepath_node;
    	
    	// getting an importer for our file extension from the list of importers registered in the Import System
    	Importer my_importer = engine.import.createImporterByFileName("my_scene.myext");
    	// if such importer was found setting up necessary import options
    	if (my_importer){
    		my_importer.setParameterInt("make_lights_white", 1);	// stripping off light color information
    		my_importer.setParameterFloat("scale", 0.5f);			// setting scale
    		my_importer.init("my_scene.myext", ~0);				// initializing the importer with default import flags IMPROT_LIGHTS, IMPORT_MESHES, IMPORT_CAMERAS, etc.
    
    		// importing our scene to the output folder
    		my_importer.import("../data/output_folder/");
    		
    		// getting a path to resulting *.node file with our imported scene
    		filepath_node = my_importer.getOutputFilepath();
    	}
    	
    	// reporting the result and adding a node reference to the world on success
    	if (filepath_node)
    	{
    		log.message("Successfully imported your scene, now you can use: %s", filepath_node);
    		NodeReference mynode = new NodeReference(filepath_node);
    	}
    	else
    		log.message("Scene import failure");
    		
    	return 1;
    }

    To import with default settings:

    Source code (C++)
    // MyProject.cpp
    int init() {
    
    	/* ... */
    	
    	// string to store the path to the resulting .node file with our imported scene
    	string filepath_node;
    	
    	// importing our scene with default settings to the output folder and getting a path to resulting *.node file
    	filepath_node = engine.import.import("my_scene.myext", "../data/output_folder/");
    
    	// reporting the result and adding a node reference to the world on success
    	if (filepath_node)
    	{
    		log.message("Successfully imported your scene, now you can use: %s", filepath_node);
    		NodeReference mynode = new NodeReference(filepath_node);
    	}
    	else
    		log.message("Scene import failure");
    		
    	return 1;
    }

Now when we launch our project with MyImporter plugin loaded and MyProject world opened the scene will imported to the world and corresponding files are saved to MyProject/data/output_folder/ folder.

Last update: 16.05.2019