1 /** @file automapwidget.cpp  GUI widget for the automap.
2  *
3  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2005-2015 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 "common.h"
22 #include "hud/widgets/automapwidget.h"
23 
24 #include <QList>
25 #include <QtAlgorithms>
26 #include <de/LogBuffer>
27 #include <de/ScriptSystem>
28 #include <de/Vector>
29 
30 #include "dmu_lib.h"
31 #include "g_common.h"
32 #include "gamesession.h"
33 #include "hu_stuff.h"
34 #include "hud/automapstyle.h"
35 #include "p_mapsetup.h"
36 #include "p_tick.h"
37 #include "r_common.h"
38 #if __JDOOM64__
39 #  include "p_inventory.h"
40 #endif
41 
42 #define UIAUTOMAP_BORDER        4  ///< In fixed 320x200 pixels.
43 
44 using namespace de;
45 
AutomapWidget_UpdateGeometry(AutomapWidget * amap)46 static void AutomapWidget_UpdateGeometry(AutomapWidget *amap)
47 {
48     DENG2_ASSERT(amap);
49     amap->updateGeometry();
50 }
51 
AutomapWidget_Draw(AutomapWidget * amap,Point2Raw const * offset)52 static void AutomapWidget_Draw(AutomapWidget *amap, Point2Raw const *offset)
53 {
54     DENG2_ASSERT(amap);
55     amap->draw(offset? Vector2i(offset->xy) : Vector2i());
56 }
57 
58 struct uiautomap_rendstate_t
59 {
60     player_t *plr;
61     dint obType;         ///< The type of object to draw. @c -1= only line specials.
62     dd_bool glowOnly;
63     dglprimtype_t primType;
64 };
65 static uiautomap_rendstate_t rs;
66 static dbyte freezeMapRLs;
67 
68 // if -1 no background image will be drawn.
69 #if __JDOOM__ || __JDOOM64__
70 static dint autopageLumpNum = -1;
71 #elif __JHERETIC__
72 static dint autopageLumpNum = 1;
73 #else
74 static dint autopageLumpNum = 1;
75 #endif
76 
77 static DGLuint amMaskTexture;  // Used to mask the map primitives.
78 
79 namespace internal
80 {
rotate(Vector2d const & point,ddouble radian)81     static Vector2d rotate(Vector2d const &point, ddouble radian)
82     {
83         ddouble const c = std::cos(radian);
84         ddouble const s = std::sin(radian);
85         return Vector2d(c * point.x - s * point.y, s * point.x + c * point.y);
86     }
87 
initAABB(coord_t aabb[4],Vector2d const & point)88     static void initAABB(coord_t aabb[4], Vector2d const &point)
89     {
90         DENG2_ASSERT(aabb);
91         aabb[BOXLEFT] = aabb[BOXRIGHT ] = point.x;
92         aabb[BOXTOP ] = aabb[BOXBOTTOM] = point.y;
93     }
94 
addToAABB(coord_t aabb[4],Vector2d const & point)95     static void addToAABB(coord_t aabb[4], Vector2d const &point)
96     {
97         DENG2_ASSERT(aabb);
98         if     (point.x < aabb[BOXLEFT  ]) aabb[BOXLEFT  ] = point.x;
99         else if (point.x > aabb[BOXRIGHT ]) aabb[BOXRIGHT ] = point.x;
100 
101         if     (point.y < aabb[BOXBOTTOM]) aabb[BOXBOTTOM] = point.y;
102         else if (point.y > aabb[BOXTOP   ]) aabb[BOXTOP   ] = point.y;
103     }
104 
interceptEdge(coord_t point[2],coord_t const startA[2],coord_t const endA[2],coord_t const startB[2],coord_t const endB[2])105     static dd_bool interceptEdge(coord_t point[2], coord_t const startA[2],
106         coord_t const endA[2], coord_t const startB[2], coord_t const endB[2])
107     {
108         coord_t directionA[2];
109         V2d_Subtract(directionA, endA, startA);
110         if (V2d_PointOnLineSide(point, startA, directionA) >= 0)
111         {
112             coord_t directionB[2];
113             V2d_Subtract(directionB, endB, startB);
114             V2d_Intersection(startA, directionA, startB, directionB, point);
115             return true;
116         }
117         return false;
118     }
119 
fitPointInRectangle(Vector2d const & point,Vector2d const & topLeft,Vector2d const & topRight,Vector2d const & bottomRight,Vector2d const & bottomLeft,Vector2d const & viewPoint)120     static Vector2d fitPointInRectangle(Vector2d const &point, Vector2d const &topLeft,
121         Vector2d const &topRight, Vector2d const &bottomRight,
122         Vector2d const &bottomLeft, Vector2d const &viewPoint)
123     {
124         ddouble pointV1[2];
125         point.decompose(pointV1);
126 
127         ddouble topLeftV1[2];
128         topLeft.decompose(topLeftV1);
129 
130         ddouble topRightV1[2];
131         topRight.decompose(topRightV1);
132 
133         ddouble bottomRightV1[2];
134         bottomRight.decompose(bottomRightV1);
135 
136         ddouble bottomLeftV1[2];
137         bottomLeft.decompose(bottomLeftV1);
138 
139         ddouble viewPointV1[2];
140         viewPoint.decompose(viewPointV1);
141 
142         // Trace a vector from the view location to the marked point and intercept
143         // vs the edges of the rotated view window.
144         if (!interceptEdge(pointV1, topLeftV1, bottomLeftV1, viewPointV1, pointV1))
145             interceptEdge(pointV1, bottomRightV1, topRightV1, viewPointV1, pointV1);
146         if (!interceptEdge(pointV1, topRightV1, topLeftV1, viewPointV1, pointV1))
147             interceptEdge(pointV1, bottomLeftV1, bottomRightV1, viewPointV1, pointV1);
148 
149         return Vector2d(pointV1);
150     }
151 
drawVectorGraphic(svgid_t vgId,Vector2d const & origin,dfloat angle,dfloat scale,Vector3f const & color,dfloat opacity,blendmode_t blendmode)152     static void drawVectorGraphic(svgid_t vgId, Vector2d const &origin, dfloat angle,
153         dfloat scale, Vector3f const &color, dfloat opacity, blendmode_t blendmode)
154     {
155         opacity = de::clamp(0.f, opacity, 1.f);
156 
157         DGL_MatrixMode(DGL_TEXTURE);
158         DGL_PushMatrix();
159         DGL_Translatef(origin.x, origin.y, 1);
160 
161         DGL_Color4f(color.x, color.y, color.z, opacity);
162         DGL_BlendMode(blendmode);
163 
164         Point2Rawf originp(origin.x, origin.y);
165         GL_DrawSvg3(vgId, &originp, scale, angle);
166 
167         DGL_MatrixMode(DGL_TEXTURE);
168         DGL_PopMatrix();
169     }
170 
playerPaletteColor(dint consoleNum)171     static dint playerPaletteColor(dint consoleNum)
172     {
173         static dint playerColors[NUMPLAYERCOLORS] = {
174             AM_PLR1_COLOR, AM_PLR2_COLOR, AM_PLR3_COLOR, AM_PLR4_COLOR,
175 #if __JHEXEN__
176             AM_PLR5_COLOR, AM_PLR6_COLOR, AM_PLR7_COLOR, AM_PLR8_COLOR
177 #endif
178         };
179 
180         if (!IS_NETGAME) return WHITE;
181 
182         return playerColors[cfg.playerColor[de::max(0, consoleNum) % MAXPLAYERS]];
183     }
184 
drawPlayerMarker(dint consoleNum,AutomapStyle * style)185     static void drawPlayerMarker(dint consoleNum, AutomapStyle *style)
186     {
187         DENG2_ASSERT(consoleNum >= 0 && consoleNum < MAXPLAYERS);
188         player_t *player = &players[consoleNum];
189         if (!player->plr->inGame) return;
190 
191         mobj_t *plrMob = player->plr->mo;
192         if (!plrMob) return;
193 
194         coord_t origin[3]; Mobj_OriginSmoothed(plrMob, origin);
195         dfloat const angle = Mobj_AngleSmoothed(plrMob) / (dfloat) ANGLE_MAX * 360; /* $unifiedangles */
196 
197         dfloat color[3]; R_GetColorPaletteRGBf(0, playerPaletteColor(consoleNum), color, false);
198 
199         dfloat opacity = cfg.common.automapLineAlpha * uiRendState->pageAlpha;
200 #if __JDOOM__ || __JHERETIC__ || __JDOOM64__
201         if (player->powers[PT_INVISIBILITY])
202             opacity *= .125f;
203 #endif
204 
205         drawVectorGraphic(style->objectSvg(AMO_THINGPLAYER), Vector2d(origin),
206                           angle, PLAYERRADIUS, Vector3f(color), opacity, BM_NORMAL);
207     }
208 
209 }  // namespace internal
210 using namespace ::internal;
211 
DENG2_PIMPL(AutomapWidget)212 DENG2_PIMPL(AutomapWidget)
213 {
214     AutomapStyle *style = nullptr;
215 
216     float pixelRatio = 1.f; // DisplayMode.PIXEL_RATIO
217 
218     //DGLuint lists[NUM_MAP_OBJECTLISTS];  ///< Each list contains one or more of given type of automap wi.
219     bool needBuildLists = false;         ///< @c true= force a rebuild of all lists.
220 
221     dint flags = 0;
222     bool open     = false;       ///< @c true= currently active.
223     bool revealed = false;
224     bool follow   = true;        ///< @c true= camera position tracks followed player.
225     bool rotate   = false;
226 
227     bool forceMaxScale = false;  ///< If the map is currently in forced max zoom mode.
228     dfloat priorToMaxScale = 0;  ///< Viewer scale before entering maxScale mode.
229 
230     dfloat minScale  = 1.f;
231     dfloat scaleMTOF = 1.f;      ///< Used by MTOF to scale from map-to-frame-buffer coords.
232     dfloat scaleFTOM = 1.f;      ///< Used by FTOM to scale from frame-buffer-to-map coords (=1/scaleMTOF).
233 
234     coord_t bounds[4];           ///< Map space bounds:
235 
236     // Visual properties:
237     dfloat opacity = 0, targetOpacity = 0, oldOpacity = 0;
238     dfloat opacityTimer = 0;
239 
240     // Viewer location on the map:
241     Vector2d view, targetView, oldView;
242     dfloat viewTimer = 0;
243 
244     coord_t maxViewPositionDelta = 128;
245 //    Vector2d viewPL;  // For the parallax layer.
246 
247     // View frame scale:
248     dfloat viewScale = 1, targetViewScale = 1, oldViewScale = 1;
249     dfloat viewScaleTimer = 0;
250 
251     bool needViewScaleUpdate = false;
252     dfloat minScaleMTOF = 0;
253     dfloat maxScaleMTOF = 0;
254 
255     // View frame rotation:
256     dfloat angle = 0, targetAngle = 0, oldAngle = 0;
257     dfloat angleTimer = 0;
258 
259     // Bounding box of the actual visible area in map coordinates.
260     Vector2d topLeft, bottomRight, topRight, bottomLeft;
261 
262     // Axis-aligned bounding box of the potentially visible area
263     // (rotation-aware) in map coordinates.
264     coord_t viewAABB[4];
265 
266     // Misc:
267     QList<MarkedPoint *> points;  ///< Player-marked points of interest.
268     dint followPlayer = 0;        ///< Player being followed.
269 
270     Impl(Public *i) : Base(i)
271     {
272         using namespace de;
273 
274         zap(bounds);
275         zap(viewAABB);
276 
277         auto &ds = ScriptSystem::get();
278 
279         if (ds.nativeModuleExists("DisplayMode"))
280         {
281             pixelRatio = ds["DisplayMode"].getf("PIXEL_RATIO");
282         }
283     }
284 
285     ~Impl()
286     {
287         clearPoints();
288     }
289 
290     void clearPoints()
291     {
292         qDeleteAll(points); points.clear();
293     }
294 
295     void setMinScale(dfloat newMinScale)
296     {
297         minScale = de::max(1.f, newMinScale);
298         needViewScaleUpdate = true;
299     }
300 
301     /**
302      * Calculate the min/max scaling factors.
303      *
304      * Take the distance from the bottom left to the top right corners and choose a max scaling
305      * factor such that this distance is short than both the automap window width and height.
306      */
307     void updateViewScale()
308     {
309         float const oldMinScale = minScaleMTOF;
310 
311         Vector2d const topRight  (bounds[BOXRIGHT], bounds[BOXTOP   ]);
312         Vector2d const bottomLeft(bounds[BOXLEFT ], bounds[BOXBOTTOM]);
313         coord_t const dist = de::abs((topRight - bottomLeft).length());
314 
315         Vector2f const dimensions(Rect_Width (&self().geometry()),
316                                   Rect_Height(&self().geometry()));
317         Vector2f const scale = dimensions / dist;
318 
319         minScaleMTOF = (scale.x < scale.y ? scale.x : scale.y);
320         maxScaleMTOF = dimensions.y / minScale;
321 
322         LOG_AS("AutomapWidget");
323         LOGDEV_XVERBOSE("updateViewScale: delta:%s dimensions:%s dist:%f scale:%s minmtof:%f",
324                    (topRight - bottomLeft).asText()
325                 << dimensions.asText() << dist
326                 << scale.asText() << minScaleMTOF);
327 
328         // Update previously set view scale accordingly.
329         /// @todo  The view scale factor needs to be resolution independent!
330         targetViewScale = viewScale = minScaleMTOF / oldMinScale * targetViewScale;
331         needViewScaleUpdate = false;
332     }
333 
334     static void drawLine2(Vector2d const &from, Vector2d const &to,
335         Vector3f const &color, dfloat opacity, glowtype_t glowType, dfloat glowStrength,
336         dfloat glowSize, dd_bool glowOnly, dd_bool scaleGlowWithView, dd_bool caps,
337         /*blendmode_t blend, */dd_bool drawNormal)
338     {
339         opacity *= uiRendState->pageAlpha;
340 
341         Vector2d const unit = (to - from).normalize();
342         Vector2d const normal(unit.y, -unit.x);
343 
344         if (de::abs(unit.length()) <= 0) return;
345 
346         //DGL_BlendMode(blend);
347 
348         // Is this a glowing line?
349         if (glowOnly && glowType != GLOW_NONE)
350         {
351             //dint const tex = Get(DD_DYNLIGHT_TEXTURE);
352 
353             // Scale line thickness relative to zoom level?
354             dfloat thickness;
355             if (scaleGlowWithView)
356                 thickness = cfg.common.automapDoorGlow * 2.5f + 3;
357             else
358                 thickness = glowSize;
359 
360             DENG_ASSERT(rs.primType == DGL_QUADS);
361 
362             // Draw a "cap" at the start of the line?
363             if (caps)
364             {
365                 Vector2f const v1 = from -   unit * thickness + normal * thickness;
366                 Vector2f const v2 = from + normal * thickness;
367                 Vector2f const v3 = from - normal * thickness;
368                 Vector2f const v4 = from -   unit * thickness - normal * thickness;
369 
370                 //if (!addToLists)
371                 {
372                     //DGL_Bind(tex);
373 
374                     DGL_Color4f(color.x, color.y, color.z, glowStrength * opacity);
375                     //DGL_BlendMode(blend);
376                 }
377 
378 //                DGL_Begin(DGL_QUADS);
379                 DGL_TexCoord2f(0, 0, 0);
380                 DGL_TexCoord2f(1, v1.x, v1.y);
381                 DGL_Vertex2f(v1.x, v1.y);
382 
383                 DGL_TexCoord2f(0, .5f, 0);
384                 DGL_TexCoord2f(1, v2.x, v2.y);
385                 DGL_Vertex2f(v2.x, v2.y);
386 
387                 DGL_TexCoord2f(0, .5f, 1);
388                 DGL_TexCoord2f(1, v3.x, v3.y);
389                 DGL_Vertex2f(v3.x, v3.y);
390 
391                 DGL_TexCoord2f(0, 0, 1);
392                 DGL_TexCoord2f(1, v4.x, v4.y);
393                 DGL_Vertex2f(v4.x, v4.y);
394 //                DGL_End();
395 
396                 //if (!addToLists)
397                     //DGL_BlendMode(BM_NORMAL);
398             }
399 
400             // The middle part of the line.
401             switch (glowType)
402             {
403             case GLOW_BOTH: {
404                 Vector2f const v1 = from + normal * thickness;
405                 Vector2f const v2 =   to + normal * thickness;
406                 Vector2f const v3 =   to - normal * thickness;
407                 Vector2f const v4 = from - normal * thickness;
408 
409                 //if (!addToLists)
410                 {
411                     //DGL_Bind(tex);
412 
413                     DGL_Color4f(color.x, color.y, color.z, glowStrength * opacity);
414                     //DGL_BlendMode(blend);
415                 }
416 
417 //                DGL_Begin(DGL_QUADS);
418                 DGL_TexCoord2f(0, .5f, 0);
419                 DGL_TexCoord2f(1, v1.x, v1.y);
420                 DGL_Vertex2f(v1.x, v1.y);
421 
422                 DGL_TexCoord2f(0, .5f, 0);
423                 DGL_TexCoord2f(1, v2.x, v2.y);
424                 DGL_Vertex2f(v2.x, v2.y);
425 
426                 DGL_TexCoord2f(0, .5f, 1);
427                 DGL_TexCoord2f(1, v3.x, v3.y);
428                 DGL_Vertex2f(v3.x, v3.y);
429 
430                 DGL_TexCoord2f(0, .5f, 1);
431                 DGL_TexCoord2f(1, v4.x, v4.y);
432                 DGL_Vertex2f(v4.x, v4.y);
433 //                DGL_End();
434 
435                 //if (!addToLists)
436                     //DGL_BlendMode(BM_NORMAL);
437                 break; }
438 
439             case GLOW_BACK: {
440                 Vector2f const v1 = from + normal * thickness;
441                 Vector2f const v2 =   to + normal * thickness;
442 
443                 //if (!addToLists)
444                 {
445                     //DGL_Bind(tex);
446 
447                     DGL_Color4f(color.x, color.y, color.z, glowStrength * opacity);
448 //                    DGL_BlendMode(blend);
449                 }
450 
451 //                DGL_Begin(DGL_QUADS);
452                 DGL_TexCoord2f(0, 0, .25f);
453                 DGL_TexCoord2f(1, v1.x, v1.y);
454                 DGL_Vertex2f(v1.x, v1.y);
455 
456                 DGL_TexCoord2f(0, 0, .25f);
457                 DGL_TexCoord2f(1, v2.x, v2.y);
458                 DGL_Vertex2f(v2.x, v2.y);
459 
460                 DGL_TexCoord2f(0, .5f, .25f);
461                 DGL_TexCoord2f(1, to.x, to.y);
462                 DGL_Vertex2f(to.x, to.y);
463 
464                 DGL_TexCoord2f(0, .5f, .25f);
465                 DGL_TexCoord2f(1, from.x, from.y);
466                 DGL_Vertex2f(from.x, from.y);
467 //                DGL_End();
468 
469                 //if (!addToLists)
470 //                    DGL_BlendMode(BM_NORMAL);
471                 break; }
472 
473             case GLOW_FRONT: {
474                 Vector2f const v3 =   to - normal * thickness;
475                 Vector2f const v4 = from - normal * thickness;
476 
477                 //if (!addToLists)
478                 {
479                     //DGL_Bind(tex);
480 
481                     DGL_Color4f(color.x, color.y, color.z, glowStrength * opacity);
482 //                    DGL_BlendMode(blend);
483                 }
484 
485 //                DGL_Begin(DGL_QUADS);
486                 DGL_TexCoord2f(0, .75f, .5f);
487                 DGL_TexCoord2f(1, from.x, from.y);
488                 DGL_Vertex2f(from.x, from.y);
489 
490                 DGL_TexCoord2f(0, .75f, .5f);
491                 DGL_TexCoord2f(1, to.x, to.y);
492                 DGL_Vertex2f(to.x, to.y);
493 
494                 DGL_TexCoord2f(0, .75f, 1);
495                 DGL_TexCoord2f(1, v3.x, v3.y);
496                 DGL_Vertex2f(v3.x, v3.y);
497 
498                 DGL_TexCoord2f(0, .75f, 1);
499                 DGL_TexCoord2f(1, v4.x, v4.y);
500                 DGL_Vertex2f(v4.x, v4.y);
501 //                DGL_End();
502 
503                 //if (!addToLists)
504 //                    DGL_BlendMode(BM_NORMAL);
505                 break; }
506 
507             default:
508                 DENG2_ASSERT(!"Unknown glowtype");
509                 break;
510             }
511 
512             if (caps)
513             {
514                 Vector2f const v1 = to + normal * thickness;
515                 Vector2f const v2 = to +   unit * thickness + normal * thickness;
516                 Vector2f const v3 = to +   unit * thickness - normal * thickness;
517                 Vector2f const v4 = to - normal * thickness;
518 
519                 //if (!addToLists)
520                 {
521                     //DGL_Bind(tex);
522 
523                     DGL_Color4f(color.x, color.y, color.z, glowStrength * opacity);
524 //                    DGL_BlendMode(blend);
525                 }
526 
527 //                DGL_Begin(DGL_QUADS);
528                 DGL_TexCoord2f(0, .5f, 0);
529                 DGL_TexCoord2f(1, v1.x, v1.y);
530                 DGL_Vertex2f(v1.x, v1.y);
531 
532                 DGL_TexCoord2f(0, 1, 0);
533                 DGL_TexCoord2f(1, v2.x, v2.y);
534                 DGL_Vertex2f(v2.x, v2.y);
535 
536                 DGL_TexCoord2f(0, 1, 1);
537                 DGL_TexCoord2f(1, v3.x, v3.y);
538                 DGL_Vertex2f(v3.x, v3.y);
539 
540                 DGL_TexCoord2f(0, .5, 1);
541                 DGL_TexCoord2f(1, v4.x, v4.y);
542                 DGL_Vertex2f(v4.x, v4.y);
543 //                DGL_End();
544 
545                 //if (!addToLists)
546 //                    DGL_BlendMode(BM_NORMAL);
547             }
548         }
549         else if (!glowOnly)
550         {
551             DENG_ASSERT(rs.primType == DGL_LINES);
552             //if (!addToLists)
553             {
554                 DGL_Color4f(color.x, color.y, color.z, opacity);
555 //                DGL_BlendMode(blend);
556             }
557 
558             //DGL_Begin(DGL_LINES);
559             DGL_TexCoord2f(0, from[0], from[1]);
560             DGL_Vertex2f(from[0], from[1]);
561             DGL_TexCoord2f(0, to[0], to[1]);
562             DGL_Vertex2f(to[0], to[1]);
563             //DGL_End();
564 
565             //if (!addToLists)
566 //                DGL_BlendMode(BM_NORMAL);
567             if (drawNormal)
568             {
569 #define NORMTAIL_LENGTH         8
570 
571                 Vector2f const v1 = (from + to) / 2;
572                 Vector2d const v2 = v1 + normal * NORMTAIL_LENGTH;
573 
574                 //if (!addToLists)
575                 {
576                     DGL_Color4f(color.x, color.y, color.z, opacity);
577                     //                DGL_BlendMode(blend);
578                 }
579 
580 //                DGL_Begin(DGL_LINES);
581                 DGL_TexCoord2f(0, v1.x, v1.y);
582                 DGL_Vertex2f(v1.x, v1.y);
583 
584                 DGL_TexCoord2f(0, v2.x, v2.y);
585                 DGL_Vertex2f(v2.x, v2.y);
586 //                DGL_End();
587 
588                 //if (!addToLists)
589                 //                DGL_BlendMode(BM_NORMAL);
590 
591 #undef NORMTAIL_LENGTH
592             }
593         }
594 
595             //DGL_BlendMode(BM_NORMAL);
596     }
597 
598     void drawLine(Line *line) const
599     {
600         DENG2_ASSERT(line);
601 
602         xline_t *xline = P_ToXLine(line);
603 
604         // Already drawn once?
605         if (xline->validCount == VALIDCOUNT)
606             return;
607 
608         // Is this line being drawn?
609         if ((xline->flags & ML_DONTDRAW) && !(flags & AWF_SHOW_ALLLINES))
610             return;
611 
612         // We only want to draw twosided lines once.
613         auto *frontSector = (Sector *)P_GetPtrp(line, DMU_FRONT_SECTOR);
614         if (frontSector && frontSector != (Sector *)P_GetPtrp(line, DMU_FRONT_SECTOR))
615         {
616             return;
617         }
618 
619         automapcfg_lineinfo_t const *info = nullptr;
620         if ((flags & AWF_SHOW_ALLLINES) || xline->mapped[rs.plr - players])
621         {
622             auto *backSector = reinterpret_cast<Sector *>(P_GetPtrp(line, DMU_BACK_SECTOR));
623 
624             // Perhaps this is a specially colored line?
625             info = style->tryFindLineInfo_special(xline->special, xline->flags,
626                                                   frontSector, backSector, flags);
627             if (rs.obType != -1 && !info)
628             {
629                 // Perhaps a default colored line?
630                 /// @todo Implement an option which changes the vanilla behavior of always
631                 ///       coloring non-secret lines with the solid-wall color to instead
632                 ///       use whichever color it would be if not flagged secret.
633                 if (!backSector || !P_GetPtrp(line, DMU_BACK) || (xline->flags & ML_SECRET))
634                 {
635                     // solid wall (well probably anyway...)
636                     info = style->tryFindLineInfo(AMO_SINGLESIDEDLINE);
637                 }
638                 else
639                 {
640                     if (!de::fequal(P_GetDoublep(backSector,  DMU_FLOOR_HEIGHT),
641                                     P_GetDoublep(frontSector, DMU_FLOOR_HEIGHT)))
642                     {
643                         // Floor level change.
644                         info = style->tryFindLineInfo(AMO_FLOORCHANGELINE);
645                     }
646                     else if (!de::fequal(P_GetDoublep(backSector,  DMU_CEILING_HEIGHT),
647                                          P_GetDoublep(frontSector, DMU_CEILING_HEIGHT)))
648                     {
649                         // Ceiling level change.
650                         info = style->tryFindLineInfo(AMO_CEILINGCHANGELINE);
651                     }
652                     else if (flags & AWF_SHOW_ALLLINES)
653                     {
654                         info = style->tryFindLineInfo(AMO_UNSEENLINE);
655                     }
656                 }
657             }
658         }
659         else if (rs.obType != -1 && revealed)
660         {
661             if (!(xline->flags & ML_DONTDRAW))
662             {
663                 // An as yet, unseen line.
664                 info = style->tryFindLineInfo(AMO_UNSEENLINE);
665             }
666         }
667 
668         if (info && (rs.obType == -1 || info == &style->lineInfo(rs.obType)))
669         {
670             ddouble from[2]; P_GetDoublepv(P_GetPtrp(line, DMU_VERTEX0), DMU_XY, from);
671             ddouble to  [2]; P_GetDoublepv(P_GetPtrp(line, DMU_VERTEX1), DMU_XY, to);
672 
673             drawLine2(Vector2d(from), Vector2d(to), Vector3f(info->rgba), info->rgba[3],
674                       (xline->special && !cfg.common.automapShowDoors ? GLOW_NONE : info->glow),
675                       info->glowStrength,
676                       info->glowSize, rs.glowOnly, info->scaleWithView,
677                       (info->glow && !(xline->special && !cfg.common.automapShowDoors)),
678                       //(xline->special && !cfg.common.automapShowDoors ? BM_NORMAL : info->blendMode),
679                       (flags & AWF_SHOW_LINE_NORMALS));
680 
681             xline->validCount = VALIDCOUNT; // Mark as drawn this frame.
682         }
683     }
684 
685     static int drawLineWorker(void *line, void *context)
686     {
687         static_cast<Impl *>(context)->drawLine((Line *)line);
688         return false;  // Continue iteration.
689     }
690 
691     static int drawLinesForSubspaceWorker(ConvexSubspace *subspace, void *context)
692     {
693         return P_Iteratep(subspace, DMU_LINE, drawLineWorker, context);
694     }
695 
696     /**
697      * Determines visible lines, draws them.
698      *
699      * @params objType  Type of map object being drawn.
700      */
701     void drawAllLines(dint obType, bool glowOnly = false) const
702     {
703         // VALIDCOUNT is used to track which lines have been drawn this frame.
704         VALIDCOUNT++;
705 
706         // Configure render state:
707         rs.obType   = obType;
708         rs.glowOnly = glowOnly;
709 
710         if (glowOnly)
711         {
712             rs.primType = DGL_QUADS;
713             DGL_Enable(DGL_TEXTURE0);
714             DGL_Bind(DGLuint(Get(DD_DYNLIGHT_TEXTURE)));
715         }
716         else
717         {
718             rs.primType = DGL_LINES;
719             if (amMaskTexture)
720             {
721                 DGL_Enable(DGL_TEXTURE0);
722                 DGL_Bind(amMaskTexture);
723             }
724         }
725 
726         DGL_Begin(rs.primType);
727 
728         // Can we use the automap's in-view bounding box to cull out of view objects?
729         //if (!addToLists)
730         {
731             AABoxd aaBox;
732             self().pvisibleBounds(&aaBox.minX, &aaBox.maxX, &aaBox.minY, &aaBox.maxY);
733             Subspace_BoxIterator(&aaBox, drawLinesForSubspaceWorker, const_cast<Impl *>(this));
734         }
735         /*else
736         {
737             // No. As the map lists are considered static we want them to contain all
738             // walls, not just those visible *now* (note rotation).
739             dint const numSubspaces = P_Count(DMU_SUBSPACE);
740             for (dint i = 0; i < numSubspaces; ++i)
741             {
742                 P_Iteratep(P_ToPtr(DMU_SUBSPACE, i), DMU_LINE, drawLineWorker, const_cast<Impl *>(this));
743             }
744         }*/
745 
746         DGL_End();
747         DGL_Enable(DGL_TEXTURE0);
748     }
749 
750     static void drawLine(Line *line, Vector3f const &color, dfloat opacity,
751                          /*blendmode_t blendMode, */bool showNormal)
752     {
753         dfloat length = P_GetFloatp(line, DMU_LENGTH);
754 
755         if (length > 0)
756         {
757             dfloat v1[2]; P_GetFloatpv(P_GetPtrp(line, DMU_VERTEX0), DMU_XY, v1);
758             dfloat v2[2]; P_GetFloatpv(P_GetPtrp(line, DMU_VERTEX1), DMU_XY, v2);
759 
760             //DGL_BlendMode(blendMode);
761             DGL_Color4f(color.x, color.y, color.z, opacity);
762 
763             DENG_ASSERT(rs.primType == DGL_LINES);
764 
765             //DGL_Begin(DGL_LINES);
766             DGL_TexCoord2f(0, v1[0], v1[1]);
767             DGL_Vertex2f(v1[0], v1[1]);
768 
769             DGL_TexCoord2f(0, v2[0], v2[1]);
770             DGL_Vertex2f(v2[0], v2[1]);
771             //DGL_End();
772 
773             if (showNormal)
774             {
775 #define NORMTAIL_LENGTH         8
776 
777                 dfloat d1[2]; P_GetFloatpv(line, DMU_DXY, d1);
778 
779                 dfloat const unit  [] = { d1[0] / length, d1[1] / length };
780                 dfloat const normal[] = { unit[1], -unit[0] };
781 
782                 // The center of the line.
783                 v1[0] += (length / 2) * unit[0];
784                 v1[1] += (length / 2) * unit[1];
785 
786                 // Outside point.
787                 v2[0] = v1[0] + normal[0] * NORMTAIL_LENGTH;
788                 v2[1] = v1[1] + normal[1] * NORMTAIL_LENGTH;
789 
790                 //DGL_Begin(DGL_LINES);
791                 DGL_TexCoord2f(0, v1[0], v1[1]);
792                 DGL_Vertex2f(v1[0], v1[1]);
793 
794                 DGL_TexCoord2f(0, v2[0], v2[1]);
795                 DGL_Vertex2f(v2[0], v2[1]);
796                 //DGL_End();
797 
798 #undef NORMTAIL_LENGTH
799             }
800 
801             //DGL_BlendMode(BM_NORMAL);
802         }
803     }
804 
805     static int drawLine_polyob(Line *line, void *context)
806     {
807         auto const *inst = static_cast<Impl *>(context);
808         DENG2_ASSERT(inst);
809 
810         dfloat const opacity = uiRendState->pageAlpha;
811 
812         xline_t *xline = P_ToXLine(line);
813         if (!xline) return false;
814 
815         // Already processed this frame?
816         if (xline->validCount == VALIDCOUNT) return false;
817 
818         if ((xline->flags & ML_DONTDRAW) && !(inst->flags & AWF_SHOW_ALLLINES))
819         {
820             return false;
821         }
822 
823         automapcfg_objectname_t amo = AMO_NONE;
824         if ((inst->flags & AWF_SHOW_ALLLINES) || xline->mapped[rs.plr - players])
825         {
826             amo = AMO_SINGLESIDEDLINE;
827         }
828         else if (rs.obType != -1 && inst->revealed)
829         {
830             if (!(xline->flags & ML_DONTDRAW))
831             {
832                 // An as yet, unseen line.
833                 amo = AMO_UNSEENLINE;
834             }
835         }
836 
837         if (automapcfg_lineinfo_t const *info = inst->style->tryFindLineInfo(amo))
838         {
839             drawLine(line, Vector3f(info->rgba), info->rgba[3] * cfg.common.automapLineAlpha * opacity,
840                      /*info->blendMode, */(inst->flags & AWF_SHOW_LINE_NORMALS));
841         }
842 
843         xline->validCount = VALIDCOUNT;  // Mark as processed this frame.
844 
845         return false;  // Continue iteration.
846     }
847 
848     void drawAllPolyobs() const
849     {
850         VALIDCOUNT++;  // Used to track which lines have been drawn this frame.
851 
852         // Configure render state:
853         rs.obType = MOL_LINEDEF;
854         rs.primType = DGL_LINES;
855 
856         DGL_Begin(rs.primType);
857         {
858             // Draw any polyobjects in view.
859             AABoxd aaBox;
860             self().pvisibleBounds(&aaBox.minX, &aaBox.maxX, &aaBox.minY, &aaBox.maxY);
861             Line_BoxIterator(&aaBox, LIF_POLYOBJ, drawLine_polyob, const_cast<Impl *>(this));
862         }
863         DGL_End();
864     }
865 
866 #if __JDOOM__ || __JHERETIC__ || __JDOOM64__
867     static int drawLine_xg(Line *line, void *context)
868     {
869         auto const *inst = static_cast<Impl *>(context);
870         DENG2_ASSERT(line && inst);
871 
872         xline_t *xline = P_ToXLine(line);
873         if (!xline) return false;
874 
875         if (xline->validCount == VALIDCOUNT) return false;
876 
877         if (!(inst->flags & AWF_SHOW_ALLLINES))
878         {
879             if (xline->flags & ML_DONTDRAW) return false;
880         }
881 
882         // Only active XG lines.
883         if (!xline->xg || !xline->xg->active) return false;
884 
885         // XG lines blink.
886         if (!(mapTime & 4)) return false;
887 
888         drawLine(line, Vector3f(.8f, 0, .8f), 1, (inst->flags & AWF_SHOW_LINE_NORMALS));
889         xline->validCount = VALIDCOUNT;  // Mark as processed this frame.
890 
891         return false;  // Continue iteration.
892     }
893 #endif
894 
895     void drawAllLines_xg() const
896     {
897 #if __JDOOM__ || __JHERETIC__ || __JDOOM64__
898         if (!(flags & AWF_SHOW_SPECIALLINES))
899             return;
900 
901         // VALIDCOUNT is used to track which lines have been drawn this frame.
902         VALIDCOUNT++;
903 
904         // Configure render state:
905         rs.glowOnly = true;
906         rs.obType   = -1;
907         rs.primType = DGL_LINES;
908 
909         DGL_BlendMode(BM_ADD);
910         DGL_Begin(rs.primType);
911         {
912             AABoxd aaBox;
913             self().pvisibleBounds(&aaBox.minX, &aaBox.maxX, &aaBox.minY, &aaBox.maxY);
914             Line_BoxIterator(&aaBox, LIF_SECTOR, drawLine_xg, const_cast<Impl *>(this));
915         }
916         DGL_End();
917         DGL_BlendMode(BM_NORMAL);
918 #endif
919     }
920 
921     /**
922      * Visualize all players on the map with SVG markers.
923      */
924     void drawAllPlayerMarkers() const
925     {
926         for (dint i = 0; i < MAXPLAYERS; ++i)
927         {
928             // Do not show markers for other players in deathmatch.
929             if (gfw_Rule(deathmatch) && i != self().player())
930             {
931                 continue;
932             }
933 
934             drawPlayerMarker(i, style);
935         }
936     }
937 
938     static dint thingColorForMobjType(mobjtype_t type)
939     {
940 #if __JHEXEN__
941         DENG2_UNUSED(type);
942         return -1;
943 #else
944         struct ThingData { mobjtype_t type; dint palColor; } static const thingData[] = {
945 #  if __JDOOM__ || __JDOOM64__
946             { MT_MISC4, KEY1_COLOR },
947             { MT_MISC5, KEY2_COLOR },
948             { MT_MISC6, KEY3_COLOR },
949             { MT_MISC7, KEY4_COLOR },
950             { MT_MISC8, KEY5_COLOR },
951             { MT_MISC9, KEY6_COLOR },
952 #  elif __JHERETIC__
953             { MT_CKEY,  KEY1_COLOR },
954             { MT_BKYY,  KEY2_COLOR },
955             { MT_AKYY,  KEY3_COLOR },
956 #  endif
957         };
958         for (auto const &thing : thingData)
959         {
960             if (thing.type == type) return thing.palColor;
961         }
962         return -1;  // None.
963 #endif
964     }
965 
966     struct drawthingpoint_params_t
967     {
968         dint flags;      ///< AWF_* flags.
969         svgid_t vgId;
970         dfloat rgb[3];
971         dfloat opacity;
972     };
973 
974     static int drawThingPoint(mobj_t *mob, void *context)
975     {
976         auto *p = reinterpret_cast<drawthingpoint_params_t *>(context);
977 
978         // Only sector linked mobjs should be visible in the automap.
979         if (!(mob->flags & MF_NOSECTOR))
980         {
981             svgid_t vgId   = p->vgId;
982             bool isVisible = false;
983             dfloat *color  = p->rgb;
984 
985             dfloat angle = 0;
986             dfloat keyColorRGB[3];
987             if (p->flags & AWF_SHOW_KEYS)
988             {
989                 dint keyColor = thingColorForMobjType(mobjtype_t(mob->type));
990                 if (keyColor != -1)
991                 {
992                     R_GetColorPaletteRGBf(0, keyColor, keyColorRGB, false);
993                     vgId      = VG_KEY;
994                     color     = keyColorRGB;
995                     isVisible = true;
996                 }
997             }
998 
999             // Something else?
1000             if (!isVisible)
1001             {
1002                 isVisible = !!(p->flags & AWF_SHOW_THINGS);
1003                 angle = Mobj_AngleSmoothed(mob) / float(ANGLE_MAX) * 360;  // In degrees.
1004             }
1005 
1006             if (isVisible)
1007             {
1008                 /* $unifiedangles */
1009                 coord_t origin[3]; Mobj_OriginSmoothed(mob, origin);
1010 
1011                 drawVectorGraphic(vgId, Vector2d(origin), angle, 16 /*radius*/,
1012                                   Vector3f(color), p->opacity, BM_NORMAL);
1013             }
1014         }
1015 
1016         return false; // Continue iteration.
1017     }
1018 
1019     void drawAllThings() const
1020     {
1021         if (!(flags & (AWF_SHOW_THINGS | AWF_SHOW_KEYS)))
1022             return;
1023 
1024         dfloat const alpha = uiRendState->pageAlpha;
1025 
1026         drawthingpoint_params_t parm; de::zap(parm);
1027         parm.flags   = flags;
1028         parm.vgId    = style->objectSvg(AMO_THING);
1029         AM_GetMapColor(parm.rgb, cfg.common.automapMobj, THINGCOLORS, customPal);
1030         parm.opacity = de::clamp(0.f, cfg.common.automapLineAlpha * alpha, 1.f);
1031 
1032         AABoxd aaBox;
1033         self().pvisibleBounds(&aaBox.minX, &aaBox.maxX, &aaBox.minY, &aaBox.maxY);
1034 
1035         VALIDCOUNT++;
1036         Mobj_BoxIterator(&aaBox, drawThingPoint, &parm);
1037     }
1038 
1039     void drawAllPoints(dfloat scale = 1) const
1040     {
1041         dfloat const alpha = uiRendState->pageAlpha;
1042 
1043         if (points.isEmpty()) return;
1044 
1045         // Calculate final scale factor.
1046         scale = self().frameToMap(1) * scale;
1047 #if __JHERETIC__ || __JHEXEN__
1048         // These games use a larger font, so use a smaller scale.
1049         scale *= .5f;
1050 #endif
1051 
1052         dint idx = 0;
1053         const Point2Raw labelOffset{};
1054         for (MarkedPoint const *point : points)
1055         {
1056             String const label    = String::number(idx++);
1057             Vector2d const origin = fitPointInRectangle(point->origin(), topLeft, topRight, bottomRight, bottomLeft, view);
1058 
1059             DGL_MatrixMode(DGL_MODELVIEW);
1060             DGL_PushMatrix();
1061             DGL_Translatef(origin.x, origin.y, 0);
1062             DGL_Scalef(scale, scale, 1);
1063             DGL_Rotatef(angle, 0, 0, 1);
1064             DGL_Scalef(1, -1, 1);
1065             DGL_Enable(DGL_TEXTURE_2D);
1066 
1067             FR_SetFont(FID(GF_MAPPOINT));
1068 #if __JDOOM__
1069             if (gameMode == doom2_hacx)
1070                 FR_SetColorAndAlpha(1, 1, 1, alpha);
1071             else
1072                 FR_SetColorAndAlpha(.22f, .22f, .22f, alpha);
1073 #else
1074             FR_SetColorAndAlpha(1, 1, 1, alpha);
1075 #endif
1076             FR_DrawText3(label.toUtf8().constData(), &labelOffset, 0, DTF_ONLY_SHADOW);
1077 
1078             DGL_Disable(DGL_TEXTURE_2D);
1079             DGL_MatrixMode(DGL_MODELVIEW);
1080             DGL_PopMatrix();
1081         }
1082     }
1083 
1084     /**
1085      * Sets up the state for automap drawing.
1086      */
1087     void setupGLStateForMap() const
1088     {
1089         const dfloat alpha = uiRendState->pageAlpha;
1090 
1091         // Store the old scissor state (to clip the map lines and stuff).
1092         DGL_PushState();
1093 
1094         DGL_MatrixMode(DGL_MODELVIEW);
1095         DGL_PushMatrix();
1096 
1097         dfloat bgColor[3];
1098 #if __JHERETIC__ || __JHEXEN__
1099         if (CentralLumpIndex().contains("AUTOPAGE.lmp"))
1100         {
1101             bgColor[0] = bgColor[1] = bgColor[2] = 1.f; // use lump colors as-is
1102         }
1103         else
1104         {
1105             // Automap background lump is missing.
1106             bgColor[0] = .55f; bgColor[1] = .45f; bgColor[2] = .35f;
1107         }
1108 #else
1109         AM_GetMapColor(bgColor, cfg.common.automapBack, BACKGROUND, customPal);
1110 #endif
1111 
1112         RectRaw geom; Rect_Raw(&self().geometry(), &geom);
1113 
1114         // Draw the AUTOPAGE background image (if available).
1115         if (autopageLumpNum != -1)
1116         {
1117             const float autopageWidth       = 320; /// @todo Could be external with different size.
1118             const float autopageHeight      = 200;
1119             const float autopageAspectRatio = autopageWidth / autopageHeight;
1120 
1121             const float texScale    = 1.f / 3000.f;
1122             const float bgScale     = texScale / scaleMTOF;
1123             const float offsetScale = texScale * autopageAspectRatio;
1124 
1125             // The autopage texture is transformed in texture coordinate space. It is drawn as
1126             // a single quad covering the entire widget.
1127 
1128             DGL_Enable(DGL_TEXTURE_2D);
1129 
1130             DGL_MatrixMode(DGL_TEXTURE);
1131             DGL_PushMatrix();
1132             DGL_LoadIdentity();
1133 
1134             DGL_SetRawImage(autopageLumpNum, DGL_REPEAT, DGL_REPEAT);
1135             DGL_Color4f(bgColor[0], bgColor[1], bgColor[2], cfg.common.automapOpacity * alpha);
1136 
1137 #if 0
1138             DGL_Translatef(geom.origin.x, geom.origin.y, 0);
1139 
1140             // Apply the parallax scrolling, map rotation and counteract the
1141             // aspect of the quad (sized to map window dimensions).
1142             DGL_Translatef(self().mapToFrame(viewPL.x) + .5f,
1143                            self().mapToFrame(viewPL.y) + .5f, 0);
1144             DGL_Scalef(1, 1.2f/*aspect correct*/, 1);
1145             DGL_Rotatef(360 - self().cameraAngle(), 0, 0, 1);
1146             DGL_Translatef(-(.5f), -(.5f), 0);
1147 #endif
1148 
1149             DENG_ASSERT(!std::isnan(view.x));
1150 
1151             DGL_Translatef(offsetScale * view.x, -offsetScale * view.y, 1.f);
1152             DGL_Scalef(autopageAspectRatio, autopageAspectRatio, 1.f);
1153             DGL_Rotatef(360.f - self().cameraAngle(), 0, 0, 1);
1154             DGL_Scalef(bgScale * float(geom.size.width), bgScale * float(geom.size.height), 1.f);
1155             DGL_Translatef(-.5f, -.5f, 0); // center
1156             DGL_DrawRectf2(geom.origin.x, geom.origin.y, geom.size.width, geom.size.height);
1157 
1158             DGL_MatrixMode(DGL_TEXTURE);
1159             DGL_PopMatrix();
1160 
1161             DGL_Disable(DGL_TEXTURE_2D);
1162         }
1163         else
1164         {
1165             // Nope just a solid color.
1166             DGL_SetNoMaterial();
1167             DGL_Color4f(bgColor[0], bgColor[1], bgColor[2], cfg.common.automapOpacity * alpha);
1168             DGL_DrawRectf2(0, 0, geom.size.width, geom.size.height);
1169         }
1170 
1171 #if __JDOOM64__
1172         // jd64 > Demon keys
1173         // If drawn in HUD we don't need them visible in the map too.
1174         if (!cfg.hudShown[HUD_INVENTORY])
1175         {
1176             static inventoryitemtype_t const items[3] = {
1177                 IIT_DEMONKEY1, IIT_DEMONKEY2, IIT_DEMONKEY3
1178             };
1179 
1180             dint player = self().player();
1181             dint num = 0;
1182             for (inventoryitemtype_t const &item : items)
1183             {
1184                 if (P_InventoryCount(player, item) > 0)
1185                     num += 1;
1186             }
1187 
1188             if (num > 0)
1189             {
1190                 static dint const invItemSprites[NUM_INVENTORYITEM_TYPES] = {
1191                     SPR_ART1, SPR_ART2, SPR_ART3
1192                 };
1193 
1194                 dfloat const iconOpacity = de::clamp(.0f, alpha, .5f);
1195                 dfloat const spacing     = geom.size.height / num;
1196 
1197                 spriteinfo_t sprInfo;
1198                 dfloat y = 0;
1199                 for (dint i = 0; i < 3; ++i)
1200                 {
1201                     if (P_InventoryCount(player, items[i]))
1202                     {
1203                         R_GetSpriteInfo(invItemSprites[i], 0, &sprInfo);
1204                         DGL_SetPSprite(sprInfo.material);
1205                         DGL_Enable(DGL_TEXTURE_2D);
1206 
1207                         dfloat const scale = geom.size.height / (sprInfo.geometry.size.height * num);
1208                         dfloat const x     = geom.size.width - sprInfo.geometry.size.width * scale;
1209                         dfloat const w     = sprInfo.geometry.size.width;
1210                         dfloat const h     = sprInfo.geometry.size.height;
1211 
1212                         DGL_Color4f(1, 1, 1, iconOpacity);
1213                         DGL_Begin(DGL_QUADS);
1214                             DGL_TexCoord2f(0, 0, 0);
1215                             DGL_Vertex2f(x, y);
1216 
1217                             DGL_TexCoord2f(0, sprInfo.texCoord[0], 0);
1218                             DGL_Vertex2f(x + w * scale, y);
1219 
1220                             DGL_TexCoord2f(0, sprInfo.texCoord[0], sprInfo.texCoord[1]);
1221                             DGL_Vertex2f(x + w * scale, y + h * scale);
1222 
1223                             DGL_TexCoord2f(0, 0, sprInfo.texCoord[1]);
1224                             DGL_Vertex2f(x, y + h * scale);
1225                         DGL_End();
1226 
1227                         DGL_Disable(DGL_TEXTURE_2D);
1228 
1229                         y += spacing;
1230                     }
1231                 }
1232             }
1233         }
1234         // < d64tc
1235 #endif
1236 
1237         // Setup the scissor clipper.
1238         /// @todo Do this in the UI module.
1239         dint const border = .5f + UIAUTOMAP_BORDER * aspectScale;
1240         RectRaw clipRegion; Rect_Raw(&self().geometry(), &clipRegion);
1241         clipRegion.origin.x += border;
1242         clipRegion.origin.y += border;
1243         clipRegion.size.width  -= 2 * border;
1244         clipRegion.size.height -= 2 * border;
1245 
1246         DGL_SetScissor(&clipRegion);
1247     }
1248 
1249     /**
1250      * Restores the previous GL draw state
1251      */
1252     void restoreGLStateFromMap()
1253     {
1254         DGL_PopState();
1255     }
1256 
1257     void drawAllVertexes()
1258     {
1259         if (!(flags & AWF_SHOW_VERTEXES))
1260             return;
1261 
1262         DGL_Color4f(.2f, .5f, 1, uiRendState->pageAlpha);
1263 
1264         DGL_Enable(DGL_POINT_SMOOTH);
1265         dfloat const oldPointSize = DGL_GetFloat(DGL_POINT_SIZE);
1266         DGL_SetFloat(DGL_POINT_SIZE, 4 * aspectScale);
1267 
1268         dfloat v[2];
1269         DGL_Begin(DGL_POINTS);
1270         for (dint i = 0; i < numvertexes; ++i)
1271         {
1272             P_GetFloatv(DMU_VERTEX, i, DMU_XY, v);
1273             DGL_TexCoord2f(0, v[0], v[1]);
1274             DGL_Vertex2f(v[0], v[1]);
1275         }
1276         DGL_End();
1277 
1278         DGL_SetFloat(DGL_POINT_SIZE, oldPointSize);
1279         DGL_Disable(DGL_POINT_SMOOTH);
1280     }
1281 };
1282 
AutomapWidget(dint player)1283 AutomapWidget::AutomapWidget(dint player)
1284     : HudWidget(function_cast<UpdateGeometryFunc>(AutomapWidget_UpdateGeometry),
1285                 function_cast<DrawFunc>(AutomapWidget_Draw),
1286                 player)
1287     , d(new Impl(this))
1288 {
1289     d->style = ST_AutomapStyle();
1290 }
1291 
~AutomapWidget()1292 AutomapWidget::~AutomapWidget()
1293 {}
1294 
cameraFollowPlayer() const1295 dint AutomapWidget::cameraFollowPlayer() const
1296 {
1297     return d->followPlayer;
1298 }
1299 
setCameraFollowPlayer(dint newPlayer)1300 void AutomapWidget::setCameraFollowPlayer(dint newPlayer)
1301 {
1302     d->followPlayer = newPlayer;
1303 }
1304 
prepareAssets()1305 void AutomapWidget::prepareAssets()  // static
1306 {
1307     LumpIndex const &lumpIndex = CentralLumpIndex();
1308 
1309     if (autopageLumpNum >= 0)
1310     {
1311         autopageLumpNum = lumpIndex.findLast("autopage.lmp");
1312     }
1313     if (!amMaskTexture)
1314     {
1315         lumpnum_t lumpNum = lumpIndex.findLast("mapmask.lmp");
1316         if (lumpNum >= 0)
1317         {
1318             File1 &file = lumpIndex[lumpNum];
1319             uint8_t const *pixels = file.cache();
1320 
1321             amMaskTexture = DGL_NewTextureWithParams(DGL_LUMINANCE, 256/*width*/, 256/*height*/,
1322                                                      pixels, 0x8, DGL_NEAREST, DGL_LINEAR,
1323                                                      0 /*no anisotropy*/, DGL_REPEAT, DGL_REPEAT);
1324 
1325             file.unlock();
1326         }
1327     }
1328 }
1329 
releaseAssets()1330 void AutomapWidget::releaseAssets()  // static
1331 {
1332     if (!amMaskTexture) return;
1333     DGL_DeleteTextures(1, &amMaskTexture);
1334     amMaskTexture = 0;
1335 }
1336 
reset()1337 void AutomapWidget::reset()
1338 {
1339     d->needBuildLists = true;
1340     d->rotate         = cfg.common.automapRotate;
1341 }
1342 
lineAutomapVisibilityChanged(Line const &)1343 void AutomapWidget::lineAutomapVisibilityChanged(Line const &)
1344 {
1345     d->needBuildLists = true;
1346 }
1347 
style() const1348 AutomapStyle *AutomapWidget::style() const
1349 {
1350     return d->style;
1351 }
1352 
draw(Vector2i const & offset) const1353 void AutomapWidget::draw(Vector2i const &offset) const
1354 {
1355     static int updateWait = 0;  /// @todo should be an instance var of AutomapWidget
1356 
1357     float const alpha = uiRendState->pageAlpha;
1358     player_t *plr = &players[player()];
1359 
1360     if (!plr->plr->inGame) return;
1361 
1362     // Configure render state:
1363     rs.plr = plr;
1364     const Vector2d viewPoint = cameraOrigin();
1365     float angle = cameraAngle();
1366     RectRaw geom; Rect_Raw(&geometry(), &geom);
1367 
1368     // Freeze the lists if the map is fading out from being open, or for debug.
1369 //    if ((++updateWait % 10) && d->needBuildLists && !freezeMapRLs && isOpen())
1370 //    {
1371 //        // Its time to rebuild the automap object display lists.
1372 //        d->buildLists();
1373 //    }
1374 
1375     // Setup for frame.
1376     d->setupGLStateForMap();
1377 
1378     // Configure the modelview matrix so that we can draw geometry for world
1379     // objects using their world-space coordinates directly.
1380     DGL_MatrixMode(DGL_MODELVIEW);
1381     DGL_Translatef(offset.x, offset.y, 0);
1382     DGL_Translatef(geom.size.width / 2, geom.size.height / 2, 0);
1383     DGL_Rotatef(angle, 0, 0, 1);
1384     DGL_Scalef(1, -1, 1); // In the world coordinate space Y+ is up.
1385     DGL_Scalef(d->scaleMTOF, d->scaleMTOF, 1);
1386     DGL_Translatef(-viewPoint.x, -viewPoint.y, 0);
1387 
1388     const float oldLineWidth = DGL_GetFloat(DGL_LINE_WIDTH);
1389     DGL_SetFloat(DGL_LINE_WIDTH, d->pixelRatio * de::clamp(.5f, cfg.common.automapLineWidth, 8.f));
1390 
1391     /*#if _DEBUG
1392     // Draw the rectangle described by the visible bounds.
1393     {
1394         coord_t topLeft[2], bottomRight[2], topRight[2], bottomLeft[2];
1395         d->visibleBounds(topLeft, bottomRight, topRight, bottomLeft);
1396         DGL_Color4f(1, 1, 1, alpha);
1397         DGL_Begin(DGL_LINES);
1398             DGL_Vertex2f(    topLeft[0],     topLeft[1]);
1399             DGL_Vertex2f(   topRight[0],    topRight[1]);
1400             DGL_Vertex2f(   topRight[0],    topRight[1]);
1401             DGL_Vertex2f(bottomRight[0], bottomRight[1]);
1402             DGL_Vertex2f(bottomRight[0], bottomRight[1]);
1403             DGL_Vertex2f( bottomLeft[0],  bottomLeft[1]);
1404             DGL_Vertex2f( bottomLeft[0],  bottomLeft[1]);
1405             DGL_Vertex2f(    topLeft[0],     topLeft[1]);
1406         DGL_End();
1407     }
1408 #endif*/
1409 
1410     if (amMaskTexture)
1411     {
1412         dint const border = .5f + UIAUTOMAP_BORDER * aspectScale;
1413 
1414         DGL_SetInteger(DGL_ACTIVE_TEXTURE, 0);
1415         DGL_MatrixMode(DGL_TEXTURE);
1416         DGL_LoadIdentity();
1417 
1418         DGL_PushMatrix();
1419         DGL_Scalef(1.f / (geom.size.width  - border*2),
1420                    1.f / (geom.size.height - border*2), 1);
1421         DGL_Translatef(geom.size.width  /2 - border,
1422                        geom.size.height /2 - border, 0);
1423         DGL_Rotatef(-angle, 0, 0, 1);
1424         DGL_Scalef(d->scaleMTOF, d->scaleMTOF, 1);
1425         DGL_Translatef(-viewPoint.x, -viewPoint.y, 0);
1426     }
1427 
1428     // Draw static map geometry.
1429     for (dint i = NUM_MAP_OBJECTLISTS-1; i >= 0; i--)
1430     {
1431         automapcfg_lineinfo_t const &info = d->style->lineInfo(i);
1432         DGL_Color4f(info.rgba[0], info.rgba[1], info.rgba[2], info.rgba[3] * cfg.common.automapLineAlpha * alpha);
1433         d->drawAllLines(i);
1434     }
1435 
1436     // Draw dynamic map geometry.
1437     d->drawAllLines_xg();
1438     d->drawAllPolyobs();
1439 
1440     // Restore the previous state.
1441     DGL_BlendMode(BM_NORMAL);
1442     DGL_Color4f(1, 1, 1, 1);
1443 
1444     d->drawAllVertexes();
1445     d->drawAllThings();
1446 
1447     // Sharp player markers.
1448     DGL_SetFloat(DGL_LINE_WIDTH, 1.f);
1449     d->drawAllPlayerMarkers();
1450     DGL_SetFloat(DGL_LINE_WIDTH, oldLineWidth);
1451 
1452     if (amMaskTexture)
1453     {
1454         DGL_Disable(DGL_TEXTURE0);
1455         DGL_MatrixMode(DGL_TEXTURE);
1456         DGL_PopMatrix();
1457     }
1458 
1459     // Draw glows?
1460     if (cfg.common.automapShowDoors)
1461     {
1462         d->drawAllLines(-1, true /*only glows*/);
1463     }
1464 
1465     d->restoreGLStateFromMap();
1466 
1467     d->drawAllPoints(aspectScale);
1468 
1469     // Return to the normal GL state.
1470     DGL_MatrixMode(DGL_MODELVIEW);
1471     DGL_PopMatrix();
1472 }
1473 
open(bool yes,bool instantly)1474 void AutomapWidget::open(bool yes, bool instantly)
1475 {
1476     if (G_GameState() != GS_MAP && yes) return;
1477 
1478     if (d->open == yes) return;  // No change.
1479 
1480     d->targetOpacity = (yes? 1.f : 0.f);
1481     if (instantly)
1482     {
1483         d->opacity = d->oldOpacity = d->targetOpacity;
1484     }
1485     else
1486     {
1487         // Reset the timer.
1488         d->oldOpacity   = d->opacity;
1489         d->opacityTimer = 0.f;
1490     }
1491 
1492     d->open = yes;
1493     if (d->open)
1494     {
1495         if (mobj_t *mob = followMobj())
1496         {
1497             // The map's target player is available.
1498             if (d->follow || cfg.common.automapPanResetOnOpen)
1499             {
1500                 coord_t origin[3]; Mobj_OriginSmoothed(mob, origin);
1501                 setCameraOrigin(Vector2d(origin));
1502             }
1503 
1504             if (!d->follow && cfg.common.automapPanResetOnOpen)
1505             {
1506                 /* $unifiedangles */
1507                 setCameraAngle((d->rotate ? (mob->angle - ANGLE_90) / (float) ANGLE_MAX * 360 : 0));
1508             }
1509         }
1510         else
1511         {
1512             // Set viewer target to the center of the map.
1513             coord_t aabb[4];
1514             pvisibleBounds(&aabb[BOXLEFT], &aabb[BOXRIGHT], &aabb[BOXBOTTOM], &aabb[BOXTOP]);
1515             setCameraOrigin(Vector2d(aabb[BOXRIGHT] - aabb[BOXLEFT], aabb[BOXTOP] - aabb[BOXBOTTOM]) / 2);
1516             setCameraAngle(0);
1517         }
1518     }
1519 
1520     if (d->open)
1521     {
1522         DD_Execute(true, "activatebcontext map");
1523         if (!d->follow)
1524             DD_Execute(true, "activatebcontext map-freepan");
1525     }
1526     else
1527     {
1528         DD_Execute(true, "deactivatebcontext map");
1529         DD_Execute(true, "deactivatebcontext map-freepan");
1530     }
1531 }
1532 
tick(timespan_t elapsed)1533 void AutomapWidget::tick(timespan_t elapsed)
1534 {
1535     dint const plrNum = player();
1536     mobj_t *followMob = followMobj();
1537 
1538     // Check the state of the controls. Done here so that offsets don't accumulate
1539     // unnecessarily, as they would, if left unread.
1540     dfloat panX[2]; P_GetControlState(plrNum, CTL_MAP_PAN_X, &panX[0], &panX[1]);
1541     dfloat panY[2]; P_GetControlState(plrNum, CTL_MAP_PAN_Y, &panY[0], &panY[1]);
1542 
1543     if (G_GameState() != GS_MAP) return;
1544 
1545     // Move towards the target alpha level for the automap.
1546     if (cfg.common.automapOpenSeconds >= .001f)
1547     {
1548         d->opacityTimer += 1.f / cfg.common.automapOpenSeconds * elapsed;
1549     }
1550     else
1551     {
1552         d->opacityTimer = 1.f; // Instant.
1553     }
1554 
1555     if (d->opacityTimer >= 1)
1556     {
1557         d->opacity = d->targetOpacity;
1558     }
1559     else
1560     {
1561         d->opacity = de::lerp(d->oldOpacity, d->targetOpacity, d->opacityTimer);
1562     }
1563 
1564     // Unless open we do nothing further.
1565     if (!isOpen()) return;
1566 
1567     // Map view zoom contol.
1568     dfloat zoomSpeed = 1 + (2 * cfg.common.automapZoomSpeed) * elapsed * TICRATE;
1569     if (players[plrNum].brain.speed)
1570     {
1571         zoomSpeed *= 1.5f;
1572     }
1573 
1574     dfloat zoomVel;
1575     P_GetControlState(plrNum, CTL_MAP_ZOOM, &zoomVel, nullptr); // ignores rel offset -jk
1576     if (zoomVel > 0) // zoom in
1577     {
1578         setScale(d->viewScale * zoomSpeed);
1579     }
1580     else if (zoomVel < 0) // zoom out
1581     {
1582         setScale(d->viewScale / zoomSpeed);
1583     }
1584 
1585     if (!d->follow || !followMob)
1586     {
1587         // Camera panning mode.
1588         dfloat panUnitsPerSecond;
1589 
1590         // DOOM.EXE pans the automap at 140 fixed pixels per second (VGA: 200 pixels tall).
1591         /// @todo This needs resolution-independent units. (The "frame" units are screen pixels.)
1592         panUnitsPerSecond = de::max(8.f,
1593                                     frameToMap(140 * Rect_Height(&geometry()) / 200.f) *
1594                                         (2 * cfg.common.automapPanSpeed));
1595 
1596         /// @todo Fix sensitivity for relative axes.
1597         Vector2d const delta = rotate(Vector2d(panX[0], panY[0]) * panUnitsPerSecond * elapsed +
1598                                           Vector2d(panX[1], panY[1]),
1599                                       degreeToRadian(d->angle));
1600         moveCameraOrigin(delta, true /*instant move*/);
1601     }
1602     else
1603     {
1604         // Camera follow mode.
1605         dfloat const angle = (d->rotate ? (followMob->angle - ANGLE_90) / (dfloat) ANGLE_MAX * 360
1606                                         : 0); /* $unifiedangles */
1607         coord_t origin[3]; Mobj_OriginSmoothed(followMob, origin);
1608         setCameraOrigin(Vector2d(origin));
1609         setCameraAngle(angle);
1610     }
1611 
1612     if (d->needViewScaleUpdate)
1613         d->updateViewScale();
1614 
1615     // Map viewer location.
1616     d->viewTimer += dfloat(.4 * elapsed * TICRATE);
1617     if (d->viewTimer >= 1)
1618     {
1619         d->view = d->targetView;
1620     }
1621     else
1622     {
1623         d->view = de::lerp(d->oldView, d->targetView, d->viewTimer);
1624     }
1625 
1626     // Move the parallax layer.
1627 //    d->viewPL = d->view / 4000;
1628 
1629     // Map view scale (zoom).
1630     d->viewScaleTimer += dfloat(.4 * elapsed * TICRATE);
1631     if (d->viewScaleTimer >= 1)
1632     {
1633         d->viewScale = d->targetViewScale;
1634     }
1635     else
1636     {
1637         d->viewScale = de::lerp(d->oldViewScale, d->targetViewScale, d->viewScaleTimer);
1638     }
1639 
1640     // Map view rotation.
1641     d->angleTimer += dfloat(.4 * elapsed * TICRATE);
1642     if (d->angleTimer >= 1)
1643     {
1644         d->angle = d->targetAngle;
1645     }
1646     else
1647     {
1648         dfloat startAngle = d->oldAngle;
1649         dfloat endAngle   = d->targetAngle;
1650 
1651         dfloat diff;
1652         if (endAngle > startAngle)
1653         {
1654             diff = endAngle - startAngle;
1655             if (diff > 180)
1656                 endAngle = startAngle - (360 - diff);
1657         }
1658         else
1659         {
1660             diff = startAngle - endAngle;
1661             if (diff > 180)
1662                 endAngle = startAngle + (360 - diff);
1663         }
1664 
1665         d->angle = de::lerp(startAngle, endAngle, d->angleTimer);
1666         if (d->angle < 0)        d->angle += 360;
1667         else if (d->angle > 360) d->angle -= 360;
1668     }
1669 
1670     //
1671     // Activate the new scale, position etc.
1672     //
1673 
1674     // Scaling multipliers.
1675     d->scaleMTOF = d->viewScale;
1676     d->scaleFTOM = 1.0f / d->scaleMTOF;
1677 
1678     // Calculate the coordinates of the rotated view window.
1679     // Determine fixed to screen space scaling factors.
1680     dint const border         = .5f + UIAUTOMAP_BORDER * aspectScale;
1681 
1682     ddouble const ang         = degreeToRadian(d->angle);
1683     Vector2d const origin     = cameraOrigin();
1684 
1685     auto const dimensions     = Vector2d(frameToMap(Rect_Width (&geometry())),
1686                                          frameToMap(Rect_Height(&geometry()))) / 2;
1687 
1688     auto const viewDimensions = Vector2d(frameToMap(Rect_Width (&geometry()) - border * 2),
1689                                          frameToMap(Rect_Height(&geometry()) - border * 2)) / 2;
1690 
1691     d->topLeft     = origin + rotate(Vector2d(-viewDimensions.x,  viewDimensions.y), ang);
1692     d->bottomRight = origin + rotate(Vector2d( viewDimensions.x, -viewDimensions.y), ang);
1693     d->bottomLeft  = origin + rotate(-viewDimensions, ang);
1694     d->topRight    = origin + rotate( viewDimensions, ang);
1695 
1696 
1697     // Calculate the in-view AABB (rotation aware).
1698     initAABB (d->viewAABB, rotate(-dimensions, ang));
1699     addToAABB(d->viewAABB, rotate(Vector2d( dimensions.x, -dimensions.y), ang));
1700     addToAABB(d->viewAABB, rotate(Vector2d(-dimensions.x,  dimensions.y), ang));
1701     addToAABB(d->viewAABB, rotate( dimensions, ang));
1702 
1703     // Translate to the camera origin.
1704     d->viewAABB[BOXLEFT  ] += origin.x;
1705     d->viewAABB[BOXRIGHT ] += origin.x;
1706     d->viewAABB[BOXTOP   ] += origin.y;
1707     d->viewAABB[BOXBOTTOM] += origin.y;
1708 }
1709 
mapToFrame(dfloat coord) const1710 dfloat AutomapWidget::mapToFrame(dfloat coord) const
1711 {
1712     return coord * d->scaleMTOF;
1713 }
1714 
frameToMap(dfloat coord) const1715 dfloat AutomapWidget::frameToMap(dfloat coord) const
1716 {
1717     return coord * d->scaleFTOM;
1718 }
1719 
updateGeometry()1720 void AutomapWidget::updateGeometry()
1721 {
1722     // Determine whether the available space has changed and thus whether
1723     // the position and/or size of the automap must therefore change too.
1724     RectRaw newGeom;
1725     R_ViewWindowGeometry(player(), &newGeom);
1726 
1727     if (newGeom.origin.x != Rect_X(&geometry()) ||
1728         newGeom.origin.y != Rect_Y(&geometry()) ||
1729         newGeom.size.width != Rect_Width(&geometry()) ||
1730         newGeom.size.height != Rect_Height(&geometry()))
1731     {
1732         Rect_SetXY(&geometry(), newGeom.origin.x, newGeom.origin.y);
1733         Rect_SetWidthHeight(&geometry(), newGeom.size.width, newGeom.size.height);
1734 
1735         // Now the screen dimensions have changed we have to update scaling
1736         // factors accordingly.
1737         d->needViewScaleUpdate = true;
1738     }
1739 }
1740 
cameraAngle() const1741 dfloat AutomapWidget::cameraAngle() const
1742 {
1743     return d->angle;
1744 }
1745 
setCameraAngle(dfloat newAngle)1746 void AutomapWidget::setCameraAngle(dfloat newAngle)
1747 {
1748     // Already at this target?
1749     newAngle = de::clamp(0.f, newAngle, 359.9999f);
1750     if (newAngle == d->targetAngle) return;
1751 
1752     // Begin animating toward the new target.
1753     d->oldAngle    = d->angle;
1754     d->targetAngle = newAngle;
1755     d->angleTimer  = 0;
1756 }
1757 
cameraOrigin() const1758 Vector2d AutomapWidget::cameraOrigin() const
1759 {
1760     return d->view;
1761 }
1762 
setCameraOrigin(Vector2d const & newOrigin,bool instantly)1763 void AutomapWidget::setCameraOrigin(Vector2d const &newOrigin, bool instantly)
1764 {
1765     // Already at this target?
1766     if (newOrigin == d->targetView)
1767         return;
1768 
1769     // If the delta is too great - perform the move instantly.
1770     if (!instantly && d->maxViewPositionDelta > 0)
1771     {
1772         coord_t const dist = de::abs((cameraOrigin() - newOrigin).length());
1773         if (dist > d->maxViewPositionDelta)
1774         {
1775             instantly = true;
1776         }
1777     }
1778 
1779     // Begin animating toward the new target.
1780     if (instantly)
1781     {
1782         d->view = d->oldView = d->targetView = newOrigin;
1783     }
1784     else
1785     {
1786         d->oldView    = d->view;
1787         d->targetView = newOrigin;
1788         d->viewTimer  = 0;
1789     }
1790 }
1791 
scale() const1792 dfloat AutomapWidget::scale() const
1793 {
1794     return d->targetViewScale;
1795 }
1796 
setScale(dfloat newScale)1797 void AutomapWidget::setScale(dfloat newScale)
1798 {
1799     if (d->needViewScaleUpdate)
1800         d->updateViewScale();
1801 
1802     newScale = de::clamp(d->minScaleMTOF, newScale, d->maxScaleMTOF);
1803 
1804     // Already at this target?
1805     if (newScale == d->targetViewScale)
1806         return;
1807 
1808     // Begin animating toward the new target.
1809     d->oldViewScale    = d->viewScale;
1810     d->viewScaleTimer  = 0;
1811     d->targetViewScale = newScale;
1812 }
1813 
isOpen() const1814 bool AutomapWidget::isOpen() const
1815 {
1816     return d->open;
1817 }
1818 
isRevealed() const1819 bool AutomapWidget::isRevealed() const
1820 {
1821     return d->revealed;
1822 }
1823 
reveal(bool yes)1824 void AutomapWidget::reveal(bool yes)
1825 {
1826     if (d->revealed != yes)
1827     {
1828         d->revealed = yes;
1829         d->needBuildLists = true;
1830     }
1831 }
1832 
pvisibleBounds(coord_t * lowX,coord_t * hiX,coord_t * lowY,coord_t * hiY) const1833 void AutomapWidget::pvisibleBounds(coord_t *lowX, coord_t *hiX, coord_t *lowY, coord_t *hiY) const
1834 {
1835     if (lowX) *lowX = d->viewAABB[BOXLEFT];
1836     if (hiX)  *hiX  = d->viewAABB[BOXRIGHT];
1837     if (lowY) *lowY = d->viewAABB[BOXBOTTOM];
1838     if (hiY)  *hiY  = d->viewAABB[BOXTOP];
1839 }
1840 
pointCount() const1841 dint AutomapWidget::pointCount() const
1842 {
1843     return d->points.count();
1844 }
1845 
addPoint(Vector3d const & origin)1846 dint AutomapWidget::addPoint(Vector3d const &origin)
1847 {
1848     d->points << new MarkedPoint(origin);
1849     dint pointNum = d->points.count() - 1;  // base 0.
1850     if (player() >= 0)
1851     {
1852         String msg = String(AMSTR_MARKEDSPOT) + " " + String::number(pointNum);
1853         P_SetMessageWithFlags(&players[player()], msg.toUtf8().constData(), LMF_NO_HIDE);
1854     }
1855     return pointNum;
1856 }
1857 
hasPoint(dint index) const1858 bool AutomapWidget::hasPoint(dint index) const
1859 {
1860     return index >= 0 && index < d->points.count();
1861 }
1862 
point(dint index) const1863 AutomapWidget::MarkedPoint &AutomapWidget::point(dint index) const
1864 {
1865     if (hasPoint(index)) return *d->points.at(index);
1866     /// @throw MissingPointError  Invalid point reference.
1867     throw MissingPointError("AutomapWidget::point", "Unknown point #" + String::number(index));
1868 }
1869 
forAllPoints(std::function<LoopResult (MarkedPoint &)> func) const1870 LoopResult AutomapWidget::forAllPoints(std::function<LoopResult (MarkedPoint &)> func) const
1871 {
1872     for (MarkedPoint *point : d->points)
1873     {
1874         if (auto result = func(*point)) return result;
1875     }
1876     return LoopContinue;
1877 }
1878 
clearAllPoints(bool silent)1879 void AutomapWidget::clearAllPoints(bool silent)
1880 {
1881     d->clearPoints();
1882 
1883     if (!silent && player() >= 0)
1884     {
1885         P_SetMessageWithFlags(&players[player()], AMSTR_MARKSCLEARED, LMF_NO_HIDE);
1886     }
1887 }
1888 
cameraZoomMode() const1889 bool AutomapWidget::cameraZoomMode() const
1890 {
1891     return d->forceMaxScale;
1892 }
1893 
setCameraZoomMode(bool yes)1894 void AutomapWidget::setCameraZoomMode(bool yes)
1895 {
1896     LOG_AS("AutomapWidget");
1897     bool const oldZoomMax = d->forceMaxScale;
1898 
1899     if (d->needViewScaleUpdate)
1900     {
1901         d->updateViewScale();
1902     }
1903 
1904     // When switching to max scale mode, store the old scale.
1905     if (!d->forceMaxScale)
1906     {
1907         d->priorToMaxScale = d->viewScale;
1908     }
1909 
1910     d->forceMaxScale = yes;
1911     setScale((d->forceMaxScale ? 0 : d->priorToMaxScale));
1912     if (oldZoomMax != d->forceMaxScale)
1913     {
1914         LOGDEV_XVERBOSE("Maximum zoom: ", DENG2_BOOL_YESNO(cameraZoomMode()));
1915     }
1916 }
1917 
cameraFollowMode() const1918 bool AutomapWidget::cameraFollowMode() const
1919 {
1920     return d->follow;
1921 }
1922 
setCameraFollowMode(bool yes)1923 void AutomapWidget::setCameraFollowMode(bool yes)
1924 {
1925     if (d->follow != yes)
1926     {
1927         d->follow = yes;
1928         if (d->open)
1929         {
1930             DD_Executef(true, "%sactivatebcontext map-freepan", d->follow? "de" : "");
1931             P_SetMessageWithFlags(&players[player()], (d->follow ? AMSTR_FOLLOWON : AMSTR_FOLLOWOFF), LMF_NO_HIDE);
1932         }
1933     }
1934 }
1935 
followMobj() const1936 mobj_t *AutomapWidget::followMobj() const
1937 {
1938     if (d->followPlayer >= 0)
1939     {
1940         player_t *player = &players[d->followPlayer];
1941         return player->plr->inGame ? player->plr->mo : nullptr;
1942     }
1943     return nullptr;
1944 }
1945 
cameraRotationMode() const1946 bool AutomapWidget::cameraRotationMode() const
1947 {
1948     return d->rotate;
1949 }
1950 
setCameraRotationMode(bool yes)1951 void AutomapWidget::setCameraRotationMode(bool yes)
1952 {
1953     d->rotate = yes;
1954 }
1955 
opacityEX() const1956 dfloat AutomapWidget::opacityEX() const
1957 {
1958     return d->opacity;
1959 }
1960 
setOpacityEX(dfloat newOpacity)1961 void AutomapWidget::setOpacityEX(dfloat newOpacity)
1962 {
1963     newOpacity = de::clamp(0.f, newOpacity, 1.f);
1964     if (newOpacity != d->targetOpacity)
1965     {
1966         // Start animating toward the new target.
1967         d->oldOpacity    = d->opacity;
1968         d->targetOpacity = newOpacity;
1969         d->opacityTimer  = 0;
1970     }
1971 }
1972 
flags() const1973 dint AutomapWidget::flags() const
1974 {
1975     return d->flags;
1976 }
1977 
setFlags(dint newFlags)1978 void AutomapWidget::setFlags(dint newFlags)
1979 {
1980     if (d->flags != newFlags)
1981     {
1982         d->flags = newFlags;
1983         // We will need to rebuild one or more display lists.
1984         d->needBuildLists = true;
1985     }
1986 }
1987 
setMapBounds(coord_t lowX,coord_t hiX,coord_t lowY,coord_t hiY)1988 void AutomapWidget::setMapBounds(coord_t lowX, coord_t hiX, coord_t lowY, coord_t hiY)
1989 {
1990     d->bounds[BOXLEFT  ] = lowX;
1991     d->bounds[BOXTOP   ] = hiY;
1992     d->bounds[BOXRIGHT ] = hiX;
1993     d->bounds[BOXBOTTOM] = lowY;
1994 
1995     d->updateViewScale();
1996 
1997     setScale(d->minScaleMTOF * 2.4f);  // Default view scale factor.
1998 }
1999 
consoleRegister()2000 void AutomapWidget::consoleRegister()  // static
2001 {
2002     C_VAR_FLOAT("map-opacity",              &cfg.common.automapOpacity,        0, 0, 1);
2003     C_VAR_BYTE ("map-neverobscure",         &cfg.common.automapNeverObscure,   0, 0, 1);
2004 #if __JDOOM__ || __JHERETIC__ || __JDOOM64__
2005     C_VAR_BYTE ("map-babykeys",             &cfg.common.automapBabyKeys,       0, 0, 1);
2006 #endif
2007     C_VAR_FLOAT("map-background-r",         &cfg.common.automapBack[0],        0, 0, 1);
2008     C_VAR_FLOAT("map-background-g",         &cfg.common.automapBack[1],        0, 0, 1);
2009     C_VAR_FLOAT("map-background-b",         &cfg.common.automapBack[2],        0, 0, 1);
2010     C_VAR_INT  ("map-customcolors",         &cfg.common.automapCustomColors,   0, 0, 1);
2011     C_VAR_FLOAT( "map-line-opacity",        &cfg.common.automapLineAlpha,      0, 0, 1);
2012     C_VAR_FLOAT("map-line-width",           &cfg.common.automapLineWidth,      0, .5f, 8);
2013     C_VAR_FLOAT("map-mobj-r",               &cfg.common.automapMobj[0],        0, 0, 1);
2014     C_VAR_FLOAT("map-mobj-g",               &cfg.common.automapMobj[1],        0, 0, 1);
2015     C_VAR_FLOAT("map-mobj-b",               &cfg.common.automapMobj[2],        0, 0, 1);
2016     C_VAR_FLOAT("map-wall-r",               &cfg.common.automapL1[0],          0, 0, 1);
2017     C_VAR_FLOAT("map-wall-g",               &cfg.common.automapL1[1],          0, 0, 1);
2018     C_VAR_FLOAT("map-wall-b",               &cfg.common.automapL1[2],          0, 0, 1);
2019     C_VAR_FLOAT("map-wall-unseen-r",        &cfg.common.automapL0[0],          0, 0, 1);
2020     C_VAR_FLOAT("map-wall-unseen-g",        &cfg.common.automapL0[1],          0, 0, 1);
2021     C_VAR_FLOAT("map-wall-unseen-b",        &cfg.common.automapL0[2],          0, 0, 1);
2022     C_VAR_FLOAT("map-wall-floorchange-r",   &cfg.common.automapL2[0],          0, 0, 1);
2023     C_VAR_FLOAT("map-wall-floorchange-g",   &cfg.common.automapL2[1],          0, 0, 1);
2024     C_VAR_FLOAT("map-wall-floorchange-b",   &cfg.common.automapL2[2],          0, 0, 1);
2025     C_VAR_FLOAT("map-wall-ceilingchange-r", &cfg.common.automapL3[0],          0, 0, 1);
2026     C_VAR_FLOAT("map-wall-ceilingchange-g", &cfg.common.automapL3[1],          0, 0, 1);
2027     C_VAR_FLOAT("map-wall-ceilingchange-b", &cfg.common.automapL3[2],          0, 0, 1);
2028     C_VAR_BYTE ("map-door-colors",          &cfg.common.automapShowDoors,      0, 0, 1);
2029     C_VAR_FLOAT("map-door-glow",            &cfg.common.automapDoorGlow,       0, 0, 200);
2030     C_VAR_INT  ("map-huddisplay",           &cfg.common.automapHudDisplay,     0, 0, 2);
2031     C_VAR_FLOAT("map-pan-speed",            &cfg.common.automapPanSpeed,       0, 0, 1);
2032     C_VAR_BYTE ("map-pan-resetonopen",      &cfg.common.automapPanResetOnOpen, 0, 0, 1);
2033     C_VAR_BYTE ("map-rotate",               &cfg.common.automapRotate,         0, 0, 1);
2034     C_VAR_FLOAT("map-zoom-speed",           &cfg.common.automapZoomSpeed,      0, 0, 1);
2035     C_VAR_FLOAT("map-open-timer",           &cfg.common.automapOpenSeconds,    CVF_NO_MAX, 0, 0);
2036     C_VAR_BYTE ("map-title-position",       &cfg.common.automapTitleAtBottom,  0, 0, 1);
2037     C_VAR_BYTE ("rend-dev-freeze-map",      &freezeMapRLs,                     CVF_NO_ARCHIVE, 0, 1);
2038 
2039     // Aliases for old names:
2040     C_VAR_FLOAT("map-alpha-lines",          &cfg.common.automapLineAlpha,      0, 0, 1);
2041 }
2042 
G_SetAutomapRotateMode(byte enableRotate)2043 void G_SetAutomapRotateMode(byte enableRotate)
2044 {
2045     cfg.common.automapRotate = enableRotate; // Note: this sets the global default.
2046 
2047     for (int i = 0; i < MAXPLAYERS; ++i)
2048     {
2049         ST_SetAutomapCameraRotation(i, cfg.common.automapRotate);
2050         if (players[i].plr->inGame)
2051         {
2052             P_SetMessageWithFlags(&players[i],
2053                                   (cfg.common.automapRotate ? AMSTR_ROTATEON : AMSTR_ROTATEOFF),
2054                                   LMF_NO_HIDE);
2055         }
2056     }
2057 }
2058