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(¤tView, 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