Support custom format through the SDK

Question and answer about Polygon Cruncher SDK
Post Reply
mootools
Site Admin
Posts: 282
Joined: Thu Jul 05, 2007 11:06 am
Contact:

Support custom format through the SDK

Post by mootools » Tue Jan 25, 2022 10:48 am

We have some 3d models in our custom format. The models are not in obj or Sketchup format. We want to optimize these models by using Polygon Cruncher.
So, we want you to guide us on how can we import the custom model data in Polygon Cruncher.
and do the optimization.
This is possible to add custom format to the SDK. Custom format can support read / write / update.

Here a the few steps to add custom format:
  • Implement a C3DParser class.
    This class is called automatically by C3DIo when you require some file to be reading or writing.
    You need to implement C3DParser::Read and/or C3DParser::Write and/or C3DParser::Update.
    Update is only possible if you support read and write. We will see in another entry the way it works.
  • Add the extension and make it recognized. This is done using fileRegisterFile in FileInfo.h
    Let's suppose the file extension is .abc and .cde

    Code: Select all

    CFileExt ext;
    ext.description = "My custom files";
    ext.ext = ".abc|.cde";
    ext.fileclass = MAKE_CUSTOM_ID('C', 'T', 'F', 'T'); // This is an internal identifier that allow to give a class to the file format.
    ext.properties = IMPORT_FILE|EXPORT_FILE|OBJECT_FILE|COMMON_EXTENSION_FILE|SAVE_HAS_DIALOG|LOAD_HAS_DIALOG; // The format support reading / writing, is related to 3D and will have a some dialogs
    ext.loader = DEI_PARSER; // use PLUGIN_PARSER is you intend to read / write the format through a dll.
    ext.saver = DEI_PARSER; // Cf. above
    
    fileRegisterFile(ext);
    
At that point you should be able to get your C3DParser::Read / C3DParser::Write methods to be called using the following piece of code.

Code: Select all

C3DIo dstfile(_T("myfile.abc"), FILE_PARSER_READING | FILE_PARSER_SILENT_MODE);
C3DScene* scene = file.Read();
if (!scene)
	return false;

mootools
Site Admin
Posts: 282
Joined: Thu Jul 05, 2007 11:06 am
Contact:

Re: Support custom format through the SDK

Post by mootools » Thu Feb 03, 2022 9:09 am

Here is a code snippest to add a ".abc" format and make it recognized by the SDK
This is the minimal code for a parser, this one provide both C3DParser::Read and C3DParser::Write methods which means abc is read and write.

The goal of this little sample is to make "abc" extension being recognized and Read/Write methods being called when an "abc" file is read or write from the SDK.

Header

Code: Select all

#pragma once

#include "Io3dmgr.h"

class C3DAbcIo : public C3DParser
{
	public:
		C3DAbcIo(CFileIo& io);

		C3DScene *Read(const CXString&, CSceneImportOptions& options);
		bool Save(const CXString&, C3DScene *, CSceneExportOptions& options);
};
Implementation

Code: Select all

// C3DAbcIo.cpp: implementation of the internal format class.

#include "stdafx.h"
#include "common_cpp_type.h"

#include "3DType.h"
#include "IoFile.h"
#include "3DScene.h"
#include "3DSceneNode.h"
#include "3DObject.h"
#include "3DGroup.h"
#include "3DAbcIo.h"

C3DAbcIo::C3DAbcIo(CFileIo & io) : C3DParser(io)
{
}

