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