Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

  1. /*
  2.  * Portions of this file are copyright Rebirth contributors and licensed as
  3.  * described in COPYING.txt.
  4.  * Portions of this file are copyright Parallax Software and licensed
  5.  * according to the Parallax license below.
  6.  * See COPYING.txt for license details.
  7.  
  8. THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
  9. SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
  10. END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
  11. ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
  12. IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
  13. SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
  14. FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
  15. CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
  16. AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
  17. COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
  18. */
  19.  
  20. /*
  21.  *
  22.  * Rendering Stuff
  23.  *
  24.  */
  25.  
  26. #include <algorithm>
  27. #include <bitset>
  28. #include <limits>
  29. #include <cstdlib>
  30. #include <stdio.h>
  31. #include <string.h>
  32. #include <math.h>
  33. #include "render_state.h"
  34. #include "inferno.h"
  35. #include "segment.h"
  36. #include "dxxerror.h"
  37. #include "bm.h"
  38. #include "texmap.h"
  39. #include "render.h"
  40. #include "game.h"
  41. #include "object.h"
  42. #include "laser.h"
  43. #include "textures.h"
  44. #include "screens.h"
  45. #include "segpoint.h"
  46. #include "wall.h"
  47. #include "texmerge.h"
  48. #include "physics.h"
  49. #include "3d.h"
  50. #include "gameseg.h"
  51. #include "vclip.h"
  52. #include "lighting.h"
  53. #include "cntrlcen.h"
  54. #include "newdemo.h"
  55. #include "automap.h"
  56. #include "endlevel.h"
  57. #include "key.h"
  58. #include "newmenu.h"
  59. #include "u_mem.h"
  60. #include "piggy.h"
  61. #include "timer.h"
  62. #include "effects.h"
  63. #include "playsave.h"
  64. #if DXX_USE_OGL
  65. #include "ogl_init.h"
  66. #endif
  67. #include "args.h"
  68.  
  69. #include "compiler-range_for.h"
  70. #include "d_range.h"
  71. #include "partial_range.h"
  72. #include "segiter.h"
  73.  
  74. #if DXX_USE_EDITOR
  75. #include "editor/editor.h"
  76. #include "editor/esegment.h"
  77. #endif
  78. #include <utility>
  79.  
  80. using std::min;
  81. using std::max;
  82.  
  83. // (former) "detail level" values
  84. #if DXX_USE_OGL
  85. int Render_depth = MAX_RENDER_SEGS; //how many segments deep to render
  86. #else
  87. int Render_depth = 20; //how many segments deep to render
  88. unsigned Max_linear_depth = 50; // Deepest segment at which linear interpolation will be used.
  89. #endif
  90.  
  91. //used for checking if points have been rotated
  92. int     Clear_window_color=-1;
  93. int     Clear_window=2; // 1 = Clear whole background window, 2 = clear view portals into rest of world, 0 = no clear
  94.  
  95. static uint16_t s_current_generation;
  96.  
  97. // When any render function needs to know what's looking at it, it should
  98. // access Viewer members.
  99. namespace dsx {
  100. const object * Viewer = NULL;
  101. }
  102.  
  103. #if !DXX_USE_EDITOR && defined(RELEASE)
  104. constexpr
  105. #endif
  106. fix Render_zoom = 0x9000;                                       //the player's zoom factor
  107.  
  108. #ifndef NDEBUG
  109. static std::bitset<MAX_OBJECTS> object_rendered;
  110. #endif
  111.  
  112. #if DXX_USE_EDITOR
  113. int     Render_only_bottom=0;
  114. int     Bottom_bitmap_num = 9;
  115. #endif
  116.  
  117. namespace dcx {
  118.  
  119. //Global vars for window clip test
  120. int Window_clip_left,Window_clip_top,Window_clip_right,Window_clip_bot;
  121.  
  122. }
  123.  
  124. #if DXX_USE_EDITOR
  125. int _search_mode = 0;                   //true if looking for curseg,side,face
  126. short _search_x,_search_y;      //pixel we're looking at
  127. static int found_side,found_face;
  128. static segnum_t found_seg;
  129. static objnum_t found_obj;
  130. #else
  131. constexpr int _search_mode = 0;
  132. #endif
  133.  
  134. #ifdef NDEBUG           //if no debug code, set these vars to constants
  135. #else
  136.  
  137. int Outline_mode=0;
  138.  
  139. int toggle_outline_mode(void)
  140. {
  141.         return Outline_mode = !Outline_mode;
  142. }
  143. #endif
  144.  
  145. #ifndef NDEBUG
  146. #if DXX_USE_OGL
  147. #define draw_outline(C,a,b)     draw_outline(a,b)
  148. #endif
  149. static void draw_outline(grs_canvas &canvas, const unsigned nverts, cg3s_point *const *const pointlist)
  150. {
  151.         const uint8_t color = BM_XRGB(63, 63, 63);
  152.  
  153.         const unsigned e = nverts - 1;
  154.         range_for (const unsigned i, xrange(e))
  155.                 g3_draw_line(canvas, *pointlist[i], *pointlist[i + 1], color);
  156.         g3_draw_line(canvas, *pointlist[e], *pointlist[0], color);
  157. }
  158. #endif
  159.  
  160. fix flash_scale;
  161.  
  162. #define FLASH_CYCLE_RATE f1_0
  163.  
  164. constexpr std::integral_constant<fix, FLASH_CYCLE_RATE> Flash_rate{};
  165.  
  166. //cycle the flashing light for when mine destroyed
  167. namespace dsx {
  168. void flash_frame()
  169. {
  170.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  171.         static fixang flash_ang=0;
  172.  
  173.         if (Endlevel_sequence)
  174.                 return;
  175.  
  176.         if (PaletteBlueAdd > 10 )               //whiting out
  177.                 return;
  178.  
  179. //      flash_ang += fixmul(FLASH_CYCLE_RATE,FrameTime);
  180. #if defined(DXX_BUILD_DESCENT_II)
  181.         if (const auto Seismic_tremor_magnitude = LevelUniqueSeismicState.Seismic_tremor_magnitude)
  182.         {
  183.                 fix     added_flash;
  184.  
  185.                 added_flash = abs(Seismic_tremor_magnitude);
  186.                 if (added_flash < F1_0)
  187.                         added_flash *= 16;
  188.  
  189.                 flash_ang += fixmul(Flash_rate, fixmul(FrameTime, added_flash+F1_0));
  190.                 flash_scale = fix_fastsin(flash_ang);
  191.                 flash_scale = (flash_scale + F1_0*3)/4; //      gets in range 0.5 to 1.0
  192.         } else
  193. #endif
  194.         if (LevelUniqueControlCenterState.Control_center_destroyed)
  195.         {
  196.                 flash_ang += fixmul(Flash_rate,FrameTime);
  197.                 flash_scale = fix_fastsin(flash_ang);
  198.                 flash_scale = (flash_scale + f1_0)/2;
  199. #if defined(DXX_BUILD_DESCENT_II)
  200.                 if (GameUniqueState.Difficulty_level == 0)
  201.                         flash_scale = (flash_scale+F1_0*3)/4;
  202. #endif
  203.         }
  204.  
  205.  
  206. }
  207.  
  208. static inline int is_alphablend_eclip(int eclip_num)
  209. {
  210. #if defined(DXX_BUILD_DESCENT_II)
  211.         if (eclip_num == ECLIP_NUM_FORCE_FIELD || eclip_num == ECLIP_NUM_FORCE_FIELD2)
  212.                 return 1;
  213. #endif
  214.         return eclip_num == ECLIP_NUM_FUELCEN;
  215. }
  216.  
  217. // ----------------------------------------------------------------------------
  218. //      Render a face.
  219. //      It would be nice to not have to pass in segnum and sidenum, but
  220. //      they are used for our hideously hacked in headlight system.
  221. //      vp is a pointer to vertex ids.
  222. //      tmap1, tmap2 are texture map ids.  tmap2 is the pasty one.
  223. static void render_face(grs_canvas &canvas, const shared_segment &segp, const unsigned sidenum, const unsigned nv, const std::array<unsigned, 4> &vp, const unsigned tmap1, const unsigned tmap2, std::array<g3s_uvl, 4> uvl_copy, const WALL_IS_DOORWAY_result_t wid_flags)
  224. {
  225.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  226.         auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
  227.         grs_bitmap  *bm;
  228.  
  229.         std::array<cg3s_point *, 4> pointlist;
  230.  
  231.         Assert(nv <= pointlist.size());
  232.  
  233.         range_for (const uint_fast32_t i, xrange(nv))
  234.         {
  235.                 pointlist[i] = &Segment_points[vp[i]];
  236.         }
  237.  
  238. #if defined(DXX_BUILD_DESCENT_I)
  239.         (void)segp;
  240.         (void)wid_flags;
  241. #if !DXX_USE_EDITOR
  242.         (void)sidenum;
  243. #endif
  244. #elif defined(DXX_BUILD_DESCENT_II)
  245.         //handle cloaked walls
  246.         if (wid_flags & WID_CLOAKED_FLAG) {
  247.                 const auto wall_num = segp.shared_segment::sides[sidenum].wall_num;
  248.                 auto &Walls = LevelUniqueWallSubsystemState.Walls;
  249.                 auto &vcwallptr = Walls.vcptr;
  250.                 gr_settransblend(canvas, vcwallptr(wall_num)->cloak_value, gr_blend::normal);
  251.                 const uint8_t color = BM_XRGB(0, 0, 0);
  252.                 // set to black (matters for s3)
  253.  
  254.                 g3_draw_poly(canvas, nv, pointlist, color);    // draw as flat poly
  255.                 gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal);
  256.  
  257.                 return;
  258.         }
  259. #endif
  260.  
  261.         if (tmap1 >= NumTextures) {
  262.                 Int3();
  263.         }
  264.  
  265. #if DXX_USE_OGL
  266.         grs_bitmap *bm2 = nullptr;
  267.         if (!CGameArg.DbgUseOldTextureMerge)
  268.         {
  269.                 PIGGY_PAGE_IN(Textures[tmap1]);
  270.                 bm = &GameBitmaps[Textures[tmap1].index];
  271.                 if (tmap2){
  272.                         PIGGY_PAGE_IN(Textures[tmap2&0x3FFF]);
  273.                         bm2 = &GameBitmaps[Textures[tmap2&0x3FFF].index];
  274.                         if (bm2->get_flag_mask(BM_FLAG_SUPER_TRANSPARENT))
  275.                         {
  276.                                 bm2 = nullptr;
  277.                         bm = &texmerge_get_cached_bitmap( tmap1, tmap2 );
  278.                         }
  279.                 }
  280.         }else
  281. #endif
  282.  
  283.                 // New code for overlapping textures...
  284.                 if (tmap2 != 0) {
  285.                         bm = &texmerge_get_cached_bitmap( tmap1, tmap2 );
  286.                 } else {
  287.                         bm = &GameBitmaps[Textures[tmap1].index];
  288.                         PIGGY_PAGE_IN(Textures[tmap1]);
  289.                 }
  290.  
  291.         assert(!bm->get_flag_mask(BM_FLAG_PAGED_OUT));
  292.  
  293.         std::array<g3s_lrgb, 4>         dyn_light;
  294. #if defined(DXX_BUILD_DESCENT_I)
  295.         const auto Seismic_tremor_magnitude = 0;
  296. #elif defined(DXX_BUILD_DESCENT_II)
  297.         const auto Seismic_tremor_magnitude = LevelUniqueSeismicState.Seismic_tremor_magnitude;
  298. #endif
  299.         const auto control_center_destroyed = LevelUniqueControlCenterState.Control_center_destroyed;
  300.         const auto need_flashing_lights = (control_center_destroyed | Seismic_tremor_magnitude);        //make lights flash
  301.         auto &Dynamic_light = LevelUniqueLightState.Dynamic_light;
  302.         //set light values for each vertex & build pointlist
  303.         range_for (const uint_fast32_t i, xrange(nv))
  304.         {
  305.                 auto &dli = dyn_light[i];
  306.                 auto &uvli = uvl_copy[i];
  307.                 auto &Dlvpi = Dynamic_light[vp[i]];
  308.                 dli.r = dli.g = dli.b = uvli.l;
  309.                 //the uvl struct has static light already in it
  310.  
  311.                 //scale static light for destruction effect
  312.                 if (need_flashing_lights)       //make lights flash
  313.                         uvli.l = fixmul(flash_scale, uvli.l);
  314.                 //add in dynamic light (from explosions, etc.)
  315.                 uvli.l += (Dlvpi.r + Dlvpi.g + Dlvpi.b) / 3;
  316.                 //saturate at max value
  317.                 if (uvli.l > MAX_LIGHT)
  318.                         uvli.l = MAX_LIGHT;
  319.  
  320.                 // And now the same for the ACTUAL (rgb) light we want to use
  321.  
  322.                 //scale static light for destruction effect
  323.                 if (need_flashing_lights)       //make lights flash
  324.                 {
  325.                         dli.g = dli.b = fixmul(flash_scale, uvli.l);
  326.                         dli.r = (!Seismic_tremor_magnitude && PlayerCfg.DynLightColor)
  327.                                 ? fixmul(std::max(static_cast<double>(flash_scale), f0_5 * 1.5), uvli.l) // let the mine glow red a little
  328.                                 : dli.g;
  329.                 }
  330.  
  331.                 // add light color
  332.                 dli.r += Dlvpi.r;
  333.                 dli.g += Dlvpi.g;
  334.                 dli.b += Dlvpi.b;
  335.                 // saturate at max value
  336.                 if (dli.r > MAX_LIGHT)
  337.                         dli.r = MAX_LIGHT;
  338.                 if (dli.g > MAX_LIGHT)
  339.                         dli.g = MAX_LIGHT;
  340.                 if (dli.b > MAX_LIGHT)
  341.                         dli.b = MAX_LIGHT;
  342.                 if (PlayerCfg.AlphaEffects) // due to additive blending, transparent sprites will become invivible in font of white surfaces (lamps). Fix that with a little desaturation
  343.                 {
  344.                         dli.r *= .93;
  345.                         dli.g *= .93;
  346.                         dli.b *= .93;
  347.                 }
  348.         }
  349.  
  350.         bool alpha = false;
  351.         if (PlayerCfg.AlphaBlendEClips && is_alphablend_eclip(TmapInfo[tmap1].eclip_num)) // set nice transparency/blending for some special effects (if we do more, we should maybe use switch here)
  352.         {
  353.                 alpha = true;
  354.                 gr_settransblend(canvas, GR_FADE_OFF, gr_blend::additive_c);
  355.         }
  356.  
  357. #if DXX_USE_EDITOR
  358.         if ((Render_only_bottom) && (sidenum == WBOTTOM))
  359.                 g3_draw_tmap(canvas, nv, pointlist, uvl_copy, dyn_light, GameBitmaps[Textures[Bottom_bitmap_num].index]);
  360.         else
  361. #endif
  362.  
  363. #if DXX_USE_OGL
  364.                 if (bm2){
  365.                         g3_draw_tmap_2(canvas, nv, pointlist, uvl_copy, dyn_light, *bm, *bm2, ((tmap2 & 0xC000) >> 14) & 3);
  366.                 }else
  367. #endif
  368.                         g3_draw_tmap(canvas, nv, pointlist, uvl_copy, dyn_light, *bm);
  369.  
  370.         if (alpha)
  371.                 gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal); // revert any transparency / blending setting back to normal
  372.  
  373. #ifndef NDEBUG
  374.         if (Outline_mode) draw_outline(canvas, nv, &pointlist[0]);
  375. #endif
  376. }
  377. }
  378.  
  379. // ----------------------------------------------------------------------------
  380. //      Only called if editor active.
  381. //      Used to determine which face was clicked on.
  382. static void check_face(grs_canvas &canvas, const vmsegidx_t segnum, const unsigned sidenum, const unsigned facenum, const unsigned nv, const std::array<unsigned, 4> &vp, const unsigned tmap1, const unsigned tmap2, const std::array<g3s_uvl, 4> &uvl_copy)
  383. {
  384. #if DXX_USE_EDITOR
  385.         if (_search_mode) {
  386.                 std::array<g3s_lrgb, 4> dyn_light{};
  387.                 std::array<cg3s_point *, 4> pointlist;
  388. #if DXX_USE_OGL
  389.                 (void)tmap1;
  390.                 (void)tmap2;
  391. #else
  392.                 grs_bitmap *bm;
  393.                 if (tmap2 > 0 )
  394.                         bm = &texmerge_get_cached_bitmap( tmap1, tmap2 );
  395.                 else
  396.                         bm = &GameBitmaps[Textures[tmap1].index];
  397. #endif
  398.                 range_for (const uint_fast32_t i, xrange(nv))
  399.                 {
  400.                         dyn_light[i].r = dyn_light[i].g = dyn_light[i].b = uvl_copy[i].l;
  401.                         pointlist[i] = &Segment_points[vp[i]];
  402.                 }
  403.  
  404. #if DXX_USE_OGL
  405.                 ogl_end_frame();
  406. #endif
  407.                 {
  408.                 uint8_t color = 0;
  409.                         gr_pixel(canvas.cv_bitmap, _search_x, _search_y, color);        //set our search pixel to color zero
  410.                 }
  411. #if DXX_USE_OGL
  412.                 ogl_start_frame(canvas);
  413. #endif
  414.                 {
  415. #if DXX_USE_OGL
  416.                         const uint8_t color = 1;
  417.                         g3_draw_poly(canvas, nv, pointlist, color);
  418. #else
  419.                         const auto save_lighting = Lighting_on;
  420.                         Lighting_on = 2;
  421.                         g3_draw_tmap(canvas, nv, pointlist, uvl_copy, dyn_light, *bm);
  422.                         Lighting_on = save_lighting;
  423. #endif
  424.                 }
  425.  
  426.                 if (gr_ugpixel(canvas.cv_bitmap,_search_x,_search_y) == 1) {
  427.                         found_seg = segnum;
  428.                         found_obj = object_none;
  429.                         found_side = sidenum;
  430.                         found_face = facenum;
  431.                 }
  432.         }
  433. #else
  434.         (void)canvas;
  435.         (void)segnum;
  436.         (void)sidenum;
  437.         (void)facenum;
  438.         (void)nv;
  439.         (void)vp;
  440.         (void)tmap1;
  441.         (void)tmap2;
  442.         (void)uvl_copy;
  443. #endif
  444. }
  445.  
  446. template <std::size_t... N>
  447. static inline void check_render_face(grs_canvas &canvas, std::index_sequence<N...>, const vcsegptridx_t segnum, const unsigned sidenum, const unsigned facenum, const std::array<unsigned, 4> &ovp, const unsigned tmap1, const unsigned tmap2, const std::array<uvl, 4> &uvlp, const WALL_IS_DOORWAY_result_t wid_flags, const std::size_t nv)
  448. {
  449.         const std::array<unsigned, 4> vp{{ovp[N]...}};
  450.         const std::array<g3s_uvl, 4> uvl_copy{{
  451.                 {uvlp[N].u, uvlp[N].v, uvlp[N].l}...
  452.         }};
  453.         render_face(canvas, segnum, sidenum, nv, vp, tmap1, tmap2, uvl_copy, wid_flags);
  454.         check_face(canvas, segnum, sidenum, facenum, nv, vp, tmap1, tmap2, uvl_copy);
  455. }
  456.  
  457. template <std::size_t N0, std::size_t N1, std::size_t N2, std::size_t N3>
  458. static inline void check_render_face(grs_canvas &canvas, std::index_sequence<N0, N1, N2, N3> is, const vcsegptridx_t segnum, const unsigned sidenum, const unsigned facenum, const std::array<unsigned, 4> &vp, const unsigned tmap1, const unsigned tmap2, const std::array<uvl, 4> &uvlp, const WALL_IS_DOORWAY_result_t wid_flags)
  459. {
  460.         check_render_face(canvas, is, segnum, sidenum, facenum, vp, tmap1, tmap2, uvlp, wid_flags, 4);
  461. }
  462.  
  463. /* Avoid default constructing final element of uvl_copy; if any members
  464.  * are default constructed, gcc zero initializes all members.
  465.  */
  466. template <std::size_t N0, std::size_t N1, std::size_t N2>
  467. static inline void check_render_face(grs_canvas &canvas, std::index_sequence<N0, N1, N2>, const vcsegptridx_t segnum, const unsigned sidenum, const unsigned facenum, const std::array<unsigned, 4> &vp, const unsigned tmap1, const unsigned tmap2, const std::array<uvl, 4> &uvlp, const WALL_IS_DOORWAY_result_t wid_flags)
  468. {
  469.         check_render_face(canvas, std::index_sequence<N0, N1, N2, 3>(), segnum, sidenum, facenum, vp, tmap1, tmap2, uvlp, wid_flags, 3);
  470. }
  471.  
  472. constexpr std::integral_constant<fix, (F1_0/4)> Tulate_min_dot{};
  473. //--unused-- fix        Tulate_min_ratio = (2*F1_0);
  474. constexpr std::integral_constant<fix, (F1_0*15/16)> Min_n0_n1_dot{};
  475.  
  476. // -----------------------------------------------------------------------------------
  477. //      Render a side.
  478. //      Check for normal facing.  If so, render faces on side dictated by sidep->type.
  479. namespace dsx {
  480. static void render_side(fvcvertptr &vcvertptr, grs_canvas &canvas, const vcsegptridx_t segp, const unsigned sidenum, const WALL_IS_DOORWAY_result_t wid_flags, const vms_vector &Viewer_eye)
  481. {
  482.         fix             min_dot, max_dot;
  483.  
  484.         if (!(wid_flags & WID_RENDER_FLAG))             //if (WALL_IS_DOORWAY(segp, sidenum) == WID_NO_WALL)
  485.                 return;
  486.  
  487.         const auto vertnum_list = get_side_verts(segp,sidenum);
  488.  
  489.         //      Regardless of whether this side is comprised of a single quad, or two triangles, we need to know one normal, so
  490.         //      deal with it, get the dot product.
  491.         const auto &sside = segp->shared_segment::sides[sidenum];
  492.         const unsigned which_vertnum =
  493.                 (sside.get_type() == side_type::tri_13)
  494.                         ? 1
  495.                         : 0;
  496.         const auto tvec = vm_vec_normalized_quick(vm_vec_sub(Viewer_eye, vcvertptr(vertnum_list[which_vertnum])));
  497.         auto &normals = sside.normals;
  498.         const auto v_dot_n0 = vm_vec_dot(tvec, normals[0]);
  499.         //      ========== Mark: Here is the change...beginning here: ==========
  500.  
  501.         std::index_sequence<0, 1, 2, 3> is_quad;
  502.         const auto &uside = segp->unique_segment::sides[sidenum];
  503.         if (sside.get_type() == side_type::quad)
  504.         {
  505.                 if (v_dot_n0 >= 0) {
  506.                         check_render_face(canvas, is_quad, segp, sidenum, 0, vertnum_list, uside.tmap_num, uside.tmap_num2, uside.uvls, wid_flags);
  507.                 }
  508.         } else {
  509.                 //      ========== Mark: The change ends here. ==========
  510.  
  511.                 //      Although this side has been triangulated, because it is not planar, see if it is acceptable
  512.                 //      to render it as a single quadrilateral.  This is a function of how far away the viewer is, how non-planar
  513.                 //      the face is, how normal to the surfaces the view is.
  514.                 //      Now, if both dot products are close to 1.0, then render two triangles as a single quad.
  515.                 const auto v_dot_n1 = vm_vec_dot(tvec, normals[1]);
  516.  
  517.                 if (v_dot_n0 < v_dot_n1) {
  518.                         min_dot = v_dot_n0;
  519.                         max_dot = v_dot_n1;
  520.                 } else {
  521.                         min_dot = v_dot_n1;
  522.                         max_dot = v_dot_n0;
  523.                 }
  524.  
  525.                 //      Determine whether to detriangulate side: (speed hack, assumes Tulate_min_ratio == F1_0*2, should fixmul(min_dot, Tulate_min_ratio))
  526.                 if (DETRIANGULATION && ((min_dot+F1_0/256 > max_dot) || ((Viewer->segnum != segp) &&  (min_dot > Tulate_min_dot) && (max_dot < min_dot*2)))) {
  527.                         fix     n0_dot_n1;
  528.  
  529.                         //      The other detriangulation code doesn't deal well with badly non-planar sides.
  530.                         n0_dot_n1 = vm_vec_dot(normals[0], normals[1]);
  531.                         if (n0_dot_n1 < Min_n0_n1_dot)
  532.                                 goto im_so_ashamed;
  533.  
  534.                         check_render_face(canvas, is_quad, segp, sidenum, 0, vertnum_list, uside.tmap_num, uside.tmap_num2, uside.uvls, wid_flags);
  535.                 } else {
  536. im_so_ashamed: ;
  537.                         if (sside.get_type() == side_type::tri_02)
  538.                         {
  539.                                 if (v_dot_n0 >= 0) {
  540.                                         check_render_face(canvas, std::index_sequence<0, 1, 2>(), segp, sidenum, 0, vertnum_list, uside.tmap_num, uside.tmap_num2, uside.uvls, wid_flags);
  541.                                 }
  542.  
  543.                                 if (v_dot_n1 >= 0) {
  544.                                         // want to render from vertices 0, 2, 3 on side
  545.                                         check_render_face(canvas, std::index_sequence<0, 2, 3>(), segp, sidenum, 1, vertnum_list, uside.tmap_num, uside.tmap_num2, uside.uvls, wid_flags);
  546.                                 }
  547.                         }
  548.                         else if (sside.get_type() == side_type::tri_13)
  549.                         {
  550.                                 if (v_dot_n1 >= 0) {
  551.                                         // rendering 1,2,3, so just skip 0
  552.                                         check_render_face(canvas, std::index_sequence<1, 2, 3>(), segp, sidenum, 1, vertnum_list, uside.tmap_num, uside.tmap_num2, uside.uvls, wid_flags);
  553.                                 }
  554.  
  555.                                 if (v_dot_n0 >= 0) {
  556.                                         // want to render from vertices 0,1,3
  557.                                         check_render_face(canvas, std::index_sequence<0, 1, 3>(), segp, sidenum, 0, vertnum_list, uside.tmap_num, uside.tmap_num2, uside.uvls, wid_flags);
  558.                                 }
  559.  
  560.                         } else
  561.                                 throw shared_side::illegal_type(segp, sside);
  562.                 }
  563.         }
  564.  
  565. }
  566.  
  567. #if DXX_USE_EDITOR
  568. static void render_object_search(grs_canvas &canvas, const d_level_unique_light_state &LevelUniqueLightState, const vmobjptridx_t obj)
  569. {
  570.         int changed=0;
  571.  
  572.         //note that we draw each pixel object twice, since we cannot control
  573.         //what color the object draws in, so we try color 0, then color 1,
  574.         //in case the object itself is rendering color 0
  575.  
  576.         {
  577.         const uint8_t color = 0;
  578.         //set our search pixel to color zero
  579. #if DXX_USE_OGL
  580.         ogl_end_frame();
  581.  
  582.         // For OpenGL we use gr_rect instead of gr_pixel,
  583.         // because in some implementations (like my Macbook Pro 5,1)
  584.         // point smoothing can't be turned off.
  585.         // Point smoothing would change the pixel to dark grey, but it MUST be black.
  586.         // Making a 3x3 rectangle wouldn't matter
  587.         // (but it only seems to draw a single pixel anyway)
  588.         gr_rect(canvas, _search_x - 1, _search_y - 1, _search_x + 1, _search_y + 1, color);
  589.  
  590.         ogl_start_frame(canvas);
  591. #else
  592.         gr_pixel(canvas.cv_bitmap, _search_x, _search_y, color);
  593. #endif
  594.         }
  595.         render_object(canvas, LevelUniqueLightState, obj);
  596.         if (gr_ugpixel(canvas.cv_bitmap,_search_x,_search_y) != 0)
  597.                 changed=1;
  598.  
  599.         {
  600.                 const uint8_t color = 1;
  601. #if DXX_USE_OGL
  602.         ogl_end_frame();
  603.         gr_rect(canvas, _search_x - 1, _search_y - 1, _search_x + 1, _search_y + 1, color);
  604.         ogl_start_frame(canvas);
  605. #else
  606.         gr_pixel(canvas.cv_bitmap, _search_x, _search_y, color);
  607. #endif
  608.         }
  609.         render_object(canvas, LevelUniqueLightState, obj);
  610.         if (gr_ugpixel(canvas.cv_bitmap,_search_x,_search_y) != 1)
  611.                 changed=1;
  612.  
  613.         if (changed) {
  614.                 if (obj->segnum != segment_none)
  615.                         Cursegp = imsegptridx(obj->segnum);
  616.                 found_seg = segment_none;
  617.                 found_obj = obj;
  618.         }
  619. }
  620. #endif
  621.  
  622. static void do_render_object(grs_canvas &canvas, const d_level_unique_light_state &LevelUniqueLightState, const vmobjptridx_t obj, window_rendered_data &window)
  623. {
  624. #if DXX_USE_EDITOR
  625.         int save_3d_outline=0;
  626.         #endif
  627.         int count = 0;
  628.  
  629.         #ifndef NDEBUG
  630.         if (object_rendered[obj]) {             //already rendered this...
  631.                 Int3();         //get Matt!!!
  632.                 return;
  633.         }
  634.  
  635.         object_rendered[obj] = true;
  636.         #endif
  637.  
  638. #if defined(DXX_BUILD_DESCENT_II)
  639.    if (Newdemo_state==ND_STATE_PLAYBACK)  
  640.          {
  641.           if ((DemoDoingLeft==6 || DemoDoingRight==6) && obj->type==OBJ_PLAYER)
  642.                 {
  643.                         // A nice fat hack: keeps the player ship from showing up in the
  644.                         // small extra view when guiding a missile in the big window
  645.                        
  646.                         return;
  647.                 }
  648.          }
  649. #endif
  650.  
  651.         //      Added by MK on 09/07/94 (at about 5:28 pm, CDT, on a beautiful, sunny late summer day!) so
  652.         //      that the guided missile system will know what objects to look at.
  653.         //      I didn't know we had guided missiles before the release of D1. --MK
  654.         if (obj->type == OBJ_ROBOT)
  655.                 window.rendered_robots.emplace_back(obj);
  656.  
  657.         if ((count++ > MAX_OBJECTS) || (obj->next == obj)) {
  658.                 Int3();                                 // infinite loop detected
  659.                 obj->next = object_none;                // won't this clean things up?
  660.                 return;                                 // get out of this infinite loop!
  661.         }
  662.  
  663.                 //g3_draw_object(obj->class_id,&obj->pos,&obj->orient,obj->size);
  664.  
  665.         //check for editor object
  666.  
  667. #if DXX_USE_EDITOR
  668.         if (EditorWindow && obj==Cur_object_index) {
  669.                 save_3d_outline = g3d_interp_outline;
  670.                 g3d_interp_outline=1;
  671.         }
  672.         #endif
  673.  
  674. #if DXX_USE_EDITOR
  675.         if (_search_mode)
  676.                 render_object_search(canvas, LevelUniqueLightState, obj);
  677.         else
  678.         #endif
  679.                 //NOTE LINK TO ABOVE
  680.                 render_object(canvas, LevelUniqueLightState, obj);
  681.  
  682.         for (auto n = obj->attached_obj; n != object_none;)
  683.         {
  684.                 const auto &&o = obj.absolute_sibling(n);
  685.                 Assert(o->type == OBJ_FIREBALL);
  686.                 Assert(o->control_type == CT_EXPLOSION);
  687.                 Assert(o->flags & OF_ATTACHED);
  688.                 n = o->ctype.expl_info.next_attach;
  689.  
  690.                 render_object(canvas, LevelUniqueLightState, o);
  691.         }
  692.  
  693.  
  694. #if DXX_USE_EDITOR
  695.         if (EditorWindow && obj==Cur_object_index)
  696.                 g3d_interp_outline = save_3d_outline;
  697.         #endif
  698.  
  699.  
  700. }
  701. }
  702.  
  703. //increment counter for checking if points rotated
  704. //This must be called at the start of the frame if rotate_list() will be used
  705. void render_start_frame()
  706. {
  707.         if (s_current_generation == std::numeric_limits<decltype(s_current_generation)>::max())
  708.         {
  709.                 Segment_points = {};
  710.                 s_current_generation = 0;
  711.         }
  712.         ++ s_current_generation;
  713. }
  714.  
  715. //Given a lit of point numbers, rotate any that haven't been rotated this frame
  716. g3s_codes rotate_list(fvcvertptr &vcvertptr, const std::size_t nv, const unsigned *const pointnumlist)
  717. {
  718.         g3s_codes cc;
  719.         const auto current_generation = s_current_generation;
  720.         const auto cheats_acid = cheats.acid;
  721.         const float f = likely(!cheats_acid)
  722.                 ? 0.0f /* unused */
  723.                 : 2.0f * (static_cast<float>(timer_query()) / F1_0);
  724.  
  725.         range_for (const auto pnum, unchecked_partial_range(pointnumlist, nv))
  726.         {
  727.                 auto &pnt = Segment_points[pnum];
  728.                 if (pnt.p3_last_generation != current_generation)
  729.                 {
  730.                         pnt.p3_last_generation = current_generation;
  731.                         auto &v = *vcvertptr(pnum);
  732.                         vertex tmpv;
  733.                         g3_rotate_point(pnt, likely(!cheats_acid) ? v : (
  734.                                 tmpv = v,
  735.                                 tmpv.x += fl2f(sinf(f + f2fl(tmpv.x))),
  736.                                 tmpv.y += fl2f(sinf(f * 1.5f + f2fl(tmpv.y))),
  737.                                 tmpv.z += fl2f(sinf(f * 2.5f + f2fl(tmpv.z))),
  738.                                 tmpv
  739.                         ));
  740.                 }
  741.                 cc.uand &= pnt.p3_codes;
  742.                 cc.uor  |= pnt.p3_codes;
  743.         }
  744.  
  745.         return cc;
  746.  
  747. }
  748.  
  749. //Given a lit of point numbers, project any that haven't been projected
  750. static void project_list(const std::array<unsigned, 8> &pointnumlist)
  751. {
  752.         range_for (const auto pnum, pointnumlist)
  753.         {
  754.                 auto &p = Segment_points[pnum];
  755.                 if (!(p.p3_flags & PF_PROJECTED))
  756.                         g3_project_point(p);
  757.         }
  758. }
  759.  
  760.  
  761. // -----------------------------------------------------------------------------------
  762. #if !DXX_USE_OGL
  763. namespace dsx {
  764. static void render_segment(fvcvertptr &vcvertptr, fvcwallptr &vcwallptr, const vms_vector &Viewer_eye, grs_canvas &canvas, const vcsegptridx_t seg)
  765. {
  766.         if (!rotate_list(vcvertptr, seg->verts).uand)
  767.         {               //all off screen?
  768.  
  769. #if defined(DXX_BUILD_DESCENT_II)
  770.                 if (Viewer->type != OBJ_ROBOT)
  771. #endif
  772.                 {
  773.                         LevelUniqueAutomapState.Automap_visited[seg] = 1;
  774.                 }
  775.  
  776.                 range_for (const uint_fast32_t sn, xrange(MAX_SIDES_PER_SEGMENT))
  777.                         render_side(vcvertptr, canvas, seg, sn, WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, seg, sn), Viewer_eye);
  778.         }
  779.  
  780.         //draw any objects that happen to be in this segment
  781.  
  782.         //sort objects!
  783.         //object_sort_segment_objects( seg );
  784. }
  785. }
  786. #endif
  787.  
  788. #if DXX_USE_EDITOR
  789. #ifndef NDEBUG
  790.  
  791. constexpr fix CROSS_WIDTH = i2f(8);
  792. constexpr fix CROSS_HEIGHT = i2f(8);
  793.  
  794. //draw outline for curside
  795. static void outline_seg_side(grs_canvas &canvas, const shared_segment &seg, const unsigned _side, const unsigned edge, const unsigned vert)
  796. {
  797.         auto &verts = seg.verts;
  798.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  799.         auto &Vertices = LevelSharedVertexState.get_vertices();
  800.         auto &vcvertptr = Vertices.vcptr;
  801.         if (!rotate_list(vcvertptr, verts).uand)
  802.         {               //all off screen?
  803.                 g3s_point *pnt;
  804.  
  805.                 //render curedge of curside of curseg in green
  806.  
  807.                 const uint8_t color = BM_XRGB(0, 63, 0);
  808.                 auto &sv = Side_to_verts[_side];
  809.                 g3_draw_line(canvas, Segment_points[verts[sv[edge]]], Segment_points[verts[sv[(edge + 1)%4]]], color);
  810.  
  811.                 //draw a little cross at the current vert
  812.  
  813.                 pnt = &Segment_points[verts[Side_to_verts[_side][vert]]];
  814.  
  815.                 g3_project_point(*pnt);         //make sure projected
  816.  
  817. //              gr_line(pnt->p3_sx-CROSS_WIDTH,pnt->p3_sy,pnt->p3_sx+CROSS_WIDTH,pnt->p3_sy);
  818. //              gr_line(pnt->p3_sx,pnt->p3_sy-CROSS_HEIGHT,pnt->p3_sx,pnt->p3_sy+CROSS_HEIGHT);
  819.  
  820.                 gr_line(canvas, pnt->p3_sx - CROSS_WIDTH, pnt->p3_sy, pnt->p3_sx, pnt->p3_sy - CROSS_HEIGHT, color);
  821.                 gr_line(canvas, pnt->p3_sx, pnt->p3_sy - CROSS_HEIGHT, pnt->p3_sx + CROSS_WIDTH, pnt->p3_sy, color);
  822.                 gr_line(canvas, pnt->p3_sx + CROSS_WIDTH, pnt->p3_sy, pnt->p3_sx, pnt->p3_sy + CROSS_HEIGHT, color);
  823.                 gr_line(canvas, pnt->p3_sx, pnt->p3_sy + CROSS_HEIGHT, pnt->p3_sx - CROSS_WIDTH, pnt->p3_sy, color);
  824.         }
  825. }
  826.  
  827. #endif
  828. #endif
  829.  
  830. static ubyte code_window_point(fix x,fix y,const rect &w)
  831. {
  832.         ubyte code=0;
  833.  
  834.         if (x <= w.left)  code |= 1;
  835.         if (x >= w.right) code |= 2;
  836.  
  837.         if (y <= w.top) code |= 4;
  838.         if (y >= w.bot) code |= 8;
  839.  
  840.         return code;
  841. }
  842.  
  843. //Given two sides of segment, tell the two verts which form the
  844. //edge between them
  845. constexpr std::array<
  846.         std::array<
  847.                 std::array<int_fast8_t, 2>,
  848.                 6>,
  849.         6> Two_sides_to_edge = {{
  850.         {{  {{edge_none,edge_none}},     {{3,7}},        {{edge_none,edge_none}},        {{2,6}},        {{6,7}},        {{2,3}}        }},
  851.         {{  {{3,7}},     {{edge_none,edge_none}},        {{0,4}},        {{edge_none,edge_none}},        {{4,7}},        {{0,3}}        }},
  852.         {{  {{edge_none,edge_none}},     {{0,4}},        {{edge_none,edge_none}},        {{1,5}},        {{4,5}},        {{0,1}}        }},
  853.         {{  {{2,6}},     {{edge_none,edge_none}},        {{1,5}},        {{edge_none,edge_none}},        {{5,6}},        {{1,2}}        }},
  854.         {{  {{6,7}},     {{4,7}},        {{4,5}},        {{5,6}},        {{edge_none,edge_none}},        {{edge_none,edge_none}}        }},
  855.         {{  {{2,3}},     {{0,3}},        {{0,1}},        {{1,2}},        {{edge_none,edge_none}},        {{edge_none,edge_none}}        }}
  856. }};
  857.  
  858. //given an edge specified by two verts, give the two sides on that edge
  859. constexpr std::array<
  860.         std::array<
  861.                 std::array<int_fast8_t, 2>,
  862.                 8>,
  863.         8> Edge_to_sides = {{
  864.         {{  {{side_none,side_none}},     {{2,5}},        {{side_none,side_none}},        {{1,5}},        {{1,2}},        {{side_none,side_none}},        {{side_none,side_none}},        {{side_none,side_none}}        }},
  865.         {{  {{2,5}},     {{side_none,side_none}},        {{3,5}},        {{side_none,side_none}},        {{side_none,side_none}},        {{2,3}},        {{side_none,side_none}},        {{side_none,side_none}}        }},
  866.         {{  {{side_none,side_none}},     {{3,5}},        {{side_none,side_none}},        {{0,5}},        {{side_none,side_none}},        {{side_none,side_none}},        {{0,3}},        {{side_none,side_none}}        }},
  867.         {{  {{1,5}},     {{side_none,side_none}},        {{0,5}},        {{side_none,side_none}},        {{side_none,side_none}},        {{side_none,side_none}},        {{side_none,side_none}},        {{0,1}}        }},
  868.         {{  {{1,2}},     {{side_none,side_none}},        {{side_none,side_none}},        {{side_none,side_none}},        {{side_none,side_none}},        {{2,4}},        {{side_none,side_none}},        {{1,4}}        }},
  869.         {{  {{side_none,side_none}},     {{2,3}},        {{side_none,side_none}},        {{side_none,side_none}},        {{2,4}},        {{side_none,side_none}},        {{3,4}},        {{side_none,side_none}}        }},
  870.         {{  {{side_none,side_none}},     {{side_none,side_none}},        {{0,3}},        {{side_none,side_none}},        {{side_none,side_none}},        {{3,4}},        {{side_none,side_none}},        {{0,4}}        }},
  871.         {{  {{side_none,side_none}},     {{side_none,side_none}},        {{side_none,side_none}},        {{0,1}},        {{1,4}},        {{side_none,side_none}},        {{0,4}},        {{side_none,side_none}}        }},
  872. }};
  873.  
  874. //@@//perform simple check on tables
  875. //@@check_check()
  876. //@@{
  877. //@@    int i,j;
  878. //@@
  879. //@@    for (i=0;i<8;i++)
  880. //@@            for (j=0;j<8;j++)
  881. //@@                    Assert(Edge_to_sides[i][j][0] == Edge_to_sides[j][i][0] &&
  882. //@@                                    Edge_to_sides[i][j][1] == Edge_to_sides[j][i][1]);
  883. //@@
  884. //@@    for (i=0;i<6;i++)
  885. //@@            for (j=0;j<6;j++)
  886. //@@                    Assert(Two_sides_to_edge[i][j][0] == Two_sides_to_edge[j][i][0] &&
  887. //@@                                    Two_sides_to_edge[i][j][1] == Two_sides_to_edge[j][i][1]);
  888. //@@
  889. //@@
  890. //@@}
  891.  
  892.  
  893. //given an edge, tell what side is on that edge
  894. __attribute_warn_unused_result
  895. static int find_seg_side(const shared_segment &seg, const std::array<unsigned, 2> &verts, const unsigned notside)
  896. {
  897.         if (notside >= MAX_SIDES_PER_SEGMENT)
  898.                 throw std::logic_error("invalid notside");
  899.  
  900.         const auto v0 = verts[0];
  901.         const auto v1 = verts[1];
  902.  
  903.         const auto b = begin(seg.verts);
  904.         const auto e = end(seg.verts);
  905.         auto iv0 = e;
  906.         auto iv1 = e;
  907.         for (auto i = b;;)
  908.         {
  909.                 if (iv0 == e && *i == v0)
  910.                 {
  911.                         iv0 = i;
  912.                         if (iv1 != e)
  913.                                 break;
  914.                 }
  915.                 if (iv1 == e && *i == v1)
  916.                 {
  917.                         iv1 = i;
  918.                         if (iv0 != e)
  919.                                 break;
  920.                 }
  921.                 if (++i == e)
  922.                         return side_none;
  923.         }
  924.  
  925.         const auto &eptr = Edge_to_sides[std::distance(b, iv0)][std::distance(b, iv1)];
  926.  
  927.         const auto side0 = eptr[0];
  928.         const auto side1 = eptr[1];
  929.  
  930.         Assert(side0 != side_none && side1 != side_none);
  931.  
  932.         if (side0 != notside) {
  933.                 Assert(side1==notside);
  934.                 return side0;
  935.         }
  936.         else {
  937.                 Assert(side0==notside);
  938.                 return side1;
  939.         }
  940.  
  941. }
  942.  
  943. __attribute_warn_unused_result
  944. static bool compare_child(fvcvertptr &vcvertptr, const vms_vector &Viewer_eye, const shared_segment &seg, const shared_segment &cseg, const sidenum_fast_t edgeside)
  945. {
  946.         const auto &cside = cseg.sides[edgeside];
  947.         const auto &sv = Side_to_verts[edgeside][cside.get_type() == side_type::tri_13 ? 1 : 0];
  948.         const auto &temp = vm_vec_sub(Viewer_eye, vcvertptr(seg.verts[sv]));
  949.         const auto &cnormal = cside.normals;
  950.         return vm_vec_dot(cnormal[0], temp) < 0 || vm_vec_dot(cnormal[1], temp) < 0;
  951. }
  952.  
  953. //see if the order matters for these two children.
  954. //returns 0 if order doesn't matter, 1 if c0 before c1, -1 if c1 before c0
  955. __attribute_warn_unused_result
  956. static bool compare_children(fvcvertptr &vcvertptr, const vms_vector &Viewer_eye, const vcsegptridx_t seg, const sidenum_fast_t s0, const sidenum_fast_t s1)
  957. {
  958.         Assert(s0 != side_none && s1 != side_none);
  959.  
  960.         if (s0 == s1)
  961.                 return false;
  962.         if (Side_opposite[s0] == s1)
  963.                 return false;
  964.         //find normals of adjoining sides
  965.         const std::array<unsigned, 2> edge_verts = {
  966.                 {seg->verts[Two_sides_to_edge[s0][s1][0]], seg->verts[Two_sides_to_edge[s0][s1][1]]}
  967.         };
  968.         if (edge_verts[0] == -1 || edge_verts[1] == -1)
  969.                 throw std::logic_error("invalid edge vert");
  970.         const auto &&seg0 = seg.absolute_sibling(seg->children[s0]);
  971.         const auto edgeside0 = find_seg_side(seg0, edge_verts, find_connect_side(seg, seg0));
  972.         if (edgeside0 == side_none)
  973.                 return false;
  974.         const auto r0 = compare_child(vcvertptr, Viewer_eye, seg, seg0, edgeside0);
  975.         if (!r0)
  976.                 return r0;
  977.         const auto &&seg1 = seg.absolute_sibling(seg->children[s1]);
  978.         const auto edgeside1 = find_seg_side(seg1, edge_verts, find_connect_side(seg, seg1));
  979.         if (edgeside1 == side_none)
  980.                 return false;
  981.         return !compare_child(vcvertptr, Viewer_eye, seg, seg1, edgeside1);
  982. }
  983.  
  984. //short the children of segment to render in the correct order
  985. //returns non-zero if swaps were made
  986. using sort_child_array_t = std::array<sidenum_fast_t, MAX_SIDES_PER_SEGMENT>;
  987. static void sort_seg_children(fvcvertptr &vcvertptr, const vms_vector &Viewer_eye, const vcsegptridx_t seg, const partial_range_t<sort_child_array_t::iterator> &r)
  988. {
  989.         //for each child,  compare with other children and see if order matters
  990.         //if order matters, fix if wrong
  991.         auto predicate = [&vcvertptr, &Viewer_eye, seg](const sidenum_fast_t a, const sidenum_fast_t b)
  992.         {
  993.                 return compare_children(vcvertptr, Viewer_eye, seg, a, b);
  994.         };
  995.                 std::sort(r.begin(), r.end(), predicate);
  996. }
  997.  
  998. static void add_obj_to_seglist(render_state_t &rstate, objnum_t objnum, segnum_t segnum)
  999. {
  1000.         auto p = rstate.render_seg_map.emplace(segnum, render_state_t::per_segment_state_t{});
  1001.         auto &o = p.first->second.objects;
  1002.         if (p.second)
  1003.                 o.reserve(16);
  1004.         o.emplace_back(render_state_t::per_segment_state_t::distant_object{objnum});
  1005. }
  1006.  
  1007. namespace {
  1008.  
  1009. using visited_twobit_array_t = visited_segment_mask_t<2>;
  1010.  
  1011. class render_compare_context_t
  1012. {
  1013.         typedef render_state_t::per_segment_state_t::distant_object distant_object;
  1014.         struct element
  1015.         {
  1016.                 fix64 dist_squared;
  1017. #if defined(DXX_BUILD_DESCENT_II)
  1018.                 const object *objp;
  1019. #endif
  1020.         };
  1021.         using array_t = std::array<element, MAX_OBJECTS>;
  1022.         array_t m_array;
  1023. public:
  1024.         array_t::reference operator[](std::size_t i) { return m_array[i]; }
  1025.         array_t::const_reference operator[](std::size_t i) const { return m_array[i]; }
  1026.         render_compare_context_t(fvcobjptr &vcobjptr, const vms_vector &Viewer_eye, const render_state_t::per_segment_state_t &segstate)
  1027.         {
  1028.                 range_for (const auto t, segstate.objects)
  1029.                 {
  1030.                         const auto objnum = t.objnum;
  1031.                         auto &objp = *vcobjptr(objnum);
  1032.                         auto &e = (*this)[objnum];
  1033. #if defined(DXX_BUILD_DESCENT_II)
  1034.                         e.objp = &objp;
  1035. #endif
  1036.                         e.dist_squared = vm_vec_dist2(objp.pos, Viewer_eye);
  1037.                 }
  1038.         }
  1039.         bool operator()(const distant_object &a, const distant_object &b) const;
  1040. };
  1041.  
  1042. //compare function for object sort.
  1043. bool render_compare_context_t::operator()(const distant_object &a, const distant_object &b) const
  1044. {
  1045.         const auto delta_dist_squared = (*this)[a.objnum].dist_squared - (*this)[b.objnum].dist_squared;
  1046.  
  1047. #if defined(DXX_BUILD_DESCENT_II)
  1048.         const auto obj_a = (*this)[a.objnum].objp;
  1049.         const auto obj_b = (*this)[b.objnum].objp;
  1050.  
  1051.         auto abs_delta_dist_squared = std::abs(delta_dist_squared);
  1052.         fix combined_size = obj_a->size + obj_b->size;
  1053.         /*
  1054.          * First check without squaring.  If true, the square can be
  1055.          * skipped.
  1056.          */
  1057.         if (abs_delta_dist_squared < combined_size || abs_delta_dist_squared < (static_cast<fix64>(combined_size) * combined_size))
  1058.         {               //same position
  1059.  
  1060.                 //these two objects are in the same position.  see if one is a fireball
  1061.                 //or laser or something that should plot on top.  Don't do this for
  1062.                 //the afterburner blobs, though.
  1063.  
  1064.                 if (obj_a->type == OBJ_WEAPON || (obj_a->type == OBJ_FIREBALL && get_fireball_id(*obj_a) != VCLIP_AFTERBURNER_BLOB))
  1065.                 {
  1066.                         if (!(obj_b->type == OBJ_WEAPON || obj_b->type == OBJ_FIREBALL))
  1067.                                 return true;    //a is weapon, b is not, so say a is closer
  1068.                         //both are weapons
  1069.                 }
  1070.                 else
  1071.                 {
  1072.                         if (obj_b->type == OBJ_WEAPON || (obj_b->type == OBJ_FIREBALL && get_fireball_id(*obj_b) != VCLIP_AFTERBURNER_BLOB))
  1073.                                 return false;   //b is weapon, a is not, so say a is farther
  1074.                 }
  1075.  
  1076.                 //no special case, fall through to normal return
  1077.         }
  1078. #endif
  1079.         return delta_dist_squared > 0;  //return distance
  1080. }
  1081.  
  1082. }
  1083.  
  1084. static void sort_segment_object_list(fvcobjptr &vcobjptr, const vms_vector &Viewer_eye, render_state_t::per_segment_state_t &segstate)
  1085. {
  1086.         render_compare_context_t context(vcobjptr, Viewer_eye, segstate);
  1087.         auto &v = segstate.objects;
  1088.         std::sort(v.begin(), v.end(), std::cref(context));
  1089. }
  1090.  
  1091. namespace dsx {
  1092.  
  1093. static void build_object_lists(object_array &Objects, fvcsegptr &vcsegptr, const vms_vector &Viewer_eye, render_state_t &rstate)
  1094. {
  1095.         const auto viewer = Viewer;
  1096.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  1097.         auto &Vertices = LevelSharedVertexState.get_vertices();
  1098.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  1099.         auto &vcvertptr = Vertices.vcptr;
  1100.         auto &vcwallptr = Walls.vcptr;
  1101.         const auto N_render_segs = rstate.N_render_segs;
  1102.         range_for (const unsigned nn, xrange(N_render_segs))
  1103.         {
  1104.                 const auto segnum = rstate.Render_list[nn];
  1105.                 if (segnum != segment_none) {
  1106.                         range_for (const auto obj, objects_in(vcsegptr(segnum), Objects.vcptridx, vcsegptr))
  1107.                         {
  1108.                                 int list_pos;
  1109.                                 if (obj->type == OBJ_NONE)
  1110.                                 {
  1111.                                         assert(obj->type != OBJ_NONE);
  1112.                                         continue;
  1113.                                 }
  1114.                                 if (unlikely(obj == viewer) && likely(obj->attached_obj == object_none))
  1115.                                         continue;
  1116.                                 if (obj->flags & OF_ATTACHED)
  1117.                                         continue;               //ignore this object
  1118.  
  1119.                                 auto new_segnum = segnum;
  1120.                                 list_pos = nn;
  1121.  
  1122. #if defined(DXX_BUILD_DESCENT_I)
  1123.                                 int did_migrate;
  1124.                                 if (obj->type != OBJ_CNTRLCEN)          //don't migrate controlcen
  1125. #elif defined(DXX_BUILD_DESCENT_II)
  1126.                                 const int did_migrate = 0;
  1127.                                 if (obj->type != OBJ_CNTRLCEN && !(obj->type==OBJ_ROBOT && get_robot_id(obj)==65))              //don't migrate controlcen
  1128. #endif
  1129.                                 do {
  1130. #if defined(DXX_BUILD_DESCENT_I)
  1131.                                         did_migrate = 0;
  1132. #endif
  1133.                                         const uint_fast32_t sidemask = get_seg_masks(vcvertptr, obj->pos, vcsegptr(new_segnum), obj->size).sidemask;
  1134.        
  1135.                                         if (sidemask) {
  1136.                                                 int sn,sf;
  1137.  
  1138.                                                 for (sn=0,sf=1;sn<6;sn++,sf<<=1)
  1139.                                                         if (sidemask & sf)
  1140.                                                         {
  1141. #if defined(DXX_BUILD_DESCENT_I)
  1142.                                                                 const auto &&seg = vcsegptr(obj->segnum);
  1143. #elif defined(DXX_BUILD_DESCENT_II)
  1144.                                                                 const auto &&seg = vcsegptr(new_segnum);
  1145. #endif
  1146.                
  1147.                                                                 if (WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, seg, sn) & WID_FLY_FLAG)
  1148.                                                                 {               //can explosion migrate through
  1149.                                                                         int child = seg->children[sn];
  1150.                                                                         int checknp;
  1151.                
  1152.                                                                         for (checknp=list_pos;checknp--;)
  1153.                                                                                 if (rstate.Render_list[checknp] == child) {
  1154.                                                                                         new_segnum = child;
  1155.                                                                                         list_pos = checknp;
  1156. #if defined(DXX_BUILD_DESCENT_I)
  1157.                                                                                         did_migrate = 1;
  1158. #endif
  1159.                                                                                 }
  1160.                                                                 }
  1161.                                                                 if (sidemask <= sf)
  1162.                                                                         break;
  1163.                                                         }
  1164.                                         }
  1165.        
  1166.                                 } while (did_migrate);
  1167.                                 add_obj_to_seglist(rstate, obj, new_segnum);
  1168.                         }
  1169.                 }
  1170.         }
  1171.  
  1172.         //now that there's a list for each segment, sort the items in those lists
  1173.         range_for (const auto segnum, partial_const_range(rstate.Render_list, rstate.N_render_segs))
  1174.         {
  1175.                 if (segnum != segment_none) {
  1176.                         sort_segment_object_list(Objects.vcptr, Viewer_eye, rstate.render_seg_map[segnum]);
  1177.                 }
  1178.         }
  1179. }
  1180. }
  1181.  
  1182. int Rear_view=0;
  1183.  
  1184. namespace dsx {
  1185. //renders onto current canvas
  1186. void render_frame(grs_canvas &canvas, fix eye_offset, window_rendered_data &window)
  1187. {
  1188.         auto &Objects = LevelUniqueObjectState.Objects;
  1189.         auto &vcobjptridx = Objects.vcptridx;
  1190.         if (Endlevel_sequence) {
  1191.                 render_endlevel_frame(canvas, eye_offset);
  1192.                 return;
  1193.         }
  1194.  
  1195.         if ( Newdemo_state == ND_STATE_RECORDING && eye_offset >= 0 )   {
  1196.      
  1197.       if (RenderingType==0)
  1198.                 newdemo_record_start_frame(FrameTime );
  1199.       if (RenderingType!=255)
  1200.                 newdemo_record_viewer_object(vcobjptridx(Viewer));
  1201.         }
  1202.  
  1203.    //Here:
  1204.  
  1205.         start_lighting_frame(*Viewer);          //this is for ugly light-smoothing hack
  1206.  
  1207.         g3_start_frame(canvas);
  1208.  
  1209.         auto Viewer_eye = Viewer->pos;
  1210.  
  1211. //      if (Viewer->type == OBJ_PLAYER && (PlayerCfg.CockpitMode[1]!=CM_REAR_VIEW))
  1212. //              vm_vec_scale_add2(&Viewer_eye,&Viewer->orient.fvec,(Viewer->size*3)/4);
  1213.  
  1214.         if (eye_offset) {
  1215.                 vm_vec_scale_add2(Viewer_eye,Viewer->orient.rvec,eye_offset);
  1216.         }
  1217.  
  1218. #if DXX_USE_EDITOR
  1219.         if (EditorWindow)
  1220.                 Viewer_eye = Viewer->pos;
  1221.         #endif
  1222.  
  1223.         const auto &&viewer_segp = Segments.vmptridx(Viewer->segnum);
  1224.         auto start_seg_num = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, Viewer_eye, viewer_segp);
  1225.  
  1226.         if (start_seg_num==segment_none)
  1227.                 start_seg_num = viewer_segp;
  1228.  
  1229.         g3_set_view_matrix(Viewer_eye,
  1230.                 (Rear_view && Viewer == ConsoleObject)
  1231.                 ? vm_matrix_x_matrix(Viewer->orient, vm_angles_2_matrix(vms_angvec{0, 0, INT16_MAX}))
  1232.                 : Viewer->orient, Render_zoom);
  1233.  
  1234.         if (Clear_window == 1) {
  1235.                 if (Clear_window_color == -1)
  1236.                         Clear_window_color = BM_XRGB(0, 0, 0);  //BM_XRGB(31, 15, 7);
  1237.                 gr_clear_canvas(canvas, Clear_window_color);
  1238.         }
  1239.  
  1240.         render_mine(canvas, Viewer_eye, start_seg_num, eye_offset, window);
  1241.  
  1242.         g3_end_frame();
  1243.  
  1244.    //RenderingType=0;
  1245.  
  1246.         // -- Moved from here by MK, 05/17/95, wrong if multiple renders/frame! FrameCount++;           //we have rendered a frame
  1247. }
  1248.  
  1249. #if defined(DXX_BUILD_DESCENT_II)
  1250. void update_rendered_data(window_rendered_data &window, const object &viewer, int rear_view_flag)
  1251. {
  1252.         window.time = timer_query();
  1253.         window.viewer = &viewer;
  1254.         window.rear_view = rear_view_flag;
  1255. }
  1256. #endif
  1257.  
  1258. //build a list of segments to be rendered
  1259. //fills in Render_list & N_render_segs
  1260. static void build_segment_list(render_state_t &rstate, const vms_vector &Viewer_eye, visited_twobit_array_t &visited, unsigned &first_terminal_seg, const vcsegidx_t start_seg_num)
  1261. {
  1262.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  1263.         auto &Vertices = LevelSharedVertexState.get_vertices();
  1264.         int     lcnt,scnt,ecnt;
  1265.         int     l;
  1266.  
  1267.         rstate.render_pos.fill(-1);
  1268.  
  1269.         lcnt = scnt = 0;
  1270.  
  1271.         rstate.Render_list[lcnt] = start_seg_num;
  1272.         visited[start_seg_num]=1;
  1273.         lcnt++;
  1274.         ecnt = lcnt;
  1275.         rstate.render_pos[start_seg_num] = 0;
  1276.         {
  1277.                 auto &rsm_start_seg = rstate.render_seg_map[start_seg_num];
  1278.                 auto &rw = rsm_start_seg.render_window;
  1279.                 rw.left = rw.top = 0;
  1280.                 rw.right = grd_curcanv->cv_bitmap.bm_w-1;
  1281.                 rw.bot = grd_curcanv->cv_bitmap.bm_h-1;
  1282.         }
  1283.  
  1284.         //breadth-first renderer
  1285.  
  1286.         //build list
  1287.  
  1288.         auto &vcvertptr = Vertices.vcptr;
  1289.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  1290.         auto &vcwallptr = Walls.vcptr;
  1291.         for (l=0;l<Render_depth;l++) {
  1292.                 for (scnt=0;scnt < ecnt;scnt++) {
  1293.                         auto segnum = rstate.Render_list[scnt];
  1294.                         if (unlikely(segnum == segment_none))
  1295.                         {
  1296.                                 assert(segnum != segment_none);
  1297.                                 continue;
  1298.                         }
  1299.  
  1300.                         auto &srsm = rstate.render_seg_map[segnum];
  1301.                         auto &processed = srsm.processed;
  1302.                         if (processed)
  1303.                                 continue;
  1304.                         const auto &check_w = srsm.render_window;
  1305.  
  1306.                         processed = true;
  1307.  
  1308.                         const auto &&seg = vcsegptridx(segnum);
  1309.                         const auto uor = rotate_list(vcvertptr, seg->verts).uor & CC_BEHIND;
  1310.  
  1311.                         //look at all sides of this segment.
  1312.                         //tricky code to look at sides in correct order follows
  1313.  
  1314.                         sort_child_array_t child_list;          //list of ordered sides to process
  1315.                         uint_fast32_t n_children = 0;                                                   //how many sides in child_list
  1316.                         for (uint_fast32_t c = 0;c < MAX_SIDES_PER_SEGMENT;c++) {               //build list of sides
  1317.                                 const auto wid = WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, seg, c);
  1318.                                 if (wid & WID_RENDPAST_FLAG)
  1319.                                 {
  1320.                                         if (auto codes_and = uor)
  1321.                                         {
  1322.                                                 range_for (const auto i, Side_to_verts[c])
  1323.                                                         codes_and &= Segment_points[seg->verts[i]].p3_codes;
  1324.                                                 if (codes_and)
  1325.                                                         continue;
  1326.                                         }
  1327.                                         child_list[n_children++] = c;
  1328.                                 }
  1329.                         }
  1330.                         if (!n_children)
  1331.                                 continue;
  1332.  
  1333.                         //now order the sides in some magical way
  1334.                         const auto &&child_range = partial_range(child_list, n_children);
  1335.                         sort_seg_children(vcvertptr, Viewer_eye, seg, child_range);
  1336.                         project_list(seg->verts);
  1337.                         range_for (const auto siden, child_range)
  1338.                         {
  1339.                                 const auto ch = seg->children[siden];
  1340.                                 {
  1341.                                         {
  1342.                                                 short min_x=32767,max_x=-32767,min_y=32767,max_y=-32767;
  1343.                                                 int no_proj_flag=0;     //a point wasn't projected
  1344.                                                 uint8_t codes_and_3d = 0xff, codes_and_2d = codes_and_3d;
  1345.                                                 range_for (const auto i, Side_to_verts[siden])
  1346.                                                 {
  1347.                                                         g3s_point *pnt = &Segment_points[seg->verts[i]];
  1348.  
  1349.                                                         if (! (pnt->p3_flags&PF_PROJECTED)) {no_proj_flag=1; break;}
  1350.  
  1351.                                                         const int16_t _x = f2i(pnt->p3_sx), _y = f2i(pnt->p3_sy);
  1352.  
  1353.                                                         if (_x < min_x) min_x = _x;
  1354.                                                         if (_x > max_x) max_x = _x;
  1355.  
  1356.                                                         if (_y < min_y) min_y = _y;
  1357.                                                         if (_y > max_y) max_y = _y;
  1358.                                                         codes_and_3d &= pnt->p3_codes;
  1359.                                                         codes_and_2d &= code_window_point(_x,_y,check_w);
  1360.                                                 }
  1361.                                                 if (no_proj_flag || (!codes_and_3d && !codes_and_2d)) { //maybe add this segment
  1362.                                                         auto rp = rstate.render_pos[ch];
  1363.                                                         rect nw;
  1364.  
  1365.                                                         if (no_proj_flag)
  1366.                                                                 nw = check_w;
  1367.                                                         else {
  1368.                                                                 nw.left  = max(check_w.left,min_x);
  1369.                                                                 nw.right = min(check_w.right,max_x);
  1370.                                                                 nw.top   = max(check_w.top,min_y);
  1371.                                                                 nw.bot   = min(check_w.bot,max_y);
  1372.                                                         }
  1373.  
  1374.                                                         //see if this seg already visited, and if so, does current window
  1375.                                                         //expand the old window?
  1376.                                                         if (rp != -1) {
  1377.                                                                 auto &old_w = rstate.render_seg_map[rstate.Render_list[rp]].render_window;
  1378.                                                                 if (nw.left < old_w.left ||
  1379.                                                                                  nw.top < old_w.top ||
  1380.                                                                                  nw.right > old_w.right ||
  1381.                                                                                  nw.bot > old_w.bot) {
  1382.  
  1383.                                                                         nw.left  = min(nw.left, old_w.left);
  1384.                                                                         nw.right = max(nw.right, old_w.right);
  1385.                                                                         nw.top   = min(nw.top, old_w.top);
  1386.                                                                         nw.bot   = max(nw.bot, old_w.bot);
  1387.  
  1388.                                                                         {
  1389.                                                                                 //no_render_flag[lcnt] = 1;
  1390.                                                                                 rstate.render_seg_map[ch].processed = false;            //force reprocess
  1391.                                                                                 rstate.Render_list[lcnt] = segment_none;
  1392.                                                                                 old_w = nw;             //get updated window
  1393.                                                                                 goto no_add;
  1394.                                                                         }
  1395.                                                                 }
  1396.                                                                 else goto no_add;
  1397.                                                         }
  1398.                                                         rstate.render_pos[ch] = lcnt;
  1399.                                                         rstate.Render_list[lcnt] = ch;
  1400.                                                         {
  1401.                                                                 auto &chrsm = rstate.render_seg_map[ch];
  1402.                                                                 chrsm.Seg_depth = l;
  1403.                                                                 chrsm.render_window = nw;
  1404.                                                         }
  1405.                                                         lcnt++;
  1406.                                                         if (lcnt >= MAX_RENDER_SEGS) {goto done_list;}
  1407.                                                         visited[ch] = 1;
  1408. no_add:
  1409.         ;
  1410.  
  1411.                                                 }
  1412.                                         }
  1413.                                 }
  1414.                         }
  1415.                 }
  1416.  
  1417.                 scnt = ecnt;
  1418.                 ecnt = lcnt;
  1419.  
  1420.         }
  1421. done_list:
  1422.  
  1423.         first_terminal_seg = scnt;
  1424.         rstate.N_render_segs = lcnt;
  1425.  
  1426. }
  1427.  
  1428. //renders onto current canvas
  1429. void render_mine(grs_canvas &canvas, const vms_vector &Viewer_eye, const vcsegidx_t start_seg_num, const fix eye_offset, window_rendered_data &window)
  1430. {
  1431.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  1432.         auto &Objects = LevelUniqueObjectState.Objects;
  1433.         auto &Vertices = LevelSharedVertexState.get_vertices();
  1434.         auto &vmobjptridx = Objects.vmptridx;
  1435. #if DXX_USE_OGL
  1436.         auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
  1437. #else
  1438.         auto &vcvertptr = Vertices.vcptr;
  1439.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  1440.         auto &vcwallptr = Walls.vcptr;
  1441. #endif
  1442.         using std::advance;
  1443.         render_state_t rstate;
  1444.         #ifndef NDEBUG
  1445.         object_rendered = {};
  1446.         #endif
  1447.  
  1448.         //set up for rendering
  1449.  
  1450.         render_start_frame();
  1451.  
  1452.         visited_twobit_array_t visited;
  1453.  
  1454.         unsigned first_terminal_seg;
  1455. #if DXX_USE_EDITOR
  1456. #if defined(DXX_BUILD_DESCENT_I)
  1457.         if (_search_mode || eye_offset>0)
  1458. #elif defined(DXX_BUILD_DESCENT_II)
  1459.         if (_search_mode)
  1460. #endif
  1461.         {
  1462.                 first_terminal_seg = 0;
  1463.         }
  1464.         //else
  1465.         #endif
  1466.                 //NOTE LINK TO ABOVE!!  -Link killed by kreatordxx to get editor selection working again
  1467.                 build_segment_list(rstate, Viewer_eye, visited, first_terminal_seg, start_seg_num);             //fills in Render_list & N_render_segs
  1468.  
  1469.         const auto &&render_range = partial_const_range(rstate.Render_list, rstate.N_render_segs);
  1470.         const auto &&reversed_render_range = render_range.reversed();
  1471.         //render away
  1472.  
  1473.         //if (!(_search_mode))
  1474.                 build_object_lists(Objects, vcsegptr, Viewer_eye, rstate);
  1475.  
  1476.         if (eye_offset<=0) // Do for left eye or zero.
  1477.                 set_dynamic_light(rstate);
  1478.  
  1479.         if (reversed_render_range.empty())
  1480.                 /* Impossible, but later code has undefined behavior if this
  1481.                  * happens
  1482.                  */
  1483.                 return;
  1484.  
  1485.         if (!_search_mode && Clear_window == 2) {
  1486.                 if (first_terminal_seg < rstate.N_render_segs) {
  1487.                         if (Clear_window_color == -1)
  1488.                                 Clear_window_color = BM_XRGB(0, 0, 0);  //BM_XRGB(31, 15, 7);
  1489.        
  1490.                         const uint8_t color = Clear_window_color;
  1491.        
  1492.                         range_for (const auto segnum, partial_const_range(rstate.Render_list, first_terminal_seg, rstate.N_render_segs))
  1493.                         {
  1494.                                 if (segnum != segment_none) {
  1495.                                         const auto &rw = rstate.render_seg_map[segnum].render_window;
  1496.                                         #ifndef NDEBUG
  1497.                                         if (rw.left == -1 || rw.top == -1 || rw.right == -1 || rw.bot == -1)
  1498.                                                 Int3();
  1499.                                         else
  1500.                                         #endif
  1501.                                                 //NOTE LINK TO ABOVE!
  1502.                                                 gr_rect(canvas, rw.left, rw.top, rw.right, rw.bot, color);
  1503.                                 }
  1504.                         }
  1505.                 }
  1506.         }
  1507. #if !DXX_USE_OGL
  1508.         range_for (const auto segnum, reversed_render_range)
  1509.         {
  1510.                 // Interpolation_method = 0;
  1511.                 auto &srsm = rstate.render_seg_map[segnum];
  1512.  
  1513.                 //if (!no_render_flag[nn])
  1514.                 if (segnum!=segment_none && (_search_mode || visited[segnum]!=3)) {
  1515.                         //set global render window vars
  1516.  
  1517.                         Current_seg_depth = srsm.Seg_depth;
  1518.                         {
  1519.                                 const auto &rw = srsm.render_window;
  1520.                                 Window_clip_left  = rw.left;
  1521.                                 Window_clip_top   = rw.top;
  1522.                                 Window_clip_right = rw.right;
  1523.                                 Window_clip_bot   = rw.bot;
  1524.                         }
  1525.  
  1526.                         render_segment(vcvertptr, vcwallptr, Viewer_eye, *grd_curcanv, vcsegptridx(segnum));
  1527.                         visited[segnum]=3;
  1528.                         if (srsm.objects.empty())
  1529.                                 continue;
  1530.  
  1531.                         {               //reset for objects
  1532.                                 Window_clip_left  = Window_clip_top = 0;
  1533.                                 Window_clip_right = canvas.cv_bitmap.bm_w-1;
  1534.                                 Window_clip_bot   = canvas.cv_bitmap.bm_h-1;
  1535.                         }
  1536.  
  1537.                         {
  1538.                                 //int n_expl_objs=0,expl_objs[5],i;
  1539.                                 const auto save_linear_depth = std::exchange(Max_linear_depth, Max_linear_depth_objects);
  1540.                                 range_for (auto &v, srsm.objects)
  1541.                                 {
  1542.                                         do_render_object(canvas, LevelUniqueLightState, vmobjptridx(v.objnum), window); // note link to above else
  1543.                                 }
  1544.                                 Max_linear_depth = save_linear_depth;
  1545.                         }
  1546.  
  1547.                 }
  1548.         }
  1549. #else
  1550.         // Two pass rendering. Since sprites and some level geometry can have transparency (blending), we need some fancy sorting.
  1551.         // GL_DEPTH_TEST helps to sort everything in view but we should make sure translucent sprites are rendered after geometry to prevent them to turn walls invisible (if rendered BEFORE geometry but still in FRONT of it).
  1552.         // If walls use blending, they should be rendered along with objects (in same pass) to prevent some ugly clipping.
  1553.  
  1554.         auto &vcvertptr = Vertices.vcptr;
  1555.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  1556.         auto &vcwallptr = Walls.vcptr;
  1557.         // First Pass: render opaque level geometry and level geometry with alpha pixels (high Alpha-Test func)
  1558.         range_for (const auto segnum, reversed_render_range)
  1559.         {
  1560.                 auto &srsm = rstate.render_seg_map[segnum];
  1561.  
  1562.                 if (segnum!=segment_none && (_search_mode || visited[segnum]!=3)) {
  1563.                         //set global render window vars
  1564.  
  1565.                         {
  1566.                                 const auto &rw = srsm.render_window;
  1567.                                 Window_clip_left  = rw.left;
  1568.                                 Window_clip_top   = rw.top;
  1569.                                 Window_clip_right = rw.right;
  1570.                                 Window_clip_bot   = rw.bot;
  1571.                         }
  1572.  
  1573.                         // render segment
  1574.                         {
  1575.                                 const auto &&seg = vcsegptridx(segnum);
  1576.                                 Assert(segnum!=segment_none && segnum<=Highest_segment_index);
  1577.                                 if (!rotate_list(vcvertptr, seg->verts).uand)
  1578.                                 {               //all off screen?
  1579.  
  1580.                                         if (Viewer->type!=OBJ_ROBOT)
  1581.                                                 LevelUniqueAutomapState.Automap_visited[segnum] = 1;
  1582.  
  1583.                                         range_for (const uint_fast32_t sn, xrange(MAX_SIDES_PER_SEGMENT))
  1584.                                         {
  1585.                                                 const auto wid = WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, seg, sn);
  1586.                                                 if (wid == WID_TRANSPARENT_WALL || wid == WID_TRANSILLUSORY_WALL
  1587. #if defined(DXX_BUILD_DESCENT_II)
  1588.                                                         || (wid & WID_CLOAKED_FLAG)
  1589. #endif
  1590.                                                         )
  1591.                                                 {
  1592.                                                         if (PlayerCfg.AlphaBlendEClips && is_alphablend_eclip(TmapInfo[seg->unique_segment::sides[sn].tmap_num].eclip_num)) // Do NOT render geometry with blending textures. Since we've not rendered any objects, yet, they would disappear behind them.
  1593.                                                                 continue;
  1594.                                                         glAlphaFunc(GL_GEQUAL,0.8); // prevent ugly outlines if an object (which is rendered later) is shown behind a grate, door, etc. if texture filtering is enabled. These sides are rendered later again with normal AlphaFunc
  1595.                                                         render_side(vcvertptr, canvas, seg, sn, wid, Viewer_eye);
  1596.                                                         glAlphaFunc(GL_GEQUAL,0.02);
  1597.                                                 }
  1598.                                                 else
  1599.                                                         render_side(vcvertptr, canvas, seg, sn, wid, Viewer_eye);
  1600.                                         }
  1601.                                 }
  1602.                         }
  1603.                 }
  1604.         }
  1605.  
  1606.         // Second pass: Render objects and level geometry with alpha pixels (normal Alpha-Test func) and eclips with blending
  1607.         range_for (const auto segnum, reversed_render_range)
  1608.         {
  1609.                 auto &srsm = rstate.render_seg_map[segnum];
  1610.  
  1611.                 if (segnum!=segment_none && (_search_mode || visited[segnum]!=3)) {
  1612.                         //set global render window vars
  1613.  
  1614.                         {
  1615.                                 const auto &rw = srsm.render_window;
  1616.                                 Window_clip_left  = rw.left;
  1617.                                 Window_clip_top   = rw.top;
  1618.                                 Window_clip_right = rw.right;
  1619.                                 Window_clip_bot   = rw.bot;
  1620.                         }
  1621.  
  1622.                         // render segment
  1623.                         {
  1624.                                 const auto &&seg = vcsegptridx(segnum);
  1625.                                 Assert(segnum!=segment_none && segnum<=Highest_segment_index);
  1626.                                 if (!rotate_list(vcvertptr, seg->verts).uand)
  1627.                                 {               //all off screen?
  1628.  
  1629.                                         if (Viewer->type!=OBJ_ROBOT)
  1630.                                                 LevelUniqueAutomapState.Automap_visited[segnum] = 1;
  1631.  
  1632.                                         range_for (const uint_fast32_t sn, xrange(MAX_SIDES_PER_SEGMENT))
  1633.                                         {
  1634.                                                 const auto wid = WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, seg, sn);
  1635.                                                 if (wid == WID_TRANSPARENT_WALL || wid == WID_TRANSILLUSORY_WALL
  1636. #if defined(DXX_BUILD_DESCENT_II)
  1637.                                                         || (wid & WID_CLOAKED_FLAG)
  1638. #endif
  1639.                                                         )
  1640.                                                 {
  1641.                                                         render_side(vcvertptr, canvas, seg, sn, wid, Viewer_eye);
  1642.                                                 }
  1643.                                         }
  1644.                                 }
  1645.                         }
  1646.                         visited[segnum]=3;
  1647.                         if (srsm.objects.empty())
  1648.                                 continue;
  1649.                         {               //reset for objects
  1650.                                 Window_clip_left  = Window_clip_top = 0;
  1651.                                 Window_clip_right = canvas.cv_bitmap.bm_w-1;
  1652.                                 Window_clip_bot   = canvas.cv_bitmap.bm_h-1;
  1653.                         }
  1654.  
  1655.                         {
  1656.                                 range_for (auto &v, srsm.objects)
  1657.                                 {
  1658.                                         do_render_object(canvas, LevelUniqueLightState, vmobjptridx(v.objnum), window); // note link to above else
  1659.                                 }
  1660.                         }
  1661.                 }
  1662.         }
  1663. #endif
  1664.  
  1665.         // -- commented out by mk on 09/14/94...did i do a good thing??  object_render_targets();
  1666.  
  1667. #if DXX_USE_EDITOR
  1668.         #ifndef NDEBUG
  1669.         //draw curedge stuff
  1670.         if (Outline_mode)
  1671.                 outline_seg_side(canvas, Cursegp, Curside, Curedge, Curvert);
  1672.         #endif
  1673. #endif
  1674. }
  1675.  
  1676. //-------------- Renders a hostage --------------------------------------------
  1677. void draw_hostage(const d_vclip_array &Vclip, grs_canvas &canvas, const d_level_unique_light_state &LevelUniqueLightState, const vmobjptridx_t obj)
  1678. {
  1679.         auto &vci = obj->rtype.vclip_info;
  1680.         draw_object_tmap_rod(canvas, &LevelUniqueLightState, obj, Vclip[vci.vclip_num].frames[vci.framenum]);
  1681. }
  1682.  
  1683. }
  1684. #if DXX_USE_EDITOR
  1685. //finds what segment is at a given x&y -  seg,side,face are filled in
  1686. //works on last frame rendered. returns true if found
  1687. //if seg<0, then an object was found, and the object number is -seg-1
  1688. int find_seg_side_face(short x,short y,segnum_t &seg,objnum_t &obj,int &side,int &face)
  1689. {
  1690.         _search_mode = -1;
  1691.  
  1692.         _search_x = x; _search_y = y;
  1693.  
  1694.         found_seg = segment_none;
  1695.         found_obj = object_none;
  1696.  
  1697.         render_frame(*(render_3d_in_big_window ? LargeView.ev_canv : Canv_editor_game), 0);
  1698.  
  1699.         _search_mode = 0;
  1700.  
  1701.         seg = found_seg;
  1702.         obj = found_obj;
  1703.         side = found_side;
  1704.         face = found_face;
  1705.         return found_seg != segment_none || found_obj != object_none;
  1706. }
  1707. #endif
  1708.