mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-10 17:46:53 -10:00
1. Add const qualifier where appropriate to API methods 2. Change `TonemapperAlgorithm` from enum to enum class, for type safety and token scope 3. Update saving of lighting profile in FRED 4. Add saving of lighting profile in qtFRED 5. Add lighting profile (and asteroid) initialization to qtFRED
1047 lines
33 KiB
C++
1047 lines
33 KiB
C++
#include "lab_ui.h"
|
|
#include "globalincs/vmallocator.h"
|
|
|
|
#include "lab_ui_helpers.h"
|
|
|
|
#include "graphics/debug_sphere.h"
|
|
#include "graphics/matrix.h"
|
|
#include "lab/labv2_internal.h"
|
|
#include "lighting/lighting_profiles.h"
|
|
#include "ship/shiphit.h"
|
|
#include "weapon/weapon.h"
|
|
#include "mission/missionload.h"
|
|
|
|
using namespace ImGui;
|
|
|
|
std::map<animation::ModelAnimationTriggerType, std::map<SCP_string, bool>> manual_animation_triggers = {};
|
|
std::map<animation::ModelAnimationTriggerType, bool> manual_animations = {};
|
|
|
|
std::array<bool, MAX_SHIP_PRIMARY_BANKS> triggered_primary_banks = {false, false, false};
|
|
std::array<bool, MAX_SHIP_SECONDARY_BANKS> triggered_secondary_banks = {false, false, false, false};
|
|
|
|
void LabUi::object_changed()
|
|
{
|
|
rebuild_after_object_change = true;
|
|
|
|
manual_animation_triggers.clear();
|
|
|
|
for (auto& trigger : triggered_primary_banks)
|
|
trigger = false;
|
|
for (auto& trigger : triggered_secondary_banks)
|
|
trigger = false;
|
|
}
|
|
|
|
void LabUi::build_species_entry(species_info species_def, int species_idx) const
|
|
{
|
|
with_TreeNode(species_def.species_name)
|
|
{
|
|
int ship_info_idx = 0;
|
|
|
|
for (auto const& class_def : Ship_info) {
|
|
if (class_def.species == species_idx) {
|
|
SCP_string node_label;
|
|
sprintf(node_label, "##ShipClassIndex%i", ship_info_idx);
|
|
TreeNodeEx(node_label.c_str(),
|
|
ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen,
|
|
"%s", class_def.name);
|
|
if (IsItemClicked() && !IsItemToggledOpen()) {
|
|
getLabManager()->changeDisplayedObject(LabMode::Ship, ship_info_idx);
|
|
}
|
|
}
|
|
ship_info_idx++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabUi::build_ship_list() const
|
|
{
|
|
with_TreeNode("Ship Classes")
|
|
{
|
|
int species_idx = 0;
|
|
for (auto const& species_def : Species_info) {
|
|
build_species_entry(species_def, species_idx);
|
|
species_idx++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabUi::build_weapon_subtype_list() const
|
|
{
|
|
for (auto weapon_subtype_idx = 0; weapon_subtype_idx < Num_weapon_subtypes; ++weapon_subtype_idx) {
|
|
with_TreeNode(Weapon_subtype_names[weapon_subtype_idx])
|
|
{
|
|
int weapon_idx = 0;
|
|
|
|
for (auto const& class_def : Weapon_info) {
|
|
if ((weapon_subtype_idx == 2 && class_def.wi_flags[Weapon::Info_Flags::Beam]) ||
|
|
(class_def.subtype == weapon_subtype_idx && !class_def.wi_flags[Weapon::Info_Flags::Beam])) {
|
|
SCP_string node_label;
|
|
sprintf(node_label, "##WeaponClassIndex%i", weapon_idx);
|
|
TreeNodeEx(node_label.c_str(),
|
|
ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen,
|
|
"%s", class_def.name);
|
|
|
|
if (IsItemClicked() && !IsItemToggledOpen()) {
|
|
getLabManager()->changeDisplayedObject(LabMode::Weapon, weapon_idx);
|
|
}
|
|
}
|
|
weapon_idx++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabUi::build_weapon_list() const
|
|
{
|
|
//weapon display needs to be rethought
|
|
|
|
//with_TreeNode("Weapon Classes")
|
|
//{
|
|
// build_weapon_subtype_list();
|
|
//}
|
|
}
|
|
|
|
void LabUi::build_background_list() const
|
|
{
|
|
SCP_vector<SCP_string> t_missions;
|
|
|
|
cf_get_file_list(t_missions, CF_TYPE_MISSIONS, NOX("*.fs2"));
|
|
|
|
// Remove any ignored missions from the list
|
|
SCP_vector<SCP_string> missions;
|
|
for (int i = 0; i < (int)t_missions.size(); i++) {
|
|
if (!mission_is_ignored(t_missions[i].c_str())) {
|
|
missions.push_back(t_missions[i]);
|
|
}
|
|
}
|
|
|
|
static SCP_map<SCP_string, SCP_vector<SCP_string>> directories;
|
|
|
|
if (directories.empty()) {
|
|
for (const auto& filename : missions) {
|
|
auto res = cf_find_file_location((filename + ".fs2").c_str(), CF_TYPE_MISSIONS);
|
|
|
|
if (res.found) {
|
|
auto location = get_directory_or_vp(res.full_name.c_str());
|
|
|
|
directories[location].push_back(filename);
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGuiTreeNodeFlags node_flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen;
|
|
|
|
TreeNodeEx(LAB_MISSION_NONE_STRING, node_flags, LAB_MISSION_NONE_STRING);
|
|
if (IsItemClicked() && !IsItemToggledOpen()) {
|
|
getLabManager()->Renderer->useBackground(LAB_MISSION_NONE_STRING);
|
|
}
|
|
|
|
for (auto const& directory : directories) {
|
|
auto directory_name = directory.first.c_str();
|
|
// if the directory name is empty, this indicates loose files in the root data directory
|
|
// since imgui requires tree nodes to have non-empty names, we substitute a static string here
|
|
if (!strlen(directory_name))
|
|
directory_name = "Root";
|
|
|
|
with_TreeNode(directory_name)
|
|
{
|
|
for (const auto& mission_name : directory.second) {
|
|
TreeNodeEx(mission_name.c_str(), node_flags, "%s", mission_name.c_str());
|
|
|
|
if (IsItemClicked() && !IsItemToggledOpen()) {
|
|
getLabManager()->Renderer->useBackground(mission_name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabUi::build_options_menu()
|
|
{
|
|
with_Menu("Options")
|
|
{
|
|
MenuItem("Render options", nullptr, &show_render_options_dialog);
|
|
MenuItem("Object selector", nullptr, &show_object_selection_dialog);
|
|
MenuItem("Background selector", nullptr, &show_background_selection_dialog);
|
|
MenuItem("Object options", nullptr, &show_object_options_dialog);
|
|
MenuItem("Close lab", "ESC", &close_lab);
|
|
}
|
|
}
|
|
|
|
void LabUi::build_toolbar_entries()
|
|
{
|
|
with_MainMenuBar
|
|
{
|
|
build_options_menu();
|
|
}
|
|
|
|
if (close_lab) {
|
|
getLabManager()->notify_close();
|
|
close_lab = false;
|
|
}
|
|
}
|
|
|
|
void LabUi::show_background_selector() const
|
|
{
|
|
with_Window("Select background")
|
|
{
|
|
with_CollapsingHeader("Mission Background")
|
|
{
|
|
build_background_list();
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabUi::show_object_selector() const
|
|
{
|
|
with_Window("Select object")
|
|
{
|
|
with_CollapsingHeader("Displayed Object")
|
|
{
|
|
build_ship_list();
|
|
|
|
build_weapon_list();
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabUi::create_ui()
|
|
{
|
|
build_toolbar_entries();
|
|
|
|
if (show_render_options_dialog)
|
|
show_render_options();
|
|
|
|
if (show_object_options_dialog)
|
|
show_object_options();
|
|
|
|
if (show_background_selection_dialog)
|
|
show_background_selector();
|
|
|
|
if (show_object_selection_dialog)
|
|
show_object_selector();
|
|
|
|
rebuild_after_object_change = false;
|
|
}
|
|
|
|
const char* antialiasing_settings[] = {
|
|
"None",
|
|
"FXAA Low",
|
|
"FXAA Medium",
|
|
"FXAA High",
|
|
"SMAA Low",
|
|
"SMAA Medium",
|
|
"SMAA High",
|
|
"SMAA Ultra",
|
|
};
|
|
|
|
SCP_string tonemappers[] = {
|
|
"Linear",
|
|
"Uncharted",
|
|
"ACES",
|
|
"ACES Approximate",
|
|
"Cineon",
|
|
"Piecewise Power Curve",
|
|
"Piecewise Power Curve (RGB)",
|
|
"Reinhard Extended",
|
|
"Reinhard Jodie",
|
|
};
|
|
|
|
const char* texture_quality_settings[] = {
|
|
"Minimum",
|
|
"Low",
|
|
"Medium",
|
|
"High",
|
|
"Maximum",
|
|
};
|
|
|
|
void LabUi::build_texture_quality_combobox()
|
|
{
|
|
with_Combo("Texture quality",
|
|
texture_quality_settings[static_cast<int>(getLabManager()->Renderer->getTextureQuality())])
|
|
{
|
|
for (int n = 0; n < IM_ARRAYSIZE(texture_quality_settings); n++) {
|
|
bool is_selected = n == static_cast<int>(getLabManager()->Renderer->getTextureQuality());
|
|
if (Selectable(texture_quality_settings[n], is_selected))
|
|
getLabManager()->Renderer->setTextureQuality(static_cast<TextureQuality>(n));
|
|
|
|
if (is_selected)
|
|
SetItemDefaultFocus();
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabUi::build_team_color_combobox() const
|
|
{
|
|
if (!Team_Colors.empty()) {
|
|
with_Combo("Team Color setting", getLabManager()->Renderer->getCurrentTeamColor().c_str())
|
|
{
|
|
for (const auto& team_color_name : Team_Colors) {
|
|
bool is_selected = team_color_name.first == getLabManager()->Renderer->getCurrentTeamColor();
|
|
|
|
if (Selectable(team_color_name.first.c_str(), is_selected)) {
|
|
getLabManager()->Renderer->setTeamColor(team_color_name.first);
|
|
}
|
|
|
|
if (is_selected)
|
|
SetItemDefaultFocus();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabUi::build_antialiasing_combobox()
|
|
{
|
|
with_Combo("Antialiasing method", antialiasing_settings[static_cast<int>(Gr_aa_mode)])
|
|
{
|
|
for (int n = 0; n < IM_ARRAYSIZE(antialiasing_settings); n++) {
|
|
bool is_selected = static_cast<int>(Gr_aa_mode) == n;
|
|
|
|
if (Selectable(antialiasing_settings[n], is_selected))
|
|
getLabManager()->Renderer->setAAMode(static_cast<AntiAliasMode>(n));
|
|
|
|
if (is_selected)
|
|
SetItemDefaultFocus();
|
|
}
|
|
}
|
|
}
|
|
namespace ltp = lighting_profiles;
|
|
using namespace ltp;
|
|
|
|
void LabUi::build_tone_mapper_combobox()
|
|
{
|
|
with_Combo("Tonemapper", ltp::tonemapper_to_name(ltp::current_tonemapper()).c_str())
|
|
{
|
|
for (int n = 0; n < IM_ARRAYSIZE(tonemappers); n++) {
|
|
const bool is_selected =
|
|
ltp::tonemapper_to_name(ltp::current_tonemapper()) == tonemappers[n];
|
|
if (Selectable(tonemappers[n].c_str(), is_selected))
|
|
ltp::lab_set_tonemapper(ltp::name_to_tonemapper(tonemappers[n]));
|
|
|
|
if (is_selected)
|
|
SetItemDefaultFocus();
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabUi::show_render_options()
|
|
{
|
|
auto profile_list = ltp::list_profiles();
|
|
int bloom_level = gr_bloom_intensity();
|
|
float ambient_factor = ltp::lab_get_ambient();
|
|
float light_factor = ltp::lab_get_light();
|
|
float emissive_factor = ltp::lab_get_emissive();
|
|
float exposure_level = ltp::current_exposure();
|
|
auto ppcv = ltp::lab_get_ppc();
|
|
|
|
bool skip_setting_light_options_this_frame = false;
|
|
|
|
with_Window("Render options")
|
|
{
|
|
Checkbox("Enable Model Rotation", &enable_model_rotation);
|
|
|
|
with_CollapsingHeader("Model features")
|
|
{
|
|
Checkbox("Rotate/Translate Subsystems", &animate_subsystems);
|
|
Checkbox("Show full detail", &show_full_detail);
|
|
Checkbox("Show thrusters", &show_thrusters);
|
|
Checkbox("Show afterburners", &show_afterburners);
|
|
Checkbox("Show weapons", &show_weapons);
|
|
Checkbox("Show Insignia", &show_insignia);
|
|
Checkbox("Show damage lightning", &show_damage_lightning);
|
|
Checkbox("No glowpoints", &no_glowpoints);
|
|
}
|
|
|
|
with_CollapsingHeader("Texture options")
|
|
{
|
|
Checkbox("Diffuse map", &diffuse_map);
|
|
Checkbox("Glow map", &glow_map);
|
|
Checkbox("Specular map", &spec_map);
|
|
Checkbox("Reflection map", &reflect_map);
|
|
Checkbox("Environment map", &env_map);
|
|
Checkbox("Normal map", &normal_map);
|
|
Checkbox("Height map", &height_map);
|
|
Checkbox("Misc map", &misc_map);
|
|
Checkbox("AO map", &ao_map);
|
|
|
|
build_texture_quality_combobox();
|
|
|
|
build_team_color_combobox();
|
|
}
|
|
|
|
with_CollapsingHeader("Scene rendering options")
|
|
{
|
|
Checkbox("Hide Post Processing", &hide_post_processing);
|
|
Checkbox("Render as wireframe", &use_wireframe_rendering);
|
|
Checkbox("Render without light", &no_lighting);
|
|
Checkbox("Render with emissive lighting", &show_emissive_lighting);
|
|
SliderFloat("Light brightness", &light_factor, 0.0f, 10.0f);
|
|
SliderFloat("Ambient factor", &ambient_factor, 0.0f, 10.0f);
|
|
SliderFloat("Emissive amount", &emissive_factor, 0.0f, 10.0f);
|
|
SliderFloat("Exposure", &exposure_level, 0.0f, 8.0f);
|
|
SliderInt("Bloom level", &bloom_level, 0, 200);
|
|
|
|
build_antialiasing_combobox();
|
|
|
|
build_tone_mapper_combobox();
|
|
|
|
if (ltp::current_tonemapper() == TonemapperAlgorithm::PPC ||
|
|
ltp::current_tonemapper() == TonemapperAlgorithm::PPC_RGB) {
|
|
SliderFloat("PPC Toe Strength", &ppcv.toe_strength, 0.0f, 1.0f);
|
|
SliderFloat("PPC Toe Length", &ppcv.toe_length, 0.0f, 1.0f);
|
|
SliderFloat("PPC Shoulder Angle", &ppcv.shoulder_angle, 0.0f, 1.0f);
|
|
SliderFloat("PPC Shoulder Length", &ppcv.shoulder_length, 0.0f, 10.0f);
|
|
SliderFloat("PPC Shoulder Strength", &ppcv.shoulder_strength, 0.0f, 1.0f);
|
|
}
|
|
|
|
if (profile_list.size()>1) {
|
|
with_CollapsingHeader("Load lighting profile...") {
|
|
for (const auto &s : profile_list) {
|
|
if (Button(s.c_str(), ImVec2(-FLT_MIN, GetTextLineHeight() * 2))) {
|
|
ltp::switch_to(s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (The_mission.volumetrics) {
|
|
with_CollapsingHeader("Volumetric nebula options")
|
|
{
|
|
bool set_to_origin = static_cast<bool>(volumetrics_pos_backup);
|
|
Checkbox("Move Volumetrics to Origin", &set_to_origin);
|
|
if (set_to_origin != static_cast<bool>(volumetrics_pos_backup)) {
|
|
if (set_to_origin) {
|
|
volumetrics_pos_backup = The_mission.volumetrics->pos;
|
|
The_mission.volumetrics->pos = vec3d ZERO_VECTOR;
|
|
}
|
|
else {
|
|
The_mission.volumetrics->pos = *volumetrics_pos_backup;
|
|
volumetrics_pos_backup.reset();
|
|
}
|
|
}
|
|
Separator();
|
|
Text("Quality Settings:");
|
|
Checkbox("Enable Edge Smoothing", &The_mission.volumetrics->doEdgeSmoothing);
|
|
SliderInt("Steps Until Opaque", &The_mission.volumetrics->steps, 2, 30);
|
|
SliderInt("Steps Towards Sun", &The_mission.volumetrics->globalLightSteps, 2, 10);
|
|
Separator();
|
|
Text("Visibility Settings:");
|
|
SliderFloat("Maximum Opacity", &The_mission.volumetrics->alphaLim, 0.0001f, 1.0f, "%.4f", ImGuiSliderFlags_Logarithmic | ImGuiSliderFlags_NoRoundToFormat);
|
|
SliderFloat("Opacity Distance", &The_mission.volumetrics->opacityDistance, 0.1f, 100.0f);
|
|
Separator();
|
|
Text("Emissive Settings:");
|
|
SliderFloat("Emissive Light Spreading", &The_mission.volumetrics->emissiveSpread, 0.1f, 2.0f);
|
|
SliderFloat("Emissive Light Intensity", &The_mission.volumetrics->emissiveIntensity, 0.0f, 2.0f);
|
|
SliderFloat("Emissive Light Falloff", &The_mission.volumetrics->emissiveFalloff, 0.1f, 2.0f);
|
|
Separator();
|
|
Text("Global Light Settings:");
|
|
SliderFloat("Henyey-Greenstein", &The_mission.volumetrics->henyeyGreensteinCoeff, -1.0f, 1.0f);
|
|
SliderFloat("Sun Falloff Factor", &The_mission.volumetrics->globalLightDistanceFactor, 0.1f, 3.0f);
|
|
Separator();
|
|
Text("Noise Settings:");
|
|
Checkbox("Noise Active", &The_mission.volumetrics->noiseActive);
|
|
SliderFloat("Noise Intensity", &The_mission.volumetrics->noiseColorIntensity, 0.0f, 3.0f);
|
|
SliderFloat("Noise Scale Base", &std::get<0>(The_mission.volumetrics->noiseScale), 1.0f, 50.0f);
|
|
SliderFloat("Noise Scale Sub", &std::get<1>(The_mission.volumetrics->noiseScale), 1.0f, 50.0f);
|
|
}
|
|
}
|
|
|
|
if (getLabManager()->Renderer->currentMissionBackground != LAB_MISSION_NONE_STRING) {
|
|
if (Button("Export environment cubemap", ImVec2(-FLT_MIN, GetTextLineHeight()*2))) {
|
|
gr_dump_envmap(getLabManager()->Renderer->currentMissionBackground.c_str());
|
|
}
|
|
}
|
|
|
|
if (graphics_options_changed()) {
|
|
if (Button("Reset graphics settings", ImVec2(-FLT_MIN, GetTextLineHeight() * 2))) {
|
|
getLabManager()->resetGraphicsSettings();
|
|
|
|
// In order to make the reset button work, we can't set anything here this frame; we'll wait until
|
|
// the next run through this method to update the state then.
|
|
skip_setting_light_options_this_frame = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!skip_setting_light_options_this_frame) {
|
|
getLabManager()->Flags.set(ManagerFlags::ModelRotationEnabled, enable_model_rotation);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::ShowInsignia, show_insignia);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::ShowDamageLightning, show_damage_lightning);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::HidePostProcessing, hide_post_processing);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::NoDiffuseMap, !diffuse_map);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::NoGlowMap, !glow_map);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::NoSpecularMap, !spec_map);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::NoReflectMap, !reflect_map);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::NoEnvMap, !env_map);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::NoNormalMap, !normal_map);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::NoHeightMap, !height_map);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::NoMiscMap, !misc_map);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::NoAOMap, !ao_map);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::NoGlowpoints, no_glowpoints);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::ShowWireframe, use_wireframe_rendering);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::NoLighting, no_lighting);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::ShowFullDetail, show_full_detail);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::ShowThrusters, show_thrusters);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::ShowAfterburners, show_afterburners);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::ShowWeapons, show_weapons);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::ShowEmissiveLighting, show_emissive_lighting);
|
|
getLabManager()->Renderer->setRenderFlag(LabRenderFlag::MoveSubsystems, animate_subsystems);
|
|
getLabManager()->Renderer->setEmissiveFactor(emissive_factor);
|
|
getLabManager()->Renderer->setAmbientFactor(ambient_factor);
|
|
getLabManager()->Renderer->setLightFactor(light_factor);
|
|
getLabManager()->Renderer->setBloomLevel(bloom_level);
|
|
getLabManager()->Renderer->setExposureLevel(exposure_level);
|
|
getLabManager()->Renderer->setPPCValues(ppcv);
|
|
}
|
|
}
|
|
|
|
void LabUi::do_triggered_anim(animation::ModelAnimationTriggerType type,
|
|
const SCP_string& name,
|
|
bool direction,
|
|
int subtype) const
|
|
{
|
|
if (getLabManager()->isSafeForShips()) {
|
|
auto shipp = &Ships[Objects[getLabManager()->CurrentObject].instance];
|
|
|
|
if (subtype != animation::ModelAnimationSet::SUBTYPE_DEFAULT)
|
|
Ship_info[shipp->ship_info_index]
|
|
.animations.getAll(model_get_instance(shipp->model_instance_num), type, subtype, true)
|
|
.start(direction ? animation::ModelAnimationDirection::RWD : animation::ModelAnimationDirection::FWD);
|
|
else
|
|
Ship_info[shipp->ship_info_index]
|
|
.animations.get(model_get_instance(shipp->model_instance_num), type, name)
|
|
.start(direction ? animation::ModelAnimationDirection::RWD : animation::ModelAnimationDirection::FWD);
|
|
}
|
|
}
|
|
|
|
#define IMGUI_TABLE_ENTRY(colA, colB) \
|
|
TableNextRow(); \
|
|
TableSetColumnIndex(0); \
|
|
TextUnformatted(colA); \
|
|
TableSetColumnIndex(1); \
|
|
TextUnformatted(colB); \
|
|
|
|
|
|
void LabUi::build_table_info_txtbox(ship_info* sip) const
|
|
{
|
|
with_TreeNode("Table information")
|
|
{
|
|
// since this dialog is closed when the displayed object changes, we need to track whether or not
|
|
// the text needs to be updated separately
|
|
static SCP_string table_text;
|
|
static int old_class = getLabManager()->CurrentClass;
|
|
|
|
if (table_text.length() == 0 || old_class != getLabManager()->CurrentClass)
|
|
table_text = get_ship_table_text(sip);
|
|
|
|
InputTextMultiline("##table_text",
|
|
const_cast<char*>(table_text.c_str()),
|
|
table_text.length(),
|
|
ImVec2(-FLT_MIN, GetTextLineHeight() * 16),
|
|
ImGuiInputTextFlags_ReadOnly);
|
|
}
|
|
}
|
|
|
|
void LabUi::build_model_info_box_actual(ship_info* sip, polymodel* pm) const
|
|
{
|
|
ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg;
|
|
|
|
with_Table("model info", 2, flags)
|
|
{
|
|
IMGUI_TABLE_ENTRY("Model File", sip->pof_file)
|
|
IMGUI_TABLE_ENTRY("Target model", sip->pof_file_hud)
|
|
IMGUI_TABLE_ENTRY("Techroom model", sip->pof_file_tech)
|
|
IMGUI_TABLE_ENTRY("Cockpit model", sip->cockpit_pof_file)
|
|
IMGUI_TABLE_ENTRY("POF version", std::to_string(pm->version).c_str());
|
|
IMGUI_TABLE_ENTRY("Materials used", std::to_string(pm->n_textures).c_str());
|
|
IMGUI_TABLE_ENTRY("Radius", std::to_string(pm->core_radius).c_str());
|
|
}
|
|
}
|
|
|
|
void LabUi::build_model_info_box(ship_info* sip, polymodel* pm) const {
|
|
with_TreeNode("Model information")
|
|
{
|
|
build_model_info_box_actual(sip, pm);
|
|
}
|
|
}
|
|
|
|
void render_subsystem(ship_subsys* ss, object* objp)
|
|
{
|
|
SCP_string buf;
|
|
|
|
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;
|
|
|
|
g3_start_frame(1);
|
|
gr_set_proj_matrix(Proj_fov, gr_screen.clip_aspect, Min_draw_distance, Max_draw_distance);
|
|
gr_set_view_matrix(&Eye_position, &Eye_matrix);
|
|
|
|
if (subobj_num != -1) {
|
|
auto bsp = &pm->submodel[subobj_num];
|
|
|
|
vec3d front_top_left = bsp->bounding_box[7];
|
|
vec3d front_top_right = bsp->bounding_box[6];
|
|
vec3d front_bot_left = bsp->bounding_box[4];
|
|
vec3d front_bot_right = bsp->bounding_box[5];
|
|
vec3d back_top_left = bsp->bounding_box[3];
|
|
vec3d back_top_right = bsp->bounding_box[2];
|
|
vec3d back_bot_left = bsp->bounding_box[0];
|
|
vec3d back_bot_right = bsp->bounding_box[1];
|
|
|
|
gr_set_color(255, 32, 32);
|
|
|
|
// get into the frame of reference of the submodel
|
|
int g3_count = 1;
|
|
g3_start_instance_matrix(&objp->pos, &objp->orient, true);
|
|
int mn = subobj_num;
|
|
while ((mn >= 0) && (pm->submodel[mn].parent >= 0)) {
|
|
g3_start_instance_matrix(&pm->submodel[mn].offset, &pmi->submodel[mn].canonical_orient, true);
|
|
g3_count++;
|
|
mn = pm->submodel[mn].parent;
|
|
}
|
|
|
|
// draw a cube around the subsystem
|
|
g3_draw_htl_line(&front_top_left, &front_top_right);
|
|
g3_draw_htl_line(&front_top_right, &front_bot_right);
|
|
g3_draw_htl_line(&front_bot_right, &front_bot_left);
|
|
g3_draw_htl_line(&front_bot_left, &front_top_left);
|
|
|
|
g3_draw_htl_line(&back_top_left, &back_top_right);
|
|
g3_draw_htl_line(&back_top_right, &back_bot_right);
|
|
g3_draw_htl_line(&back_bot_right, &back_bot_left);
|
|
g3_draw_htl_line(&back_bot_left, &back_top_left);
|
|
|
|
g3_draw_htl_line(&front_top_left, &back_top_left);
|
|
g3_draw_htl_line(&front_top_right, &back_top_right);
|
|
g3_draw_htl_line(&front_bot_left, &back_bot_left);
|
|
g3_draw_htl_line(&front_bot_right, &back_bot_right);
|
|
|
|
// draw another cube around a gun for a two-part turret
|
|
if ((ss->system_info->turret_gun_sobj >= 0) &&
|
|
(ss->system_info->turret_gun_sobj != ss->system_info->subobj_num)) {
|
|
bsp_info* bsp_turret = &pm->submodel[ss->system_info->turret_gun_sobj];
|
|
|
|
front_top_left = bsp_turret->bounding_box[7];
|
|
front_top_right = bsp_turret->bounding_box[6];
|
|
front_bot_left = bsp_turret->bounding_box[4];
|
|
front_bot_right = bsp_turret->bounding_box[5];
|
|
back_top_left = bsp_turret->bounding_box[3];
|
|
back_top_right = bsp_turret->bounding_box[2];
|
|
back_bot_left = bsp_turret->bounding_box[0];
|
|
back_bot_right = bsp_turret->bounding_box[1];
|
|
|
|
g3_start_instance_matrix(&bsp_turret->offset,
|
|
&pmi->submodel[ss->system_info->turret_gun_sobj].canonical_orient,
|
|
true);
|
|
|
|
g3_draw_htl_line(&front_top_left, &front_top_right);
|
|
g3_draw_htl_line(&front_top_right, &front_bot_right);
|
|
g3_draw_htl_line(&front_bot_right, &front_bot_left);
|
|
g3_draw_htl_line(&front_bot_left, &front_top_left);
|
|
|
|
g3_draw_htl_line(&back_top_left, &back_top_right);
|
|
g3_draw_htl_line(&back_top_right, &back_bot_right);
|
|
g3_draw_htl_line(&back_bot_right, &back_bot_left);
|
|
g3_draw_htl_line(&back_bot_left, &back_top_left);
|
|
|
|
g3_draw_htl_line(&front_top_left, &back_top_left);
|
|
g3_draw_htl_line(&front_top_right, &back_top_right);
|
|
g3_draw_htl_line(&front_bot_left, &back_bot_left);
|
|
g3_draw_htl_line(&front_bot_right, &back_bot_right);
|
|
|
|
g3_done_instance(true);
|
|
}
|
|
|
|
for (int i = 0; i < g3_count; i++)
|
|
g3_done_instance(true);
|
|
} else {
|
|
vec3d subsys_position;
|
|
vm_vec_add(&subsys_position, &objp->pos, &ss->system_info->pnt);
|
|
vec3d rotated_position;
|
|
vm_vec_unrotate(&rotated_position, &subsys_position, &objp->orient);
|
|
|
|
color c;
|
|
gr_init_alphacolor(&c, 255, 32, 32, 128);
|
|
|
|
g3_draw_htl_sphere(&c,
|
|
&rotated_position,
|
|
ss->system_info->radius,
|
|
ALPHA_BLEND_ALPHA_BLEND_ALPHA,
|
|
ZBUFFER_TYPE_FULL);
|
|
}
|
|
|
|
gr_end_proj_matrix();
|
|
gr_end_view_matrix();
|
|
g3_end_frame();
|
|
}
|
|
|
|
void LabUi::build_subsystem_list(object* objp, ship* shipp) const {
|
|
with_TreeNode("Subsystems")
|
|
{
|
|
size_t subsys_index = 0;
|
|
static SCP_vector<bool> show_subsys;
|
|
static int ship_class_idx = shipp->ship_info_index;
|
|
if (ship_class_idx != shipp->ship_info_index) {
|
|
ship_class_idx = shipp->ship_info_index;
|
|
show_subsys.clear();
|
|
}
|
|
|
|
for (auto cur_subsys = GET_FIRST(&shipp->subsys_list); cur_subsys != END_OF_LIST(&shipp->subsys_list);
|
|
cur_subsys = GET_NEXT(cur_subsys)) {
|
|
if (show_subsys.size() <= subsys_index)
|
|
show_subsys.push_back(false);
|
|
|
|
auto subsys_name_tmp = cur_subsys->sub_name;
|
|
if (strlen(subsys_name_tmp) == 0)
|
|
subsys_name_tmp = cur_subsys->system_info->name;
|
|
|
|
SCP_string subsys_name;
|
|
sprintf(subsys_name, "%s (%i)", subsys_name_tmp, (int)subsys_index);
|
|
|
|
build_subsystem_list_entry(subsys_name, show_subsys, subsys_index, cur_subsys, objp, shipp);
|
|
|
|
subsys_index++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabUi::build_subsystem_list_entry(SCP_string& subsys_name,
|
|
SCP_vector<bool>& show_subsys,
|
|
const size_t& subsys_index,
|
|
ship_subsys* cur_subsys,
|
|
object* objp,
|
|
ship* shipp) const
|
|
{
|
|
with_TreeNode(subsys_name.c_str())
|
|
{
|
|
SCP_string node_name;
|
|
sprintf(node_name, "Highlight system##%s", subsys_name.c_str());
|
|
|
|
auto display_this = show_subsys[subsys_index] == true;
|
|
|
|
Checkbox(node_name.c_str(), &display_this);
|
|
|
|
if (display_this) {
|
|
render_subsystem(cur_subsys, objp);
|
|
}
|
|
|
|
show_subsys[subsys_index] = display_this;
|
|
|
|
sprintf(node_name, "Destroy system##%s", subsys_name.c_str());
|
|
|
|
if (Button(node_name.c_str())) {
|
|
cur_subsys->current_hits = 0;
|
|
do_subobj_destroyed_stuff(shipp, cur_subsys, nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabUi::build_weapon_options(ship* shipp) const {
|
|
with_TreeNode("Primaries")
|
|
{
|
|
auto bank = 0;
|
|
for (auto& primary_slot : shipp->weapons.primary_bank_weapons) {
|
|
if (primary_slot != -1) {
|
|
auto wip = &Weapon_info[primary_slot];
|
|
|
|
SCP_string text;
|
|
sprintf(text, "##Primary bank %i", bank);
|
|
|
|
build_primary_weapon_combobox(text, wip, primary_slot);
|
|
SameLine();
|
|
static bool should_fire[MAX_SHIP_PRIMARY_BANKS] = {false, false, false};
|
|
SCP_string cb_text;
|
|
sprintf(cb_text, "Fire bank %i", bank);
|
|
Checkbox(cb_text.c_str(), &should_fire[bank]);
|
|
if (should_fire[bank]) {
|
|
getLabManager()->FirePrimaries |= 1 << bank;
|
|
} else {
|
|
getLabManager()->FirePrimaries &= ~(1 << bank);
|
|
}
|
|
|
|
bank++;
|
|
}
|
|
}
|
|
}
|
|
|
|
with_TreeNode("Secondaries")
|
|
{
|
|
auto bank = 0;
|
|
for (auto& secondary_slot : shipp->weapons.secondary_bank_weapons) {
|
|
if (secondary_slot != -1) {
|
|
auto wip = &Weapon_info[secondary_slot];
|
|
|
|
SCP_string text;
|
|
sprintf(text, "##Secondary bank %i", bank);
|
|
build_secondary_weapon_combobox(text, wip, secondary_slot);
|
|
SameLine();
|
|
static bool should_fire[MAX_SHIP_SECONDARY_BANKS] = {false, false, false, false};
|
|
SCP_string cb_text;
|
|
sprintf(cb_text, "Fire bank %i##secondary", bank);
|
|
Checkbox(cb_text.c_str(), &should_fire[bank]);
|
|
if (should_fire[bank]) {
|
|
getLabManager()->FireSecondaries |= 1 << bank;
|
|
} else {
|
|
getLabManager()->FireSecondaries &= ~(1 << bank);
|
|
}
|
|
|
|
bank++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabUi::build_primary_weapon_combobox(SCP_string& text,
|
|
weapon_info* wip,
|
|
int& primary_slot) const
|
|
{
|
|
with_Combo(text.c_str(), wip->name)
|
|
{
|
|
for (size_t i = 0; i < Weapon_info.size(); i++) {
|
|
if (Weapon_info[i].subtype == WP_MISSILE)
|
|
continue;
|
|
bool is_selected = i == (size_t)primary_slot;
|
|
if (Selectable(Weapon_info[i].name, is_selected))
|
|
primary_slot = (int)i;
|
|
if (is_selected)
|
|
SetItemDefaultFocus();
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabUi::build_secondary_weapon_combobox(SCP_string& text, weapon_info* wip, int& secondary_slot) const
|
|
{
|
|
with_Combo(text.c_str(), wip->name)
|
|
{
|
|
for (size_t i = 0; i < Weapon_info.size(); i++) {
|
|
if (Weapon_info[i].subtype != WP_MISSILE)
|
|
continue;
|
|
bool is_selected = i == (size_t)secondary_slot;
|
|
if (Selectable(Weapon_info[i].name, is_selected))
|
|
secondary_slot = (int)i;
|
|
if (is_selected)
|
|
SetItemDefaultFocus();
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabUi::reset_animations(ship* shipp, ship_info* sip) const
|
|
{
|
|
polymodel_instance* shipp_pmi = model_get_instance(shipp->model_instance_num);
|
|
|
|
for (auto i = 0; i < MAX_SHIP_PRIMARY_BANKS; ++i) {
|
|
if (triggered_primary_banks[i]) {
|
|
sip->animations.getAll(shipp_pmi, animation::ModelAnimationTriggerType::PrimaryBank, i)
|
|
.start(animation::ModelAnimationDirection::RWD);
|
|
triggered_primary_banks[i] = false;
|
|
}
|
|
}
|
|
|
|
for (auto i = 0; i < MAX_SHIP_SECONDARY_BANKS; ++i) {
|
|
if (triggered_secondary_banks[i]) {
|
|
sip->animations.getAll(shipp_pmi, animation::ModelAnimationTriggerType::SecondaryBank, i)
|
|
.start(animation::ModelAnimationDirection::RWD);
|
|
triggered_secondary_banks[i] = false;
|
|
}
|
|
}
|
|
|
|
for (auto entry : manual_animations) {
|
|
if (manual_animations[entry.first]) {
|
|
sip->animations.getAll(shipp_pmi, entry.first).start(animation::ModelAnimationDirection::RWD);
|
|
manual_animations[entry.first] = false;
|
|
}
|
|
}
|
|
|
|
for (const auto& entry : manual_animation_triggers) {
|
|
auto animation_type = entry.first;
|
|
sip->animations.getAll(shipp_pmi, animation_type).start(animation::ModelAnimationDirection::RWD);
|
|
}
|
|
}
|
|
|
|
void LabUi::maybe_show_animation_category(const SCP_vector<animation::ModelAnimationSet::RegisteredTrigger>& anim_triggers,
|
|
animation::ModelAnimationTriggerType trigger_type, SCP_string label) const
|
|
{
|
|
if (std::any_of(anim_triggers.begin(), anim_triggers.end(), [trigger_type](animation::ModelAnimationSet::RegisteredTrigger t) {
|
|
return t.type == trigger_type;
|
|
})) {
|
|
with_TreeNode(label.c_str())
|
|
{
|
|
for (const auto& anim_trigger : anim_triggers) {
|
|
if (anim_trigger.type == trigger_type) {
|
|
|
|
if (Button(anim_trigger.name.c_str())) {
|
|
auto& scripted_triggers = manual_animation_triggers[trigger_type];
|
|
auto direction = scripted_triggers[anim_trigger.name];
|
|
do_triggered_anim(trigger_type,
|
|
anim_trigger.name,
|
|
direction,
|
|
anim_trigger.subtype);
|
|
scripted_triggers[anim_trigger.name] = !scripted_triggers[anim_trigger.name];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabUi::build_animation_options(ship* shipp, ship_info* sip) const
|
|
{
|
|
with_TreeNode("Animations")
|
|
{
|
|
const auto& anim_triggers = sip->animations.getRegisteredTriggers();
|
|
|
|
if (Button("Reset animations")) {
|
|
reset_animations(shipp, sip);
|
|
}
|
|
|
|
if (shipp->weapons.num_primary_banks > 0) {
|
|
create_primary_weapon_anim_node(shipp, sip);
|
|
}
|
|
|
|
if (shipp->weapons.num_secondary_banks > 0) {
|
|
create_secondary_weapon_anim_node(shipp, sip);
|
|
}
|
|
|
|
if (std::any_of(anim_triggers.begin(),
|
|
anim_triggers.end(),
|
|
[](animation::ModelAnimationSet::RegisteredTrigger t) {
|
|
return t.type == animation::ModelAnimationTriggerType::Afterburner;
|
|
})) {
|
|
create_afterburner_animation_node(anim_triggers);
|
|
}
|
|
|
|
maybe_show_animation_category(anim_triggers,
|
|
animation::ModelAnimationTriggerType::TurretFiring,
|
|
"Turret firing##anims");
|
|
maybe_show_animation_category(anim_triggers,
|
|
animation::ModelAnimationTriggerType::TurretFired,
|
|
"Turret fired##anims");
|
|
maybe_show_animation_category(anim_triggers,
|
|
animation::ModelAnimationTriggerType::Scripted,
|
|
"Scripted animations##anims");
|
|
maybe_show_animation_category(anim_triggers,
|
|
animation::ModelAnimationTriggerType::DockBayDoor,
|
|
"Dock bay door##anims");
|
|
maybe_show_animation_category(anim_triggers,
|
|
animation::ModelAnimationTriggerType::Docking_Stage1,
|
|
"Docking stage 1##anims");
|
|
maybe_show_animation_category(anim_triggers,
|
|
animation::ModelAnimationTriggerType::Docking_Stage2,
|
|
"Docking stage 2##anims");
|
|
maybe_show_animation_category(anim_triggers,
|
|
animation::ModelAnimationTriggerType::Docking_Stage3,
|
|
"Docking stage 3##anims");
|
|
}
|
|
}
|
|
|
|
void LabUi::create_afterburner_animation_node(
|
|
const SCP_vector<animation::ModelAnimationSet::RegisteredTrigger>& anim_triggers) const
|
|
{
|
|
with_TreeNode("Afterburner")
|
|
{
|
|
if (Button("Trigger afterburner animations")) {
|
|
for (const auto& anim_trigger : anim_triggers) {
|
|
if (anim_trigger.type == animation::ModelAnimationTriggerType::Afterburner) {
|
|
auto& ab_triggers = manual_animation_triggers[animation::ModelAnimationTriggerType::Afterburner];
|
|
do_triggered_anim(animation::ModelAnimationTriggerType::Afterburner,
|
|
anim_trigger.name,
|
|
ab_triggers[anim_trigger.name],
|
|
anim_trigger.subtype);
|
|
ab_triggers[anim_trigger.name] = !ab_triggers[anim_trigger.name];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabUi::create_secondary_weapon_anim_node(
|
|
ship* shipp,
|
|
ship_info* sip) const
|
|
{
|
|
with_TreeNode("Secondary Weapons##Anims")
|
|
{
|
|
for (auto i = 0; i < shipp->weapons.num_secondary_banks; ++i) {
|
|
SCP_string button_label;
|
|
sprintf(button_label, "Trigger animation for secondary bank %i", i);
|
|
if (Button(button_label.c_str())) {
|
|
sip->animations
|
|
.getAll(model_get_instance(shipp->model_instance_num),
|
|
animation::ModelAnimationTriggerType::SecondaryBank,
|
|
i)
|
|
.start(triggered_secondary_banks[i] ? animation::ModelAnimationDirection::RWD
|
|
: animation::ModelAnimationDirection::FWD);
|
|
triggered_secondary_banks[i] = !triggered_secondary_banks[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabUi::create_primary_weapon_anim_node(
|
|
ship* shipp,
|
|
ship_info* sip) const
|
|
{
|
|
with_TreeNode("Primary Weapons##Anims")
|
|
{
|
|
for (auto i = 0; i < shipp->weapons.num_primary_banks; ++i) {
|
|
SCP_string button_label;
|
|
sprintf(button_label, "Trigger animation for primary bank %i", i);
|
|
if (Button(button_label.c_str())) {
|
|
sip->animations
|
|
.getAll(model_get_instance(shipp->model_instance_num),
|
|
animation::ModelAnimationTriggerType::PrimaryBank,
|
|
i)
|
|
.start(triggered_primary_banks[i] ? animation::ModelAnimationDirection::RWD
|
|
: animation::ModelAnimationDirection::FWD);
|
|
triggered_primary_banks[i] = !triggered_primary_banks[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LabUi::show_object_options() const
|
|
{
|
|
|
|
with_Window("Object Information")
|
|
{
|
|
if (getLabManager()->CurrentMode == LabMode::Ship && getLabManager()->isSafeForShips()) {
|
|
auto objp = &Objects[getLabManager()->CurrentObject];
|
|
auto shipp = &Ships[objp->instance];
|
|
auto sip = &Ship_info[shipp->ship_info_index];
|
|
auto pm = model_get(sip->model_num);
|
|
|
|
with_CollapsingHeader(sip->name)
|
|
{
|
|
build_table_info_txtbox(sip);
|
|
|
|
build_model_info_box(sip, pm);
|
|
|
|
build_subsystem_list(objp, shipp);
|
|
}
|
|
|
|
with_CollapsingHeader("Object actions")
|
|
{
|
|
if (getLabManager()->isSafeForShips()) {
|
|
if (Button("Destroy ship")) {
|
|
if (Objects[getLabManager()->CurrentObject].type == OBJ_SHIP) {
|
|
auto obj = &Objects[getLabManager()->CurrentObject];
|
|
|
|
obj->flags.remove(Object::Object_Flags::Player_ship);
|
|
ship_self_destruct(obj);
|
|
}
|
|
}
|
|
|
|
build_animation_options(shipp, sip);
|
|
}
|
|
}
|
|
|
|
with_CollapsingHeader("Weapons")
|
|
{
|
|
build_weapon_options(shipp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|