void Unreal3DExport::WriteTracking()
{
    Tab<Point3> Loc;
    Tab<Quat> Quat;
    Tab<Point3> Euler;

    Loc.SetCount(FrameCount);
    Quat.SetCount(FrameCount);
    Euler.SetCount(FrameCount);

    for( int n=0; n<TrackedNodes.Count(); ++n )
    {
        IGameNode* node = TrackedNodes[n];

        for( int t=0; t<FrameCount; ++t )
        {            
            // Progress
            CheckCancel();
            
            // Set frame
            int curframe = FrameStart + t;
            pScene->SetStaticFrame(curframe);

            // Write tracking
            GMatrix objTM = node->GetWorldTM();
            Loc[t] = objTM.Translation();
            Quat[t] = objTM.Rotation();

            float eu[3];
            QuatToEuler(Quat[t],eu);
            Euler[t]=Point3(eu[0],eu[1],eu[2]);
            Euler[t] *= 180.0f/pi;

            eu[1] *= -1;
            EulerToQuat(eu,Quat[t],EULERTYPE_YXZ);
        }
        
        for( int t=0; t<FrameCount; ++t )
        {    
            _ftprintf( fLog, _T("%sLoc[%d]=(X=%f,Y=%f,Z=%f)\n"), node->GetName(), t, Loc[t].x, Loc[t].y, Loc[t].z );
        }
        
        for( int t=0; t<FrameCount; ++t )
        {    
            _ftprintf( fLog, _T("%sQuat[%d]=(W=%f,X=%f,Y=%f,Z=%f)\n"), node->GetName(), t, Quat[t].w, Quat[t].x, Quat[t].y, Quat[t].z ); 
        }
        
        for( int t=0; t<FrameCount; ++t )
        {    
            _ftprintf( fLog, _T("%sEuler[%d]=(X=%f,Y=%f,Z=%f)\n"), node->GetName(), t, Euler[t].x, Euler[t].y, Euler[t].z ); 
        }
    }
}
예제 #2
0
ExporterF3D::EExportError ExporterF3D::CreateModel()
{
    m_scene = new CSceneData();

    s32 nodeCount = m_iGameScene->GetTopLevelNodeCount();
    LOG_INFO("In Game Scene found [%d] Nodes", nodeCount);

    if (nodeCount == 0)
    {
        return eSceneEmptyError;
    }

    s32 id = 0;
    m_scene->setId(id);
    m_scene->setName("");
    LOG_INFO("Game Scene Name %s, id %d", TCHARToString(m_iGameScene->GetSceneFileName()).c_str(), id);

    for (u32 objectIndex = 0; objectIndex < nodeCount; ++objectIndex)
    {
        IGameNode* gameNode = m_iGameScene->GetTopLevelNode(objectIndex);
        LOG_INFO("Processing Parent object [%d/%d:%s] ", objectIndex + 1, nodeCount, TCHARToString(gameNode->GetName()).c_str());

        EExportError error = ExportNode(gameNode, objectIndex);
        if (error != eNoError)
        {
            return error;
        }
    }

    return eNoError;
}
	bool MeshXMLExporter::Export(OutputMap& output) 
	{

		try {

			// write XML to a strstream
			std::stringstream of;

			while (!m_submeshNames.empty())
				m_submeshNames.pop();

			if (m_pGame) {
				m_pGame->ReleaseIGame();
				m_pGame = 0;
			}

			m_ei->theScene->EnumTree(this);

			m_pGame = GetIGameInterface();
			IGameConversionManager* cm = GetConversionManager();
			cm->SetCoordSystem(IGameConversionManager::IGAME_OGL);
			m_pGame->InitialiseIGame(m_nodeTab, false);
			m_pGame->SetStaticFrame(0);
			int nodeCount = m_pGame->GetTopLevelNodeCount();

			if (nodeCount == 0) {
				MessageBox(GetActiveWindow(), "No nodes available to export, aborting...", "Nothing To Export", MB_ICONINFORMATION);
				m_pGame->ReleaseIGame();
				return false;
			}

			// if we are writing everything to one file, use the name provided when the user first started the export;
			// otherwise, create filenames on the basis of the node (submesh) names
			std::string fileName;
			IGameNode* node = m_pGame->GetTopLevelNode(0);
			if (!m_config.getExportMultipleFiles())
				fileName = m_config.getExportFilename();
			else {
				fileName = m_config.getExportPath() + "\\";
				fileName += node->GetName();
				fileName += ".mesh.xml";
			}

			// write start of XML data
			streamFileHeader(of);

			int nodeIdx = 0;
			std::map<std::string, std::string> materialScripts;

			while (nodeIdx < nodeCount) {

				std::string mtlName;
				IGameNode* node = m_pGame->GetTopLevelNode(nodeIdx);
				IGameObject* obj = node->GetIGameObject();

				// InitializeData() is important -- it performs all of the WSM/time eval for us; no data without it
				obj->InitializeData();
				
				IGameMaterial* mtl = node->GetNodeMaterial();
				if (mtl == NULL)
					mtlName = m_config.getDefaultMaterialName();
				else
					mtlName = mtl->GetMaterialName();

				// clean out any spaces the user left in their material name
				std::string::size_type pos;
				while ((pos = mtlName.find_first_of(' ')) != std::string::npos)
					mtlName.replace(pos, 1, _T("_"));

				if (materialScripts.find(mtlName) == materialScripts.end()) {

					// export new material script
					MaterialExporter mexp(m_config, m_materialMap);
					std::string script;

					mexp.buildMaterial(mtl, mtlName, script);
					materialScripts[mtlName] = script;
				}

				//if (streamSubmesh(of, node, mtlName))
				if (streamSubmesh(of, obj, mtlName))
					m_submeshNames.push(std::string(node->GetName()));

				node->ReleaseIGameObject();
				nodeIdx++;

				if (m_config.getExportMultipleFiles() || nodeIdx == nodeCount) {

					// write end of this file's XML
					streamFileFooter(of);

					// insert new filename --> stream pair into output map
					output[fileName] = std::string(of.str());
					of.str("");

					if (nodeIdx != nodeCount) {
						fileName = m_config.getExportPath() + "\\";
						node = m_pGame->GetTopLevelNode(nodeIdx);
						fileName += node->GetName();
						fileName += ".mesh.xml";

						// start over again with new data
						streamFileHeader(of);
					}
				}
			}

			m_pGame->ReleaseIGame();

			// export materials if we are writing those
			if (m_config.getExportMaterial()) {

				std::ofstream materialFile;
				materialFile.open((m_config.getExportPath() + "\\" + m_config.getMaterialFilename()).c_str(), std::ios::out);

				if (materialFile.is_open()) {
					for (std::map<std::string, std::string>::iterator it = materialScripts.begin();
						it != materialScripts.end(); ++it)
					{
						materialFile << it->second;
					}

					materialFile.close();
				}
			}

			return true;
		}
		catch (...) {
			MessageBox(GetActiveWindow(), "An unexpected error has occurred while trying to export, aborting", "Error", MB_ICONEXCLAMATION);

			if (m_pGame)
				m_pGame->ReleaseIGame();

			return false;
		}
	}
