SOLVED Importing morph problem

Hello,
I need to write morph data into a polygon object.
I use the following code, but there is a problem with the imported data and the morph tag cache is large.
How can I import morph data?

//Initialization morph tag.
		CAPoseMorphTag* morph_tag = CAPoseMorphTag::Alloc();
		if (morph_tag == nullptr)
		{
			GePrint(GeLoadString(IDS_MES_IMPORT_ERR) + GeLoadString(IDS_MES_MEM_ERR));
			MessageDialog(GeLoadString(IDS_MES_IMPORT_ERR) + GeLoadString(IDS_MES_MEM_ERR));
			return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION, GeLoadString(IDS_MES_IMPORT_ERR) + GeLoadString(IDS_MES_MEM_ERR));
		}
		model->InsertTag(morph_tag);
		morph_tag->InitMorphs();

		//Add base morph to the tag.
		morph_tag->SetParameter(ID_CA_POSE_POINTS, true, DESCFLAGS_SET::NONE);
		morph_tag->ExitEdit(doc, true);
		CAMorph* base_morph = morph_tag->AddMorph();
		if (base_morph == nullptr)
		{
			GePrint(GeLoadString(IDS_MES_IMPORT_ERR) + GeLoadString(IDS_MES_MEM_ERR));
			MessageDialog(GeLoadString(IDS_MES_IMPORT_ERR) + GeLoadString(IDS_MES_MEM_ERR));
			return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION, GeLoadString(IDS_MES_IMPORT_ERR) + GeLoadString(IDS_MES_MEM_ERR));
		}
		base_morph->Store(doc, morph_tag, CAMORPH_DATA_FLAGS::POINTS);
		morph_tag->UpdateMorphs();
		EventAdd();

		//Get the morph data count.
		Int32 morph_data_count = pmx_model->model_data_count.morph_data_count;


		for (Int32 i = 0; i < morph_data_count; i++)
		{
			StatusSetText("Import morphs..."_s);
			StatusSetBar(i * 100 / morph_data_count);
			PMX_Morph_Data* morph_data = pmx_model->morph_data[i];
			if (morph_data->morph_type == 1) {
				CAMorph* morph = morph_tag->AddMorph();
				if (morph == nullptr)
				{
					GePrint(GeLoadString(IDS_MES_IMPORT_ERR) + GeLoadString(IDS_MES_MEM_ERR));
					MessageDialog(GeLoadString(IDS_MES_IMPORT_ERR) + GeLoadString(IDS_MES_MEM_ERR));
					return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION, GeLoadString(IDS_MES_IMPORT_ERR) + GeLoadString(IDS_MES_MEM_ERR));
				}
				maxon::PointerArray<PMX_Morph_vertex>* vertex_morph_data_arr = (maxon::PointerArray<PMX_Morph_vertex>*)morph_data->offset_data;
				morph_tag->ExitEdit(doc, true);
				Int32 offset_count = morph_data->offset_count;

				//Set the position of the deformed point.
				for (Int32 j = 0; j < offset_count; j++) 
				{
					//This structure records the index of the point and the displacement of the point.
					PMX_Morph_vertex vertex_morph_data = (*vertex_morph_data_arr)[j];

					//Move the point.
					//"vertex_morph_data.vertex_index"(UInt32) is index of the point.
					//"vertex_morph_data.translation"(vec3) is displacement of the point.
					//The parameter "PositionMultiple"(Float32) is used to set the displacement scaling.
					//"model_point_obj"(Vector[]) is the point write handle of the model.
					model_points[vertex_morph_data.vertex_index] += vertex_morph_data.translation * PositionMultiple;
				}
				//Update point data.
				model_point_obj->Message(MSG_UPDATE);
				morph->Store(doc, morph_tag, CAMORPH_DATA_FLAGS::POINTS);
				morph_tag->UpdateMorphs();
				EventAdd();
				morph->SetName(morph_data->morph_name_local);
				morph->SetStrength(0);

				//The position of the recovery point is the initial.
				for (Int32 j = 0; j < offset_count; j++) 
				{
					PMX_Morph_vertex vertex_morph_data = (*vertex_morph_data_arr)[j];
					model_points[vertex_morph_data.vertex_index] = pmx_model->vertex_data[vertex_morph_data.vertex_index]->position * PositionMultiple;
				}
				model_point_obj->Message(MSG_UPDATE);
			}
		}
		//Set "ID_CA_POSE_MODE" parameter to animation.
		morph_tag->SetParameter(DescID(ID_CA_POSE_MODE), ID_CA_POSE_MODE_ANIMATE, DESCFLAGS_SET::NONE);

