1
2 // model.cpp [pengine]
3
4 // Copyright 2004-2006 Jasmine Langridge, jas@jareiko.net
5 // License: GPL version 2 (see included gpl.txt)
6
7
8 #include "pengine.h"
9 #include "physfs_utils.h"
10
PSSModel(PApp & parentApp)11 PSSModel::PSSModel(PApp &parentApp) : PSubsystem(parentApp)
12 {
13 PUtil::outLog() << "Initialising model subsystem" << std::endl;
14 }
15
~PSSModel()16 PSSModel::~PSSModel()
17 {
18 PUtil::outLog() << "Shutting down model subsystem" << std::endl;
19
20 modlist.clear();
21 }
22
23
loadModel(const std::string & name)24 PModel *PSSModel::loadModel(const std::string &name)
25 {
26 PModel *mdl = modlist.find(name);
27 if (!mdl) {
28 try
29 {
30 mdl = new PModel (name);
31 }
32 catch (PException &e)
33 {
34 if (PUtil::isDebugLevel(DEBUGLEVEL_ENDUSER))
35 PUtil::outLog() << "Failed to load " << name << ": " << e.what () << std::endl;
36 return nullptr;
37 }
38 modlist.add(mdl);
39 }
40 return mdl;
41 }
42
43
44 #define dcon_printf(...)
45
46
47
strtok2(char * input)48 char *strtok2(char *input)
49 {
50 static char *inputstore = nullptr;
51
52 if (input != nullptr) inputstore = input;
53
54 if (!inputstore) return nullptr;
55
56 // eat whitespace
57 while (*inputstore == ' ' || *inputstore == '\t' || *inputstore == '\n' || *inputstore == '\r') inputstore++;
58
59 char *tokstart = inputstore;
60
61 if (*inputstore == '\"') {
62 tokstart++; inputstore++;
63 while ((*inputstore)) {
64 if (*inputstore == '\"') {
65 *inputstore = 0;
66 inputstore++;
67 break;
68 }
69 inputstore++;
70 }
71 } else {
72 while ((*inputstore)) {
73 if (*inputstore == ' ' || *inputstore == '\t' || *inputstore == '\n' || *inputstore == '\r') break;
74 inputstore++;
75 }
76 }
77
78 if (*inputstore) {
79 char *nullout = inputstore;
80 // eat whitespace
81 while (*inputstore == ' ' || *inputstore == '\t' || *inputstore == '\n' || *inputstore == '\r') inputstore++;
82 *nullout = 0;
83 if (!*inputstore)
84 inputstore = nullptr;
85 } else {
86 inputstore = nullptr;
87 }
88
89 return tokstart;
90 }
91
92
93 // PModel
94
95
96
getExtents() const97 std::pair<vec3f, vec3f> PModel::getExtents() const
98 {
99 vec3f v_min(1000000000.0, 1000000000.0, 1000000000.0),
100 v_max(-1000000000.0, -1000000000.0, -1000000000.0);
101
102 for (unsigned int a=0; a<mesh.size(); ++a) {
103 for (unsigned int b=0; b<mesh[a].vert.size(); ++b) {
104 if (v_min.x > mesh[a].vert[b].x)
105 v_min.x = mesh[a].vert[b].x;
106 if (v_max.x < mesh[a].vert[b].x)
107 v_max.x = mesh[a].vert[b].x;
108 if (v_min.y > mesh[a].vert[b].y)
109 v_min.y = mesh[a].vert[b].y;
110 if (v_max.y < mesh[a].vert[b].y)
111 v_max.y = mesh[a].vert[b].y;
112 if (v_min.z > mesh[a].vert[b].z)
113 v_min.z = mesh[a].vert[b].z;
114 if (v_max.z < mesh[a].vert[b].z)
115 v_max.z = mesh[a].vert[b].z;
116 }
117 }
118
119 return std::pair<vec3f, vec3f> (v_min, v_max);
120 }
121
122
123 struct matl_s {
124 std::string filename;
125 };
126
127
PModel(const std::string & filename,float globalScale)128 PModel::PModel (const std::string &filename, float globalScale)
129 {
130 /* Let's check each model type will load (ASE or OBJ) */
131 if(filename.find(".ase") != std::string::npos)
132 {
133 loadASE(filename, globalScale);
134 }
135 else
136 {
137 loadOBJ(filename, globalScale);
138 }
139 }
140
141 /*! Load an .obj model from file to the pengine structures.
142 * \note: All model faces must be triangles;
143 * \note: Not setting the pengine per face normal (mesh.facenormal) as the
144 * renderer is ignoring it (only setting vertex normals)
145 * FIXME: Restriction: Model must have only a single material.
146 * See comment bellow on how to fix it. */
147
148 #define OBJ_BUFFER_SIZE 65552
149
loadOBJ(const std::string & filename,float globalScale)150 void PModel::loadOBJ(const std::string &filename, float globalScale)
151 {
152 std::vector<char> buff (OBJ_BUFFER_SIZE); /**< File buffer */
153 PHYSFS_file* pfile; /**< The real .obj file */
154 std::string tok; /**< Readed token from line */
155 std::string value; /**< Readed value from line */
156 unsigned int curFace=-1; /**< Current readed face */
157 int objNumber=0; /**< Number of objects declared */
158 PMesh* curMesh; /**< Current loading mesh */
159 vec3f v3; /**< Vector to parse from lines */
160 vec2f v2; /**< Vector to parse from lines */
161
162 /* Initing debug message */
163 if(PUtil::isDebugLevel(DEBUGLEVEL_TEST))
164 {
165 PUtil::outLog() << "Loading OBJ model \"" << filename
166 << "\"" << std::endl;
167 }
168
169 /* Open the .obj file */
170 pfile = PHYSFS_openRead(filename.c_str());
171 if(pfile == NULL)
172 {
173 throw MakePException(filename + ", PhysFS: " + physfs_getErrorString());
174 }
175
176 /* Create the single mesh (.obj isn't a multimesh file) */
177 mesh.push_back(PMesh());
178 curMesh = &mesh.back();
179
180 /* Loop throught all file */
181 while(PUtil::fgets2(buff.data(), buff.size() ,pfile))
182 {
183 if(PUtil::getToken(buff.data(), tok, value))
184 {
185
186 if(tok == "f")
187 {
188 std::vector<PFace>& facebuf = curMesh->face;
189
190 /* Face (triangle) declaration */
191 int v[3],uv[3],vn[3];
192
193 if (facebuf.size() == curFace)
194 facebuf.reserve(curFace + 256);
195
196 curFace++;
197
198 facebuf.resize(curFace+1);
199 if(sscanf(value.c_str(), "%d/%d/%d %d/%d/%d %d/%d/%d",
200 &v[0], &uv[0], &vn[0],
201 &v[1], &uv[1], &vn[1],
202 &v[2], &uv[2], &vn[2]) == 9)
203 {
204 /* NOTE: all index are dec by 1, as .obj range is
205 * [1,total] and pengine vector is [0,total) */
206
207 /* Set Vertex Index */
208 facebuf[curFace].vt[0] = v[0]-1;
209 facebuf[curFace].vt[1] = v[1]-1;
210 facebuf[curFace].vt[2] = v[2]-1;
211 /* Set UV Index */
212 facebuf[curFace].tc[0] = uv[0]-1;
213 facebuf[curFace].tc[1] = uv[1]-1;
214 facebuf[curFace].tc[2] = uv[2]-1;
215 /* Set Normal Index */
216 facebuf[curFace].nr[0] = vn[0]-1;
217 facebuf[curFace].nr[1] = vn[1]-1;
218 facebuf[curFace].nr[2] = vn[2]-1;
219 }
220 }
221 else if(tok == "vt")
222 {
223 /* Vertex st texture coordinate */
224
225 if (curMesh->texco.size() == curMesh->texco.size())
226 curMesh->texco.reserve(curMesh->texco.size() + 256);
227
228 if(sscanf(value.c_str(), "%f %f", &v2.x, &v2.y) == 2)
229 curMesh->texco.push_back (v2);
230 }
231 else if(tok == "v")
232 {
233 /* Vertex declaration */
234
235 if (curMesh->vert.size() == curMesh->vert.capacity())
236 curMesh->vert.reserve(curMesh->vert.capacity() + 128);
237
238 if(sscanf(value.c_str(), "%f %f %f", &v3.x, &v3.y, &v3.z) == 3)
239 curMesh->vert.push_back(v3 * globalScale);
240 }
241 else if(tok == "vn")
242 {
243 /* Vertex Normal declaraction */
244
245 if (curMesh->norm.size() == curMesh->norm.capacity())
246 curMesh->norm.reserve(curMesh->norm.capacity() + 128);
247
248 if(sscanf(value.c_str(), "%f %f %f",
249 &v3.x, &v3.y, &v3.z) == 3)
250 {
251 v3.normalize();
252 curMesh->norm.push_back(v3);
253 }
254 }
255 else if(tok == "mtllib")
256 {
257 /* Material Library declaration (mtllib) */
258 curMesh->fxname = PUtil::assemblePath(value/*"focus_tex.fx"*/,
259 filename);
260 }
261 else if(tok == "o")
262 {
263 /* Object name. Just ignore. */
264 objNumber++;
265 if(objNumber > 1)
266 {
267 PUtil::outLog() << "Warning: Object file \"" << filename
268 << "\" has more than one object defined!" << std::endl;
269 }
270 }
271 else if(tok[0] == '#')
272 {
273 /* Comment. Just ignore. */
274 }
275 else if(tok == "usemtl")
276 {
277 /* Face material usage. (usemtl).
278 * FIXME: Ignoring, as the pengine renderer is
279 * using only a single "fx" per mesh.
280 *
281 * A bad fix should just duplicate each distinct material faces
282 * as different meshes.
283 *
284 * A good fix should rewrite the renderer (at ./app.cpp) to
285 * change materials on a single mesh as needed, allowing multiple
286 * material meshes.
287 *
288 * I'm do either of them, but just mark it as a restriction to
289 * .obj files on trigger. Someone must remove this restriction
290 * latter */
291 }
292 else if(tok == "s")
293 {
294 /* Smooth toggle. Ignoring. */
295 }
296 else
297 {
298 PUtil::outLog () << "Warning: unknown token \"" << tok
299 << "\" in file \"" << filename << "\"" << std::endl;
300 }
301 }
302 }
303
304 /* Verify if normals were defined */
305 if(curMesh->norm.size() == 0)
306 {
307 PUtil::outLog() << "Warning: Object file \"" << filename
308 << "\" had no normals defined!" << std::endl;
309 }
310
311 /* Finally, close file and done. */
312 PHYSFS_close(pfile);
313 name = filename;
314 }
315
316
317
loadASE(const std::string & filename,float globalScale)318 void PModel::loadASE (const std::string &filename, float globalScale)
319 {
320 if (PUtil::isDebugLevel(DEBUGLEVEL_TEST))
321 PUtil::outLog() << "Loading ASE model \"" << filename << "\"" << std::endl;
322
323 PHYSFS_file *pfile = PHYSFS_openRead(filename.c_str());
324 if (pfile == nullptr) {
325 throw MakePException (filename + ", PhysFS: " + physfs_getErrorString());
326 }
327
328 std::vector<matl_s> matlist;
329
330 char buff[1000],buff2[1000],buff3[1000];
331 std::vector<char*> tok;
332
333 int unknowndepth = 0;
334
335 #define TOKENIZE_LINE_AND_CHECK \
336 { \
337 char *thistok; \
338 tok.clear(); \
339 thistok = strtok2(buff); \
340 while (thistok) { \
341 tok.push_back(thistok); \
342 thistok = strtok2(NULL); \
343 } \
344 } \
345 if (!tok.size()) continue; \
346 if (unknowndepth > 0) { \
347 if (!strcmp(tok[0],"}")) \
348 unknowndepth--; \
349 continue; \
350 }
351
352 while (PUtil::fgets2(buff,1000,pfile)) {
353 dcon_printf("\"%s\"<br>\n",buff);
354 TOKENIZE_LINE_AND_CHECK
355 if (tok.size() == 2 && !strcmp(tok[1],"{")) {
356 if (!strcmp(tok[0],"*MATERIAL_LIST")) {
357 dcon_printf("*MATERIAL_LIST<br>\n");
358 while (PUtil::fgets2(buff,1000,pfile)) {
359 strcpy(buff2,buff);
360 TOKENIZE_LINE_AND_CHECK
361 if (!strcmp(tok[0],"}")) break;
362 #if 0
363 if (!strcmp(tok[0],"*MATERIAL")) {
364 dcon_printf("*MATERIAL<br>\n");
365 int matind = atoi(tok[1]);
366 dcon_printf("index = %i, matlist.size() = %i<br>\n",matind,matlist.size());
367 if (matind < 0) continue;
368 if (matind >= matlist.size()) matlist.resize(matind+1);
369 while (fgets2(buff,1000,pfile)) {
370 strcpy(buff2,buff);
371 TOKENIZE_LINE_AND_CHECK
372 if (!strcmp(tok[0],"}")) break;
373 if (!strcmp(tok[0],"*MAP_DIFFUSE")) {
374 dcon_printf("*MAP_DIFFUSE<br>\n");
375 while (fgets2(buff,1000,pfile)) {
376 strcpy(buff2,buff);
377 TOKENIZE_LINE_AND_CHECK
378 if (!strcmp(tok[0],"}")) break;
379 if (sscanf(buff2," *BITMAP \"%[^\"]\"",buff3) == 1) {
380 matlist[matind].filename = PUtil::assemblePath(buff3, filename);
381 }
382 }
383 }
384 }
385 }
386 #else
387 unsigned int matind;
388 if (sscanf(buff2," *MATERIAL %u \"%[^\"]\"",&matind,buff3) == 2) {
389 //if (matind >= 0) {
390 if (matind >= matlist.size()) matlist.resize(matind+1);
391 matlist[matind].filename = PUtil::assemblePath(buff3, filename);
392 //}
393 }
394 #endif
395 }
396 } else if (!strcmp(tok[0],"*GEOMOBJECT")) {
397 dcon_printf("*GEOMOBJECT<br>\n");
398 vec3f tm[4];
399 int tempi;
400 PMesh *curmesh = nullptr;
401 tm[0] = vec3f(1,0,0);
402 tm[1] = vec3f(0,1,0);
403 tm[2] = vec3f(0,0,1);
404 tm[3] = vec3f::zero();
405 #define DO_TM2(v) (vec3f((v)*tm[0],(v)*tm[1],(v)*tm[2]))
406 while (PUtil::fgets2(buff,1000,pfile)) {
407 strcpy(buff2,buff);
408 TOKENIZE_LINE_AND_CHECK
409 if (!strcmp(tok[0],"}")) break;
410 if (tok.size() == 2 && !strcmp(tok[1],"{")) {
411 if (!strcmp(tok[0],"*MESH")) {
412 dcon_printf("*MESH<br>\n");
413 mesh.push_back(PMesh());
414 curmesh = &mesh.back();
415 curmesh->effect = nullptr;
416 while (PUtil::fgets2(buff,1000,pfile)) {
417 TOKENIZE_LINE_AND_CHECK
418 if (!strcmp(tok[0],"}")) break;
419 if (tok.size() == 2 && !strcmp(tok[1],"{")) {
420 if (!strcmp(tok[0],"*MESH_VERTEX_LIST")) {
421 unsigned int vnum;
422 vec3f vpos;
423 while (PUtil::fgets2(buff,1000,pfile)) {
424 strcpy(buff2,buff);
425 TOKENIZE_LINE_AND_CHECK
426 if (!strcmp(tok[0],"}")) break;
427 // TODO: check if intentional " *MESH_VERTEX %i %f %f %f"
428 if (sscanf(buff2," *MESH_VERTEX %u %f %f %f",&vnum,&vpos.x,&vpos.y,&vpos.z) == 4) {
429 if (vnum < curmesh->vert.size())
430 curmesh->vert[vnum] = vpos * globalScale;
431 }
432 }
433 } else if (!strcmp(tok[0],"*MESH_TVERTLIST")) {
434 unsigned int vnum;
435 vec2f vco;
436 while (PUtil::fgets2(buff,1000,pfile)) {
437 strcpy(buff2,buff);
438 TOKENIZE_LINE_AND_CHECK
439 if (!strcmp(tok[0],"}")) break;
440 // TODO: check if intentional " *MESH_TVERT %i %f %f"
441 if (sscanf(buff2," *MESH_TVERT %u %f %f",&vnum,&vco.x,&vco.y) == 3) {
442 if (vnum < curmesh->texco.size()) {
443 curmesh->texco[vnum] = vco;
444 //curmesh->texco[vnum].y *= -1.0;
445 }
446 }
447 }
448 } else if (!strcmp(tok[0],"*MESH_FACE_LIST")) {
449 unsigned int fnum;
450 int fvt[3];
451 while (PUtil::fgets2(buff,1000,pfile)) {
452 strcpy(buff2,buff);
453 TOKENIZE_LINE_AND_CHECK
454 if (!strcmp(tok[0],"}")) break;
455 // TODO: check if intentional " *MESH_FACE %i: A: %i B: %i C: %i"
456 if (sscanf(buff2," *MESH_FACE %u: A: %i B: %i C: %i",&fnum,&fvt[0],&fvt[1],&fvt[2]) == 4) {
457 if (fnum < curmesh->face.size()) {
458 curmesh->face[fnum].vt[0] = fvt[0];
459 curmesh->face[fnum].vt[1] = fvt[1];
460 curmesh->face[fnum].vt[2] = fvt[2];
461 }
462 }
463 }
464 } else if (!strcmp(tok[0],"*MESH_TFACELIST")) {
465 unsigned int fnum;
466 int fvt[3];
467 while (PUtil::fgets2(buff,1000,pfile)) {
468 strcpy(buff2,buff);
469 TOKENIZE_LINE_AND_CHECK
470 if (!strcmp(tok[0],"}")) break;
471 // TODO: check if intentional " *MESH_TFACE %i %i %i %i"
472 if (sscanf(buff2," *MESH_TFACE %u %i %i %i",&fnum,&fvt[0],&fvt[1],&fvt[2]) == 4) {
473 if (fnum < curmesh->face.size()) {
474 curmesh->face[fnum].tc[0] = fvt[0];
475 curmesh->face[fnum].tc[1] = fvt[1];
476 curmesh->face[fnum].tc[2] = fvt[2];
477 }
478 }
479 }
480 } else if (!strcmp(tok[0],"*MESH_NORMALS")) {
481 unsigned int fnum, vnum;
482 vec3f nrm;
483 while (PUtil::fgets2(buff,1000,pfile)) {
484 strcpy(buff2,buff);
485 TOKENIZE_LINE_AND_CHECK
486 if (!strcmp(tok[0],"}")) break;
487 // TODO: check if intentional " *MESH_FACENORMAL %i %f %f %f"
488 if (sscanf(buff2," *MESH_FACENORMAL %u %f %f %f",&fnum,&nrm.x,&nrm.y,&nrm.z) == 4) {
489 if (fnum < curmesh->face.size()) {
490 curmesh->face[fnum].facenormal = DO_TM2(nrm);
491 curmesh->face[fnum].facenormal.normalize();
492 curmesh->face[fnum].nr[0] = fnum*3+0;
493 curmesh->face[fnum].nr[1] = fnum*3+1;
494 curmesh->face[fnum].nr[2] = fnum*3+2;
495 }
496 // TODO: check if intentional " *MESH_VERTEXNORMAL %i %f %f %f"
497 } else if (sscanf(buff2," *MESH_VERTEXNORMAL %u %f %f %f",&vnum,&nrm.x,&nrm.y,&nrm.z) == 4) {
498 if (vnum == curmesh->face[fnum].vt[0]) {
499 curmesh->norm[fnum*3+0] = DO_TM2(nrm);
500 curmesh->norm[fnum*3+0].normalize();
501 } else if (vnum == curmesh->face[fnum].vt[1]) {
502 curmesh->norm[fnum*3+1] = DO_TM2(nrm);
503 curmesh->norm[fnum*3+1].normalize();
504 } else if (vnum == curmesh->face[fnum].vt[2]) {
505 curmesh->norm[fnum*3+2] = DO_TM2(nrm);
506 curmesh->norm[fnum*3+2].normalize();
507 }
508 }
509 }
510 } else {
511 unknowndepth++;
512 }
513 } else if (!strcmp(tok[0],"*MESH_NUMVERTEX")) {
514 curmesh->vert.resize(atoi(tok[1]));
515 } else if (!strcmp(tok[0],"*MESH_NUMFACES")) {
516 curmesh->face.resize(atoi(tok[1]));
517 curmesh->norm.resize(atoi(tok[1])*3); // 3 normals per face
518 } else if (!strcmp(tok[0],"*MESH_NUMTVERTEX")) {
519 curmesh->texco.resize(atoi(tok[1]));
520 }
521 }
522 } else if (!strcmp(tok[0],"*NODE_TM")) {
523 dcon_printf("*NODE_TM<br>\n");
524 while (PUtil::fgets2(buff,1000,pfile)) {
525 strcpy(buff2,buff);
526 TOKENIZE_LINE_AND_CHECK
527 if (!strcmp(tok[0],"}")) {
528 // end of NODE_TM, do some processing
529 vec3f st[3] = { tm[0], tm[1], tm[2] };
530 tm[0] = vec3f(st[0].x, st[1].x, st[2].x);
531 tm[1] = vec3f(st[0].y, st[1].y, st[2].y);
532 tm[2] = vec3f(st[0].z, st[1].z, st[2].z);
533 break;
534 }
535 if (sscanf(buff2," *TM_ROW0 %f %f %f",&tm[0].x,&tm[0].y,&tm[0].z) == 3) {
536 // do nothing!
537 } else if (sscanf(buff2," *TM_ROW1 %f %f %f",&tm[1].x,&tm[1].y,&tm[1].z) == 3) {
538 // do nothing!
539 } else if (sscanf(buff2," *TM_ROW2 %f %f %f",&tm[2].x,&tm[2].y,&tm[2].z) == 3) {
540 // do nothing!
541 } else if (sscanf(buff2," *TM_ROW3 %f %f %f",&tm[3].x,&tm[3].y,&tm[3].z) == 3) {
542 // do nothing!
543 }
544 }
545 } else {
546 unknowndepth++;
547 }
548 } else if (sscanf(buff2," *MATERIAL_REF %i",&tempi) == 1) {
549 dcon_printf("*MATERIAL_REF<br>\n");
550 if (!curmesh) {
551 PUtil::outLog () << "warning: material ref before mesh in \"" << filename << "\"" << std::endl;
552 continue;
553 }
554 if (0 <= tempi && tempi < (int)matlist.size()) {
555 curmesh->fxname = matlist[tempi].filename;
556 }
557 }
558 }
559 } else {
560 unknowndepth++;
561 }
562 }
563 }
564
565 PHYSFS_close(pfile);
566
567 name = filename;
568 }
569
570
571