Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions code/graphics/render.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,20 @@ void gr_string(float sx, float sy, const char* s, int resize_mode, float scaleMu
}
}

void gr_string_outlined(int x, int y, const char* text, const color* foreground, const color* outline, int offset, int resize_mode, float scaleMultiplier, size_t length)
{
// draw outline by rendering text at all surrounding offsets
gr_set_color_fast(outline);
for (int dx = -offset; dx <= offset; dx++)
for (int dy = -offset; dy <= offset; dy++)
if (dx || dy)
gr_string(x + dx, y + dy, text, resize_mode, scaleMultiplier, length);

// draw foreground text on top
gr_set_color_fast(foreground);
gr_string(x, y, text, resize_mode, scaleMultiplier, length);
}

static void gr_line(float x1, float y1, float x2, float y2, int resize_mode) {
auto path = beginDrawing(resize_mode);

Expand Down
19 changes: 19 additions & 0 deletions code/graphics/render.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,25 @@ inline void gr_string(int x, int y, const char* string, int resize_mode = GR_RES
gr_string(i2fl(x), i2fl(y), string, resize_mode, scaleMultiplier, length);
}

/**
* @brief Draws outlined text at the given position
*
* @details Renders the text string with an outline by drawing the text
* at surrounding offsets in the outline color, then drawing the main text on top
* in the foreground color.
*
* @param x The x-coordinate
* @param y The y-coordinate
* @param text The text to draw
* @param foreground Color for the main text
* @param outline Color for the outline
* @param offset Pixel offset for the outline (default 1)
* @param resize_mode The mode for translating the screen positions
* @param scaleMultiplier The scale to use to apply scaling in addition to user settings
* @param length The number of bytes in the string to render. -1 will render the whole string.
*/
void gr_string_outlined(int x, int y, const char* text, const color* foreground, const color* outline, int offset = 1, int resize_mode = GR_RESIZE_FULL, float scaleMultiplier = 1.0f, size_t length = std::string::npos);

/**
* @brief Draws a single line segment to the screen.
* @param x1 The starting x-coordinate
Expand Down
2 changes: 2 additions & 0 deletions fred2/fred.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ BOOL CFREDApp::InitInstance() {
Draw_outlines_on_selected_ships = GetProfileInt("Preferences", "Draw outlines on selected ships", 1) != 0;
Point_using_uvec = GetProfileInt("Preferences", "Point using uvec", Point_using_uvec);
Draw_outline_at_warpin_position = GetProfileInt("Preferences", "Draw outline at warpin position", 0) != 0;
Outline_lod = GetProfileInt("Preferences", "Outline LOD", 1);
Always_save_display_names = GetProfileInt("Preferences", "Always save display names", 0) != 0;
Error_checker_checks_potential_issues = GetProfileInt("Preferences", "Error checker checks potential issues", 1) != 0;

Expand Down Expand Up @@ -546,6 +547,7 @@ void CFREDApp::write_ini_file(int degree) {
WriteProfileInt("Preferences", "Draw outlines on selected ships", Draw_outlines_on_selected_ships ? 1 : 0);
WriteProfileInt("Preferences", "Point using uvec", Point_using_uvec);
WriteProfileInt("Preferences", "Draw outline at warpin position", Draw_outline_at_warpin_position ? 1 : 0);
WriteProfileInt("Preferences", "Outline LOD", Outline_lod);
WriteProfileInt("Preferences", "Always save display names", Always_save_display_names ? 1 : 0);
WriteProfileInt("Preferences", "Error checker checks potential issues", Error_checker_checks_potential_issues ? 1 : 0);

Expand Down
8 changes: 8 additions & 0 deletions fred2/fred.rc
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,14 @@ BEGIN
MENUITEM "Show IFF 8", 33089
MENUITEM "Show IFF 9", 32988
END
POPUP "Outline LOD"
BEGIN
MENUITEM "LOD 0 (highest detail)", 33107
MENUITEM "LOD 1", 33108, CHECKED
MENUITEM "LOD 2", 33109
MENUITEM "LOD 3", 33110
MENUITEM "LOD 4 (lowest detail)", 33111
END
MENUITEM SEPARATOR
MENUITEM "Hide Marked Objects", 33002
MENUITEM "Show Hidden Objects", 33003
Expand Down
200 changes: 87 additions & 113 deletions fred2/fredrender.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ int Show_horizon = 0;
int Show_outlines = 0;
bool Draw_outlines_on_selected_ships = true;
bool Draw_outline_at_warpin_position = false;
int Outline_lod = 1;
bool Always_save_display_names = false;
bool Error_checker_checks_potential_issues = true;
bool Error_checker_checks_potential_issues_once = false;
Expand Down Expand Up @@ -211,12 +212,13 @@ int get_subsys_bounding_rect(object *ship_obj, ship_subsys *subsys, int *x1, int
*/
void render_models(void);

enum class subsystem_highlight { BOUNDING_BOX, LABEL };
/**
* @brief Renders the bounding box for the given subsystem with HTL
*
* @param[in] s2r Subsystem to render a box for
*/
void fredhtl_render_subsystem_bounding_box(subsys_to_render * s2r);
void fredhtl_render_subsystem_highlight(subsys_to_render *s2r, subsystem_highlight highlight);

/**
* @brief Draws the X from a elevation line on the grid
Expand Down Expand Up @@ -414,10 +416,18 @@ void display_active_ship_subsystem() {
if (Highlight_selectable_subsys) {
auto shipp = &Ships[objp->instance];

for (auto ss = GET_FIRST(&shipp->subsys_list); ss != END_OF_LIST(&shipp->subsys_list); ss = GET_NEXT(ss)) {
// first pass: draw all bounding boxes
for (auto ss : list_range(&shipp->subsys_list)) {
if (ss->system_info->subobj_num != -1) {
subsys_to_render s2r = { true, objp, ss };
fredhtl_render_subsystem_bounding_box(&s2r);
fredhtl_render_subsystem_highlight(&s2r, subsystem_highlight::BOUNDING_BOX);
}
}
// second pass: draw all labels
for (auto ss : list_range(&shipp->subsys_list)) {
if (ss->system_info->subobj_num != -1) {
subsys_to_render s2r = { true, objp, ss };
fredhtl_render_subsystem_highlight(&s2r, subsystem_highlight::LABEL);
}
}
}
Expand All @@ -430,7 +440,8 @@ void display_active_ship_subsystem() {
}

if (Render_subsys.do_render) {
fredhtl_render_subsystem_bounding_box(&Render_subsys);
fredhtl_render_subsystem_highlight(&Render_subsys, subsystem_highlight::BOUNDING_BOX);
fredhtl_render_subsystem_highlight(&Render_subsys, subsystem_highlight::LABEL);
} else {
cancel_display_active_ship_subsystem();
}
Expand Down Expand Up @@ -726,7 +737,7 @@ void draw_orient_sphere2(int col, object *obj, int r, int g, int b) {
}
}

void fredhtl_render_subsystem_bounding_box(subsys_to_render *s2r)
void fredhtl_render_subsystem_highlight(subsys_to_render *s2r, subsystem_highlight highlight)
{
vertex text_center;
SCP_string buf;
Expand All @@ -740,122 +751,75 @@ void fredhtl_render_subsystem_bounding_box(subsys_to_render *s2r)

auto bsp = &pm->submodel[subobj_num];

vec3d front_top_left = bsp->bounding_box[7];
vec3d front_top_right = bsp->bounding_box[6];
vec3d front_bot_left = bsp->bounding_box[4];
vec3d front_bot_right = bsp->bounding_box[5];
vec3d back_top_left = bsp->bounding_box[3];
vec3d back_top_right = bsp->bounding_box[2];
vec3d back_bot_left = bsp->bounding_box[0];
vec3d back_bot_right = bsp->bounding_box[1];

gr_set_color(255, 32, 32);

fred_enable_htl();

// get into the frame of reference of the submodel
int g3_count = 1;
g3_start_instance_matrix(&objp->pos, &objp->orient, true);
int mn = subobj_num;
while ((mn >= 0) && (pm->submodel[mn].parent >= 0))
{
vec3d offset = pm->submodel[mn].offset;
vm_vec_add2(&offset, &pmi->submodel[mn].canonical_offset);

g3_start_instance_matrix(&offset, &pmi->submodel[mn].canonical_orient, true);
g3_count++;
mn = pm->submodel[mn].parent;
}


//draw a cube around the subsystem
g3_draw_htl_line(&front_top_left, &front_top_right);
g3_draw_htl_line(&front_top_right, &front_bot_right);
g3_draw_htl_line(&front_bot_right, &front_bot_left);
g3_draw_htl_line(&front_bot_left, &front_top_left);

g3_draw_htl_line(&back_top_left, &back_top_right);
g3_draw_htl_line(&back_top_right, &back_bot_right);
g3_draw_htl_line(&back_bot_right, &back_bot_left);
g3_draw_htl_line(&back_bot_left, &back_top_left);

g3_draw_htl_line(&front_top_left, &back_top_left);
g3_draw_htl_line(&front_top_right, &back_top_right);
g3_draw_htl_line(&front_bot_left, &back_bot_left);
g3_draw_htl_line(&front_bot_right, &back_bot_right);

// transform bounding box corners from submodel-local space to world space
// and draw edges as thick camera-facing quads via g3_render_rod
color clr_red;
gr_init_color(&clr_red, 255, 32, 32);
float rod_width = 2.0f;

auto transform_and_draw_box = [&](const vec3d *bbox, int sobj_num) {
vec3d corners[8];
for (int i = 0; i < 8; i++)
model_instance_local_to_global_point(&corners[i], &bbox[i], pm, pmi, sobj_num, &objp->orient, &objp->pos);

// 12 edges of a box: front face, back face, connecting edges
// bounding_box indices: 0=BBL 1=BBR 2=BTR 3=BTL 4=FBL 5=FBR 6=FTR 7=FTL
static const int edges[12][2] = {
{7, 6}, {6, 5}, {5, 4}, {4, 7}, // front face
{3, 2}, {2, 1}, {1, 0}, {0, 3}, // back face
{7, 3}, {6, 2}, {4, 0}, {5, 1}, // connecting edges
};

for (const auto& edge : edges) {
vec3d pts[2] = { corners[edge[0]], corners[edge[1]] };
g3_render_rod(&clr_red, 2, pts, rod_width);
}
};

//draw another cube around a gun for a two-part turret
if ((ss->system_info->turret_gun_sobj >= 0) && (ss->system_info->turret_gun_sobj != ss->system_info->subobj_num))
{
bsp_info *bsp_turret = &pm->submodel[ss->system_info->turret_gun_sobj];

front_top_left = bsp_turret->bounding_box[7];
front_top_right = bsp_turret->bounding_box[6];
front_bot_left = bsp_turret->bounding_box[4];
front_bot_right = bsp_turret->bounding_box[5];
back_top_left = bsp_turret->bounding_box[3];
back_top_right = bsp_turret->bounding_box[2];
back_bot_left = bsp_turret->bounding_box[0];
back_bot_right = bsp_turret->bounding_box[1];

g3_start_instance_matrix(&bsp_turret->offset, &pmi->submodel[ss->system_info->turret_gun_sobj].canonical_orient, true);

g3_draw_htl_line(&front_top_left, &front_top_right);
g3_draw_htl_line(&front_top_right, &front_bot_right);
g3_draw_htl_line(&front_bot_right, &front_bot_left);
g3_draw_htl_line(&front_bot_left, &front_top_left);

g3_draw_htl_line(&back_top_left, &back_top_right);
g3_draw_htl_line(&back_top_right, &back_bot_right);
g3_draw_htl_line(&back_bot_right, &back_bot_left);
g3_draw_htl_line(&back_bot_left, &back_top_left);

g3_draw_htl_line(&front_top_left, &back_top_left);
g3_draw_htl_line(&front_top_right, &back_top_right);
g3_draw_htl_line(&front_bot_left, &back_bot_left);
g3_draw_htl_line(&front_bot_right, &back_bot_right);

g3_done_instance(true);
}
if (highlight == subsystem_highlight::BOUNDING_BOX) {
fred_enable_htl();

for (int i = 0; i < g3_count; i++)
g3_done_instance(true);
// draw a box around the subsystem
transform_and_draw_box(bsp->bounding_box, subobj_num);

fred_disable_htl();
// draw another box around a gun for a two-part turret
if ((ss->system_info->turret_gun_sobj >= 0) && (ss->system_info->turret_gun_sobj != ss->system_info->subobj_num))
transform_and_draw_box(pm->submodel[ss->system_info->turret_gun_sobj].bounding_box, ss->system_info->turret_gun_sobj);

// get text
buf = ss->system_info->subobj_name;
fred_disable_htl();
} else {
// get text
buf = ss->system_info->subobj_name;

// add weapons if present
for (int i = 0; i < ss->weapons.num_primary_banks; ++i)
{
int wi = ss->weapons.primary_bank_weapons[i];
if (wi >= 0)
// add weapons if present
for (int i = 0; i < ss->weapons.num_primary_banks; ++i)
{
buf += "\n";
buf += Weapon_info[wi].name;
int wi = ss->weapons.primary_bank_weapons[i];
if (wi >= 0)
{
buf += "\n";
buf += Weapon_info[wi].name;
}
}
}
for (int i = 0; i < ss->weapons.num_secondary_banks; ++i)
{
int wi = ss->weapons.secondary_bank_weapons[i];
if (wi >= 0)
for (int i = 0; i < ss->weapons.num_secondary_banks; ++i)
{
buf += "\n";
buf += Weapon_info[wi].name;
int wi = ss->weapons.secondary_bank_weapons[i];
if (wi >= 0)
{
buf += "\n";
buf += Weapon_info[wi].name;
}
}
}

//draw the text. rotate the center of the subsystem into place before finding out where to put the text
vec3d center_pt;
vm_vec_unrotate(&center_pt, &bsp->offset, &objp->orient);
vm_vec_add2(&center_pt, &objp->pos);
g3_rotate_vertex(&text_center, &center_pt);
g3_project_vertex(&text_center);
if (!(text_center.flags & PF_OVERFLOW)) {
gr_set_color_fast(&colour_white);
gr_string((int)text_center.screen.xyw.x, (int)text_center.screen.xyw.y, buf.c_str());
//draw the text. rotate the center of the subsystem into place before finding out where to put the text
vec3d center_pt;
vm_vec_unrotate(&center_pt, &bsp->offset, &objp->orient);
vm_vec_add2(&center_pt, &objp->pos);
g3_rotate_vertex(&text_center, &center_pt);
g3_project_vertex(&text_center);
if (!(text_center.flags & PF_OVERFLOW)) {
gr_string_outlined((int)text_center.screen.xyw.x, (int)text_center.screen.xyw.y, buf.c_str(), &colour_white, &colour_black, 2);
}
}
}

Expand Down Expand Up @@ -1889,9 +1853,14 @@ void render_one_model_htl(object *objp) {
prop* propp = prop_id_lookup(z);

if (Fred_outline) {
// use a different LOD for the wireframe to reduce visual clutter on high-poly models
int prop_model_num = Prop_info[propp->prop_info_index].model_num;
int outline_lod = std::min(Outline_lod, model_get(prop_model_num)->n_detail_levels - 1);
render_info.set_detail_level_lock(outline_lod);
render_info.set_color(Fred_outline >> 16, (Fred_outline >> 8) & 0xff, Fred_outline & 0xff);
render_info.set_flags(flags | MR_SHOW_OUTLINE_HTL | MR_NO_LIGHTING | MR_NO_POLYS | MR_NO_TEXTURING);
model_render_immediate(&render_info, Prop_info[propp->prop_info_index].model_num, propp->model_instance_num, &objp->orient, &objp->pos);
model_render_immediate(&render_info, prop_model_num, propp->model_instance_num, &objp->orient, &objp->pos);
render_info.set_detail_level_lock(-1);
}
//
render_info.set_flags(flags);
Expand Down Expand Up @@ -1930,9 +1899,14 @@ void render_one_model_htl(object *objp) {
render_info.set_replacement_textures(model_get_instance(Ships[z].model_instance_num)->texture_replace);

if (Fred_outline) {
// use a different LOD for the wireframe to reduce visual clutter on high-poly models
int ship_model_num = Ship_info[Ships[z].ship_info_index].model_num;
int outline_lod = std::min(Outline_lod, model_get(ship_model_num)->n_detail_levels - 1);
render_info.set_detail_level_lock(outline_lod);
render_info.set_color(Fred_outline >> 16, (Fred_outline >> 8) & 0xff, Fred_outline & 0xff);
render_info.set_flags(flags | MR_SHOW_OUTLINE_HTL | MR_NO_LIGHTING | MR_NO_POLYS | MR_NO_TEXTURING);
model_render_immediate(&render_info, Ship_info[Ships[z].ship_info_index].model_num, Ships[z].model_instance_num, &objp->orient, &objp->pos);
model_render_immediate(&render_info, ship_model_num, Ships[z].model_instance_num, &objp->orient, &objp->pos);
render_info.set_detail_level_lock(-1);
}

if (Draw_outline_at_warpin_position
Expand Down
1 change: 1 addition & 0 deletions fred2/fredrender.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ extern int Show_coordinates; //!< Bool. If nonzero, draw the coordinates
extern int Show_outlines; //!< Bool. If nonzero, draw each object's mesh. If models are shown, highlight them in white.
extern bool Draw_outlines_on_selected_ships; // If a ship is selected, draw mesh lines
extern bool Draw_outline_at_warpin_position; // Project an outline at the place where the ship will arrive after warping in
extern int Outline_lod; // The LOD to use for wireframe outlines (0 = highest detail, default 1)
extern bool Always_save_display_names; // When saving a mission, always write display names to the mission file even if the display name is not set.
// But ships in wings are excepted, because a display name will cause a ship to have the same name in every wave.
// In the future, a display name feature could be added to the wing dialog to handle this case.
Expand Down
Loading
Loading