1 /** @file mapimporter.cpp Resource importer for id Tech 1 format maps.
2 *
3 * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 * @authors Copyright © 2006-2014 Daniel Swanson <danij@dengine.net>
5 *
6 * @par License
7 * GPL: http://www.gnu.org/licenses/gpl.html
8 *
9 * <small>This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by the
11 * Free Software Foundation; either version 2 of the License, or (at your
12 * option) any later version. This program is distributed in the hope that it
13 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15 * Public License for more details. You should have received a copy of the GNU
16 * General Public License along with this program; if not, write to the Free
17 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18 * 02110-1301 USA</small>
19 */
20
21 #include "mapimporter.h"
22 #include <de/libcore.h>
23 #include <de/Error>
24 #include <de/ByteRefArray>
25 #include <de/LogBuffer>
26 #include <de/Reader>
27 #include <de/Time>
28 #include <de/Vector>
29 #include "importidtech1.h"
30
31 #include <QVector>
32
33 #include <vector>
34 #include <list>
35 #include <set>
36
37 using namespace de;
38
39 namespace idtech1 {
40 namespace internal {
41
42 /**
43 * Intersect an unbounded line with a bounded line segment.
44 *
45 * @todo This is from libgloom (geomath.h); should not duplicate it here but use
46 * that one in the future when it is available.
47 *
48 * @returns true, if line A-B intersects the line segment @a other.
49 */
lineSegmentIntersection(double & lineT,const Vec2d & lineA,const Vec2d & lineB,const Vec2d & segmentA,const Vec2d & segmentB)50 static bool lineSegmentIntersection(double &lineT,
51 const Vec2d &lineA, const Vec2d &lineB,
52 const Vec2d &segmentA, const Vec2d &segmentB)
53 {
54 const auto &p = segmentA;
55 const auto r = segmentB - segmentA;
56
57 const auto &q = lineA;
58 const auto s = lineB - lineA;
59
60 const double r_s = r.cross(s);
61 if (std::abs(r_s) < EPSILON)
62 {
63 return false;
64 }
65 lineT = (q - p).cross(r) / r_s;
66
67 // It has to hit somewhere on `other`.
68 const double u = (q - p).cross(s) / r_s;
69 return u >= 0 && u < 1;
70 }
71
72 /// @todo kludge - remove me.
73 class Id1MapElement
74 {
75 public:
Id1MapElement(MapImporter & map)76 Id1MapElement(MapImporter &map) : _map(&map) {}
Id1MapElement(Id1MapElement const & other)77 Id1MapElement(Id1MapElement const &other) : _map(other._map) {}
~Id1MapElement()78 virtual ~Id1MapElement() {}
79
map() const80 MapImporter &map() const {
81 DENG2_ASSERT(_map != 0);
82 return *_map;
83 }
84
85 MapImporter *_map;
86 };
87
88 struct Vertex
89 {
90 Vec2d pos;
91 std::set<int> lines; // lines connected to this vertex
92 };
93
94 struct SideDef : public Id1MapElement
95 {
96 dint index;
97 dint16 offset[2];
98 MaterialId topMaterial;
99 MaterialId bottomMaterial;
100 MaterialId middleMaterial;
101 dint sector;
102
SideDefidtech1::internal::SideDef103 SideDef(MapImporter &map) : Id1MapElement(map) {}
104
operator <<idtech1::internal::SideDef105 void operator << (de::Reader &from)
106 {
107 Id1MapRecognizer::Format format = Id1MapRecognizer::Format(from.version());
108
109 from >> offset[VX]
110 >> offset[VY];
111
112 dint idx;
113 switch(format)
114 {
115 case Id1MapRecognizer::DoomFormat:
116 case Id1MapRecognizer::HexenFormat: {
117 Block name;
118 from.readBytes(8, name);
119 topMaterial = map().toMaterialId(name.constData(), WallMaterials);
120
121 from.readBytes(8, name);
122 bottomMaterial = map().toMaterialId(name.constData(), WallMaterials);
123
124 from.readBytes(8, name);
125 middleMaterial = map().toMaterialId(name.constData(), WallMaterials);
126 break; }
127
128 case Id1MapRecognizer::Doom64Format:
129 from.readAs<duint16>(idx);
130 topMaterial = map().toMaterialId(idx, WallMaterials);
131
132 from.readAs<duint16>(idx);
133 bottomMaterial = map().toMaterialId(idx, WallMaterials);
134
135 from.readAs<duint16>(idx);
136 middleMaterial = map().toMaterialId(idx, WallMaterials);
137 break;
138
139 default:
140 DENG2_ASSERT(!"idtech1::SideDef::read: unknown map format!");
141 break;
142 };
143
144 from.readAs<duint16>(idx);
145 sector = (idx == 0xFFFF? -1 : idx);
146 }
147 };
148
149 /**
150 * @defgroup lineAnalysisFlags Line Analysis flags
151 */
152 ///@{
153 #define LAF_POLYOBJ 0x1 ///< Line defines a polyobj segment.
154 ///@}
155
156 #define PO_LINE_START (1) ///< Polyobj line start special.
157 #define PO_LINE_EXPLICIT (5)
158
159 #define SEQTYPE_NUMSEQ (10)
160
161 struct LineDef : public Id1MapElement
162 {
163 enum Side {
164 Front,
165 Back
166 };
167
168 dint index;
169 dint v[2];
170 dint sides[2];
171 dint16 flags; ///< MF_* flags.
172
173 // Analysis data:
174 dint16 aFlags;
175
176 // DOOM format members:
177 dint16 dType;
178 dint16 dTag;
179
180 // Hexen format members:
181 dint8 xType;
182 dint8 xArgs[5];
183
184 // DOOM64 format members:
185 dint8 d64drawFlags;
186 dint8 d64texFlags;
187 dint8 d64type;
188 dint8 d64useType;
189 dint16 d64tag;
190
191 dint ddFlags;
192 duint validCount; ///< Used for polyobj line collection.
193
LineDefidtech1::internal::LineDef194 LineDef(MapImporter &map) : Id1MapElement(map) {}
195
sideIndexidtech1::internal::LineDef196 int sideIndex(Side which) const
197 {
198 DENG2_ASSERT(which == Front || which == Back);
199 return sides[which];
200 }
201
hasSideidtech1::internal::LineDef202 inline bool hasSide(Side which) const { return sideIndex(which) >= 0; }
203
hasFrontidtech1::internal::LineDef204 inline bool hasFront() const { return hasSide(Front); }
hasBackidtech1::internal::LineDef205 inline bool hasBack() const { return hasSide(Back); }
isTwoSidedidtech1::internal::LineDef206 inline bool isTwoSided() const { return hasFront() && hasBack(); }
207
frontidtech1::internal::LineDef208 inline dint front() const { return sideIndex(Front); }
backidtech1::internal::LineDef209 inline dint back() const { return sideIndex(Back); }
210
operator <<idtech1::internal::LineDef211 void operator << (de::Reader &from)
212 {
213 Id1MapRecognizer::Format format = Id1MapRecognizer::Format(from.version());
214
215 dint idx;
216 from.readAs<duint16>(idx);
217 v[0] = (idx == 0xFFFF? -1 : idx);
218
219 from.readAs<duint16>(idx);
220 v[1] = (idx == 0xFFFF? -1 : idx);
221
222 from >> flags;
223
224 switch(format)
225 {
226 case Id1MapRecognizer::DoomFormat:
227 from >> dType
228 >> dTag;
229 break;
230
231 case Id1MapRecognizer::Doom64Format:
232 from >> d64drawFlags
233 >> d64texFlags
234 >> d64type
235 >> d64useType
236 >> d64tag;
237 break;
238
239 case Id1MapRecognizer::HexenFormat:
240 from >> xType
241 >> xArgs[0]
242 >> xArgs[1]
243 >> xArgs[2]
244 >> xArgs[3]
245 >> xArgs[4];
246 break;
247
248 default:
249 DENG2_ASSERT(!"idtech1::LineDef::read: unknown map format!");
250 break;
251 };
252
253 from.readAs<duint16>(idx);
254 sides[Front] = (idx == 0xFFFF? -1 : idx);
255
256 from.readAs<duint16>(idx);
257 sides[Back] = (idx == 0xFFFF? -1 : idx);
258
259 aFlags = 0;
260 validCount = 0;
261 ddFlags = 0;
262
263 // Translate the line flags for Doomsday:
264 const int16_t ML_BLOCKING = 1; // Solid, is an obstacle.
265 const int16_t ML_DONTPEGTOP = 8; // Upper texture unpegged.
266 const int16_t ML_DONTPEGBOTTOM = 16; // Lower texture unpegged.
267
268 /// If set ALL flags NOT in DOOM v1.9 will be zeroed upon map load.
269 const int16_t ML_INVALID = 2048;
270 const int16_t DOOM_VALIDMASK = 0x01ff;
271
272 /**
273 * Zero unused flags if ML_INVALID is set.
274 *
275 * @attention "This has been found to be necessary because of errors
276 * in Ultimate DOOM's E2M7, where around 1000 linedefs have
277 * the value 0xFE00 masked into the flags value.
278 * There could potentially be many more maps with this problem,
279 * as it is well-known that Hellmaker wads set all bits in
280 * mapthings that it does not understand."
281 * Thanks to Quasar for the heads up.
282 *
283 * Only valid for DOOM format maps.
284 */
285 if(format == Id1MapRecognizer::DoomFormat)
286 {
287 if(flags & ML_INVALID)
288 flags &= DOOM_VALIDMASK;
289 }
290
291 if(flags & ML_BLOCKING)
292 {
293 ddFlags |= DDLF_BLOCKING;
294 flags &= ~ML_BLOCKING;
295 }
296
297 if(flags & ML_DONTPEGTOP)
298 {
299 ddFlags |= DDLF_DONTPEGTOP;
300 flags &= ~ML_DONTPEGTOP;
301 }
302
303 if(flags & ML_DONTPEGBOTTOM)
304 {
305 ddFlags |= DDLF_DONTPEGBOTTOM;
306 flags &= ~ML_DONTPEGBOTTOM;
307 }
308 }
309 };
310
opposite(LineDef::Side side)311 inline LineDef::Side opposite(LineDef::Side side)
312 {
313 return side == LineDef::Front ? LineDef::Back : LineDef::Front;
314 }
315
316 enum {
317 // Sector analysis flags.
318 SAF_NONE = 0,
319 SAF_IS_LINK_TARGET = 0x1,
320 SAF_HAS_AT_LEAST_ONE_SELF_REFERENCING_LINE = 0x2,
321 SAF_HAS_SELF_REFERENCING_LOOP = 0x4,
322
323 // Detected hacks.
324 HACK_SELF_REFERENCING = 0x01,
325 HACK_MISSING_OUTSIDE_TOP = 0x02, // invisible door
326 HACK_MISSING_OUTSIDE_BOTTOM = 0x04, // invisible platform
327 HACK_MISSING_INSIDE_TOP = 0x08, // flat bleeding in ceiling
328 HACK_MISSING_INSIDE_BOTTOM = 0x10, // flat bleeding in floor
329 };
330
331 struct SectorDef : public Id1MapElement
332 {
333 dint index;
334 dint16 floorHeight;
335 dint16 ceilHeight;
336 dint16 lightLevel;
337 dint16 type;
338 dint16 tag;
339 MaterialId floorMaterial;
340 MaterialId ceilMaterial;
341
342 // DOOM64 format members:
343 dint16 d64flags;
344 duint16 d64floorColor;
345 duint16 d64ceilingColor;
346 duint16 d64unknownColor;
347 duint16 d64wallTopColor;
348 duint16 d64wallBottomColor;
349
350 // Internal bookkeeping:
351 std::set<int> lines;
352 std::vector<int> selfRefLoop;
353 //int singleSidedCount = 0;
354 int aFlags = 0;
355 int foundHacks = 0;
356 struct de_api_sector_hacks_s hackParams{{}, -1};
357
SectorDefidtech1::internal::SectorDef358 SectorDef(MapImporter &map) : Id1MapElement(map) {}
359
operator <<idtech1::internal::SectorDef360 void operator << (de::Reader &from)
361 {
362 Id1MapRecognizer::Format format = Id1MapRecognizer::Format(from.version());
363
364 from >> floorHeight
365 >> ceilHeight;
366
367 switch(format)
368 {
369 case Id1MapRecognizer::DoomFormat:
370 case Id1MapRecognizer::HexenFormat: {
371 Block name;
372 from.readBytes(8, name);
373 floorMaterial = map().toMaterialId(name.constData(), PlaneMaterials);
374
375 from.readBytes(8, name);
376 ceilMaterial = map().toMaterialId(name.constData(), PlaneMaterials);
377
378 from >> lightLevel;
379 break; }
380
381 case Id1MapRecognizer::Doom64Format: {
382 duint16 idx;
383 from >> idx;
384 floorMaterial = map().toMaterialId(idx, PlaneMaterials);
385
386 from >> idx;
387 ceilMaterial = map().toMaterialId(idx, PlaneMaterials);
388
389 from >> d64ceilingColor
390 >> d64floorColor
391 >> d64unknownColor
392 >> d64wallTopColor
393 >> d64wallBottomColor;
394
395 lightLevel = 160; ///?
396 break; }
397
398 default:
399 DENG2_ASSERT(!"idtech1::SectorDef::read: unknown map format!");
400 break;
401 };
402
403 from >> type
404 >> tag;
405
406 if(format == Id1MapRecognizer::Doom64Format)
407 from >> d64flags;
408 }
409 };
410
411 // Thing DoomEdNums for polyobj anchors/spawn spots.
412 #define PO_ANCHOR_DOOMEDNUM (3000)
413 #define PO_SPAWN_DOOMEDNUM (3001)
414 #define PO_SPAWNCRUSH_DOOMEDNUM (3002)
415
416 /// @todo Get these from a game api header.
417 #define MTF_Z_FLOOR 0x20000000 ///< Spawn relative to floor height.
418 #define MTF_Z_CEIL 0x40000000 ///< Spawn relative to ceiling height (minus thing height).
419 #define MTF_Z_RANDOM 0x80000000 ///< Random point between floor and ceiling.
420
421 #define ANG45 0x20000000
422
423 struct Thing : public Id1MapElement
424 {
425 dint index;
426 dint16 origin[3];
427 angle_t angle;
428 dint16 doomEdNum;
429 dint32 flags;
430 dint32 skillModes;
431
432 // Hexen format members:
433 dint16 xTID;
434 dint8 xSpecial;
435 dint8 xArgs[5];
436
437 // DOOM64 format members:
438 dint16 d64TID;
439
Thingidtech1::internal::Thing440 Thing(MapImporter &map) : Id1MapElement(map) {}
441
operator <<idtech1::internal::Thing442 void operator << (de::Reader &from)
443 {
444 Id1MapRecognizer::Format format = Id1MapRecognizer::Format(from.version());
445
446 switch(format)
447 {
448 case Id1MapRecognizer::DoomFormat: {
449 #define MTF_EASY 0x00000001 ///< Can be spawned in Easy skill modes.
450 #define MTF_MEDIUM 0x00000002 ///< Can be spawned in Medium skill modes.
451 #define MTF_HARD 0x00000004 ///< Can be spawned in Hard skill modes.
452 #define MTF_DEAF 0x00000008 ///< Mobj will be deaf spawned deaf.
453 #define MTF_NOTSINGLE 0x00000010 ///< (BOOM) Can not be spawned in single player gamemodes.
454 #define MTF_NOTDM 0x00000020 ///< (BOOM) Can not be spawned in the Deathmatch gameMode.
455 #define MTF_NOTCOOP 0x00000040 ///< (BOOM) Can not be spawned in the Co-op gameMode.
456 #define MTF_FRIENDLY 0x00000080 ///< (BOOM) friendly monster.
457
458 #define MASK_UNKNOWN_THING_FLAGS (0xffffffff \
459 ^ (MTF_EASY|MTF_MEDIUM|MTF_HARD|MTF_DEAF|MTF_NOTSINGLE|MTF_NOTDM|MTF_NOTCOOP|MTF_FRIENDLY))
460
461 origin[VZ] = 0;
462 from >> origin[VX]
463 >> origin[VY];
464
465 from.readAs<dint16>(angle);
466 angle = (angle / 45) * ANG45;
467
468 from >> doomEdNum;
469 from.readAs<dint16>(flags);
470
471 skillModes = 0;
472 if(flags & MTF_EASY) skillModes |= 0x00000001 | 0x00000002;
473 if(flags & MTF_MEDIUM) skillModes |= 0x00000004;
474 if(flags & MTF_HARD) skillModes |= 0x00000008 | 0x00000010;
475
476 flags &= ~MASK_UNKNOWN_THING_FLAGS;
477 // DOOM format things spawn on the floor by default unless their
478 // type-specific flags override.
479 flags |= MTF_Z_FLOOR;
480
481 #undef MASK_UNKNOWN_THING_FLAGS
482 #undef MTF_FRIENDLY
483 #undef MTF_NOTCOOP
484 #undef MTF_NOTDM
485 #undef MTF_NOTSINGLE
486 #undef MTF_AMBUSH
487 #undef MTF_HARD
488 #undef MTF_MEDIUM
489 #undef MTF_EASY
490 break; }
491
492 case Id1MapRecognizer::Doom64Format: {
493 #define MTF_EASY 0x00000001 ///< Appears in easy skill modes.
494 #define MTF_MEDIUM 0x00000002 ///< Appears in medium skill modes.
495 #define MTF_HARD 0x00000004 ///< Appears in hard skill modes.
496 #define MTF_DEAF 0x00000008 ///< Thing is deaf.
497 #define MTF_NOTSINGLE 0x00000010 ///< Appears in multiplayer game modes only.
498 #define MTF_DONTSPAWNATSTART 0x00000020 ///< Do not spawn this thing at map start.
499 #define MTF_SCRIPT_TOUCH 0x00000040 ///< Mobjs spawned from this spot will envoke a script when touched.
500 #define MTF_SCRIPT_DEATH 0x00000080 ///< Mobjs spawned from this spot will envoke a script on death.
501 #define MTF_SECRET 0x00000100 ///< A secret (bonus) item.
502 #define MTF_NOTARGET 0x00000200 ///< Mobjs spawned from this spot will not target their attacker when hurt.
503 #define MTF_NOTDM 0x00000400 ///< Can not be spawned in the Deathmatch gameMode.
504 #define MTF_NOTCOOP 0x00000800 ///< Can not be spawned in the Co-op gameMode.
505
506 #define MASK_UNKNOWN_THING_FLAGS (0xffffffff \
507 ^ (MTF_EASY|MTF_MEDIUM|MTF_HARD|MTF_DEAF|MTF_NOTSINGLE|MTF_DONTSPAWNATSTART|MTF_SCRIPT_TOUCH|MTF_SCRIPT_DEATH|MTF_SECRET|MTF_NOTARGET|MTF_NOTDM|MTF_NOTCOOP))
508
509 from >> origin[VX]
510 >> origin[VY]
511 >> origin[VZ];
512
513 from.readAs<dint16>(angle);
514 angle = (angle / 45) * ANG45;
515
516 from >> doomEdNum;
517 from.readAs<dint32>(flags);
518
519 skillModes = 0;
520 if(flags & MTF_EASY) skillModes |= 0x00000001;
521 if(flags & MTF_MEDIUM) skillModes |= 0x00000002;
522 if(flags & MTF_HARD) skillModes |= 0x00000004 | 0x00000008;
523
524 flags &= ~MASK_UNKNOWN_THING_FLAGS;
525 // DOOM64 format things spawn relative to the floor by default
526 // unless their type-specific flags override.
527 flags |= MTF_Z_FLOOR;
528
529 from >> d64TID;
530
531 #undef MASK_UNKNOWN_THING_FLAGS
532 #undef MTF_NOTCOOP
533 #undef MTF_NOTDM
534 #undef MTF_NOTARGET
535 #undef MTF_SECRET
536 #undef MTF_SCRIPT_DEATH
537 #undef MTF_SCRIPT_TOUCH
538 #undef MTF_DONTSPAWNATSTART
539 #undef MTF_NOTSINGLE
540 #undef MTF_DEAF
541 #undef MTF_HARD
542 #undef MTF_MEDIUM
543 #undef MTF_EASY
544 break; }
545
546 case Id1MapRecognizer::HexenFormat: {
547 #define MTF_EASY 0x00000001
548 #define MTF_MEDIUM 0x00000002
549 #define MTF_HARD 0x00000004
550 #define MTF_AMBUSH 0x00000008
551 #define MTF_DORMANT 0x00000010
552 #define MTF_FIGHTER 0x00000020
553 #define MTF_CLERIC 0x00000040
554 #define MTF_MAGE 0x00000080
555 #define MTF_GSINGLE 0x00000100
556 #define MTF_GCOOP 0x00000200
557 #define MTF_GDEATHMATCH 0x00000400
558 // The following are not currently used:
559 #define MTF_SHADOW 0x00000800 ///< (ZDOOM) Thing is 25% translucent.
560 #define MTF_INVISIBLE 0x00001000 ///< (ZDOOM) Makes the thing invisible.
561 #define MTF_FRIENDLY 0x00002000 ///< (ZDOOM) Friendly monster.
562 #define MTF_STILL 0x00004000 ///< (ZDOOM) Thing stands still (only useful for specific Strife monsters or friendlies).
563
564 #define MASK_UNKNOWN_THING_FLAGS (0xffffffff \
565 ^ (MTF_EASY|MTF_MEDIUM|MTF_HARD|MTF_AMBUSH|MTF_DORMANT|MTF_FIGHTER|MTF_CLERIC|MTF_MAGE|MTF_GSINGLE|MTF_GCOOP|MTF_GDEATHMATCH|MTF_SHADOW|MTF_INVISIBLE|MTF_FRIENDLY|MTF_STILL))
566
567 from >> xTID
568 >> origin[VX]
569 >> origin[VY]
570 >> origin[VZ];
571
572 from.readAs<dint16>(angle);
573
574 from >> doomEdNum;
575
576 // For some reason, the Hexen format stores polyobject tags in the
577 // angle field in THINGS. Thus, we cannot translate the angle until
578 // we know whether it is a polyobject type or not.
579 if(doomEdNum != PO_ANCHOR_DOOMEDNUM &&
580 doomEdNum != PO_SPAWN_DOOMEDNUM &&
581 doomEdNum != PO_SPAWNCRUSH_DOOMEDNUM)
582 {
583 angle = ANG45 * (angle / 45);
584 }
585
586 from.readAs<dint16>(flags);
587
588 skillModes = 0;
589 if(flags & MTF_EASY) skillModes |= 0x00000001 | 0x00000002;
590 if(flags & MTF_MEDIUM) skillModes |= 0x00000004;
591 if(flags & MTF_HARD) skillModes |= 0x00000008 | 0x00000010;
592
593 flags &= ~MASK_UNKNOWN_THING_FLAGS;
594
595 // Translate flags:
596 // Game type logic is inverted.
597 flags ^= (MTF_GSINGLE|MTF_GCOOP|MTF_GDEATHMATCH);
598
599 // HEXEN format things spawn relative to the floor by default
600 // unless their type-specific flags override.
601 flags |= MTF_Z_FLOOR;
602
603 from >> xSpecial
604 >> xArgs[0]
605 >> xArgs[1]
606 >> xArgs[2]
607 >> xArgs[3]
608 >> xArgs[4];
609
610 #undef MASK_UNKNOWN_THING_FLAGS
611 #undef MTF_STILL
612 #undef MTF_FRIENDLY
613 #undef MTF_INVISIBLE
614 #undef MTF_SHADOW
615 #undef MTF_GDEATHMATCH
616 #undef MTF_GCOOP
617 #undef MTF_GSINGLE
618 #undef MTF_MAGE
619 #undef MTF_CLERIC
620 #undef MTF_FIGHTER
621 #undef MTF_DORMANT
622 #undef MTF_AMBUSH
623 #undef MTF_HARD
624 #undef MTF_NORMAL
625 #undef MTF_EASY
626 break; }
627
628 default:
629 DENG2_ASSERT(!"idtech1::Thing::read: unknown map format!");
630 break;
631 };
632 }
633 };
634
635 struct TintColor : public Id1MapElement
636 {
637 dint index;
638 dfloat rgb[3];
639 dint8 xx[3];
640
TintColoridtech1::internal::TintColor641 TintColor(MapImporter &map) : Id1MapElement(map) {}
642
operator <<idtech1::internal::TintColor643 void operator << (de::Reader &from)
644 {
645 //Id1Map::Format format = Id1Map::Format(from.version());
646
647 from.readAs<dint8>(rgb[0]); rgb[0] /= 255;
648 from.readAs<dint8>(rgb[1]); rgb[1] /= 255;
649 from.readAs<dint8>(rgb[2]); rgb[2] /= 255;
650
651 from >> xx[0]
652 >> xx[1]
653 >> xx[2];
654 }
655 };
656
657 struct Polyobj
658 {
659 typedef QVector<int> LineIndices;
660
661 dint index;
662 LineIndices lineIndices;
663 dint tag;
664 dint seqType;
665 dint16 anchor[2];
666 };
667
668 struct MaterialDict
669 {
670 StringPool dict;
671
findidtech1::internal::MaterialDict672 String const &find(MaterialId id) const
673 {
674 return dict.stringRef(id);
675 }
676
toMaterialIdidtech1::internal::MaterialDict677 MaterialId toMaterialId(String name, MaterialGroup group)
678 {
679 // In original DOOM, texture name references beginning with the
680 // hypen '-' character are always treated as meaning "no reference"
681 // or "invalid texture" and surfaces using them were not drawn.
682 if(group != PlaneMaterials && name[0] == '-')
683 {
684 return 0; // Not a valid id.
685 }
686
687 // Prepare the encoded URI for insertion into the dictionary.
688 // Material paths must be encoded.
689 AutoStr *path = Str_PercentEncode(AutoStr_FromText(name.toUtf8().constData()));
690 de::Uri uri(Str_Text(path), RC_NULL);
691 uri.setScheme(group == PlaneMaterials? "Flats" : "Textures");
692
693 // Intern this material URI in the dictionary.
694 return dict.intern(uri.compose());
695 }
696
toMaterialIdidtech1::internal::MaterialDict697 MaterialId toMaterialId(dint uniqueId, MaterialGroup group)
698 {
699 // Prepare the encoded URI for insertion into the dictionary.
700 de::Uri textureUrn(String("urn:%1:%2").arg(group == PlaneMaterials? "Flats" : "Textures").arg(uniqueId), RC_NULL);
701 uri_s *uri = Materials_ComposeUri(P_ToIndex(DD_MaterialForTextureUri(reinterpret_cast<uri_s *>(&textureUrn))));
702 String uriComposedAsString = Str_Text(Uri_Compose(uri));
703 Uri_Delete(uri);
704
705 // Intern this material URI in the dictionary.
706 return dict.intern(uriComposedAsString);
707 }
708 };
709
710 } // namespace internal
711
712 using namespace internal;
713
714 static uint validCount = 0; ///< Used with Polyobj LineDef collection.
715
DENG2_PIMPL(MapImporter)716 DENG2_PIMPL(MapImporter)
717 {
718 Id1MapRecognizer::Format format;
719
720 std::vector<Vertex> vertices;
721
722 typedef std::vector<LineDef> Lines;
723 Lines lines;
724
725 typedef std::vector<SideDef> Sides;
726 Sides sides;
727
728 typedef std::vector<SectorDef> Sectors;
729 Sectors sectors;
730
731 typedef std::vector<Thing> Things;
732 Things things;
733
734 typedef std::vector<TintColor> SurfaceTints;
735 SurfaceTints surfaceTints;
736
737 typedef std::list<Polyobj> Polyobjs;
738 Polyobjs polyobjs;
739
740 internal::MaterialDict materials;
741
742 Impl(Public *i) : Base(i), format(Id1MapRecognizer::UnknownFormat)
743 {}
744
745 int indexOf(const SectorDef §or) const
746 {
747 return int(§or - sectors.data());
748 }
749
750 void readVertexes(de::Reader &from, dint numElements)
751 {
752 vertices.resize(size_t(numElements));
753
754 Id1MapRecognizer::Format format = Id1MapRecognizer::Format(from.version());
755 for (auto &vert : vertices)
756 {
757 switch (format)
758 {
759 case Id1MapRecognizer::Doom64Format: {
760 // 16:16 fixed-point.
761 dint32 x, y;
762 from >> x >> y;
763 vert.pos.x = FIX2FLT(x);
764 vert.pos.y = FIX2FLT(y);
765 break;
766 }
767 default: {
768 dint16 x, y;
769 from >> x >> y;
770 vert.pos.x = x;
771 vert.pos.y = y;
772 break;
773 }
774 }
775 }
776 }
777
778 void readLineDefs(de::Reader &reader, dint numElements)
779 {
780 if(numElements <= 0) return;
781 lines.reserve(lines.size() + numElements);
782 for(dint n = 0; n < numElements; ++n)
783 {
784 lines.push_back(LineDef(self()));
785 LineDef &line = lines.back();
786 line << reader;
787 line.index = n;
788 }
789 }
790
791 void readSideDefs(de::Reader &reader, dint numElements)
792 {
793 if(numElements <= 0) return;
794 sides.reserve(sides.size() + numElements);
795 for(dint n = 0; n < numElements; ++n)
796 {
797 sides.push_back(SideDef(self()));
798 SideDef &side = sides.back();
799 side << reader;
800 side.index = n;
801 }
802 }
803
804 void readSectorDefs(de::Reader &reader, dint numElements)
805 {
806 if(numElements <= 0) return;
807 sectors.reserve(sectors.size() + numElements);
808 for(dint n = 0; n < numElements; ++n)
809 {
810 sectors.push_back(SectorDef(self()));
811 SectorDef §or = sectors.back();
812 sector << reader;
813 sector.index = n;
814 }
815 }
816
817 void readThings(de::Reader &reader, dint numElements)
818 {
819 if(numElements <= 0) return;
820 things.reserve(things.size() + numElements);
821 for(dint n = 0; n < numElements; ++n)
822 {
823 things.push_back(Thing(self()));
824 Thing &thing = things.back();
825 thing << reader;
826 thing.index = n;
827 }
828 }
829
830 void readTintColors(de::Reader &reader, dint numElements)
831 {
832 if(numElements <= 0) return;
833 surfaceTints.reserve(surfaceTints.size() + numElements);
834 for(dint n = 0; n < numElements; ++n)
835 {
836 surfaceTints.push_back(TintColor(self()));
837 TintColor &tint = surfaceTints.back();
838 tint << reader;
839 tint.index = n;
840 }
841 }
842
843 void linkLines()
844 {
845 for (int i = 0; i < int(lines.size()); ++i)
846 {
847 const auto &line = lines[i];
848
849 // Link to vertices.
850 for (int p = 0; p < 2; ++p)
851 {
852 const int vertIndex = line.v[p];
853 if (vertIndex >= 0 && vertIndex < int(vertices.size()))
854 {
855 vertices[vertIndex].lines.insert(i);
856 }
857 }
858
859 // Link to sectors.
860 for (auto s : {LineDef::Front, LineDef::Back})
861 {
862 if (line.hasSide(s))
863 {
864 const auto sec = sides[line.sideIndex(s)].sector;
865 if (sec >= 0 && sec < int(sectors.size()))
866 {
867 sectors[sec].lines.insert(i);
868 }
869 }
870 }
871 }
872 }
873
874 bool isSelfReferencing(const LineDef &line) const
875 {
876 // Use of middle materials indicates that this is not a render hack.
877 const auto *s = line.sides;
878 return !(line.aFlags & LAF_POLYOBJ) &&
879 line.isTwoSided() &&
880 !sides[s[0]].middleMaterial &&
881 !sides[s[1]].middleMaterial &&
882 sides[s[0]].sector == sides[s[1]].sector &&
883 sides[s[0]].sector >= 0;
884 }
885
886 double lineLength(const LineDef &line) const
887 {
888 return (vertices[line.v[0]].pos - vertices[line.v[1]].pos).length();
889 }
890
891 int otherSector(const LineDef &line, int sectorIndex) const
892 {
893 DENG2_ASSERT(line.isTwoSided());
894 if (sides[line.sides[0]].sector == sectorIndex)
895 {
896 return sides[line.sides[1]].sector;
897 }
898 return sides[line.sides[0]].sector;
899 }
900
901 int sideOfSector(const LineDef &line, int sectorIndex) const
902 {
903 for (auto s : {LineDef::Front, LineDef::Back})
904 {
905 if (line.sides[s] >= 0)
906 {
907 if (sides[line.sides[s]].sector == sectorIndex)
908 {
909 return s;
910 }
911 }
912 }
913 return -1;
914 }
915
916 std::set<int> sectorVertices(const SectorDef §or) const
917 {
918 std::set<int> verts;
919 // If a self-referencing loop has been detected in the sector, we are only interested
920 // in the loop because it is being used for render hacks.
921 if (!sector.selfRefLoop.empty())
922 {
923 for (int i : sector.selfRefLoop)
924 {
925 verts.insert(lines[i].v[0]);
926 verts.insert(lines[i].v[1]);
927 }
928 }
929 else for (int i : sector.lines)
930 {
931 verts.insert(lines[i].v[0]);
932 verts.insert(lines[i].v[1]);
933 }
934 return verts;
935 }
936
937 #if 0
938 Vec2d sectorBoundsMiddle(const SectorDef §or) const
939 {
940 Vec2d mid;
941 int count = 0;
942 for (int v : sectorVertices(sector))
943 {
944 mid += vertices[v].pos;
945 ++count;
946 }
947 if (count > 0) mid /= count;
948 return mid;
949 }
950 #endif
951
952 std::vector<double> findSectorIntercepts(const SectorDef §or, const Vec2d &start, const Vec2d &dir) const
953 {
954 const Vec2d end = start + dir;
955
956 std::vector<double> intercepts;
957 for (int i : sector.lines)
958 {
959 const auto &line = lines[i];
960 const Vec2d a = vertices[line.v[0]].pos;
961 const Vec2d b = vertices[line.v[1]].pos;
962
963 double t;
964 if (lineSegmentIntersection(t, start, end, a, b))
965 {
966 if (t > 0)
967 {
968 intercepts.push_back(t);
969 }
970 }
971 }
972 return intercepts;
973 }
974
975 /**
976 * Finds a point that is inside the sector. The first option is to use the
977 * middle of the sector's bounding box, but if that is outside the sector,
978 * tries to intersect against the sector lines to find a valid point inside.
979 *
980 * @param sector Sector.
981 *
982 * @return A point inside the sector.
983 */
984 Vec2d findPointInsideSector(const SectorDef §or) const
985 {
986 Vec2d inside;
987 int count = 0;
988 for (int i : sectorVertices(sector))
989 {
990 inside += vertices[i].pos;
991 count++;
992 }
993 if (count > 0) inside /= count;
994
995 // Is this actually inside the sector? Need to do a polygon check.
996 {
997 Vec2d dir{1, 0};
998 std::vector<double> intercepts = findSectorIntercepts(sector, inside, dir);
999 if (intercepts.empty())
1000 {
1001 dir = {-1, 0};
1002 intercepts = findSectorIntercepts(sector, inside, dir);
1003 }
1004 if (intercepts.empty())
1005 {
1006 dir = {0, -1};
1007 intercepts = findSectorIntercepts(sector, inside, dir);
1008 }
1009
1010 if (intercepts.size() > 0 && intercepts.size() % 2 == 0)
1011 {
1012 qDebug("(%f,%f) is not inside!", inside.x, inside.y);
1013
1014 const Vec2d first = inside + dir * intercepts[0];
1015 const Vec2d second = inside + dir * intercepts[1];
1016
1017 inside = (first + second) * 0.5f;
1018
1019 qDebug(" -> choosing (%f,%f) instead", inside.x, inside.y);
1020 }
1021 }
1022
1023 return inside;
1024 }
1025
1026 struct IntersectionResult
1027 {
1028 bool valid;
1029 double t;
1030 LineDef::Side side;
1031 };
1032
1033 IntersectionResult findIntersection(
1034 const LineDef &line, const Vec2d &start, const Vec2d &end) const
1035 {
1036 const Vec2d a = vertices[line.v[0]].pos;
1037 const Vec2d b = vertices[line.v[1]].pos;
1038
1039 double t;
1040 if (lineSegmentIntersection(t, start, end, a, b))
1041 {
1042 const Vec2d dir = (end - start).normalize();
1043 const Vec2d lineDir = (b - a).normalize();
1044 const Vec2d lineNormal = {lineDir.y, -lineDir.x};
1045
1046 return {true, t, lineNormal.dot(dir) < 0 ? LineDef::Front : LineDef::Back};
1047 }
1048 return {false, 0.0, LineDef::Front};
1049 }
1050
1051 void locateContainingSector(SectorDef §or)
1052 {
1053 if (sector.lines.empty()) return;
1054
1055 const Vec2d start = findPointInsideSector(sector);
1056 const Vec2d end = start + Vec2d(0.001, 1.0);
1057
1058 std::pair<double, int> nearestContainer{std::numeric_limits<double>::max(), -1};
1059
1060 // Look for intersecting lines in other, normal sectors.
1061 for (int lineIndex = 0; lineIndex < int(lines.size()); ++lineIndex)
1062 {
1063 const auto &line = lines[lineIndex];
1064
1065 if (!isSelfReferencing(line) &&
1066 sector.lines.find(lineIndex) == sector.lines.end())
1067 {
1068 const auto hit = findIntersection(line, start, end);
1069
1070 if (hit.valid && hit.t > 0.0 && hit.t < nearestContainer.first)
1071 {
1072 if (line.hasSide(hit.side))
1073 {
1074 const int sector = sides[line.sideIndex(hit.side)].sector;
1075
1076 // It must be a regular sector, but multiple hacked sectors
1077 // can link to the same regular one.
1078 if (sector >= 0 && !sectors[sector].foundHacks)
1079 {
1080 nearestContainer = {hit.t, sector};
1081 }
1082 }
1083 }
1084 }
1085 }
1086
1087 if (nearestContainer.second >= 0)
1088 {
1089 sectors[nearestContainer.second].aFlags |= SAF_IS_LINK_TARGET;
1090
1091 sector.hackParams.visPlaneLinkTargetSector = nearestContainer.second;
1092 sector.hackParams.flags.linkFloorPlane = true;
1093 sector.hackParams.flags.linkCeilingPlane = true;
1094
1095 qDebug("sector %i contained by %i", indexOf(sector), nearestContainer.second);
1096 }
1097 }
1098
1099 /**
1100 * Create a temporary polyobj.
1101 */
1102 Polyobj *createPolyobj(Polyobj::LineIndices const &lineIndices, dint tag,
1103 dint sequenceType, dint16 anchorX, dint16 anchorY)
1104 {
1105 // Allocate the new polyobj.
1106 polyobjs.push_back(Polyobj());
1107 Polyobj *po = &polyobjs.back();
1108
1109 po->index = polyobjs.size() - 1;
1110 po->tag = tag;
1111 po->seqType = sequenceType;
1112 po->anchor[VX] = anchorX;
1113 po->anchor[VY] = anchorY;
1114 po->lineIndices = lineIndices; // A copy is made.
1115
1116 foreach(dint lineIdx, po->lineIndices)
1117 {
1118 LineDef *line = &lines[lineIdx];
1119
1120 // This line now belongs to a polyobj.
1121 line->aFlags |= LAF_POLYOBJ;
1122
1123 // Due a logic error in hexen.exe, when the column drawer is presented
1124 // with polyobj segs built from two-sided linedefs; clipping is always
1125 // calculated using the pegging logic for single-sided linedefs.
1126 //
1127 // Here we emulate this behavior by automatically applying bottom unpegging
1128 // for two-sided linedefs.
1129 if(line->hasBack())
1130 {
1131 line->ddFlags |= DDLF_DONTPEGBOTTOM;
1132 }
1133 }
1134
1135 return po;
1136 }
1137
1138 /**
1139 * Find all linedefs marked as belonging to a polyobject with the given tag
1140 * and attempt to create a polyobject from them.
1141 *
1142 * @param tag Line tag of linedefs to search for.
1143 *
1144 * @return @c true = successfully created polyobj.
1145 */
1146 bool findAndCreatePolyobj(dint16 tag, dint16 anchorX, dint16 anchorY)
1147 {
1148 Polyobj::LineIndices polyLines;
1149
1150 // First look for a PO_LINE_START linedef set with this tag.
1151 for (size_t i = 0; i < lines.size(); ++i)
1152 {
1153 auto &line = lines[i];
1154
1155 // Already belongs to another polyobj?
1156 if(line.aFlags & LAF_POLYOBJ) continue;
1157
1158 if(!(line.xType == PO_LINE_START && line.xArgs[0] == tag)) continue;
1159
1160 if (collectPolyobjLines(polyLines, i))
1161 {
1162 dint8 sequenceType = line.xArgs[2];
1163 if(sequenceType >= SEQTYPE_NUMSEQ) sequenceType = 0;
1164
1165 createPolyobj(polyLines, tag, sequenceType, anchorX, anchorY);
1166 return true;
1167 }
1168 return false;
1169 }
1170
1171 // Perhaps a PO_LINE_EXPLICIT linedef set with this tag?
1172 for(dint n = 0; ; ++n)
1173 {
1174 bool foundAnotherLine = false;
1175
1176 DENG2_FOR_EACH(Lines, i, lines)
1177 {
1178 // Already belongs to another polyobj?
1179 if(i->aFlags & LAF_POLYOBJ) continue;
1180
1181 if(i->xType == PO_LINE_EXPLICIT && i->xArgs[0] == tag)
1182 {
1183 if(i->xArgs[1] <= 0)
1184 {
1185 LOGDEV_MAP_WARNING("Linedef missing (probably #%d) in explicit polyobj (tag:%d)") << n + 1 << tag;
1186 return false;
1187 }
1188
1189 if(i->xArgs[1] == n + 1)
1190 {
1191 // Add this line to the list.
1192 polyLines.append( i - lines.begin() );
1193 foundAnotherLine = true;
1194
1195 // Clear any special.
1196 i->xType = 0;
1197 i->xArgs[0] = 0;
1198 }
1199 }
1200 }
1201
1202 if(foundAnotherLine)
1203 {
1204 // Check if an explicit line order has been skipped.
1205 // A line has been skipped if there are any more explicit lines with
1206 // the current tag value.
1207 DENG2_FOR_EACH(Lines, i, lines)
1208 {
1209 if(i->xType == PO_LINE_EXPLICIT && i->xArgs[0] == tag)
1210 {
1211 LOGDEV_MAP_WARNING("Linedef missing (#%d) in explicit polyobj (tag:%d)") << n << tag;
1212 return false;
1213 }
1214 }
1215 }
1216 else
1217 {
1218 // All lines have now been found.
1219 break;
1220 }
1221 }
1222
1223 if(polyLines.isEmpty())
1224 {
1225 LOGDEV_MAP_WARNING("Failed to locate a single line for polyobj (tag:%d)") << tag;
1226 return false;
1227 }
1228
1229 LineDef *line = &lines[ polyLines.first() ];
1230 dint8 const sequenceType = line->xArgs[3];
1231
1232 // Setup the mirror if it exists.
1233 line->xArgs[1] = line->xArgs[2];
1234
1235 createPolyobj(polyLines, tag, sequenceType, anchorX, anchorY);
1236 return true;
1237 }
1238
1239 size_t collectPolyobjLines(Polyobj::LineIndices &lineList, size_t startLine)
1240 {
1241 validCount++;
1242
1243 LineDef &line = lines[startLine];
1244 line.xType = 0;
1245 line.xArgs[0] = 0;
1246 line.validCount = validCount;
1247
1248 // Keep going until we run out of possible lines.
1249 for (int currentLine = int(startLine); currentLine >= 0; )
1250 {
1251 lineList.push_back(currentLine);
1252
1253 const int currentEnd = lines[currentLine].v[1];
1254 int nextLine = -1;
1255
1256 // Look for a line starting where current line ends.
1257 for (int i : vertices[currentEnd].lines)
1258 {
1259 auto &other = lines[i];
1260 if ((other.aFlags & LAF_POLYOBJ) || other.validCount == validCount)
1261 {
1262 continue;
1263 }
1264 if (other.v[0] == currentEnd)
1265 {
1266 // Use this one.
1267 other.validCount = validCount;
1268 nextLine = i;
1269 break;
1270 }
1271 }
1272
1273 currentLine = nextLine;
1274 }
1275
1276 return lineList.size();
1277 }
1278
1279 struct LineDefSet : public std::set<const LineDef *>
1280 {
1281 using Base = std::set<const LineDef *>;
1282
1283 const LineDef *take()
1284 {
1285 if (empty()) return nullptr;
1286
1287 auto iter = begin();
1288 const auto *line = *iter;
1289 erase(iter);
1290 return line;
1291 }
1292
1293 bool contains(const LineDef &line) const
1294 {
1295 return Base::find(&line) != Base::end();
1296 }
1297 };
1298
1299 bool isLoopContainedWithinSameSector(const std::vector<int> &loop, int sector) const
1300 {
1301 LineDefSet loopSet;
1302 for (int lineIndex : loop)
1303 {
1304 DE_ASSERT(isSelfReferencing(lines[lineIndex]));
1305 loopSet.insert(&lines[lineIndex]);
1306 }
1307
1308 LineDefSet regularSectorLines;
1309 for (int lineIndex : sectors[sector].lines)
1310 {
1311 const auto &line = lines[lineIndex];
1312 if (!isSelfReferencing(line))
1313 {
1314 DE_ASSERT(!loopSet.contains(line));
1315 regularSectorLines.insert(&line);
1316 }
1317 }
1318
1319 const Vec2d interceptDirs[] = {{0, -1}, {1, 0}, {0, 1}, {-1, 0}};
1320
1321 // Check intercepts extending outward from the loop. They should all contact a
1322 // regular sector line.
1323 for (const auto *loopLine : loopSet)
1324 {
1325 const Vec2d midPoint = (vertices[loopLine->v[0]].pos + vertices[loopLine->v[1]].pos) / 2;
1326
1327 for (const auto &dir : interceptDirs)
1328 {
1329 bool intercepted = false;
1330 for (const auto *regular : regularSectorLines)
1331 {
1332 auto hit = findIntersection(*regular, midPoint, midPoint + dir);
1333 if (hit.valid && hit.t > 0.0)
1334 {
1335 intercepted = true;
1336 break;
1337 }
1338 }
1339 if (!intercepted)
1340 {
1341 // No containment in this direction.
1342 return false;
1343 }
1344 }
1345 }
1346
1347 // Fully contained in all directions.
1348 return true;
1349 }
1350
1351 void analyze()
1352 {
1353 Time begunAt;
1354
1355 if (format == Id1MapRecognizer::HexenFormat)
1356 {
1357 LOGDEV_MAP_XVERBOSE("Locating polyobjs...", "");
1358 DENG2_FOR_EACH(Things, i, things)
1359 {
1360 // A polyobj anchor?
1361 if(i->doomEdNum == PO_ANCHOR_DOOMEDNUM)
1362 {
1363 dint const tag = i->angle;
1364 findAndCreatePolyobj(tag, i->origin[VX], i->origin[VY]);
1365 }
1366 }
1367 }
1368
1369 // Detect self-referencing sectors: all lines of the sector are two-sided and both
1370 // sides refer to the sector itself.
1371 //
1372 // For example:
1373 // - TNT map02 deep water: single sector with self-referencing lines
1374 // - AV map11 deep water (x=2736, y=8): multiple connected self-referencing sectors
1375 {
1376 // First look for potentially self-referencing sectors that have at least one
1377 // self-referencing line. Also be on the lookout for line loops composed of
1378 // self-referencing lines.
1379 for (auto §or : sectors)
1380 {
1381 const int sectorIndex = int(§or - sectors.data());
1382
1383 LineDefSet selfRefLines;
1384 bool hasSingleSided = false;
1385 for (int lineIndex : sector.lines)
1386 {
1387 auto &line = lines[lineIndex];
1388 if (!line.isTwoSided())
1389 {
1390 hasSingleSided = true;
1391 // sector.singleSidedCount++;
1392 }
1393 if (isSelfReferencing(line))
1394 {
1395 selfRefLines.insert(&line);
1396 }
1397 }
1398
1399 // Detect loops in the self-referencing lines.
1400 if (!selfRefLines.empty())
1401 {
1402 std::vector<const LineDef *> loop;
1403 const LineDef * atLine;
1404 auto remaining = selfRefLines;
1405
1406 loop.push_back(atLine = remaining.take());
1407 int atVertex = atLine->v[0];
1408
1409 for (;;)
1410 {
1411 const int nextVertex = atLine->v[atLine->v[0] == atVertex ? 1 : 0];
1412 const LineDef *nextLine = nullptr;
1413
1414 // Was a loop completed?
1415 if (loop.size() >= 3)
1416 {
1417 if (nextVertex == loop.front()->v[0] || nextVertex == loop.front()->v[1])
1418 {
1419 qDebug("sector %d has a self-ref loop:", sectorIndex);
1420 for (const auto *ld : loop)
1421 {
1422 const int lineIndex = int(ld - lines.data());
1423 sector.selfRefLoop.push_back(lineIndex);
1424 qDebug(" line %d", lineIndex);
1425 }
1426 sector.aFlags |= SAF_HAS_SELF_REFERENCING_LOOP;
1427 if (isLoopContainedWithinSameSector(sector.selfRefLoop, sectorIndex))
1428 {
1429 qDebug(" but the loop is contained inside sector %d, so ignoring the loop",
1430 sectorIndex);
1431 sector.aFlags &= ~SAF_HAS_SELF_REFERENCING_LOOP;
1432 sector.selfRefLoop.clear();
1433 }
1434 break;
1435 }
1436 }
1437
1438 for (int lineIdx : vertices[nextVertex].lines)
1439 {
1440 const auto &check = lines[lineIdx];
1441 if (remaining.find(&check) != remaining.end())
1442 {
1443 if (check.v[0] == nextVertex || check.v[1] == nextVertex)
1444 {
1445 if (nextLine)
1446 {
1447 // Multiple self-referencing lines of the same sector
1448 // connect to this vertex. This is likely a 3D bridge.
1449 qDebug("possible 3D bridge in sector %d", sectorIndex);
1450 nextLine = nullptr;
1451 break;
1452 }
1453 nextLine = ✓
1454 }
1455 }
1456 }
1457 if (!nextLine) break; // No more connected lines, give up.
1458
1459 remaining.erase(nextLine);
1460 loop.push_back(nextLine);
1461 atLine = nextLine;
1462 atVertex = nextVertex;
1463 }
1464 }
1465
1466 if (!selfRefLines.empty() && !hasSingleSided)
1467 {
1468 sector.aFlags |= SAF_HAS_AT_LEAST_ONE_SELF_REFERENCING_LINE;
1469 qDebug("possibly a self-referencing sector %d", int(§or - sectors.data()));
1470 }
1471 }
1472
1473 bool foundSelfRefs = false;
1474 for (int sectorIndex = 0; sectorIndex < int(sectors.size()); ++sectorIndex)
1475 {
1476 auto §or = sectors[sectorIndex];
1477
1478 if (sector.lines.empty()) continue;
1479
1480 if (!(sector.aFlags & (SAF_HAS_AT_LEAST_ONE_SELF_REFERENCING_LINE |
1481 SAF_HAS_SELF_REFERENCING_LOOP)))
1482 {
1483 continue;
1484 }
1485
1486 int numSelfRef = 0;
1487
1488 bool good = true;
1489 for (int lineIndex : sector.lines)
1490 {
1491 auto & line = lines[lineIndex];
1492 const bool isSelfRef = isSelfReferencing(line);
1493
1494 if (isSelfRef) ++numSelfRef;
1495
1496 // Sectors with a loop of self-referencing lines can contain any number
1497 // of other lines, we'll still consider them self-referencing.
1498 if (!isSelfRef && !(sector.aFlags & SAF_HAS_SELF_REFERENCING_LOOP))
1499 {
1500 if (!line.isTwoSided())
1501 {
1502 good = false;
1503 break;
1504 }
1505 // Combine multiple self-referencing sectors.
1506 const int other = otherSector(line, sectorIndex);
1507 if (other >= 0 && !(sectors[other].aFlags &
1508 SAF_HAS_AT_LEAST_ONE_SELF_REFERENCING_LINE))
1509 {
1510 good = false;
1511 break;
1512 }
1513 }
1514 }
1515 if (!(sector.aFlags & SAF_HAS_SELF_REFERENCING_LOOP) &&
1516 float(numSelfRef) / float(sector.lines.size()) < 0.25f)
1517 {
1518 // Mostly regular lines and no loops.
1519 good = false;
1520 }
1521 if (good)
1522 {
1523 foundSelfRefs = true;
1524 sector.foundHacks |= HACK_SELF_REFERENCING;
1525 qDebug("self-referencing sector %d (ceil:%s floor:%s)", sectorIndex,
1526 materials.find(sector.ceilMaterial).toUtf8().constData(),
1527 materials.find(sector.floorMaterial).toUtf8().constData());
1528 }
1529 }
1530
1531 if (foundSelfRefs)
1532 {
1533 // Look for the normal sectors that contain the self-referencing sectors.
1534 for (auto §or : sectors)
1535 {
1536 if (sector.foundHacks & HACK_SELF_REFERENCING)
1537 {
1538 locateContainingSector(sector);
1539 }
1540 }
1541 }
1542 }
1543
1544 // Missing upper/lower textures are used for transparent doors and platform.
1545 // Depending on the plane heights, they also cause flat bleeding.
1546 // For example: TNT map31 suspended Arachnotrons.
1547 {
1548 for (int currentSector = 0; currentSector < int(sectors.size()); ++currentSector)
1549 {
1550 auto §or = sectors[currentSector];
1551 if (sector.foundHacks) continue;
1552
1553 int goodHacks = HACK_MISSING_INSIDE_TOP | HACK_MISSING_INSIDE_BOTTOM |
1554 HACK_MISSING_OUTSIDE_TOP | HACK_MISSING_OUTSIDE_BOTTOM;
1555 int surroundingSector = -1;
1556
1557 for (int lineIndex : sector.lines)
1558 {
1559 if (!goodHacks) break;
1560
1561 const auto &line = lines[lineIndex];
1562
1563 if (!line.isTwoSided() || line.aFlags & LAF_POLYOBJ)
1564 {
1565 goodHacks = 0;
1566 break;
1567 }
1568 if (sides[line.sides[0]].sector == sides[line.sides[1]].sector)
1569 {
1570 // Does not affect this hack.
1571 continue;
1572 }
1573
1574 const auto innerSide = LineDef::Side(sideOfSector(line, currentSector));
1575 const auto outerSide = opposite(innerSide);
1576
1577 if (sides[line.sides[outerSide]].topMaterial)
1578 {
1579 goodHacks &= ~HACK_MISSING_OUTSIDE_TOP;
1580 }
1581 if (sides[line.sides[outerSide]].bottomMaterial)
1582 {
1583 goodHacks &= ~HACK_MISSING_OUTSIDE_BOTTOM;
1584 }
1585 if (sides[line.sides[innerSide]].topMaterial)
1586 {
1587 goodHacks &= ~HACK_MISSING_INSIDE_TOP;
1588 }
1589 if (sides[line.sides[innerSide]].bottomMaterial)
1590 {
1591 goodHacks &= ~HACK_MISSING_INSIDE_BOTTOM;
1592 }
1593
1594 const int other = otherSector(line, currentSector);
1595 if (surroundingSector < 0)
1596 {
1597 surroundingSector = other;
1598 }
1599 else if (other != surroundingSector)
1600 {
1601 goodHacks = 0;
1602 break;
1603 }
1604 }
1605
1606 if (surroundingSector < 0 || surroundingSector == currentSector)
1607 {
1608 goodHacks = 0;
1609 }
1610
1611 if (goodHacks)
1612 {
1613 sector.foundHacks |= goodHacks;
1614 sector.hackParams.visPlaneLinkTargetSector = surroundingSector;
1615 sector.hackParams.flags.linkCeilingPlane =
1616 (goodHacks & (HACK_MISSING_INSIDE_TOP | HACK_MISSING_OUTSIDE_TOP)) != 0;
1617 sector.hackParams.flags.linkFloorPlane =
1618 (goodHacks & (HACK_MISSING_INSIDE_BOTTOM | HACK_MISSING_OUTSIDE_BOTTOM)) != 0;
1619 sector.hackParams.flags.missingInsideTop =
1620 (goodHacks & HACK_MISSING_INSIDE_TOP) != 0;
1621 sector.hackParams.flags.missingInsideBottom =
1622 (goodHacks & HACK_MISSING_INSIDE_BOTTOM) != 0;
1623 sector.hackParams.flags.missingOutsideTop =
1624 (goodHacks & HACK_MISSING_OUTSIDE_TOP) != 0;
1625 sector.hackParams.flags.missingOutsideBottom =
1626 (goodHacks & HACK_MISSING_OUTSIDE_BOTTOM) != 0;
1627
1628 StringList missDesc;
1629 if (sector.hackParams.flags.missingInsideTop) missDesc << "inside upper";
1630 if (sector.hackParams.flags.missingInsideBottom) missDesc << "inside lower";
1631 if (sector.hackParams.flags.missingOutsideTop) missDesc << "outside upper";
1632 if (sector.hackParams.flags.missingOutsideBottom) missDesc << "outside lower";
1633
1634 qDebug("sector %d missing %s walls (surrounded by sector %d)",
1635 currentSector,
1636 String::join(missDesc, ", ").toLatin1().constData(),
1637 surroundingSector);
1638 }
1639 }
1640 }
1641
1642 // Flat bleeding caused by sector without wall textures.
1643 // For example: TNT map09 transparent window.
1644 {
1645 for (auto §or : sectors)
1646 {
1647 const int currentSector = indexOf(sector);
1648
1649 if (sector.foundHacks) continue;
1650
1651 bool good = true;
1652 int adjacentSector = -1;
1653
1654 for (int lineIndex : sector.lines)
1655 {
1656 const auto &line = lines[lineIndex];
1657
1658 if (!line.isTwoSided() || line.aFlags & LAF_POLYOBJ)
1659 {
1660 good = false;
1661 break;
1662 }
1663
1664 const int otherSector = this->otherSector(line, currentSector);
1665
1666 if (otherSector == currentSector || sectors[otherSector].foundHacks)
1667 {
1668 good = false;
1669 break;
1670 }
1671
1672 if (lineLength(line) < 8.5)
1673 {
1674 // Very short line, probably inconsequential.
1675 // Bit of a kludge for TNT map09 transparent window.
1676 continue;
1677 }
1678
1679 const auto innerSide = LineDef::Side(sideOfSector(line, currentSector));
1680 const int innerSideNum = line.sides[innerSide];
1681 const int outerSideNum = line.sides[opposite(innerSide)];
1682
1683 if (sides[innerSideNum].bottomMaterial ||
1684 sides[innerSideNum].topMaterial ||
1685 sides[innerSideNum].middleMaterial ||
1686 sides[outerSideNum].bottomMaterial ||
1687 sides[outerSideNum].topMaterial ||
1688 sides[outerSideNum].middleMaterial)
1689 {
1690 good = false;
1691 break;
1692 }
1693
1694 if (adjacentSector < 0 &&
1695 sectors[otherSector].foundHacks == 0)
1696 {
1697 adjacentSector = otherSector;
1698 }
1699 }
1700
1701 if (adjacentSector < 0)
1702 {
1703 good = false;
1704 }
1705
1706 if (good)
1707 {
1708 qDebug("completely untextured lines in sector %d, linking floor to adjacent sector %d",
1709 currentSector,
1710 adjacentSector);
1711
1712 sector.foundHacks |= HACK_MISSING_INSIDE_BOTTOM | HACK_MISSING_OUTSIDE_BOTTOM;
1713 sector.hackParams.visPlaneLinkTargetSector = adjacentSector;
1714 sector.hackParams.flags.linkFloorPlane = true;
1715 }
1716 }
1717 }
1718
1719 // Cannot link to hacks.
1720 {
1721 for (auto §or : sectors)
1722 {
1723 if (sector.foundHacks &&
1724 (sectors[sector.hackParams.visPlaneLinkTargetSector].foundHacks &
1725 ~(HACK_MISSING_OUTSIDE_BOTTOM | HACK_MISSING_OUTSIDE_TOP))) /* allow linking to deep water */
1726 {
1727 qDebug("sector %d is linked to hacked sector %d -> cancelling",
1728 indexOf(sector), sector.hackParams.visPlaneLinkTargetSector);
1729
1730 sector.hackParams.visPlaneLinkTargetSector = -1;
1731 sector.foundHacks = 0;
1732 }
1733 }
1734 }
1735
1736 LOGDEV_MAP_MSG("Analyses completed in %.2f seconds") << begunAt.since();
1737 }
1738
1739 void transferVertexes()
1740 {
1741 LOGDEV_MAP_XVERBOSE("Transfering vertexes...", "");
1742 const int numVertexes = int(vertices.size());
1743 int * indices = new dint[numVertexes];
1744 coord_t * vertCoords = new coord_t[numVertexes * 2];
1745 coord_t * vert = vertCoords;
1746 for (int i = 0; i < numVertexes; ++i)
1747 {
1748 indices[i] = i;
1749 *vert++ = vertices[i].pos.x;
1750 *vert++ = vertices[i].pos.y;
1751 }
1752 MPE_VertexCreatev(numVertexes, vertCoords, indices, 0);
1753 delete[] indices;
1754 delete[] vertCoords;
1755 }
1756
1757 void transferSectors()
1758 {
1759 LOGDEV_MAP_XVERBOSE("Transfering sectors...", "");
1760
1761 DENG2_FOR_EACH(Sectors, i, sectors)
1762 {
1763 dint idx = MPE_SectorCreate(
1764 dfloat(i->lightLevel) / 255.0f, 1, 1, 1, &i->hackParams, i->index);
1765
1766 MPE_PlaneCreate(idx, i->floorHeight, materials.find(i->floorMaterial).toUtf8(),
1767 0, 0, 1, 1, 1, 1, 0, 0, 1, -1);
1768 MPE_PlaneCreate(idx, i->ceilHeight, materials.find(i->ceilMaterial).toUtf8(),
1769 0, 0, 1, 1, 1, 1, 0, 0, -1, -1);
1770
1771 MPE_GameObjProperty("XSector", idx, "Tag", DDVT_SHORT, &i->tag);
1772 MPE_GameObjProperty("XSector", idx, "Type", DDVT_SHORT, &i->type);
1773
1774 if(format == Id1MapRecognizer::Doom64Format)
1775 {
1776 MPE_GameObjProperty("XSector", idx, "Flags", DDVT_SHORT, &i->d64flags);
1777 MPE_GameObjProperty("XSector", idx, "CeilingColor", DDVT_SHORT, &i->d64ceilingColor);
1778 MPE_GameObjProperty("XSector", idx, "FloorColor", DDVT_SHORT, &i->d64floorColor);
1779 MPE_GameObjProperty("XSector", idx, "UnknownColor", DDVT_SHORT, &i->d64unknownColor);
1780 MPE_GameObjProperty("XSector", idx, "WallTopColor", DDVT_SHORT, &i->d64wallTopColor);
1781 MPE_GameObjProperty("XSector", idx, "WallBottomColor", DDVT_SHORT, &i->d64wallBottomColor);
1782 }
1783 }
1784 }
1785
1786 void transferLinesAndSides()
1787 {
1788 auto transferSide = [this](int lineIdx, short sideFlags, SideDef *side, LineDef::Side sideIndex)
1789 {
1790 const auto topUri = materials.find(side->topMaterial).toUtf8();
1791 const auto midUri = materials.find(side->middleMaterial).toUtf8();
1792 const auto botUri = materials.find(side->bottomMaterial).toUtf8();
1793
1794 struct de_api_side_section_s top = {
1795 topUri, {float(side->offset[VX]), float(side->offset[VY])}, {1, 1, 1, 1}};
1796
1797 auto middle = top;
1798 middle.material = midUri;
1799
1800 auto bottom = top;
1801 bottom.material = botUri;
1802
1803 MPE_LineAddSide(
1804 lineIdx, sideIndex, sideFlags, &top, &middle, &bottom, side->index);
1805 };
1806
1807 LOGDEV_MAP_XVERBOSE("Transfering lines and sides...", "");
1808 DENG2_FOR_EACH(Lines, i, lines)
1809 {
1810 SideDef *front = (i->hasFront()? &sides[i->front()] : 0);
1811 SideDef *back = (i->hasBack() ? &sides[i->back() ] : 0);
1812
1813 short sideFlags = (format == Id1MapRecognizer::Doom64Format? SDF_MIDDLE_STRETCH : 0);
1814
1815 // Interpret the lack of a ML_TWOSIDED line flag to mean the
1816 // suppression of the side relative back sector.
1817 if(!(i->flags & 0x4 /*ML_TWOSIDED*/) && front && back)
1818 sideFlags |= SDF_SUPPRESS_BACK_SECTOR;
1819
1820 dint lineIdx = MPE_LineCreate(i->v[0], i->v[1], front? front->sector : -1,
1821 back? back->sector : -1, i->ddFlags, i->index);
1822
1823 if (front)
1824 {
1825 transferSide(lineIdx, sideFlags, front, LineDef::Front);
1826 }
1827 if (back)
1828 {
1829 transferSide(lineIdx, sideFlags, back, LineDef::Back);
1830 }
1831
1832 MPE_GameObjProperty("XLinedef", lineIdx, "Flags", DDVT_SHORT, &i->flags);
1833
1834 switch(format)
1835 {
1836 default:
1837 case Id1MapRecognizer::DoomFormat:
1838 MPE_GameObjProperty("XLinedef", lineIdx, "Type", DDVT_SHORT, &i->dType);
1839 MPE_GameObjProperty("XLinedef", lineIdx, "Tag", DDVT_SHORT, &i->dTag);
1840 break;
1841
1842 case Id1MapRecognizer::Doom64Format:
1843 MPE_GameObjProperty("XLinedef", lineIdx, "DrawFlags", DDVT_BYTE, &i->d64drawFlags);
1844 MPE_GameObjProperty("XLinedef", lineIdx, "TexFlags", DDVT_BYTE, &i->d64texFlags);
1845 MPE_GameObjProperty("XLinedef", lineIdx, "Type", DDVT_BYTE, &i->d64type);
1846 MPE_GameObjProperty("XLinedef", lineIdx, "UseType", DDVT_BYTE, &i->d64useType);
1847 MPE_GameObjProperty("XLinedef", lineIdx, "Tag", DDVT_SHORT, &i->d64tag);
1848 break;
1849
1850 case Id1MapRecognizer::HexenFormat:
1851 MPE_GameObjProperty("XLinedef", lineIdx, "Type", DDVT_BYTE, &i->xType);
1852 MPE_GameObjProperty("XLinedef", lineIdx, "Arg0", DDVT_BYTE, &i->xArgs[0]);
1853 MPE_GameObjProperty("XLinedef", lineIdx, "Arg1", DDVT_BYTE, &i->xArgs[1]);
1854 MPE_GameObjProperty("XLinedef", lineIdx, "Arg2", DDVT_BYTE, &i->xArgs[2]);
1855 MPE_GameObjProperty("XLinedef", lineIdx, "Arg3", DDVT_BYTE, &i->xArgs[3]);
1856 MPE_GameObjProperty("XLinedef", lineIdx, "Arg4", DDVT_BYTE, &i->xArgs[4]);
1857 break;
1858 }
1859 }
1860 }
1861
1862 void transferSurfaceTints()
1863 {
1864 if(surfaceTints.empty()) return;
1865
1866 LOGDEV_MAP_XVERBOSE("Transfering surface tints...", "");
1867 DENG2_FOR_EACH(SurfaceTints, i, surfaceTints)
1868 {
1869 dint idx = i - surfaceTints.begin();
1870
1871 MPE_GameObjProperty("Light", idx, "ColorR", DDVT_FLOAT, &i->rgb[0]);
1872 MPE_GameObjProperty("Light", idx, "ColorG", DDVT_FLOAT, &i->rgb[1]);
1873 MPE_GameObjProperty("Light", idx, "ColorB", DDVT_FLOAT, &i->rgb[2]);
1874 MPE_GameObjProperty("Light", idx, "XX0", DDVT_BYTE, &i->xx[0]);
1875 MPE_GameObjProperty("Light", idx, "XX1", DDVT_BYTE, &i->xx[1]);
1876 MPE_GameObjProperty("Light", idx, "XX2", DDVT_BYTE, &i->xx[2]);
1877 }
1878 }
1879
1880 void transferPolyobjs()
1881 {
1882 if(polyobjs.empty()) return;
1883
1884 LOGDEV_MAP_XVERBOSE("Transfering polyobjs...", "");
1885 DENG2_FOR_EACH(Polyobjs, i, polyobjs)
1886 {
1887 MPE_PolyobjCreate(i->lineIndices.constData(), i->lineIndices.count(),
1888 i->tag, i->seqType,
1889 coord_t(i->anchor[VX]), coord_t(i->anchor[VY]),
1890 i->index);
1891 }
1892 }
1893
1894 void transferThings()
1895 {
1896 if(things.empty()) return;
1897
1898 LOGDEV_MAP_XVERBOSE("Transfering things...", "");
1899 DENG2_FOR_EACH(Things, i, things)
1900 {
1901 dint idx = i - things.begin();
1902
1903 MPE_GameObjProperty("Thing", idx, "X", DDVT_SHORT, &i->origin[VX]);
1904 MPE_GameObjProperty("Thing", idx, "Y", DDVT_SHORT, &i->origin[VY]);
1905 MPE_GameObjProperty("Thing", idx, "Z", DDVT_SHORT, &i->origin[VZ]);
1906 MPE_GameObjProperty("Thing", idx, "Angle", DDVT_ANGLE, &i->angle);
1907 MPE_GameObjProperty("Thing", idx, "DoomEdNum", DDVT_SHORT, &i->doomEdNum);
1908 MPE_GameObjProperty("Thing", idx, "SkillModes", DDVT_INT, &i->skillModes);
1909 MPE_GameObjProperty("Thing", idx, "Flags", DDVT_INT, &i->flags);
1910
1911 if(format == Id1MapRecognizer::Doom64Format)
1912 {
1913 MPE_GameObjProperty("Thing", idx, "ID", DDVT_SHORT, &i->d64TID);
1914 }
1915 else if(format == Id1MapRecognizer::HexenFormat)
1916 {
1917 MPE_GameObjProperty("Thing", idx, "Special", DDVT_BYTE, &i->xSpecial);
1918 MPE_GameObjProperty("Thing", idx, "ID", DDVT_SHORT, &i->xTID);
1919 MPE_GameObjProperty("Thing", idx, "Arg0", DDVT_BYTE, &i->xArgs[0]);
1920 MPE_GameObjProperty("Thing", idx, "Arg1", DDVT_BYTE, &i->xArgs[1]);
1921 MPE_GameObjProperty("Thing", idx, "Arg2", DDVT_BYTE, &i->xArgs[2]);
1922 MPE_GameObjProperty("Thing", idx, "Arg3", DDVT_BYTE, &i->xArgs[3]);
1923 MPE_GameObjProperty("Thing", idx, "Arg4", DDVT_BYTE, &i->xArgs[4]);
1924 }
1925 }
1926 }
1927
1928 #if 0
1929 /**
1930 * @param lineList @c NULL, will cause IterFindPolyLines to count the number
1931 * of lines in the polyobj.
1932 */
1933 void collectPolyobjLinesWorker(Polyobj::LineIndices &lineList, Vec2d const &point)
1934 {
1935 DENG2_FOR_EACH(Lines, i, lines)
1936 {
1937 // Already belongs to another polyobj?
1938 if(i->aFlags & LAF_POLYOBJ) continue;
1939
1940 // Have we already encounterd this?
1941 if(i->validCount == validCount) continue;
1942
1943 if(point == vertexAsVec2d(i->v[0]))
1944 {
1945 i->validCount = validCount;
1946 lineList.append( i - lines.begin() );
1947 collectPolyobjLinesWorker(lineList, vertexAsVec2d(i->v[1]));
1948 }
1949 }
1950 }
1951 #endif
1952 };
1953
MapImporter(Id1MapRecognizer const & recognized)1954 MapImporter::MapImporter(Id1MapRecognizer const &recognized)
1955 : d(new Impl(this))
1956 {
1957 d->format = recognized.format();
1958 if(d->format == Id1MapRecognizer::UnknownFormat)
1959 throw LoadError("MapImporter", "Format unrecognized");
1960
1961 #if 0
1962 // Allocate the vertices first as a large contiguous array suitable for
1963 // passing directly to Doomsday's MapEdit interface.
1964 duint vertexCount = recognized.lumps().find(Id1MapRecognizer::VertexData).value()->size()
1965 / Id1MapRecognizer::elementSizeForDataType(d->format, Id1MapRecognizer::VertexData);
1966 d->vertCoords.resize(vertexCount * 2);
1967 #endif
1968
1969 DENG2_FOR_EACH_CONST(Id1MapRecognizer::Lumps, i, recognized.lumps())
1970 {
1971 Id1MapRecognizer::DataType dataType = i.key();
1972 File1 *lump = i.value();
1973
1974 dsize lumpLength = lump->size();
1975 if(!lumpLength) continue;
1976
1977 dsize elemSize = Id1MapRecognizer::elementSizeForDataType(d->format, dataType);
1978 if(!elemSize) continue;
1979
1980 // Process this data lump.
1981 duint const elemCount = lumpLength / elemSize;
1982 ByteRefArray lumpData(lump->cache(), lumpLength);
1983 de::Reader reader(lumpData);
1984 reader.setVersion(d->format);
1985 switch(dataType)
1986 {
1987 default: break;
1988
1989 case Id1MapRecognizer::VertexData: d->readVertexes (reader, elemCount); break;
1990 case Id1MapRecognizer::LineDefData: d->readLineDefs (reader, elemCount); break;
1991 case Id1MapRecognizer::SideDefData: d->readSideDefs (reader, elemCount); break;
1992 case Id1MapRecognizer::SectorDefData: d->readSectorDefs(reader, elemCount); break;
1993 case Id1MapRecognizer::ThingData: d->readThings (reader, elemCount); break;
1994 case Id1MapRecognizer::TintColorData: d->readTintColors(reader, elemCount); break;
1995 }
1996
1997 lump->unlock();
1998 }
1999
2000 d->linkLines();
2001 d->analyze();
2002 }
2003
transfer()2004 void MapImporter::transfer()
2005 {
2006 LOG_AS("MapImporter");
2007
2008 Time begunAt;
2009
2010 MPE_Begin(0/*dummy*/);
2011 d->transferVertexes();
2012 d->transferSectors();
2013 d->transferLinesAndSides();
2014 d->transferSurfaceTints();
2015 d->transferPolyobjs();
2016 d->transferThings();
2017 MPE_End();
2018
2019 LOGDEV_MAP_VERBOSE("Transfer completed in %.2f seconds") << begunAt.since();
2020 }
2021
toMaterialId(String name,MaterialGroup group)2022 MaterialId MapImporter::toMaterialId(String name, MaterialGroup group)
2023 {
2024 return d->materials.toMaterialId(name, group);
2025 }
2026
toMaterialId(int uniqueId,MaterialGroup group)2027 MaterialId MapImporter::toMaterialId(int uniqueId, MaterialGroup group)
2028 {
2029 return d->materials.toMaterialId(uniqueId, group);
2030 }
2031
2032 } // namespace idtech1
2033