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 &sector) const
746     {
747         return int(&sector - 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 &sector = 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 &sector) 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 &sector) 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 &sector, 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 &sector) 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 &sector)
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 &sector : sectors)
1380             {
1381                 const int sectorIndex = int(&sector - 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 = &check;
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(&sector - sectors.data()));
1470                 }
1471             }
1472 
1473             bool foundSelfRefs = false;
1474             for (int sectorIndex = 0; sectorIndex < int(sectors.size()); ++sectorIndex)
1475             {
1476                 auto &sector = 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 &sector : 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 &sector = 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 &sector : 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 &sector : 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