mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 22:56:30 -10:00
* change to new fog system * Fix fog conversion issues * remove unused old neb values * Update scripting API * temporary old-value round trip * Rewrite nebula fog with four explicit parameters Replace the legacy `+Fog Near Mult` / `+Fog Far Mult` model — which depended on fixed engine constants — with four parameters that map directly to renderer behavior: +Fog 1000m Visibility fraction of light surviving 1000m of fog +Fog Near Distance meters from camera where fog begins +Fog Skybox Clip Distance max render distance for the skybox in fog +Fog Clip Distance max render distance for the scene in fog The deferred-lighting fog shader, material system, model renderer, object sort, and the Lua mission API all consume the new parameters directly. The legacy `Neb2_fog_near_mult` / `Neb2_fog_far_mult` globals are removed. Mission parsing accepts either the legacy keys or the modern keys. Legacy values are converted to the modern set at parse time, and a per-mission flag (`Neb2_fog_use_legacy_values`) records which keys to write back on save. The flag is cleared only when something actually changes, so opening and OK'ing an unmodified legacy mission preserves its on-disk format. Saving uses `optional_string_fred` to preserve user comments through edit cycles. FRED2 and QtFRED background editors both expose the four parameters in place of the old multipliers, with friendly labels and tooltips. Both editors validate input (visibility clamped to [0, 1]; near distance must be > 0; skybox and scene clip distances may be 0 to disable fog at that range) and use a snapshot-and-compare at close time so that a no-op open-then-OK never spuriously rewrites a legacy mission in the modern format. FRED2's background dialog is also reorganized so that "Display bg bitmaps in nebula", "Override nebula fog palette", the RGB row, and the four fog parameters all sit inside the Nebula group box; the "Old Nebula" group box is restored below it with its Pattern, Color, Pitch, Bank, and Heading controls back inside the frame. The dialog now overrides PreTranslateMessage so that the new fog tooltips — and the previously broken "Save sun/bitmap angles in correct format" tooltip — actually fire. QtFRED's BackgroundEditor.ui adds four QDoubleSpinBox controls in the existing nebula form layout, with per-field decimals chosen to avoid scientific notation, and per-field min/max declaring the validation rules. The QtFRED help page is updated to describe the new fields. * fix tooltips * restore forward-renderer fog * Clang Tidy * address feedback * Refine nebula fog rewrite - Rename Neb2_fog_use_legacy_values to Neb2_fog_save_legacy_values to reflect that the flag governs save format, not runtime behavior. - Decide both the save flag and the legacy-to-modern conversion purely from whether the file contains modern fog keys, dropping the prior heuristic based on required_fso_version. A mission without modern keys (legacy- or retail-format) converts on load and saves back in legacy format; missions with modern keys are saved as modern. - Snapshot the four fog values as floats at editor open and use fl_equal for the no-change comparison in both FRED2 and QtFRED, replacing the FRED2 CString comparison and the QtFRED strict inequality. - Document the on-disk invariant in missionsave: when not saving legacy keys, the four modern keys must always be written (never gated on "only if non-default"), since the parser uses their presence to decide whether to run the conversion on next load. --------- Co-authored-by: Goober5000 <[email protected]>
1284 lines
37 KiB
C++
1284 lines
37 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 "bmpman/bmpman.h"
|
|
#include "cmdline/cmdline.h"
|
|
#include "ddsutils/ddsutils.h"
|
|
#include "debugconsole/console.h"
|
|
#include "freespace.h"
|
|
#include "jpgutils/jpgutils.h"
|
|
#include "math/bitarray.h"
|
|
#include "mission/missionparse.h"
|
|
#include "nebula/neb.h"
|
|
#include "object/object.h"
|
|
#include "options/Option.h"
|
|
#include "parse/parselo.h"
|
|
#include "pcxutils/pcxutils.h"
|
|
#include "render/3d.h"
|
|
#include "render/batching.h"
|
|
#include "ship/ship.h"
|
|
#include "starfield/starfield.h"
|
|
#include "tgautils/tgautils.h"
|
|
#include "tracing/tracing.h"
|
|
#include "graphics/light.h"
|
|
|
|
|
|
// --------------------------------------------------------------------------------------------------------
|
|
// NEBULA DEFINES/VARS
|
|
//
|
|
|
|
bool Nebula_sexp_used = false;
|
|
|
|
ubyte Neb2_fog_color[3] = { 0,0,0 };
|
|
|
|
// #define NEB2_THUMBNAIL
|
|
|
|
/*
|
|
3D CARDS THAT FOG PROPERLY
|
|
Voodoo1
|
|
Voodoo2
|
|
G200
|
|
TNT
|
|
|
|
3D CARDS THAT DON'T FOG PROPERLY
|
|
Permedia2
|
|
AccelStar II
|
|
*/
|
|
|
|
// if nebula rendering is active (DCF stuff - not mission specific)
|
|
int Neb2_render_mode = NEB2_RENDER_NONE;
|
|
|
|
SCP_vector<poof_info> Poof_info;
|
|
|
|
float Poof_dist_threshold;
|
|
vec3d Poof_last_gen_pos;
|
|
SCP_vector<float> Poof_accum;
|
|
float Poof_density_multiplier;
|
|
|
|
const float UPKEEP_DIST_MULT = 1.2f;
|
|
|
|
const float PROBABLY_TOO_MANY_POOFS = 100000.0f;
|
|
|
|
// bit array of neb2 poofs
|
|
std::unique_ptr<ubyte[]> Neb2_poof_flags;
|
|
|
|
// array of neb2 bitmaps
|
|
SCP_vector<SCP_string> Neb2_bitmap_filenames;
|
|
|
|
// texture to use for this level
|
|
char Neb2_texture_name[MAX_FILENAME_LEN] = "";
|
|
|
|
float max_rotation = 3.75f;
|
|
float neb2_flash_fade = 0.3f;
|
|
|
|
// stats
|
|
int pneb_tried = 0; // total pnebs tried to render
|
|
int pneb_tossed_alpha = 0; // pnebs tossed because of alpha optimization
|
|
int pneb_tossed_dot = 0; // pnebs tossed because of dot product
|
|
int pneb_tossed_off = 0; // pnebs tossed because of being offscree
|
|
int neb_tried = 0; // total nebs tried
|
|
int neb_tossed_alpha = 0; // nebs tossed because of alpha
|
|
int neb_tossed_dot = 0; // nebs tossed because of dot product
|
|
int neb_tossed_count = 0; // nebs tossed because of max render count
|
|
|
|
// the AWACS suppression level for the nebula
|
|
float Neb2_awacs = -1.0f;
|
|
|
|
// Deprecated! Only used for compatibility
|
|
bool Neb2_fog_save_legacy_values = false;
|
|
float Neb2_fog_legacy_near_mult = 1.0f;
|
|
float Neb2_fog_legacy_far_mult = 1.0f;
|
|
|
|
// The visual render distance multipliers for the nebula
|
|
float Neb2_fog_near_distance = 10.0f;
|
|
float Neb2_fog_1000m_visibility = 0.00464158883f;
|
|
float Neb2_fog_skybox_clip_distance = Default_max_draw_distance;
|
|
float Neb2_fog_clip_distance = Default_max_draw_distance;
|
|
|
|
float Neb2_fog_visibility_trail = 1.0f;
|
|
float Neb2_fog_visibility_thruster = 1.5f;
|
|
float Neb2_fog_visibility_weapon = 1.3f;
|
|
float Neb2_fog_visibility_shield = 1.2f;
|
|
float Neb2_fog_visibility_glowpoint = 1.2f;
|
|
float Neb2_fog_visibility_beam_const = 4.0f;
|
|
float Neb2_fog_visibility_beam_scaled_factor = 0.1f;
|
|
float Neb2_fog_visibility_particle_const = 1.0f;
|
|
float Neb2_fog_visibility_particle_scaled_factor = 1.0f / 12.0f;
|
|
float Neb2_fog_visibility_shockwave = 2.5f;
|
|
float Neb2_fog_visibility_fireball_const = 1.2f;
|
|
float Neb2_fog_visibility_fireball_scaled_factor = 1.0f / 12.0f;
|
|
|
|
|
|
SCP_vector<poof> Neb2_poofs;
|
|
|
|
int Neb2_background_color[3] = {0, 0, 255}; // rgb background color (used for lame rendering)
|
|
|
|
const SCP_vector<std::pair<int, std::pair<const char*, int>>> DetailLevelValues = {{ 0, {"Minimum", 1680}},
|
|
{ 1, {"Low", 1160}},
|
|
{ 2, {"Medium", 1161}},
|
|
{ 3, {"High", 1162}},
|
|
{ 4, {"Ultra", 1721}}};
|
|
|
|
static void parse_nebula_detail_func()
|
|
{
|
|
constexpr int num_detail_presets = static_cast<int>(DefaultDetailPreset::Num_detail_presets);
|
|
int value[num_detail_presets];
|
|
stuff_int_list(value, num_detail_presets, ParseLookupType::RAW_INTEGER_TYPE);
|
|
|
|
for (int i = 0; i < num_detail_presets; i++) {
|
|
|
|
if (value[i] < 0 || value[i] > MAX_DETAIL_VALUE) {
|
|
error_display(0, "%i is an invalid detail level value!", value[i]);
|
|
} else {
|
|
change_default_detail_level(static_cast<DefaultDetailPreset>(i), DetailSetting::NebulaDetail, value[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
|
|
const auto NebulaDetailOption __UNUSED = options::OptionBuilder<int>("Graphics.NebulaDetail",
|
|
std::pair<const char*, int>{"Nebula Detail", 1361},
|
|
std::pair<const char*, int>{"Detail level of nebulas", 1697})
|
|
.category(std::make_pair("Graphics", 1825))
|
|
.values(DetailLevelValues)
|
|
.default_func([]() { return Detail.nebula_detail; })
|
|
.importance(7)
|
|
.change_listener([](int val, bool) {
|
|
Detail.nebula_detail = val;
|
|
return true;
|
|
})
|
|
.flags({options::OptionFlags::RetailBuiltinOption})
|
|
.parser(parse_nebula_detail_func)
|
|
.finish();
|
|
|
|
// --------------------------------------------------------------------------------------------------------
|
|
// NEBULA FORWARD DECLARATIONS
|
|
//
|
|
|
|
// do a pre-render of the background nebula
|
|
void neb2_pre_render(camid cid);
|
|
|
|
// fill in the position of the eye for this frame
|
|
void neb2_get_eye_pos(vec3d *eye_vector);
|
|
|
|
// fill in the eye orient for this frame
|
|
void neb2_get_eye_orient(matrix *eye_matrix);
|
|
|
|
// --------------------------------------------------------------------------------------------------------
|
|
// NEBULA FUNCTIONS
|
|
//
|
|
|
|
static poof_info* get_nebula_poof_pointer(char* nebula_name)
|
|
{
|
|
for (size_t i = 0; i < Poof_info.size(); i++) {
|
|
if (!stricmp(nebula_name, Poof_info[i].name)) {
|
|
return &Poof_info[i];
|
|
}
|
|
}
|
|
|
|
// Didn't find anything.
|
|
return nullptr;
|
|
}
|
|
|
|
void parse_nebula_table(const char* filename)
|
|
{
|
|
char name[MAX_FILENAME_LEN];
|
|
|
|
try
|
|
{
|
|
// read in the nebula.tbl
|
|
read_file_text(filename, CF_TYPE_TABLES);
|
|
reset_parse();
|
|
|
|
// allow modular tables to not define bitmaps
|
|
bool skip_background_bitmaps = false;
|
|
if (Parsing_modular_table && (check_for_string("+Poof:") || check_for_string("$Name:")))
|
|
skip_background_bitmaps = true;
|
|
|
|
// background bitmaps
|
|
while (!skip_background_bitmaps && !optional_string("#end")) {
|
|
// nebula
|
|
optional_string("+Nebula:");
|
|
stuff_string(name, F_NAME, MAX_FILENAME_LEN);
|
|
|
|
if (!generic_bitmap_exists(name) && !generic_anim_exists(name)) {
|
|
error_display(0, "Nebula bitmap %s was not found!, skipping!", name);
|
|
continue;
|
|
}
|
|
|
|
Neb2_bitmap_filenames.push_back(name);
|
|
}
|
|
|
|
// allow modular tables to not define poofs
|
|
if (Parsing_modular_table && check_for_eof())
|
|
return;
|
|
|
|
// poofs (see also check_for_string above)
|
|
while (required_string_one_of(3, "#end", "+Poof:", "$Name:")) {
|
|
bool create_if_not_found = true;
|
|
poof_info pooft;
|
|
poof_info* poofp;
|
|
bool poof_new = true;
|
|
|
|
if (optional_string("+Poof:")) { // retail style
|
|
stuff_string(name, F_NAME, MAX_FILENAME_LEN);
|
|
strcpy_s(pooft.bitmap_filename, name);
|
|
|
|
strcpy_s(pooft.name, name);
|
|
|
|
if (!generic_bitmap_exists(pooft.bitmap_filename) && !generic_anim_exists(pooft.bitmap_filename)) {
|
|
error_display(0, "Bitmap defined for nebula poof %s was not found. Skipping!", pooft.name);
|
|
continue;
|
|
}
|
|
|
|
generic_anim_init(&pooft.bitmap, name);
|
|
|
|
Poof_info.push_back(pooft);
|
|
} else if (optional_string("$Name:")) { // new style
|
|
stuff_string(pooft.name, F_NAME, NAME_LENGTH);
|
|
|
|
if (optional_string("+nocreate")) {
|
|
if (!Parsing_modular_table) {
|
|
Warning(LOCATION, "+nocreate flag used for nebula poof in non-modular table\n");
|
|
}
|
|
create_if_not_found = false;
|
|
}
|
|
|
|
// Does this poof exist already?
|
|
// If so, load this new info into it
|
|
poofp = get_nebula_poof_pointer(pooft.name);
|
|
if (poofp != nullptr) {
|
|
if (!Parsing_modular_table) {
|
|
error_display(1,
|
|
"Error: Nebula Poof %s already exists. All nebula poof names must be unique.",
|
|
pooft.name);
|
|
}
|
|
poof_new = false;
|
|
} else {
|
|
// Don't create poof if it has +nocreate and is in a modular table.
|
|
if (!create_if_not_found && Parsing_modular_table) {
|
|
if (!skip_to_start_of_string_either("$Name:", "#end")) {
|
|
error_display(1, "Missing [#end] or [$Name] after nebula poof %s", pooft.name);
|
|
}
|
|
continue;
|
|
}
|
|
Poof_info.push_back(pooft);
|
|
poofp = &Poof_info[Poof_info.size() - 1];
|
|
}
|
|
|
|
if (poof_new) {
|
|
required_string("$Bitmap:");
|
|
stuff_string(name, F_NAME, MAX_FILENAME_LEN);
|
|
strcpy_s(poofp->bitmap_filename, name);
|
|
generic_anim_init(&poofp->bitmap, name);
|
|
} else {
|
|
if (optional_string("$Bitmap:")) {
|
|
stuff_string(name, F_NAME, MAX_FILENAME_LEN);
|
|
strcpy_s(poofp->bitmap_filename, name);
|
|
generic_anim_init(&poofp->bitmap, name);
|
|
}
|
|
}
|
|
|
|
//We can't skip here because we'd have to back out the entire poof from the vector-Mjn
|
|
if (!generic_bitmap_exists(poofp->bitmap_filename) && !generic_anim_exists(poofp->bitmap_filename))
|
|
error_display(0, "Bitmap defined for nebula poof %s was not found!", poofp->name);
|
|
|
|
if (optional_string("$Scale:"))
|
|
poofp->scale = ::util::ParsedRandomFloatRange::parseRandomRange(0.01f, 100000.0f);
|
|
|
|
if (optional_string("$Density:")) {
|
|
stuff_float(&poofp->density);
|
|
if (poofp->density <= 0.0f) {
|
|
Warning(LOCATION, "Poof %s must have a density greater than 0.", poofp->name);
|
|
poofp->density = 150.0f;
|
|
}
|
|
poofp->density = 1 / (poofp->density * poofp->density * poofp->density);
|
|
}
|
|
|
|
if (optional_string("$Alignment:")) {
|
|
SCP_string type;
|
|
stuff_string(type, F_NAME);
|
|
|
|
if (!stricmp(type.c_str(), "VERTICAL"))
|
|
poofp->alignment = vmd_y_vector;
|
|
else
|
|
Warning(LOCATION, "Unrecognized alignment type '%s' for nebula poof %s", type.c_str(), poofp->name);
|
|
}
|
|
|
|
if (optional_string("$Rotation:"))
|
|
poofp->rotation = util::ParsedRandomFloatRange::parseRandomRange(-1000.0f, 1000.0f);
|
|
|
|
if (optional_string("$View Distance:")) {
|
|
stuff_float(&poofp->view_dist);
|
|
if (poofp->view_dist < 0.0f) {
|
|
Warning(LOCATION, "Poof %s must have a positive view distance.", poofp->name);
|
|
poofp->view_dist = 360.f;
|
|
}
|
|
|
|
float volume = PI * 4 / 3 * (poofp->view_dist * poofp->view_dist * poofp->view_dist);
|
|
if (volume * poofp->density > PROBABLY_TOO_MANY_POOFS) {
|
|
Warning(LOCATION, "Poof %s will have over 100,000 poofs on the field at once, and could cause serious performance issues. "
|
|
"Remember that as $Density decreases and $View Distance increases, the total number of "
|
|
"poofs increases exponentially.",
|
|
poofp->name);
|
|
}
|
|
}
|
|
|
|
if (optional_string("$Alpha:")) {
|
|
poofp->alpha = util::ParsedRandomFloatRange::parseRandomRange(0.0f, 1.0f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (const parse::ParseException& e)
|
|
{
|
|
mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", "nebula.tbl", e.what()));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// initialize neb2 stuff at game startup
|
|
void neb2_init()
|
|
{
|
|
// first parse the default table
|
|
parse_nebula_table("nebula.tbl");
|
|
|
|
// parse any modular tables
|
|
parse_modular_table("*-neb.tbm", parse_nebula_table);
|
|
|
|
// align Poof_accum with Poof_info
|
|
Poof_accum.resize(Poof_info.size());
|
|
|
|
// set up bit string
|
|
Neb2_poof_flags = std::make_unique<ubyte[]>(calculate_num_bytes(Poof_info.size()));
|
|
clear_all_bits(Neb2_poof_flags.get(), Poof_info.size());
|
|
}
|
|
|
|
// set the bits for poofs from a list of poof names
|
|
void neb2_set_poof_bits(const SCP_vector<SCP_string>& list)
|
|
{
|
|
clear_all_bits(Neb2_poof_flags.get(), Poof_info.size()); //Make absolutely sure flags are zero'd before we start adding to it-Mjn
|
|
for (const SCP_string& thisPoof : list) {
|
|
for (size_t i = 0; i < Poof_info.size(); i++) {
|
|
if (lcase_equal(Poof_info[i].name, thisPoof)) {
|
|
set_bit(Neb2_poof_flags.get(), i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool poof_is_used(size_t idx) {
|
|
return get_bit(Neb2_poof_flags.get(), idx) != 0;
|
|
}
|
|
|
|
void neb2_get_fog_color(ubyte *r, ubyte *g, ubyte *b)
|
|
{
|
|
if (r) *r = Neb2_fog_color[0];
|
|
if (g) *g = Neb2_fog_color[1];
|
|
if (b) *b = Neb2_fog_color[2];
|
|
}
|
|
|
|
void neb2_pre_level_init()
|
|
{
|
|
Neb2_awacs = -1.0f;
|
|
|
|
Neb2_fog_save_legacy_values = false;
|
|
Neb2_fog_legacy_near_mult = 1.0f;
|
|
Neb2_fog_legacy_far_mult = 1.0f;
|
|
|
|
Neb2_fog_near_distance = 10.0f;
|
|
Neb2_fog_1000m_visibility = 0.00464158883f;
|
|
Neb2_fog_skybox_clip_distance = Max_draw_distance;
|
|
Neb2_fog_clip_distance = Max_draw_distance;
|
|
|
|
strcpy_s(Neb2_texture_name, "");
|
|
clear_all_bits(Neb2_poof_flags.get(), Poof_info.size());
|
|
|
|
strcpy_s(Mission_parse_storm_name, "none");
|
|
}
|
|
|
|
void neb2_level_init()
|
|
{
|
|
Nebula_sexp_used = false;
|
|
}
|
|
|
|
float nNf_near, nNf_density;
|
|
|
|
void neb2_poof_setup()
|
|
{
|
|
if (!any_bits_set(Neb2_poof_flags.get(), Poof_info.size()))
|
|
return;
|
|
|
|
// make the total density of poofs be the average of all poofs, and each poofs density is its relative proportion compared to others
|
|
// this way we maintain the retail way of not affecting total density by having more poof types
|
|
float Poof_density_sum_square = 0.0f;
|
|
float Poof_density_sum = 0.0f;
|
|
|
|
// also determine the minimum distance before re-triggering a poof upkeep
|
|
Poof_dist_threshold = 9999.0f;
|
|
for (size_t i = 0; i < Poof_info.size(); i++) {
|
|
if (poof_is_used(i)) {
|
|
Poof_density_sum_square += Poof_info[i].density * Poof_info[i].density;
|
|
Poof_density_sum += Poof_info[i].density;
|
|
|
|
float dist_threshold = Poof_info[i].view_dist * (UPKEEP_DIST_MULT - 1.0f);
|
|
if (dist_threshold < Poof_dist_threshold)
|
|
Poof_dist_threshold = dist_threshold;
|
|
}
|
|
}
|
|
Poof_density_multiplier = Poof_density_sum_square / (Poof_density_sum * Poof_density_sum);
|
|
Poof_density_multiplier *= (Detail.nebula_detail + 0.5f) / (MAX_DETAIL_VALUE + 0.5f); // scale the poofs down based on detail level
|
|
}
|
|
|
|
void neb2_generate_fog_color(const char *fog_color_palette, ubyte fog_color[])
|
|
{
|
|
// Set a default colour just in case something goes wrong
|
|
fog_color[0] = 30;
|
|
fog_color[1] = 52;
|
|
fog_color[2] = 157;
|
|
|
|
if (!fog_color_palette || !strlen(fog_color_palette))
|
|
return;
|
|
|
|
auto fog_data = new ubyte[768];
|
|
|
|
if (pcx_read_header(fog_color_palette, nullptr, nullptr, nullptr, nullptr, fog_data) == PCX_ERROR_NONE) {
|
|
// based on the palette, get an average color value (this doesn't really account for actual pixel usage though)
|
|
ushort r = 0, g = 0, b = 0, pcount = 0;
|
|
for (int idx = 0; idx < 768; idx += 3) {
|
|
if (fog_data[idx] || fog_data[idx+1] || fog_data[idx+2]) {
|
|
r = r + fog_data[idx];
|
|
g = g + fog_data[idx+1];
|
|
b = b + fog_data[idx+2];
|
|
pcount++;
|
|
}
|
|
}
|
|
|
|
if (pcount > 0) {
|
|
fog_color[0] = (ubyte)(r / pcount);
|
|
fog_color[1] = (ubyte)(g / pcount);
|
|
fog_color[2] = (ubyte)(b / pcount);
|
|
} else {
|
|
// it's just black
|
|
fog_color[0] = fog_color[1] = fog_color[2] = 0;
|
|
}
|
|
}
|
|
|
|
// done, now free up the palette data
|
|
delete[] fog_data;
|
|
}
|
|
|
|
// initialize nebula stuff - call from game_post_level_init(), so the mission has been loaded
|
|
void neb2_post_level_init(bool fog_color_override)
|
|
{
|
|
// standalone servers can bail here
|
|
if (Game_mode & GM_STANDALONE_SERVER) {
|
|
return;
|
|
}
|
|
|
|
// Skip actual rendering if we're in FRED.
|
|
if (Fred_running)
|
|
{
|
|
Neb2_render_mode = NEB2_RENDER_NONE;
|
|
return;
|
|
}
|
|
|
|
// if the mission is not a fullneb mission, skip
|
|
if ( !((The_mission.flags[Mission::Mission_Flags::Fullneb]) || Nebula_sexp_used) ) {
|
|
Neb2_render_mode = NEB2_RENDER_NONE;
|
|
Neb2_awacs = -1.0f;
|
|
return;
|
|
}
|
|
|
|
// OK, lets try something a bit more interesting
|
|
if (!fog_color_override && strlen(Neb2_texture_name)) {
|
|
neb2_generate_fog_color(Neb2_texture_name, Neb2_fog_color);
|
|
}
|
|
|
|
Neb2_render_mode = NEB2_RENDER_HTL;
|
|
|
|
// load in all nebula bitmaps
|
|
for (poof_info &pinfo : Poof_info) {
|
|
if (pinfo.bitmap.first_frame < 0) {
|
|
if (generic_anim_load(&pinfo.bitmap)) {
|
|
// fall back to non-animated type
|
|
pinfo.bitmap.first_frame = bm_load(pinfo.bitmap_filename);
|
|
if (pinfo.bitmap.first_frame >= 0) {
|
|
pinfo.bitmap.num_frames = 1;
|
|
pinfo.bitmap.total_time = 1.0f;
|
|
} else {
|
|
mprintf(("Could not find a usable bitmap for nebula poof '%s'!\n", pinfo.name));
|
|
Warning(LOCATION, "Could not find a usable bitmap (%s) for nebula poof '%s'!\n", pinfo.bitmap_filename, pinfo.name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pneb_tried = 0;
|
|
pneb_tossed_alpha = 0;
|
|
pneb_tossed_dot = 0;
|
|
neb_tried = 0;
|
|
neb_tossed_alpha = 0;
|
|
neb_tossed_dot = 0;
|
|
neb_tossed_count = 0;
|
|
|
|
// if we are going to use fullneb, but aren't fullneb yet, then be sure to reset our mode
|
|
if ( !(The_mission.flags[Mission::Mission_Flags::Fullneb]) ) {
|
|
Neb2_render_mode = NEB2_RENDER_NONE;
|
|
Neb2_awacs = -1.0f;
|
|
}
|
|
|
|
// set the mission fog near dist and density
|
|
neb2_get_adjusted_fog_values(&nNf_near, &nNf_density);
|
|
|
|
for (float& accum : Poof_accum)
|
|
accum = 0.0f;
|
|
|
|
// a bit awkward but this will force a full sphere gen
|
|
vm_vec_make(&Poof_last_gen_pos, 999999.0f, 999999.0f, 999999.0f);
|
|
|
|
neb2_poof_setup();
|
|
}
|
|
|
|
// shutdown nebula stuff
|
|
void neb2_level_close()
|
|
{
|
|
// standalone servers can bail here
|
|
if (Game_mode & GM_STANDALONE_SERVER) {
|
|
return;
|
|
}
|
|
|
|
// if the mission is not a fullneb mission, skip
|
|
if ( !((The_mission.flags[Mission::Mission_Flags::Fullneb]) || Nebula_sexp_used) ) {
|
|
return;
|
|
}
|
|
|
|
// unload all nebula bitmaps
|
|
for (poof_info& pinfo : Poof_info) {
|
|
if (pinfo.bitmap.first_frame >= 0) {
|
|
bm_release(pinfo.bitmap.first_frame);
|
|
pinfo.bitmap.first_frame = -1;
|
|
}
|
|
}
|
|
|
|
// clear da poofs
|
|
Neb2_poofs.clear();
|
|
|
|
// unflag the mission as being fullneb so stuff doesn't fog in the techdata room :D
|
|
The_mission.flags.remove(Mission::Mission_Flags::Fullneb);
|
|
}
|
|
|
|
// call before beginning all rendering
|
|
void neb2_render_setup(camid cid)
|
|
{
|
|
GR_DEBUG_SCOPE("Nebula Setup");
|
|
TRACE_SCOPE(tracing::SetupNebula);
|
|
|
|
// standalone servers can bail here
|
|
if (Game_mode & GM_STANDALONE_SERVER) {
|
|
return;
|
|
}
|
|
|
|
// if the mission is not a fullneb mission, skip
|
|
if ( !(The_mission.flags[Mission::Mission_Flags::Fullneb]) ) {
|
|
return;
|
|
}
|
|
|
|
if (Neb2_render_mode == NEB2_RENDER_HTL) {
|
|
// RT The background needs to be the same colour as the fog and this seems
|
|
// to be the ideal place to do it
|
|
ubyte tr = gr_screen.current_clear_color.red;
|
|
ubyte tg = gr_screen.current_clear_color.green;
|
|
ubyte tb = gr_screen.current_clear_color.blue;
|
|
|
|
neb2_get_fog_color(
|
|
&gr_screen.current_clear_color.red,
|
|
&gr_screen.current_clear_color.green,
|
|
&gr_screen.current_clear_color.blue);
|
|
|
|
gr_clear();
|
|
|
|
gr_screen.current_clear_color.red = tr;
|
|
gr_screen.current_clear_color.green = tg;
|
|
gr_screen.current_clear_color.blue = tb;
|
|
|
|
return;
|
|
}
|
|
|
|
// pre-render the real background nebula
|
|
neb2_pre_render(cid);
|
|
}
|
|
|
|
// level paging code
|
|
void neb2_page_in()
|
|
{
|
|
// load in all nebula bitmaps
|
|
if ( (The_mission.flags[Mission::Mission_Flags::Fullneb]) || Nebula_sexp_used ) {
|
|
for (size_t idx = 0; idx < Poof_info.size(); idx++) {
|
|
if (Poof_info[idx].bitmap.first_frame >= 0 && poof_is_used(idx)) {
|
|
bm_page_in_texture(Poof_info[idx].bitmap.first_frame);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// should we not render this object because its obscured by the nebula?
|
|
int neb_skip_opt = 0;
|
|
DCF(neb_skip, "Toggles culling of objects obscured by nebula")
|
|
{
|
|
neb_skip_opt = !neb_skip_opt;
|
|
if (neb_skip_opt) {
|
|
dc_printf("Using neb object skipping!\n");
|
|
} else {
|
|
dc_printf("Not using neb object skipping!\n");
|
|
}
|
|
}
|
|
int neb2_skip_render(object *objp, float z_depth)
|
|
{
|
|
float fog_near, fog_density;
|
|
|
|
// if we're never skipping
|
|
if (!neb_skip_opt) {
|
|
return 0;
|
|
}
|
|
|
|
// get near and far fog values based upon object type and rendering mode
|
|
neb2_get_adjusted_fog_values(&fog_near, &fog_density);
|
|
float fog = pow(fog_density, z_depth - fog_near + objp->radius);
|
|
|
|
// by object type
|
|
switch( objp->type ) {
|
|
// some objects we always render
|
|
case OBJ_SHOCKWAVE:
|
|
case OBJ_JUMP_NODE:
|
|
case OBJ_NONE:
|
|
case OBJ_GHOST:
|
|
case OBJ_BEAM:
|
|
case OBJ_WAYPOINT:
|
|
return 0;
|
|
|
|
// any weapon over 500 meters away
|
|
// Use the "far" distance multiplier here
|
|
case OBJ_WEAPON:
|
|
if (fog < 0.05f) {
|
|
return 1;
|
|
}
|
|
break;
|
|
|
|
// any ship or raw pof less than 3% visible at their closest point
|
|
case OBJ_RAW_POF:
|
|
case OBJ_PROP:
|
|
case OBJ_SHIP:
|
|
if (fog < 0.03f)
|
|
return 1;
|
|
break;
|
|
|
|
// any fireball over the fog limit for small ships
|
|
case OBJ_FIREBALL:
|
|
return 0;
|
|
break;
|
|
|
|
// any debris over the fog limit for small ships
|
|
case OBJ_DEBRIS:
|
|
return 0;
|
|
break;
|
|
|
|
// any asteroid less than 3% visible at their closest point
|
|
case OBJ_ASTEROID:
|
|
if (fog < 0.03f)
|
|
return 1;
|
|
break;
|
|
|
|
// hmmm. unknown object type - should probably let it through
|
|
default:
|
|
Int3();
|
|
return 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// extend LOD
|
|
float neb2_get_lod_scale(int objnum)
|
|
{
|
|
ship *shipp;
|
|
ship_info *sip;
|
|
|
|
// bogus
|
|
if ( (objnum < 0)
|
|
|| (objnum >= MAX_OBJECTS)
|
|
|| (Objects[objnum].type != OBJ_SHIP)
|
|
|| (Objects[objnum].instance < 0)
|
|
|| (Objects[objnum].instance >= MAX_SHIPS)) {
|
|
return 1.0f;
|
|
}
|
|
shipp = &Ships[Objects[objnum].instance];
|
|
sip = &Ship_info[shipp->ship_info_index];
|
|
|
|
// small ship?
|
|
if (sip->is_small_ship()) {
|
|
return 1.8f;
|
|
} else if (sip->is_big_ship()) {
|
|
return 1.4f;
|
|
}
|
|
|
|
// hmm
|
|
return 1.0f;
|
|
}
|
|
|
|
|
|
// --------------------------------------------------------------------------------------------------------
|
|
// NEBULA FORWARD DEFINITIONS
|
|
//
|
|
|
|
// return the alpha the passed poof should be rendered with, for a 2 shell nebula
|
|
float neb2_get_alpha_2shell(float alpha, float inner_radius, float outer_radius, float magic_num, vec3d *v)
|
|
{
|
|
float dist;
|
|
vec3d eye_pos;
|
|
|
|
// get the eye position
|
|
neb2_get_eye_pos(&eye_pos);
|
|
|
|
// determine what alpha to draw this bitmap with
|
|
// higher alpha the closer the bitmap gets to the eye
|
|
dist = vm_vec_dist_quick(&eye_pos, v);
|
|
|
|
// if the point is inside the inner radius, alpha is based on distance to the player's eye,
|
|
// becoming more transparent as it gets close
|
|
if (dist <= inner_radius) {
|
|
// alpha per meter between the magic # and the inner radius
|
|
alpha = alpha / (inner_radius - magic_num);
|
|
|
|
// above value times the # of meters away we are
|
|
alpha *= (dist - magic_num);
|
|
return alpha < 0.0f ? 0.0f : alpha;
|
|
}
|
|
// if the point is outside the inner radius, it starts out as completely transparent at max
|
|
// outer radius, and becomes more opaque as it moves towards inner radius
|
|
else if (dist <= outer_radius) {
|
|
// alpha per meter between the outer radius and the inner radius
|
|
alpha = alpha / (outer_radius - inner_radius);
|
|
|
|
// above value times the range between the outer radius and the poof
|
|
return alpha < 0.0f ? 0.0f : alpha * (outer_radius - dist);
|
|
}
|
|
|
|
// otherwise transparent
|
|
return 0.0f;
|
|
}
|
|
|
|
// Calculate the alpha multiplier for a poof type. This is used for fading in and out
|
|
void neb2_calc_poof_fades() {
|
|
|
|
for (size_t i = 0; i < Poof_info.size(); i++) {
|
|
|
|
poof_info* pinfo = &Poof_info[i];
|
|
|
|
if (pinfo->fade_duration > -1) {
|
|
|
|
// Initialize variables
|
|
if (pinfo->fade_start == TIMESTAMP::invalid()) {
|
|
pinfo->fade_start = _timestamp();
|
|
}
|
|
|
|
// Calculate the elapsed time in milliseconds
|
|
auto elapsedTime = timestamp_since(pinfo->fade_start);
|
|
|
|
// fade duration of 0 can be reasonably set to 1 to prevent divide by 0
|
|
if (pinfo->fade_duration == 0) {
|
|
pinfo->fade_duration = 1;
|
|
}
|
|
|
|
if (pinfo->fade_in) {
|
|
// Make sure to enable this poof type if we're fading it in
|
|
if (pinfo->fade_multiplier < 0) {
|
|
set_bit(Neb2_poof_flags.get(), i);
|
|
neb2_poof_setup();
|
|
}
|
|
pinfo->fade_multiplier = 0.0f + ((float)elapsedTime / pinfo->fade_duration);
|
|
} else {
|
|
pinfo->fade_multiplier = 1.0f - ((float)elapsedTime / pinfo->fade_duration);
|
|
}
|
|
|
|
// if we're finished then reset pf_info
|
|
if ((pinfo->fade_multiplier >= 1.0f && pinfo->fade_in) || (pinfo->fade_multiplier <= 0.0f && !pinfo->fade_in)) {
|
|
|
|
// turn off any faded out poof types
|
|
if (!pinfo->fade_in) {
|
|
clear_bit(Neb2_poof_flags.get(), i);
|
|
neb2_poof_setup();
|
|
}
|
|
|
|
// reset the values to default
|
|
pinfo->fade_duration = -1;
|
|
pinfo->fade_start = TIMESTAMP::invalid();
|
|
pinfo->fade_in = true;
|
|
pinfo->fade_multiplier = -1.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
// WACKY LOCAL PLAYER NEBULA STUFF
|
|
//
|
|
|
|
void neb2_toggle_poof(int poof_idx, bool enabling)
|
|
{
|
|
if (enabling)
|
|
set_bit(Neb2_poof_flags.get(), poof_idx);
|
|
else
|
|
clear_bit(Neb2_poof_flags.get(), poof_idx);
|
|
}
|
|
|
|
void neb2_toggle_poof_finalize()
|
|
{
|
|
Neb2_poofs.clear();
|
|
|
|
// a bit awkward but this will force a full sphere gen
|
|
vm_vec_make(&Poof_last_gen_pos, 999999.0f, 999999.0f, 999999.0f);
|
|
|
|
neb2_poof_setup();
|
|
}
|
|
|
|
void neb2_fade_poof(int poof_idx, int time, bool type)
|
|
{
|
|
poof_info* pinfo = &Poof_info[poof_idx];
|
|
|
|
pinfo->fade_duration = time;
|
|
pinfo->fade_in = type;
|
|
}
|
|
|
|
void new_poof(size_t poof_info_idx, vec3d* pos) {
|
|
poof new_poof;
|
|
poof_info* pinfo = &Poof_info[poof_info_idx];
|
|
|
|
new_poof.poof_info_index = poof_info_idx;
|
|
new_poof.flash = 0;
|
|
new_poof.radius = pinfo->scale.next();
|
|
new_poof.pt = *pos;
|
|
new_poof.rot_speed = fl_radians(pinfo->rotation.next());
|
|
new_poof.alpha = pinfo->alpha.next();
|
|
new_poof.anim_time = frand_range(0.0f, pinfo->bitmap.total_time);
|
|
if (pinfo->alignment == vmd_zero_vector)
|
|
vm_vec_rand_vec(&new_poof.up_vec);
|
|
else
|
|
new_poof.up_vec = pinfo->alignment;
|
|
|
|
Neb2_poofs.push_back(new_poof);
|
|
}
|
|
|
|
static uint neb_rand_seed = 0;
|
|
|
|
void upkeep_poofs()
|
|
{
|
|
vec3d eye_pos;
|
|
neb2_get_eye_pos(&eye_pos);
|
|
|
|
// cull distant poofs
|
|
if (!Neb2_poofs.empty()) {
|
|
for (size_t i = 0; i < Neb2_poofs.size();) {
|
|
|
|
// If the poof type is not used or if the poof is too far then cull it
|
|
if (!poof_is_used(Neb2_poofs[i].poof_info_index) || (
|
|
vm_vec_dist(&Neb2_poofs[i].pt, &eye_pos) >
|
|
Poof_info[Neb2_poofs[i].poof_info_index].view_dist * UPKEEP_DIST_MULT)) {
|
|
Neb2_poofs[i] = Neb2_poofs.back();
|
|
Neb2_poofs.pop_back();
|
|
}
|
|
else // if we needed to cull we should not advance because we just moved a new poof into this spot
|
|
i++;
|
|
}
|
|
}
|
|
|
|
neb_rand_seed = 0;
|
|
|
|
// make new poofs
|
|
for (size_t i = 0; i < Poof_info.size(); i++) {
|
|
if (!poof_is_used(i))
|
|
continue;
|
|
poof_info* pinfo = &Poof_info[i];
|
|
|
|
float gen_side_length = (pinfo->view_dist * UPKEEP_DIST_MULT) * 2;
|
|
float gen_density = pinfo->density * Poof_density_multiplier;
|
|
|
|
float poofs_to_gen = gen_side_length * gen_side_length * gen_side_length * gen_density;
|
|
|
|
// store the fractional part, take the integer part
|
|
Poof_accum[i] = modff(Poof_accum[i] + (poofs_to_gen), &poofs_to_gen);
|
|
for (int j = 0; j < poofs_to_gen; j++) {
|
|
vec3d pos = eye_pos;
|
|
vec3d offset = eye_pos / gen_side_length;
|
|
vec3d rand_pos = vm_well_distributed_rand_vec(neb_rand_seed, &offset);
|
|
neb_rand_seed++;
|
|
|
|
rand_pos.xyz.x *= gen_side_length / 2;
|
|
rand_pos.xyz.y *= gen_side_length / 2;
|
|
rand_pos.xyz.z *= gen_side_length / 2;
|
|
|
|
pos += rand_pos;
|
|
|
|
// we generated poofs in a cube, now keep only those that are within the view sphere, and weren't within the last view sphere
|
|
// not terribly efficient but very simple
|
|
if (vm_vec_dist(&eye_pos, &pos) <= (gen_side_length / 2) &&
|
|
vm_vec_dist(&Poof_last_gen_pos, &pos) > (gen_side_length / 2))
|
|
new_poof(i, &pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
void neb2_render_poofs()
|
|
{
|
|
GR_DEBUG_SCOPE("Nebula render player");
|
|
TRACE_SCOPE(tracing::DrawPoofs);
|
|
|
|
vertex p, ptemp;
|
|
float alpha;
|
|
vec3d eye_pos;
|
|
matrix eye_orient;
|
|
|
|
// standalone servers can bail here
|
|
if (Game_mode & GM_STANDALONE_SERVER) {
|
|
return;
|
|
}
|
|
|
|
// if the mission is not a fullneb mission, skip
|
|
if (!(The_mission.flags[Mission::Mission_Flags::Fullneb])) {
|
|
return;
|
|
}
|
|
|
|
memset(&p, 0, sizeof(p));
|
|
memset(&ptemp, 0, sizeof(ptemp));
|
|
|
|
// upkeep poof fade stuff
|
|
neb2_calc_poof_fades();
|
|
|
|
// get eye position and orientation
|
|
neb2_get_eye_pos(&eye_pos);
|
|
neb2_get_eye_orient(&eye_orient);
|
|
|
|
// maybe swap stuff around if the player crossed the dist threshold
|
|
if (vm_vec_dist(&eye_pos, &Poof_last_gen_pos) > Poof_dist_threshold) {
|
|
upkeep_poofs();
|
|
Poof_last_gen_pos = eye_pos;
|
|
}
|
|
|
|
// if we've switched nebula rendering off
|
|
if (Neb2_render_mode == NEB2_RENDER_NONE) {
|
|
return;
|
|
}
|
|
|
|
// render the nebula
|
|
for (poof &pf : Neb2_poofs) {
|
|
poof_info* pinfo = &Poof_info[pf.poof_info_index];
|
|
|
|
// Miss this one out if the id is -1
|
|
if (pinfo->bitmap.first_frame < 0)
|
|
continue;
|
|
|
|
// It's possible for some faded-out poofs to get rendered after a poof type has finished a fade out
|
|
// and reset it's multiplier but before the poof is culled from the poof vector. So this small safety
|
|
// prevents them from flickering.
|
|
if (!poof_is_used(pf.poof_info_index)) {
|
|
continue;
|
|
}
|
|
|
|
// do animation upkeep
|
|
int framenum = 0;
|
|
if (pinfo->bitmap.num_frames > 1) {
|
|
pf.anim_time += flFrametime;
|
|
|
|
framenum = bm_get_anim_frame(pinfo->bitmap.first_frame, pf.anim_time, pinfo->bitmap.total_time, true);
|
|
}
|
|
|
|
// generate the bitmap orient
|
|
// If the bitmap is large and distant and points in the view fvec direction (like retail poofs do) this looks good when moving,
|
|
// but bad when you rotate (since the bitmaps rotate in place with you, which looks weird).
|
|
// Conversely, if the bitmap is close and small (like retail size-ish) and points at the view position, it looks good
|
|
// when rotating (since the bitmaps remain static) but bad when moving (as they rotate in place to continue pointing at you)
|
|
//
|
|
// So blend between the two styles based on promixity and size, since distant (relative to their size) bitmaps
|
|
// are more affected by rotating than moving and close bitmaps are vice versa
|
|
// We will scale the bitmap direction from the view position to "off to infinity" in the negative view fvec direction - Asteroth
|
|
matrix orient;
|
|
vec3d view_pos;
|
|
{
|
|
float scalar = -1 / powf((vm_vec_dist(&eye_pos, &pf.pt) / (10 * pf.radius)), 3.f);
|
|
if (pinfo->alignment != vmd_zero_vector)
|
|
scalar = 0.0f;
|
|
|
|
vm_vec_scale_add(&view_pos, &eye_pos, &eye_orient.vec.fvec, scalar);
|
|
|
|
view_pos -= pf.pt;
|
|
|
|
if (pinfo->alignment != vmd_zero_vector)
|
|
vm_project_point_onto_plane(&view_pos, &view_pos, &pinfo->alignment, &vmd_zero_vector);
|
|
|
|
vm_vec_normalize(&view_pos);
|
|
|
|
vm_vector_2_matrix_norm(&orient, &view_pos, &pf.up_vec, nullptr);
|
|
}
|
|
|
|
// update the poof's up vector to be perpindicular to the camera and also rotated by however much its rotating
|
|
vec3d poof_direction;
|
|
vm_vec_normalized_dir(&poof_direction, &pf.pt, &eye_pos);
|
|
vm_project_point_onto_plane(&pf.up_vec, &pf.up_vec, &view_pos, &vmd_zero_vector);
|
|
vm_vec_normalize(&pf.up_vec);
|
|
vm_rot_point_around_line(&pf.up_vec, &pf.up_vec, pf.rot_speed * flFrametime, &vmd_zero_vector, &view_pos);
|
|
|
|
// optimization 1 - don't draw backfacing poly's
|
|
if (vm_vec_dot_to_point(&eye_orient.vec.fvec, &eye_pos, &pf.pt) <= 0.0f)
|
|
continue;
|
|
|
|
// get the proper alpha value
|
|
alpha = neb2_get_alpha_2shell(pf.alpha, pf.radius, pinfo->view_dist, pf.radius/4, &pf.pt);
|
|
|
|
if (pinfo->fade_duration > -1) {
|
|
alpha = alpha * pinfo->fade_multiplier;
|
|
}
|
|
|
|
// optimization 2 - don't draw 0.0f or less poly's
|
|
// this amounts to big savings
|
|
if (alpha <= 0.0f)
|
|
continue;
|
|
|
|
// render!
|
|
batching_add_volume_polygon(pinfo->bitmap.first_frame + framenum, &pf.pt, &orient, pf.radius, pf.radius, alpha);
|
|
}
|
|
|
|
// gr_set_color_fast(&Color_bright_red);
|
|
// gr_printf(30, 100, "Area %.3f", total_area);
|
|
#ifdef NEB2_THUMBNAIL
|
|
extern int tbmap;
|
|
if (tbmap != -1) {
|
|
gr_set_bitmap(tbmap);
|
|
gr_bitmap(0, 0);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// This version of the function allows for global adjustment to fog values
|
|
void neb2_get_adjusted_fog_values(float *fnear, float *fdensity)
|
|
{
|
|
// There was code here that adjusted distanced based on object type, but at the time of reworking this, the code was effectively dead and never used.
|
|
|
|
*fnear = Neb2_fog_near_distance;
|
|
|
|
if (fdensity != nullptr)
|
|
*fdensity = powf(Neb2_fog_1000m_visibility, 1.f / 1000.f);
|
|
}
|
|
|
|
// given a position, returns 0 - 1 the fog visibility of that position, 0 = completely obscured
|
|
// distance_mult will multiply the result, use for things that can be obscured but can 'shine through' the nebula more than normal
|
|
float neb2_get_fog_visibility(const vec3d *pos, float distance_mult)
|
|
{
|
|
float pct;
|
|
|
|
// get the fog pct
|
|
pct = powf(nNf_density, (vm_vec_dist(&Eye_position, pos) - nNf_near) / distance_mult);
|
|
|
|
CLAMP(pct, 0.0f, 1.0f);
|
|
|
|
return pct;
|
|
}
|
|
|
|
bool nebula_handle_alpha(float& alpha, const vec3d* pos, float distance_mult) {
|
|
|
|
bool bHasNebula = false;
|
|
float fAlphaMult = 1.0f;
|
|
|
|
if (The_mission.flags[Mission::Mission_Flags::Fullneb])
|
|
{
|
|
fAlphaMult *= neb2_get_fog_visibility(pos, distance_mult);
|
|
bHasNebula = true;
|
|
}
|
|
|
|
if (The_mission.volumetrics)
|
|
{
|
|
fAlphaMult *= The_mission.volumetrics->getAlphaToPos(*pos, distance_mult);
|
|
bHasNebula = true;
|
|
}
|
|
|
|
alpha *= fAlphaMult;
|
|
return bHasNebula;
|
|
}
|
|
|
|
// fogging stuff --------------------------------------------------------------------
|
|
|
|
// do a pre-render of the background nebula
|
|
#define ESIZE 32
|
|
ubyte tpixels[ESIZE * ESIZE * 4]; // for 32 bits
|
|
int last_esize = -1;
|
|
int this_esize = ESIZE;
|
|
float ex_scale, ey_scale;
|
|
int tbmap = -1;
|
|
// UnknownPlayer : Contained herein, the origins of the nebula rendering bug!
|
|
// I am really not entirely sure what this code achieves, but the old
|
|
// D3D calls were the cause of the nebula bug - they have been commented out.
|
|
// If you want to save some rendering time, I would suggest maybe kill this off.
|
|
// It doesn't use much, but it APPEARS to be fairly useless unless someone wants
|
|
// to enlighten me.
|
|
//
|
|
void neb2_pre_render(camid cid)
|
|
{
|
|
// if the mission is not a fullneb mission, skip
|
|
if (!(The_mission.flags[Mission::Mission_Flags::Fullneb])) {
|
|
return;
|
|
}
|
|
|
|
// bail early in lame and poly modes
|
|
if (Neb2_render_mode != NEB2_RENDER_POF) {
|
|
return;
|
|
}
|
|
|
|
// set the view clip
|
|
gr_screen.clip_width = this_esize;
|
|
gr_screen.clip_height = this_esize;
|
|
g3_start_frame(1); // Turn on zbuffering
|
|
g3_set_view(cid.getCamera());
|
|
gr_set_clip(0, 0, this_esize, this_esize);
|
|
|
|
// render the background properly
|
|
// hack - turn off nebula stuff
|
|
int neb_save = Neb2_render_mode;
|
|
Neb2_render_mode = NEB2_RENDER_NONE;
|
|
|
|
// draw background stuff nebula
|
|
stars_draw_background();
|
|
|
|
Neb2_render_mode = neb_save;
|
|
|
|
// grab the region
|
|
gr_get_region(0, this_esize, this_esize, (ubyte*)tpixels);
|
|
|
|
#ifdef NEB2_THUMBNAIL
|
|
if (tbmap == -1) {
|
|
tbmap = bm_create(16, this_esize, this_esize, tpixels, 0);
|
|
bm_lock(tbmap, 16, 0);
|
|
bm_unlock(tbmap);
|
|
}
|
|
#endif
|
|
|
|
// maybe do some swizzling
|
|
|
|
// end the frame
|
|
g3_end_frame();
|
|
|
|
gr_clear();
|
|
|
|
// if the size has changed between frames, make a new bitmap
|
|
if (this_esize != last_esize) {
|
|
last_esize = this_esize;
|
|
|
|
// recalculate ex_scale and ey_scale values for looking up color values
|
|
ex_scale = (float)this_esize / (float)gr_screen.max_w;
|
|
ey_scale = (float)this_esize / (float)gr_screen.max_h;
|
|
}
|
|
}
|
|
|
|
// fill in the position of the eye for this frame
|
|
void neb2_get_eye_pos(vec3d *eye_vector)
|
|
{
|
|
*eye_vector = Eye_position;
|
|
}
|
|
|
|
// fill in the eye orient for this frame
|
|
void neb2_get_eye_orient(matrix *eye_matrix)
|
|
{
|
|
*eye_matrix = Eye_matrix;
|
|
}
|
|
|
|
// nebula DCF functions ------------------------------------------------------
|
|
// TODO: With the new debug parser in place, most of these sub-commands can now be handled by neb2. This should clear up the DCF list a bit
|
|
DCF(neb2, "list nebula console commands")
|
|
{
|
|
// dc_printf("neb2_fog <X> <float> <float> : set near and far fog planes for ship type X\n");
|
|
// dc_printf("where X is an integer from 1 - 11\n");
|
|
// dc_printf("1 = cargo containers, 2 = fighters/bombers, 3 = cruisers\n");
|
|
// dc_printf("4 = freighters, 5 = capital ships, 6 = transports, 7 = support ships\n");
|
|
// dc_printf("8 = navbuoys, 9 = sentryguns, 10 = escape pods, 11 = background nebula polygons\n\n");
|
|
|
|
dc_printf("neb2_select : <int> <int> where the first # is the bitmap to be adjusting (0 through 5), and the second int is a 0 or 1, to turn off and on\n");
|
|
dc_printf("neb2_mode : switch between no nebula, polygon background, pof background, lame, or HTL rendering (0, 1, 2, 3 and 4 respectively)\n\n");
|
|
dc_printf("neb2_ff : flash fade/sec\n");
|
|
dc_printf("neb2_background : rgb background color\n");
|
|
dc_printf("neb2_fog_color : rgb fog color\n");
|
|
|
|
// dc_printf("neb2_fog_vals : display all the current settings for all above values\n");
|
|
}
|
|
|
|
DCF(neb2_select, "Enables/disables a poof bitmap")
|
|
{
|
|
int bmap;
|
|
bool val_b;
|
|
|
|
dc_stuff_int(&bmap);
|
|
|
|
if ( (bmap >= 0) && (bmap < static_cast<int>(Poof_info.size())) ) {
|
|
dc_stuff_boolean(&val_b);
|
|
|
|
val_b ? (set_bit(Neb2_poof_flags.get(), bmap)) : (clear_bit(Neb2_poof_flags.get(), bmap));
|
|
}
|
|
}
|
|
|
|
DCF(neb2_ff, "flash fade/sec")
|
|
{
|
|
dc_stuff_float(&neb2_flash_fade);
|
|
}
|
|
|
|
DCF(neb2_mode, "Switches nebula render modes")
|
|
{
|
|
int mode;
|
|
dc_stuff_int(&mode);
|
|
|
|
switch (mode) {
|
|
case NEB2_RENDER_NONE:
|
|
Neb2_render_mode = NEB2_RENDER_NONE;
|
|
break;
|
|
|
|
case NEB2_RENDER_POF:
|
|
Neb2_render_mode = NEB2_RENDER_POF;
|
|
stars_set_background_model(BACKGROUND_MODEL_FILENAME, "Eraseme3");
|
|
stars_set_background_orientation();
|
|
break;
|
|
|
|
case NEB2_RENDER_HTL:
|
|
Neb2_render_mode = NEB2_RENDER_HTL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
DCF(neb2_background, "Sets the RGB background color (lame rendering)")
|
|
{
|
|
int r, g, b;
|
|
|
|
dc_stuff_int(&r);
|
|
dc_stuff_int(&g);
|
|
dc_stuff_int(&b);
|
|
|
|
Neb2_background_color[0] = r;
|
|
Neb2_background_color[1] = g;
|
|
Neb2_background_color[2] = b;
|
|
}
|
|
|
|
DCF(neb2_fog_color, "Sets the RGB fog color (HTL)")
|
|
{
|
|
ubyte r, g, b;
|
|
|
|
dc_stuff_ubyte(&r);
|
|
dc_stuff_ubyte(&g);
|
|
dc_stuff_ubyte(&b);
|
|
|
|
Neb2_fog_color[0] = r;
|
|
Neb2_fog_color[1] = g;
|
|
Neb2_fog_color[2] = b;
|
|
}
|