/* * 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/pstypes.h" #include #include #include "freespace.h" #include "cmdline/cmdline.h" #include "ddsutils/ddsutils.h" #include "debugconsole/console.h" #include "graphics/matrix.h" #include "graphics/paths/PathRenderer.h" #include "hud/hud.h" #include "hud/hudtarget.h" #include "io/timer.h" #include "lighting/lighting.h" #include "math/vecmat.h" #include "mission/missionparse.h" #include "model/modelrender.h" #include "nebula/neb.h" #include "options/Option.h" #include "osapi/dialogs.h" #include "parse/parselo.h" #include "render/3d.h" #include "render/batching.h" #include "starfield/nebula.h" #include "starfield/starfield.h" #include "graphics/shadows.h" #include "starfield/sun_disc.h" #include "starfield/supernova.h" #include "tracing/tracing.h" #include "utils/Random.h" #define DEBRIS_ROT_MIN 10000 #define DEBRIS_ROT_RANGE 8 #define DEBRIS_ROT_RANGE_SCALER 10000 #define RND_MAX_MASK 0x3fff #define HALF_RND_MAX 0x2000 #define MAX_MOTION_DEBRIS 300 typedef struct { vec3d pos; int vclip; float size; } motion_debris_instance; const float MAX_DIST_RANGE = 80.0f; const float MIN_DIST_RANGE = 14.0f; const float BASE_SIZE = 0.04f; const float BASE_SIZE_NEB = 0.15f; static int Subspace_model_inner = -1; static int Subspace_model_outer = -1; static int Rendering_to_env = 0; int Num_stars = 500; // A timestamp for animated skyboxes -MageKing17 TIMESTAMP Skybox_timestamp; #define MAX_FLARE_COUNT 10 #define MAX_FLARE_BMP 6 typedef struct flare_info { float pos; float scale; int tex_num; } flare_info; typedef struct flare_bitmap { char filename[MAX_FILENAME_LEN]; int bitmap_id; } flare_bitmap; // global info (not individual instances) typedef struct starfield_bitmap { char filename[MAX_FILENAME_LEN]; // bitmap filename char glow_filename[MAX_FILENAME_LEN]; // only for suns int bitmap_id; // bitmap handle int n_frames; int fps; int glow_bitmap; // only for suns int glow_n_frames; int glow_fps; int xparent; float r, g, b, i; // only for suns float angular_size; // only for suns -- this sun's apparent diameter from stars.tbl; // see SUN_ANGULAR_SIZE_UNSPECIFIED float disc_fraction; // only for suns -- cached sun_disc_measure_bitmap() result for // disc_measured_for; negative means "not measured yet" int disc_measured_for; // only for suns -- bitmap_id disc_fraction was measured from int glare; // only for suns int flare; // Is there a lens-flare for this sun? flare_info flare_infos[MAX_FLARE_COUNT]; // each flare can use a texture in flare_bmp, with different scale flare_bitmap flare_bitmaps[MAX_FLARE_BMP]; // bitmaps for different lens flares (can be re-used) int n_flares; // number of flares actually used int n_flare_bitmaps; // number of flare bitmaps available int used_this_level; int preload; } starfield_bitmap; // starfield bitmap instance typedef struct starfield_bitmap_instance { float scale_x, scale_y; // x and y scale int div_x, div_y; // # of x and y divisions angles ang; // angles from FRED float angular_size; // only for suns -- this mission's apparent diameter for the sun, // in degrees; see SUN_ANGULAR_SIZE_UNSPECIFIED int star_bitmap_index; // index into starfield_bitmap array int n_verts; vertex *verts; starfield_bitmap_instance() : scale_x(1.0f), scale_y(1.0f), div_x(1), div_y(1), angular_size(SUN_ANGULAR_SIZE_UNSPECIFIED), star_bitmap_index(0), n_verts(0), verts(nullptr) { ang.p = 0.0f; ang.b = 0.0f; ang.h = 0.0f; } } starfield_bitmap_instance; // for drawing cool stuff on the background - comes from a table static SCP_vector Starfield_bitmaps; static SCP_vector Starfield_bitmap_instances; // sun bitmaps and sun glow bitmaps static SCP_vector Sun_bitmaps; float Sun_angular_size_override = -1.0f; static SCP_vector Suns; // Goober5000 int Cur_background = -1; SCP_vector Backgrounds; SCP_vector Preload_background_indexes; void stars_preload_background(int background_idx); int last_stars_filled = 0; color star_colors[8]; color star_aacolors[8]; typedef struct vDist { int x; int y; } vDist; std::unique_ptr Stars = make_unique(MAX_STARS); motion_debris_instance Motion_debris[MAX_MOTION_DEBRIS]; SCP_vector Motion_debris_info; motion_debris_bitmaps* Motion_debris_ptr = nullptr; // background data int Stars_background_inited = 0; // if we're inited int Nmodel_num = -1; // model num int Nmodel_instance_num = -1; // model instance num matrix Nmodel_orient = IDENTITY_MATRIX; // model orientation uint64_t Nmodel_flags = DEFAULT_NMODEL_FLAGS; // model flags int Nmodel_bitmap = -1; // model texture float Nmodel_alpha = 1.0f; // model transparency bool Dynamic_environment = false; bool Motion_debris_override = false; bool Motion_debris_enabled = true; static void parse_motion_debris_func() { bool enabled; stuff_boolean(&enabled); Motion_debris_enabled = enabled; } // coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton auto MotionDebrisOption = options::OptionBuilder("Graphics.MotionDebris", std::pair{"Motion Debris", 1713}, std::pair{"Enable or disable visible motion debris", 1714}) .category(std::make_pair("Graphics", 1825)) .bind_to(&Motion_debris_enabled) .default_func([]() { return Motion_debris_enabled;}) .level(options::ExpertLevel::Advanced) .importance(67) .parser(parse_motion_debris_func) .finish(); static int Default_env_map = -1; static int Mission_env_map = -1; static bool Env_cubemap_drawn = false; static bool Irr_cubemap_drawn = false; int get_motion_debris_by_name(const SCP_string &name) { int count = static_cast(Motion_debris_info.size()); for (int i = 0; i < count; i++) { if (lcase_equal(Motion_debris_info[i].name, name)) { return i; } } return -1; } void stars_release_motion_debris(motion_debris_bitmaps* vclips) { if (vclips == nullptr) return; for (int i = 0; i < MAX_MOTION_DEBRIS_BITMAPS; i++) { if ( (vclips[i].bm >= 0) && bm_release(vclips[i].bm) ) { vclips[i].bm = -1; vclips[i].nframes = -1; } } } void stars_load_motion_debris(motion_debris_bitmaps* vclips) { Assertion(vclips != nullptr, "Motion debris not loaded!"); for (int i = 0; i < MAX_MOTION_DEBRIS_BITMAPS; i++) { vclips[i].bm = bm_load_animation( vclips[i].name, &vclips[i].nframes, nullptr, nullptr, nullptr, true ); if ( vclips[i].bm < 0 ) { // try loading it as a single bitmap vclips[i].bm = bm_load(vclips[i].name); vclips[i].nframes = 1; if (vclips[i].bm <= 0) { Error( LOCATION, "Couldn't load animation/bitmap '%s'\n", vclips[i].name ); } } } } void stars_load_debris(int fullneb, const SCP_string &custom_name) { if (!Motion_debris_enabled || Motion_debris_info.empty()) { return; } SCP_string debris_name = ""; if (!custom_name.empty()) { debris_name = custom_name; } // if not using custom the load the default type for the mission if (fullneb && debris_name.empty()) { // if we're in nebula mode debris_name = "nebula"; } else if (debris_name.empty()) { debris_name = "default"; } int debris_index = get_motion_debris_by_name(debris_name); //If we can't find the motion debris that's been called for then warn and //set debris override to prevent downstream errors. Then abort. if (debris_index < 0) { Warning(LOCATION, "Motion debris '%s' not found in stars.tbl!\n", debris_name.c_str()); Motion_debris_override = true; Motion_debris_ptr = nullptr; return; } stars_release_motion_debris(Motion_debris_ptr); stars_load_motion_debris(Motion_debris_info[debris_index].bitmaps); Motion_debris_ptr = Motion_debris_info[debris_index].bitmaps; } const int MAX_PERSPECTIVE_DIVISIONS = 5; const float p_phi = 10.0f, p_theta = 10.0f; extern void stars_project_2d_onto_sphere( vec3d *pnt, float rho, float phi, float theta ); static void starfield_create_bitmap_buffer(const size_t si_idx) { vec3d s_points[MAX_PERSPECTIVE_DIVISIONS+1][MAX_PERSPECTIVE_DIVISIONS+1]; vertex v[4]; matrix m; int idx, s_idx; float ui, vi; starfield_bitmap_instance *sbi = &Starfield_bitmap_instances[si_idx]; angles *a = &sbi->ang; float scale_x = sbi->scale_x; float scale_y = sbi->scale_y; int div_x = sbi->div_x; int div_y = sbi->div_y; // cap division values // div_x = div_x > MAX_PERSPECTIVE_DIVISIONS ? MAX_PERSPECTIVE_DIVISIONS : div_x; div_x = 1; div_y = div_y > MAX_PERSPECTIVE_DIVISIONS ? MAX_PERSPECTIVE_DIVISIONS : div_y; if (sbi->verts != NULL) { delete [] sbi->verts; } sbi->verts = new(std::nothrow) vertex[div_x * div_y * 6]; if (sbi->verts == NULL) { sbi->star_bitmap_index = -1; return; } sbi->n_verts = div_x * div_y * 6; // texture increment values ui = 1.0f / (float)div_x; vi = 1.0f / (float)div_y; // adjust for aspect ratio //scale_x *= (gr_screen.clip_aspect + 0.55f); // fudge factor //scale_x *= (gr_screen.clip_aspect + (0.7333333f/gr_screen.clip_aspect)); // fudge factor scale_x *= 1.883333f; //fudge factor float s_phi = 0.5f + (((p_phi * scale_x) / 360.0f) / 2.0f); float s_theta = (((p_theta * scale_y) / 360.0f) / 2.0f); float d_phi = -(((p_phi * scale_x) / 360.0f) / (float)(div_x)); float d_theta = -(((p_theta * scale_y) / 360.0f) / (float)(div_y)); // bank matrix vm_angles_2_matrix(&m, a); // generate the bitmap points for(idx=0; idx<=div_x; idx++) { for(s_idx=0; s_idx<=div_y; s_idx++) { // get world spherical coords stars_project_2d_onto_sphere(&s_points[idx][s_idx], 1000.0f, s_phi + ((float)idx*d_phi), s_theta + ((float)s_idx*d_theta)); // (un)rotate on the sphere vm_vec_unrotate(&s_points[idx][s_idx], &s_points[idx][s_idx], &m); } } memset(v, 0, sizeof(vertex) * 4); int j = 0; vertex *verts = sbi->verts; for (idx = 0; idx < div_x; idx++) { for (s_idx = 0; s_idx < div_y; s_idx++) { // stuff texture coords v[0].texture_position.u = ui * float(idx); v[0].texture_position.v = vi * float(s_idx); v[1].texture_position.u = ui * float(idx+1); v[1].texture_position.v = vi * float(s_idx); v[2].texture_position.u = ui * float(idx+1); v[2].texture_position.v = vi * float(s_idx+1); v[3].texture_position.u = ui * float(idx); v[3].texture_position.v = vi * float(s_idx+1); g3_transfer_vertex(&v[0], &s_points[idx][s_idx]); g3_transfer_vertex(&v[1], &s_points[idx+1][s_idx]); g3_transfer_vertex(&v[2], &s_points[idx+1][s_idx+1]); g3_transfer_vertex(&v[3], &s_points[idx][s_idx+1]); // poly 1 verts[j++] = v[0]; verts[j++] = v[1]; verts[j++] = v[2]; // poly 2 verts[j++] = v[0]; verts[j++] = v[2]; verts[j++] = v[3]; } } Assert( j == sbi->n_verts ); } // take the Starfield_bitmap_instances[] and make all the vertex buffers that you'll need to draw it static void starfield_generate_bitmap_buffers() { auto sb_instances = Starfield_bitmap_instances.size(); for (size_t idx = 0; idx < sb_instances; idx++) { if (Starfield_bitmap_instances[idx].star_bitmap_index < 0) continue; starfield_create_bitmap_buffer(idx); } } static void starfield_bitmap_entry_init(starfield_bitmap *sbm) { int i; Assert( sbm != NULL ); memset( sbm, 0, sizeof(starfield_bitmap) ); sbm->bitmap_id = -1; sbm->glow_bitmap = -1; sbm->glow_n_frames = 1; sbm->angular_size = SUN_ANGULAR_SIZE_UNSPECIFIED; sbm->disc_fraction = -1.0f; sbm->disc_measured_for = -1; for (i = 0; i < MAX_FLARE_BMP; i++) { sbm->flare_bitmaps[i].bitmap_id = -1; } for (i = 0; i < MAX_FLARE_COUNT; i++) { sbm->flare_infos[i].tex_num = -1; } } #define CHECK_END() { \ if (in_check) { \ required_string("#end"); \ in_check = false; \ run_count = 0; \ } \ } void parse_startbl(const char *filename) { char name[MAX_FILENAME_LEN], tempf[16]; starfield_bitmap sbm; int idx; bool in_check = false; int rc = -1; int run_count = 0; try { read_file_text(filename, CF_TYPE_TABLES); reset_parse(); // freaky! ;) while (!check_for_eof()) { optional_string("#Background Bitmaps"); while ((rc = optional_string_either("$Bitmap:", "$BitmapX:")) != -1) { in_check = true; starfield_bitmap_entry_init(&sbm); stuff_string(sbm.filename, F_NAME, MAX_FILENAME_LEN); sbm.xparent = rc; // 0 == intensity alpha bitmap, 1 == green xparency bitmap if ((idx = stars_find_bitmap(sbm.filename)) >= 0) { if (sbm.xparent == Starfield_bitmaps[idx].xparent) { if (!Parsing_modular_table) Warning(LOCATION, "Starfield bitmap '%s' listed more than once!! Only using the first entry!", sbm.filename); } else { Warning(LOCATION, "Starfield bitmap '%s' already listed as a %s bitmap!! Only using the xparent version!", sbm.filename, (rc) ? "xparent" : "non-xparent"); } } else { Starfield_bitmaps.push_back(std::move(sbm)); } } CHECK_END(); optional_string("#Stars"); while (optional_string("$Sun:")) { in_check = true; starfield_bitmap_entry_init(&sbm); stuff_string(sbm.filename, F_NAME, MAX_FILENAME_LEN); // associated glow required_string("$Sunglow:"); stuff_string(sbm.glow_filename, F_NAME, MAX_FILENAME_LEN); // associated lighting values required_string("$SunRGBI:"); stuff_float(&sbm.r); stuff_float(&sbm.g); stuff_float(&sbm.b); stuff_float(&sbm.i); if (optional_string("$SunSpecularRGB:")) { SCP_string warning; sprintf(warning, "Sun %s tried to set SunSpecularRGB. This feature has been deprecated and will be ignored.", sbm.filename); float spec_r, spec_g, spec_b; stuff_float(&spec_r); stuff_float(&spec_g); stuff_float(&spec_b); if (fl_equal(sbm.r, spec_r) && fl_equal(sbm.g, spec_g) && fl_equal(sbm.b, spec_b)) mprintf(("%s\n", warning.c_str())); // default case is not significant else Warning(LOCATION, "%s", warning.c_str()); // customized case is significant } // apparent diameter of the sun's disc in degrees (Sol is ~0.53). Only affects // raytraced shadows, which get a penumbra sized to match. Left out, the size is // measured from the sun bitmap instead -- see sun_angular_radius_tangent() -- so // use 0 to ask for hard shadows and "derived" to say "measure it" outright. if (optional_string("$SunAngularSize:")) { if ( !optional_string("derived") ) { stuff_float(&sbm.angular_size); if (sbm.angular_size < 0.0f) { error_display(0, "$SunAngularSize: for sun '%s' must be >= 0 (got %f); measuring the bitmap instead.", sbm.filename, sbm.angular_size); sbm.angular_size = SUN_ANGULAR_SIZE_UNSPECIFIED; } } } // lens flare stuff if (optional_string("$Flare:")) { sbm.flare = 1; required_string("+FlareCount:"); stuff_int(&sbm.n_flares); // if there's a flare, it has to have at least one texture required_string("$FlareTexture1:"); stuff_string(sbm.flare_bitmaps[0].filename, F_NAME, MAX_FILENAME_LEN); sbm.n_flare_bitmaps = 1; for (idx = 1; idx < MAX_FLARE_BMP; idx++) { // allow 9999 textures (theoretically speaking, that is) sprintf(tempf, "$FlareTexture%d:", idx + 1); if (optional_string(tempf)) { sbm.n_flare_bitmaps++; stuff_string(sbm.flare_bitmaps[idx].filename, F_NAME, MAX_FILENAME_LEN); } // else break; //don't allow flaretexture1 and then 3, etc. } required_string("$FlareGlow1:"); required_string("+FlareTexture:"); stuff_int(&sbm.flare_infos[0].tex_num); required_string("+FlarePos:"); stuff_float(&sbm.flare_infos[0].pos); required_string("+FlareScale:"); stuff_float(&sbm.flare_infos[0].scale); sbm.n_flares = 1; for (idx = 1; idx < MAX_FLARE_COUNT; idx++) { // allow a lot of glows sprintf(tempf, "$FlareGlow%d:", idx + 1); if (optional_string(tempf)) { sbm.n_flares++; required_string("+FlareTexture:"); stuff_int(&sbm.flare_infos[idx].tex_num); required_string("+FlarePos:"); stuff_float(&sbm.flare_infos[idx].pos); required_string("+FlareScale:"); stuff_float(&sbm.flare_infos[idx].scale); } // else break; //don't allow "flare 1" and then "flare 3" } } sbm.glare = !optional_string("$NoGlare:"); sbm.xparent = 1; if ((idx = stars_find_sun(sbm.filename)) >= 0) { if (Parsing_modular_table) Sun_bitmaps[idx] = sbm; else Warning(LOCATION, "Sun bitmap '%s' listed more than once!! Only using the first entry!", sbm.filename); } else { Sun_bitmaps.push_back(std::move(sbm)); } } CHECK_END(); optional_string("#Motion Debris"); // normal debris pieces // leaving this for retail - Mjn if (check_for_string("$Debris:")) { mprintf(("Using deprecated motion debris parsing for Default motion debris!\n")); motion_debris_types this_debris; this_debris.name = "Default"; int count = 0; while (optional_string("$Debris:")) { in_check = true; stuff_string(name, F_NAME, MAX_FILENAME_LEN); if (count < MAX_MOTION_DEBRIS_BITMAPS) { strcpy_s(this_debris.bitmaps[count++].name, name); } else { Warning(LOCATION, "Could not load normal motion debris '%s'; maximum of %d exceeded.", name, MAX_MOTION_DEBRIS_BITMAPS); } } if (count == MAX_MOTION_DEBRIS_BITMAPS) { Motion_debris_info.push_back(std::move(this_debris)); } else { error_display(0, "Not enough bitmaps defined for motion debris '%s'. Skipping!\n", this_debris.name.c_str()); } } CHECK_END(); // nebula debris pieces // leaving this for retail - Mjn if (check_for_string("$DebrisNeb:")) { mprintf(("Using deprecated motion debris parsing for Nebula motion debris!\n")); motion_debris_types this_debris; this_debris.name = "Nebula"; int count = 0; while (optional_string("$DebrisNeb:")) { in_check = true; stuff_string(name, F_NAME, MAX_FILENAME_LEN); if (count < MAX_MOTION_DEBRIS_BITMAPS) { strcpy_s(this_debris.bitmaps[count++].name, name); } else { Warning(LOCATION, "Could not load nebula motion debris '%s'; maximum of %d exceeded.", name, MAX_MOTION_DEBRIS_BITMAPS); } } if (count == MAX_MOTION_DEBRIS_BITMAPS) { Motion_debris_info.push_back(std::move(this_debris)); } else { error_display(0, "Not enough bitmaps defined for motion debris '%s'. Skipping!\n", this_debris.name.c_str()); } } CHECK_END(); // custom debris pieces while (optional_string("$Motion Debris Name:")) { in_check = true; stuff_string(name, F_NAME, MAX_NAME_LEN); motion_debris_types this_debris; this_debris.name = name; // check if we will replace an existing entry int check = get_motion_debris_by_name(name); motion_debris_types* debris_ptr; //If we're going to create a new motion debris then set it up if (check == -1) { // initialize all the names as empty strings for later checking for (int i = 0; i < MAX_MOTION_DEBRIS_BITMAPS; i++) { this_debris.bitmaps[i].name[0] = '\0'; } Motion_debris_info.push_back(std::move(this_debris)); check = static_cast(Motion_debris_info.size()) - 1; } debris_ptr = &Motion_debris_info[check]; int count = 0; while (count < MAX_MOTION_DEBRIS_BITMAPS){ required_string("+Bitmap:"); stuff_string(name, F_NAME, MAX_FILENAME_LEN); strcpy_s(debris_ptr->bitmaps[count++].name, name); } for (int i = 0; i < MAX_MOTION_DEBRIS_BITMAPS; i++) { if(debris_ptr->bitmaps[i].name[0] == '\0'){ error_display(0, "Not enough bitmaps defined for motion debris '%s'. Removing!\n", this_debris.name.c_str()); Motion_debris_info.erase(Motion_debris_info.begin() + check); break; } } } CHECK_END(); // since it's possible for some idiot to have a tbl screwed up enough // that this ends up in an endless loop, give an opportunity to advance // through the file no matter what, because even the retail tbl has an // extra "#end" line in it. if (optional_string("#end") || (run_count++ > 5)) { run_count = 0; advance_to_eoln(NULL); } } } catch (const parse::ParseException& e) { mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", filename, e.what())); return; } } void stars_load_all_bitmaps() { static int Star_bitmaps_loaded = 0; if (Star_bitmaps_loaded) return; // pre-load all starfield bitmaps. ONLY SHOULD DO THIS FOR FRED!! // this can get nasty when a lot of bitmaps are in use so spare it for // the normal game and only do this in FRED int mprintf_count = 0; for (auto &sb : Starfield_bitmaps) { if (sb.bitmap_id < 0) { sb.bitmap_id = bm_load(sb.filename); // maybe didn't load a static image so try for an animated one if (sb.bitmap_id < 0) { sb.bitmap_id = bm_load_animation(sb.filename, &sb.n_frames, &sb.fps, nullptr, nullptr, true); if (sb.bitmap_id < 0) { mprintf(("Unable to load starfield bitmap: '%s'!\n", sb.filename)); mprintf_count++; } } } } if (mprintf_count > 0) { Warning(LOCATION, "Unable to load %d starfield bitmap(s)!\n", mprintf_count); } for (auto &sb : Sun_bitmaps) { // normal bitmap if (sb.bitmap_id < 0) { sb.bitmap_id = bm_load(sb.filename); // maybe didn't load a static image so try for an animated one if (sb.bitmap_id < 0) { sb.bitmap_id = bm_load_animation(sb.filename, &sb.n_frames, &sb.fps, nullptr, nullptr, true); if (sb.bitmap_id < 0) { Warning(LOCATION, "Unable to load sun bitmap: '%s'!\n", sb.filename); } } } // glow bitmap if (sb.glow_bitmap < 0) { sb.glow_bitmap = bm_load(sb.glow_filename); // maybe didn't load a static image so try for an animated one if (sb.glow_bitmap < 0) { sb.glow_bitmap = bm_load_animation(sb.glow_filename, &sb.glow_n_frames, &sb.glow_fps, nullptr, nullptr, true); if (sb.glow_bitmap < 0) { Warning(LOCATION, "Unable to load sun glow bitmap: '%s'!\n", sb.glow_filename); } } } if (sb.flare) { for (int i = 0; i < MAX_FLARE_BMP; i++) { if ( !strlen(sb.flare_bitmaps[i].filename) ) continue; if (sb.flare_bitmaps[i].bitmap_id < 0) { sb.flare_bitmaps[i].bitmap_id = bm_load(sb.flare_bitmaps[i].filename); if (sb.flare_bitmaps[i].bitmap_id < 0) { Warning(LOCATION, "Unable to load sun flare bitmap: '%s'!\n", sb.flare_bitmaps[i].filename); continue; } } } } } Star_bitmaps_loaded = 1; } void stars_clear_instances() { for (auto &sbi : Starfield_bitmap_instances) { delete [] sbi.verts; sbi.verts = nullptr; } Starfield_bitmap_instances.clear(); Suns.clear(); } // call on game startup void stars_init() { // parse stars.tbl parse_startbl("stars.tbl"); parse_modular_table("*-str.tbm", parse_startbl); // Warn if we can't find the two retail motion debris types. if (get_motion_debris_by_name("Default") < 0) Warning(LOCATION, "Motion debris 'Default' not found in stars.tbl. Motion debris will be disabled!\n"); if (get_motion_debris_by_name("Nebula") < 0) Warning(LOCATION, "Motion debris 'Nebula' not found in stars.tbl. Motion debris will be disabled!\n"); if (Cmdline_env) { ENVMAP = Default_env_map = bm_load("cubemap"); } } // call only from game_shutdown()!! void stars_close() { stars_clear_instances(); // any other code goes here } // called before mission parse so we can clear out all of the old stuff void stars_pre_level_init(bool clear_backgrounds) { Num_stars = 500; // we used to clear all the array entries, but now we can just wipe the vector if (clear_backgrounds) Backgrounds.clear(); stars_clear_instances(); stars_set_background_model(nullptr, nullptr); stars_set_background_orientation(); // mark all starfield and sun bitmaps as unused for this mission and release any current bitmaps // NOTE: that because of how we have to load the bitmaps it's important to release all of // them first thing rather than after we have marked and loaded only what's needed // NOTE2: there is a reason that we don't check for release before setting the handle to -1 so // be aware that this is NOT a bug. also, bmpman should NEVER return 0 as a valid handle! if ( !Fred_running ) { for (auto &sb : Starfield_bitmaps) { if (sb.bitmap_id > 0) { bm_release(sb.bitmap_id); sb.bitmap_id = -1; } sb.used_this_level = 0; sb.preload = 0; } for (auto &sb : Sun_bitmaps) { if (sb.bitmap_id > 0) { bm_release(sb.bitmap_id); sb.bitmap_id = -1; } // drop any derived angular size along with the bitmap it was measured from. The // handle check in sun_angular_radius_tangent() can't be relied on for this: bm_load() // hands back a recycled slot index, so a reloaded bitmap can come back on the handle // it had before. This is what makes a size change take effect when the background // changes -- which, outside of a mission, is every time the lab switches backgrounds. sb.disc_fraction = -1.0f; sb.disc_measured_for = -1; if (sb.glow_bitmap > 0) { bm_release(sb.glow_bitmap); sb.glow_bitmap = -1; } for (int i = 0; i < MAX_FLARE_BMP; i++) { if (sb.flare_bitmaps[i].bitmap_id > 0) { bm_release(sb.flare_bitmaps[i].bitmap_id); sb.flare_bitmaps[i].bitmap_id = -1; } } sb.used_this_level = 0; sb.preload = 0; } } // also clear the preload indexes Preload_background_indexes.clear(); Dynamic_environment = false; Motion_debris_override = false; Env_cubemap_drawn = false; Irr_cubemap_drawn = false; // reset the skybox timestamp, used for animated textures Skybox_timestamp = _timestamp(); } // setup the render target ready for this mission's environment map static void environment_map_gen() { const int size = 512; int gen_flags = (BMP_FLAG_RENDER_TARGET_STATIC | BMP_FLAG_CUBEMAP | BMP_FLAG_RENDER_TARGET_MIPMAP); if ( !Cmdline_env ) { return; } if (gr_screen.envmap_render_target >= 0) { if ( !bm_release(gr_screen.envmap_render_target, 1) ) { Warning(LOCATION, "Unable to release environment map render target."); } gr_screen.envmap_render_target = -1; } if ( Dynamic_environment || (The_mission.flags[Mission::Mission_Flags::Subspace]) ) { Dynamic_environment = true; gen_flags &= ~BMP_FLAG_RENDER_TARGET_STATIC; gen_flags |= BMP_FLAG_RENDER_TARGET_DYNAMIC; } // bail if we are going to be static, and have an envmap specified already else if ( strlen(The_mission.envmap_name) ) { // Load the mission map so we can use it later Mission_env_map = bm_load(The_mission.envmap_name); // DX10 (BC7) files keep no cube flag, so they cannot be checked here if (Mission_env_map < 0 || bm_is_cubemap(Mission_env_map) || bm_is_compressed(Mission_env_map) == DDS_BC7) { return; } Warning(LOCATION, "Environment map '%s' is not a cubemap; using the generated environment map instead.", The_mission.envmap_name); bm_release(Mission_env_map); Mission_env_map = -1; } gr_screen.envmap_render_target = bm_make_render_target(size, size, gen_flags); } // setup the render target ready for this mission's environment map static void irradiance_map_gen() { const int irr_size = 16; int gen_flags = (BMP_FLAG_RENDER_TARGET_STATIC | BMP_FLAG_CUBEMAP | BMP_FLAG_RENDER_TARGET_MIPMAP); if (!Cmdline_env) { return; } if (gr_screen.irrmap_render_target >= 0) { if (!bm_release(gr_screen.irrmap_render_target, 1)) { Warning(LOCATION, "Unable to release environment map render target."); } gr_screen.irrmap_render_target = -1; IRRMAP = -1; } if (Dynamic_environment || (The_mission.flags[Mission::Mission_Flags::Subspace])) { Dynamic_environment = true; gen_flags &= ~BMP_FLAG_RENDER_TARGET_STATIC; gen_flags |= BMP_FLAG_RENDER_TARGET_DYNAMIC; } gr_screen.irrmap_render_target = bm_make_render_target(irr_size, irr_size, gen_flags); IRRMAP = gr_screen.irrmap_render_target; } // call this in game_post_level_init() so we know whether we're running in full nebula mode or not void stars_post_level_init() { int i; vec3d v; float dist, dist_max; ubyte red,green,blue,alpha; if (gr_screen.mode == GraphicsAPI::Stub) { return; } // in FRED, make sure we always have at least one background if (Fred_running) { if (Backgrounds.empty()) stars_add_blank_background(true); } // in FSO, see if we have any backgrounds to preload // (since the backgrounds aren't parsed at the time we parse the sexps) else { for (int idx : Preload_background_indexes) stars_preload_background(idx); } stars_set_background_model(The_mission.skybox_model, NULL, The_mission.skybox_flags); stars_set_background_orientation(&The_mission.skybox_orientation); stars_load_debris( ((The_mission.flags[Mission::Mission_Flags::Fullneb]) || Nebula_sexp_used) ); // following code randomly distributes star points within a sphere volume, which // avoids there being denser areas along the edges and in corners that we had in the // old rectangular distribution scheme. dist_max = (float) (HALF_RND_MAX * HALF_RND_MAX); for (i=0; i= dist_max) { v.xyz.x = (float) ((Random::next() & RND_MAX_MASK) - HALF_RND_MAX); v.xyz.y = (float) ((Random::next() & RND_MAX_MASK) - HALF_RND_MAX); v.xyz.z = (float) ((Random::next() & RND_MAX_MASK) - HALF_RND_MAX); dist = v.xyz.x * v.xyz.x + v.xyz.y * v.xyz.y + v.xyz.z * v.xyz.z; } vm_vec_copy_normalize(&Stars[i].pos, &v); { red = (ubyte)Random::next(192, 255); green = (ubyte)Random::next(192, 255); blue = (ubyte)Random::next(192, 255); alpha = (ubyte)Random::next(24, 216); gr_init_alphacolor(&Stars[i].col, red, green, blue, alpha, AC_TYPE_BLEND); } } memset( &Motion_debris, 0, sizeof(motion_debris_instance) * MAX_MOTION_DEBRIS ); for (i=0; i<8; i++ ) { ubyte intensity = (ubyte)((i + 1) * 24); gr_init_alphacolor(&star_aacolors[i], 255, 255, 255, intensity, AC_TYPE_BLEND ); gr_init_color(&star_colors[i], intensity, intensity, intensity ); } last_stars_filled = 0; // if we have no sun instances, create one if ( Suns.empty() ) { if ( !strlen(Sun_bitmaps[0].filename) ) { mprintf(("Trying to add default sun but no default exists!!\n")); } else { mprintf(("Adding default sun.\n")); starfield_bitmap_instance def_sun; // stuff some values def_sun.ang.h = fl_radians(-60.0f); Suns.push_back(def_sun); } } // FRED doesn't do normal page_in stuff so we need to load up the bitmaps here instead if (Fred_running) { stars_load_all_bitmaps(); // see whether we are missing any suns or bitmaps int count = static_cast(Backgrounds.size()); for (i = 0; i < count; ++i) { SCP_string failed_suns; for (auto &sun : Backgrounds[i].suns) { if (stars_find_sun(sun.filename) < 0) { failed_suns += sun.filename; failed_suns += "\n"; } } SCP_string failed_bitmaps; for (auto &bitmap : Backgrounds[i].bitmaps) { if (stars_find_bitmap(bitmap.filename) < 0) { failed_bitmaps += bitmap.filename; failed_bitmaps += "\n"; } } if (!failed_suns.empty()) { Warning(LOCATION, "In Background %d, failed to load the following suns:\n%s", (i + 1), failed_suns.c_str()); } if (!failed_bitmaps.empty()) { Warning(LOCATION, "In Background %d, failed to load the following bitmaps:\n%s", (i + 1), failed_bitmaps.c_str()); } } } starfield_generate_bitmap_buffers(); environment_map_gen(); } void stars_level_close() { if (gr_screen.envmap_render_target >= 0) { if ( bm_release(gr_screen.envmap_render_target, 1) ) { gr_screen.envmap_render_target = -1; } } if (Mission_env_map >= 0) { bm_release(Mission_env_map); Mission_env_map = -1; } ENVMAP = Default_env_map; } extern object * Player_obj; #define STAR_AMOUNT_DEFAULT 0.75f #define STAR_DIM_DEFAULT 7800.0f #define STAR_CAP_DEFAULT 75.0f #define STAR_MAX_LENGTH_DEFAULT 0.04f // 312 float Star_amount = STAR_AMOUNT_DEFAULT; float Star_dim = STAR_DIM_DEFAULT; float Star_cap = STAR_CAP_DEFAULT; float Star_max_length = STAR_MAX_LENGTH_DEFAULT; #define STAR_FLAG_TAIL (1<<0) // Draw a tail when moving #define STAR_FLAG_DIM (1<<1) // Dim as you move #define STAR_FLAG_ANTIALIAS (1<<2) // Draw the star using antialiased lines #define STAR_FLAG_DEFAULT (STAR_FLAG_TAIL | STAR_FLAG_DIM) uint Star_flags = STAR_FLAG_DEFAULT; //XSTR:OFF DCF(stars,"Set parameters for starfield") { SCP_string arg; float val_f; int val_i; if (dc_optional_string_either("help", "--help")) { dc_printf( "Usage: stars keyword\nWhere keyword can be in the following forms:\n" ); dc_printf( "stars default Resets stars to all default values\n" ); dc_printf( "stars num X Sets number of stars to X. Between 0 and %d.\n", MAX_STARS ); dc_printf( "stars tail X Where X is the percent of 'tail' between 0 and 1.0\n" ); dc_printf( "stars dim X Where X is the amount stars dim between 0 and 255.0\n" ); dc_printf( "stars cap X Where X is the cap of dimming between 0 and 255.\n" ); dc_printf( "stars len X Where X is the cap of length.\n" ); dc_printf( "stars m0 Macro0. Old 'pixel type' crappy stars. flags=none\n" ); dc_printf( "stars m1 Macro1. (default) tail=.75, dim=20.0, cap=75.0, flags=dim,tail\n" ); dc_printf( "stars m2 Macro2. tail=.75, dim=20.0, cap=75.0, flags=dim,tail,aa\n" ); dc_printf( "stars flag X Toggles flag X, where X is tail or dim or aa (aa=antialias)\n" ); dc_printf( "\nHINT: set cap to 0 to get dim rate and tail down, then use\n" ); dc_printf( "cap to keep the lines from going away when moving too fast.\n" ); dc_printf( "\nUse '? stars' to see current values.\n" ); return; // don't print status if help is printed. Too messy. } if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) { dc_printf( "Num_stars: %d\n", Num_stars ); dc_printf( "Tail: %.2f\n", Star_amount ); dc_printf( "Dim : %.2f\n", Star_dim ); dc_printf( "Cap : %.2f\n", Star_cap ); dc_printf( "Max length: %.2f\n", Star_max_length ); dc_printf( "Flags:\n" ); dc_printf( " Tail : %s\n", (Star_flags&STAR_FLAG_TAIL?"On":"Off") ); dc_printf( " Dim : %s\n", (Star_flags&STAR_FLAG_DIM?"On":"Off") ); dc_printf( " Antialias: %s\n", (Star_flags&STAR_FLAG_ANTIALIAS?"On":"Off") ); dc_printf( "\nTHESE AREN'T SAVED TO DISK, SO IF YOU TWEAK\n" ); dc_printf( "THESE AND LIKE THEM, WRITE THEM DOWN!!\n" ); return; } dc_stuff_string_white(arg); // "stars default" is handled by "stars m1" if (arg == "num") { dc_stuff_int(&val_i); CLAMP(val_i, 0, MAX_STARS); Num_stars = val_i; dc_printf("Num_stars set to %i\n", Num_stars); } else if (arg == "tail") { dc_stuff_float(&val_f); CLAMP(val_f, 0.0f, 1.0f); Star_amount = val_f; dc_printf("Star_amount set to %f\n", Star_amount); } else if (arg == "dim") { dc_stuff_float(&val_f); if (val_f > 0.0f ) { Star_dim = val_f; dc_printf("Star_dim set to %f\n", Star_dim); } else { dc_printf("Error: Star_dim value must be non-negative\n"); } } else if (arg == "cap") { dc_stuff_float(&val_f); CLAMP(val_f, 0.0f, 255.0f); Star_cap = val_f; dc_printf("Star_cap set to %f\n", Star_cap); } else if (arg == "len") { dc_stuff_float(&Star_max_length); dc_printf("Star_max_length set to %f\n", Star_max_length); } else if (arg == "m0") { Star_amount = 0.0f; Star_dim = 0.0f; Star_cap = 0.0f; Star_flags = 0; Star_max_length = STAR_MAX_LENGTH_DEFAULT; dc_printf("Starfield set: Old 'pixel type' crappy stars. flags=none\n"); } else if ((arg == "m1") || (arg == "default")) { Star_amount = STAR_AMOUNT_DEFAULT; Star_dim = STAR_DIM_DEFAULT; Star_cap = STAR_CAP_DEFAULT; Star_flags = STAR_FLAG_DEFAULT; Star_max_length = STAR_MAX_LENGTH_DEFAULT; dc_printf("Starfield set: (default) tail=.75, dim=20.0, cap=75.0, flags=dim,tail\n"); } else if (arg == "m2") { Star_amount = 0.75f; Star_dim = 20.0f; Star_cap = 75.0f; Star_flags = STAR_FLAG_TAIL|STAR_FLAG_DIM|STAR_FLAG_ANTIALIAS; Star_max_length = STAR_MAX_LENGTH_DEFAULT; dc_printf("Starfield set: tail=.75, dim=20.0, cap=75.0, flags=dim,tail,aa\n"); } else if (arg == "flag") { dc_stuff_string_white(arg); if (arg == "tail") { Star_flags ^= STAR_FLAG_TAIL; } else if (arg == "dim" ) { Star_flags ^= STAR_FLAG_DIM; } else if (arg == "aa" ) { Star_flags ^= STAR_FLAG_ANTIALIAS; } else { dc_printf("Error: unknown flag argument '%s'\n", arg.c_str()); } } else { dc_printf("Error: Unknown argument '%s'", arg.c_str()); } } //XSTR:ON bool refresh_motion_debris = true; // If set to true, then regenerate the positions of motion debris // Call this if camera "cuts" or moves long distances // so blur effect doesn't draw lines all over the screen. void stars_camera_cut() { last_stars_filled = 0; refresh_motion_debris = true; } //#define TIME_STAR_CODE // enable to time star code extern int Sun_drew; // get the world coords of the sun pos on the unit sphere. void stars_get_sun_pos(int sun_n, vec3d *pos) { vec3d temp; matrix rot; // sanity Assert(SCP_vector_inbounds(Suns, sun_n)); if (!SCP_vector_inbounds(Suns, sun_n)) { return; } // rotate the sun properly temp = vmd_zero_vector; temp.xyz.z = 1.0f; // rotation matrix vm_angles_2_matrix(&rot, &Suns[sun_n].ang); vm_vec_unrotate(pos, &temp, &rot); } /** * @brief The apparent diameter this sun should use, in degrees, or negative if nothing sets one * * @param mission_angular_size this sun instance's +AngularSize: from the mission file */ static float sun_explicit_angular_size(const starfield_bitmap *bm, float mission_angular_size) { // the first layer that specifies a size wins: the LabUi session override, then the // mission's value for this sun instance, then the sun's stars.tbl entry for (float degrees : {Sun_angular_size_override, mission_angular_size, bm->angular_size}) { if (degrees >= 0.0f) { return degrees; } } return SUN_ANGULAR_SIZE_UNSPECIFIED; } /** * @brief How much of this sun's bitmap is its emitting disc, measuring it on first use * * @return the disc fraction, or 0 for a sun whose bitmap has no measurable disc */ static float sun_measured_disc_fraction(starfield_bitmap *bm) { // measuring reads the bitmap's pixels, so it happens once and is cached against the // handle it was measured from if ( (bm->disc_fraction >= 0.0f) && (bm->disc_measured_for == bm->bitmap_id) ) { return bm->disc_fraction; } const float measured = sun_disc_measure_bitmap(bm->bitmap_id); // both a bitmap we couldn't read and one with no drawn disc at all fall back to hard // shadows, but they are very different things -- keep them apart in the log, or a broken // read reads as "working as intended". Neither is worth a Warning(): this runs for every // sun in every mission, not just for content that asked for it, and hard shadows are a // perfectly serviceable outcome if (measured < 0.0f) { mprintf(("Sun '%s': could not read the bitmap to measure it, using hard shadows\n", bm->filename)); } else if (measured == 0.0f) { // mods ship blank sun bitmaps to get a light source without a visible sun mprintf(("Sun '%s': no drawn disc, using hard shadows\n", bm->filename)); } else { // logged once per sun bitmap, not per frame -- this is what a mod tunes its suns // against. The size is quoted at +Scale: 1.0 since the measurement is a property of // the bitmap; a mission scaling its sun scales this with it. mprintf(("Sun '%s': measured disc fraction %.3f (%.2f degrees at +Scale: 1.0)\n", bm->filename, measured, fl_degrees(2.0f * atanf(sun_disc_tangent_from_fraction(measured, 1.0f))))); } bm->disc_fraction = (measured > 0.0f) ? measured : 0.0f; bm->disc_measured_for = bm->bitmap_id; return bm->disc_fraction; } /** * @brief Tangent of a sun's angular radius, which is what a directional light's source_radius is * * See traceShadowRayCone() in shadows.sdr: for directional lights the source radius *is* the * tangent of the angular radius, so the penumbra widens with occluder distance the way a real * area light's would. 0 means a point source, i.e. hard shadows. * * @param scale_x the mission's +Scale: for this sun instance * @param mission_angular_size this sun instance's +AngularSize: from the mission file */ static float sun_angular_radius_tangent(starfield_bitmap *bm, float scale_x, float mission_angular_size) { const float specified = sun_explicit_angular_size(bm, mission_angular_size); if (specified >= 0.0f) { return sun_disc_tangent_from_diameter(specified); } // the size is only used to soften shadows, so do not read the bitmap when shadows are off if (Shadow_quality == ShadowQuality::Disabled) { return 0.0f; } return sun_disc_tangent_from_fraction(sun_measured_disc_fraction(bm), scale_x); } // draw sun void stars_draw_sun(int show_sun) { GR_DEBUG_SCOPE("Draw Suns"); TRACE_SCOPE(tracing::DrawSuns); int idx; vec3d sun_pos; vec3d sun_dir; vertex sun_vex; starfield_bitmap *bm; float local_scale; if (gr_screen.mode == GraphicsAPI::Stub) { return; } // should we even be here? if (!show_sun) return; // no suns drew yet Sun_drew = 0; // draw all suns int num_suns = static_cast(Suns.size()); for (idx = 0; idx < num_suns; idx++) { // get the instance if (Suns[idx].star_bitmap_index < 0) return; bm = &Sun_bitmaps[Suns[idx].star_bitmap_index]; // if no bitmap then bail... if (bm->bitmap_id < 0) continue; memset( &sun_vex, 0, sizeof(vertex) ); // get sun pos sun_pos = vmd_zero_vector; sun_pos.xyz.y = 1.0f; stars_get_sun_pos(idx, &sun_pos); // get the direction sun_dir = sun_pos; vm_vec_normalize(&sun_dir); // add the light source corresponding to the sun, except when rendering to an envmap. // For directional lights, source_radius carries the tangent of the sun's angular // radius (see traceShadowRayCone() in shadows.sdr), sizing its shadow penumbra. if ( !Rendering_to_env ) { light_add_directional(&sun_dir, idx, !bm->glare, bm->i, bm->r, bm->g, bm->b, sun_angular_radius_tangent(bm, Suns[idx].scale_x, Suns[idx].angular_size)); } // if supernova if ( supernova_active() && (idx == 0) ) local_scale = 1.0f + (SUPERNOVA_SUN_SCALE * supernova_pct_complete()); else local_scale = 1.0f; // draw the sun itself, keep track of how many we drew int bitmap_id = -1; if (bm->fps) { bitmap_id = bm->bitmap_id + ((timestamp() * bm->fps / MILLISECONDS_PER_SECOND) % bm->n_frames); } else { bitmap_id = bm->bitmap_id; } g3_rotate_faraway_vertex(&sun_vex, &sun_pos); if ( sun_vex.codes & (CC_BEHIND|CC_OFF_USER) ) { continue; } if ( !(sun_vex.flags & PF_PROJECTED) ) { g3_project_vertex(&sun_vex); } if ( sun_vex.flags & PF_OVERFLOW ) { continue; } material mat_params; material_set_unlit(&mat_params, bitmap_id, 0.999f, true, false); g3_render_rect_screen_aligned_2d(&mat_params, &sun_vex, 0, 0.05f * Suns[idx].scale_x * local_scale, true); Sun_drew++; // if ( !g3_draw_bitmap(&sun_vex, 0, 0.05f * Suns[idx].scale_x * local_scale, TMAP_FLAG_TEXTURED) ) // Sun_drew++; } } // draw a star's lens-flare void stars_draw_lens_flare(vertex *sun_vex, int sun_n) { starfield_bitmap *bm; int i,j; float dx,dy; vertex flare_vex = *sun_vex; //copy over to flare_vex to get all sorts of properties if (gr_screen.mode == GraphicsAPI::Stub) { return; } Assert(SCP_vector_inbounds(Suns, sun_n)); if (!SCP_vector_inbounds(Suns, sun_n)) { return; } // get the instance if (Suns[sun_n].star_bitmap_index < 0) { return; } else { bm = &Sun_bitmaps[Suns[sun_n].star_bitmap_index]; } if (!bm->flare) return; /* (dx,dy) is a 2d vector equal to two times the vector from the sun's position to the center fo the screen meaning it is the vector to the opposite position on the screen. */ dx = 2.0f*(i2fl(gr_screen.clip_right-gr_screen.clip_left)*0.5f - sun_vex->screen.xyw.x); dy = 2.0f*(i2fl(gr_screen.clip_bottom-gr_screen.clip_top)*0.5f - sun_vex->screen.xyw.y); for (j = 0; j < bm->n_flare_bitmaps; j++) { // if no bitmap then bail... if (bm->flare_bitmaps[j].bitmap_id < 0) continue; //gr_set_bitmap(bm->flare_bitmaps[j].bitmap_id, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 0.999f); for (i = 0; i < bm->n_flares; i++) { // draw sorted by texture, to minimize texture changes. not the most efficient way, but better than non-sorted if (bm->flare_infos[i].tex_num == j) { flare_vex.screen.xyw.x = sun_vex->screen.xyw.x + dx * bm->flare_infos[i].pos; flare_vex.screen.xyw.y = sun_vex->screen.xyw.y + dy * bm->flare_infos[i].pos; //g3_draw_bitmap(&flare_vex, 0, 0.05f * bm->flare_infos[i].scale, TMAP_FLAG_TEXTURED); material mat_params; material_set_unlit(&mat_params, bm->flare_bitmaps[j].bitmap_id, 0.999f, true, false); g3_render_rect_screen_aligned_2d(&mat_params, &flare_vex, 0, 0.05f * bm->flare_infos[i].scale); } } } } // draw the corresponding glow for sun_n void stars_draw_sun_glow(int sun_n) { starfield_bitmap *bm; vec3d sun_pos, sun_dir; vertex sun_vex; float local_scale; if (gr_screen.mode == GraphicsAPI::Stub) { return; } // sanity Assert(SCP_vector_inbounds(Suns, sun_n)); if (!SCP_vector_inbounds(Suns, sun_n)) { return; } // get the instance if (Suns[sun_n].star_bitmap_index < 0) return; bm = &Sun_bitmaps[Suns[sun_n].star_bitmap_index]; // if no bitmap then bail... if (bm->glow_bitmap < 0) return; memset( &sun_vex, 0, sizeof(vertex) ); // get sun pos sun_pos = vmd_zero_vector; sun_pos.xyz.y = 1.0f; stars_get_sun_pos(sun_n, &sun_pos); // get the direction sun_dir = sun_pos; vm_vec_normalize(&sun_dir); // if supernova if ( supernova_active() && (sun_n == 0) ) local_scale = 1.0f + (SUPERNOVA_SUN_SCALE * supernova_pct_complete()); else local_scale = 1.0f; // draw the sun itself, keep track of how many we drew int bitmap_id = -1; if (bm->glow_fps) { bitmap_id = bm->glow_bitmap + ((timestamp() * bm->glow_fps / MILLISECONDS_PER_SECOND) % bm->glow_n_frames); } else { bitmap_id = bm->glow_bitmap; } g3_rotate_faraway_vertex(&sun_vex, &sun_pos); //int zbuff = gr_zbuffer_set(GR_ZBUFF_NONE); //g3_draw_bitmap(&sun_vex, 0, 0.10f * Suns[sun_n].scale_x * local_scale, TMAP_FLAG_TEXTURED); material mat_params; material_set_unlit(&mat_params, bitmap_id, 0.5f, true, false); g3_render_rect_screen_aligned_2d(&mat_params, &sun_vex, 0, 0.10f * Suns[sun_n].scale_x * local_scale, true); if (bm->flare) { vec3d light_dir; vec3d local_light_dir; light_get_global_dir(&light_dir, sun_n); vm_vec_rotate(&local_light_dir, &light_dir, &Eye_matrix); float dot=vm_vec_dot( &light_dir, &Eye_matrix.vec.fvec ); if (dot > 0.7f) // Only render the flares if the sun is reasonably near the center of the screen stars_draw_lens_flare(&sun_vex, sun_n); } //gr_zbuffer_set(zbuff); } void stars_draw_bitmaps(int show_bitmaps) { GR_DEBUG_SCOPE("Draw Bitmaps"); TRACE_SCOPE(tracing::DrawBitmaps); int idx; int star_index; if (gr_screen.mode == GraphicsAPI::Stub) { return; } // should we even be here? if ( !show_bitmaps ) return; // if we're in the nebula, don't render any backgrounds if (The_mission.flags[Mission::Mission_Flags::Fullneb] && !The_mission.flags[Mission::Mission_Flags::Fullneb_background_bitmaps]) return; // detail settings if ( !Detail.planets_suns ) return; gr_start_instance_matrix(&Eye_position, &vmd_identity_matrix); int sb_instances = static_cast(Starfield_bitmap_instances.size()); for (idx = 0; idx < sb_instances; idx++) { // lookup the info index star_index = Starfield_bitmap_instances[idx].star_bitmap_index; if (star_index < 0) { continue; } // if no bitmap then bail... if (Starfield_bitmaps[star_index].bitmap_id < 0) { continue; } int bitmap_id; bool blending = false; float alpha = 1.0f; if (Starfield_bitmaps[star_index].xparent) { if (Starfield_bitmaps[star_index].fps) { bitmap_id = Starfield_bitmaps[star_index].bitmap_id + (((timestamp() * Starfield_bitmaps[star_index].fps) / MILLISECONDS_PER_SECOND) % Starfield_bitmaps[star_index].n_frames); } else { bitmap_id = Starfield_bitmaps[star_index].bitmap_id; } } else { if (Starfield_bitmaps[star_index].fps) { bitmap_id = Starfield_bitmaps[star_index].bitmap_id + (((timestamp() * Starfield_bitmaps[star_index].fps) / MILLISECONDS_PER_SECOND) % Starfield_bitmaps[star_index].n_frames); blending = true; alpha = 0.9999f; } else { bitmap_id = Starfield_bitmaps[star_index].bitmap_id; blending = true; alpha = 0.9999f; } } material material_params; material_set_unlit(&material_params, bitmap_id, alpha, blending, false); g3_render_primitives_textured(&material_params, Starfield_bitmap_instances[idx].verts, Starfield_bitmap_instances[idx].n_verts, PRIM_TYPE_TRIS, false); } gr_end_instance_matrix(); } extern int Interp_subspace; extern float Interp_subspace_offset_u; extern float Interp_subspace_offset_u; extern float Interp_subspace_offset_v; float subspace_offset_u = 0.0f; float subspace_offset_u_inner = 0.0f; float subspace_offset_v = 0.0f; float subspace_u_speed = 0.07f; // how fast u changes float subspace_v_speed = 0.05f; // how fast v changes int Subspace_glow_bitmap = -1; float Subspace_glow_frame = 0.0f; float Subspace_glow_rate = 1.0f; //XSTR:OFF DCF(subspace_set,"Set parameters for subspace effect") { SCP_string arg; float value; if (dc_optional_string_either("help", "--help")) { dc_printf( "Usage: subspace [--status] \n"); dc_printf("[--status] -- Displays the current speeds for both axes\n"); dc_printf(" -- May be either 'u' or 'v', and corresponds to the texture axis\n"); dc_printf(" -- is the speed along the axis that the texture is moved\n"); return; } if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) { dc_printf( "u: %.2f\n", subspace_u_speed ); dc_printf( "v: %.2f\n", subspace_v_speed ); return; } dc_stuff_string_white(arg); if (arg == "u") { dc_stuff_float(&value); if ( value < 0.0f ) { dc_printf("Error: speed must be non-negative"); return; } subspace_u_speed = value; } else if (arg == "v") { dc_stuff_float(&value); if (value < 0.0f) { dc_printf("Error: speed must be non-negative"); return; } subspace_v_speed = value; } else { dc_printf("Error: Unknown axis '%s'", arg.c_str()); } } //XSTR:ON void subspace_render() { int framenum = 0; if (gr_screen.mode == GraphicsAPI::Stub) { return; } if ( Subspace_model_inner < 0 ) { Subspace_model_inner = model_load( "subspace_small.pof" ); Assert(Subspace_model_inner >= 0); } if ( Subspace_model_outer < 0 ) { Subspace_model_outer = model_load( "subspace_big.pof" ); Assert(Subspace_model_outer >= 0); } if ( Subspace_glow_bitmap < 0 ) { Subspace_glow_bitmap = bm_load( NOX("SunGlow01")); Assert(Subspace_glow_bitmap >= 0); } if ( !Rendering_to_env ) { Subspace_glow_frame += flFrametime * 1.0f; float total_time = i2fl(NOISE_NUM_FRAMES) / 15.0f; // Sanity checks if ( Subspace_glow_frame < 0.0f ) Subspace_glow_frame = 0.0f; if ( Subspace_glow_frame > 100.0f ) Subspace_glow_frame = 0.0f; while ( Subspace_glow_frame > total_time ) { Subspace_glow_frame -= total_time; } framenum = fl2i( (Subspace_glow_frame*NOISE_NUM_FRAMES) / total_time ); if ( framenum < 0 ) framenum = 0; if ( framenum >= NOISE_NUM_FRAMES ) framenum = NOISE_NUM_FRAMES-1; subspace_offset_u += flFrametime*subspace_u_speed; if (subspace_offset_u > 1.0f ) { subspace_offset_u -= 1.0f; } subspace_offset_u_inner += flFrametime*subspace_u_speed*3.0f; if (subspace_offset_u > 1.0f ) { subspace_offset_u -= 1.0f; } subspace_offset_v += flFrametime*subspace_v_speed; if (subspace_offset_v > 1.0f ) { subspace_offset_v -= 1.0f; } } matrix tmp; angles angs = vmd_zero_angles; angs.b = subspace_offset_v * PI2; vm_angles_2_matrix(&tmp,&angs); int saved_gr_zbuffering = gr_zbuffer_get(); gr_zbuffer_set(GR_ZBUFF_NONE); uint64_t render_flags = MR_NO_LIGHTING | MR_ALL_XPARENT; Interp_subspace = 1; Interp_subspace_offset_u = 1.0f - subspace_offset_u; Interp_subspace_offset_v = 0.0f; model_render_params render_info; render_info.set_alpha(1.0f); render_info.set_flags(render_flags); gr_set_texture_panning(Interp_subspace_offset_v, Interp_subspace_offset_u, true); model_render_immediate( &render_info, Subspace_model_outer, &tmp, &Eye_position); //MR_NO_CORRECT|MR_SHOW_OUTLINE gr_set_texture_panning(0, 0, false); Interp_subspace = 1; Interp_subspace_offset_u = 1.0f - subspace_offset_u_inner; Interp_subspace_offset_v = 0.0f; angs.b = -subspace_offset_v * PI2; vm_angles_2_matrix(&tmp,&angs); render_info.set_color(255, 255, 255); render_info.set_alpha(1.0f); render_info.set_flags(render_flags); gr_set_texture_panning(Interp_subspace_offset_v, Interp_subspace_offset_u, true); model_render_immediate( &render_info, Subspace_model_inner, &tmp, &Eye_position ); //MR_NO_CORRECT|MR_SHOW_OUTLINE gr_set_texture_panning(0, 0, false); //Render subspace glows here and not as thrusters - Valathil vec3d glow_pos; vertex glow_vex; glow_pos.xyz.x = 0.0f; glow_pos.xyz.y = 0.0f; glow_pos.xyz.z = 1.0f; //gr_set_bitmap(Subspace_glow_bitmap, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 1.0f); material mat_params; material_set_unlit(&mat_params, Subspace_glow_bitmap, 1.0f, true, false); g3_rotate_faraway_vertex(&glow_vex, &glow_pos); //g3_draw_bitmap(&glow_vex, 0, 17.0f + 0.5f * Noise[framenum], TMAP_FLAG_TEXTURED); g3_render_rect_screen_aligned_2d(&mat_params, &glow_vex, 0, (17.0f + 0.5f * Noise[framenum]) * 0.01f, true); glow_pos.xyz.z = -1.0f; g3_rotate_faraway_vertex(&glow_vex, &glow_pos); //g3_draw_bitmap(&glow_vex, 0, 17.0f + 0.5f * Noise[framenum], TMAP_FLAG_TEXTURED); g3_render_rect_screen_aligned_2d(&mat_params, &glow_vex, 0, (17.0f + 0.5f * Noise[framenum]) * 0.01f, true); Interp_subspace = 0; gr_zbuffer_set(saved_gr_zbuffering); } void stars_draw_stars() { GR_DEBUG_SCOPE("Draw Starfield"); TRACE_SCOPE(tracing::DrawStarfield); int i; star *sp; float dist = 0.0f; float ratio; vDist vDst; vertex p1, p2; int can_draw = 1; if (gr_screen.mode == GraphicsAPI::Stub) { return; } if ( !last_stars_filled ) { for (i = 0; i < Num_stars; i++) { g3_rotate_faraway_vertex(&p2, &Stars[i].pos); Stars[i].last_star_pos = p2.world; } } int tmp_num_stars = 0; tmp_num_stars = (Detail.num_stars * Num_stars) / MAX_DETAIL_VALUE; CLAMP(tmp_num_stars, 0, Num_stars); auto path = graphics::paths::PathRenderer::instance(); path->saveState(); path->resetState(); path->beginFrame(); for (i = 0; i < tmp_num_stars; i++) { sp = &Stars[i]; can_draw = 1; memset(&p1, 0, sizeof(vertex)); memset(&p2, 0, sizeof(vertex)); // This makes a star look "proper" by not translating the // point around the viewer's eye before rotation. In other // words, when the ship translates, the stars do not change. g3_rotate_faraway_vertex(&p2, &sp->pos); if ( p2.codes ) { can_draw = 0; } else { g3_project_vertex(&p2); if ( p2.flags & PF_OVERFLOW ) { can_draw = 0; } } if ( can_draw && (Star_flags & (STAR_FLAG_TAIL|STAR_FLAG_DIM)) ) { dist = vm_vec_dist_quick( &sp->last_star_pos, &p2.world ); if ( dist > Star_max_length ) { ratio = Star_max_length / dist; dist = Star_max_length; } else { ratio = 1.0f; } ratio *= Star_amount; vm_vec_sub(&p1.world, &sp->last_star_pos, &p2.world); vm_vec_scale(&p1.world, ratio); vm_vec_add2(&p1.world, &p2.world); p1.flags = 0; // not projected g3_code_vertex( &p1 ); if ( p1.codes ) { can_draw = 0; } else { g3_project_vertex(&p1); if ( p1.flags & PF_OVERFLOW ) { can_draw = 0; } } } sp->last_star_pos = p2.world; if ( !can_draw ) continue; vDst.x = fl2i(p1.screen.xyw.x) - fl2i(p2.screen.xyw.x); vDst.y = fl2i(p1.screen.xyw.y) - fl2i(p2.screen.xyw.y); float len = sqrtf((float)((vDst.x * vDst.x) + (vDst.y * vDst.y))); color col = sp->col; if (len <= 2.0f ) { p1.screen.xyw.x = p2.screen.xyw.x + 1.0f; p1.screen.xyw.y = p2.screen.xyw.y; } else { // gamma correction col.red = (ubyte)((float)col.red / powf(len, 1.0f / 2.2f)); col.green = (ubyte)((float)col.green / powf(len, 1.0f / 2.2f)); col.blue = (ubyte)((float)col.blue / powf(len, 1.0f / 2.2f)); col.alpha = (ubyte)((float)col.alpha / powf(len, 1.0f / 2.2f)); } path->beginPath(); path->moveTo(p1.screen.xyw.x, p1.screen.xyw.y); path->lineTo(p2.screen.xyw.x, p2.screen.xyw.y); path->setStrokeColor(&col); path->stroke(); } path->endFrame(); path->restoreState(); } void stars_draw_motion_debris() { GR_DEBUG_SCOPE("Draw motion debris"); TRACE_SCOPE(tracing::DrawMotionDebris); if (gr_screen.mode == GraphicsAPI::Stub) { return; } if (Motion_debris_override) return; if (!Motion_debris_enabled || Motion_debris_info.empty()) { return; } for (motion_debris_instance &mdebris : Motion_debris) { float vdist = vm_vec_dist(&mdebris.pos, &Eye_position); if ((vdist < MIN_DIST_RANGE) || (vdist > MAX_DIST_RANGE)) { // if we just had a camera "cut" and should refresh the debris then generate in the sphere, else just on its surface vm_vec_random_in_sphere(&mdebris.pos, &Eye_position, MAX_DIST_RANGE, !refresh_motion_debris); vdist = vm_vec_dist(&mdebris.pos, &Eye_position); mdebris.vclip = Random::next(MAX_MOTION_DEBRIS_BITMAPS); // rand() // if we're in full neb mode const float size_multiplier = i2fl(Random::next(4)); if((The_mission.flags[Mission::Mission_Flags::Fullneb]) && (Neb2_render_mode != NEB2_RENDER_NONE)) { mdebris.size = size_multiplier * BASE_SIZE_NEB; } else { mdebris.size = size_multiplier * BASE_SIZE; } } vertex pnt; g3_rotate_vertex(&pnt, &mdebris.pos); if (pnt.codes == 0) { int frame = Missiontime / (DEBRIS_ROT_MIN + (1 % DEBRIS_ROT_RANGE) * DEBRIS_ROT_RANGE_SCALER); frame %= Motion_debris_ptr[mdebris.vclip].nframes; float alpha; if ( (The_mission.flags[Mission::Mission_Flags::Fullneb]) && (Neb2_render_mode != NEB2_RENDER_NONE) ) { alpha = 0.3f; } else { alpha = 1.0f; } // scale alpha from 0 at max range to full at 60% range alpha *= (vdist - MAX_DIST_RANGE) / -(MAX_DIST_RANGE * 0.6f); g3_transfer_vertex(&pnt, &mdebris.pos); batching_add_bitmap(Motion_debris_ptr[mdebris.vclip].bm + frame, &pnt, 0, mdebris.size, alpha); } } if (refresh_motion_debris) refresh_motion_debris = false; } void stars_draw(int show_stars, int show_suns, int /*show_nebulas*/, int show_subspace, int env, bool in_mission) { GR_DEBUG_SCOPE("Draw Stars"); TRACE_SCOPE(tracing::DrawStars); if (gr_screen.mode == GraphicsAPI::Stub) { return; } int gr_zbuffering_save = gr_zbuffer_get(); gr_zbuffer_set(GR_ZBUFF_NONE); Rendering_to_env = env; if (show_subspace) subspace_render(); if (Num_stars >= MAX_STARS) Num_stars = MAX_STARS; #ifdef TIME_STAR_CODE fix xt1, xt2; xt1 = timer_get_fixed_seconds(); #endif // draw background stuff if ( show_stars ) { // semi-hack, do we don't fog the background int neb_save = Neb2_render_mode; Neb2_render_mode = NEB2_RENDER_NONE; stars_draw_background(); Neb2_render_mode = neb_save; } else if ( !show_subspace ) // don't render the background pof when rendering subspace { stars_draw_background(); } if ( !env && show_stars && (Nmodel_num < 0) && (Game_detail_flags & DETAIL_FLAG_STARS) && !(The_mission.flags[Mission::Mission_Flags::Fullneb]) && (supernova_stage() < SUPERNOVA_STAGE::TOOLTIME) ) { stars_draw_stars(); } last_stars_filled = 1; #ifdef TIME_STAR_CODE xt2 = timer_get_fixed_seconds(); mprintf(( "Stars: %d\n", xt2-xt1 )); #endif if ( !Rendering_to_env && (Game_detail_flags & DETAIL_FLAG_MOTION) && (!Fred_running) && (supernova_stage() < SUPERNOVA_STAGE::TOOLTIME) && in_mission) { stars_draw_motion_debris(); } //if we're not drawing them, quit here if (show_suns) { stars_draw_sun( show_suns ); stars_draw_bitmaps( show_suns ); } gr_zbuffer_set( gr_zbuffering_save ); Rendering_to_env = 0; } void stars_preload_background(const char *token) { if (!token) return; // we can only preload raw numbers, but that's probably sufficient if (!can_construe_as_integer(token)) return; int background_idx = atoi(token); // human/computer offset background_idx--; // store it for now Preload_background_indexes.push_back(background_idx); } void stars_preload_background(int background_idx) { // range check if (!SCP_vector_inbounds(Backgrounds, background_idx)) return; // preload all the stuff for this background for (auto &bgsun : Backgrounds[background_idx].suns) stars_preload_sun_bitmap(bgsun.filename); for (auto &bitmap : Backgrounds[background_idx].bitmaps) stars_preload_background_bitmap(bitmap.filename); } void stars_preload_sun_bitmap(const char *fname) { int idx; if (fname == NULL) return; idx = stars_find_sun(fname); if (idx == -1) { return; } Sun_bitmaps[idx].preload = 1; } void stars_preload_background_bitmap(const char *fname) { int idx; if (fname == NULL) return; idx = stars_find_bitmap(fname); if (idx == -1) { return; } Starfield_bitmaps[idx].preload = 1; } void stars_page_in() { int idx, i; if (gr_screen.mode == GraphicsAPI::Stub) { return; } // Initialize the subspace stuff if (Game_subspace_effect || (The_mission.flags[Mission::Mission_Flags::Preload_subspace])) { Subspace_model_inner = model_load("subspace_small.pof"); Assert(Subspace_model_inner >= 0); Subspace_model_outer = model_load("subspace_big.pof"); Assert(Subspace_model_outer >= 0); polymodel *pm; pm = model_get(Subspace_model_inner); nprintf(( "Paging", "Paging in textures for inner subspace effect.\n" )); for (idx = 0; idx < pm->n_textures; idx++) { pm->maps[idx].PageIn(); } pm = model_get(Subspace_model_outer); nprintf(( "Paging", "Paging in textures for outer subspace effect.\n" )); for (idx = 0; idx < pm->n_textures; idx++) { pm->maps[idx].PageIn(); } if (Subspace_glow_bitmap < 0) { Subspace_glow_bitmap = bm_load( NOX("SunGlow01")); } bm_page_in_xparent_texture(Subspace_glow_bitmap); } else { Subspace_model_inner = -1; Subspace_model_outer = -1; if (Subspace_glow_bitmap > 0) { bm_release(Subspace_glow_bitmap); Subspace_glow_bitmap = -1; } } // extra SEXP related checks to preload anything that might get used from there for (auto &sb : Starfield_bitmaps) { if (sb.used_this_level) continue; if (sb.preload) { if (sb.bitmap_id < 0) { sb.bitmap_id = bm_load(sb.filename); // maybe didn't load a static image so try for an animated one if (sb.bitmap_id < 0) { sb.bitmap_id = bm_load_animation(sb.filename, &sb.n_frames, &sb.fps, nullptr, nullptr, true); if (sb.bitmap_id < 0) { Warning(LOCATION, "Unable to load starfield bitmap: '%s'!\n", sb.filename); } } } // this happens whether it loaded properly or not, no harm should come from it if (sb.xparent) { bm_page_in_xparent_texture(sb.bitmap_id); } else { bm_page_in_texture(sb.bitmap_id); } sb.used_this_level++; } } for (auto &sb : Sun_bitmaps) { if (sb.used_this_level) continue; if (sb.preload) { // normal bitmap if (sb.bitmap_id < 0) { sb.bitmap_id = bm_load(sb.filename); // maybe didn't load a static image so try for an animated one if (sb.bitmap_id < 0) { sb.bitmap_id = bm_load_animation(sb.filename, &sb.n_frames, &sb.fps, nullptr, nullptr, true); if (sb.bitmap_id < 0) { Warning(LOCATION, "Unable to load sun bitmap: '%s'!\n", sb.filename); } } } // glow bitmap if (sb.glow_bitmap < 0) { sb.glow_bitmap = bm_load(sb.glow_filename); // maybe didn't load a static image so try for an animated one if (sb.glow_bitmap < 0) { sb.glow_bitmap = bm_load_animation(sb.glow_filename, &sb.glow_n_frames, &sb.glow_fps, nullptr, nullptr, true); if (sb.glow_bitmap < 0) { Warning(LOCATION, "Unable to load sun glow bitmap: '%s'!\n", sb.glow_filename); } } } if (sb.flare) { for (i = 0; i < MAX_FLARE_BMP; i++) { if ( !strlen(sb.flare_bitmaps[i].filename) ) continue; if (sb.flare_bitmaps[i].bitmap_id < 0) { sb.flare_bitmaps[i].bitmap_id = bm_load(sb.flare_bitmaps[i].filename); if (sb.flare_bitmaps[i].bitmap_id < 0) { Warning(LOCATION, "Unable to load sun flare bitmap: '%s'!\n", sb.flare_bitmaps[i].filename); continue; } } bm_page_in_texture(sb.flare_bitmaps[i].bitmap_id); } } bm_page_in_texture(sb.bitmap_id); bm_page_in_texture(sb.glow_bitmap); sb.used_this_level++; } } // load and page in needed starfield bitmaps for (auto &sbi : Starfield_bitmap_instances) { if (sbi.star_bitmap_index < 0) continue; auto &sb = Starfield_bitmaps[sbi.star_bitmap_index]; if (sb.used_this_level) continue; if (sb.bitmap_id < 0 ) { sb.bitmap_id = bm_load(sb.filename); // maybe didn't load a static image so try for an animated one if (sb.bitmap_id < 0) { sb.bitmap_id = bm_load_animation(sb.filename, &sb.n_frames, &sb.fps, nullptr, nullptr, true); if (sb.bitmap_id < 0) { Warning(LOCATION, "Unable to load starfield bitmap: '%s'!\n", sb.filename); } } } // this happens whether it loaded properly or not, no harm should come from it if (sb.xparent) { bm_page_in_xparent_texture(sb.bitmap_id); } else { bm_page_in_texture(sb.bitmap_id); } sb.used_this_level++; } // now for sun bitmaps and glows for (auto &sbi : Suns) { if (sbi.star_bitmap_index < 0) continue; auto &sb = Sun_bitmaps[sbi.star_bitmap_index]; if (sb.used_this_level) continue; // normal bitmap if (sb.bitmap_id < 0) { sb.bitmap_id = bm_load(sb.filename); // maybe didn't load a static image so try for an animated one if (sb.bitmap_id < 0) { sb.bitmap_id = bm_load_animation(sb.filename, &sb.n_frames, &sb.fps, nullptr, nullptr, true); if (sb.bitmap_id < 0) { Warning(LOCATION, "Unable to load sun bitmap: '%s'!\n", sb.filename); } } } // glow bitmap if (sb.glow_bitmap < 0) { sb.glow_bitmap = bm_load(sb.glow_filename); // maybe didn't load a static image so try for an animated one if (sb.glow_bitmap < 0) { sb.glow_bitmap = bm_load_animation(sb.glow_filename, &sb.glow_n_frames, &sb.glow_fps, nullptr, nullptr, true); if (sb.glow_bitmap < 0) { Warning(LOCATION, "Unable to load sun glow bitmap: '%s'!\n", sb.glow_filename); } } } if (sb.flare) { for (i = 0; i < MAX_FLARE_BMP; i++) { if ( !strlen(sb.flare_bitmaps[i].filename) ) continue; if (sb.flare_bitmaps[i].bitmap_id < 0) { sb.flare_bitmaps[i].bitmap_id = bm_load(sb.flare_bitmaps[i].filename); if (sb.flare_bitmaps[i].bitmap_id < 0) { Warning(LOCATION, "Unable to load sun flare bitmap: '%s'!\n", sb.flare_bitmaps[i].filename); continue; } } bm_page_in_texture(sb.flare_bitmaps[i].bitmap_id); } } bm_page_in_texture(sb.bitmap_id); bm_page_in_texture(sb.glow_bitmap); sb.used_this_level++; } if (!Motion_debris_enabled || Motion_debris_override) return; for (idx = 0; idx < MAX_MOTION_DEBRIS_BITMAPS; idx++) { bm_page_in_xparent_texture(Motion_debris_ptr[idx].bm, Motion_debris_ptr[idx].nframes); } } // background nebula models and planets void stars_draw_background() { GR_DEBUG_SCOPE("Draw Background"); TRACE_SCOPE(tracing::DrawBackground); if (gr_screen.mode == GraphicsAPI::Stub) { return; } // detail settings if (!Detail.planets_suns) return; if (Nmodel_num < 0) return; model_render_params render_info; if (Nmodel_bitmap >= 0) { render_info.set_forced_bitmap(Nmodel_bitmap); } // draw the model at the player's eye with no z-buffering if (Nmodel_alpha < 1.0f) render_info.set_alpha_mult(Nmodel_alpha); render_info.set_flags(Nmodel_flags | MR_SKYBOX); if (Nmodel_instance_num >= 0) render_info.set_replacement_textures(model_get_instance(Nmodel_instance_num)->texture_replace); // if No Z-Buffer is on in FRED then check mod flag to see // if skybox submodels should still have proper z-sorting // wookieejedi bool special_z_buff = ((Nmodel_flags & MR_NO_ZBUFFER) && Skybox_internal_depth_consistency); if (special_z_buff) { render_info.set_flags(Nmodel_flags & ~MR_NO_ZBUFFER); } model_render_immediate(&render_info, Nmodel_num, Nmodel_instance_num, &Nmodel_orient, &Eye_position, MODEL_RENDER_ALL, false); if (special_z_buff) { gr_zbuffer_clear(TRUE); } } void stars_set_background_model(int new_model, int new_bitmap, uint64_t flags, float alpha) { if (gr_screen.mode == GraphicsAPI::Stub) { return; } CLAMP(alpha, 0.0f, 1.0f); // see if we are actually changing anything if (Nmodel_num == new_model && Nmodel_bitmap == new_bitmap && Nmodel_flags == flags && Nmodel_alpha == alpha) { return; } if (Nmodel_bitmap >= 0) { bm_unload(Nmodel_bitmap); Nmodel_bitmap = -1; } if (Nmodel_num >= 0) { model_unload(Nmodel_num); Nmodel_num = -1; } if (Nmodel_instance_num >= 0) { model_delete_instance(Nmodel_instance_num); Nmodel_instance_num = -1; } Nmodel_flags = flags; Nmodel_num = new_model; Nmodel_bitmap = new_bitmap; Nmodel_alpha = alpha; if (Nmodel_num >= 0) { model_page_in_textures(Nmodel_num); Nmodel_instance_num = model_create_instance(model_objnum_special::OBJNUM_NONE, Nmodel_num); The_mission.skybox_model_animations.initializeMoveables(model_get_instance(Nmodel_instance_num)); } // Since we have a new skybox we need to rerender the environment map stars_invalidate_environment_map(); } // call this to set a specific model as the background model void stars_set_background_model(const char* model_name, const char* texture_name, uint64_t flags, float alpha) { int new_model = -1; int new_bitmap = -1; if (gr_screen.mode == GraphicsAPI::Stub) { return; } if (model_name != nullptr && *model_name != '\0' && stricmp(model_name, "none") != 0) { new_model = model_load(model_name, nullptr, ErrorType::NONE); if (texture_name != nullptr && *texture_name != '\0') { new_bitmap = bm_load(texture_name); } } stars_set_background_model(new_model, new_bitmap, flags, alpha); } // call this to set a specific orientation for the background void stars_set_background_orientation(const matrix *orient) { if (orient == NULL) { vm_set_identity(&Nmodel_orient); } else { Nmodel_orient = *orient; } } void stars_set_background_alpha(float alpha) { CLAMP(alpha, 0.0f, 1.0f); Nmodel_alpha = alpha; } // lookup a starfield bitmap, return index or -1 on fail int stars_find_bitmap(const char *name) { if (name == nullptr) return -1; // lookup for (size_t idx = 0; idx < Starfield_bitmaps.size(); ++idx) { if ( !stricmp(name, Starfield_bitmaps[idx].filename) ) { return static_cast(idx); } } // not found return -1; } // The mission-side list entry and the in-world instance carry the same fields, so keeping the // copy in one place is what stops the three call sites drifting apart. angular_size is only // meaningful for suns, but copying it for bitmaps too is harmless and keeps every caller // identical -- which beats being selectively correct in a way no reader can verify. static void starfield_copy_entry_to_instance(const starfield_list_entry *sle, starfield_bitmap_instance &sbi) { sbi.ang = sle->ang; sbi.scale_x = sle->scale_x; sbi.scale_y = sle->scale_y; sbi.div_x = sle->div_x; sbi.div_y = sle->div_y; sbi.angular_size = sle->angular_size; } // lookup a sun by bitmap filename, return index or -1 on fail int stars_find_sun(const char *name) { if (name == nullptr) return -1; // lookup for (size_t idx = 0; idx < Sun_bitmaps.size(); ++idx) { if ( !stricmp(name, Sun_bitmaps[idx].filename) ) { return static_cast(idx); } } // not found return -1; } void stars_get_data(bool is_sun, int idx, starfield_list_entry& sle) { const auto& collection = is_sun ? Suns : Starfield_bitmap_instances; if (!SCP_vector_inbounds(collection, idx)) return; const auto& item = collection[idx]; sle.filename[0] = '\0'; sle.ang = item.ang; sle.div_x = item.div_x; sle.div_y = item.div_y; sle.scale_x = item.scale_x; sle.scale_y = item.scale_y; sle.angular_size = item.angular_size; } void stars_set_data(bool is_sun, int idx, starfield_list_entry& sle) { auto& collection = is_sun ? Suns : Starfield_bitmap_instances; if (!SCP_vector_inbounds(collection, idx)) return; auto& item = collection[idx]; item.ang = sle.ang; item.div_x = sle.div_x; item.div_y = sle.div_y; item.scale_x = sle.scale_x; item.scale_y = sle.scale_y; item.angular_size = sle.angular_size; // this is necessary when modifying bitmaps, but not when modifying suns if (!is_sun) starfield_create_bitmap_buffer(static_cast(idx)); } // add an instance for a sun (something actually used in a mission) // NOTE that we assume a duplicate is ok here int stars_add_sun_entry(starfield_list_entry *sun_ptr) { starfield_bitmap_instance sbi; Assert(sun_ptr != NULL); // copy information starfield_copy_entry_to_instance(sun_ptr, sbi); int idx = stars_find_sun(sun_ptr->filename); if (idx == -1) { if (!Fred_running) { Warning(LOCATION, "Trying to add a sun '%s' that does not exist in stars.tbl!", sun_ptr->filename); } return -1; } sbi.star_bitmap_index = idx; // make sure any needed bitmaps are loaded if (Sun_bitmaps[idx].bitmap_id < 0) { // normal bitmap Sun_bitmaps[idx].bitmap_id = bm_load(Sun_bitmaps[idx].filename); // maybe didn't load a static image so try for an animated one if (Sun_bitmaps[idx].bitmap_id < 0) { Sun_bitmaps[idx].bitmap_id = bm_load_animation(Sun_bitmaps[idx].filename, &Sun_bitmaps[idx].n_frames, &Sun_bitmaps[idx].fps, nullptr, nullptr, true); if (Sun_bitmaps[idx].bitmap_id < 0) { // failed return -1; } } // glow bitmap if (Sun_bitmaps[idx].glow_bitmap < 0) { Sun_bitmaps[idx].glow_bitmap = bm_load(Sun_bitmaps[idx].glow_filename); // maybe didn't load a static image so try for an animated one if (Sun_bitmaps[idx].glow_bitmap < 0) { Sun_bitmaps[idx].glow_bitmap = bm_load_animation(Sun_bitmaps[idx].glow_filename, &Sun_bitmaps[idx].glow_n_frames, &Sun_bitmaps[idx].glow_fps, nullptr, nullptr, true); if (Sun_bitmaps[idx].glow_bitmap < 0) { Warning(LOCATION, "Unable to load sun glow bitmap: '%s'!\n", Sun_bitmaps[idx].glow_filename); } } } if (Sun_bitmaps[idx].flare) { for (int i = 0; i < MAX_FLARE_BMP; i++) { flare_bitmap* fbp = &Sun_bitmaps[idx].flare_bitmaps[i]; if ( !strlen(fbp->filename) ) continue; if (fbp->bitmap_id < 0) { fbp->bitmap_id = bm_load(fbp->filename); if (fbp->bitmap_id < 0) { Warning(LOCATION, "Unable to load sun flare bitmap: '%s'!\n", Sun_bitmaps[idx].flare_bitmaps[i].filename); continue; } } } } } // The background changed so we need to invalidate the environment map stars_invalidate_environment_map(); // now check if we can make use of a previously discarded instance entry // this should never happen with FRED if ( !Fred_running ) { for (size_t i = 0; i < Suns.size(); ++i) { if ( Suns[i].star_bitmap_index < 0 ) { Suns[i] = sbi; return static_cast(i); } } } // ... or add a new one Suns.push_back(sbi); return static_cast(Suns.size() - 1); } // add an instance for a starfield bitmap (something actually used in a mission) // NOTE that we assume a duplicate is ok here int stars_add_bitmap_entry(starfield_list_entry *sle) { int idx; starfield_bitmap_instance sbi; Assert(sle != NULL); // copy information starfield_copy_entry_to_instance(sle, sbi); idx = stars_find_bitmap(sle->filename); if (idx == -1) { if (!Fred_running) { Warning(LOCATION, "Trying to add a bitmap '%s' that does not exist in stars.tbl!", sle->filename); } return -1; } sbi.star_bitmap_index = idx; // make sure any needed bitmaps are loaded if (Starfield_bitmaps[idx].bitmap_id < 0) { Starfield_bitmaps[idx].bitmap_id = bm_load(Starfield_bitmaps[idx].filename); // maybe didn't load a static image so try for an animated one if (Starfield_bitmaps[idx].bitmap_id < 0) { Starfield_bitmaps[idx].bitmap_id = bm_load_animation(Starfield_bitmaps[idx].filename, &Starfield_bitmaps[idx].n_frames, &Starfield_bitmaps[idx].fps, nullptr, nullptr, true); if (Starfield_bitmaps[idx].bitmap_id < 0) { // failed return -1; } } } // The background changed so we need to invalidate the environment map stars_invalidate_environment_map(); // now check if we can make use of a previously discarded instance entry for (size_t i = 0; i < Starfield_bitmap_instances.size(); ++i) { if ( Starfield_bitmap_instances[i].star_bitmap_index < 0 ) { Starfield_bitmap_instances[i] = sbi; starfield_create_bitmap_buffer(i); return static_cast(i); } } // ... or add a new one Starfield_bitmap_instances.push_back(sbi); starfield_create_bitmap_buffer(Starfield_bitmap_instances.size() - 1); return static_cast(Starfield_bitmap_instances.size() - 1); } void stars_correct_background_sun_angles(angles* angs_to_correct) { matrix mat; vm_angles_2_matrix(&mat, angs_to_correct); vm_transpose(&mat); vm_extract_angles_matrix(angs_to_correct, &mat); angs_to_correct->p = fmod(angs_to_correct->p + PI2, PI2); angs_to_correct->b = fmod(angs_to_correct->b + PI2, PI2); angs_to_correct->h = fmod(angs_to_correct->h + PI2, PI2); } void stars_uncorrect_background_sun_angles(angles* angs_to_uncorrect) { // the actual operation is an inversion so 'correcting' and 'uncorrecting' are the same stars_correct_background_sun_angles(angs_to_uncorrect); } void stars_correct_background_bitmap_angles(angles *angs_to_correct) { matrix mat1, mat2; angles ang1 = vm_angles_new(0.0, angs_to_correct->b, 0.0); angles ang2 = vm_angles_new(angs_to_correct->p, 0.0, angs_to_correct->h); vm_angles_2_matrix(&mat1, &ang1); vm_angles_2_matrix(&mat2, &ang2); matrix mat3 = mat2 * mat1; vm_transpose(&mat3); vm_extract_angles_matrix(angs_to_correct, &mat3); angs_to_correct->p = fmod(angs_to_correct->p + PI2, PI2); angs_to_correct->b = fmod(angs_to_correct->b + PI2, PI2); angs_to_correct->h = fmod(angs_to_correct->h + PI2, PI2); } void stars_uncorrect_background_bitmap_angles(angles *angs_to_uncorrect) { matrix mat; vm_angles_2_matrix(&mat, angs_to_uncorrect); // transpose and {2,3,1} permute rows and cols matrix mat_permuted = vm_matrix_new( mat.vec.uvec.xyz.y, mat.vec.fvec.xyz.y, mat.vec.rvec.xyz.y, mat.vec.uvec.xyz.z, mat.vec.fvec.xyz.z, mat.vec.rvec.xyz.z, mat.vec.uvec.xyz.x, mat.vec.fvec.xyz.x, mat.vec.rvec.xyz.x ); angles angs_permuted; vm_extract_angles_matrix(&angs_permuted, &mat_permuted); // {2,3,1} unpermute p,b,h angs_to_uncorrect->p = angs_permuted.b; angs_to_uncorrect->h = angs_permuted.p; angs_to_uncorrect->b = angs_permuted.h; } // get the number of entries that each vector contains // "is_a_sun" will get sun instance counts, otherwise it gets normal starfield bitmap instance counts // "bitmap_count" will get number of starfield_bitmap entries rather than starfield_bitmap_instance entries int stars_get_num_entries(bool is_a_sun, bool bitmap_count) { // try for instance counts first if (!bitmap_count) { if (is_a_sun) { return static_cast(Suns.size()); } else { return static_cast(Starfield_bitmap_instances.size()); } } // looks like we want bitmap counts (probably only FRED uses this) else { if (is_a_sun) { return static_cast(Sun_bitmaps.size()); } else { return static_cast(Starfield_bitmaps.size()); } } } // get a starfield_bitmap entry providing only an instance starfield_bitmap *stars_get_bitmap_entry(int index, bool is_a_sun) { int max_index = static_cast(is_a_sun ? Suns.size() : Starfield_bitmap_instances.size()); //WMC - Commented out because it keeps happening, and I don't know what this means. //Assert( (index >= 0) && (index < max_index) ); if ( (index < 0) || (index >= max_index) ) return NULL; if (is_a_sun && (Suns[index].star_bitmap_index >= 0)) { return &Sun_bitmaps[Suns[index].star_bitmap_index]; } else if (!is_a_sun && (Starfield_bitmap_instances[index].star_bitmap_index >= 0)) { return &Starfield_bitmaps[Starfield_bitmap_instances[index].star_bitmap_index]; } return NULL; } // set an instace to not render void stars_mark_instance_unused(int index, bool is_a_sun) { int max_index = static_cast(is_a_sun ? Suns.size() : Starfield_bitmap_instances.size()); Assert( (index >= 0) && (index < max_index) ); if ( (index < 0) || (index >= max_index) ) return; if (is_a_sun) { Suns[index].star_bitmap_index = -1; } else { Starfield_bitmap_instances[index].star_bitmap_index = -1; } if ( !is_a_sun ) { delete [] Starfield_bitmap_instances[index].verts; Starfield_bitmap_instances[index].verts = NULL; } // The background changed so we need to invalidate the environment map stars_invalidate_environment_map(); } // retrieves the name from starfield_bitmap for the instance index // NOTE: it's unsafe to return NULL here so use for invalid entries const char *stars_get_name_from_instance(int index, bool is_a_sun) { int max_index = static_cast(is_a_sun ? Suns.size() : Starfield_bitmap_instances.size()); Assert( (index >= 0) && (index < max_index) ); if ( (index < 0) || (index >= max_index) ) return NOX(""); if (is_a_sun && (Suns[index].star_bitmap_index >= 0)) { return Sun_bitmaps[Suns[index].star_bitmap_index].filename; } else if (!is_a_sun && (Starfield_bitmap_instances[index].star_bitmap_index >= 0)) { return Starfield_bitmaps[Starfield_bitmap_instances[index].star_bitmap_index].filename; } return NOX(""); } // WMC/Goober5000 void stars_set_nebula(bool activate, float range) { The_mission.flags.set(Mission::Mission_Flags::Fullneb, activate); if (activate) { Toggle_text_alpha = TOGGLE_TEXT_NEBULA_ALPHA; HUD_high_contrast = true; Neb2_render_mode = NEB2_RENDER_HTL; Neb2_awacs = range; if (Neb2_awacs < 0.1f) Neb2_awacs = 3000.0f; // this is also the default in the background editor // this function is not currently called in FRED, but this would be needed for FRED support if (Fred_running) { Neb2_render_mode = NEB2_RENDER_POF; stars_set_background_model(BACKGROUND_MODEL_FILENAME, Neb2_texture_name); stars_set_background_orientation(); } } else { Toggle_text_alpha = TOGGLE_TEXT_NORMAL_ALPHA; HUD_high_contrast = false; Neb2_render_mode = NEB2_RENDER_NONE; Neb2_awacs = -1.0f; } // (DahBlount) // This needs to be done regardless of whether we're in nebula or not // Should ensure that we're actually loading the needed anims into BMPMan stars_load_debris(static_cast(activate)); // We need to reload the environment map now stars_invalidate_environment_map(); } // retrieves the name from starfield_bitmap, really only used by FRED2 // NOTE: it is unsafe to return NULL here, but because that's bad anyway it really shouldn't happen, so we do return NULL. const char *stars_get_name_FRED(int index, bool is_a_sun) { if (!Fred_running) return NULL; int max_index = static_cast(is_a_sun ? Sun_bitmaps.size() : Starfield_bitmaps.size()); Assert( (index >= 0) && (index < max_index) ); if ( (index < 0) || (index >= max_index) ) return NULL; if (is_a_sun) { return Sun_bitmaps[index].filename; } else { return Starfield_bitmaps[index].filename; } } // modify an existing starfield bitmap instance, or add a new one if needed void stars_modify_entry_FRED(int index, const char *name, starfield_list_entry *sbi_new, bool is_a_sun) { if (!Fred_running) return; starfield_bitmap_instance sbi; int idx; int add_new = index > static_cast(is_a_sun ? Sun_bitmaps.size() : Starfield_bitmaps.size()); Assert( index >= 0 ); Assert( sbi_new != NULL ); // copy information starfield_copy_entry_to_instance(sbi_new, sbi); if (is_a_sun) { idx = stars_find_sun((char*)name); } else { idx = stars_find_bitmap((char*)name); } // this shouldn't ever happen from FRED since you select the name from a list of those available if (idx == -1) return; sbi.star_bitmap_index = idx; if (add_new) { if (is_a_sun) { Suns.push_back( sbi ); } else { Starfield_bitmap_instances.push_back( sbi ); } } else { if (is_a_sun) { Suns[index] = sbi; } else { Starfield_bitmap_instances[index] = sbi; } } if ( !is_a_sun ) { starfield_create_bitmap_buffer(static_cast(index)); } } // erase an instance, note that this is very slow so it should only be done in FRED void stars_delete_entry_FRED(int index, bool is_a_sun) { if (!Fred_running) return; int max_index = static_cast(is_a_sun ? Suns.size() : Starfield_bitmap_instances.size()); Assert( (index >= 0) && (index < max_index) ); if ( (index < 0) || (index >= max_index) ) return; if (is_a_sun) { Suns.erase( Suns.begin() + index ); } else { Starfield_bitmap_instances.erase( Starfield_bitmap_instances.begin() + index ); } } // Goober5000 void stars_add_blank_background(bool creating_in_fred) { Backgrounds.emplace_back(); // any background that is created will have correct angles, so be sure to set the flag if (creating_in_fred) Backgrounds.back().flags.set(Starfield::Background_Flags::Corrected_angles_in_mission_file); } // Goober5000 void stars_load_first_valid_background() { int background_idx = stars_get_first_valid_background(); stars_load_background(background_idx); } // Goober5000 int stars_get_first_valid_background() { size_t i, j; if (Backgrounds.empty()) return -1; // scan every background except the last and return the first one that has all its suns and bitmaps present for (i = 0; i < Backgrounds.size() - 1; i++) { bool valid = true; background_t *background = &Backgrounds[i]; for (j = 0; j < background->suns.size(); j++) { if (stars_find_sun(background->suns[j].filename) < 0) { mprintf(("Failed to load sun %s for background " SIZE_T_ARG ", falling back to background " SIZE_T_ARG "\n", background->suns[j].filename, i + 1, i + 2)); valid = false; break; } } if (valid) { for (j = 0; j < background->bitmaps.size(); j++) { if (stars_find_bitmap(background->bitmaps[j].filename) < 0) { mprintf(("Failed to load bitmap %s for background " SIZE_T_ARG ", falling back to background " SIZE_T_ARG "\n", background->bitmaps[j].filename, i + 1, i + 2)); valid = false; break; } } } if (valid) return static_cast(i); } // didn't find a valid background yet, so return the last one return static_cast(Backgrounds.size() - 1); } // Goober5000 void stars_load_background(int background_idx) { size_t j; stars_clear_instances(); Cur_background = background_idx; if (Cur_background >= 0) { background_t *background = &Backgrounds[Cur_background]; int failed_suns = 0; for (j = 0; j < background->suns.size(); j++) { if ((stars_add_sun_entry(&background->suns[j]) < 0) && !Fred_running) { nprintf(("General", "Failed to add sun '%s' to the mission!", background->suns[j].filename)); failed_suns++; } } if (failed_suns > 0) Warning(LOCATION, "Failed to add %d sun bitmaps to the mission!", failed_suns); int failed_stars = 0; for (j = 0; j < background->bitmaps.size(); j++) { if ((stars_add_bitmap_entry(&background->bitmaps[j]) < 0) && !Fred_running) { nprintf(("General", "Failed to add starfield bitmap '%s' to the mission!", background->bitmaps[j].filename)); failed_stars++; } } if (failed_stars > 0) Warning(LOCATION, "Failed to add %d starfield bitmaps to the mission!", failed_stars); } } // Goober5000 void stars_copy_background(background_t *dest, background_t *src) { dest->flags = src->flags; dest->suns.assign(src->suns.begin(), src->suns.end()); dest->bitmaps.assign(src->bitmaps.begin(), src->bitmaps.end()); } // Goober5000 void stars_swap_backgrounds(int idx1, int idx2) { background_t temp; stars_copy_background(&temp, &Backgrounds[idx1]); stars_copy_background(&Backgrounds[idx1], &Backgrounds[idx2]); stars_copy_background(&Backgrounds[idx2], &temp); } // Goober5000 bool stars_background_empty(const background_t &bg) { return (bg.suns.empty() && bg.bitmaps.empty()); } // Goober5000 void stars_pack_backgrounds() { size_t remove_count = 0; // remove all empty backgrounds, with a caveat: // in FRED, make sure we always have at least one background // (note: older code removed all blank backgrounds and re-added one if necessary; but that method caused flag changes to be lost) Backgrounds.erase( std::remove_if(Backgrounds.begin(), Backgrounds.end(), [&](const background_t& bg) { if (stars_background_empty(bg)) { if (Fred_running && ++remove_count == Backgrounds.size()) return false; // cancel the last removal if FRED is running and if the last removal would result in all backgrounds being removed (i.e. all were blank) return true; } return false; }), Backgrounds.end()); } static void render_environment(int i, vec3d *eye_pos, matrix *new_orient, fov_t new_zoom) { bm_set_render_target(gr_screen.envmap_render_target, i); gr_clear(); g3_set_view_matrix( eye_pos, new_orient, new_zoom ); gr_set_proj_matrix( new_zoom * PI_2, 1.0f, Min_draw_distance, Max_draw_distance); gr_set_view_matrix( &Eye_position, &Eye_matrix ); if ( Game_subspace_effect ) { stars_draw(0, 0, 0, 1, 1); } else { stars_draw(0, 1, 1, 0, 1); } gr_end_view_matrix(); gr_end_proj_matrix(); } void stars_setup_environment_mapping(camid cid) { matrix new_orient = IDENTITY_MATRIX; extern fov_t View_zoom; fov_t old_zoom = View_zoom, new_zoom = 1.0f;//0.925f; if (gr_screen.mode == GraphicsAPI::Stub) { return; } if(!cid.isValid()) return; vec3d cam_pos; matrix cam_orient; cid.getCamera()->get_info(&cam_pos, &cam_orient); bool renderEnv = true; // prefer the mission specified envmap over the static-generated envmap, but // the dynamic envmap should always get preference if in a subspace mission if (!Dynamic_environment && Mission_env_map >= 0) { ENVMAP = Mission_env_map; renderEnv = false; } else if (gr_screen.envmap_render_target < 0) { if (ENVMAP >= 0) { renderEnv = false; } else if (Mission_env_map >= 0) { ENVMAP = Mission_env_map; } else { ENVMAP = Default_env_map; } renderEnv = false; } else if (Env_cubemap_drawn) { // Nothing to do here anymore renderEnv = false; } /* * Envmap matrix setup -- left-handed * ------------------------------------------------- * Face -- Forward Up Right * px +X +Y -Z * nx -X +Y +Z * py +Y -Z +X * ny -Y +Z +X * pz +Z +Y +X * nz -Z +Y -X */ // NOTE: OpenGL needs up/down reversed // Save the previous render target so we can reset it once we are done here auto previous_target = gr_screen.rendering_to_texture; // Encode above table into directions and values. // 0 = X, 1 = Y, 2 = Z int f_dir[6] = {0, 0, 1, 1, 2, 2}; int u_dir[6] = {1, 1, 2, 2, 1, 1}; int r_dir[6] = {2, 2, 0, 0, 0, 0}; float f_val[6] = {1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f}; float u_val[6] = {1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f}; float r_val[6] = {-1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f}; if (renderEnv) { GR_DEBUG_SCOPE("Environment Mapping"); TRACE_SCOPE(tracing::EnvironmentMapping); ENVMAP = gr_screen.envmap_render_target; for (int i = 0; i < 6; i++) { memset(&new_orient, 0, sizeof(matrix)); new_orient.vec.fvec.a1d[f_dir[i]] = f_val[i]; new_orient.vec.uvec.a1d[u_dir[i]] = u_val[i]; new_orient.vec.rvec.a1d[r_dir[i]] = r_val[i]; render_environment(i, &cam_pos, &new_orient, new_zoom); } // we're done, so now reset bm_set_render_target(previous_target); g3_set_view_matrix(&cam_pos, &cam_orient, old_zoom); } // Draw irr map if we've just updated the envmap // or otherwise invalidated the irradiance map (i.e. custom/default envmap) if ((!Irr_cubemap_drawn || renderEnv) && (ENVMAP >= 0)) { // Generate irradiance map. if (gr_screen.irrmap_render_target < 0) { irradiance_map_gen(); IRRMAP = gr_screen.irrmap_render_target; } gr_screen.gf_calculate_irrmap(); } if ( !Dynamic_environment ) { Env_cubemap_drawn = true; Irr_cubemap_drawn = true; } } void stars_set_dynamic_environment(bool dynamic) { Dynamic_environment = dynamic; stars_invalidate_environment_map(); } void stars_invalidate_environment_map() { // This will cause a redraw in the next frame Env_cubemap_drawn = false; Irr_cubemap_drawn = false; }