Files
dustinspace217andDustinspace217 499b3fb38b P4: qtFRED: rotate around the centre of a multi-selection (#7843)
* qtFRED: rotate around the centre of a multi-selection

Rotate Around Object (Alt+R) and the orbit pivot both used the current
object's position, so with several ships marked the camera still
circled whichever one happened to be current. Add
EditorViewport::selectionCenter(), which returns the centroid of the
marked objects when more than one is marked and the given object's
position otherwise, and use it at the three places that chose the
point: the look-at camera update, orbitCameraGetPivot(), and the Alt+R
handler that turns the camera toward the target when the mode is
switched on.

orbitCameraGetPivot() also serves middle-mouse orbit and wheel zoom in
normal mode, so those pivot on the selection centre as well. Every
marked type counts at full weight, including waypoints and jump nodes.
With a single object marked nothing changes.

Fixes #7724

* Make EditorViewport::selectionCenter const

It only reads the marked-object list and the editor's marked count;
clang-tidy (readability-make-member-function-const) flagged it in CI.

* Mirror the multi-selection orbit pivot to FRED, trim comments

FRED gets the same selection_center() as qtFRED's selectionCenter(),
used at the same three places: the look-at camera update, the orbit
pivot, and the Rotate Around Object toggle. Also removes the qtFRED
comments flagged in review.

* Make the selection-center pivot optional and share object bounds

Look-at and orbit use the current object again unless the new option is
on (qtFRED: Preferences > Controls; FRED: View menu), and the selection
center is now the bounding-box midpoint. view_universe in both editors,
the pivot, and qtFRED's selection radius all use get_object_bounds() in
missioneditor/common.

* Reset the cached orbit pivot when the selection-center option changes

Wheel zoom reuses the orbit pivot until the camera moves, so without this
the option only took effect for scrolling after the next camera move.

* Address review: move and rename the selection-centroid option

- FRED: the option moves from the View menu to Settings (and Misc in the
  classic layout), under Move Ships When Undocking, with hover help, and is
  renamed Rotate Around Selection Centroid.
- qtFRED: the checkbox moves next to Move Ships When Undocking in the
  preferences dialog, with the same name.
- setOrbitSelectionCenter only resets the orbit when the value changes.
- get_object_bounds iterates with list_range.
- The bounds test resets the object system with obj_init and takes objects
  off the free list, so it leaves a valid state for other tests.

* Rename the option to Selection Midpoint, move it to qtFRED's Controls tab, and document it

* Fix overlapping Appearance rows in qtFRED Preferences, realign a settings line

The last commit renumbered the Viewport label size row of the Appearance
group from 4 to 3, which is the Long list menu style row, so the label
size pair fell out of the layout and its spin box drew over the group
title. Put it back at row 4.

---------

Co-authored-by: Dustinspace217 <[email protected]>
2026-11-04 01:17:00 -05:00

2349 lines
63 KiB
C++

/*
* 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 <algorithm>
#include <cmath>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <float.h>
#include <windows.h>
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(&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, 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, &center);
vm_vec_scale_add(&view_pos, &center, &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, &center, &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<io::spacemouse::SpaceMouse> 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.
}