Thank,
AiMiDi

hi,

it's not clear in your code what is model_points

I creating this example. It will pick the active object and create a morph tag with a morph to match the object next to it.
I've included the use of ParallelFor but it's not always benefict to parallel things.

But instead of loading this to a morph tag, you may want to load the animation to a PLA track ?
(if I'm correct that it's to load MMD animation)

        BaseObject* target = doc->GetActiveObject();
	if (target == nullptr)
		return maxon::NullptrError(MAXON_SOURCE_LOCATION);

	if (!target->IsInstanceOf(Opolygon))
		return maxon::IllegalArgumentError(MAXON_SOURCE_LOCATION);

	BaseObject* source = target->GetNext();
	if (source == nullptr)
		return maxon::NullptrError(MAXON_SOURCE_LOCATION);

	if (!source->IsInstanceOf(Opolygon))
		return maxon::IllegalArgumentError(MAXON_SOURCE_LOCATION);

	// some checks
	const Int32 sourcePointCount = ToPoly(source)->GetPointCount();
	const Int32 targetPointCount = ToPoly(target)->GetPointCount();
	const Matrix sourceMg = source->GetMg();
	const Matrix TargetMg = target->GetMg();
	const Matrix InvTargetMg = ~TargetMg;
	const Matrix pointTransfert = InvTargetMg * sourceMg;

	
	if (sourcePointCount != targetPointCount)
		return maxon::IllegalArgumentError(MAXON_SOURCE_LOCATION, "source and target doesn't have the same point number"_s);

	const Vector* padrSource = ToPoly(source)->GetPointR();

	CAPoseMorphTag* morph_tag = CAPoseMorphTag::Alloc();
	if (morph_tag == nullptr)
		return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION);

	target->InsertTag(morph_tag);
	morph_tag->SetParameter(ID_CA_POSE_POINTS, true, DESCFLAGS_SET::NONE);

	morph_tag->InitMorphs();


	// Create Base Morph
	morph_tag->ExitEdit(doc, true);
	CAMorph* base_morph = morph_tag->AddMorph();
	if (base_morph == nullptr)
		return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION, "couldn't create the base morph"_s);
	base_morph->Store(doc, morph_tag, CAMORPH_DATA_FLAGS::POINTS);
	morph_tag->UpdateMorphs();

	// Create a New morph
	morph_tag->ExitEdit(doc, true);
	CAMorph* newMorph = morph_tag->AddMorph();
	newMorph->SetName("first morph"_s);
	newMorph->Store(doc, morph_tag, CAMORPH_DATA_FLAGS::ASTAG);
	

	CAMorphNode* mnode = newMorph->GetFirst();

	// Set the mode for the morph as  CAMORPH_MODE::ABS so we can defined the point position in absolute mode.
	newMorph->SetMode(doc, morph_tag, CAMORPH_MODE_FLAGS::ALL | CAMORPH_MODE_FLAGS::EXPAND, CAMORPH_MODE::ABS);
	// Check if point information is preset.
	if (mnode->GetInfo() & CAMORPH_DATA_FLAGS::POINTS)
	{
		mnode->SetPointCount(sourcePointCount);
		/*
		for (Int32 pointIndex = 0; pointIndex < sourcePointCount; pointIndex++)
		{
			// we need to retrieve the point position in the target's space (source local -> global -> target local)
			Vector pointPos = pointTransfert * padrSource[pointIndex];
			mnode->SetPoint(pointIndex, pointPos);
		}
		*/

		// you can use ParallelFor if you need speed but that could be worst depending of the point number.
		maxon::ParallelFor::Dynamic(0, sourcePointCount, [&mnode, &pointTransfert, &padrSource](const Int32 pointIndex)
			{
				Vector pointPos = pointTransfert * padrSource[pointIndex];
				mnode->SetPoint(pointIndex, pointPos);
			});

	}
	newMorph->SetMode(doc, morph_tag, CAMORPH_MODE_FLAGS::ALL | CAMORPH_MODE_FLAGS::COLLAPSE, CAMORPH_MODE::AUTO);

	morph_tag->UpdateMorphs();
	morph_tag->Message(MSG_UPDATE);

