1 /** @file rend_main.cpp  World Map Renderer.
2  *
3  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2006-2015 Daniel Swanson <danij@dengine.net>
5  * @authors Copyright © 2006 Jamie Jones <jamie_jones_au@yahoo.com.au>
6  *
7  * @par License
8  * GPL: http://www.gnu.org/licenses/gpl.html
9  *
10  * <small>This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by the
12  * Free Software Foundation; either version 2 of the License, or (at your
13  * option) any later version. This program is distributed in the hope that it
14  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
15  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
16  * Public License for more details. You should have received a copy of the GNU
17  * General Public License along with this program; if not, write to the Free
18  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19  * 02110-1301 USA</small>
20  */
21 
22 #include "de_platform.h"
23 #include "render/rend_main.h"
24 
25 #include "MaterialVariantSpec"
26 #include "ClientTexture"
27 #include "Face"
28 
29 #include "world/map.h"
30 #include "world/blockmap.h"
31 #include "world/lineowner.h"
32 #include "world/p_object.h"
33 #include "world/p_players.h"
34 #include "world/sky.h"
35 #include "world/thinkers.h"
36 #include "world/surface.h"
37 #include "BspLeaf"
38 #include "Contact"
39 #include "ConvexSubspace"
40 //#include "Hand"
41 #include "client/clientsubsector.h"
42 #include "client/clskyplane.h"
43 //#include "BiasIllum"
44 //#include "HueCircleVisual"
45 #include "LightDecoration"
46 #include "Lumobj"
47 //#include "Shard"
48 #include "SkyFixEdge"
49 #include "TriangleStripBuilder"
50 #include "WallEdge"
51 
52 #include "gl/gl_main.h"
53 #include "gl/gl_tex.h"  // pointlight_analysis_t
54 #include "gl/gl_texmanager.h"
55 #include "gl/sys_opengl.h"
56 
57 #include "api_fontrender.h"
58 #include "misc/r_util.h"
59 #include "render/fx/bloom.h"
60 #include "render/fx/vignette.h"
61 #include "render/fx/lensflares.h"
62 #include "render/angleclipper.h"
63 #include "render/blockmapvisual.h"
64 #include "render/billboard.h"
65 #include "render/cameralensfx.h"
66 #include "render/modelrenderer.h"
67 #include "render/r_main.h"
68 #include "render/r_things.h"
69 #include "render/rendersystem.h"
70 #include "render/rend_fakeradio.h"
71 #include "render/rend_halo.h"
72 #include "render/rend_particle.h"
73 #include "render/rendpoly.h"
74 #include "render/skydrawable.h"
75 #include "render/store.h"
76 #include "render/viewports.h"
77 #include "render/vissprite.h"
78 #include "render/vr.h"
79 
80 #include "ui/editors/rendererappearanceeditor.h"
81 #include "ui/editors/modelasseteditor.h"
82 #include "ui/ui_main.h"
83 #include "ui/postprocessing.h"
84 //#include "ui/editors/edit_bias.h"
85 
86 #include "sys_system.h"
87 #include "dd_main.h"
88 #include "clientapp.h"
89 #include "network/net_main.h"
90 
91 #include <doomsday/console/cmd.h>
92 #include <doomsday/console/var.h>
93 #include <doomsday/defs/sprite.h>
94 #include <doomsday/res/TextureManifest>
95 #include <doomsday/world/Materials>
96 #include <doomsday/BspNode>
97 #include <de/concurrency.h>
98 #include <de/timer.h>
99 #include <de/texgamma.h>
100 #include <de/vector1.h>
101 #include <de/GLInfo>
102 #include <de/GLState>
103 #include <QtAlgorithms>
104 #include <QBitArray>
105 #include <cmath>
106 #include <cstdio>
107 #include <cstdlib>
108 
109 using namespace de;
110 using namespace world;
111 
112 /**
113  * POD structure used to transport vertex data refs as a logical unit.
114  * @note The geometric data itself is not owned!
115  */
116 struct Geometry
117 {
118     Vector3f *pos;
119     Vector4f *color;
120     Vector2f *tex;
121     Vector2f *tex2;
122 };
123 
124 /**
125  * POD structure used to describe texture modulation data.
126  */
127 struct TexModulationData
128 {
129     DGLuint texture = 0;
130     Vector3f color;
131     Vector2f topLeft;
132     Vector2f bottomRight;
133 };
134 
135 // Surface (tangent-space) Vector Flags.
136 #define SVF_TANGENT             0x01
137 #define SVF_BITANGENT           0x02
138 #define SVF_NORMAL              0x04
139 
140 /**
141  * @defgroup soundOriginFlags  Sound Origin Flags
142  * Flags for use with the sound origin debug display.
143  * @ingroup flags
144  */
145 ///@{
146 #define SOF_SECTOR              0x01
147 #define SOF_PLANE               0x02
148 #define SOF_SIDE                0x04
149 ///@}
150 
151 void Rend_DrawBBox(Vector3d const &pos, coord_t w, coord_t l, coord_t h, dfloat a,
152     dfloat const color[3], dfloat alpha, dfloat br, bool alignToBase = true);
153 
154 void Rend_DrawArrow(Vector3d const &pos, dfloat a, dfloat s, dfloat const color3f[3], dfloat alpha);
155 
156 D_CMD(OpenRendererAppearanceEditor);
157 D_CMD(LowRes);
158 D_CMD(MipMap);
159 D_CMD(TexReset);
160 D_CMD(CubeShot);
161 
162 #if 0
163 dint useBias;  ///< Shadow Bias enabled? cvar
164 #endif
165 
166 FogParams fogParams;
167 dfloat fieldOfView = 95.0f;
168 dbyte smoothTexAnim = true;
169 
170 dint renderTextures = true;
171 #if defined (DENG_OPENGL)
172 dint renderWireframe;
173 #endif
174 
175 dint dynlightBlend;
176 
177 Vector3f torchColor(1, 1, 1);
178 
179 dint useShinySurfaces = true;
180 
181 dint useDynLights = true;
182 dfloat dynlightFactor = .7f;
183 dfloat dynlightFogBright = .15f;
184 
185 dint useGlowOnWalls = true;
186 dfloat glowFactor = .8f;
187 dfloat glowHeightFactor = 3;  ///< Glow height as a multiplier.
188 dint glowHeightMax = 100;     ///< 100 is the default (0-1024).
189 
190 dint useShadows = true;
191 dfloat shadowFactor = 1.2f;
192 dint shadowMaxRadius = 80;
193 dint shadowMaxDistance = 1000;
194 
195 dbyte useLightDecorations = true;  ///< cvar
196 
197 dfloat detailFactor = .5f;
198 dfloat detailScale = 4;
199 
200 dint mipmapping = 5;
201 dint filterUI   = 1;
202 dint texQuality = TEXQ_BEST;
203 
204 dint ratioLimit;      ///< Zero if none.
205 dd_bool fillOutlines = true;
206 dint useSmartFilter;  ///< Smart filter mode (cvar: 1=hq2x)
207 dint filterSprites = true;
208 dint texMagMode = 1;  ///< Linear.
209 dint texAniso = -1;   ///< Use best.
210 
211 //dd_bool noHighResTex;
212 //dd_bool noHighResPatches;
213 //dd_bool highResWithPWAD;
214 dbyte loadExtAlways;  ///< Always check for extres (cvar)
215 
216 dfloat texGamma;
217 
218 dint glmode[6] = // Indexed by 'mipmapping'.
219 {
220     GL_NEAREST,
221     GL_LINEAR,
222     GL_NEAREST_MIPMAP_NEAREST,
223     GL_LINEAR_MIPMAP_NEAREST,
224     GL_NEAREST_MIPMAP_LINEAR,
225     GL_LINEAR_MIPMAP_LINEAR
226 };
227 
228 Vector3d vOrigin;
229 dfloat vang, vpitch;
230 dfloat viewsidex, viewsidey;
231 
232 // Helper for overriding the normal view matrices.
233 struct FixedView
234 {
235     Matrix4f projectionMatrix;
236     Matrix4f modelViewMatrix;
237     float yaw;
238     float pitch;
239     float horizontalFov;
240 };
241 static std::unique_ptr<FixedView> fixedView;
242 
243 dbyte freezeRLs;
244 dint devNoCulling;  ///< @c 1= disabled (cvar).
245 dint devRendSkyMode;
246 dbyte devRendSkyAlways;
247 
248 // Ambient lighting, rAmbient is used within the renderer, ambientLight is
249 // used to store the value of the ambient light cvar.
250 // The value chosen for rAmbient occurs in Rend_UpdateLightModMatrix
251 // for convenience (since we would have to recalculate the matrix anyway).
252 dint rAmbient, ambientLight;
253 
254 //dint viewpw, viewph;  ///< Viewport size, in pixels.
255 //dint viewpx, viewpy;  ///< Viewpoint top left corner, in pixels.
256 
257 dfloat yfov;
258 
259 dint gameDrawHUD = 1;  ///< Set to zero when we advise that the HUD should not be drawn
260 
261 /**
262  * Implements a pre-calculated LUT for light level limiting and range
263  * compression offsets, arranged such that it may be indexed with a
264  * light level value. Return value is an appropriate delta (considering
265  * all applicable renderer properties) which has been pre-clamped such
266  * that when summed with the original light value the result remains in
267  * the normalized range [0..1].
268  */
269 dfloat lightRangeCompression;
270 dfloat lightModRange[255];
271 byte devLightModRange;
272 
273 dfloat rendLightDistanceAttenuation = 924;
274 dint rendLightAttenuateFixedColormap = 1;
275 
276 dfloat rendLightWallAngle = 1.2f; ///< Intensity of angle-based wall lighting.
277 dbyte rendLightWallAngleSmooth = true;
278 
279 dfloat rendSkyLight = .273f;     ///< Intensity factor.
280 dbyte rendSkyLightAuto = true;
281 dint rendMaxLumobjs;             ///< Max lumobjs per viewer, per frame. @c 0= no maximum.
282 
283 dint extraLight;  ///< Bumped light from gun blasts.
284 dfloat extraLightDelta;
285 
286 //DGLuint dlBBox;  ///< Display list id for the active-textured bbox model.
287 
288 /*
289  * Debug/Development cvars:
290  */
291 
292 dbyte devMobjVLights;            ///< @c 1= Draw mobj vertex lighting vector.
293 dint devMobjBBox;                ///< @c 1= Draw mobj bounding boxes.
294 dint devPolyobjBBox;             ///< @c 1= Draw polyobj bounding boxes.
295 
296 dbyte devVertexIndices;          ///< @c 1= Draw vertex indices.
297 dbyte devVertexBars;             ///< @c 1= Draw vertex position bars.
298 
299 dbyte devDrawGenerators;         ///< @c 1= Draw active generators.
300 dbyte devSoundEmitters;          ///< @c 1= Draw sound emitters.
301 dbyte devSurfaceVectors;         ///< @c 1= Draw tangent space vectors for surfaces.
302 dbyte devNoTexFix;               ///< @c 1= Draw "missing" rather than fix materials.
303 
304 dbyte devSectorIndices;          ///< @c 1= Draw sector indicies.
305 dbyte devThinkerIds;             ///< @c 1= Draw (mobj) thinker indicies.
306 
307 dbyte rendInfoLums;              ///< @c 1= Print lumobj debug info to the console.
308 dbyte devDrawLums;               ///< @c 1= Draw lumobjs origins.
309 
310 #if 0
311 dbyte devLightGrid;              ///< @c 1= Draw lightgrid debug visual.
312 dfloat devLightGridSize = 1.5f;  ///< Lightgrid debug visual size factor.
313 #endif
314 
315 //static void drawBiasEditingVisuals(Map &map);
316 //static void drawFakeRadioShadowPoints(Map &map);
317 static void drawGenerators(Map &map);
318 static void drawLumobjs(Map &map);
319 static void drawMobjBoundingBoxes(Map &map);
320 static void drawSectors(Map &map);
321 static void drawSoundEmitters(Map &map);
322 static void drawSurfaceTangentVectors(Map &map);
323 static void drawThinkers(Map &map);
324 static void drawVertexes(Map &map);
325 
326 // Draw state:
327 static Vector3d eyeOrigin;            ///< Viewer origin.
328 static ConvexSubspace *curSubspace;   ///< Subspace currently being drawn.
329 static Vector3f curSectorLightColor;
330 static dfloat curSectorLightLevel;
331 static bool firstSubspace;            ///< No range checking for the first one.
332 
333 // State lookup (for speed):
334 static MaterialVariantSpec const *lookupMapSurfaceMaterialSpec = nullptr;
335 static QHash<Record const *, MaterialAnimator *> lookupSpriteMaterialAnimators;
336 
Rend_ResetLookups()337 void Rend_ResetLookups()
338 {
339     lookupMapSurfaceMaterialSpec = nullptr;
340     lookupSpriteMaterialAnimators.clear();
341 
342     if (ClientApp::world().hasMap())
343     {
344         auto &map = ClientApp::world().map();
345 
346         map.forAllSectors([] (Sector &sector)
347         {
348             sector.forAllPlanes([] (Plane &plane)
349             {
350                 plane.surface().resetLookups();
351                 return LoopContinue;
352             });
353             return LoopContinue;
354         });
355 
356         map.forAllLines([] (Line &line)
357         {
358             auto resetFunc = [] (Surface &surface) {
359                 surface.resetLookups();
360                 return LoopContinue;
361             };
362 
363             line.front().forAllSurfaces(resetFunc);
364             line.back() .forAllSurfaces(resetFunc);
365             return LoopContinue;
366         });
367     }
368 }
369 
reportWallDrawn(Line & line)370 static void reportWallDrawn(Line &line)
371 {
372     // Already been here?
373     dint playerNum = DoomsdayApp::players().indexOf(viewPlayer);
374     if (line.isMappedByPlayer(playerNum)) return;
375 
376     // Mark as drawn.
377     line.setMappedByPlayer(playerNum);
378 
379     // Send a status report.
380     if (gx.HandleMapObjectStatusReport)
381     {
382         gx.HandleMapObjectStatusReport(DMUSC_LINE_FIRSTRENDERED, line.indexInMap(),
383                                        DMU_LINE, &playerNum);
384     }
385 }
386 
387 #if 0
388 static void scheduleFullLightGridUpdate()
389 {
390     if (!ClientApp::world().hasMap()) return;
391 
392     // Schedule a LightGrid update.
393     if (ClientApp::world().map().hasLightGrid())
394     {
395         ClientApp::world().map().lightGrid().scheduleFullUpdate();
396     }
397 }
398 #endif
399 
400 /// World/map renderer reset.
Rend_Reset()401 void Rend_Reset()
402 {
403     R_ClearViewData();
404     if (App_World().hasMap())
405     {
406         App_World().map().removeAllLumobjs();
407     }
408 //    if (dlBBox)
409 //    {
410 //        GL_DeleteLists(dlBBox, 1);
411 //        dlBBox = 0;
412 //    }
413 }
414 
Rend_IsMTexLights()415 bool Rend_IsMTexLights()
416 {
417     return IS_MTEX_LIGHTS;
418 }
419 
Rend_IsMTexDetails()420 bool Rend_IsMTexDetails()
421 {
422     return IS_MTEX_DETAILS;
423 }
424 
Rend_FieldOfView()425 dfloat Rend_FieldOfView()
426 {
427     if (fixedView)
428     {
429         return fixedView->horizontalFov;
430     }
431 
432     if (vrCfg().mode() == VRConfig::OculusRift)
433     {
434         // OVR tells us which FOV to use.
435         return vrCfg().oculusRift().fovX();
436     }
437     else
438     {
439         return de::clamp(1.f, fieldOfView, 179.f);
440     }
441 }
442 
443 static Vector3d vEyeOrigin;
444 
Rend_EyeOrigin()445 Vector3d Rend_EyeOrigin()
446 {
447     return vEyeOrigin;
448 }
449 
Rend_SetFixedView(int consoleNum,float yaw,float pitch,float fov,Vector2f viewportSize)450 void Rend_SetFixedView(int consoleNum, float yaw, float pitch, float fov, Vector2f viewportSize)
451 {
452     viewdata_t const *viewData = &DD_Player(consoleNum)->viewport();
453 
454     fixedView.reset(new FixedView);
455 
456     fixedView->yaw = yaw;
457     fixedView->pitch = pitch;
458     fixedView->horizontalFov = fov;
459     fixedView->modelViewMatrix =
460             Matrix4f::rotate(pitch, Vector3f(1, 0, 0)) *
461             Matrix4f::rotate(yaw,   Vector3f(0, 1, 0)) *
462             Matrix4f::scale(Vector3f(1.0f, 1.2f, 1.0f)) * // This is the aspect correction.
463             Matrix4f::translate(-viewData->current.origin.xzy());
464 
465     Rangef const clip = GL_DepthClipRange();
466     fixedView->projectionMatrix = BaseGuiApp::app().vr()
467             .projectionMatrix(fov, viewportSize, clip.start, clip.end) *
468             Matrix4f::scale(Vector3f(1, 1, -1));
469 }
470 
Rend_UnsetFixedView()471 void Rend_UnsetFixedView()
472 {
473     fixedView.reset();
474 }
475 
Rend_GetModelViewMatrix(dint consoleNum,bool inWorldSpace)476 Matrix4f Rend_GetModelViewMatrix(dint consoleNum, bool inWorldSpace)
477 {
478     viewdata_t const *viewData = &DD_Player(consoleNum)->viewport();
479 
480     /// @todo vOrigin et al. shouldn't be changed in a getter function. -jk
481 
482     vOrigin = viewData->current.origin.xzy();
483     vEyeOrigin = vOrigin;
484 
485     if (fixedView)
486     {
487         vang   = fixedView->yaw;
488         vpitch = fixedView->pitch;
489         return fixedView->modelViewMatrix;
490     }
491 
492     vang   = viewData->current.angle() / (dfloat) ANGLE_MAX * 360 - 90;  // head tracking included
493     vpitch = viewData->current.pitch * 85.0 / 110.0;
494 
495     dfloat bodyAngle = viewData->current.angleWithoutHeadTracking() / (dfloat) ANGLE_MAX * 360 - 90;
496 
497     OculusRift &ovr = vrCfg().oculusRift();
498     bool const applyHead = (vrCfg().mode() == VRConfig::OculusRift && ovr.isReady());
499 
500     Matrix4f modelView;
501     Matrix4f headOrientation;
502     Matrix4f headOffset;
503 
504     if (applyHead)
505     {
506         Vector3f headPos = swizzle(Matrix4f::rotate(bodyAngle, Vector3f(0, 1, 0))
507                                        * ovr.headPosition() * vrCfg().mapUnitsPerMeter()
508                                    , AxisNegX, AxisNegY, AxisZ);
509         headOffset = Matrix4f::translate(headPos);
510 
511         vEyeOrigin -= headPos;
512     }
513 
514     if (inWorldSpace)
515     {
516         dfloat yaw   = vang;
517         dfloat pitch = vpitch;
518         dfloat roll  = 0;
519 
520         /// @todo Elevate roll angle use into viewer_t, and maybe all the way up into player
521         /// model.
522 
523         // Pitch and yaw can be taken directly from the head tracker, as the game is aware of
524         // these values and is syncing with them independently (however, game has more
525         // latency).
526         if (applyHead)
527         {
528             // Use angles directly from the Rift for best response.
529             Vector3f const pry = ovr.headOrientation();
530             roll  = -radianToDegree(pry[1]);
531             pitch =  radianToDegree(pry[0]);
532         }
533 
534         headOrientation = Matrix4f::rotate(roll,  Vector3f(0, 0, 1))
535                         * Matrix4f::rotate(pitch, Vector3f(1, 0, 0))
536                         * Matrix4f::rotate(yaw,   Vector3f(0, 1, 0));
537 
538         modelView = headOrientation * headOffset;
539     }
540 
541     if (applyHead)
542     {
543         // Apply the current eye offset to the eye origin.
544         vEyeOrigin -= headOrientation.inverse() * (ovr.eyeOffset() * vrCfg().mapUnitsPerMeter());
545     }
546 
547     return (modelView
548             * Matrix4f::scale(Vector3f(1.0f, 1.2f, 1.0f)) // This is the aspect correction.
549             * Matrix4f::translate(-vOrigin));
550 }
551 
Rend_ModelViewMatrix(bool inWorldSpace)552 void Rend_ModelViewMatrix(bool inWorldSpace)
553 {
554     DENG2_ASSERT_IN_RENDER_THREAD();
555     DENG_ASSERT_GL_CONTEXT_ACTIVE();
556 
557     DGL_MatrixMode(DGL_MODELVIEW);
558     DGL_LoadMatrix(
559         Rend_GetModelViewMatrix(DoomsdayApp::players().indexOf(viewPlayer), inWorldSpace).values());
560 }
561 
Rend_GetProjectionMatrix(float fixedFov,float clipRangeScale)562 Matrix4f Rend_GetProjectionMatrix(float fixedFov, float clipRangeScale)
563 {
564     if (fixedView)
565     {
566         return fixedView->projectionMatrix;
567     }
568 
569     const Vector2f size = R_Console3DViewRect(displayPlayer).size();
570 
571     // IssueID #2379: adapt fixed FOV angle for a 4:3 aspect ratio
572     if (fixedFov > 0)
573     {
574         const float ASPECT_DEFAULT = 16.0f / 9.0f;
575         const float ASPECT_MIN     = 4.0f / 3.0f;
576         const float aspect         = de::max(size.x / size.y, ASPECT_MIN);
577 
578         if (aspect < ASPECT_DEFAULT)
579         {
580             // The fixed FOV angle is specified for a 16:9 horizontal view.
581             // Adjust for this aspect ratio.
582             fixedFov *= lerp(0.825f, 1.0f, (aspect - ASPECT_MIN) / (ASPECT_DEFAULT - ASPECT_MIN));
583         }
584     }
585 
586     const dfloat   fov  = (fixedFov > 0 ? fixedFov : Rend_FieldOfView());
587     yfov                = vrCfg().verticalFieldOfView(fov, size);
588     const Rangef clip   = GL_DepthClipRange();
589     return vrCfg().projectionMatrix(
590                fov, size, clip.start * clipRangeScale, clip.end * clipRangeScale) *
591            Matrix4f::scale(Vector3f(1, 1, -1));
592 }
593 
viewFacingDot(Vector2d const & v1,Vector2d const & v2)594 static inline ddouble viewFacingDot(Vector2d const &v1, Vector2d const &v2)
595 {
596     // The dot product.
597     return (v1.y - v2.y) * (v1.x - Rend_EyeOrigin().x) + (v2.x - v1.x) * (v1.y - Rend_EyeOrigin().z);
598 }
599 
Rend_ExtraLightDelta()600 dfloat Rend_ExtraLightDelta()
601 {
602     return extraLightDelta;
603 }
604 
Rend_ApplyTorchLight(Vector4f & color,dfloat distance)605 void Rend_ApplyTorchLight(Vector4f &color, dfloat distance)
606 {
607     ddplayer_t *ddpl = &viewPlayer->publicData();
608 
609     // Disabled?
610     if (!ddpl->fixedColorMap) return;
611 
612     // Check for torch.
613     if (!rendLightAttenuateFixedColormap || distance < 1024)
614     {
615         // Colormap 1 is the brightest. I'm guessing 16 would be
616         // the darkest.
617         dfloat d = (16 - ddpl->fixedColorMap) / 15.0f;
618         if (rendLightAttenuateFixedColormap)
619         {
620             d *= (1024 - distance) / 1024.0f;
621         }
622 
623         color = color.max(torchColor * d);
624     }
625 }
626 
Rend_ApplyTorchLight(dfloat * color3,dfloat distance)627 void Rend_ApplyTorchLight(dfloat *color3, dfloat distance)
628 {
629     Vector4f tmp(color3, 0);
630     Rend_ApplyTorchLight(tmp, distance);
631     for (dint i = 0; i < 3; ++i)
632     {
633         color3[i] = tmp[i];
634     }
635 }
636 
Rend_AttenuateLightLevel(dfloat distToViewer,dfloat lightLevel)637 dfloat Rend_AttenuateLightLevel(dfloat distToViewer, dfloat lightLevel)
638 {
639     if (distToViewer > 0 && rendLightDistanceAttenuation > 0)
640     {
641         dfloat real = lightLevel -
642             (distToViewer - 32) / rendLightDistanceAttenuation *
643                 (1 - lightLevel);
644 
645         dfloat minimum = de::max(0.f, de::squared(lightLevel) + (lightLevel - .63f) * .5f);
646         if (real < minimum)
647             real = minimum; // Clamp it.
648 
649         return de::min(real, 1.f);
650     }
651 
652     return lightLevel;
653 }
654 
Rend_ShadowAttenuationFactor(coord_t distance)655 dfloat Rend_ShadowAttenuationFactor(coord_t distance)
656 {
657     if (shadowMaxDistance > 0 && distance > 3 * shadowMaxDistance / 4)
658     {
659         return (shadowMaxDistance - distance) / (shadowMaxDistance / 4);
660     }
661     return 1;
662 }
663 
664 static Vector3f skyLightColor;
665 static Vector3f oldSkyAmbientColor(-1.f, -1.f, -1.f);
666 static dfloat oldRendSkyLight = -1;
667 
Rend_SkyLightIsEnabled()668 bool Rend_SkyLightIsEnabled()
669 {
670     return rendSkyLight > .001f;
671 }
672 
Rend_SkyLightColor()673 Vector3f Rend_SkyLightColor()
674 {
675     if (Rend_SkyLightIsEnabled() && ClientApp::world().hasMap())
676     {
677         Sky &sky = ClientApp::world().map().sky();
678         Vector3f const &ambientColor = sky.ambientColor();
679 
680         if (rendSkyLight != oldRendSkyLight
681             || !INRANGE_OF(ambientColor.x, oldSkyAmbientColor.x, .001f)
682             || !INRANGE_OF(ambientColor.y, oldSkyAmbientColor.y, .001f)
683             || !INRANGE_OF(ambientColor.z, oldSkyAmbientColor.z, .001f))
684         {
685             skyLightColor = ambientColor;
686             R_AmplifyColor(skyLightColor);
687 
688             // Apply the intensity factor cvar.
689             for (dint i = 0; i < 3; ++i)
690             {
691                 skyLightColor[i] = skyLightColor[i] + (1 - rendSkyLight) * (1.f - skyLightColor[i]);
692             }
693 
694 #if 0
695             // When the sky light color changes we must update the light grid.
696             scheduleFullLightGridUpdate();
697 #endif
698             oldSkyAmbientColor = ambientColor;
699         }
700 
701         oldRendSkyLight = rendSkyLight;
702         return skyLightColor;
703     }
704 
705     return Vector3f(1, 1, 1);
706 }
707 
708 /**
709  * Determine the effective ambient light color for the given @a sector. Usually
710  * one would obtain this info from Subsector, however in some situations the
711  * correct light color is *not* that of the subsector (e.g., where map hacks use
712  * mapped planes to reference another sector).
713  */
Rend_AmbientLightColor(Sector const & sector)714 static Vector3f Rend_AmbientLightColor(Sector const &sector)
715 {
716     if (Rend_SkyLightIsEnabled() && sector.hasSkyMaskPlane())
717     {
718         return Rend_SkyLightColor();
719     }
720 
721     // A non-skylight sector (i.e., everything else!)
722     // Return the sector's ambient light color.
723     return sector.lightColor();
724 }
725 
Rend_LuminousColor(Vector3f const & color,dfloat light)726 Vector3f Rend_LuminousColor(Vector3f const &color, dfloat light)
727 {
728     light = de::clamp(0.f, light, 1.f) * dynlightFactor;
729 
730     // In fog additive blending is used; the normal fog color is way too bright.
731     if (fogParams.usingFog) light *= dynlightFogBright;
732 
733     // Multiply light with (ambient) color.
734     return color * light;
735 }
736 
Rend_PlaneGlowHeight(dfloat intensity)737 coord_t Rend_PlaneGlowHeight(dfloat intensity)
738 {
739     return de::clamp<ddouble>(0, GLOW_HEIGHT_MAX * intensity * glowHeightFactor, glowHeightMax);
740 }
741 
Rend_ChooseMapSurfaceMaterial(Surface const & surface)742 ClientMaterial *Rend_ChooseMapSurfaceMaterial(Surface const &surface)
743 {
744     switch (renderTextures)
745     {
746     case 0:  // No texture mode.
747     case 1:  // Normal mode.
748         if (!(devNoTexFix && surface.hasFixMaterial()))
749         {
750             if (!surface.hasMaterial() && surface.parent().type() == DMU_SIDE)
751             {
752                 const Line::Side &side = surface.parent().as<Line::Side>();
753                 if (side.hasSector())
754                 {
755                     if (auto *midAnim = side.middle().materialAnimator())
756                     {
757                         DENG2_ASSERT(&surface != &side.middle());
758                         if (!midAnim->isOpaque())
759                         {
760                             return &midAnim->material();
761                         }
762                     }
763 
764                     if (&surface != &side.top() && !side.hasAtLeastOneMaterial())
765                     {
766                         // Keep it blank.
767                         return nullptr;
768                     }
769 
770                     // Use the ceiling for missing top section, and floor for missing bottom section.
771                     if (&surface == &side.bottom())
772                     {
773                         return static_cast<ClientMaterial *>(side.sector().floor().surface().materialPtr());
774                     }
775                     else if (&surface == &side.top())
776                     {
777                         /*
778                         // TNT map31: missing upper texture, high ceiling in the room
779                         if (side.back().hasSector())
780                         {
781                             const auto &backSector = side.back().sector();
782                             //const auto backCeilZ = backSector.ceiling().heightSmoothed();
783                             //const auto frontCeilZ = side.sector().ceiling().heightSmoothed();
784                             if (fequal(backSector.ceiling().height(), backSector.floor().height()))
785                             {
786                                 return nullptr;
787                             }
788                         }*/
789 
790                         return static_cast<ClientMaterial *>(side.sector().ceiling().surface().materialPtr());
791                     }
792                 }
793             }
794             if (surface.hasMaterial() || surface.parent().type() != DMU_PLANE)
795             {
796                 return static_cast<ClientMaterial *>(surface.materialPtr());
797             }
798         }
799 
800         // Use special "missing" material.
801         return &ClientMaterial::find(de::Uri("System", Path("missing")));
802 
803     case 2:  // Lighting debug mode.
804         if (surface.hasMaterial() && !(!devNoTexFix && surface.hasFixMaterial()))
805         {
806             if (!surface.hasSkyMaskedMaterial() || devRendSkyMode)
807             {
808                 // Use the special "gray" material.
809                 return &ClientMaterial::find(de::Uri("System", Path("gray")));
810             }
811         }
812         break;
813 
814     default: break;
815     }
816 
817     // No material, then.
818     return nullptr;
819 }
820 
821 /**
822  * This doesn't create a rendering primitive but a vissprite! The vissprite
823  * represents the masked poly and will be rendered during the rendering
824  * of sprites. This is necessary because all masked polygons must be
825  * rendered back-to-front, or there will be alpha artifacts along edges.
826  */
Rend_AddMaskedPoly(Vector3f const * rvertices,Vector4f const * rcolors,coord_t wallLength,MaterialAnimator * matAnimator,Vector2f const & origin,blendmode_t blendMode,duint lightListIdx,dfloat glow)827 void Rend_AddMaskedPoly(Vector3f const *rvertices, Vector4f const *rcolors,
828     coord_t wallLength, MaterialAnimator *matAnimator, Vector2f const &origin,
829     blendmode_t blendMode, duint lightListIdx, dfloat glow)
830 {
831     vissprite_t *vis = R_NewVisSprite(VSPR_MASKED_WALL);
832 
833     vis->pose.origin   = (rvertices[0] + rvertices[3]) / 2;
834     vis->pose.distance = Rend_PointDist2D(vis->pose.origin);
835 
836     VS_WALL(vis)->texOffset[0] = origin[0];
837     VS_WALL(vis)->texOffset[1] = origin[1];
838 
839     // Masked walls are sometimes used for special effects like arcs,
840     // cobwebs and bottoms of sails. In order for them to look right,
841     // we need to disable texture wrapping on the horizontal axis (S).
842     // Most masked walls need wrapping, though. What we need to do is
843     // look at the texture coordinates and see if they require texture
844     // wrapping.
845     if (renderTextures)
846     {
847         // Ensure we've up to date info about the material.
848         matAnimator->prepare();
849 
850         Vector2ui const &matDimensions = matAnimator->dimensions();
851 
852         VS_WALL(vis)->texCoord[0][0] = VS_WALL(vis)->texOffset[0] / matDimensions.x;
853         VS_WALL(vis)->texCoord[1][0] = VS_WALL(vis)->texCoord[0][0] + wallLength / matDimensions.x;
854         VS_WALL(vis)->texCoord[0][1] = VS_WALL(vis)->texOffset[1] / matDimensions.y;
855         VS_WALL(vis)->texCoord[1][1] = VS_WALL(vis)->texCoord[0][1] +
856                 (rvertices[3].z - rvertices[0].z) / matDimensions.y;
857 
858         dint wrapS = GL_REPEAT, wrapT = GL_REPEAT;
859         if (!matAnimator->isOpaque())
860         {
861             if (!(VS_WALL(vis)->texCoord[0][0] < 0 || VS_WALL(vis)->texCoord[0][0] > 1 ||
862                  VS_WALL(vis)->texCoord[1][0] < 0 || VS_WALL(vis)->texCoord[1][0] > 1))
863             {
864                 // Visible portion is within the actual [0..1] range.
865                 wrapS = GL_CLAMP_TO_EDGE;
866             }
867 
868             // Clamp on the vertical axis if the coords are in the normal [0..1] range.
869             if (!(VS_WALL(vis)->texCoord[0][1] < 0 || VS_WALL(vis)->texCoord[0][1] > 1 ||
870                  VS_WALL(vis)->texCoord[1][1] < 0 || VS_WALL(vis)->texCoord[1][1] > 1))
871             {
872                 wrapT = GL_CLAMP_TO_EDGE;
873             }
874         }
875 
876         // Choose a specific variant for use as a middle wall section.
877         matAnimator = &matAnimator->material().getAnimator(Rend_MapSurfaceMaterialSpec(wrapS, wrapT));
878     }
879 
880     VS_WALL(vis)->animator  = matAnimator;
881     VS_WALL(vis)->blendMode = blendMode;
882 
883     for (dint i = 0; i < 4; ++i)
884     {
885         VS_WALL(vis)->vertices[i].pos[0] = rvertices[i].x;
886         VS_WALL(vis)->vertices[i].pos[1] = rvertices[i].y;
887         VS_WALL(vis)->vertices[i].pos[2] = rvertices[i].z;
888 
889         for (dint c = 0; c < 4; ++c)
890         {
891             /// @todo Do not clamp here.
892             VS_WALL(vis)->vertices[i].color[c] = de::clamp(0.f, rcolors[i][c], 1.f);
893         }
894     }
895 
896     /// @todo Semitransparent masked polys arn't lit atm
897     if (glow < 1 && lightListIdx && !(rcolors[0].w < 1))
898     {
899         // The dynlights will have already been sorted so that the brightest
900         // and largest of them is first in the list. So grab that one.
901         ClientApp::renderSystem().forAllSurfaceProjections(lightListIdx, [&vis] (ProjectedTextureData const &tp)
902         {
903             VS_WALL(vis)->modTex = tp.texture;
904             VS_WALL(vis)->modTexCoord[0][0] = tp.topLeft.x;
905             VS_WALL(vis)->modTexCoord[0][1] = tp.topLeft.y;
906             VS_WALL(vis)->modTexCoord[1][0] = tp.bottomRight.x;
907             VS_WALL(vis)->modTexCoord[1][1] = tp.bottomRight.y;
908             for (dint c = 0; c < 4; ++c)
909             {
910                 VS_WALL(vis)->modColor[c] = tp.color[c];
911             }
912             return LoopAbort;
913         });
914     }
915     else
916     {
917         VS_WALL(vis)->modTex = 0;
918     }
919 }
920 
quadTexCoords(Vector2f * tc,Vector3f const * rverts,coord_t wallLength,Vector3d const & topLeft)921 static void quadTexCoords(Vector2f *tc, Vector3f const *rverts, coord_t wallLength, Vector3d const &topLeft)
922 {
923     DENG2_ASSERT(tc && rverts);
924     tc[0].x = tc[1].x = rverts[0].x - topLeft.x;
925     tc[3].y = tc[1].y = rverts[0].y - topLeft.y;
926     tc[3].x = tc[2].x = tc[0].x + wallLength;
927     tc[2].y = tc[3].y + (rverts[1].z - rverts[0].z);
928     tc[0].y = tc[3].y + (rverts[3].z - rverts[2].z);
929 }
930 
lightVertex(Vector4f & color,Vector3f const & vtx,dfloat lightLevel,Vector3f const & ambientColor)931 static void lightVertex(Vector4f &color, Vector3f const &vtx, dfloat lightLevel, Vector3f const &ambientColor)
932 {
933     dfloat const dist = Rend_PointDist2D(vtx);
934 
935     // Apply distance attenuation.
936     lightLevel = Rend_AttenuateLightLevel(dist, lightLevel);
937 
938     // Add extra light.
939     lightLevel = de::clamp(0.f, lightLevel + Rend_ExtraLightDelta(), 1.f);
940 
941     Rend_ApplyLightAdaptation(lightLevel);
942 
943     color.x = lightLevel * ambientColor.x;
944     color.y = lightLevel * ambientColor.y;
945     color.z = lightLevel * ambientColor.z;
946 }
947 
948 /**
949  * Apply map-space lighting to the given geometry. All vertex lighting contributions affecting
950  * map-space geometry are applied here.
951  *
952  * @param verts             Geometry to be illuminated.
953  *
954  * Surface geometry:
955  * @param numVertices       Total number of map-space surface geometry vertices.
956  * @param posCoords         Position coordinates for the map-space surface geometry.
957  * @param mapElement        Source MapElement for the map-space surface geometry.
958  * @param geomGroup         Source MapElement geometry-group selector, for the map-space surface geometry.
959  * @param surfaceTangents   Tangent-space vectors for the map-space surface geometry.
960  *
961  * Surface lighting characteristics:
962  * @param color             Tint color.
963  * @param color2            Secondary tint color, for walls (if any).
964  * @param glowing           Self-luminosity factor (normalized [0..1]).
965  * @param luminosityDeltas  Edge luminosity deltas (for walls [left edge, right edge]).
966  */
lightWallOrFlatGeometry(Geometry & verts,duint numVertices,Vector3f const * posCoords,MapElement & mapElement,dint,Matrix3f const &,Vector3f const & color,Vector3f const * color2,dfloat glowing,dfloat const luminosityDeltas[2])967 static void lightWallOrFlatGeometry(Geometry &verts, duint numVertices, Vector3f const *posCoords,
968     MapElement &mapElement, dint /*geomGroup*/, Matrix3f const &/*surfaceTangents*/,
969     Vector3f const &color, Vector3f const *color2, dfloat glowing, dfloat const luminosityDeltas[2])
970 {
971     bool const haveWall = is<LineSideSegment>(mapElement);
972     //auto &subsec = ::curSubspace->subsector().as<world::ClientSubsector>();
973 
974     // Uniform color?
975     if (::levelFullBright || !(glowing < 1))
976     {
977         dfloat const lum = de::clamp(0.f, ::curSectorLightLevel + (::levelFullBright? 1 : glowing), 1.f);
978         Vector4f const uniformColor(lum, lum, lum, 0);
979         for (duint i = 0; i < numVertices; ++i)
980         {
981             verts.color[i] = uniformColor;
982         }
983         return;
984     }
985 
986 #if 0
987     if (::useBias)  // Bias lighting model.
988     {
989         Map &map     = subsec.sector().map();
990         Shard &shard = subsec.shard(mapElement, geomGroup);
991 
992         // Apply the ambient light term from the grid (if available).
993         if (map.hasLightGrid())
994         {
995             for (duint i = 0; i < numVertices; ++i)
996             {
997                 verts.color[i] = map.lightGrid().evaluate(posCoords[i]);
998             }
999         }
1000 
1001         // Apply bias light source contributions.
1002         shard.lightWithBiasSources(posCoords, verts.color, surfaceTangents, map.biasCurrentTime());
1003 
1004         // Apply surface glow.
1005         if (glowing > 0)
1006         {
1007             Vector4f const glow(glowing, glowing, glowing, 0);
1008             for (duint i = 0; i < numVertices; ++i)
1009             {
1010                 verts.color[i] += glow;
1011             }
1012         }
1013 
1014         // Apply light range compression and clamp.
1015         for (duint i = 0; i < numVertices; ++i)
1016         {
1017             Vector4f &color = verts.color[i];
1018             for (dint k = 0; k < 3; ++k)
1019             {
1020                 color[k] = de::clamp(0.f, color[k] + Rend_LightAdaptationDelta(color[k]), 1.f);
1021             }
1022         }
1023     }
1024     else  // Doom lighting model.
1025 #endif
1026     {
1027         // Blend sector light color with the surface color tint.
1028         Vector3f const colorBlended = ::curSectorLightColor * color;
1029         dfloat const lumLeft  = de::clamp(0.f, ::curSectorLightLevel + luminosityDeltas[0] + glowing, 1.f);
1030         dfloat const lumRight = de::clamp(0.f, ::curSectorLightLevel + luminosityDeltas[1] + glowing, 1.f);
1031 
1032         if (haveWall && !de::fequal(lumLeft, lumRight))
1033         {
1034             lightVertex(verts.color[0], posCoords[0], lumLeft,  colorBlended);
1035             lightVertex(verts.color[1], posCoords[1], lumLeft,  colorBlended);
1036             lightVertex(verts.color[2], posCoords[2], lumRight, colorBlended);
1037             lightVertex(verts.color[3], posCoords[3], lumRight, colorBlended);
1038         }
1039         else
1040         {
1041             for (duint i = 0; i < numVertices; ++i)
1042             {
1043                 lightVertex(verts.color[i], posCoords[i], lumLeft, colorBlended);
1044             }
1045         }
1046 
1047         // Secondary color?
1048         if (haveWall && color2)
1049         {
1050             // Blend the secondary surface color tint with the sector light color.
1051             Vector3f const color2Blended = ::curSectorLightColor * (*color2);
1052             lightVertex(verts.color[0], posCoords[0], lumLeft,  color2Blended);
1053             lightVertex(verts.color[2], posCoords[2], lumRight, color2Blended);
1054         }
1055     }
1056 
1057     // Apply torch light?
1058     DENG2_ASSERT(::viewPlayer);
1059     if (::viewPlayer->publicData().fixedColorMap)
1060     {
1061         for (duint i = 0; i < numVertices; ++i)
1062         {
1063             Rend_ApplyTorchLight(verts.color[i], Rend_PointDist2D(posCoords[i]));
1064         }
1065     }
1066 }
1067 
makeFlatGeometry(Geometry & verts,duint numVertices,Vector3f const * posCoords,Vector3d const & topLeft,Vector3d const &,MapElement & mapElement,dint geomGroup,Matrix3f const & surfaceTangents,dfloat uniformOpacity,Vector3f const & color,Vector3f const * color2,dfloat glowing,dfloat const luminosityDeltas[2],bool useVertexLighting=true)1068 static void makeFlatGeometry(Geometry &verts, duint numVertices, Vector3f const *posCoords,
1069     Vector3d const &topLeft, Vector3d const & /*bottomRight*/, MapElement &mapElement, dint geomGroup,
1070     Matrix3f const &surfaceTangents, dfloat uniformOpacity, Vector3f const &color, Vector3f const *color2,
1071     dfloat glowing, dfloat const luminosityDeltas[2], bool useVertexLighting = true)
1072 {
1073     DENG2_ASSERT(posCoords);
1074 
1075     for (duint i = 0; i < numVertices; ++i)
1076     {
1077         verts.pos[i] = posCoords[i];
1078 
1079         Vector3f const delta(posCoords[i] - topLeft);
1080         if (verts.tex)  // Primary.
1081         {
1082             verts.tex[i] = Vector2f(delta.x, -delta.y);
1083         }
1084         if (verts.tex2)  // Inter.
1085         {
1086             verts.tex2[i] = Vector2f(delta.x, -delta.y);
1087         }
1088     }
1089 
1090     // Light the geometry?
1091     if (useVertexLighting)
1092     {
1093         lightWallOrFlatGeometry(verts, numVertices, posCoords, mapElement, geomGroup, surfaceTangents,
1094                                 color, color2, glowing, luminosityDeltas);
1095 
1096         // Apply uniform opacity (overwritting luminance factors).
1097         for (duint i = 0; i < numVertices; ++i)
1098         {
1099             verts.color[i].w = uniformOpacity;
1100         }
1101     }
1102 }
1103 
makeWallGeometry(Geometry & verts,duint numVertices,Vector3f const * posCoords,Vector3d const & topLeft,Vector3d const &,coord_t sectionWidth,MapElement & mapElement,dint geomGroup,Matrix3f const & surfaceTangents,dfloat uniformOpacity,Vector3f const & color,Vector3f const * color2,dfloat glowing,dfloat const luminosityDeltas[2],bool useVertexLighting=true)1104 static void makeWallGeometry(Geometry &verts, duint numVertices, Vector3f const *posCoords,
1105     Vector3d const &topLeft, Vector3d const & /*bottomRight*/, coord_t sectionWidth,
1106     MapElement &mapElement, dint geomGroup, Matrix3f const &surfaceTangents, dfloat uniformOpacity,
1107     Vector3f const &color, Vector3f const *color2, dfloat glowing, dfloat const luminosityDeltas[2],
1108     bool useVertexLighting = true)
1109 {
1110     DENG2_ASSERT(posCoords);
1111 
1112     for (duint i = 0; i < numVertices; ++i)
1113     {
1114         verts.pos[i] = posCoords[i];
1115     }
1116     if (verts.tex)  // Primary.
1117     {
1118         quadTexCoords(verts.tex, posCoords, sectionWidth, topLeft);
1119     }
1120     if (verts.tex2)  // Inter.
1121     {
1122         quadTexCoords(verts.tex2, posCoords, sectionWidth, topLeft);
1123     }
1124 
1125     // Light the geometry?
1126     if (useVertexLighting)
1127     {
1128         lightWallOrFlatGeometry(verts, numVertices, posCoords, mapElement, geomGroup, surfaceTangents,
1129                                 color, color2, glowing, luminosityDeltas);
1130 
1131         // Apply uniform opacity (overwritting luminance factors).
1132         for (duint i = 0; i < numVertices; ++i)
1133         {
1134             verts.color[i].w = uniformOpacity;
1135         }
1136     }
1137 }
1138 
shineVertical(dfloat dy,dfloat dx)1139 static inline dfloat shineVertical(dfloat dy, dfloat dx)
1140 {
1141     return ((std::atan(dy/dx) / (PI/2)) + 1) / 2;
1142 }
1143 
makeFlatShineGeometry(Geometry & verts,duint numVertices,Vector3f const * posCoords,Geometry const & mainVerts,Vector3f const & shineColor,dfloat shineOpacity)1144 static void makeFlatShineGeometry(Geometry &verts, duint numVertices, Vector3f const *posCoords,
1145     Geometry const &mainVerts, Vector3f const &shineColor, dfloat shineOpacity)
1146 {
1147     DENG2_ASSERT(posCoords);
1148 
1149     for (duint i = 0; i < numVertices; ++i)
1150     {
1151         Vector3f const eye = Rend_EyeOrigin();
1152 
1153         // Determine distance to viewer.
1154         dfloat distToEye = (eye.xz() - Vector2f(posCoords[i])).normalize().length();
1155         // Short distances produce an ugly 'crunch' below and above the viewpoint.
1156         if (distToEye < 10) distToEye = 10;
1157 
1158         // Offset from the normal view plane.
1159         dfloat offset = ((eye.y - posCoords[i].y) * sin(.4f)/*viewFrontVec[0]*/ -
1160                          (eye.x - posCoords[i].x) * cos(.4f)/*viewFrontVec[2]*/);
1161 
1162         verts.tex[i] = Vector2f(((shineVertical(offset, distToEye) - .5f) * 2) + .5f,
1163                                 shineVertical(eye.y - posCoords[i].z, distToEye));
1164     }
1165 
1166     for (duint i = 0; i < numVertices; ++i)
1167     {
1168         verts.color[i] = Vector4f(Vector3f(mainVerts.color[i]).max(shineColor), shineOpacity);
1169     }
1170 }
1171 
makeWallShineGeometry(Geometry & verts,duint numVertices,Vector3f const * posCoords,Geometry const & mainVerts,coord_t sectionWidth,Vector3f const & shineColor,dfloat shineOpactiy)1172 static void makeWallShineGeometry(Geometry &verts, duint numVertices, Vector3f const *posCoords,
1173     Geometry const &mainVerts, coord_t sectionWidth, Vector3f const &shineColor, dfloat shineOpactiy)
1174 {
1175     DENG2_ASSERT(posCoords);
1176 
1177     Vector3f const &topLeft     = posCoords[1];
1178     Vector3f const &bottomRight = posCoords[2];
1179 
1180     // Quad surface vector.
1181     Vector2f const normal = Vector2f( (bottomRight.y - topLeft.y) / sectionWidth,
1182                                      -(bottomRight.x - topLeft.x) / sectionWidth);
1183 
1184     // Calculate coordinates based on viewpoint and surface normal.
1185     Vector3f const eye = Rend_EyeOrigin();
1186     dfloat prevAngle = 0;
1187     for (duint i = 0; i < 2; ++i)
1188     {
1189         Vector2f const eyeToVert = eye.xz() - (i == 0? Vector2f(topLeft) : Vector2f(bottomRight));
1190         dfloat const distToEye   = eyeToVert.length();
1191         Vector2f const view      = eyeToVert.normalize();
1192 
1193         Vector2f projected;
1194         dfloat const div = normal.lengthSquared();
1195         if (div != 0)
1196         {
1197             projected = normal * view.dot(normal) / div;
1198         }
1199 
1200         Vector2f const reflected = view + (projected - view) * 2;
1201         dfloat angle = std::acos(reflected.y) / PI;
1202         if (reflected.x < 0)
1203         {
1204             angle = 1 - angle;
1205         }
1206 
1207         if (i == 0)
1208         {
1209             prevAngle = angle;
1210         }
1211         else if (angle > prevAngle)
1212         {
1213             angle -= 1;
1214         }
1215 
1216         // Horizontal coordinates.
1217         verts.tex[ (i == 0 ? 1 : 2) ].x = angle + .3f /*std::acos(-dot)/PI*/;
1218         verts.tex[ (i == 0 ? 0 : 3) ].x = angle + .3f /*std::acos(-dot)/PI*/;
1219 
1220         // Vertical coordinates.
1221         verts.tex[ (i == 0 ? 0 : 2) ].y = shineVertical(eye.y - bottomRight.z, distToEye);
1222         verts.tex[ (i == 0 ? 1 : 3) ].y = shineVertical(eye.y - topLeft.z,     distToEye);
1223     }
1224 
1225     for (duint i = 0; i < numVertices; ++i)
1226     {
1227         verts.color[i] = Vector4f(Vector3f(mainVerts.color[i]).max(shineColor), shineOpactiy);
1228     }
1229 }
1230 
makeFlatShadowGeometry(Geometry & verts,Vector3d const & topLeft,Vector3d const & bottomRight,duint numVertices,Vector3f const * posCoords,ProjectedTextureData const & tp)1231 static void makeFlatShadowGeometry(Geometry &verts, Vector3d const &topLeft, Vector3d const &bottomRight,
1232     duint numVertices, Vector3f const *posCoords,
1233     ProjectedTextureData const &tp)
1234 {
1235     DENG2_ASSERT(posCoords);
1236 
1237     Vector4f const colorClamped = tp.color.min(Vector4f(1, 1, 1, 1)).max(Vector4f(0, 0, 0, 0));
1238     for (duint i = 0; i < numVertices; ++i)
1239     {
1240         verts.color[i] = colorClamped;
1241     }
1242 
1243     dfloat const width  = bottomRight.x - topLeft.x;
1244     dfloat const height = bottomRight.y - topLeft.y;
1245 
1246     for (duint i = 0; i < numVertices; ++i)
1247     {
1248         verts.tex[i].x = ((bottomRight.x - posCoords[i].x) / width * tp.topLeft.x) +
1249             ((posCoords[i].x - topLeft.x) / width * tp.bottomRight.x);
1250 
1251         verts.tex[i].y = ((bottomRight.y - posCoords[i].y) / height * tp.topLeft.y) +
1252             ((posCoords[i].y - topLeft.y) / height * tp.bottomRight.y);
1253     }
1254 
1255     std::memcpy(verts.pos, posCoords, sizeof(Vector3f) * numVertices);
1256 }
1257 
makeWallShadowGeometry(Geometry & verts,Vector3d const &,Vector3d const &,duint numVertices,Vector3f const * posCoords,WallEdge const & leftEdge,WallEdge const & rightEdge,ProjectedTextureData const & tp)1258 static void makeWallShadowGeometry(Geometry &verts, Vector3d const &/*topLeft*/, Vector3d const &/*bottomRight*/,
1259     duint numVertices, Vector3f const *posCoords, WallEdge const &leftEdge, WallEdge const &rightEdge,
1260     ProjectedTextureData const &tp)
1261 {
1262     DENG2_ASSERT(posCoords);
1263 
1264     Vector4f const colorClamped = tp.color.min(Vector4f(1, 1, 1, 1)).max(Vector4f(0, 0, 0, 0));
1265     for (duint i = 0; i < numVertices; ++i)
1266     {
1267         verts.color[i] = colorClamped;
1268     }
1269 
1270     verts.tex[1].x = verts.tex[0].x = tp.topLeft.x;
1271     verts.tex[1].y = verts.tex[3].y = tp.topLeft.y;
1272     verts.tex[3].x = verts.tex[2].x = tp.bottomRight.x;
1273     verts.tex[2].y = verts.tex[0].y = tp.bottomRight.y;
1274 
1275     // If either edge has divisions - make two trifans.
1276     if (leftEdge.divisionCount() || rightEdge.divisionCount())
1277     {
1278         // Need to swap indices around into fans set the position of the
1279         // division vertices, interpolate texcoords and color.
1280 
1281         Vector3f origPosCoords[4]; std::memcpy(origPosCoords, posCoords,   sizeof(Vector3f) * 4);
1282         Vector2f origTexCoords[4]; std::memcpy(origTexCoords, verts.tex,   sizeof(Vector2f) * 4);
1283         Vector4f origColors[4];    std::memcpy(origColors,    verts.color, sizeof(Vector4f) * 4);
1284 
1285         R_DivVerts     (verts.pos,   origPosCoords, leftEdge, rightEdge);
1286         R_DivTexCoords (verts.tex,   origTexCoords, leftEdge, rightEdge);
1287         R_DivVertColors(verts.color, origColors,    leftEdge, rightEdge);
1288     }
1289     else
1290     {
1291         std::memcpy(verts.pos, posCoords, sizeof(Vector3f) * numVertices);
1292     }
1293 }
1294 
makeFlatLightGeometry(Geometry & verts,Vector3d const & topLeft,Vector3d const & bottomRight,duint numVertices,Vector3f const * posCoords,ProjectedTextureData const & tp)1295 static void makeFlatLightGeometry(Geometry &verts, Vector3d const &topLeft, Vector3d const &bottomRight,
1296     duint numVertices, Vector3f const *posCoords,
1297     ProjectedTextureData const &tp)
1298 {
1299     DENG2_ASSERT(posCoords);
1300 
1301     Vector4f const colorClamped = tp.color.min(Vector4f(1, 1, 1, 1)).max(Vector4f(0, 0, 0, 0));
1302     for (duint i = 0; i < numVertices; ++i)
1303     {
1304         verts.color[i] = colorClamped;
1305     }
1306 
1307     dfloat const width  = bottomRight.x - topLeft.x;
1308     dfloat const height = bottomRight.y - topLeft.y;
1309     for (duint i = 0; i < numVertices; ++i)
1310     {
1311         verts.tex[i].x = ((bottomRight.x - posCoords[i].x) / width * tp.topLeft.x) +
1312             ((posCoords[i].x - topLeft.x) / width * tp.bottomRight.x);
1313 
1314         verts.tex[i].y = ((bottomRight.y - posCoords[i].y) / height * tp.topLeft.y) +
1315             ((posCoords[i].y - topLeft.y) / height * tp.bottomRight.y);
1316     }
1317 
1318     std::memcpy(verts.pos, posCoords, sizeof(Vector3f) * numVertices);
1319 }
1320 
makeWallLightGeometry(Geometry & verts,Vector3d const &,Vector3d const &,duint numVertices,Vector3f const * posCoords,WallEdge const & leftEdge,WallEdge const & rightEdge,ProjectedTextureData const & tp)1321 static void makeWallLightGeometry(Geometry &verts, Vector3d const &/*topLeft*/, Vector3d const &/*bottomRight*/,
1322     duint numVertices, Vector3f const *posCoords, WallEdge const &leftEdge, WallEdge const &rightEdge,
1323     ProjectedTextureData const &tp)
1324 {
1325     DENG2_ASSERT(posCoords);
1326 
1327     Vector4f const colorClamped = tp.color.min(Vector4f(1, 1, 1, 1)).max(Vector4f(0, 0, 0, 0));
1328     for (duint i = 0; i < numVertices; ++i)
1329     {
1330         verts.color[i] = colorClamped;
1331     }
1332 
1333     verts.tex[1].x = verts.tex[0].x = tp.topLeft.x;
1334     verts.tex[1].y = verts.tex[3].y = tp.topLeft.y;
1335     verts.tex[3].x = verts.tex[2].x = tp.bottomRight.x;
1336     verts.tex[2].y = verts.tex[0].y = tp.bottomRight.y;
1337 
1338     // If either edge has divisions - make two trifans.
1339     if (leftEdge.divisionCount() || rightEdge.divisionCount())
1340     {
1341         // Need to swap indices around into fans set the position
1342         // of the division vertices, interpolate texcoords and color.
1343 
1344         Vector3f origPosCoords[4]; std::memcpy(origPosCoords, posCoords,   sizeof(Vector3f) * 4);
1345         Vector2f origTexCoords[4]; std::memcpy(origTexCoords, verts.tex,   sizeof(Vector2f) * 4);
1346         Vector4f origColors[4];    std::memcpy(origColors,    verts.color, sizeof(Vector4f) * 4);
1347 
1348         R_DivVerts     (verts.pos,   origPosCoords, leftEdge, rightEdge);
1349         R_DivTexCoords (verts.tex,   origTexCoords, leftEdge, rightEdge);
1350         R_DivVertColors(verts.color, origColors,    leftEdge, rightEdge);
1351     }
1352     else
1353     {
1354         std::memcpy(verts.pos, posCoords, sizeof(Vector3f) * numVertices);
1355     }
1356 }
1357 
averageLuminosity(Vector4f const * rgbaValues,duint count)1358 static dfloat averageLuminosity(Vector4f const *rgbaValues, duint count)
1359 {
1360     DENG2_ASSERT(rgbaValues);
1361     dfloat avg = 0;
1362     for (duint i = 0; i < count; ++i)
1363     {
1364         Vector4f const &color = rgbaValues[i];
1365         avg += color.x + color.y + color.z;
1366     }
1367     return avg / (count * 3);
1368 }
1369 
1370 struct rendworldpoly_params_t
1371 {
1372     bool            skyMasked;
1373     blendmode_t     blendMode;
1374     Vector3d const *topLeft;
1375     Vector3d const *bottomRight;
1376     Vector2f const *materialOrigin;
1377     Vector2f const *materialScale;
1378     dfloat          alpha;
1379     dfloat          surfaceLuminosityDeltas[2];
1380     Vector3f const *surfaceColor;
1381     Matrix3f const *surfaceTangentMatrix;
1382 
1383     duint           lightListIdx;   ///< List of lights that affect this poly.
1384     duint           shadowListIdx;  ///< List of shadows that affect this poly.
1385     dfloat          glowing;
1386     bool            forceOpaque;
1387     MapElement     *mapElement;
1388     dint            geomGroup;
1389 
1390     bool            isWall;
1391 // Wall only:
1392     struct {
1393         coord_t width;
1394         Vector3f const *surfaceColor2;  ///< Secondary color.
1395         WallEdge const *leftEdge;
1396         WallEdge const *rightEdge;
1397     } wall;
1398 };
1399 
renderWorldPoly(Vector3f const * rvertices,duint numVertices,rendworldpoly_params_t const & p,MaterialAnimator & matAnimator)1400 static bool renderWorldPoly(Vector3f const *rvertices, duint numVertices,
1401     rendworldpoly_params_t const &p, MaterialAnimator &matAnimator)
1402 {
1403     using Parm = DrawList::PrimitiveParams;
1404 
1405     DENG2_ASSERT(rvertices);
1406 
1407     static DrawList::Indices indices;
1408 
1409     // Ensure we've up to date info about the material.
1410     matAnimator.prepare();
1411 
1412     // Sky-masked polys (flats and walls)
1413     bool const skyMaskedMaterial        = (p.skyMasked || (matAnimator.material().isSkyMasked()));
1414 
1415     // Masked polys (walls) get a special treatment (=> vissprite).
1416     bool const drawAsVisSprite          = (!p.forceOpaque && !p.skyMasked && (!matAnimator.isOpaque() || p.alpha < 1 || p.blendMode > 0));
1417 
1418     // Map RTU configuration.
1419     GLTextureUnit const *layer0RTU      = (!p.skyMasked)? &matAnimator.texUnit(MaterialAnimator::TU_LAYER0) : nullptr;
1420     GLTextureUnit const *layer0InterRTU = (!p.skyMasked && !drawAsVisSprite && matAnimator.texUnit(MaterialAnimator::TU_LAYER0_INTER).hasTexture())? &matAnimator.texUnit(MaterialAnimator::TU_LAYER0_INTER) : nullptr;
1421     GLTextureUnit const *detailRTU      = (r_detail && !p.skyMasked && matAnimator.texUnit(MaterialAnimator::TU_DETAIL).hasTexture())? &matAnimator.texUnit(MaterialAnimator::TU_DETAIL) : nullptr;
1422     GLTextureUnit const *detailInterRTU = (r_detail && !p.skyMasked && matAnimator.texUnit(MaterialAnimator::TU_DETAIL_INTER).hasTexture())? &matAnimator.texUnit(MaterialAnimator::TU_DETAIL_INTER) : nullptr;
1423 
1424     GLTextureUnit const *shineRTU       = (::useShinySurfaces && !skyMaskedMaterial && !drawAsVisSprite && matAnimator.texUnit(MaterialAnimator::TU_SHINE).hasTexture())? &matAnimator.texUnit(MaterialAnimator::TU_SHINE) : nullptr;
1425     GLTextureUnit const *shineMaskRTU   = (::useShinySurfaces && !skyMaskedMaterial && !drawAsVisSprite && matAnimator.texUnit(MaterialAnimator::TU_SHINE).hasTexture() && matAnimator.texUnit(MaterialAnimator::TU_SHINE_MASK).hasTexture())? &matAnimator.texUnit(MaterialAnimator::TU_SHINE_MASK) : nullptr;
1426 
1427     // Make surface geometry (position, primary texture, inter texture and color coords).
1428     Geometry verts;
1429     bool const mustSubdivide = (p.isWall && (p.wall.leftEdge->divisionCount() || p.wall.rightEdge->divisionCount()));
1430     duint const numVerts     = (mustSubdivide && !drawAsVisSprite?   3 + p.wall.leftEdge ->divisionCount()
1431                                                                    + 3 + p.wall.rightEdge->divisionCount()
1432                                                                  : numVertices);
1433     // Allocate vertices from the pools.
1434     verts.pos   = R_AllocRendVertices(numVerts);
1435     verts.color = !skyMaskedMaterial? R_AllocRendColors   (numVerts) : nullptr;
1436     verts.tex   = layer0RTU         ? R_AllocRendTexCoords(numVerts) : nullptr;
1437     verts.tex2  = layer0InterRTU    ? R_AllocRendTexCoords(numVerts) : nullptr;
1438     if (p.isWall)
1439     {
1440         makeWallGeometry(verts, numVertices, rvertices, *p.topLeft, *p.bottomRight, p.wall.width,
1441                          *p.mapElement, p.geomGroup, *p.surfaceTangentMatrix,
1442                          p.alpha, *p.surfaceColor, p.wall.surfaceColor2, p.glowing, p.surfaceLuminosityDeltas,
1443                          !skyMaskedMaterial);
1444     }
1445     else
1446     {
1447         makeFlatGeometry(verts, numVertices, rvertices, *p.topLeft, *p.bottomRight,
1448                          *p.mapElement, p.geomGroup, *p.surfaceTangentMatrix,
1449                          p.alpha, *p.surfaceColor, p.wall.surfaceColor2, p.glowing, p.surfaceLuminosityDeltas,
1450                          !skyMaskedMaterial);
1451     }
1452 
1453     if (drawAsVisSprite)
1454     {
1455         DENG2_ASSERT(p.isWall);
1456 
1457         // This is needed because all masked polys must be sorted (sprites are masked polys).
1458         // Otherwise there will be artifacts.
1459         Rend_AddMaskedPoly(verts.pos, verts.color, p.wall.width, &matAnimator,
1460                            *p.materialOrigin, p.blendMode, p.lightListIdx, p.glowing);
1461 
1462         R_FreeRendVertices (verts.pos);
1463         R_FreeRendColors   (verts.color);
1464         R_FreeRendTexCoords(verts.tex);
1465         R_FreeRendTexCoords(verts.tex2);
1466 
1467         return false;  // We HAD to use a vissprite, so it MUST not be opaque.
1468     }
1469 
1470     // Skip drawing dynamic light/shadow on surfaces too bright/dark to benefit.
1471     bool useLights  = (p.lightListIdx  && !skyMaskedMaterial && !drawAsVisSprite && p.glowing < 1? true : false);
1472     bool useShadows = (p.shadowListIdx && !skyMaskedMaterial && !drawAsVisSprite && p.glowing < 1? true : false);
1473     if (useLights || useShadows)
1474     {
1475         dfloat const avg = averageLuminosity(verts.color, numVertices);
1476         if (avg > 0.98f)  // Fully bright.
1477         {
1478             useLights = false;  // Skip lights.
1479         }
1480         if (avg < 0.02f)  // Fully dark.
1481         {
1482             useShadows = false;  // Skip shadows.
1483         }
1484     }
1485 
1486     // If multitexturing is enabled and there is at least one dynlight affecting the surface,
1487     // locate the projection data and use it for modulation.
1488     TexModulationData mod;
1489     Vector2f *modTexCoords = nullptr;
1490     if (useLights && Rend_IsMTexLights())
1491     {
1492         ClientApp::renderSystem().forAllSurfaceProjections(p.lightListIdx, [&mod] (ProjectedTextureData const &dyn)
1493         {
1494             mod.texture     = dyn.texture;
1495             mod.color       = dyn.color;
1496             mod.topLeft     = dyn.topLeft;
1497             mod.bottomRight = dyn.bottomRight;
1498             return LoopAbort;
1499         });
1500 
1501         if (mod.texture)
1502         {
1503             // Prepare modulation texture coordinates.
1504             modTexCoords = R_AllocRendTexCoords(numVerts);
1505             if (p.isWall)
1506             {
1507                 modTexCoords[0] = Vector2f(mod.topLeft.x, mod.bottomRight.y);
1508                 modTexCoords[1] = mod.topLeft;
1509                 modTexCoords[2] = mod.bottomRight;
1510                 modTexCoords[3] = Vector2f(mod.bottomRight.x, mod.topLeft.y);
1511             }
1512             else
1513             {
1514                 for (duint i = 0; i < numVertices; ++i)
1515                 {
1516                     modTexCoords[i] = (( Vector2f(*p.bottomRight) - Vector2f(rvertices[i]) ) / ( Vector2f(*p.bottomRight) - Vector2f(*p.topLeft) ) * mod.topLeft    )
1517                                     + (( Vector2f(rvertices[i]  ) - Vector2f(*p.topLeft  ) ) / ( Vector2f(*p.bottomRight) - Vector2f(*p.topLeft) ) * mod.bottomRight);
1518                 }
1519             }
1520         }
1521     }
1522 
1523     bool hasDynlights = false;
1524     if (useLights)
1525     {
1526         // Write projected lights.
1527         // Multitexturing can be used for the first light.
1528         bool const skipFirst = Rend_IsMTexLights();
1529 
1530         duint numProcessed = 0;
1531         ClientApp::renderSystem().forAllSurfaceProjections(p.lightListIdx,
1532                                            [&p, &mustSubdivide, &rvertices, &numVertices
1533                                            , &skipFirst, &numProcessed] (ProjectedTextureData const &tp)
1534         {
1535             if (!(skipFirst && numProcessed == 0))
1536             {
1537                 // Light texture determines the list to write to.
1538                 DrawListSpec listSpec;
1539                 listSpec.group = LightGeom;
1540                 listSpec.texunits[TU_PRIMARY] = GLTextureUnit(tp.texture, gl::ClampToEdge, gl::ClampToEdge);
1541                 DrawList &lightList = ClientApp::renderSystem().drawLists().find(listSpec);
1542 
1543                 // Make geometry.
1544                 Geometry verts;
1545                 duint const numVerts = mustSubdivide ?   3 + p.wall.leftEdge ->divisionCount()
1546                                                        + 3 + p.wall.rightEdge->divisionCount()
1547                                                      : numVertices;
1548                 // Allocate verts from the pools.
1549                 verts.pos   = R_AllocRendVertices (numVerts);
1550                 verts.color = R_AllocRendColors   (numVerts);
1551                 verts.tex   = R_AllocRendTexCoords(numVerts);
1552                 if (p.isWall)
1553                 {
1554                     makeWallLightGeometry(verts, *p.topLeft, *p.bottomRight,
1555                                           numVertices, rvertices, *p.wall.leftEdge, *p.wall.rightEdge, tp);
1556                 }
1557                 else
1558                 {
1559                     makeFlatLightGeometry(verts, *p.topLeft, *p.bottomRight,
1560                                           numVertices, rvertices, tp);
1561                 }
1562 
1563                 // Write geometry.
1564                 // Walls with edge divisions mean two trifans.
1565                 if (mustSubdivide)
1566                 {
1567                     DENG2_ASSERT(p.isWall);
1568                     duint const numLeftVerts  = 3 + p.wall.leftEdge ->divisionCount();
1569                     duint const numRightVerts = 3 + p.wall.rightEdge->divisionCount();
1570 
1571                     Store &buffer = ClientApp::renderSystem().buffer();
1572                     {
1573                         duint base = buffer.allocateVertices(numRightVerts);
1574                         DrawList::reserveSpace(indices, numRightVerts);
1575                         for (duint i = 0; i < numRightVerts; ++i)
1576                         {
1577                             indices[i] = base + i;
1578                             buffer.posCoords   [indices[i]] = verts.pos[numLeftVerts + i];
1579                             buffer.colorCoords [indices[i]] = (verts.color[numLeftVerts + i] * 255).toVector4ub();
1580                             buffer.texCoords[0][indices[i]] = verts.tex[numLeftVerts + i];
1581                         }
1582                         lightList.write(buffer, indices.constData(), numRightVerts, gl::TriangleFan);
1583                     }
1584                     {
1585                         duint base = buffer.allocateVertices(numLeftVerts);
1586                         DrawList::reserveSpace(indices, numLeftVerts);
1587                         for (duint i = 0; i < numLeftVerts; ++i)
1588                         {
1589                             indices[i] = base + i;
1590                             buffer.posCoords   [indices[i]] = verts.pos[i];
1591                             buffer.colorCoords [indices[i]] = (verts.color[i] * 255).toVector4ub();
1592                             buffer.texCoords[0][indices[i]] = verts.tex[i];
1593                         }
1594                         lightList.write(buffer, indices.constData(), numLeftVerts, gl::TriangleFan);
1595                     }
1596                 }
1597                 else
1598                 {
1599                     Store &buffer = ClientApp::renderSystem().buffer();
1600                     duint base = buffer.allocateVertices(numVertices);
1601                     DrawList::reserveSpace(indices, numVertices);
1602                     for (duint i = 0; i < numVertices; ++i)
1603                     {
1604                         indices[i] = base + i;
1605                         buffer.posCoords   [indices[i]] = verts.pos[i];
1606                         buffer.colorCoords [indices[i]] = (verts.color[i] * 255).toVector4ub();
1607                         buffer.texCoords[0][indices[i]] = verts.tex[i];
1608                     }
1609                     lightList.write(buffer, indices.constData(), numVertices, p.isWall? gl::TriangleStrip : gl::TriangleFan);
1610                 }
1611 
1612                 // We're done with the geometry.
1613                 R_FreeRendVertices (verts.pos);
1614                 R_FreeRendColors   (verts.color);
1615                 R_FreeRendTexCoords(verts.tex);
1616             }
1617             numProcessed += 1;
1618             return LoopContinue;
1619         });
1620 
1621         hasDynlights = (numProcessed > duint(skipFirst? 1 : 0));
1622     }
1623 
1624     if (useShadows)
1625     {
1626         // Write projected shadows.
1627         // All shadows use the same texture (so use the same list).
1628         DrawListSpec listSpec;
1629         listSpec.group = ShadowGeom;
1630         listSpec.texunits[TU_PRIMARY] = GLTextureUnit(GL_PrepareLSTexture(LST_DYNAMIC), gl::ClampToEdge, gl::ClampToEdge);
1631         DrawList &shadowList = ClientApp::renderSystem().drawLists().find(listSpec);
1632 
1633         ClientApp::renderSystem().forAllSurfaceProjections(p.shadowListIdx,
1634                                            [&p, &mustSubdivide, &rvertices, &numVertices, &shadowList]
1635                                            (ProjectedTextureData const &tp)
1636         {
1637             // Make geometry.
1638             Geometry verts;
1639             duint const numVerts = mustSubdivide ?   3 + p.wall.leftEdge ->divisionCount()
1640                                                    + 3 + p.wall.rightEdge->divisionCount()
1641                                                  : numVertices;
1642             // Allocate verts from the pools.
1643             verts.pos   = R_AllocRendVertices (numVerts);
1644             verts.color = R_AllocRendColors   (numVerts);
1645             verts.tex   = R_AllocRendTexCoords(numVerts);
1646             if (p.isWall)
1647             {
1648                 makeWallShadowGeometry(verts, *p.topLeft, *p.bottomRight,
1649                                        numVertices, rvertices, *p.wall.leftEdge, *p.wall.rightEdge, tp);
1650             }
1651             else
1652             {
1653                 makeFlatShadowGeometry(verts, *p.topLeft, *p.bottomRight,
1654                                        numVertices, rvertices, tp);
1655             }
1656 
1657             // Write geometry.
1658             // Walls with edge divisions mean two trifans.
1659             if (mustSubdivide)
1660             {
1661                 DENG2_ASSERT(p.isWall);
1662                 duint const numLeftVerts  = 3 + p.wall.leftEdge ->divisionCount();
1663                 duint const numRightVerts = 3 + p.wall.rightEdge->divisionCount();
1664 
1665                 Store &buffer = ClientApp::renderSystem().buffer();
1666                 {
1667                     duint base = buffer.allocateVertices(numRightVerts);
1668                     DrawList::reserveSpace(indices, numRightVerts);
1669                     for (duint i = 0; i < numRightVerts; ++i)
1670                     {
1671                         indices[i] = base + i;
1672                         buffer.posCoords   [indices[i]] = verts.pos[numLeftVerts + i];
1673                         buffer.colorCoords [indices[i]] = (verts.color[numLeftVerts + i] * 255).toVector4ub();
1674                         buffer.texCoords[0][indices[i]] = verts.tex[numLeftVerts + i];
1675                     }
1676                     shadowList.write(buffer, indices.constData(), numRightVerts, gl::TriangleFan);
1677                 }
1678                 {
1679                     duint base = buffer.allocateVertices(numLeftVerts);
1680                     DrawList::reserveSpace(indices, numLeftVerts);
1681                     for (duint i = 0; i < numLeftVerts; ++i)
1682                     {
1683                         indices[i] = base + i;
1684                         buffer.posCoords   [indices[i]] = verts.pos[i];
1685                         buffer.colorCoords [indices[i]] = (verts.color[i] * 255).toVector4ub();
1686                         buffer.texCoords[0][indices[i]] = verts.tex[i];
1687                     }
1688                     shadowList.write(buffer, indices.constData(), numLeftVerts, gl::TriangleFan);
1689                 }
1690             }
1691             else
1692             {
1693                 Store &buffer = ClientApp::renderSystem().buffer();
1694                 duint base = buffer.allocateVertices(numVerts);
1695                 DrawList::reserveSpace(indices, numVerts);
1696                 for (duint i = 0; i < numVerts; ++i)
1697                 {
1698                     indices[i] = base + i;
1699                     buffer.posCoords   [indices[i]] = verts.pos[i];
1700                     buffer.colorCoords [indices[i]] = (verts.color[i] * 255).toVector4ub();
1701                     buffer.texCoords[0][indices[i]] = verts.tex[i];
1702                 }
1703                 shadowList.write(buffer, indices.constData(), numVerts, p.isWall ? gl::TriangleStrip : gl::TriangleFan);
1704             }
1705 
1706             // We're done with the geometry.
1707             R_FreeRendVertices (verts.pos);
1708             R_FreeRendColors   (verts.color);
1709             R_FreeRendTexCoords(verts.tex);
1710 
1711             return LoopContinue;
1712         });
1713     }
1714 
1715     // Write geometry.
1716     // Walls with edge divisions mean two trifans.
1717     if (mustSubdivide)
1718     {
1719         DENG2_ASSERT(p.isWall);
1720         duint const numLeftVerts  = 3 + p.wall.leftEdge ->divisionCount();
1721         duint const numRightVerts = 3 + p.wall.rightEdge->divisionCount();
1722 
1723         // Need to swap indices around into fans set the position of the division
1724         // vertices, interpolate texcoords and color.
1725 
1726         Vector3f origPos[4]; std::memcpy(origPos, verts.pos, sizeof(origPos));
1727         R_DivVerts(verts.pos, origPos, *p.wall.leftEdge, *p.wall.rightEdge);
1728 
1729         if (verts.color)
1730         {
1731             Vector4f orig[4]; std::memcpy(orig, verts.color, sizeof(orig));
1732             R_DivVertColors(verts.color, orig, *p.wall.leftEdge, *p.wall.rightEdge);
1733         }
1734         if (verts.tex)
1735         {
1736             Vector2f orig[4]; std::memcpy(orig, verts.tex, sizeof(orig));
1737             R_DivTexCoords(verts.tex, orig, *p.wall.leftEdge, *p.wall.rightEdge);
1738         }
1739         if (verts.tex2)
1740         {
1741             Vector2f orig[4]; std::memcpy(orig, verts.tex2, sizeof(orig));
1742             R_DivTexCoords(verts.tex2, orig, *p.wall.leftEdge, *p.wall.rightEdge);
1743         }
1744         if (modTexCoords)
1745         {
1746             Vector2f orig[4]; std::memcpy(orig, modTexCoords, sizeof(orig));
1747             R_DivTexCoords(modTexCoords, orig, *p.wall.leftEdge, *p.wall.rightEdge);
1748         }
1749 
1750         if (p.skyMasked)
1751         {
1752             DrawList &skyMaskList = ClientApp::renderSystem().drawLists().find(DrawListSpec(SkyMaskGeom));
1753 
1754             Store &buffer = ClientApp::renderSystem().buffer();
1755             {
1756                 duint base = buffer.allocateVertices(numRightVerts);
1757                 DrawList::reserveSpace(indices, numRightVerts);
1758                 for (duint i = 0; i < numRightVerts; ++i)
1759                 {
1760                     indices[i] = base + i;
1761                     buffer.posCoords[indices[i]] = verts.pos[numLeftVerts + i];
1762                 }
1763                 skyMaskList.write(buffer, indices.constData(), numRightVerts, gl::TriangleFan);
1764             }
1765             {
1766                 duint base = buffer.allocateVertices(numLeftVerts);
1767                 DrawList::reserveSpace(indices, numLeftVerts);
1768                 for (duint i = 0; i < numLeftVerts; ++i)
1769                 {
1770                     indices[i] = base + i;
1771                     buffer.posCoords[indices[i]] = verts.pos[i];
1772                 }
1773                 skyMaskList.write(buffer, indices.constData(), numLeftVerts, gl::TriangleFan);
1774             }
1775         }
1776         else
1777         {
1778             DrawListSpec listSpec((mod.texture || hasDynlights)? LitGeom : UnlitGeom);
1779             if (layer0RTU)
1780             {
1781                 listSpec.texunits[TU_PRIMARY] = *layer0RTU;
1782                 if (p.materialOrigin)
1783                 {
1784                     listSpec.texunits[TU_PRIMARY].offset += *p.materialOrigin;
1785                 }
1786                 if (p.materialScale)
1787                 {
1788                     listSpec.texunits[TU_PRIMARY].scale  *= *p.materialScale;
1789                     listSpec.texunits[TU_PRIMARY].offset *= *p.materialScale;
1790                 }
1791             }
1792             if (layer0InterRTU)
1793             {
1794                 listSpec.texunits[TU_INTER] = *layer0InterRTU;
1795                 if (p.materialOrigin)
1796                 {
1797                     listSpec.texunits[TU_INTER].offset += *p.materialOrigin;
1798                 }
1799                 if (p.materialScale)
1800                 {
1801                     listSpec.texunits[TU_INTER].scale  *= *p.materialScale;
1802                     listSpec.texunits[TU_INTER].offset *= *p.materialScale;
1803                 }
1804             }
1805             if (detailRTU)
1806             {
1807                 listSpec.texunits[TU_PRIMARY_DETAIL] = *detailRTU;
1808                 if (p.materialOrigin)
1809                 {
1810                     listSpec.texunits[TU_PRIMARY_DETAIL].offset += *p.materialOrigin;
1811                 }
1812             }
1813             if (detailInterRTU)
1814             {
1815                 listSpec.texunits[TU_INTER_DETAIL] = *detailInterRTU;
1816                 if (p.materialOrigin)
1817                 {
1818                     listSpec.texunits[TU_INTER_DETAIL].offset += *p.materialOrigin;
1819                 }
1820             }
1821             DrawList &drawList = ClientApp::renderSystem().drawLists().find(listSpec);
1822             // Is the geometry lit?
1823             Parm::Flags primFlags;
1824             //bool oneLight   = false;
1825             //bool manyLights = false;
1826             if (mod.texture && !hasDynlights)
1827             {
1828                 primFlags |= Parm::OneLight; //oneLight = true;  // Using modulation.
1829             }
1830             else if (mod.texture || hasDynlights)
1831             {
1832                 //manyLights = true;
1833                 primFlags |= Parm::ManyLights;
1834             }
1835 
1836             Store &buffer = ClientApp::renderSystem().buffer();
1837             {
1838                 duint base = buffer.allocateVertices(numRightVerts);
1839                 DrawList::reserveSpace(indices, numRightVerts);
1840                 static Vector4ub const white(255, 255, 255, 255);
1841                 for (duint i = 0; i < numRightVerts; ++i)
1842                 {
1843                     indices[i] = base + i;
1844                     buffer.posCoords[indices[i]] = verts.pos[numLeftVerts + i];
1845                     if (verts.color)
1846                     {
1847                         buffer.colorCoords[indices[i]] = (verts.color[numLeftVerts + i] * 255).toVector4ub();
1848                     }
1849                     else
1850                     {
1851                         buffer.colorCoords[indices[i]] = white;
1852                     }
1853                     if (verts.tex)
1854                     {
1855                         buffer.texCoords[0][indices[i]] = verts.tex[numLeftVerts + i];
1856                     }
1857                     if (verts.tex2)
1858                     {
1859                         buffer.texCoords[1][indices[i]] = verts.tex2[numLeftVerts + i];
1860                     }
1861                     if ((primFlags & (Parm::OneLight | Parm::ManyLights)) && Rend_IsMTexLights())
1862                     {
1863                         DENG2_ASSERT(modTexCoords);
1864                         buffer.modCoords[indices[i]] = modTexCoords[numLeftVerts + i];
1865                     }
1866                 }
1867                 drawList.write(buffer, indices.constData(), numRightVerts,
1868                                Parm(gl::TriangleFan,
1869                                     listSpec.unit(TU_PRIMARY       ).scale,
1870                                     listSpec.unit(TU_PRIMARY       ).offset,
1871                                     listSpec.unit(TU_PRIMARY_DETAIL).scale,
1872                                     listSpec.unit(TU_PRIMARY_DETAIL).offset,
1873                                     primFlags, BM_NORMAL,
1874                                     mod.texture, mod.color));
1875             }
1876             {
1877                 duint base = buffer.allocateVertices(numLeftVerts);
1878                 DrawList::reserveSpace(indices, numLeftVerts);
1879                 static Vector4ub const white(255, 255, 255, 255);
1880                 for (duint i = 0; i < numLeftVerts; ++i)
1881                 {
1882                     indices[i] = base + i;
1883                     buffer.posCoords[indices[i]] = verts.pos[i];
1884                     if (verts.color)
1885                     {
1886                         buffer.colorCoords[indices[i]] = (verts.color[i] * 255).toVector4ub();
1887                     }
1888                     else
1889                     {
1890                         buffer.colorCoords[indices[i]] = white;
1891                     }
1892                     if (verts.tex)
1893                     {
1894                         buffer.texCoords[0][indices[i]] = verts.tex[i];
1895                     }
1896                     if (verts.tex2)
1897                     {
1898                         buffer.texCoords[1][indices[i]] = verts.tex2[i];
1899                     }
1900                     if ((primFlags & (Parm::OneLight | Parm::ManyLights)) && Rend_IsMTexLights())
1901                     {
1902                         DENG2_ASSERT(modTexCoords);
1903                         buffer.modCoords[indices[i]] = modTexCoords[i];
1904                     }
1905                 }
1906                 drawList.write(buffer, indices.constData(), numLeftVerts,
1907                                Parm(gl::TriangleFan,
1908                                     listSpec.unit(TU_PRIMARY       ).scale,
1909                                     listSpec.unit(TU_PRIMARY       ).offset,
1910                                     listSpec.unit(TU_PRIMARY_DETAIL).scale,
1911                                     listSpec.unit(TU_PRIMARY_DETAIL).offset,
1912                                     primFlags, BM_NORMAL,
1913                                     mod.texture, mod.color));
1914             }
1915         }
1916     }
1917     else
1918     {
1919         if (p.skyMasked)
1920         {
1921             Store &buffer = ClientApp::renderSystem().buffer();
1922             duint base = buffer.allocateVertices(numVerts);
1923             DrawList::reserveSpace(indices, numVerts);
1924             for (duint i = 0; i < numVerts; ++i)
1925             {
1926                 indices[i] = base + i;
1927                 buffer.posCoords[indices[i]] = verts.pos[i];
1928             }
1929             ClientApp::renderSystem()
1930                 .drawLists().find(DrawListSpec(SkyMaskGeom))
1931                     .write(buffer, indices.constData(), numVerts, p.isWall? gl::TriangleStrip : gl::TriangleFan);
1932         }
1933         else
1934         {
1935             DrawListSpec listSpec((mod.texture || hasDynlights) ? LitGeom : UnlitGeom);
1936             if (layer0RTU)
1937             {
1938                 listSpec.texunits[TU_PRIMARY] = *layer0RTU;
1939                 if (p.materialOrigin)
1940                 {
1941                     listSpec.texunits[TU_PRIMARY].offset += *p.materialOrigin;
1942                 }
1943                 if (p.materialScale)
1944                 {
1945                     listSpec.texunits[TU_PRIMARY].scale  *= *p.materialScale;
1946                     listSpec.texunits[TU_PRIMARY].offset *= *p.materialScale;
1947                 }
1948             }
1949             if (layer0InterRTU)
1950             {
1951                 listSpec.texunits[TU_INTER] = *layer0InterRTU;
1952                 if (p.materialOrigin)
1953                 {
1954                     listSpec.texunits[TU_INTER].offset += *p.materialOrigin;
1955                 }
1956                 if (p.materialScale)
1957                 {
1958                     listSpec.texunits[TU_INTER].scale  *= *p.materialScale;
1959                     listSpec.texunits[TU_INTER].offset *= *p.materialScale;
1960                 }
1961             }
1962             if (detailRTU)
1963             {
1964                 listSpec.texunits[TU_PRIMARY_DETAIL] = *detailRTU;
1965                 if (p.materialOrigin)
1966                 {
1967                     listSpec.texunits[TU_PRIMARY_DETAIL].offset += *p.materialOrigin;
1968                 }
1969             }
1970             if (detailInterRTU)
1971             {
1972                 listSpec.texunits[TU_INTER_DETAIL] = *detailInterRTU;
1973                 if (p.materialOrigin)
1974                 {
1975                     listSpec.texunits[TU_INTER_DETAIL].offset += *p.materialOrigin;
1976                 }
1977             }
1978 
1979             // Is the geometry lit?
1980             Parm::Flags primFlags;
1981             //bool oneLight   = false;
1982             //bool manyLights = false;
1983             if (mod.texture && !hasDynlights)
1984             {
1985                 primFlags |= Parm::OneLight; // Using modulation.
1986             }
1987             else if (mod.texture || hasDynlights)
1988             {
1989                 primFlags |= Parm::ManyLights; //manyLights = true;
1990             }
1991 
1992             Store &buffer = ClientApp::renderSystem().buffer();
1993             duint base = buffer.allocateVertices(numVertices);
1994             DrawList::reserveSpace(indices, numVertices);
1995             static Vector4ub const white(255, 255, 255, 255);
1996             for (duint i = 0; i < numVertices; ++i)
1997             {
1998                 indices[i] = base + i;
1999                 buffer.posCoords[indices[i]] = verts.pos[i];
2000                 if (verts.color)
2001                 {
2002                     buffer.colorCoords[indices[i]] = (verts.color[i] * 255).toVector4ub();
2003                 }
2004                 else
2005                 {
2006                     buffer.colorCoords[indices[i]] = white;
2007                 }
2008                 if (verts.tex)
2009                 {
2010                     buffer.texCoords[0][indices[i]] = verts.tex[i];
2011                 }
2012                 if (verts.tex2)
2013                 {
2014                     buffer.texCoords[1][indices[i]] = verts.tex2[i];
2015                 }
2016                 if ((primFlags & (Parm::OneLight | Parm::ManyLights)) && Rend_IsMTexLights())
2017                 {
2018                     DENG2_ASSERT(modTexCoords);
2019                     buffer.modCoords[indices[i]] = modTexCoords[i];
2020                 }
2021             }
2022             ClientApp::renderSystem()
2023                 .drawLists().find(listSpec)
2024                     .write(buffer, indices.constData(), numVertices,
2025                            Parm(p.isWall? gl::TriangleStrip  : gl::TriangleFan,
2026                                 listSpec.unit(TU_PRIMARY       ).scale,
2027                                 listSpec.unit(TU_PRIMARY       ).offset,
2028                                 listSpec.unit(TU_PRIMARY_DETAIL).scale,
2029                                 listSpec.unit(TU_PRIMARY_DETAIL).offset,
2030                                 primFlags, BM_NORMAL,
2031                                 mod.texture, mod.color));
2032         }
2033     }
2034 
2035     if (shineRTU)
2036     {
2037         // Make shine geometry.
2038         // Surface shine geometry (primary texture and color coords).
2039         // Use the surface geometry for position coords.
2040         // Use the surface texture coords with the mask.
2041         Geometry shineVerts = Geometry();
2042         {
2043             Vector3f const &shineColor = matAnimator.shineMinColor();  // Shine strength.
2044             dfloat const shineOpacity  = shineRTU->opacity;
2045 
2046             // Allocate vertices from the pools.
2047             shineVerts.color = R_AllocRendColors(numVerts);
2048             shineVerts.tex   = R_AllocRendTexCoords(numVerts);
2049 
2050             if (p.isWall)
2051             {
2052                 makeWallShineGeometry(shineVerts, numVertices, rvertices, verts, p.wall.width,
2053                                       shineColor, shineOpacity);
2054             }
2055             else
2056             {
2057                 makeFlatShineGeometry(shineVerts, numVertices, rvertices, verts,
2058                                       shineColor, shineOpacity);
2059             }
2060         }
2061 
2062         // Write shine geometry.
2063         DrawListSpec listSpec(ShineGeom);
2064         listSpec.texunits[TU_PRIMARY] = *shineRTU;
2065         if (shineMaskRTU)
2066         {
2067             listSpec.texunits[TU_INTER] = *shineMaskRTU;
2068             if (p.materialOrigin)
2069             {
2070                 listSpec.texunits[TU_INTER].offset += *p.materialOrigin;
2071             }
2072             if (p.materialScale)
2073             {
2074                 listSpec.texunits[TU_INTER].scale  *= *p.materialScale;
2075                 listSpec.texunits[TU_INTER].offset *= *p.materialScale;
2076             }
2077         }
2078         DrawList &shineList = ClientApp::renderSystem().drawLists().find(listSpec);
2079 
2080         Parm shineParams(gl::TriangleFan,
2081                          listSpec.unit(TU_INTER).scale,
2082                          listSpec.unit(TU_INTER).offset,
2083                          Vector2f(1, 1),
2084                          Vector2f(0, 0),
2085                          Parm::Unlit,
2086                          matAnimator.shineBlendMode());
2087 
2088         // Walls with edge divisions mean two trifans.
2089         if (mustSubdivide)
2090         {
2091             DENG2_ASSERT(p.isWall);
2092             duint const numLeftVerts  = 3 + p.wall.leftEdge ->divisionCount();
2093             duint const numRightVerts = 3 + p.wall.rightEdge->divisionCount();
2094 
2095             {
2096                 Vector2f orig[4]; std::memcpy(orig, shineVerts.tex, sizeof(orig));
2097                 R_DivTexCoords(shineVerts.tex, orig, *p.wall.leftEdge, *p.wall.rightEdge);
2098             }
2099             {
2100                 Vector4f orig[4]; std::memcpy(orig, shineVerts.color, sizeof(orig));
2101                 R_DivVertColors(shineVerts.color, orig, *p.wall.leftEdge, *p.wall.rightEdge);
2102             }
2103 
2104             Store &buffer = ClientApp::renderSystem().buffer();
2105             {
2106                 duint base = buffer.allocateVertices(numRightVerts);
2107                 DrawList::reserveSpace(indices, numRightVerts);
2108                 for (duint i = 0; i < numRightVerts; ++i)
2109                 {
2110                     indices[i] = base + i;
2111                     buffer.posCoords   [indices[i]] = verts.pos[numLeftVerts + i];
2112                     buffer.colorCoords [indices[i]] = (shineVerts.color[numLeftVerts + i] * 255).toVector4ub();
2113                     buffer.texCoords[0][indices[i]] = shineVerts.tex[numLeftVerts + i];
2114                     if (shineMaskRTU)
2115                     {
2116                         buffer.texCoords[1][indices[i]] = verts.tex[numLeftVerts + i];
2117                     }
2118                 }
2119                 shineList.write(buffer, indices.constData(), numRightVerts, shineParams);
2120             }
2121             {
2122                 duint base = buffer.allocateVertices(numLeftVerts);
2123                 DrawList::reserveSpace(indices, numLeftVerts);
2124                 for (duint i = 0; i < numLeftVerts; ++i)
2125                 {
2126                     indices[i] = base + i;
2127                     buffer.posCoords   [indices[i]] = verts.pos[i];
2128                     buffer.colorCoords [indices[i]] = (shineVerts.color[i] * 255).toVector4ub();
2129                     buffer.texCoords[0][indices[i]] = shineVerts.tex[i];
2130                     if (shineMaskRTU)
2131                     {
2132                         buffer.texCoords[1][indices[i]] = verts.tex[i];
2133                     }
2134                 }
2135                 shineList.write(buffer, indices.constData(), numLeftVerts, shineParams);
2136             }
2137         }
2138         else
2139         {
2140             Store &buffer = ClientApp::renderSystem().buffer();
2141             duint base = buffer.allocateVertices(numVertices);
2142             DrawList::reserveSpace(indices, numVertices);
2143             for (duint i = 0; i < numVertices; ++i)
2144             {
2145                 indices[i] = base + i;
2146                 buffer.posCoords   [indices[i]] = verts.pos[i];
2147                 buffer.colorCoords [indices[i]] = (shineVerts.color[i] * 255).toVector4ub();
2148                 buffer.texCoords[0][indices[i]] = shineVerts.tex[i];
2149                 if (shineMaskRTU)
2150                 {
2151                     buffer.texCoords[1][indices[i]] = verts.tex[i];
2152                 }
2153             }
2154             shineParams.type = p.isWall? gl::TriangleStrip : gl::TriangleFan;
2155             shineList.write(buffer, indices.constData(), numVertices, shineParams);
2156         }
2157 
2158         // We're done with the shine geometry.
2159         R_FreeRendColors   (shineVerts.color);
2160         R_FreeRendTexCoords(shineVerts.tex);
2161     }
2162 
2163     // We're done with the geometry.
2164     R_FreeRendTexCoords(modTexCoords);
2165 
2166     R_FreeRendVertices (verts.pos);
2167     R_FreeRendColors   (verts.color);
2168     R_FreeRendTexCoords(verts.tex);
2169     R_FreeRendTexCoords(verts.tex2);
2170 
2171     return (p.forceOpaque || skyMaskedMaterial
2172             || !(p.alpha < 1 || !matAnimator.isOpaque() || p.blendMode > 0));
2173 }
2174 
lightmapForSurface(Surface const & surface)2175 static Lumobj::LightmapSemantic lightmapForSurface(Surface const &surface)
2176 {
2177     if (surface.parent().type() == DMU_SIDE) return Lumobj::Side;
2178     // Must be a plane then.
2179     auto const &plane = surface.parent().as<Plane>();
2180     return plane.isSectorFloor() ? Lumobj::Down : Lumobj::Up;
2181 }
2182 
prepareLightmap(ClientTexture * tex=nullptr)2183 static DGLuint prepareLightmap(ClientTexture *tex = nullptr)
2184 {
2185     if (tex)
2186     {
2187         if (TextureVariant *variant = tex->prepareVariant(Rend_MapSurfaceLightmapTextureSpec()))
2188         {
2189             return variant->glName();
2190         }
2191         // Dang...
2192     }
2193     // Prepare the default/fallback lightmap instead.
2194     return GL_PrepareLSTexture(LST_DYNAMIC);
2195 }
2196 
projectDynlight(Vector3d const & topLeft,Vector3d const & bottomRight,Lumobj const & lum,Surface const & surface,dfloat blendFactor,ProjectedTextureData & projected)2197 static bool projectDynlight(Vector3d const &topLeft, Vector3d const &bottomRight,
2198     Lumobj const &lum, Surface const &surface, dfloat blendFactor,
2199     ProjectedTextureData &projected)
2200 {
2201     if (blendFactor < OMNILIGHT_SURFACE_LUMINOSITY_ATTRIBUTION_MIN)
2202         return false;
2203 
2204     // Has this already been occluded?
2205     if (R_ViewerLumobjIsHidden(lum.indexInMap()))
2206         return false;
2207 
2208     // No lightmap texture?
2209     DGLuint tex = prepareLightmap(lum.lightmap(lightmapForSurface(surface)));
2210     if (!tex) return false;
2211 
2212     Vector3d lumCenter = lum.origin();
2213     lumCenter.z += lum.zOffset();
2214 
2215     // On the right side?
2216     Vector3d topLeftToLum = topLeft - lumCenter;
2217     if (topLeftToLum.dot(surface.tangentMatrix().column(2)) > 0.f)
2218         return false;
2219 
2220     // Calculate 3D distance between surface and lumobj.
2221     Vector3d pointOnPlane = R_ClosestPointOnPlane(surface.tangentMatrix().column(2)/*normal*/,
2222                                                   topLeft, lumCenter);
2223 
2224     coord_t distToLum = (lumCenter - pointOnPlane).length();
2225     if (distToLum <= 0 || distToLum > lum.radius())
2226         return false;
2227 
2228     // Calculate the final surface light attribution factor.
2229     dfloat luma = 1.5f - 1.5f * distToLum / lum.radius();
2230 
2231     // Fade out as distance from viewer increases.
2232     luma *= lum.attenuation(R_ViewerLumobjDistance(lum.indexInMap()));
2233 
2234     // Would this be seen?
2235     if (luma * blendFactor < OMNILIGHT_SURFACE_LUMINOSITY_ATTRIBUTION_MIN)
2236         return false;
2237 
2238     // Project, counteracting aspect correction slightly.
2239     Vector2f s, t;
2240     dfloat const scale = 1.0f / ((2.f * lum.radius()) - distToLum);
2241     if (!R_GenerateTexCoords(s, t, pointOnPlane, scale, scale * 1.08f,
2242                             topLeft, bottomRight, surface.tangentMatrix()))
2243         return false;
2244 
2245     projected = {};
2246     projected.texture     = tex;
2247     projected.topLeft     = Vector2f(s[0], t[0]);
2248     projected.bottomRight = Vector2f(s[1], t[1]);
2249     projected.color       = Vector4f(Rend_LuminousColor(lum.color(), luma), blendFactor);
2250 
2251     return true;
2252 }
2253 
projectPlaneGlow(Vector3d const & topLeft,Vector3d const & bottomRight,Plane const & plane,Vector3d const & pointOnPlane,dfloat blendFactor,ProjectedTextureData & projected)2254 static bool projectPlaneGlow(Vector3d const &topLeft, Vector3d const &bottomRight,
2255     Plane const &plane, Vector3d const &pointOnPlane, dfloat blendFactor,
2256     ProjectedTextureData &projected)
2257 {
2258     if (blendFactor < OMNILIGHT_SURFACE_LUMINOSITY_ATTRIBUTION_MIN)
2259         return false;
2260 
2261     Vector3f color;
2262     dfloat intensity = plane.surface().glow(color);
2263 
2264     // Is the material glowing at this moment?
2265     if (intensity < .05f)
2266         return false;
2267 
2268     coord_t glowHeight = Rend_PlaneGlowHeight(intensity);
2269     if (glowHeight < 2) return false;  // Not too small!
2270 
2271     // Calculate coords.
2272     dfloat bottom, top;
2273     if (plane.surface().normal().z < 0)
2274     {
2275         // Cast downward.
2276               bottom = (pointOnPlane.z - topLeft.z) / glowHeight;
2277         top = bottom + (topLeft.z - bottomRight.z) / glowHeight;
2278     }
2279     else
2280     {
2281         // Cast upward.
2282                  top = (bottomRight.z - pointOnPlane.z) / glowHeight;
2283         bottom = top + (topLeft.z - bottomRight.z) / glowHeight;
2284     }
2285 
2286     // Within range on the Z axis?
2287     if (!(bottom <= 1 || top >= 0)) return false;
2288 
2289     de::zap(projected);
2290     projected.texture     = GL_PrepareLSTexture(LST_GRADIENT);
2291     projected.topLeft     = Vector2f(0, bottom);
2292     projected.bottomRight = Vector2f(1, top);
2293     projected.color       = Vector4f(Rend_LuminousColor(color, intensity), blendFactor);
2294     return true;
2295 }
2296 
projectShadow(Vector3d const & topLeft,Vector3d const & bottomRight,mobj_t const & mob,Surface const & surface,dfloat blendFactor,ProjectedTextureData & projected)2297 static bool projectShadow(Vector3d const &topLeft, Vector3d const &bottomRight,
2298     mobj_t const &mob, Surface const &surface, dfloat blendFactor,
2299     ProjectedTextureData &projected)
2300 {
2301     static Vector3f const black;  // shadows are black
2302 
2303     coord_t mobOrigin[3];
2304     Mobj_OriginSmoothed(const_cast<mobj_t *>(&mob), mobOrigin);
2305 
2306     // Is this too far?
2307     coord_t distanceFromViewer = 0;
2308     if (shadowMaxDistance > 0)
2309     {
2310         distanceFromViewer = Rend_PointDist2D(mobOrigin);
2311         if (distanceFromViewer > shadowMaxDistance)
2312             return false;
2313     }
2314 
2315     dfloat shadowStrength = Mobj_ShadowStrength(mob) * ::shadowFactor;
2316     if (fogParams.usingFog) shadowStrength /= 2;
2317     if (shadowStrength <= 0) return false;
2318 
2319     coord_t shadowRadius = Mobj_ShadowRadius(mob);
2320     if (shadowRadius > ::shadowMaxRadius)
2321         shadowRadius = ::shadowMaxRadius;
2322     if (shadowRadius <= 0) return false;
2323 
2324     mobOrigin[2] -= mob.floorClip;
2325     if (mob.ddFlags & DDMF_BOB)
2326         mobOrigin[2] -= Mobj_BobOffset(mob);
2327 
2328     coord_t mobHeight = mob.height;
2329     if (!mobHeight) mobHeight = 1;
2330 
2331     // If this were a light this is where we would check whether the origin is on
2332     // the right side of the surface. However this is a shadow and light is moving
2333     // in the opposite direction (inward toward the mobj's origin), therefore this
2334     // has "volume/depth".
2335 
2336     // Calculate 3D distance between surface and mobj.
2337     Vector3d point = R_ClosestPointOnPlane(surface.tangentMatrix().column(2)/*normal*/,
2338                                            topLeft, mobOrigin);
2339     coord_t distFromSurface = (Vector3d(mobOrigin) - Vector3d(point)).length();
2340 
2341     // Too far above or below the shadowed surface?
2342     if (distFromSurface > mob.height)
2343         return false;
2344     if (mobOrigin[2] + mob.height < point.z)
2345         return false;
2346     if (distFromSurface > shadowRadius)
2347         return false;
2348 
2349     // Calculate the final strength of the shadow's attribution to the surface.
2350     shadowStrength *= 1.5f - 1.5f * distFromSurface / shadowRadius;
2351 
2352     // Fade at half mobj height for smooth fade out when embedded in the surface.
2353     coord_t halfMobjHeight = mobHeight / 2;
2354     if (distFromSurface > halfMobjHeight)
2355     {
2356         shadowStrength *= 1 - (distFromSurface - halfMobjHeight) / (mobHeight - halfMobjHeight);
2357     }
2358 
2359     // Fade when nearing the maximum distance?
2360     shadowStrength *= Rend_ShadowAttenuationFactor(distanceFromViewer);
2361     shadowStrength *= blendFactor;
2362 
2363     // Would this shadow be seen?
2364     if (shadowStrength < SHADOW_SURFACE_LUMINOSITY_ATTRIBUTION_MIN)
2365         return false;
2366 
2367     // Project, counteracting aspect correction slightly.
2368     Vector2f s, t;
2369     dfloat const scale = 1.0f / ((2.f * shadowRadius) - distFromSurface);
2370     if (!R_GenerateTexCoords(s, t, point, scale, scale * 1.08f,
2371                             topLeft, bottomRight, surface.tangentMatrix()))
2372         return false;
2373 
2374     de::zap(projected);
2375     projected.texture     = GL_PrepareLSTexture(LST_DYNAMIC);
2376     projected.topLeft     = Vector2f(s[0], t[0]);
2377     projected.bottomRight = Vector2f(s[1], t[1]);
2378     projected.color       = Vector4f(black, shadowStrength);
2379     return true;
2380 }
2381 
2382 /**
2383  * @pre The coordinates of the given quad must be contained wholly within the subspoce
2384  * specified. This is due to an optimization within the lumobj management which separates
2385  * them by subspace.
2386  */
projectDynamics(Surface const & surface,dfloat glowStrength,Vector3d const & topLeft,Vector3d const & bottomRight,bool noLights,bool noShadows,bool sortLights,duint & lightListIdx,duint & shadowListIdx)2387 static void projectDynamics(Surface const &surface, dfloat glowStrength,
2388     Vector3d const &topLeft, Vector3d const &bottomRight,
2389     bool noLights, bool noShadows, bool sortLights,
2390     duint &lightListIdx, duint &shadowListIdx)
2391 {
2392     DENG2_ASSERT(curSubspace);
2393 
2394     if (levelFullBright) return;
2395     if (glowStrength >= 1) return;
2396 
2397     // lights?
2398     if (!noLights)
2399     {
2400         dfloat const blendFactor = 1;
2401 
2402         if (::useDynLights)
2403         {
2404             // Project all lumobjs affecting the given quad (world space), calculate
2405             // coordinates (in texture space) then store into a new list of projections.
2406             R_ForAllSubspaceLumContacts(*curSubspace, [&topLeft, &bottomRight, &surface
2407                                                       , &blendFactor, &sortLights
2408                                                       , &lightListIdx] (Lumobj &lum)
2409             {
2410                 ProjectedTextureData projected;
2411                 if (projectDynlight(topLeft, bottomRight, lum, surface, blendFactor,
2412                                    projected))
2413                 {
2414                     ClientApp::renderSystem().findSurfaceProjectionList(&lightListIdx, sortLights)
2415                                 << projected;  // a copy is made.
2416                 }
2417                 return LoopContinue;
2418             });
2419         }
2420 
2421         if (::useGlowOnWalls && surface.parent().type() == DMU_SIDE && bottomRight.z < topLeft.z)
2422         {
2423             // Project all plane glows affecting the given quad (world space), calculate
2424             // coordinates (in texture space) then store into a new list of projections.
2425             auto const &subsec = curSubspace->subsector().as<world::ClientSubsector>();
2426             for (dint i = 0; i < subsec.visPlaneCount(); ++i)
2427             {
2428                 Plane const &plane = subsec.visPlane(i);
2429                 Vector3d const pointOnPlane(subsec.center(), plane.heightSmoothed());
2430 
2431                 ProjectedTextureData projected;
2432                 if (projectPlaneGlow(topLeft, bottomRight, plane, pointOnPlane, blendFactor,
2433                                     projected))
2434                 {
2435                     ClientApp::renderSystem().findSurfaceProjectionList(&lightListIdx, sortLights)
2436                                 << projected;  // a copy is made.
2437                 }
2438             }
2439         }
2440     }
2441 
2442     // Shadows?
2443     if (!noShadows && ::useShadows)
2444     {
2445         // Glow inversely diminishes shadow strength.
2446         dfloat blendFactor = 1 - glowStrength;
2447         if (blendFactor >= SHADOW_SURFACE_LUMINOSITY_ATTRIBUTION_MIN)
2448         {
2449             blendFactor = de::clamp(0.f, blendFactor, 1.f);
2450 
2451             // Project all mobj shadows affecting the given quad (world space), calculate
2452             // coordinates (in texture space) then store into a new list of projections.
2453             R_ForAllSubspaceMobContacts(*curSubspace, [&topLeft, &bottomRight, &surface
2454                                                       , &blendFactor, &shadowListIdx] (mobj_t &mob)
2455             {
2456                 ProjectedTextureData projected;
2457                 if (projectShadow(topLeft, bottomRight, mob, surface, blendFactor,
2458                                  projected))
2459                 {
2460                     ClientApp::renderSystem().findSurfaceProjectionList(&shadowListIdx)
2461                                 << projected;  // a copy is made.
2462                 }
2463                 return LoopContinue;
2464             });
2465         }
2466     }
2467 }
2468 
2469 /**
2470  * World light can both light and shade. Normal objects get more shade than light
2471  * (preventing them from ending up too bright compared to the ambient light).
2472  */
lightWithWorldLight(Vector3d const &,Vector3f const & ambientColor,bool starkLight,VectorLightData & vlight)2473 static bool lightWithWorldLight(Vector3d const & /*point*/, Vector3f const &ambientColor,
2474     bool starkLight, VectorLightData &vlight)
2475 {
2476     static Vector3f const worldLight(-.400891f, -.200445f, .601336f);
2477 
2478     vlight = {};
2479     vlight.direction         = worldLight;
2480     vlight.color             = ambientColor;
2481     vlight.affectedByAmbient = false;
2482     vlight.approxDist        = 0;
2483     if (starkLight)
2484     {
2485         vlight.lightSide = .35f;
2486         vlight.darkSide  = .5f;
2487         vlight.offset    = 0;
2488     }
2489     else
2490     {
2491         vlight.lightSide = .2f;
2492         vlight.darkSide  = .8f;
2493         vlight.offset    = .3f;
2494     }
2495     vlight.sourceMobj = nullptr;
2496     return true;
2497 }
2498 
lightWithLumobj(Vector3d const & point,Lumobj const & lum,VectorLightData & vlight)2499 static bool lightWithLumobj(Vector3d const &point, Lumobj const &lum, VectorLightData &vlight)
2500 {
2501     Vector3d const lumCenter(lum.x(), lum.y(), lum.z() + lum.zOffset());
2502 
2503     // Is this light close enough?
2504     ddouble const dist = M_ApproxDistance(M_ApproxDistance(lumCenter.x - point.x,
2505                                                            lumCenter.y - point.y),
2506                                           point.z - lumCenter.z);
2507     dfloat intensity = 0;
2508     if (dist < Lumobj::radiusMax())
2509     {
2510         intensity = de::clamp(0.f, dfloat(1 - dist / lum.radius()) * 2, 1.f);
2511     }
2512     if (intensity < .05f) return false;
2513 
2514     vlight = {};
2515     vlight.direction         = (lumCenter - point) / dist;
2516     vlight.color             = lum.color() * intensity;
2517     vlight.affectedByAmbient = true;
2518     vlight.approxDist        = dist;
2519     vlight.lightSide         = 1;
2520     vlight.darkSide          = 0;
2521     vlight.offset            = 0;
2522     vlight.sourceMobj        = lum.sourceMobj();
2523     return true;
2524 }
2525 
lightWithPlaneGlow(Vector3d const & point,Subsector const & subsec,dint visPlaneIndex,VectorLightData & vlight)2526 static bool lightWithPlaneGlow(Vector3d const &point, Subsector const &subsec,
2527     dint visPlaneIndex, VectorLightData &vlight)
2528 {
2529     Plane const &plane     = subsec.as<world::ClientSubsector>().visPlane(visPlaneIndex);
2530     Surface const &surface = plane.surface();
2531 
2532     // Glowing at this moment?
2533     Vector3f glowColor;
2534     dfloat intensity = surface.glow(glowColor);
2535     if (intensity < .05f) return false;
2536 
2537     coord_t const glowHeight = Rend_PlaneGlowHeight(intensity);
2538     if (glowHeight < 2) return false;  // Not too small!
2539 
2540     // In front of the plane?
2541     Vector3d const pointOnPlane(subsec.center(), plane.heightSmoothed());
2542     ddouble const dist = (point - pointOnPlane).dot(surface.normal());
2543     if (dist < 0) return false;
2544 
2545     intensity *= 1 - dist / glowHeight;
2546     if (intensity < .05f) return false;
2547 
2548     Vector3f const color = Rend_LuminousColor(glowColor, intensity);
2549     if (color == Vector3f()) return false;
2550 
2551     vlight = {};
2552     vlight.direction         = Vector3f(surface.normal().x, surface.normal().y, -surface.normal().z);
2553     vlight.color             = color;
2554     vlight.affectedByAmbient = true;
2555     vlight.approxDist        = dist;
2556     vlight.lightSide         = 1;
2557     vlight.darkSide          = 0;
2558     vlight.offset            = 0.3f;
2559     vlight.sourceMobj        = nullptr;
2560     return true;
2561 }
2562 
Rend_CollectAffectingLights(Vector3d const & point,Vector3f const & ambientColor,ConvexSubspace * subspace,bool starkLight)2563 duint Rend_CollectAffectingLights(Vector3d const &point, Vector3f const &ambientColor,
2564     ConvexSubspace *subspace, bool starkLight)
2565 {
2566     duint lightListIdx = 0;
2567 
2568     // Always apply an ambient world light.
2569     {
2570         VectorLightData vlight;
2571         if (lightWithWorldLight(point, ambientColor, starkLight, vlight))
2572         {
2573             ClientApp::renderSystem().findVectorLightList(&lightListIdx)
2574                     << vlight;  // a copy is made.
2575         }
2576     }
2577 
2578     // Add extra light by interpreting nearby sources.
2579     if (subspace)
2580     {
2581         // Interpret lighting from luminous-objects near the origin and which
2582         // are in contact the specified subspace and add them to the identified list.
2583         R_ForAllSubspaceLumContacts(*subspace, [&point, &lightListIdx] (Lumobj &lum)
2584         {
2585             VectorLightData vlight;
2586             if (lightWithLumobj(point, lum, vlight))
2587             {
2588                 ClientApp::renderSystem().findVectorLightList(&lightListIdx)
2589                         << vlight;  // a copy is made.
2590             }
2591             return LoopContinue;
2592         });
2593 
2594         // Interpret vlights from glowing planes at the origin in the specfified
2595         // subspace and add them to the identified list.
2596         auto const &subsec = subspace->subsector().as<world::ClientSubsector>();
2597         for (dint i = 0; i < subsec.sector().planeCount(); ++i)
2598         {
2599             VectorLightData vlight;
2600             if (lightWithPlaneGlow(point, subsec, i, vlight))
2601             {
2602                 ClientApp::renderSystem().findVectorLightList(&lightListIdx)
2603                         << vlight;  // a copy is made.
2604             }
2605         }
2606     }
2607 
2608     return lightListIdx;
2609 }
2610 
2611 /**
2612  * Fade the specified @a opacity value to fully transparent the closer the view
2613  * player is to the geometry.
2614  *
2615  * @note When the viewer is close enough we should NOT try to occlude with this
2616  * section in the angle clipper, otherwise HOM would occur when directly on top
2617  * of the wall (e.g., passing through an opaque waterfall).
2618  *
2619  * @return  @c true= fading was applied (see above note), otherwise @c false.
2620  */
applyNearFadeOpacity(WallEdge const & leftEdge,WallEdge const & rightEdge,dfloat & opacity)2621 static bool applyNearFadeOpacity(WallEdge const &leftEdge, WallEdge const &rightEdge,
2622     dfloat &opacity)
2623 {
2624     if (!leftEdge.spec().flags.testFlag(WallSpec::NearFade))
2625         return false;
2626 
2627     if (Rend_EyeOrigin().y < leftEdge.bottom().z() || Rend_EyeOrigin().y > rightEdge.top().z())
2628         return false;
2629 
2630     mobj_t const *mo         = viewPlayer->publicData().mo;
2631     Line const &line         = leftEdge.lineSide().line();
2632 
2633     coord_t linePoint[2]     = { line.from().x(), line.from().y() };
2634     coord_t lineDirection[2] = {  line.direction().x,  line.direction().y };
2635     vec2d_t result;
2636     ddouble pos = V2d_ProjectOnLine(result, mo->origin, linePoint, lineDirection);
2637 
2638     if (!(pos > 0 && pos < 1))
2639         return false;
2640 
2641     coord_t const maxDistance = Mobj_Radius(*mo) * .8f;
2642 
2643     auto delta       = Vector2d(result) - Vector2d(mo->origin);
2644     coord_t distance = delta.length();
2645 
2646     if (de::abs(distance) > maxDistance)
2647         return false;
2648 
2649     if (distance > 0)
2650     {
2651         opacity = (opacity / maxDistance) * distance;
2652         opacity = de::clamp(0.f, opacity, 1.f);
2653     }
2654 
2655     return true;
2656 }
2657 
2658 /**
2659  * The DOOM lighting model applies a sector light level delta when drawing
2660  * walls based on their 2D world angle.
2661  *
2662  * @todo WallEdge should encapsulate.
2663  */
wallLuminosityDeltaFromNormal(Vector3f const & normal)2664 static dfloat wallLuminosityDeltaFromNormal(Vector3f const &normal)
2665 {
2666     return (1.0f / 255) * (normal.x * 18) * ::rendLightWallAngle;
2667 }
2668 
wallLuminosityDeltas(WallEdge const & leftEdge,WallEdge const & rightEdge,dfloat luminosityDeltas[2])2669 static void wallLuminosityDeltas(WallEdge const &leftEdge, WallEdge const &rightEdge,
2670     dfloat luminosityDeltas[2])
2671 {
2672     dfloat &leftDelta  = luminosityDeltas[0];
2673     dfloat &rightDelta = luminosityDeltas[1];
2674 
2675     if (leftEdge.spec().flags.testFlag(WallSpec::NoLightDeltas))
2676     {
2677         leftDelta = rightDelta = 0;
2678         return;
2679     }
2680 
2681     leftDelta = wallLuminosityDeltaFromNormal(leftEdge.normal());
2682 
2683     if (leftEdge.normal() == rightEdge.normal())
2684     {
2685         rightDelta = leftDelta;
2686     }
2687     else
2688     {
2689         rightDelta = wallLuminosityDeltaFromNormal(rightEdge.normal());
2690 
2691         // Linearly interpolate to find the light level delta values for the
2692         // vertical edges of this wall section.
2693         coord_t const lineLength    = leftEdge.lineSide().line().length();
2694         coord_t const sectionOffset = leftEdge.lineSideOffset();
2695         coord_t const sectionWidth  = de::abs(Vector2d(rightEdge.origin() - leftEdge.origin()).length());
2696 
2697         dfloat deltaDiff = rightDelta - leftDelta;
2698         rightDelta = leftDelta + ((sectionOffset + sectionWidth) / lineLength) * deltaDiff;
2699         leftDelta += (sectionOffset / lineLength) * deltaDiff;
2700     }
2701 }
2702 
writeWall(WallEdge const & leftEdge,WallEdge const & rightEdge,bool * retWroteOpaque=nullptr,coord_t * retBottomZ=nullptr,coord_t * retTopZ=nullptr)2703 static void writeWall(WallEdge const &leftEdge, WallEdge const &rightEdge,
2704     bool *retWroteOpaque = nullptr, coord_t *retBottomZ = nullptr, coord_t *retTopZ = nullptr)
2705 {
2706     DENG2_ASSERT(leftEdge.lineSideSegment().isFrontFacing() && leftEdge.lineSide().hasSections());
2707 
2708     if (retWroteOpaque) *retWroteOpaque = false;
2709     if (retBottomZ)     *retBottomZ     = 0;
2710     if (retTopZ)        *retTopZ        = 0;
2711 
2712     auto &subsec = curSubspace->subsector().as<world::ClientSubsector>();
2713     Surface &surface = leftEdge.lineSide().surface(leftEdge.spec().section);
2714 
2715     // Skip nearly transparent surfaces.
2716     dfloat opacity = surface.opacity();
2717     if (opacity < .001f)
2718         return;
2719 
2720     // Determine which Material to use (a drawable material is required).
2721     ClientMaterial *material = Rend_ChooseMapSurfaceMaterial(surface);
2722     if (!material || !material->isDrawable())
2723         return;
2724 
2725     // Do the edge geometries describe a valid polygon?
2726     if (!leftEdge.isValid() || !rightEdge.isValid()
2727         || de::fequal(leftEdge.bottom().z(), rightEdge.top().z()))
2728         return;
2729 
2730     WallSpec const &wallSpec      = leftEdge.spec();
2731     bool const didNearFade        = applyNearFadeOpacity(leftEdge, rightEdge, opacity);
2732     bool const skyMasked          = material->isSkyMasked() && !::devRendSkyMode;
2733     bool const twoSidedMiddle     = (wallSpec.section == LineSide::Middle && !leftEdge.lineSide().considerOneSided());
2734 
2735     MaterialAnimator &matAnimator = material->getAnimator(Rend_MapSurfaceMaterialSpec());
2736     Vector2f const materialScale  = surface.materialScale();
2737     Vector3f const materialOrigin = leftEdge.materialOrigin();
2738     Vector3d const topLeft        = leftEdge .top   ().origin();
2739     Vector3d const bottomRight    = rightEdge.bottom().origin();
2740 
2741     rendworldpoly_params_t parm; de::zap(parm);
2742     parm.skyMasked            = skyMasked;
2743     parm.mapElement           = &leftEdge.lineSideSegment();
2744     parm.geomGroup            = wallSpec.section;
2745     parm.topLeft              = &topLeft;
2746     parm.bottomRight          = &bottomRight;
2747     parm.forceOpaque          = wallSpec.flags.testFlag(WallSpec::ForceOpaque);
2748     parm.alpha                = parm.forceOpaque? 1 : opacity;
2749     parm.surfaceTangentMatrix = &surface.tangentMatrix();
2750     parm.blendMode            = BM_NORMAL;
2751     parm.materialOrigin       = &materialOrigin;
2752     parm.materialScale        = &materialScale;
2753 
2754     parm.isWall               = true;
2755     parm.wall.width           = de::abs(Vector2d(rightEdge.origin() - leftEdge.origin()).length());
2756     parm.wall.leftEdge        = &leftEdge;
2757     parm.wall.rightEdge       = &rightEdge;
2758     // Calculate the angle-based luminosity deltas.
2759     wallLuminosityDeltas(leftEdge, rightEdge, parm.surfaceLuminosityDeltas);
2760 
2761     LineSide &side = leftEdge.lineSide();
2762     if (!parm.skyMasked)
2763     {
2764         if (glowFactor > .0001f)
2765         {
2766 //            if (material == surface.materialPtr())
2767 //            {
2768             parm.glowing = matAnimator.glowStrength();
2769 //            }
2770 //            else
2771 //            {
2772 //                auto *actualMaterial =
2773 //                    surface.hasMaterial() ? static_cast<ClientMaterial *>(surface.materialPtr())
2774 //                                          : &ClientMaterial::find(de::Uri("System", Path("missing")));
2775 
2776 //                parm.glowing = actualMaterial->getAnimator(Rend_MapSurfaceMaterialSpec()).glowStrength();
2777 //            }
2778 
2779             parm.glowing *= glowFactor;
2780         }
2781 
2782         projectDynamics(surface, parm.glowing, *parm.topLeft, *parm.bottomRight,
2783                         wallSpec.flags.testFlag(WallSpec::NoDynLights),
2784                         wallSpec.flags.testFlag(WallSpec::NoDynShadows),
2785                         wallSpec.flags.testFlag(WallSpec::SortDynLights),
2786                         parm.lightListIdx, parm.shadowListIdx);
2787 
2788         if (twoSidedMiddle)
2789         {
2790             parm.blendMode = surface.blendMode();
2791             if (parm.blendMode == BM_NORMAL && noSpriteTrans)
2792                 parm.blendMode = BM_ZEROALPHA;  // "no translucency" mode
2793         }
2794 
2795         side.chooseSurfaceColors(wallSpec.section, &parm.surfaceColor, &parm.wall.surfaceColor2);
2796     }
2797 
2798     //
2799     // Geometry write/drawing begins.
2800     //
2801 
2802     if (twoSidedMiddle && side.sectorPtr() != &subsec.sector())
2803     {
2804         // Temporarily modify the draw state.
2805         curSectorLightColor = Rend_AmbientLightColor(side.sector());
2806         curSectorLightLevel = side.sector().lightLevel();
2807     }
2808 
2809     Vector3f const posCoords[] = {
2810         leftEdge .bottom().origin(),
2811         leftEdge .top   ().origin(),
2812         rightEdge.bottom().origin(),
2813         rightEdge.top   ().origin()
2814     };
2815 
2816     // Draw this wall.
2817     bool const wroteOpaque = renderWorldPoly(posCoords, 4, parm, matAnimator);
2818 
2819     // Draw FakeRadio for this wall?
2820     if (wroteOpaque && !skyMasked && !(parm.glowing > 0))
2821     {
2822         Rend_DrawWallRadio(leftEdge, rightEdge, ::curSectorLightLevel);
2823     }
2824 
2825     if (twoSidedMiddle && side.sectorPtr() != &subsec.sector())
2826     {
2827         // Undo temporary draw state changes.
2828         Vector4f const color = subsec.lightSourceColorfIntensity();
2829         curSectorLightColor = color.toVector3f();
2830         curSectorLightLevel = color.w;
2831     }
2832 
2833     if (retWroteOpaque) *retWroteOpaque = wroteOpaque && !didNearFade;
2834     if (retBottomZ)     *retBottomZ     = leftEdge .bottom().z();
2835     if (retTopZ)        *retTopZ        = rightEdge.top   ().z();
2836 }
2837 
2838 /**
2839  * Prepare a trifan geometry according to the edges of the current subspace.
2840  * If a fan base HEdge has been chosen it will be used as the center of the
2841  * trifan, else the mid point of this leaf will be used instead.
2842  *
2843  * @param direction  Vertex winding direction.
2844  * @param height     Z map space height coordinate to be set for each vertex.
2845  * @param verts      Built position coordinates are written here. It is the
2846  *                   responsibility of the caller to release this storage with
2847  *                   @ref R_FreeRendVertices() when done.
2848  *
2849  * @return  Number of built vertices.
2850  */
buildSubspacePlaneGeometry(ClockDirection direction,coord_t height,Vector3f ** verts)2851 static duint buildSubspacePlaneGeometry(ClockDirection direction, coord_t height,
2852     Vector3f **verts)
2853 {
2854     DENG2_ASSERT(verts);
2855 
2856     Face const &poly       = curSubspace->poly();
2857     HEdge *fanBase         = curSubspace->fanBase();
2858     duint const totalVerts = poly.hedgeCount() + (!fanBase? 2 : 0);
2859 
2860     *verts = R_AllocRendVertices(totalVerts);
2861 
2862     duint n = 0;
2863     if (!fanBase)
2864     {
2865         (*verts)[n] = Vector3f(poly.center(), height);
2866         n++;
2867     }
2868 
2869     // Add the vertices for each hedge.
2870     HEdge *baseNode = fanBase? fanBase : poly.hedge();
2871     HEdge *node = baseNode;
2872     do
2873     {
2874         (*verts)[n] = Vector3f(node->origin(), height);
2875         n++;
2876     } while ((node = &node->neighbor(direction)) != baseNode);
2877 
2878     // The last vertex is always equal to the first.
2879     if (!fanBase)
2880     {
2881         (*verts)[n] = Vector3f(poly.hedge()->origin(), height);
2882     }
2883 
2884     return totalVerts;
2885 }
2886 
writeSubspacePlane(Plane & plane)2887 static void writeSubspacePlane(Plane &plane)
2888 {
2889     Face const &poly       = curSubspace->poly();
2890     Surface const &surface = plane.surface();
2891 
2892     // Skip nearly transparent surfaces.
2893     dfloat const opacity = surface.opacity();
2894     if (opacity < .001f) return;
2895 
2896     // Determine which Material to use (a drawable material is required).
2897     ClientMaterial *material = Rend_ChooseMapSurfaceMaterial(surface);
2898     if (!material || !material->isDrawable())
2899         return;
2900 
2901     // Skip planes with a sky-masked material (drawn with the mask geometry)?
2902     if (!::devRendSkyMode && surface.hasSkyMaskedMaterial() && plane.indexInSector() <= Sector::Ceiling)
2903         return;
2904 
2905     MaterialAnimator &matAnimator = material->getAnimator(Rend_MapSurfaceMaterialSpec());
2906 
2907     Vec2f materialOrigin =
2908         // Align to the worldwide grid.
2909         Vec2f(fmod(curSubspace->poly().bounds().minX, material->width()),
2910               fmod(curSubspace->poly().bounds().maxY, material->height())) +
2911         surface.originSmoothed();
2912 
2913     // Add the Y offset to orient the Y flipped material.
2914     /// @todo fixme: What is this meant to do? -ds
2915     if (plane.isSectorCeiling())
2916     {
2917         materialOrigin.y -= poly.bounds().maxY - poly.bounds().minY;
2918     }
2919     materialOrigin.y = -materialOrigin.y;
2920 
2921     Vector2f const materialScale = surface.materialScale();
2922 
2923     // Set the texture origin, Y is flipped for the ceiling.
2924     Vector3d topLeft(poly.bounds().minX,
2925                      poly.bounds().arvec2[plane.isSectorFloor()? 1 : 0][1],
2926                      plane.heightSmoothed());
2927     Vector3d bottomRight(poly.bounds().maxX,
2928                          poly.bounds().arvec2[plane.isSectorFloor()? 0 : 1][1],
2929                          plane.heightSmoothed());
2930 
2931     rendworldpoly_params_t parm; de::zap(parm);
2932     parm.mapElement           = curSubspace;
2933     parm.geomGroup            = plane.indexInSector();
2934     parm.topLeft              = &topLeft;
2935     parm.bottomRight          = &bottomRight;
2936     parm.materialOrigin       = &materialOrigin;
2937     parm.materialScale        = &materialScale;
2938     parm.surfaceColor         = &surface.color();
2939     parm.surfaceTangentMatrix = &surface.tangentMatrix();
2940 
2941     if (material->isSkyMasked())
2942     {
2943         // In devRendSkyMode mode we render all polys destined for the
2944         // skymask as regular world polys (with a few obvious properties).
2945         if (devRendSkyMode)
2946         {
2947             parm.blendMode   = BM_NORMAL;
2948             parm.forceOpaque = true;
2949         }
2950         else
2951         {
2952             // We'll mask this.
2953             parm.skyMasked = true;
2954         }
2955     }
2956     else if (plane.indexInSector() <= Sector::Ceiling)
2957     {
2958         parm.blendMode   = BM_NORMAL;
2959         parm.forceOpaque = true;
2960     }
2961     else
2962     {
2963         parm.blendMode = surface.blendMode();
2964         if (parm.blendMode == BM_NORMAL && noSpriteTrans)
2965         {
2966             parm.blendMode = BM_ZEROALPHA;  // "no translucency" mode
2967         }
2968 
2969         parm.alpha = surface.opacity();
2970     }
2971 
2972     if (!parm.skyMasked)
2973     {
2974         if (glowFactor > .0001f)
2975         {
2976             if (material == surface.materialPtr())
2977             {
2978                 parm.glowing = matAnimator.glowStrength();
2979             }
2980             else
2981             {
2982                 world::Material *actualMaterial =
2983                     surface.hasMaterial()? surface.materialPtr()
2984                                          : &world::Materials::get().material(de::Uri("System", Path("missing")));
2985 
2986                 parm.glowing = actualMaterial->as<ClientMaterial>().getAnimator(Rend_MapSurfaceMaterialSpec()).glowStrength();
2987             }
2988 
2989             parm.glowing *= ::glowFactor;
2990         }
2991 
2992         projectDynamics(surface, parm.glowing, *parm.topLeft, *parm.bottomRight,
2993                         false /*do light*/, false /*do shadow*/, false /*don't sort*/,
2994                         parm.lightListIdx, parm.shadowListIdx);
2995     }
2996 
2997     //
2998     // Geometry write/drawing begins.
2999     //
3000 
3001     if (&plane.sector() != &curSubspace->subsector().sector())
3002     {
3003         // Temporarily modify the draw state.
3004         curSectorLightColor = Rend_AmbientLightColor(plane.sector());
3005         curSectorLightLevel = plane.sector().lightLevel();
3006     }
3007 
3008     // Allocate position coordinates.
3009     Vector3f *posCoords;
3010     duint vertCount = buildSubspacePlaneGeometry((plane.isSectorCeiling())? Anticlockwise : Clockwise,
3011                                                  plane.heightSmoothed(), &posCoords);
3012 
3013     // Draw this section.
3014     renderWorldPoly(posCoords, vertCount, parm, matAnimator);
3015 
3016     if (&plane.sector() != &curSubspace->subsector().sector())
3017     {
3018         // Undo temporary draw state changes.
3019         Vector4f const color = curSubspace->subsector().as<world::ClientSubsector>().lightSourceColorfIntensity();
3020         curSectorLightColor = color.toVector3f();
3021         curSectorLightLevel = color.w;
3022     }
3023 
3024     R_FreeRendVertices(posCoords);
3025 }
3026 
writeSkyMaskStrip(dint vertCount,Vector3f const * posCoords,Vector2f const * texCoords,Material * material)3027 static void writeSkyMaskStrip(dint vertCount, Vector3f const *posCoords, Vector2f const *texCoords,
3028     Material *material)
3029 {
3030     DENG2_ASSERT(posCoords);
3031 
3032     static DrawList::Indices indices;
3033 
3034     if (!devRendSkyMode)
3035     {
3036         Store &buffer = ClientApp::renderSystem().buffer();
3037         duint base = buffer.allocateVertices(vertCount);
3038         DrawList::reserveSpace(indices, vertCount);
3039         for (dint i = 0; i < vertCount; ++i)
3040         {
3041             indices[i] = base + i;
3042             buffer.posCoords[indices[i]] = posCoords[i];
3043         }
3044         ClientApp::renderSystem().drawLists().find(DrawListSpec(SkyMaskGeom))
3045                       .write(buffer, indices.constData(), vertCount, gl::TriangleStrip);
3046     }
3047     else
3048     {
3049         DENG2_ASSERT(texCoords);
3050 
3051         DrawListSpec listSpec;
3052         listSpec.group = UnlitGeom;
3053         if (renderTextures != 2)
3054         {
3055             DENG2_ASSERT(material);
3056             MaterialAnimator &matAnimator = material->as<ClientMaterial>().getAnimator(Rend_MapSurfaceMaterialSpec());
3057 
3058             // Ensure we've up to date info about the material.
3059             matAnimator.prepare();
3060 
3061             // Map RTU configuration from the sky surface material.
3062             listSpec.texunits[TU_PRIMARY]        = matAnimator.texUnit(MaterialAnimator::TU_LAYER0);
3063             listSpec.texunits[TU_PRIMARY_DETAIL] = matAnimator.texUnit(MaterialAnimator::TU_DETAIL);
3064             listSpec.texunits[TU_INTER]          = matAnimator.texUnit(MaterialAnimator::TU_LAYER0_INTER);
3065             listSpec.texunits[TU_INTER_DETAIL]   = matAnimator.texUnit(MaterialAnimator::TU_DETAIL_INTER);
3066         }
3067 
3068         Store &buffer = ClientApp::renderSystem().buffer();
3069         duint base = buffer.allocateVertices(vertCount);
3070         DrawList::reserveSpace(indices, vertCount);
3071         for (dint i = 0; i < vertCount; ++i)
3072         {
3073             indices[i] = base + i;
3074             buffer.posCoords   [indices[i]] = posCoords[i];
3075             buffer.texCoords[0][indices[i]] = texCoords[i];
3076             buffer.colorCoords [indices[i]] = Vector4ub(255, 255, 255, 255);
3077         }
3078 
3079         ClientApp::renderSystem().drawLists().find(listSpec)
3080                       .write(buffer, indices.constData(), vertCount,
3081                              DrawList::PrimitiveParams(gl::TriangleStrip,
3082                                                        listSpec.unit(TU_PRIMARY       ).scale,
3083                                                        listSpec.unit(TU_PRIMARY       ).offset,
3084                                                        listSpec.unit(TU_PRIMARY_DETAIL).scale,
3085                                                        listSpec.unit(TU_PRIMARY_DETAIL).offset));
3086     }
3087 }
3088 
writeSubspaceSkyMaskStrips(SkyFixEdge::FixType fixType)3089 static void writeSubspaceSkyMaskStrips(SkyFixEdge::FixType fixType)
3090 {
3091     // Determine strip generation behavior.
3092     ClockDirection const direction   = Clockwise;
3093     bool const splitOnMaterialChange = (::devRendSkyMode && ::renderTextures != 2);
3094 
3095     // Determine the relative sky plane (for monitoring material changes).
3096     dint const relPlane = (fixType == SkyFixEdge::Upper ? Sector::Ceiling : Sector::Floor);
3097 
3098     // Configure the strip builder wrt vertex attributes.
3099     TriangleStripBuilder stripBuilder(CPP_BOOL(::devRendSkyMode));
3100 
3101     // Configure the strip build state (we'll most likely need to break edge loop
3102     // into multiple strips).
3103     Material *scanMaterial    = nullptr;
3104     dfloat scanMaterialOffset = 0;
3105     HEdge *scanNode           = nullptr;
3106     coord_t scanZBottom       = 0;
3107     coord_t scanZTop          = 0;
3108 
3109     // Begin generating geometry.
3110     HEdge *base  = curSubspace->poly().hedge();
3111     HEdge *hedge = base;
3112     forever
3113     {
3114         // Are we monitoring material changes?
3115         Material *skyMaterial = nullptr;
3116         if (splitOnMaterialChange)
3117         {
3118             skyMaterial = hedge->face().mapElementAs<ConvexSubspace>()
3119                               .subsector().as<world::ClientSubsector>()
3120                                    .visPlane(relPlane).surface().materialPtr();
3121         }
3122 
3123         // Add a first (left) edge to the current strip?
3124         if (!scanNode && hedge->hasMapElement())
3125         {
3126             scanMaterialOffset = hedge->mapElementAs<LineSideSegment>().lineSideOffset();
3127 
3128             // Prepare the edge geometry
3129             SkyFixEdge skyEdge(*hedge, fixType, (direction == Anticlockwise ? Line::To : Line::From),
3130                                scanMaterialOffset);
3131 
3132             if (skyEdge.isValid() && skyEdge.bottom().z() < skyEdge.top().z())
3133             {
3134                 // A new strip begins.
3135                 stripBuilder.begin(direction);
3136                 stripBuilder << skyEdge;
3137 
3138                 // Update the strip build state.
3139                 scanNode     = hedge;
3140                 scanZBottom  = skyEdge.bottom().z();
3141                 scanZTop     = skyEdge.top   ().z();
3142                 scanMaterial = skyMaterial;
3143             }
3144         }
3145 
3146         bool beginNewStrip = false;
3147 
3148         // Add the i'th (right) edge to the current strip?
3149         if (scanNode)
3150         {
3151             // Stop if we've reached a "null" edge.
3152             bool endStrip = false;
3153             if (hedge->hasMapElement())
3154             {
3155                 scanMaterialOffset += hedge->mapElementAs<LineSideSegment>().length() *
3156                                       (direction == Anticlockwise ? -1 : 1);
3157 
3158                 // Prepare the edge geometry
3159                 SkyFixEdge skyEdge(*hedge, fixType, (direction == Anticlockwise)? Line::From : Line::To,
3160                                    scanMaterialOffset);
3161 
3162                 if (!(skyEdge.isValid() && skyEdge.bottom().z() < skyEdge.top().z()))
3163                 {
3164                     endStrip = true;
3165                 }
3166                 // Must we split the strip here?
3167                 else if (hedge != scanNode &&
3168                         (   !de::fequal(skyEdge.bottom().z(), scanZBottom)
3169                          || !de::fequal(skyEdge.top   ().z(), scanZTop)
3170                          || (splitOnMaterialChange && skyMaterial != scanMaterial)))
3171                 {
3172                     endStrip      = true;
3173                     beginNewStrip = true;  // We'll continue from here.
3174                 }
3175                 else
3176                 {
3177                     // Extend the strip geometry.
3178                     stripBuilder << skyEdge;
3179                 }
3180             }
3181             else
3182             {
3183                 endStrip = true;
3184             }
3185 
3186             if (endStrip || &hedge->neighbor(direction) == base)
3187             {
3188                 // End the current strip.
3189                 scanNode = nullptr;
3190 
3191                 // Take ownership of the built geometry.
3192                 PositionBuffer *positions = nullptr;
3193                 TexCoordBuffer *texcoords = nullptr;
3194                 dint const numVerts = stripBuilder.take(&positions, &texcoords);
3195 
3196                 // Write the strip geometry to the render lists.
3197                 writeSkyMaskStrip(numVerts, positions->constData(),
3198                                   (texcoords ? texcoords->constData() : nullptr),
3199                                   scanMaterial);
3200 
3201                 delete positions;
3202                 delete texcoords;
3203             }
3204         }
3205 
3206         // Start a new strip from the current node?
3207         if (beginNewStrip) continue;
3208 
3209         // On to the next node.
3210         hedge = &hedge->neighbor(direction);
3211 
3212         // Are we done?
3213         if (hedge == base) break;
3214     }
3215 }
3216 
3217 /**
3218  * @defgroup skyCapFlags  Sky Cap Flags
3219  * @ingroup flags
3220  */
3221 ///@{
3222 #define SKYCAP_LOWER        0x1
3223 #define SKYCAP_UPPER        0x2
3224 ///@}
3225 
makeFlatSkyMaskGeometry(DrawList::Indices & indices,Store & verts,gl::Primitive & primitive,ConvexSubspace const & subspace,coord_t worldZPosition=0,ClockDirection direction=Clockwise)3226 static uint makeFlatSkyMaskGeometry(DrawList::Indices &indices, Store &verts, gl::Primitive &primitive,
3227     ConvexSubspace const &subspace, coord_t worldZPosition = 0, ClockDirection direction = Clockwise)
3228 {
3229     Face const &poly = subspace.poly();
3230     HEdge *fanBase   = subspace.fanBase();
3231 
3232     // Assign indices.
3233     duint const vertCount = poly.hedgeCount() + (!fanBase? 2 : 0);
3234     duint const base      = verts.allocateVertices(vertCount);
3235     DrawList::reserveSpace(indices, vertCount);
3236     for (duint i = 0; i < vertCount; ++i)
3237     {
3238         indices[i] = base + i;
3239     }
3240 
3241     //
3242     // Build geometry.
3243     //
3244     primitive = gl::TriangleFan;
3245     duint n = 0;
3246     if (!fanBase)
3247     {
3248         verts.posCoords[indices[n++]] = Vector3f(poly.center(), worldZPosition);
3249     }
3250     HEdge *baseNode = fanBase? fanBase : poly.hedge();
3251     HEdge *node = baseNode;
3252     do
3253     {
3254         verts.posCoords[indices[n++]] = Vector3f(node->origin(), worldZPosition);
3255     } while ((node = &node->neighbor(direction)) != baseNode);
3256     if (!fanBase)
3257     {
3258         verts.posCoords[indices[n  ]] = Vector3f(node->origin(), worldZPosition);
3259     }
3260 
3261     return vertCount;
3262 }
3263 
3264 /// @param skyCap  @ref skyCapFlags
writeSubspaceSkyMask(dint skyCap=SKYCAP_LOWER|SKYCAP_UPPER)3265 static void writeSubspaceSkyMask(dint skyCap = SKYCAP_LOWER | SKYCAP_UPPER)
3266 {
3267     DENG2_ASSERT(::curSubspace);
3268 
3269     // No work to do?
3270     if (!skyCap) return;
3271 
3272     auto &subsec = curSubspace->subsector().as<world::ClientSubsector>();
3273     world::Map &map = subsec.sector().map();
3274 
3275     DrawList &dlist = ClientApp::renderSystem().drawLists().find(DrawListSpec(SkyMaskGeom));
3276     static DrawList::Indices indices;
3277 
3278     // Lower?
3279     if ((skyCap & SKYCAP_LOWER) && subsec.hasSkyFloor())
3280     {
3281         world::ClSkyPlane &skyFloor = map.skyFloor();
3282 
3283         writeSubspaceSkyMaskStrips(SkyFixEdge::Lower);
3284 
3285         // Draw a cap? (handled as a regular plane in sky-debug mode).
3286         if (!::devRendSkyMode)
3287         {
3288             ddouble const height =
3289                 P_IsInVoid(::viewPlayer) ? subsec.visFloor().heightSmoothed() : skyFloor.height();
3290 
3291             // Make geometry.
3292             Store &verts = ClientApp::renderSystem().buffer();
3293             gl::Primitive primitive;
3294             uint vertCount = makeFlatSkyMaskGeometry(indices, verts, primitive, *curSubspace, height, Clockwise);
3295 
3296             // Write geometry.
3297             dlist.write(verts, indices.constData(), vertCount, primitive);
3298         }
3299     }
3300 
3301     // Upper?
3302     if ((skyCap & SKYCAP_UPPER) && subsec.hasSkyCeiling())
3303     {
3304         world::ClSkyPlane &skyCeiling = map.skyCeiling();
3305 
3306         writeSubspaceSkyMaskStrips(SkyFixEdge::Upper);
3307 
3308         // Draw a cap? (handled as a regular plane in sky-debug mode).
3309         if (!::devRendSkyMode)
3310         {
3311             ddouble const height =
3312                 P_IsInVoid(::viewPlayer) ? subsec.visCeiling().heightSmoothed() : skyCeiling.height();
3313 
3314             // Make geometry.
3315             Store &verts = ClientApp::renderSystem().buffer();
3316             gl::Primitive primitive;
3317             uint vertCount = makeFlatSkyMaskGeometry(indices, verts, primitive, *curSubspace, height, Anticlockwise);
3318 
3319             // Write geometry.
3320             dlist.write(verts, indices.constData(), vertCount, primitive);
3321         }
3322     }
3323 }
3324 
coveredOpenRange(HEdge & hedge,coord_t middleBottomZ,coord_t middleTopZ,bool wroteOpaqueMiddle)3325 static bool coveredOpenRange(HEdge &hedge, coord_t middleBottomZ, coord_t middleTopZ,
3326     bool wroteOpaqueMiddle)
3327 {
3328     LineSide const &front = hedge.mapElementAs<LineSideSegment>().lineSide();
3329 
3330     // TNT map09 transparent window: blank line
3331     if (!front.hasAtLeastOneMaterial())
3332     {
3333         return false;
3334     }
3335 
3336     // TNT map02 window grille: transparent masked wall
3337     if (auto *anim = front.middle().materialAnimator())
3338     {
3339         if (!anim->isOpaque())
3340         {
3341             return false;
3342         }
3343     }
3344 
3345     if (front.considerOneSided())
3346     {
3347         return wroteOpaqueMiddle;
3348     }
3349 
3350     /// @todo fixme: This additional test should not be necessary. For the obove
3351     /// test to fail and this to pass means that the geometry produced by the BSP
3352     /// builder is not correct (see: eternall.wad MAP10; note mapping errors).
3353     if (!hedge.twin().hasFace())
3354     {
3355         return wroteOpaqueMiddle;
3356     }
3357 
3358     auto const &subsec     = hedge.face().mapElementAs<ConvexSubspace>().subsector().as<world::ClientSubsector>();
3359     auto const &backSubsec = hedge.twin().face().mapElementAs<ConvexSubspace>().subsector().as<world::ClientSubsector>();
3360 
3361     ddouble const ffloor   = subsec.visFloor().heightSmoothed();
3362     ddouble const fceil    = subsec.visCeiling().heightSmoothed();
3363 
3364     ddouble const bfloor   = backSubsec.visFloor().heightSmoothed();
3365     ddouble const bceil    = backSubsec.visCeiling().heightSmoothed();
3366 
3367     bool middleCoversOpening = false;
3368     if (wroteOpaqueMiddle)
3369     {
3370         ddouble xbottom = de::max(bfloor, ffloor);
3371         ddouble xtop    = de::min(bceil,  fceil);
3372 
3373         xbottom += front.middle().originSmoothed().y;
3374         xtop    += front.middle().originSmoothed().y;
3375 
3376         middleCoversOpening = (middleTopZ >= xtop && middleBottomZ <= xbottom);
3377     }
3378 
3379     if (wroteOpaqueMiddle && middleCoversOpening)
3380         return true;
3381 
3382     if (  (bceil  <= ffloor && (front.top   ().hasMaterial() || front.middle().hasMaterial()))
3383        || (bfloor >= fceil  && (front.bottom().hasMaterial() || front.middle().hasMaterial())))
3384     {
3385         Surface const &ffloorSurface = subsec.visFloor  ().surface();
3386         Surface const &fceilSurface  = subsec.visCeiling().surface();
3387         Surface const &bfloorSurface = backSubsec.visFloor  ().surface();
3388         Surface const &bceilSurface  = backSubsec.visCeiling().surface();
3389 
3390         // A closed gap?
3391         if (de::fequal(fceil, bfloor))
3392         {
3393             return (bceil <= bfloor)
3394                    || !(   fceilSurface.hasSkyMaskedMaterial()
3395                         && bceilSurface.hasSkyMaskedMaterial());
3396         }
3397 
3398         if (de::fequal(ffloor, bceil))
3399         {
3400             return (bfloor >= bceil)
3401                    || !(   ffloorSurface.hasSkyMaskedMaterial()
3402                         && bfloorSurface.hasSkyMaskedMaterial());
3403         }
3404 
3405         return true;
3406     }
3407 
3408     /// @todo Is this still necessary?
3409     if (bceil <= bfloor
3410         || (!(bceil - bfloor > 0) && bfloor > ffloor && bceil < fceil
3411             && front.top().hasMaterial() && front.bottom().hasMaterial()))
3412     {
3413         // A zero height back segment
3414         return true;
3415     }
3416 
3417     return false;
3418 }
3419 
writeAllWalls(HEdge & hedge)3420 static void writeAllWalls(HEdge &hedge)
3421 {
3422     // Edges without a map line segment implicitly have no surfaces.
3423     if (!hedge.hasMapElement())
3424         return;
3425 
3426     // We are only interested in front facing segments with sections.
3427     auto &seg = hedge.mapElementAs<LineSideSegment>();
3428     if (!seg.isFrontFacing() || !seg.lineSide().hasSections())
3429         return;
3430 
3431     // Done here because of the logic of doom.exe wrt the automap.
3432     reportWallDrawn(seg.line());
3433 
3434     bool wroteOpaqueMiddle = false;
3435     coord_t middleBottomZ  = 0;
3436     coord_t middleTopZ     = 0;
3437 
3438     writeWall(WallEdge(WallSpec::fromMapSide(seg.lineSide(), LineSide::Bottom), hedge, Line::From),
3439               WallEdge(WallSpec::fromMapSide(seg.lineSide(), LineSide::Bottom), hedge, Line::To  ));
3440     writeWall(WallEdge(WallSpec::fromMapSide(seg.lineSide(), LineSide::Top),    hedge, Line::From),
3441               WallEdge(WallSpec::fromMapSide(seg.lineSide(), LineSide::Top),    hedge, Line::To  ));
3442     writeWall(WallEdge(WallSpec::fromMapSide(seg.lineSide(), LineSide::Middle), hedge, Line::From),
3443               WallEdge(WallSpec::fromMapSide(seg.lineSide(), LineSide::Middle), hedge, Line::To  ),
3444               &wroteOpaqueMiddle, &middleBottomZ, &middleTopZ);
3445 
3446     // We can occlude the angle range defined by the X|Y origins of the
3447     // line segment if the open range has been covered (when the viewer
3448     // is not in the void).
3449     if (!P_IsInVoid(viewPlayer) && coveredOpenRange(hedge, middleBottomZ, middleTopZ, wroteOpaqueMiddle))
3450     {
3451         if (hedge.hasMapElement())
3452         {
3453             // IssueID #2306: Black segments appear in the sky due to polyobj walls being marked
3454             // as occluding angle ranges. As a workaround, don't consider these walls occluding.
3455             if (hedge.mapElementAs<LineSideSegment>().line().definesPolyobj())
3456             {
3457                 const Polyobj &poly = hedge.mapElementAs<LineSideSegment>().line().polyobj();
3458                 if (poly.sector().ceiling().surface().hasSkyMaskedMaterial())
3459                 {
3460                     return;
3461                 }
3462             }
3463         }
3464         ClientApp::renderSystem().angleClipper()
3465             .addRangeFromViewRelPoints(hedge.origin(), hedge.twin().origin());
3466     }
3467 }
3468 
writeSubspaceWalls()3469 static void writeSubspaceWalls()
3470 {
3471     DENG2_ASSERT(::curSubspace);
3472     HEdge *base  = ::curSubspace->poly().hedge();
3473     DENG2_ASSERT(base);
3474     HEdge *hedge = base;
3475     do
3476     {
3477         writeAllWalls(*hedge);
3478     } while ((hedge = &hedge->next()) != base);
3479 
3480     ::curSubspace->forAllExtraMeshes([] (Mesh &mesh)
3481     {
3482         for (HEdge *hedge : mesh.hedges())
3483         {
3484             writeAllWalls(*hedge);
3485         }
3486         return LoopContinue;
3487     });
3488 
3489     ::curSubspace->forAllPolyobjs([] (Polyobj &pob)
3490     {
3491         for (HEdge *hedge : pob.mesh().hedges())
3492         {
3493             writeAllWalls(*hedge);
3494         }
3495         return LoopContinue;
3496     });
3497 }
3498 
writeSubspaceFlats()3499 static void writeSubspaceFlats()
3500 {
3501     DENG2_ASSERT(::curSubspace);
3502     auto &subsec = ::curSubspace->subsector().as<world::ClientSubsector>();
3503     for (dint i = 0; i < subsec.visPlaneCount(); ++i)
3504     {
3505         // Skip planes facing away from the viewer.
3506         Plane &plane = subsec.visPlane(i);
3507         Vector3d const pointOnPlane(subsec.center(), plane.heightSmoothed());
3508         if ((eyeOrigin - pointOnPlane).dot(plane.surface().normal()) < 0)
3509             continue;
3510 
3511         writeSubspacePlane(plane);
3512     }
3513 }
3514 
markFrontFacingWalls(HEdge & hedge)3515 static void markFrontFacingWalls(HEdge &hedge)
3516 {
3517     if (!hedge.hasMapElement()) return;
3518     // Which way is the line segment facing?
3519     hedge.mapElementAs<LineSideSegment>()
3520               .setFrontFacing(viewFacingDot(hedge.origin(), hedge.twin().origin()) >= 0);
3521 }
3522 
markSubspaceFrontFacingWalls()3523 static void markSubspaceFrontFacingWalls()
3524 {
3525     DENG2_ASSERT(::curSubspace);
3526 
3527     HEdge *base  = ::curSubspace->poly().hedge();
3528     DENG2_ASSERT(base);
3529     HEdge *hedge = base;
3530     do
3531     {
3532         markFrontFacingWalls(*hedge);
3533     } while ((hedge = &hedge->next()) != base);
3534 
3535     ::curSubspace->forAllExtraMeshes([] (Mesh &mesh)
3536     {
3537         for (HEdge *hedge : mesh.hedges())
3538         {
3539             markFrontFacingWalls(*hedge);
3540         }
3541         return LoopContinue;
3542     });
3543 
3544     ::curSubspace->forAllPolyobjs([] (Polyobj &pob)
3545     {
3546         for (HEdge *hedge : pob.mesh().hedges())
3547         {
3548             markFrontFacingWalls(*hedge);
3549         }
3550         return LoopContinue;
3551     });
3552 }
3553 
canOccludeEdgeBetweenPlanes(Plane const & frontPlane,Plane const & backPlane)3554 static inline bool canOccludeEdgeBetweenPlanes(Plane const &frontPlane, Plane const &backPlane)
3555 {
3556     // Do not create an occlusion between two sky-masked planes.
3557     // Only because the open range does not account for the sky plane height? -ds
3558     return !(frontPlane.surface().hasSkyMaskedMaterial() && backPlane .surface().hasSkyMaskedMaterial());
3559 }
3560 
3561 /**
3562  * Add angle clipper occlusion ranges for the edges of the current subspace.
3563  */
occludeSubspace(bool frontFacing)3564 static void occludeSubspace(bool frontFacing)
3565 {
3566     if (devNoCulling) return;
3567     if (P_IsInVoid(viewPlayer)) return;
3568 
3569     AngleClipper &clipper = ClientApp::renderSystem().angleClipper();
3570 
3571     auto const &subsec = ::curSubspace->subsector().as<world::ClientSubsector>();
3572     HEdge const *base  = ::curSubspace->poly().hedge();
3573     DENG2_ASSERT(base);
3574     HEdge const *hedge = base;
3575     do
3576     {
3577         // Edges without a line segment can never occlude.
3578         if (!hedge->hasMapElement())
3579             continue;
3580 
3581         auto &seg = hedge->mapElementAs<LineSideSegment>();
3582 
3583         // Only front-facing edges can occlude.
3584         if (frontFacing != seg.isFrontFacing())
3585             continue;
3586 
3587         // Edges without line segment surface sections can never occlude.
3588         if (!seg.lineSide().hasSections())
3589             continue;
3590 
3591         // Occlusions should only happen where two sectors meet.
3592         if (!hedge->hasTwin() || !hedge->twin().hasFace()
3593             || !hedge->twin().face().hasMapElement())
3594             continue;
3595 
3596         auto const &backSubspace = hedge->twin().face().mapElementAs<ConvexSubspace>();
3597         auto const &backSubsec   = backSubspace.subsector().as<world::ClientSubsector>();
3598 
3599         // Determine the opening between plane heights at this edge.
3600         ddouble openBottom = de::max(backSubsec.visFloor().heightSmoothed(),
3601                                      subsec    .visFloor().heightSmoothed());
3602 
3603         ddouble openTop = de::min(backSubsec.visCeiling().heightSmoothed(),
3604                                   subsec    .visCeiling().heightSmoothed());
3605 
3606         // Choose start and end vertexes so that it's facing forward.
3607         Vertex const &from = frontFacing ? hedge->vertex() : hedge->twin().vertex();
3608         Vertex const &to   = frontFacing ? hedge->twin().vertex() : hedge->vertex();
3609 
3610         // Does the floor create an occlusion?
3611         if ( (   (openBottom > subsec    .visFloor().heightSmoothed() && Rend_EyeOrigin().y <= openBottom)
3612               || (openBottom > backSubsec.visFloor().heightSmoothed() && Rend_EyeOrigin().y >= openBottom))
3613             && canOccludeEdgeBetweenPlanes(subsec.visFloor(), backSubsec.visFloor()))
3614         {
3615             clipper.addViewRelOcclusion(from.origin(), to.origin(), openBottom, false);
3616         }
3617 
3618         // Does the ceiling create an occlusion?
3619         if ( (   (openTop < subsec    .visCeiling().heightSmoothed() && Rend_EyeOrigin().y >= openTop)
3620               || (openTop < backSubsec.visCeiling().heightSmoothed() && Rend_EyeOrigin().y <= openTop))
3621             && canOccludeEdgeBetweenPlanes(subsec.visCeiling(), backSubsec.visCeiling()))
3622         {
3623             clipper.addViewRelOcclusion(from.origin(), to.origin(), openTop, true);
3624         }
3625     } while ((hedge = &hedge->next()) != base);
3626 }
3627 
clipSubspaceLumobjs()3628 static void clipSubspaceLumobjs()
3629 {
3630     DENG2_ASSERT(::curSubspace);
3631     ::curSubspace->forAllLumobjs([] (Lumobj &lob)
3632     {
3633         R_ViewerClipLumobj(&lob);
3634         return LoopContinue;
3635     });
3636 }
3637 
3638 /**
3639  * In the situation where a subspace contains both lumobjs and a polyobj, lumobjs
3640  * must be clipped more carefully. Here we check if the line of sight intersects
3641  * any of the polyobj half-edges facing the viewer.
3642  */
clipSubspaceLumobjsBySight()3643 static void clipSubspaceLumobjsBySight()
3644 {
3645     DENG2_ASSERT(::curSubspace);
3646 
3647     // Any work to do?
3648     if (!::curSubspace->polyobjCount())
3649         return;
3650 
3651     ::curSubspace->forAllLumobjs([] (Lumobj &lob)
3652     {
3653         R_ViewerClipLumobjBySight(&lob, ::curSubspace);
3654         return LoopContinue;
3655     });
3656 }
3657 
3658 /// If not front facing this is no-op.
clipFrontFacingWalls(HEdge & hedge)3659 static void clipFrontFacingWalls(HEdge &hedge)
3660 {
3661     if (!hedge.hasMapElement())
3662         return;
3663 
3664     auto &seg = hedge.mapElementAs<LineSideSegment>();
3665     if (seg.isFrontFacing())
3666     {
3667         if (!ClientApp::renderSystem().angleClipper().checkRangeFromViewRelPoints(hedge.origin(), hedge.twin().origin()))
3668         {
3669             seg.setFrontFacing(false);
3670         }
3671     }
3672 }
3673 
clipSubspaceFrontFacingWalls()3674 static void clipSubspaceFrontFacingWalls()
3675 {
3676     DENG2_ASSERT(::curSubspace);
3677 
3678     HEdge *base  = ::curSubspace->poly().hedge();
3679     DENG2_ASSERT(base);
3680     HEdge *hedge = base;
3681     do
3682     {
3683         clipFrontFacingWalls(*hedge);
3684     } while ((hedge = &hedge->next()) != base);
3685 
3686     ::curSubspace->forAllExtraMeshes([] (Mesh &mesh)
3687     {
3688         for (HEdge *hedge : mesh.hedges())
3689         {
3690             clipFrontFacingWalls(*hedge);
3691         }
3692         return LoopContinue;
3693     });
3694 
3695     ::curSubspace->forAllPolyobjs([] (Polyobj &pob)
3696     {
3697         for (HEdge *hedge : pob.mesh().hedges())
3698         {
3699             clipFrontFacingWalls(*hedge);
3700         }
3701         return LoopContinue;
3702     });
3703 }
3704 
projectSubspaceSprites()3705 static void projectSubspaceSprites()
3706 {
3707     DENG2_ASSERT(::curSubspace);
3708 
3709     // Do not use validCount because other parts of the renderer may change it.
3710     if (::curSubspace->lastSpriteProjectFrame() == R_FrameCount())
3711         return;  // Already added.
3712 
3713     R_ForAllSubspaceMobContacts(*::curSubspace, [] (mobj_t &mob)
3714     {
3715         auto const &subsec = ::curSubspace->subsector().as<world::ClientSubsector>();
3716         if (mob.addFrameCount != R_FrameCount())
3717         {
3718             mob.addFrameCount = R_FrameCount();
3719 
3720             R_ProjectSprite(mob);
3721 
3722             // Kludge: Map-objects have a tendency to extend into the ceiling in
3723             // sky sectors. Here we will raise the skyfix dynamically, to make
3724             // sure they don't get clipped by the sky.
3725             if (subsec.visCeiling().surface().hasSkyMaskedMaterial())
3726             {
3727                 /// @todo fixme: Consider 3D models, also. -ds
3728                 if (Record const *spriteRec = Mobj_SpritePtr(mob))
3729                 {
3730                     defn::Sprite const sprite(*spriteRec);
3731                     de::Uri const &viewMaterial = sprite.viewMaterial(0);
3732                     if (!viewMaterial.isEmpty())
3733                     {
3734                         if (world::Material *material = world::Materials::get().materialPtr(viewMaterial))
3735                         {
3736                             if (!(mob.dPlayer && (mob.dPlayer->flags & DDPF_CAMERA))
3737                                && mob.origin[2] <= subsec.visCeiling().heightSmoothed()
3738                                && mob.origin[2] >= subsec.visFloor  ().heightSmoothed())
3739                             {
3740                                 world::ClSkyPlane &skyCeiling = subsec.sector().map().skyCeiling();
3741                                 ddouble visibleTop = mob.origin[2] + material->height();
3742                                 if (visibleTop > skyCeiling.height())
3743                                 {
3744                                     // Raise the ceiling!
3745                                     skyCeiling.setHeight(visibleTop + 16/*leeway*/);
3746                                 }
3747                             }
3748                         }
3749                     }
3750                 }
3751             }
3752         }
3753         return LoopContinue;
3754     });
3755 
3756     ::curSubspace->setLastSpriteProjectFrame(R_FrameCount());
3757 }
3758 
3759 /**
3760  * @pre Assumes the subspace is at least partially visible.
3761  */
drawCurrentSubspace()3762 static void drawCurrentSubspace()
3763 {
3764     DENG2_ASSERT(curSubspace);
3765 
3766     Sector &sector = ::curSubspace->sector();
3767 
3768     // Mark the leaf as visible for this frame.
3769     R_ViewerSubspaceMarkVisible(*::curSubspace);
3770 
3771     markSubspaceFrontFacingWalls();
3772 
3773     // Perform contact spreading for this map region.
3774     sector.map().spreadAllContacts(::curSubspace->poly().bounds());
3775 
3776     Rend_DrawFlatRadio(*::curSubspace);
3777 
3778     // Before clip testing lumobjs (for halos), range-occlude the back facing edges.
3779     // After testing, range-occlude the front facing edges. Done before drawing wall
3780     // sections so that opening occlusions cut out unnecessary oranges.
3781 
3782     occludeSubspace(false /* back facing */);
3783     clipSubspaceLumobjs();
3784     occludeSubspace(true /* front facing */);
3785 
3786     clipSubspaceFrontFacingWalls();
3787     clipSubspaceLumobjsBySight();
3788 
3789     // Mark generators in the sector visible.
3790     if (::useParticles)
3791     {
3792         sector.map().forAllGeneratorsInSector(sector, [] (Generator &gen)
3793         {
3794             R_ViewerGeneratorMarkVisible(gen);
3795             return LoopContinue;
3796         });
3797     }
3798 
3799     // Sprites for this subspace have to be drawn.
3800     //
3801     // Must be done BEFORE the wall segments of this subspace are added to the
3802     // clipper. Otherwise the sprites would get clipped by them, and that wouldn't
3803     // be right.
3804     //
3805     // Must be done AFTER the lumobjs have been clipped as this affects the projection
3806     // of halos.
3807     projectSubspaceSprites();
3808 
3809     writeSubspaceSkyMask();
3810     writeSubspaceWalls();
3811     writeSubspaceFlats();
3812 }
3813 
3814 /**
3815  * Change the current subspace (updating any relevant draw state properties
3816  * accordingly).
3817  *
3818  * @param subspace  The new subspace to make current.
3819  */
makeCurrent(ConvexSubspace & subspace)3820 static void makeCurrent(ConvexSubspace &subspace)
3821 {
3822     bool const subsecChanged = (!::curSubspace || ::curSubspace->subsectorPtr() != subspace.subsectorPtr());
3823 
3824     ::curSubspace = &subspace;
3825 
3826     // Update draw state.
3827     if (subsecChanged)
3828     {
3829         Vector4f const color = subspace.subsector().as<world::ClientSubsector>().lightSourceColorfIntensity();
3830         ::curSectorLightColor = color.toVector3f();
3831         ::curSectorLightLevel = color.w;
3832     }
3833 }
3834 
traverseBspTreeAndDrawSubspaces(BspTree const * bspTree)3835 static void traverseBspTreeAndDrawSubspaces(BspTree const *bspTree)
3836 {
3837     DENG2_ASSERT(bspTree);
3838     AngleClipper const &clipper = ClientApp::renderSystem().angleClipper();
3839 
3840     while (!bspTree->isLeaf())
3841     {
3842         // Descend deeper into the nodes.
3843         auto const &bspNode = bspTree->userData()->as<BspNode>();
3844         // Decide which side the view point is on.
3845         dint const eyeSide  = bspNode.pointOnSide(eyeOrigin) < 0;
3846 
3847         // Recursively divide front space.
3848         traverseBspTreeAndDrawSubspaces(bspTree->childPtr(BspTree::ChildId(eyeSide)));
3849 
3850         // If the clipper is full we're pretty much done. This means no geometry
3851         // will be visible in the distance because every direction has already
3852         // been fully covered by geometry.
3853         if (!::firstSubspace && clipper.isFull())
3854             return;
3855 
3856         // ...and back space.
3857         bspTree = bspTree->childPtr(BspTree::ChildId(eyeSide ^ 1));
3858     }
3859     // We've arrived at a leaf.
3860 
3861     // Only leafs with a convex subspace geometry contain surfaces to draw.
3862     if (ConvexSubspace *subspace = bspTree->userData()->as<BspLeaf>().subspacePtr())
3863     {
3864         DENG2_ASSERT(subspace->hasSubsector());
3865 
3866         // Skip zero-volume subspaces.
3867         // (Neighbors handle the angle clipper ranges.)
3868         if (!subspace->subsector().as<world::ClientSubsector>().hasWorldVolume())
3869             return;
3870 
3871         // Is this subspace visible?
3872         if (!::firstSubspace && !clipper.isPolyVisible(subspace->poly()))
3873             return;
3874 
3875         // This is now the current subspace.
3876         makeCurrent(*subspace);
3877 
3878         drawCurrentSubspace();
3879 
3880         // This is no longer the first subspace.
3881         ::firstSubspace = false;
3882     }
3883 }
3884 
3885 /**
3886  * Project all the non-clipped decorations. They become regular vissprites.
3887  */
generateDecorationFlares(Map & map)3888 static void generateDecorationFlares(Map &map)
3889 {
3890     Vector3d const viewPos = Rend_EyeOrigin().xzy();
3891     map.forAllLumobjs([&viewPos] (Lumobj &lob)
3892     {
3893         lob.generateFlare(viewPos, R_ViewerLumobjDistance(lob.indexInMap()));
3894 
3895         /// @todo mark these light sources visible for LensFx
3896         return LoopContinue;
3897     });
3898 }
3899 
Rend_SpriteMaterialAnimator(Record const & spriteDef)3900 MaterialAnimator *Rend_SpriteMaterialAnimator(Record const &spriteDef)
3901 {
3902     MaterialAnimator *matAnimator = nullptr;
3903 
3904     // Check the cache first.
3905     auto found = lookupSpriteMaterialAnimators.constFind(&spriteDef);
3906     if (found != lookupSpriteMaterialAnimators.constEnd())
3907     {
3908         matAnimator = found.value();
3909     }
3910     else
3911     {
3912         // Look it up...
3913         defn::Sprite const sprite(spriteDef);
3914         de::Uri const &viewMaterial = sprite.viewMaterial(0);
3915         if (!viewMaterial.isEmpty())
3916         {
3917             if (world::Material *mat = world::Materials::get().materialPtr(viewMaterial))
3918             {
3919                 matAnimator = &mat->as<ClientMaterial>().getAnimator(Rend_SpriteMaterialSpec());
3920             }
3921         }
3922         lookupSpriteMaterialAnimators.insert(&spriteDef, matAnimator);
3923     }
3924     return matAnimator;
3925 }
3926 
Rend_VisualRadius(Record const & spriteDef)3927 ddouble Rend_VisualRadius(Record const &spriteDef)
3928 {
3929     if (auto *anim = Rend_SpriteMaterialAnimator(spriteDef))
3930     {
3931         anim->prepare();  // Ensure we've up to date info.
3932         return anim->dimensions().x / 2;
3933     }
3934     return 0;
3935 }
3936 
Rend_MakeLumobj(Record const & spriteDef)3937 Lumobj *Rend_MakeLumobj(Record const &spriteDef)
3938 {
3939     LOG_AS("Rend_MakeLumobj");
3940 
3941     //defn::Sprite const sprite(spriteRec);
3942     //de::Uri const &viewMaterial = sprite.viewMaterial(0);
3943 
3944     // Always use the front view.
3945     /// @todo We could do better here...
3946     //if (viewMaterial.isEmpty()) return nullptr;
3947 
3948     //world::Material *mat = world::Materials::get().materialPtr(viewMaterial);
3949     //if (!mat) return nullptr;
3950 
3951     MaterialAnimator *matAnimator = Rend_SpriteMaterialAnimator(spriteDef); //mat->as<ClientMaterial>().getAnimator(Rend_SpriteMaterialSpec());
3952     if (!matAnimator) return nullptr;
3953 
3954     matAnimator->prepare();  // Ensure we have up-to-date info.
3955 
3956     TextureVariant *texture = matAnimator->texUnit(MaterialAnimator::TU_LAYER0).texture;
3957     if (!texture) return nullptr;  // Unloadable texture?
3958 
3959     auto const *pl = (pointlight_analysis_t const *)
3960             texture->base().analysisDataPointer(res::Texture::BrightPointAnalysis);
3961     if (!pl)
3962     {
3963         LOGDEV_RES_WARNING("Texture \"%s\" has no BrightPointAnalysis")
3964                 << texture->base().manifest().composeUri();
3965         return nullptr;
3966     }
3967 
3968     // Apply the auto-calculated color.
3969     return &(new Lumobj(Vector3d(), pl->brightMul, pl->color.rgb))
3970             ->setZOffset(-texture->base().origin().y - pl->originY * matAnimator->dimensions().y);
3971 }
3972 
3973 /**
3974  * Setup GL state for an entire rendering pass (compassing multiple lists).
3975  */
pushGLStateForPass(DrawMode mode,TexUnitMap & texUnitMap)3976 static void pushGLStateForPass(DrawMode mode, TexUnitMap &texUnitMap)
3977 {
3978     static dfloat const black[] = { 0, 0, 0, 0 };
3979 
3980     texUnitMap.fill(-1);
3981 
3982     switch (mode)
3983     {
3984     case DM_SKYMASK:
3985         GL_SelectTexUnits(0);
3986         DGL_Disable(DGL_ALPHA_TEST);
3987         DGL_Enable(DGL_DEPTH_WRITE);
3988         DGL_Enable(DGL_DEPTH_TEST);
3989         DGL_DepthFunc(DGL_LESS);
3990         break;
3991 
3992     case DM_BLENDED:
3993         GL_SelectTexUnits(2);
3994 
3995         // Intentional fall-through.
3996 
3997     case DM_ALL:
3998         // The first texture unit is used for the main texture.
3999         texUnitMap[0] = AttributeSpec::TexCoord0;
4000         texUnitMap[1] = AttributeSpec::TexCoord1;
4001         DGL_Disable(DGL_ALPHA_TEST);
4002         DGL_Enable(DGL_DEPTH_WRITE);
4003         DGL_Enable(DGL_DEPTH_TEST);
4004         DGL_DepthFunc(DGL_LESS);
4005 
4006         // Fog is allowed during this pass.
4007         if (fogParams.usingFog)
4008         {
4009             DGL_Enable(DGL_FOG);
4010         }
4011         // All of the surfaces are opaque.
4012         DGL_Disable(DGL_BLEND);
4013         break;
4014 
4015     case DM_LIGHT_MOD_TEXTURE:
4016     case DM_TEXTURE_PLUS_LIGHT:
4017         // Modulate sector light, dynamic light and regular texture.
4018         GL_SelectTexUnits(2);
4019         if (mode == DM_LIGHT_MOD_TEXTURE)
4020         {
4021             texUnitMap[0] = AttributeSpec::ModTexCoord;
4022             texUnitMap[1] = AttributeSpec::TexCoord0;
4023             DGL_ModulateTexture(4);  // Light * texture.
4024         }
4025         else
4026         {
4027             texUnitMap[0] = AttributeSpec::TexCoord0;
4028             texUnitMap[1] = AttributeSpec::ModTexCoord;
4029             DGL_ModulateTexture(5);  // Texture + light.
4030         }
4031         DGL_Disable(DGL_ALPHA_TEST);
4032         DGL_Enable(DGL_DEPTH_WRITE);
4033         DGL_Enable(DGL_DEPTH_TEST);
4034         DGL_DepthFunc(DGL_LESS);
4035 
4036         // Fog is allowed during this pass.
4037         if (fogParams.usingFog)
4038         {
4039             DGL_Enable(DGL_FOG);
4040         }
4041         // All of the surfaces are opaque.
4042         DGL_Disable(DGL_BLEND);
4043         break;
4044 
4045     case DM_FIRST_LIGHT:
4046         // One light, no texture.
4047         GL_SelectTexUnits(1);
4048         texUnitMap[0] = AttributeSpec::ModTexCoord;
4049         DGL_ModulateTexture(6);
4050         DGL_Disable(DGL_ALPHA_TEST);
4051         DGL_Enable(DGL_DEPTH_WRITE);
4052         DGL_Enable(DGL_DEPTH_TEST);
4053         DGL_DepthFunc(DGL_LESS);
4054         // All of the surfaces are opaque.
4055         DGL_Disable(DGL_BLEND);
4056         break;
4057 
4058     case DM_BLENDED_FIRST_LIGHT:
4059         // One additive light, no texture.
4060         GL_SelectTexUnits(1);
4061         texUnitMap[0] = AttributeSpec::ModTexCoord;
4062         DGL_ModulateTexture(7);  // Add light, no color.
4063         DGL_Enable(DGL_ALPHA_TEST);
4064         DGL_SetFloat(DGL_ALPHA_LIMIT, 1 / 255.0f);
4065         DGL_Disable(DGL_DEPTH_WRITE);
4066         DGL_Enable(DGL_DEPTH_TEST);
4067         DGL_DepthFunc(DGL_LEQUAL);
4068         // All of the surfaces are opaque.
4069         DGL_Enable(DGL_BLEND);
4070         DGL_BlendFunc(DGL_ONE, DGL_ONE);
4071         break;
4072 
4073     case DM_WITHOUT_TEXTURE:
4074         GL_SelectTexUnits(0);
4075         DGL_ModulateTexture(1);
4076         DGL_Disable(DGL_ALPHA_TEST);
4077         DGL_Enable(DGL_DEPTH_WRITE);
4078         DGL_Enable(DGL_DEPTH_TEST);
4079         DGL_DepthFunc(DGL_LESS);
4080         // All of the surfaces are opaque.
4081         DGL_Disable(DGL_BLEND);
4082         break;
4083 
4084     case DM_LIGHTS:
4085         GL_SelectTexUnits(1);
4086         texUnitMap[0] = AttributeSpec::TexCoord0;
4087         DGL_ModulateTexture(1);
4088         DGL_Enable(DGL_ALPHA_TEST);
4089         DGL_SetFloat(DGL_ALPHA_LIMIT, 1 / 255.0f);
4090         DGL_Disable(DGL_DEPTH_WRITE);
4091         DGL_Enable(DGL_DEPTH_TEST);
4092         DGL_DepthFunc(DGL_LEQUAL);
4093 
4094         if (fogParams.usingFog)
4095         {
4096             DGL_Enable(DGL_FOG);
4097             DGL_Fogfv(DGL_FOG_COLOR, black);
4098         }
4099 
4100         DGL_Enable(DGL_BLEND);
4101         GL_BlendMode(BM_ADD);
4102         break;
4103 
4104     case DM_MOD_TEXTURE:
4105     case DM_MOD_TEXTURE_MANY_LIGHTS:
4106     case DM_BLENDED_MOD_TEXTURE:
4107         // The first texture unit is used for the main texture.
4108         texUnitMap[0] = AttributeSpec::TexCoord0;
4109         texUnitMap[1] = AttributeSpec::TexCoord1;
4110         DGL_Disable(DGL_ALPHA_TEST);
4111         DGL_Disable(DGL_DEPTH_WRITE);
4112         DGL_Enable(DGL_DEPTH_TEST);
4113         DGL_DepthFunc(DGL_LEQUAL);
4114 
4115         // All of the surfaces are opaque.
4116         DGL_Enable(DGL_BLEND);
4117         DGL_BlendFunc(DGL_DST_COLOR, DGL_ZERO);
4118         break;
4119 
4120     case DM_UNBLENDED_TEXTURE_AND_DETAIL:
4121         texUnitMap[0] = AttributeSpec::TexCoord0;
4122         texUnitMap[1] = AttributeSpec::TexCoord0;
4123         DGL_Disable(DGL_ALPHA_TEST);
4124         DGL_Enable(DGL_DEPTH_WRITE);
4125         DGL_Enable(DGL_DEPTH_TEST);
4126         DGL_DepthFunc(DGL_LESS);
4127 
4128         // All of the surfaces are opaque.
4129         DGL_Disable(DGL_BLEND);
4130         // Fog is allowed.
4131         if (fogParams.usingFog)
4132         {
4133             DGL_Enable(DGL_FOG);
4134         }
4135         break;
4136 
4137     case DM_UNBLENDED_MOD_TEXTURE_AND_DETAIL:
4138         texUnitMap[0] = AttributeSpec::TexCoord0;
4139         texUnitMap[1] = AttributeSpec::TexCoord0;
4140         DGL_Disable(DGL_ALPHA_TEST);
4141         DGL_Disable(DGL_DEPTH_WRITE);
4142         DGL_Enable(DGL_DEPTH_TEST);
4143         DGL_DepthFunc(DGL_LEQUAL);
4144 
4145         // All of the surfaces are opaque.
4146         DGL_Enable(DGL_BLEND);
4147         DGL_BlendFunc(DGL_DST_COLOR, DGL_ZERO);
4148         break;
4149 
4150     case DM_ALL_DETAILS:
4151         GL_SelectTexUnits(1);
4152         texUnitMap[0] = AttributeSpec::TexCoord0;
4153         DGL_ModulateTexture(0);
4154         DGL_Disable(DGL_ALPHA_TEST);
4155         DGL_Disable(DGL_DEPTH_WRITE);
4156         DGL_Enable(DGL_DEPTH_TEST);
4157         DGL_DepthFunc(DGL_LEQUAL);
4158 
4159         // All of the surfaces are opaque.
4160         DGL_Enable(DGL_BLEND);
4161         DGL_BlendFunc(DGL_DST_COLOR, DGL_SRC_COLOR);
4162         // Use fog to fade the details, if fog is enabled.
4163         if (fogParams.usingFog)
4164         {
4165             DGL_Enable(DGL_FOG);
4166             dfloat const midGray[] = { .5f, .5f, .5f, fogParams.fogColor[3] };  // The alpha is probably meaningless?
4167             DGL_Fogfv(DGL_FOG_COLOR, midGray);
4168         }
4169         break;
4170 
4171     case DM_BLENDED_DETAILS:
4172         GL_SelectTexUnits(2);
4173         texUnitMap[0] = AttributeSpec::TexCoord0;
4174         texUnitMap[1] = AttributeSpec::TexCoord1;
4175         DGL_ModulateTexture(3);
4176         DGL_Disable(DGL_ALPHA_TEST);
4177         DGL_Disable(DGL_DEPTH_WRITE);
4178         DGL_Enable(DGL_DEPTH_TEST);
4179         DGL_DepthFunc(DGL_LEQUAL);
4180 
4181         // All of the surfaces are opaque.
4182         DGL_Enable(DGL_BLEND);
4183         DGL_BlendFunc(DGL_DST_COLOR, DGL_SRC_COLOR);
4184         // Use fog to fade the details, if fog is enabled.
4185         if (fogParams.usingFog)
4186         {
4187             DGL_Enable(DGL_FOG);
4188             dfloat const midGray[] = { .5f, .5f, .5f, fogParams.fogColor[3] };  // The alpha is probably meaningless?
4189             DGL_Fogfv(DGL_FOG_COLOR, midGray);
4190         }
4191         break;
4192 
4193     case DM_SHADOW:
4194         // A bit like 'negative lights'.
4195         GL_SelectTexUnits(1);
4196         texUnitMap[0] = AttributeSpec::TexCoord0;
4197         DGL_ModulateTexture(1);
4198         DGL_Enable(DGL_ALPHA_TEST);
4199         DGL_SetFloat(DGL_ALPHA_LIMIT, 1 / 255.0f);
4200         DGL_Disable(DGL_DEPTH_WRITE);
4201         DGL_Enable(DGL_DEPTH_TEST);
4202         DGL_DepthFunc(DGL_LEQUAL);
4203         // Set normal fog, if it's enabled.
4204         if (fogParams.usingFog)
4205         {
4206             DGL_Enable(DGL_FOG);
4207             DGL_Fogfv(DGL_FOG_COLOR, fogParams.fogColor);
4208         }
4209         DGL_Enable(DGL_BLEND);
4210         GL_BlendMode(BM_NORMAL);
4211         break;
4212 
4213     case DM_SHINY:
4214         GL_SelectTexUnits(1);
4215         texUnitMap[0] = AttributeSpec::TexCoord0;
4216         DGL_ModulateTexture(1);  // 8 for multitexture
4217         DGL_Disable(DGL_ALPHA_TEST);
4218         DGL_Disable(DGL_DEPTH_WRITE);
4219         DGL_Enable(DGL_DEPTH_TEST);
4220         DGL_DepthFunc(DGL_LEQUAL);
4221 
4222         if (fogParams.usingFog)
4223         {
4224             // Fog makes the shininess diminish in the distance.
4225             DGL_Enable(DGL_FOG);
4226             DGL_Fogfv(DGL_FOG_COLOR, black);
4227         }
4228         DGL_Enable(DGL_BLEND);
4229         GL_BlendMode(BM_ADD);  // Purely additive.
4230         break;
4231 
4232     case DM_MASKED_SHINY:
4233         GL_SelectTexUnits(2);
4234         texUnitMap[0] = AttributeSpec::TexCoord0;
4235         texUnitMap[1] = AttributeSpec::TexCoord1;  // the mask
4236         DGL_ModulateTexture(8);  // same as with details
4237         DGL_Disable(DGL_ALPHA_TEST);
4238         DGL_Disable(DGL_DEPTH_WRITE);
4239         DGL_Enable(DGL_DEPTH_TEST);
4240         DGL_DepthFunc(DGL_LEQUAL);
4241 
4242         if (fogParams.usingFog)
4243         {
4244             // Fog makes the shininess diminish in the distance.
4245             DGL_Enable(DGL_FOG);
4246             DGL_Fogfv(DGL_FOG_COLOR, black);
4247         }
4248         DGL_Enable(DGL_BLEND);
4249         GL_BlendMode(BM_ADD);  // Purely additive.
4250         break;
4251 
4252     default: break;
4253     }
4254 }
4255 
popGLStateForPass(DrawMode mode)4256 static void popGLStateForPass(DrawMode mode)
4257 {
4258     switch (mode)
4259     {
4260     default: break;
4261 
4262     case DM_SKYMASK:
4263         GL_SelectTexUnits(1);
4264         DGL_Enable(DGL_ALPHA_TEST);
4265         DGL_Disable(DGL_DEPTH_TEST);
4266         break;
4267 
4268     case DM_BLENDED:
4269         GL_SelectTexUnits(1);
4270 
4271         // Intentional fall-through.
4272     case DM_ALL:
4273         DGL_Enable(DGL_ALPHA_TEST);
4274         DGL_Disable(DGL_DEPTH_TEST);
4275         if (fogParams.usingFog)
4276         {
4277             DGL_Disable(DGL_FOG);
4278         }
4279         DGL_Enable(DGL_BLEND);
4280         break;
4281 
4282     case DM_LIGHT_MOD_TEXTURE:
4283     case DM_TEXTURE_PLUS_LIGHT:
4284         GL_SelectTexUnits(1);
4285         DGL_ModulateTexture(1);
4286         DGL_Enable(DGL_ALPHA_TEST);
4287         DGL_Disable(DGL_DEPTH_TEST);
4288         if (fogParams.usingFog)
4289         {
4290             DGL_Disable(DGL_FOG);
4291         }
4292         DGL_Enable(DGL_BLEND);
4293         break;
4294 
4295     case DM_FIRST_LIGHT:
4296         DGL_ModulateTexture(1);
4297         DGL_Enable(DGL_ALPHA_TEST);
4298         DGL_Disable(DGL_DEPTH_TEST);
4299         DGL_Enable(DGL_BLEND);
4300         break;
4301 
4302     case DM_BLENDED_FIRST_LIGHT:
4303         DGL_ModulateTexture(1);
4304         DGL_Disable(DGL_DEPTH_TEST);
4305         DGL_BlendFunc(DGL_SRC_ALPHA, DGL_ONE_MINUS_SRC_ALPHA);
4306         break;
4307 
4308     case DM_WITHOUT_TEXTURE:
4309         GL_SelectTexUnits(1);
4310         DGL_ModulateTexture(1);
4311         DGL_Enable(DGL_ALPHA_TEST);
4312         DGL_Disable(DGL_DEPTH_TEST);
4313         DGL_Enable(DGL_BLEND);
4314         break;
4315 
4316     case DM_LIGHTS:
4317         DGL_Disable(DGL_DEPTH_TEST);
4318         if (fogParams.usingFog)
4319         {
4320             DGL_Disable(DGL_FOG);
4321         }
4322         GL_BlendMode(BM_NORMAL);
4323         break;
4324 
4325     case DM_MOD_TEXTURE:
4326     case DM_MOD_TEXTURE_MANY_LIGHTS:
4327     case DM_BLENDED_MOD_TEXTURE:
4328         DGL_Enable(DGL_ALPHA_TEST);
4329         DGL_Disable(DGL_DEPTH_TEST);
4330         DGL_BlendFunc(DGL_SRC_ALPHA, DGL_ONE_MINUS_SRC_ALPHA);
4331         break;
4332 
4333     case DM_UNBLENDED_TEXTURE_AND_DETAIL:
4334         DGL_Enable(DGL_ALPHA_TEST);
4335         DGL_Disable(DGL_DEPTH_TEST);
4336         DGL_Enable(DGL_BLEND);
4337         if (fogParams.usingFog)
4338         {
4339             DGL_Disable(DGL_FOG);
4340         }
4341         break;
4342 
4343     case DM_UNBLENDED_MOD_TEXTURE_AND_DETAIL:
4344         DGL_Enable(DGL_ALPHA_TEST);
4345         DGL_Disable(DGL_DEPTH_TEST);
4346         DGL_BlendFunc(DGL_SRC_ALPHA, DGL_ONE_MINUS_SRC_ALPHA);
4347         break;
4348 
4349     case DM_ALL_DETAILS:
4350         DGL_ModulateTexture(1);
4351         DGL_Enable(DGL_ALPHA_TEST);
4352         DGL_Disable(DGL_DEPTH_TEST);
4353         DGL_BlendFunc(DGL_SRC_ALPHA, DGL_ONE_MINUS_SRC_ALPHA);
4354         if (fogParams.usingFog)
4355         {
4356             DGL_Disable(DGL_FOG);
4357         }
4358         break;
4359 
4360     case DM_BLENDED_DETAILS:
4361         GL_SelectTexUnits(1);
4362         DGL_ModulateTexture(1);
4363         DGL_Enable(DGL_ALPHA_TEST);
4364         DGL_Disable(DGL_DEPTH_TEST);
4365         DGL_BlendFunc(DGL_SRC_ALPHA, DGL_ONE_MINUS_SRC_ALPHA);
4366         if (fogParams.usingFog)
4367         {
4368             DGL_Disable(DGL_FOG);
4369         }
4370         break;
4371 
4372     case DM_SHADOW:
4373         DGL_Disable(DGL_DEPTH_TEST);
4374         if (fogParams.usingFog)
4375         {
4376             DGL_Disable(DGL_FOG);
4377         }
4378         break;
4379 
4380     case DM_SHINY:
4381         DGL_Enable(DGL_ALPHA_TEST);
4382         DGL_Disable(DGL_DEPTH_TEST);
4383         if (fogParams.usingFog)
4384         {
4385             DGL_Disable(DGL_FOG);
4386         }
4387         GL_BlendMode(BM_NORMAL);
4388         break;
4389 
4390     case DM_MASKED_SHINY:
4391         GL_SelectTexUnits(1);
4392         DGL_ModulateTexture(1);
4393         DGL_Enable(DGL_ALPHA_TEST);
4394         DGL_Disable(DGL_DEPTH_TEST);
4395         if (fogParams.usingFog)
4396         {
4397             DGL_Disable(DGL_FOG);
4398         }
4399         GL_BlendMode(BM_NORMAL);
4400         break;
4401     }
4402 }
4403 
drawLists(DrawLists::FoundLists const & lists,DrawMode mode)4404 static void drawLists(DrawLists::FoundLists const &lists, DrawMode mode)
4405 {
4406     if (lists.isEmpty()) return;
4407     // If the first list is empty -- do nothing.
4408     if (lists.at(0)->isEmpty()) return;
4409 
4410     // Setup GL state that's common to all the lists in this mode.
4411     TexUnitMap texUnitMap;
4412     pushGLStateForPass(mode, texUnitMap);
4413 
4414     // Draw each given list.
4415     for (dint i = 0; i < lists.count(); ++i)
4416     {
4417         lists.at(i)->draw(mode, texUnitMap);
4418     }
4419 
4420     popGLStateForPass(mode);
4421 }
4422 
drawSky()4423 static void drawSky()
4424 {
4425     DrawLists::FoundLists lists;
4426     ClientApp::renderSystem().drawLists().findAll(SkyMaskGeom, lists);
4427     if (!devRendSkyAlways && lists.isEmpty())
4428     {
4429         return;
4430     }
4431 
4432     // We do not want to update color and/or depth.
4433     DGL_PushState();
4434     DGL_Disable(DGL_DEPTH_TEST);
4435     DGL_Disable(DGL_DEPTH_WRITE);
4436 
4437     GLState::current().setColorMask(gl::WriteNone);
4438 
4439     // Mask out stencil buffer, setting the drawn areas to 1.
4440     LIBGUI_GL.glEnable(GL_STENCIL_TEST);
4441     LIBGUI_GL.glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
4442     LIBGUI_GL.glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
4443 
4444     if (!devRendSkyAlways)
4445     {
4446         drawLists(lists, DM_SKYMASK);
4447     }
4448     else
4449     {
4450         LIBGUI_GL.glClearStencil(1);
4451         LIBGUI_GL.glClear(GL_STENCIL_BUFFER_BIT);
4452     }
4453 
4454     // Restore previous GL state.
4455     DGL_PopState();
4456     LIBGUI_GL.glDisable(GL_STENCIL_TEST);
4457 
4458     // Now, only render where the stencil is set to 1.
4459     LIBGUI_GL.glEnable(GL_STENCIL_TEST);
4460     LIBGUI_GL.glStencilFunc(GL_EQUAL, 1, 0xffffffff);
4461     LIBGUI_GL.glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
4462 
4463     ClientApp::renderSystem().sky().draw(&ClientApp::world().map().skyAnimator());
4464 
4465     if (!devRendSkyAlways)
4466     {
4467         LIBGUI_GL.glClearStencil(0);
4468     }
4469 
4470     // Return GL state to normal.
4471     LIBGUI_GL.glDisable(GL_STENCIL_TEST);
4472 }
4473 
generateHaloForVisSprite(vissprite_t const * spr,bool primary=false)4474 static bool generateHaloForVisSprite(vissprite_t const *spr, bool primary = false)
4475 {
4476     DENG2_ASSERT(spr);
4477 
4478     if (primary && (spr->data.flare.flags & RFF_NO_PRIMARY))
4479     {
4480         return false;
4481     }
4482 
4483     dfloat occlusionFactor;
4484     if (spr->data.flare.isDecoration)
4485     {
4486         // Surface decorations do not yet persist over frames, so we do
4487         // not smoothly occlude their flares. Instead, we will have to
4488         // put up with them instantly appearing/disappearing.
4489         occlusionFactor = R_ViewerLumobjIsClipped(spr->data.flare.lumIdx)? 0 : 1;
4490     }
4491     else
4492     {
4493         occlusionFactor = (spr->data.flare.factor & 0x7f) / 127.0f;
4494     }
4495 
4496     return H_RenderHalo(spr->pose.origin,
4497                         spr->data.flare.size,
4498                         spr->data.flare.tex,
4499                         spr->data.flare.color,
4500                         spr->pose.distance,
4501                         occlusionFactor, spr->data.flare.mul,
4502                         spr->data.flare.xOff, primary,
4503                         (spr->data.flare.flags & RFF_NO_TURN) == 0);
4504 }
4505 
4506 /**
4507  * Render sprites, 3D models, masked wall segments and halos, ordered back to
4508  * front. Halos are rendered with Z-buffer tests and writes disabled, so they
4509  * don't go into walls or interfere with real objects. It means that halos can
4510  * be partly occluded by objects that are closer to the viewpoint, but that's
4511  * the price to pay for not having access to the actual Z-buffer per-pixel depth
4512  * information. The other option would be for halos to shine through masked walls,
4513  * sprites and models, which looks even worse. (Plus, they are *halos*, not real
4514  * lens flares...)
4515  */
drawMasked()4516 static void drawMasked()
4517 {
4518     if (::devNoSprites) return;
4519 
4520     R_SortVisSprites();
4521 
4522     if (::visSpriteP && ::visSpriteP > ::visSprites)
4523     {
4524         bool primaryHaloDrawn = false;
4525 
4526         // Draw all vissprites back to front.
4527         // Sprites look better with Z buffer writes turned off.
4528         for (vissprite_t *spr = ::visSprSortedHead.next; spr != &::visSprSortedHead; spr = spr->next)
4529         {
4530             switch (spr->type)
4531             {
4532             default: break;
4533 
4534             case VSPR_MASKED_WALL:
4535                 // A masked wall is a specialized sprite.
4536                 Rend_DrawMaskedWall(spr->data.wall);
4537                 break;
4538 
4539             case VSPR_SPRITE:
4540                 // Render an old fashioned sprite, ah the nostalgia...
4541                 Rend_DrawSprite(*spr);
4542                 break;
4543 
4544             case VSPR_MODEL:
4545                 Rend_DrawModel(*spr);
4546                 break;
4547 
4548             case VSPR_MODELDRAWABLE:
4549                 DGL_Flush();
4550                 ClientApp::renderSystem().modelRenderer().render(*spr);
4551                 break;
4552 
4553             case VSPR_FLARE:
4554                 if (generateHaloForVisSprite(spr, true))
4555                 {
4556                     primaryHaloDrawn = true;
4557                 }
4558                 break;
4559             }
4560         }
4561 
4562         // Draw secondary halos?
4563         if (primaryHaloDrawn && ::haloMode > 1)
4564         {
4565             // Now we can setup the state only once.
4566             H_SetupState(true);
4567 
4568             for (vissprite_t *spr = ::visSprSortedHead.next; spr != &::visSprSortedHead; spr = spr->next)
4569             {
4570                 if (spr->type == VSPR_FLARE)
4571                 {
4572                     generateHaloForVisSprite(spr);
4573                 }
4574             }
4575 
4576             // And we're done...
4577             H_SetupState(false);
4578         }
4579     }
4580 }
4581 
4582 /**
4583  * We have several different paths to accommodate both multitextured details and
4584  * dynamic lights. Details take precedence (they always cover entire primitives
4585  * and usually *all* of the surfaces in a scene).
4586  */
drawAllLists(Map & map)4587 static void drawAllLists(Map &map)
4588 {
4589     DENG2_ASSERT(!Sys_GLCheckError());
4590     DENG2_ASSERT_IN_RENDER_THREAD();
4591     DENG_ASSERT_GL_CONTEXT_ACTIVE();
4592 
4593     drawSky();
4594 
4595     // Render the real surfaces of the visible world.
4596 
4597     //
4598     // Pass: Unlit geometries (all normal lists).
4599     //
4600     DrawLists::FoundLists lists;
4601     ClientApp::renderSystem().drawLists().findAll(UnlitGeom, lists);
4602     if (IS_MTEX_DETAILS)
4603     {
4604         // Draw details for unblended surfaces in this pass.
4605         drawLists(lists, DM_UNBLENDED_TEXTURE_AND_DETAIL);
4606 
4607         // Blended surfaces.
4608         drawLists(lists, DM_BLENDED);
4609     }
4610     else
4611     {
4612         // Blending is done during this pass.
4613         drawLists(lists, DM_ALL);
4614     }
4615 
4616     //
4617     // Pass: Lit geometries.
4618     //
4619     ClientApp::renderSystem().drawLists().findAll(LitGeom, lists);
4620 
4621     // If multitexturing is available, we'll use it to our advantage when
4622     // rendering lights.
4623     if (IS_MTEX_LIGHTS && dynlightBlend != 2)
4624     {
4625         if (IS_MUL)
4626         {
4627             // All (unblended) surfaces with exactly one light can be
4628             // rendered in a single pass.
4629             drawLists(lists, DM_LIGHT_MOD_TEXTURE);
4630 
4631             // Render surfaces with many lights without a texture, just
4632             // with the first light.
4633             drawLists(lists, DM_FIRST_LIGHT);
4634         }
4635         else // Additive ('foggy') lights.
4636         {
4637             drawLists(lists, DM_TEXTURE_PLUS_LIGHT);
4638 
4639             // Render surfaces with blending.
4640             drawLists(lists, DM_BLENDED);
4641 
4642             // Render the first light for surfaces with blending.
4643             // (Not optimal but shouldn't matter; texture is changed for
4644             // each primitive.)
4645             drawLists(lists, DM_BLENDED_FIRST_LIGHT);
4646         }
4647     }
4648     else // Multitexturing is not available for lights.
4649     {
4650         if (IS_MUL)
4651         {
4652             // Render all lit surfaces without a texture.
4653             drawLists(lists, DM_WITHOUT_TEXTURE);
4654         }
4655         else
4656         {
4657             if (IS_MTEX_DETAILS) // Draw detail textures using multitexturing.
4658             {
4659                 // Unblended surfaces with a detail.
4660                 drawLists(lists, DM_UNBLENDED_TEXTURE_AND_DETAIL);
4661 
4662                 // Blended surfaces without details.
4663                 drawLists(lists, DM_BLENDED);
4664 
4665                 // Details for blended surfaces.
4666                 drawLists(lists, DM_BLENDED_DETAILS);
4667             }
4668             else
4669             {
4670                 drawLists(lists, DM_ALL);
4671             }
4672         }
4673     }
4674 
4675     //
4676     // Pass: All light geometries (always additive).
4677     //
4678     if (dynlightBlend != 2)
4679     {
4680         ClientApp::renderSystem().drawLists().findAll(LightGeom, lists);
4681         drawLists(lists, DM_LIGHTS);
4682     }
4683 
4684     //
4685     // Pass: Geometries with texture modulation.
4686     //
4687     if (IS_MUL)
4688     {
4689         // Finish the lit surfaces that didn't yet get a texture.
4690         ClientApp::renderSystem().drawLists().findAll(LitGeom, lists);
4691         if (IS_MTEX_DETAILS)
4692         {
4693             drawLists(lists, DM_UNBLENDED_MOD_TEXTURE_AND_DETAIL);
4694             drawLists(lists, DM_BLENDED_MOD_TEXTURE);
4695             drawLists(lists, DM_BLENDED_DETAILS);
4696         }
4697         else
4698         {
4699             if (IS_MTEX_LIGHTS && dynlightBlend != 2)
4700             {
4701                 drawLists(lists, DM_MOD_TEXTURE_MANY_LIGHTS);
4702             }
4703             else
4704             {
4705                 drawLists(lists, DM_MOD_TEXTURE);
4706             }
4707         }
4708     }
4709 
4710     //
4711     // Pass: Geometries with details & modulation.
4712     //
4713     // If multitexturing is not available for details, we need to apply them as
4714     // an extra pass over all the detailed surfaces.
4715     //
4716     if (r_detail)
4717     {
4718         // Render detail textures for all surfaces that need them.
4719         ClientApp::renderSystem().drawLists().findAll(UnlitGeom, lists);
4720         if (IS_MTEX_DETAILS)
4721         {
4722             // Blended detail textures.
4723             drawLists(lists, DM_BLENDED_DETAILS);
4724         }
4725         else
4726         {
4727             drawLists(lists, DM_ALL_DETAILS);
4728 
4729             ClientApp::renderSystem().drawLists().findAll(LitGeom, lists);
4730             drawLists(lists, DM_ALL_DETAILS);
4731         }
4732     }
4733 
4734     //
4735     // Pass: Shiny geometries.
4736     //
4737     // If we have two texture units, the shiny masks will be enabled. Otherwise
4738     // the masks are ignored. The shine is basically specular environmental
4739     // additive light, multiplied by the mask so that black texels from the mask
4740     // produce areas without shine.
4741     //
4742 
4743     ClientApp::renderSystem().drawLists().findAll(ShineGeom, lists);
4744 
4745     // Render masked shiny surfaces in a separate pass.
4746     drawLists(lists, DM_SHINY);
4747     drawLists(lists, DM_MASKED_SHINY);
4748 
4749     //
4750     // Pass: Shadow geometries (objects and Fake Radio).
4751     //
4752     dint const oldRenderTextures = renderTextures;
4753 
4754     renderTextures = true;
4755 
4756     ClientApp::renderSystem().drawLists().findAll(ShadowGeom, lists);
4757     drawLists(lists, DM_SHADOW);
4758 
4759     renderTextures = oldRenderTextures;
4760 
4761     DGL_Disable(DGL_TEXTURE_2D);
4762 
4763     // The draw lists do not modify these states -ds
4764     DGL_Enable(DGL_BLEND);
4765     DGL_Enable(DGL_DEPTH_WRITE);
4766     DGL_Enable(DGL_DEPTH_TEST);
4767     DGL_DepthFunc(DGL_LESS);
4768     DGL_Enable(DGL_ALPHA_TEST);
4769     DGL_SetFloat(DGL_ALPHA_LIMIT, 0);
4770     if (fogParams.usingFog)
4771     {
4772         DGL_Enable(DGL_FOG);
4773         DGL_Fogfv(DGL_FOG_COLOR, fogParams.fogColor);
4774     }
4775 
4776     // Draw masked walls, sprites and models.
4777     drawMasked();
4778 
4779     // Draw particles.
4780     Rend_RenderParticles(map);
4781 
4782     if (fogParams.usingFog)
4783     {
4784         DGL_Disable(DGL_FOG);
4785     }
4786 
4787     DENG2_ASSERT(!Sys_GLCheckError());
4788 }
4789 
Rend_RenderMap(Map & map)4790 void Rend_RenderMap(Map &map)
4791 {
4792     //GL_SetMultisample(true);
4793 
4794     // Setup the modelview matrix.
4795     Rend_ModelViewMatrix();
4796 
4797     if (!freezeRLs)
4798     {
4799         // Prepare for rendering.
4800         ClientApp::renderSystem().beginFrame();
4801 
4802         // Make vissprites of all the visible decorations.
4803         generateDecorationFlares(map);
4804 
4805         viewdata_t const *viewData = &viewPlayer->viewport();
4806         eyeOrigin = viewData->current.origin;
4807 
4808         // Add the backside clipping range (if vpitch allows).
4809         if (vpitch <= 90 - yfov / 2 && vpitch >= -90 + yfov / 2)
4810         {
4811             AngleClipper &clipper = ClientApp::renderSystem().angleClipper();
4812 
4813             dfloat a = de::abs(vpitch) / (90 - yfov / 2);
4814             binangle_t startAngle = binangle_t(BANG_45 * Rend_FieldOfView() / 90) * (1 + a);
4815             binangle_t angLen = BANG_180 - startAngle;
4816 
4817             binangle_t viewside = (viewData->current.angle() >> (32 - BAMS_BITS)) + startAngle;
4818             clipper.safeAddRange(viewside, viewside + angLen);
4819             clipper.safeAddRange(viewside + angLen, viewside + 2 * angLen);
4820         }
4821 
4822         // The viewside line for the depth cue.
4823         viewsidex = -viewData->viewSin;
4824         viewsidey = viewData->viewCos;
4825 
4826         // We don't want BSP clip checking for the first subspace.
4827         firstSubspace = true;
4828 
4829         // No current subspace as of yet.
4830         curSubspace = nullptr;
4831 
4832         // Draw the world!
4833         traverseBspTreeAndDrawSubspaces(&map.bspTree());
4834     }
4835     drawAllLists(map);
4836 
4837     // Draw various debugging displays:
4838     //drawFakeRadioShadowPoints(map);
4839     drawSurfaceTangentVectors(map);
4840     drawLumobjs(map);
4841     drawMobjBoundingBoxes(map);
4842     drawSectors(map);
4843     drawVertexes(map);
4844     drawThinkers(map);
4845     drawSoundEmitters(map);
4846     drawGenerators(map);
4847     //drawBiasEditingVisuals(map);
4848 
4849     //GL_SetMultisample(false);
4850 
4851     DGL_Flush();
4852 }
4853 
4854 #if 0
4855 static void drawStar(Vector3d const &origin, dfloat size, Vector4f const &color)
4856 {
4857     static dfloat const black[] = { 0, 0, 0, 0 };
4858 
4859     DGL_Begin(DGL_LINES);
4860         DGL_Color4fv(black);
4861         DGL_Vertex3f(origin.x - size, origin.z, origin.y);
4862         DGL_Color4f(color.x, color.y, color.z, color.w);
4863         DGL_Vertex3f(origin.x, origin.z, origin.y);
4864         DGL_Vertex3f(origin.x, origin.z, origin.y);
4865         DGL_Color4fv(black);
4866         DGL_Vertex3f(origin.x + size, origin.z, origin.y);
4867 
4868         DGL_Vertex3f(origin.x, origin.z - size, origin.y);
4869         DGL_Color4f(color.x, color.y, color.z, color.w);
4870         DGL_Vertex3f(origin.x, origin.z, origin.y);
4871         DGL_Vertex3f(origin.x, origin.z, origin.y);
4872         DGL_Color4fv(black);
4873         DGL_Vertex3f(origin.x, origin.z + size, origin.y);
4874 
4875         DGL_Vertex3f(origin.x, origin.z, origin.y - size);
4876         DGL_Color4f(color.x, color.y, color.z, color.w);
4877         DGL_Vertex3f(origin.x, origin.z, origin.y);
4878         DGL_Vertex3f(origin.x, origin.z, origin.y);
4879         DGL_Color4fv(black);
4880         DGL_Vertex3f(origin.x, origin.z, origin.y + size);
4881     DGL_End();
4882 }
4883 #endif
4884 
drawLabel(String const & label,Vector3d const & origin,dfloat scale,dfloat opacity)4885 static void drawLabel(String const &label, Vector3d const &origin, dfloat scale, dfloat opacity)
4886 {
4887     if (label.isEmpty()) return;
4888 
4889     DGL_MatrixMode(DGL_MODELVIEW);
4890     DGL_PushMatrix();
4891     DGL_Translatef(origin.x, origin.z, origin.y);
4892     DGL_Rotatef(-vang + 180, 0, 1, 0);
4893     DGL_Rotatef(vpitch, 1, 0, 0);
4894     DGL_Scalef(-scale, -scale, 1);
4895 
4896     Point2Raw offset = {{{2, 2}}};
4897     UI_TextOutEx(label.toUtf8().constData(), &offset, UI_Color(UIC_TITLE), opacity);
4898 
4899     DGL_MatrixMode(DGL_MODELVIEW);
4900     DGL_PopMatrix();
4901 }
4902 
drawLabel(String const & label,Vector3d const & origin,ddouble maxDistance=2000)4903 static void drawLabel(String const &label, Vector3d const &origin, ddouble maxDistance = 2000)
4904 {
4905     ddouble const distToEye = (Rend_EyeOrigin().xzy() - origin).length();
4906     if (distToEye < maxDistance)
4907     {
4908         drawLabel(label, origin, distToEye / (DENG_GAMEVIEW_WIDTH / 2), 1 - distToEye / maxDistance);
4909     }
4910 }
4911 
Rend_UpdateLightModMatrix()4912 void Rend_UpdateLightModMatrix()
4913 {
4914     if (novideo) return;
4915 
4916     de::zap(lightModRange);
4917 
4918     if (!App_World().hasMap())
4919     {
4920         rAmbient = 0;
4921         return;
4922     }
4923 
4924     dint mapAmbient = App_World().map().ambientLightLevel();
4925     if (mapAmbient > ambientLight)
4926     {
4927         rAmbient = mapAmbient;
4928     }
4929     else
4930     {
4931         rAmbient = ambientLight;
4932     }
4933 
4934     for (dint i = 0; i < 255; ++i)
4935     {
4936         // Adjust the white point/dark point?
4937         dfloat lightlevel = 0;
4938         if (lightRangeCompression != 0)
4939         {
4940             if (lightRangeCompression >= 0)
4941             {
4942                 // Brighten dark areas.
4943                 lightlevel = dfloat(255 - i) * lightRangeCompression;
4944             }
4945             else
4946             {
4947                 // Darken bright areas.
4948                 lightlevel = dfloat(-i) * -lightRangeCompression;
4949             }
4950         }
4951 
4952         // Lower than the ambient limit?
4953         if (rAmbient != 0 && i+lightlevel <= rAmbient)
4954         {
4955             lightlevel = rAmbient - i;
4956         }
4957 
4958         // Clamp the result as a modifier to the light value (j).
4959         if ((i + lightlevel) >= 255)
4960         {
4961             lightlevel = 255 - i;
4962         }
4963         else if ((i + lightlevel) <= 0)
4964         {
4965             lightlevel = -i;
4966         }
4967 
4968         // Insert it into the matrix.
4969         lightModRange[i] = lightlevel / 255.0f;
4970 
4971         // Ensure the resultant value never exceeds the expected [0..1] range.
4972         DENG2_ASSERT(INRANGE_OF(i / 255.0f + lightModRange[i], 0.f, 1.f));
4973     }
4974 }
4975 
Rend_LightAdaptationDelta(dfloat val)4976 dfloat Rend_LightAdaptationDelta(dfloat val)
4977 {
4978     dint clampedVal = de::clamp(0, de::roundi(255.0f * val), 254);
4979     return lightModRange[clampedVal];
4980 }
4981 
Rend_ApplyLightAdaptation(dfloat & val)4982 void Rend_ApplyLightAdaptation(dfloat &val)
4983 {
4984     val += Rend_LightAdaptationDelta(val);
4985 }
4986 
Rend_DrawLightModMatrix()4987 void Rend_DrawLightModMatrix()
4988 {
4989 #define BLOCK_WIDTH             ( 1.0f )
4990 #define BLOCK_HEIGHT            ( BLOCK_WIDTH * 255.0f )
4991 #define BORDER                  ( 20 )
4992 
4993     // Disabled?
4994     if (!devLightModRange) return;
4995 
4996     DGL_MatrixMode(DGL_PROJECTION);
4997     DGL_PushMatrix();
4998     DGL_LoadIdentity();
4999     DGL_Ortho(0, 0, DENG_GAMEVIEW_WIDTH, DENG_GAMEVIEW_HEIGHT, -1, 1);
5000 
5001     DGL_Translatef(BORDER, BORDER, 0);
5002 
5003     // Draw an outside border.
5004     DGL_Color4f(1, 1, 0, 1);
5005     DGL_Begin(DGL_LINES);
5006         DGL_Vertex2f(-1, -1);
5007         DGL_Vertex2f(255 + 1, -1);
5008         DGL_Vertex2f(255 + 1, -1);
5009         DGL_Vertex2f(255 + 1, BLOCK_HEIGHT + 1);
5010         DGL_Vertex2f(255 + 1, BLOCK_HEIGHT + 1);
5011         DGL_Vertex2f(-1, BLOCK_HEIGHT + 1);
5012         DGL_Vertex2f(-1, BLOCK_HEIGHT + 1);
5013         DGL_Vertex2f(-1, -1);
5014     DGL_End();
5015 
5016     DGL_Begin(DGL_QUADS);
5017     dfloat c = 0;
5018     for (dint i = 0; i < 255; ++i, c += (1.0f/255.0f))
5019     {
5020         // Get the result of the source light level + offset.
5021         dfloat off = lightModRange[i];
5022 
5023         DGL_Color4f(c + off, c + off, c + off, 1);
5024         DGL_Vertex2f(i * BLOCK_WIDTH, 0);
5025         DGL_Vertex2f(i * BLOCK_WIDTH + BLOCK_WIDTH, 0);
5026         DGL_Vertex2f(i * BLOCK_WIDTH + BLOCK_WIDTH, BLOCK_HEIGHT);
5027         DGL_Vertex2f(i * BLOCK_WIDTH, BLOCK_HEIGHT);
5028     }
5029     DGL_End();
5030 
5031     DGL_MatrixMode(DGL_PROJECTION);
5032     DGL_PopMatrix();
5033 
5034 #undef BORDER
5035 #undef BLOCK_HEIGHT
5036 #undef BLOCK_WIDTH
5037 }
5038 
drawBBox(dfloat br)5039 static void drawBBox(dfloat br)
5040 {
5041 //    if (GL_NewList(name, GL_COMPILE))
5042 //    {
5043     DGL_Begin(DGL_QUADS);
5044     {
5045         // Top
5046         DGL_TexCoord2f(0, 1.0f, 1.0f); DGL_Vertex3f( 1.0f+br, 1.0f,-1.0f-br);  // TR
5047         DGL_TexCoord2f(0, 0.0f, 1.0f); DGL_Vertex3f(-1.0f-br, 1.0f,-1.0f-br);  // TL
5048         DGL_TexCoord2f(0, 0.0f, 0.0f); DGL_Vertex3f(-1.0f-br, 1.0f, 1.0f+br);  // BL
5049         DGL_TexCoord2f(0, 1.0f, 0.0f); DGL_Vertex3f( 1.0f+br, 1.0f, 1.0f+br);  // BR
5050         // Bottom
5051         DGL_TexCoord2f(0, 1.0f, 1.0f); DGL_Vertex3f( 1.0f+br,-1.0f, 1.0f+br);  // TR
5052         DGL_TexCoord2f(0, 0.0f, 1.0f); DGL_Vertex3f(-1.0f-br,-1.0f, 1.0f+br);  // TL
5053         DGL_TexCoord2f(0, 0.0f, 0.0f); DGL_Vertex3f(-1.0f-br,-1.0f,-1.0f-br);  // BL
5054         DGL_TexCoord2f(0, 1.0f, 0.0f); DGL_Vertex3f( 1.0f+br,-1.0f,-1.0f-br);  // BR
5055         // Front
5056         DGL_TexCoord2f(0, 1.0f, 1.0f); DGL_Vertex3f( 1.0f+br, 1.0f+br, 1.0f);  // TR
5057         DGL_TexCoord2f(0, 0.0f, 1.0f); DGL_Vertex3f(-1.0f-br, 1.0f+br, 1.0f);  // TL
5058         DGL_TexCoord2f(0, 0.0f, 0.0f); DGL_Vertex3f(-1.0f-br,-1.0f-br, 1.0f);  // BL
5059         DGL_TexCoord2f(0, 1.0f, 0.0f); DGL_Vertex3f( 1.0f+br,-1.0f-br, 1.0f);  // BR
5060         // Back
5061         DGL_TexCoord2f(0, 1.0f, 1.0f); DGL_Vertex3f( 1.0f+br,-1.0f-br,-1.0f);  // TR
5062         DGL_TexCoord2f(0, 0.0f, 1.0f); DGL_Vertex3f(-1.0f-br,-1.0f-br,-1.0f);  // TL
5063         DGL_TexCoord2f(0, 0.0f, 0.0f); DGL_Vertex3f(-1.0f-br, 1.0f+br,-1.0f);  // BL
5064         DGL_TexCoord2f(0, 1.0f, 0.0f); DGL_Vertex3f( 1.0f+br, 1.0f+br,-1.0f);  // BR
5065         // Left
5066         DGL_TexCoord2f(0, 1.0f, 1.0f); DGL_Vertex3f(-1.0f, 1.0f+br, 1.0f+br);  // TR
5067         DGL_TexCoord2f(0, 0.0f, 1.0f); DGL_Vertex3f(-1.0f, 1.0f+br,-1.0f-br);  // TL
5068         DGL_TexCoord2f(0, 0.0f, 0.0f); DGL_Vertex3f(-1.0f,-1.0f-br,-1.0f-br);  // BL
5069         DGL_TexCoord2f(0, 1.0f, 0.0f); DGL_Vertex3f(-1.0f,-1.0f-br, 1.0f+br);  // BR
5070         // Right
5071         DGL_TexCoord2f(0, 1.0f, 1.0f); DGL_Vertex3f( 1.0f, 1.0f+br,-1.0f-br);  // TR
5072         DGL_TexCoord2f(0, 0.0f, 1.0f); DGL_Vertex3f( 1.0f, 1.0f+br, 1.0f+br);  // TL
5073         DGL_TexCoord2f(0, 0.0f, 0.0f); DGL_Vertex3f( 1.0f,-1.0f-br, 1.0f+br);  // BL
5074         DGL_TexCoord2f(0, 1.0f, 0.0f); DGL_Vertex3f( 1.0f,-1.0f-br,-1.0f-br);  // BR
5075     }
5076     DGL_End();
5077     //    return GL_EndList();
5078     //}
5079     //return 0;
5080 }
5081 
5082 /**
5083  * Draws a textured cube using the currently bound gl texture.
5084  * Used to draw mobj bounding boxes.
5085  *
5086  * @param pos          Coordinates of the center of the box (in map space units).
5087  * @param w            Width of the box.
5088  * @param l            Length of the box.
5089  * @param h            Height of the box.
5090  * @param a            Angle of the box.
5091  * @param color        Color to make the box (uniform vertex color).
5092  * @param alpha        Alpha to make the box (uniform vertex color).
5093  * @param br           Border amount to overlap box faces.
5094  * @param alignToBase  @c true= align the base of the box to the Z coordinate.
5095  */
Rend_DrawBBox(Vector3d const & pos,coord_t w,coord_t l,coord_t h,dfloat a,dfloat const color[3],dfloat alpha,dfloat br,bool alignToBase)5096 void Rend_DrawBBox(Vector3d const &pos, coord_t w, coord_t l, coord_t h,
5097     dfloat a, dfloat const color[3], dfloat alpha, dfloat br, bool alignToBase)
5098 {
5099     DGL_MatrixMode(DGL_MODELVIEW);
5100     DGL_PushMatrix();
5101 
5102     if (alignToBase)
5103         // The Z coordinate is to the bottom of the object.
5104         DGL_Translatef(pos.x, pos.z + h, pos.y);
5105     else
5106         DGL_Translatef(pos.x, pos.z, pos.y);
5107 
5108     DGL_Rotatef(0, 0, 0, 1);
5109     DGL_Rotatef(0, 1, 0, 0);
5110     DGL_Rotatef(a, 0, 1, 0);
5111 
5112     DGL_Scalef(w - br - br, h - br - br, l - br - br);
5113     DGL_Color4f(color[0], color[1], color[2], alpha);
5114 
5115     //GL_CallList(dlBBox);
5116     drawBBox(.08f);
5117 
5118     DGL_MatrixMode(DGL_MODELVIEW);
5119     DGL_PopMatrix();
5120 }
5121 
5122 /**
5123  * Draws a textured triangle using the currently bound gl texture.
5124  * Used to draw mobj angle direction arrow.
5125  *
5126  * @param pos    World space coordinates of the center of the base of the triangle.
5127  * @param a      Angle to point the triangle in.
5128  * @param s      Scale of the triangle.
5129  * @param color  Color to make the box (uniform vertex color).
5130  * @param alpha  Alpha to make the box (uniform vertex color).
5131  */
Rend_DrawArrow(Vector3d const & pos,dfloat a,dfloat s,dfloat const color[3],dfloat alpha)5132 void Rend_DrawArrow(Vector3d const &pos, dfloat a, dfloat s, dfloat const color[3],
5133                     dfloat alpha)
5134 {
5135     DGL_MatrixMode(DGL_MODELVIEW);
5136     DGL_PushMatrix();
5137 
5138     DGL_Translatef(pos.x, pos.z, pos.y);
5139 
5140     DGL_Rotatef(0, 0, 0, 1);
5141     DGL_Rotatef(0, 1, 0, 0);
5142     DGL_Rotatef(a, 0, 1, 0);
5143 
5144     DGL_Scalef(s, 0, s);
5145 
5146     DGL_Begin(DGL_TRIANGLES);
5147     {
5148         DGL_Color4f(0.0f, 0.0f, 0.0f, 0.5f);
5149         DGL_TexCoord2f(0, 1.0f, 1.0f);
5150         DGL_Vertex3f( 1.0f, 1.0f,-1.0f);  // L
5151 
5152         DGL_Color4f(color[0], color[1], color[2], alpha);
5153         DGL_TexCoord2f(0, 0.0f, 1.0f);
5154         DGL_Vertex3f(-1.0f, 1.0f,-1.0f);  // Point
5155 
5156         DGL_Color4f(0.0f, 0.0f, 0.0f, 0.5f);
5157         DGL_TexCoord2f(0, 0.0f, 0.0f);
5158         DGL_Vertex3f(-1.0f, 1.0f, 1.0f);  // R
5159     }
5160     DGL_End();
5161 
5162     DGL_MatrixMode(DGL_MODELVIEW);
5163     DGL_PopMatrix();
5164 }
5165 
drawMobjBBox(mobj_t & mob)5166 static void drawMobjBBox(mobj_t &mob)
5167 {
5168     static dfloat const red   [] = { 1,    0.2f, 0.2f };  // non-solid objects
5169     static dfloat const green [] = { 0.2f, 1,    0.2f };  // solid objects
5170     static dfloat const yellow[] = { 0.7f, 0.7f, 0.2f };  // missiles
5171 
5172     // We don't want the console player.
5173     if (&mob == DD_Player(consolePlayer)->publicData().mo)
5174         return;
5175 
5176     // Is it vissible?
5177     if (!Mobj_IsLinked(mob)) return;
5178 
5179     BspLeaf const &bspLeaf = Mobj_BspLeafAtOrigin(mob);
5180     if (!bspLeaf.hasSubspace() || !R_ViewerSubspaceIsVisible(bspLeaf.subspace()))
5181         return;
5182 
5183     ddouble const distToEye = (eyeOrigin - Mobj_Origin(mob)).length();
5184     dfloat alpha = 1 - ((distToEye / (DENG_GAMEVIEW_WIDTH/2)) / 4);
5185     if (alpha < .25f)
5186         alpha = .25f; // Don't make them totally invisible.
5187 
5188     // Draw a bounding box in an appropriate color.
5189     coord_t size = Mobj_Radius(mob);
5190     Rend_DrawBBox(mob.origin, size, size, mob.height/2, 0,
5191                   (mob.ddFlags & DDMF_MISSILE)? yellow :
5192                   (mob.ddFlags & DDMF_SOLID)? green : red,
5193                   alpha, .08f);
5194 
5195     Rend_DrawArrow(mob.origin, ((mob.angle + ANG45 + ANG90) / (dfloat) ANGLE_MAX *-360), size*1.25,
5196                    (mob.ddFlags & DDMF_MISSILE)? yellow :
5197                    (mob.ddFlags & DDMF_SOLID)? green : red, alpha);
5198 }
5199 
5200 /**
5201  * Renders bounding boxes for all mobj's (linked in sec->mobjList, except
5202  * the console player) in all sectors that are currently marked as vissible.
5203  *
5204  * Depth test is disabled to show all mobjs that are being rendered, regardless
5205  * if they are actually vissible (hidden by previously drawn map geometry).
5206  */
drawMobjBoundingBoxes(Map & map)5207 static void drawMobjBoundingBoxes(Map &map)
5208 {
5209     //static dfloat const red   [] = { 1,    0.2f, 0.2f };  // non-solid objects
5210     static dfloat const green [] = { 0.2f, 1,    0.2f };  // solid objects
5211     static dfloat const yellow[] = { 0.7f, 0.7f, 0.2f };  // missiles
5212 
5213     if (!devMobjBBox && !devPolyobjBBox) return;
5214 
5215 #ifndef _DEBUG
5216     // Bounding boxes are not allowed in non-debug netgames.
5217     if (netGame) return;
5218 #endif
5219 
5220 //    if (!dlBBox)
5221 //        dlBBox = constructBBox(0, .08f);
5222 
5223     DGL_Disable(DGL_DEPTH_TEST);
5224     DGL_Enable(DGL_TEXTURE_2D);
5225     //glDisable(GL_CULL_FACE);
5226     DGL_CullFace(DGL_NONE);
5227 
5228     MaterialAnimator &matAnimator = ClientMaterial::find(de::Uri("System", Path("bbox")))
5229             .getAnimator(Rend_SpriteMaterialSpec());
5230 
5231     // Ensure we've up to date info about the material.
5232     matAnimator.prepare();
5233 
5234     GL_BindTexture(matAnimator.texUnit(MaterialAnimator::TU_LAYER0).texture);
5235     GL_BlendMode(BM_ADD);
5236 
5237     if (devMobjBBox)
5238     {
5239         map.thinkers().forAll(reinterpret_cast<thinkfunc_t>(gx.MobjThinker), 0x1, [] (thinker_t *th)
5240         {
5241             drawMobjBBox(*reinterpret_cast<mobj_t *>(th));
5242             return LoopContinue;
5243         });
5244     }
5245 
5246     if (devPolyobjBBox)
5247     {
5248         map.forAllPolyobjs([] (Polyobj &pob)
5249         {
5250             Sector const &sec = pob.sector();
5251 
5252             coord_t width  = (pob.bounds.maxX - pob.bounds.minX)/2;
5253             coord_t length = (pob.bounds.maxY - pob.bounds.minY)/2;
5254             coord_t height = (sec.ceiling().height() - sec.floor().height())/2;
5255 
5256             Vector3d pos(pob.bounds.minX + width,
5257                          pob.bounds.minY + length,
5258                          sec.floor().height());
5259 
5260             ddouble const distToEye = (eyeOrigin - pos).length();
5261             dfloat alpha = 1 - ((distToEye / (DENG_GAMEVIEW_WIDTH/2)) / 4);
5262             if (alpha < .25f)
5263                 alpha = .25f; // Don't make them totally invisible.
5264 
5265             Rend_DrawBBox(pos, width, length, height, 0, yellow, alpha, .08f);
5266 
5267             for (Line *line : pob.lines())
5268             {
5269                 Vector3d pos(line->center(), sec.floor().height());
5270 
5271                 Rend_DrawBBox(pos, 0, line->length() / 2, height,
5272                               BANG2DEG(BANG_90 - line->angle()),
5273                               green, alpha, 0);
5274             }
5275 
5276             return LoopContinue;
5277         });
5278     }
5279 
5280     GL_BlendMode(BM_NORMAL);
5281 
5282     DGL_PopState();
5283     DGL_Disable(DGL_TEXTURE_2D);
5284     DGL_Enable(DGL_DEPTH_TEST);
5285 }
5286 
drawPoint(Vector3d const & origin,Vector4f const & color=Vector4f (1,1,1,1))5287 static void drawPoint(Vector3d const &origin, Vector4f const &color = Vector4f(1, 1, 1, 1))
5288 {
5289     DGL_Begin(DGL_POINTS);
5290         DGL_Color4f(color.x, color.y, color.z, color.w);
5291         DGL_Vertex3f(origin.x, origin.z, origin.y);
5292     DGL_End();
5293 }
5294 
drawVector(Vector3f const & vector,dfloat scalar,Vector4f const & color=Vector4f (1,1,1,1))5295 static void drawVector(Vector3f const &vector, dfloat scalar, Vector4f const &color = Vector4f(1, 1, 1, 1))
5296 {
5297     static dfloat const black[] = { 0, 0, 0, 0 };
5298 
5299     DGL_Begin(DGL_LINES);
5300         DGL_Color4fv(black);
5301         DGL_Vertex3f(scalar * vector.x, scalar * vector.z, scalar * vector.y);
5302         DGL_Color4f(color.x, color.y, color.z, color.w);
5303         DGL_Vertex3f(0, 0, 0);
5304     DGL_End();
5305 }
5306 
drawTangentVectorsForSurface(Surface const & suf,Vector3d const & origin)5307 static void drawTangentVectorsForSurface(Surface const &suf, Vector3d const &origin)
5308 {
5309     static dint const VISUAL_LENGTH = 20;
5310     static Vector4f const red  ( 1, 0, 0, 1);
5311     static Vector4f const green( 0, 1, 0, 1);
5312     static Vector4f const blue ( 0, 0, 1, 1);
5313 
5314     DGL_MatrixMode(DGL_MODELVIEW);
5315     DGL_PushMatrix();
5316     DGL_Translatef(origin.x, origin.z, origin.y);
5317 
5318     if (::devSurfaceVectors & SVF_TANGENT)   drawVector(suf.tangent(),   VISUAL_LENGTH, red);
5319     if (::devSurfaceVectors & SVF_BITANGENT) drawVector(suf.bitangent(), VISUAL_LENGTH, green);
5320     if (::devSurfaceVectors & SVF_NORMAL)    drawVector(suf.normal(),    VISUAL_LENGTH, blue);
5321 
5322     DGL_MatrixMode(DGL_MODELVIEW);
5323     DGL_PopMatrix();
5324 }
5325 
5326 /**
5327  * @todo Determine Z-axis origin from a WallEdge.
5328  */
drawTangentVectorsForWalls(HEdge const * hedge)5329 static void drawTangentVectorsForWalls(HEdge const *hedge)
5330 {
5331     if (!hedge || !hedge->hasMapElement())
5332         return;
5333 
5334     LineSideSegment const &seg = hedge->mapElementAs<LineSideSegment>();
5335     LineSide const &lineSide   = seg.lineSide();
5336     Line const &line           = lineSide.line();
5337     Vector2d const center      = (hedge->twin().origin() + hedge->origin()) / 2;
5338 
5339     if (lineSide.considerOneSided())
5340     {
5341         auto &subsec =
5342             (line.definesPolyobj() ? line.polyobj().bspLeaf().subspace()
5343                                    : hedge->face().mapElementAs<ConvexSubspace>())
5344                 .subsector().as<world::ClientSubsector>();
5345 
5346         ddouble const bottom = subsec.  visFloor().heightSmoothed();
5347         ddouble const top    = subsec.visCeiling().heightSmoothed();
5348 
5349         drawTangentVectorsForSurface(lineSide.middle(),
5350                                      Vector3d(center, bottom + (top - bottom) / 2));
5351     }
5352     else
5353     {
5354         auto &subsec =
5355             (line.definesPolyobj() ? line.polyobj().bspLeaf().subspace()
5356                                    : hedge->face().mapElementAs<ConvexSubspace>())
5357                 .subsector().as<world::ClientSubsector>();
5358 
5359         auto &backSubsec =
5360             (line.definesPolyobj() ? line.polyobj().bspLeaf().subspace()
5361                                    : hedge->twin().face().mapElementAs<ConvexSubspace>())
5362                 .subsector().as<world::ClientSubsector>();
5363 
5364         if (lineSide.middle().hasMaterial())
5365         {
5366             ddouble const bottom = subsec.  visFloor().heightSmoothed();
5367             ddouble const top    = subsec.visCeiling().heightSmoothed();
5368 
5369             drawTangentVectorsForSurface(lineSide.middle(),
5370                                          Vector3d(center, bottom + (top - bottom) / 2));
5371         }
5372 
5373         if (backSubsec.visCeiling().heightSmoothed() < subsec.visCeiling().heightSmoothed() &&
5374            !(subsec.    visCeiling().surface().hasSkyMaskedMaterial() &&
5375              backSubsec.visCeiling().surface().hasSkyMaskedMaterial()))
5376         {
5377             coord_t const bottom = backSubsec.visCeiling().heightSmoothed();
5378             coord_t const top    = subsec.    visCeiling().heightSmoothed();
5379 
5380             drawTangentVectorsForSurface(lineSide.top(),
5381                                          Vector3d(center, bottom + (top - bottom) / 2));
5382         }
5383 
5384         if (backSubsec.visFloor().heightSmoothed() > subsec.visFloor().heightSmoothed() &&
5385            !(subsec.    visFloor().surface().hasSkyMaskedMaterial() &&
5386              backSubsec.visFloor().surface().hasSkyMaskedMaterial()))
5387         {
5388             coord_t const bottom = subsec.    visFloor().heightSmoothed();
5389             coord_t const top    = backSubsec.visFloor().heightSmoothed();
5390 
5391             drawTangentVectorsForSurface(lineSide.bottom(),
5392                                          Vector3d(center, bottom + (top - bottom) / 2));
5393         }
5394     }
5395 }
5396 
5397 /**
5398  * @todo Use drawTangentVectorsForWalls() for polyobjs too.
5399  */
drawSurfaceTangentVectors(Subsector & subsec)5400 static void drawSurfaceTangentVectors(Subsector &subsec)
5401 {
5402     subsec.forAllSubspaces([] (ConvexSubspace &space)
5403     {
5404         HEdge const *base  = space.poly().hedge();
5405         HEdge const *hedge = base;
5406         do
5407         {
5408             drawTangentVectorsForWalls(hedge);
5409         } while ((hedge = &hedge->next()) != base);
5410 
5411         space.forAllExtraMeshes([] (Mesh &mesh)
5412         {
5413             for (HEdge *hedge : mesh.hedges())
5414             {
5415                 drawTangentVectorsForWalls(hedge);
5416             }
5417             return LoopContinue;
5418         });
5419 
5420         space.forAllPolyobjs([] (Polyobj &pob)
5421         {
5422             for (HEdge *hedge : pob.mesh().hedges())
5423             {
5424                 drawTangentVectorsForWalls(hedge);
5425             }
5426             return LoopContinue;
5427         });
5428 
5429         return LoopContinue;
5430     });
5431 
5432     dint const planeCount = subsec.sector().planeCount();
5433     for (dint i = 0; i < planeCount; ++i)
5434     {
5435         Plane const &plane = subsec.as<world::ClientSubsector>().visPlane(i);
5436         ddouble height     = 0;
5437         if (plane.surface().hasSkyMaskedMaterial()
5438             && (plane.isSectorFloor() || plane.isSectorCeiling()))
5439         {
5440             height = plane.map().skyPlane(plane.isSectorCeiling()).height();
5441         }
5442         else
5443         {
5444             height = plane.heightSmoothed();
5445         }
5446 
5447         drawTangentVectorsForSurface(plane.surface(), Vector3d(subsec.center(), height));
5448     }
5449 }
5450 
5451 /**
5452  * Draw the surface tangent space vectors, primarily for debug.
5453  */
drawSurfaceTangentVectors(Map & map)5454 static void drawSurfaceTangentVectors(Map &map)
5455 {
5456     if (!::devSurfaceVectors) return;
5457 
5458     //glDisable(GL_CULL_FACE);
5459     DGL_CullFace(DGL_NONE);
5460 
5461     map.forAllSectors([] (Sector &sec)
5462     {
5463         return sec.forAllSubsectors([] (Subsector &subsec)
5464         {
5465             drawSurfaceTangentVectors(subsec);
5466             return LoopContinue;
5467         });
5468     });
5469 
5470     //glEnable(GL_CULL_FACE);
5471     DGL_PopState();
5472 }
5473 
drawLumobjs(Map & map)5474 static void drawLumobjs(Map &map)
5475 {
5476     static dfloat const black[] = { 0, 0, 0, 0 };
5477 
5478     if (!devDrawLums) return;
5479 
5480     DGL_PushState();
5481     DGL_Disable(DGL_DEPTH_TEST);
5482     DGL_CullFace(DGL_NONE);
5483 
5484     map.forAllLumobjs([] (Lumobj &lob)
5485     {
5486         if (rendMaxLumobjs > 0 && R_ViewerLumobjIsHidden(lob.indexInMap()))
5487             return LoopContinue;
5488 
5489         DGL_MatrixMode(DGL_MODELVIEW);
5490         DGL_PushMatrix();
5491 
5492         DGL_Translatef(lob.origin().x, lob.origin().z + lob.zOffset(), lob.origin().y);
5493 
5494         DGL_Begin(DGL_LINES);
5495         {
5496             DGL_Color4fv(black);
5497             DGL_Vertex3f(-lob.radius(), 0, 0);
5498             DGL_Color4f(lob.color().x, lob.color().y, lob.color().z, 1);
5499             DGL_Vertex3f(0, 0, 0);
5500             DGL_Vertex3f(0, 0, 0);
5501             DGL_Color4fv(black);
5502             DGL_Vertex3f(lob.radius(), 0, 0);
5503 
5504             DGL_Vertex3f(0, -lob.radius(), 0);
5505             DGL_Color4f(lob.color().x, lob.color().y, lob.color().z, 1);
5506             DGL_Vertex3f(0, 0, 0);
5507             DGL_Vertex3f(0, 0, 0);
5508             DGL_Color4fv(black);
5509             DGL_Vertex3f(0, lob.radius(), 0);
5510 
5511             DGL_Vertex3f(0, 0, -lob.radius());
5512             DGL_Color4f(lob.color().x, lob.color().y, lob.color().z, 1);
5513             DGL_Vertex3f(0, 0, 0);
5514             DGL_Vertex3f(0, 0, 0);
5515             DGL_Color4fv(black);
5516             DGL_Vertex3f(0, 0, lob.radius());
5517         }
5518         DGL_End();
5519 
5520         DGL_MatrixMode(DGL_MODELVIEW);
5521         DGL_PopMatrix();
5522         return LoopContinue;
5523     });
5524 
5525     DGL_PopState();
5526 }
5527 
labelForLineSideSection(LineSide & side,dint sectionId)5528 static String labelForLineSideSection(LineSide &side, dint sectionId)
5529 {
5530     return String("Line #%1 (%2, %3)")
5531                .arg(side.line().indexInMap())
5532                .arg(Line::sideIdAsText(side.sideId()).upperFirstChar())
5533                .arg(LineSide::sectionIdAsText(sectionId));
5534 }
5535 
labelForSector(Sector & sector)5536 static String labelForSector(Sector &sector)
5537 {
5538     return String("Sector #%1").arg(sector.indexInMap());
5539 }
5540 
labelForSectorPlane(Plane & plane)5541 static String labelForSectorPlane(Plane &plane)
5542 {
5543     return String("Sector #%1 (%2)")
5544                .arg(plane.sector().indexInMap())
5545                .arg(Sector::planeIdAsText(plane.indexInSector()).upperFirstChar());
5546 }
5547 
5548 /**
5549  * Debugging aid for visualizing sound origins.
5550  */
drawSoundEmitters(Map & map)5551 static void drawSoundEmitters(Map &map)
5552 {
5553     static ddouble const MAX_DISTANCE = 384;
5554 
5555     if (!devSoundEmitters) return;
5556 
5557     FR_SetFont(fontFixed);
5558     FR_LoadDefaultAttrib();
5559     FR_SetShadowOffset(UI_SHADOW_OFFSET, UI_SHADOW_OFFSET);
5560     FR_SetShadowStrength(UI_SHADOW_STRENGTH);
5561 
5562     DGL_Disable(DGL_DEPTH_TEST);
5563     DGL_Enable(DGL_TEXTURE_2D);
5564 
5565     if (devSoundEmitters & SOF_SIDE)
5566     {
5567         map.forAllLines([] (Line &line)
5568         {
5569             for (dint i = 0; i < 2; ++i)
5570             {
5571                 LineSide &side = line.side(i);
5572                 if (!side.hasSections()) continue;
5573 
5574                 drawLabel(labelForLineSideSection(side, LineSide::Middle)
5575                           , Vector3d(side.middleSoundEmitter().origin), MAX_DISTANCE);
5576 
5577                 drawLabel(labelForLineSideSection(side, LineSide::Bottom)
5578                           , Vector3d(side.bottomSoundEmitter().origin), MAX_DISTANCE);
5579 
5580                 drawLabel(labelForLineSideSection(side, LineSide::Top)
5581                           , Vector3d(side.topSoundEmitter   ().origin), MAX_DISTANCE);
5582             }
5583             return LoopContinue;
5584         });
5585     }
5586 
5587     if (devSoundEmitters & (SOF_SECTOR | SOF_PLANE))
5588     {
5589         map.forAllSectors([] (Sector &sector)
5590         {
5591             if (devSoundEmitters & SOF_PLANE)
5592             {
5593                 sector.forAllPlanes([] (Plane &plane)
5594                 {
5595                     drawLabel(labelForSectorPlane(plane)
5596                               , Vector3d(plane.soundEmitter().origin), MAX_DISTANCE);
5597                     return LoopContinue;
5598                 });
5599             }
5600 
5601             if (devSoundEmitters & SOF_SECTOR)
5602             {
5603                 drawLabel(labelForSector(sector)
5604                           , Vector3d(sector.soundEmitter().origin), MAX_DISTANCE);
5605             }
5606             return LoopContinue;
5607         });
5608     }
5609 
5610     DGL_Enable(DGL_DEPTH_TEST);
5611     DGL_Disable(DGL_TEXTURE_2D);
5612 }
5613 
Rend_DrawVectorLight(VectorLightData const & vlight,dfloat alpha)5614 void Rend_DrawVectorLight(VectorLightData const &vlight, dfloat alpha)
5615 {
5616     if (alpha < .0001f) return;
5617 
5618     dfloat const unitLength = 100;
5619 
5620     DGL_Begin(DGL_LINES);
5621         DGL_Color4f(vlight.color.x, vlight.color.y, vlight.color.z, alpha);
5622         DGL_Vertex3f(unitLength * vlight.direction.x, unitLength * vlight.direction.z, unitLength * vlight.direction.y);
5623         DGL_Color4f(vlight.color.x, vlight.color.y, vlight.color.z, 0);
5624         DGL_Vertex3f(0, 0, 0);
5625     DGL_End();
5626 }
5627 
labelForGenerator(Generator const & gen)5628 static String labelForGenerator(Generator const &gen)
5629 {
5630     return String("%1").arg(gen.id());
5631 }
5632 
drawGenerator(Generator const & gen)5633 static void drawGenerator(Generator const &gen)
5634 {
5635     static dint const MAX_GENERATOR_DIST = 2048;
5636 
5637     if (gen.source || gen.isUntriggered())
5638     {
5639         Vector3d const origin   = gen.origin();
5640         ddouble const distToEye = (eyeOrigin - origin).length();
5641         if (distToEye < MAX_GENERATOR_DIST)
5642         {
5643             drawLabel(labelForGenerator(gen), origin, distToEye / (DENG_GAMEVIEW_WIDTH / 2)
5644                       , 1 - distToEye / MAX_GENERATOR_DIST);
5645         }
5646     }
5647 }
5648 
5649 /**
5650  * Debugging aid; Draw all active generators.
5651  */
drawGenerators(Map & map)5652 static void drawGenerators(Map &map)
5653 {
5654     if (!devDrawGenerators) return;
5655 
5656     FR_SetFont(fontFixed);
5657     FR_LoadDefaultAttrib();
5658     FR_SetShadowOffset(UI_SHADOW_OFFSET, UI_SHADOW_OFFSET);
5659     FR_SetShadowStrength(UI_SHADOW_STRENGTH);
5660 
5661     DGL_Disable(DGL_DEPTH_TEST);
5662     DGL_Enable(DGL_TEXTURE_2D);
5663 
5664     map.forAllGenerators([] (Generator &gen)
5665     {
5666         drawGenerator(gen);
5667         return LoopContinue;
5668     });
5669 
5670     DGL_Enable(DGL_DEPTH_TEST);
5671     DGL_Disable(DGL_TEXTURE_2D);
5672 }
5673 
drawBar(Vector3d const & origin,coord_t height,dfloat opacity)5674 static void drawBar(Vector3d const &origin, coord_t height, dfloat opacity)
5675 {
5676     static dint const EXTEND_DIST = 64;
5677     static dfloat const black[] = { 0, 0, 0, 0 };
5678 
5679     DGL_Begin(DGL_LINES);
5680         DGL_Color4fv(black);
5681         DGL_Vertex3f(origin.x, origin.z - EXTEND_DIST, origin.y);
5682         DGL_Color4f(1, 1, 1, opacity);
5683         DGL_Vertex3f(origin.x, origin.z, origin.y);
5684         DGL_Vertex3f(origin.x, origin.z, origin.y);
5685         DGL_Vertex3f(origin.x, origin.z + height, origin.y);
5686         DGL_Vertex3f(origin.x, origin.z + height, origin.y);
5687         DGL_Color4fv(black);
5688         DGL_Vertex3f(origin.x, origin.z + height + EXTEND_DIST, origin.y);
5689     DGL_End();
5690 }
5691 
labelForVertex(Vertex const * vtx)5692 static String labelForVertex(Vertex const *vtx)
5693 {
5694     DENG2_ASSERT(vtx);
5695     return String("%1").arg(vtx->indexInMap());
5696 }
5697 
5698 struct drawvertexvisual_parameters_t
5699 {
5700     dint maxDistance;
5701     bool drawOrigin;
5702     bool drawBar;
5703     bool drawLabel;
5704     QBitArray *drawnVerts;
5705 };
5706 
drawVertexVisual(Vertex const & vertex,ddouble minHeight,ddouble maxHeight,drawvertexvisual_parameters_t & parms)5707 static void drawVertexVisual(Vertex const &vertex, ddouble minHeight, ddouble maxHeight,
5708     drawvertexvisual_parameters_t &parms)
5709 {
5710     if (!parms.drawOrigin && !parms.drawBar && !parms.drawLabel)
5711         return;
5712 
5713     // Skip vertexes produced by the space partitioner.
5714     if (vertex.indexInArchive() == MapElement::NoIndex)
5715         return;
5716 
5717     // Skip already processed verts?
5718     if (parms.drawnVerts)
5719     {
5720         if (parms.drawnVerts->testBit(vertex.indexInArchive()))
5721             return;
5722         parms.drawnVerts->setBit(vertex.indexInArchive());
5723     }
5724 
5725     // Distance in 2D determines visibility/opacity.
5726     ddouble distToEye = (Vector2d(eyeOrigin.x, eyeOrigin.y) - vertex.origin()).length();
5727     if (distToEye >= parms.maxDistance)
5728         return;
5729 
5730     Vector3d const origin(vertex.origin(), minHeight);
5731     dfloat const opacity = 1 - distToEye / parms.maxDistance;
5732 
5733     if (parms.drawBar)
5734     {
5735         drawBar(origin, maxHeight - minHeight, opacity);
5736     }
5737     if (parms.drawOrigin)
5738     {
5739         drawPoint(origin, Vector4f(.7f, .7f, .2f, opacity * 4));
5740     }
5741     if (parms.drawLabel)
5742     {
5743         DGL_Disable(DGL_DEPTH_TEST);
5744         DGL_Enable(DGL_TEXTURE_2D);
5745 
5746         drawLabel(labelForVertex(&vertex), origin, distToEye / (DENG_GAMEVIEW_WIDTH / 2), opacity);
5747 
5748         DGL_Enable(DGL_DEPTH_TEST);
5749         DGL_Disable(DGL_TEXTURE_2D);
5750     }
5751 }
5752 
5753 /**
5754  * Find the relative next minmal and/or maximal visual height(s) of all sector
5755  * planes which "interface" at the half-edge, edge vertex.
5756  *
5757  * @param base  Base half-edge to find heights for.
5758  * @param edge  Edge of the half-edge.
5759  * @param min   Current minimal height to use as a base (will be overwritten).
5760  *              Use DDMAXFLOAT if the base is unknown.
5761  * @param min   Current maximal height to use as a base (will be overwritten).
5762  *              Use DDMINFLOAT if the base is unknown.
5763  *
5764  * @todo Don't stop when a zero-volume back neighbor is found; process all of
5765  * the neighbors at the specified vertex (the half-edge geometry will need to
5766  * be linked such that "outside" edges are neighbor-linked similarly to those
5767  * with a face).
5768  */
findMinMaxPlaneHeightsAtVertex(HEdge * base,dint edge,ddouble & min,ddouble & max)5769 static void findMinMaxPlaneHeightsAtVertex(HEdge *base, dint edge,
5770     ddouble &min, ddouble &max)
5771 {
5772     if (!base || !base->hasFace())
5773         return;
5774 
5775     if (!base->face().hasMapElement() || !base->face().mapElementAs<ConvexSubspace>().hasSubsector())
5776         return;
5777 
5778     // Process neighbors?
5779     if (!Subsector::isInternalEdge(base))
5780     {
5781         ClockDirection const direction = edge? Clockwise : Anticlockwise;
5782         HEdge *hedge = base;
5783         while ((hedge = &SubsectorCirculator::findBackNeighbor(*hedge, direction)) != base)
5784         {
5785             // Stop if there is no back space.
5786             auto const *backSpace = hedge->hasFace() ? &hedge->face().mapElementAs<ConvexSubspace>() : nullptr;
5787             if (!backSpace) break;
5788 
5789             auto const &subsec = backSpace->subsector().as<world::ClientSubsector>();
5790             if (subsec.visFloor().heightSmoothed() < min)
5791             {
5792                 min = subsec.visFloor().heightSmoothed();
5793             }
5794 
5795             if (subsec.visCeiling().heightSmoothed() > max)
5796             {
5797                 max = subsec.visCeiling().heightSmoothed();
5798             }
5799         }
5800     }
5801 }
5802 
drawSubspaceVertexs(ConvexSubspace & sub,drawvertexvisual_parameters_t & parms)5803 static void drawSubspaceVertexs(ConvexSubspace &sub, drawvertexvisual_parameters_t &parms)
5804 {
5805     auto &subsec = sub.subsector().as<world::ClientSubsector>();
5806     ddouble const min = subsec.  visFloor().heightSmoothed();
5807     ddouble const max = subsec.visCeiling().heightSmoothed();
5808 
5809     HEdge *base  = sub.poly().hedge();
5810     HEdge *hedge = base;
5811     do
5812     {
5813         ddouble edgeMin = min;
5814         ddouble edgeMax = max;
5815         findMinMaxPlaneHeightsAtVertex(hedge, 0 /*left edge*/, edgeMin, edgeMax);
5816 
5817         drawVertexVisual(hedge->vertex(), min, max, parms);
5818 
5819     } while ((hedge = &hedge->next()) != base);
5820 
5821     sub.forAllExtraMeshes([&min, &max, &parms] (Mesh &mesh)
5822     {
5823         for (HEdge *hedge : mesh.hedges())
5824         {
5825             drawVertexVisual(hedge->vertex(), min, max, parms);
5826             drawVertexVisual(hedge->twin().vertex(), min, max, parms);
5827         }
5828         return LoopContinue;
5829     });
5830 
5831     sub.forAllPolyobjs([&min, &max, &parms] (Polyobj &pob)
5832     {
5833         for (Line *line : pob.lines())
5834         {
5835             drawVertexVisual(line->from(), min, max, parms);
5836             drawVertexVisual(line->to(), min, max, parms);
5837         }
5838         return LoopContinue;
5839     });
5840 }
5841 
5842 /**
5843  * Draw the various vertex debug aids.
5844  */
drawVertexes(Map & map)5845 static void drawVertexes(Map &map)
5846 {
5847 #define MAX_DISTANCE            1280  ///< From the viewer.
5848 
5849     dfloat oldLineWidth = -1;
5850 
5851     if (!devVertexBars && !devVertexIndices)
5852         return;
5853 
5854     AABoxd box(eyeOrigin.x - MAX_DISTANCE, eyeOrigin.y - MAX_DISTANCE,
5855                eyeOrigin.x + MAX_DISTANCE, eyeOrigin.y + MAX_DISTANCE);
5856 
5857     QBitArray drawnVerts(map.vertexCount());
5858     drawvertexvisual_parameters_t parms;
5859     parms.maxDistance = MAX_DISTANCE;
5860     parms.drawnVerts  = &drawnVerts;
5861 
5862     FR_SetFont(fontFixed);
5863     FR_LoadDefaultAttrib();
5864     FR_SetShadowOffset(UI_SHADOW_OFFSET, UI_SHADOW_OFFSET);
5865     FR_SetShadowStrength(UI_SHADOW_STRENGTH);
5866 
5867     if (devVertexBars)
5868     {
5869         DGL_Disable(DGL_DEPTH_TEST);
5870 
5871 #if defined (DENG_OPENGL)
5872         LIBGUI_GL.glEnable(GL_LINE_SMOOTH);
5873 #endif
5874         oldLineWidth = DGL_GetFloat(DGL_LINE_WIDTH);
5875         DGL_SetFloat(DGL_LINE_WIDTH, 2);
5876 
5877         parms.drawBar   = true;
5878         parms.drawLabel = parms.drawOrigin = false;
5879 
5880         map.subspaceBlockmap().forAllInBox(box, [&box, &parms] (void *object)
5881         {
5882             // Check the bounds.
5883             auto &subspace = *(ConvexSubspace *)object;
5884             AABoxd const &polyBounds = subspace.poly().bounds();
5885             if (!(   polyBounds.maxX < box.minX
5886                  || polyBounds.minX > box.maxX
5887                  || polyBounds.minY > box.maxY
5888                  || polyBounds.maxY < box.minY))
5889             {
5890                 drawSubspaceVertexs(subspace, parms);
5891             }
5892             return LoopContinue;
5893         });
5894 
5895         DGL_Enable(DGL_DEPTH_TEST);
5896     }
5897 
5898     // Draw the vertex origins.
5899     dfloat const oldPointSize = DGL_GetFloat(DGL_POINT_SIZE);
5900 
5901 #if defined (DENG_OPENGL)
5902     LIBGUI_GL.glEnable(GL_POINT_SMOOTH);
5903 #endif
5904     DGL_SetFloat(DGL_POINT_SIZE, 6);
5905 
5906     DGL_Disable(DGL_DEPTH_TEST);
5907 
5908     parms.drawnVerts->fill(false);  // Process all again.
5909     parms.drawOrigin = true;
5910     parms.drawBar    = parms.drawLabel = false;
5911 
5912     map.subspaceBlockmap().forAllInBox(box, [&box, &parms] (void *object)
5913     {
5914         // Check the bounds.
5915         auto &subspace = *(ConvexSubspace *)object;
5916         AABoxd const &polyBounds = subspace.poly().bounds();
5917         if (!(   polyBounds.maxX < box.minX
5918               || polyBounds.minX > box.maxX
5919               || polyBounds.minY > box.maxY
5920               || polyBounds.maxY < box.minY))
5921         {
5922             drawSubspaceVertexs(subspace, parms);
5923         }
5924         return LoopContinue;
5925     });
5926 
5927     DGL_Enable(DGL_DEPTH_TEST);
5928 
5929     if (devVertexIndices)
5930     {
5931         parms.drawnVerts->fill(false);  // Process all again.
5932         parms.drawLabel = true;
5933         parms.drawBar   = parms.drawOrigin = false;
5934 
5935         map.subspaceBlockmap().forAllInBox(box, [&box, &parms] (void *object)
5936         {
5937             auto &subspace = *(ConvexSubspace *)object;
5938             // Check the bounds.
5939             AABoxd const &polyBounds = subspace.poly().bounds();
5940             if (!(   polyBounds.maxX < box.minX
5941                   || polyBounds.minX > box.maxX
5942                   || polyBounds.minY > box.maxY
5943                   || polyBounds.maxY < box.minY))
5944             {
5945                 drawSubspaceVertexs(subspace, parms);
5946             }
5947             return LoopContinue;
5948         });
5949     }
5950 
5951     // Restore previous state.
5952     if (devVertexBars)
5953     {
5954         DGL_SetFloat(DGL_LINE_WIDTH, oldLineWidth);
5955 #if defined (DENG_OPENGL)
5956         LIBGUI_GL.glDisable(GL_LINE_SMOOTH);
5957 #endif
5958     }
5959     DGL_SetFloat(DGL_POINT_SIZE, oldPointSize);
5960 #if defined (DENG_OPENGL)
5961     LIBGUI_GL.glDisable(GL_POINT_SMOOTH);
5962 #endif
5963 
5964 #undef MAX_VERTEX_POINT_DIST
5965 }
5966 
labelForSubsector(Subsector const & subsec)5967 static String labelForSubsector(Subsector const &subsec)
5968 {
5969     return String::number(subsec.sector().indexInMap());
5970 }
5971 
5972 /**
5973  * Draw the sector debugging aids.
5974  */
drawSectors(Map & map)5975 static void drawSectors(Map &map)
5976 {
5977     static ddouble const MAX_LABEL_DIST = 1280;
5978 
5979     if (!devSectorIndices) return;
5980 
5981     FR_SetFont(fontFixed);
5982     FR_LoadDefaultAttrib();
5983     FR_SetShadowOffset(UI_SHADOW_OFFSET, UI_SHADOW_OFFSET);
5984     FR_SetShadowStrength(UI_SHADOW_STRENGTH);
5985 
5986     DGL_Disable(DGL_DEPTH_TEST);
5987     DGL_Enable(DGL_TEXTURE_2D);
5988 
5989     // Draw a sector label at the center of each subsector:
5990     map.forAllSectors([] (Sector &sec)
5991     {
5992         return sec.forAllSubsectors([] (Subsector &subsec)
5993         {
5994             Vector3d const origin(subsec.center(), subsec.as<world::ClientSubsector>().visFloor().heightSmoothed());
5995             ddouble const distToEye = (eyeOrigin - origin).length();
5996             if (distToEye < MAX_LABEL_DIST)
5997             {
5998                 drawLabel(labelForSubsector(subsec), origin, distToEye / (DENG_GAMEVIEW_WIDTH / 2)
5999                           , 1 - distToEye / MAX_LABEL_DIST);
6000             }
6001             return LoopContinue;
6002         });
6003     });
6004 
6005     DGL_Enable(DGL_DEPTH_TEST);
6006     DGL_Disable(DGL_TEXTURE_2D);
6007 }
6008 
labelForThinker(thinker_t * thinker)6009 static String labelForThinker(thinker_t *thinker)
6010 {
6011     DENG2_ASSERT(thinker);
6012     return String("%1").arg(thinker->id);
6013 }
6014 
6015 /**
6016  * Debugging aid for visualizing thinker IDs.
6017  */
drawThinkers(Map & map)6018 static void drawThinkers(Map &map)
6019 {
6020     static ddouble const MAX_THINKER_DIST = 2048;
6021 
6022     if (!devThinkerIds) return;
6023 
6024     FR_SetFont(fontFixed);
6025     FR_LoadDefaultAttrib();
6026     FR_SetShadowOffset(UI_SHADOW_OFFSET, UI_SHADOW_OFFSET);
6027     FR_SetShadowStrength(UI_SHADOW_STRENGTH);
6028 
6029     DGL_Disable(DGL_DEPTH_TEST);
6030     DGL_Enable(DGL_TEXTURE_2D);
6031 
6032     map.thinkers().forAll(0x1 | 0x2, [] (thinker_t *th)
6033     {
6034         // Ignore non-mobjs.
6035         if (Thinker_IsMobj(th))
6036         {
6037             Vector3d const origin   = Mobj_Center(*(mobj_t *)th);
6038             ddouble const distToEye = (eyeOrigin - origin).length();
6039             if (distToEye < MAX_THINKER_DIST)
6040             {
6041                 drawLabel(labelForThinker(th), origin,  distToEye / (DENG_GAMEVIEW_WIDTH / 2)
6042                           , 1 - distToEye / MAX_THINKER_DIST);
6043             }
6044         }
6045         return LoopContinue;
6046     });
6047 
6048     DGL_Enable(DGL_DEPTH_TEST);
6049     DGL_Disable(DGL_TEXTURE_2D);
6050 }
6051 
6052 #if 0
6053 void Rend_LightGridVisual(LightGrid &lg)
6054 {
6055     static Vector3f const red(1, 0, 0);
6056     static dint blink = 0;
6057 
6058     // Disabled?
6059     if (!devLightGrid) return;
6060 
6061     DENG_ASSERT_IN_MAIN_THREAD();
6062     DENG_ASSERT_GL_CONTEXT_ACTIVE();
6063 
6064     // Determine the grid reference of the view player.
6065     LightGrid::Index viewerGridIndex = 0;
6066     if (viewPlayer)
6067     {
6068         blink++;
6069         viewerGridIndex = lg.toIndex(lg.toRef(viewPlayer->publicData().mo->origin));
6070     }
6071 
6072     // Go into screen projection mode.
6073     DGL_MatrixMode(DGL_PROJECTION);
6074     DGL_PushMatrix();
6075     DGL_LoadIdentity();
6076     DGL_Ortho(0, 0, DENG_GAMEVIEW_WIDTH, DENG_GAMEVIEW_HEIGHT, -1, 1);
6077 
6078     for (dint y = 0; y < lg.dimensions().y; ++y)
6079     {
6080         DGL_Begin(DGL_QUADS);
6081         for (dint x = 0; x < lg.dimensions().x; ++x)
6082         {
6083             LightGrid::Index gridIndex = lg.toIndex(x, lg.dimensions().y - 1 - y);
6084             bool const isViewerIndex   = (viewPlayer && viewerGridIndex == gridIndex);
6085 
6086             Vector3f const *color = 0;
6087             if (isViewerIndex && (blink & 16))
6088             {
6089                 color = &red;
6090             }
6091             else if (lg.primarySource(gridIndex))
6092             {
6093                 color = &lg.rawColorRef(gridIndex);
6094             }
6095 
6096             if (!color) continue;
6097 
6098             LIBGUI_GL.glColor3f(color->x, color->y, color->z);
6099 
6100             DGL_Vertex2f(x * devLightGridSize, y * devLightGridSize);
6101             DGL_Vertex2f(x * devLightGridSize + devLightGridSize, y * devLightGridSize);
6102             DGL_Vertex2f(x * devLightGridSize + devLightGridSize,
6103                        y * devLightGridSize + devLightGridSize);
6104             DGL_Vertex2f(x * devLightGridSize, y * devLightGridSize + devLightGridSize);
6105         }
6106         DGL_End();
6107     }
6108 
6109     DGL_MatrixMode(DGL_PROJECTION);
6110     DGL_PopMatrix();
6111 }
6112 #endif
6113 
Rend_MapSurfaceMaterialSpec(dint wrapS,dint wrapT)6114 MaterialVariantSpec const &Rend_MapSurfaceMaterialSpec(dint wrapS, dint wrapT)
6115 {
6116     return ClientApp::resources().materialSpec(MapSurfaceContext, 0, 0, 0, 0, wrapS, wrapT,
6117                                                -1, -1, -1, true, true, false, false);
6118 }
6119 
Rend_MapSurfaceMaterialSpec()6120 MaterialVariantSpec const &Rend_MapSurfaceMaterialSpec()
6121 {
6122     if (!lookupMapSurfaceMaterialSpec)
6123     {
6124         lookupMapSurfaceMaterialSpec = &Rend_MapSurfaceMaterialSpec(GL_REPEAT, GL_REPEAT);
6125     }
6126     return *lookupMapSurfaceMaterialSpec;
6127 }
6128 
6129 /// Returns the texture variant specification for lightmaps.
Rend_MapSurfaceLightmapTextureSpec()6130 TextureVariantSpec const &Rend_MapSurfaceLightmapTextureSpec()
6131 {
6132     return ClientApp::resources().textureSpec(TC_MAPSURFACE_LIGHTMAP, 0, 0, 0, 0, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE,
6133                                 1, -1, -1, false, false, false, true);
6134 }
6135 
Rend_MapSurfaceShinyTextureSpec()6136 TextureVariantSpec const &Rend_MapSurfaceShinyTextureSpec()
6137 {
6138     return ClientApp::resources().textureSpec(TC_MAPSURFACE_REFLECTION, TSF_NO_COMPRESSION, 0, 0, 0,
6139                                 GL_REPEAT, GL_REPEAT, 1, 1, -1, false, false, false, false);
6140 }
6141 
Rend_MapSurfaceShinyMaskTextureSpec()6142 TextureVariantSpec const &Rend_MapSurfaceShinyMaskTextureSpec()
6143 {
6144     return ClientApp::resources().textureSpec(TC_MAPSURFACE_REFLECTIONMASK, 0, 0, 0, 0, GL_REPEAT, GL_REPEAT,
6145                                 -1, -1, -1, true, false, false, false);
6146 }
6147 
D_CMD(OpenRendererAppearanceEditor)6148 D_CMD(OpenRendererAppearanceEditor)
6149 {
6150     DENG2_UNUSED3(src, argc, argv);
6151 
6152     if (!App_GameLoaded())
6153     {
6154         LOG_ERROR("A game must be loaded before the Renderer Appearance editor can be opened");
6155         return false;
6156     }
6157 
6158     if (!ClientWindow::main().hasSidebar())
6159     {
6160         // The editor sidebar will give its ownership automatically
6161         // to the window.
6162         RendererAppearanceEditor *editor = new RendererAppearanceEditor;
6163         editor->open();
6164     }
6165     return true;
6166 }
6167 
D_CMD(OpenModelAssetEditor)6168 D_CMD(OpenModelAssetEditor)
6169 {
6170     DENG2_UNUSED3(src, argc, argv);
6171 
6172     if (!App_GameLoaded())
6173     {
6174         LOG_ERROR("A game must be loaded before the Model Asset editor can be opened");
6175         return false;
6176     }
6177 
6178     if (!ClientWindow::main().hasSidebar())
6179     {
6180         ModelAssetEditor *editor = new ModelAssetEditor;
6181         editor->open();
6182     }
6183     return true;
6184 }
6185 
D_CMD(LowRes)6186 D_CMD(LowRes)
6187 {
6188     DENG2_UNUSED3(src, argv, argc);
6189 
6190     // Set everything as low as they go.
6191     filterSprites = 0;
6192     filterUI      = 0;
6193     texMagMode    = 0;
6194 
6195     //GL_SetAllTexturesMinFilter(GL_NEAREST);
6196     GL_SetRawTexturesMinFilter(GL_NEAREST);
6197 
6198     // And do a texreset so everything is updated.
6199     GL_TexReset();
6200     return true;
6201 }
6202 
D_CMD(MipMap)6203 D_CMD(MipMap)
6204 {
6205     DENG2_UNUSED2(src, argc);
6206 
6207     dint newMipMode = String(argv[1]).toInt();
6208     if (newMipMode < 0 || newMipMode > 5)
6209     {
6210         LOG_SCR_ERROR("Invalid mipmapping mode %i; the valid range is 0...5") << newMipMode;
6211         return false;
6212     }
6213 
6214     mipmapping = newMipMode;
6215     //GL_SetAllTexturesMinFilter(glmode[mipmapping]);
6216     return true;
6217 }
6218 
D_CMD(TexReset)6219 D_CMD(TexReset)
6220 {
6221     DENG2_UNUSED(src);
6222 
6223     if (argc == 2 && !String(argv[1]).compareWithoutCase("raw"))
6224     {
6225         // Reset just raw images.
6226         GL_ReleaseTexturesForRawImages();
6227     }
6228     else
6229     {
6230         // Reset everything.
6231         GL_TexReset();
6232     }
6233     if (auto *map = App_World().mapPtr())
6234     {
6235         // Texture IDs are cached in Lumobjs, so have to redo all of them.
6236         map->redecorate();
6237     }
6238     return true;
6239 }
6240 
detailFactorChanged()6241 static void detailFactorChanged()
6242 {
6243     App_Resources().releaseGLTexturesByScheme("Details");
6244 }
6245 
6246 /*
6247 static void fieldOfViewChanged()
6248 {
6249     if (vrCfg().mode() == VRConfig::OculusRift)
6250     {
6251         if (Con_GetFloat("rend-vr-rift-fovx") != fieldOfView)
6252             Con_SetFloat("rend-vr-rift-fovx", fieldOfView);
6253     }
6254     else
6255     {
6256         if (Con_GetFloat("rend-vr-nonrift-fovx") != fieldOfView)
6257             Con_SetFloat("rend-vr-nonrift-fovx", fieldOfView);
6258     }
6259 }*/
6260 
loadExtAlwaysChanged()6261 static void loadExtAlwaysChanged()
6262 {
6263     GL_TexReset();
6264 }
6265 
mipmappingChanged()6266 static void mipmappingChanged()
6267 {
6268     GL_TexReset();
6269 }
6270 
texGammaChanged()6271 static void texGammaChanged()
6272 {
6273     R_BuildTexGammaLut(texGamma);
6274     GL_TexReset();
6275     LOG_GL_MSG("Texture gamma correction set to %f") << texGamma;
6276 }
6277 
texQualityChanged()6278 static void texQualityChanged()
6279 {
6280     GL_TexReset();
6281 }
6282 
useDynlightsChanged()6283 static void useDynlightsChanged()
6284 {
6285     if (!ClientApp::world().hasMap()) return;
6286 
6287     // Unlink luminous objects.
6288     ClientApp::world().map().thinkers()
6289         .forAll(reinterpret_cast<thinkfunc_t>(gx.MobjThinker), 0x1, [](thinker_t *th)
6290     {
6291         Mobj_UnlinkLumobjs(reinterpret_cast<mobj_t *>(th));
6292         return LoopContinue;
6293     });
6294 }
6295 
6296 #if 0
6297 static void useSkylightChanged()
6298 {
6299     scheduleFullLightGridUpdate();
6300 }
6301 #endif
6302 
useSmartFilterChanged()6303 static void useSmartFilterChanged()
6304 {
6305     GL_TexReset();
6306 }
6307 
Rend_Register()6308 void Rend_Register()
6309 {
6310     //C_VAR_INT("rend-bias", &useBias, 0, 0, 1);
6311     C_VAR_FLOAT("rend-camera-fov", &fieldOfView, 0, 1, 179);
6312 
6313     C_VAR_FLOAT("rend-glow", &glowFactor, 0, 0, 2);
6314     C_VAR_INT("rend-glow-height", &glowHeightMax, 0, 0, 1024);
6315     C_VAR_FLOAT("rend-glow-scale", &glowHeightFactor, 0, 0.1f, 10);
6316     C_VAR_INT("rend-glow-wall", &useGlowOnWalls, 0, 0, 1);
6317 
6318     C_VAR_BYTE("rend-info-lums", &rendInfoLums, 0, 0, 1);
6319 
6320     C_VAR_INT2("rend-light", &useDynLights, 0, 0, 1, useDynlightsChanged);
6321     C_VAR_INT2("rend-light-ambient", &ambientLight, 0, 0, 255, Rend_UpdateLightModMatrix);
6322     C_VAR_FLOAT("rend-light-attenuation", &rendLightDistanceAttenuation, CVF_NO_MAX, 0, 0);
6323     C_VAR_INT("rend-light-blend", &dynlightBlend, 0, 0, 2);
6324     C_VAR_FLOAT("rend-light-bright", &dynlightFactor, 0, 0, 1);
6325     C_VAR_FLOAT2("rend-light-compression", &lightRangeCompression, 0, -1, 1, Rend_UpdateLightModMatrix);
6326     C_VAR_BYTE("rend-light-decor", &useLightDecorations, 0, 0, 1);
6327     C_VAR_FLOAT("rend-light-fog-bright", &dynlightFogBright, 0, 0, 1);
6328     //C_VAR_INT("rend-light-multitex", &useMultiTexLights, 0, 0, 1);
6329     C_VAR_INT("rend-light-num", &rendMaxLumobjs, CVF_NO_MAX, 0, 0);
6330     C_VAR_FLOAT("rend-light-sky", &rendSkyLight, 0, 0, 1/*, useSkylightChanged*/);
6331     C_VAR_BYTE("rend-light-sky-auto", &rendSkyLightAuto, 0, 0, 1/*, useSkylightChanged*/);
6332     C_VAR_FLOAT("rend-light-wall-angle", &rendLightWallAngle, CVF_NO_MAX, 0, 0);
6333     C_VAR_BYTE("rend-light-wall-angle-smooth", &rendLightWallAngleSmooth, 0, 0, 1);
6334 
6335     C_VAR_BYTE("rend-map-material-precache", &precacheMapMaterials, 0, 0, 1);
6336 
6337     C_VAR_INT("rend-shadow", &useShadows, 0, 0, 1);
6338     C_VAR_FLOAT("rend-shadow-darkness", &shadowFactor, 0, 0, 2);
6339     C_VAR_INT("rend-shadow-far", &shadowMaxDistance, CVF_NO_MAX, 0, 0);
6340     C_VAR_INT("rend-shadow-radius-max", &shadowMaxRadius, CVF_NO_MAX, 0, 0);
6341 
6342     C_VAR_INT("rend-tex", &renderTextures, CVF_NO_ARCHIVE, 0, 2);
6343     C_VAR_BYTE("rend-tex-anim-smooth", &smoothTexAnim, 0, 0, 1);
6344     C_VAR_INT("rend-tex-detail", &r_detail, 0, 0, 1);
6345     //C_VAR_INT("rend-tex-detail-multitex", &useMultiTexDetails, 0, 0, 1);
6346     C_VAR_FLOAT("rend-tex-detail-scale", &detailScale, CVF_NO_MIN | CVF_NO_MAX, 0, 0);
6347     C_VAR_FLOAT2("rend-tex-detail-strength", &detailFactor, 0, 0, 5, detailFactorChanged);
6348     C_VAR_BYTE2("rend-tex-external-always", &loadExtAlways, 0, 0, 1, loadExtAlwaysChanged);
6349     C_VAR_INT("rend-tex-filter-anisotropic", &texAniso, 0, -1, 4);
6350     C_VAR_INT("rend-tex-filter-mag", &texMagMode, 0, 0, 1);
6351     C_VAR_INT2("rend-tex-filter-smart", &useSmartFilter, 0, 0, 1, useSmartFilterChanged);
6352     C_VAR_INT("rend-tex-filter-sprite", &filterSprites, 0, 0, 1);
6353     C_VAR_INT("rend-tex-filter-ui", &filterUI, 0, 0, 1);
6354     C_VAR_FLOAT2("rend-tex-gamma", &texGamma, 0, 0, 1, texGammaChanged);
6355     C_VAR_INT2("rend-tex-mipmap", &mipmapping, CVF_PROTECTED, 0, 5, mipmappingChanged);
6356     C_VAR_INT2("rend-tex-quality", &texQuality, 0, 0, 8, texQualityChanged);
6357     C_VAR_INT("rend-tex-shiny", &useShinySurfaces, 0, 0, 1);
6358 
6359     //C_VAR_BYTE("rend-bias-grid-debug", &devLightGrid, CVF_NO_ARCHIVE, 0, 1);
6360     //C_VAR_FLOAT("rend-bias-grid-debug-size", &devLightGridSize, 0, .1f, 100);
6361     C_VAR_BYTE("rend-dev-blockmap-debug", &bmapShowDebug, CVF_NO_ARCHIVE, 0, 4);
6362     C_VAR_FLOAT("rend-dev-blockmap-debug-size", &bmapDebugSize, CVF_NO_ARCHIVE, .1f, 100);
6363     C_VAR_INT("rend-dev-cull-leafs", &devNoCulling, CVF_NO_ARCHIVE, 0, 1);
6364     C_VAR_BYTE("rend-dev-freeze", &freezeRLs, CVF_NO_ARCHIVE, 0, 1);
6365     C_VAR_BYTE("rend-dev-generator-show-indices", &devDrawGenerators, CVF_NO_ARCHIVE, 0, 1);
6366     C_VAR_BYTE("rend-dev-light-mod", &devLightModRange, CVF_NO_ARCHIVE, 0, 1);
6367     C_VAR_BYTE("rend-dev-lums", &devDrawLums, CVF_NO_ARCHIVE, 0, 1);
6368     C_VAR_INT("rend-dev-mobj-bbox", &devMobjBBox, CVF_NO_ARCHIVE, 0, 1);
6369     C_VAR_BYTE("rend-dev-mobj-show-vlights", &devMobjVLights, CVF_NO_ARCHIVE, 0, 1);
6370     C_VAR_INT("rend-dev-polyobj-bbox", &devPolyobjBBox, CVF_NO_ARCHIVE, 0, 1);
6371     C_VAR_BYTE("rend-dev-sector-show-indices", &devSectorIndices, CVF_NO_ARCHIVE, 0, 1);
6372     C_VAR_INT("rend-dev-sky", &devRendSkyMode, CVF_NO_ARCHIVE, 0, 1);
6373     C_VAR_BYTE("rend-dev-sky-always", &devRendSkyAlways, CVF_NO_ARCHIVE, 0, 1);
6374     C_VAR_BYTE("rend-dev-soundorigins", &devSoundEmitters, CVF_NO_ARCHIVE, 0, 7);
6375     C_VAR_BYTE("rend-dev-surface-show-vectors", &devSurfaceVectors, CVF_NO_ARCHIVE, 0, 7);
6376     C_VAR_BYTE("rend-dev-thinker-ids", &devThinkerIds, CVF_NO_ARCHIVE, 0, 1);
6377     C_VAR_BYTE("rend-dev-tex-showfix", &devNoTexFix, CVF_NO_ARCHIVE, 0, 1);
6378     C_VAR_BYTE("rend-dev-vertex-show-bars", &devVertexBars, CVF_NO_ARCHIVE, 0, 1);
6379     C_VAR_BYTE("rend-dev-vertex-show-indices", &devVertexIndices, CVF_NO_ARCHIVE, 0, 1);
6380 
6381     C_CMD("rendedit", "", OpenRendererAppearanceEditor);
6382     C_CMD("modeledit", "", OpenModelAssetEditor);
6383     C_CMD("cubeshot", "i", CubeShot);
6384 
6385     C_CMD_FLAGS("lowres", "", LowRes, CMDF_NO_DEDICATED);
6386     C_CMD_FLAGS("mipmap", "i", MipMap, CMDF_NO_DEDICATED);
6387     C_CMD_FLAGS("texreset", "", TexReset, CMDF_NO_DEDICATED);
6388     C_CMD_FLAGS("texreset", "s", TexReset, CMDF_NO_DEDICATED);
6389 
6390     LightDecoration::consoleRegister();
6391 #if 0
6392     BiasIllum::consoleRegister();
6393     LightGrid::consoleRegister();
6394 #endif
6395     Lumobj::consoleRegister();
6396     SkyDrawable::consoleRegister();
6397     Rend_ModelRegister();
6398     Rend_ParticleRegister();
6399 
6400     world::Generator::consoleRegister();
6401 
6402     Rend_RadioRegister();
6403     Rend_SpriteRegister();
6404     PostProcessing::consoleRegister();
6405     fx::Bloom::consoleRegister();
6406     fx::Vignette::consoleRegister();
6407     fx::LensFlares::consoleRegister();
6408 #if 0
6409     Shard::consoleRegister();
6410 #endif
6411     VR_ConsoleRegister();
6412 }
6413 
R_Config()6414 ResourceConfigVars &R_Config()
6415 {
6416     static ResourceConfigVars vars { nullptr, nullptr, nullptr};
6417     if (!vars.noHighResTex)
6418     {
6419         vars.noHighResTex     = &App::config("resource.noHighResTex");
6420         vars.noHighResPatches = &App::config("resource.noHighResPatches");
6421         vars.highResWithPWAD  = &App::config("resource.highResWithPWAD");
6422     }
6423     return vars;
6424 }
6425