1 /** @file viewports.cpp  Player viewports and related low-level rendering.
2  *
3  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2006-2015 Daniel Swanson <danij@dengine.net>
5  *
6  * @par License
7  * GPL: http://www.gnu.org/licenses/gpl.html
8  *
9  * <small>This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by the
11  * Free Software Foundation; either version 2 of the License, or (at your
12  * option) any later version. This program is distributed in the hope that it
13  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15  * Public License for more details. You should have received a copy of the GNU
16  * General Public License along with this program; if not, see:
17  * http://www.gnu.org/licenses</small>
18  */
19 
20 #include "de_platform.h"
21 #include "render/viewports.h"
22 
23 #include <QBitArray>
24 #include <de/concurrency.h>
25 #include <de/timer.h>
26 #include <de/vector1.h>
27 #include <de/GLInfo>
28 #include <de/GLState>
29 #include <doomsday/filesys/fs_util.h>
30 
31 #include "clientapp.h"
32 #include "api_console.h"
33 #include "dd_main.h"
34 #include "dd_loop.h"
35 //#include "ui/editors/edit_bias.h"
36 
37 #include "gl/gl_main.h"
38 
39 #include "api_render.h"
40 #include "render/angleclipper.h"
41 #include "render/cameralensfx.h"
42 #include "render/fx/bloom.h"
43 #include "render/playerweaponanimator.h"
44 #include "render/r_draw.h"
45 #include "render/r_main.h"
46 #include "render/rendersystem.h"
47 #include "render/rendpoly.h"
48 #include "render/skydrawable.h"
49 #include "render/vissprite.h"
50 #include "render/vr.h"
51 
52 #include "network/net_demo.h"
53 
54 #include "world/linesighttest.h"
55 #include "world/thinkers.h"
56 #include "world/p_object.h"
57 #include "world/p_players.h"
58 #include "world/sky.h"
59 #include "BspLeaf"
60 #include "ConvexSubspace"
61 #include "Surface"
62 #include "Contact"
63 #include "client/clientsubsector.h"
64 
65 #include "ui/ui_main.h"
66 #include "ui/clientwindow.h"
67 //#include "ui/widgets/gameuiwidget.h"
68 
69 using namespace de;
70 using namespace world;
71 
72 dd_bool firstFrameAfterLoad;
73 
74 static dint loadInStartupMode;
75 static dint rendCameraSmooth = true;  ///< Smoothed by default.
76 static dbyte showFrameTimePos;
77 static dbyte showViewAngleDeltas;
78 static dbyte showViewPosDeltas;
79 
80 dint rendInfoTris;
81 
82 static viewport_t *currentViewport;
83 
84 struct FrameLuminous
85 {
86     coord_t distance;
87     duint isClipped;
88 };
89 static QVector<FrameLuminous> frameLuminous;
90 
91 static QBitArray subspacesVisible;
92 
93 static QBitArray generatorsVisible(Map::MAX_GENERATORS);
94 
95 static dint frameCount;
96 
97 static dint gridCols, gridRows;
98 static viewport_t viewportOfLocalPlayer[DDMAXPLAYERS];
99 
100 static dint resetNextViewer = true;
101 
R_FrameCount()102 dint R_FrameCount()
103 {
104     return frameCount;
105 }
106 
R_ResetFrameCount()107 void R_ResetFrameCount()
108 {
109     frameCount = 0;
110 }
111 
112 #undef R_SetViewOrigin
R_SetViewOrigin(dint consoleNum,coord_t const origin[3])113 DENG_EXTERN_C void R_SetViewOrigin(dint consoleNum, coord_t const origin[3])
114 {
115     if(consoleNum < 0 || consoleNum >= DDMAXPLAYERS) return;
116     DD_Player(consoleNum)->viewport().latest.origin = Vector3d(origin);
117 }
118 
119 #undef R_SetViewAngle
R_SetViewAngle(dint consoleNum,angle_t angle)120 DENG_EXTERN_C void R_SetViewAngle(dint consoleNum, angle_t angle)
121 {
122     if(consoleNum < 0 || consoleNum >= DDMAXPLAYERS) return;
123     DD_Player(consoleNum)->viewport().latest.setAngle(angle);
124 }
125 
126 #undef R_SetViewPitch
R_SetViewPitch(dint consoleNum,dfloat pitch)127 DENG_EXTERN_C void R_SetViewPitch(dint consoleNum, dfloat pitch)
128 {
129     if(consoleNum < 0 || consoleNum >= DDMAXPLAYERS) return;
130     DD_Player(consoleNum)->viewport().latest.pitch = pitch;
131 }
132 
R_SetupDefaultViewWindow(dint consoleNum)133 void R_SetupDefaultViewWindow(dint consoleNum)
134 {
135     viewdata_t *vd = &DD_Player(consoleNum)->viewport();
136     if(consoleNum < 0 || consoleNum >= DDMAXPLAYERS) return;
137 
138     vd->window =
139         vd->windowOld =
140             vd->windowTarget = Rectanglei::fromSize(Vector2i(0, 0), Vector2ui(DENG_GAMEVIEW_WIDTH, DENG_GAMEVIEW_HEIGHT));
141     vd->windowInter = 1;
142 }
143 
R_ViewWindowTicker(dint consoleNum,timespan_t ticLength)144 void R_ViewWindowTicker(dint consoleNum, timespan_t ticLength)
145 {
146     viewdata_t *vd = &DD_Player(consoleNum)->viewport();
147     if(consoleNum < 0 || consoleNum >= DDMAXPLAYERS)
148     {
149         return;
150     }
151 
152     vd->windowInter += dfloat(.4 * ticLength * TICRATE);
153     if(vd->windowInter >= 1)
154     {
155         vd->window = vd->windowTarget;
156     }
157     else
158     {
159         vd->window.moveTopLeft(Vector2i(de::roundf(de::lerp<dfloat>(vd->windowOld.topLeft.x, vd->windowTarget.topLeft.x, vd->windowInter)),
160                                         de::roundf(de::lerp<dfloat>(vd->windowOld.topLeft.y, vd->windowTarget.topLeft.y, vd->windowInter))));
161         vd->window.setSize(Vector2ui(de::roundf(de::lerp<dfloat>(vd->windowOld.width(),  vd->windowTarget.width(),  vd->windowInter)),
162                                      de::roundf(de::lerp<dfloat>(vd->windowOld.height(), vd->windowTarget.height(), vd->windowInter))));
163     }
164 }
165 
166 #undef R_ViewWindowGeometry
R_ViewWindowGeometry(dint player,RectRaw * geometry)167 DENG_EXTERN_C dint R_ViewWindowGeometry(dint player, RectRaw *geometry)
168 {
169     if(!geometry) return false;
170     if(player < 0 || player >= DDMAXPLAYERS) return false;
171 
172     viewdata_t const &vd = DD_Player(player)->viewport();
173     geometry->origin.x    = vd.window.topLeft.x;
174     geometry->origin.y    = vd.window.topLeft.y;
175     geometry->size.width  = vd.window.width();
176     geometry->size.height = vd.window.height();
177     return true;
178 }
179 
180 #undef R_ViewWindowOrigin
R_ViewWindowOrigin(dint player,Point2Raw * origin)181 DENG_EXTERN_C dint R_ViewWindowOrigin(dint player, Point2Raw *origin)
182 {
183     if(!origin) return false;
184     if(player < 0 || player >= DDMAXPLAYERS) return false;
185 
186     viewdata_t const &vd = DD_Player(player)->viewport();
187     origin->x = vd.window.topLeft.x;
188     origin->y = vd.window.topLeft.y;
189     return true;
190 }
191 
192 #undef R_ViewWindowSize
R_ViewWindowSize(dint player,Size2Raw * size)193 DENG_EXTERN_C dint R_ViewWindowSize(dint player, Size2Raw *size)
194 {
195     if(!size) return false;
196     if(player < 0 || player >= DDMAXPLAYERS) return false;
197 
198     viewdata_t const &vd = DD_Player(player)->viewport();
199     size->width  = vd.window.width();
200     size->height = vd.window.height();
201     return true;
202 }
203 
204 /**
205  * @note Do not change values used during refresh here because we might be
206  * partway through rendering a frame. Changes should take effect on next
207  * refresh only.
208  */
209 #undef R_SetViewWindowGeometry
R_SetViewWindowGeometry(dint player,RectRaw const * geometry,dd_bool interpolate)210 DENG_EXTERN_C void R_SetViewWindowGeometry(dint player, RectRaw const *geometry, dd_bool interpolate)
211 {
212     dint p = P_ConsoleToLocal(player);
213     if(p < 0) return;
214 
215     viewport_t const *vp = &viewportOfLocalPlayer[p];
216     viewdata_t *vd = &DD_Player(player)->viewport();
217 
218     Rectanglei newGeom = Rectanglei::fromSize(Vector2i(de::clamp<dint>(0, geometry->origin.x, vp->geometry.width()),
219                                                        de::clamp<dint>(0, geometry->origin.y, vp->geometry.height())),
220                                               Vector2ui(de::abs(geometry->size.width),
221                                                         de::abs(geometry->size.height)));
222 
223     if((unsigned) newGeom.bottomRight.x > vp->geometry.width())
224     {
225         newGeom.setWidth(vp->geometry.width() - newGeom.topLeft.x);
226     }
227     if((unsigned) newGeom.bottomRight.y > vp->geometry.height())
228     {
229         newGeom.setHeight(vp->geometry.height() - newGeom.topLeft.y);
230     }
231 
232     // Already at this target?
233     if(vd->window == newGeom)
234     {
235         return;
236     }
237 
238     // Record the new target.
239     vd->windowTarget = newGeom;
240 
241     // Restart or advance the interpolation timer?
242     // If dimensions have not yet been set - do not interpolate.
243     if(interpolate && vd->window.size() != Vector2ui(0, 0))
244     {
245         vd->windowOld   = vd->window;
246         vd->windowInter = 0;
247     }
248     else
249     {
250         vd->windowOld   = vd->windowTarget;
251         vd->windowInter = 1;  // Update on next frame.
252     }
253 }
254 
255 #undef R_ViewPortGeometry
R_ViewPortGeometry(dint player,RectRaw * geometry)256 DENG_EXTERN_C dint R_ViewPortGeometry(dint player, RectRaw *geometry)
257 {
258     if(!geometry) return false;
259 
260     dint p = P_ConsoleToLocal(player);
261     if(p == -1) return false;
262 
263     viewport_t const &vp = viewportOfLocalPlayer[p];
264     geometry->origin.x    = vp.geometry.topLeft.x;
265     geometry->origin.y    = vp.geometry.topLeft.y;
266     geometry->size.width  = vp.geometry.width();
267     geometry->size.height = vp.geometry.height();
268     return true;
269 }
270 
271 #undef R_ViewPortOrigin
R_ViewPortOrigin(dint player,Point2Raw * origin)272 DENG_EXTERN_C dint R_ViewPortOrigin(dint player, Point2Raw *origin)
273 {
274     if(!origin) return false;
275 
276     dint p = P_ConsoleToLocal(player);
277     if(p == -1) return false;
278 
279     viewport_t const &vp = viewportOfLocalPlayer[p];
280     origin->x = vp.geometry.topLeft.x;
281     origin->y = vp.geometry.topLeft.y;
282     return true;
283 }
284 
285 #undef R_ViewPortSize
R_ViewPortSize(dint player,Size2Raw * size)286 DENG_EXTERN_C dint R_ViewPortSize(dint player, Size2Raw *size)
287 {
288     if(!size) return false;
289 
290     dint p = P_ConsoleToLocal(player);
291     if(p == -1) return false;
292 
293     viewport_t const &vp = viewportOfLocalPlayer[p];
294     size->width  = vp.geometry.width();
295     size->height = vp.geometry.height();
296     return true;
297 }
298 
299 #undef R_SetViewPortPlayer
R_SetViewPortPlayer(dint consoleNum,dint viewPlayer)300 DENG_EXTERN_C void R_SetViewPortPlayer(dint consoleNum, dint viewPlayer)
301 {
302     dint p = P_ConsoleToLocal(consoleNum);
303     if(p != -1)
304     {
305         viewportOfLocalPlayer[p].console = viewPlayer;
306     }
307 }
308 
309 /**
310  * Calculate the placement and dimensions of a specific viewport.
311  * Assumes that the grid has already been configured.
312  */
R_UpdateViewPortGeometry(viewport_t * port,dint col,dint row)313 void R_UpdateViewPortGeometry(viewport_t *port, dint col, dint row)
314 {
315     DENG2_ASSERT(port);
316 
317     Rectanglei newGeom = Rectanglei(Vector2i(DENG_GAMEVIEW_X + col * DENG_GAMEVIEW_WIDTH  / gridCols,
318                                              DENG_GAMEVIEW_Y + row * DENG_GAMEVIEW_HEIGHT / gridRows),
319                                     Vector2i(DENG_GAMEVIEW_X + (col+1) * DENG_GAMEVIEW_WIDTH  / gridCols,
320                                              DENG_GAMEVIEW_Y + (row+1) * DENG_GAMEVIEW_HEIGHT / gridRows));
321     ddhook_viewport_reshape_t p;
322 
323     if(port->geometry == newGeom) return;
324 
325     bool doReshape = false;
326     if(port->console != -1 && Plug_CheckForHook(HOOK_VIEWPORT_RESHAPE))
327     {
328         p.oldGeometry.origin.x    = port->geometry.topLeft.x;
329         p.oldGeometry.origin.y    = port->geometry.topLeft.y;
330         p.oldGeometry.size.width  = port->geometry.width();
331         p.oldGeometry.size.height = port->geometry.height();
332         doReshape = true;
333     }
334 
335     port->geometry = newGeom;
336 
337     if(doReshape)
338     {
339         p.geometry.origin.x    = port->geometry.topLeft.x;
340         p.geometry.origin.y    = port->geometry.topLeft.y;
341         p.geometry.size.width  = port->geometry.width();
342         p.geometry.size.height = port->geometry.height();
343 
344         DoomsdayApp::plugins().callAllHooks(HOOK_VIEWPORT_RESHAPE, port->console, (void *)&p);
345     }
346 }
347 
R_SetViewGrid(dint numCols,dint numRows)348 bool R_SetViewGrid(dint numCols, dint numRows)
349 {
350     if(numCols > 0 && numRows > 0)
351     {
352         if(numCols * numRows > DDMAXPLAYERS)
353         {
354             return false;
355         }
356 
357         if(numCols != gridCols || numRows != gridRows)
358         {
359             // The number of consoles has changes; LensFx needs to reallocate resources
360             // only for the consoles in use.
361             /// @todo This could be done smarter, only for the affected viewports. -jk
362             LensFx_GLRelease();
363         }
364 
365         if(numCols > DDMAXPLAYERS)
366             numCols = DDMAXPLAYERS;
367         if(numRows > DDMAXPLAYERS)
368             numRows = DDMAXPLAYERS;
369 
370         gridCols = numCols;
371         gridRows = numRows;
372     }
373 
374     dint p = 0;
375     for(dint y = 0; y < gridRows; ++y)
376     for(dint x = 0; x < gridCols; ++x)
377     {
378         // The console number is -1 if the viewport belongs to no one.
379         viewport_t *vp = &viewportOfLocalPlayer[p];
380 
381         dint const console = P_LocalToConsole(p);
382         if(console != -1)
383         {
384             vp->console = DD_Player(console)->viewConsole;
385         }
386         else
387         {
388             vp->console = -1;
389         }
390 
391         R_UpdateViewPortGeometry(vp, x, y);
392         ++p;
393     }
394 
395     return true;
396 }
397 
R_ResetViewer()398 void R_ResetViewer()
399 {
400     resetNextViewer = 1;
401 }
402 
R_NextViewer()403 dint R_NextViewer()
404 {
405     return resetNextViewer;
406 }
407 
408 /**
409  * The components whose difference is too large for interpolation will be
410  * snapped to the sharp values.
411  */
R_CheckViewerLimits(viewer_t * src,viewer_t * dst)412 void R_CheckViewerLimits(viewer_t *src, viewer_t *dst)
413 {
414     dint const MAXMOVE = 32;
415 
416     /// @todo Remove this snapping. The game should determine this and disable the
417     ///       the interpolation as required.
418     if(fabs(dst->origin.x - src->origin.x) > MAXMOVE ||
419        fabs(dst->origin.y - src->origin.y) > MAXMOVE)
420     {
421         src->origin = dst->origin;
422     }
423 
424     /*
425     if(abs(dint(dst->angle) - dint(src->angle)) >= ANGLE_45)
426     {
427         LOG_DEBUG("R_CheckViewerLimits: Snap camera angle to %08x.") << dst->angle;
428         src->angle = dst->angle;
429     }
430     */
431 }
432 
433 /**
434  * Retrieve the current sharp camera position.
435  */
R_SharpViewer(ClientPlayer & player)436 viewer_t R_SharpViewer(ClientPlayer &player)
437 {
438     DENG2_ASSERT(player.publicData().mo);
439 
440     ddplayer_t const &ddpl = player.publicData();
441 
442     viewer_t view(player.viewport().latest);
443 
444     if((ddpl.flags & DDPF_CHASECAM) && !(ddpl.flags & DDPF_CAMERA))
445     {
446         // STUB
447         // This needs to be fleshed out with a proper third person
448         // camera control setup. Currently we simply project the viewer's
449         // position a set distance behind the ddpl.
450         dfloat const distance = 90;
451 
452         duint angle = view.angle() >> ANGLETOFINESHIFT;
453         duint pitch = angle_t(LOOKDIR2DEG(view.pitch) / 360 * ANGLE_MAX) >> ANGLETOFINESHIFT;
454 
455         view.origin -= Vector3d(FIX2FLT(fineCosine[angle]),
456                                 FIX2FLT(finesine[angle]),
457                                 FIX2FLT(finesine[pitch])) * distance;
458     }
459 
460     // Check that the viewZ doesn't go too high or low.
461     // Cameras are not restricted.
462     if(!(ddpl.flags & DDPF_CAMERA))
463     {
464         if(view.origin.z > ddpl.mo->ceilingZ - 4)
465         {
466             view.origin.z = ddpl.mo->ceilingZ - 4;
467         }
468 
469         if(view.origin.z < ddpl.mo->floorZ + 4)
470         {
471             view.origin.z = ddpl.mo->floorZ + 4;
472         }
473     }
474 
475     return view;
476 }
477 
R_NewSharpWorld()478 void R_NewSharpWorld()
479 {
480     if(resetNextViewer)
481     {
482         resetNextViewer = 2;
483     }
484 
485     for(dint i = 0; i < DDMAXPLAYERS; ++i)
486     {
487         player_t *plr  = DD_Player(i);
488         viewdata_t *vd = &plr->viewport();
489 
490         if(!plr->isInGame())
491         {
492             continue;
493         }
494 
495         viewer_t sharpView = R_SharpViewer(*plr);
496 
497         // The game tic has changed, which means we have an updated sharp
498         // camera position.  However, the position is at the beginning of
499         // the tic and we are most likely not at a sharp tic boundary, in
500         // time.  We will move the viewer positions one step back in the
501         // buffer.  The effect of this is that [0] is the previous sharp
502         // position and [1] is the current one.
503 
504         vd->lastSharp[0] = vd->lastSharp[1];
505         vd->lastSharp[1] = sharpView;
506 
507         R_CheckViewerLimits(vd->lastSharp, &sharpView);
508     }
509 
510     if(ClientApp::world().hasMap())
511     {
512         Map &map = ClientApp::world().map();
513         map.updateTrackedPlanes();
514         map.updateScrollingSurfaces();
515     }
516 }
517 
R_UpdateViewer(dint consoleNum)518 void R_UpdateViewer(dint consoleNum)
519 {
520     DENG2_ASSERT(consoleNum >= 0 && consoleNum < DDMAXPLAYERS);
521 
522     dint const VIEWPOS_MAX_SMOOTHDISTANCE = 172;
523 
524     player_t *player = DD_Player(consoleNum);
525     viewdata_t *vd   = &player->viewport();
526 
527     if(!player->isInGame()) return;
528 
529     viewer_t sharpView = R_SharpViewer(*player);
530 
531     if(resetNextViewer ||
532        (sharpView.origin - vd->current.origin).length() > VIEWPOS_MAX_SMOOTHDISTANCE)
533     {
534         // Keep reseting until a new sharp world has arrived.
535         if(resetNextViewer > 1)
536         {
537             resetNextViewer = 0;
538         }
539 
540         // Just view from the sharp position.
541         vd->current = sharpView;
542 
543         vd->lastSharp[0] = vd->lastSharp[1] = sharpView;
544     }
545     // While the game is paused there is no need to calculate any
546     // time offsets or interpolated camera positions.
547     else  //if(!clientPaused)
548     {
549         // Calculate the smoothed camera position, which is somewhere between
550         // the previous and current sharp positions. This introduces a slight
551         // delay (max. 1/35 sec) to the movement of the smoothed camera.
552         viewer_t smoothView = vd->lastSharp[0].lerp(vd->lastSharp[1], frameTimePos);
553 
554         // Use the latest view angles known to us if the interpolation flags
555         // are not set. The interpolation flags are used when the view angles
556         // are updated during the sharp tics and need to be smoothed out here.
557         // For example, view locking (dead or camera setlock).
558         /*if(!(player->shared.flags & DDPF_INTERYAW))
559             smoothView.angle = sharpView.angle;*/
560         /*if(!(player->shared.flags & DDPF_INTERPITCH))
561             smoothView.pitch = sharpView.pitch;*/
562 
563         vd->current = smoothView;
564 
565         // Monitor smoothness of yaw/pitch changes.
566         if(showViewAngleDeltas)
567         {
568             struct OldAngle {
569                 ddouble time;
570                 dfloat yaw;
571                 dfloat pitch;
572             };
573 
574             static OldAngle oldAngle[DDMAXPLAYERS];
575             OldAngle *old = &oldAngle[DoomsdayApp::players().indexOf(viewPlayer)];
576             dfloat yaw    = (ddouble)smoothView.angle() / ANGLE_MAX * 360;
577 
578             LOGDEV_MSG("(%i) F=%.3f dt=%-10.3f dx=%-10.3f dy=%-10.3f "
579                        "Rdx=%-10.3f Rdy=%-10.3f")
580                     << SECONDS_TO_TICKS(gameTime)
581                     << frameTimePos
582                     << sysTime - old->time
583                     << yaw - old->yaw
584                     << smoothView.pitch - old->pitch
585                     << (yaw - old->yaw) / (sysTime - old->time)
586                     << (smoothView.pitch - old->pitch) / (sysTime - old->time);
587 
588             old->yaw   = yaw;
589             old->pitch = smoothView.pitch;
590             old->time  = sysTime;
591         }
592 
593         // The Rdx and Rdy should stay constant when moving.
594         if(showViewPosDeltas)
595         {
596             struct OldPos {
597                 ddouble time;
598                 Vector3f pos;
599             };
600 
601             static OldPos oldPos[DDMAXPLAYERS];
602             OldPos *old = &oldPos[DoomsdayApp::players().indexOf(viewPlayer)];
603 
604             LOGDEV_MSG("(%i) F=%.3f dt=%-10.3f dx=%-10.3f dy=%-10.3f dz=%-10.3f dx/dt=%-10.3f dy/dt=%-10.3f")
605                     << SECONDS_TO_TICKS(gameTime)
606                     << frameTimePos
607                     << sysTime - old->time
608                     << smoothView.origin.x - old->pos.x
609                     << smoothView.origin.y - old->pos.y
610                     << smoothView.origin.z - old->pos.z
611                     << (smoothView.origin.x - old->pos.x) / (sysTime - old->time)
612                     << (smoothView.origin.y - old->pos.y) / (sysTime - old->time);
613 
614             old->pos  = smoothView.origin;
615             old->time = sysTime;
616         }
617     }
618 
619     // Update viewer.
620     angle_t const viewYaw = vd->current.angle();
621 
622     duint const an = viewYaw >> ANGLETOFINESHIFT;
623     vd->viewSin = FIX2FLT(finesine[an]);
624     vd->viewCos = FIX2FLT(fineCosine[an]);
625 
626     // Calculate the front, up and side unit vectors.
627     dfloat const yawRad   = ((viewYaw / (dfloat) ANGLE_MAX) *2) * PI;
628     dfloat const pitchRad = vd->current.pitch * 85 / 110.f / 180 * PI;
629 
630     // The front vector.
631     vd->frontVec.x = cos(yawRad) * cos(pitchRad);
632     vd->frontVec.z = sin(yawRad) * cos(pitchRad);
633     vd->frontVec.y = sin(pitchRad);
634 
635     // The up vector.
636     vd->upVec.x = -cos(yawRad) * sin(pitchRad);
637     vd->upVec.z = -sin(yawRad) * sin(pitchRad);
638     vd->upVec.y = cos(pitchRad);
639 
640     // The side vector is the cross product of the front and up vectors.
641     vd->sideVec = vd->frontVec.cross(vd->upVec);
642 }
643 
644 /**
645  * Prepare rendering the view of the given player.
646  */
R_SetupFrame(player_t * player)647 void R_SetupFrame(player_t *player)
648 {
649 #define MINEXTRALIGHTFRAMES         2
650 
651     // This is now the current view player.
652     viewPlayer = player;
653 
654     // Reset the GL triangle counter.
655     //polyCounter = 0;
656 
657     if(showFrameTimePos)
658     {
659         LOGDEV_VERBOSE("frametime = %f") << frameTimePos;
660     }
661 
662     // Handle extralight (used to light up the world momentarily (used for
663     // e.g. gun flashes). We want to avoid flickering, so when ever it is
664     // enabled; make it last for a few frames.
665     if(player->targetExtraLight != player->publicData().extraLight)
666     {
667         player->targetExtraLight = player->publicData().extraLight;
668         player->extraLightCounter = MINEXTRALIGHTFRAMES;
669     }
670 
671     if(player->extraLightCounter > 0)
672     {
673         player->extraLightCounter--;
674         if(player->extraLightCounter == 0)
675             player->extraLight = player->targetExtraLight;
676     }
677 
678     validCount++;
679 
680     extraLight      = player->extraLight;
681     extraLightDelta = extraLight / 16.0f;
682 
683     if(!freezeRLs)
684     {
685         R_ClearVisSprites();
686     }
687 
688 #undef MINEXTRALIGHTFRAMES
689 }
690 
R_RenderPlayerViewBorder()691 void R_RenderPlayerViewBorder()
692 {
693     R_DrawViewBorder();
694 }
695 
R_UseViewPort(viewport_t const * vp)696 void R_UseViewPort(viewport_t const *vp)
697 {
698     DENG2_ASSERT_IN_RENDER_THREAD();
699     DENG_ASSERT_GL_CONTEXT_ACTIVE();
700 
701     if (!vp)
702     {
703         currentViewport = nullptr;
704         /*ClientWindow::main().game().glApplyViewport(
705                 Rectanglei::fromSize(Vector2i(DENG_GAMEVIEW_X, DENG_GAMEVIEW_Y),
706                                      Vector2ui(DENG_GAMEVIEW_WIDTH, DENG_GAMEVIEW_HEIGHT)));*/
707     }
708     else
709     {
710         currentViewport = const_cast<viewport_t *>(vp);
711         //ClientWindow::main().game().glApplyViewport(vp->geometry);
712     }
713 }
714 
R_UseViewPort(int consoleNum)715 void R_UseViewPort(int consoleNum)
716 {
717     int local = P_ConsoleToLocal(consoleNum);
718     if (local >= 0)
719     {
720         R_UseViewPort(&viewportOfLocalPlayer[local]);
721     }
722     else
723     {
724         R_UseViewPort(nullptr);
725     }
726 }
727 
R_ConsoleRect(int console)728 Rectanglei R_ConsoleRect(int console)
729 {
730     int local = P_ConsoleToLocal(console);
731     if (local < 0) return Rectanglei();
732 
733     auto const &port = viewportOfLocalPlayer[local];
734 
735     return Rectanglei(port.geometry.topLeft.x,
736                       port.geometry.topLeft.y,
737                       port.geometry.width(),
738                       port.geometry.height());
739 }
740 
R_Console3DViewRect(int console)741 Rectanglei R_Console3DViewRect(int console)
742 {
743     Rectanglei rect = R_ConsoleRect(console);
744 
745     auto const &pv = DD_Player(console)->viewport();
746     return Rectanglei(rect.left() + pv.window.topLeft.x,
747                       rect.top()  + pv.window.topLeft.y,
748                       de::min(rect.width(),  pv.window.width()),
749                       de::min(rect.height(), pv.window.height()));
750 }
751 
R_CurrentViewPort()752 viewport_t const *R_CurrentViewPort()
753 {
754     return currentViewport;
755 }
756 
R_RenderBlankView()757 void R_RenderBlankView()
758 {
759     UI_DrawDDBackground(Point2Raw{0, 0}, Size2Raw{{{320, 200}}}, 1);
760 }
761 
setupPlayerSprites()762 static void setupPlayerSprites()
763 {
764     DENG2_ASSERT(viewPlayer);
765 
766     // There are no 3D psprites.
767     ::psp3d = false;
768 
769     ddplayer_t *ddpl = &viewPlayer->publicData();
770 
771     // Cameramen have no psprites.
772     if((ddpl->flags & DDPF_CAMERA) || (ddpl->flags & DDPF_CHASECAM))
773         return;
774 
775     if(!ddpl->mo) return;
776     mobj_t *mob = ddpl->mo;
777 
778     if(!Mobj_HasSubsector(*mob)) return;
779     auto &subsec = Mobj_Subsector(*mob).as<world::ClientSubsector>();
780 
781     // Determine if we should be drawing all the psprites full bright?
782     bool fullBright = CPP_BOOL(::levelFullBright);
783     if(!fullBright)
784     {
785         for(ddpsprite_t const &psp : ddpl->pSprites)
786         {
787             if(!psp.statePtr) continue;
788 
789             // If one of the psprites is fullbright, both are.
790             if(psp.statePtr->flags & STF_FULLBRIGHT)
791             {
792                 fullBright = true;
793             }
794         }
795     }
796 
797     viewdata_t const *viewData = &viewPlayer->viewport();
798 
799     for(dint i = 0; i < DDMAXPSPRITES; ++i)
800     {
801         vispsprite_t *spr = &visPSprites[i];
802 
803         spr->type    = VPSPR_SPRITE;
804         spr->psp     = &ddpl->pSprites[i];
805         spr->origin  = viewData->current.origin;
806         spr->bspLeaf = &Mobj_BspLeafAtOrigin(*mob);
807         spr->alpha   = spr->psp->alpha;
808 
809         if(!spr->psp->statePtr) continue;
810 
811         spr->light.isFullBright = (spr->psp->flags & DDPSPF_FULLBRIGHT) != 0;
812 
813         // First, determine whether this is a model or a sprite.
814         FrameModelDef *mf = nullptr, *nextmf = nullptr;
815         dfloat inter = 0;
816         if(useModels)
817         {
818             if(viewPlayer->playerWeaponAnimator().hasModel())
819             {
820                 viewPlayer->playerWeaponAnimator().setupVisPSprite(*spr);
821 
822                 // There are 3D psprites.
823                 ::psp3d = true;
824                 continue;
825             }
826             else
827             {
828                 // Is there a model for this frame?
829                 MobjThinker dummy;
830 
831                 // Setup a dummy for the call to R_CheckModelFor.
832                 dummy->state = spr->psp->statePtr;
833                 dummy->tics  = spr->psp->tics;
834 
835                 mf = Mobj_ModelDef(dummy, &nextmf, &inter);
836             }
837         }
838 
839         // Use a 3D model?
840         if(mf)
841         {
842             // There are 3D psprites.
843             ::psp3d = true;
844 
845             spr->type = VPSPR_MODEL;
846 
847             spr->data.model.flags       = 0;
848             // 32 is the raised weapon height.
849             spr->data.model.topZ        = viewData->current.origin.z;
850             spr->data.model.secFloor    = subsec.visFloor().heightSmoothed();
851             spr->data.model.secCeil     = subsec.visCeiling().heightSmoothed();
852             spr->data.model.pClass      = 0;
853             spr->data.model.floorClip   = 0;
854 
855             spr->data.model.mf          = mf;
856             spr->data.model.nextMF      = nextmf;
857             spr->data.model.inter       = inter;
858             spr->data.model.viewAligned = true;
859 
860             // Offsets to rotation angles.
861             spr->data.model.yawAngleOffset   = spr->psp->pos[0] * weaponOffsetScale - 90;
862             spr->data.model.pitchAngleOffset =
863                 (32 - spr->psp->pos[1]) * weaponOffsetScale * weaponOffsetScaleY / 1000.0f;
864             // Is the FOV shift in effect?
865             if (weaponFOVShift > 0 && weaponFixedFOV > 90)
866             {
867                 spr->data.model.pitchAngleOffset -= weaponFOVShift * (weaponFixedFOV - 90) / 90;
868             }
869             // Real rotation angles.
870             spr->data.model.yaw =
871                 viewData->current.angle() / (dfloat) ANGLE_MAX *-360 + spr->data.model.yawAngleOffset + 90;
872             spr->data.model.pitch = viewData->current.pitch * 85 / 110 + spr->data.model.yawAngleOffset;
873             std::memset(spr->data.model.visOff, 0, sizeof(spr->data.model.visOff));
874         }
875         else
876         {
877             // No, draw a 2D sprite (in Rend_DrawPlayerSprites).
878             spr->type = VPSPR_SPRITE;
879         }
880     }
881 }
882 
883 static Matrix4f frameViewerMatrix;
884 
setupViewMatrix()885 static void setupViewMatrix()
886 {
887     auto &rend = ClientApp::renderSystem();
888 
889     // These will be the matrices for the current frame.
890     rend.uProjectionMatrix() = Rend_GetProjectionMatrix();
891     rend.uViewMatrix()       = Rend_GetModelViewMatrix(DoomsdayApp::players().indexOf(viewPlayer));
892 
893     frameViewerMatrix        = rend.uProjectionMatrix().toMatrix4f() * rend.uViewMatrix().toMatrix4f();
894 }
895 
Viewer_Matrix()896 Matrix4f const &Viewer_Matrix()
897 {
898     return frameViewerMatrix;
899 }
900 
901 enum ViewState { Default2D, PlayerView3D, PlayerSprite2D };
902 
changeViewState(ViewState viewState)903 static void changeViewState(ViewState viewState) //, viewport_t const *port, viewdata_t const *viewData)
904 {
905     //DENG2_ASSERT(port && viewData);
906 
907     DENG2_ASSERT_IN_RENDER_THREAD();
908     DENG_ASSERT_GL_CONTEXT_ACTIVE();
909 
910     switch (viewState)
911     {
912     case PlayerView3D:
913         DGL_CullFace(DGL_BACK);
914         DGL_Enable(DGL_DEPTH_TEST);
915         GL_ProjectionMatrix(); // The 3D projection matrix.
916         break;
917 
918     case PlayerSprite2D:
919     {
920         auto const conRect  = R_ConsoleRect(displayPlayer);
921         auto const viewRect = R_Console3DViewRect(displayPlayer);
922 
923         dint const height = dint(SCREENHEIGHT
924                 * ( float(conRect.width()) * float(viewRect.height())
925                                            / float(viewRect.width()) )
926                 / float(conRect.height()));
927 
928         scalemode_t sm = R_ChooseScaleMode(SCREENWIDTH, SCREENHEIGHT,
929                                            conRect.width(), conRect.height(),
930                                            scalemode_t(weaponScaleMode));
931 
932         DGL_MatrixMode(DGL_PROJECTION);
933         DGL_LoadIdentity();
934 
935         if(sm == SCALEMODE_STRETCH)
936         {
937             DGL_Ortho(0, 0, SCREENWIDTH, height, -1, 1);
938         }
939         else
940         {
941             // Use an orthographic projection in native screenspace. Then
942             // translate and scale the projection to produce an aspect
943             // corrected coordinate space at 4:3, aligned vertically to
944             // the bottom and centered horizontally in the window.
945             DGL_Ortho(0, 0, conRect.width(), conRect.height(), -1, 1);
946             DGL_Translatef(conRect.width()/2, conRect.height(), 0);
947 
948             if(conRect.width() >= conRect.height())
949             {
950                 DGL_Scalef(dfloat( conRect.height() ) / SCREENHEIGHT,
951                                    dfloat( conRect.height() ) / SCREENHEIGHT, 1);
952             }
953             else
954             {
955                 DGL_Scalef(dfloat( conRect.width() ) / SCREENWIDTH,
956                                    dfloat( conRect.width() ) / SCREENWIDTH, 1);
957             }
958 
959             // Special case: viewport height is greater than width.
960             // Apply an additional scaling factor to prevent player sprites
961             // looking too small.
962             if(conRect.height() > conRect.width())
963             {
964                 dfloat extraScale = (dfloat(conRect.height() * 2) / conRect.width()) / 2;
965                 DGL_Scalef(extraScale, extraScale, 1);
966             }
967 
968             DGL_Translatef(-(SCREENWIDTH / 2), -SCREENHEIGHT, 0);
969             DGL_Scalef(1, dfloat( SCREENHEIGHT ) / height, 1);
970         }
971 
972         DGL_MatrixMode(DGL_MODELVIEW);
973         DGL_LoadIdentity();
974 
975         // Depth testing must be disabled so that psprite 1 will be drawn
976         // on top of psprite 0 (Doom plasma rifle fire).
977         DGL_Disable(DGL_DEPTH_TEST);
978 
979         break;
980     }
981 
982     case Default2D:
983         DGL_CullFace(DGL_NONE);
984         DGL_Disable(DGL_DEPTH_TEST);
985         break;
986     }
987 
988 
989     //std::memcpy(&currentView, port, sizeof(currentView));
990 
991     //viewpx = port->geometry.topLeft.x + viewData->window.topLeft.x;
992     //viewpy = port->geometry.topLeft.y + viewData->window.topLeft.y;
993 
994     /*auto const viewRect = R_Console3DViewRect(displayPlayer);
995     viewpx = 0;
996     viewpy = 0;
997     viewpw = int(viewRect.width());
998     viewph = int(viewRect.height());*/
999 
1000     //viewpw = de::min(port->geometry.width(), viewData->window.width());
1001     //viewph = de::min(port->geometry.height(), viewData->window.height());
1002 
1003     /*ClientWindow::main().game().glApplyViewport(Rectanglei::fromSize(Vector2i(viewpx, viewpy),
1004                                                                      Vector2ui(viewpw, viewph)));*/
1005 
1006 }
1007 
1008 #undef R_RenderPlayerView
R_RenderPlayerView(dint num)1009 DENG_EXTERN_C void R_RenderPlayerView(dint num)
1010 {
1011     if (num < 0 || num >= DDMAXPLAYERS) return; // Huh?
1012     player_t *player = DD_Player(num);
1013 
1014     if (!player->publicData().inGame) return;
1015     if (!player->publicData().mo) return;
1016     if (!ClientApp::world().hasMap()) return;
1017 
1018     if (firstFrameAfterLoad)
1019     {
1020         // Don't let the clock run yet.  There may be some texture
1021         // loading still left to do that we have been unable to
1022         // predetermine.
1023         firstFrameAfterLoad = false;
1024         DD_ResetTimer();
1025     }
1026 
1027     // Too early? Game has not configured the view window?
1028     viewdata_t *vd = &player->viewport();
1029     if (vd->window.isNull()) return;
1030 
1031     // Setup for rendering the frame.
1032     R_SetupFrame(player);
1033 
1034     vrCfg().setEyeHeightInMapUnits(Con_GetInteger("player-eyeheight"));
1035 
1036     setupViewMatrix();
1037     setupPlayerSprites();
1038 
1039     if (ClientApp::vr().mode() == VRConfig::OculusRift &&
1040         ClientApp::world().map().isPointInVoid(Rend_EyeOrigin().xzy()))
1041     {
1042         // Putting one's head in the wall will cause a blank screen.
1043         GLState::current().target().clear(GLFramebuffer::Color);
1044         return;
1045     }
1046 
1047     // Hide the viewPlayer's mobj?
1048     dint oldFlags = 0;
1049     if (!(player->publicData().flags & DDPF_CHASECAM))
1050     {
1051         oldFlags = player->publicData().mo->ddFlags;
1052         player->publicData().mo->ddFlags |= DDMF_DONTDRAW;
1053     }
1054 
1055     // Go to wireframe mode?
1056 #if defined (DENG_OPENGL)
1057     if (renderWireframe)
1058     {
1059         LIBGUI_GL.glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
1060     }
1061 #endif
1062 
1063     DGL_MatrixMode(DGL_PROJECTION);
1064     DGL_PushMatrix();
1065     DGL_MatrixMode(DGL_MODELVIEW);
1066     DGL_PushMatrix();
1067 
1068     // GL is in 3D transformation state only during the frame.
1069     //switchTo3DState(true); //, currentViewport, vd);
1070     changeViewState(PlayerView3D);
1071 
1072     Rend_RenderMap(ClientApp::world().map());
1073 
1074     // Orthogonal projection to the view window.
1075     //restore2DState(1); //, currentViewport, vd);
1076     changeViewState(PlayerSprite2D);
1077 
1078     // Don't render in wireframe mode with 2D psprites.
1079 #if defined (DENG_OPENGL)
1080     if (renderWireframe)
1081     {
1082         LIBGUI_GL.glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
1083     }
1084 #endif
1085 
1086     Rend_Draw2DPlayerSprites();  // If the 2D versions are needed.
1087 
1088 #if defined (DENG_OPENGL)
1089     if (renderWireframe)
1090     {
1091         LIBGUI_GL.glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
1092     }
1093 #endif
1094 
1095     // Do we need to render any 3D psprites?
1096     if (psp3d)
1097     {
1098         //switchTo3DState(false); //, currentViewport, vd);
1099         changeViewState(PlayerView3D);
1100         Rend_Draw3DPlayerSprites();
1101     }
1102 
1103     // Restore fullscreen viewport, original matrices and state: back to normal 2D.
1104     //restore2DState(2); //, currentViewport, vd);
1105     changeViewState(Default2D);
1106 
1107     DGL_MatrixMode(DGL_PROJECTION);
1108     DGL_PopMatrix();
1109     DGL_MatrixMode(DGL_MODELVIEW);
1110     DGL_PopMatrix();
1111 
1112     DGL_Flush();
1113 
1114 #if defined (DENG_OPENGL)
1115     // Back from wireframe mode?
1116     if (renderWireframe)
1117     {
1118         LIBGUI_GL.glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
1119     }
1120 #endif
1121 
1122     // Now we can show the viewPlayer's mobj again.
1123     if (!(player->publicData().flags & DDPF_CHASECAM))
1124     {
1125         player->publicData().mo->ddFlags = oldFlags;
1126     }
1127 
1128     R_PrintRendPoolInfo();
1129 }
1130 
1131 /**
1132  * Should be called when returning from a game-side drawing method to ensure
1133  * that our assumptions of the GL state are valid. This is necessary because
1134  * DGL affords the user the posibility of modifiying the GL state.
1135  *
1136  * @todo: A cleaner approach would be a DGL state stack which could simply pop.
1137  */
restoreDefaultGLState()1138 static void restoreDefaultGLState()
1139 {
1140     // Here we use the DGL methods as this ensures it's state is kept in sync.
1141     DGL_Disable(DGL_FOG);
1142     DGL_Disable(DGL_SCISSOR_TEST);
1143     DGL_Disable(DGL_TEXTURE_2D);
1144     DGL_Enable(DGL_LINE_SMOOTH);
1145     DGL_Enable(DGL_POINT_SMOOTH);
1146 }
1147 
R_RenderViewPort(int playerNum)1148 void R_RenderViewPort(int playerNum)
1149 {
1150     int localNum = P_ConsoleToLocal(playerNum);
1151     if (localNum < 0) return;
1152 
1153     viewport_t const *vp = &viewportOfLocalPlayer[localNum];
1154 
1155     DENG2_ASSERT(vp->console == playerNum);
1156 
1157     dint const oldDisplay = displayPlayer;
1158     displayPlayer = vp->console;
1159     //R_UseViewPort(vp);
1160     //currentViewport = vp;
1161 
1162     if(displayPlayer < 0 || (DD_Player(displayPlayer)->publicData().flags & DDPF_UNDEFINED_ORIGIN))
1163     {
1164         R_RenderBlankView();
1165         displayPlayer = oldDisplay;
1166         return;
1167     }
1168 
1169     DGL_MatrixMode(DGL_PROJECTION);
1170     DGL_PushMatrix();
1171     DGL_LoadIdentity();
1172 
1173     Rectanglei viewRect = R_Console3DViewRect(playerNum);
1174 
1175     // Use an orthographic projection in real pixel dimensions.
1176     DGL_Ortho(0, 0, viewRect.width(), viewRect.height(), -1, 1);
1177 
1178     viewdata_t const *vd = &DD_Player(vp->console)->viewport();
1179     RectRaw vpGeometry = {{vp->geometry.topLeft.x, vp->geometry.topLeft.y},
1180                           {int(vp->geometry.width()), int(vp->geometry.height())}};
1181 
1182     RectRaw vdWindow = {{vd->window.topLeft.x, vd->window.topLeft.y},
1183                         {int(vd->window.width()), int(vd->window.height())}};
1184 
1185     R_UpdateViewer(vp->console);
1186 
1187     gx.DrawViewPort(localNum, &vpGeometry, &vdWindow, displayPlayer, /* layer: */ 0);
1188 
1189     // Apply camera lens effects on the rendered view.
1190     LensFx_Draw(vp->console);
1191 
1192     restoreDefaultGLState();
1193 
1194     DGL_MatrixMode(DGL_PROJECTION);
1195     DGL_PopMatrix();
1196 
1197     // Increment the internal frame count. This does not
1198     // affect the window's FPS counter.
1199     frameCount++;
1200 
1201     // Keep reseting until a new sharp world has arrived.
1202     if(resetNextViewer > 1) resetNextViewer = 0;
1203 
1204     // Restore things back to normal.
1205     displayPlayer = oldDisplay;
1206 }
1207 
R_ClearViewData()1208 void R_ClearViewData()
1209 {
1210     frameLuminous.clear();
1211 }
1212 
1213 /**
1214  * Viewer specific override controlling whether a given sky layer is enabled.
1215  *
1216  * @todo The override should be applied at SkyDrawable level. We have Raven to
1217  * thank for this nonsense (Hexen's sector special 200)... -ds
1218  */
1219 #undef R_SkyParams
R_SkyParams(dint layerIndex,dint param,void *)1220 DENG_EXTERN_C void R_SkyParams(dint layerIndex, dint param, void * /*data*/)
1221 {
1222     LOG_AS("R_SkyParams");
1223     if (!ClientApp::world().hasMap())
1224     {
1225         LOG_GL_WARNING("No map currently loaded, ignoring");
1226         return;
1227     }
1228     Sky &sky = ClientApp::world().map().sky();
1229     if (layerIndex >= 0 && layerIndex < sky.layerCount())
1230     {
1231         SkyLayer &layer = sky.layer(layerIndex);
1232         switch (param)
1233         {
1234         case DD_ENABLE:  layer.enable();  break;
1235         case DD_DISABLE: layer.disable(); break;
1236 
1237         default: // Log but otherwise ignore this error.
1238             LOG_GL_WARNING("Failed configuring layer #%i: bad parameter %i")
1239                 << layerIndex << param;
1240         }
1241         return;
1242     }
1243     LOG_GL_WARNING("Invalid layer #%i") << + layerIndex;
1244 }
1245 
R_ViewerSubspaceIsVisible(ConvexSubspace const & subspace)1246 bool R_ViewerSubspaceIsVisible(ConvexSubspace const &subspace)
1247 {
1248     DENG2_ASSERT(subspace.indexInMap() != MapElement::NoIndex);
1249     return subspacesVisible.testBit(subspace.indexInMap());
1250 }
1251 
R_ViewerSubspaceMarkVisible(ConvexSubspace const & subspace,bool yes)1252 void R_ViewerSubspaceMarkVisible(ConvexSubspace const &subspace, bool yes)
1253 {
1254     DENG2_ASSERT(subspace.indexInMap() != MapElement::NoIndex);
1255     subspacesVisible.setBit(subspace.indexInMap(), yes);
1256 }
1257 
R_ViewerGeneratorIsVisible(Generator const & generator)1258 bool R_ViewerGeneratorIsVisible(Generator const &generator)
1259 {
1260     return generatorsVisible.testBit(generator.id() - 1 /* id is 1-based index */);
1261 }
1262 
R_ViewerGeneratorMarkVisible(Generator const & generator,bool yes)1263 void R_ViewerGeneratorMarkVisible(Generator const &generator, bool yes)
1264 {
1265     generatorsVisible.setBit(generator.id() - 1 /* id is 1-based index */, yes);
1266 }
1267 
R_ViewerLumobjDistance(dint idx)1268 ddouble R_ViewerLumobjDistance(dint idx)
1269 {
1270     /// @todo Do not assume the current map.
1271     if(idx >= 0 && idx < ClientApp::world().map().lumobjCount())
1272     {
1273         return frameLuminous.at(idx).distance;
1274     }
1275     return 0;
1276 }
1277 
R_ViewerLumobjIsClipped(dint idx)1278 bool R_ViewerLumobjIsClipped(dint idx)
1279 {
1280     if (idx >= 0 && idx < frameLuminous.size())
1281     {
1282         return CPP_BOOL(frameLuminous.at(idx).isClipped);
1283     }
1284     return false;
1285 }
1286 
R_ViewerLumobjIsHidden(dint idx)1287 bool R_ViewerLumobjIsHidden(dint idx)
1288 {
1289     if (idx >= 0 && idx < frameLuminous.size())
1290     {
1291         return frameLuminous.at(idx).isClipped == 2;
1292     }
1293     return false;
1294 }
1295 
markLumobjClipped(Lumobj const & lob,bool yes=true)1296 static void markLumobjClipped(Lumobj const &lob, bool yes = true)
1297 {
1298     dint const index = lob.indexInMap();
1299     DENG_ASSERT(index >= 0 && index < lob.map().lumobjCount());
1300     DENG_ASSERT(index < frameLuminous.size());
1301     frameLuminous[index].isClipped = yes? 1 : 0;
1302 }
1303 
R_BeginFrame()1304 void R_BeginFrame()
1305 {
1306     static QVector<int> frameLuminousOrder;
1307 
1308     Map &map = ClientApp::world().map();
1309 
1310     subspacesVisible.resize(map.subspaceCount());
1311     subspacesVisible.fill(false);
1312 
1313     // Clear all generator visibility flags.
1314     generatorsVisible.fill(false);
1315 
1316     int numLuminous = map.lumobjCount();
1317     if (!numLuminous) return;
1318 
1319     // Resize the associated buffers used for per-frame stuff.
1320     //int maxLuminous = numLuminous;
1321     if (frameLuminous.size() < numLuminous)
1322     {
1323         frameLuminous.resize(numLuminous);
1324         frameLuminousOrder.resize(numLuminous);
1325     }
1326 
1327     // Update viewer => lumobj distances ready for linking and sorting.
1328     viewdata_t const *viewData = &viewPlayer->viewport();
1329     map.forAllLumobjs([&viewData] (Lumobj &lob)
1330     {
1331         // Approximate the distance in 3D.
1332         Vector3d delta = lob.origin() - viewData->current.origin;
1333         frameLuminous[lob.indexInMap()].distance = M_ApproxDistance3(delta.x, delta.y, delta.z * 1.2 /*correct aspect*/);
1334         return LoopContinue;
1335     });
1336 
1337     if (rendMaxLumobjs > 0 && numLuminous > rendMaxLumobjs)
1338     {
1339         // Sort lumobjs by distance from the viewer. Then clip all lumobjs
1340         // so that only the closest are visible (max loMaxLumobjs).
1341 
1342         // Init the lumobj indices, sort array.
1343         // Mark all as hidden.
1344         for (int i = 0; i < numLuminous; ++i)
1345         {
1346             frameLuminousOrder[i] = i;
1347             frameLuminous[i].isClipped = 2;
1348         }
1349         qSort(frameLuminousOrder.begin(),
1350               frameLuminousOrder.begin() + numLuminous,
1351               [] (int a, int b)
1352         {
1353             return frameLuminous[a].distance < frameLuminous[b].distance;
1354         });
1355 
1356         for (int i = 0, n = 0; i < numLuminous; ++i)
1357         {
1358             if (n++ > rendMaxLumobjs)
1359                 break;
1360 
1361             // Unhide this lumobj.
1362             frameLuminous[frameLuminousOrder[i]].isClipped = 1;
1363         }
1364     }
1365     else
1366     {
1367         // Mark all as clipped.
1368         for (auto &lum : frameLuminous)
1369         {
1370             lum.isClipped = 1;
1371         }
1372     }
1373 }
1374 
R_ViewerClipLumobj(Lumobj * lum)1375 void R_ViewerClipLumobj(Lumobj *lum)
1376 {
1377     if (!lum) return;
1378 
1379     // Has this already been occluded?
1380     dint lumIdx = lum->indexInMap();
1381     if (frameLuminous.at(lumIdx).isClipped > 1) return;
1382 
1383     markLumobjClipped(*lum, false);
1384 
1385     /// @todo Determine the exact centerpoint of the light in addLuminous!
1386     Vector3d const origin(lum->x(), lum->y(), lum->z() + lum->zOffset());
1387 
1388     if (!P_IsInVoid(DD_Player(displayPlayer)) && !devNoCulling)
1389     {
1390         if (!ClientApp::renderSystem().angleClipper().isPointVisible(origin))
1391         {
1392             markLumobjClipped(*lum); // Won't have a halo.
1393         }
1394     }
1395     else
1396     {
1397         markLumobjClipped(*lum);
1398 
1399         Vector3d const eye = Rend_EyeOrigin().xzy();
1400         if (LineSightTest(eye, origin, -1, 1, LS_PASSLEFT | LS_PASSOVER | LS_PASSUNDER)
1401                 .trace(lum->map().bspTree()))
1402         {
1403             markLumobjClipped(*lum, false); // Will have a halo.
1404         }
1405     }
1406 }
1407 
R_ViewerClipLumobjBySight(Lumobj * lob,ConvexSubspace * subspace)1408 void R_ViewerClipLumobjBySight(Lumobj *lob, ConvexSubspace *subspace)
1409 {
1410     if(!lob || !subspace) return;
1411 
1412     // Already clipped?
1413     if (frameLuminous.at(lob->indexInMap()).isClipped)
1414         return;
1415 
1416     // We need to figure out if any of the polyobj's segments lies
1417     // between the viewpoint and the lumobj.
1418     Vector3d const eye = Rend_EyeOrigin().xzy();
1419 
1420     subspace->forAllPolyobjs([&lob, &eye] (Polyobj &pob)
1421     {
1422         for(HEdge *hedge : pob.mesh().hedges())
1423         {
1424             // Is this on the back of a one-sided line?
1425             if(!hedge->hasMapElement())
1426                 continue;
1427 
1428             // Ignore half-edges facing the wrong way.
1429             if(hedge->mapElementAs<LineSideSegment>().isFrontFacing())
1430             {
1431                 coord_t eyeV1[2]       = { eye.x, eye.y };
1432                 coord_t lumOriginV1[2] = { lob->origin().x, lob->origin().y };
1433                 coord_t fromV1[2]      = { hedge->origin().x, hedge->origin().y };
1434                 coord_t toV1[2]        = { hedge->twin().origin().x, hedge->twin().origin().y };
1435                 if(V2d_Intercept2(lumOriginV1, eyeV1, fromV1, toV1, 0, 0, 0))
1436                 {
1437                     markLumobjClipped(*lob);
1438                     break;
1439                 }
1440             }
1441         }
1442         return LoopContinue;
1443     });
1444 }
1445 
angle() const1446 angle_t viewer_t::angle() const
1447 {
1448     angle_t a = _angle;
1449     if(DD_GetInteger(DD_USING_HEAD_TRACKING))
1450     {
1451         // Apply the actual, current yaw offset. The game has omitted the "body yaw"
1452         // portion from the value already.
1453         a += fixed_t(radianToDegree(vrCfg().oculusRift().headOrientation().z) / 180 * ANGLE_180);
1454     }
1455     return a;
1456 }
1457 
D_CMD(ViewGrid)1458 D_CMD(ViewGrid)
1459 {
1460     DENG2_UNUSED2(src, argc);
1461     // Recalculate viewports.
1462     return R_SetViewGrid(String(argv[1]).toInt(), String(argv[2]).toInt());
1463 }
1464 
Viewports_Register()1465 void Viewports_Register()
1466 {
1467     C_VAR_INT ("con-show-during-setup",     &loadInStartupMode,     0, 0, 1);
1468 
1469     C_VAR_INT ("rend-camera-smooth",        &rendCameraSmooth,      CVF_HIDE, 0, 1);
1470 
1471     C_VAR_BYTE("rend-info-deltas-angles",   &showViewAngleDeltas,   0, 0, 1);
1472     C_VAR_BYTE("rend-info-deltas-pos",      &showViewPosDeltas,     0, 0, 1);
1473     C_VAR_BYTE("rend-info-frametime",       &showFrameTimePos,      0, 0, 1);
1474     C_VAR_BYTE("rend-info-rendpolys",       &rendInfoRPolys,        CVF_NO_ARCHIVE, 0, 1);
1475     //C_VAR_INT ("rend-info-tris",            &rendInfoTris,          0, 0, 1); // not implemented atm
1476 
1477     C_CMD("viewgrid", "ii", ViewGrid);
1478 }
1479