コード例 #1
0
//we need to not only check the expose node and reference node to see if there flags are set, but 
//we also need to check their parents since a call on node->GetNodeTM may call node->parent->UpdateTM
//node->parent->parent->UpdateTM.. etc... So all of the parents need to get checked to.
BOOL BaseExposeControl::AreNodesOrParentsInTMUpdate()
{
	//collect expose node parents.
	if(exposeTransform)
	{
		INode *exposeNode = exposeTransform->GetExposeNode();
		if(exposeNode)
		{
			INodeTab nodes;
			nodes.Append(1,&exposeNode);
			CollectParents(nodes,exposeNode);
	
			//simple check to see if referenceNode isn't exposeNodeParent.. if not.. collect them too
			INode *refNode = exposeTransform->GetReferenceNode();
			if(refNode&&refNode!=exposeNode->GetParentNode())
			{
				nodes.Append(1,&refNode);
				CollectParents(nodes,refNode);
			}

			for(int i=0;i<nodes.Count();++i)
			{
				if(nodes[i]->TestAFlag(A_INODE_IN_UPDATE_TM)==TRUE)
					return TRUE;
			}
		}
	}

	return FALSE;
}
コード例 #2
0
ファイル: export_skinning.cpp プロジェクト: CCChaos/RyzomCore
// ***************************************************************************
bool CExportNel::getNELUnHeritFatherScale(INode &node)
{
	// Default.
	bool	ret= false;


	/* If my parent exist, and if my parent is a BipedNode, always suppose I must unherit scale.
		This is because Biped NodeTM always have a Scale 1,1,1. Hence Sons bones do not herit scale.
	*/
	INode	*parentNode= node.GetParentNode();
	if(parentNode && isBipedNode(*parentNode) )
	{
		ret= true;
	}
	// Else, test the std way.
	else
	{
		// Get the TM controler
		Control *c;
		c = node.GetTMController();

		// If a TM controler exist
		if (c)
		{
			// Get the inherit flags
			DWORD flags=c->GetInheritanceFlags();

			// Unherit scale if all scale inherit flags are cleared
			ret= (flags&(INHERIT_SCL_X|INHERIT_SCL_Y|INHERIT_SCL_Z))!=0;
		}
	}

	return ret;
}
コード例 #3
0
bool plMaxNodeBase::IsXRef()
{
    // Is this an XRef'd object?
    Object *obj = GetObjectRef();
    if (obj->SuperClassID() == SYSTEM_CLASS_ID && obj->ClassID() == XREFOBJ_CLASS_ID)
        return true;

    //
    // Is this part of an XRef'd scene?
    //
    // Walk up to our root node
    INode *root = GetParentNode();
    while (!root->IsRootNode())
        root = root->GetParentNode();
    // If our root isn't the main root, we're in an XRef'd scene.
    if (root != GetCOREInterface()->GetRootNode())
        return true;

    return false;
}
コード例 #4
0
ファイル: ExportKeys.cpp プロジェクト: AntonioModer/xray-16
bool MeshExpUtility::SaveSkinKeys(const char* n){
	CFS_Memory F;

	int FramesPerSecond = GetFrameRate();
	int TicksPerFrame	= GetTicksPerFrame();
	int FirstTick		= ip->GetAnimRange().Start();
	int LastTick		= ip->GetAnimRange().End();

	Point3 v;
	Matrix3 tm;

	// Write signature and version
	char S[MAX_PATH];
	sprintf(S, "KEYEXP 3.0");
	F.Wstring(S);

	INode *node;
	ObjectEntry *Current;

	//-----------------------------------------------------------------------
	// Count bones and report

	int NumBones = 0;
	Current = theObjects->head;
	while(Current)
	{
		/*
		if(Current->entry->type != OBTYPE_BONE)
		{
			Current = Current->next;
			continue;
		}
		*/

		NumBones++;

		Current = Current->next;
	}

	sprintf(S, "Number of Bones = %d", NumBones);
	F.Wstring(S);
	ELog.Msg(mtInformation,S);

	//-----------------------------------------------------------------------
	// Write out necessary data for motion keys

	sprintf(S, "Key Data");
	F.Wstring(S);

	TimeValue t;
	Quat qq;
	Point3 tp, sp;
	INode* parent;
	Matrix3 tmp;

	sprintf(S, "%d %d %d", FirstTick / TicksPerFrame, LastTick / TicksPerFrame, FramesPerSecond);
	F.Wstring(S);

	Current = theObjects->head;
	while(Current){
		/*
		if(Current->entry->type != OBTYPE_BONE)
		{
			Current = Current->next;
			continue;
		}
		*/

		Matrix3 tm;

		node = Current->entry->node;

		sprintf(S, "Node: %s", node->GetName());
		F.Wstring(S);
		ELog.Msg(mtInformation,S);

		// Print notetrack info
		{
			int NumNT, n, i, j, NumNotes;
			NoteTrack* pNT;
			DefNoteTrack* pDNT;
			NoteKey* pNK;

			NumNT = node->NumNoteTracks();

			// count all of the notes on all of the notetracks
			NumNotes = 0;
			for(n=0;n<NumNT;n++){
				pNT = node->GetNoteTrack(n);
				if(pNT->ClassID() == Class_ID(NOTETRACK_CLASS_ID, 0)){
					pDNT = (DefNoteTrack*)pNT;
					j = pDNT->NumKeys();
					for(i=0;i<j;i++){
						pNK = pDNT->keys[i];
						if( (pNK->time >= FirstTick) && (pNK->time <= LastTick) )
							NumNotes++;
					}
				}
			}

			sprintf(S, "Number of Notes = %d", NumNotes);
			F.Wstring(S);

			for(n=0;n<NumNT;n++){
				pNT = node->GetNoteTrack(n);
				if(pNT->ClassID() == Class_ID(NOTETRACK_CLASS_ID, 0)){
					pDNT = (DefNoteTrack*)pNT;
					j = pDNT->NumKeys();
					for(i=0;i<j;i++){
						pNK = pDNT->keys[i];
						if( (pNK->time >= FirstTick) && (pNK->time <= LastTick) ){
							sprintf(S, "%d: %s", (pNK->time - FirstTick) / TicksPerFrame, pNK->note);
							F.Wstring(S);
						}
					}
				}
			}
		}

		for(t=FirstTick;t<=LastTick;t+=TicksPerFrame){
			tm = node->GetNodeTM(t);
			DecomposeMatrix(tm, tp, qq, sp);
			qq.MakeMatrix	(tm);
			tm.SetTrans		(tp);

			parent = node->GetParentNode();
			if(parent){
				tmp = parent->GetNodeTM(t);
				DecomposeMatrix(tmp, tp, qq, sp);
				qq.MakeMatrix(tmp);
				tmp.SetTrans(tp);
				tmp = Inverse(tmp);
				tm *= tmp;
			}

			DecomposeMatrix(tm, tp, qq, sp);
			sprintf(S,"%f %f %f %f",qq.x, qq.y, qq.z, qq.w);
			F.Wstring(S);
			sprintf(S,"%f %f %f",tp.x,tp.y,tp.z);
			F.Wstring(S);

			/*
			// Euler angles
			Point3			E;
			QuatToEuler		(Quat(tm), E);
			fprintf			(f,"%f %f %f",E.x,E.y,E.z);

			// Translate
			DecomposeMatrix	(tm, tp, qq, sp);
			fprintf			(f,"%f %f %f",tp.x,tp.y,tp.z);
			*/

//			Matrix3fprint(f, tm);
		}
		Current = Current->next;
	}

	sprintf(S, "Key Data Complete");
	F.Wstring(S);
	ELog.Msg(mtInformation,S);

	F.SaveTo(n,0);

	return true;
}
コード例 #5
0
ファイル: export_scene.cpp プロジェクト: mixxit/solinia
CInstanceGroup*	CExportNel::buildInstanceGroup(const vector<INode*>& vectNode, vector<INode*>& resultInstanceNode, TimeValue tvTime)
{
	// Extract from the node the name, the transformations and the parent

	CInstanceGroup::TInstanceArray aIGArray;
	uint32 i, nNumIG;
	uint32 j,k,m;

	aIGArray.empty ();
	resultInstanceNode.empty ();
	aIGArray.resize (vectNode.size());
	resultInstanceNode.resize (vectNode.size());

	int nNbInstance = 0;
	for (i = 0; i < vectNode.size(); ++i)
	{
		INode *pNode = vectNode[i];

		int nAccelType = CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_ACCEL, 32);

		if ((nAccelType&3) == 0) // If not an accelerator
		if (!RPO::isZone (*pNode, tvTime))
		if (CExportNel::isMesh (*pNode, tvTime) || CExportNel::isDummy(*pNode, tvTime))
		{
			++nNbInstance;
		}
	}

	// Check integrity of the hierarchy and set the parents
	std::vector<INode*>::const_iterator it = vectNode.begin();
	nNumIG = 0;
	for (i = 0; i < (sint)vectNode.size(); ++i, ++it)
	{
		INode *pNode = *it;

		int nAccelType = CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_ACCEL, 32);

		if ((nAccelType&3) == 0) // If not an accelerator
		if (!RPO::isZone( *pNode, tvTime ))
		if (CExportNel::isMesh( *pNode, tvTime ) || CExportNel::isDummy(*pNode, tvTime))
		{
			aIGArray[nNumIG].DontAddToScene = CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_DONT_ADD_TO_SCENE, 0)?true:false;
			aIGArray[nNumIG].InstanceName = CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_INSTANCE_NAME, "");
			resultInstanceNode[nNumIG] = pNode;
			if (aIGArray[nNumIG].InstanceName == "") // no instance name was set, takes the node name instead
			{
				aIGArray[nNumIG].InstanceName = pNode->GetName();
			}

			// Visible? always true, but if special flag for camera collision
			sint	appDataCameraCol= CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_CAMERA_COLLISION_MESH_GENERATION, 0);
			aIGArray[nNumIG].Visible= appDataCameraCol!=3;


			INode *pParent = pNode->GetParentNode();

			// Set the DontCastShadow flag.
			aIGArray[nNumIG].DontCastShadow= pNode->CastShadows()==0;

			// Set the Special DontCastShadow flag.
			aIGArray[nNumIG].DontCastShadowForInterior= CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_LIGHT_DONT_CAST_SHADOW_INTERIOR, BST_UNCHECKED)?true:false;
			aIGArray[nNumIG].DontCastShadowForExterior= CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_LIGHT_DONT_CAST_SHADOW_EXTERIOR, BST_UNCHECKED)?true:false;

			// Is the pNode has the root node for parent ?
			if( pParent->IsRootNode() == 0 )
			{
				// Look if the parent is in the selection
				int nNumIG2 = 0;
				for (j = 0; j < vectNode.size(); ++j)
				{
					INode *pNode2 = vectNode[j];

					int nAccelType2 = CExportNel::getScriptAppData (pNode2, NEL3D_APPDATA_ACCEL, 32);
					if ((nAccelType2&3) == 0) // If not an accelerator
					if (!RPO::isZone( *pNode2, tvTime ))
					if (CExportNel::isMesh( *pNode2, tvTime ))
					{
						if (pNode2 == pParent)
							break;
						++nNumIG2;
					}
				}
				if (nNumIG2 == nNbInstance)
				{
					// The parent is not selected ! link to root
					aIGArray[nNumIG].nParent = -1;
				}
				else
				{
					aIGArray[nNumIG].nParent = nNumIG2;
				}
			}
			else
			{
				aIGArray[nNumIG].nParent = -1;
			}
			++nNumIG;
		}
	}
	aIGArray.resize( nNumIG );
	resultInstanceNode.resize( nNumIG );

	// Build the array of node
	vGlobalPos = CVector(0,0,0);
	nNumIG = 0;
	it = vectNode.begin();
	for (i = 0; i < (sint)vectNode.size(); ++i, ++it)
	{
		INode *pNode = *it;

		int nAccelType = CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_ACCEL, 32);

		if ((nAccelType&3) == 0) // If not an accelerator
		if (!RPO::isZone (*pNode, tvTime))
		if (CExportNel::isMesh (*pNode, tvTime) || CExportNel::isDummy(*pNode, tvTime))
		{
			CVector vScaleTemp;
			CQuat qRotTemp;
			CVector vPosTemp;

			// Get Nel Name for the object.
			aIGArray[nNumIG].Name= CExportNel::getNelObjectName(*pNode);

			//Get the local transformation matrix
			Matrix3 nodeTM = pNode->GetNodeTM(0);
			INode *pParent = pNode->GetParentNode();
			Matrix3 parentTM = pParent->GetNodeTM(0);
			Matrix3 localTM	= nodeTM*Inverse(parentTM);

			// Extract transformations
			CExportNel::decompMatrix (vScaleTemp, qRotTemp, vPosTemp, localTM);
			aIGArray[nNumIG].Rot   = qRotTemp;
			aIGArray[nNumIG].Pos   = vPosTemp;
			aIGArray[nNumIG].Scale = vScaleTemp;
			vGlobalPos += vPosTemp;
			++nNumIG;
		}
	}
	// todo Make this work (precision):
	/*
	vGlobalPos = vGlobalPos / nNumIG;
	for (i = 0; i < nNumIG; ++i)
		aIGArray[i].Pos -= vGlobalPos;
	*/

	vGlobalPos = CVector(0,0,0); // Temporary !!!

	// Accelerator Portal/Cluster part
	//=================

	// Creation of all the clusters
	vector<CCluster> vClusters;
	it = vectNode.begin();
	for (i = 0; i < (sint)vectNode.size(); ++i, ++it)
	{
		INode *pNode = *it;

		int nAccelType = CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_ACCEL, NEL3D_APPDATA_ACCEL_DEFAULT);
		bool bFatherVisible = nAccelType&NEL3D_APPDATA_ACCEL_FATHER_VISIBLE?true:false;
		bool bVisibleFromFather = nAccelType&NEL3D_APPDATA_ACCEL_VISIBLE_FROM_FATHER?true:false;
		bool bAudibleLikeVisible = (nAccelType&NEL3D_APPDATA_ACCEL_AUDIBLE_NOT_LIKE_VISIBLE)?false:true;
		bool bFatherAudible = bAudibleLikeVisible ? bFatherVisible : nAccelType&NEL3D_APPDATA_ACCEL_FATHER_AUDIBLE?true:false;
		bool bAudibleFromFather = bAudibleLikeVisible ? bVisibleFromFather : nAccelType&NEL3D_APPDATA_ACCEL_AUDIBLE_FROM_FATHER?true:false;

		if ((nAccelType&NEL3D_APPDATA_ACCEL_TYPE) == NEL3D_APPDATA_ACCEL_CLUSTER) // If cluster
		if (!RPO::isZone (*pNode, tvTime))
		if (CExportNel::isMesh(*pNode, tvTime))
		{
			CCluster clusterTemp;
			std::string temp;

			temp = CExportNel::getScriptAppData(pNode, NEL3D_APPDATA_SOUND_GROUP, "no sound");
			clusterTemp.setSoundGroup(temp != "no sound" ? temp : "");
			temp = CExportNel::getScriptAppData(pNode, NEL3D_APPDATA_ENV_FX, "no fx");
			clusterTemp.setEnvironmentFx(temp != "no fx" ? temp : "");

			CMesh::CMeshBuild *pMB;
			CMeshBase::CMeshBaseBuild *pMBB;
			pMB = createMeshBuild (*pNode, tvTime, pMBB);

			convertToWorldCoordinate( pMB, pMBB );

			for (j = 0; j < pMB->Faces.size(); ++j)
			{
				if (!clusterTemp.makeVolume (pMB->Vertices[pMB->Faces[j].Corner[0].Vertex],
											 pMB->Vertices[pMB->Faces[j].Corner[1].Vertex],
											 pMB->Vertices[pMB->Faces[j].Corner[2].Vertex]) )
				{
					// ERROR : The volume is not convex !!!
					char tam[256];
					sprintf(tam,"ERROR: The cluster %s is not convex.",vectNode[i]->GetName());
					//MessageBox(NULL,tam,"Error",MB_OK|MB_ICONERROR);
					nlwarning(tam);
				}
			}

			clusterTemp.FatherVisible = bFatherVisible;
			clusterTemp.VisibleFromFather = bVisibleFromFather;
			clusterTemp.FatherAudible = bFatherAudible;
			clusterTemp.AudibleFromFather = bAudibleFromFather;
			clusterTemp.Name = pNode->GetName();

			vClusters.push_back (clusterTemp);
			delete pMB;
			delete pMBB;
		}
	}

	// Creation of all the portals
	vector<CPortal> vPortals;
	it = vectNode.begin();
	for (i = 0; i < (sint)vectNode.size(); ++i, ++it)
	{
		INode *pNode = *it;

		int nAccelType = CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_ACCEL, 32);

		if ((nAccelType&3) == 1) // If Portal
		if (!RPO::isZone (*pNode, tvTime))
		if (CExportNel::isMesh(*pNode, tvTime))
		{
			CPortal portalTemp;
			std::string temp;

			temp = CExportNel::getScriptAppData(pNode, NEL3D_APPDATA_OCC_MODEL, "no occlusion");
			portalTemp.setOcclusionModel(temp != "no occlusion" ? temp : "");
			temp = CExportNel::getScriptAppData(pNode, NEL3D_APPDATA_OPEN_OCC_MODEL, "no occlusion");
			portalTemp.setOpenOcclusionModel(temp != "no occlusion" ? temp : "");

			CMesh::CMeshBuild *pMB;
			CMeshBase::CMeshBaseBuild *pMBB;
			pMB = createMeshBuild (*pNode, tvTime, pMBB);

			convertToWorldCoordinate( pMB, pMBB );

			vector<sint32> poly;
			vector<bool> facechecked;
			facechecked.resize (pMB->Faces.size());
			for (j = 0; j < pMB->Faces.size(); ++j)
				facechecked[j] = false;

			poly.push_back(pMB->Faces[0].Corner[0].Vertex);
			poly.push_back(pMB->Faces[0].Corner[1].Vertex);
			poly.push_back(pMB->Faces[0].Corner[2].Vertex);
			facechecked[0] = true;
			for (j = 0; j < pMB->Faces.size(); ++j)
			if (!facechecked[j])
			{
				bool found = false;

				for(k = 0; k < 3; ++k)
				{
					for(m = 0; m < poly.size(); ++m)
					{
						if ((pMB->Faces[j].Corner[k].Vertex == poly[m]) &&
							(pMB->Faces[j].Corner[(k+1)%3].Vertex == poly[(m+1)%poly.size()]))
						{
							found = true;
							break;
						}
						if ((pMB->Faces[j].Corner[(k+1)%3].Vertex == poly[m]) &&
							(pMB->Faces[j].Corner[k].Vertex == poly[(m+1)%poly.size()]))
						{
							found = true;
							break;
						}
					}
					if (found)
						break;
				}
				if (found)
				{
					// insert an empty space in poly between m and m+1
					poly.resize (poly.size()+1);
					for (uint32 a = poly.size()-2; a > m; --a)
						poly[a+1] = poly[a];
					poly[m+1] = pMB->Faces[j].Corner[(k+2)%3].Vertex;
					facechecked[j] = true;
					j = 0;
				}
			}
			vector<CVector> polyv;
			polyv.resize (poly.size());
			for (j = 0; j < poly.size(); ++j)
				polyv[j] = pMB->Vertices[poly[j]];
			
			if (!portalTemp.setPoly (polyv))
			{
				// ERROR : Poly not convex, or set of vertices not plane
				char tam[256];
				sprintf(tam,"ERROR: The portal %s is not convex.",vectNode[i]->GetName());
				//MessageBox(NULL,tam,"Error",MB_OK|MB_ICONERROR);
				nlwarning(tam);
			}

			if (nAccelType&16) // is dynamic portal ?
			{
				string InstanceName = CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_INSTANCE_NAME, "");
				if (!InstanceName.empty())
					portalTemp.setName (InstanceName);
				else
					portalTemp.setName (string(pNode->GetName()));
			}

			// Check if portal has 2 cluster
			int nNbCluster = 0;
			for (j = 0; j < vClusters.size(); ++j)
			{
				bool bPortalInCluster = true;
				for (k = 0; k < polyv.size(); ++k)
					if (!vClusters[j].isIn (polyv[k]) )
					{
						bPortalInCluster = false;
						break;
					}
				if (bPortalInCluster)
					++nNbCluster;
			}
			if (nNbCluster != 2)
			{
				// ERROR
				char tam[256];
				sprintf(tam,"ERROR: The portal %s has not 2 clusters but %d",vectNode[i]->GetName(), nNbCluster);
				//MessageBox(NULL,tam,"Error",MB_OK|MB_ICONERROR);
				nlwarning(tam);
			}


			vPortals.push_back (portalTemp);
			delete pMB;
			delete pMBB;
		}
	}

	// Link instance to clusters (an instance has a list of clusters)
	nNumIG = 0;
	it = vectNode.begin();
	for (i = 0; i < (sint)vectNode.size(); ++i, ++it)
	{
		INode *pNode = *it;

		int nAccelType = CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_ACCEL, 32);

		if ((nAccelType&3) == 0) // If not an accelerator
		if (!RPO::isZone (*pNode, tvTime))
		if (CExportNel::isMesh (*pNode, tvTime) || CExportNel::isDummy(*pNode, tvTime))
		{
			if (nAccelType&32) // Is the flag clusterize set ?
			{
				// Test against all clusters

				// The list of vertices used to test against cluster
				std::vector<NLMISC::CVector> *testVertices;
				std::vector<NLMISC::CVector>       FXVertices;  // Used only if the obj is a fx. It contains the corners of the bbox.
				bool  buildMeshBBox = true;

				/** If it is a mesh, we build its bbox and transform in world
				  * If it is a FX, we read its bbox from its shape
				  * If we can't read it, we use the bbox of the fx helper in max
				  */
				Object *obj = pNode->EvalWorldState(tvTime).obj;
				// Check if there is an object
				if (obj)
				{
					Class_ID  clid = obj->ClassID();
					// is the object a particle system ?					
					if (clid.PartA() == NEL_PARTICLE_SYSTEM_CLASS_ID)
					{
						// build the shape from the file name
						std::string objName = CExportNel::getNelObjectName(*pNode); 						
						if (!objName.empty())
						{											
							NL3D::CShapeStream ss;
							NLMISC::CIFile iF;
							if (iF.open(objName.c_str()))
							{
								try
								{								
									iF.serial(ss);
									NL3D::CParticleSystemShape *pss = dynamic_cast<NL3D::CParticleSystemShape *>(ss.getShapePointer());
									if (!pss)
									{
										nlwarning("ERROR: Node %s shape is not a FX", CExportNel::getName(*pNode).c_str());
									}
									else
									{									
										NLMISC::CAABBox bbox;
										pss->getAABBox(bbox);
										// transform in world
										Matrix3 xForm = pNode->GetNodeTM(tvTime);
										NLMISC::CMatrix nelXForm;
										CExportNel::convertMatrix(nelXForm, xForm);									
										bbox = NLMISC::CAABBox::transformAABBox(nelXForm, bbox);
										// store vertices of the bbox in the list
										FXVertices.reserve(8);
										for(uint k = 0; k < 8; ++k)
										{
											FXVertices.push_back(CVector(((k & 1) ? 1 : -1) * bbox.getHalfSize().x + bbox.getCenter().x,
																		 ((k & 2) ? 1 : -1) * bbox.getHalfSize().y + bbox.getCenter().y,
																		 ((k & 4) ? 1 : -1) * bbox.getHalfSize().z + bbox.getCenter().z));
										}
										//
										testVertices = &FXVertices;
										buildMeshBBox = false;
									}
									delete ss.getShapePointer();
								}
								catch (NLMISC::Exception &e)
								{
									nlwarning(e.what());									
								}
							}							
							if (buildMeshBBox)
							{
								nlwarning("ERROR: Can't get bbox of a particle system from its shape, using helper bbox instead");
							}
						}
					}
				}

				CMesh::CMeshBuild *pMB = NULL;
				CMeshBase::CMeshBaseBuild *pMBB = NULL;

				if (buildMeshBBox)
				{				
					pMB = createMeshBuild (*pNode, tvTime, pMBB);
					convertToWorldCoordinate( pMB, pMBB );
					testVertices = &pMB->Vertices;
				}

				for(k = 0; k < vClusters.size(); ++k)
				{
					bool bMeshInCluster = false;

					for(j = 0; j < testVertices->size(); ++j)
					{
						if (vClusters[k].isIn ((*testVertices)[j]))
						{
							bMeshInCluster = true;
							break;
						}
					}

					if (bMeshInCluster)
					{
						aIGArray[nNumIG].Clusters.push_back (k);
					}
				}
				
				// debug purpose : to remove
				if (vClusters.size() > 0)
				if (aIGArray[nNumIG].Clusters.size() == 0)
				{
					char tam[256];
					sprintf(tam,"ERROR: Object %s is not attached to any cluster\nbut his flag clusterize is set", pNode->GetName());
					//MessageBox(NULL, tam, "Warning", MB_OK);
					nlwarning(tam);
				}
				// debug purpose : to remove

				delete pMB;
				delete pMBB;
			}
			
			++nNumIG;
		}
		// debug purpose : to remove
		/*
		if ((nAccelType&3) == 0) // If not an accelerator
		if (!(nAccelType&32))
		{
			char tam[256];
			sprintf(tam,"Object %s is not clusterized", pNode->GetName());
			MessageBox(NULL, tam, "Info", MB_OK);
		}
		*/
		// debug purpose : to remove

	}


	// PointLight part
	//=================
	bool	sunLightEnabled= false;
	sint	nNumPointLight = 0;
	vector<CPointLightNamed>	pointLights;
	pointLights.resize(vectNode.size());
	// For all nodes
	for (i = 0; i < (sint)vectNode.size(); ++i)
	{
		INode *pNode = vectNode[i];

		SLightBuild		sLightBuild;

		// If it is a Max Light.
		if ( sLightBuild.canConvertFromMaxLight(pNode, tvTime) )
		{
			// And if this light is checked to realtime export
			int		nRTExport= CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_EXPORT_REALTIME_LIGHT, BST_CHECKED);
			if(nRTExport == BST_CHECKED)
			{
				// get Max Light info.
				sLightBuild.convertFromMaxLight(pNode, tvTime);

				// Skip if LightDir
				if(sLightBuild.Type != SLightBuild::LightDir)
				{
					// Fill PointLight Info.
					NL3D::CPointLightNamed	&plNamed= pointLights[nNumPointLight];

					// Position
					plNamed.setPosition(sLightBuild.Position);
					// Attenuation
					plNamed.setupAttenuation(sLightBuild.rRadiusMin, sLightBuild.rRadiusMax);
					// Colors
					// Ensure A=255 for localAmbient to work.
					NLMISC::CRGBA	ambient= sLightBuild.Ambient;
					ambient.A= 255;
					plNamed.setDefaultAmbient(ambient);
					plNamed.setAmbient(ambient);
					plNamed.setDefaultDiffuse(sLightBuild.Diffuse);
					plNamed.setDiffuse(sLightBuild.Diffuse);
					plNamed.setDefaultSpecular(sLightBuild.Specular);
					plNamed.setSpecular(sLightBuild.Specular);

					// GroupName.
					plNamed.AnimatedLight = sLightBuild.AnimatedLight;
					plNamed.LightGroup = sLightBuild.LightGroup;

					// Which light type??
					if(sLightBuild.bAmbientOnly || sLightBuild.Type== SLightBuild::LightAmbient)
					{
						plNamed.setType(CPointLight::AmbientLight);
						// Special ambient info
						int		nRTAmbAdd= CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_REALTIME_AMBIENT_ADD_SUN, BST_UNCHECKED);
						plNamed.setAddAmbientWithSun(nRTAmbAdd==BST_CHECKED);
					}
					else if(sLightBuild.Type== SLightBuild::LightPoint)
					{
						plNamed.setType(CPointLight::PointLight);
					}
					else if(sLightBuild.Type== SLightBuild::LightSpot)
					{
						plNamed.setType(CPointLight::SpotLight);
						// Export Spot infos.
						plNamed.setupSpotDirection(sLightBuild.Direction);
						plNamed.setupSpotAngle(sLightBuild.rHotspot, sLightBuild.rFallof);
					}
					else
					{
						// What???
						nlstop;
					}


					// inc Size
					++nNumPointLight;
				}
			}

			// if this light is a directionnal and checked to export as Sun Light
			int		nExportSun= CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_EXPORT_AS_SUN_LIGHT, BST_UNCHECKED);
			if(nExportSun== BST_CHECKED)
			{
				// get Max Light info.
				sLightBuild.convertFromMaxLight(pNode, tvTime);

				// Skip if not dirLight.
				if(sLightBuild.Type == SLightBuild::LightDir)
					sunLightEnabled= true;
			}
		}
	}
	// Good size
	pointLights.resize(nNumPointLight);


	// Build the ig
	//=================

	CInstanceGroup* pIG = new CInstanceGroup;

	// Link portals and clusters and create meta cluster if one
	pIG->build (vGlobalPos,  aIGArray, vClusters, vPortals, pointLights);

	// IG touched by sun ??
	pIG->enableRealTimeSunContribution(sunLightEnabled);

	return pIG;
}
コード例 #6
0
int
CMaxAnimationImport::DoImport(
  const TCHAR* name,
  ImpInterface* ii,
  Interface* i,
  BOOL suppressPrompts)
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState());

  HWND window = i->GetMAXHWnd();

  CFileDialog fileDialog(TRUE, "xsf", 0, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
                         0, CWnd::FromHandle(window));
  if (fileDialog.DoModal() != IDOK) {
    return IMPEXP_CANCEL;
  }

  CString skeleton = fileDialog.GetPathName();
  
	cal3d::RefPtr<CalCoreSkeleton> skel = CalLoader::loadCoreSkeleton(std::string(skeleton));
  if (!skel) {
    MessageBox(
      window, "Loading skeleton file failed",
      "Import Cal3D Animation", MB_OK | MB_ICONERROR);
    return IMPEXP_FAIL;
  }

	cal3d::RefPtr<CalCoreAnimation> anim = CalLoader::loadCoreAnimation(name);
  if (!anim) {
    MessageBox(
      window, "Loading animation file failed",
      "Import Cal3D Animation", MB_OK | MB_ICONERROR);
    return IMPEXP_FAIL;
  }

  typedef std::list<CalCoreTrack*> CoreTrackList;
  CoreTrackList& trackList = anim->getListCoreTrack();
  for (CoreTrackList::iterator itr = trackList.begin(); itr != trackList.end(); ++itr) {
    CalCoreTrack* track = *itr;

    int boneId = track->getCoreBoneId();
    CalCoreBone* bone = skel->getCoreBone(boneId);
    if (!bone) continue;

    INode* node = i->GetINodeByName(bone->getName().c_str());
    if (!node) continue;

    unsigned kfCount = track->getCoreKeyframeCount();

    SuspendAnimate();
    AnimateOn();
    for (unsigned i = 0; i < kfCount; ++i) {
      CalCoreKeyframe* kf = track->getCoreKeyframe(i);
      CalQuaternion kf_q = kf->getRotation();
      CalVector     kf_v = kf->getTranslation();
      TimeValue     time = SecToTicks(kf->getTime());

      Matrix3 tm;
      tm.IdentityMatrix();
      Quat(kf_q.x, kf_q.y, kf_q.z, kf_q.w).MakeMatrix(tm);
      tm.SetTrans(Point3(kf_v.x, kf_v.y, kf_v.z));

      INode* parent = node->GetParentNode();
      if (parent) {
        tm *= parent->GetNodeTM(time);
      }

      node->SetNodeTM(time, tm);
    }
    ResumeAnimate();

/*
    typedef std::map<float, CalCoreKeyframe*> KeyMap;
    KeyMap& keys = track->getMapCoreKeyframe();

    int mapsize = sizeof(keys);

    int size = keys.size();

    int idx = 0;
    for (KeyMap::iterator mi = keys.begin(); mi != keys.end(); ++mi) {
      Point3 p;
      CalCoreKeyframe* kf = mi->second;
      p.x = kf->getTranslation().x;
      p.y = kf->getTranslation().y;
      p.z = kf->getTranslation().z;
      pos->SetValue(SecToTicks(mi->first), &p);
    }
*/

/*
    IKeyControl* kc = GetKeyControlInterface(pos);
    if (!kc) continue;

    typedef std::map<float, CalCoreKeyframe*> KeyMap;
    KeyMap& keys = track->getMapCoreKeyframe();
    kc->SetNumKeys(keys.size());

    int idx = 0;
    for (KeyMap::iterator mi = keys.begin(); mi != keys.end(); ++mi) {
      ITCBPoint3Key key;
      key.time = SecToTicks(mi->first);
      key.tens = 0;
      key.cont = 0;
      key.bias = 0;
      key.easeIn = 25.0;
      key.easeOut = 25.0;
      key.val.x = mi->second->getTranslation().x;
      key.val.y = mi->second->getTranslation().y;
      key.val.z = mi->second->getTranslation().z;

      kc->SetKey(idx++, &key);
    }

    kc->SortKeys();
*/
  }
 
  return IMPEXP_SUCCESS;
}
コード例 #7
0
void NifImporter::ImportBones(NiNodeRef node, bool recurse)
{
   try 
   {
      if (uncontrolledDummies)
         BuildControllerRefList(node, ctrlCount);

      string name = node->GetName();

      vector<NiAVObjectRef> children = node->GetChildren();
      vector<NiNodeRef> childNodes = DynamicCast<NiNode>(children);

      NiAVObject::CollisionType cType = node->GetCollisionMode();
      if (children.empty() && name == "Bounding Box")
         return;

      // Do all node manipulations here
      NiNodeRef parent = node->GetParent();
      string parentname = (parent ? parent->GetName() : "");
      Matrix44 m4 = node->GetWorldTransform();

      // Check for Prn strings and change parent if necessary
      if (supportPrnStrings) {
         list<NiStringExtraDataRef> strings = DynamicCast<NiStringExtraData>(node->GetExtraData());
         for (list<NiStringExtraDataRef>::iterator itr = strings.begin(); itr != strings.end(); ++itr){
            if (strmatch((*itr)->GetName(), "Prn")) {
               parentname = (*itr)->GetData();
               if (INode *pn = gi->GetINodeByName(parentname.c_str())){
                  // Apparently Heads tend to need to be rotated 90 degrees on import for 
                  if (!rotate90Degrees.empty() && wildmatch(rotate90Degrees, parentname)) {
                     m4 *= TOMATRIX4(RotateYMatrix(TORAD(90)));
                  }
                  m4 *= TOMATRIX4(pn->GetObjTMAfterWSM(0, NULL));
               }
            }
         }
      }

      float len = node->GetLocalTranslation().Magnitude();

      // Remove NonAccum nodes and merge into primary bone
      if (mergeNonAccum && wildmatch("* NonAccum", name) && parent)
      {
         string realname = name.substr(0, name.length() - 9);
         if (strmatch(realname, parent->GetName()))
         {
            Matrix44 tm = parent->GetLocalTransform() * node->GetLocalTransform();
            name = realname;
            len += tm.GetTranslation().Magnitude();
			parent = parent->GetParent();
         }
      }

      PosRotScale prs = prsDefault;
      Vector3 pos; Matrix33 rot; float scale;
      m4.Decompose(pos, rot, scale);

      Matrix3 im = TOMATRIX3(m4);
      Point3 p = im.GetTrans();
      Quat q(im);
      //q.Normalize();
      Vector3 ppos;
      Point3 zAxis(0,0,0);
      bool hasChildren = !children.empty();
      if (hasChildren) {
         float len = 0.0f;
         for (vector<NiAVObjectRef>::iterator itr=children.begin(), end = children.end(); itr != end; ++itr) {
            len += GetObjectLength(*itr);
         }
         len /= float(children.size());
         ppos = pos + Vector3(len, 0.0f, 0.0f); // just really need magnitude as rotation will take care of positioning
      }
      else if (parent)
      {
         ppos = pos + Vector3(len/3.0f, 0.0f, 0.0f);
      }
      Point3 pp(ppos.x, ppos.y, ppos.z);

      Point3 qp = TORAD(TOEULER(im));


      INode *bone = NULL;
	  if (!doNotReuseExistingBones) // Games like BC3 reuse the same bone names
	  {
		  bone = FindNode(node);
		  if (bone == NULL) 
			  bone = gi->GetINodeByName(name.c_str());
	  }
      if (bone)
      {
         // Is there a better way of "Affect Pivot Only" behaviors?
         INode *pinode = bone->GetParentNode();
         if (pinode)
            bone->Detach(0,1);
         PosRotScaleNode(bone, p, q, scale, prs);
         if (pinode)
            pinode->AttachChild(bone, 1);
      }
      else
      {
         bool isDummy = ( (uncontrolledDummies && !HasControllerRef(ctrlCount, name))
                     || (!dummyNodeMatches.empty() && wildmatch(dummyNodeMatches, name))
                     || (convertBillboardsToDummyNodes && node->IsDerivedType(NiBillboardNode::TYPE))
                      );
         if (wildmatch("Camera*", name)) {
            if (enableCameras) {
               if (bone = CreateCamera(name)) {
                  PosRotScaleNode(bone, p, q, scale, prs);
                  bone->Hide(node->GetVisibility() ? FALSE : TRUE);
               }
            }
         }else if (isDummy && createNubsForBones)
            bone = CreateHelper(name, p);
         else if (bone = CreateBone(name, p, pp, zAxis))
         {
            PosRotScaleNode(bone, p, q, scale, prs);
            bone->Hide(node->GetVisibility() ? FALSE : TRUE);
         }
         if (bone)
         {
            if (!parentname.empty())
            {
               if (mergeNonAccum && wildmatch("* NonAccum", parentname)) {
                  parentname = parentname.substr(0, parentname.length() - 9);
               }
               if (INode *pn = gi->GetINodeByName(parentname.c_str()))
                  pn->AttachChild(bone, 1);
            }
			RegisterNode(node, bone);
         }
      }
      // Import UPB
      if (bone) ImportUPB(bone, node);

      // Import Havok Collision Data surrounding node,  
	  //   unfortunately this causes double import of collision so I'm disabling it for now.
	  if (enableCollision && node->GetParent()) {
		ImportCollision(node);
	  }

      if (bone && recurse)
      {
         ImportBones(childNodes);
      }
   }
   catch( exception & e ) 
   {
      e=e;
   }
   catch( ... ) 
   {
   }
}
コード例 #8
0
void NifImporter::AlignBiped(IBipMaster* master, NiNodeRef node)
{
#ifdef USE_BIPED
   NiNodeRef parent = node->GetParent();
   string name = node->GetName();
   vector<NiAVObjectRef> children = node->GetChildren();
   vector<NiNodeRef> childNodes = DynamicCast<NiNode>(children);

   TSTR s1 = FormatText("Processing %s:", name.c_str());
   TSTR s2 = FormatText("Processing %s:", name.c_str());
   INode *bone = GetNode(node);
   if (bone != NULL) 
   {
      if (uncontrolledDummies)
         BuildControllerRefList(node, ctrlCount);

      Matrix44 m4 = node->GetWorldTransform();
      Vector3 pos; Matrix33 rot; float scale;
      m4.Decompose(pos, rot, scale);
      Matrix3 m = TOMATRIX3(m4);
      Point3 p = m.GetTrans();
      Quat q(m);

      s1 += FormatText(" ( %s)", PrintMatrix3(m).data());
      if (strmatch(name, master->GetRootName()))
      {
         // Align COM
         //PosRotScaleNode(bone, p, q, 1.0f, prsPos);
         PosRotScaleBiped(master, bone, p, q, 1.0f, prsPos);
      }
      else if (INode *pnode = bone->GetParentNode())
      {
         // Reparent if necessary
         if (!strmatch(parent->GetName(), pnode->GetName())) {
            if (pnode = FindNode(parent)) {
               bone->Detach(0);
               pnode->AttachChild(bone);
            }
         }

         // Hack to scale the object until it fits
         for (int i=0; i<10; ++i) {
            float s = CalcScale(bone, node, childNodes);
            if (fabs(s-1.0f) < (FLT_EPSILON*100.0f))
               break;
            s1 += FormatText(" (%g)", s);
            master->SetBipedScale(TRUE, ScaleValue(Point3(s,s,s)), 0, bone);
         }
         PosRotScale prs = prsDefault;
         PosRotScaleBiped(master, bone, p, q, scale, prs);

         // Rotation with Clavicle is useless in Figure Mode using the standard interface
         //   I was tring unsuccessfully to correct for it
         //if (wildcmpi("Bip?? ? Clavicle", name.c_str())) {
         //   AngAxis a1 = CalcTransform(bone, node, childNodes);
         //   Matrix3 tm1 = GenerateRotMatrix(a1);
         //   Quat nq = TransformQuat(tm1, q);
         //   PosRotScaleNode(bone, p, nq, scale, prsRot);
         //}
      }
      s2 += FormatText(" ( %s)", PrintMatrix3(bone->GetNodeTM(0)).data());
   }
   else
   {
      ImportBones(node, false);
   }
   for (char *p = s1; *p != 0; ++p) if (isspace(*p)) *p = ' ';
   for (char *p = s2; *p != 0; ++p) if (isspace(*p)) *p = ' ';
   OutputDebugString(s1 + "\n");
   OutputDebugString(s2 + "\n");

   for (vector<NiNodeRef>::iterator itr = childNodes.begin(), end = childNodes.end(); itr != end; ++itr){
      AlignBiped(master, *itr);
   }
#endif
}
コード例 #9
0
int
CMaxAnimationImport::DoImport(
  const TCHAR* name,
  ImpInterface* ii,
  Interface* i,
  BOOL suppressPrompts)
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState());

  HWND window = i->GetMAXHWnd();

  CFileDialog fileDialog(TRUE, "xsf", 0, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
                         0, CWnd::FromHandle(window));
  if (fileDialog.DoModal() != IDOK) {
    return IMPEXP_CANCEL;
  }

  CString skeleton = fileDialog.GetPathName();
  
  CalCoreSkeletonPtr skel = CalLoader::loadCoreSkeleton(std::string(skeleton));
  if (!skel) {
    MessageBox(
      window, "Loading skeleton file failed",
      "Import Cal3D Animation", MB_OK | MB_ICONERROR);
    return IMPEXP_FAIL;
  }

  CalCoreAnimationPtr anim = CalLoader::loadCoreAnimation(name);
  if (!anim) {
    MessageBox(
      window, "Loading animation file failed",
      "Import Cal3D Animation", MB_OK | MB_ICONERROR);
    return IMPEXP_FAIL;
  }

  // Get the pose information in the animation
  const std::vector<CalTransform>& poses = anim->getPoses();
  unsigned int num_poses = poses.size() / anim->getTrackCount();

  // Calculate the time_per_frame incorrectly since the duration for animations
  // is stored incorrectly.
  float time_per_frame = anim->getDuration() / num_poses;

  // Import each track
  for (unsigned track_id = 0; track_id < anim->getTrackCount(); ++track_id)
  {
    // Get the core bone mapped to the animation
    int bone_id = anim->getBoneAssignment(track_id);
    CalCoreBone* bone = skel->getCoreBone(bone_id);
    if (!bone) continue;

    // Get the max node for the bone
    INode* node = i->GetINodeByName(bone->getName().c_str());
    if (!node) continue;

    SuspendAnimate();
    AnimateOn();

    // Add each pose keyframe in the track
    float keyframe_time = 0.0f;
    for (unsigned keyframe_index = 0; keyframe_index < num_poses; ++keyframe_index)
    {
      // Get the keyframe data
      const CalTransform& pose_coord_sys = poses[(keyframe_index * anim->getTrackCount()) + track_id];
      const CalVector     &kf_v = pose_coord_sys.getTranslation();
      const CalQuaternion &kf_q = pose_coord_sys.getRotation();
      TimeValue time = SecToTicks(keyframe_time);

      // Convert to Max math
      Matrix3 tm;
      tm.IdentityMatrix();
      Quat(kf_q.x, kf_q.y, kf_q.z, kf_q.w).MakeMatrix(tm);
      tm.SetTrans(Point3(kf_v.x, kf_v.y, kf_v.z));

      // Convert the transform to world space
      INode* parent = node->GetParentNode();
      if (parent)
      {
        tm *= parent->GetNodeTM(time);
      }

      // Set the new transform on the node
      node->SetNodeTM(time, tm);

      keyframe_time += time_per_frame;
    }

    ResumeAnimate();

/*
    typedef std::map<float, CalCoreKeyframe*> KeyMap;
    KeyMap& keys = track->getMapCoreKeyframe();

    int mapsize = sizeof(keys);

    int size = keys.size();

    int idx = 0;
    for (KeyMap::iterator mi = keys.begin(); mi != keys.end(); ++mi) {
      Point3 p;
      CalCoreKeyframe* kf = mi->second;
      p.x = kf->getTranslation().x;
      p.y = kf->getTranslation().y;
      p.z = kf->getTranslation().z;
      pos->SetValue(SecToTicks(mi->first), &p);
    }
*/

/*
    IKeyControl* kc = GetKeyControlInterface(pos);
    if (!kc) continue;

    typedef std::map<float, CalCoreKeyframe*> KeyMap;
    KeyMap& keys = track->getMapCoreKeyframe();
    kc->SetNumKeys(keys.size());

    int idx = 0;
    for (KeyMap::iterator mi = keys.begin(); mi != keys.end(); ++mi) {
      ITCBPoint3Key key;
      key.time = SecToTicks(mi->first);
      key.tens = 0;
      key.cont = 0;
      key.bias = 0;
      key.easeIn = 25.0;
      key.easeOut = 25.0;
      key.val.x = mi->second->getTranslation().x;
      key.val.y = mi->second->getTranslation().y;
      key.val.z = mi->second->getTranslation().z;

      kc->SetKey(idx++, &key);
    }

    kc->SortKeys();
*/
  }
  

  return IMPEXP_SUCCESS;
}
コード例 #10
0
ファイル: export_skinning.cpp プロジェクト: CCChaos/RyzomCore
// ***************************************************************************
void CExportNel::getNELBoneLocalTM(INode &node, TimeValue time,
		NLMISC::CVector &nelScale, NLMISC::CQuat &nelQuat, NLMISC::CVector &nelPos)
{
	// get the Max local pos.
	Matrix3 localTM (TRUE);
	getLocalMatrix (localTM, node, time);
	NLMISC::CVector		maxScale;
	NLMISC::CQuat		maxRot;
	NLMISC::CVector		maxPos;
	decompMatrix (maxScale, maxRot, maxPos, localTM);

	// get bone Rot.
	//=============

	// Same Rot.
	nelQuat= maxRot;
	nelQuat.normalize();


	// get Bone Scale.
	//=============

	// Get the NEL local scale.
	getNELBoneLocalScale(node, time, nelScale);

	// get bone Pos.
	//=============

	// Default is same pos.
	nelPos= maxPos;

	/* The following is important To have correct NEL position
		Let me explain: 
			- Biped bones NodeTM have correct World Position, but NodeTM.Scale == 1,1,1 (always)
				We can get the bone local scale from OffsetScale, this why we need a Reference Node, to perform the 
				difference (because the Reference Node ofsset scale is not a 1,1,1 scale)
			- Nel bones Pos are the LOCAL pos, and they INHERIT parent scale (like in Maya).
				Nel UnheritScale is used to unherit the scale from father to the rotation/scale only part of the son.
		The problem is that if we export Position default pos from the current scaled "node", we will have the 
		"already scaled from father Local Pos" from Biped. Instead we want the "not scaled from father Local Pos"
		because NEL will do the "scaled from father" job in RealTime.
		
		So to get the correct local Position, we must remove the fahter Nel Local Scale.
	*/

	// If my father is a biped bone, then do special stuff.
	INode	*parentNode= node.GetParentNode();
	// NB: don't need to test If I must unherit scale, because sons of Biped nodes always unherit scale...
	if(parentNode && isBipedNode(*parentNode))
	{
		/* In this case, I must remove my father Nel scale, because the biped NodeTM has a scale 1,1,1.
			Hence the Local position, computed with getLocalMatrix(), we have is false.
		*/
		CVector		fatherScale;
		// NB: avoid calling getNELBoneLocalTM() for father else Recursive call untill to the root !!!
		// We need just the scale here.
		getNELBoneLocalScale(*parentNode, time, fatherScale);
		nelPos.x/= fatherScale.x;
		nelPos.y/= fatherScale.y;
		nelPos.z/= fatherScale.z;
	}
}