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 §or)
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 §or)
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 §or = ::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 §or)
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 §or)
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