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