Cheers,
Manuel

hi,

it's not clear in your code what is model_points

I creating this example. It will pick the active object and create a morph tag with a morph to match the object next to it.
I've included the use of ParallelFor but it's not always benefict to parallel things.

But instead of loading this to a morph tag, you may want to load the animation to a PLA track ?
(if I'm correct that it's to load MMD animation)

        BaseObject* target = doc->GetActiveObject();
	if (target == nullptr)
		return maxon::NullptrError(MAXON_SOURCE_LOCATION);

	if (!target->IsInstanceOf(Opolygon))
		return maxon::IllegalArgumentError(MAXON_SOURCE_LOCATION);

	BaseObject* source = target->GetNext();
	if (source == nullptr)
		return maxon::NullptrError(MAXON_SOURCE_LOCATION);

	if (!source->IsInstanceOf(Opolygon))
		return maxon::IllegalArgumentError(MAXON_SOURCE_LOCATION);

	// some checks
	const Int32 sourcePointCount = ToPoly(source)->GetPointCount();
	const Int32 targetPointCount = ToPoly(target)->GetPointCount();
	const Matrix sourceMg = source->GetMg();
	const Matrix TargetMg = target->GetMg();
	const Matrix InvTargetMg = ~TargetMg;
	const Matrix pointTransfert = InvTargetMg * sourceMg;

	
	if (sourcePointCount != targetPointCount)
		return maxon::IllegalArgumentError(MAXON_SOURCE_LOCATION, "source and target doesn't have the same point number"_s);

	const Vector* padrSource = ToPoly(source)->GetPointR();

	CAPoseMorphTag* morph_tag = CAPoseMorphTag::Alloc();
	if (morph_tag == nullptr)
		return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION);

	target->InsertTag(morph_tag);
	morph_tag->SetParameter(ID_CA_POSE_POINTS, true, DESCFLAGS_SET::NONE);

	morph_tag->InitMorphs();


	// Create Base Morph
	morph_tag->ExitEdit(doc, true);
	CAMorph* base_morph = morph_tag->AddMorph();
	if (base_morph == nullptr)
		return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION, "couldn't create the base morph"_s);
	base_morph->Store(doc, morph_tag, CAMORPH_DATA_FLAGS::POINTS);
	morph_tag->UpdateMorphs();

	// Create a New morph
	morph_tag->ExitEdit(doc, true);
	CAMorph* newMorph = morph_tag->AddMorph();
	newMorph->SetName("first morph"_s);
	newMorph->Store(doc, morph_tag, CAMORPH_DATA_FLAGS::ASTAG);
	

	CAMorphNode* mnode = newMorph->GetFirst();

	// Set the mode for the morph as  CAMORPH_MODE::ABS so we can defined the point position in absolute mode.
	newMorph->SetMode(doc, morph_tag, CAMORPH_MODE_FLAGS::ALL | CAMORPH_MODE_FLAGS::EXPAND, CAMORPH_MODE::ABS);
	// Check if point information is preset.
	if (mnode->GetInfo() & CAMORPH_DATA_FLAGS::POINTS)
	{
		mnode->SetPointCount(sourcePointCount);
		/*
		for (Int32 pointIndex = 0; pointIndex < sourcePointCount; pointIndex++)
		{
			// we need to retrieve the point position in the target's space (source local -> global -> target local)
			Vector pointPos = pointTransfert * padrSource[pointIndex];
			mnode->SetPoint(pointIndex, pointPos);
		}
		*/

		// you can use ParallelFor if you need speed but that could be worst depending of the point number.
		maxon::ParallelFor::Dynamic(0, sourcePointCount, [&mnode, &pointTransfert, &padrSource](const Int32 pointIndex)
			{
				Vector pointPos = pointTransfert * padrSource[pointIndex];
				mnode->SetPoint(pointIndex, pointPos);
			});

	}
	newMorph->SetMode(doc, morph_tag, CAMORPH_MODE_FLAGS::ALL | CAMORPH_MODE_FLAGS::COLLAPSE, CAMORPH_MODE::AUTO);

	morph_tag->UpdateMorphs();
	morph_tag->Message(MSG_UPDATE);

Cheers,
Manuel

Hi,@m_magalhaes Thank you very much for your reply.

