@@ -103,6 +103,7 @@ int Show_horizon = 0;
103103int Show_outlines = 0 ;
104104bool Draw_outlines_on_selected_ships = true ;
105105bool Draw_outline_at_warpin_position = false ;
106+ int Outline_lod = 1 ;
106107bool Always_save_display_names = false ;
107108bool Error_checker_checks_potential_issues = true ;
108109bool Error_checker_checks_potential_issues_once = false ;
@@ -211,12 +212,13 @@ int get_subsys_bounding_rect(object *ship_obj, ship_subsys *subsys, int *x1, int
211212 */
212213void render_models (void );
213214
215+ enum class subsystem_highlight { BOUNDING_BOX, LABEL };
214216/* *
215217 * @brief Renders the bounding box for the given subsystem with HTL
216218 *
217219 * @param[in] s2r Subsystem to render a box for
218220 */
219- void fredhtl_render_subsystem_bounding_box (subsys_to_render * s2r);
221+ void fredhtl_render_subsystem_highlight (subsys_to_render *s2r, subsystem_highlight highlight );
220222
221223/* *
222224 * @brief Draws the X from a elevation line on the grid
@@ -414,10 +416,18 @@ void display_active_ship_subsystem() {
414416 if (Highlight_selectable_subsys) {
415417 auto shipp = &Ships[objp->instance ];
416418
417- for (auto ss = GET_FIRST (&shipp->subsys_list ); ss != END_OF_LIST (&shipp->subsys_list ); ss = GET_NEXT (ss)) {
419+ // first pass: draw all bounding boxes
420+ for (auto ss : list_range (&shipp->subsys_list )) {
418421 if (ss->system_info ->subobj_num != -1 ) {
419422 subsys_to_render s2r = { true , objp, ss };
420- fredhtl_render_subsystem_bounding_box (&s2r);
423+ fredhtl_render_subsystem_highlight (&s2r, subsystem_highlight::BOUNDING_BOX);
424+ }
425+ }
426+ // second pass: draw all labels
427+ for (auto ss : list_range (&shipp->subsys_list )) {
428+ if (ss->system_info ->subobj_num != -1 ) {
429+ subsys_to_render s2r = { true , objp, ss };
430+ fredhtl_render_subsystem_highlight (&s2r, subsystem_highlight::LABEL);
421431 }
422432 }
423433 }
@@ -430,7 +440,8 @@ void display_active_ship_subsystem() {
430440 }
431441
432442 if (Render_subsys.do_render ) {
433- fredhtl_render_subsystem_bounding_box (&Render_subsys);
443+ fredhtl_render_subsystem_highlight (&Render_subsys, subsystem_highlight::BOUNDING_BOX);
444+ fredhtl_render_subsystem_highlight (&Render_subsys, subsystem_highlight::LABEL);
434445 } else {
435446 cancel_display_active_ship_subsystem ();
436447 }
@@ -726,7 +737,7 @@ void draw_orient_sphere2(int col, object *obj, int r, int g, int b) {
726737 }
727738}
728739
729- void fredhtl_render_subsystem_bounding_box (subsys_to_render *s2r)
740+ void fredhtl_render_subsystem_highlight (subsys_to_render *s2r, subsystem_highlight highlight )
730741{
731742 vertex text_center;
732743 SCP_string buf;
@@ -740,122 +751,75 @@ void fredhtl_render_subsystem_bounding_box(subsys_to_render *s2r)
740751
741752 auto bsp = &pm->submodel [subobj_num];
742753
743- vec3d front_top_left = bsp->bounding_box [7 ];
744- vec3d front_top_right = bsp->bounding_box [6 ];
745- vec3d front_bot_left = bsp->bounding_box [4 ];
746- vec3d front_bot_right = bsp->bounding_box [5 ];
747- vec3d back_top_left = bsp->bounding_box [3 ];
748- vec3d back_top_right = bsp->bounding_box [2 ];
749- vec3d back_bot_left = bsp->bounding_box [0 ];
750- vec3d back_bot_right = bsp->bounding_box [1 ];
751-
752- gr_set_color (255 , 32 , 32 );
753-
754- fred_enable_htl ();
755-
756- // get into the frame of reference of the submodel
757- int g3_count = 1 ;
758- g3_start_instance_matrix (&objp->pos , &objp->orient , true );
759- int mn = subobj_num;
760- while ((mn >= 0 ) && (pm->submodel [mn].parent >= 0 ))
761- {
762- vec3d offset = pm->submodel [mn].offset ;
763- vm_vec_add2 (&offset, &pmi->submodel [mn].canonical_offset );
764-
765- g3_start_instance_matrix (&offset, &pmi->submodel [mn].canonical_orient , true );
766- g3_count++;
767- mn = pm->submodel [mn].parent ;
768- }
769-
770-
771- // draw a cube around the subsystem
772- g3_draw_htl_line (&front_top_left, &front_top_right);
773- g3_draw_htl_line (&front_top_right, &front_bot_right);
774- g3_draw_htl_line (&front_bot_right, &front_bot_left);
775- g3_draw_htl_line (&front_bot_left, &front_top_left);
776-
777- g3_draw_htl_line (&back_top_left, &back_top_right);
778- g3_draw_htl_line (&back_top_right, &back_bot_right);
779- g3_draw_htl_line (&back_bot_right, &back_bot_left);
780- g3_draw_htl_line (&back_bot_left, &back_top_left);
781-
782- g3_draw_htl_line (&front_top_left, &back_top_left);
783- g3_draw_htl_line (&front_top_right, &back_top_right);
784- g3_draw_htl_line (&front_bot_left, &back_bot_left);
785- g3_draw_htl_line (&front_bot_right, &back_bot_right);
786-
754+ // transform bounding box corners from submodel-local space to world space
755+ // and draw edges as thick camera-facing quads via g3_render_rod
756+ color clr_red;
757+ gr_init_color (&clr_red, 255 , 32 , 32 );
758+ float rod_width = 2 .0f ;
759+
760+ auto transform_and_draw_box = [&](const vec3d *bbox, int sobj_num) {
761+ vec3d corners[8 ];
762+ for (int i = 0 ; i < 8 ; i++)
763+ model_instance_local_to_global_point (&corners[i], &bbox[i], pm, pmi, sobj_num, &objp->orient , &objp->pos );
764+
765+ // 12 edges of a box: front face, back face, connecting edges
766+ // bounding_box indices: 0=BBL 1=BBR 2=BTR 3=BTL 4=FBL 5=FBR 6=FTR 7=FTL
767+ static const int edges[12 ][2 ] = {
768+ {7 , 6 }, {6 , 5 }, {5 , 4 }, {4 , 7 }, // front face
769+ {3 , 2 }, {2 , 1 }, {1 , 0 }, {0 , 3 }, // back face
770+ {7 , 3 }, {6 , 2 }, {4 , 0 }, {5 , 1 }, // connecting edges
771+ };
772+
773+ for (const auto & edge : edges) {
774+ vec3d pts[2 ] = { corners[edge[0 ]], corners[edge[1 ]] };
775+ g3_render_rod (&clr_red, 2 , pts, rod_width);
776+ }
777+ };
787778
788- // draw another cube around a gun for a two-part turret
789- if ((ss->system_info ->turret_gun_sobj >= 0 ) && (ss->system_info ->turret_gun_sobj != ss->system_info ->subobj_num ))
790- {
791- bsp_info *bsp_turret = &pm->submodel [ss->system_info ->turret_gun_sobj ];
792-
793- front_top_left = bsp_turret->bounding_box [7 ];
794- front_top_right = bsp_turret->bounding_box [6 ];
795- front_bot_left = bsp_turret->bounding_box [4 ];
796- front_bot_right = bsp_turret->bounding_box [5 ];
797- back_top_left = bsp_turret->bounding_box [3 ];
798- back_top_right = bsp_turret->bounding_box [2 ];
799- back_bot_left = bsp_turret->bounding_box [0 ];
800- back_bot_right = bsp_turret->bounding_box [1 ];
801-
802- g3_start_instance_matrix (&bsp_turret->offset , &pmi->submodel [ss->system_info ->turret_gun_sobj ].canonical_orient , true );
803-
804- g3_draw_htl_line (&front_top_left, &front_top_right);
805- g3_draw_htl_line (&front_top_right, &front_bot_right);
806- g3_draw_htl_line (&front_bot_right, &front_bot_left);
807- g3_draw_htl_line (&front_bot_left, &front_top_left);
808-
809- g3_draw_htl_line (&back_top_left, &back_top_right);
810- g3_draw_htl_line (&back_top_right, &back_bot_right);
811- g3_draw_htl_line (&back_bot_right, &back_bot_left);
812- g3_draw_htl_line (&back_bot_left, &back_top_left);
813-
814- g3_draw_htl_line (&front_top_left, &back_top_left);
815- g3_draw_htl_line (&front_top_right, &back_top_right);
816- g3_draw_htl_line (&front_bot_left, &back_bot_left);
817- g3_draw_htl_line (&front_bot_right, &back_bot_right);
818-
819- g3_done_instance (true );
820- }
779+ if (highlight == subsystem_highlight::BOUNDING_BOX) {
780+ fred_enable_htl ();
821781
822- for ( int i = 0 ; i < g3_count; i++)
823- g3_done_instance ( true );
782+ // draw a box around the subsystem
783+ transform_and_draw_box (bsp-> bounding_box , subobj_num );
824784
825- fred_disable_htl ();
785+ // draw another box around a gun for a two-part turret
786+ if ((ss->system_info ->turret_gun_sobj >= 0 ) && (ss->system_info ->turret_gun_sobj != ss->system_info ->subobj_num ))
787+ transform_and_draw_box (pm->submodel [ss->system_info ->turret_gun_sobj ].bounding_box , ss->system_info ->turret_gun_sobj );
826788
827- // get text
828- buf = ss->system_info ->subobj_name ;
789+ fred_disable_htl ();
790+ } else {
791+ // get text
792+ buf = ss->system_info ->subobj_name ;
829793
830- // add weapons if present
831- for (int i = 0 ; i < ss->weapons .num_primary_banks ; ++i)
832- {
833- int wi = ss->weapons .primary_bank_weapons [i];
834- if (wi >= 0 )
794+ // add weapons if present
795+ for (int i = 0 ; i < ss->weapons .num_primary_banks ; ++i)
835796 {
836- buf += " \n " ;
837- buf += Weapon_info[wi].name ;
797+ int wi = ss->weapons .primary_bank_weapons [i];
798+ if (wi >= 0 )
799+ {
800+ buf += " \n " ;
801+ buf += Weapon_info[wi].name ;
802+ }
838803 }
839- }
840- for (int i = 0 ; i < ss->weapons .num_secondary_banks ; ++i)
841- {
842- int wi = ss->weapons .secondary_bank_weapons [i];
843- if (wi >= 0 )
804+ for (int i = 0 ; i < ss->weapons .num_secondary_banks ; ++i)
844805 {
845- buf += " \n " ;
846- buf += Weapon_info[wi].name ;
806+ int wi = ss->weapons .secondary_bank_weapons [i];
807+ if (wi >= 0 )
808+ {
809+ buf += " \n " ;
810+ buf += Weapon_info[wi].name ;
811+ }
847812 }
848- }
849813
850- // draw the text. rotate the center of the subsystem into place before finding out where to put the text
851- vec3d center_pt;
852- vm_vec_unrotate (¢er_pt, &bsp->offset , &objp->orient );
853- vm_vec_add2 (¢er_pt, &objp->pos );
854- g3_rotate_vertex (&text_center, ¢er_pt);
855- g3_project_vertex (&text_center);
856- if (!(text_center.flags & PF_OVERFLOW)) {
857- gr_set_color_fast ( &colour_white);
858- gr_string (( int )text_center. screen . xyw . x , ( int )text_center. screen . xyw . y , buf. c_str ());
814+ // draw the text. rotate the center of the subsystem into place before finding out where to put the text
815+ vec3d center_pt;
816+ vm_vec_unrotate (¢er_pt, &bsp->offset , &objp->orient );
817+ vm_vec_add2 (¢er_pt, &objp->pos );
818+ g3_rotate_vertex (&text_center, ¢er_pt);
819+ g3_project_vertex (&text_center);
820+ if (!(text_center.flags & PF_OVERFLOW)) {
821+ gr_string_outlined (( int )text_center. screen . xyw . x , ( int )text_center. screen . xyw . y , buf. c_str (), &colour_white, &colour_black, 2 );
822+ }
859823 }
860824}
861825
@@ -1889,9 +1853,14 @@ void render_one_model_htl(object *objp) {
18891853 prop* propp = prop_id_lookup (z);
18901854
18911855 if (Fred_outline) {
1856+ // use a different LOD for the wireframe to reduce visual clutter on high-poly models
1857+ int prop_model_num = Prop_info[propp->prop_info_index ].model_num ;
1858+ int outline_lod = std::min (Outline_lod, model_get (prop_model_num)->n_detail_levels - 1 );
1859+ render_info.set_detail_level_lock (outline_lod);
18921860 render_info.set_color (Fred_outline >> 16 , (Fred_outline >> 8 ) & 0xff , Fred_outline & 0xff );
18931861 render_info.set_flags (flags | MR_SHOW_OUTLINE_HTL | MR_NO_LIGHTING | MR_NO_POLYS | MR_NO_TEXTURING);
1894- model_render_immediate (&render_info, Prop_info[propp->prop_info_index ].model_num , propp->model_instance_num , &objp->orient , &objp->pos );
1862+ model_render_immediate (&render_info, prop_model_num, propp->model_instance_num , &objp->orient , &objp->pos );
1863+ render_info.set_detail_level_lock (-1 );
18951864 }
18961865 //
18971866 render_info.set_flags (flags);
@@ -1930,9 +1899,14 @@ void render_one_model_htl(object *objp) {
19301899 render_info.set_replacement_textures (model_get_instance (Ships[z].model_instance_num )->texture_replace );
19311900
19321901 if (Fred_outline) {
1902+ // use a different LOD for the wireframe to reduce visual clutter on high-poly models
1903+ int ship_model_num = Ship_info[Ships[z].ship_info_index ].model_num ;
1904+ int outline_lod = std::min (Outline_lod, model_get (ship_model_num)->n_detail_levels - 1 );
1905+ render_info.set_detail_level_lock (outline_lod);
19331906 render_info.set_color (Fred_outline >> 16 , (Fred_outline >> 8 ) & 0xff , Fred_outline & 0xff );
19341907 render_info.set_flags (flags | MR_SHOW_OUTLINE_HTL | MR_NO_LIGHTING | MR_NO_POLYS | MR_NO_TEXTURING);
1935- model_render_immediate (&render_info, Ship_info[Ships[z].ship_info_index ].model_num , Ships[z].model_instance_num , &objp->orient , &objp->pos );
1908+ model_render_immediate (&render_info, ship_model_num, Ships[z].model_instance_num , &objp->orient , &objp->pos );
1909+ render_info.set_detail_level_lock (-1 );
19361910 }
19371911
19381912 if (Draw_outline_at_warpin_position
0 commit comments