WoW Model Viewer
Your premiere tool for viewing, equipping and animating World of Warcraft models.
Loading...
Searching...
No Matches
OBJExporter.cpp
Go to the documentation of this file.
1/*----------------------------------------------------------------------*\
2| This file is part of WoW Model Viewer |
3| |
4| WoW Model Viewer is free software: you can redistribute it and/or |
5| modify it under the terms of the GNU General Public License as |
6| published by the Free Software Foundation, either version 3 of the |
7| License, or (at your option) any later version. |
8| |
9| WoW Model Viewer is distributed in the hope that it will be useful, |
10| but WITHOUT ANY WARRANTY; without even the implied warranty of |
11| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12| GNU General Public License for more details. |
13| |
14| You should have received a copy of the GNU General Public License |
15| along with WoW Model Viewer. |
16| If not, see <http://www.gnu.org/licenses/>. |
17\*----------------------------------------------------------------------*/
18
19/*
20 * OBJExporter.cpp
21 *
22 * Created on: 17 feb. 2015
23 * Copyright: 2015 , WoW Model Viewer (http://wowmodelviewer.net)
24 */
25
26#include "OBJExporter.h"
27
28#include <cstdio>
29#include <filesystem>
30
31#include "glm/glm.hpp"
32
33#include "Bone.h"
34#include "ModelRenderPass.h"
35#include "WoWModel.h"
36
37#include "GlobalSettings.h"
38#include "Logger.h"
39
40OBJExporter::OBJExporter() = default;
41
42// Change a glm::vec4 so it now faces forwards
43void MakeModelFaceForwards(glm::vec4& vect)
44{
45 glm::vec4 Temp;
46
47 Temp.x = vect.x;
48 Temp.y = vect.z;
49 Temp.z = -vect.y;
50
51 vect = Temp;
52}
53
54std::wstring OBJExporter::menuLabel() const
55{
56 return L"OBJ...";
57}
58
59std::wstring OBJExporter::fileSaveTitle() const
60{
61 return L"Save OBJ file";
62}
63
64std::wstring OBJExporter::fileSaveFilter() const
65{
66 return L"OBJ files (*.obj)|*.obj";
67}
68
69bool OBJExporter::exportModel(Model* m, std::wstring target)
70{
71 WoWModel* model = dynamic_cast<WoWModel*>(m);
72
73 if (!model)
74 return false;
75
76 namespace fs = std::filesystem;
77
78 // prepare obj file
79 const fs::path targetPath(target);
80
81 std::ofstream file(targetPath);
82 if (!file.is_open())
83 {
84 LOG_ERROR << "Unable to open" << target;
85 return false;
86 }
87
88 LOG_INFO << "Exporting" << model->modelname.c_str() << "in" << target;
89
90 // prepare mtl file
91 const fs::path matPath = targetPath.parent_path() / (targetPath.stem().wstring() + L".mtl");
92
93 LOG_INFO << "Exporting" << model->modelname.c_str() << "materials in" << matPath.wstring();
94
95 std::ofstream matFile(matPath);
96 if (!matFile.is_open())
97 {
98 LOG_ERROR << "Unable to open" << matPath.wstring();
99 return false;
100 }
101
102 std::ofstream& obj = file;
103 std::ofstream& mtl = matFile;
104
105 obj << "# Wavefront OBJ exported by ";
106 {
107 std::wstring appName = GLOBALSETTINGS.appName();
108 std::wstring appVer = GLOBALSETTINGS.appVersion();
109 obj << fs::path(appName).string() << " "
110 << fs::path(appVer).string() << "\n";
111 }
112 obj << "\n";
113 obj << "mtllib " << matPath.filename().string() << "\n";
114 obj << "\n";
115
116
117 mtl << "#" << "\n";
118 mtl << "# mtl file for " << targetPath.filename().string() << " obj file" << "\n";
119 mtl << "#" << "\n";
120 mtl << "\n";
121
122 int counter = 1;
123
124 // export main model
125 if (!exportModelVertices(model, obj, counter))
126 {
127 LOG_ERROR << "Error during obj export for model" << model->modelname.c_str();
128 return false;
129 }
130
131 if (!exportModelMaterials(model, mtl, matPath.string()))
132 {
133 LOG_ERROR << "Error during materials export for model" << model->modelname.c_str();
134 return false;
135 }
136
137 // export equipped items
138 if (!GLOBALSETTINGS.bInitPoseOnlyExport)
139 {
140 for (WoWModel::iterator it = model->begin();
141 it != model->end();
142 ++it)
143 {
144 std::map<POSITION_SLOTS, WoWModel*> itemModels = (*it)->models();
145 if (!itemModels.empty())
146 {
147 obj << "# " << "\n";
148 obj << "# " << (*it)->name() << "\n";
149 obj << "# " << "\n";
150 for (const auto& It : itemModels)
151 {
152 WoWModel* itemModel = It.second;
153 LOG_INFO << "Exporting attached item" << itemModel->modelname.c_str();
154
155 // find matrix
156 const int l = model->attLookup[It.first];
157 glm::mat4 M;
158 glm::vec3 pos;
159 if (l > -1)
160 {
161 M = model->bones[model->atts[l].bone].mat;
162 pos = model->atts[l].pos;
163 }
164
165 if (!exportModelVertices(itemModel, obj, counter, M, pos))
166 {
167 LOG_ERROR << "Error during obj export for model" << itemModel->modelname.c_str();
168 return false;
169 }
170
171 if (!exportModelMaterials(itemModel, mtl, matPath.string()))
172 {
173 LOG_ERROR << "Error during materials export for model" << itemModel->modelname.c_str();
174 return false;
175 }
176 }
177 }
178 }
179 }
180
181 file.close();
182 matFile.close();
183
184 return true;
185}
186
187bool OBJExporter::exportModelVertices(WoWModel* model, std::ofstream& file, int& counter, glm::mat4 mat,
188 glm::vec3 pos) const
189{
190 bool vertMsg = false;
191 // output all the vertice data
192 int vertics = 0;
193 char buf[128];
194 for (size_t i = 0; i < model->passes.size(); i++)
195 {
196 ModelRenderPass* p = model->passes[i];
197
198 if (p->init())
199 {
200 ModelGeosetHD* geoset = model->geosets[p->geoIndex];
201 for (size_t k = 0, b = geoset->istart; k < geoset->icount; k++, b++)
202 {
203 uint32 a = model->indices[b];
204 glm::vec4 vert;
205 if ((model->animated == true) && (model->vertices) && !GLOBALSETTINGS.bInitPoseOnlyExport)
206 {
207 if (vertMsg == false)
208 {
209 LOG_INFO << "Using Verticies";
210 vertMsg = true;
211 }
212 vert = mat * glm::vec4((model->vertices[a] + pos), 1.0);
213 }
214 else
215 {
216 if (vertMsg == false)
217 {
218 LOG_INFO << "Using Original Verticies";
219 vertMsg = true;
220 }
221 vert = mat * glm::vec4((model->origVertices[a].pos + pos), 1.0);
222 }
224 vert *= 1.0;
225 snprintf(buf, sizeof(buf), "v %.06f %.06f %.06f", vert.x, vert.y, vert.z);
226 file << buf << "\n";
227
228 vertics++;
229 }
230 }
231 }
232
233 file << "# " << vertics << " vertices" << "\n" << "\n";
234 file << "\n";
235 // output all the texture coordinate data
236 int textures = 0;
237 for (size_t i = 0; i < model->passes.size(); i++)
238 {
239 ModelRenderPass* p = model->passes[i];
240 // we don't want to render completely transparent parts
241 if (p->init())
242 {
243 ModelGeosetHD* geoset = model->geosets[p->geoIndex];
244 for (size_t k = 0, b = geoset->istart; k < geoset->icount; k++, b++)
245 {
246 uint32 a = model->indices[b];
247 glm::vec2 tc = model->origVertices[a].texcoords;
248 snprintf(buf, sizeof(buf), "vt %.06f %.06f", tc.x, 1 - tc.y);
249 file << buf << "\n";
250 textures++;
251 }
252 }
253 }
254
255 // output all the vertice normals data
256 int normals = 0;
257 for (size_t i = 0; i < model->passes.size(); i++)
258 {
259 ModelRenderPass* p = model->passes[i];
260 if (p->init())
261 {
262 ModelGeosetHD* geoset = model->geosets[p->geoIndex];
263 for (size_t k = 0, b = geoset->istart; k < geoset->icount; k++, b++)
264 {
265 uint16 a = model->indices[b];
266 glm::vec3 n = model->origVertices[a].normal;
267 snprintf(buf, sizeof(buf), "vn %.06f %.06f %.06f", n.x, n.y, n.z);
268 file << buf << "\n";
269 normals++;
270 }
271 }
272 }
273
274 file << "\n";
275 uint32 pointnum = 0;
276 // Polygon Data
277 int triangles_total = 0;
278 for (size_t i = 0; i < model->passes.size(); i++)
279 {
280 ModelRenderPass* p = model->passes[i];
281
282 if (p->init())
283 {
284 ModelGeosetHD* geoset = model->geosets[p->geoIndex];
285 // Build Vert2Point DB
286 uint16* Vert2Point = new uint16[geoset->vstart + geoset->vcount];
287 for (uint16 v = geoset->vstart; v < (geoset->vstart + geoset->vcount); v++, pointnum++)
288 Vert2Point[v] = pointnum;
289
290 int g = geoset->id;
291
292 snprintf(buf, sizeof(buf), "Geoset_%03i", g);
293 std::string val(buf);
294 std::string matName = model->modelname + "_" + val;
295 std::replace(matName.begin(), matName.end(), '\\', '_');
296 std::string partName = matName;
297
298 if (p->unlit == true)
299 matName = matName + "_Lum";
300
301 if (!p->cull)
302 matName = matName + "_Dbl";
303
304 // Part Names
305 int mesh = g / 100;
306
307 std::string cgGroupName = WoWModel::getCGGroupName(static_cast<CharGeosets>(mesh));
308
309 if ((model->modelType == MT_CHAR) && (cgGroupName != ""))
310 partName += "-" + cgGroupName;
311
312 file << "g " << partName << "\n";
313 file << "usemtl " << matName << "\n";
314 file << "s 1" << "\n";
315 int triangles = 0;
316 for (size_t k = 0; k < geoset->icount; k += 3)
317 {
318 file << "f ";
319 file << counter << "/" << counter << "/" << counter << " ";
320 counter++;
321 file << counter << "/" << counter << "/" << counter << " ";
322 counter++;
323 file << counter << "/" << counter << "/" << counter << "\n";
324 counter++;
325 triangles++;
326 }
327 file << "# " << triangles << " triangles in group" << "\n" << "\n";
328
329 // Check for potential overflow
330 if (triangles_total > UINT64_MAX - triangles)
331 {
332 LOG_ERROR << "Overflow detected in triangles_total!";
333 return false; // Handle overflow gracefully
334 }
335
336 triangles_total += triangles;
337 }
338 }
339 file << "# " << triangles_total << " triangles total" << "\n" << "\n";
340 return true;
341}
342
343bool OBJExporter::exportModelMaterials(WoWModel* model, std::ofstream& file, std::string mtlFile) const
344{
345 namespace fs = std::filesystem;
346
347 std::map<std::wstring, GLuint> texToExport;
348 char buf[128];
349
350 for (size_t i = 0; i < model->passes.size(); i++)
351 {
352 ModelRenderPass* p = model->passes[i];
353
354 if (p->init())
355 {
356 std::string tex = model->getNameForTex(p->tex);
357 std::string texStem = fs::path(tex).stem().string();
358 std::string mtlStem = fs::path(mtlFile).stem().string();
359 std::string texFile = mtlStem + "_" + texStem + ".png";
360
361 float amb = 0.25f;
362 glm::vec4 diff = p->ocol;
363
364 snprintf(buf, sizeof(buf), "Geoset_%03i", model->geosets[p->geoIndex]->id);
365 std::string material = model->modelname + "_" + buf;
366 std::replace(material.begin(), material.end(), '\\', '_');
367 if (p->unlit == true)
368 {
369 // Add Lum, just in case there's a non-luminous surface with the same name.
370 material = material + "_Lum";
371 amb = 1.0f;
372 diff = glm::vec4(0, 0, 0, 0);
373 }
374
375 // If Doublesided
376 if (!p->cull)
377 {
378 material = material + "_Dbl";
379 }
380
381 file << "newmtl " << material << "\n";
382 file << "illum 2" << "\n";
383 snprintf(buf, sizeof(buf), "Kd %.06f %.06f %.06f", diff.x, diff.y, diff.z);
384 file << buf << "\n";
385 snprintf(buf, sizeof(buf), "Ka %.06f %.06f %.06f", amb, amb, amb);
386 file << buf << "\n";
387 snprintf(buf, sizeof(buf), "Ks %.06f %.06f %.06f", p->ecol.x, p->ecol.y, p->ecol.z);
388 file << buf << "\n";
389 file << "Ke 0.000000 0.000000 0.000000" << "\n";
390 snprintf(buf, sizeof(buf), "Ns %0.6f", 0.0f);
391 file << buf << "\n";
392
393 file << "map_Kd " << texFile << "\n";
394 std::string fullTexPath = fs::path(mtlFile).parent_path().string() + "\\" + texFile;
395 texToExport[fs::path(fullTexPath).wstring()] = model->getGLTexture(p->tex);
396 }
397 }
398
399 LOG_INFO << "nb textures to export :" << texToExport.size();
400
401 for (const auto& it : texToExport)
402 exportGLTexture(it.second, it.first);
403
404 return true;
405}
#define GLOBALSETTINGS
#define LOG_ERROR
Definition Logger.h:11
#define LOG_INFO
Definition Logger.h:10
void MakeModelFaceForwards(glm::vec4 &vect)
iterator begin()
Definition Container.h:41
iterator end()
Definition Container.h:46
std::unordered_set< WoWItem * >::iterator iterator
Definition Container.h:15
void exportGLTexture(GLuint id, std::wstring filename) const
Extended geoset with 32-bit index start to support HD models with > 65535 indices.
Represents a single render pass (material + geometry) for an M2 model geoset.
uint16 tex
Texture index.
bool init()
Initialise render state from the model's material data.
glm::vec4 ecol
Output and emissive colours.
int geoIndex
Geoset index this pass draws.
Abstract base interface for all 3D model types.
Definition Model.h:5
bool exportModelVertices(WoWModel *model, std::ofstream &file, int &counter, glm::mat4 m=glm::mat4(1.0), glm::vec3 pos=glm::vec3(0.0f)) const
bool exportModelMaterials(WoWModel *model, std::ofstream &file, std::string mtlFile) const
bool exportModel(Model *, std::wstring file)
std::wstring fileSaveFilter() const
std::wstring menuLabel() const
std::wstring fileSaveTitle() const
Core WoW .m2 model: geometry, animation, textures, and character data.
Definition WoWModel.h:50
GLuint getGLTexture(uint16 tex) const
int16 attLookup[ATT_MAX]
Definition WoWModel.h:209
glm::vec3 * vertices
Definition WoWModel.h:138
ModelType modelType
Definition WoWModel.h:212
bool animated
Definition WoWModel.h:172
std::vector< uint32 > indices
Definition WoWModel.h:139
std::vector< Bone > bones
Definition WoWModel.h:181
std::string modelname
Definition WoWModel.h:145
std::vector< ModelRenderPass * > passes
Definition WoWModel.h:148
std::string getNameForTex(uint16 tex)
static std::string getCGGroupName(CharGeosets cg)
std::vector< ModelAttachment > atts
Definition WoWModel.h:207
std::vector< ModelGeosetHD * > geosets
Definition WoWModel.h:149
std::vector< ModelVertex > origVertices
Definition WoWModel.h:132
uint16_t uint16
Definition types.h:32
uint32_t uint32
Definition types.h:34
CharGeosets
Character geoset group identifiers (mesh IDs for body/armour regions).
Definition wow_enums.h:26
@ MT_CHAR
Definition wow_enums.h:214