/* * Copyright (C) Volition, Inc. 1999. All rights reserved. * * All source code herein is the property of Volition, Inc. You may not sell * or otherwise commercially exploit the source or things you created based on the * source. * */ #include "stdafx.h" #include "FRED.h" #include "FREDDoc.h" #include "FREDView.h" #include "MainFrm.h" #include "FredRender.h" #include "Management.h" #include "wing.h" #include "asteroid/asteroid.h" #include "bmpman/bmpman.h" #include "cfile/cfile.h" #include "cmdline/cmdline.h" #include "globalincs/linklist.h" #include "graphics/2d.h" #include "graphics/matrix.h" #include "graphics/light.h" #include "graphics/font.h" #include "graphics/tmapper.h" #include "iff_defs/iff_defs.h" #include "io/key.h" #include "io/spacemouse.h" #include "io/timer.h" #include "jumpnode/jumpnode.h" #include "lighting/lighting.h" #include "math/floating.h" #include "math/fvi.h" #include "math/vecmat.h" #include "mission/missiongrid.h" #include "mission/missionparse.h" #include "model/model.h" #include "model/model.h" #include "mod_table/mod_table.h" #include "missioneditor/common.h" #include "object/object.h" #include "physics/physics.h" #include "prop/prop.h" #include "render/3d.h" #include "render/3dinternal.h" #include "ship/ship.h" #include "sound/audiostr.h" #include "starfield/starfield.h" #include "weapon/weapon.h" #include #include #include #include #include #include #include extern float flFrametime; extern subsys_to_render Render_subsys; #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #define MAX_FRAMETIME (F1_0/4) // Frametime gets saturated at this. #define MIN_FRAMETIME (F1_0/120) #define LOLLIPOP_SIZE 2.5f #define CONVERT_DEGREES 57.29578f // conversion factor from radians to degrees #define FRED_COLOUR_WHITE 0xffffff #define FRED_COLOUR_YELLOW_GREEN 0xc8ff00 const float FRED_DEFAULT_HTL_FOV = 0.485f; const float FRED_BRIEFING_HTL_FOV = 0.325f; const float FRED_DEAFULT_HTL_DRAW_DIST = 300000.0f; int Aa_gridlines = 0; int Control_mode = 0; int Editing_mode = 1; int Fixed_briefing_size = 1; int Flying_controls_mode = 1; int Fred_outline = 0; int Group_rotate = 1; int info_popup_active = 0; int inited = -1; int Last_cursor_over = -1; int last_x = 0; int last_y = 0; int Lookat_mode = 0; // Orbit camera state vec3d Orbit_pivot = vmd_zero_vector; matrix Orbit_grid_orient = vmd_identity_matrix; float Orbit_distance = 200.0f; float Orbit_phi = 1.24f; float Orbit_theta = 2.25f; bool Orbit_active = false; int rendering_order[MAX_SHIPS]; int render_count = 0; int Show_asteroid_field = 1; int Show_coordinates = 0; float Fred_label_font_scale = 1.0f; int Show_distances = 0; int Show_grid = 1; int Show_grid_positions = 1; int Show_horizon = 0; int Show_outlines = 0; bool Draw_outlines_on_selected_ships = true; bool Draw_outline_at_warpin_position = false; bool Orbit_selection_midpoint = 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; bool Classic_menu_layout = false; int Show_stars = 1; int Single_axis_constraint = 0; int True_rw, True_rh; int Universal_heading = 0; fix lasttime = 0; vec3d my_pos = { 0.0f, 0.0f, -5.0f }; vec3d view_pos, eye_pos, Viewer_pos, Last_eye_pos = { 0.0f }; vec3d Last_control_pos = { 0.0f }; vec3d Constraint = { 1.0f, 0.0f, 1.0f }; vec3d Anticonstraint = { 0.0f, 1.0f, 0.0f }; vec3d Grid_center; matrix Grid_gmatrix; matrix my_orient = IDENTITY_MATRIX; matrix trackball_orient = IDENTITY_MATRIX; matrix view_orient = IDENTITY_MATRIX, eye_orient, Last_eye_orient = IDENTITY_MATRIX; matrix Last_control_orient = IDENTITY_MATRIX; physics_info view_physics; control_info view_controls; CWnd info_popup; color colour_black; color colour_green; color colour_white; color colour_yellow_green; static vec3d Global_light_world = { 0.208758f, -0.688253f, -0.694782f }; int Fred_grid_colors_inited = 0; color Fred_grid_bright, Fred_grid_dark, Fred_grid_bright_aa, Fred_grid_dark_aa; /** * @brief Enables HTL */ void fred_enable_htl(); /** * @brief Disables HTL */ void fred_disable_htl(); /** * @brief Makes the orientation "wings level" with the grid plane. * * @param[in,out] orient Orientation matrix to modify */ void level_object(matrix *orient); /** * @brief Snaps the given vector to the closest unit vector * * @param[in,out] v Vector to modify */ void align_vector_to_axis(vec3d *v); /** * @brief Snap the given orientation to the closest unit vector * * @param[in,out] orient Orientation matrix of the object to adjust */ void verticalize_object(matrix *orient); /** * @brief Process the given key. If it turns out to be a system key then do something. * * @param[in] key Key to process */ void process_system_keys(int key); /** * @brief Get the world position for a given subobject on a ship * * @param[in] parent_obj The ship * @param[in] subsys The subsystem * @param[out] world_pos The world position of the subsystem * * @TODO Need a better way of error handling here. Maybe return a bool instead. */ vec3d* get_subsystem_world_pos2(object* parent_obj, ship_subsys* subsys, vec3d* world_pos); /** * @brief Gets the bounding rectangle (2D) for the given subsystem of a given ship * * @param[in] ship_obj The ship * @param[in] subsys The subsystem * @param[out] x1 The first X coordinate * @param[out] x2 The second X coordinate * @param[out] y1 The first Y coordinate * @param[out] y2 The second Y coordinate * * @returns 1 if successful, or * @returns 0 if not */ int get_subsys_bounding_rect(object *ship_obj, ship_subsys *subsys, int *x1, int *x2, int *y1, int *y2); /** * @brief Renders all models */ 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_highlight(subsys_to_render *s2r, subsystem_highlight highlight); /** * @brief Draws the X from a elevation line on the grid * * @param[in] pos Position of the object. The X's position is calculated from this. * @param[in] gridp The grid we're referencing against * @param[in] col_scheme Color scheme? */ void render_model_x(const vec3d *pos, const grid *gridp, int col_scheme = 0); /** * @brief Draws the X from a elevation line on the grid with HTL * * @param[in] pos Position of the object. The X's position is calculated from this. * @param[in] gridp The grid we're referencing against * @param[in] col_scheme Color scheme? */ void render_model_x_htl(vec3d *pos, grid *gridp, int col_scheme = 0); /** * @brief Draws a model with HTL * * @param[in] objp Object to maybe draw */ void render_one_model_htl(object *objp); /** * @brief Draws a single briefing icon, if the given object is an icon * * @param[in] objp Object to maybe draw */ void render_one_model_briefing_screen(object *objp); /** * @brief Draws the boundries of the asteroid field */ void draw_asteroid_field(); /** * @brief Highlights the bitmaps used on the skybox * * @note Currently does nothing */ void hilight_bitmap(); /** * @brief Displays the active ship subsystem */ void display_active_ship_subsystem(); /** * @brief Displays the distances between each object in the mission. */ void display_distances(); /** * @brief Draws an arrow for the compass * * @param[in] Vector of the arrow we're drawing * * @details Actually draws a stick, but whatever. */ void draw_compass_arrow(vec3d *v0); /** * @brief Draws a lollipop * * @param[in] obj Object to draw a lollipop for * @param[in] r The red color value * @param[in] g The green color value * @param[in] b The blue color value */ void draw_orient_sphere(object *obj, int r, int g, int b); /** * @brief Draws a lollipop * * @param[in] col * @param[in] obj Object to draw a lollipop for * @param[in] r The red color value * @param[in] g The green color value * @param[in] b The blue color value */ void draw_orient_sphere2(int col, object *obj, int r, int g, int b); /** * @brief Renders the given grid * * @param[in] gridp Grid to render */ void fred_render_grid(grid *gridp); /** * @brief Renders the selection rectangle */ void render_active_rect(void); /** * @brief Render the universal compass */ void render_compass(void); /** * See if object obstructs the vector from point p0 to point p1. * * @param[in] objp Object to check * @param[in] p0 Origin of vector * @param[in] p1 Destination of vector * @param[out] hitpost If the object does obstuct the vector, this is the tosition where a collision occurs * * @returns TRUE if a collision occured, or * @returns FALSE if no collision */ int object_check_collision(object *objp, vec3d *p0, vec3d *p1, vec3d *hitpos); /** * @brief Process the controls * * @param[in,out] pos Position of the camera or controlled object to modify * @param[in,out] orient Orientation of the camera or controlled object to modify * @param[in] frametime Frametime of when this function was called * @param[in] key Key to (maybe) process * @param[in] mode Optional. Mode that we're currently in */ void process_controls(vec3d *pos, matrix *orient, float frametime, int key, int mode = 0); /** * @brief Processes the given key. Does something if it is a movement key. * * @param[in] key Key to process * @param[in,out] mvec Movement vector to modify * @param[in,out] angs Angles to modify */ void process_movement_keys(int key, vec3d *mvec, angles *angs); /** * @brief Increments the mission timer */ void inc_mission_time(); /** * @brief Disables rendering of the active ship subsystem */ void cancel_display_active_ship_subsystem(); void align_vector_to_axis(vec3d *v) { float x, y, z; x = v->xyz.x; if (x < 0) x = -x; y = v->xyz.y; if (y < 0) y = -y; z = v->xyz.z; if (z < 0) z = -z; if ((x > y) && (x > z)) { // x axis if (v->xyz.x < 0) // negative x vm_vec_make(v, -1.0f, 0.0f, 0.0f); else // positive x vm_vec_make(v, 1.0f, 0.0f, 0.0f); } else if (y > z) { // y axis if (v->xyz.y < 0) // negative y vm_vec_make(v, 0.0f, -1.0f, 0.0f); else // positive y vm_vec_make(v, 0.0f, 1.0f, 0.0f); } else { // z axis if (v->xyz.z < 0) // negative z vm_vec_make(v, 0.0f, 0.0f, -1.0f); else // positive z vm_vec_make(v, 0.0f, 0.0f, 1.0f); } } void cancel_display_active_ship_subsystem() { Render_subsys.do_render = false; Render_subsys.ship_obj = NULL; Render_subsys.cur_subsys = NULL; } void display_active_ship_subsystem() { if (cur_object_index != -1) { if (Objects[cur_object_index].type == OBJ_SHIP) { object *objp = &Objects[cur_object_index]; // if this option is checked, we want to render info for all subsystems, not just the ones we select with K and Shift-K if (Highlight_selectable_subsys) { auto shipp = &Ships[objp->instance]; // 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_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); } } } // otherwise select individual subsystems, or not, as normal else { // switching to a new ship, so reset if (objp != Render_subsys.ship_obj) { cancel_display_active_ship_subsystem(); return; } if (Render_subsys.do_render) { 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(); } } } } } void display_distances() { char buf[20]; object *objp, *o2; vec3d pos; vertex v; gr_set_color(255, 0, 0); objp = GET_FIRST(&obj_used_list); while (objp != END_OF_LIST(&obj_used_list)) { if (objp->flags[Object::Object_Flags::Marked]) { o2 = GET_NEXT(objp); while (o2 != END_OF_LIST(&obj_used_list)) { if (o2->flags[Object::Object_Flags::Marked]) { rpd_line(&objp->pos, &o2->pos); vm_vec_avg(&pos, &objp->pos, &o2->pos); g3_rotate_vertex(&v, &pos); if (!(v.codes & CC_BEHIND)) if (!(g3_project_vertex(&v) & PF_OVERFLOW)) { sprintf(buf, "%.1f", vm_vec_dist(&objp->pos, &o2->pos)); gr_set_color_fast(&colour_white); gr_string((int) v.screen.xyw.x, (int) v.screen.xyw.y, buf, GR_RESIZE_FULL, Fred_label_font_scale); } } o2 = GET_NEXT(o2); } } objp = GET_NEXT(objp); } } void display_ship_info() { char buf[512], pos[80]; int render = 1; object *objp; vertex v; objp = GET_FIRST(&obj_used_list); while (objp != END_OF_LIST(&obj_used_list)) { Assert(objp->type != OBJ_NONE); Fred_outline = 0; render = 1; if (OBJ_INDEX(objp) == cur_object_index) Fred_outline = FRED_COLOUR_WHITE; else if (objp->flags[Object::Object_Flags::Marked]) // is it a marked object? Fred_outline = FRED_COLOUR_YELLOW_GREEN; else Fred_outline = 0; if ((objp->type == OBJ_WAYPOINT) && !Show_waypoints) render = 0; if ((objp->type == OBJ_START) && !Show_starts) render = 0; if ((objp->type == OBJ_SHIP) || (objp->type == OBJ_START)) { if (!Show_ships) render = 0; if (!Show_iff[Ships[objp->instance].team]) render = 0; } if (objp->flags[Object::Object_Flags::Hidden]) render = 0; g3_rotate_vertex(&v, &objp->pos); if (!(v.codes & CC_BEHIND) && render) if (!(g3_project_vertex(&v) & PF_OVERFLOW)) { *buf = 0; if (Show_ship_info) { if ((objp->type == OBJ_SHIP) || (objp->type == OBJ_START)) { ship *shipp; int ship_type; shipp = &Ships[objp->instance]; ship_type = shipp->ship_info_index; Assert(ship_type >= 0); sprintf(buf, "%s\n%s", shipp->ship_name, Ship_info[ship_type].short_name); } else if (objp->type == OBJ_WAYPOINT) { int idx; waypoint_list *wp_list = find_waypoint_list_with_instance(objp->instance, &idx); Assert(wp_list != NULL); sprintf(buf, "%s\nWaypoint %d", wp_list->get_name(), idx + 1); } else if (objp->type == OBJ_POINT) { if (objp->instance == BRIEFING_LOOKAT_POINT_ID) strcpy_s(buf, "Camera lookat point"); else strcpy_s(buf, "Briefing icon"); } else if (objp->type == OBJ_JUMP_NODE) { CJumpNode* jnp = jumpnode_get_by_objnum(OBJ_INDEX(objp)); sprintf(buf, "%s\n%s", jnp->GetName(), jnp->GetDisplayName()); } else if (objp->type == OBJ_PROP) { prop* propp; int prop_type; propp = prop_id_lookup(objp->instance); prop_type = propp->prop_info_index; Assert(prop_type >= 0); sprintf(buf, "%s\n", propp->prop_name); } else Assert(0); } if (Show_coordinates) { sprintf(pos, "(%.0f,%.0f,%.0f)", objp->pos.xyz.x, objp->pos.xyz.y, objp->pos.xyz.z); if (*buf) strcat_s(buf, "\n"); strcat_s(buf, pos); } if (*buf) { if (Fred_outline == FRED_COLOUR_WHITE) gr_set_color_fast(&colour_green); else if (Fred_outline == FRED_COLOUR_YELLOW_GREEN) gr_set_color_fast(&colour_yellow_green); else gr_set_color_fast(&colour_white); gr_string((int) v.screen.xyw.x, (int) v.screen.xyw.y, buf, GR_RESIZE_FULL, Fred_label_font_scale); } } objp = GET_NEXT(objp); } } void draw_asteroid_field() { int i, j; vec3d p[8], ip[8]; vertex v[8], iv[8]; for (i = 0; i < 1 /*MAX_ASTEROID_FIELDS*/; i++) if (Asteroid_field.num_initial_asteroids) { p[0].xyz.x = p[2].xyz.x = p[4].xyz.x = p[6].xyz.x = Asteroid_field.min_bound.xyz.x; p[1].xyz.x = p[3].xyz.x = p[5].xyz.x = p[7].xyz.x = Asteroid_field.max_bound.xyz.x; p[0].xyz.y = p[1].xyz.y = p[4].xyz.y = p[5].xyz.y = Asteroid_field.min_bound.xyz.y; p[2].xyz.y = p[3].xyz.y = p[6].xyz.y = p[7].xyz.y = Asteroid_field.max_bound.xyz.y; p[0].xyz.z = p[1].xyz.z = p[2].xyz.z = p[3].xyz.z = Asteroid_field.min_bound.xyz.z; p[4].xyz.z = p[5].xyz.z = p[6].xyz.z = p[7].xyz.z = Asteroid_field.max_bound.xyz.z; for (j = 0; j < 8; j++) g3_rotate_vertex(&v[j], &p[j]); g3_draw_line(&v[0], &v[1]); g3_draw_line(&v[2], &v[3]); g3_draw_line(&v[4], &v[5]); g3_draw_line(&v[6], &v[7]); g3_draw_line(&v[0], &v[2]); g3_draw_line(&v[1], &v[3]); g3_draw_line(&v[4], &v[6]); g3_draw_line(&v[5], &v[7]); g3_draw_line(&v[0], &v[4]); g3_draw_line(&v[1], &v[5]); g3_draw_line(&v[2], &v[6]); g3_draw_line(&v[3], &v[7]); // maybe draw inner box if (Asteroid_field.has_inner_bound) { gr_set_color(16, 192, 92); ip[0].xyz.x = ip[2].xyz.x = ip[4].xyz.x = ip[6].xyz.x = Asteroid_field.inner_min_bound.xyz.x; ip[1].xyz.x = ip[3].xyz.x = ip[5].xyz.x = ip[7].xyz.x = Asteroid_field.inner_max_bound.xyz.x; ip[0].xyz.y = ip[1].xyz.y = ip[4].xyz.y = ip[5].xyz.y = Asteroid_field.inner_min_bound.xyz.y; ip[2].xyz.y = ip[3].xyz.y = ip[6].xyz.y = ip[7].xyz.y = Asteroid_field.inner_max_bound.xyz.y; ip[0].xyz.z = ip[1].xyz.z = ip[2].xyz.z = ip[3].xyz.z = Asteroid_field.inner_min_bound.xyz.z; ip[4].xyz.z = ip[5].xyz.z = ip[6].xyz.z = ip[7].xyz.z = Asteroid_field.inner_max_bound.xyz.z; for (j = 0; j < 8; j++) g3_rotate_vertex(&iv[j], &ip[j]); g3_draw_line(&iv[0], &iv[1]); g3_draw_line(&iv[2], &iv[3]); g3_draw_line(&iv[4], &iv[5]); g3_draw_line(&iv[6], &iv[7]); g3_draw_line(&iv[0], &iv[2]); g3_draw_line(&iv[1], &iv[3]); g3_draw_line(&iv[4], &iv[6]); g3_draw_line(&iv[5], &iv[7]); g3_draw_line(&iv[0], &iv[4]); g3_draw_line(&iv[1], &iv[5]); g3_draw_line(&iv[2], &iv[6]); g3_draw_line(&iv[3], &iv[7]); } } } void draw_compass_arrow(vec3d *v0) { vec3d v1 = { 0.0f }; vertex tv0, tv1; g3_rotate_vertex(&tv0, v0); g3_rotate_vertex(&tv1, &v1); g3_project_vertex(&tv0); g3_project_vertex(&tv1); // tv0.sx = (tv0.sx - tv1.sx) * 1 + tv1.sx; // tv0.sy = (tv0.sy - tv1.sy) * 1 + tv1.sy; g3_draw_line(&tv0, &tv1); } void draw_orient_sphere(object *obj, int r, int g, int b) { int flag = 0; vertex v; vec3d v1, v2; float size; size = fl_sqrt(vm_vec_dist(&eye_pos, &obj->pos) / 20.0f); if (size < LOLLIPOP_SIZE) size = LOLLIPOP_SIZE; if ((obj->type != OBJ_WAYPOINT) && (obj->type != OBJ_POINT)) { flag = (vm_vec_dot(&eye_orient.vec.fvec, &obj->orient.vec.fvec) < 0.0f); v1 = v2 = obj->pos; vm_vec_scale_add2(&v1, &obj->orient.vec.fvec, size); vm_vec_scale_add2(&v2, &obj->orient.vec.fvec, size * 1.5f); if (!flag) { gr_set_color(192, 192, 192); rpd_line(&v1, &v2); } } gr_set_color(r, g, b); g3_rotate_vertex(&v, &obj->pos); if (!(v.codes & CC_BEHIND)) if (!(g3_project_vertex(&v) & PF_OVERFLOW)) g3_draw_sphere(&v, size); if (flag) { gr_set_color(192, 192, 192); rpd_line(&v1, &v2); } } void draw_orient_sphere2(int col, object *obj, int r, int g, int b) { int flag = 0; vertex v; vec3d v1, v2; float size; size = fl_sqrt(vm_vec_dist(&eye_pos, &obj->pos) / 20.0f); if (size < LOLLIPOP_SIZE) size = LOLLIPOP_SIZE; if ((obj->type != OBJ_WAYPOINT) && (obj->type != OBJ_POINT)) { flag = (vm_vec_dot(&eye_orient.vec.fvec, &obj->orient.vec.fvec) < 0.0f); v1 = v2 = obj->pos; vm_vec_scale_add2(&v1, &obj->orient.vec.fvec, size); vm_vec_scale_add2(&v2, &obj->orient.vec.fvec, size * 1.5f); if (!flag) { gr_set_color(192, 192, 192); rpd_line(&v1, &v2); } } g3_rotate_vertex(&v, &obj->pos); if (!(v.codes & CC_BEHIND)) if (!(g3_project_vertex(&v) & PF_OVERFLOW)) { gr_set_color((col >> 16) & 0xff, (col >> 8) & 0xff, col & 0xff); g3_draw_sphere(&v, size); gr_set_color(r, g, b); g3_draw_sphere(&v, size * 0.75f); } if (flag) { gr_set_color(192, 192, 192); rpd_line(&v1, &v2); } } void fredhtl_render_subsystem_highlight(subsys_to_render *s2r, subsystem_highlight highlight) { vertex text_center; SCP_string buf; auto objp = s2r->ship_obj; auto ss = s2r->cur_subsys; auto pmi = model_get_instance(Ships[objp->instance].model_instance_num); auto pm = model_get(pmi->model_num); int subobj_num = ss->system_info->subobj_num; auto bsp = &pm->submodel[subobj_num]; // 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); } }; if (highlight == subsystem_highlight::BOUNDING_BOX) { fred_enable_htl(); // draw a box around the subsystem transform_and_draw_box(bsp->bounding_box, subobj_num); // 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); 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) { 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) { 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(¢er_pt, &bsp->offset, &objp->orient); vm_vec_add2(¢er_pt, &objp->pos); g3_rotate_vertex(&text_center, ¢er_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, GR_RESIZE_FULL, Fred_label_font_scale); } } } void fred_disable_htl() { gr_end_proj_matrix(); gr_end_view_matrix(); } void fred_enable_htl() { gr_set_proj_matrix((4.0f / 9.0f) * PI * (Briefing_dialog ? Briefing_window_FOV : FRED_DEFAULT_HTL_FOV), gr_screen.aspect*(float) gr_screen.clip_width / (float) gr_screen.clip_height, 1.0f, FRED_DEAFULT_HTL_DRAW_DIST); gr_set_view_matrix(&Eye_position, &Eye_matrix); } void fred_render_grid(grid *gridp) { int i, ncols, nrows; fred_enable_htl(); gr_zbuffer_set(0); if (!Fred_grid_colors_inited) { Fred_grid_colors_inited = 1; gr_init_alphacolor(&Fred_grid_dark_aa, 64, 64, 64, 255); gr_init_alphacolor(&Fred_grid_bright_aa, 128, 128, 128, 255); gr_init_color(&Fred_grid_dark, 64, 64, 64); gr_init_color(&Fred_grid_bright, 128, 128, 128); } ncols = gridp->ncols; nrows = gridp->nrows; if (double_fine_gridlines) { ncols *= 2; nrows *= 2; } if (Aa_gridlines) gr_set_color_fast(&Fred_grid_dark_aa); else gr_set_color_fast(&Fred_grid_dark); // Draw the column lines. for (i = 0; i <= ncols; i++) { g3_draw_htl_line(&gridp->gpoints1[i], &gridp->gpoints2[i]); } // Draw the row lines. for (i = 0; i <= nrows; i++) { g3_draw_htl_line(&gridp->gpoints3[i], &gridp->gpoints4[i]); } ncols = gridp->ncols / 2; nrows = gridp->nrows / 2; // now draw the larger, brighter gridlines that is x10 the scale of smaller one. if (Aa_gridlines) gr_set_color_fast(&Fred_grid_bright_aa); else gr_set_color_fast(&Fred_grid_bright); for (i = 0; i <= ncols; i++) { g3_draw_htl_line(&gridp->gpoints5[i], &gridp->gpoints6[i]); } for (i = 0; i <= nrows; i++) { g3_draw_htl_line(&gridp->gpoints7[i], &gridp->gpoints8[i]); } fred_disable_htl(); gr_zbuffer_set(1); } void fred_render_init() { vec3d f, u, r; physics_init(&view_physics); view_physics.max_vel.xyz.z = 5.0f; //forward/backward view_physics.max_rotvel.xyz.x = 1.5f; //pitch memset(&view_controls, 0, sizeof(control_info)); vm_vec_make(&view_pos, 0.0f, 150.0f, -200.0f); vm_vec_make(&f, 0.0f, -0.5f, 0.866025404f); // 30 degree angle vm_vec_make(&u, 0.0f, 0.866025404f, 0.5f); vm_vec_make(&r, 1.0f, 0.0f, 0.0f); vm_vector_2_matrix(&view_orient, &f, &u, &r); The_grid = create_default_grid(); maybe_create_new_grid(The_grid, &view_pos, &view_orient, 1); // vm_set_identity(&view_orient); gr_init_alphacolor(&colour_white, 255, 255, 255, 255); gr_init_alphacolor(&colour_green, 0, 200, 0, 255); gr_init_alphacolor(&colour_yellow_green, 200, 255, 0, 255); gr_init_alphacolor(&colour_black, 0, 0, 0, 255); } void game_do_frame() { int key, cmode; vec3d viewer_position, control_pos; object *objp; matrix control_orient; inc_mission_time(); // sync all timestamps across the entire frame timer_start_frame(); viewer_position = my_orient.vec.fvec; vm_vec_scale(&viewer_position, my_pos.xyz.z); if ((viewpoint == 1) && !query_valid_object(view_obj)) viewpoint = 0; //If the music player dialog is open and music is selected if (Music_player_dialog.IsPlayerActive()) { Music_player_dialog.DoFrame(); } key = key_inkey(); process_system_keys(key); cmode = Control_mode; if ((viewpoint == 1) && !cmode) cmode = 2; control_pos = Last_control_pos; control_orient = Last_control_orient; // if ((key & KEY_MASK) == key) // unmodified switch (cmode) { case 0: // Control the viewer's location and orientation process_controls(&view_pos, &view_orient, f2fl(Frametime), key, 1); control_pos = view_pos; control_orient = view_orient; break; case 2: // Control viewpoint object if (!Objects[view_obj].flags[Object::Object_Flags::Locked_from_editing]) { process_controls(&Objects[view_obj].pos, &Objects[view_obj].orient, f2fl(Frametime), key); object_moved(&Objects[view_obj]); control_pos = Objects[view_obj].pos; control_orient = Objects[view_obj].orient; } break; case 1: // Control the current object's location and orientation if (query_valid_object() && !Objects[cur_object_index].flags[Object::Object_Flags::Locked_from_editing]) { vec3d delta_pos, leader_old_pos; matrix leader_orient, leader_transpose, tmp; object *leader; leader = &Objects[cur_object_index]; leader_old_pos = leader->pos; // save original position leader_orient = leader->orient; // save original orientation vm_copy_transpose(&leader_transpose, &leader_orient); process_controls(&leader->pos, &leader->orient, f2fl(Frametime), key); vm_vec_sub(&delta_pos, &leader->pos, &leader_old_pos); // get position change control_pos = leader->pos; control_orient = leader->orient; objp = GET_FIRST(&obj_used_list); while (objp != END_OF_LIST(&obj_used_list)) { Assert(objp->type != OBJ_NONE); if ((objp->flags[Object::Object_Flags::Marked]) && (cur_object_index != OBJ_INDEX(objp))) { if (Group_rotate) { matrix rot_trans; vec3d tmpv1, tmpv2; // change rotation matrix to rotate in opposite direction. This rotation // matrix is what the leader ship has rotated by. vm_copy_transpose(&rot_trans, &view_physics.last_rotmat); // get point relative to our point of rotation (make POR the origin). Since // only the leader has been moved yet, and not the objects, we have to use // the old leader's position. vm_vec_sub(&tmpv1, &objp->pos, &leader_old_pos); // convert point from real-world coordinates to leader's relative coordinate // system (z=forward vec, y=up vec, x=right vec vm_vec_rotate(&tmpv2, &tmpv1, &leader_orient); // now rotate the point by the transpose from above. vm_vec_rotate(&tmpv1, &tmpv2, &rot_trans); // convert point back into real-world coordinates vm_vec_rotate(&tmpv2, &tmpv1, &leader_transpose); // and move origin back to real-world origin. Object is now at its correct // position. Note we used the leader's new position, instead of old position. vm_vec_add(&objp->pos, &leader->pos, &tmpv2); // Now fix the object's orientation to what it should be. vm_matrix_x_matrix(&tmp, &objp->orient, &view_physics.last_rotmat); vm_orthogonalize_matrix(&tmp); // safety check objp->orient = tmp; } else { vm_vec_add2(&objp->pos, &delta_pos); vm_matrix_x_matrix(&tmp, &objp->orient, &view_physics.last_rotmat); objp->orient = tmp; } } objp = GET_NEXT(objp); } objp = GET_FIRST(&obj_used_list); while (objp != END_OF_LIST(&obj_used_list)) { if (objp->flags[Object::Object_Flags::Marked]) object_moved(objp); objp = GET_NEXT(objp); } set_modified(); } break; default: Assert(0); } if (Lookat_mode && query_valid_object()) { vec3d center = selection_midpoint(); float dist = vm_vec_dist(&view_pos, ¢er); vm_vec_scale_add(&view_pos, ¢er, &view_orient.vec.fvec, -dist); } switch (viewpoint) { case 0: eye_pos = view_pos; eye_orient = view_orient; break; case 1: eye_pos = Objects[view_obj].pos; eye_orient = Objects[view_obj].orient; break; default: Assert(0); } maybe_create_new_grid(The_grid, &eye_pos, &eye_orient); if (Cursor_over != Last_cursor_over) { Last_cursor_over = Cursor_over; Update_window = 1; } // redraw screen if controlled object moved or rotated if (vm_vec_cmp(&control_pos, &Last_control_pos) || vm_matrix_cmp(&control_orient, &Last_control_orient)) { Update_window = 1; Last_control_pos = control_pos; Last_control_orient = control_orient; } // redraw screen if current viewpoint moved or rotated if (vm_vec_cmp(&eye_pos, &Last_eye_pos) || vm_matrix_cmp(&eye_orient, &Last_eye_orient)) { Update_window = 1; Last_eye_pos = eye_pos; Last_eye_orient = eye_orient; } } vec3d* get_subsystem_world_pos2(object* parent_obj, ship_subsys* subsys, vec3d* world_pos) { if (subsys == NULL) { *world_pos = parent_obj->pos; return world_pos; } vm_vec_unrotate(world_pos, &subsys->system_info->pnt, &parent_obj->orient); vm_vec_add2(world_pos, &parent_obj->pos); return world_pos; } int get_subsys_bounding_rect(object *ship_obj, ship_subsys *subsys, int *x1, int *x2, int *y1, int *y2) { if (subsys != NULL) { vertex subobj_vertex; vec3d subobj_pos; get_subsystem_world_pos2(ship_obj, subsys, &subobj_pos); g3_rotate_vertex(&subobj_vertex, &subobj_pos); g3_project_vertex(&subobj_vertex); if (subobj_vertex.flags & PF_OVERFLOW) // if overflow, no point in drawing brackets return 0; int bound_rc; bound_rc = subobj_find_2d_bound(subsys->system_info->radius, &ship_obj->orient, &subobj_pos, x1, y1, x2, y2); if (bound_rc != 0) return 0; return 1; } return 0; } void hilight_bitmap() { /* int i; vertex p[4]; if (Starfield_bitmaps[Cur_bitmap].bitmap_index == -1) // can't draw if no bitmap return; for (i=0; i<4; i++) { g3_rotate_faraway_vertex(&p[i], &Starfield_bitmaps[Cur_bitmap].points[i]); if (p[i].codes & CC_BEHIND) return; g3_project_vertex(&p[i]); if (p[i].flags & PF_OVERFLOW) return; } gr_set_color(255, 255, 255); g3_draw_line(&p[0], &p[1]); g3_draw_line(&p[1], &p[2]); g3_draw_line(&p[2], &p[3]); g3_draw_line(&p[3], &p[0]); */ } void inc_mission_time() { fix thistime; thistime = timer_get_fixed_seconds(); if (!lasttime) { Frametime = F1_0 / 30; } else { Frametime = thistime - lasttime; } if (Frametime > MAX_FRAMETIME) { Frametime = MAX_FRAMETIME; } else if (Frametime < 0) { Frametime = thistime = MIN_FRAMETIME; } else if (Frametime < MIN_FRAMETIME) { if (!Cmdline_NoFPSCap) { thistime = MIN_FRAMETIME - Frametime; Sleep(DWORD(f2fl(thistime) * 1000.0f)); thistime = timer_get_fixed_seconds(); } Frametime = MIN_FRAMETIME; } Missiontime += Frametime; lasttime = thistime; } void level_controlled() { int cmode, count = 0; object *objp; cmode = Control_mode; if ((viewpoint == 1) && !cmode) cmode = 2; switch (cmode) { case 0: // Control the viewer's location and orientation level_object(&view_orient); break; case 2: // Control viewpoint object if (!Objects[view_obj].flags[Object::Object_Flags::Locked_from_editing]) { level_object(&Objects[view_obj].orient); object_moved(&Objects[view_obj]); set_modified(); FREDDoc_ptr->autosave("level object"); } break; case 1: // Control the current object's location and orientation objp = GET_FIRST(&obj_used_list); while (objp != END_OF_LIST(&obj_used_list)) { if (objp->flags[Object::Object_Flags::Marked]) level_object(&objp->orient); objp = GET_NEXT(objp); } objp = GET_FIRST(&obj_used_list); while (objp != END_OF_LIST(&obj_used_list)) { if (objp->flags[Object::Object_Flags::Marked]) { object_moved(objp); count++; } objp = GET_NEXT(objp); } if (count) { if (count > 1) FREDDoc_ptr->autosave("level objects"); else FREDDoc_ptr->autosave("level object"); set_modified(); } break; } return; } void level_object(matrix *orient) { vec3d u; u = orient->vec.uvec = The_grid->gmatrix.vec.uvec; if (u.xyz.x) // y-z plane { orient->vec.fvec.xyz.x = orient->vec.rvec.xyz.x = 0.0f; } else if (u.xyz.y) { // x-z plane orient->vec.fvec.xyz.y = orient->vec.rvec.xyz.y = 0.0f; } else if (u.xyz.z) { // x-y plane orient->vec.fvec.xyz.z = orient->vec.rvec.xyz.z = 0.0f; } vm_fix_matrix(orient); } void move_mouse(int btn, int mdx, int mdy) { int dx, dy; dx = mdx - last_x; dy = mdy - last_y; last_x = mdx; last_y = mdy; if (btn & 1) { matrix tempm, mousem; if (dx || dy) { vm_trackball(dx, dy, &mousem); vm_matrix_x_matrix(&tempm, &trackball_orient, &mousem); trackball_orient = tempm; view_orient = trackball_orient; } } if (btn & 2) { my_pos.xyz.z += (float) dy; } } vec3d selection_midpoint() { vec3d center; float radius; if (Orbit_selection_midpoint && Marked > 1 && get_object_bounds(true, ¢er, &radius)) { return center; } return Objects[cur_object_index].pos; } // ---------- Orbit camera functions ---------- vec3d orbit_camera_get_pivot() { vec3d pivot; if (query_valid_object()) { // Pivot on the selection pivot = selection_midpoint(); } else if (!The_grid) { // Pivot on the origin, if no grid pivot = ZERO_VECTOR; } else { // Intersect camera forward ray with the grid plane vec3d *grid_normal = &The_grid->gmatrix.vec.uvec; float denom = vm_vec_dot(grid_normal, &view_orient.vec.fvec); if (fl_abs(denom) > 0.0001f) { // t = -(dot(normal, view_pos) + planeD) / dot(normal, fvec) float t = -(vm_vec_dot(grid_normal, &view_pos) + The_grid->planeD) / denom; if (t > 0.0f) { vm_vec_scale_add(&pivot, &view_pos, &view_orient.vec.fvec, t); } else { pivot = The_grid->center; } } else { // Camera is parallel to grid plane; fall back to grid center pivot = The_grid->center; } } return pivot; } void orbit_camera_init_from_current_view(const vec3d *pivot, const matrix *grid_orient) { Orbit_pivot = pivot ? *pivot : vmd_zero_vector; Orbit_grid_orient = grid_orient ? *grid_orient : vmd_identity_matrix; vec3d offset; vm_vec_sub(&offset, &view_pos, &Orbit_pivot); Orbit_distance = vm_vec_mag(&offset); if (Orbit_distance < 1.0f) Orbit_distance = 100.0f; // Transform offset into grid-local frame for angle decomposition // In local frame, Y is always "up" (the grid plane normal) vec3d local_offset; vm_vec_rotate(&local_offset, &offset, &Orbit_grid_orient); Orbit_phi = acosf(std::clamp(local_offset.xyz.y / Orbit_distance, -1.0f, 1.0f)); Orbit_theta = atan2f(local_offset.xyz.z, local_offset.xyz.x); Orbit_active = true; } void orbit_camera_apply() { float sp = sinf(Orbit_phi); // Build offset in grid-local coordinates (Y = up/normal) vec3d local_pos; local_pos.xyz.x = sp * cosf(Orbit_theta); local_pos.xyz.y = cosf(Orbit_phi); local_pos.xyz.z = sp * sinf(Orbit_theta); // Transform back to world space vec3d world_offset; vm_vec_unrotate(&world_offset, &local_pos, &Orbit_grid_orient); vm_vec_scale(&world_offset, Orbit_distance); vm_vec_add(&view_pos, &Orbit_pivot, &world_offset); // Point camera at pivot, using grid's up vector vec3d look_dir; vm_vec_sub(&look_dir, &Orbit_pivot, &view_pos); if (vm_vec_mag(&look_dir) > 0.001f) { vec3d grid_up = Orbit_grid_orient.vec.uvec; vm_vector_2_matrix(&view_orient, &look_dir, &grid_up, nullptr); } } void orbit_camera_rotate(int dx, int dy) { float rot_scale = physics_rot / 300.0f; Orbit_theta += dx / 100.0f * rot_scale; Orbit_phi += dy / 100.0f * rot_scale; CLAMP(Orbit_phi, 0.01f, PI - 0.01f); orbit_camera_apply(); } void orbit_camera_pan(int dx, int dy) { float speed_factor = 1.0f + (physics_speed - 1) / 499.0f * 9.0f; float pan_scale = Orbit_distance * 0.002f * speed_factor; vec3d pan_delta; vm_vec_copy_scale(&pan_delta, &view_orient.vec.rvec, -(float)dx * pan_scale); vm_vec_scale_add2(&pan_delta, &view_orient.vec.uvec, (float)dy * pan_scale); vm_vec_add2(&Orbit_pivot, &pan_delta); orbit_camera_apply(); } void orbit_camera_zoom(float delta) { float zoom_speed = 1.0f + (physics_speed - 1) / 499.0f * 4.0f; Orbit_distance *= powf(2.0f, delta * zoom_speed); CLAMP(Orbit_distance, 1.0f, 10000000.0f); orbit_camera_apply(); } // ---------- End orbit camera functions ---------- int object_check_collision(object *objp, vec3d *p0, vec3d *p1, vec3d *hitpos) { mc_info mc; if ((objp->type == OBJ_NONE) || (objp->type == OBJ_POINT)) return 0; if ((objp->type == OBJ_WAYPOINT) && !Show_waypoints) return 0; if ((objp->type == OBJ_START) && !Show_starts) return 0; if ((objp->type == OBJ_SHIP) || (objp->type == OBJ_START)) { if (!Show_ships) return 0; if (!Show_iff[Ships[objp->instance].team]) return 0; } if (objp->type == OBJ_PROP) { if (!Props[objp->instance].has_value()) return 0; } if (objp->flags.any_of(Object::Object_Flags::Hidden, Object::Object_Flags::Locked_from_editing)) return 0; mc.model_instance_num = -1; if ((Show_ship_models || Show_outlines) && (objp->type == OBJ_SHIP || objp->type == OBJ_START)) { auto& shp = Ships[objp->instance]; mc.model_num = Ship_info[shp.ship_info_index].model_num; // Fill in the model to check mc.model_instance_num = shp.model_instance_num; } else if ((Show_ship_models || Show_outlines) && (objp->type == OBJ_PROP)) { auto& prp = Props[objp->instance].value(); mc.model_num = Prop_info[prp.prop_info_index].model_num; // Fill in the model to check mc.model_instance_num = prp.model_instance_num; } else return fvi_ray_sphere(hitpos, p0, p1, &objp->pos, (objp->radius > 0.1f) ? objp->radius : LOLLIPOP_SIZE); mc.orient = &objp->orient; // The object's orient mc.pos = &objp->pos; // The object's position mc.p0 = p0; // Point 1 of ray to check mc.p1 = p1; // Point 2 of ray to check mc.flags = MC_CHECK_MODEL | MC_CHECK_RAY; // flags model_collide(&mc); *hitpos = mc.hit_point_world; if (mc.num_hits < 1) { // check shield mc.orient = &objp->orient; // The object's orient mc.pos = &objp->pos; // The object's position mc.p0 = p0; // Point 1 of ray to check mc.p1 = p1; // Point 2 of ray to check mc.flags = MC_CHECK_SHIELD; // flags model_collide(&mc); *hitpos = mc.hit_point_world; } return mc.num_hits; } void process_controls(vec3d *pos, matrix *orient, float frametime, int key, int mode) { static std::unique_ptr spacemouse = io::spacemouse::SpaceMouse::searchSpaceMice(0); bool wantsUpdate = false; if (Flying_controls_mode) { grid_read_camera_controls(&view_controls, frametime); if (spacemouse != nullptr) { auto spacemouse_movement = spacemouse->getMovement(); spacemouse_movement.handleNonlinearities(Fred_spacemouse_nonlinearity); view_controls.pitch += spacemouse_movement.rotation.p; view_controls.vertical += spacemouse_movement.translation.xyz.z; view_controls.heading += spacemouse_movement.rotation.h; view_controls.sideways += spacemouse_movement.translation.xyz.x; view_controls.bank += spacemouse_movement.rotation.b; view_controls.forward += spacemouse_movement.translation.xyz.y; } if (key_get_shift_status()) memset(&view_controls, 0, sizeof(control_info)); wantsUpdate = (fabs(view_controls.pitch) > (frametime / 100)) || (fabs(view_controls.vertical) > (frametime / 100)) || (fabs(view_controls.heading) > (frametime / 100)) || (fabs(view_controls.sideways) > (frametime / 100)) || (fabs(view_controls.bank) > (frametime / 100)) || (fabs(view_controls.forward) > (frametime / 100)); if (wantsUpdate) Update_window = 1; flFrametime = frametime; physics_read_flying_controls(orient, &view_physics, &view_controls, flFrametime); if (mode) physics_sim_editor(pos, orient, &view_physics, frametime); else physics_sim(pos, orient, &view_physics, &vmd_zero_vector, frametime); } else { vec3d movement_vec, rel_movement_vec; angles rotangs; matrix newmat, rotmat; process_movement_keys(key, &movement_vec, &rotangs); if (spacemouse != nullptr) { auto spacemouse_movement = spacemouse->getMovement(); spacemouse_movement.handleNonlinearities(Fred_spacemouse_nonlinearity); movement_vec += spacemouse_movement.translation; rotangs += spacemouse_movement.rotation; } vm_vec_rotate(&rel_movement_vec, &movement_vec, &The_grid->gmatrix); vm_vec_add2(pos, &rel_movement_vec); vm_angles_2_matrix(&rotmat, &rotangs); if (rotangs.h && Universal_heading) vm_transpose(orient); vm_matrix_x_matrix(&newmat, orient, &rotmat); *orient = newmat; if (rotangs.h && Universal_heading) vm_transpose(orient); wantsUpdate = !IS_VEC_NULL(&movement_vec) || rotangs.p != 0.0f || rotangs.h != 0.0f || rotangs.b != 0.0f; } // Invalidate orbit camera state when the user moves the camera another way if (wantsUpdate) Orbit_active = false; } void process_movement_keys(int key, vec3d *mvec, angles *angs) { int raw_key; mvec->xyz.x = 0.0f; mvec->xyz.y = 0.0f; mvec->xyz.z = 0.0f; angs->p = 0.0f; angs->b = 0.0f; angs->h = 0.0f; raw_key = key & 0xff; switch (raw_key) { case KEY_PAD1: mvec->xyz.x += -1.0f; break; case KEY_PAD3: mvec->xyz.x += +1.0f; break; case KEY_PADPLUS: mvec->xyz.y += -1.0f; break; case KEY_PADMINUS: mvec->xyz.y += +1.0f; break; case KEY_A: mvec->xyz.z += +1.0f; break; case KEY_Z: mvec->xyz.z += -1.0f; break; case KEY_PAD4: angs->h += -0.1f; break; case KEY_PAD6: angs->h += +0.1f; break; case KEY_PAD8: angs->p += -0.1f; break; case KEY_PAD2: angs->p += +0.1f; break; case KEY_PAD7: angs->b += -0.1f; break; case KEY_PAD9: angs->b += +0.1f; break; } if (key & KEY_SHIFTED) { vm_vec_scale(mvec, 5.0f); angs->p *= 5.0f; angs->b *= 5.0f; angs->h *= 5.0f; } } void process_system_keys(int key) { // mprintf(("Key = %d\n", key)); switch (key) { case KEY_LAPOSTRO: CFREDView::GetView()->cycle_constraint(); break; case KEY_R: // for some stupid reason, an accelerator for 'R' doesn't work. Editing_mode = 2; break; case KEY_SPACEBAR: Selection_lock = !Selection_lock; break; case KEY_ESC: if (button_down) cancel_drag(); break; } } void render_active_rect(void) { if (box_marking) { gr_set_color(255, 255, 255); gr_line(marking_box.x1, marking_box.y1, marking_box.x1, marking_box.y2); gr_line(marking_box.x1, marking_box.y2, marking_box.x2, marking_box.y2); gr_line(marking_box.x2, marking_box.y2, marking_box.x2, marking_box.y1); gr_line(marking_box.x2, marking_box.y1, marking_box.x1, marking_box.y1); } } void render_compass(void) { vec3d v, eye = { 0.0f }; if (!Show_compass) return; gr_set_clip(gr_screen.max_w - 100, 0, 100, 100); g3_start_frame(0); // ** Accounted for // required !!! vm_vec_scale_add2(&eye, &eye_orient.vec.fvec, -1.5f); g3_set_view_matrix(&eye, &eye_orient, 1.0f); v.xyz.x = 1.0f; v.xyz.y = v.xyz.z = 0.0f; if (vm_vec_dot(&eye, &v) < 0.0f) gr_set_color(159, 20, 20); else gr_set_color(255, 32, 32); draw_compass_arrow(&v); v.xyz.y = 1.0f; v.xyz.x = v.xyz.z = 0.0f; if (vm_vec_dot(&eye, &v) < 0.0f) gr_set_color(20, 159, 20); else gr_set_color(32, 255, 32); draw_compass_arrow(&v); v.xyz.z = 1.0f; v.xyz.x = v.xyz.y = 0.0f; if (vm_vec_dot(&eye, &v) < 0.0f) gr_set_color(20, 20, 159); else gr_set_color(32, 32, 255); draw_compass_arrow(&v); g3_end_frame(); // ** Accounted for } void render_frame() { GR_DEBUG_SCOPE("Fred render frame"); char buf[256]; int x, y, w, h, inst; vec3d pos; vertex v; angles a, a_deg; //a is in rads, a_deg is in degrees g3_end_frame(); // ** Accounted for gr_reset_clip(); gr_clear(); if (Briefing_dialog) { CRect rect; Fred_main_wnd->GetClientRect(rect); True_rw = rect.Width(); True_rh = rect.Height(); if (Fixed_briefing_size) { True_rw = Briefing_window_resolution[0]; True_rh = Briefing_window_resolution[1]; } else { if ((float) True_rh / (float) True_rw > (float) Briefing_window_resolution[1] / (float) Briefing_window_resolution[0]) { True_rh = (int) ((float) Briefing_window_resolution[1] * (float) True_rw / (float) Briefing_window_resolution[0]); } else { // Fred is wider than briefing window True_rw = (int) ((float) Briefing_window_resolution[0] * (float) True_rh / (float) Briefing_window_resolution[1]); } } g3_start_frame(0); // ** Accounted for gr_set_color(255, 255, 255); gr_line(0, True_rh, True_rw, True_rh); gr_line(True_rw, 0, True_rw, True_rh); g3_end_frame(); // ** Accounted for gr_set_clip(0, 0, True_rw, True_rh); } g3_start_frame(1); // ** Accounted for // 1 means use zbuffering font::set_font(font::FONT1); light_reset(); g3_set_view_matrix(&eye_pos, &eye_orient, (Briefing_dialog ? Briefing_window_FOV : FRED_DEFAULT_HTL_FOV)); Viewer_pos = eye_pos; // for starfield code fred_enable_htl(); if (Bg_bitmap_dialog) { stars_draw(Show_stars, 1, Show_stars, 0, 0); } else { stars_draw(Show_stars, Show_stars, Show_stars, 0, 0); } fred_disable_htl(); if (Show_horizon) { gr_set_color(128, 128, 64); g3_draw_horizon_line(); } if (Show_asteroid_field) { gr_set_color(192, 96, 16); draw_asteroid_field(); } if (Show_grid) fred_render_grid(The_grid); if (Bg_bitmap_dialog) hilight_bitmap(); gr_set_color(0, 0, 64); render_models(); if (Show_distances) { display_distances(); } display_ship_info(); display_active_ship_subsystem(); render_active_rect(); if (query_valid_object(Cursor_over)) { // display a tool-tip like infobox pos = Objects[Cursor_over].pos; inst = Objects[Cursor_over].instance; if ((Objects[Cursor_over].type == OBJ_SHIP) || (Objects[Cursor_over].type == OBJ_START)) { vm_extract_angles_matrix(&a, &Objects[Cursor_over].orient); a_deg.h = a.h * CONVERT_DEGREES; // convert angles to more readable degrees a_deg.p = a.p * CONVERT_DEGREES; a_deg.b = a.b * CONVERT_DEGREES; sprintf(buf, "%s\n%s\n( %.1f , %.1f , %.1f ) \nPitch: %.2f\nBank: %.2f\nHeading: %.2f", Ships[inst].ship_name, Ship_info[Ships[inst].ship_info_index].short_name, pos.xyz.x, pos.xyz.y, pos.xyz.z, a_deg.p, a_deg.b, a_deg.h); } else if (Objects[Cursor_over].type == OBJ_WAYPOINT) { int idx; waypoint_list *wp_list = find_waypoint_list_with_instance(inst, &idx); Assert(wp_list != NULL); sprintf(buf, "%s\nWaypoint %d\n( %.1f , %.1f , %.1f ) ", wp_list->get_name(), idx + 1, pos.xyz.x, pos.xyz.y, pos.xyz.z); } else if (Objects[Cursor_over].type == OBJ_POINT) { sprintf(buf, "Briefing icon\n( %.1f , %.1f , %.1f ) ", pos.xyz.x, pos.xyz.y, pos.xyz.z); } else sprintf(buf, "( %.1f , %.1f , %.1f ) ", pos.xyz.x, pos.xyz.y, pos.xyz.z); g3_rotate_vertex(&v, &pos); if (!(v.codes & CC_BEHIND)) if (!(g3_project_vertex(&v) & PF_OVERFLOW)) { GR_DEBUG_SCOPE("Draw tooltip"); gr_get_string_size(&w, &h, buf); // scale the box to match the scaled label text w = fl2i(w * Fred_label_font_scale); h = fl2i(h * Fred_label_font_scale); x = (int) v.screen.xyw.x; y = (int) v.screen.xyw.y + 20; gr_set_color_fast(&colour_white); gr_rect(x - 7, y - 6, w + 8, h + 7); gr_set_color_fast(&colour_black); gr_rect(x - 5, y - 5, w + 5, h + 5); gr_set_color_fast(&colour_white); gr_string(x, y, buf, GR_RESIZE_FULL, Fred_label_font_scale); } } gr_set_color(0, 160, 0); fred_enable_htl(); jumpnode_render_all(); fred_disable_htl(); sprintf(buf, "(%.1f,%.1f,%.1f)", eye_pos.xyz.x, eye_pos.xyz.y, eye_pos.xyz.z); gr_get_string_size(&w, &h, buf); w = fl2i(w * Fred_label_font_scale); gr_set_color_fast(&colour_white); gr_string(gr_screen.max_w - w - 2, 2, buf, GR_RESIZE_FULL, Fred_label_font_scale); g3_end_frame(); // ** Accounted for render_compass(); gr_flip(); gr_reset_clip(); if (Briefing_dialog) gr_set_clip(0, 0, True_rw, True_rh); g3_start_frame(0); // ** Accounted for g3_set_view_matrix(&eye_pos, &eye_orient, (Briefing_dialog ? Briefing_window_FOV : FRED_DEFAULT_HTL_FOV)); } void render_models(void) { GR_DEBUG_SCOPE("Render models"); gr_set_color_fast(&colour_white); render_count = 0; if ((ENVMAP == -1) && strlen(The_mission.envmap_name)) { ENVMAP = bm_load(The_mission.envmap_name); } bool f = false; fred_enable_htl(); obj_render_all(render_one_model_htl, &f); fred_disable_htl(); if (Briefing_dialog) { obj_render_all(render_one_model_briefing_screen, &f); Briefing_dialog->batch_render(); } } void render_model_x(const vec3d *pos, const grid *gridp, int col_scheme) { vec3d gpos; // Location of point on grid. vec3d tpos; float dxz; plane tplane; const vec3d *gv; if (!Show_grid_positions) return; tplane.A = gridp->gmatrix.vec.uvec.xyz.x; tplane.B = gridp->gmatrix.vec.uvec.xyz.y; tplane.C = gridp->gmatrix.vec.uvec.xyz.z; tplane.D = gridp->planeD; compute_point_on_plane(&gpos, &tplane, pos); dxz = vm_vec_dist(pos, &gpos) / 8.0f; gv = &gridp->gmatrix.vec.uvec; if (gv->xyz.x * pos->xyz.x + gv->xyz.y * pos->xyz.y + gv->xyz.z * pos->xyz.z < -gridp->planeD) gr_set_color(0, 127, 0); else gr_set_color(192, 192, 192); rpd_line(&gpos, pos); // Line from grid to object center. tpos = gpos; vm_vec_scale_add2(&gpos, &gridp->gmatrix.vec.rvec, -dxz / 2); vm_vec_scale_add2(&gpos, &gridp->gmatrix.vec.fvec, -dxz / 2); vm_vec_scale_add2(&tpos, &gridp->gmatrix.vec.rvec, dxz / 2); vm_vec_scale_add2(&tpos, &gridp->gmatrix.vec.fvec, dxz / 2); rpd_line(&gpos, &tpos); vm_vec_scale_add2(&gpos, &gridp->gmatrix.vec.rvec, dxz); vm_vec_scale_add2(&tpos, &gridp->gmatrix.vec.rvec, -dxz); rpd_line(&gpos, &tpos); } void render_model_x_htl(vec3d *pos, grid *gridp, int col_scheme) { vec3d gpos; // Location of point on grid. vec3d tpos; float dxz; plane tplane; vec3d *gv; if (!Show_grid_positions) return; tplane.A = gridp->gmatrix.vec.uvec.xyz.x; tplane.B = gridp->gmatrix.vec.uvec.xyz.y; tplane.C = gridp->gmatrix.vec.uvec.xyz.z; tplane.D = gridp->planeD; compute_point_on_plane(&gpos, &tplane, pos); dxz = vm_vec_dist(pos, &gpos) / 8.0f; gv = &gridp->gmatrix.vec.uvec; if (gv->xyz.x * pos->xyz.x + gv->xyz.y * pos->xyz.y + gv->xyz.z * pos->xyz.z < -gridp->planeD) gr_set_color(0, 127, 0); else gr_set_color(192, 192, 192); g3_draw_htl_line(&gpos, pos); // Line from grid to object center. tpos = gpos; vm_vec_scale_add2(&gpos, &gridp->gmatrix.vec.rvec, -dxz / 2); vm_vec_scale_add2(&gpos, &gridp->gmatrix.vec.fvec, -dxz / 2); vm_vec_scale_add2(&tpos, &gridp->gmatrix.vec.rvec, dxz / 2); vm_vec_scale_add2(&tpos, &gridp->gmatrix.vec.fvec, dxz / 2); g3_draw_htl_line(&gpos, &tpos); vm_vec_scale_add2(&gpos, &gridp->gmatrix.vec.rvec, dxz); vm_vec_scale_add2(&tpos, &gridp->gmatrix.vec.rvec, -dxz); g3_draw_htl_line(&gpos, &tpos); } void render_one_model_briefing_screen(object *objp) { if (objp->type == OBJ_POINT) { if (objp->instance != BRIEFING_LOOKAT_POINT_ID) { Assert(Briefing_dialog); Briefing_dialog->draw_icon(objp); render_model_x_htl(&objp->pos, The_grid); render_count++; } } } void render_one_model_htl(object *objp) { int z; uint debug_flags = 0; object *o2; Assert(objp->type != OBJ_NONE); // if this object isn't fully created yet, don't render it if (objp->type == OBJ_SHIP && Ships[objp->instance].create_time == 0) return; if (objp->type == OBJ_PROP && (!Props[objp->instance].has_value() || Props[objp->instance].value().create_time == 0)) return; if (objp->type == OBJ_JUMP_NODE) { return; } if ((objp->type == OBJ_WAYPOINT) && !Show_waypoints) return; if ((objp->type == OBJ_START) && !Show_starts) return; if ((objp->type == OBJ_SHIP) || (objp->type == OBJ_START)) { if (!Show_ships) return; if (!Show_iff[Ships[objp->instance].team]) return; } if (objp->flags[Object::Object_Flags::Hidden]) return; rendering_order[render_count] = OBJ_INDEX(objp); Fred_outline = 0; if (!Draw_outlines_on_selected_ships && ((OBJ_INDEX(objp) == cur_object_index) || (objp->flags[Object::Object_Flags::Marked]))) /* don't draw the outlines we would normally draw */; else if ((OBJ_INDEX(objp) == cur_object_index) && !Bg_bitmap_dialog) Fred_outline = FRED_COLOUR_WHITE; else if ((objp->flags[Object::Object_Flags::Marked]) && !Bg_bitmap_dialog) // is it a marked object? Fred_outline = FRED_COLOUR_YELLOW_GREEN; else if ((objp->type == OBJ_SHIP) && Show_outlines) { color *iff_color = iff_get_color_by_team_and_object(Ships[objp->instance].team, -1, 1, objp); Fred_outline = (iff_color->red << 16) | (iff_color->green << 8) | (iff_color->blue); } else if ((objp->type == OBJ_START) && Show_outlines) { Fred_outline = 0x007f00; } else Fred_outline = 0; if (objp->type == OBJ_PROP) { uint64_t flags = MR_NORMAL; if (!Lighting_on) { flags |= MR_NO_LIGHTING; } if (FullDetail) { flags |= MR_FULL_DETAIL; } z = objp->instance; model_render_params render_info; render_info.set_debug_flags(debug_flags); 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_model_num, propp->model_instance_num, &objp->orient, &objp->pos); render_info.set_detail_level_lock(-1); } // render_info.set_flags(flags); model_render_immediate(&render_info, Prop_info[propp->prop_info_index].model_num, propp->model_instance_num, &objp->orient, &objp->pos); } // build flags if ((Show_ship_models || Show_outlines) && ((objp->type == OBJ_SHIP) || (objp->type == OBJ_START))) { g3_start_instance_matrix(&Eye_position, &Eye_matrix, 0); uint64_t flags = MR_NORMAL; if (Show_dock_points) { debug_flags |= MR_DEBUG_BAY_PATHS | MR_DEBUG_DOCK_POINTS; } if (Show_paths_fred) { debug_flags |= MR_DEBUG_PATHS; } z = objp->instance; model_clear_instance(Ship_info[Ships[z].ship_info_index].model_num); if (!Lighting_on) { flags |= MR_NO_LIGHTING; } if (FullDetail) { flags |= MR_FULL_DETAIL; } model_render_params render_info; render_info.set_debug_flags(debug_flags); 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_model_num, Ships[z].model_instance_num, &objp->orient, &objp->pos); render_info.set_detail_level_lock(-1); } if (Draw_outline_at_warpin_position && (Ships[z].arrival_cue != Locked_sexp_true || Ships[z].arrival_delay > 0) && Ships[z].arrival_cue != Locked_sexp_false && !Ships[z].flags[Ship::Ship_Flags::No_arrival_warp]) { int warp_type = Warp_params[Ships[z].warpin_params_index].warp_type; if (warp_type == WT_DEFAULT || warp_type == WT_KNOSSOS || warp_type == WT_DEFAULT_THEN_KNOSSOS || (warp_type & WT_DEFAULT_WITH_FIREBALL)) { float warpin_dist = shipfx_calculate_arrival_warp_distance(objp); // project the ship forward as far as it should go vec3d warpin_pos; vm_vec_scale_add(&warpin_pos, &objp->pos, &objp->orient.vec.fvec, warpin_dist); render_info.set_color(65, 65, 65); // grey; see rgba_defaults 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, &warpin_pos); } } g3_done_instance(0); if (Show_ship_models) { if (Ship_info[Ships[z].ship_info_index].uses_team_colors) render_info.set_team_color(Ships[z].team_name, Ships[z].secondary_team_name, Ships[z].team_change_timestamp, Ships[z].team_change_time); render_info.set_flags(flags); model_render_immediate(&render_info, Ship_info[Ships[z].ship_info_index].model_num, Ships[z].model_instance_num, &objp->orient, &objp->pos); } } else { int r = 0, g = 0, b = 0; if (objp->type == OBJ_SHIP) { if (!Show_ships) { return; } color *iff_color = iff_get_color_by_team_and_object(Ships[objp->instance].team, -1, 1, objp); r = iff_color->red; g = iff_color->green; b = iff_color->blue; } else if (objp->type == OBJ_START) { r = 0; g = 127; b = 0; } else if (objp->type == OBJ_WAYPOINT) { r = 96; g = 0; b = 112; } else if (objp->type == OBJ_POINT) { if (objp->instance != BRIEFING_LOOKAT_POINT_ID) { Assert(Briefing_dialog); return; } r = 196; g = 32; b = 196; } else if (objp->type == OBJ_PROP) { r = 255; g = 255; b = 255; } else Assert(0); float size = fl_sqrt(vm_vec_dist(&eye_pos, &objp->pos) / 20.0f); if (size < LOLLIPOP_SIZE) size = LOLLIPOP_SIZE; if (Fred_outline) { gr_set_color(__min(r * 2, 255), __min(g * 2, 255), __min(b * 2, 255)); g3_draw_htl_sphere(&objp->pos, size * 1.5f); } else { gr_set_color(r, g, b); g3_draw_htl_sphere(&objp->pos, size); } } if (objp->type == OBJ_WAYPOINT) { for (int j = 0; j < render_count; j++) { o2 = &Objects[rendering_order[j]]; if (o2->type == OBJ_WAYPOINT) { if ((o2->instance == objp->instance - 1) || (o2->instance == objp->instance + 1)) { g3_draw_htl_line(&o2->pos, &objp->pos); } } } } render_model_x_htl(&objp->pos, The_grid); render_count++; } void render_waypoints(void) { vertex v; for (const auto &ii: Waypoint_lists) { const vec3d *prev_vec = nullptr; for (const auto &jj: ii.get_waypoints()) { g3_rotate_vertex(&v, jj.get_pos()); if (!(v.codes & CC_BEHIND)) { if (!(g3_project_vertex(&v) & PF_OVERFLOW)) { if (jj.get_objnum() == cur_waypoint->get_objnum()) gr_set_color(255, 255, 255); else if (Objects[jj.get_objnum()].flags[Object::Object_Flags::Marked]) gr_set_color(160, 255, 0); else gr_set_color(160, 96, 0); g3_draw_sphere(&v, LOLLIPOP_SIZE); if (prev_vec == NULL) gr_set_color(160, 96, 0); else gr_set_color(0, 0, 0); g3_draw_sphere(&v, LOLLIPOP_SIZE * 0.66667f); gr_set_color(160, 96, 0); g3_draw_sphere(&v, LOLLIPOP_SIZE * 0.33333f); } } render_model_x(jj.get_pos(), The_grid, 1); gr_set_color(160, 96, 0); if (prev_vec != NULL) rpd_line(prev_vec, jj.get_pos()); prev_vec = jj.get_pos(); } } } int select_object(int cx, int cy) { int best = -1; double dist, best_dist = 9e99; vec3d p0, p1, v, hitpos; vertex vt; object *ptr; if (Briefing_dialog) { best = Briefing_dialog->check_mouse_hit(cx, cy); if (best >= 0) { if ((Selection_lock && !Objects[best].flags[Object::Object_Flags::Marked]) || Objects[best].flags[Object::Object_Flags::Locked_from_editing]) { return -1; } return best; } } /* gr_reset_clip(); g3_start_frame(0); //////////////// g3_set_view_matrix(&eye_pos, &eye_orient, 0.5f);*/ // Get 3d vector specified by mouse cursor location. g3_point_to_vec(&v, cx, cy); // g3_end_frame(); if (!v.xyz.x && !v.xyz.y && !v.xyz.z) // zero vector return -1; p0 = view_pos; vm_vec_scale_add(&p1, &p0, &v, 100.0f); ptr = GET_FIRST(&obj_used_list); while (ptr != END_OF_LIST(&obj_used_list)) { if (object_check_collision(ptr, &p0, &p1, &hitpos)) { hitpos.xyz.x = ptr->pos.xyz.x - view_pos.xyz.x; hitpos.xyz.y = ptr->pos.xyz.y - view_pos.xyz.y; hitpos.xyz.z = ptr->pos.xyz.z - view_pos.xyz.z; dist = hitpos.xyz.x * hitpos.xyz.x + hitpos.xyz.y * hitpos.xyz.y + hitpos.xyz.z * hitpos.xyz.z; if (dist < best_dist) { best = OBJ_INDEX(ptr); best_dist = dist; } } ptr = GET_NEXT(ptr); } if (best >= 0) { if ((Selection_lock && !Objects[best].flags[Object::Object_Flags::Marked]) || Objects[best].flags[Object::Object_Flags::Locked_from_editing]) { return -1; } return best; } ptr = GET_FIRST(&obj_used_list); while (ptr != END_OF_LIST(&obj_used_list)) { g3_rotate_vertex(&vt, &ptr->pos); if (!(vt.codes & CC_BEHIND)) if (!(g3_project_vertex(&vt) & PF_OVERFLOW)) { hitpos.xyz.x = vt.screen.xyw.x - cx; hitpos.xyz.y = vt.screen.xyw.y - cy; dist = hitpos.xyz.x * hitpos.xyz.x + hitpos.xyz.y * hitpos.xyz.y; if ((dist < 8) && (dist < best_dist)) { best = OBJ_INDEX(ptr); best_dist = dist; } } ptr = GET_NEXT(ptr); } if ((Selection_lock && !Objects[best].flags[Object::Object_Flags::Marked]) || Objects[best].flags[Object::Object_Flags::Locked_from_editing]) { return -1; } return best; } void verticalize_controlled() { int cmode, count = 0; object *objp; cmode = Control_mode; if ((viewpoint == 1) && !cmode) cmode = 2; switch (cmode) { case 0: // Control the viewer's location and orientation verticalize_object(&view_orient); break; case 2: // Control viewpoint object if (!Objects[view_obj].flags[Object::Object_Flags::Locked_from_editing]) { verticalize_object(&Objects[view_obj].orient); object_moved(&Objects[view_obj]); FREDDoc_ptr->autosave("align object"); set_modified(); } break; case 1: // Control the current object's location and orientation objp = GET_FIRST(&obj_used_list); while (objp != END_OF_LIST(&obj_used_list)) { if (objp->flags[Object::Object_Flags::Marked]) verticalize_object(&objp->orient); objp = GET_NEXT(objp); } objp = GET_FIRST(&obj_used_list); while (objp != END_OF_LIST(&obj_used_list)) { if (objp->flags[Object::Object_Flags::Marked]) { object_moved(objp); count++; } objp = GET_NEXT(objp); } if (count) { if (count > 1) FREDDoc_ptr->autosave("align objects"); else FREDDoc_ptr->autosave("align object"); set_modified(); } break; } return; } void verticalize_object(matrix *orient) { align_vector_to_axis(&orient->vec.fvec); align_vector_to_axis(&orient->vec.uvec); align_vector_to_axis(&orient->vec.rvec); vm_fix_matrix(orient); // just in case something odd occurs. }