Files
fs2open.github.com/fred2/bgbitmapdlg.cpp
the-eandClaude Opus 5 5e745f7535 Add RTAO and unify raytraced/shadow-mapped shadow softness on sun angular size
Raytraced shadow penumbras needed a hand-authored $SunAngularSize: in
stars.tbl, which no shipped content sets -- so in practice nothing ever
got a penumbra, and the shadow-mapped path's $Shadow Smoothness Factor:
had no relation to it at all. Derive a size from the sun bitmap instead
and drive both methods from the same number.

g3_render_rect_screen_aligned_2d() sizes the sun quad so its rad is the
tangent of the half-angle it subtends, making tan(angular radius) equal
to 0.05 * the mission's +Scale: * the emitting disc's fraction of the
bitmap. That fraction is the area of the pixels at or above 90% of the
brightest one, converted to an equivalent radius. Across 90 sun bitmaps
from retail, the MediaVPs, Blue Planet and BtA it lands at a median of
0.261 (retail's chunkier art at 0.487), and the threshold acts as a
constant factor rather than a per-bitmap judgement call -- Spearman rho
0.96 between a 90% and a 50% cutoff.

Taken literally that yields suns 3.9-8.5x Sol's apparent diameter once
+Scale: is folded in (median 1.55, up to 5.0 in retail's own missions),
so the measurement is scaled by 0.25 and clamped to 1.5 degrees, putting
typical content near Sol. Derivation is the default: $SunAngularSize: 0
asks for hard shadows, an explicit value still wins, and missions can
also set +AngularSize: on an individual sun to override both, with FRED
and QtFRED UI (validation and fallback included) to author it. Bitmaps
that can't be read -- and the deliberately blank sun bitmaps mods ship
to get a light source with no visible disc -- fall back to hard shadows
rather than to the widest possible penumbra.

That angular size now sizes the raytraced penumbra cone directly
(traceShadowRayCone() in shadows.sdr), with sample count following the
same Shadow_quality tier that picks the shadow map's resolution (1 ray
at Low, up to 16 at Ultra) rather than being a separate setting. The
shadow-mapped path's tabled smoothness values were tuned by eye against
Sol-sized retail/MediaVPs art, so shadow_smoothness_scale() in
shadows.cpp scales them by the active sun's size relative to Sol,
clamped to what the shadow map can represent (shadow_clamp_smoothness()).
The two methods still disagree about occluder distance -- the raytracer
uses the real one, the shadow map's tabled values stand in for a blocker
distance pinned to the cascade's own extent -- so RT_SHADOW_SUN_SIZE_CALIBRATION
in shadows.sdr is a constant factor that makes a given $SunAngularSize:
read as roughly the same softness either way; it's the number to revisit
if the shadow map ever grows a real blocker search.

Also adds RTAO (raytraced ambient occlusion) sharing the same TLAS as
the shadow rays, with lighting-profile-driven radius/strength, and lab
controls for RT shadow quality (Low/High), the local light cap, RT
shadow samples, RTAO, and an angular-size override for comparing both
shadow methods against the same sun. All are safe to change live: they
only gate which lights are picked as shadow casters or scale existing
uniforms while filling the per-frame light data.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-11-07 19:45:47 +01:00

1765 lines
49 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 "globalincs/vmallocator.h"
#include "stdafx.h"
#include "FRED.h"
#include "FREDView.h"
#include "FREDDoc.h"
#include "BgBitmapDlg.h"
#include "listitemchooser.h"
#include "bmpman/bmpman.h"
#include "graphics/light.h"
#include "lighting/lighting_profiles.h"
#include "math/bitarray.h"
#include "mission/missionparse.h"
#include "nebula/neb.h"
#include "nebula/neblightning.h"
#include "parse/parselo.h"
#include "starfield/starfield.h"
#include "starfield/nebula.h"
#include "missioneditor/common.h"
#include <vcruntime.h>
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// bg_bitmap_dlg dialog
bg_bitmap_dlg::bg_bitmap_dlg(CWnd* pParent) : CDialog(bg_bitmap_dlg::IDD, pParent)
{
//{{AFX_DATA_INIT(bg_bitmap_dlg)
m_neb_intensity = _T("");
m_envmap = _T("");
m_nebula_color = -1;
m_nebula_index = -1;
m_bank = 0;
m_heading = 0;
m_pitch = 0;
m_neb2_texture = 0;
m_subspace = FALSE;
m_fullneb = FALSE;
m_fog_color_override = FALSE;
m_fullneb_background_bitmaps = FALSE;
m_fog_r = 0;
m_fog_g = 0;
m_fog_b = 0;
m_toggle_trails = FALSE;
m_corrected_angles_in_mission_file = FALSE;
s_pitch = 0.f;
s_bank = 0.f;
s_heading = 0.f;
s_scale = 1.0f;
s_angular_size_override = FALSE;
s_angular_size = SUN_ANGULAR_SIZE_SOL;
s_index = -1;
b_pitch = 0.f;
b_bank = 0.f;
b_heading = 0.f;
b_scale_x = 1.0f; b_scale_y = 1.0f;
b_div_x = 1; b_div_y = 1;
b_index = -1;
m_skybox_model = _T("");
m_skybox_pitch = 0.f;
m_skybox_bank = 0.f;
m_skybox_heading = 0.f;
m_sky_flag_1 = The_mission.skybox_flags & MR_NO_LIGHTING ? 1 : 0;
m_sky_flag_2 = The_mission.skybox_flags & MR_ALL_XPARENT ? 1 : 0;
m_sky_flag_3 = The_mission.skybox_flags & MR_NO_ZBUFFER ? 1 : 0;
m_sky_flag_4 = The_mission.skybox_flags & MR_NO_CULL ? 1 : 0;
m_sky_flag_5 = The_mission.skybox_flags & MR_NO_GLOWMAPS ? 1 : 0;
m_sky_flag_6 = The_mission.skybox_flags & MR_FORCE_CLAMP ? 1 : 0;
m_light_profile_index = 0;
//}}AFX_DATA_INIT
}
void bg_bitmap_dlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(bg_bitmap_dlg)
DDX_Control(pDX, IDC_AMBIENT_B_SLIDER, m_amb_blue);
DDX_Control(pDX, IDC_AMBIENT_G_SLIDER, m_amb_green);
DDX_Control(pDX, IDC_AMBIENT_R_SLIDER, m_amb_red);
DDX_Text(pDX, IDC_NEB2_INTENSITY, m_neb_intensity);
DDX_CBIndex(pDX, IDC_NEBCOLOR, m_nebula_color);
DDX_CBIndex(pDX, IDC_NEBPATTERN, m_nebula_index);
DDX_Text(pDX, IDC_BANK, m_bank);
DDX_Text(pDX, IDC_HEADING, m_heading);
DDX_Text(pDX, IDC_PITCH, m_pitch);
DDX_Control(pDX, IDC_SLIDER1, m_slider);
DDX_CBIndex(pDX, IDC_NEB2_TEXTURE, m_neb2_texture);
DDX_Check(pDX, IDC_SUBSPACE, m_subspace);
DDX_Check(pDX, IDC_FULLNEB, m_fullneb);
DDX_Check(pDX, IDC_NEB2_PALETTE_OVERRIDE, m_fog_color_override);
DDX_Check(pDX, IDC_NEB2_BACKGROUND_BITMAPS, m_fullneb_background_bitmaps);
DDX_Check(pDX, IDC_CORRECTED_ANGLES_IN_MISSION_FILE, m_corrected_angles_in_mission_file);
DDX_Check(pDX, IDC_NEB2_TOGGLE_TRAILS, m_toggle_trails);
DDX_Text(pDX, IDC_SUN1, s_name);
DDX_Text(pDX, IDC_SUN1_P, s_pitch);
DDV_MinMaxFloat(pDX, s_pitch, 0.f, DEGREE_UB);
DDX_Text(pDX, IDC_SUN1_B, s_bank);
DDV_MinMaxFloat(pDX, s_bank, 0.f, DEGREE_UB);
DDX_Text(pDX, IDC_SUN1_H, s_heading);
DDV_MinMaxFloat(pDX, s_heading, 0.f, DEGREE_UB);
DDX_Text(pDX, IDC_SUN1_SCALE, s_scale);
DDV_MinMaxFloat(pDX, s_scale, 0.1f, 50.0f);
DDX_Check(pDX, IDC_SUN1_ANGULAR_SIZE_OVERRIDE, s_angular_size_override);
DDX_Text(pDX, IDC_SUN1_ANGULAR_SIZE, s_angular_size);
DDV_MinMaxFloat(pDX, s_angular_size, 0.0f, SUN_ANGULAR_SIZE_MAX);
DDX_Text(pDX, IDC_SBITMAP, b_name);
DDX_Text(pDX, IDC_SBITMAP_P, b_pitch);
DDV_MinMaxFloat(pDX, b_pitch, 0.f, DEGREE_UB);
DDX_Text(pDX, IDC_SBITMAP_B, b_bank);
DDV_MinMaxFloat(pDX, b_bank, 0.f, DEGREE_UB);
DDX_Text(pDX, IDC_SBITMAP_H, b_heading);
DDV_MinMaxFloat(pDX, b_heading, 0.f, DEGREE_UB);
DDX_Text(pDX, IDC_SBITMAP_SCALE_X, b_scale_x);
DDV_MinMaxFloat(pDX, b_scale_x, .001f, 18.0f);
DDX_Text(pDX, IDC_SBITMAP_SCALE_Y, b_scale_y);
DDV_MinMaxFloat(pDX, b_scale_y, .001f, 18.0f);
DDX_Text(pDX, IDC_SBITMAP_DIV_X, b_div_x);
DDV_MinMaxInt(pDX, b_div_x, 1, 5);
DDX_Text(pDX, IDC_SBITMAP_DIV_Y, b_div_y);
DDV_MinMaxInt(pDX, b_div_y, 1, 5);
DDX_Text(pDX, IDC_SKYBOX_FNAME, m_skybox_model);
DDX_Text(pDX, IDC_SKYBOX_P, m_skybox_pitch);
DDV_MinMaxFloat(pDX, m_skybox_pitch, 0.f, DEGREE_UB);
DDX_Text(pDX, IDC_SKYBOX_B, m_skybox_bank);
DDV_MinMaxFloat(pDX, m_skybox_bank, 0.f, DEGREE_UB);
DDX_Text(pDX, IDC_SKYBOX_H, m_skybox_heading);
DDV_MinMaxFloat(pDX, m_skybox_heading, 0.f, DEGREE_UB);
DDX_Text(pDX, IDC_ENVMAP, m_envmap);
DDX_Check(pDX, IDC_SKY_FLAG_NO_LIGHTING, m_sky_flag_1);
DDX_Check(pDX, IDC_SKY_FLAG_XPARENT, m_sky_flag_2);
DDX_Check(pDX, IDC_SKY_FLAG_NO_ZBUFF, m_sky_flag_3);
DDX_Check(pDX, IDC_SKY_FLAG_NO_CULL, m_sky_flag_4);
DDX_Check(pDX, IDC_SKY_FLAG_NO_GLOW, m_sky_flag_5);
DDX_Check(pDX, IDC_SKY_FLAG_CLAMP, m_sky_flag_6);
DDX_Text(pDX, IDC_NEB2_FOG_1000M_VIS, m_neb_fog_1000m_vis);
DDX_Text(pDX, IDC_NEB2_FOG_NEAR_DIST, m_neb_fog_near_dist);
DDX_Text(pDX, IDC_NEB2_FOG_SKYBOX_CLIP, m_neb_fog_skybox_clip);
DDX_Text(pDX, IDC_NEB2_FOG_CLIP, m_neb_fog_clip);
DDX_CBIndex(pDX, IDC_LIGHT_PROFILE, m_light_profile_index);
DDX_Text(pDX, IDC_NEB2_FOG_R, m_fog_r);
DDV_MinMaxInt(pDX, m_fog_r, 0, 255);
DDX_Text(pDX, IDC_NEB2_FOG_G, m_fog_g);
DDV_MinMaxInt(pDX, m_fog_g, 0, 255);
DDX_Text(pDX, IDC_NEB2_FOG_B, m_fog_b);
DDV_MinMaxInt(pDX, m_fog_b, 0, 255);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(bg_bitmap_dlg, CDialog)
//{{AFX_MSG_MAP(bg_bitmap_dlg)
ON_WM_CLOSE()
ON_CBN_SELCHANGE(IDC_NEBCOLOR, OnSelchangeNebcolor)
ON_CBN_SELCHANGE(IDC_NEBPATTERN, OnSelchangeNebpattern)
ON_BN_CLICKED(IDC_FULLNEB, OnFullNeb)
ON_BN_CLICKED(IDC_NEB2_PALETTE_OVERRIDE, OnNeb2PaletteOverride)
ON_CBN_SELCHANGE(IDC_NEB2_TEXTURE, OnSelchangeNeb2Texture)
ON_WM_HSCROLL()
ON_LBN_SELCHANGE(IDC_SUN1_LIST, OnSunChange)
ON_BN_CLICKED(IDC_SUN1_ANGULAR_SIZE_OVERRIDE, OnSunAngularSizeOverride)
ON_BN_CLICKED(IDC_ADD_SUN, OnAddSun)
ON_BN_CLICKED(IDC_DEL_SUN, OnDelSun)
ON_CBN_SELCHANGE(IDC_SUN1, OnSunDropdownChange)
ON_LBN_SELCHANGE(IDC_SBITMAP_LIST, OnBitmapChange)
ON_BN_CLICKED(IDC_ADD_SBITMAP, OnAddBitmap)
ON_BN_CLICKED(IDC_DEL_SBITMAP, OnDelBitmap)
ON_CBN_SELCHANGE(IDC_SBITMAP, OnBitmapDropdownChange)
ON_NOTIFY(UDN_DELTAPOS, IDC_SBITMAP_P_SPIN, OnDeltaposSbitmapPSpin)
ON_NOTIFY(UDN_DELTAPOS, IDC_SBITMAP_B_SPIN, OnDeltaposSbitmapBSpin)
ON_NOTIFY(UDN_DELTAPOS, IDC_SBITMAP_H_SPIN, OnDeltaposSbitmapHSpin)
ON_EN_KILLFOCUS(IDC_SBITMAP_SCALE_X, OnKillfocusSbitmapScaleX)
ON_EN_KILLFOCUS(IDC_SBITMAP_SCALE_Y, OnKillfocusSbitmapScaleY)
ON_EN_KILLFOCUS(IDC_SBITMAP_DIV_X, OnKillfocusSbitmapDivX)
ON_EN_KILLFOCUS(IDC_SBITMAP_DIV_Y, OnKillfocusSbitmapDivY)
ON_EN_KILLFOCUS(IDC_SBITMAP_P, OnKillfocusSbitmapP)
ON_EN_KILLFOCUS(IDC_SBITMAP_B, OnKillfocusSbitmapB)
ON_EN_KILLFOCUS(IDC_SBITMAP_H, OnKillfocusSbitmapH)
ON_NOTIFY(UDN_DELTAPOS, IDC_SUN1_P_SPIN, OnDeltaposSun1PSpin)
ON_NOTIFY(UDN_DELTAPOS, IDC_SUN1_H_SPIN, OnDeltaposSun1HSpin)
ON_NOTIFY(UDN_DELTAPOS, IDC_SUN1_B_SPIN, OnDeltaposSun1BSpin)
ON_EN_KILLFOCUS(IDC_SUN1_P, OnKillfocusSun1P)
ON_EN_KILLFOCUS(IDC_SUN1_H, OnKillfocusSun1H)
ON_EN_KILLFOCUS(IDC_SUN1_B, OnKillfocusSun1B)
ON_EN_KILLFOCUS(IDC_SUN1_SCALE, OnKillfocusSun1Scale)
ON_EN_KILLFOCUS(IDC_SUN1_ANGULAR_SIZE, OnKillfocusSun1AngularSize)
ON_BN_CLICKED(IDC_ADD_BACKGROUND, OnAddBackground)
ON_BN_CLICKED(IDC_REMOVE_BACKGROUND, OnRemoveBackground)
ON_BN_CLICKED(IDC_IMPORT_BACKGROUND, OnImportBackground)
ON_BN_CLICKED(IDC_SWAP_BACKGROUND, OnSwapBackground)
ON_CBN_SELCHANGE(IDC_BACKGROUND_NUM, OnBackgroundDropdownChange)
ON_BN_CLICKED(IDC_SKYBOX_MODEL, OnSkyboxBrowse)
ON_NOTIFY(UDN_DELTAPOS, IDC_SKYBOX_P_SPIN, OnDeltaposSkyboxPSpin)
ON_NOTIFY(UDN_DELTAPOS, IDC_SKYBOX_B_SPIN, OnDeltaposSkyboxBSpin)
ON_NOTIFY(UDN_DELTAPOS, IDC_SKYBOX_H_SPIN, OnDeltaposSkyboxHSpin)
ON_EN_KILLFOCUS(IDC_SKYBOX_P, OnKillfocusSkyboxP)
ON_EN_KILLFOCUS(IDC_SKYBOX_B, OnKillfocusSkyboxB)
ON_EN_KILLFOCUS(IDC_SKYBOX_H, OnKillfocusSkyboxH)
ON_BN_CLICKED(IDC_ENVMAP_BROWSE, OnEnvmapBrowse)
ON_EN_KILLFOCUS(IDC_NEB2_FOG_R, OnKillfocusNeb2FogR)
ON_EN_KILLFOCUS(IDC_NEB2_FOG_G, OnKillfocusNeb2FogG)
ON_EN_KILLFOCUS(IDC_NEB2_FOG_B, OnKillfocusNeb2FogB)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// bg_bitmap_dlg message handlers
BOOL bg_bitmap_dlg::OnInitDialog()
{
CDialog::OnInitDialog();
//create tool tip controls
m_CorrectedAnglesToolTip.Create(this);
CWnd* pWnd = GetDlgItem(IDC_CORRECTED_ANGLES_IN_MISSION_FILE);
m_CorrectedAnglesToolTip.AddTool(pWnd, "Mission files saved in 22.0 and earlier versions of FRED use incorrect math for calculating the background angles");
m_CorrectedAnglesToolTip.Activate(TRUE);
m_FogParamsToolTip.Create(this);
m_FogParamsToolTip.AddTool(GetDlgItem(IDC_NEB2_FOG_1000M_VIS), "Fraction of light that survives 1000 meters of fog (smaller = thicker fog).");
m_FogParamsToolTip.AddTool(GetDlgItem(IDC_NEB2_FOG_NEAR_DIST), "Distance from the camera where fog starts (in meters).");
m_FogParamsToolTip.AddTool(GetDlgItem(IDC_NEB2_FOG_SKYBOX_CLIP), "Maximum render distance for the skybox in fog (in meters). 0 disables skybox fog.");
m_FogParamsToolTip.AddTool(GetDlgItem(IDC_NEB2_FOG_CLIP), "Maximum render distance for the scene in fog (in meters). 0 disables scene fog.");
m_FogParamsToolTip.Activate(TRUE);
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
BOOL bg_bitmap_dlg::PreTranslateMessage(MSG* pMsg)
{
if (m_CorrectedAnglesToolTip.GetSafeHwnd() != nullptr)
m_CorrectedAnglesToolTip.RelayEvent(pMsg);
if (m_FogParamsToolTip.GetSafeHwnd() != nullptr)
m_FogParamsToolTip.RelayEvent(pMsg);
return CDialog::PreTranslateMessage(pMsg);
}
void bg_bitmap_dlg::create()
{
char buf[40];
CComboBox *box;
CDialog::Create(bg_bitmap_dlg::IDD);
theApp.init_window(&Bg_wnd_data, this);
// disable the sun bank controls because, per Asteroth, setting the bank is no longer meaningful
GetDlgItem(IDC_SUN1_B)->EnableWindow(FALSE);
GetDlgItem(IDC_SUN1_B_SPIN)->EnableWindow(FALSE);
box = (CComboBox *) GetDlgItem(IDC_NEBCOLOR);
for (int i=0; i<NUM_NEBULA_COLORS; i++){
box->AddString(Nebula_colors[i]);
}
m_slider.SetRange(0, MAX_STARS);
m_slider.SetPos(Num_stars);
sprintf(buf, "%d", Num_stars);
GetDlgItem(IDC_TOTAL)->SetWindowText(buf);
build_nebfile_list();
// setup neb poof names
for (size_t i = 0; i < Poof_info.size(); ++i)
{
((CListBox*) GetDlgItem(IDC_NEB2_POOF_LIST))->AddString(Poof_info[i].name);
// check all relevant poofs
if (get_bit(Neb2_poof_flags.get(), i))
((CListBox*) GetDlgItem(IDC_NEB2_POOF_LIST))->SetSel(static_cast<int>(i), TRUE);
else
((CListBox*) GetDlgItem(IDC_NEB2_POOF_LIST))->SetSel(static_cast<int>(i), FALSE);
}
m_skybox_model = _T(The_mission.skybox_model);
m_envmap = _T(The_mission.envmap_name);
angles skybox_angles;
vm_extract_angles_matrix(&skybox_angles, &The_mission.skybox_orientation);
m_skybox_pitch = fl_degrees_100ths(skybox_angles.p);
m_skybox_bank = fl_degrees_100ths(skybox_angles.b);
m_skybox_heading = fl_degrees_100ths(skybox_angles.h);
//make sure angle values are in the 0-359 degree range
if (m_skybox_pitch < 0) m_skybox_pitch += 360;
if (m_skybox_bank < 0) m_skybox_bank += 360;
if (m_skybox_heading < 0) m_skybox_heading += 360;
for (size_t i = 0; i < Neb2_bitmap_filenames.size(); i++) {
((CComboBox*)GetDlgItem(IDC_NEB2_TEXTURE))->AddString(Neb2_bitmap_filenames[i].c_str());
}
// if we have a texture selected already
if(strlen(Neb2_texture_name) > 0){ //-V805
m_neb2_texture = ((CComboBox*)GetDlgItem(IDC_NEB2_TEXTURE))->SelectString(-1, Neb2_texture_name);
if(m_neb2_texture == CB_ERR){
((CComboBox*)GetDlgItem(IDC_NEB2_TEXTURE))->SetCurSel(0);
m_neb2_texture = 0;
}
} else {
((CComboBox*)GetDlgItem(IDC_NEB2_TEXTURE))->SetCurSel(0);
}
// setup lightning storm names
((CComboBox*)GetDlgItem(IDC_NEB2_LIGHTNING))->ResetContent();
((CComboBox*)GetDlgItem(IDC_NEB2_LIGHTNING))->AddString(CString("none"));
for(size_t j=0; j<Storm_types.size(); j++){
((CComboBox*)GetDlgItem(IDC_NEB2_LIGHTNING))->AddString(CString(Storm_types[j].name));
}
((CComboBox*)GetDlgItem(IDC_NEB2_LIGHTNING))->SelectString(-1, Mission_parse_storm_name);
// if the nebula intensity wasn't set before - set it now
if(Neb2_awacs < 0.0f){
m_neb_intensity = CString("3000");
} else {
char whee[25] = "";
m_neb_intensity = CString(itoa((int)Neb2_awacs, whee, 10));
}
m_fullneb = The_mission.flags[Mission::Mission_Flags::Fullneb] ? TRUE : FALSE;
m_fog_color_override = The_mission.flags[Mission::Mission_Flags::Neb2_fog_color_override] ? TRUE : FALSE;
m_fullneb_background_bitmaps = The_mission.flags[Mission::Mission_Flags::Fullneb_background_bitmaps] ? TRUE : FALSE;
// determine if a full Neb2 is active - load in the full nebula filenames or the partial neb
// filenames
if (!m_fullneb) {
// since there is no "none" option for the full nebulas
m_nebula_index = Nebula_index + 1;
m_nebula_color = Mission_palette;
if (Nebula_index < 0){
GetDlgItem(IDC_NEBCOLOR)->EnableWindow(FALSE);
}
m_pitch = Nebula_pitch;
m_bank = Nebula_bank;
m_heading = Nebula_heading;
// no full nebula, no override or bitmaps
m_fog_color_override = FALSE;
m_fullneb_background_bitmaps = FALSE;
}
((CButton*)GetDlgItem(IDC_FULLNEB))->SetCheck(m_fullneb);
((CButton*)GetDlgItem(IDC_NEB2_PALETTE_OVERRIDE))->SetCheck(m_fog_color_override);
((CButton*)GetDlgItem(IDC_NEB2_BACKGROUND_BITMAPS))->SetCheck(m_fullneb_background_bitmaps);
m_fog_r = Neb2_fog_color[0];
m_fog_g = Neb2_fog_color[1];
m_fog_b = Neb2_fog_color[2];
m_toggle_trails = (The_mission.flags[Mission::Mission_Flags::Toggle_ship_trails]) ? 1 : 0;
((CButton*)GetDlgItem(IDC_NEB2_TOGGLE_TRAILS))->SetCheck(m_toggle_trails);
// setup background numbering
for (size_t i = 0; i < Backgrounds.size(); i++)
{
char temp[NAME_LENGTH];
sprintf(temp, "Background " SIZE_T_ARG, i + 1);
((CComboBox*) GetDlgItem(IDC_BACKGROUND_NUM))->AddString(temp);
((CComboBox*) GetDlgItem(IDC_BACKGROUND_SWAP_NUM))->AddString(temp);
}
((CComboBox*) GetDlgItem(IDC_BACKGROUND_NUM))->SetCurSel(0);
((CComboBox*) GetDlgItem(IDC_BACKGROUND_SWAP_NUM))->SetCurSel(0);
// we can't remove the only remaining background
GetDlgItem(IDC_REMOVE_BACKGROUND)->EnableWindow(Backgrounds.size() > 1);
// setup sun and sunglow controls
sun_data_init();
// setup bitmap info
bitmap_data_init();
// determine if subspace is active
m_subspace = (The_mission.flags[Mission::Mission_Flags::Subspace]) ? 1 : 0;
m_amb_red.SetRange(1,255);
m_amb_green.SetRange(1,255);
m_amb_blue.SetRange(1,255);
m_amb_red.SetPos(The_mission.ambient_light_level & 0xff);
m_amb_green.SetPos((The_mission.ambient_light_level >> 8) & 0xff);
m_amb_blue.SetPos((The_mission.ambient_light_level >> 16) & 0xff);
sprintf(buf, "Red: %d", m_amb_red.GetPos());
GetDlgItem(IDC_AMBIENT_R_TEXT)->SetWindowText(buf);
sprintf(buf, "Green: %d", m_amb_green.GetPos());
GetDlgItem(IDC_AMBIENT_G_TEXT)->SetWindowText(buf);
sprintf(buf, "Blue: %d", m_amb_blue.GetPos());
GetDlgItem(IDC_AMBIENT_B_TEXT)->SetWindowText(buf);
m_neb_fog_1000m_vis.Format("%.10g", Neb2_fog_1000m_visibility);
m_neb_fog_near_dist.Format("%.10g", Neb2_fog_near_distance);
m_neb_fog_skybox_clip.Format("%.10g", Neb2_fog_skybox_clip_distance);
m_neb_fog_clip.Format("%.10g", Neb2_fog_clip_distance);
m_initial_neb_fog_1000m_vis = Neb2_fog_1000m_visibility;
m_initial_neb_fog_near_dist = Neb2_fog_near_distance;
m_initial_neb_fog_skybox_clip = Neb2_fog_skybox_clip_distance;
m_initial_neb_fog_clip = Neb2_fog_clip_distance;
box = (CComboBox *) GetDlgItem(IDC_LIGHT_PROFILE);
SCP_vector<SCP_string> profiles = lighting_profiles::list_profiles();
m_light_profile_index = 0;
for(size_t idx = 0; idx<profiles.size();idx++){
SCP_string n = profiles[idx];
box->AddString(profiles[idx].c_str());
if(The_mission.lighting_profile_name == n)
m_light_profile_index = (int) idx;
}
box->SetCurSel(m_light_profile_index);
background_flags_init();
UpdateData(FALSE);
OnFullNeb();
update_data();
update_map_window();
set_modified();
}
void bg_bitmap_dlg::OnOK()
{
OnClose();
}
void bg_bitmap_dlg::OnCancel()
{
OnClose();
}
BOOL bg_bitmap_dlg::DestroyWindow()
{
Bg_bitmap_dialog = nullptr;
return CDialog::DestroyWindow();
}
void bg_bitmap_dlg::OnClose()
{
UpdateData(TRUE);
Mission_palette = m_nebula_color;
if(m_fullneb){
The_mission.flags.set(Mission::Mission_Flags::Fullneb);
Neb2_awacs = (float)atoi((LPCSTR)m_neb_intensity);
// override dumb values with reasonable ones
if(Neb2_awacs <= 0.00000001f){
Neb2_awacs = DEFAULT_NEBULA_RANGE;
}
// store poof flags
clear_all_bits(Neb2_poof_flags.get(), Poof_info.size());
for (size_t i = 0; i < Poof_info.size(); ++i)
{
if (((CListBox*) GetDlgItem(IDC_NEB2_POOF_LIST))->GetSel(static_cast<int>(i)))
set_bit(Neb2_poof_flags.get(), i);
}
// get the bitmap name
if ((m_neb2_texture >= 0) && (m_neb2_texture < (int)Neb2_bitmap_filenames.size())){
strcpy_s(Neb2_texture_name, Neb2_bitmap_filenames[m_neb2_texture].c_str());
}
// init the nebula
neb2_level_init();
} else {
The_mission.flags.remove(Mission::Mission_Flags::Fullneb);
Nebula_index = m_nebula_index - 1;
Neb2_awacs = -1.0f;
strcpy_s(Neb2_texture_name, "");
// no full nebula, no override or bitmaps
m_fog_color_override = FALSE;
m_fullneb_background_bitmaps = FALSE;
}
The_mission.flags.set(Mission::Mission_Flags::Neb2_fog_color_override, m_fog_color_override == TRUE);
if (m_fog_color_override) {
Neb2_fog_color[0] = (ubyte)m_fog_r;
Neb2_fog_color[1] = (ubyte)m_fog_g;
Neb2_fog_color[2] = (ubyte)m_fog_b;
}
The_mission.flags.set(Mission::Mission_Flags::Fullneb_background_bitmaps, m_fullneb_background_bitmaps == TRUE);
// check for no ship trails -C
The_mission.flags.set(Mission::Mission_Flags::Toggle_ship_trails, m_toggle_trails != 0);
// get selected storm
((CComboBox*)GetDlgItem(IDC_NEB2_LIGHTNING))->GetLBText(((CComboBox*)GetDlgItem(IDC_NEB2_LIGHTNING))->GetCurSel(), Mission_parse_storm_name);
Nebula_pitch = m_pitch;
Nebula_bank = m_bank;
Nebula_heading = m_heading;
nebula_init(Nebula_index, m_pitch, m_bank, m_heading);
The_mission.flags.set(Mission::Mission_Flags::Subspace, m_subspace != 0);
string_copy(The_mission.skybox_model, m_skybox_model, NAME_LENGTH - 1, 1);
string_copy(The_mission.envmap_name, m_envmap, NAME_LENGTH - 1, 1);
angles skybox_angles;
skybox_angles.p = fl_radians(m_skybox_pitch);
skybox_angles.b = fl_radians(m_skybox_bank);
skybox_angles.h = fl_radians(m_skybox_heading);
vm_angles_2_matrix(&The_mission.skybox_orientation, &skybox_angles);
//store the skybox flags
The_mission.skybox_flags = 0;
if(m_sky_flag_1) {
The_mission.skybox_flags |= MR_NO_LIGHTING;
}
if(m_sky_flag_2) {
The_mission.skybox_flags |= MR_ALL_XPARENT;
}
if(m_sky_flag_3) {
The_mission.skybox_flags |= MR_NO_ZBUFFER;
}
if(m_sky_flag_4) {
The_mission.skybox_flags |= MR_NO_CULL;
}
if(m_sky_flag_5) {
The_mission.skybox_flags |= MR_NO_GLOWMAPS;
}
if(m_sky_flag_6) {
The_mission.skybox_flags |= MR_FORCE_CLAMP;
}
Neb2_fog_1000m_visibility = (float)atof((LPCSTR)m_neb_fog_1000m_vis);
Neb2_fog_near_distance = (float)atof((LPCSTR)m_neb_fog_near_dist);
Neb2_fog_skybox_clip_distance = (float)atof((LPCSTR)m_neb_fog_skybox_clip);
Neb2_fog_clip_distance = (float)atof((LPCSTR)m_neb_fog_clip);
// if the user set or changed any modern value, we can no longer save the legacy values
// (and we might not have saved them in the first place)
if (!fl_equal(Neb2_fog_1000m_visibility, m_initial_neb_fog_1000m_vis)
|| !fl_equal(Neb2_fog_near_distance, m_initial_neb_fog_near_dist)
|| !fl_equal(Neb2_fog_skybox_clip_distance, m_initial_neb_fog_skybox_clip)
|| !fl_equal(Neb2_fog_clip_distance, m_initial_neb_fog_clip))
{
Neb2_fog_save_legacy_values = false;
}
extern const float Default_max_draw_distance;
CLAMP(Neb2_fog_1000m_visibility, 0.0f, 1.0f);
if (Neb2_fog_near_distance <= 0.0f)
Neb2_fog_near_distance = 10.0f;
if (Neb2_fog_skybox_clip_distance < 0.0f)
Neb2_fog_skybox_clip_distance = Default_max_draw_distance;
if (Neb2_fog_clip_distance < 0.0f)
Neb2_fog_clip_distance = Default_max_draw_distance;
The_mission.lighting_profile_name = lighting_profiles::list_profiles()[m_light_profile_index];
// close sun data
sun_data_close();
// close bitmap data
bitmap_data_close();
// store current flags
background_flags_close();
// reset the background
stars_pack_backgrounds();
stars_load_first_valid_background();
// close window stuff
theApp.record_window_data(&Bg_wnd_data, this);
delete Bg_bitmap_dialog;
Bg_bitmap_dialog = nullptr;
FREDDoc_ptr->autosave("background editor");
}
void bg_bitmap_dlg::update_data(int update)
{
if (update){
UpdateData(TRUE);
}
UpdateData(FALSE);
}
void bg_bitmap_dlg::OnSelchangeNebcolor()
{
CWaitCursor wait;
UpdateData(TRUE);
Mission_palette = m_nebula_color;
Update_window = 1;
}
void bg_bitmap_dlg::OnSelchangeNebpattern()
{
CWaitCursor wait;
UpdateData(TRUE);
// fullneb indexes differently
Nebula_index = m_nebula_index - 1;
GetDlgItem(IDC_NEBCOLOR)->EnableWindow(m_nebula_index ? TRUE : FALSE);
nebula_init(Nebula_index, m_pitch, m_bank, m_heading);
Update_window = 1;
}
void bg_bitmap_dlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
{
char buf[40];
CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
MODIFY(Num_stars, m_slider.GetPos());
sprintf(buf, "%d", Num_stars);
GetDlgItem(IDC_TOTAL)->SetWindowText(buf);
int col = 0;
col |= m_amb_red.GetPos();
col |= m_amb_green.GetPos() << 8;
col |= m_amb_blue.GetPos() << 16;
sprintf(buf, "Red: %d", m_amb_red.GetPos());
GetDlgItem(IDC_AMBIENT_R_TEXT)->SetWindowText(buf);
sprintf(buf, "Green: %d", m_amb_green.GetPos());
GetDlgItem(IDC_AMBIENT_G_TEXT)->SetWindowText(buf);
sprintf(buf, "Blue: %d", m_amb_blue.GetPos());
GetDlgItem(IDC_AMBIENT_B_TEXT)->SetWindowText(buf);
The_mission.ambient_light_level = col;
gr_set_ambient_light(m_amb_red.GetPos(), m_amb_green.GetPos(), m_amb_blue.GetPos());
}
// when the user toggled the "Full Nebula" button
void bg_bitmap_dlg::OnFullNeb()
{
// determine what state we're in
UpdateData(TRUE);
if (m_fullneb) {
// enable all fullneb controls
GetDlgItem(IDC_NEB2_INTENSITY)->EnableWindow(TRUE);
GetDlgItem(IDC_NEB2_TEXTURE)->EnableWindow(TRUE);
GetDlgItem(IDC_NEB2_LIGHTNING)->EnableWindow(TRUE);
GetDlgItem(IDC_NEB2_POOF_LIST)->EnableWindow(TRUE);
GetDlgItem(IDC_NEB2_FOG_1000M_VIS)->EnableWindow(TRUE);
GetDlgItem(IDC_NEB2_FOG_NEAR_DIST)->EnableWindow(TRUE);
GetDlgItem(IDC_NEB2_FOG_SKYBOX_CLIP)->EnableWindow(TRUE);
GetDlgItem(IDC_NEB2_FOG_CLIP)->EnableWindow(TRUE);
GetDlgItem(IDC_NEB2_PALETTE_OVERRIDE)->EnableWindow(TRUE);
GetDlgItem(IDC_NEB2_FOG_R)->EnableWindow(m_fog_color_override);
GetDlgItem(IDC_NEB2_FOG_G)->EnableWindow(m_fog_color_override);
GetDlgItem(IDC_NEB2_FOG_B)->EnableWindow(m_fog_color_override);
GetDlgItem(IDC_NEB2_BACKGROUND_BITMAPS)->EnableWindow(TRUE);
GetDlgItem(IDC_NEB2_TOGGLE_TRAILS)->EnableWindow(TRUE);
// disable non-fullneb controls
GetDlgItem(IDC_NEBPATTERN)->EnableWindow(FALSE);
GetDlgItem(IDC_NEBCOLOR)->EnableWindow(FALSE);
GetDlgItem(IDC_PITCH)->EnableWindow(FALSE);
GetDlgItem(IDC_BANK)->EnableWindow(FALSE);
GetDlgItem(IDC_HEADING)->EnableWindow(FALSE);
} else {
// enable all non-fullneb controls
GetDlgItem(IDC_NEBPATTERN)->EnableWindow(TRUE);
GetDlgItem(IDC_NEBCOLOR)->EnableWindow(TRUE);
GetDlgItem(IDC_PITCH)->EnableWindow(TRUE);
GetDlgItem(IDC_BANK)->EnableWindow(TRUE);
GetDlgItem(IDC_HEADING)->EnableWindow(TRUE);
// disable all fullneb controls
GetDlgItem(IDC_NEB2_INTENSITY)->EnableWindow(FALSE);
GetDlgItem(IDC_NEB2_TEXTURE)->EnableWindow(FALSE);
GetDlgItem(IDC_NEB2_LIGHTNING)->EnableWindow(FALSE);
GetDlgItem(IDC_NEB2_POOF_LIST)->EnableWindow(FALSE);
GetDlgItem(IDC_NEB2_FOG_1000M_VIS)->EnableWindow(FALSE);
GetDlgItem(IDC_NEB2_FOG_NEAR_DIST)->EnableWindow(FALSE);
GetDlgItem(IDC_NEB2_FOG_SKYBOX_CLIP)->EnableWindow(FALSE);
GetDlgItem(IDC_NEB2_FOG_CLIP)->EnableWindow(FALSE);
GetDlgItem(IDC_NEB2_PALETTE_OVERRIDE)->EnableWindow(FALSE);
GetDlgItem(IDC_NEB2_FOG_R)->EnableWindow(FALSE);
GetDlgItem(IDC_NEB2_FOG_G)->EnableWindow(FALSE);
GetDlgItem(IDC_NEB2_FOG_B)->EnableWindow(FALSE);
GetDlgItem(IDC_NEB2_BACKGROUND_BITMAPS)->EnableWindow(FALSE);
GetDlgItem(IDC_NEB2_TOGGLE_TRAILS)->EnableWindow(FALSE);
}
}
void bg_bitmap_dlg::OnNeb2PaletteOverride()
{
UpdateData(TRUE);
GetDlgItem(IDC_NEB2_FOG_R)->EnableWindow(m_fog_color_override);
GetDlgItem(IDC_NEB2_FOG_G)->EnableWindow(m_fog_color_override);
GetDlgItem(IDC_NEB2_FOG_B)->EnableWindow(m_fog_color_override);
OnSelchangeNeb2Texture();
}
void bg_bitmap_dlg::OnSelchangeNeb2Texture()
{
UpdateData(TRUE);
// regenerate the palette colors
if (m_fog_color_override)
{
ubyte rgb[3];
neb2_generate_fog_color(m_neb2_texture >= 0 ? Neb2_bitmap_filenames[m_neb2_texture].c_str() : "", rgb);
m_fog_r = rgb[0];
m_fog_g = rgb[1];
m_fog_b = rgb[2];
UpdateData(FALSE);
}
}
// clear and build the nebula filename list appropriately
void bg_bitmap_dlg::build_nebfile_list()
{
int i;
CComboBox *box = (CComboBox *) GetDlgItem(IDC_NEBPATTERN);
// wacky
Assert(box != NULL);
if(box == NULL){
return;
}
// clear the box
box->ResetContent();
// add all necessary strings
box->AddString("None");
for (i=0; i<NUM_NEBULAS; i++){
box->AddString(Nebula_filenames[i]);
}
// select the first elementccombobox
box->SetCurSel(0);
OnSelchangeNebpattern();
}
void bg_bitmap_dlg::sun_data_init()
{
int idx;
CComboBox *ccb = (CComboBox*) GetDlgItem(IDC_SUN1);
CListBox *clb = (CListBox*) GetDlgItem(IDC_SUN1_LIST);
background_t *background = &Backgrounds[get_active_background()];
// clear if necessary
ccb->ResetContent();
clb->ResetContent();
// add all suns to the drop down
for (idx = 0; idx < stars_get_num_entries(true, true); idx++)
{
ccb->AddString(stars_get_name_FRED(idx, true));
}
// add all suns by bitmap filename to the list
for (idx = 0; idx < (int)background->suns.size(); idx++)
{
clb->AddString(background->suns[idx].filename);
}
// if we have at least one item, select it
if (!background->suns.empty())
{
clb->SetCurSel(0);
OnSunChange();
}
else
{
update_sun_angular_size_enabled();
}
}
void bg_bitmap_dlg::sun_data_close()
{
// if there is an active sun, save it
sun_data_save_current();
}
void bg_bitmap_dlg::sun_data_save_current()
{
// if we have an active item
if (s_index >= 0)
{
background_t *background = &Backgrounds[get_active_background()];
starfield_list_entry *sle = &background->suns[s_index];
// read out of the controls
UpdateData(TRUE);
// store the data
strcpy_s(sle->filename, s_name);
sle->ang.p = fl_radians(s_pitch);
sle->ang.b = fl_radians(s_bank);
sle->ang.h = fl_radians(s_heading);
sle->scale_x = (float) s_scale;
sle->scale_y = 1.0f;
sle->div_x = 1;
sle->div_y = 1;
sle->angular_size = s_angular_size_override ? s_angular_size : SUN_ANGULAR_SIZE_UNSPECIFIED;
}
}
void bg_bitmap_dlg::OnSunChange()
{
// save the current sun
sun_data_save_current();
// select the new one
s_index = ((CListBox*) GetDlgItem(IDC_SUN1_LIST))->GetCurSel();
// setup data
if (s_index >= 0)
{
int drop_index;
background_t *background = &Backgrounds[get_active_background()];
starfield_list_entry *sle = &background->suns[s_index];
s_name = CString(sle->filename);
s_pitch = fl_degrees_100ths(sle->ang.p);
s_bank = fl_degrees_100ths(sle->ang.b);
s_heading = fl_degrees_100ths(sle->ang.h);
s_scale = sle->scale_x;
// an unset angular size leaves the edit box showing Sol's, so that ticking the
// checkbox starts somewhere sensible rather than at whatever was last selected
s_angular_size_override = (sle->angular_size >= 0.0f) ? TRUE : FALSE;
s_angular_size = (sle->angular_size >= 0.0f) ? sle->angular_size : SUN_ANGULAR_SIZE_SOL;
// make sure angles are in the 0-359 degree range;
// an angle of 6.28318310, which is less than 6.28318548,
// is converted to 359.999847, which (if converted to int) is rounded to 360
if (s_pitch >= 360) s_pitch -= 360;
if (s_bank >= 360) s_bank -= 360;
if (s_heading >= 360) s_heading -= 360;
// stuff back into the controls
UpdateData(FALSE);
// select the proper item from the dropdown
drop_index = ((CComboBox*) GetDlgItem(IDC_SUN1))->FindString(-1, sle->filename);
if(drop_index != CB_ERR)
((CComboBox*) GetDlgItem(IDC_SUN1))->SetCurSel(drop_index);
}
update_sun_angular_size_enabled();
// refresh the background
stars_load_background(get_active_background());
}
void bg_bitmap_dlg::OnAddSun()
{
starfield_list_entry sle;
// save any current
sun_data_save_current();
// select the first sun by default
strcpy_s(sle.filename, stars_get_name_FRED(0, true));
sle.ang.p = 0;
sle.ang.b = 0;
sle.ang.h = 0;
sle.scale_x = 1.0f;
sle.scale_y = 1.0f;
sle.div_x = 1;
sle.div_y = 1;
Backgrounds[get_active_background()].suns.push_back(sle);
// add to the listbox and select it
int add_index = ((CListBox*) GetDlgItem(IDC_SUN1_LIST))->AddString(sle.filename);
((CListBox*) GetDlgItem(IDC_SUN1_LIST))->SetCurSel(add_index);
// call the OnSunChange function to setup all relevant data in the class
OnSunChange();
}
void bg_bitmap_dlg::OnDelSun()
{
// if we don't have an active item
if(s_index < 0)
return;
// remove the item from the list
((CListBox*) GetDlgItem(IDC_SUN1_LIST))->DeleteString(s_index);
// remove it from the list
background_t *background = &Backgrounds[get_active_background()];
background->suns.erase(background->suns.begin() + s_index);
// no item selected, let the message handler assign a new one
s_index = -1;
// refresh the background
stars_load_background(get_active_background());
}
void bg_bitmap_dlg::OnSunDropdownChange()
{
// if we have no active sun, do nothing
if (s_index < 0)
return;
int new_index = ((CComboBox*) GetDlgItem(IDC_SUN1))->GetCurSel();
Assert(new_index != CB_ERR);
// get the new string
if(new_index != CB_ERR)
{
((CComboBox*) GetDlgItem(IDC_SUN1))->GetLBText(new_index, s_name);
// change the name of the string in the listbox
((CListBox*) GetDlgItem(IDC_SUN1_LIST))->DeleteString(s_index);
((CListBox*) GetDlgItem(IDC_SUN1_LIST))->InsertString(s_index, (const char*) s_name);
OnSunChange();
}
}
void bg_bitmap_dlg::bitmap_data_init()
{
int idx;
CComboBox *ccb = (CComboBox*) GetDlgItem(IDC_SBITMAP);
CListBox *clb = (CListBox*) GetDlgItem(IDC_SBITMAP_LIST);
background_t *background = &Backgrounds[get_active_background()];
// clear if necessary
ccb->ResetContent();
clb->ResetContent();
// add all bitmaps to the drop down
for (idx = 0; idx < stars_get_num_entries(false, true); idx++)
{
ccb->AddString(stars_get_name_FRED(idx, false));
}
// add all bitmaps by bitmap filename to the list
for (idx = 0; idx < (int)background->bitmaps.size(); idx++)
{
clb->AddString(background->bitmaps[idx].filename);
}
// if we have at least one item, select it
if (!background->bitmaps.empty())
{
clb->SetCurSel(0);
OnBitmapChange();
}
}
void bg_bitmap_dlg::bitmap_data_close()
{
// if there is an active bitmap, save it
bitmap_data_save_current();
}
void bg_bitmap_dlg::bitmap_data_save_current()
{
// if we have an active item
if (b_index >= 0)
{
background_t *background = &Backgrounds[get_active_background()];
starfield_list_entry *sle = &background->bitmaps[b_index];
// read out of the controls
UpdateData(TRUE);
// store the data
strcpy_s(sle->filename, b_name);
sle->ang.p = fl_radians(b_pitch);
sle->ang.b = fl_radians(b_bank);
sle->ang.h = fl_radians(b_heading);
sle->scale_x = (float) b_scale_x;
sle->scale_y = (float) b_scale_y;
sle->div_x = b_div_x;
sle->div_y = b_div_y;
}
}
void bg_bitmap_dlg::OnBitmapChange()
{
// save the current bitmap
bitmap_data_save_current();
// select the new one
b_index = ((CListBox*) GetDlgItem(IDC_SBITMAP_LIST))->GetCurSel();
// setup data
if (b_index >= 0)
{
int drop_index;
background_t *background = &Backgrounds[get_active_background()];
starfield_list_entry *sle = &background->bitmaps[b_index];
b_name = CString(sle->filename);
b_pitch = fl_degrees_100ths(sle->ang.p);
b_bank = fl_degrees_100ths(sle->ang.b);
b_heading = fl_degrees_100ths(sle->ang.h);
b_scale_x = sle->scale_x;
b_scale_y = sle->scale_y;
b_div_x = sle->div_x;
b_div_y = sle->div_y;
// make sure angles are in the 0-359 degree range
if (b_pitch >= 360) b_pitch -= 360;
if (b_bank >= 360) b_bank -= 360;
if (b_heading >= 360) b_heading -= 360;
// stuff back into the controls
UpdateData(FALSE);
// select the proper item from the dropdown
drop_index = ((CComboBox*) GetDlgItem(IDC_SBITMAP))->FindString(-1, sle->filename);
if(drop_index != CB_ERR)
((CComboBox*) GetDlgItem(IDC_SBITMAP))->SetCurSel(drop_index);
}
// refresh the background
stars_load_background(get_active_background());
}
void bg_bitmap_dlg::OnAddBitmap()
{
starfield_list_entry sle;
// save any current
bitmap_data_save_current();
// select the first bitmap by default
strcpy_s(sle.filename, stars_get_name_FRED(0, false));
sle.ang.p = 0;
sle.ang.b = 0;
sle.ang.h = 0;
sle.scale_x = 1.0f;
sle.scale_y = 1.0f;
sle.div_x = 1;
sle.div_y = 1;
Backgrounds[get_active_background()].bitmaps.push_back(sle);
// add to the listbox and select it
int add_index = ((CListBox*) GetDlgItem(IDC_SBITMAP_LIST))->AddString(sle.filename);
((CListBox*) GetDlgItem(IDC_SBITMAP_LIST))->SetCurSel(add_index);
// call the OnBitmapChange function to setup all relevant data in the class
OnBitmapChange();
}
void bg_bitmap_dlg::OnDelBitmap()
{
// if we don't have an active item
if(b_index < 0)
return;
// remove the item from the list
((CListBox*) GetDlgItem(IDC_SBITMAP_LIST))->DeleteString(b_index);
// remove it from the list
background_t *background = &Backgrounds[get_active_background()];
background->bitmaps.erase(background->bitmaps.begin() + b_index);
// no item selected, let the message handler assign a new one
b_index = -1;
// refresh the background
stars_load_background(get_active_background());
}
void bg_bitmap_dlg::OnBitmapDropdownChange()
{
// if we have no active bitmap, do nothing
if (b_index < 0)
return;
int new_index = ((CComboBox*) GetDlgItem(IDC_SBITMAP))->GetCurSel();
Assert(new_index != CB_ERR);
// get the new string
if(new_index != CB_ERR)
{
((CComboBox*) GetDlgItem(IDC_SBITMAP))->GetLBText(new_index, b_name);
// change the name of the string in the listbox
((CListBox*) GetDlgItem(IDC_SBITMAP_LIST))->DeleteString(b_index);
((CListBox*) GetDlgItem(IDC_SBITMAP_LIST))->InsertString(b_index, (const char*) b_name);
OnBitmapChange();
}
}
void bg_bitmap_dlg::get_data_spinner(NM_UPDOWN* pUD, int id, float *var, float min, float max)
{
float old_val = *var;
if (pUD->iDelta > 0)
{
*var = std::floor(*var);
if (fl_equal(*var, old_val))
(*var)--;
//go min->max
if (*var < min)
{
*var = std::floor(max);
}
}
else
{
*var = std::ceil(*var);
if (fl_equal(*var, old_val))
(*var)++;
//go max->min
if (*var > max)
{
*var = std::ceil(min);
}
}
// there is no SetDlgItemFloat, so let's treat it as int
// (note, there will be no fractional part here, unless we later decide to use a fractional increment)
this->SetDlgItemInt(id, static_cast<int>(*var));
}
void bg_bitmap_dlg::get_data_float(int id, float *var, float min, float max, int precision)
{
Assertion(precision >= 1 && precision <= 3, "Precision must be between 1 and 3!");
char buf[16];
char max_ch[16];
char min_ch[16];
this->GetDlgItemText(id, buf, 16);
*var = (float)atof(buf);
sprintf(max_ch, "%.*f", precision, max);
sprintf(min_ch, "%.*f", precision, min);
CString error_msg = "Please enter a number between ";
error_msg += min_ch;
error_msg += " and ";
error_msg += max_ch;
if (*var < min)
{
*var = min;
this->SetDlgItemText(id, min_ch);
MessageBox(error_msg, "Error", MB_OK | MB_ICONWARNING);
}
if (*var > max)
{
*var = max;
this->SetDlgItemText(id, max_ch);
MessageBox(error_msg, "Error", MB_OK | MB_ICONWARNING);
}
}
void bg_bitmap_dlg::get_data_int(int id, int *var, int min, int max)
{
char max_ch[16];
char min_ch[16];
*var=this->GetDlgItemInt(id);
sprintf(max_ch,"%d",max);
sprintf(min_ch,"%d",min);
CString error_msg = "Please enter a number between ";
error_msg += min_ch;
error_msg += " and ";
error_msg += max_ch;
if (*var < min)
{
*var = min;
SetDlgItemInt(id, min);
MessageBox(error_msg, "Error", MB_OK | MB_ICONWARNING);
}
if (*var > max)
{
*var = max;
SetDlgItemInt(id, max);
MessageBox(error_msg, "Error", MB_OK | MB_ICONWARNING);
}
}
void bg_bitmap_dlg::OnDeltaposSbitmapPSpin(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_UPDOWN* pNMUpDown = (NM_UPDOWN*)pNMHDR;
if (b_index < 0) return;
get_data_spinner(pNMUpDown, IDC_SBITMAP_P, &b_pitch, 0.f, DEGREE_UB);
OnBitmapChange();
*pResult = 0;
}
void bg_bitmap_dlg::OnDeltaposSbitmapBSpin(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_UPDOWN* pNMUpDown = (NM_UPDOWN*)pNMHDR;
if (b_index < 0) return;
get_data_spinner(pNMUpDown, IDC_SBITMAP_B, &b_bank, 0.f, DEGREE_UB);
OnBitmapChange();
*pResult = 0;
}
void bg_bitmap_dlg::OnDeltaposSbitmapHSpin(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_UPDOWN* pNMUpDown = (NM_UPDOWN*)pNMHDR;
if (b_index < 0) return;
get_data_spinner(pNMUpDown, IDC_SBITMAP_H, &b_heading, 0.f, DEGREE_UB);
OnBitmapChange();
*pResult = 0;
}
void bg_bitmap_dlg::OnKillfocusSbitmapScaleX()
{
if (b_index < 0) return;
get_data_float(IDC_SBITMAP_SCALE_X, &b_scale_x, 0.001f, 18.0f, 3);
OnBitmapChange();
}
void bg_bitmap_dlg::OnKillfocusSbitmapScaleY()
{
if (b_index < 0) return;
get_data_float(IDC_SBITMAP_SCALE_Y, &b_scale_y, 0.001f, 18.0f, 3);
OnBitmapChange();
}
void bg_bitmap_dlg::OnKillfocusSbitmapDivX()
{
if (b_index < 0) return;
get_data_int(IDC_SBITMAP_DIV_X, &b_div_x, 1,5);
OnBitmapChange();
}
void bg_bitmap_dlg::OnKillfocusSbitmapDivY()
{
if (b_index < 0) return;
get_data_int(IDC_SBITMAP_DIV_Y, &b_div_y, 1,5);
OnBitmapChange();
}
void bg_bitmap_dlg::OnKillfocusSbitmapP()
{
if (b_index < 0) return;
get_data_float(IDC_SBITMAP_P, &b_pitch, 0.f, DEGREE_UB, 2);
OnBitmapChange();
}
void bg_bitmap_dlg::OnKillfocusSbitmapB()
{
if (b_index < 0) return;
get_data_float(IDC_SBITMAP_B, &b_bank, 0.f, DEGREE_UB, 2);
OnBitmapChange();
}
void bg_bitmap_dlg::OnKillfocusSbitmapH()
{
if (b_index < 0) return;
get_data_float(IDC_SBITMAP_H, &b_heading, 0.f, DEGREE_UB, 2);
OnBitmapChange();
}
void bg_bitmap_dlg::OnDeltaposSun1PSpin(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_UPDOWN* pNMUpDown = (NM_UPDOWN*)pNMHDR;
if (s_index < 0) return;
get_data_spinner(pNMUpDown, IDC_SUN1_P, &s_pitch, 0.f, DEGREE_UB);
OnSunChange();
*pResult = 0;
}
void bg_bitmap_dlg::OnDeltaposSun1BSpin(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_UPDOWN* pNMUpDown = (NM_UPDOWN*)pNMHDR;
if (s_index < 0) return;
get_data_spinner(pNMUpDown, IDC_SUN1_B, &s_bank, 0.f, DEGREE_UB);
OnSunChange();
*pResult = 0;
}
void bg_bitmap_dlg::OnDeltaposSun1HSpin(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_UPDOWN* pNMUpDown = (NM_UPDOWN*)pNMHDR;
if (s_index < 0) return;
get_data_spinner(pNMUpDown, IDC_SUN1_H, &s_heading, 0.f, DEGREE_UB);
OnSunChange();
*pResult = 0;
}
void bg_bitmap_dlg::OnKillfocusSun1P()
{
if (s_index < 0) return;
get_data_float(IDC_SUN1_P, &s_pitch, 0.f, DEGREE_UB, 2);
OnSunChange();
}
void bg_bitmap_dlg::OnKillfocusSun1H()
{
if (s_index < 0) return;
get_data_float(IDC_SUN1_H, &s_heading, 0.f, DEGREE_UB, 2);
OnSunChange();
}
void bg_bitmap_dlg::OnKillfocusSun1B()
{
if (s_index < 0) return;
get_data_float(IDC_SUN1_B, &s_bank, 0.f, DEGREE_UB, 2);
OnSunChange();
}
void bg_bitmap_dlg::OnKillfocusSun1Scale()
{
if (s_index < 0) return;
get_data_float(IDC_SUN1_SCALE, &s_scale, 0.1f, 50.0f, 3);
OnSunChange();
}
void bg_bitmap_dlg::OnKillfocusSun1AngularSize()
{
if (s_index < 0) return;
get_data_float(IDC_SUN1_ANGULAR_SIZE, &s_angular_size, 0.0f, SUN_ANGULAR_SIZE_MAX, 3);
OnSunChange();
}
void bg_bitmap_dlg::OnSunAngularSizeOverride()
{
UpdateData(TRUE);
update_sun_angular_size_enabled();
sun_data_save_current();
}
// the size only means anything when the mission is actually setting one
void bg_bitmap_dlg::update_sun_angular_size_enabled()
{
CWnd *size_edit = GetDlgItem(IDC_SUN1_ANGULAR_SIZE);
if (size_edit != nullptr)
size_edit->EnableWindow((s_index >= 0) && s_angular_size_override);
CWnd *size_check = GetDlgItem(IDC_SUN1_ANGULAR_SIZE_OVERRIDE);
if (size_check != nullptr)
size_check->EnableWindow(s_index >= 0);
}
void bg_bitmap_dlg::OnDeltaposSkyboxPSpin(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_UPDOWN* pNMUpDown = (NM_UPDOWN*)pNMHDR;
get_data_spinner(pNMUpDown, IDC_SKYBOX_P, &m_skybox_pitch, 0.f, DEGREE_UB);
OnOrientationChange();
*pResult = 0;
}
void bg_bitmap_dlg::OnDeltaposSkyboxBSpin(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_UPDOWN* pNMUpDown = (NM_UPDOWN*)pNMHDR;
get_data_spinner(pNMUpDown, IDC_SKYBOX_B, &m_skybox_bank, 0.f, DEGREE_UB);
OnOrientationChange();
*pResult = 0;
}
void bg_bitmap_dlg::OnDeltaposSkyboxHSpin(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_UPDOWN* pNMUpDown = (NM_UPDOWN*)pNMHDR;
get_data_spinner(pNMUpDown, IDC_SKYBOX_H, &m_skybox_heading, 0.f, DEGREE_UB);
OnOrientationChange();
*pResult = 0;
}
void bg_bitmap_dlg::OnKillfocusSkyboxP()
{
get_data_float(IDC_SKYBOX_P, &m_skybox_pitch, 0.f, DEGREE_UB, 2);
OnOrientationChange();
}
void bg_bitmap_dlg::OnKillfocusSkyboxB()
{
get_data_float(IDC_SKYBOX_B, &m_skybox_bank, 0.f, DEGREE_UB, 2);
OnOrientationChange();
}
void bg_bitmap_dlg::OnKillfocusSkyboxH()
{
get_data_float(IDC_SKYBOX_H, &m_skybox_heading, 0.f, DEGREE_UB, 2);
OnOrientationChange();
}
void bg_bitmap_dlg::OnKillfocusNeb2FogR()
{
get_data_int(IDC_NEB2_FOG_R, &m_fog_r, 0, 255);
}
void bg_bitmap_dlg::OnKillfocusNeb2FogG()
{
get_data_int(IDC_NEB2_FOG_G, &m_fog_g, 0, 255);
}
void bg_bitmap_dlg::OnKillfocusNeb2FogB()
{
get_data_int(IDC_NEB2_FOG_B, &m_fog_b, 0, 255);
}
extern void parse_one_background(background_t *background);
void bg_bitmap_dlg::OnImportBackground()
{
CFileDialog cfd(TRUE, ".fs2", NULL, 0, "FreeSpace2 Missions (*.fs2)|*.fs2||\0");
char filename[256], error_str[1024];
int temp, count;
char *saved_mp;
//warn on pressing the button
if (!stars_background_empty(Backgrounds[get_active_background()]))
{
if (MessageBox("This action will erase any stars and bitmaps already placed. Continue?", "Fred2", MB_ICONWARNING | MB_YESNO) == IDNO)
return;
}
//check if cancel was pressed
if (cfd.DoModal() == IDCANCEL)
return;
strcpy_s(filename, cfd.GetPathName());
try
{
// parse in the new file
read_file_text(filename);
reset_parse();
if (!skip_to_start_of_string("#Background bitmaps"))
return;
// skip beginning stuff
required_string("#Background bitmaps");
required_string("$Num stars:");
stuff_int(&temp);
required_string("$Ambient light level:");
stuff_int(&temp);
saved_mp = Mp;
// see if we have more than one background in this mission
count = 0;
while (skip_to_string("$Bitmap List:"))
count++;
Mp = saved_mp;
// pick one (if count is 0, it's retail with just one background)
if (count > 0)
{
int i, which = 0;
if (count > 1)
{
SCP_vector<SCP_string> backgrounds;
for (i = 1; i <= count; ++i)
{
SCP_string str("Background ");
str += std::to_string(i);
backgrounds.push_back(std::move(str));
}
ListItemChooser dlg(backgrounds);
if (dlg.DoModal() == IDCANCEL)
return;
which = dlg.GetChosenIndex();
}
for (i = 0; i < which + 1; i++)
skip_to_string("$Bitmap List:");
}
// now parse the background we've selected
parse_one_background(&Backgrounds[get_active_background()]);
reinitialize_lists();
}
catch (const parse::ParseException& e)
{
mprintf(("BGBITMAPDLG: Unable to parse '%s'! Error message = %s.\n", filename, e.what()));
sprintf(error_str, "Could not parse file: %s\n\nError message: %s", filename, e.what());
MessageBox((LPCTSTR)error_str, (LPCTSTR) "Unable to import mission background!", MB_ICONERROR | MB_OK);
return;
}
}
void bg_bitmap_dlg::reinitialize_lists()
{
b_index = -1;
s_index = -1;
// repopulate
sun_data_init();
bitmap_data_init();
// set the flags for the active background
background_flags_init();
// refresh the background
stars_load_background(get_active_background());
}
void bg_bitmap_dlg::background_flags_init()
{
auto bg = &Backgrounds[get_active_background()];
m_corrected_angles_in_mission_file = bg->flags[Starfield::Background_Flags::Corrected_angles_in_mission_file] ? TRUE : FALSE;
UpdateData(FALSE);
}
void bg_bitmap_dlg::background_flags_close()
{
UpdateData(TRUE);
auto bg = &Backgrounds[get_previous_active_background()];
bg->flags.set(Starfield::Background_Flags::Corrected_angles_in_mission_file, m_corrected_angles_in_mission_file == TRUE);
}
static int Previous_active_background = 0;
int bg_bitmap_dlg::get_previous_active_background()
{
return Previous_active_background;
}
int bg_bitmap_dlg::get_active_background()
{
// find out which background we're editing
int idx = ((CComboBox *) GetDlgItem(IDC_BACKGROUND_NUM))->GetCurSel();
if (idx < 0 || idx >= (int)Backgrounds.size())
idx = 0;
Previous_active_background = idx;
return idx;
}
int bg_bitmap_dlg::get_swap_background()
{
// find out which background we're swapping
int idx = ((CComboBox *) GetDlgItem(IDC_BACKGROUND_SWAP_NUM))->GetCurSel();
if (idx < 0 || idx >= (int)Backgrounds.size())
idx = 0;
return idx;
}
void bg_bitmap_dlg::OnBackgroundDropdownChange()
{
background_flags_close();
reinitialize_lists();
}
void bg_bitmap_dlg::OnAddBackground()
{
// store current flags
background_flags_close();
int new_index = (int)Backgrounds.size();
// add new combo box entry
char temp[NAME_LENGTH];
sprintf(temp, "Background %d", new_index + 1);
((CComboBox*)GetDlgItem(IDC_BACKGROUND_NUM))->AddString(temp);
((CComboBox*)GetDlgItem(IDC_BACKGROUND_SWAP_NUM))->AddString(temp);
// add the background slot
stars_add_blank_background(true);
// select the new entry
((CComboBox*)GetDlgItem(IDC_BACKGROUND_NUM))->SetCurSel(new_index);
((CComboBox*)GetDlgItem(IDC_BACKGROUND_SWAP_NUM))->SetCurSel(new_index);
// can remove all but one background
if (Backgrounds.size() > 1)
GetDlgItem(IDC_REMOVE_BACKGROUND)->EnableWindow(TRUE);
// refresh dialog
reinitialize_lists();
}
void bg_bitmap_dlg::OnRemoveBackground()
{
int old_index = get_active_background();
//warn on pressing the button
if (!stars_background_empty(Backgrounds[old_index]))
{
if (MessageBox("Are you sure you want to remove the current background and all of its stars and bitmaps?", "Fred2", MB_ICONWARNING | MB_YESNO) == IDNO)
return;
}
// remove the last combo box entry (not the current one, because they are numbered in order)
((CComboBox*)GetDlgItem(IDC_BACKGROUND_NUM))->DeleteString((int)Backgrounds.size() - 1);
((CComboBox*)GetDlgItem(IDC_BACKGROUND_SWAP_NUM))->DeleteString((int)Backgrounds.size() - 1);
// remove the background slot
Backgrounds.erase(Backgrounds.begin() + old_index);
// if the index is no longer valid, adjust it
if (old_index >= (int)Backgrounds.size())
old_index--;
// select the old entry
((CComboBox*)GetDlgItem(IDC_BACKGROUND_NUM))->SetCurSel(old_index);
((CComboBox*)GetDlgItem(IDC_BACKGROUND_SWAP_NUM))->SetCurSel(old_index);
// can remove all but one background
if (Backgrounds.size() <= 1)
GetDlgItem(IDC_REMOVE_BACKGROUND)->EnableWindow(FALSE);
// refresh dialog
reinitialize_lists();
}
void bg_bitmap_dlg::OnSwapBackground()
{
// store current flags
background_flags_close();
int idx1 = get_active_background();
int idx2 = get_swap_background();
// don't swap if they're the same
if (idx1 == idx2)
{
MessageBox("Cannot swap a background with itself.", "FRED2", MB_OK);
return;
}
// swap
stars_swap_backgrounds(idx1, idx2);
// refresh dialog
reinitialize_lists();
}
char *Model_file_ext = "Model Files (*.pof)|*.pof|"
"|";
char *Image_file_ext = "DDS Files (*.dds)|*.dds|"
"|";
void bg_bitmap_dlg::OnSkyboxBrowse()
{
CString filename;
int z;
UpdateData(TRUE);
// get list of
z = cfile_push_chdir(CF_TYPE_DATA);
CFileDialog dlg(TRUE, NULL, filename, OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR, Model_file_ext);
// if we have a result
if (dlg.DoModal() == IDOK) {
m_skybox_model = dlg.GetFileName();
// } else {
// m_skybox_model = _T("");
}
UpdateData(FALSE);
// restore directory
if ( !z )
cfile_pop_dir();
// load/display new skybox model (if one was selected)
stars_set_background_model( (char*)(LPCTSTR)m_skybox_model, NULL );
}
void bg_bitmap_dlg::OnOrientationChange()
{
angles temp_angles;
matrix temp_orient;
temp_angles.p = fl_radians(m_skybox_pitch);
temp_angles.b = fl_radians(m_skybox_bank);
temp_angles.h = fl_radians(m_skybox_heading);
vm_angles_2_matrix(&temp_orient, &temp_angles);
stars_set_background_orientation( &temp_orient );
}
void bg_bitmap_dlg::OnEnvmapBrowse()
{
CString filename;
int z;
UpdateData(TRUE);
ENVMAP = -1;
// get list of
z = cfile_push_chdir(CF_TYPE_DATA);
CFileDialog dlg(TRUE, NULL, filename, OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR, Image_file_ext);
// if we have a result
if (dlg.DoModal() == IDOK) {
m_envmap = dlg.GetFileName();
// } else {
// m_envmap = _T("");
}
UpdateData(FALSE);
// restore directory
if ( !z )
cfile_pop_dir();
// set the new envmap
ENVMAP = bm_load( (char*)(LPCTSTR)m_envmap );
}