Files
the-e eec4a9ce3e Refactor Vulkan descriptor set binding to reduce dynamic offset churn
- Consolidated dynamic descriptor offset logic into `DescriptorWriter::bindSets`, replacing repetitive `vkCmdBindDescriptorSets` patterns across Vulkan modules.
- Eliminated redundant descriptor binding operations for Material and PerDraw sets by centralizing logic in `bindSets`.
- Simplified descriptors by directly splitting offset changes from descriptor contents, avoiding unnecessary updates.
- Updated comments and documentation to align with the new approach while maintaining reference consistency.
- Applied optimizations across Vulkan modules (`VulkanPostProcessing`, `VulkanDescriptorManager`, etc.) to reduce per-draw overhead and improve readability.
2026-11-07 19:36:13 +01:00

881 lines
26 KiB
C++

#include "lab/labv2_internal.h"
#include "lab/manager/lab_manager.h"
#include "lab/renderer/lab_renderer.h"
#include "io/key.h"
#include "math/staticrand.h"
#include "missionui/missionscreencommon.h"
#include "model/modelrender.h"
#include "object/object.h"
#include "object/objectdock.h"
#include "debris/debris.h"
#include "ship/ship.h"
#include "ship/shipfx.h"
#include "particle/particle.h"
#include "prop/prop.h"
#include "weapon/muzzleflash.h"
#include "weapon/beam.h"
#include "ai/aigoals.h"
#include "freespace.h"
#include "io/mouse.h"
#undef LOCAL
#include "extensions/ImGuizmo.h"
//Turret firing forward declarations
void ai_turret_execute_behavior(const ship* shipp, ship_subsys* ss);
extern void beam_delete(beam* b);
void lab_exit() {
getLabManager()->notify_close();
}
namespace ltp = lighting_profiles;
LabManager::LabManager() {
The_mission.Reset();
if (The_mission.ai_profile == nullptr)
The_mission.ai_profile = &Ai_profiles[Default_ai_profile];
Renderer.reset(new LabRenderer());
labUi = LabUi();
obj_init();
fireball_init();
debris_init();
extern void debris_page_in();
debris_page_in();
props_level_init();
asteroid_level_init();
shockwave_level_init();
ship_level_init();
shipfx_flash_init();
weapon_level_init();
beam_level_init();
particle::init();
init_ai_system();
ai_paused = 1;
// No collisions because the Lab is a lie and collisions will be bad
Saved_cmdline_collisions_value = Cmdline_dis_collisions;
Cmdline_dis_collisions = 1;
// do some other setup
// External weapon displays require a call to weapons_page_in, which in turn requires team data to be set
Num_teams = 1;
team_data* teamp = &Team_data[0];
// In the lab, all ships are valid
teamp->ship_choices.clear();
for (size_t i = 0; i < Ship_info.size(); ++i) {
auto &entry = teamp->ship_choices.emplace_back();
entry.class_index = sz2i(i);
entry.count = 1;
}
teamp->default_ship = 0;
// you want guns? you get guns.
teamp->weapon_choices.clear();
for (size_t i = 0; i < Weapon_info.size(); ++i) {
auto &entry = teamp->weapon_choices.emplace_back();
entry.class_index = sz2i(i);
entry.count = 640; // should be enough for everyone
}
Game_mode |= GM_LAB;
using namespace ltp;
graphicsSettings = gfx_options();
graphicsSettings.ppcv = ltp::lab_get_ppc();
graphicsSettings.ambient_factor = ltp::lab_get_ambient();
graphicsSettings.light_factor = ltp::lab_get_light();
graphicsSettings.emissive_factor = ltp::lab_get_emissive();
graphicsSettings.exposure_level = ltp::current_exposure();
graphicsSettings.tonemapper = ltp::current_tonemapper();
graphicsSettings.bloom_level = gr_bloom_intensity();
graphicsSettings.aa_mode = Gr_aa_mode;
}
LabManager::~LabManager()
{
obj_delete_all();
}
void LabManager::resetGraphicsSettings() {
Renderer->resetGraphicsSettings(graphicsSettings);
}
void LabManager::onFrame(float frametime) {
gr_imgui_begin_frame();
Renderer->onFrame(frametime);
labUi.create_ui();
int key = game_check_key();
int dx, dy, dz;
mouse_get_delta(&dx, &dy);
mouse_get_wheel_delta(nullptr, &dz);
int mouse_x = 0;
int mouse_y = 0;
mouse_get_pos(&mouse_x, &mouse_y);
const bool lmb_down = mouse_down(MOUSE_LEFT_BUTTON) != 0;
bool lmb_pressed = lmb_down && !LastLmbDown;
LastLmbDown = lmb_down;
if (lmb_pressed && ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) {
lmb_pressed = false;
}
if (dz != 0 && ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) {
dz = 0;
}
auto& current_camera = Renderer->getCurrentCamera();
current_camera->handleInput(
dx, dy, dz, lmb_down, lmb_pressed, mouse_down(MOUSE_RIGHT_BUTTON) != 0, key_get_shift_status(), mouse_x, mouse_y);
if (!current_camera->handlesObjectPlacement()) {
const bool over_camera_overlay = Renderer->getShowOrientationWidget() && current_camera->isOverlayHit(mouse_x, mouse_y);
if (lmb_down && !over_camera_overlay) {
angles rot_angle;
vm_extract_angles_matrix_alternate(&rot_angle, &CurrentOrientation);
if (RotationMode == LabRotationMode::Yaw) {
rot_angle.h += dx / RotationSpeedDivisor;
}
if (RotationMode == LabRotationMode::Pitch) {
rot_angle.p += dy / RotationSpeedDivisor;
}
if (RotationMode == LabRotationMode::Both) {
rot_angle.h += dx / RotationSpeedDivisor;
rot_angle.p += dy / RotationSpeedDivisor;
}
if (RotationMode == LabRotationMode::Roll) {
rot_angle.b += dx / RotationSpeedDivisor;
}
if (rot_angle.h < -PI)
rot_angle.h = PI - 0.001f;
if (rot_angle.h > PI)
rot_angle.h = -PI + 0.001f;
CLAMP(rot_angle.p, -PI_2, PI_2);
if (rot_angle.b < -PI)
rot_angle.b = PI - 0.001f;
if (rot_angle.b > PI)
rot_angle.b = -PI + 0.001f;
vm_angles_2_matrix(&CurrentOrientation, &rot_angle);
}
}
if (CurrentMode == LabMode::Ship) {
Lab_thrust_afterburn = check_control(AFTERBURNER) != 0;
} else {
Lab_thrust_afterburn = false;
}
if (key != 0) {
// handle any key presses
switch (key) {
// Adjust AA presets
case KEY_T:
Renderer->useNextTeamColorPreset();
break;
case KEY_Y:
Renderer->usePreviousTeamColorPreset();
break;
case KEY_V:
Renderer->resetView();
break;
case KEY_UP:
break;
case KEY_DOWN:
break;
case KEY_R:
switch (RotationMode) {
case LabRotationMode::Both:
RotationMode = LabRotationMode::Yaw;
break;
case LabRotationMode::Yaw:
RotationMode = LabRotationMode::Pitch;
break;
case LabRotationMode::Pitch:
RotationMode = LabRotationMode::Roll;
break;
case LabRotationMode::Roll:
RotationMode = LabRotationMode::Both;
break;
}
break;
case KEY_S:
RotationSpeedDivisor *= 10.f;
if (RotationSpeedDivisor > 10000.f)
RotationSpeedDivisor = 100.f;
break;
case KEY_M:
gr_dump_envmap(Renderer->currentMissionBackground.c_str());
break;
case KEY_ESC:
notify_close();
break;
default:
// check for game-specific controls
if (CurrentMode == LabMode::Ship) {
// These don't work because the lab is a lie and ships don't actually move
// Also the ships are AI and don't really respond to player input anyway so
// getting these working will be tricky
/*if (check_control(PLUS_5_PERCENT_THROTTLE, key))
Lab_thrust_len += 0.05f;
else if (check_control(MINUS_5_PERCENT_THROTTLE, key))
Lab_thrust_len -= 0.05f;
CLAMP(Lab_thrust_len, 0.0f, 1.0f);*/
}
break;
}
}
float rev_rate;
ship_info* sip = nullptr;
// First delete any dead objects so we don't end up processing them
obj_delete_all_that_should_be_dead();
if (CurrentObject != -1 && (Objects[CurrentObject].type == OBJ_SHIP)) {
sip = &Ship_info[Ships[Objects[CurrentObject].instance].ship_info_index];
auto obj = &Objects[CurrentObject];
bool weapons_firing = false;
for (auto i = 0; i < Ships[obj->instance].weapons.num_primary_banks; ++i) {
if (FirePrimaries[i]) {
weapons_firing = true;
Ships[obj->instance].weapons.current_primary_bank = i;
ship_fire_primary(obj);
Ships[obj->instance].weapon_energy = sip->max_weapon_reserve;
}
}
Ships[obj->instance].flags.set(Ship::Ship_Flags::Trigger_down, weapons_firing);
for (auto i = 0; i < Ships[obj->instance].weapons.num_secondary_banks; ++i) {
if (FireSecondaries[i]) {
Ships[obj->instance].weapons.current_secondary_bank = i;
ship_fire_secondary(obj);
}
}
ship_process_post(obj, frametime);
ai_process_subobjects(CurrentObject); // So that animations get reset
if (!getLabManager()->FireTurrets.empty()) {
for (auto& [subsys, mode, fire] : getLabManager()->FireTurrets) {
if (!fire || subsys == nullptr)
continue;
vec3d new_pos, new_vec;
ship_get_global_turret_info(&Objects[subsys->parent_objnum], subsys->system_info, &new_pos, &new_vec);
bool multipart = false;
// Turret is multipart
if (subsys->system_info->turret_gun_sobj >= 0 && subsys->system_info->subobj_num != subsys->system_info->turret_gun_sobj) {
multipart = true;
}
switch (mode) {
case LabTurretAimType::UVEC: {
subsys->last_aim_enemy_pos = new_pos + new_vec * 500.0f;
break;
}
case LabTurretAimType::INITIAL: {
subsys->last_aim_enemy_pos = vmd_zero_vector;
break;
}
case LabTurretAimType::RANDOM: {
bool gen_new_vec = !multipart || subsys->points_to_target <= 0.010f;
if (gen_new_vec && timestamp_elapsed(subsys->turret_next_fire_stamp)) {
vec3d rand_vec;
const int MAX_ATTEMPTS = 100;
bool valid_vec_found = false;
// You get 100 tries to find a set of random coords to fire at
for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
float full_fov_degrees = 2.0f * acosf(subsys->system_info->turret_fov) * (180.0f / PI);
vm_vec_random_cone(&rand_vec, &new_vec, full_fov_degrees);
vec3d target_point;
vm_vec_scale_add(&target_point, &new_pos, &rand_vec, 1000.0f);
// Create a vector from the turret to the random point.
vec3d turret_to_target;
vm_vec_sub(&turret_to_target, &target_point, &new_pos);
vm_vec_normalize(&turret_to_target);
// Test if the generated vector is within the FOV
if (turret_fov_test(subsys, &new_vec, &turret_to_target, 0.0f)) {
valid_vec_found = true;
rand_vec = target_point;
break;
}
}
if (valid_vec_found) {
subsys->last_aim_enemy_pos = rand_vec;
} else {
subsys->last_aim_enemy_pos = new_pos + new_vec * 500.0f;
}
}
break;
}
default:
UNREACHABLE("Invalid Lab Turret Aim Type %d!", static_cast<int>(mode));
break;
}
ai_turret_execute_behavior(&Ships[obj->instance], subsys);
}
}
// Check if we have finished an undock test. If so, delete the docker ship
if (DockerObject >= 0) {
object* docker_objp = &Objects[DockerObject];
ship* shipp = &Ships[docker_objp->instance];
ai_info* aip = &Ai_info[shipp->ai_index];
bool hasDockGoal = false;
for (const auto& goal : aip->goals) {
if (goal.ai_mode == AI_GOAL_DOCK || goal.ai_mode == AI_GOAL_UNDOCK) {
hasDockGoal = true;
break;
}
}
if (!hasDockGoal && !object_is_docked(docker_objp)) {
deleteDockerObject();
}
}
// Check if we have finished a bay test. If so, delete the bay ship
if (BayObject >= 0) {
object* bay_objp = &Objects[BayObject];
ship* shipp = &Ships[bay_objp->instance];
ai_info* aip = &Ai_info[shipp->ai_index];
bool hasBayGoal = aip->mode == AIM_BAY_EMERGE || aip->mode == AIM_BAY_DEPART;
if (!hasBayGoal) {
deleteBayObject();
}
}
}
// get correct revolution rate
rev_rate = REVOLUTION_RATE;
if (sip != nullptr) {
if (sip->is_big_ship()) {
rev_rate *= 1.7f;
}
else if (sip->is_huge_ship()) {
rev_rate *= 3.0f;
}
}
if (Flags[ManagerFlags::ModelRotationEnabled]) {
angles rot_angles;
rot_angles.p = 0.0f;
rot_angles.b = 0.0f;
rot_angles.h = PI2 * frametime / rev_rate;
vm_rotate_matrix_by_angles(&CurrentOrientation, &rot_angles);
}
if (Cmdline_show_imgui_debug)
ImGui::ShowDemoWindow();
if (CloseThis)
close();
// gr_flip() renders and submits the ImGui frame opened above.
gr_flip();
}
//Cleans the scene and resets object actions. Stops any firing weapons.
void LabManager::cleanup() {
if (CurrentObject != -1) {
// Stop any firing weapons
FireTurrets.clear();
FirePrimaries.fill(false);
FireSecondaries.fill(false);
// Remove all beams
beam_delete_all();
// Properly clean up the test objects
deleteTestObjects();
// Remove all objects
obj_delete_all();
// Reset large-ship split explosion state. In the lab we can delete exploding ships while
// cycling classes, so clear any lingering Split_ships entries tied to the previous view.
shipfx_large_blowup_level_init();
// Clean up the particles
particle::kill_all();
// Clean up our path mess
reset_ai_path_points();
// Reset lab variables
CurrentMode = LabMode::None;
CurrentObject = -1;
CurrentSubtype = -1;
CurrentClass = -1;
DockerDockPoint.clear();
DockeeDockPoint.clear();
BayPathMask = 0;
CurrentPosition = vmd_zero_vector;
CurrentOrientation = vmd_identity_matrix;
ModelFilename = "";
Player_ship = nullptr;
Lab_object_detail_level = -1;
}
}
void LabManager::deleteTestObjects() {
deleteDockerObject();
deleteBayObject();
}
void LabManager::deleteDockerObject() {
if (DockerObject >= 0) {
object* obj = &Objects[CurrentObject];
while (object_is_docked(obj)) {
object_jettison_cargo(obj, dock_get_first_docked_object(obj), 50, true);
}
obj_delete(DockerObject);
DockerObject = -1;
reset_ai_path_points();
}
}
void LabManager::deleteBayObject()
{
if (BayObject >= 0) {
object* bay_objp = &Objects[BayObject];
ai_info* aip = &Ai_info[Ships[bay_objp->instance].ai_index];
// This is kind of a hack but it gets the job done
// Since we're deleting the bay object we can flag bay doors to close
Ships[Objects[CurrentObject].instance].bay_doors_wanting_open = 0;
extern void ai_manage_bay_doors(object * pl_objp, ai_info * aip, bool done);
ai_manage_bay_doors(bay_objp, aip, true);
obj_delete(BayObject);
BayObject = -1;
reset_ai_path_points();
}
}
void LabManager::spawnDockerObject() {
deleteTestObjects();
if (DockerDockPoint.empty() || DockeeDockPoint.empty()) {
return;
}
// Check ship class index
if (DockerClass < 0 || DockerClass >= static_cast<int>(Ship_info.size())) {
mprintf(("Invalid ship class index %d\n", DockerClass));
return;
}
object* obj = &Objects[CurrentObject];
// Spawn near the target
vec3d spawn_pos = obj->pos;
vec3d offset = {{{0.0f, -50000.0f, -50000.0f}}}; // Spawn it far away then we can move it based on its radius
vec3d final_pos;
vm_vec_add(&final_pos, &spawn_pos, &offset);
matrix spawn_orient = vmd_identity_matrix;
DockerObject = ship_create(&spawn_orient, &final_pos, DockerClass, nullptr, true);
if (DockerObject < 0) {
mprintf(("Failed to create docker object with ship class index %d!\n", DockerClass));
return;
}
object* new_objp = &Objects[DockerObject];
// Set a more reasonable starting position
float offset_radius = obj->radius + new_objp->radius;
offset = {{{0.0f, obj->pos.xyz.y + offset_radius, obj->pos.xyz.z - offset_radius}}}; // Make this selectable or random?
vm_vec_add(&final_pos, &spawn_pos, &offset);
new_objp->pos = final_pos;
}
void LabManager::spawnBayObject()
{
deleteTestObjects();
// Technically this would work but that's not the intention of the test
// and suggests something went wrong
if (BayPathMask == 0) {
return;
}
// Check ship class index
if (!SCP_vector_inbounds(Ship_info, BayClass)) {
mprintf(("Invalid ship class index %d\n", BayClass));
return;
}
object* obj = &Objects[CurrentObject];
// Spawn near the target
vec3d spawn_pos = obj->pos;
vec3d offset = {{{0.0f, -50000.0f, -50000.0f}}}; // Spawn it far away then we can move it based on its radius
vec3d final_pos;
vm_vec_add(&final_pos, &spawn_pos, &offset);
matrix spawn_orient = vmd_identity_matrix;
BayObject = ship_create(&spawn_orient, &final_pos, BayClass, nullptr, true);
if (BayObject < 0) {
mprintf(("Failed to create bay test object with ship class index %d!\n", BayClass));
return;
}
object* new_objp = &Objects[BayObject];
// Set a more reasonable starting position
float offset_radius = obj->radius + new_objp->radius;
offset = {{{0.0f, obj->pos.xyz.y + offset_radius, obj->pos.xyz.z - offset_radius}}}; // Make this selectable or random?
vm_vec_add(&final_pos, &spawn_pos, &offset);
new_objp->pos = final_pos;
}
void LabManager::beginDockingTest() {
// Spawn a docker object
spawnDockerObject();
if (DockerObject >= 0) {
object* new_objp = &Objects[DockerObject];
ship* new_shipp = &Ships[new_objp->instance];
ai_info* aip = &Ai_info[new_shipp->ai_index];
// TODO: Get the pos of the first dock path point and set the ship's position to that
// Ensure AI is ready
ai_clear_ship_goals(aip);
// Create the dock order
ai_goal_type type = ai_goal_type::EVENT_SHIP;
ai_goal* aigp = &aip->goals[0];
ai_goal_reset(aigp, true);
aigp->type = type;
aigp->target_name = ai_get_goal_target_name(Ships[Objects[CurrentObject].instance].ship_name, &aigp->target_name_index);
aigp->docker.name = ai_add_dock_name(DockerDockPoint.c_str());
aigp->dockee.name = ai_add_dock_name(DockeeDockPoint.c_str());
aigp->priority = 200;
aigp->ai_mode = AI_GOAL_DOCK;
aigp->ai_submode = AIS_DOCK_0;
aigp->flags.set(AI::Goal_Flags::Afterburn_hard);
}
}
void LabManager::beginUndockingTest() {
// Spawn a docker object if necessary
if (DockerObject < 0 || !object_is_docked(&Objects[DockerObject])) {
spawnDockerObject();
// Once spawned we need to instantly set it to docked
if (DockerObject >= 0) {
object* dockee_objp = &Objects[CurrentObject];
object* docker_objp = &Objects[DockerObject];
int docker_point_index = model_find_dock_name_index(Ship_info[Ships[docker_objp->instance].ship_info_index].model_num, DockerDockPoint.c_str());
int dockee_point_index = model_find_dock_name_index(Ship_info[Ships[dockee_objp->instance].ship_info_index].model_num, DockeeDockPoint.c_str());
// set model animations correctly
// (fortunately, this function is called AFTER model_anim_set_initial_states in the sea of ship creation
// functions, which is necessary for model animations to start from t=0 at the correct positions)
ship *shipp = &Ships[docker_objp->instance];
ship *goal_shipp = &Ships[dockee_objp->instance];
ship_info* sip = &Ship_info[shipp->ship_info_index];
ship_info* goal_sip = &Ship_info[goal_shipp->ship_info_index];
polymodel_instance* shipp_pmi = model_get_instance(shipp->model_instance_num);
polymodel_instance* goal_shipp_pmi = model_get_instance(goal_shipp->model_instance_num);
(sip->animations.getAll(shipp_pmi, animation::ModelAnimationTriggerType::Docking_Stage1, docker_point_index)
+ sip->animations.getAll(shipp_pmi, animation::ModelAnimationTriggerType::Docking_Stage2, docker_point_index)
+ sip->animations.getAll(shipp_pmi, animation::ModelAnimationTriggerType::Docking_Stage3, docker_point_index)
+ sip->animations.getAll(shipp_pmi, animation::ModelAnimationTriggerType::Docked, docker_point_index)).start(animation::ModelAnimationDirection::FWD, true, true);
(goal_sip->animations.getAll(goal_shipp_pmi, animation::ModelAnimationTriggerType::Docking_Stage1, dockee_point_index)
+ goal_sip->animations.getAll(goal_shipp_pmi, animation::ModelAnimationTriggerType::Docking_Stage2, dockee_point_index)
+ goal_sip->animations.getAll(goal_shipp_pmi, animation::ModelAnimationTriggerType::Docking_Stage3, dockee_point_index)
+ goal_sip->animations.getAll(goal_shipp_pmi, animation::ModelAnimationTriggerType::Docked, dockee_point_index)).start(animation::ModelAnimationDirection::FWD, true, true);
// Set docker as instantly docked
dock_orient_and_approach(docker_objp, docker_point_index, dockee_objp, dockee_point_index, DOA_DOCK_STAY);
ai_do_objects_docked_stuff(docker_objp, docker_point_index, dockee_objp, dockee_point_index, true);
}
}
if (DockerObject >= 0) {
ai_info* aip = &Ai_info[Ships[Objects[DockerObject].instance].ai_index];
// Ensure AI is ready
ai_clear_ship_goals(aip);
// Create the undock order
int gindex = 0;
ai_goal_type type = ai_goal_type::EVENT_SHIP;
ai_goal* aigp = &aip->goals[gindex];
ai_goal_reset(aigp, true);
aigp->type = type;
aigp->target_name = ai_get_goal_target_name(Ships[Objects[CurrentObject].instance].ship_name, &aigp->target_name_index);
aigp->priority = 200;
aigp->ai_mode = AI_GOAL_UNDOCK;
aigp->ai_submode = AIS_UNDOCK_0;
}
}
void LabManager::beginBayTest()
{
// Spawn a bay object
spawnBayObject();
if (BayObject < 0)
return;
// Reset the bay status of the current ship
ship* shipp = &Ships[Objects[CurrentObject].instance];
shipp->bay_doors_wanting_open = 0;
shipp->bay_doors_status = MA_POS_NOT_SET;
if (BayTestMode == BayMode::Arrival) {
object* new_objp = &Objects[BayObject];
ship* new_shipp = &Ships[new_objp->instance];
ai_info* aip = &Ai_info[new_shipp->ai_index];
// Ensure AI is ready
ai_clear_ship_goals(aip);
if (ai_acquire_emerge_path(new_objp, CurrentObject, BayPathMask) == -1) {
mprintf(("Unable to acquire arrival path on anchor ship %s\n", Ships[Objects[CurrentObject].instance].ship_name)); // Warning instead of print?
deleteBayObject();
return;
}
}
if (BayTestMode == BayMode::Departure) {
object* new_objp = &Objects[BayObject];
ship* new_shipp = &Ships[new_objp->instance];
ai_info* aip = &Ai_info[new_shipp->ai_index];
// Ensure AI is ready
ai_clear_ship_goals(aip);
if (ai_acquire_depart_path(new_objp, CurrentObject, BayPathMask) == -1) {
mprintf(("Unable to acquire departure path on anchor ship %s\n", Ships[Objects[CurrentObject].instance].ship_name)); // Warning instead of print?
deleteBayObject();
return;
}
// Set the object's position to the start of the path
new_objp->pos = Path_points[aip->path_start].pos;
}
}
void LabManager::changeDisplayedObject(LabMode mode, int info_index, int subtype) {
// Removing this allows reseting by clicking on the object again,
// making it easier to respawn destroyed objects
// If this is re-enabled then it will need to be modified so that
// ShowingTechModel bool toggles are also accounted for
//if (mode == CurrentMode && info_index == CurrentClass)
//return;
// Toggle the show thrusters default when we change modes
if (mode != CurrentMode) {
if (mode == LabMode::Ship) {
labUi.show_thrusters = false;
}
if (mode == LabMode::Weapon) {
labUi.show_thrusters = true;
}
// Set the render flag now
Renderer->setRenderFlag(LabRenderFlag::ShowThrusters, labUi.show_thrusters);
}
cleanup();
CurrentMode = mode;
CurrentClass = info_index;
if (CurrentMode == LabMode::Asteroid)
CurrentSubtype = subtype;
ai_paused = 1;
Player_ship = nullptr;
DockeeDockPoint.clear();
DockerDockPoint.clear();
BayPathMask = 0;
switch (CurrentMode) {
case LabMode::Ship:
CurrentObject = ship_create(&CurrentOrientation, &CurrentPosition, CurrentClass);
changeShipInternal();
if (isSafeForShips()) {
Player_ship = &Ships[Objects[CurrentObject].instance];
ai_paused = 0;
// Set the ship to play dead so it doesn't move. For the lab there are two special carveouts:
// 1: Allow subsystem rotations/translations
// 2: Allow subystems to be processed
ai_add_ship_goal_scripting(AI_GOAL_PLAY_DEAD_PERSISTENT, -1, 100, nullptr, &Ai_info[Player_ship->ai_index], 0, 0);
}
break;
case LabMode::Prop:
CurrentObject = prop_create(&CurrentOrientation, &CurrentPosition, CurrentClass);
if (isSafeForProps()) {
ModelFilename = Prop_info[CurrentClass].pof_file;
}
break;
case LabMode::Weapon:
if (ShowingTechModel && VALID_FNAME(Weapon_info[CurrentClass].tech_model)) {
ModelFilename = Weapon_info[CurrentClass].tech_model;
CurrentObject = obj_raw_pof_create(ModelFilename.c_str(), &CurrentOrientation, &CurrentPosition);
}else if (Weapon_info[CurrentClass].wi_flags[Weapon::Info_Flags::Beam]) {
beam_fire_info fire_info;
memset(&fire_info, 0, sizeof(beam_fire_info));
fire_info.accuracy = 0.000001f; // this will guarantee a hit
fire_info.bfi_flags |= BFIF_FLOATING_BEAM;
fire_info.turret = nullptr; // A free-floating beam isn't fired from a subsystem.
fire_info.burst_index = 0;
fire_info.beam_info_index = CurrentClass;
fire_info.shooter = nullptr;
fire_info.team = 0;
fire_info.starting_pos = CurrentPosition;
fire_info.target = nullptr;
fire_info.target_subsys = nullptr;
fire_info.bfi_flags |= BFIF_TARGETING_COORDS;
fire_info.fire_method = BFM_SEXP_FLOATING_FIRED;
// Fire beam straight ahead from spawn origin
vec3d origin = CurrentPosition;
vec3d endpoint;
vm_vec_scale_add(&endpoint, &origin, &vmd_z_vector, 1500.0f); // Fire forward along +Z
fire_info.target_pos1 = endpoint;
fire_info.target_pos2 = endpoint;
CurrentObject = beam_fire(&fire_info);
} else {
CurrentObject = weapon_create(&CurrentPosition, &CurrentOrientation, CurrentClass, -1);
if (Weapon_info[CurrentClass].model_num != -1) {
ModelFilename = model_get(Weapon_info[CurrentClass].model_num)->filename;
}
}
break;
case LabMode::Asteroid: {
// Ensure model is loaded before creating asteroid
asteroid_load(CurrentClass, CurrentSubtype);
object* objp = asteroid_create(&Asteroid_field, CurrentClass, CurrentSubtype, false);
if (objp != nullptr) {
CurrentObject = OBJ_INDEX(objp);
// Zero out asteroid velocity
vm_vec_zero(&objp->phys_info.rotvel);
vm_vec_zero(&objp->phys_info.desired_rotvel);
objp->flags.remove(Object::Object_Flags::Physics);
} else {
CurrentObject = -1;
mprintf(("LabManager: Failed to create asteroid for index %d, subtype %d\n", CurrentClass, CurrentSubtype));
}
break;
}
default:
UNREACHABLE("Unhandled lab mode %d", static_cast<int>(mode));
ModelFilename = "";
break;
}
Assert(CurrentObject != -1);
Renderer->getCurrentCamera()->displayedObjectChanged();
}
void LabManager::changeShipInternal() {
auto ship_objp = &Ships[Objects[CurrentObject].instance];
auto ship_infop = &Ship_info[ship_objp->ship_info_index];
// This is normally set during mission parse
ship_objp->special_exp_damage = -1;
// If the ship class defines replacement textures, load them and apply them to the ship
// load the texture
auto replacements = ship_infop->replacement_textures;
for (auto& tr : replacements) {
if (!stricmp(tr.new_texture, "invisible"))
{
// invisible is a special case
tr.new_texture_id = REPLACE_WITH_INVISIBLE;
}
else
{
// try to load texture or anim as normal
tr.new_texture_id = bm_load_either(tr.new_texture);
}
}
ship_objp->apply_replacement_textures(replacements);
ship_page_in_textures(ship_objp->ship_info_index);
if (!ship_infop->default_team_name.empty())
Renderer->setTeamColor(ship_infop->default_team_name);
ModelFilename = model_get(ship_infop->model_num)->filename;
}