예제 #4
0
/**
*  @brief
*    Adds a 3ds Max node to the scene
*/
bool PLSceneContainer::AddIGameNode(IGameNode &cIGameNode)
{
	// Unknown node type by default
	EType nType = TypeUnknown;

	// Is there a '.' within the node name? If yes, replace it by '-'.
	String sName = cIGameNode.GetName();
	int nIndex = sName.IndexOf(".");
	if (nIndex >= 0) {
		g_pLog->LogFLine(PLLog::Warning, "Node name '%s' has '.' within it, '.' is replaced by '-' automatically", cIGameNode.GetName());
		char *pszName = const_cast<char*>(sName.GetASCII()) + nIndex;
		while (*pszName != '\0') {
			if (*pszName == '.')
				*pszName = '-';
			pszName++;
		}
	}
	const char *pszName = sName.GetASCII();

	// Look for 'cell_' (cell_<cell name>_<node name> or cell_<cell name>_<mesh name>_<instance name>)
	String sSceneCellName, sTargetSceneCellName, sSceneNodeName, sMeshName;
	if (!_strnicmp(pszName, "cell_", 5)) {
		// Get the name of the cell
		const char *pszNameT = pszName += 5;

		// Check for '\0'
		if (*pszNameT == '\0') {
			g_pLog->LogFLine(PLLog::Error, "Node name '%s' does not follow the name convention cell_<cell name>_<node name> or cell_<cell name>_<mesh name>_<instance name>. <cell name> is missing!", cIGameNode.GetName());
			sSceneCellName = "?";
			sMeshName	   = "?";
			sSceneNodeName = "?";
		} else {
			// Read the cell name
			while (*pszNameT != '_' && *pszNameT != '\0')
				pszNameT++;
			sSceneCellName.Insert(pszName, 0, pszNameT-pszName);

			// Check for '_'
			if (*pszNameT != '_') {
				g_pLog->LogFLine(PLLog::Error, "Node name '%s' does not follow the name convention cell_<cell name>_<node name> or cell_<cell name>_<mesh name>_<instance name>. <mesh name>/<instance name> is missing!", cIGameNode.GetName());
				sMeshName	   = "?";
				sSceneNodeName = "?";
			} else {
				// Skip '_'
				pszNameT++; 

				// Get the name of the node/mesh
				pszName = pszNameT;
				while (*pszNameT != '_' && *pszNameT != '\0')
					pszNameT++;
				sMeshName.Insert(pszName, 0, pszNameT-pszName);

				// Check for '_'
				if (*pszNameT != '_') {
					// Check for spaces within the mesh name
					CheckAndCorrectName(sMeshName, cIGameNode.GetName(), "mesh");

					// cell_<cell name>_<node name> is used so node name = mesh name
					sSceneNodeName = sMeshName;

					// Construct a 'save' mesh name later if the final scene node name is known...
					sMeshName = "";
				} else {
					// Skip '_'
					pszNameT++; 

					// Get the name of the scene node
					sSceneNodeName = sMeshName+"_";
					pszName = pszNameT;
					while (*pszNameT != '_' && *pszNameT != '\0')
						pszNameT++;
					sSceneNodeName.Insert(pszName, sSceneNodeName.GetLength(), pszNameT-pszName);

					// Is there an instance name? - No log hint because people find this behavior annoying...
				//	if (!(pszNameT-pszName))
				//		g_pLog->LogFLine(PLLog::Hint, "Node name '%s' does not follow the name convention cell_<cell name>_<mesh name>_<instance name>. <instance name> is missing!", cIGameNode.GetName());

					// Check for spaces and tabs within the mesh and node names
					CheckAndCorrectName(sMeshName,      cIGameNode.GetName(), "mesh");
					CheckAndCorrectName(sSceneNodeName, cIGameNode.GetName(), "node");
				}
			}
		}

		// Check for spaces/tabs within the cell name
		CheckAndCorrectName(sSceneCellName, cIGameNode.GetName(), "cell");

	// Look for 'portal_' (portal_<from cell>_<to cell>)
	} else if (!_strnicmp(pszName, "portal_", 7)) {
		// Get the name of the cell, the cell-portal is a scene node of this cell :)
		const char *pszNameT = pszName += 7;

		// Check for '\0'
		if (*pszNameT == '\0') {
			g_pLog->LogFLine(PLLog::Error, "Node name '%s' does not follow the name convention portal_<from cell>_<to cell>. <from cell> is missing!", cIGameNode.GetName());
			sSceneCellName       = "?";
			sTargetSceneCellName = "?";
		} else {
			// Read the cell name
			while (*pszNameT != '_' && *pszNameT != '\0')
				pszNameT++;
			sSceneCellName.Insert(pszName, 0, pszNameT-pszName);

			// Check for '_'
			if (*pszNameT != '_') {
				g_pLog->LogFLine(PLLog::Error, "Node name '%s' does not follow the name convention portal_<from cell>_<to cell>. <to cell> is missing!", cIGameNode.GetName());
				sTargetSceneCellName = "?";
			} else {
				// Skip '_'
				pszNameT++; 

				// Get the name of the target cell
				pszName = pszNameT;
				while (*pszNameT != '_' && *pszNameT != '\0')
					pszNameT++;
				sTargetSceneCellName.Insert(pszName, 0, pszNameT-pszName);
			}
		}

		// Check for spaces/tabs within the names
		CheckAndCorrectName(sSceneCellName,       cIGameNode.GetName(), "cell");
		CheckAndCorrectName(sTargetSceneCellName, cIGameNode.GetName(), "target cell");

		// Get the name of the scene node
		sSceneNodeName = "CellPortalTo_";
		sSceneNodeName += sTargetSceneCellName;

		// Get the 'real' target cell name - in our case, this cell MUST be within the parent container...
		sTargetSceneCellName = "Parent." + sTargetSceneCellName;

		// Set the correct scene node type
		nType = TypeCellPortal;

	// Look for 'antiportal_' (antiportal_<name>)
	} else if (!_strnicmp(pszName, "antiportal_", 11)) {
		// Get the name of the anti-portal)
		sSceneNodeName = String("AntiPortal_") + (pszName += 11);

		// Set the correct scene node type
		nType = TypeAntiPortal;

	// ...
	} else {
		// No log hint because people find this behavior annoying...
	//	g_pLog->LogFLine(PLLog::Hint, "Node name '%s' does not follow the name convention cell_<cell name>_<node name> or cell_<cell name>_<mesh name>_<instance name>. Node is added to the scene root.", cIGameNode.GetName());

		// Set scene node name
		sSceneNodeName = pszName;
	}

	// Check whether the scene cell and node names are valid
	if (sSceneCellName.CompareNoCase("This")) {
		sSceneCellName += '-';
		g_pLog->LogFLine(PLLog::Warning, "'This' is NOT allowed as node name. (3ds Max node '%s') Name is changed into '%s' ", cIGameNode.GetName(), sSceneCellName.GetASCII());
	}
	if (sSceneCellName.CompareNoCase("Root")) {
		sSceneCellName += '-';
		g_pLog->LogFLine(PLLog::Warning, "'Root' is NOT allowed as node name. (3ds Max node '%s') Name is changed into '%s' ", cIGameNode.GetName(), sSceneCellName.GetASCII());
	}
	if (sSceneCellName.CompareNoCase("Parent")) {
		sSceneCellName += '-';
		g_pLog->LogFLine(PLLog::Warning, "'Parent' is NOT allowed as node name. (3ds Max node '%s') Name is changed into '%s' ", cIGameNode.GetName(), sSceneCellName.GetASCII());
	}
	if (sSceneNodeName.CompareNoCase("This")) {
		sSceneNodeName += '-';
		g_pLog->LogFLine(PLLog::Warning, "'This' is NOT allowed as node name. (3ds Max node '%s') Name is changed into '%s' ", cIGameNode.GetName(), sSceneNodeName.GetASCII());
	}
	if (sSceneNodeName.CompareNoCase("Root")) {
		sSceneNodeName += '-';
		g_pLog->LogFLine(PLLog::Warning, "'Root' is NOT allowed as node name. (3ds Max node '%s') Name is changed into '%s' ", cIGameNode.GetName(), sSceneNodeName.GetASCII());
	}
	if (sSceneNodeName.CompareNoCase("Parent")) {
		sSceneNodeName += '-';
		g_pLog->LogFLine(PLLog::Warning, "'Root' is NOT allowed as node name. (3ds Max node '%s') Name is changed into '%s' ", cIGameNode.GetName(), sSceneNodeName.GetASCII());
	}

	// Get cell, this scene node is in
	PLSceneCell *pCell = GetCell(sSceneCellName, cIGameNode);

	// The exporter isn't case sensitive, but compare the 'real' cell names just for sure :)
	if (pCell && pCell->GetName() != sSceneCellName)
		g_pLog->LogFLine(PLLog::Warning, "Node '%s' is within the cell '%s', but '%s' was written -> It's recommended to take care of lower/upper case!", cIGameNode.GetName(), pCell->GetName().GetASCII(), sSceneCellName.GetASCII());

	// Get the container the new scene node is created in. If no cell was found, create the scene node
	// within THIS container.
	PLSceneContainer *pContainer = pCell ? pCell : this;

	// Check whether there's already an scene node with this name, if so, rename it and write a warning
	// into the log
	String sSceneNodeNameLower = sSceneNodeName;
	sSceneNodeNameLower.ToLower(); // Do ONLY use lower case, else the hashing will NOT return the same values!
	std::map<String, PLSceneNode*>::iterator pIterator = pContainer->m_mapNodes.find(sSceneNodeNameLower);
	if (pIterator != pContainer->m_mapNodes.end()) {
		String sNewName;
		int nConflictIndex = 1;

		// Find an unused scene node name
		do {
			sNewName = sSceneNodeName;
			sNewName += "_Conflict_";
			sNewName += PLTools::ToString(nConflictIndex);
			nConflictIndex++;
			sSceneNodeNameLower = sNewName;
			sSceneNodeNameLower.ToLower(); // Do ONLY use lower case, else the hashing will NOT return the same values!
			pIterator = pContainer->m_mapNodes.find(sSceneNodeNameLower);
		} while (pIterator != pContainer->m_mapNodes.end());

		// Write a log message
		g_pLog->LogFLine(PLLog::Warning, "'%s': There's already a scene node with the name '%s' within the container '%s' -> Changed name into '%s'",
			cIGameNode.GetName(), sSceneNodeName.GetASCII(), sSceneCellName.GetASCII(), sNewName.GetASCII());

		// Set the new name
		sSceneNodeName = sNewName;
	}

	// Construct a 'save' mesh name...
	if (!sMeshName.GetLength())
		sMeshName = sSceneCellName.GetLength() ? sSceneCellName + '_' + sSceneNodeName : sSceneNodeName;

	// Are there any children? If yes, we need to create a container for this node containing THIS node AND the children...
	if (cIGameNode.GetChildCount()) {
		PLSceneContainer *pNewContainer = new PLSceneContainer(pContainer, sSceneNodeName);

		// Register the new scene node
		pContainer->m_lstSceneNodes.push_back(pNewContainer);
		sSceneNodeNameLower = sSceneNodeName;
		sSceneNodeNameLower.ToLower(); // Do ONLY use lower case, else the hashing will NOT return the same values!
		pContainer->m_mapNodes.insert(std::make_pair(sSceneNodeNameLower, pNewContainer));

		// The new container becomes the current container
		pContainer = pNewContainer;

		// [TODO] Update the statistics
		// pContainer->m_sStatistics.nNumOfUnknown++;
		// GetScene().m_sSceneStatistics.nNumOfUnknown++;
	}

	// Create the scene node...
	PLSceneNode *pSceneNode = nullptr;
	INode *pMaxNode = cIGameNode.GetMaxNode();
	if (pMaxNode) {
		Object *pMaxObject = pMaxNode->GetObjectRef();
		if (pMaxObject) {
			// Get 'real' 3ds Max object (we really need to do this)
			while (pMaxObject->SuperClassID() == GEN_DERIVOB_CLASS_ID)
				pMaxObject = static_cast<IDerivedObject*>(pMaxObject)->GetObjRef();

			// Check the type of the object
			switch (pMaxObject->SuperClassID()) {
				case LIGHT_CLASS_ID:
					// Create the scene node
					pSceneNode = new PLSceneLight(*pContainer, cIGameNode, sSceneNodeName);

					// Update the statistics
					pContainer->m_sStatistics.nNumOfLights++;
					GetScene().m_sSceneStatistics.nNumOfLights++;
					break;

				case GEOMOBJECT_CLASS_ID:
					// Is this a cell-portal?
					if (nType == TypeCellPortal) {
						// Create the scene node
						pSceneNode = new PLSceneCellPortal(*pContainer, cIGameNode, sSceneNodeName, sTargetSceneCellName);

						// Update the number of outgoing cell-portals
						if (pContainer->GetType() == TypeCell) {
							static_cast<PLSceneCell*>(pContainer)->m_lstOutgoingCellPortals.push_back(static_cast<PLSceneCellPortal*>(pSceneNode));

						// ?? There's something totally wrong! ??
						} else {
							g_pLog->LogFLine(PLLog::Warning, "'%s': This cell-portal is within the container '%s', but cell-portals should only be within cells!",
											 cIGameNode.GetName(), sTargetSceneCellName.GetASCII());
						}

						// Update the statistics
						pContainer->m_sStatistics.nNumOfCellPortals++;
						GetScene().m_sSceneStatistics.nNumOfCellPortals++;

					// Is this a anti-portal?
					} else if (nType == TypeAntiPortal) {
						// Create the scene node
						pSceneNode = new PLSceneAntiPortal(*pContainer, cIGameNode, sSceneNodeName);

						// Update the statistics
						pContainer->m_sStatistics.nNumOfAntiPortals++;
						GetScene().m_sSceneStatistics.nNumOfAntiPortals++;

					} else {
						// [TODO] Add rename to mesh node...
						// Create the scene node
						pSceneNode = new PLSceneObject(*pContainer, cIGameNode, sSceneNodeName, GetScene().AddMesh(cIGameNode, sMeshName));

						// Update the statistics
						pContainer->m_sStatistics.nNumOfObjects++;
						GetScene().m_sSceneStatistics.nNumOfObjects++;
					}
					break;

				case SHAPE_CLASS_ID:
					// Create the scene node
					pSceneNode = new PLSceneSpline(*pContainer, cIGameNode, sSceneNodeName);

					// We do not need to update the statistics...
					break;

				case CAMERA_CLASS_ID:
					// Create the scene node
					pSceneNode = new PLSceneCamera(*pContainer, cIGameNode, sSceneNodeName);

					// Update the statistics
					pContainer->m_sStatistics.nNumOfCameras++;
					GetScene().m_sSceneStatistics.nNumOfCameras++;
					break;

				case HELPER_CLASS_ID:
					// Create the scene node
					pSceneNode = new PLSceneHelper(*pContainer, cIGameNode, sSceneNodeName);

					// Update the statistics
					pContainer->m_sStatistics.nNumOfHelpers++;
					GetScene().m_sSceneStatistics.nNumOfHelpers++;
					break;

				default:
					// Create the scene node
					pSceneNode = new PLSceneUnknown(*pContainer, cIGameNode, sSceneNodeName);

					// Update the statistics
					pContainer->m_sStatistics.nNumOfUnknown++;
					GetScene().m_sSceneStatistics.nNumOfUnknown++;
					break;
			}
		}
	}

	// Valid scene node?
	if (pSceneNode) {
		// Are there any children? If yes, we need to create a container for this node containing THIS node AND the children...
		if (cIGameNode.GetChildCount()) {
			// Setup the scene container
			pContainer->m_bFixedCenter = true;
			pContainer->m_vCenter.Set(0.0f, 0.0f, 0.0f);
			pContainer->m_vPos = pSceneNode->m_vPos;
			pContainer->m_vRot = pSceneNode->m_vRot;

			// Setup the scene node 'creating' the scene container
			pSceneNode->m_vPos.Set(0.0f, 0.0f, 0.0f);
			pSceneNode->m_vRot.Set(0.0f, 0.0f, 0.0f);
		}

		// Register the new scene node
		pContainer->m_lstSceneNodes.push_back(pSceneNode);
		sSceneNodeNameLower = sSceneNodeName;
		sSceneNodeNameLower.ToLower(); // Do ONLY use lower case, else the hashing will NOT return the same values!
		pContainer->m_mapNodes.insert(std::make_pair(sSceneNodeNameLower, pSceneNode));

		{ // Add to 3ds Max node to PL node map
			String sKey = String::Format("%19p", cIGameNode.GetMaxNode());
			GetScene().m_mapMaxToPLNodes.insert(std::make_pair(sKey, pSceneNode));
		}

		// Loop through all child nodes
		for (int nNode=0; nNode<cIGameNode.GetChildCount(); nNode++) {
			IGameNode *pIGameNode = cIGameNode.GetNodeChild(nNode);
			if (pIGameNode) {
				g_pLog->LogFLine(PLLog::Scene, "Found 3ds Max child node: %s", pIGameNode->GetName());
				pContainer->AddIGameNode(*pIGameNode);
			}
		}

		// Done
		return true;
	}

	// Error!
	return false;
}
void Unreal3DExport::GetTris()
{
    
    // Export triangle data
    FJSMeshTri nulltri = FJSMeshTri();
    for( int n=0; n<Nodes.Count(); ++n )
    {
        CheckCancel();
        
        IGameNode* node = Nodes[n];
        IGameMesh* mesh = static_cast<IGameMesh*>(node->GetIGameObject());
        if( mesh->InitializeData() )
        {
            int vertcount = mesh->GetNumberOfVerts();
            int tricount = mesh->GetNumberOfFaces();
            if( vertcount > 0 && tricount > 0 )
            {
                // Progress
                ProgressMsg.printf(GetString(IDS_INFO_MESH),n+1,Nodes.Count(),TSTR(node->GetName()));
                pInt->ProgressUpdate(Progress+(static_cast<float>(n)/Nodes.Count()*U3D_PROGRESS_MESH), FALSE, ProgressMsg.data());

                // Alloc triangles space
                Tris.Resize(Tris.Count()+tricount);

                // Append triangles
                for( int i=0; i!=tricount; ++i )
                {
                    FaceEx* f = mesh->GetFace(i);
                    if( f )
                    {
                        FJSMeshTri tri(nulltri);

                        // TODO: add material id listing
                        RegisterMaterial( node, mesh, f, &tri );

                        tri.iVertex[0] = VertsPerFrame + f->vert[0];
                        tri.iVertex[1] = VertsPerFrame + f->vert[1];
                        tri.iVertex[2] = VertsPerFrame + f->vert[2];

                        Point2 p;
                        if( mesh->GetTexVertex(f->texCoord[0],p) ){
                            tri.Tex[0] = FMeshUV(p);
                        }
                        if( mesh->GetTexVertex(f->texCoord[1],p) ){
                            tri.Tex[1] = FMeshUV(p);
                        }
                        if( mesh->GetTexVertex(f->texCoord[2],p) ){
                            tri.Tex[2] = FMeshUV(p);
                        }
                        
                        Tris.Append(1,&tri);                       
                    }
                }

                VertsPerFrame += vertcount;
            }
            else
            {
                // remove invalid node
                Nodes.Delete(n--,1);
            }
        }
        node->ReleaseIGameObject();
    }
    Progress += U3D_PROGRESS_MESH;
}