C3DScene* C3DAbcIo::Read(const CXString& filename, CSceneImportOptions& options)
{
	// File is opened using IoFile but any others file functions can be used (ie fopen)
	IoFile file(IsSilentMode(), IOFILE_MEMORY_FILE);
	if (!file.OpenFile(filename, true))
	{
		io->SetIoError(IO_FILE_CANT_OPEN_FILE, &options, IoLogInfo::LOG_ERROR);
		file.CloseFile();
		return NULL;
	}

	ioscene = xNew(C3DScene); // The scene we will fill with the readed info

	// Do you implementation here. Add nodes to the scene while you read it, using the 3D classes
	while (readingYourFile)
	{
		Entity* entity = GetYourEntitiesFromYourFile(file);

		if (entity->GetType() == ENTITY_IS_POLYGONAL_OBJET)
		{
			C3DFaceList* faces = xNew(C3DFaceList);
			faces->SetSize(yourFaceCount, 4);

			for (int j = 0; j < yourFaceCount; j++)
			{
				int* indexes = faces->SetFaceSize(j, 4); // A quad face
				for (int i = 0; i < 4; i++)
					indexes[i] = yourIndexes;
			}

			C3DPoint* pt;
			C3DPointList* pts = xNew(C3DPointList);
			pts->SetSize(yourPointCount);
			for (int i = 0; i < yourPointCount; i++)
			{
				pt = (*pts)[i];
				pt->x = yourPt.x; pt->y = yourPt.y; pt->z = yourPt.z;
			}

			C3DObject* object = xNew(C3DObject);
			object->SetPointList(pts);
			object->SetFaceList(faces);

			{
				CUVWChannel* uvwchannel = xNew(CUVWChannel);
				CUVWPointList* uvpts = uvwchannel->GetUVWPointList();
				CUVWFaceList* uvfaces = uvwchannel->GetUVWFaceList();

				uvfaces->SetSize(yourUVFaceCount, 4);
				for (int j = 0; j < yourUVFaceCount; j++)
				{
					int* indexes = uvfaces->SetFaceSize(j, 4);
					for (int i = 0; i < 4; i++)
						indexes[i] = i;
				}

				CUVWPoint* uvpt;
				uvpts->SetSize(yourUVPointCount);
				for (int i = 0; i < yourUVPointCount; i++)
				{
					uvpt = (*uvpts)[0];
					uvpt->u = yourUV.u; uvpt->v = yourUV.u; uvpt->v = yourUV.w;
				}

				object->AddChannel(uvwchannel);
			}

			C3DSceneNode* node = xNew(C3DSceneNode);
			node->SetObject(object);
			node->SetName(yourName);
			scene->AddNode(pParentNode, node);

			C4x4Matrix mat(true);
			// Fill the matrix here
			C3DMatrixKey* matrixKey = (C3DMatrixKey*)node->CreateKey(0, KEYFRAME_MATRIX);
			matrixKey->SetMatrix(mat);
		}
		if (entity->GetType() == ENTITY_IS_GROUP)
		{
			// ...
		}
	}

	file.CloseFile();

	return ioscene;
}

bool C3DAbcIo::Save(const CXString& filename, C3DScene* userScene, CSceneExportOptions& options)
{
	// File is opened using IoFile but any others file functions can be used (ie fopen)
	IoFile iofile(IsSilentMode(), IOFILE_MEMORY_FILE);
	if (!iofile.OpenFile(filename, false))
	{
		io->SetIoError(IO_FILE_CANT_OPEN_FILE, &options, IoLogInfo::LOG_ERROR);
		return false;
	}

	// Do you implementation here. Write your format from scene content
	C3DBaseObject* object;
	C3DSceneNode* node;
	C3DNodePos pos = ioscene->GetFirstNode();
	while (pos)
	{
		node = ioscene->GetNextNode(object, pos);
		if (!IsValidNode(node, object))
			continue;

		if (object->GetKindOf() == OBJECT_MESH)
		{
			C3DObject* geomobject = (C3DObject*)object;
			C3DPointList* faces = geomobject->GetPointList();
			C3DFaceList* faces = geomobject->GetFaceList();

			// ...
		}
		else if (object->GetKindOf() == OBJECT_GROUP)
		{
			C3DGroup* groupobject = (C3DGroup*)object;
			// ...
		}

	iofile.CloseFile();

	return true;
}
Format declaration to the SDK

Code: Select all



// Windows code for the dll entry point.
// The extension dll must be .moox, so it will be automatically recognized by the SDK
// The dll must be compiled with CRUNCHERSDK_DLL_USE preprocessor define
// Ie. Abc.moox
#include "common_cpp_type.h"
#include "3DType.h"
#include "Io3dmgr.h"
#include "3DAbcIo.h"
#include "Plugins.h"


extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
	if (dwReason == DLL_PROCESS_ATTACH)
	{
		XTRACE("core dll Initializing!\n");
		//...
	}
	else if (dwReason == DLL_PROCESS_DETACH)
	{
		XTRACE("core dll Terminating!\n");
		//...
	}

	return 1;   // ok
}
#endif // MFC

// Return the version of the plugins to check it match the version of the SDK
// This function is exported
unsigned int MootoolsGetPluginVersion()
{
	return MOOTOOLS_PLUGIN_VERSION;
}

#define ABC_FILE_FORMAT MAKE_CUSTOM_ID('A', 'B', 'C', 'F')
// This method is called when the plugin is initialized (IO_INIT_PLUGIN) to declare abc extension format
// It is also called when the SDK read a file with the extension abc (IO_GET_PARSER).
bool Init3DParser(IoPluginMode mode, void* data)
{
	switch (mode)
	{
		case IO_INIT_PLUGIN:
		{
			IOInitPluginInfo* initInfo = (IOInitPluginInfo*)data;

			CFileExt ext;
			ext.description = "My custom files";
			ext.ext = ".abc|.cde";
			ext.fileclass = ABC_FILE_FORMAT;
			ext.properties = IMPORT_FILE | EXPORT_FILE | OBJECT_FILE | COMMON_EXTENSION_FILE; // The format support reading / writing, is related to 3D and will have a some dialogs
			ext.loader = PLUGIN_PARSER; // PLUGIN_PARSER means that the format is read through a dll.
			ext.saver = PLUGIN_PARSER; // Cf. above
			initInfo->fileExtInfo.Add(ext);
			break;
		}

		case IO_GET_PARSER:
		{
			IOGetParserInfo* initParserInfo = (IOGetParserInfo*)data;
			switch (initParserInfo->fileclass)
			{
				case ABC_FILE_FORMAT:
					initParserInfo->parser = xNewParams(C3DAbcIo, *(initParserInfo->io));
					break;
			}
			break;
		}
	}

	return true;
}

