OBJ Import

    Idea is understanable

    
    enum class OBJFaceType
    {
    	p,
    	pu,
    	pun,
    	pn
    };
    
    struct OBJSimpleArr
    {
    	OBJSimpleArr() {
    		sz = 0;
    	}
    
    	int data[0x100];
    	unsigned int sz;
    
    	void push_back(int v) { data[sz++] = v; }
    	unsigned int size() { return sz; }
    	void reset() { sz = 0; }
    };
    
    struct OBJFace
    {
    	OBJFace() {
    		ft = OBJFaceType::pun;
    	}
    
    	OBJSimpleArr p, u, n;
    	OBJFaceType ft;
    
    	void reset()
    	{
    		ft = OBJFaceType::pun;
    		p.reset();
    		u.reset();
    		n.reset();
    	}
    };
    
    unsigned char * OBJNextLine(unsigned char * ptr);
    unsigned char * OBJSkipSpaces(unsigned char * ptr);
    unsigned char * OBJReadVec2(unsigned char * ptr, v2f& vec2);
    unsigned char * OBJReadFloat(unsigned char * ptr, float& value);
    unsigned char * OBJReadVec3(unsigned char * ptr, v3f& vec3);
    unsigned char * OBJReadFace(unsigned char * ptr, OBJFace& f, char * s);
    unsigned char * OBJReadWord(unsigned char * ptr, miString& str);
    unsigned char * OBJReadLastWord(unsigned char * ptr, miString& str);
    
    bool g_ImportOBJ_triangulate = false;
    bool g_ImportOBJ_readMTL = true;
    
    struct OBJMaterial
    {
    	OBJMaterial() {
    		m_specularExponent = 10.f;
    		m_opacity = 1.f;
    		m_transparency = 0.f;
    		m_refraction = 1.f;
    	}
    
    	miString m_name; // newmtl
    	v3f m_ambient;   // Ka
    	v3f m_diffuse;   // Kd
    	v3f m_specular;  // Ks
    	f32 m_specularExponent; // Ns
    	f32 m_opacity;    // d
    	f32 m_transparency; // Tr
    	f32 m_refraction;  // Ni
    	
    	miString m_map_diffuse; // map_Kd  
    	miString m_map_ambient; // map_Ka 
    	miString m_map_specular; // map_Ks   
    	miString m_map_specularHighlight; // map_Ns    
    	miString m_map_alpha; // map_d     
    	miString m_map_bump; // map_bump bump 
    	miString m_map_displacement; // disp 
    	miString m_map_reflection; // refl  
    };
    
    bool OBJStringEqual(const miString& str, const char* c_Str)
    {
    	auto c_str_len = std::strlen(c_Str);
    	if (c_str_len != str.size())
    		return false;
    
    	for (size_t i = 0, sz = str.size(); i < sz; ++i)
    	{
    		if (str[i] != (miString::value_type)c_Str[i])
    			return false;
    	}
    
    	return true;
    }
    
    void miplStd_ImportOBJ_MTL(
    	miArray<OBJMaterial*>& materials, 
    	const char* obj_fileName,
    	const char* mtl_fileName
    	) 
    {
    	auto relPath = g_sdk->FileGetRelativePath(obj_fileName);
    	
    	for (u32 i = 0, sz = relPath.size(); i < sz; ++i)
    	{
    		auto c = relPath.pop_back_return();
    		if (c == L'/' || c == L'\\')
    		{
    			relPath += c;
    			break;
    		}
    	}
    
    	miString mtlPath = relPath;
    	mtlPath += mtl_fileName;
    
    	if (g_sdk->FileExist(mtlPath.data()))
    	{
    		auto mbStr = g_sdk->StringWideToMultiByte(mtlPath.data());
    
    		FILE* file = fopen(mbStr.data(), "rb");
    		auto file_size = (size_t)g_sdk->FileSize(mbStr.data());
    
    		std::vector<unsigned char> file_byte_array((unsigned int)file_size + 2);
    		unsigned char * ptr = file_byte_array.data();
    
    		fread(ptr, 1, file_size, file);
    		fclose(file);
    
    		ptr[(unsigned int)file_size] = ' ';
    		ptr[(unsigned int)file_size + 1] = 0;
    
    		OBJMaterial* curMaterial = 0;
    
    		while (*ptr)
    		{
    			switch (*ptr)
    			{
    			case '#':
    			case 'd':
    			case 'T':
    			case 'i':
    			case 'K':
    				ptr = OBJNextLine(ptr);
    				break;
    			case 'n':
    			{
    				miString mtlWord;
    				ptr = OBJReadWord(ptr, mtlWord);
    
    				if (OBJStringEqual(mtlWord, "newmtl"))
    				{
    					curMaterial = new OBJMaterial;
    					materials.push_back(curMaterial);
    
    					ptr = OBJReadWord(ptr, curMaterial->m_name);
    				}
    			}break;
    			case 'N':
    			{
    				miString word;
    				ptr = OBJReadWord(ptr, word);
    				if (OBJStringEqual(word, "Ns"))
    				{
    					ptr = OBJSkipSpaces(ptr);
    					ptr = OBJReadFloat(ptr, curMaterial->m_specularExponent);
    				}
    				else if (OBJStringEqual(word, "Ni"))
    				{
    					ptr = OBJSkipSpaces(ptr);
    					ptr = OBJReadFloat(ptr, curMaterial->m_refraction);
    				}
    				else
    					ptr = OBJNextLine(ptr);
    
    			}break;
    			case 'm':
    			{
    				miString word;
    				ptr = OBJReadWord(ptr, word);
    				if (OBJStringEqual(word, "map_Kd"))
    				{
    					ptr = OBJReadLastWord(ptr, word);
    					
    					miString mapPath = word;
    
    					if (!g_sdk->FileExist(mapPath.data()))
    					{
    						mapPath = relPath;
    						mapPath += word.data();
    
    						//if (g_sdk->FileExist(mapPath.data()))
    						//	printf("OK\n");
    					}
    
    					curMaterial->m_map_diffuse = mapPath;
    
    					/*ptr = OBJSkipSpaces(ptr);
    					if (*ptr == L'-')
    					{
    						ptr = OBJReadWord(ptr, word);
    						if (OBJStringEqual(word, "-bm"))
    						{
    
    						}
    					}*/
    				}
    				else
    					ptr = OBJNextLine(ptr);
    			}break;
    			default:
    				++ptr;
    				break;
    			}
    		}
    	}
    }
    
    OBJMaterial* OBJGetMaterial(
    	miArray<OBJMaterial*>& materials,
    	const miString& name
    	) 
    {
    	for (u32 i = 0; i < materials.m_size; ++i)
    	{
    		if (materials.m_data[i]->m_name == name)
    			return materials.m_data[i];
    	}
    	return 0;
    }
    
    void miplStd_ImportOBJ(const wchar_t* fileName) {
    	assert(fileName);
    	auto mbStr = g_sdk->StringWideToMultiByte(fileName);
    
    	miArray<OBJMaterial*> obj_materials;
    	
    	FILE* file = fopen(mbStr.data(), "rb");
    	auto file_size = (size_t)g_sdk->FileSize(mbStr.data());
    
    	std::vector<unsigned char> file_byte_array((unsigned int)file_size + 2);
    
    	unsigned char * ptr = file_byte_array.data();
    	fread(ptr, 1, file_size, file);
    	fclose(file);
    
    	ptr[(unsigned int)file_size] = ' ';
    	ptr[(unsigned int)file_size + 1] = 0;
    
    	bool groupBegin = false;
    	bool isModel = false;
    	bool grpFound = false;
    
    	v2f tcoords;
    	v3f pos;
    	v3f norm;
    
    	std::vector<v3f> position;
    	std::vector<v2f> uv;
    	std::vector<v3f> normal;
    
    	position.reserve(0xffff);
    	uv.reserve(0xffff);
    	normal.reserve(0xffff);
    
    	position.reserve(0xffff);
    	uv.reserve(0xffff);
    	normal.reserve(0xffff);
    
    	//std::string name_word;
    	miString tmp_word;
    	miString curr_word;
    	miString prev_word;
    
    	OBJFace f;
    	char s[0xff];
    
    	int last_counter[3] = {0,0,0};
    	
    	std::unordered_map<std::string, unsigned int> map;
    	std::string hash;
    
    	miSDKImporterHelper importerHelper;
    	miPolygonCreator triangulationPolygonCreator;
    
    	OBJMaterial* currMaterial = 0;
    
    	while (*ptr)
    	{
    		switch (*ptr)
    		{
    		case 'm'://mtllib
    		{
    			miString word;
    			ptr = OBJReadWord(ptr, word);
    
    			if (OBJStringEqual(word, "mtllib"))
    			{
    				ptr = OBJReadWord(ptr, word);
    				//wprintf(L"MTL: %s\n", mtlWord.data());
    				miStringA stra;
    				stra = word.data();
    				miplStd_ImportOBJ_MTL(obj_materials, mbStr.data(), stra.data());
    			}
    		}break;
    		case 'u'://usemtl
    		{
    			miString word;
    			ptr = OBJReadWord(ptr, word);
    
    			if (OBJStringEqual(word, "usemtl"))
    			{
    				ptr = OBJReadWord(ptr, word);
    				currMaterial = OBJGetMaterial(obj_materials, word);
    			}
    		}break;
    		case 's':
    		case 'l':
    		case 'c'://curv
    		case 'p'://parm
    		case 'd'://deg
    		case '#':
    		case 'e'://end
    			ptr = OBJNextLine(ptr);
    			break;
    		case 'v':
    		{
    			++ptr;
    			if (groupBegin)
    				groupBegin = false;
    			switch (*ptr)
    			{
    			case 't':
    				ptr = OBJReadVec2(++ptr, tcoords);
    				uv.push_back(tcoords);
    				++last_counter[1];
    				break;
    			case 'n':
    				ptr = OBJReadVec3(++ptr, norm);
    				normal.push_back(norm);
    				++last_counter[2];
    				break;
    			default:
    				ptr = OBJReadVec3(ptr, pos);
    				position.push_back(pos);
    				++last_counter[0];
    				//newModel->m_aabb.add(pos);
    				break;
    			}
    		}break;
    		case 'f':
    		{
    			isModel = true;
    			f.reset();
    			ptr = OBJReadFace(++ptr, f, s);
    
    			importerHelper.m_polygonCreator.Clear();
    
    			if (!importerHelper.m_meshBuilder->m_isBegin)
    			{
    				importerHelper.m_meshBuilder->Begin();
    			}
    
    			bool weld = false;
    			bool genNormals = true;
    
    			for (size_t sz2 = f.p.size(), i2 = 0; i2 < sz2; ++i2)
    			{
    				auto index = i2;
    				auto pos_index = f.p.data[index];
    				
    
    				if (pos_index < 0)
    				{
    					// если индекс отрицательный то он указывает на позицию относительно последнего элемента
    					// -1 = последний элемент
    					pos_index = last_counter[0] + pos_index + 1;
    				}
    
    				{
    					hash.clear();
    					hash += pos_index;
    
    					// это я не помню зачем сделал
    					// когда дойду до control вершин, станет ясно зачем это здесь
    					auto it = map.find(hash);
    					if (it == map.end())
    					{
    						map[hash] = pos_index;
    					}
    					else
    					{
    						weld = true;
    					}
    				}
    
    				auto v = position[pos_index];
    
    				//geometry_creator->AddPosition(v.x, v.y, v.z);
    				v3f pcPos, pcNorm;
    				v2f pcUV;
    
    				pcPos = v;
    
    				if (f.ft == OBJFaceType::pu || f.ft == OBJFaceType::pun)
    				{
    					auto uv_index = f.u.data[index];
    
    					if (uv_index < 0)
    					{
    						uv_index = last_counter[1] + uv_index + 1;
    					}
    
    					auto u = uv[uv_index];
    				//	geometry_creator->AddUV(u.x, u.y);
    					pcUV.x = u.x;
    					pcUV.y = 1.f - u.y;
    				}
    
    				if (f.ft == OBJFaceType::pn || f.ft == OBJFaceType::pun)
    				{
    					auto nor_index = f.n.data[index];
    
    					if (nor_index < 0)
    					{
    						nor_index = last_counter[2] + nor_index + 1;
    					}
    
    					auto n = normal[nor_index];
    				//	geometry_creator->AddNormal(n.x, n.y, n.z);
    					pcNorm = n;
    					genNormals = false;
    				}
    				importerHelper.m_polygonCreator.Add(pcPos, weld, false, pcNorm, pcUV);
    			}
    
    			if (g_ImportOBJ_triangulate && importerHelper.m_polygonCreator.Size() > 3)
    			{
    				u32 triCount = importerHelper.m_polygonCreator.Size() - 2;
    				auto _positions = importerHelper.m_polygonCreator.GetPositions();
    				auto _normals = importerHelper.m_polygonCreator.GetNormals();
    				auto _tcoords = importerHelper.m_polygonCreator.GetTCoords();
    				for (u32 i = 0; i < triCount; ++i)
    				{
    					triangulationPolygonCreator.Clear();
    					triangulationPolygonCreator.Add(_positions[0].m_first, weld, false, _normals[0], _tcoords[0]);
    					triangulationPolygonCreator.Add(_positions[i+1].m_first, weld, false, _normals[i+1], _tcoords[i+1]);
    					triangulationPolygonCreator.Add(_positions[i+2].m_first, weld, false, _normals[i+2], _tcoords[i+2]);
    
    					importerHelper.m_meshBuilder->AddPolygon(&triangulationPolygonCreator, genNormals);
    				}
    			}
    			else
    			{
    				importerHelper.m_meshBuilder->AddPolygon(&importerHelper.m_polygonCreator, genNormals);
    			}
    		}break;
    		case 'o':
    		case 'g':
    		{
    			if (!groupBegin)
    				groupBegin = true;
    			else
    			{
    				ptr = OBJNextLine(ptr);
    				break;
    			}
    
    			/*std::string tmp_word;
    			ptr = OBJReadWord(++ptr, tmp_word);
    			if (tmp_word.size())
    			{
    				if (!name_word.size())
    					name_word = tmp_word;
    			}*/
    			ptr = OBJReadWord(++ptr, tmp_word);
    			if (tmp_word.size())
    			{
    				prev_word = curr_word;
    				curr_word = tmp_word;
    			}
    
    			if (grpFound)
    			{
    				if (importerHelper.m_meshBuilder->m_isBegin)
    				{
    					importerHelper.m_meshBuilder->End();
    					miMaterial* m = 0;
    					if (currMaterial)
    					{
    						m = g_sdk->CreateMaterial(currMaterial->m_name.data());
    						if (currMaterial->m_map_diffuse.size())
    							m->m_maps[m->mapSlot_Diffuse].m_texturePath = currMaterial->m_map_diffuse;
    					}
    					g_sdk->CreateSceneObjectFromHelper(&importerHelper, prev_word.data(), m);
    					// just create again
    					importerHelper.Create(); 
    					importerHelper.m_meshBuilder->Begin();
    				}
    			}
    			grpFound = true;
    		}break;
    		default:
    			++ptr;
    			break;
    		}
    	}
    
    	if (importerHelper.m_meshBuilder->m_isBegin)
    	{
    		importerHelper.m_meshBuilder->End();
    
    		miMaterial* m = 0;
    		if (currMaterial)
    		{
    			m = g_sdk->CreateMaterial(currMaterial->m_name.data());
    			if (currMaterial->m_map_diffuse.size())
    				m->m_maps[m->mapSlot_Diffuse].m_texturePath = currMaterial->m_map_diffuse;
    		}
    
    		g_sdk->CreateSceneObjectFromHelper(&importerHelper, curr_word.data(), m);
    	}
    
    	g_sdk->UpdateSceneAabb();
    
    	for (u32 i = 0; i < obj_materials.m_size; ++i)
    	{
    		delete obj_materials.m_data[i];
    	}
    }
    
    unsigned char * OBJNextLine(unsigned char * ptr)
    {
    	while (*ptr)
    	{
    		if (*ptr == '\n')
    		{
    			ptr++;
    			return ptr;
    		}
    		ptr++;
    	}
    	return ptr;
    }
    
    unsigned char * OBJReadVec2(unsigned char * ptr, v2f& vec2)
    {
    	ptr = OBJSkipSpaces(ptr);
    	float x, y;
    	if (*ptr == '\n')
    	{
    		ptr++;
    	}
    	else
    	{
    		ptr = OBJReadFloat(ptr, x);
    		ptr = OBJSkipSpaces(ptr);
    		ptr = OBJReadFloat(ptr, y);
    		ptr = OBJNextLine(ptr);
    		vec2.x = x;
    		vec2.y = y;
    	}
    	return ptr;
    }
    
    unsigned char * OBJSkipSpaces(unsigned char * ptr)
    {
    	while (*ptr)
    	{
    		if (*ptr != '\t' && *ptr != ' ')
    			break;
    		ptr++;
    	}
    	return ptr;
    }
    
    unsigned char * OBJReadFloat(unsigned char * ptr, float& value)
    {
    	char str[32u];
    	memset(str, 0, 32);
    	char * p = &str[0u];
    	while (*ptr) {
    		if (!isdigit(*ptr) && (*ptr != '-') && (*ptr != '+')
    			&& (*ptr != 'e') && (*ptr != 'E') && (*ptr != '.')) break;
    		*p = *ptr;
    		++p;
    		++ptr;
    	}
    	value = (float)atof(str);
    	return ptr;
    }
    
    unsigned char * OBJReadVec3(unsigned char * ptr, v3f& vec3) {
    	ptr = OBJSkipSpaces(ptr);
    	float x, y, z;
    	if (*ptr == '\n') {
    		ptr++;
    	}
    	else {
    		ptr = OBJReadFloat(ptr, x);
    		ptr = OBJSkipSpaces(ptr);
    		ptr = OBJReadFloat(ptr, y);
    		ptr = OBJSkipSpaces(ptr);
    		ptr = OBJReadFloat(ptr, z);
    		ptr = OBJNextLine(ptr);
    		vec3.x = x;
    		vec3.y = y;
    		vec3.z = z;
    	}
    	return ptr;
    }
    
    unsigned char * OBJSkipSpace(unsigned char * ptr) {
    	while (*ptr) {
    		if (*ptr != ' ' && *ptr != '\t') break;
    		ptr++;
    	}
    	return ptr;
    }
    
    unsigned char * OBJGetInt(unsigned char * p, int& i)
    {
    	char str[8u];
    	memset(str, 0, 8);
    	char * pi = &str[0u];
    
    	while (*p)
    	{
    		/*if( *p == '-' )
    		{
    		++p;
    		continue;
    		}*/
    
    		if (!isdigit(*p) && *p != '-') break;
    
    
    		*pi = *p;
    		++pi;
    		++p;
    	}
    	i = atoi(str);
    	return p;
    }
    
    unsigned char * OBJReadFace(unsigned char * ptr, OBJFace& f, char * s) {
    	ptr = OBJSkipSpaces(ptr);
    	if (*ptr == '\n')
    	{
    		ptr++;
    	}
    	else
    	{
    		while (true)
    		{
    			int p = 1;
    			int u = 1;
    			int n = 1;
    
    			ptr = OBJGetInt(ptr, p);
    
    			if (*ptr == '/')
    			{
    				ptr++;
    				if (*ptr == '/')
    				{
    					ptr++;
    					f.ft = OBJFaceType::pn;
    					ptr = OBJGetInt(ptr, n);
    				}
    				else
    				{
    					ptr = OBJGetInt(ptr, u);
    					if (*ptr == '/')
    					{
    						ptr++;
    						f.ft = OBJFaceType::pun;
    						ptr = OBJGetInt(ptr, n);
    					}
    					else
    					{
    						f.ft = OBJFaceType::pu;
    					}
    				}
    			}
    			else
    			{
    				f.ft = OBJFaceType::p;
    			}
    			f.n.push_back(n - 1);
    			f.u.push_back(u - 1);
    			f.p.push_back(p - 1);
    			ptr = OBJSkipSpace(ptr);
    
    			if (*ptr == '\r')
    				break;
    			else if (*ptr == '\n')
    				break;
    		}
    	}
    	return ptr;
    }
    
    unsigned char * OBJReadWord(unsigned char * ptr, miString& str)
    {
    	ptr = OBJSkipSpaces(ptr);
    	str.clear();
    	while (*ptr)
    	{
    		if (isspace(*ptr))
    			break;
    		str += (wchar_t)*ptr;
    		ptr++;
    	}
    	return ptr;
    }
    
    unsigned char * OBJReadLastWord(unsigned char * ptr, miString& str) {
    	while (true)
    	{
    		ptr = OBJSkipSpaces(ptr);
    		ptr = OBJReadWord(ptr, str);
    
    		ptr = OBJSkipSpaces(ptr);
    
    		if (*ptr == '\r' || *ptr == '\n')
    		{
    			break;
    		}
    
    		if (*ptr == 0)
    			break;
    	}
    	return ptr;
    }
    

    OBJ Export

    
    bool g_ExportOBJ_triangulate = false;
    bool g_ExportOBJ_optimize = true;
    bool g_ExportOBJ_writeNormals = true;
    bool g_ExportOBJ_writeUVs = true;
    bool g_ExportOBJ_createMTL = true;
    bool g_ExportOBJ_onlySelected = false;
    u32 g_ExportOBJ_vIndex = 0;
    u32 g_ExportOBJ_vtIndex = 0;
    u32 g_ExportOBJ_vnIndex = 0;
    
    class OBJWriter
    {
    	miStringA str_for_map;
    public:
    	OBJWriter() {}
    	~OBJWriter() {}
    
    	void WriteF( miStringA& f_str, u32 vI, u32 vtI, u32 vnI)
    	{
    		f_str += vI;
    		if (g_ExportOBJ_writeUVs && g_ExportOBJ_writeNormals)
    		{
    			f_str += "/";
    			f_str += vtI;
    			f_str += "/";
    			f_str += vnI;
    		}
    		else if (g_ExportOBJ_writeUVs && !g_ExportOBJ_writeNormals)
    		{
    			f_str += "/";
    			f_str += vtI;
    		}
    		else if (!g_ExportOBJ_writeUVs && g_ExportOBJ_writeNormals)
    		{
    			f_str += "/";
    			f_str += "/";
    			f_str += vnI;
    		}
    
    		f_str += " ";
    	}
    
    	void WritePosition(
    		const v3f& position,
    		miStringA& v_str
    	)
    	{
    		v_str += "v ";
    		v_str.append_float(position.x);
    		v_str += " ";
    		v_str.append_float(position.y);
    		v_str += " ";
    		v_str.append_float(position.z);
    		v_str += "\r\n";
    	}
    
    	void WriteUV(
    		const v2f& UVs,
    		miStringA& vt_str
    		)
    	{
    		vt_str += "vt ";
    		vt_str.append_float(UVs.x);
    		vt_str += " ";
    		vt_str.append_float(1.f - UVs.y);
    		vt_str += " ";
    		vt_str.append_float(0.f);
    		vt_str += "\r\n";
    	}
    
    	void WriteNormal(
    		const v3f& normal,
    		miStringA& vn_str
    	)
    	{
    		vn_str += "vn ";
    		vn_str.append_float(normal.x);
    		vn_str += " ";
    		vn_str.append_float(normal.y);
    		vn_str += " ";
    		vn_str.append_float(normal.z);
    		vn_str += "\r\n";
    	}
    
    	void WriteData(
    		const v3f& position, 
    		const v2f& UV, 
    		const v3f& normal, 
    		miStringA& v_str,
    		miStringA& vt_str,
    		miStringA& vn_str
    	) 
    	{
    		WritePosition(position, v_str);
    
    		if (g_ExportOBJ_writeUVs)
    			WriteUV(UV, vt_str);
    
    		if (g_ExportOBJ_writeNormals)
    			WriteNormal(normal, vn_str);
    	}
    
    	void WriteMaterial(FILE* mtl_file, miMaterial* m)
    	{
    		fprintf(mtl_file, "\r\n");
    
    		miStringA astr;
    		astr += m->m_name.data();
    
    		fprintf(mtl_file, "newmtl %s\r\n", astr.data());
    		fprintf(mtl_file, "\tNs %.4f\r\n", m->m_specularExponent);
    		fprintf(mtl_file, "\tNi %.4f\r\n", m->m_refraction);
    		fprintf(mtl_file, "\td %.4f\r\n", m->m_opacity);
    		fprintf(mtl_file, "\tTr %.4f\r\n", 1.f - m->m_opacity);
    		fprintf(mtl_file, "\tKa %.4f %.4f %.4f\r\n", m->m_colorAmbient.x(), m->m_colorAmbient.y(), m->m_colorAmbient.z());
    		fprintf(mtl_file, "\tKd %.4f %.4f %.4f\r\n", m->m_colorDiffuse.x(), m->m_colorDiffuse.y(), m->m_colorDiffuse.z());
    		fprintf(mtl_file, "\tKs %.4f %.4f %.4f\r\n", m->m_colorSpecular.x(), m->m_colorSpecular.y(), m->m_colorSpecular.z());
    
    		if (m->m_maps[m->mapSlot_Diffuse].m_texturePath.size())
    		{
    			auto s = g_sdk->FileGetName(m->m_maps[m->mapSlot_Diffuse].m_texturePath.data());
    			astr = s.data();
    			fprintf(mtl_file, "\tmap_Kd %s\r\n", astr.data());
    
    		}
    	}
    
    	void WriteObject(FILE* file, FILE* mtl_file, miSceneObject* object)
    	{
    
    		auto objectType = object->GetObjectType();
    		if (objectType == miSceneObjectType::MeshObject)
    		{
    			bool good = true;
    
    			if (g_ExportOBJ_onlySelected)
    				good = object->IsSelected();
    
    			auto meshCount = object->GetMeshCount();
    			if (meshCount && good)
    			{
    				miStringA stra;
    				stra = object->GetName().data();
    				fprintf(file, "\r\no %s \r\n", stra.data());
    
    				if (g_ExportOBJ_createMTL && object->m_material)
    				{
    					if (object->m_material)
    					{
    						miStringA astr;
    						astr += object->m_material->m_name.c_str();
    						fprintf(file, "usemtl %s \r\n", astr.data());
    						WriteMaterial(mtl_file, object->m_material);
    					}
    				}
    
    				miStringA v_str;  v_str.reserve(0xffff);
    				miStringA vn_str;  vn_str.reserve(0xffff);
    				miStringA vt_str;  vt_str.reserve(0xffff);
    				miStringA f_str;  f_str.reserve(0xffff);
    
    				u32 v_count = 0;
    				u32 vn_count = 0;
    				u32 vt_count = 0;
    				u32 f_count = 0;
    
    				auto pivot = object->GetGlobalPosition();
    
    				for (s32 i = 0; i < meshCount; ++i)
    				{
    					auto mesh = object->GetMesh(i);
    
    					if (!mesh->m_first_polygon)
    						continue;
    
    					auto cp = mesh->m_first_polygon;
    					auto lp = cp->m_left;
    					while (true)
    					{
    						if (g_ExportOBJ_triangulate)
    						{
    							auto vertex_1 = cp->m_verts.m_head;
    							auto vertex_3 = vertex_1->m_right;
    							auto vertex_2 = vertex_3->m_right;
    							while (true)
    							{
    								f_str += "f ";
    								++f_count;
    
    								auto vertex = vertex_1;
    
    								v3f position = math::mulBasis(vertex->m_data.m_vertex->m_position, *object->GetWorldMatrix()) + *pivot;
    								v2f UVs = vertex->m_data.m_uv;
    								v3f normals = vertex->m_data.m_normal;
    
    								if (g_ExportOBJ_optimize)
    								{
    									WriteOptimize(position, UVs, normals, v_count, vt_count, vn_count, f_str, v_str, vn_str, vt_str);
    								}
    								else
    								{
    									++g_ExportOBJ_vIndex;
    									++g_ExportOBJ_vtIndex;
    									++g_ExportOBJ_vnIndex;
    									++v_count;
    									++vt_count;
    									++vn_count;
    
    									WriteData(position, UVs, normals, v_str, vt_str, vn_str);
    									WriteF(f_str, g_ExportOBJ_vIndex, g_ExportOBJ_vtIndex, g_ExportOBJ_vnIndex);
    								}
    
    								vertex = vertex_2;
    
    								position = math::mulBasis(vertex->m_data.m_vertex->m_position, *object->GetWorldMatrix()) + *pivot;
    								UVs = vertex->m_data.m_uv;
    								normals = vertex->m_data.m_normal;
    
    								if (g_ExportOBJ_optimize)
    								{
    									WriteOptimize(position, UVs, normals, v_count, vt_count, vn_count, f_str, v_str, vn_str, vt_str);
    								}
    								else
    								{
    									++g_ExportOBJ_vIndex;
    									++g_ExportOBJ_vtIndex;
    									++g_ExportOBJ_vnIndex;
    									++v_count;
    									++vt_count;
    									++vn_count;
    
    									WriteData(position, UVs, normals, v_str, vt_str, vn_str);
    									WriteF(f_str, g_ExportOBJ_vIndex, g_ExportOBJ_vtIndex, g_ExportOBJ_vnIndex);
    								}
    
    								vertex = vertex_3;
    
    								position = math::mulBasis(vertex->m_data.m_vertex->m_position, *object->GetWorldMatrix()) + *pivot;
    								UVs = vertex->m_data.m_uv;
    								normals = vertex->m_data.m_normal;
    
    								if (g_ExportOBJ_optimize)
    								{
    									WriteOptimize(position, UVs, normals, v_count, vt_count, vn_count, f_str, v_str, vn_str, vt_str);
    								}
    								else
    								{
    									++g_ExportOBJ_vIndex;
    									++g_ExportOBJ_vtIndex;
    									++g_ExportOBJ_vnIndex;
    									++v_count;
    									++vt_count;
    									++vn_count;
    
    									WriteData(position, UVs, normals, v_str, vt_str, vn_str);
    									WriteF(f_str, g_ExportOBJ_vIndex, g_ExportOBJ_vtIndex, g_ExportOBJ_vnIndex);
    								}
    
    								f_str += "\r\n";
    
    								vertex_2 = vertex_2->m_right;
    								vertex_3 = vertex_3->m_right;
    								if (vertex_2 == vertex_1)
    									break;
    							}
    						}
    						else
    						{
    							f_str += "f ";
    							++f_count;
    
    							auto cv = cp->m_verts.m_head;
    							auto lv = cv->m_left;
    							while (true)
    							{
    								v3f position = math::mulBasis(cv->m_data.m_vertex->m_position, *object->GetWorldMatrix()) + *pivot;
    								v2f UVs = cv->m_data.m_uv;
    								v3f normals = cv->m_data.m_normal;
    
    								if (g_ExportOBJ_optimize)
    								{
    									WriteOptimize(position, UVs, normals, v_count, vt_count, vn_count, f_str, v_str, vn_str, vt_str);
    								}
    								else
    								{
    									++g_ExportOBJ_vIndex;
    									++g_ExportOBJ_vtIndex;
    									++g_ExportOBJ_vnIndex;
    
    									++v_count;
    									++vt_count;
    									++vn_count;
    
    									WriteData(position, UVs, normals, v_str, vt_str, vn_str);
    									WriteF(f_str, g_ExportOBJ_vIndex, g_ExportOBJ_vtIndex, g_ExportOBJ_vnIndex);
    								}
    
    								if (cv == lv)
    									break;
    								cv = cv->m_right;
    							}
    							f_str += "\r\n";
    						}
    
    						if (cp == lp)
    							break;
    						cp = cp->m_right;
    					}
    				}
    
    				fprintf(file, "%s \r\n", v_str.data());
    				fprintf(file, "# %u vertices \r\n\r\n", v_count);
    
    				if (g_ExportOBJ_writeUVs)
    				{
    					fprintf(file, "%s \r\n", vt_str.data());
    					fprintf(file, "# %u texture coordinates \r\n\r\n", vt_count);
    				}
    
    				if (g_ExportOBJ_writeNormals)
    				{
    					fprintf(file, "%s \r\n", vn_str.data());
    					fprintf(file, "# %u normals \r\n\r\n", vn_count);
    				}
    
    				fprintf(file, "%s \r\n", f_str.data());
    				fprintf(file, "# %u faces \r\n\r\n", f_count);
    			}
    		}
    
    		// other objects on scene
    		auto children = object->GetChildren();
    		if (children->m_head)
    		{
    			auto c = children->m_head;
    			auto l = c->m_left;
    			while (true)
    			{
    				WriteObject(file, mtl_file, c->m_data);
    				if (c == l)
    					break;
    				c = c->m_right;
    			}
    		}
    	}
    
    	void WriteOptimize(
    		const v3f& position, 
    		const v2f& UVs, 
    		const v3f& normals,
    		u32& v_count,
    		u32& vt_count,
    		u32& vn_count,
    		miStringA& f_str,
    		miStringA& v_str,
    		miStringA& vn_str,
    		miStringA& vt_str
    		)
    	{
    		u32 vI = 0;
    		u32 vtI = 0;
    		u32 vnI = 0;
    
    		str_for_map.clear();
    		str_for_map.append_float(position.x);
    		str_for_map.append_float(position.y);
    		str_for_map.append_float(position.z);
    
    		auto itP = map_position.find(str_for_map.data());
    		if (itP == map_position.end())
    		{
    			++g_ExportOBJ_vIndex;
    			++v_count;
    			map_position.insert(std::pair<const char*, u32>(str_for_map.data(), g_ExportOBJ_vIndex));
    			WritePosition(position, v_str);
    			vI = g_ExportOBJ_vIndex;
    		}
    		else
    		{
    			vI = itP->second;
    		}
    
    		if (g_ExportOBJ_writeUVs)
    		{
    			str_for_map.clear();
    			str_for_map.append_float(UVs.x);
    			str_for_map.append_float(UVs.y);
    
    			auto itU = map_UV.find(str_for_map.data());
    			if (itU == map_UV.end())
    			{
    				++g_ExportOBJ_vtIndex;
    				++vt_count;
    				map_UV.insert(std::pair<const char*, u32>(str_for_map.data(), g_ExportOBJ_vtIndex));
    				WriteUV(UVs, vt_str);
    				vtI = g_ExportOBJ_vtIndex;
    			}
    			else
    			{
    				vtI = itU->second;
    			}
    		}
    
    		if (g_ExportOBJ_writeNormals)
    		{
    			str_for_map.clear();
    			str_for_map.append_float(normals.x);
    			str_for_map.append_float(normals.y);
    			str_for_map.append_float(normals.z);
    
    			auto itN = map_normal.find(str_for_map.data());
    			if (itN == map_normal.end())
    			{
    				++g_ExportOBJ_vnIndex;
    				++vn_count;
    				map_normal.insert(std::pair<const char*, u32>(str_for_map.data(), g_ExportOBJ_vnIndex));
    				WriteNormal(normals, vn_str);
    				vnI = g_ExportOBJ_vnIndex;
    			}
    			else
    			{
    				vnI = itN->second;
    			}
    		}
    
    		WriteF(f_str, vI, vtI, vnI);
    	}
    
    	std::map<std::string, u32> map_position;
    	std::map<std::string, u32> map_UV;
    	std::map<std::string, u32> map_normal;
    };
    
    void miplStd_ExportOBJ(const wchar_t* fileName) {
    	//wprintf(L"Export: %s\n", fileName);
    	g_ExportOBJ_vIndex = 0;
    	g_ExportOBJ_vtIndex = 0;
    	g_ExportOBJ_vnIndex = 0;
    
    	auto mbStr = g_sdk->StringWideToMultiByte(fileName);
    
    	FILE* file = fopen(mbStr.data(), "wb");
    
    	fprintf(file, "# Mixer Wavefront OBJ Exporter v1.0\r\n\r\n");
    
    	FILE* mtl_file = 0;
    	if (g_ExportOBJ_createMTL)
    	{
    		miString s = g_sdk->FileGetName(fileName);
    		while (true)
    		{
    			auto c = s.pop_back_return();
    			if (c == L'.')
    				break;
    
    			if (s.size() == 0)
    				break;
    		}
    		
    		miStringA astr;
    		astr += s.data();
    		fprintf(file, "mtllib %s.mtl \r\n", astr.data());
    
    		astr = mbStr;
    		astr.replace('\\', '/');
    		astr.pop_back_before('/');
    
    		astr += s.data();
    		astr += ".mtl";
    		mtl_file = fopen(astr.data(), "wb");
    		fprintf(mtl_file, "# Mixer Wavefront OBJ Exporter v1.0\r\n\r\n");
    	}
    
    	OBJWriter o;
    	o.WriteObject(file, mtl_file, g_sdk->GetRootObject());
    
    	if (mtl_file)
    		fclose(mtl_file);
    	fclose(file);
    }
    
    Download