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 "bmpman/bmpman.h"
13 #include "cmdline/cmdline.h"
14 #include "ddsutils/ddsutils.h"
15 #include "debugconsole/console.h"
16 #include "freespace.h"
17 #include "jpgutils/jpgutils.h"
18 #include "mission/missionparse.h"
19 #include "nebula/neb.h"
20 #include "object/object.h"
21 #include "options/Option.h"
22 #include "parse/parselo.h"
23 #include "pcxutils/pcxutils.h"
24 #include "render/3d.h"
25 #include "render/batching.h"
26 #include "ship/ship.h"
27 #include "starfield/starfield.h"
28 #include "tgautils/tgautils.h"
29 #include "tracing/tracing.h"
30 #include "graphics/light.h"
31 
32 
33 // --------------------------------------------------------------------------------------------------------
34 // NEBULA DEFINES/VARS
35 //
36 
37 bool Nebula_sexp_used = false;
38 
39 ubyte Neb2_fog_color[3] = { 0,0,0 };
40 
41 static ubyte *Neb2_htl_fog_data = NULL;
42 
43 // #define NEB2_THUMBNAIL
44 
45 /*
46 3D CARDS THAT FOG PROPERLY
47 Voodoo1
48 Voodoo2
49 G200
50 TNT
51 
52 3D CARDS THAT DON'T FOG PROPERLY
53 Permedia2
54 AccelStar II
55 */
56 
57 // if nebula rendering is active (DCF stuff - not mission specific)
58 int Neb2_render_mode = NEB2_RENDER_NONE;
59 
60 SCP_vector<poof_info> Poof_info;
61 
62 float Poof_dist_threshold;
63 vec3d Poof_last_gen_pos;
64 float Poof_accum[MAX_NEB2_POOFS];
65 float Poof_density_multiplier;
66 
67 const float UPKEEP_DIST_MULT = 1.2f;
68 
69 const float PROBABLY_TOO_MANY_POOFS = 100000.0f;
70 
71 // array of neb2 poofs
72 int32_t Neb2_poof_flags = 0;
73 
74 // array of neb2 bitmaps
75 char Neb2_bitmap_filenames[MAX_NEB2_BITMAPS][MAX_FILENAME_LEN] = {
76 	"", "", "", "", "", ""
77 };
78 int Neb2_bitmap[MAX_NEB2_BITMAPS] = { -1, -1, -1, -1, -1, -1 };
79 int Neb2_bitmap_count = 0;
80 
81 // texture to use for this level
82 char Neb2_texture_name[MAX_FILENAME_LEN] = "";
83 
84 float max_rotation = 3.75f;
85 float neb2_flash_fade = 0.3f;
86 
87 //WMC - these were originally indexed to SHIP_TYPE_FIGHTER_BOMBER
88 const static float Default_fog_near = 10.0f;
89 const static float Default_fog_far = 750.0f;
90 
91 // fog near and far values for rendering the background nebula
92 #define NEB_BACKG_FOG_NEAR_GLIDE		2.5f
93 #define NEB_BACKG_FOG_NEAR_D3D			4.5f
94 #define NEB_BACKG_FOG_FAR_GLIDE			10.0f
95 #define NEB_BACKG_FOG_FAR_D3D			10.0f
96 float Neb_backg_fog_near = NEB_BACKG_FOG_NEAR_GLIDE;
97 float Neb_backg_fog_far = NEB_BACKG_FOG_FAR_GLIDE;
98 
99 // stats
100 int pneb_tried = 0;				// total pnebs tried to render
101 int pneb_tossed_alpha = 0;		// pnebs tossed because of alpha optimization
102 int pneb_tossed_dot = 0;		// pnebs tossed because of dot product
103 int pneb_tossed_off = 0;		// pnebs tossed because of being offscree
104 int neb_tried = 0;				// total nebs tried
105 int neb_tossed_alpha = 0;		// nebs tossed because of alpha
106 int neb_tossed_dot = 0;			// nebs tossed because of dot product
107 int neb_tossed_count = 0;		// nebs tossed because of max render count
108 
109 // the AWACS suppression level for the nebula
110 float Neb2_awacs = -1.0f;
111 
112 // The visual render distance multipliers for the nebula
113 float Neb2_fog_near_mult = 1.0f;
114 float Neb2_fog_far_mult = 1.0f;
115 
116 
117 // this is the percent of visibility at the fog far distance
118 const float NEB_FOG_FAR_PCT = 0.1f;
119 
120 SCP_vector<poof> Neb2_poofs;
121 
122 int Neb2_background_color[3] = {0, 0, 255};			// rgb background color (used for lame rendering)
123 
124 const SCP_vector<std::pair<int, SCP_string>> DetailLevelValues = {{ 0, "Minimum" },
125                                                                   { 1, "Low" },
126                                                                   { 2, "Medium" },
127                                                                   { 3, "High" },
128                                                                   { 4, "Ultra" }, };
129 
130 const auto ModelDetailOption = options::OptionBuilder<int>("Graphics.NebulaDetail",
131                                                            "Nebula Detail",
132                                                            "Detail level of nebulas").category("Graphics").values(
__anon14e217050102(int val, bool) 133 	DetailLevelValues).default_val(MAX_DETAIL_LEVEL).importance(7).change_listener([](int val, bool) {
134 	Detail.nebula_detail = val;
135 	return true;
136 }).finish();
137 
138 // --------------------------------------------------------------------------------------------------------
139 // NEBULA FORWARD DECLARATIONS
140 //
141 
142 // return the alpha the passed poof should be rendered with, for a 2 shell nebula
143 float neb2_get_alpha_2shell(float inner_radius, float outer_radius, float magic_num, vec3d *v);
144 
145 // return an alpha value for a bitmap offscreen based upon "break" value
146 float neb2_get_alpha_offscreen(float sx, float sy, float incoming_alpha);
147 
148 // do a pre-render of the background nebula
149 void neb2_pre_render(camid cid);
150 
151 // fill in the position of the eye for this frame
152 void neb2_get_eye_pos(vec3d *eye_vector);
153 
154 // fill in the eye orient for this frame
155 void neb2_get_eye_orient(matrix *eye_matrix);
156 
157 // --------------------------------------------------------------------------------------------------------
158 // NEBULA FUNCTIONS
159 //
160 
161 // initialize neb2 stuff at game startup
neb2_init()162 void neb2_init()
163 {
164 	char name[MAX_FILENAME_LEN];
165 
166 	try
167 	{
168 		// read in the nebula.tbl
169 		read_file_text("nebula.tbl", CF_TYPE_TABLES);
170 		reset_parse();
171 
172 		// background bitmaps
173 		Neb2_bitmap_count = 0;
174 		while (!optional_string("#end")) {
175 			// nebula
176 			required_string("+Nebula:");
177 			stuff_string(name, F_NAME, MAX_FILENAME_LEN);
178 
179 			if (Neb2_bitmap_count < MAX_NEB2_BITMAPS) {
180 				strcpy_s(Neb2_bitmap_filenames[Neb2_bitmap_count++], name);
181 			}
182 			else {
183 				WarningEx(LOCATION, "nebula.tbl\nExceeded maximum number of nebulas (%d)!\nSkipping %s.", MAX_NEB2_BITMAPS, name);
184 			}
185 		}
186 
187 		// poofs
188 		while (required_string_one_of(3, "#end", "+Poof:", "$Name:")) {
189 
190 			if (Poof_info.size() < MAX_NEB2_POOFS) {
191 				poof_info new_poof;
192 
193 				if (optional_string("+Poof:")) { // retail style
194 					stuff_string(name, F_NAME, MAX_FILENAME_LEN);
195 					strcpy_s(new_poof.bitmap_filename, name);
196 
197 					strcpy_s(new_poof.name, name);
198 
199 					Poof_info.push_back(new_poof);
200 				} else if (optional_string("$Name:")) { // new style
201 					stuff_string(new_poof.name, F_NAME, NAME_LENGTH);
202 
203 					required_string("$Bitmap:");
204 					stuff_string(new_poof.bitmap_filename, F_NAME, MAX_FILENAME_LEN);
205 
206 					if (optional_string("$Scale:"))
207 						new_poof.scale = ::util::parseUniformRange<float>(0.01f, 100000.0f);
208 
209 					if (optional_string("$Density:")) {
210 						stuff_float(&new_poof.density);
211 						if (new_poof.density <= 0.0f) {
212 							Warning(LOCATION, "Poof %s must have a density greater than 0.", new_poof.name);
213 							new_poof.density = 150.0f;
214 						}
215 						new_poof.density = 1 / (new_poof.density * new_poof.density * new_poof.density);
216 					}
217 
218 					if (optional_string("$Rotation:"))
219 						new_poof.rotation = ::util::parseUniformRange<float>(-1000.0f, 1000.0f);
220 
221 					if (optional_string("$View Distance:")) {
222 						stuff_float(&new_poof.view_dist);
223 						if (new_poof.view_dist < 0.0f) {
224 							Warning(LOCATION, "Poof %s must have a positive view distance.", new_poof.name);
225 							new_poof.view_dist = 360.f;
226 						}
227 
228 						float volume = PI * 4 / 3 * (new_poof.view_dist * new_poof.view_dist * new_poof.view_dist);
229 						if (volume * new_poof.density > PROBABLY_TOO_MANY_POOFS) {
230 							Warning(LOCATION, "Poof %s will have over 100,000 poofs on the field at once, and could cause serious performance issues. "
231 								"Remember that as $Density decreases and $View Distance increases, the total number of poofs increases exponentially.", new_poof.name);
232 						}
233 					}
234 
235 					if (optional_string("$Alpha:")) {
236 						new_poof.alpha = ::util::parseUniformRange<float>(0.0f, 1.0f);
237 					}
238 
239 					Poof_info.push_back(new_poof);
240 				}
241 			}
242 			else {
243 				WarningEx(LOCATION, "nebula.tbl\nExceeded maximum number of nebula poofs (%d)!\nSkipping %s.", (int)MAX_NEB2_POOFS, name);
244 			}
245 		}
246 	}
247 	catch (const parse::ParseException& e)
248 	{
249 		mprintf(("TABLES: Unable to parse '%s'!  Error message = %s.\n", "nebula.tbl", e.what()));
250 		return;
251 	}
252 }
253 
poof_is_used(size_t idx)254 bool poof_is_used(size_t idx) {
255 	return (Neb2_poof_flags & (1 << idx)) != 0;
256 }
257 
neb2_get_fog_color(ubyte * r,ubyte * g,ubyte * b)258 void neb2_get_fog_color(ubyte *r, ubyte *g, ubyte *b)
259 {
260 	if (r) *r = Neb2_fog_color[0];
261 	if (g) *g = Neb2_fog_color[1];
262 	if (b) *b = Neb2_fog_color[2];
263 }
264 
neb2_level_init()265 void neb2_level_init()
266 {
267 	Nebula_sexp_used = false;
268 }
269 
270 float nNf_near, nNf_density;
271 
neb2_poof_setup()272 void neb2_poof_setup() {
273 	if (!Neb2_poof_flags)
274 		return;
275 
276 	// make the total density of poofs be the average of all poofs, and each poofs density is its relative proportion compared to others
277 	// this way we maintain the retail way of not affecting total density by having more poof types
278 	float Poof_density_sum_square = 0.0f;
279 	float Poof_density_sum = 0.0f;
280 
281 	// also determine the minimum distance before re-triggering a poof upkeep
282 	Poof_dist_threshold = 9999.0f;
283 	for (size_t i = 0; i < Poof_info.size(); i++) {
284 		if (poof_is_used(i)) {
285 			Poof_density_sum_square += Poof_info[i].density * Poof_info[i].density;
286 			Poof_density_sum += Poof_info[i].density;
287 
288 			float dist_threshold = Poof_info[i].view_dist * (UPKEEP_DIST_MULT - 1.0f);
289 			if (dist_threshold < Poof_dist_threshold)
290 				Poof_dist_threshold = dist_threshold;
291 		}
292 	}
293 	Poof_density_multiplier = Poof_density_sum_square / (Poof_density_sum * Poof_density_sum);
294 	Poof_density_multiplier *= (Detail.nebula_detail + 0.5f) / (MAX_DETAIL_LEVEL + 0.5f); // scale the poofs down based on detail level
295 }
296 
297 // initialize nebula stuff - call from game_post_level_init(), so the mission has been loaded
neb2_post_level_init()298 void neb2_post_level_init()
299 {
300 	int idx;
301 
302 	// standalone servers can bail here
303 	if (Game_mode & GM_STANDALONE_SERVER) {
304 		return;
305 	}
306 
307 	// Skip actual rendering if we're in FRED.
308 	if(Fred_running)
309 	{
310 		Neb2_render_mode = NEB2_RENDER_NONE;
311 		return;
312 	}
313 
314 	// if the mission is not a fullneb mission, skip
315 	if ( !((The_mission.flags[Mission::Mission_Flags::Fullneb]) || Nebula_sexp_used) ) {
316 		Neb2_render_mode = NEB2_RENDER_NONE;
317 		Neb2_awacs = -1.0f;
318 		return;
319 	}
320 
321 	// OK, lets try something a bit more interesting
322 	if (strlen(Neb2_texture_name)) {
323 		// Set a default colour just in case something goes wrong
324 		Neb2_fog_color[0] = 30;
325 		Neb2_fog_color[1] = 52;
326 		Neb2_fog_color[2] = 157;
327 
328 		Neb2_htl_fog_data = new ubyte[768];
329 
330 		if ((Neb2_htl_fog_data != NULL) && (pcx_read_header(Neb2_texture_name, NULL, NULL, NULL, NULL, Neb2_htl_fog_data) == PCX_ERROR_NONE)) {
331 			// based on the palette, get an average color value (this doesn't really account for actual pixel usage though)
332 			ushort r = 0, g = 0, b = 0, pcount = 0;
333 			for (idx = 0; idx < 768; idx += 3) {
334 				if (Neb2_htl_fog_data[idx] || Neb2_htl_fog_data[idx+1] || Neb2_htl_fog_data[idx+2]) {
335 					r = r + Neb2_htl_fog_data[idx];
336 					g = g + Neb2_htl_fog_data[idx+1];
337 					b = b + Neb2_htl_fog_data[idx+2];
338 					pcount++;
339 				}
340 			}
341 
342 			if (pcount > 0) {
343 				Neb2_fog_color[0] = (ubyte)(r / pcount);
344 				Neb2_fog_color[1] = (ubyte)(g / pcount);
345 				Neb2_fog_color[2] = (ubyte)(b / pcount);
346 			} else {
347 				// it's just black
348 				Neb2_fog_color[0] = Neb2_fog_color[1] = Neb2_fog_color[2] = 0;
349 			}
350 
351 			// done, now free up the palette data
352 			if ( Neb2_htl_fog_data != NULL ) {
353 				delete[] Neb2_htl_fog_data;
354 				Neb2_htl_fog_data = NULL;
355 			}
356 		}
357 	}
358 
359 	Neb2_render_mode = NEB2_RENDER_HTL;
360 
361 	// load in all nebula bitmaps
362 	for (poof_info &pinfo : Poof_info) {
363 		if (pinfo.bitmap < 0) {
364 			pinfo.bitmap = bm_load(pinfo.bitmap_filename);
365 		}
366 	}
367 
368 	pneb_tried = 0;
369 	pneb_tossed_alpha = 0;
370 	pneb_tossed_dot = 0;
371 	neb_tried = 0;
372 	neb_tossed_alpha = 0;
373 	neb_tossed_dot = 0;
374 	neb_tossed_count = 0;
375 
376 	// setup proper fogging values
377 	Neb_backg_fog_near = NEB_BACKG_FOG_NEAR_D3D;
378 	Neb_backg_fog_far = NEB_BACKG_FOG_FAR_D3D;
379 
380 	// if we are going to use fullneb, but aren't fullneb yet, then be sure to reset our mode
381 	if ( !(The_mission.flags[Mission::Mission_Flags::Fullneb]) ) {
382 		Neb2_render_mode = NEB2_RENDER_NONE;
383 		Neb2_awacs = -1.0f;
384 	}
385 
386 	// truncate the poof flags down to the poofs we have
387 	if (Poof_info.size() < MAX_NEB2_POOFS) {
388 		int available_poofs_mask = (1 << Poof_info.size()) - 1;
389 
390 		// check for negative here too, because if we're not at max, 32, then we're gauranteed not to have the sign bit
391 		if (Neb2_poof_flags > available_poofs_mask || Neb2_poof_flags < 0)
392 			Warning(LOCATION, "One or more invalid nebula poofs detected!");
393 
394 		Neb2_poof_flags = Neb2_poof_flags & available_poofs_mask;
395 	}
396 
397 	// set the mission fog near dist and density
398 	float fog_far;
399 	neb2_get_adjusted_fog_values(&nNf_near, &fog_far, &nNf_density, nullptr);
400 
401 	for (float& accum : Poof_accum)
402 		accum = 0.0f;
403 
404 	// a bit awkward but this will force a full sphere gen
405 	vm_vec_make(&Poof_last_gen_pos, 999999.0f, 999999.0f, 999999.0f);
406 
407 	neb2_poof_setup();
408 }
409 
410 // shutdown nebula stuff
neb2_level_close()411 void neb2_level_close()
412 {
413 	// standalone servers can bail here
414 	if (Game_mode & GM_STANDALONE_SERVER) {
415 		return;
416 	}
417 
418 	// if the mission is not a fullneb mission, skip
419 	if ( !((The_mission.flags[Mission::Mission_Flags::Fullneb]) || Nebula_sexp_used) ) {
420 		return;
421 	}
422 
423 	// unload all nebula bitmaps
424 	for (poof_info& pinfo : Poof_info) {
425 		if (pinfo.bitmap >= 0) {
426 			bm_release(pinfo.bitmap);
427 			pinfo.bitmap = -1;
428 		}
429 	}
430 
431 	// clear da poofs
432 	Neb2_poofs.clear();
433 
434 	// unflag the mission as being fullneb so stuff doesn't fog in the techdata room :D
435     The_mission.flags.remove(Mission::Mission_Flags::Fullneb);
436 
437 	if (Neb2_htl_fog_data) {
438 		delete[] Neb2_htl_fog_data;
439 		Neb2_htl_fog_data = NULL;
440 	}
441 }
442 
443 // call before beginning all rendering
neb2_render_setup(camid cid)444 void neb2_render_setup(camid cid)
445 {
446 	GR_DEBUG_SCOPE("Nebula Setup");
447 	TRACE_SCOPE(tracing::SetupNebula);
448 
449 	// standalone servers can bail here
450 	if (Game_mode & GM_STANDALONE_SERVER) {
451 		return;
452 	}
453 
454 	// if the mission is not a fullneb mission, skip
455 	if ( !(The_mission.flags[Mission::Mission_Flags::Fullneb]) ) {
456 		return;
457 	}
458 
459 	if (Neb2_render_mode == NEB2_RENDER_HTL) {
460 		// RT The background needs to be the same colour as the fog and this seems
461 		// to be the ideal place to do it
462 		ubyte tr = gr_screen.current_clear_color.red;
463 		ubyte tg = gr_screen.current_clear_color.green;
464 		ubyte tb = gr_screen.current_clear_color.blue;
465 
466 		neb2_get_fog_color(
467 			&gr_screen.current_clear_color.red,
468 			&gr_screen.current_clear_color.green,
469 			&gr_screen.current_clear_color.blue);
470 
471 		gr_clear();
472 
473 		gr_screen.current_clear_color.red   = tr;
474 		gr_screen.current_clear_color.green = tg;
475 		gr_screen.current_clear_color.blue  = tb;
476 
477 		return;
478 	}
479 
480 	// pre-render the real background nebula
481 	neb2_pre_render(cid);
482 }
483 
484 // level paging code
neb2_page_in()485 void neb2_page_in()
486 {
487 	// load in all nebula bitmaps
488 	if ( (The_mission.flags[Mission::Mission_Flags::Fullneb]) || Nebula_sexp_used ) {
489 		for (size_t idx = 0; idx < Poof_info.size(); idx++) {
490 			if (Poof_info[idx].bitmap >= 0 && poof_is_used(idx)) {
491 				bm_page_in_texture(Poof_info[idx].bitmap);
492 			}
493 		}
494 	}
495 }
496 
497 // should we not render this object because its obscured by the nebula?
498 int neb_skip_opt = 0;
499 DCF(neb_skip, "Toggles culling of objects obscured by nebula")
500 {
501 	neb_skip_opt = !neb_skip_opt;
502 	if (neb_skip_opt) {
503 		dc_printf("Using neb object skipping!\n");
504 	} else {
505 		dc_printf("Not using neb object skipping!\n");
506 	}
507 }
neb2_skip_render(object * objp,float z_depth)508 int neb2_skip_render(object *objp, float z_depth)
509 {
510 	float fog_near, fog_far, fog_density;
511 
512 	// if we're never skipping
513 	if (!neb_skip_opt) {
514 		return 0;
515 	}
516 
517 	// get near and far fog values based upon object type and rendering mode
518 	neb2_get_adjusted_fog_values(&fog_near, &fog_far, &fog_density);
519 	float fog = pow(fog_density, z_depth - fog_near + objp->radius);
520 
521 	// by object type
522 	switch( objp->type ) {
523 	// some objects we always render
524 		case OBJ_SHOCKWAVE:
525 		case OBJ_JUMP_NODE:
526 		case OBJ_NONE:
527 		case OBJ_GHOST:
528 		case OBJ_BEAM:
529 		case OBJ_WAYPOINT:
530 		return 0;
531 
532 		// any weapon over 500 meters away
533 		// Use the "far" distance multiplier here
534 		case OBJ_WEAPON:
535 			if (fog < 0.05f) {
536 				return 1;
537 			}
538 			break;
539 
540 		// any ship less than 3% visible at their closest point
541 		case OBJ_SHIP:
542 			if (fog < 0.03f)
543 				return 1;
544 			break;
545 
546 		// any fireball over the fog limit for small ships
547 		case OBJ_FIREBALL:
548 			return 0;
549 			break;
550 
551 		// any debris over the fog limit for small ships
552 		case OBJ_DEBRIS:
553 			return 0;
554 			break;
555 
556 		// any asteroid less than 3% visible at their closest point
557 		case OBJ_ASTEROID:
558 			if (fog < 0.03f)
559 				return 1;
560 			break;
561 
562 		// hmmm. unknown object type - should probably let it through
563 		default:
564 			Int3();
565 		return 0;
566 	}
567 	return 0;
568 }
569 
570 // extend LOD
neb2_get_lod_scale(int objnum)571 float neb2_get_lod_scale(int objnum)
572 {
573 	ship *shipp;
574 	ship_info *sip;
575 
576 	// bogus
577 	if ( (objnum < 0)
578 		|| (objnum >= MAX_OBJECTS)
579 		|| (Objects[objnum].type != OBJ_SHIP)
580 		|| (Objects[objnum].instance < 0)
581 		|| (Objects[objnum].instance >= MAX_SHIPS)) {
582 		return 1.0f;
583 	}
584 	shipp = &Ships[Objects[objnum].instance];
585 	sip = &Ship_info[shipp->ship_info_index];
586 
587 	// small ship?
588 	if (sip->is_small_ship()) {
589 		return 1.8f;
590 	} else if (sip->is_big_ship()) {
591 		return 1.4f;
592 	}
593 
594 	// hmm
595 	return 1.0f;
596 }
597 
598 
599 // --------------------------------------------------------------------------------------------------------
600 // NEBULA FORWARD DEFINITIONS
601 //
602 
603 // return the alpha the passed poof should be rendered with, for a 2 shell nebula
neb2_get_alpha_2shell(float alpha,float inner_radius,float outer_radius,float magic_num,vec3d * v)604 float neb2_get_alpha_2shell(float alpha, float inner_radius, float outer_radius, float magic_num, vec3d *v)
605 {
606 	float dist;
607 	vec3d eye_pos;
608 
609 	// get the eye position
610 	neb2_get_eye_pos(&eye_pos);
611 
612 	// determine what alpha to draw this bitmap with
613 	// higher alpha the closer the bitmap gets to the eye
614 	dist = vm_vec_dist_quick(&eye_pos, v);
615 
616 	// if the point is inside the inner radius, alpha is based on distance to the player's eye,
617 	// becoming more transparent as it gets close
618 	if (dist <= inner_radius) {
619 		// alpha per meter between the magic # and the inner radius
620 		alpha = alpha / (inner_radius - magic_num);
621 
622 		// above value times the # of meters away we are
623 		alpha *= (dist - magic_num);
624 		return alpha < 0.0f ? 0.0f : alpha;
625 	}
626 	// if the point is outside the inner radius, it starts out as completely transparent at max
627 	// outer radius, and becomes more opaque as it moves towards inner radius
628 	else if (dist <= outer_radius) {
629 		// alpha per meter between the outer radius and the inner radius
630 		alpha = alpha / (outer_radius - inner_radius);
631 
632 		// above value times the range between the outer radius and the poof
633 		return alpha < 0.0f ? 0.0f : alpha * (outer_radius - dist);
634 	}
635 
636 	// otherwise transparent
637 	return 0.0f;
638 }
639 
640 // -------------------------------------------------------------------------------------------------
641 // WACKY LOCAL PLAYER NEBULA STUFF
642 //
643 
neb2_toggle_poof(int poof_idx,bool enabling)644 void neb2_toggle_poof(int poof_idx, bool enabling) {
645 
646 	if (enabling) Neb2_poof_flags |= (1 << poof_idx);
647 	else Neb2_poof_flags &= ~(1 << poof_idx);
648 
649 	Neb2_poofs.clear();
650 
651 	// a bit awkward but this will force a full sphere gen
652 	vm_vec_make(&Poof_last_gen_pos, 999999.0f, 999999.0f, 999999.0f);
653 
654 	neb2_poof_setup();
655 }
656 
new_poof(size_t poof_info_idx,vec3d * pos)657 void new_poof(size_t poof_info_idx, vec3d* pos) {
658 	poof new_poof;
659 	poof_info* pinfo = &Poof_info[poof_info_idx];
660 
661 	new_poof.poof_info_index = poof_info_idx;
662 	new_poof.flash = 0;
663 	new_poof.radius = pinfo->scale.next();
664 	new_poof.pt = *pos;
665 	new_poof.rot_speed = fl_radians(pinfo->rotation.next());
666 	new_poof.alpha = pinfo->alpha.next();
667 	vm_vec_rand_vec(&new_poof.up_vec);
668 
669 	Neb2_poofs.push_back(new_poof);
670 }
671 
672 static uint neb_rand_seed = 0;
673 
upkeep_poofs()674 void upkeep_poofs()
675 {
676 	vec3d eye_pos;
677 	neb2_get_eye_pos(&eye_pos);
678 
679 	// cull distant poofs
680 	if (!Neb2_poofs.empty()) {
681 		for (size_t i = 0; i < Neb2_poofs.size();) {
682 			if (vm_vec_dist(&Neb2_poofs[i].pt, &eye_pos) > Poof_info[Neb2_poofs[i].poof_info_index].view_dist * UPKEEP_DIST_MULT) {
683 				Neb2_poofs[i] = Neb2_poofs.back();
684 				Neb2_poofs.pop_back();
685 			}
686 			else // if we needed to cull we should not advance because we just moved a new poof into this spot
687 				i++;
688 		}
689 	}
690 
691 	neb_rand_seed = 0;
692 
693 	// make new poofs
694 	for (size_t i = 0; i < Poof_info.size(); i++) {
695 		if (!poof_is_used(i))
696 			continue;
697 		poof_info* pinfo = &Poof_info[i];
698 
699 		float gen_side_length = (pinfo->view_dist * UPKEEP_DIST_MULT) * 2;
700 		float gen_density = pinfo->density * Poof_density_multiplier;
701 
702 		float poofs_to_gen = gen_side_length * gen_side_length * gen_side_length * gen_density;
703 
704 		// store the fractional part, take the integer part
705 		Poof_accum[i] = modff(Poof_accum[i] + (poofs_to_gen), &poofs_to_gen);
706 		for (int j = 0; j < poofs_to_gen; j++) {
707 			vec3d pos = eye_pos;
708 			vec3d offset = eye_pos / gen_side_length;
709 			vec3d rand_pos = vm_well_distributed_rand_vec(neb_rand_seed, &offset);
710 			neb_rand_seed++;
711 
712 			rand_pos.xyz.x *= gen_side_length / 2;
713 			rand_pos.xyz.y *= gen_side_length / 2;
714 			rand_pos.xyz.z *= gen_side_length / 2;
715 
716 			pos += rand_pos;
717 
718 			// we generated poofs in a cube, now keep only those that are within the view sphere, and weren't within the last view sphere
719 			// not terribly efficient but very simple
720 			if (vm_vec_dist(&eye_pos, &pos) <= (gen_side_length / 2) &&
721 				vm_vec_dist(&Poof_last_gen_pos, &pos) > (gen_side_length / 2))
722 				new_poof(i, &pos);
723 		}
724 	}
725 }
726 
neb2_render_poofs()727 void neb2_render_poofs()
728 {
729 	GR_DEBUG_SCOPE("Nebula render player");
730 	TRACE_SCOPE(tracing::DrawPoofs);
731 
732 	vertex p, ptemp;
733 	float alpha;
734 	vec3d eye_pos;
735 	matrix eye_orient;
736 
737 	// standalone servers can bail here
738 	if (Game_mode & GM_STANDALONE_SERVER) {
739 		return;
740 	}
741 
742 	// if the mission is not a fullneb mission, skip
743 	if (!(The_mission.flags[Mission::Mission_Flags::Fullneb])) {
744 		return;
745 	}
746 
747     memset(&p, 0, sizeof(p));
748 	memset(&ptemp, 0, sizeof(ptemp));
749 
750 	// get eye position and orientation
751 	neb2_get_eye_pos(&eye_pos);
752 	neb2_get_eye_orient(&eye_orient);
753 
754 	// maybe swap stuff around if the player crossed the dist threshold
755 	if (vm_vec_dist(&eye_pos, &Poof_last_gen_pos) > Poof_dist_threshold) {
756 		upkeep_poofs();
757 		Poof_last_gen_pos = eye_pos;
758 	}
759 
760 	// if we've switched nebula rendering off
761 	if (Neb2_render_mode == NEB2_RENDER_NONE) {
762 		return;
763 	}
764 
765 	// render the nebula
766 	for (poof &pf : Neb2_poofs) {
767 		poof_info* pinfo = &Poof_info[pf.poof_info_index];
768 
769 		// Miss this one out if the id is -1
770 		if (pinfo->bitmap < 0)
771 			continue;
772 
773 		// generate the bitmap orient
774 		// If the bitmap is large and distant and points in the view fvec direction (like retail poofs do) this looks good when moving,
775 		// but bad when you rotate (since the bitmaps rotate in place with you, which looks weird).
776 		// Conversely, if the bitmap is close and small (like retail size-ish) and points at the view position, it looks good
777 		// when rotating (since the bitmaps remain static) but bad when moving (as they rotate in place to continue pointing at you)
778 		//
779 		// So blend between the two styles based on promixity and size, since distant (relative to their size) bitmaps
780 		// are more affected by rotating than moving and close bitmaps are vice versa
781 		// We will scale the bitmap direction from the view position to "off to infinity" in the negative view fvec direction - Asteroth
782 		matrix orient;
783 		vec3d view_pos;
784 		{
785 			float scalar = -1 / powf((vm_vec_dist(&eye_pos, &pf.pt) / (10 * pf.radius)), 3.f);
786 
787 			vm_vec_scale_add(&view_pos, &eye_pos, &eye_orient.vec.fvec, scalar);
788 
789 			view_pos -= pf.pt;
790 			vm_vec_normalize(&view_pos);
791 
792 			vm_vector_2_matrix(&orient, &view_pos, &pf.up_vec, nullptr);
793 		}
794 
795 		// update the poof's up vector to be perpindicular to the camera and also rotated by however much its rotating
796 		vec3d poof_direction;
797 		vm_vec_normalized_dir(&poof_direction, &pf.pt, &eye_pos);
798 		vm_project_point_onto_plane(&pf.up_vec, &pf.up_vec, &view_pos, &vmd_zero_vector);
799 		vm_vec_normalize(&pf.up_vec);
800 		vm_rot_point_around_line(&pf.up_vec, &pf.up_vec, pf.rot_speed * flFrametime, &vmd_zero_vector, &view_pos);
801 
802 		// optimization 1 - don't draw backfacing poly's
803 		if (vm_vec_dot_to_point(&eye_orient.vec.fvec, &eye_pos, &pf.pt) <= 0.0f)
804 			continue;
805 
806 		// get the proper alpha value
807 		alpha = neb2_get_alpha_2shell(pf.alpha, pf.radius, pinfo->view_dist, pf.radius/4, &pf.pt);
808 
809 		// optimization 2 - don't draw 0.0f or less poly's
810 		// this amounts to big savings
811 		if (alpha <= 0.0f)
812 			continue;
813 
814 		// render!
815 		batching_add_polygon(pinfo->bitmap, &pf.pt, &orient, pf.radius, pf.radius, alpha);
816 	}
817 
818 	// gr_set_color_fast(&Color_bright_red);
819 	// gr_printf(30, 100, "Area %.3f", total_area);
820 #ifdef NEB2_THUMBNAIL
821 	extern int tbmap;
822 	if (tbmap != -1) {
823 		gr_set_bitmap(tbmap);
824 		gr_bitmap(0, 0);
825 	}
826 #endif
827 }
828 
829 /*
830 //Object types
831 #define OBJ_NONE		0	//unused object
832 #define OBJ_SHIP		1	//a ship
833 #define OBJ_WEAPON		2	//a laser, missile, etc
834 #define OBJ_FIREBALL	3	//an explosion
835 #define OBJ_START		4	//a starting point marker (player start, etc)
836 #define OBJ_WAYPOINT	5	//a waypoint object, maybe only ever used by Fred
837 #define OBJ_DEBRIS		6	//a flying piece of ship debris
838 #define OBJ_CMEASURE	7	//a countermeasure, such as chaff
839 #define OBJ_GHOST		8	//so far, just a placeholder for when a player dies.
840 #define OBJ_POINT		9	//generic object type to display a point in Fred.
841 #define OBJ_SHOCKWAVE	10	// a shockwave
842 #define OBJ_WING		11	// not really a type used anywhere, but I need it for Fred.
843 #define OBJ_OBSERVER	12	// used for multiplayer observers (possibly single player later)
844 #define OBJ_ASTEROID	13	// An asteroid, you know, a big rock, like debris, sort of.
845 #define OBJ_JUMP_NODE	14	// A jump node object, used only in Fred.
846 #define OBJ_BEAM		15	// beam weapons. we have to roll them into the object system to get the benefits of the collision pairs
847 */
848 // get near and far fog values based upon object type and rendering mode
neb2_get_fog_values(float * fnear,float * ffar,object * objp)849 void neb2_get_fog_values(float *fnear, float *ffar, object *objp)
850 {
851 	int type_index = -1;
852 
853 	//use defaults
854 	*fnear = Default_fog_near;
855 	*ffar = Default_fog_far;
856 
857 	if (objp == NULL) {
858 		return;
859 	}
860 
861 	// determine what fog index to use
862 	if(objp->type == OBJ_SHIP) {
863 		Assert((objp->instance >= 0) && (objp->instance < MAX_SHIPS));
864 		if((objp->instance >= 0) && (objp->instance < MAX_SHIPS)) {
865 			type_index = ship_query_general_type(objp->instance);
866 			if(type_index > 0) {
867 				*fnear = Ship_types[type_index].fog_start_dist;
868 				*ffar = Ship_types[type_index].fog_complete_dist;
869 			}
870 		}
871 	} else if (objp->type == OBJ_FIREBALL) { //mostly here for the warp effect
872 		*fnear = objp->radius*2;
873 		*ffar = (objp->radius*objp->radius*200)+objp->radius*200;
874 		return;
875 	}
876 }
877 
878 // This version of the function allows for global adjustment to fog values
neb2_get_adjusted_fog_values(float * fnear,float * ffar,float * fdensity,object * objp)879 void neb2_get_adjusted_fog_values(float *fnear, float *ffar, float *fdensity, object *objp)
880 {
881 	neb2_get_fog_values(fnear, ffar, objp);
882 
883 	// Multiply fog distances by mission multipliers
884 	*fnear *= Neb2_fog_near_mult;
885 	*ffar *= Neb2_fog_far_mult;
886 
887 	// Avoide divide-by-zero
888 	if ((*fnear - *ffar) == 0)
889 		*ffar = *fnear + 1.0f;
890 
891 	if (fdensity != nullptr)
892 		*fdensity = powf(NEB_FOG_FAR_PCT, 1 / (*ffar - *fnear));
893 }
894 
895 // given a position, returns 0 - 1 the fog visibility of that position, 0 = completely obscured
896 // distance_mult will multiply the result, use for things that can be obscured but can 'shine through' the nebula more than normal
neb2_get_fog_visibility(vec3d * pos,float distance_mult)897 float neb2_get_fog_visibility(vec3d *pos, float distance_mult)
898 {
899 	float pct;
900 
901 	// get the fog pct
902 	pct = powf(nNf_density, (vm_vec_dist(&Eye_position, pos) - nNf_near) / distance_mult);
903 
904     CLAMP(pct, 0.0f, 1.0f);
905 
906 	return pct;
907 }
908 
909 // fogging stuff --------------------------------------------------------------------
910 
911 // do a pre-render of the background nebula
912 #define ESIZE		32
913 ubyte tpixels[ESIZE * ESIZE * 4];		// for 32 bits
914 int last_esize = -1;
915 int this_esize = ESIZE;
916 float ex_scale, ey_scale;
917 int tbmap = -1;
918 // UnknownPlayer : Contained herein, the origins of the nebula rendering bug!
919 // I am really not entirely sure what this code achieves, but the old
920 // D3D calls were the cause of the nebula bug - they have been commented out.
921 // If you want to save some rendering time, I would suggest maybe kill this off.
922 // It doesn't use much, but it APPEARS to be fairly useless unless someone wants
923 // to enlighten me.
924 //
neb2_pre_render(camid cid)925 void neb2_pre_render(camid cid)
926 {
927 	// if the mission is not a fullneb mission, skip
928 	if (!(The_mission.flags[Mission::Mission_Flags::Fullneb])) {
929 		return;
930 	}
931 
932 	// bail early in lame and poly modes
933 	if (Neb2_render_mode != NEB2_RENDER_POF) {
934 		return;
935 	}
936 
937 	// set the view clip
938 	gr_screen.clip_width = this_esize;
939 	gr_screen.clip_height = this_esize;
940 	g3_start_frame(1);						// Turn on zbuffering
941 	g3_set_view(cid.getCamera());
942 	gr_set_clip(0, 0, this_esize, this_esize);
943 
944 	// render the background properly
945 	// hack - turn off nebula stuff
946 	int neb_save = Neb2_render_mode;
947 	Neb2_render_mode = NEB2_RENDER_NONE;
948 
949 	// draw background stuff nebula
950 	stars_draw_background();
951 
952 	Neb2_render_mode = neb_save;
953 
954 	// grab the region
955 	gr_get_region(0, this_esize, this_esize, (ubyte*)tpixels);
956 
957 #ifdef NEB2_THUMBNAIL
958 	if (tbmap == -1) {
959 		tbmap = bm_create(16, this_esize, this_esize, tpixels, 0);
960 		bm_lock(tbmap, 16, 0);
961 		bm_unlock(tbmap);
962 	}
963 #endif
964 
965 	// maybe do some swizzling
966 
967 	// end the frame
968 	g3_end_frame();
969 
970 	gr_clear();
971 
972 	// if the size has changed between frames, make a new bitmap
973 	if (this_esize != last_esize) {
974 		last_esize = this_esize;
975 
976 		// recalculate ex_scale and ey_scale values for looking up color values
977 		ex_scale = (float)this_esize / (float)gr_screen.max_w;
978 		ey_scale = (float)this_esize / (float)gr_screen.max_h;
979 	}
980 }
981 
982 // fill in the position of the eye for this frame
neb2_get_eye_pos(vec3d * eye_vector)983 void neb2_get_eye_pos(vec3d *eye_vector)
984 {
985 	*eye_vector = Eye_position;
986 }
987 
988 // fill in the eye orient for this frame
neb2_get_eye_orient(matrix * eye_matrix)989 void neb2_get_eye_orient(matrix *eye_matrix)
990 {
991 	*eye_matrix = Eye_matrix;
992 }
993 
994 // nebula DCF functions ------------------------------------------------------
995 // TODO: With the new debug parser in place, most of these sub-commands can now be handled by neb2. This should clear up the DCF list a bit
996 DCF(neb2, "list nebula console commands")
997 {
998 //	dc_printf("neb2_fog <X> <float> <float>  : set near and far fog planes for ship type X\n");
999 //	dc_printf("where X is an integer from 1 - 11\n");
1000 //	dc_printf("1 = cargo containers, 2 = fighters/bombers, 3 = cruisers\n");
1001 //	dc_printf("4 = freighters, 5 = capital ships, 6 = transports, 7 = support ships\n");
1002 //	dc_printf("8 = navbuoys, 9 = sentryguns, 10 = escape pods, 11 = background nebula polygons\n\n");
1003 
1004 	dc_printf("neb2_select      : <int> <int>  where the first # is the bitmap to be adjusting (0 through 5), and the second int is a 0 or 1, to turn off and on\n");
1005 	dc_printf("neb2_mode        : switch between no nebula, polygon background, pof background, lame, or HTL rendering (0, 1, 2, 3 and 4 respectively)\n\n");
1006 	dc_printf("neb2_ff          : flash fade/sec\n");
1007 	dc_printf("neb2_background	: rgb background color\n");
1008 	dc_printf("neb2_fog_color   : rgb fog color\n");
1009 
1010 //	dc_printf("neb2_fog_vals    : display all the current settings for all above values\n");
1011 }
1012 
1013 DCF(neb2_select, "Enables/disables a poof bitmap")
1014 {
1015 	int bmap;
1016 	bool val_b;
1017 
1018 	dc_stuff_int(&bmap);
1019 
1020 	if ( (bmap >= 0) && (bmap < (int)Poof_info.size()) ) {
1021 		dc_stuff_boolean(&val_b);
1022 
1023 		val_b ? (Neb2_poof_flags |= (1<<bmap)) : (Neb2_poof_flags &= ~(1<<bmap));
1024 	}
1025 }
1026 
1027 DCF(neb2_ff, "flash fade/sec")
1028 {
1029 	dc_stuff_float(&neb2_flash_fade);
1030 }
1031 
1032 DCF(neb2_mode, "Switches nebula render modes")
1033 {
1034 	int mode;
1035 	dc_stuff_int(&mode);
1036 
1037 	switch (mode) {
1038 		case NEB2_RENDER_NONE:
1039 			Neb2_render_mode = NEB2_RENDER_NONE;
1040 		break;
1041 
1042 		case NEB2_RENDER_POF:
1043 			Neb2_render_mode = NEB2_RENDER_POF;
1044 			stars_set_background_model(BACKGROUND_MODEL_FILENAME, "Eraseme3");
1045 			stars_set_background_orientation();
1046 		break;
1047 
1048 		case NEB2_RENDER_HTL:
1049 			Neb2_render_mode = NEB2_RENDER_HTL;
1050 		break;
1051 	}
1052 }
1053 
1054 DCF(neb2_background, "Sets the RGB background color (lame rendering)")
1055 {
1056 	int r, g, b;
1057 
1058 	dc_stuff_int(&r);
1059 	dc_stuff_int(&g);
1060 	dc_stuff_int(&b);
1061 
1062 	Neb2_background_color[0] = r;
1063 	Neb2_background_color[1] = g;
1064 	Neb2_background_color[2] = b;
1065 }
1066 
1067 DCF(neb2_fog_color, "Sets the RGB fog color (HTL)")
1068 {
1069 	ubyte r, g, b;
1070 
1071 	dc_stuff_ubyte(&r);
1072 	dc_stuff_ubyte(&g);
1073 	dc_stuff_ubyte(&b);
1074 
1075 	Neb2_fog_color[0] = r;
1076 	Neb2_fog_color[1] = g;
1077 	Neb2_fog_color[2] = b;
1078 }
1079