The model_points is "ToPoly(model)->GetPointW()".
I really want to import MMD data.
But it's not an animation data, it's a model data.
The detailed code can be viewed here.
The MMD pmx file file structure document can be viewed here.

Thank,
AiMiDi

hi,

does the provided code works for you ?
i don't know how to read those file and what they contains, specially what they call morph may not be the equivalent of our morph.

Cheers,
Manuel

hi,
I read the code you provided, but I cannot use it.
I want to read the morph data that I want to import from the file, and then write it to the tag instead of importing it from another object.

The morph of the MMD model is indeed different from the c4d. I just want to import the morph of the MMD model.
The file records the point index (I have ensured that these points exist in the model.) and the point displacement (this is a Vector32, which represents the displacement of the deformation point, and the final position of the point is "point position + point displacement").

I noticed that you used "CAMorphNode". I don't know how to use it. Can you explain it in detail? I can't understand the document.
When I try to use "CAMorphNode", I don't know if the point index recorded in it is the point index of the model.

Thank,
AiMiDi

hi,

Taking the next objet was just to have a source of points/vectors. It can be any kind of source as long as you know if the vector are relative or absolute.

You are saying that the morph vector are relative to the point position so you need to change the last flag of this line
from CAMORPH_MODE::ABS to CAMORPH_MODE::REL

newMorph->SetMode(doc, morph_tag, CAMORPH_MODE_FLAGS::ALL | CAMORPH_MODE_FLAGS::EXPAND, CAMORPH_MODE::REL);

That way, the vector you pass will be added to the current point position.

You got our manual about CAMorphNode
It's an object stored specially useful if the morph tag is in Hierarchy mode.

The point are the same, that's why you need to set the mode.
First you need to EXPAND and once you are done, you need to COLLAPSE so only delta will be stored in the tag.

let me know if it's still not clear :)

Cheers,
Manuel

hi,
I modified it based on the code you provided, and it works.
I use multi-threading, but sometimes it will get stuck when calling multi-thread (console display handles are not equal). I don't know how to solve this problem.
And if I use optimization commands, it will destroy morph, what should I do.

Thank,
AiMiDi

hi,

it's a bit hard to tell without more informations.

Could you provide an example of what's going on ? Code, Workflow, does Cinema 4D hangs, crash ? What is your expected result ?

About the optimization command, of course optimizing the mesh may reduce the point number or the order of points.
But what do you call "destroy morph" ? Are only the points that are optimized that get wrong ? Does every points get wrong ?
Is it only the geometry or the uvs ?

cheers,
Manuel

hi,
The problem with multithreading is this:
The cpu is still occupied, but the utilization rate is very low, and the thread does not exit.
e23cf9b4-2055-45f7-b665-628c3d160177-image.png
When there is a problem, the console often has this:

Exception filter was modified, handler is 0x00007ffe2ff9f430 but should be 0x00007ffe2e4f7320

The imported model is available, but after I use the optimization command,

doc->SetSelection(model);
doc->SetMode(Mpolygons);
BaseSelect* all_select = model->GetPolygonS();
all_select->SelectAll(0, model->GetPolygonCount() - 1);
CallCommand(14039);
all_select->DeselectAll();
doc->SetMode(Mmodel);

it became as shown in the figure.
3286ac8f-2b5a-47b1-845c-9ae7b7e1ab91-image.png

The optimization command may change the index of the point, how do I get these changes, I noticed that some messages will be sent after the change, and how to use it.

Thank,
AiMiDi

hi,

sorry for the delay, we have our internal summit and we have a bit less time this week to work on support cases.
it's a normal result, if you are changing the mesh, than the morph cannot convert the data.
You need to import all your mesh. combine them into one and optimize the mesh and than create your morph.

For that you need to track your points from what mesh they are coming from and what was the index on your morph.

To track the points, you can create and add a customDataTag that will store information for each points. (the orginal mesh name and the index from that mesh).
You can create one tag on each mesh part and when you combine all the parts, our core system will combine them. (you can implement the "add" function in your customDataTag to tell how you want to data to be combined)

You can read information about CustomDatTag in our manual
We also have examples in our sdk you can found them on github

I will try to test it myself.

Cheers,
Manuel

Thanks,
I will try it myself, thank you for your help.

Thank,
AiMiDi

Hello @AiMiDi,

without any further feedback or questions, we will consider this topic as solved by Wednesday and flag it accordingly.

Thank you for your understanding,
Ferdinand