1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell
5  * or otherwise commercially exploit the source or things you created based on the
6  * source.
7  *
8 */
9 
10 
11 
12 #include <climits>
13 
14 #include "freespace.h"
15 #include "cmdline/cmdline.h"
16 #include "debugconsole/console.h"
17 #include "graphics/matrix.h"
18 #include "graphics/paths/PathRenderer.h"
19 #include "hud/hud.h"
20 #include "hud/hudtarget.h"
21 #include "io/timer.h"
22 #include "lighting/lighting.h"
23 #include "math/vecmat.h"
24 #include "mission/missionparse.h"
25 #include "model/modelrender.h"
26 #include "nebula/neb.h"
27 #include "options/Option.h"
28 #include "parse/parselo.h"
29 #include "render/3d.h"
30 #include "render/batching.h"
31 #include "starfield/nebula.h"
32 #include "starfield/starfield.h"
33 #include "starfield/supernova.h"
34 #include "tracing/tracing.h"
35 #include "utils/Random.h"
36 
37 #define MAX_DEBRIS_VCLIPS			4
38 #define DEBRIS_ROT_MIN				10000
39 #define DEBRIS_ROT_RANGE			8
40 #define DEBRIS_ROT_RANGE_SCALER		10000
41 #define RND_MAX_MASK				0x3fff
42 #define HALF_RND_MAX				0x2000
43 
44 typedef struct {
45 	vec3d pos;
46 	int vclip;
47 	float size;
48 } motion_debris;
49 
50 const int MAX_DEBRIS = 300;
51 const int MAX_STARS = 2000;
52 const float MAX_DIST_RANGE = 80.0f;
53 const float MIN_DIST_RANGE = 14.0f;
54 const float BASE_SIZE = 0.04f;
55 const float BASE_SIZE_NEB = 0.15f;
56 
57 static int Subspace_model_inner = -1;
58 static int Subspace_model_outer = -1;
59 static int Rendering_to_env = 0;
60 
61 int Num_stars = 500;
62 fix starfield_timestamp = 0;
63 
64 #define MAX_FLARE_COUNT 10
65 #define MAX_FLARE_BMP 6
66 
67 typedef struct flare_info {
68 	float pos;
69 	float scale;
70 	int tex_num;
71 } flare_info;
72 
73 typedef struct flare_bitmap {
74 	char filename[MAX_FILENAME_LEN];
75 	int bitmap_id;
76 } flare_bitmap;
77 
78 
79 // global info (not individual instances)
80 typedef struct starfield_bitmap {
81 	char filename[MAX_FILENAME_LEN];				// bitmap filename
82 	char glow_filename[MAX_FILENAME_LEN];			// only for suns
83 	int bitmap_id;									// bitmap handle
84 	int n_frames;
85 	int fps;
86 	int glow_bitmap;								// only for suns
87 	int glow_n_frames;
88 	int glow_fps;
89 	int xparent;
90 	float r, g, b, i, spec_r, spec_g, spec_b;		// only for suns
91 	int glare;										// only for suns
92 	int flare;										// Is there a lens-flare for this sun?
93 	flare_info flare_infos[MAX_FLARE_COUNT];		// each flare can use a texture in flare_bmp, with different scale
94 	flare_bitmap flare_bitmaps[MAX_FLARE_BMP];		// bitmaps for different lens flares (can be re-used)
95 	int n_flares;									// number of flares actually used
96 	int n_flare_bitmaps;							// number of flare bitmaps available
97 	int used_this_level;
98 	int preload;
99 } starfield_bitmap;
100 
101 // starfield bitmap instance
102 typedef struct starfield_bitmap_instance {
103 	float scale_x, scale_y;							// x and y scale
104 	int div_x, div_y;								// # of x and y divisions
105 	angles ang;										// angles from FRED
106 	int star_bitmap_index;							// index into starfield_bitmap array
107 	int n_verts;
108 	vertex *verts;
109 
starfield_bitmap_instancestarfield_bitmap_instance110 	starfield_bitmap_instance() : scale_x(1.0f), scale_y(1.0f), div_x(1), div_y(1), star_bitmap_index(0), n_verts(0), verts(NULL) {
111 		ang.p = 0.0f;
112 		ang.b = 0.0f;
113 		ang.h = 0.0f;
114     }
115 } starfield_bitmap_instance;
116 
117 // for drawing cool stuff on the background - comes from a table
118 static SCP_vector<starfield_bitmap> Starfield_bitmaps;
119 static SCP_vector<starfield_bitmap_instance> Starfield_bitmap_instances;
120 
121 // sun bitmaps and sun glow bitmaps
122 static SCP_vector<starfield_bitmap> Sun_bitmaps;
123 static SCP_vector<starfield_bitmap_instance> Suns;
124 
125 // Goober5000
126 int Cur_background = -1;
127 SCP_vector<background_t> Backgrounds;
128 
129 SCP_vector<int> Preload_background_indexes;
130 void stars_preload_background(int background_idx);
131 
132 int last_stars_filled = 0;
133 color star_colors[8];
134 color star_aacolors[8];
135 
136 typedef struct star {
137 	vec3d pos;
138 	vec3d last_star_pos;
139 	color col;
140 } star;
141 
142 typedef struct vDist {
143 	int x;
144 	int y;
145 } vDist;
146 
147 star Stars[MAX_STARS];
148 
149 motion_debris Motion_debris[MAX_DEBRIS];
150 
151 
152 typedef struct debris_vclip {
153 	int	bm;
154 	int	nframes;
155 	char  name[MAX_FILENAME_LEN];
156 } debris_vclip;
157 extern debris_vclip Debris_vclips_normal[];
158 extern debris_vclip Debris_vclips_nebula[];
159 extern debris_vclip *Debris_vclips;
160 
161 //XSTR:OFF
162 debris_vclip Debris_vclips_normal[MAX_DEBRIS_VCLIPS] = { { -1, -1, "debris01" }, { -1, -1, "debris02" }, { -1, -1, "debris03" }, { -1, -1, "debris04" } };
163 debris_vclip Debris_vclips_nebula[MAX_DEBRIS_VCLIPS] = { { -1, -1, "nebdeb01" }, { -1, -1, "nebdeb02" }, { -1, -1, "nebdeb03" }, { -1, -1, "nebdeb04" } };
164 debris_vclip *Debris_vclips = Debris_vclips_normal;
165 //XSTR:ON
166 
167 int stars_debris_loaded = 0;	// 0 = not loaded, 1 = normal vclips, 2 = nebula vclips
168 
169 // background data
170 int Stars_background_inited = 0;			// if we're inited
171 int Nmodel_num = -1;							// model num
172 int Nmodel_instance_num = -1;					// model instance num
173 matrix Nmodel_orient = IDENTITY_MATRIX;			// model orientation
174 int Nmodel_flags = DEFAULT_NMODEL_FLAGS;		// model flags
175 int Nmodel_bitmap = -1;						// model texture
176 
177 int Num_debris_normal = 0;
178 int Num_debris_nebula = 0;
179 
180 bool Dynamic_environment = false;
181 bool Motion_debris_override = false;
182 
183 bool Motion_debris_enabled = true;
184 
185 auto MotionDebrisOption = options::OptionBuilder<bool>("Graphics.MotionDebris", "Motion Debris",
186                                                        "Controls whether motion debris are shown or not")
187                               .category("Graphics")
188                               .bind_to_once(&Motion_debris_enabled)
189                               .default_val(true)
190                               .level(options::ExpertLevel::Advanced)
191                               .importance(67)
192                               .finish();
193 
194 static int Default_env_map = -1;
195 static int Mission_env_map = -1;
196 static bool Env_cubemap_drawn = false;
197 
stars_release_debris_vclips(debris_vclip * vclips)198 void stars_release_debris_vclips(debris_vclip *vclips)
199 {
200 	int i;
201 
202 	if (vclips == NULL)
203 		return;
204 
205 	for (i = 0; i < MAX_DEBRIS_VCLIPS; i++) {
206 		if ( (vclips[i].bm >= 0) && bm_release(vclips[i].bm) ) {
207 			vclips[i].bm = -1;
208 			vclips[i].nframes = -1;
209 		}
210 	}
211 }
212 
stars_load_debris_vclips(debris_vclip * vclips)213 void stars_load_debris_vclips(debris_vclip *vclips)
214 {
215 	int i;
216 
217 	if (vclips == NULL) {
218 		Int3();
219 		return;
220 	}
221 
222 	for (i = 0; i < MAX_DEBRIS_VCLIPS; i++) {
223 		vclips[i].bm = bm_load_animation( vclips[i].name, &vclips[i].nframes, nullptr, nullptr, nullptr, true );
224 
225 		if ( vclips[i].bm < 0 ) {
226 			// try loading it as a single bitmap
227 			vclips[i].bm = bm_load(Debris_vclips[i].name);
228 			vclips[i].nframes = 1;
229 
230 			if (vclips[i].bm <= 0) {
231 				Error( LOCATION, "Couldn't load animation/bitmap '%s'\n", vclips[i].name );
232 			}
233 		}
234 	}
235 }
236 
stars_load_debris(int fullneb)237 void stars_load_debris(int fullneb)
238 {
239 	if (!Motion_debris_enabled) {
240 		return;
241 	}
242 
243 	// if we're in nebula mode
244 	if ( fullneb && (stars_debris_loaded != 2) ) {
245 		stars_release_debris_vclips(Debris_vclips);
246 		stars_load_debris_vclips(Debris_vclips_nebula);
247 		Debris_vclips = Debris_vclips_nebula;
248 		stars_debris_loaded = 2;
249 	} else if (stars_debris_loaded != 1) {
250 		stars_release_debris_vclips(Debris_vclips);
251 		stars_load_debris_vclips(Debris_vclips_normal);
252 		Debris_vclips = Debris_vclips_normal;
253 		stars_debris_loaded = 1;
254 	}
255 }
256 
257 const int MAX_PERSPECTIVE_DIVISIONS = 5;
258 const float p_phi = 10.0f, p_theta = 10.0f;
259 
260 extern void stars_project_2d_onto_sphere( vec3d *pnt, float rho, float phi, float theta );
261 
starfield_create_bitmap_buffer(const int si_idx)262 static void starfield_create_bitmap_buffer(const int si_idx)
263 {
264 	vec3d s_points[MAX_PERSPECTIVE_DIVISIONS+1][MAX_PERSPECTIVE_DIVISIONS+1];
265 	vec3d t_points[MAX_PERSPECTIVE_DIVISIONS+1][MAX_PERSPECTIVE_DIVISIONS+1];
266 
267 	vertex v[4];
268 	matrix m, m_bank;
269 	int idx, s_idx;
270 	float ui, vi;
271 	angles bank_first;
272 
273 	starfield_bitmap_instance *sbi = &Starfield_bitmap_instances[si_idx];
274 
275 	angles *a = &sbi->ang;
276 	float scale_x = sbi->scale_x;
277 	float scale_y = sbi->scale_y;
278 	int div_x = sbi->div_x;
279 	int div_y = sbi->div_y;
280 
281 	// cap division values
282 //	div_x = div_x > MAX_PERSPECTIVE_DIVISIONS ? MAX_PERSPECTIVE_DIVISIONS : div_x;
283 	div_x = 1;
284 	div_y = div_y > MAX_PERSPECTIVE_DIVISIONS ? MAX_PERSPECTIVE_DIVISIONS : div_y;
285 
286 	if (sbi->verts != NULL) {
287 		delete [] sbi->verts;
288 	}
289 
290 	sbi->verts = new(std::nothrow) vertex[div_x * div_y * 6];
291 
292 	if (sbi->verts == NULL) {
293 		sbi->star_bitmap_index = -1;
294 		return;
295 	}
296 
297 	sbi->n_verts = div_x * div_y * 6;
298 
299 	// texture increment values
300 	ui = 1.0f / (float)div_x;
301 	vi = 1.0f / (float)div_y;
302 
303 	// adjust for aspect ratio
304 	//scale_x *= (gr_screen.clip_aspect + 0.55f); // fudge factor
305 	//scale_x *= (gr_screen.clip_aspect + (0.7333333f/gr_screen.clip_aspect)); // fudge factor
306 	scale_x *= 1.883333f; //fudge factor
307 
308 	float s_phi = 0.5f + (((p_phi * scale_x) / 360.0f) / 2.0f);
309 	float s_theta = (((p_theta * scale_y) / 360.0f) / 2.0f);
310 	float d_phi = -(((p_phi * scale_x) / 360.0f) / (float)(div_x));
311 	float d_theta = -(((p_theta * scale_y) / 360.0f) / (float)(div_y));
312 
313 	// bank matrix
314 	bank_first.p = 0.0f;
315 	bank_first.b = a->b;
316 	bank_first.h = 0.0f;
317 	vm_angles_2_matrix(&m_bank, &bank_first);
318 
319 	// convert angles to matrix
320 	angles a_temp = *a;
321 	a_temp.b = 0.0f;
322 	vm_angles_2_matrix(&m, &a_temp);
323 
324 	// generate the bitmap points
325 	for(idx=0; idx<=div_x; idx++) {
326 		for(s_idx=0; s_idx<=div_y; s_idx++) {
327 			// get world spherical coords
328 			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));
329 
330 			// bank the bitmap first
331 			vm_vec_rotate(&t_points[idx][s_idx], &s_points[idx][s_idx], &m_bank);
332 
333 			// rotate on the sphere
334 			vm_vec_rotate(&s_points[idx][s_idx], &t_points[idx][s_idx], &m);
335 		}
336 	}
337 
338 	memset(v, 0, sizeof(vertex) * 4);
339 
340 	int j = 0;
341 
342 	vertex *verts = sbi->verts;
343 
344 	for (idx = 0; idx < div_x; idx++) {
345 		for (s_idx = 0; s_idx < div_y; s_idx++) {
346 			// stuff texture coords
347 			v[0].texture_position.u = ui * float(idx);
348 			v[0].texture_position.v = vi * float(s_idx);
349 
350 			v[1].texture_position.u = ui * float(idx+1);
351 			v[1].texture_position.v = vi * float(s_idx);
352 
353 			v[2].texture_position.u = ui * float(idx+1);
354 			v[2].texture_position.v = vi * float(s_idx+1);
355 
356 			v[3].texture_position.u = ui * float(idx);
357 			v[3].texture_position.v = vi * float(s_idx+1);
358 
359 			g3_transfer_vertex(&v[0], &s_points[idx][s_idx]);
360 			g3_transfer_vertex(&v[1], &s_points[idx+1][s_idx]);
361 			g3_transfer_vertex(&v[2], &s_points[idx+1][s_idx+1]);
362 			g3_transfer_vertex(&v[3], &s_points[idx][s_idx+1]);
363 
364 			// poly 1
365 			verts[j++] = v[0];
366 			verts[j++] = v[1];
367 			verts[j++] = v[2];
368 			// poly 2
369 			verts[j++] = v[0];
370 			verts[j++] = v[2];
371 			verts[j++] = v[3];
372 		}
373 	}
374 
375 	Assert( j == sbi->n_verts );
376 }
377 
378 // take the Starfield_bitmap_instances[] and make all the vertex buffers that you'll need to draw it
starfield_generate_bitmap_buffers()379 static void starfield_generate_bitmap_buffers()
380 {
381 	int idx;
382 
383 	int sb_instances = (int)Starfield_bitmap_instances.size();
384 
385 	for (idx = 0; idx < sb_instances; idx++) {
386 		if (Starfield_bitmap_instances[idx].star_bitmap_index < 0) {
387 			continue;
388 		}
389 
390 		starfield_create_bitmap_buffer(idx);
391 	}
392 }
393 
starfield_bitmap_entry_init(starfield_bitmap * sbm)394 static void starfield_bitmap_entry_init(starfield_bitmap *sbm)
395 {
396 	int i;
397 
398 	Assert( sbm != NULL );
399 
400 	memset( sbm, 0, sizeof(starfield_bitmap) );
401 
402 	sbm->bitmap_id = -1;
403 	sbm->glow_bitmap = -1;
404 	sbm->glow_n_frames = 1;
405 
406 	for (i = 0; i < MAX_FLARE_BMP; i++) {
407 		sbm->flare_bitmaps[i].bitmap_id = -1;
408 	}
409 
410 	for (i = 0; i < MAX_FLARE_COUNT; i++) {
411 		sbm->flare_infos[i].tex_num = -1;
412 	}
413 }
414 
415 #define CHECK_END() {	\
416 	if (in_check) {	\
417 		required_string("#end");	\
418 		in_check = false;	\
419 		run_count = 0;	\
420 	}	\
421 }
422 
parse_startbl(const char * filename)423 void parse_startbl(const char *filename)
424 {
425 	char name[MAX_FILENAME_LEN], tempf[16];
426 	starfield_bitmap sbm;
427 	int idx;
428 	bool in_check = false;
429 	int rc = -1;
430 	int run_count = 0;
431 
432 	try
433 	{
434 		read_file_text(filename, CF_TYPE_TABLES);
435 		reset_parse();
436 
437 		// freaky! ;)
438 		while (!check_for_eof()) {
439 			while ((rc = optional_string_either("$Bitmap:", "$BitmapX:")) != -1) {
440 				in_check = true;
441 
442 				starfield_bitmap_entry_init(&sbm);
443 
444 				stuff_string(sbm.filename, F_NAME, MAX_FILENAME_LEN);
445 				sbm.xparent = rc;  // 0 == intensity alpha bitmap,  1 == green xparency bitmap
446 
447 				if ((idx = stars_find_bitmap(sbm.filename)) >= 0) {
448 					if (sbm.xparent == Starfield_bitmaps[idx].xparent) {
449 						if (!Parsing_modular_table)
450 							Warning(LOCATION, "Starfield bitmap '%s' listed more than once!!  Only using the first entry!", sbm.filename);
451 					}
452 					else {
453 						Warning(LOCATION, "Starfield bitmap '%s' already listed as a %s bitmap!!  Only using the xparent version!",
454 							sbm.filename, (rc) ? "xparent" : "non-xparent");
455 					}
456 				}
457 				else {
458 					Starfield_bitmaps.push_back(sbm);
459 				}
460 			}
461 
462 			CHECK_END();
463 
464 			while (optional_string("$Sun:")) {
465 				in_check = true;
466 
467 				starfield_bitmap_entry_init(&sbm);
468 
469 				stuff_string(sbm.filename, F_NAME, MAX_FILENAME_LEN);
470 
471 				// associated glow
472 				required_string("$Sunglow:");
473 				stuff_string(sbm.glow_filename, F_NAME, MAX_FILENAME_LEN);
474 
475 				// associated lighting values
476 				required_string("$SunRGBI:");
477 				stuff_float(&sbm.r);
478 				stuff_float(&sbm.g);
479 				stuff_float(&sbm.b);
480 				stuff_float(&sbm.i);
481 
482 				if (optional_string("$SunSpecularRGB:")) {
483 					stuff_float(&sbm.spec_r);
484 					stuff_float(&sbm.spec_g);
485 					stuff_float(&sbm.spec_b);
486 				}
487 				else {
488 					sbm.spec_r = sbm.r;
489 					sbm.spec_g = sbm.g;
490 					sbm.spec_b = sbm.b;
491 				}
492 
493 				// lens flare stuff
494 				if (optional_string("$Flare:")) {
495 					sbm.flare = 1;
496 
497 					required_string("+FlareCount:");
498 					stuff_int(&sbm.n_flares);
499 
500 					// if there's a flare, it has to have at least one texture
501 					required_string("$FlareTexture1:");
502 					stuff_string(sbm.flare_bitmaps[0].filename, F_NAME, MAX_FILENAME_LEN);
503 
504 					sbm.n_flare_bitmaps = 1;
505 
506 					for (idx = 1; idx < MAX_FLARE_BMP; idx++) {
507 						// allow 9999 textures (theoretically speaking, that is)
508 						sprintf(tempf, "$FlareTexture%d:", idx + 1);
509 
510 						if (optional_string(tempf)) {
511 							sbm.n_flare_bitmaps++;
512 							stuff_string(sbm.flare_bitmaps[idx].filename, F_NAME, MAX_FILENAME_LEN);
513 						}
514 						//	else break; //don't allow flaretexture1 and then 3, etc.
515 					}
516 
517 					required_string("$FlareGlow1:");
518 
519 					required_string("+FlareTexture:");
520 					stuff_int(&sbm.flare_infos[0].tex_num);
521 
522 					required_string("+FlarePos:");
523 					stuff_float(&sbm.flare_infos[0].pos);
524 
525 					required_string("+FlareScale:");
526 					stuff_float(&sbm.flare_infos[0].scale);
527 
528 					sbm.n_flares = 1;
529 
530 					for (idx = 1; idx < MAX_FLARE_COUNT; idx++) {
531 						// allow a lot of glows
532 						sprintf(tempf, "$FlareGlow%d:", idx + 1);
533 
534 						if (optional_string(tempf)) {
535 							sbm.n_flares++;
536 
537 							required_string("+FlareTexture:");
538 							stuff_int(&sbm.flare_infos[idx].tex_num);
539 
540 							required_string("+FlarePos:");
541 							stuff_float(&sbm.flare_infos[idx].pos);
542 
543 							required_string("+FlareScale:");
544 							stuff_float(&sbm.flare_infos[idx].scale);
545 						}
546 						//	else break; //don't allow "flare 1" and then "flare 3"
547 					}
548 				}
549 
550 				sbm.glare = !optional_string("$NoGlare:");
551 
552 				sbm.xparent = 1;
553 
554 				if ((idx = stars_find_sun(sbm.filename)) >= 0) {
555 					if (Parsing_modular_table)
556 						Sun_bitmaps[idx] = sbm;
557 					else
558 						Warning(LOCATION, "Sun bitmap '%s' listed more than once!!  Only using the first entry!", sbm.filename);
559 				}
560 				else {
561 					Sun_bitmaps.push_back(sbm);
562 				}
563 			}
564 
565 			CHECK_END();
566 
567 			// normal debris pieces
568 			while (optional_string("$Debris:")) {
569 				in_check = true;
570 
571 				stuff_string(name, F_NAME, MAX_FILENAME_LEN);
572 
573 				if (Num_debris_normal < MAX_DEBRIS_VCLIPS) {
574 					strcpy_s(Debris_vclips_normal[Num_debris_normal++].name, name);
575 				}
576 				else {
577 					Warning(LOCATION, "Could not load normal motion debris '%s'; maximum of %d exceeded.", name, MAX_DEBRIS_VCLIPS);
578 				}
579 			}
580 
581 			CHECK_END();
582 
583 			// nebula debris pieces
584 			while (optional_string("$DebrisNeb:")) {
585 				in_check = true;
586 
587 				stuff_string(name, F_NAME, MAX_FILENAME_LEN);
588 
589 				if (Num_debris_nebula < MAX_DEBRIS_VCLIPS) {
590 					strcpy_s(Debris_vclips_nebula[Num_debris_nebula++].name, name);
591 				}
592 				else {
593 					Warning(LOCATION, "Could not load nebula motion debris '%s'; maximum of %d exceeded.", name, MAX_DEBRIS_VCLIPS);
594 				}
595 			}
596 
597 			CHECK_END();
598 
599 			// since it's possible for some idiot to have a tbl screwed up enough
600 			// that this ends up in an endless loop, give an opportunity to advance
601 			// through the file no matter what, because even the retail tbl has an
602 			// extra "#end" line in it.
603 			if (optional_string("#end") || (run_count++ > 5)) {
604 				run_count = 0;
605 				advance_to_eoln(NULL);
606 			}
607 		}
608 	}
609 	catch (const parse::ParseException& e)
610 	{
611 		mprintf(("TABLES: Unable to parse '%s'!  Error message = %s.\n", filename, e.what()));
612 		return;
613 	}
614 }
615 
stars_load_all_bitmaps()616 void stars_load_all_bitmaps()
617 {
618 	int idx, i;
619 	starfield_bitmap *sb = NULL;
620 	static int Star_bitmaps_loaded = 0;
621 
622 	if (Star_bitmaps_loaded)
623 		return;
624 
625 	// pre-load all starfield bitmaps.  ONLY SHOULD DO THIS FOR FRED!!
626 	// this can get nasty when a lot of bitmaps are in use so spare it for
627 	// the normal game and only do this in FRED
628 	int mprintf_count = 0;
629 	for (idx = 0; idx < (int)Starfield_bitmaps.size(); idx++) {
630 		sb = &Starfield_bitmaps[idx];
631 
632 		if (sb->bitmap_id < 0) {
633 			sb->bitmap_id = bm_load(sb->filename);
634 
635 			// maybe didn't load a static image so try for an animated one
636 			if (sb->bitmap_id < 0) {
637 				sb->bitmap_id = bm_load_animation(sb->filename, &sb->n_frames, &sb->fps, nullptr, nullptr, true);
638 
639 				if (sb->bitmap_id < 0) {
640 					mprintf(("Unable to load starfield bitmap: '%s'!\n", sb->filename));
641 					mprintf_count++;
642 				}
643 			}
644 		}
645 	}
646 	if (mprintf_count > 0) {
647 		Warning(LOCATION, "Unable to load %d starfield bitmap(s)!\n", mprintf_count);
648 	}
649 
650 	for (idx = 0; idx < (int)Sun_bitmaps.size(); idx++) {
651 		sb = &Sun_bitmaps[idx];
652 
653 		// normal bitmap
654 		if (sb->bitmap_id < 0) {
655 			sb->bitmap_id = bm_load(sb->filename);
656 
657 			// maybe didn't load a static image so try for an animated one
658 			if (sb->bitmap_id < 0) {
659 				sb->bitmap_id = bm_load_animation(sb->filename, &sb->n_frames, &sb->fps, nullptr, nullptr, true);
660 
661 				if (sb->bitmap_id < 0) {
662 					Warning(LOCATION, "Unable to load sun bitmap: '%s'!\n", sb->filename);
663 				}
664 			}
665 		}
666 
667 		// glow bitmap
668 		if (sb->glow_bitmap < 0) {
669 			sb->glow_bitmap = bm_load(sb->glow_filename);
670 
671 			// maybe didn't load a static image so try for an animated one
672 			if (sb->glow_bitmap < 0) {
673 				sb->glow_bitmap = bm_load_animation(sb->glow_filename, &sb->glow_n_frames, &sb->glow_fps, nullptr, nullptr, true);
674 
675 				if (sb->glow_bitmap < 0) {
676 					Warning(LOCATION, "Unable to load sun glow bitmap: '%s'!\n", sb->glow_filename);
677 				}
678 			}
679 		}
680 
681 		if (sb->flare) {
682 			for (i = 0; i < MAX_FLARE_BMP; i++) {
683 				if ( !strlen(sb->flare_bitmaps[i].filename) )
684 					continue;
685 
686 				if (sb->flare_bitmaps[i].bitmap_id < 0) {
687 					sb->flare_bitmaps[i].bitmap_id = bm_load(sb->flare_bitmaps[i].filename);
688 
689 					if (sb->flare_bitmaps[i].bitmap_id < 0) {
690 						Warning(LOCATION, "Unable to load sun flare bitmap: '%s'!\n", sb->flare_bitmaps[i].filename);
691 						continue;
692 					}
693 				}
694 			}
695 		}
696 	}
697 
698 	Star_bitmaps_loaded = 1;
699 }
700 
stars_clear_instances()701 void stars_clear_instances()
702 {
703 	for (uint i = 0; i < Starfield_bitmap_instances.size(); i++) {
704 		delete [] Starfield_bitmap_instances[i].verts;
705 		Starfield_bitmap_instances[i].verts = NULL;
706 	}
707 
708 	Starfield_bitmap_instances.clear();
709 	Suns.clear();
710 }
711 
712 // call on game startup
stars_init()713 void stars_init()
714 {
715 	// starfield bitmaps
716 	Num_debris_normal = 0;
717 	Num_debris_nebula = 0;
718 
719 	// parse stars.tbl
720 	parse_startbl("stars.tbl");
721 
722 	parse_modular_table("*-str.tbm", parse_startbl);
723 
724 	if (Cmdline_env) {
725 		ENVMAP = Default_env_map = bm_load("cubemap");
726 	}
727 }
728 
729 // call only from game_shutdown()!!
stars_close()730 void stars_close()
731 {
732 	stars_clear_instances();
733 
734 	// any other code goes here
735 }
736 
737 // called before mission parse so we can clear out all of the old stuff
stars_pre_level_init(bool clear_backgrounds)738 void stars_pre_level_init(bool clear_backgrounds)
739 {
740 	uint idx, i;
741 	starfield_bitmap *sb = NULL;
742 
743 	// we used to clear all the array entries, but now we can just wipe the vector
744 	if (clear_backgrounds)
745 		Backgrounds.clear();
746 
747 	stars_clear_instances();
748 
749 	stars_set_background_model(NULL, NULL);
750 	stars_set_background_orientation();
751 
752 	// mark all starfield and sun bitmaps as unused for this mission and release any current bitmaps
753 	// NOTE: that because of how we have to load the bitmaps it's important to release all of
754 	//       them first thing rather than after we have marked and loaded only what's needed
755 	// NOTE2: there is a reason that we don't check for release before setting the handle to -1 so
756 	//        be aware that this is NOT a bug. also, bmpman should NEVER return 0 as a valid handle!
757 	if ( !Fred_running ) {
758 		for (idx = 0; idx < Starfield_bitmaps.size(); idx++) {
759 			sb = &Starfield_bitmaps[idx];
760 
761 			if (sb->bitmap_id > 0) {
762 				bm_release(sb->bitmap_id);
763 				sb->bitmap_id = -1;
764 			}
765 
766 			sb->used_this_level = 0;
767 			sb->preload = 0;
768 		}
769 
770 		for (idx = 0; idx < Sun_bitmaps.size(); idx++) {
771 			sb = &Sun_bitmaps[idx];
772 
773 			if (sb->bitmap_id > 0) {
774 				bm_release(sb->bitmap_id);
775 				sb->bitmap_id = -1;
776 			}
777 
778 			if (sb->glow_bitmap > 0) {
779 				bm_release(sb->glow_bitmap);
780 				sb->glow_bitmap = -1;
781 			}
782 
783 			for (i = 0; i < MAX_FLARE_BMP; i++) {
784 				if (sb->flare_bitmaps[i].bitmap_id > 0) {
785 					bm_release(sb->flare_bitmaps[i].bitmap_id);
786 					sb->flare_bitmaps[i].bitmap_id = -1;
787 				}
788 			}
789 
790 			sb->used_this_level = 0;
791 			sb->preload = 0;
792 		}
793 	}
794 
795 	// also clear the preload indexes
796 	Preload_background_indexes.clear();
797 
798 	Dynamic_environment = false;
799 	Motion_debris_override = false;
800 
801 	Env_cubemap_drawn = false;
802 }
803 
804 // setup the render target ready for this mission's environment map
environment_map_gen()805 static void environment_map_gen()
806 {
807 	const int size = 512;
808 	int gen_flags = (BMP_FLAG_RENDER_TARGET_STATIC | BMP_FLAG_CUBEMAP | BMP_FLAG_RENDER_TARGET_MIPMAP);
809 
810 	if ( !Cmdline_env ) {
811 		return;
812 	}
813 
814 	if (gr_screen.envmap_render_target >= 0) {
815 		if ( !bm_release(gr_screen.envmap_render_target, 1) ) {
816 			Warning(LOCATION, "Unable to release environment map render target.");
817 		}
818 
819 		gr_screen.envmap_render_target = -1;
820 	}
821 
822 	if ( Dynamic_environment || (The_mission.flags[Mission::Mission_Flags::Subspace]) ) {
823 		Dynamic_environment = true;
824 		gen_flags &= ~BMP_FLAG_RENDER_TARGET_STATIC;
825 		gen_flags |= BMP_FLAG_RENDER_TARGET_DYNAMIC;
826 	}
827 		// bail if we are going to be static, and have an envmap specified already
828 	else if ( strlen(The_mission.envmap_name) ) {
829 		// Load the mission map so we can use it later
830 		Mission_env_map = bm_load(The_mission.envmap_name);
831 		return;
832 	}
833 
834 	gr_screen.envmap_render_target = bm_make_render_target(size, size, gen_flags);
835 }
836 
837 // call this in game_post_level_init() so we know whether we're running in full nebula mode or not
stars_post_level_init()838 void stars_post_level_init()
839 {
840 	int i;
841 	vec3d v;
842 	float dist, dist_max;
843 	ubyte red,green,blue,alpha;
844 
845 	// in FRED, make sure we always have at least one background
846 	if (Fred_running)
847 	{
848 		if (Backgrounds.empty())
849 			Backgrounds.emplace_back();
850 	}
851 	// in FSO, see if we have any backgrounds to preload
852 	// (since the backgrounds aren't parsed at the time we parse the sexps)
853 	else
854 	{
855 		for (int idx : Preload_background_indexes)
856 			stars_preload_background(idx);
857 	}
858 
859 	stars_set_background_model(The_mission.skybox_model, NULL, The_mission.skybox_flags);
860 	stars_set_background_orientation(&The_mission.skybox_orientation);
861 
862 	stars_load_debris( ((The_mission.flags[Mission::Mission_Flags::Fullneb]) || Nebula_sexp_used) );
863 
864 // following code randomly distributes star points within a sphere volume, which
865 // avoids there being denser areas along the edges and in corners that we had in the
866 // old rectangular distribution scheme.
867 	dist_max = (float) (HALF_RND_MAX * HALF_RND_MAX);
868 	for (i=0; i<MAX_STARS; i++) {
869 		dist = dist_max;
870 		while (dist >= dist_max) {
871 			v.xyz.x = (float) ((Random::next() & RND_MAX_MASK) - HALF_RND_MAX);
872 			v.xyz.y = (float) ((Random::next() & RND_MAX_MASK) - HALF_RND_MAX);
873 			v.xyz.z = (float) ((Random::next() & RND_MAX_MASK) - HALF_RND_MAX);
874 
875 			dist = v.xyz.x * v.xyz.x + v.xyz.y * v.xyz.y + v.xyz.z * v.xyz.z;
876 		}
877 		vm_vec_copy_normalize(&Stars[i].pos, &v);
878 
879 		{
880 			red = (ubyte)Random::next(192, 255);
881 			green = (ubyte)Random::next(192, 255);
882 			blue = (ubyte)Random::next(192, 255);
883 			alpha = (ubyte)Random::next(24, 216);
884 
885 			gr_init_alphacolor(&Stars[i].col, red, green, blue, alpha, AC_TYPE_BLEND);
886 		}
887 
888 	}
889 
890 	memset( &Motion_debris, 0, sizeof(motion_debris) * MAX_DEBRIS );
891 
892 
893 	for (i=0; i<8; i++ ) {
894 		ubyte intensity = (ubyte)((i + 1) * 24);
895 		gr_init_alphacolor(&star_aacolors[i], 255, 255, 255, intensity, AC_TYPE_BLEND );
896 		gr_init_color(&star_colors[i], intensity, intensity, intensity );
897 	}
898 
899 	last_stars_filled = 0;
900 
901 	// if we have no sun instances, create one
902 	if ( !Suns.size() ) {
903 		if ( !strlen(Sun_bitmaps[0].filename) ) {
904 			mprintf(("Trying to add default sun but no default exists!!\n"));
905 		} else {
906 			mprintf(("Adding default sun.\n"));
907 
908 			starfield_bitmap_instance def_sun;
909 
910 			// stuff some values
911 			def_sun.ang.h = fl_radians(60.0f);
912 
913 			Suns.push_back(def_sun);
914 		}
915 	}
916 
917 	// FRED doesn't do normal page_in stuff so we need to load up the bitmaps here instead
918 	if (Fred_running) {
919 		stars_load_all_bitmaps();
920 
921 		// see whether we are missing any suns or bitmaps
922 		for (i = 0; i < (int)Backgrounds.size(); ++i) {
923 			SCP_string failed_suns;
924 			for (auto &sun : Backgrounds[i].suns) {
925 				if (stars_find_sun(sun.filename) < 0) {
926 					failed_suns += sun.filename;
927 					failed_suns += "\n";
928 				}
929 			}
930 
931 			SCP_string failed_bitmaps;
932 			for (auto &bitmap : Backgrounds[i].bitmaps) {
933 				if (stars_find_bitmap(bitmap.filename) < 0) {
934 					failed_bitmaps += bitmap.filename;
935 					failed_bitmaps += "\n";
936 				}
937 			}
938 
939 			if (!failed_suns.empty()) {
940 				Warning(LOCATION, "In Background %d, failed to load the following suns:\n%s", (i + 1), failed_suns.c_str());
941 			}
942 			if (!failed_bitmaps.empty()) {
943 				Warning(LOCATION, "In Background %d, failed to load the following bitmaps:\n%s", (i + 1), failed_bitmaps.c_str());
944 			}
945 		}
946 	}
947 
948 	starfield_generate_bitmap_buffers();
949 
950 	environment_map_gen();
951 }
952 
stars_level_close()953 void stars_level_close() {
954 	if (gr_screen.envmap_render_target >= 0) {
955 		if ( bm_release(gr_screen.envmap_render_target, 1) ) {
956 			gr_screen.envmap_render_target = -1;
957 		}
958 	}
959 
960 	if (Mission_env_map >= 0) {
961 		bm_release(Mission_env_map);
962 		Mission_env_map = -1;
963 	}
964 
965 	ENVMAP = Default_env_map;
966 }
967 
968 
969 extern object * Player_obj;
970 
971 #define STAR_AMOUNT_DEFAULT 0.75f
972 #define STAR_DIM_DEFAULT 7800.0f
973 #define STAR_CAP_DEFAULT 75.0f
974 #define STAR_MAX_LENGTH_DEFAULT 0.04f		// 312
975 
976 float Star_amount = STAR_AMOUNT_DEFAULT;
977 float Star_dim = STAR_DIM_DEFAULT;
978 float Star_cap = STAR_CAP_DEFAULT;
979 float Star_max_length = STAR_MAX_LENGTH_DEFAULT;
980 
981 #define STAR_FLAG_TAIL			(1<<0)	// Draw a tail when moving
982 #define STAR_FLAG_DIM			(1<<1)	// Dim as you move
983 #define STAR_FLAG_ANTIALIAS		(1<<2)	// Draw the star using antialiased lines
984 #define STAR_FLAG_DEFAULT		(STAR_FLAG_TAIL | STAR_FLAG_DIM)
985 
986 uint Star_flags = STAR_FLAG_DEFAULT;
987 
988 //XSTR:OFF
989 DCF(stars,"Set parameters for starfield")
990 {
991 	SCP_string arg;
992 	float val_f;
993 	int val_i;
994 
995 	if (dc_optional_string_either("help", "--help")) {
996 		dc_printf( "Usage: stars keyword\nWhere keyword can be in the following forms:\n" );
997 		dc_printf( "stars default   Resets stars to all default values\n" );
998 		dc_printf( "stars num X     Sets number of stars to X.  Between 0 and %d.\n", MAX_STARS );
999 		dc_printf( "stars tail X    Where X is the percent of 'tail' between 0 and 1.0\n" );
1000 		dc_printf( "stars dim X     Where X is the amount stars dim between 0 and 255.0\n" );
1001 		dc_printf( "stars cap X     Where X is the cap of dimming between 0 and 255.\n" );
1002 		dc_printf( "stars len X     Where X is the cap of length.\n" );
1003 		dc_printf( "stars m0        Macro0. Old 'pixel type' crappy stars. flags=none\n" );
1004 		dc_printf( "stars m1        Macro1. (default) tail=.75, dim=20.0, cap=75.0, flags=dim,tail\n" );
1005 		dc_printf( "stars m2        Macro2. tail=.75, dim=20.0, cap=75.0, flags=dim,tail,aa\n" );
1006 		dc_printf( "stars flag X    Toggles flag X, where X is tail or dim or aa (aa=antialias)\n" );
1007 		dc_printf( "\nHINT: set cap to 0 to get dim rate and tail down, then use\n" );
1008 		dc_printf( "cap to keep the lines from going away when moving too fast.\n" );
1009 		dc_printf( "\nUse '? stars' to see current values.\n" );
1010 		return;	// don't print status if help is printed.  Too messy.
1011 	}
1012 
1013 	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
1014 		dc_printf( "Num_stars: %d\n", Num_stars );
1015 		dc_printf( "Tail: %.2f\n", Star_amount );
1016 		dc_printf( "Dim : %.2f\n", Star_dim );
1017 		dc_printf( "Cap : %.2f\n", Star_cap );
1018 		dc_printf( "Max length: %.2f\n", Star_max_length );
1019 		dc_printf( "Flags:\n" );
1020 		dc_printf( "  Tail : %s\n", (Star_flags&STAR_FLAG_TAIL?"On":"Off") );
1021 		dc_printf( "  Dim  : %s\n", (Star_flags&STAR_FLAG_DIM?"On":"Off") );
1022 		dc_printf( "  Antialias: %s\n", (Star_flags&STAR_FLAG_ANTIALIAS?"On":"Off") );
1023 		dc_printf( "\nTHESE AREN'T SAVED TO DISK, SO IF YOU TWEAK\n" );
1024 		dc_printf( "THESE AND LIKE THEM, WRITE THEM DOWN!!\n" );
1025 		return;
1026 	}
1027 
1028 	dc_stuff_string_white(arg);
1029 	// "stars default" is handled by "stars m1"
1030 	if (arg == "num") {
1031 		dc_stuff_int(&val_i);
1032 
1033 		CLAMP(val_i, 0, MAX_STARS);
1034 		Num_stars = val_i;
1035 
1036 		dc_printf("Num_stars set to %i\n", Num_stars);
1037 
1038 	} else if (arg == "tail") {
1039 		dc_stuff_float(&val_f);
1040 		CLAMP(val_f, 0.0, 1.0);
1041 		Star_amount = val_f;
1042 
1043 		dc_printf("Star_amount set to %f\n", Star_amount);
1044 
1045 	} else if (arg == "dim") {
1046 		dc_stuff_float(&val_f);
1047 
1048 		if (val_f > 0.0f ) {
1049 			Star_dim = val_f;
1050 			dc_printf("Star_dim set to %f\n", Star_dim);
1051 
1052 		} else {
1053 			dc_printf("Error: Star_dim value must be non-negative\n");
1054 		}
1055 
1056 	} else if (arg == "cap") {
1057 		dc_stuff_float(&val_f);
1058 		CLAMP(val_f, 0.0, 255);
1059 		Star_cap = val_f;
1060 
1061 		dc_printf("Star_cap set to %f\n", Star_cap);
1062 
1063 	} else if (arg == "len") {
1064 		dc_stuff_float(&Star_max_length);
1065 
1066 		dc_printf("Star_max_length set to %f\n", Star_max_length);
1067 
1068 	} else if (arg == "m0") {
1069 		Star_amount = 0.0f;
1070 		Star_dim = 0.0f;
1071 		Star_cap = 0.0f;
1072 		Star_flags = 0;
1073 		Star_max_length = STAR_MAX_LENGTH_DEFAULT;
1074 
1075 		dc_printf("Starfield set: Old 'pixel type' crappy stars. flags=none\n");
1076 
1077 	} else if ((arg == "m1") || (arg == "default")) {
1078 		Star_amount = STAR_AMOUNT_DEFAULT;
1079 		Star_dim = STAR_DIM_DEFAULT;
1080 		Star_cap = STAR_CAP_DEFAULT;
1081 		Star_flags = STAR_FLAG_DEFAULT;
1082 		Star_max_length = STAR_MAX_LENGTH_DEFAULT;
1083 
1084 		dc_printf("Starfield set: (default) tail=.75, dim=20.0, cap=75.0, flags=dim,tail\n");
1085 
1086 	} else if (arg == "m2") {
1087 		Star_amount = 0.75f;
1088 		Star_dim = 20.0f;
1089 		Star_cap = 75.0f;
1090 		Star_flags = STAR_FLAG_TAIL|STAR_FLAG_DIM|STAR_FLAG_ANTIALIAS;
1091 		Star_max_length = STAR_MAX_LENGTH_DEFAULT;
1092 
1093 		dc_printf("Starfield set: tail=.75, dim=20.0, cap=75.0, flags=dim,tail,aa\n");
1094 
1095 	} else if (arg == "flag") {
1096 		dc_stuff_string_white(arg);
1097 		if (arg == "tail") {
1098 			Star_flags ^= STAR_FLAG_TAIL;
1099 		} else if (arg == "dim" ) {
1100 			Star_flags ^= STAR_FLAG_DIM;
1101 		} else if (arg == "aa" ) {
1102 			Star_flags ^= STAR_FLAG_ANTIALIAS;
1103 		} else {
1104 			dc_printf("Error: unknown flag argument '%s'\n", arg.c_str());
1105 		}
1106 
1107 	} else {
1108 		dc_printf("Error: Unknown argument '%s'", arg.c_str());
1109 	}
1110 }
1111 //XSTR:ON
1112 
1113 bool refresh_motion_debris = true; // If set to true, then regenerate the positions of motion debris
1114 // Call this if camera "cuts" or moves long distances
1115 // so blur effect doesn't draw lines all over the screen.
stars_camera_cut()1116 void stars_camera_cut()
1117 {
1118 	last_stars_filled = 0;
1119 	refresh_motion_debris = true;
1120 }
1121 
1122 //#define TIME_STAR_CODE		// enable to time star code
1123 
1124 extern int Sun_drew;
1125 
1126 // get the world coords of the sun pos on the unit sphere.
stars_get_sun_pos(int sun_n,vec3d * pos)1127 void stars_get_sun_pos(int sun_n, vec3d *pos)
1128 {
1129 	vec3d temp;
1130 	matrix rot;
1131 
1132 	// sanity
1133 	Assert( sun_n < (int)Suns.size() );
1134 
1135 	if ( (sun_n >= (int)Suns.size()) || (sun_n < 0) ) {
1136 		return;
1137 	}
1138 
1139 	// rotate the sun properly
1140 	temp = vmd_zero_vector;
1141 	temp.xyz.z = 1.0f;
1142 
1143 	// rotation matrix
1144 	vm_angles_2_matrix(&rot, &Suns[sun_n].ang);
1145 	vm_vec_rotate(pos, &temp, &rot);
1146 }
1147 
1148 // draw sun
stars_draw_sun(int show_sun)1149 void stars_draw_sun(int show_sun)
1150 {
1151 	GR_DEBUG_SCOPE("Draw Suns");
1152 	TRACE_SCOPE(tracing::DrawSuns);
1153 
1154 	int idx;
1155 	vec3d sun_pos;
1156 	vec3d sun_dir;
1157 	vertex sun_vex;
1158 	starfield_bitmap *bm;
1159 	float local_scale;
1160 
1161 	// should we even be here?
1162 	if (!show_sun)
1163 		return;
1164 
1165 	// no suns drew yet
1166 	Sun_drew = 0;
1167 
1168 	// draw all suns
1169 	int num_suns = (int)Suns.size();
1170 
1171 	for (idx = 0; idx < num_suns; idx++) {
1172 		// get the instance
1173 		if (Suns[idx].star_bitmap_index < 0)
1174 			return;
1175 
1176 		bm = &Sun_bitmaps[Suns[idx].star_bitmap_index];
1177 
1178 		// if no bitmap then bail...
1179 		if (bm->bitmap_id < 0)
1180 			continue;
1181 
1182 		memset( &sun_vex, 0, sizeof(vertex) );
1183 
1184 		// get sun pos
1185 		sun_pos = vmd_zero_vector;
1186 		sun_pos.xyz.y = 1.0f;
1187 		stars_get_sun_pos(idx, &sun_pos);
1188 
1189 		// get the direction
1190 		sun_dir = sun_pos;
1191 		vm_vec_normalize(&sun_dir);
1192 
1193 		// add the light source corresponding to the sun, except when rendering to an envmap
1194 		if ( !Rendering_to_env )
1195 			light_add_directional(&sun_dir, bm->i, bm->r, bm->g, bm->b, bm->spec_r, bm->spec_g, bm->spec_b, true);
1196 
1197 		// if supernova
1198 		if ( supernova_active() && (idx == 0) )
1199 			local_scale = 1.0f + (SUPERNOVA_SUN_SCALE * supernova_pct_complete());
1200 		else
1201 			local_scale = 1.0f;
1202 
1203 		// draw the sun itself, keep track of how many we drew
1204 		int bitmap_id = -1;
1205 		if (bm->fps) {
1206 			//gr_set_bitmap(bm->bitmap_id + ((timestamp() / (int)(bm->fps)) % bm->n_frames), GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 0.999f);
1207 			bitmap_id = bm->bitmap_id + ((timestamp() / (int)(bm->fps)) % bm->n_frames);
1208 		} else {
1209 			//gr_set_bitmap(bm->bitmap_id, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 0.999f);
1210 			bitmap_id = bm->bitmap_id;
1211 		}
1212 
1213 		g3_rotate_faraway_vertex(&sun_vex, &sun_pos);
1214 
1215 		if ( sun_vex.codes & (CC_BEHIND|CC_OFF_USER) ) {
1216 			continue;
1217 		}
1218 
1219 		if ( !(sun_vex.flags & PF_PROJECTED) ) {
1220 			g3_project_vertex(&sun_vex);
1221 		}
1222 
1223 		if ( sun_vex.flags & PF_OVERFLOW ) {
1224 			continue;
1225 		}
1226 
1227 		material mat_params;
1228 		material_set_unlit(&mat_params, bitmap_id, 0.999f, true, false);
1229 		g3_render_rect_screen_aligned_2d(&mat_params, &sun_vex, 0, 0.05f * Suns[idx].scale_x * local_scale);
1230 		Sun_drew++;
1231 
1232 // 		if ( !g3_draw_bitmap(&sun_vex, 0, 0.05f * Suns[idx].scale_x * local_scale, TMAP_FLAG_TEXTURED) )
1233 // 			Sun_drew++;
1234 	}
1235 }
1236 
1237 // draw a star's lens-flare
stars_draw_lens_flare(vertex * sun_vex,int sun_n)1238 void stars_draw_lens_flare(vertex *sun_vex, int sun_n)
1239 {
1240 	starfield_bitmap *bm;
1241 	int i,j;
1242 	float dx,dy;
1243 	vertex flare_vex = *sun_vex; //copy over to flare_vex to get all sorts of properties
1244 
1245 	Assert( sun_n < (int)Suns.size() );
1246 
1247 	if ( (sun_n >= (int)Suns.size()) || (sun_n < 0) ) {
1248 		return;
1249 	}
1250 
1251 	// get the instance
1252 	if (Suns[sun_n].star_bitmap_index < 0) {
1253 		return;
1254 	} else {
1255 		bm = &Sun_bitmaps[Suns[sun_n].star_bitmap_index];
1256 	}
1257 
1258 	if (!bm->flare)
1259 		return;
1260 
1261 	/* (dx,dy) is a 2d vector equal to two times the vector from the sun's
1262 	position to the center fo the screen meaning it is the vector to the
1263 	opposite position on the screen. */
1264 	dx = 2.0f*(i2fl(gr_screen.clip_right-gr_screen.clip_left)*0.5f - sun_vex->screen.xyw.x);
1265 	dy = 2.0f*(i2fl(gr_screen.clip_bottom-gr_screen.clip_top)*0.5f - sun_vex->screen.xyw.y);
1266 
1267 	for (j = 0; j < bm->n_flare_bitmaps; j++)
1268 	{
1269 		// if no bitmap then bail...
1270 		if (bm->flare_bitmaps[j].bitmap_id < 0)
1271 			continue;
1272 
1273 		//gr_set_bitmap(bm->flare_bitmaps[j].bitmap_id, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 0.999f);
1274 
1275 		for (i = 0; i < bm->n_flares; i++) {
1276 			// draw sorted by texture, to minimize texture changes. not the most efficient way, but better than non-sorted
1277 			if (bm->flare_infos[i].tex_num == j) {
1278 				flare_vex.screen.xyw.x = sun_vex->screen.xyw.x + dx * bm->flare_infos[i].pos;
1279 				flare_vex.screen.xyw.y = sun_vex->screen.xyw.y + dy * bm->flare_infos[i].pos;
1280 				//g3_draw_bitmap(&flare_vex, 0, 0.05f * bm->flare_infos[i].scale, TMAP_FLAG_TEXTURED);
1281 				material mat_params;
1282 				material_set_unlit(&mat_params, bm->flare_bitmaps[j].bitmap_id, 0.999f, true, false);
1283 				g3_render_rect_screen_aligned_2d(&mat_params, &flare_vex, 0, 0.05f * bm->flare_infos[i].scale);
1284 			}
1285 		}
1286 	}
1287 }
1288 
1289 // draw the corresponding glow for sun_n
stars_draw_sun_glow(int sun_n)1290 void stars_draw_sun_glow(int sun_n)
1291 {
1292 	starfield_bitmap *bm;
1293 	vec3d sun_pos, sun_dir;
1294 	vertex sun_vex;
1295 	float local_scale;
1296 
1297 	// sanity
1298 	//WMC - Dunno why this is getting hit...
1299 	//Assert( sun_n < (int)Suns.size() );
1300 
1301 	if ( (sun_n >= (int)Suns.size()) || (sun_n < 0) ) {
1302 		return;
1303 	}
1304 
1305 	// get the instance
1306 	if (Suns[sun_n].star_bitmap_index < 0)
1307 		return;
1308 
1309 	bm = &Sun_bitmaps[Suns[sun_n].star_bitmap_index];
1310 
1311 	// if no bitmap then bail...
1312 	if (bm->glow_bitmap < 0)
1313 		return;
1314 
1315 	memset( &sun_vex, 0, sizeof(vertex) );
1316 
1317 	// get sun pos
1318 	sun_pos = vmd_zero_vector;
1319 	sun_pos.xyz.y = 1.0f;
1320 	stars_get_sun_pos(sun_n, &sun_pos);
1321 
1322 	// get the direction
1323 	sun_dir = sun_pos;
1324 	vm_vec_normalize(&sun_dir);
1325 
1326 	// if supernova
1327 	if ( supernova_active() && (sun_n == 0) )
1328 		local_scale = 1.0f + (SUPERNOVA_SUN_SCALE * supernova_pct_complete());
1329 	else
1330 		local_scale = 1.0f;
1331 
1332 	// draw the sun itself, keep track of how many we drew
1333 	int bitmap_id = -1;
1334 	if (bm->glow_fps) {
1335 		//gr_set_bitmap(bm->glow_bitmap + ((timestamp() / (int)(bm->glow_fps)) % bm->glow_n_frames), GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 0.5f);
1336 		bitmap_id = bm->glow_bitmap + ((timestamp() / (int)(bm->glow_fps)) % bm->glow_n_frames);
1337 	} else {
1338 		//gr_set_bitmap(bm->glow_bitmap, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 0.5f);
1339 		bitmap_id = bm->glow_bitmap;
1340 	}
1341 
1342 	g3_rotate_faraway_vertex(&sun_vex, &sun_pos);
1343 	//int zbuff = gr_zbuffer_set(GR_ZBUFF_NONE);
1344 	//g3_draw_bitmap(&sun_vex, 0, 0.10f * Suns[sun_n].scale_x * local_scale, TMAP_FLAG_TEXTURED);
1345 	material mat_params;
1346 	material_set_unlit(&mat_params, bitmap_id, 0.5f, true, false);
1347 	g3_render_rect_screen_aligned_2d(&mat_params, &sun_vex, 0, 0.10f * Suns[sun_n].scale_x * local_scale);
1348 
1349 	if (bm->flare) {
1350 		vec3d light_dir;
1351 		vec3d local_light_dir;
1352 		light_get_global_dir(&light_dir, sun_n);
1353 		vm_vec_rotate(&local_light_dir, &light_dir, &Eye_matrix);
1354 		float dot=vm_vec_dot( &light_dir, &Eye_matrix.vec.fvec );
1355 		if (dot > 0.7f) // Only render the flares if the sun is reasonably near the center of the screen
1356 			stars_draw_lens_flare(&sun_vex, sun_n);
1357 	}
1358 
1359 	//gr_zbuffer_set(zbuff);
1360 }
1361 
stars_draw_bitmaps(int show_bitmaps)1362 void stars_draw_bitmaps(int show_bitmaps)
1363 {
1364 	GR_DEBUG_SCOPE("Draw Bitmaps");
1365 	TRACE_SCOPE(tracing::DrawBitmaps);
1366 
1367 	int idx;
1368 	int star_index;
1369 
1370 	// should we even be here?
1371 	if ( !show_bitmaps )
1372 		return;
1373 
1374 	// if we're in the nebula, don't render any backgrounds
1375 	if (The_mission.flags[Mission::Mission_Flags::Fullneb])
1376 		return;
1377 
1378 	// detail settings
1379 	if ( !Detail.planets_suns )
1380 		return;
1381 
1382 	gr_start_instance_matrix(&Eye_position, &vmd_identity_matrix);
1383 
1384 	int sb_instances = (int)Starfield_bitmap_instances.size();
1385 
1386 	for (idx = 0; idx < sb_instances; idx++) {
1387 		// lookup the info index
1388 		star_index = Starfield_bitmap_instances[idx].star_bitmap_index;
1389 
1390 		if (star_index < 0) {
1391 			continue;
1392 		}
1393 
1394 		// if no bitmap then bail...
1395 		if (Starfield_bitmaps[star_index].bitmap_id < 0) {
1396 			continue;
1397 		}
1398 
1399 		int bitmap_id;
1400 		bool blending = false;
1401 		float alpha = 1.0f;
1402 
1403 		if (Starfield_bitmaps[star_index].xparent) {
1404 			if (Starfield_bitmaps[star_index].fps) {
1405 				bitmap_id = Starfield_bitmaps[star_index].bitmap_id + ((timestamp() / (int)(Starfield_bitmaps[star_index].fps)) % Starfield_bitmaps[star_index].n_frames);
1406 			} else {
1407 				bitmap_id = Starfield_bitmaps[star_index].bitmap_id;
1408 			}
1409 		} else {
1410 			if (Starfield_bitmaps[star_index].fps) {
1411 				bitmap_id = Starfield_bitmaps[star_index].bitmap_id + ((timestamp() / (int)(Starfield_bitmaps[star_index].fps)) % Starfield_bitmaps[star_index].n_frames);
1412 				blending = true;
1413 				alpha = 0.9999f;
1414 			} else {
1415 				bitmap_id = Starfield_bitmaps[star_index].bitmap_id;
1416 				blending = true;
1417 				alpha = 0.9999f;
1418 			}
1419 		}
1420 
1421 		material material_params;
1422 		material_set_unlit(&material_params, bitmap_id, alpha, blending, false);
1423 		g3_render_primitives_textured(&material_params, Starfield_bitmap_instances[idx].verts, Starfield_bitmap_instances[idx].n_verts, PRIM_TYPE_TRIS, false);
1424 	}
1425 
1426 	gr_end_instance_matrix();
1427 }
1428 
1429 extern int Interp_subspace;
1430 extern float Interp_subspace_offset_u;
1431 extern float Interp_subspace_offset_u;
1432 extern float Interp_subspace_offset_v;
1433 
1434 float subspace_offset_u = 0.0f;
1435 float subspace_offset_u_inner = 0.0f;
1436 float subspace_offset_v = 0.0f;
1437 
1438 float subspace_u_speed = 0.07f;			// how fast u changes
1439 float subspace_v_speed = 0.05f;			// how fast v changes
1440 
1441 int Subspace_glow_bitmap = -1;
1442 
1443 float Subspace_glow_frame = 0.0f;
1444 float Subspace_glow_rate = 1.0f;
1445 
1446 
1447 //XSTR:OFF
1448 DCF(subspace_set,"Set parameters for subspace effect")
1449 {
1450 	SCP_string arg;
1451 	float value;
1452 
1453 	if (dc_optional_string_either("help", "--help")) {
1454 		dc_printf( "Usage: subspace [--status] <axis> <speed>\n");
1455 		dc_printf("[--status] -- Displays the current speeds for both axes\n");
1456 		dc_printf("<axis>  -- May be either 'u' or 'v', and corresponds to the texture axis\n");
1457 		dc_printf("<speed> -- is the speed along the axis that the texture is moved\n");
1458 		return;
1459 	}
1460 
1461 	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
1462 		dc_printf( "u: %.2f\n", subspace_u_speed );
1463 		dc_printf( "v: %.2f\n", subspace_v_speed );
1464 		return;
1465 	}
1466 
1467 	dc_stuff_string_white(arg);
1468 	if (arg == "u") {
1469 		dc_stuff_float(&value);
1470 
1471 		if ( value < 0.0f ) {
1472 			dc_printf("Error: speed must be non-negative");
1473 			return;
1474 		}
1475 		subspace_u_speed = value;
1476 
1477 	} else if (arg == "v") {
1478 		dc_stuff_float(&value);
1479 
1480 		if (value < 0.0f) {
1481 			dc_printf("Error: speed must be non-negative");
1482 			return;
1483 		}
1484 		subspace_v_speed = value;
1485 
1486 	} else {
1487 		dc_printf("Error: Unknown axis '%s'", arg.c_str());
1488 	}
1489 }
1490 //XSTR:ON
1491 
subspace_render()1492 void subspace_render()
1493 {
1494 	int framenum = 0;
1495 
1496 	if ( Subspace_model_inner == -1 )	{
1497 		Subspace_model_inner = model_load( "subspace_small.pof", 0, NULL );
1498 		Assert(Subspace_model_inner >= 0);
1499 	}
1500 
1501 	if ( Subspace_model_outer == -1 )	{
1502 		Subspace_model_outer = model_load( "subspace_big.pof", 0, NULL );
1503 		Assert(Subspace_model_outer >= 0);
1504 	}
1505 
1506 	if ( Subspace_glow_bitmap == -1 )	{
1507 		Subspace_glow_bitmap = bm_load( NOX("SunGlow01"));
1508 		Assert(Subspace_glow_bitmap >= 0);
1509 	}
1510 
1511 	if ( !Rendering_to_env ) {
1512 		Subspace_glow_frame += flFrametime * 1.0f;
1513 
1514 		float total_time = i2fl(NOISE_NUM_FRAMES) / 15.0f;
1515 
1516 		// Sanity checks
1517 		if ( Subspace_glow_frame < 0.0f )
1518 			Subspace_glow_frame = 0.0f;
1519 		if ( Subspace_glow_frame > 100.0f )
1520 			Subspace_glow_frame = 0.0f;
1521 
1522 		while ( Subspace_glow_frame > total_time ) {
1523 			Subspace_glow_frame -= total_time;
1524 		}
1525 
1526 		framenum = fl2i( (Subspace_glow_frame*NOISE_NUM_FRAMES) / total_time );
1527 
1528 		if ( framenum < 0 )
1529 			framenum = 0;
1530 		if ( framenum >= NOISE_NUM_FRAMES )
1531 			framenum = NOISE_NUM_FRAMES-1;
1532 
1533 		subspace_offset_u += flFrametime*subspace_u_speed;
1534 		if (subspace_offset_u > 1.0f ) {
1535 			subspace_offset_u -= 1.0f;
1536 		}
1537 
1538 		subspace_offset_u_inner += flFrametime*subspace_u_speed*3.0f;
1539 		if (subspace_offset_u > 1.0f ) {
1540 			subspace_offset_u -= 1.0f;
1541 		}
1542 
1543 		subspace_offset_v += flFrametime*subspace_v_speed;
1544 		if (subspace_offset_v > 1.0f ) {
1545 			subspace_offset_v -= 1.0f;
1546 		}
1547 	}
1548 
1549 
1550 	matrix tmp;
1551 	angles angs = vmd_zero_angles;
1552 
1553 	angs.b = subspace_offset_v * PI2;
1554 
1555 	vm_angles_2_matrix(&tmp,&angs);
1556 
1557 	int saved_gr_zbuffering = gr_zbuffer_get();
1558 
1559 	gr_zbuffer_set(GR_ZBUFF_NONE);
1560 
1561 	int render_flags = MR_NO_LIGHTING | MR_ALL_XPARENT;
1562 
1563 	Interp_subspace = 1;
1564 	Interp_subspace_offset_u = 1.0f - subspace_offset_u;
1565 	Interp_subspace_offset_v = 0.0f;
1566 
1567 	model_render_params render_info;
1568 	render_info.set_alpha(1.0f);
1569 	render_info.set_flags(render_flags);
1570 
1571 	gr_set_texture_panning(Interp_subspace_offset_v, Interp_subspace_offset_u, true);
1572 
1573 	model_render_immediate( &render_info, Subspace_model_outer, &tmp, &Eye_position);	//MR_NO_CORRECT|MR_SHOW_OUTLINE
1574 
1575 	gr_set_texture_panning(0, 0, false);
1576 
1577 	Interp_subspace = 1;
1578 	Interp_subspace_offset_u = 1.0f - subspace_offset_u_inner;
1579 	Interp_subspace_offset_v = 0.0f;
1580 
1581 	angs.b = -subspace_offset_v * PI2;
1582 
1583 	vm_angles_2_matrix(&tmp,&angs);
1584 
1585 	render_info.set_color(255, 255, 255);
1586 	render_info.set_alpha(1.0f);
1587 	render_info.set_flags(render_flags);
1588 
1589 	gr_set_texture_panning(Interp_subspace_offset_v, Interp_subspace_offset_u, true);
1590 
1591 	model_render_immediate( &render_info, Subspace_model_inner, &tmp, &Eye_position );	//MR_NO_CORRECT|MR_SHOW_OUTLINE
1592 
1593 	gr_set_texture_panning(0, 0, false);
1594 
1595 	//Render subspace glows here and not as thrusters - Valathil
1596 	vec3d glow_pos;
1597 	vertex glow_vex;
1598 
1599 	glow_pos.xyz.x = 0.0f;
1600 	glow_pos.xyz.y = 0.0f;
1601 	glow_pos.xyz.z = 100.0f;
1602 
1603 	//gr_set_bitmap(Subspace_glow_bitmap, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 1.0f);
1604 	material mat_params;
1605 	material_set_unlit(&mat_params, Subspace_glow_bitmap, 1.0f, true, false);
1606 
1607 	g3_rotate_faraway_vertex(&glow_vex, &glow_pos);
1608 	//g3_draw_bitmap(&glow_vex, 0, 17.0f + 0.5f * Noise[framenum], TMAP_FLAG_TEXTURED);
1609 	g3_render_rect_screen_aligned_2d(&mat_params, &glow_vex, 0, 17.0f + 0.5f * Noise[framenum]);
1610 
1611 	glow_pos.xyz.z = -100.0f;
1612 
1613 	g3_rotate_faraway_vertex(&glow_vex, &glow_pos);
1614 	//g3_draw_bitmap(&glow_vex, 0, 17.0f + 0.5f * Noise[framenum], TMAP_FLAG_TEXTURED);
1615 	g3_render_rect_screen_aligned_2d(&mat_params, &glow_vex, 0, 17.0f + 0.5f * Noise[framenum]);
1616 
1617 	Interp_subspace = 0;
1618 	gr_zbuffer_set(saved_gr_zbuffering);
1619 }
1620 
stars_draw_stars()1621 void stars_draw_stars()
1622 {
1623 	GR_DEBUG_SCOPE("Draw Starfield");
1624 	TRACE_SCOPE(tracing::DrawStarfield);
1625 
1626 	int i;
1627 	star *sp;
1628 	float dist = 0.0f;
1629 	float ratio;
1630 	vDist vDst;
1631 	vertex p1, p2;
1632 	int can_draw = 1;
1633 
1634 	if ( !last_stars_filled ) {
1635 		for (i = 0; i < Num_stars; i++) {
1636 			g3_rotate_faraway_vertex(&p2, &Stars[i].pos);
1637 			Stars[i].last_star_pos = p2.world;
1638 		}
1639 	}
1640 
1641 	int tmp_num_stars = 0;
1642 
1643 	tmp_num_stars = (Detail.num_stars * Num_stars) / MAX_DETAIL_LEVEL;
1644 	CLAMP(tmp_num_stars, 0, Num_stars);
1645 
1646 	auto path = graphics::paths::PathRenderer::instance();
1647 
1648 	path->saveState();
1649 	path->resetState();
1650 
1651 	path->beginFrame();
1652 
1653 	for (i = 0; i < tmp_num_stars; i++) {
1654 		sp = &Stars[i];
1655 
1656 		can_draw = 1;
1657 		memset(&p1, 0, sizeof(vertex));
1658 		memset(&p2, 0, sizeof(vertex));
1659 
1660 		// This makes a star look "proper" by not translating the
1661 		// point around the viewer's eye before rotation.  In other
1662 		// words, when the ship translates, the stars do not change.
1663 
1664 		g3_rotate_faraway_vertex(&p2, &sp->pos);
1665 		if ( p2.codes )	{
1666 			can_draw = 0;
1667 		} else {
1668 			g3_project_vertex(&p2);
1669 			if ( p2.flags & PF_OVERFLOW ) {
1670 				can_draw = 0;
1671 			}
1672 		}
1673 
1674 		if ( can_draw && (Star_flags & (STAR_FLAG_TAIL|STAR_FLAG_DIM)) ) {
1675 			dist = vm_vec_dist_quick( &sp->last_star_pos, &p2.world );
1676 
1677 			if ( dist > Star_max_length ) {
1678  				ratio = Star_max_length / dist;
1679 				dist = Star_max_length;
1680 			} else {
1681 				ratio = 1.0f;
1682 			}
1683 
1684 			ratio *= Star_amount;
1685 
1686 			vm_vec_sub(&p1.world, &sp->last_star_pos, &p2.world);
1687 			vm_vec_scale(&p1.world, ratio);
1688 			vm_vec_add2(&p1.world, &p2.world);
1689 
1690 			p1.flags = 0;	// not projected
1691 			g3_code_vertex( &p1 );
1692 
1693 			if ( p1.codes )	{
1694 				can_draw = 0;
1695 			} else {
1696 				g3_project_vertex(&p1);
1697 				if ( p1.flags & PF_OVERFLOW ) {
1698 					can_draw = 0;
1699 				}
1700 			}
1701 		}
1702 
1703 		sp->last_star_pos = p2.world;
1704 
1705 		if ( !can_draw )
1706 			continue;
1707 
1708 		vDst.x = fl2i(p1.screen.xyw.x) - fl2i(p2.screen.xyw.x);
1709 		vDst.y = fl2i(p1.screen.xyw.y) - fl2i(p2.screen.xyw.y);
1710 
1711 		if ( ((vDst.x * vDst.x) + (vDst.y * vDst.y)) <= 4 ) {
1712 			p1.screen.xyw.x = p2.screen.xyw.x + 1.0f;
1713 			p1.screen.xyw.y = p2.screen.xyw.y;
1714 		}
1715 		path->beginPath();
1716 
1717 		path->moveTo(p1.screen.xyw.x, p1.screen.xyw.y);
1718 		path->lineTo(p2.screen.xyw.x, p2.screen.xyw.y);
1719 
1720 		path->setStrokeColor(&sp->col);
1721 		path->stroke();
1722 	}
1723 	path->endFrame();
1724 
1725 	path->restoreState();
1726 }
1727 
stars_draw_motion_debris()1728 void stars_draw_motion_debris()
1729 {
1730 	GR_DEBUG_SCOPE("Draw motion debris");
1731 	TRACE_SCOPE(tracing::DrawMotionDebris);
1732 
1733 	if (Motion_debris_override)
1734 		return;
1735 
1736 	if (!Motion_debris_enabled) {
1737 		return;
1738 	}
1739 
1740 	for (motion_debris &mdebris : Motion_debris) {
1741 		float vdist = vm_vec_dist(&mdebris.pos, &Eye_position);
1742 
1743 		if ((vdist < MIN_DIST_RANGE) || (vdist > MAX_DIST_RANGE)) {
1744 			// if we just had a camera "cut" and should refresh the debris then generate in the sphere, else just on its surface
1745 			vm_vec_random_in_sphere(&mdebris.pos, &Eye_position, MAX_DIST_RANGE, !refresh_motion_debris);
1746 			vdist = vm_vec_dist(&mdebris.pos, &Eye_position);
1747 
1748 			mdebris.vclip = Random::next(MAX_DEBRIS_VCLIPS);	//rand()
1749 
1750 			// if we're in full neb mode
1751 			const float size_multiplier = i2fl(Random::next(4));
1752 			if((The_mission.flags[Mission::Mission_Flags::Fullneb]) && (Neb2_render_mode != NEB2_RENDER_NONE)) {
1753 				mdebris.size = size_multiplier * BASE_SIZE_NEB;
1754 			} else {
1755 				mdebris.size = size_multiplier * BASE_SIZE;
1756 			}
1757 		}
1758 
1759 		vertex pnt;
1760 		g3_rotate_vertex(&pnt, &mdebris.pos);
1761 
1762 		if (pnt.codes == 0) {
1763 			int frame = Missiontime / (DEBRIS_ROT_MIN + (1 % DEBRIS_ROT_RANGE) * DEBRIS_ROT_RANGE_SCALER);
1764 			frame %= Debris_vclips[mdebris.vclip].nframes;
1765 
1766 			float alpha;
1767 
1768 			if ( (The_mission.flags[Mission::Mission_Flags::Fullneb]) && (Neb2_render_mode != NEB2_RENDER_NONE) ) {
1769 				alpha = 0.3f;
1770 			} else {
1771 				alpha = 1.0f;
1772 			}
1773 
1774 			// scale alpha from 0 at max range to full at 60% range
1775 			alpha *= (vdist - MAX_DIST_RANGE) / -(MAX_DIST_RANGE * 0.6f);
1776 
1777 			g3_transfer_vertex(&pnt, &mdebris.pos);
1778 
1779 			batching_add_bitmap(Debris_vclips[mdebris.vclip].bm + frame, &pnt, 0, mdebris.size, alpha);
1780 		}
1781 	}
1782 
1783 	if (refresh_motion_debris)
1784 		refresh_motion_debris = false;
1785 }
1786 
stars_draw(int show_stars,int show_suns,int,int show_subspace,int env,bool in_mission)1787 void stars_draw(int show_stars, int show_suns, int  /*show_nebulas*/, int show_subspace, int env, bool in_mission)
1788 {
1789 	GR_DEBUG_SCOPE("Draw Stars");
1790 	TRACE_SCOPE(tracing::DrawStars);
1791 
1792 	int gr_zbuffering_save = gr_zbuffer_get();
1793 	gr_zbuffer_set(GR_ZBUFF_NONE);
1794 
1795 	Rendering_to_env = env;
1796 
1797 	if (show_subspace)
1798 		subspace_render();
1799 
1800 	if (Num_stars >= MAX_STARS)
1801 		Num_stars = MAX_STARS;
1802 
1803 #ifdef TIME_STAR_CODE
1804 	fix xt1, xt2;
1805 	xt1 = timer_get_fixed_seconds();
1806 #endif
1807 
1808 	// draw background stuff
1809 	if ( show_stars ) {
1810 		// semi-hack, do we don't fog the background
1811 		int neb_save = Neb2_render_mode;
1812 		Neb2_render_mode = NEB2_RENDER_NONE;
1813 		stars_draw_background();
1814 		Neb2_render_mode = neb_save;
1815 	}
1816 	else if ( !show_subspace ) // don't render the background pof when rendering subspace
1817 	{
1818 		stars_draw_background();
1819 	}
1820 
1821 	if ( !env && show_stars && (Nmodel_num < 0) && (Game_detail_flags & DETAIL_FLAG_STARS) && !(The_mission.flags[Mission::Mission_Flags::Fullneb]) && (supernova_active() < 3) ) {
1822 		stars_draw_stars();
1823 	}
1824 
1825 	last_stars_filled = 1;
1826 
1827 #ifdef TIME_STAR_CODE
1828 	xt2 = timer_get_fixed_seconds();
1829 	mprintf(( "Stars: %d\n", xt2-xt1 ));
1830 #endif
1831 
1832 	if ( !Rendering_to_env && (Game_detail_flags & DETAIL_FLAG_MOTION) && (!Fred_running) && (supernova_active() < 3) && in_mission)	{
1833 		stars_draw_motion_debris();
1834 	}
1835 
1836 	//if we're not drawing them, quit here
1837 	if (show_suns) {
1838 		stars_draw_sun( show_suns );
1839 		stars_draw_bitmaps( show_suns );
1840 	}
1841 
1842 	gr_zbuffer_set( gr_zbuffering_save );
1843 	Rendering_to_env = 0;
1844 }
1845 
stars_preload_background(const char * token)1846 void stars_preload_background(const char *token)
1847 {
1848 	if (!token)
1849 		return;
1850 
1851 	// we can only preload raw numbers, but that's probably sufficient
1852 	if (!can_construe_as_integer(token))
1853 		return;
1854 
1855 	int background_idx = atoi(token);
1856 
1857 	// human/computer offset
1858 	background_idx--;
1859 
1860 	// store it for now
1861 	Preload_background_indexes.push_back(background_idx);
1862 }
1863 
stars_preload_background(int background_idx)1864 void stars_preload_background(int background_idx)
1865 {
1866 	// range check
1867 	if (background_idx < 0 || background_idx >= (int)Backgrounds.size())
1868 		return;
1869 
1870 	// preload all the stuff for this background
1871 	for (auto &bgsun : Backgrounds[background_idx].suns)
1872 		stars_preload_sun_bitmap(bgsun.filename);
1873 	for (auto &bitmap : Backgrounds[background_idx].bitmaps)
1874 		stars_preload_background_bitmap(bitmap.filename);
1875 }
1876 
stars_preload_sun_bitmap(const char * fname)1877 void stars_preload_sun_bitmap(const char *fname)
1878 {
1879 	int idx;
1880 
1881 	if (fname == NULL)
1882 		return;
1883 
1884 	idx = stars_find_sun(fname);
1885 
1886 	if (idx == -1) {
1887 		return;
1888 	}
1889 
1890 	Sun_bitmaps[idx].preload = 1;
1891 }
1892 
stars_preload_background_bitmap(const char * fname)1893 void stars_preload_background_bitmap(const char *fname)
1894 {
1895 	int idx;
1896 
1897 	if (fname == NULL)
1898 		return;
1899 
1900 	idx = stars_find_bitmap(fname);
1901 
1902 	if (idx == -1) {
1903 		return;
1904 	}
1905 
1906 	Starfield_bitmaps[idx].preload = 1;
1907 }
1908 
stars_page_in()1909 void stars_page_in()
1910 {
1911 	int idx, i;
1912 	starfield_bitmap_instance *sbi;
1913 	starfield_bitmap *sb;
1914 
1915 	// Initialize the subspace stuff
1916 
1917 	if ( Game_subspace_effect )	{
1918 		Subspace_model_inner = model_load( "subspace_small.pof", 0, NULL );
1919 		Assert(Subspace_model_inner >= 0);
1920 		Subspace_model_outer = model_load( "subspace_big.pof", 0, NULL );
1921 		Assert(Subspace_model_outer >= 0);
1922 
1923 		polymodel *pm;
1924 
1925 		pm = model_get(Subspace_model_inner);
1926 
1927 		nprintf(( "Paging", "Paging in textures for subspace effect.\n" ));
1928 
1929 		for (idx = 0; idx < pm->n_textures; idx++) {
1930 			pm->maps[idx].PageIn();
1931 		}
1932 
1933 		pm = model_get(Subspace_model_outer);
1934 
1935 		nprintf(( "Paging", "Paging in textures for subspace effect.\n" ));
1936 
1937 		for (idx = 0; idx < pm->n_textures; idx++) {
1938 			pm->maps[idx].PageIn();
1939 		}
1940 
1941 		if (Subspace_glow_bitmap < 0) {
1942 			Subspace_glow_bitmap = bm_load( NOX("SunGlow01"));
1943 		}
1944 
1945 		bm_page_in_xparent_texture(Subspace_glow_bitmap);
1946 	} else {
1947 		Subspace_model_inner = -1;
1948 		Subspace_model_outer = -1;
1949 
1950 		if (Subspace_glow_bitmap > 0) {
1951 			bm_release(Subspace_glow_bitmap);
1952 			Subspace_glow_bitmap = -1;
1953 		}
1954 	}
1955 
1956 	// extra SEXP related checks to preload anything that might get used from there
1957 	for (idx = 0; idx < (int)Starfield_bitmaps.size(); idx++) {
1958 		sb = &Starfield_bitmaps[idx];
1959 
1960 		if (sb->used_this_level)
1961 			continue;
1962 
1963 		if (sb->preload) {
1964 			if (sb->bitmap_id < 0) {
1965 				sb->bitmap_id = bm_load(sb->filename);
1966 
1967 				// maybe didn't load a static image so try for an animated one
1968 				if (sb->bitmap_id < 0) {
1969 					sb->bitmap_id = bm_load_animation(sb->filename, &sb->n_frames, &sb->fps, nullptr, nullptr, true);
1970 
1971 					if (sb->bitmap_id < 0) {
1972 						Warning(LOCATION, "Unable to load starfield bitmap: '%s'!\n", sb->filename);
1973 					}
1974 				}
1975 			}
1976 
1977 			// this happens whether it loaded properly or not, no harm should come from it
1978 			if (sb->xparent) {
1979 				bm_page_in_xparent_texture(sb->bitmap_id);
1980 			} else {
1981 				bm_page_in_texture(sb->bitmap_id);
1982 			}
1983 
1984 			sb->used_this_level++;
1985 		}
1986 	}
1987 
1988 	for (idx = 0; idx < (int)Sun_bitmaps.size(); idx++) {
1989 		sb = &Sun_bitmaps[idx];
1990 
1991 		if (sb->used_this_level)
1992 			continue;
1993 
1994 		if (sb->preload) {
1995 			// normal bitmap
1996 			if (sb->bitmap_id < 0) {
1997 				sb->bitmap_id = bm_load(sb->filename);
1998 
1999 				// maybe didn't load a static image so try for an animated one
2000 				if (sb->bitmap_id < 0) {
2001 					sb->bitmap_id = bm_load_animation(sb->filename, &sb->n_frames, &sb->fps, nullptr, nullptr, true);
2002 
2003 					if (sb->bitmap_id < 0) {
2004 						Warning(LOCATION, "Unable to load sun bitmap: '%s'!\n", sb->filename);
2005 					}
2006 				}
2007 			}
2008 
2009 			// glow bitmap
2010 			if (sb->glow_bitmap < 0) {
2011 				sb->glow_bitmap = bm_load(sb->glow_filename);
2012 
2013 				// maybe didn't load a static image so try for an animated one
2014 				if (sb->glow_bitmap < 0) {
2015 					sb->glow_bitmap = bm_load_animation(sb->glow_filename, &sb->glow_n_frames, &sb->glow_fps, nullptr, nullptr, true);
2016 
2017 					if (sb->glow_bitmap < 0) {
2018 						Warning(LOCATION, "Unable to load sun glow bitmap: '%s'!\n", sb->glow_filename);
2019 					}
2020 				}
2021 			}
2022 
2023 			if (sb->flare) {
2024 				for (i = 0; i < MAX_FLARE_BMP; i++) {
2025 					if ( !strlen(sb->flare_bitmaps[i].filename) )
2026 						continue;
2027 
2028 					if (sb->flare_bitmaps[i].bitmap_id < 0) {
2029 						sb->flare_bitmaps[i].bitmap_id = bm_load(sb->flare_bitmaps[i].filename);
2030 
2031 						if (sb->flare_bitmaps[i].bitmap_id < 0) {
2032 							Warning(LOCATION, "Unable to load sun flare bitmap: '%s'!\n", sb->flare_bitmaps[i].filename);
2033 							continue;
2034 						}
2035 					}
2036 
2037 					bm_page_in_texture(sb->flare_bitmaps[i].bitmap_id);
2038 				}
2039 			}
2040 
2041 			bm_page_in_texture(sb->bitmap_id);
2042 			bm_page_in_texture(sb->glow_bitmap);
2043 
2044 			sb->used_this_level++;
2045 		}
2046 	}
2047 
2048 	// load and page in needed starfield bitmaps
2049 	for (idx = 0; idx < (int)Starfield_bitmap_instances.size(); idx++) {
2050 		sbi = &Starfield_bitmap_instances[idx];
2051 
2052 		if (sbi->star_bitmap_index < 0)
2053 			continue;
2054 
2055 		sb = &Starfield_bitmaps[sbi->star_bitmap_index];
2056 
2057 		if (sb->used_this_level)
2058 			continue;
2059 
2060 		if (sb->bitmap_id < 0 ) {
2061 			sb->bitmap_id = bm_load(sb->filename);
2062 
2063 			// maybe didn't load a static image so try for an animated one
2064 			if (sb->bitmap_id < 0) {
2065 				sb->bitmap_id = bm_load_animation(sb->filename, &sb->n_frames, &sb->fps, nullptr, nullptr, true);
2066 
2067 				if (sb->bitmap_id < 0) {
2068 					Warning(LOCATION, "Unable to load starfield bitmap: '%s'!\n", sb->filename);
2069 				}
2070 			}
2071 		}
2072 
2073 		// this happens whether it loaded properly or not, no harm should come from it
2074 		if (sb->xparent) {
2075 			bm_page_in_xparent_texture(sb->bitmap_id);
2076 		} else {
2077 			bm_page_in_texture(sb->bitmap_id);
2078 		}
2079 
2080 		sb->used_this_level++;
2081 	}
2082 
2083 	// now for sun bitmaps and glows
2084 	for (idx = 0; idx < (int)Suns.size(); idx++) {
2085 		sbi = &Suns[idx];
2086 
2087 		if (sbi->star_bitmap_index < 0)
2088 			continue;
2089 
2090 		sb = &Sun_bitmaps[sbi->star_bitmap_index];
2091 
2092 		if (sb->used_this_level)
2093 			continue;
2094 
2095 		// normal bitmap
2096 		if (sb->bitmap_id < 0) {
2097 			sb->bitmap_id = bm_load(sb->filename);
2098 
2099 			// maybe didn't load a static image so try for an animated one
2100 			if (sb->bitmap_id < 0) {
2101 				sb->bitmap_id = bm_load_animation(sb->filename, &sb->n_frames, &sb->fps, nullptr, nullptr, true);
2102 
2103 				if (sb->bitmap_id < 0) {
2104 					Warning(LOCATION, "Unable to load sun bitmap: '%s'!\n", sb->filename);
2105 				}
2106 			}
2107 		}
2108 
2109 		// glow bitmap
2110 		if (sb->glow_bitmap < 0) {
2111 			sb->glow_bitmap = bm_load(sb->glow_filename);
2112 
2113 			// maybe didn't load a static image so try for an animated one
2114 			if (sb->glow_bitmap < 0) {
2115 				sb->glow_bitmap = bm_load_animation(sb->glow_filename, &sb->glow_n_frames, &sb->glow_fps, nullptr, nullptr, true);
2116 
2117 				if (sb->glow_bitmap < 0) {
2118 					Warning(LOCATION, "Unable to load sun glow bitmap: '%s'!\n", sb->glow_filename);
2119 				}
2120 			}
2121 		}
2122 
2123 		if (sb->flare) {
2124 			for (i = 0; i < MAX_FLARE_BMP; i++) {
2125 				if ( !strlen(sb->flare_bitmaps[i].filename) )
2126 					continue;
2127 
2128 				if (sb->flare_bitmaps[i].bitmap_id < 0) {
2129 					sb->flare_bitmaps[i].bitmap_id = bm_load(sb->flare_bitmaps[i].filename);
2130 
2131 					if (sb->flare_bitmaps[i].bitmap_id < 0) {
2132 						Warning(LOCATION, "Unable to load sun flare bitmap: '%s'!\n", sb->flare_bitmaps[i].filename);
2133 						continue;
2134 					}
2135 				}
2136 
2137 				bm_page_in_texture(sb->flare_bitmaps[i].bitmap_id);
2138 			}
2139 		}
2140 
2141 		bm_page_in_texture(sb->bitmap_id);
2142 		bm_page_in_texture(sb->glow_bitmap);
2143 
2144 		sb->used_this_level++;
2145 	}
2146 
2147 
2148 	if (!Motion_debris_enabled)
2149 		return;
2150 
2151 	for (idx = 0; idx < MAX_DEBRIS_VCLIPS; idx++) {
2152 		bm_page_in_xparent_texture(Debris_vclips[idx].bm, Debris_vclips[idx].nframes);
2153 	}
2154 }
2155 
2156 // background nebula models and planets
stars_draw_background()2157 void stars_draw_background()
2158 {
2159 	GR_DEBUG_SCOPE("Draw Background");
2160 	TRACE_SCOPE(tracing::DrawBackground);
2161 
2162 	if (Nmodel_num < 0)
2163 		return;
2164 
2165 	model_render_params render_info;
2166 
2167 	if (Nmodel_bitmap >= 0) {
2168 		render_info.set_forced_bitmap(Nmodel_bitmap);
2169 	}
2170 
2171 	// draw the model at the player's eye with no z-buffering
2172 	render_info.set_alpha(1.0f);
2173 	render_info.set_flags(Nmodel_flags | MR_SKYBOX);
2174 
2175 	model_render_immediate(&render_info, Nmodel_num, &Nmodel_orient, &Eye_position, MODEL_RENDER_ALL, false);
2176 }
2177 
2178 // call this to set a specific model as the background model
stars_set_background_model(const char * model_name,const char * texture_name,int flags)2179 void stars_set_background_model(const char *model_name, const char *texture_name, int flags)
2180 {
2181 	if (Nmodel_bitmap >= 0) {
2182 		bm_unload(Nmodel_bitmap);
2183 		Nmodel_bitmap = -1;
2184 	}
2185 
2186 	if (Nmodel_num >= 0) {
2187 		model_unload(Nmodel_num);
2188 		Nmodel_num = -1;
2189 	}
2190 
2191 	if (Nmodel_instance_num >= 0) {
2192 		model_delete_instance(Nmodel_instance_num);
2193 		Nmodel_instance_num = -1;
2194 	}
2195 
2196 	Nmodel_flags = flags;
2197 
2198 	if ( (model_name == NULL) || (*model_name == '\0') )
2199 		return;
2200 
2201 	Nmodel_num = model_load(model_name, 0, NULL, -1);
2202 	Nmodel_bitmap = bm_load(texture_name);
2203 
2204 	if (Nmodel_num >= 0) {
2205 		model_page_in_textures(Nmodel_num);
2206 
2207 		if (model_get(Nmodel_num)->flags & PM_FLAG_HAS_INTRINSIC_ROTATE) {
2208 			Nmodel_instance_num = model_create_instance(false, Nmodel_num);
2209 		}
2210 	}
2211 
2212 	// Since we have a new skybox we need to rerender the environment map
2213 	stars_invalidate_environment_map();
2214 }
2215 
2216 // call this to set a specific orientation for the background
stars_set_background_orientation(const matrix * orient)2217 void stars_set_background_orientation(const matrix *orient)
2218 {
2219 	if (orient == NULL) {
2220 		vm_set_identity(&Nmodel_orient);
2221 	} else {
2222 		Nmodel_orient = *orient;
2223 	}
2224 }
2225 
2226 // lookup a starfield bitmap, return index or -1 on fail
stars_find_bitmap(const char * name)2227 int stars_find_bitmap(const char *name)
2228 {
2229 	int idx;
2230 
2231 	if (name == NULL)
2232 		return -1;
2233 
2234 	// lookup
2235 	for (idx = 0; idx < (int)Starfield_bitmaps.size(); idx++) {
2236 		if ( !stricmp(name, Starfield_bitmaps[idx].filename) ) {
2237 			return idx;
2238 		}
2239 	}
2240 
2241 	// not found
2242 	return -1;
2243 }
2244 
2245 // lookup a sun by bitmap filename, return index or -1 on fail
stars_find_sun(const char * name)2246 int stars_find_sun(const char *name)
2247 {
2248 	int idx;
2249 
2250 	if (name == NULL)
2251 		return -1;
2252 
2253 	// lookup
2254 	for (idx = 0; idx < (int)Sun_bitmaps.size(); idx++) {
2255 		if ( !stricmp(name, Sun_bitmaps[idx].filename) ) {
2256 			return idx;
2257 		}
2258 	}
2259 
2260 	// not found
2261 	return -1;
2262 }
2263 
2264 // add an instance for a sun (something actually used in a mission)
2265 // NOTE that we assume a duplicate is ok here
stars_add_sun_entry(starfield_list_entry * sun_ptr)2266 int stars_add_sun_entry(starfield_list_entry *sun_ptr)
2267 {
2268 	int idx, i;
2269 	starfield_bitmap_instance sbi;
2270 
2271 	Assert(sun_ptr != NULL);
2272 
2273 	// copy information
2274 	sbi.ang.p = sun_ptr->ang.p;
2275 	sbi.ang.b = sun_ptr->ang.b;
2276 	sbi.ang.h = sun_ptr->ang.h;
2277 	sbi.scale_x = sun_ptr->scale_x;
2278 	sbi.scale_y = sun_ptr->scale_y;
2279 	sbi.div_x = sun_ptr->div_x;
2280 	sbi.div_y = sun_ptr->div_y;
2281 
2282 	idx = stars_find_sun(sun_ptr->filename);
2283 
2284 	if (idx == -1) {
2285 		if (!Fred_running) {
2286 			Warning(LOCATION, "Trying to add a sun '%s' that does not exist in stars.tbl!", sun_ptr->filename);
2287 		}
2288 		return -1;
2289 	}
2290 
2291 	sbi.star_bitmap_index = idx;
2292 
2293 	// make sure any needed bitmaps are loaded
2294 	if (Sun_bitmaps[idx].bitmap_id < 0) {
2295 		// normal bitmap
2296 		Sun_bitmaps[idx].bitmap_id = bm_load(Sun_bitmaps[idx].filename);
2297 
2298 			// maybe didn't load a static image so try for an animated one
2299 		if (Sun_bitmaps[idx].bitmap_id < 0) {
2300 			Sun_bitmaps[idx].bitmap_id = bm_load_animation(Sun_bitmaps[idx].filename, &Sun_bitmaps[idx].n_frames, &Sun_bitmaps[idx].fps, nullptr, nullptr, true);
2301 
2302 			if (Sun_bitmaps[idx].bitmap_id < 0) {
2303 				// failed
2304 				return -1;
2305 			}
2306 		}
2307 
2308 		// glow bitmap
2309 		if (Sun_bitmaps[idx].glow_bitmap < 0) {
2310 			Sun_bitmaps[idx].glow_bitmap = bm_load(Sun_bitmaps[idx].glow_filename);
2311 
2312 			// maybe didn't load a static image so try for an animated one
2313 			if (Sun_bitmaps[idx].glow_bitmap < 0) {
2314 				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);
2315 
2316 				if (Sun_bitmaps[idx].glow_bitmap < 0) {
2317 					Warning(LOCATION, "Unable to load sun glow bitmap: '%s'!\n", Sun_bitmaps[idx].glow_filename);
2318 				}
2319 			}
2320 		}
2321 
2322 		if (Sun_bitmaps[idx].flare) {
2323 			for (i = 0; i < MAX_FLARE_BMP; i++) {
2324 				flare_bitmap* fbp = &Sun_bitmaps[idx].flare_bitmaps[i];
2325 				if ( !strlen(fbp->filename) )
2326 					continue;
2327 
2328 				if (fbp->bitmap_id < 0) {
2329 					fbp->bitmap_id = bm_load(fbp->filename);
2330 
2331 					if (fbp->bitmap_id < 0) {
2332 						Warning(LOCATION, "Unable to load sun flare bitmap: '%s'!\n", Sun_bitmaps[idx].flare_bitmaps[i].filename);
2333 						continue;
2334 					}
2335 				}
2336 			}
2337 		}
2338 	}
2339 
2340 	// The background changed so we need to invalidate the environment map
2341 	stars_invalidate_environment_map();
2342 
2343 	// now check if we can make use of a previously discarded instance entry
2344 	// this should never happen with FRED
2345 	if ( !Fred_running ) {
2346 		for (i = 0; i < (int)Suns.size(); i++) {
2347 			if ( Suns[i].star_bitmap_index < 0 ) {
2348 				Suns[i] = sbi;
2349 				return i;
2350 			}
2351 		}
2352 	}
2353 
2354 	// ... or add a new one
2355 	Suns.push_back(sbi);
2356 
2357 	return (int)(Suns.size() - 1);
2358 }
2359 
2360 // add an instance for a starfield bitmap (something actually used in a mission)
2361 // NOTE that we assume a duplicate is ok here
stars_add_bitmap_entry(starfield_list_entry * sle)2362 int stars_add_bitmap_entry(starfield_list_entry *sle)
2363 {
2364 	int idx;
2365 	starfield_bitmap_instance sbi;
2366 
2367 	Assert(sle != NULL);
2368 
2369 	// copy information
2370 	sbi.ang.p = sle->ang.p;
2371 	sbi.ang.b = sle->ang.b;
2372 	sbi.ang.h = sle->ang.h;
2373 	sbi.scale_x = sle->scale_x;
2374 	sbi.scale_y = sle->scale_y;
2375 	sbi.div_x = sle->div_x;
2376 	sbi.div_y = sle->div_y;
2377 
2378 	idx = stars_find_bitmap(sle->filename);
2379 
2380 	if (idx == -1) {
2381 		if (!Fred_running) {
2382 			Warning(LOCATION, "Trying to add a bitmap '%s' that does not exist in stars.tbl!", sle->filename);
2383 		}
2384 		return -1;
2385 	}
2386 
2387 	sbi.star_bitmap_index = idx;
2388 
2389 	// make sure any needed bitmaps are loaded
2390 	if (Starfield_bitmaps[idx].bitmap_id < 0) {
2391 		Starfield_bitmaps[idx].bitmap_id = bm_load(Starfield_bitmaps[idx].filename);
2392 
2393 		// maybe didn't load a static image so try for an animated one
2394 		if (Starfield_bitmaps[idx].bitmap_id < 0) {
2395 			Starfield_bitmaps[idx].bitmap_id = bm_load_animation(Starfield_bitmaps[idx].filename, &Starfield_bitmaps[idx].n_frames, &Starfield_bitmaps[idx].fps, nullptr, nullptr, true);
2396 
2397 			if (Starfield_bitmaps[idx].bitmap_id < 0) {
2398 				// failed
2399 				return -1;
2400 			}
2401 		}
2402 	}
2403 
2404 	// The background changed so we need to invalidate the environment map
2405 	stars_invalidate_environment_map();
2406 
2407 	// now check if we can make use of a previously discarded instance entry
2408 	for (int i = 0; i < (int)Starfield_bitmap_instances.size(); i++) {
2409 		if ( Starfield_bitmap_instances[i].star_bitmap_index < 0 ) {
2410 			// starfield_update_index_buffers(i, 0);
2411 			Starfield_bitmap_instances[i] = sbi;
2412 			starfield_create_bitmap_buffer(i);
2413 			return i;
2414 		}
2415 	}
2416 
2417 	// ... or add a new one
2418 	Starfield_bitmap_instances.push_back(sbi);
2419 	starfield_create_bitmap_buffer((int)(Starfield_bitmap_instances.size() - 1));
2420 
2421 	return (int)(Starfield_bitmap_instances.size() - 1);
2422 }
2423 
2424 // get the number of entries that each vector contains
2425 // "is_a_sun" will get sun instance counts, otherwise it gets normal starfield bitmap instance counts
2426 // "bitmap_count" will get number of starfield_bitmap entries rather than starfield_bitmap_instance entries
stars_get_num_entries(bool is_a_sun,bool bitmap_count)2427 int stars_get_num_entries(bool is_a_sun, bool bitmap_count)
2428 {
2429 	// try for instance counts first
2430 	if (!bitmap_count) {
2431 		if (is_a_sun) {
2432 			return (int)Suns.size();
2433 		} else {
2434 			return (int)Starfield_bitmap_instances.size();
2435 		}
2436 	}
2437 	// looks like we want bitmap counts (probably only FRED uses this)
2438 	else {
2439 		if (is_a_sun) {
2440 			return (int)Sun_bitmaps.size();
2441 		} else {
2442 			return (int)Starfield_bitmaps.size();
2443 		}
2444 	}
2445 }
2446 
2447 
2448 // get a starfield_bitmap entry providing only an instance
stars_get_bitmap_entry(int index,bool is_a_sun)2449 starfield_bitmap *stars_get_bitmap_entry(int index, bool is_a_sun)
2450 {
2451 	int max_index = (is_a_sun) ? (int)Suns.size() : (int)Starfield_bitmap_instances.size();
2452 
2453 	//WMC - Commented out because it keeps happening, and I don't know what this means.
2454 	//Assert( (index >= 0) && (index < max_index) );
2455 
2456 	if ( (index < 0) || (index >= max_index) )
2457 		return NULL;
2458 
2459 	if (is_a_sun && (Suns[index].star_bitmap_index >= 0)) {
2460 		return &Sun_bitmaps[Suns[index].star_bitmap_index];
2461 	} else if (!is_a_sun && (Starfield_bitmap_instances[index].star_bitmap_index >= 0)) {
2462 		return &Starfield_bitmaps[Starfield_bitmap_instances[index].star_bitmap_index];
2463 	}
2464 
2465 	return NULL;
2466 }
2467 
stars_sun_has_glare(int index)2468 bool stars_sun_has_glare(int index)
2469 {
2470 	starfield_bitmap *sb = stars_get_bitmap_entry(index, true);
2471 	return (sb && sb->glare);
2472 }
2473 
2474 // set an instace to not render
stars_mark_instance_unused(int index,bool is_a_sun)2475 void stars_mark_instance_unused(int index, bool is_a_sun)
2476 {
2477 	int max_index = (is_a_sun) ? (int)Suns.size() : (int)Starfield_bitmap_instances.size();
2478 
2479 	Assert( (index >= 0) && (index < max_index) );
2480 
2481 	if ( (index < 0) || (index >= max_index) )
2482 		return;
2483 
2484 	if (is_a_sun) {
2485 		Suns[index].star_bitmap_index =  -1;
2486 	} else {
2487 		Starfield_bitmap_instances[index].star_bitmap_index = -1;
2488 	}
2489 
2490 	if ( !is_a_sun ) {
2491 		delete [] Starfield_bitmap_instances[index].verts;
2492 		Starfield_bitmap_instances[index].verts = NULL;
2493 	}
2494 
2495 	// The background changed so we need to invalidate the environment map
2496 	stars_invalidate_environment_map();
2497 }
2498 
2499 // retrieves the name from starfield_bitmap for the instance index
2500 // NOTE: it's unsafe to return NULL here so use <none> for invalid entries
stars_get_name_from_instance(int index,bool is_a_sun)2501 const char *stars_get_name_from_instance(int index, bool is_a_sun)
2502 {
2503 	int max_index = (is_a_sun) ? (int)Suns.size() : (int)Starfield_bitmap_instances.size();
2504 
2505 	Assert( (index >= 0) && (index < max_index) );
2506 
2507 	if ( (index < 0) || (index >= max_index) )
2508 		return NOX("<none>");
2509 
2510 	if (is_a_sun && (Suns[index].star_bitmap_index >= 0)) {
2511 		return Sun_bitmaps[Suns[index].star_bitmap_index].filename;
2512 	} else if (!is_a_sun && (Starfield_bitmap_instances[index].star_bitmap_index >= 0)) {
2513 		return Starfield_bitmaps[Starfield_bitmap_instances[index].star_bitmap_index].filename;
2514 	}
2515 
2516 	return NOX("<none>");
2517 }
2518 
2519 // WMC/Goober5000
stars_set_nebula(bool activate,float range)2520 void stars_set_nebula(bool activate, float range)
2521 {
2522     The_mission.flags.set(Mission::Mission_Flags::Fullneb, activate);
2523 
2524     if (activate)
2525 	{
2526 		Toggle_text_alpha = TOGGLE_TEXT_NEBULA_ALPHA;
2527 		HUD_contrast = 1;
2528 
2529 		Neb2_render_mode = NEB2_RENDER_HTL;
2530 		Neb2_awacs = range;
2531 		if (Neb2_awacs < 0.1f)
2532 			Neb2_awacs = 3000.0f;	// this is also the default in the background editor
2533 
2534 		// this function is not currently called in FRED, but this would be needed for FRED support
2535 		if (Fred_running)
2536 		{
2537 			Neb2_render_mode = NEB2_RENDER_POF;
2538 			stars_set_background_model(BACKGROUND_MODEL_FILENAME, Neb2_texture_name);
2539 			stars_set_background_orientation();
2540 		}
2541 	}
2542 	else
2543 	{
2544 		Toggle_text_alpha = TOGGLE_TEXT_NORMAL_ALPHA;
2545 		HUD_contrast = 0;
2546 
2547 		Neb2_render_mode = NEB2_RENDER_NONE;
2548 		Neb2_awacs = -1.0f;
2549 	}
2550 
2551 	// (DahBlount)
2552 	// This needs to be done regardless of whether we're in nebula or not
2553 	// Should ensure that we're actually loading the needed anims into BMPMan
2554 	stars_load_debris(static_cast<int>(activate));
2555 
2556 	// We need to reload the environment map now
2557 	stars_invalidate_environment_map();
2558 }
2559 
2560 // retrieves the name from starfield_bitmap, really only used by FRED2
2561 // NOTE: it is unsafe to return NULL here, but because that's bad anyway it really shouldn't happen, so we do return NULL.
stars_get_name_FRED(int index,bool is_a_sun)2562 const char *stars_get_name_FRED(int index, bool is_a_sun)
2563 {
2564 	if (!Fred_running)
2565 		return NULL;
2566 
2567 	int max_index = (is_a_sun) ? (int)Sun_bitmaps.size() : (int)Starfield_bitmaps.size();
2568 
2569 	Assert( (index >= 0) && (index < max_index) );
2570 
2571 	if ( (index < 0) || (index >= max_index) )
2572 		return NULL;
2573 
2574 	if (is_a_sun) {
2575 		return Sun_bitmaps[index].filename;
2576 	} else {
2577 		return Starfield_bitmaps[index].filename;
2578 	}
2579 }
2580 
2581 // modify an existing starfield bitmap instance, or add a new one if needed
stars_modify_entry_FRED(int index,const char * name,starfield_list_entry * sbi_new,bool is_a_sun)2582 void stars_modify_entry_FRED(int index, const char *name, starfield_list_entry *sbi_new, bool is_a_sun)
2583 {
2584 	if (!Fred_running)
2585 		return;
2586 
2587 	starfield_bitmap_instance sbi;
2588 	int idx;
2589 	int add_new = index > ((is_a_sun) ? (int)Sun_bitmaps.size() : (int)Starfield_bitmaps.size());
2590 
2591 	Assert( index >= 0 );
2592 	Assert( sbi_new != NULL );
2593 
2594     // copy information
2595     sbi.ang.p = sbi_new->ang.p;
2596 	sbi.ang.b = sbi_new->ang.b;
2597 	sbi.ang.h = sbi_new->ang.h;
2598 	sbi.scale_x = sbi_new->scale_x;
2599 	sbi.scale_y = sbi_new->scale_y;
2600 	sbi.div_x = sbi_new->div_x;
2601 	sbi.div_y = sbi_new->div_y;
2602 
2603 	if (is_a_sun) {
2604 		idx = stars_find_sun((char*)name);
2605 	} else {
2606 		idx = stars_find_bitmap((char*)name);
2607 	}
2608 
2609 	// this shouldn't ever happen from FRED since you select the name from a list of those available
2610 	if (idx == -1)
2611 		return;
2612 
2613 	sbi.star_bitmap_index = idx;
2614 
2615 	if (add_new) {
2616 		if (is_a_sun) {
2617 			Suns.push_back( sbi );
2618 		} else {
2619 			Starfield_bitmap_instances.push_back( sbi );
2620 		}
2621 	} else {
2622 		if (is_a_sun) {
2623 			Suns[index] = sbi;
2624 		} else {
2625 			Starfield_bitmap_instances[index] = sbi;
2626 		}
2627 	}
2628 
2629 	if ( !is_a_sun ) {
2630 		starfield_create_bitmap_buffer(index);
2631 	}
2632 }
2633 
2634 // erase an instance, note that this is very slow so it should only be done in FRED
stars_delete_entry_FRED(int index,bool is_a_sun)2635 void stars_delete_entry_FRED(int index, bool is_a_sun)
2636 {
2637 	if (!Fred_running)
2638 		return;
2639 
2640 	int max_index = (is_a_sun) ? (int)Suns.size() : (int)Starfield_bitmap_instances.size();
2641 
2642 	Assert( (index >= 0) && (index < max_index) );
2643 
2644 	if ( (index < 0) || (index >= max_index) )
2645 		return;
2646 
2647 	if (is_a_sun) {
2648 		Suns.erase( Suns.begin() + index );
2649 	} else {
2650 		Starfield_bitmap_instances.erase( Starfield_bitmap_instances.begin() + index );
2651 	}
2652 }
2653 
2654 // Goober5000
stars_load_first_valid_background()2655 void stars_load_first_valid_background()
2656 {
2657 	int background_idx = stars_get_first_valid_background();
2658 	stars_load_background(background_idx);
2659 }
2660 
2661 // Goober5000
stars_get_first_valid_background()2662 int stars_get_first_valid_background()
2663 {
2664 	uint i, j;
2665 
2666 	if (Backgrounds.empty())
2667 		return -1;
2668 
2669 	// scan every background except the last and return the first one that has all its suns and bitmaps present
2670 	for (i = 0; i < Backgrounds.size() - 1; i++)
2671 	{
2672 		bool valid = true;
2673 		background_t *background = &Backgrounds[i];
2674 
2675 		for (j = 0; j < background->suns.size(); j++)
2676 		{
2677 			if (stars_find_sun(background->suns[j].filename) < 0)
2678 			{
2679 				mprintf(("Failed to load sun %s for background %d, falling back to background %d\n",
2680 					background->suns[j].filename, i + 1, i + 2));
2681 				valid = false;
2682 				break;
2683 			}
2684 		}
2685 
2686 		if (valid)
2687 		{
2688 			for (j = 0; j < background->bitmaps.size(); j++)
2689 			{
2690 				if (stars_find_bitmap(background->bitmaps[j].filename) < 0)
2691 				{
2692 					mprintf(("Failed to load bitmap %s for background %d, falling back to background %d\n",
2693 						background->bitmaps[j].filename, i + 1, i + 2));
2694 					valid = false;
2695 					break;
2696 				}
2697 			}
2698 		}
2699 
2700 		if (valid)
2701 			return i;
2702 	}
2703 
2704 	// didn't find a valid background yet, so return the last one
2705 	return (int)Backgrounds.size() - 1;
2706 }
2707 
2708 // Goober5000
stars_load_background(int background_idx)2709 void stars_load_background(int background_idx)
2710 {
2711 	uint j;
2712 
2713 	stars_clear_instances();
2714 	Cur_background = background_idx;
2715 
2716 	if (Cur_background >= 0)
2717 	{
2718 		background_t *background = &Backgrounds[Cur_background];
2719 
2720 		int failed_suns = 0;
2721 		for (j = 0; j < background->suns.size(); j++)
2722 		{
2723 			if ((stars_add_sun_entry(&background->suns[j]) < 0) && !Fred_running)
2724 			{
2725 				nprintf(("General", "Failed to add sun '%s' to the mission!", background->suns[j].filename));
2726 				failed_suns++;
2727 			}
2728 		}
2729 		if (failed_suns > 0)
2730 			Warning(LOCATION, "Failed to add %d sun bitmaps to the mission!", failed_suns);
2731 
2732 		int failed_stars = 0;
2733 		for (j = 0; j < background->bitmaps.size(); j++)
2734 		{
2735 			if ((stars_add_bitmap_entry(&background->bitmaps[j]) < 0) && !Fred_running)
2736 			{
2737 				nprintf(("General", "Failed to add starfield bitmap '%s' to the mission!", background->bitmaps[j].filename));
2738 				failed_stars++;
2739 			}
2740 		}
2741 		if (failed_stars > 0)
2742 			Warning(LOCATION, "Failed to add %d starfield bitmaps to the mission!", failed_stars);
2743 	}
2744 }
2745 
2746 // Goober5000
stars_copy_background(background_t * dest,background_t * src)2747 void stars_copy_background(background_t *dest, background_t *src)
2748 {
2749 	dest->suns.assign(src->suns.begin(), src->suns.end());
2750 	dest->bitmaps.assign(src->bitmaps.begin(), src->bitmaps.end());
2751 }
2752 
2753 // Goober5000
stars_swap_backgrounds(int idx1,int idx2)2754 void stars_swap_backgrounds(int idx1, int idx2)
2755 {
2756 	background_t temp;
2757 	stars_copy_background(&temp, &Backgrounds[idx1]);
2758 	stars_copy_background(&Backgrounds[idx1], &Backgrounds[idx2]);
2759 	stars_copy_background(&Backgrounds[idx2], &temp);
2760 }
2761 
2762 // Goober5000
stars_background_empty(const background_t & bg)2763 bool stars_background_empty(const background_t &bg)
2764 {
2765 	return (bg.suns.empty() && bg.bitmaps.empty());
2766 }
2767 
2768 // Goober5000
stars_pack_backgrounds()2769 void stars_pack_backgrounds()
2770 {
2771 	// remove all empty backgrounds
2772 	Backgrounds.erase(
2773 		std::remove_if(Backgrounds.begin(), Backgrounds.end(), stars_background_empty),
2774 		Backgrounds.end());
2775 }
2776 
render_environment(int i,vec3d * eye_pos,matrix * new_orient,float new_zoom)2777 static void render_environment(int i, vec3d *eye_pos, matrix *new_orient, float new_zoom)
2778 {
2779 	bm_set_render_target(gr_screen.envmap_render_target, i);
2780 
2781 	gr_clear();
2782 
2783 	g3_set_view_matrix( eye_pos, new_orient, new_zoom );
2784 
2785 	gr_set_proj_matrix( PI_2 * new_zoom, 1.0f, Min_draw_distance, Max_draw_distance);
2786 	gr_set_view_matrix( &Eye_position, &Eye_matrix );
2787 
2788 	if ( Game_subspace_effect ) {
2789 		stars_draw(0, 0, 0, 1, 1);
2790 	} else {
2791 		stars_draw(0, 1, 1, 0, 1);
2792 	}
2793 
2794 	gr_end_view_matrix();
2795 	gr_end_proj_matrix();
2796 }
2797 
stars_setup_environment_mapping(camid cid)2798 void stars_setup_environment_mapping(camid cid) {
2799 	matrix new_orient = IDENTITY_MATRIX;
2800 
2801 	extern float View_zoom;
2802 	float old_zoom = View_zoom, new_zoom = 1.0f;//0.925f;
2803 	int i = 0;
2804 
2805 	if(!cid.isValid())
2806 		return;
2807 
2808 	vec3d cam_pos;
2809 	matrix cam_orient;
2810 	cid.getCamera()->get_info(&cam_pos, &cam_orient);
2811 
2812 	// prefer the mission specified envmap over the static-generated envmap, but
2813 	// the dynamic envmap should always get preference if in a subspace mission
2814 	if ( !Dynamic_environment && Mission_env_map >= 0 ) {
2815 		ENVMAP = Mission_env_map;
2816 		return;
2817 	}
2818 
2819 	if (gr_screen.envmap_render_target < 0) {
2820 		if (ENVMAP >= 0)
2821 			return;
2822 
2823 		if (Mission_env_map >= 0) {
2824 			ENVMAP = Mission_env_map;
2825 		} else {
2826 			ENVMAP = Default_env_map;
2827 		}
2828 
2829 		return;
2830 	}
2831 
2832 	if (Env_cubemap_drawn) {
2833 		// Nothing to do here anymore
2834 		return;
2835 	}
2836 
2837 	GR_DEBUG_SCOPE("Environment Mapping");
2838 	TRACE_SCOPE(tracing::EnvironmentMapping);
2839 
2840 	ENVMAP = gr_screen.envmap_render_target;
2841 
2842 	/*
2843 	 * Envmap matrix setup -- left-handed
2844 	 * -------------------------------------------------
2845 	 * Face --	Forward		Up		Right
2846 	 * px		+X			+Y		-Z
2847 	 * nx		-X			+Y		+Z
2848 	 * py		+Y			-Z		+X
2849 	 * ny		-Y			+Z		+X
2850 	 * pz		+Z 			+Y		+X
2851 	 * nz		-Z			+Y		-X
2852 	*/
2853 	// NOTE: OpenGL needs up/down reversed
2854 
2855 	// Save the previous render target so we can reset it once we are done here
2856 	auto previous_target = gr_screen.rendering_to_texture;
2857 
2858 	// face 1 (px / right)
2859 	memset( &new_orient, 0, sizeof(matrix) );
2860 	new_orient.vec.fvec.xyz.x =  1.0f;
2861 	new_orient.vec.uvec.xyz.y =  1.0f;
2862 	new_orient.vec.rvec.xyz.z = -1.0f;
2863 	render_environment(i, &cam_pos, &new_orient, new_zoom);
2864 	i++; // bump!
2865 
2866 	// face 2 (nx / left)
2867 	memset( &new_orient, 0, sizeof(matrix) );
2868 	new_orient.vec.fvec.xyz.x = -1.0f;
2869 	new_orient.vec.uvec.xyz.y =  1.0f;
2870 	new_orient.vec.rvec.xyz.z =  1.0f;
2871 	render_environment(i, &cam_pos, &new_orient, new_zoom);
2872 	i++; // bump!
2873 
2874 	// face 3 (py / up)
2875 	memset( &new_orient, 0, sizeof(matrix) );
2876 	new_orient.vec.fvec.xyz.y =  1.0f;
2877 	new_orient.vec.uvec.xyz.z =  -1.0f;
2878 	new_orient.vec.rvec.xyz.x =  1.0f;
2879 	render_environment(i, &cam_pos, &new_orient, new_zoom);
2880 	i++; // bump!
2881 
2882 	// face 4 (ny / down)
2883 	memset( &new_orient, 0, sizeof(matrix) );
2884 	new_orient.vec.fvec.xyz.y =  -1.0f;
2885 	new_orient.vec.uvec.xyz.z =  1.0f ;
2886 	new_orient.vec.rvec.xyz.x =  1.0f;
2887 	render_environment(i, &cam_pos, &new_orient, new_zoom);
2888 	i++; // bump!
2889 
2890 	// face 5 (pz / forward)
2891 	memset( &new_orient, 0, sizeof(matrix) );
2892 	new_orient.vec.fvec.xyz.z =  1.0f;
2893 	new_orient.vec.uvec.xyz.y =  1.0f;
2894 	new_orient.vec.rvec.xyz.x =  1.0f;
2895 	render_environment(i, &cam_pos, &new_orient, new_zoom);
2896 	i++; // bump!
2897 
2898 	// face 6 (nz / back)
2899 	memset( &new_orient, 0, sizeof(matrix) );
2900 	new_orient.vec.fvec.xyz.z = -1.0f;
2901 	new_orient.vec.uvec.xyz.y =  1.0f;
2902 	new_orient.vec.rvec.xyz.x = -1.0f;
2903 	render_environment(i, &cam_pos, &new_orient, new_zoom);
2904 
2905 
2906 	// we're done, so now reset
2907 	bm_set_render_target(previous_target);
2908 	g3_set_view_matrix( &cam_pos, &cam_orient, old_zoom );
2909 
2910 	if ( !Dynamic_environment ) {
2911 		Env_cubemap_drawn = true;
2912 	}
2913 }
stars_set_dynamic_environment(bool dynamic)2914 void stars_set_dynamic_environment(bool dynamic) {
2915 	Dynamic_environment = dynamic;
2916 	stars_invalidate_environment_map();
2917 }
stars_invalidate_environment_map()2918 void stars_invalidate_environment_map() {
2919 	// This will cause a redraw in the next frame
2920 	Env_cubemap_drawn = false;
2921 }
2922