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