1 /*
2 	This file is part of Warzone 2100.
3 	Copyright (C) 1999-2004  Eidos Interactive
4 	Copyright (C) 2005-2020  Warzone 2100 Project
5 
6 	Warzone 2100 is free software; you can redistribute it and/or modify
7 	it under the terms of the GNU General Public License as published by
8 	the Free Software Foundation; either version 2 of the License, or
9 	(at your option) any later version.
10 
11 	Warzone 2100 is distributed in the hope that it will be useful,
12 	but WITHOUT ANY WARRANTY; without even the implied warranty of
13 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 	GNU General Public License for more details.
15 
16 	You should have received a copy of the GNU General Public License
17 	along with Warzone 2100; if not, write to the Free Software
18 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 
21 /*!
22  * \file imdload.c
23  *
24  * Load IMD (.pie) files
25  */
26 
27 #include <string>
28 #include <unordered_map>
29 
30 #include "lib/framework/frame.h"
31 #include "lib/framework/string_ext.h"
32 #include "lib/framework/frameresource.h"
33 #include "lib/framework/fixedpoint.h"
34 #include "lib/framework/file.h"
35 #include "lib/framework/physfs_ext.h"
36 #include "lib/ivis_opengl/piematrix.h"
37 #include "lib/ivis_opengl/pienormalize.h"
38 #include "lib/ivis_opengl/piestate.h"
39 
40 #include "ivisdef.h" // for imd structures
41 #include "imd.h" // for imd structures
42 #include "tex.h" // texture page loading
43 
44 #include <glm/vec4.hpp>
45 using Vector4f = glm::vec4;
46 
47 // Scale animation numbers from int to float
48 #define INT_SCALE       1000
49 
50 static std::unordered_map<std::string, iIMDShape> models;
51 
52 static void iV_ProcessIMD(const WzString &filename, const char **ppFileData, const char *FileDataEnd);
53 
~iIMDShape()54 iIMDShape::~iIMDShape()
55 {
56 	free(connectors);
57 	free(shadowEdgeList);
58 	for (auto* buffer : buffers)
59 	{
60 		delete buffer;
61 	}
62 }
63 
modelShutdown()64 void modelShutdown()
65 {
66 	models.clear();
67 }
68 
enumerateLoadedModels(const std::function<void (const std::string & modelName,iIMDShape & model)> & func)69 void enumerateLoadedModels(const std::function<void (const std::string& modelName, iIMDShape& model)>& func)
70 {
71 	for (auto& keyvaluepair : models)
72 	{
73 		func(keyvaluepair.first, keyvaluepair.second);
74 	}
75 }
76 
tryLoad(const WzString & path,const WzString & filename)77 static bool tryLoad(const WzString &path, const WzString &filename)
78 {
79 	if (PHYSFS_exists(path + filename))
80 	{
81 		char *pFileData = nullptr, *fileEnd;
82 		UDWORD size = 0;
83 		if (!loadFile(WzString(path + filename).toUtf8().c_str(), &pFileData, &size))
84 		{
85 			debug(LOG_ERROR, "Failed to load model file: %s", WzString(path + filename).toUtf8().c_str());
86 			return false;
87 		}
88 		fileEnd = pFileData + size;
89 		const char *pFileDataPt = pFileData;
90 		iV_ProcessIMD(filename, (const char **)&pFileDataPt, fileEnd);
91 		free(pFileData);
92 		return true;
93 	}
94 	return false;
95 }
96 
modelName(iIMDShape * model)97 const std::string &modelName(iIMDShape *model)
98 {
99 	for (const auto &pair : models)
100 	{
101 		if (&pair.second == model)
102 		{
103 			return pair.first;
104 		}
105 	}
106 	ASSERT(false, "An IMD pointer could not be backtraced to a filename!");
107 	static std::string error;
108 	return error;
109 }
110 
modelGet(const WzString & filename)111 iIMDShape *modelGet(const WzString &filename)
112 {
113 	WzString name(filename.toLower());
114 	auto it = models.find(name.toStdString());
115 	if (it != models.end())
116 	{
117 		return &it->second; // cached
118 	}
119 	else if (tryLoad("structs/", name) || tryLoad("misc/", name) || tryLoad("effects/", name)
120 	         || tryLoad("components/prop/", name) || tryLoad("components/weapons/", name)
121 	         || tryLoad("components/bodies/", name) || tryLoad("features/", name)
122 	         || tryLoad("misc/micnum/", name) || tryLoad("misc/minum/", name) || tryLoad("misc/mivnum/", name) || tryLoad("misc/researchimds/", name))
123 	{
124 		return &models.at(name.toStdString());
125 	}
126 	debug(LOG_ERROR, "Could not find: %s", name.toUtf8().c_str());
127 	return nullptr;
128 }
129 
AtEndOfFile(const char * CurPos,const char * EndOfFile)130 static bool AtEndOfFile(const char *CurPos, const char *EndOfFile)
131 {
132 	while (*CurPos == 0x00 || *CurPos == 0x09 || *CurPos == 0x0a || *CurPos == 0x0d || *CurPos == 0x20)
133 	{
134 		CurPos++;
135 		if (CurPos >= EndOfFile)
136 		{
137 			return true;
138 		}
139 	}
140 
141 	if (CurPos >= EndOfFile)
142 	{
143 		return true;
144 	}
145 	return false;
146 }
147 
148 
149 /*!
150  * Load shape level polygons
151  * \param ppFileData Pointer to the data (usually read from a file)
152  * \param s Pointer to shape level
153  * \return false on error (memory allocation failure/bad file format), true otherwise
154  */
_imd_load_polys(const WzString & filename,const char ** ppFileData,iIMDShape * s,int pieVersion,const uint32_t npoints)155 static bool _imd_load_polys(const WzString &filename, const char **ppFileData, iIMDShape *s, int pieVersion, const uint32_t npoints)
156 {
157 	const char *pFileData = *ppFileData;
158 
159 	s->numFrames = 0;
160 	s->animInterval = 0;
161 
162 	for (unsigned i = 0; i < s->polys.size(); i++)
163 	{
164 		iIMDPoly *poly = &s->polys[i];
165 		unsigned int flags, npnts;
166 		int cnt;
167 
168 		if (sscanf(pFileData, "%x %u%n", &flags, &npnts, &cnt) != 2)
169 		{
170 			debug(LOG_ERROR, "(_load_polys) [poly %u] error loading flags and npoints", i);
171 		}
172 		pFileData += cnt;
173 
174 		poly->flags = flags;
175 		ASSERT_OR_RETURN(false, npnts == 3, "Invalid polygon size (%d)", npnts);
176 		if (sscanf(pFileData, "%" PRIu32 "%" PRIu32 "%" PRIu32 "%n", &poly->pindex[0], &poly->pindex[1], &poly->pindex[2], &cnt) != 3)
177 		{
178 			debug(LOG_ERROR, "failed reading triangle, point %d", i);
179 			return false;
180 		}
181 		pFileData += cnt;
182 
183 		// sanity check
184 		for (size_t pIdx = 0; pIdx < 3; pIdx++)
185 		{
186 			ASSERT_OR_RETURN(false, (poly->pindex[pIdx] < npoints), "Point index (%" PRIu32 ") exceeds max index (%" PRIu32 ")", poly->pindex[pIdx], (npoints - 1));
187 		}
188 
189 		// calc poly normal
190 		{
191 			Vector3f p0, p1, p2;
192 
193 			//assumes points already set
194 			p0.x = s->points[poly->pindex[0]].x;
195 			p0.y = s->points[poly->pindex[0]].y;
196 			p0.z = s->points[poly->pindex[0]].z;
197 
198 			p1.x = s->points[poly->pindex[1]].x;
199 			p1.y = s->points[poly->pindex[1]].y;
200 			p1.z = s->points[poly->pindex[1]].z;
201 
202 			p2.x = s->points[poly->pindex[2]].x;
203 			p2.y = s->points[poly->pindex[2]].y;
204 			p2.z = s->points[poly->pindex[2]].z;
205 
206 			poly->normal = pie_SurfaceNormal3fv(p0, p1, p2);
207 		}
208 
209 		// texture coord routine
210 		if (poly->flags & iV_IMD_TEX)
211 		{
212 			int nFrames, framesPerLine, frame, pbRate;
213 			float tWidth, tHeight;
214 
215 			if (poly->flags & iV_IMD_TEXANIM)
216 			{
217 				if (sscanf(pFileData, "%d %d %f %f%n", &nFrames, &pbRate, &tWidth, &tHeight, &cnt) != 4)
218 				{
219 					debug(LOG_ERROR, "(_load_polys) [poly %u] error reading texanim data", i);
220 					return false;
221 				}
222 				pFileData += cnt;
223 
224 				ASSERT(tWidth > 0.0001f, "%s: texture width = %f", filename.toUtf8().c_str(), tWidth);
225 				ASSERT(tHeight > 0.f, "%s: texture height = %f (width=%f)", filename.toUtf8().c_str(), tHeight, tWidth);
226 				ASSERT(nFrames > 1, "%s: animation frames = %d", filename.toUtf8().c_str(), nFrames);
227 				ASSERT(pbRate > 0, "%s: animation interval = %d ms", filename.toUtf8().c_str(), pbRate);
228 
229 				/* Must have same number of frames and same playback rate for all polygons */
230 				if (s->numFrames == 0)
231 				{
232 					s->numFrames = nFrames;
233 					s->animInterval = pbRate;
234 				}
235 				else
236 				{
237 					ASSERT(s->numFrames == nFrames,
238 					       "%s: varying number of frames within one PIE level: %d != %d",
239 					       filename.toUtf8().c_str(), nFrames, s->numFrames);
240 					ASSERT(s->animInterval == pbRate,
241 					       "%s: varying animation intervals within one PIE level: %d != %d",
242 					       filename.toUtf8().c_str(), pbRate, s->animInterval);
243 				}
244 
245 				poly->texAnim.x = tWidth;
246 				poly->texAnim.y = tHeight;
247 
248 				if (pieVersion != PIE_FLOAT_VER)
249 				{
250 					poly->texAnim.x /= OLD_TEXTURE_SIZE_FIX;
251 					poly->texAnim.y /= OLD_TEXTURE_SIZE_FIX;
252 				}
253 				framesPerLine = 1 / poly->texAnim.x;
254 			}
255 			else
256 			{
257 				nFrames = 1;
258 				framesPerLine = 1;
259 				pbRate = 1;
260 				tWidth = 0.f;
261 				tHeight = 0.f;
262 				poly->texAnim.x = 0;
263 				poly->texAnim.y = 0;
264 			}
265 
266 			poly->texCoord.resize(nFrames * 3);
267 			for (unsigned j = 0; j < 3; j++)
268 			{
269 				float VertexU, VertexV;
270 
271 				if (sscanf(pFileData, "%f %f%n", &VertexU, &VertexV, &cnt) != 2)
272 				{
273 					debug(LOG_ERROR, "(_load_polys) [poly %u] error reading tex outline", i);
274 					return false;
275 				}
276 				pFileData += cnt;
277 
278 				if (pieVersion != PIE_FLOAT_VER)
279 				{
280 					VertexU /= OLD_TEXTURE_SIZE_FIX;
281 					VertexV /= OLD_TEXTURE_SIZE_FIX;
282 				}
283 
284 				for (frame = 0; frame < nFrames; frame++)
285 				{
286 					const float uFrame = (frame % framesPerLine) * poly->texAnim.x;
287 					const float vFrame = (frame / framesPerLine) * poly->texAnim.y;
288 					Vector2f *c = &poly->texCoord[frame * 3 + j];
289 
290 					c->x = VertexU + uFrame;
291 					c->y = VertexV + vFrame;
292 				}
293 			}
294 		}
295 		else
296 		{
297 			ASSERT_OR_RETURN(false, !(poly->flags & iV_IMD_TEXANIM), "Polygons with texture animation must have textures!");
298 			poly->texCoord.clear();
299 		}
300 	}
301 
302 	*ppFileData = pFileData;
303 
304 	return true;
305 }
306 
307 
ReadPoints(const char ** ppFileData,iIMDShape & s)308 static bool ReadPoints(const char **ppFileData, iIMDShape &s)
309 {
310 	const char *pFileData = *ppFileData;
311 	int cnt;
312 
313 	for (Vector3f &points : s.points)
314 	{
315 		if (sscanf(pFileData, "%f %f %f%n", &points.x, &points.y, &points.z, &cnt) != 3)
316 		{
317 			debug(LOG_ERROR, "File corrupt - could not read points");
318 			return false;
319 		}
320 		pFileData += cnt;
321 	}
322 
323 	*ppFileData = pFileData;
324 
325 	return true;
326 }
327 
_imd_calc_bounds(iIMDShape & s)328 static void _imd_calc_bounds(iIMDShape &s)
329 {
330 	int32_t xmax, ymax, zmax;
331 	double dx, dy, dz, rad_sq, rad, old_to_p_sq, old_to_p, old_to_new;
332 	double xspan, yspan, zspan, maxspan;
333 	Vector3f dia1, dia2, cen;
334 	Vector3f vxmin(0, 0, 0), vymin(0, 0, 0), vzmin(0, 0, 0),
335 	         vxmax(0, 0, 0), vymax(0, 0, 0), vzmax(0, 0, 0);
336 
337 	s.max.x = s.max.y = s.max.z = -FP12_MULTIPLIER;
338 	s.min.x = s.min.y = s.min.z = FP12_MULTIPLIER;
339 
340 	vxmax.x = vymax.y = vzmax.z = -FP12_MULTIPLIER;
341 	vxmin.x = vymin.y = vzmin.z = FP12_MULTIPLIER;
342 
343 	// set up bounding data for minimum number of vertices
344 	for (const Vector3f &p : s.points)
345 	{
346 		if (p.x > s.max.x)
347 		{
348 			s.max.x = p.x;
349 		}
350 		if (p.x < s.min.x)
351 		{
352 			s.min.x = p.x;
353 		}
354 
355 		if (p.y > s.max.y)
356 		{
357 			s.max.y = p.y;
358 		}
359 		if (p.y < s.min.y)
360 		{
361 			s.min.y = p.y;
362 		}
363 
364 		if (p.z > s.max.z)
365 		{
366 			s.max.z = p.z;
367 		}
368 		if (p.z < s.min.z)
369 		{
370 			s.min.z = p.z;
371 		}
372 
373 		// for tight sphere calculations
374 		if (p.x < vxmin.x)
375 		{
376 			vxmin.x = p.x;
377 			vxmin.y = p.y;
378 			vxmin.z = p.z;
379 		}
380 
381 		if (p.x > vxmax.x)
382 		{
383 			vxmax.x = p.x;
384 			vxmax.y = p.y;
385 			vxmax.z = p.z;
386 		}
387 
388 		if (p.y < vymin.y)
389 		{
390 			vymin.x = p.x;
391 			vymin.y = p.y;
392 			vymin.z = p.z;
393 		}
394 
395 		if (p.y > vymax.y)
396 		{
397 			vymax.x = p.x;
398 			vymax.y = p.y;
399 			vymax.z = p.z;
400 		}
401 
402 		if (p.z < vzmin.z)
403 		{
404 			vzmin.x = p.x;
405 			vzmin.y = p.y;
406 			vzmin.z = p.z;
407 		}
408 
409 		if (p.z > vzmax.z)
410 		{
411 			vzmax.x = p.x;
412 			vzmax.y = p.y;
413 			vzmax.z = p.z;
414 		}
415 	}
416 
417 	// no need to scale an IMD shape (only FSD)
418 	xmax = MAX(s.max.x, -s.min.x);
419 	ymax = MAX(s.max.y, -s.min.y);
420 	zmax = MAX(s.max.z, -s.min.z);
421 
422 	s.radius = MAX(xmax, MAX(ymax, zmax));
423 	s.sradius = sqrtf(xmax * xmax + ymax * ymax + zmax * zmax);
424 
425 // START: tight bounding sphere
426 
427 	// set xspan = distance between 2 points xmin & xmax (squared)
428 	dx = vxmax.x - vxmin.x;
429 	dy = vxmax.y - vxmin.y;
430 	dz = vxmax.z - vxmin.z;
431 	xspan = dx * dx + dy * dy + dz * dz;
432 
433 	// same for yspan
434 	dx = vymax.x - vymin.x;
435 	dy = vymax.y - vymin.y;
436 	dz = vymax.z - vymin.z;
437 	yspan = dx * dx + dy * dy + dz * dz;
438 
439 	// and ofcourse zspan
440 	dx = vzmax.x - vzmin.x;
441 	dy = vzmax.y - vzmin.y;
442 	dz = vzmax.z - vzmin.z;
443 	zspan = dx * dx + dy * dy + dz * dz;
444 
445 	// set points dia1 & dia2 to maximally separated pair
446 	// assume xspan biggest
447 	dia1 = vxmin;
448 	dia2 = vxmax;
449 	maxspan = xspan;
450 
451 	if (yspan > maxspan)
452 	{
453 		maxspan = yspan;
454 		dia1 = vymin;
455 		dia2 = vymax;
456 	}
457 
458 	if (zspan > maxspan)
459 	{
460 		dia1 = vzmin;
461 		dia2 = vzmax;
462 	}
463 
464 	// dia1, dia2 diameter of initial sphere
465 	cen.x = (dia1.x + dia2.x) / 2.;
466 	cen.y = (dia1.y + dia2.y) / 2.;
467 	cen.z = (dia1.z + dia2.z) / 2.;
468 
469 	// calc initial radius
470 	dx = dia2.x - cen.x;
471 	dy = dia2.y - cen.y;
472 	dz = dia2.z - cen.z;
473 
474 	rad_sq = dx * dx + dy * dy + dz * dz;
475 	rad = sqrt((double)rad_sq);
476 
477 	// second pass (find tight sphere)
478 	for (const Vector3f &p : s.points)
479 	{
480 		dx = p.x - cen.x;
481 		dy = p.y - cen.y;
482 		dz = p.z - cen.z;
483 		old_to_p_sq = dx * dx + dy * dy + dz * dz;
484 
485 		// do r**2 first
486 		if (old_to_p_sq > rad_sq)
487 		{
488 			// this point outside current sphere
489 			old_to_p = sqrt((double)old_to_p_sq);
490 			// radius of new sphere
491 			rad = (rad + old_to_p) / 2.;
492 			// rad**2 for next compare
493 			rad_sq = rad * rad;
494 			old_to_new = old_to_p - rad;
495 			// centre of new sphere
496 			cen.x = (rad * cen.x + old_to_new * p.x) / old_to_p;
497 			cen.y = (rad * cen.y + old_to_new * p.y) / old_to_p;
498 			cen.z = (rad * cen.z + old_to_new * p.z) / old_to_p;
499 		}
500 	}
501 
502 	s.ocen = cen;
503 
504 // END: tight bounding sphere
505 }
506 
_imd_load_points(const char ** ppFileData,iIMDShape & s,uint32_t npoints)507 static bool _imd_load_points(const char **ppFileData, iIMDShape &s, uint32_t npoints)
508 {
509 	//load the points then pass through a second time to setup bounding datavalues
510 	s.points.resize(npoints);
511 
512 	// Read in points and remove duplicates (!)
513 	if (ReadPoints(ppFileData, s) == false)
514 	{
515 		s.points.clear();
516 		return false;
517 	}
518 
519 	_imd_calc_bounds(s);
520 
521 	return true;
522 }
523 
524 
525 /*!
526  * Load shape level connectors
527  * \param ppFileData Pointer to the data (usually read from a file)
528  * \param s Pointer to shape level
529  * \return false on error (memory allocation failure/bad file format), true otherwise
530  * \pre ppFileData loaded
531  * \pre s allocated
532  * \pre s->nconnectors set
533  * \post s->connectors allocated
534  */
_imd_load_connectors(const char ** ppFileData,iIMDShape & s)535 bool _imd_load_connectors(const char **ppFileData, iIMDShape &s)
536 {
537 	const char *pFileData = *ppFileData;
538 	int cnt;
539 	Vector3i *p = nullptr, newVector(0, 0, 0);
540 
541 	s.connectors = (Vector3i *)malloc(sizeof(Vector3i) * s.nconnectors);
542 	for (p = s.connectors; p < s.connectors + s.nconnectors; p++)
543 	{
544 		if (sscanf(pFileData, "%d %d %d%n",                         &newVector.x, &newVector.y, &newVector.z, &cnt) != 3 &&
545 		    sscanf(pFileData, "%d%*[.0-9] %d%*[.0-9] %d%*[.0-9]%n", &newVector.x, &newVector.y, &newVector.z, &cnt) != 3)
546 		{
547 			debug(LOG_ERROR, "(_load_connectors) file corrupt -M");
548 			return false;
549 		}
550 		pFileData += cnt;
551 		*p = newVector;
552 	}
553 
554 	*ppFileData = pFileData;
555 
556 	return true;
557 }
558 
559 // performance hack
560 static std::vector<gfx_api::gfxFloat> vertices;
561 static std::vector<gfx_api::gfxFloat> normals;
562 static std::vector<gfx_api::gfxFloat> texcoords;
563 static std::vector<gfx_api::gfxFloat> tangents;
564 static std::vector<gfx_api::gfxFloat> bitangents;
565 static std::vector<uint16_t> indices; // size is npolys * 3 * numFrames
566 static uint16_t vertexCount = 0;
567 
ReadNormals(const char ** ppFileData,std::vector<Vector3f> & pie_level_normals)568 static bool ReadNormals(const char **ppFileData, std::vector<Vector3f> &pie_level_normals)
569 {
570    const char *pFileData = *ppFileData;
571    int dataCnt;
572 
573    for (Vector3f &normal : pie_level_normals)
574    {
575 	   if (sscanf(pFileData, "%f %f %f%n", &normal.x, &normal.y, &normal.z, &dataCnt) != 3)
576 	   {
577 		   debug(LOG_ERROR, "File corrupt - could not read normals");
578 		   return false;
579 	   }
580 	   pFileData += dataCnt;
581    }
582 
583    *ppFileData = pFileData;
584 
585    return true;
586 }
587 
_imd_load_normals(const char ** ppFileData,std::vector<Vector3f> & pie_level_normals,uint32_t num_normal_lines)588 static bool _imd_load_normals(const char **ppFileData, std::vector<Vector3f> &pie_level_normals, uint32_t num_normal_lines)
589 {
590    // We only support triangles!
591    pie_level_normals.resize(static_cast<size_t>(num_normal_lines * 3));
592 
593    if (ReadNormals(ppFileData, pie_level_normals) == false)
594    {
595 	   pie_level_normals.clear();
596 	   return false;
597    }
598 
599    return true;
600 }
601 
addVertex(iIMDShape & s,size_t i,const iIMDPoly * p,int frameidx,size_t polyIdx,std::vector<Vector3f> & pie_level_normals)602 static inline uint16_t addVertex(iIMDShape &s, size_t i, const iIMDPoly *p, int frameidx, size_t polyIdx, std::vector<Vector3f> &pie_level_normals)
603 {
604 	// if texture animation flag is present, fetch animation coordinates for this polygon
605 	// otherwise just show the first set of texel coordinates
606 	int frame = (p->flags & iV_IMD_TEXANIM) ? frameidx : 0;
607 
608 	const Vector3f* normal;
609 
610  	// Do not weld for for models with normals, those are presumed to be correct. Otherwise, it will break tangents
611  	if (pie_level_normals.empty())
612  	{
613 		normal = &p->normal;
614 		// See if we already have this defined, if so, return reference to it.
615 		for (uint16_t j = 0; j < vertexCount; j++)
616 		{
617 			if (texcoords[j * 2 + 0] == p->texCoord[frame * 3 + i].x
618  			    && texcoords[j * 2 + 1] == p->texCoord[frame * 3 + i].y
619  			    && vertices[j * 3 + 0] == s.points[p->pindex[i]].x
620  			    && vertices[j * 3 + 1] == s.points[p->pindex[i]].y
621  			    && vertices[j * 3 + 2] == s.points[p->pindex[i]].z
622  			    && normals[j * 3 + 0] == normal->x
623  			    && normals[j * 3 + 1] == normal->y
624  			    && normals[j * 3 + 2] == normal->z)
625  			{
626  				return j;
627  			}
628 		}
629 	}
630 	else
631 	{
632 		normal = &pie_level_normals[polyIdx * 3 + i];
633 	}
634 
635 	// We don't have it, add it.
636 	normals.emplace_back(normal->x);
637  	normals.emplace_back(normal->y);
638  	normals.emplace_back(normal->z);
639 	texcoords.emplace_back(p->texCoord[frame * 3 + i].x);
640 	texcoords.emplace_back(p->texCoord[frame * 3 + i].y);
641 	vertices.emplace_back(s.points[p->pindex[i]].x);
642 	vertices.emplace_back(s.points[p->pindex[i]].y);
643 	vertices.emplace_back(s.points[p->pindex[i]].z);
644 	vertexCount++;
645 
646 	return vertexCount - 1;
647 }
648 
calculateTangentsForTriangle(const uint16_t ia,const uint16_t ib,const uint16_t ic)649 void calculateTangentsForTriangle(const uint16_t ia, const uint16_t ib, const uint16_t ic)
650 {
651    // This will work as long as vecs are packed (which is default in glm)
652    const Vector3f* verticesAsVec3 = reinterpret_cast<const Vector3f*>(vertices.data());
653    const Vector2f* texcoordsAsVec2 = reinterpret_cast<const Vector2f*>(texcoords.data());
654    Vector4f* tangentsAsVec4 = reinterpret_cast<Vector4f*>(tangents.data());
655    Vector3f* bitangentsAsVec3 = reinterpret_cast<Vector3f*>(bitangents.data());
656 
657    // Shortcuts for vertices
658    const Vector3f& va(verticesAsVec3[ia]);
659    const Vector3f& vb(verticesAsVec3[ib]);
660    const Vector3f& vc(verticesAsVec3[ic]);
661 
662    // Shortcuts for UVs
663    const Vector2f& uva(texcoordsAsVec2[ia]);
664    const Vector2f& uvb(texcoordsAsVec2[ib]);
665    const Vector2f& uvc(texcoordsAsVec2[ic]);
666 
667    // Edges of the triangle : postion delta
668    const Vector3f deltaPos1 = vb - va;
669    const Vector3f deltaPos2 = vc - va;
670 
671    // UV delta
672    const Vector2f deltaUV1 = uvb - uva;
673    const Vector2f deltaUV2 = uvc - uva;
674 
675    // check for nan
676    float r = deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x;
677    if (r != 0.f)
678 	   r = 1.f / r;
679 
680    const Vector4f tangent(Vector3f(deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r, 0.f);
681    const Vector3f bitangent(Vector3f(deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r);
682 
683    tangentsAsVec4[ia] += tangent;
684    tangentsAsVec4[ib] += tangent;
685    tangentsAsVec4[ic] += tangent;
686 
687    bitangentsAsVec3[ia] += bitangent;
688    bitangentsAsVec3[ib] += bitangent;
689    bitangentsAsVec3[ic] += bitangent;
690 }
691 
finishTangentsGeneration()692 void finishTangentsGeneration()
693 {
694    // This will work as long as vecs are packed (which is default in glm)
695    const Vector3f* normalsAsVec3 = reinterpret_cast<const Vector3f*>(normals.data());
696    Vector4f* tangentsAsVec4 = reinterpret_cast<Vector4f*>(tangents.data());
697    const Vector3f* bitangentsAsVec3 = reinterpret_cast<const Vector3f*>(bitangents.data());
698 
699    Vector3f t;
700 
701    for (auto i = 0; i < vertexCount; ++i)
702    {
703 	   const Vector3f& n = normalsAsVec3[i];
704 	   const Vector3f& b = bitangentsAsVec3[i];
705 	   t = tangentsAsVec4[i].xyz();
706 
707 	   // Gram-Schmidt orthogonalize
708 	   t = glm::normalize(t - n * glm::dot(n, t));
709 
710 	   // Calculate handedness
711 	   if (glm::dot(glm::cross(n, t), b) < 0.f)
712 	   {
713 		   tangentsAsVec4[i] = Vector4f(-t, 1.f);
714 	   }
715 	   else
716 	   {
717 		   tangentsAsVec4[i] = Vector4f(-t, -1.f);
718 	   }
719    }
720 }
721 
722 
723 /*!
724  * Load shape levels recursively
725  * \param ppFileData Pointer to the data (usually read from a file)
726  * \param FileDataEnd ???
727  * \param nlevels Number of levels to load
728  * \return pointer to iFSDShape structure (or NULL on error)
729  * \pre ppFileData loaded
730  * \post s allocated
731  */
_imd_load_level(const WzString & filename,const char ** ppFileData,const char * FileDataEnd,int nlevels,int pieVersion,int level)732 static iIMDShape *_imd_load_level(const WzString &filename, const char **ppFileData, const char *FileDataEnd, int nlevels, int pieVersion, int level)
733 {
734 	const char *pFileData = *ppFileData;
735 	char buffer[PATH_MAX] = {'\0'};
736 	int cnt = 0, n = 0, scanResult = 0;
737 	float dummy;
738 
739 	if (nlevels == 0)
740 	{
741 		return nullptr;
742 	}
743 
744 	// insert model
745 	std::string key = filename.toStdString();
746 	if (level > 0)
747 	{
748 		key += "_" + std::to_string(level);
749 	}
750 	ASSERT(models.count(key) == 0, "Duplicate model load for %s!", key.c_str());
751 	iIMDShape &s = models[key]; // create entry and return reference
752 	s.pShadowPoints = &s.points;
753 	s.pShadowPolys = &s.polys;
754 
755 	scanResult = sscanf(pFileData, "%255s %n", buffer, &cnt);
756 	ASSERT_OR_RETURN(nullptr, scanResult == 1, "Bad directive following LEVEL");
757 
758 	// Optionally load and ignore deprecated MATERIALS directive
759 	if (strcmp(buffer, "MATERIALS") == 0)
760 	{
761 		scanResult = sscanf(pFileData, "%255s %f %f %f %f %f %f %f %f %f %f%n", buffer, &dummy, &dummy, &dummy, &dummy,
762 		           &dummy, &dummy, &dummy, &dummy, &dummy, &dummy, &cnt);
763 		ASSERT_OR_RETURN(nullptr, scanResult == 11, "Bad MATERIALS directive");
764 		debug(LOG_WARNING, "MATERIALS directive no longer supported!");
765 		pFileData += cnt;
766 	}
767 	else if (strcmp(buffer, "SHADERS") == 0)
768 	{
769 		char vertex[PATH_MAX], fragment[PATH_MAX];
770 
771 		if (sscanf(pFileData, "%255s %255s %255s%n", buffer, vertex, fragment, &cnt) != 3)
772 		{
773 			debug(LOG_ERROR, "%s shader corrupt: %s", filename.toUtf8().c_str(), buffer);
774 			return nullptr;
775 		}
776 		debug(LOG_WARNING, "SHADERS directive no longer supported!");
777 		pFileData += cnt;
778 	}
779 
780 	uint32_t npoints = 0;
781 	if (sscanf(pFileData, "%255s %" PRIu32 "%n", buffer, &npoints, &cnt) != 2)
782 	{
783 		debug(LOG_ERROR, "_imd_load_level(2): file corrupt");
784 		return nullptr;
785 	}
786 	pFileData += cnt;
787 
788 	// load points
789 
790 	ASSERT_OR_RETURN(nullptr, strcmp(buffer, "POINTS") == 0, "Expecting 'POINTS' directive, got: %s", buffer);
791 
792 	_imd_load_points(&pFileData, s, npoints);
793 
794 	uint32_t npolys = 0;
795 	if (sscanf(pFileData, "%255s %" PRIu32 "%n", buffer, &npolys, &cnt) != 2)
796 	{
797 		debug(LOG_ERROR, "_imd_load_level(3): file corrupt");
798 		return nullptr;
799 	}
800 	pFileData += cnt;
801 
802 	std::vector<Vector3f> pie_level_normals;
803 
804 	// It could be optional normals directive
805  	if (strcmp(buffer, "NORMALS") == 0)
806  	{
807  		_imd_load_normals(&pFileData, pie_level_normals, npolys);
808 
809  		// Attemps to read polys again
810  		if (sscanf(pFileData, "%255s %" PRIu32 "%n", buffer, &npolys, &cnt) != 2)
811  		{
812  			debug(LOG_ERROR, "_imd_load_level(3a): file corrupt");
813  			return nullptr;
814  		}
815 		debug(LOG_3D, "imd[_load_level] = normals %d", static_cast<int>(pie_level_normals.size()));
816  		pFileData += cnt;
817  	}
818 
819 	s.polys.resize(npolys);
820 
821 	ASSERT_OR_RETURN(nullptr, strcmp(buffer, "POLYGONS") == 0, "Expecting 'POLYGONS' directive, got: %s", buffer);
822 
823 	_imd_load_polys(filename, &pFileData, &s, pieVersion, npoints);
824 
825 	// optional stuff : levels, object animations, connectors, shadow points + polys
826 	s.objanimframes = 0;
827 	while (!AtEndOfFile(pFileData, FileDataEnd)) // check for end of file (give or take white space)
828 	{
829 		// Scans in the line ... if we don't get 2 parameters then quit
830 		if (sscanf(pFileData, "%255s %d%n", buffer, &n, &cnt) != 2)
831 		{
832 			break;
833 		}
834 		pFileData += cnt;
835 
836 		if (strcmp(buffer, "LEVEL") == 0)	// check for next level
837 		{
838 			debug(LOG_3D, "imd[_load_level] = npoints %" PRIu32 ", npolys %" PRIu32 "", npoints, npolys);
839 			s.next = _imd_load_level(filename, &pFileData, FileDataEnd, nlevels - 1, pieVersion, level + 1);
840 		}
841 		else if (strcmp(buffer, "CONNECTORS") == 0)
842 		{
843 			//load connector stuff
844 			s.nconnectors = n;
845 			_imd_load_connectors(&pFileData, s);
846 		}
847 		else if (strcmp(buffer, "ANIMOBJECT") == 0)
848 		{
849 			s.objanimtime = n;
850 			if (sscanf(pFileData, "%d %d%n", &s.objanimcycles, &s.objanimframes, &cnt) != 2)
851 			{
852 				debug(LOG_ERROR, "%s bad ANIMOBJ: %s", filename.toUtf8().c_str(), pFileData);
853 				return nullptr;
854 			}
855 			pFileData += cnt;
856 			s.objanimdata.resize(s.objanimframes);
857 			for (int i = 0; i < s.objanimframes; i++)
858 			{
859 				int frame;
860 				Vector3i pos(0, 0, 0), rot(0, 0, 0);
861 
862 				if (sscanf(pFileData, "%d %d %d %d %d %d %d %f %f %f%n",
863 				           &frame, &pos.x, &pos.y, &pos.z, &rot.x, &rot.y, &rot.z,
864 				           &s.objanimdata[i].scale.x, &s.objanimdata[i].scale.y, &s.objanimdata[i].scale.z, &cnt) != 10)
865 				{
866 					debug(LOG_ERROR, "%s: Invalid object animation level %d, line %d, frame %d", filename.toUtf8().c_str(), level, i, frame);
867 				}
868 				ASSERT(frame == i, "%s: Invalid frame enumeration object animation (level %d) %d: %d", filename.toUtf8().c_str(), level, i, frame);
869 				s.objanimdata[i].pos.x = pos.x / INT_SCALE;
870 				s.objanimdata[i].pos.y = pos.z / INT_SCALE;
871 				s.objanimdata[i].pos.z = pos.y / INT_SCALE;
872 				s.objanimdata[i].rot.pitch = -(rot.x * DEG_1 / INT_SCALE);
873 				s.objanimdata[i].rot.direction = -(rot.z * DEG_1 / INT_SCALE);
874 				s.objanimdata[i].rot.roll = -(rot.y * DEG_1 / INT_SCALE);
875 				pFileData += cnt;
876 			}
877 		}
878 		else if (strcmp(buffer, "SHADOWPOINTS") == 0)
879 		{
880 			ASSERT_OR_RETURN(nullptr, n > 0, "Invalid 'SHADOW_POINTS' count, got: %d", n);
881 			uint32_t nShadowPoints = static_cast<uint32_t>(n);
882 
883 			iIMDShape tmpShadowShape;
884 			_imd_load_points(&pFileData, tmpShadowShape, nShadowPoints);
885 
886 			s.altShadowPoints = std::move(tmpShadowShape.points);
887 			s.pShadowPoints = &s.altShadowPoints;
888 		}
889 		else if (strcmp(buffer, "SHADOWPOLYGONS") == 0)
890 		{
891 			ASSERT_OR_RETURN(nullptr, s.altShadowPoints.size() > 0, "'SHADOW_POLYGONS' must follow a non-empty SHADOW_POINTS section");
892 			ASSERT_OR_RETURN(nullptr, n > 0, "Invalid 'SHADOW_POLYGONS' count, got: %d", n);
893 			uint32_t nShadowPolys = static_cast<uint32_t>(n);
894 
895 			iIMDShape tmpShadowShape;
896 			tmpShadowShape.polys.resize(nShadowPolys);
897 			_imd_load_polys(filename, &pFileData, &tmpShadowShape, PIE_FLOAT_VER, static_cast<uint32_t>(s.altShadowPoints.size()));
898 
899 			s.altShadowPolys = std::move(tmpShadowShape.polys);
900 			s.pShadowPolys = &s.altShadowPolys;
901 		}
902 		else
903 		{
904 			debug(LOG_ERROR, "(_load_level) unexpected directive %s %d", buffer, n);
905 			break;
906 		}
907 	}
908 
909 	// Sanity checks
910  	if (!pie_level_normals.empty())
911  	{
912  		if (s.polys.size() * 3 != pie_level_normals.size())
913  		{
914  			debug(LOG_ERROR, "imd[_load_level] = got %" PRIu32 " npolys, but there are only %zu normals! Discarding normals...",
915  			      npolys, pie_level_normals.size());
916  			// This will force usage of calculated normals in addVertex
917  			pie_level_normals.clear();
918  		}
919  	}
920 	if (!s.altShadowPoints.empty())
921 	{
922 		if (s.altShadowPolys.empty())
923 		{
924 			debug(LOG_ERROR, "imd[_load_level] = %zu SHADOWPOINTS specified, but there are no SHADOWPOLYGONS! Discarding shadow points...",
925 				  s.altShadowPoints.size());
926 			s.altShadowPoints.clear();
927 			s.altShadowPolys.clear();
928 			s.pShadowPoints = &s.points;
929 			s.pShadowPolys = &s.polys;
930 		}
931 	}
932 
933 	// FINALLY, massage the data into what can stream directly to OpenGL
934 	vertexCount = 0;
935 	for (int k = 0; k < MAX(1, s.numFrames); k++)
936 	{
937 		// Go through all polygons for each frame
938 		for (size_t npol = 0; npol < s.polys.size(); ++npol)
939 		{
940 			const iIMDPoly& p = s.polys[npol];
941 			// Do we already have the vertex data for this polygon?
942 			indices.emplace_back(addVertex(s, 0, &p, k, npol, pie_level_normals));
943  			indices.emplace_back(addVertex(s, 1, &p, k, npol, pie_level_normals));
944  			indices.emplace_back(addVertex(s, 2, &p, k, npol, pie_level_normals));
945 		}
946 	}
947 
948 	s.vertexCount = vertexCount;
949 
950 	// Tangents are optional, only if normals were loaded and passed sanity check above
951  	if (!pie_level_normals.empty())
952  	{
953  		tangents.resize(vertexCount * 4);
954  		bitangents.resize(vertexCount * 3);
955 
956  		for (size_t i = 0; i < indices.size(); i += 3)
957  			calculateTangentsForTriangle(indices[i], indices[i+1], indices[i+2]);
958  		finishTangentsGeneration();
959 
960  		if (!tangents.empty())
961  		{
962  			if (!s.buffers[VBO_TANGENT])
963  				s.buffers[VBO_TANGENT] = gfx_api::context::get().create_buffer_object(gfx_api::buffer::usage::vertex_buffer);
964  			s.buffers[VBO_TANGENT]->upload(tangents.size() * sizeof(gfx_api::gfxFloat), tangents.data());
965  		}
966  	}
967 
968 	if (!s.buffers[VBO_VERTEX])
969 		s.buffers[VBO_VERTEX] = gfx_api::context::get().create_buffer_object(gfx_api::buffer::usage::vertex_buffer);
970 	s.buffers[VBO_VERTEX]->upload(vertices.size() * sizeof(gfx_api::gfxFloat), vertices.data());
971 
972 	if (!s.buffers[VBO_NORMAL])
973 		s.buffers[VBO_NORMAL] = gfx_api::context::get().create_buffer_object(gfx_api::buffer::usage::vertex_buffer);
974 	s.buffers[VBO_NORMAL]->upload(normals.size() * sizeof(gfx_api::gfxFloat), normals.data());
975 
976 	if (!s.buffers[VBO_INDEX])
977 		s.buffers[VBO_INDEX] = gfx_api::context::get().create_buffer_object(gfx_api::buffer::usage::index_buffer);
978 	s.buffers[VBO_INDEX]->upload(indices.size() * sizeof(uint16_t), indices.data());
979 
980 	if (!s.buffers[VBO_TEXCOORD])
981 		s.buffers[VBO_TEXCOORD] = gfx_api::context::get().create_buffer_object(gfx_api::buffer::usage::vertex_buffer);
982 	s.buffers[VBO_TEXCOORD]->upload(texcoords.size() * sizeof(gfx_api::gfxFloat), texcoords.data());
983 
984 	indices.resize(0);
985 	vertices.resize(0);
986 	texcoords.resize(0);
987 	normals.resize(0);
988 	tangents.resize(0);
989 	bitangents.resize(0);
990 
991 	*ppFileData = pFileData;
992 
993 	return &s;
994 }
995 
996 /*!
997  * Load ppFileData into a shape
998  * \param ppFileData Data from the IMD file
999  * \param FileDataEnd Endpointer
1000  * \return The shape, constructed from the data read
1001  */
1002 // ppFileData is incremented to the end of the file on exit!
iV_ProcessIMD(const WzString & filename,const char ** ppFileData,const char * FileDataEnd)1003 static void iV_ProcessIMD(const WzString &filename, const char **ppFileData, const char *FileDataEnd)
1004 {
1005 	const char *pFileData = *ppFileData;
1006 	char buffer[PATH_MAX], texfile[PATH_MAX], normalfile[PATH_MAX], specfile[PATH_MAX];
1007 	int cnt, nlevels, interpolate;
1008 	UDWORD level;
1009 	int32_t imd_version;
1010 	uint32_t imd_flags;
1011 	bool bTextured = false;
1012 	bool readInterpolate = false;
1013 	iIMDShape *objanimpie[ANIM_EVENT_COUNT];
1014 
1015 	memset(normalfile, 0, sizeof(normalfile));
1016 	memset(specfile, 0, sizeof(specfile));
1017 
1018 	if (sscanf(pFileData, "%255s %d%n", buffer, &imd_version, &cnt) != 2)
1019 	{
1020 		debug(LOG_ERROR, "%s: bad PIE version: (%s)", filename.toUtf8().c_str(), buffer);
1021 		assert(false);
1022 		return;
1023 	}
1024 	pFileData += cnt;
1025 
1026 	if (strcmp(PIE_NAME, buffer) != 0)
1027 	{
1028 		debug(LOG_ERROR, "%s: Not an IMD file (%s %d)", filename.toUtf8().c_str(), buffer, imd_version);
1029 		return;
1030 	}
1031 
1032 	//Now supporting version PIE_VER and PIE_FLOAT_VER files
1033 	if (imd_version != PIE_VER && imd_version != PIE_FLOAT_VER)
1034 	{
1035 		debug(LOG_ERROR, "%s: Version %d not supported", filename.toUtf8().c_str(), imd_version);
1036 		return;
1037 	}
1038 
1039 	// Read flag
1040 	if (sscanf(pFileData, "%255s %x%n", buffer, &imd_flags, &cnt) != 2)
1041 	{
1042 		debug(LOG_ERROR, "%s: bad flags: %s", filename.toUtf8().c_str(), buffer);
1043 		return;
1044 	}
1045 	pFileData += cnt;
1046 
1047 	// Read interpolation -- optional
1048 	if (sscanf(pFileData, "%255s %d%n", buffer, &interpolate, &cnt) != 2)
1049 	{
1050 		debug(LOG_ERROR, "%s: Expecting INTERPOLATE: %s", filename.toUtf8().c_str(), buffer);
1051 		return;
1052 	}
1053 	if (strncmp(buffer, "INTERPOLATE", 11) == 0)
1054 	{
1055 		readInterpolate = true;
1056 		pFileData += cnt;
1057 	}
1058 
1059 	/* This can be either texture or levels */
1060 	if (sscanf(pFileData, "%255s %d%n", buffer, &nlevels, &cnt) != 2)
1061 	{
1062 		debug(LOG_ERROR, "%s: Expecting TEXTURE or LEVELS: %s", filename.toUtf8().c_str(), buffer);
1063 		return;
1064 	}
1065 	pFileData += cnt;
1066 
1067 	// get texture page if specified
1068 	if (strncmp(buffer, "TEXTURE", 7) == 0)
1069 	{
1070 		int i, pwidth, pheight;
1071 		char ch, texType[PATH_MAX];
1072 
1073 		/* the first parameter for textures is always ignored; which is why we ignore
1074 		 * nlevels read in above */
1075 		ch = *pFileData++;
1076 
1077 		// Run up to the dot or till the buffer is filled. Leave room for the extension.
1078 		for (i = 0; i < PATH_MAX - 5 && (ch = *pFileData++) != '\0' && ch != '.'; ++i)
1079 		{
1080 			texfile[i] = ch;
1081 		}
1082 		texfile[i] = '\0';
1083 
1084 		if (sscanf(pFileData, "%255s%n", texType, &cnt) != 1)
1085 		{
1086 			debug(LOG_ERROR, "%s: Texture info corrupt: %s", filename.toUtf8().c_str(), buffer);
1087 			return;
1088 		}
1089 		pFileData += cnt;
1090 
1091 		if (strcmp(texType, "png") != 0)
1092 		{
1093 			debug(LOG_ERROR, "%s: Only png textures supported", filename.toUtf8().c_str());
1094 			return;
1095 		}
1096 		sstrcat(texfile, ".png");
1097 
1098 		if (sscanf(pFileData, "%d %d%n", &pwidth, &pheight, &cnt) != 2)
1099 		{
1100 			debug(LOG_ERROR, "%s: Bad texture size: %s", filename.toUtf8().c_str(), buffer);
1101 			return;
1102 		}
1103 		pFileData += cnt;
1104 
1105 		/* Now read in LEVELS directive */
1106 		if (sscanf(pFileData, "%255s %d%n", buffer, &nlevels, &cnt) != 2)
1107 		{
1108 			debug(LOG_ERROR, "%s: Bad levels info: %s", filename.toUtf8().c_str(), buffer);
1109 			return;
1110 		}
1111 		pFileData += cnt;
1112 
1113 		bTextured = true;
1114 	}
1115 
1116 	if (strncmp(buffer, "NORMALMAP", 9) == 0)
1117 	{
1118 		char ch, texType[PATH_MAX];
1119 		int i;
1120 
1121 		/* the first parameter for textures is always ignored; which is why we ignore
1122 		 * nlevels read in above */
1123 		ch = *pFileData++;
1124 
1125 		// Run up to the dot or till the buffer is filled. Leave room for the extension.
1126 		for (i = 0; i < PATH_MAX - 5 && (ch = *pFileData++) != '\0' && ch != '.'; ++i)
1127 		{
1128 			normalfile[i] = ch;
1129 		}
1130 		normalfile[i] = '\0';
1131 
1132 		if (sscanf(pFileData, "%255s%n", texType, &cnt) != 1)
1133 		{
1134 			debug(LOG_ERROR, "%s: Normal map info corrupt: %s", filename.toUtf8().c_str(), buffer);
1135 			return;
1136 		}
1137 		pFileData += cnt;
1138 
1139 		if (strcmp(texType, "png") != 0)
1140 		{
1141 			debug(LOG_ERROR, "%s: Only png normal maps supported", filename.toUtf8().c_str());
1142 			return;
1143 		}
1144 		sstrcat(normalfile, ".png");
1145 
1146 		/* Now read in LEVELS directive */
1147 		if (sscanf(pFileData, "%255s %d%n", buffer, &nlevels, &cnt) != 2)
1148 		{
1149 			debug(LOG_ERROR, "%s: Bad levels info: %s", filename.toUtf8().c_str(), buffer);
1150 			return;
1151 		}
1152 		pFileData += cnt;
1153 	}
1154 
1155 	if (strncmp(buffer, "SPECULARMAP", 11) == 0)
1156 	{
1157 		char ch, texType[PATH_MAX];
1158 		int i;
1159 
1160 		/* the first parameter for textures is always ignored; which is why we ignore nlevels read in above */
1161 		ch = *pFileData++;
1162 
1163 		// Run up to the dot or till the buffer is filled. Leave room for the extension.
1164 		for (i = 0; i < PATH_MAX - 5 && (ch = *pFileData++) != '\0' && ch != '.'; ++i)
1165 		{
1166 			specfile[i] = ch;
1167 		}
1168 		specfile[i] = '\0';
1169 
1170 		if (sscanf(pFileData, "%255s%n", texType, &cnt) != 1)
1171 		{
1172 			debug(LOG_ERROR, "%s specular map info corrupt: %s", filename.toUtf8().c_str(), buffer);
1173 			return;
1174 		}
1175 		pFileData += cnt;
1176 
1177 		if (strcmp(texType, "png") != 0)
1178 		{
1179 			debug(LOG_ERROR, "%s: only png specular maps supported", filename.toUtf8().c_str());
1180 			return;
1181 		}
1182 		sstrcat(specfile, ".png");
1183 
1184 		/* Try -again- to read in LEVELS directive */
1185 		if (sscanf(pFileData, "%255s %d%n", buffer, &nlevels, &cnt) != 2)
1186 		{
1187 			debug(LOG_ERROR, "%s: Bad levels info: %s", filename.toUtf8().c_str(), buffer);
1188 			return;
1189 		}
1190 		pFileData += cnt;
1191 	}
1192 
1193 	for (int i = 0; i < ANIM_EVENT_COUNT; i++)
1194 	{
1195 		objanimpie[i] = nullptr;
1196 	}
1197 	while (strncmp(buffer, "EVENT", 5) == 0)
1198 	{
1199 		char animpie[PATH_MAX];
1200 
1201 		ASSERT(nlevels < ANIM_EVENT_COUNT && nlevels >= 0, "Invalid event type %d", nlevels);
1202 		pFileData++;
1203 		if (sscanf(pFileData, "%255s%n", animpie, &cnt) != 1)
1204 		{
1205 			debug(LOG_ERROR, "%s animation model corrupt: %s", filename.toUtf8().c_str(), buffer);
1206 			return;
1207 		}
1208 		pFileData += cnt;
1209 
1210 		objanimpie[nlevels] = modelGet(animpie);
1211 
1212 		/* Try -yet again- to read in LEVELS directive */
1213 		if (sscanf(pFileData, "%255s %d%n", buffer, &nlevels, &cnt) != 2)
1214 		{
1215 			debug(LOG_ERROR, "%s: Bad levels info: %s", filename.toUtf8().c_str(), buffer);
1216 			return;
1217 		}
1218 		pFileData += cnt;
1219 	}
1220 
1221 	if (strncmp(buffer, "LEVELS", 6) != 0)
1222 	{
1223 		debug(LOG_ERROR, "%s: Expecting 'LEVELS' directive (%s)", filename.toUtf8().c_str(), buffer);
1224 		return;
1225 	}
1226 
1227 	/* Read first LEVEL directive */
1228 	if (sscanf(pFileData, "%255s %u%n", buffer, &level, &cnt) != 2)
1229 	{
1230 		debug(LOG_ERROR, "(_load_level) file corrupt -J");
1231 		return;
1232 	}
1233 	pFileData += cnt;
1234 	level--; // make zero indexed
1235 
1236 	if (strncmp(buffer, "LEVEL", 5) != 0)
1237 	{
1238 		debug(LOG_ERROR, "%s: Expecting 'LEVEL' directive (%s)", filename.toUtf8().c_str(), buffer);
1239 		return;
1240 	}
1241 
1242 	iIMDShape *shape = _imd_load_level(filename, &pFileData, FileDataEnd, nlevels, imd_version, level);
1243 	if (shape == nullptr)
1244 	{
1245 		debug(LOG_ERROR, "%s: Unsuccessful", filename.toUtf8().c_str());
1246 		return;
1247 	}
1248 
1249 	// load texture page if specified
1250 	if (bTextured)
1251 	{
1252 		optional<size_t> texpage = iV_GetTexture(texfile);
1253 		optional<size_t> normalpage;
1254 		optional<size_t> specpage;
1255 
1256 		ASSERT_OR_RETURN(, texpage.has_value(), "%s could not load tex page %s", filename.toUtf8().c_str(), texfile);
1257 
1258 		if (normalfile[0] != '\0')
1259 		{
1260 			debug(LOG_TEXTURE, "Loading normal map %s for %s", normalfile, filename.toUtf8().c_str());
1261 			normalpage = iV_GetTexture(normalfile, false);
1262 			ASSERT_OR_RETURN(, normalpage.has_value(), "%s could not load tex page %s", filename.toUtf8().c_str(), normalfile);
1263 		}
1264 
1265 		if (specfile[0] != '\0')
1266 		{
1267 			debug(LOG_TEXTURE, "Loading specular map %s for %s", specfile, filename.toUtf8().c_str());
1268 			specpage = iV_GetTexture(specfile, false);
1269 			ASSERT_OR_RETURN(, specpage.has_value(), "%s could not load tex page %s", filename.toUtf8().c_str(), specfile);
1270 		}
1271 
1272 		// assign tex pages and flags to all levels
1273 		for (iIMDShape *psShape = shape; psShape != nullptr; psShape = psShape->next)
1274 		{
1275 			psShape->texpage = texpage.value();
1276 			psShape->normalpage = (normalpage.has_value()) ? normalpage.value() : iV_TEX_INVALID;
1277 			psShape->specularpage = (specpage.has_value()) ? specpage.value() : iV_TEX_INVALID;
1278 			psShape->flags = imd_flags;
1279 			psShape->interpolate = (readInterpolate) ? interpolate : 1;
1280 		}
1281 
1282 		// check if model should use team colour mask
1283 		if (imd_flags & iV_IMD_TCMASK)
1284 		{
1285 			std::string tcmask_name = pie_MakeTexPageTCMaskName(texfile);
1286 			tcmask_name += ".png";
1287 			optional<size_t> texpage_mask = iV_GetTexture(tcmask_name.c_str());
1288 
1289 			ASSERT_OR_RETURN(, texpage_mask.has_value(), "%s could not load tcmask %s", filename.toUtf8().c_str(), tcmask_name.c_str());
1290 
1291 			// Propagate settings through levels
1292 			for (iIMDShape *psShape = shape; psShape != nullptr; psShape = psShape->next)
1293 			{
1294 				psShape->tcmaskpage = (texpage_mask.has_value()) ? texpage_mask.value() : iV_TEX_INVALID;
1295 			}
1296 		}
1297 	}
1298 
1299 	// copy over model-wide animation information, stored only in the first level
1300 	for (int i = 0; i < ANIM_EVENT_COUNT; i++)
1301 	{
1302 		shape->objanimpie[i] = objanimpie[i];
1303 	}
1304 
1305 	*ppFileData = pFileData;
1306 }
1307