mootools
Site Admin
Posts: 282
Joined: Thu Jul 05, 2007 11:06 am
Contact:

Re: Support custom format through the SDK

Post by mootools » Mon Feb 21, 2022 11:17 am

What exactly CUVWFaceList is referring to? We have already populated triangles into C3DFacelist, is it different from that?
Most of the time there is only a single index face list which refers to point index, UV index, normal index...
This is the case for most of the visualization solution (OpenGL, DirectX...).

The SDK handles different topologies for geometrical face, UV faces, normal faces.
This is useful to handle seams without duplicating vertex position.

Saying that the CUVWFaceList must have exactly the same number of faces than the C3DFaceList.
If a mesh has no seams, then most of the time CUVWFaceList contains the same index list than C3DFaceList.

The CUVWFaceList contains index that refers to the CUVWPointList it is linked with through the CUVWChannel.
C4x4Matrix: Do we need to populate this matrix ourselves or is it getting automatically populated through node data? if yes, in what form do we have to fill it in?
You don't need to handle C4x4Matrix if your format have global world position.
When reading, any node will automatically have the identity matrix.
When writing your have to convert the scene graph to global world position. You can add a method to do this simply:

Code: Select all

void C3DAbcIo::SaveDefaultOptions(CSceneExportOptions& options) const
{
	options.neededflags = SCENE_EXPORT_GLOBAL_COORDINATES; // Convert local point position to world position before calling C3DAbcIo::Save
}
If your meshes have inherited transformation, then you have to fill C4x4Matrix accordingly when reading and get back matrix transform of each node when writing using C3DSceneNode::GetNodeLocalTM
We have faces (triangles) grouped based on material. During the optimization, we are sending them together and after the optimization, we want to again divide them into original material-based groups. Is there any way to attach custom data to each face that we can reuse to group back after optimization?
You can attach any kind of data to your face list. But I there might be a simplest way to do what you need, if I clearly understand what you're trying to do.
You can attach a materialID to each face using C3DFaceList::SetMaterialID.
When reading, you can attach a specific ID to each group. When saving, just sort the face by materials then save each group independently.

You can use bool C3DGeomObject::SortFaces(FACE_SORT_MODE sortmode) to sort your faces by material after optimization.
And just to confirm we are putting W = 1 (default) in UVWpoints and parentNode as NULL if we have a single Node. Hope that is correct.
You're right!

mootools
Site Admin
Posts: 282
Joined: Thu Jul 05, 2007 11:06 am
Contact:

Re: Support custom format through the SDK

Post by mootools » Thu Mar 10, 2022 2:48 pm

faces->SetFaceMaterial(face_index, mat_id);
Yes this is the right way.

Code: Select all

About channel ID
The channel ID depend on the material which is assign to face.
You can retrieve the face material, then the material (using C3DScene::GetMaterialByID)
You can then use C3DMaterial::GetUVWChannelID to know the ChannelID which is associate to a given material Map.
C3DMaterial::GetUVWChannelID will return GetGlobalUVWChannel if appropriate.

Code: Select all

Do we have to retrieve it using channel of "SPEC_NORMAL_CHANNEL" 
When you use GenerateNormals you'll generate a specified normal channel.
You can then retrieve it using it's index (there's only one specified channel, no matter it's ID)
CChannel* normasChannel = (CChannel*)geomobject->GetChannelByIndex(0, SPEC_NORMAL_CHANNEL);

Code: Select all

And simply we can use x, y, z value of these points as normal ?
The specified normal channel contains a C3DVectorList which contains C3DVector

Code: Select all

Confusion on registering format. Probably a bit more details will be helpful
Do we have to use the code shared by you, with our modification, to create a separate solution in VS and build it as a library

You have to genere a .moox library which is of the type dll on windows

That means this preprocessor should have been added into this code, which I can not see. Or is there any other way?

You have to compile with CRUNCHERSDK_DLL_USE defined and link with the SDK library.
CRUNCHERSDK_DLL_USE allow to import correctly the library functions when linking.

Where do we need to put this build dll extension? How will it get recognized?
You can put this .moox (= dll) library in the same folder than PolygonCruncherSDKIo.moox dll and PolygonCruncherSDK.dll
It will then be automatically loaded and the extension define in will be recognized.

Is this the only way to let a custom file get recognized? Or can it be done by calling "fileRegisterFile(ext);" from any file before calling optimization? as you mentioned in first reply as I feel that working on the above points to get extension recognized is a significant task

This is a taks like creating an external dll. There is no real difference.
This is the only way to perform the task. We might tought to a way to add the extension through some callback.
That's an idea for a next release that can help to add a custom extension...

Post Reply

Who is online

Users browsing this forum: No registered users and 3 guests