Files
fs2open.github.com/code/nebula/neblightning.cpp
T
asarium 8f06e338a4 Add typesafe handle types and use it in game sound system
This adds a new class which allows to create type safe resource handle
types. At the moment, the engine uses ints pretty much everywhere for
providing handles to resources created by the specific subsystems. Since
all ints are the same to the C/C++ typesystem it is perfectly legal to
pass a sound handle to the graphics API as a buffer handle which will
obviously cause issues.

This adds a new type called "ID" which is a wrapper class for a generic
handle but with added type safety. An instance of this class cannot be
passed to a function expecting another type of handle which provided
compile-time type safety for these APIs.

As an initial test I converted the game sound handles to this new type.
I already found a bug in the mainhall system thanks to this new type.
The mainhall used the game sound handles as sound instance handles which
probably meant that that specific code never really worked right. I
removed the code since there was no better alternative for fixing it.
2018-04-22 16:22:45 +02:00

1128 lines
28 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#include "debugconsole/console.h"
#include "freespace.h"
#include "gamesnd/gamesnd.h"
#include "globalincs/linklist.h"
#include "io/timer.h"
#include "mission/missionparse.h"
#include "nebula/neb.h"
#include "nebula/neblightning.h"
#include "network/multi.h"
#include "network/multimsgs.h"
#include "graphics/material.h"
#include "parse/parselo.h"
#include "render/3d.h"
#include "weapon/emp.h"
// ------------------------------------------------------------------------------------------------------
// NEBULA LIGHTNING DEFINES/VARS
//
// Lightning nodes
int Num_lnodes = 0;
l_node Nebl_nodes[MAX_LIGHTNING_NODES];
l_node Nebl_free_list;
l_node Nebl_used_list;
// nodes in a lightning bolt
#define LINK_LEFT 0
#define LINK_RIGHT 1
#define LINK_CHILD 2
// Lightning bolts
int Num_lbolts = 0;
l_bolt Nebl_bolts[MAX_LIGHTNING_BOLTS];
// Lightning bolt types
SCP_vector<bolt_type> Bolt_types;
// Lightning storm types
SCP_vector<storm_type> Storm_types;
// points on the basic cross section
vec3d Nebl_ring[3] = {
{ { { -1.0f, 0.0f, 0.0f } } },
{ { { 1.0f, 0.70f, 0.0f } } },
{ { { 1.0f, -0.70f, 0.0f } } }
};
// pinched off cross-section
vec3d Nebl_ring_pinched[3] = {
{ { { -0.05f, 0.0f, 0.0f } } },
{ { { 0.05f, 0.035f, 0.0f } } },
{ { { 0.05f, -0.035f, 0.0f } } }
};
// globals used for rendering and generating bolts
int Nebl_flash_count = 0; // # of points rendered onscreen for this bolt
float Nebl_flash_x = 0.0f; // avg x of the points rendered
float Nebl_flash_y = 0.0f; // avg y of the points rendered
float Nebl_bang = 0.0; // distance to the viewer object
float Nebl_alpha = 0.0f; // alpha to use when rendering the bolt itself
float Nebl_glow_alpha = 0.0f; // alpha to use when rendering the bolt glow
int Nebl_stamp = -1; // random timestamp for making bolts
float Nebl_bolt_len; // length of the current bolt being generated
bolt_type *Nebl_type; // bolt type
matrix Nebl_bolt_dir; // orientation matrix of the bolt being generated
vec3d Nebl_bolt_start; // start point of the bolt being generated
vec3d Nebl_bolt_strike; // strike point of the bolt being generated
// the type of active storm
storm_type *Storm = NULL;
// vars
DCF(b_scale, "Sets the scale factor for debug nebula bolts")
{
dc_stuff_float(&Bolt_types[DEBUG_BOLT].b_scale);
}
DCF(b_rand, "Sets the randomness factor for debug nebula bolts")
{
dc_stuff_float(&Bolt_types[DEBUG_BOLT].b_rand);
}
DCF(b_shrink, "Sets the shrink factor for debug nebula bolts")
{
dc_stuff_float(&Bolt_types[DEBUG_BOLT].b_shrink);
}
DCF(b_poly_pct, "Sets b_poly_pct")
{
dc_stuff_float(&Bolt_types[DEBUG_BOLT].b_poly_pct);
}
DCF(b_add, "Sets b_add")
{
dc_stuff_float(&Bolt_types[DEBUG_BOLT].b_add);
}
DCF(b_strikes, "Sets num_strikes")
{
dc_stuff_int(&Bolt_types[DEBUG_BOLT].num_strikes);
}
DCF(b_noise, "Sets noise factor")
{
dc_stuff_float(&Bolt_types[DEBUG_BOLT].noise);
}
DCF(b_bright, "Sets brightness factor")
{
dc_stuff_float(&Bolt_types[DEBUG_BOLT].b_bright);
}
DCF(b_lifetime, "Sets lifetime duration")
{
dc_stuff_int(&Bolt_types[DEBUG_BOLT].lifetime);
}
DCF(b_list, "Displays status of debug lightning commands")
{
dc_printf("Debug lightning bolt settings :\n");
dc_printf("b_scale : %f\n", Bolt_types[DEBUG_BOLT].b_scale);
dc_printf("b_rand : %f\n", Bolt_types[DEBUG_BOLT].b_rand);
dc_printf("b_shrink : %f\n", Bolt_types[DEBUG_BOLT].b_shrink);
dc_printf("b_poly_pct : %f\n", Bolt_types[DEBUG_BOLT].b_poly_pct);
dc_printf("b_add : %f\n", Bolt_types[DEBUG_BOLT].b_add);
dc_printf("b_strikes : %d\n", Bolt_types[DEBUG_BOLT].num_strikes);
dc_printf("b_noise : %f\n", Bolt_types[DEBUG_BOLT].noise);
dc_printf("b_bright : %f\n", Bolt_types[DEBUG_BOLT].b_bright);
dc_printf("b_lifetime : %d\n", Bolt_types[DEBUG_BOLT].lifetime);
}
// nebula lightning intensity (0.0 to 1.0)
float Nebl_intensity = 0.6667f;
DCF(lightning_intensity, "Sets lightning intensity between 0.0 and 1.0 (Default is 0.6667)")
{
float val;
dc_stuff_float(&val);
CLAMP(val, 0.0f, 1.0f);
Nebl_intensity = 1.0f - val;
}
// ------------------------------------------------------------------------------------------------------
// NEBULA LIGHTNING FUNCTIONS
//
// initialize nebula lightning at game startup
void nebl_init()
{
char name[MAX_FILENAME_LEN];
try
{
// parse the lightning table
read_file_text("lightning.tbl", CF_TYPE_TABLES);
reset_parse();
// parse the individual lightning bolt types
required_string("#Bolts begin");
while (!optional_string("#Bolts end")){
bolt_type new_bolt_type;
// bolt title
required_string("$Bolt:");
stuff_string(new_bolt_type.name, F_NAME, NAME_LENGTH);
// b_scale
required_string("+b_scale:");
stuff_float(&new_bolt_type.b_scale);
// b_shrink
required_string("+b_shrink:");
stuff_float(&new_bolt_type.b_shrink);
// b_poly_pct
required_string("+b_poly_pct:");
stuff_float(&new_bolt_type.b_poly_pct);
// child rand
required_string("+b_rand:");
stuff_float(&new_bolt_type.b_rand);
// z add
required_string("+b_add:");
stuff_float(&new_bolt_type.b_add);
// # strikes
required_string("+b_strikes:");
stuff_int(&new_bolt_type.num_strikes);
// lifetime
required_string("+b_lifetime:");
stuff_int(&new_bolt_type.lifetime);
// noise
required_string("+b_noise:");
stuff_float(&new_bolt_type.noise);
// emp effect
required_string("+b_emp:");
stuff_float(&new_bolt_type.emp_intensity);
stuff_float(&new_bolt_type.emp_time);
// texture
required_string("+b_texture:");
stuff_string(name, F_NAME, sizeof(name));
if (!Fred_running){
new_bolt_type.texture = bm_load(name);
}
// glow
required_string("+b_glow:");
stuff_string(name, F_NAME, sizeof(name));
if (!Fred_running){
new_bolt_type.glow = bm_load(name);
}
// brightness
required_string("+b_bright:");
stuff_float(&new_bolt_type.b_bright);
Bolt_types.push_back(new_bolt_type);
}
// parse lightning storm types
required_string("#Storms begin");
while (!optional_string("#Storms end")){
storm_type new_storm_type;
// bolt title
required_string("$Storm:");
stuff_string(new_storm_type.name, F_NAME, NAME_LENGTH);
// bolt types
while (optional_string("+bolt:")){
if (new_storm_type.num_bolt_types < MAX_BOLT_TYPES_PER_STORM){
stuff_string(name, F_NAME, sizeof(name));
new_storm_type.bolt_types[new_storm_type.num_bolt_types] = nebl_get_bolt_index(name);
Assert(new_storm_type.bolt_types[new_storm_type.num_bolt_types] != (size_t)-1);
new_storm_type.num_bolt_types++;
}
}
// flavor
required_string("+flavor:");
stuff_float(&new_storm_type.flavor.xyz.x);
stuff_float(&new_storm_type.flavor.xyz.y);
stuff_float(&new_storm_type.flavor.xyz.z);
// frequencies
required_string("+random_freq:");
stuff_int(&new_storm_type.min);
stuff_int(&new_storm_type.max);
// counts
required_string("+random_count:");
stuff_int(&new_storm_type.min_count);
stuff_int(&new_storm_type.max_count);
Storm_types.push_back(new_storm_type);
}
}
catch (const parse::ParseException& e)
{
mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", "lightning.tbl", e.what()));
return;
}
}
// initialize lightning before entering a level
void nebl_level_init()
{
size_t idx;
// zero all lightning bolts
for(idx=0; idx<MAX_LIGHTNING_BOLTS; idx++){
Nebl_bolts[idx].head = NULL;
Nebl_bolts[idx].bolt_life = -1;
Nebl_bolts[idx].used = 0;
}
// initialize node list
Num_lnodes = 0;
list_init( &Nebl_free_list );
list_init( &Nebl_used_list );
// Link all object slots into the free list
for (idx=0; idx<MAX_LIGHTNING_NODES; idx++) {
list_append(&Nebl_free_list, &Nebl_nodes[idx] );
}
// zero the random timestamp
Nebl_stamp = -1;
// null the storm. let mission parsing set it up
Storm = NULL;
}
// set the storm (call from mission parse)
void nebl_set_storm(char *name)
{
// sanity
Storm = NULL;
size_t index = nebl_get_storm_index(name);
if(index == (size_t)-1)
return;
Storm = &Storm_types[index];
}
// render all lightning bolts
void nebl_render_all()
{
GR_DEBUG_SCOPE("Nebula render all");
l_bolt *b;
bolt_type *bi;
// no lightning in non-nebula missions
if(!(The_mission.flags[Mission::Mission_Flags::Fullneb])){
return;
}
// if we have no storm
if(Storm == NULL){
return;
}
// traverse the list
for(size_t idx=0; idx<MAX_LIGHTNING_BOLTS; idx++){
b = &Nebl_bolts[idx];
// if this is being used
if(b->used){
Assert(b->head != NULL);
// bogus bolt
if(b->head == NULL){
b->used = 0;
continue;
}
if( b->type >= Bolt_types.size() ){
b->used = 0;
continue;
}
bi = &Bolt_types[b->type];
// if this guy is still on a delay
if(b->delay != -1){
if(timestamp_elapsed(b->delay)){
b->delay = -1;
} else {
continue;
}
}
// if the timestamp on this guy has expired
if((b->bolt_life < 0) || timestamp_elapsed(b->bolt_life)){
// if this is a multiple strike bolt, jitter it and reset
if(b->strikes_left-1 > 0){
b->bolt_life = timestamp(bi->lifetime / bi->num_strikes);
b->first_frame = 1;
b->strikes_left--;
nebl_jitter(b);
// by continuing here we skip rendering for one frame, which makes it look more like real lightning
continue;
}
// otherwise he's completely done, so release him
else {
// maybe free up node data
if(b->head != NULL){
nebl_release(b->head);
b->head = NULL;
Num_lbolts--;
nprintf(("lightning", "Released bolt. %d used nodes!\n", Num_lnodes));
}
b->used = 0;
}
}
// pick some cool alpha values
Nebl_alpha = frand();
Nebl_glow_alpha = frand();
// otherwise render him
Nebl_flash_count = 0;
Nebl_flash_x = 0.0f;
Nebl_flash_y = 0.0f;
Nebl_bang = 10000000.0f;
nebl_render(bi, b->head, b->width);
// if this is the first frame he has been rendered, determine if we need to make a flash and sound effect
if(b->first_frame){
float flash = 0.0f;
b->first_frame = 0;
// if we rendered any points
if(Nebl_flash_count){
Nebl_flash_x /= (float)Nebl_flash_count;
Nebl_flash_y /= (float)Nebl_flash_count;
// quick distance from the center of the screen
float x = Nebl_flash_x - (gr_screen.max_w / 2.0f);
float y = Nebl_flash_y - (gr_screen.max_h / 2.0f);
float dist = fl_sqrt((x * x) + (y * y));
if(dist / (gr_screen.max_w / 2.0f) < 1.0f){
flash = 1.0f - (dist / (gr_screen.max_w / 2.0f));
// scale the flash by bolt type
flash *= bi->b_bright;
game_flash(flash, flash, flash);
}
// do some special stuff on the very first strike of the bolt
if(b->strikes_left == bi->num_strikes){
// play a sound
float bang;
if(Nebl_bang < 40.0f){
bang = 1.0f;
} else if(Nebl_bang > 400.0f){
bang = 0.0f;
} else {
bang = 1.0f - (Nebl_bang / 400.0f);
}
if(frand_range(0.0f, 1.0f) < 0.5f){
snd_play(gamesnd_get_game_sound(GameSounds::LIGHTNING_2), 0.0f, bang, SND_PRIORITY_DOUBLE_INSTANCE);
} else {
snd_play(gamesnd_get_game_sound(GameSounds::LIGHTNING_1), 0.0f, bang, SND_PRIORITY_DOUBLE_INSTANCE);
}
// apply em pulse
if(bi->emp_intensity > 0.0f){
emp_apply(&b->midpoint, 0.0f, vm_vec_dist(&b->start, &b->strike), bi->emp_intensity, bi->emp_time);
}
}
}
}
}
}
}
// process lightning (randomly generate bolts, etc, etc);
void nebl_process()
{
uint num_bolts, idx;
// non-nebula mission
if(!(The_mission.flags[Mission::Mission_Flags::Fullneb])){
return;
}
// non servers in multiplayer don't do this
if((Game_mode & GM_MULTIPLAYER) && !MULTIPLAYER_MASTER){
return;
}
// standalones shouldn't be doing this either
if (Is_standalone) {
return;
}
// if there's no chosen storm
if(Storm == NULL){
return;
}
// don't process lightning bolts unless we're a few seconds in
if(f2fl(Missiontime) < 3.0f){
return;
}
// random stamp
if(Nebl_stamp == -1){
Nebl_stamp = timestamp((int)frand_range((float)Storm->min, (float)Storm->max));
return;
}
// maybe make a bolt
if(timestamp_elapsed(Nebl_stamp)){
// determine how many bolts to spew
num_bolts = (uint)frand_range((float)Storm->min_count, (float)Storm->max_count);
for(idx=0; idx<num_bolts; idx++){
// hmm. for now just pick a random bolt type and run with it
int s1, s2, s3;
int e1, e2, e3;
do {
s1 = (int)frand_range(0.0f, (float)Neb2_slices);
s2 = (int)frand_range(0.0f, (float)Neb2_slices);
s3 = (int)frand_range(0.0f, (float)Neb2_slices);
e1 = (int)frand_range(0.0f, (float)Neb2_slices);
e2 = (int)frand_range(0.0f, (float)Neb2_slices);
e3 = (int)frand_range(0.0f, (float)Neb2_slices);
// never choose the middle cube
if((s1 == 2) && (s2 == 2) && (s3 == 2)){
s1 = 4;
s2 = 0;
}
if((e1 == 2) && (e2 == 2) && (e3 == 2)){
e1 = 0;
e2 = 4;
}
// sanity
} while((s1 == e1) && (s2 == e2) && (s3 == e3));
vec3d start = Neb2_cubes[s1][s2][s3].pt;
vec3d strike = Neb2_cubes[e1][e2][e3].pt;
// add some flavor to the bolt. mmmmmmmm, lightning
if(!IS_VEC_NULL_SQ_SAFE(&Storm->flavor)){
// start with your basic hot sauce. measure how much you have
vec3d your_basic_hot_sauce;
vm_vec_sub(&your_basic_hot_sauce, &strike, &start);
float how_much_hot_sauce = vm_vec_normalize(&your_basic_hot_sauce);
// now figure out how much of that good wing sauce to add
vec3d wing_sauce = Storm->flavor;
if(frand_range(0.0, 1.0f) < 0.5f){
vm_vec_scale(&wing_sauce, -1.0f);
}
float how_much_of_that_good_wing_sauce_to_add = vm_vec_normalize(&wing_sauce);
// mix the two together, taking care not to add too much
vec3d the_mixture;
if(how_much_of_that_good_wing_sauce_to_add > 1000.0f){
how_much_of_that_good_wing_sauce_to_add = 1000.0f;
}
vm_vec_interp_constant(&the_mixture, &your_basic_hot_sauce, &wing_sauce, how_much_of_that_good_wing_sauce_to_add / 1000.0f);
// take the final sauce and store it in the proper container
vm_vec_scale(&the_mixture, how_much_hot_sauce);
// make sure to put it on everything! whee!
vm_vec_add(&strike, &start, &the_mixture);
}
size_t type = (size_t)frand_range(0.0f, (float)(Storm->num_bolt_types-1));
nebl_bolt(Storm->bolt_types[type], &start, &strike);
}
// reset the timestamp
Nebl_stamp = timestamp((int)frand_range((float)Storm->min, (float)Storm->max));
}
}
// create a lightning bolt
void nebl_bolt(size_t type, vec3d *start, vec3d *strike)
{
vec3d dir;
l_bolt *bolt;
l_node *tail;
size_t idx;
bool found;
bolt_type *bi;
float bolt_len;
if(!(The_mission.flags[Mission::Mission_Flags::Fullneb])){
return;
}
// find a free bolt
found = 0;
for(idx=0; idx<MAX_LIGHTNING_BOLTS; idx++){
if(!Nebl_bolts[idx].used){
found = 1;
break;
}
}
if(!found){
return;
}
if( type >= Bolt_types.size() ){
return;
}
bi = &Bolt_types[type];
// get a pointer to the bolt
bolt = &Nebl_bolts[idx];
// setup bolt into
bolt->start = *start;
bolt->strike = *strike;
bolt->strikes_left = bi->num_strikes;
bolt->delay = -1;
bolt->type = (char)type;
bolt->first_frame = 1;
bolt->bolt_life = timestamp(bi->lifetime / bi->num_strikes);
Nebl_bolt_start = *start;
Nebl_bolt_strike = *strike;
// setup fire delay
if(bolt->delay != -1){
bolt->delay = timestamp(bolt->delay);
}
// setup the rest of the important bolt data
if(vm_vec_same(&Nebl_bolt_start, &Nebl_bolt_strike)){
Nebl_bolt_strike.xyz.z += 150.0f;
}
Nebl_bolt_len = vm_vec_dist(&Nebl_bolt_start, &Nebl_bolt_strike);
vm_vec_sub(&dir, &Nebl_bolt_strike, &Nebl_bolt_start);
// setup midpoint
vm_vec_scale_add(&bolt->midpoint, &Nebl_bolt_start, &dir, 0.5f);
bolt_len = vm_vec_normalize(&dir);
vm_vector_2_matrix(&Nebl_bolt_dir, &dir, NULL, NULL);
// global type for generating the bolt
Nebl_type = bi;
// try and make the bolt
if(!nebl_gen(&Nebl_bolt_start, &Nebl_bolt_strike, 0, 4, 0, &bolt->head, &tail)){
if(bolt->head != NULL){
nebl_release(bolt->head);
}
return;
}
Num_lbolts++;
// setup the rest of the data
bolt->used = 1;
bolt->width = bi->b_poly_pct * bolt_len;
// if i'm a multiplayer master, send a bolt packet
if(MULTIPLAYER_MASTER){
send_lightning_packet((int)type, start, strike);
}
}
// get the current # of active lightning bolts
int nebl_get_active_bolts()
{
return Num_lbolts;
}
// get the current # of active nodes
int nebl_get_active_nodes()
{
return Num_lnodes;
}
// "new" a lightning node
l_node *nebl_new()
{
l_node *lp;
// if we're out of nodes
if(Num_lnodes >= MAX_LIGHTNING_NODES){
nprintf(("lightning", "Out of lightning nodes!\n"));
return NULL;
}
// get a new node off the freelist
lp = GET_FIRST(&Nebl_free_list);
Assert( lp != &Nebl_free_list ); // shouldn't have the dummy element
// remove trailp from the free list
list_remove( &Nebl_free_list, lp );
// insert trailp onto the end of used list
list_append( &Nebl_used_list, lp );
// increment counter
Num_lnodes++;
lp->links[0] = NULL;
lp->links[1] = NULL;
lp->links[2] = NULL;
// return the pointer
return lp;
}
// "delete" a lightning node
void nebl_delete(l_node *lp)
{
// remove objp from the used list
list_remove( &Nebl_used_list, lp );
// add objp to the end of the free
list_append( &Nebl_free_list, lp );
// decrement counter
Num_lnodes--;
}
// free a lightning bolt
void nebl_release(l_node *whee)
{
// if we're invalid
if(whee == NULL){
return;
}
// release all of our children
if(whee->links[LINK_RIGHT] != NULL){
nebl_release(whee->links[LINK_RIGHT]);
}
if(whee->links[LINK_CHILD] != NULL){
nebl_release(whee->links[LINK_CHILD]);
}
// delete this node
nebl_delete(whee);
}
int nebl_gen(vec3d *left, vec3d *right, float depth, float max_depth, int child, l_node **l_left, l_node **l_right)
{
l_node *child_node = NULL;
float d = vm_vec_dist_quick( left, right );
// if we've reached the critical point
if ( d < 0.30f || (depth > max_depth) ){
// generate ne items
l_node *new_left = nebl_new();
if(new_left == NULL){
return 0;
}
new_left->links[0] = NULL; new_left->links[1] = NULL; new_left->links[2] = NULL;
new_left->pos = vmd_zero_vector;
l_node *new_right = nebl_new();
if(new_right == NULL){
nebl_delete(new_left);
return 0;
}
new_right->links[0] = NULL; new_right->links[1] = NULL; new_right->links[2] = NULL;
new_right->pos = vmd_zero_vector;
// left side
new_left->pos = *left;
new_left->links[LINK_RIGHT] = new_right;
*l_left = new_left;
// right side
new_right->pos = *right;
new_right->links[LINK_LEFT] = new_left;
*l_right = new_right;
// done
return 1;
}
// divide in half
vec3d tmp;
vm_vec_avg( &tmp, left, right );
// sometimes generate children
if(!child && (frand() <= Nebl_type->b_rand)){
// get a point on the plane of the strike
vec3d tmp2;
vm_vec_random_in_circle(&tmp2, &Nebl_bolt_strike, &Nebl_bolt_dir, Nebl_bolt_len * Nebl_type->b_scale, 0);
// maybe move away from the plane
vec3d dir;
vm_vec_sub(&dir, &tmp2, &tmp);
vm_vec_scale_add(&tmp2, &tmp, &dir, Nebl_type->b_shrink);
// child
l_node *argh;
if(!nebl_gen(&tmp, &tmp2, 0, 2, 1, &child_node, &argh)){
if(child_node != NULL){
nebl_release(child_node);
}
return 0;
}
}
float scaler = 0.30f;
tmp.xyz.x += (frand()-0.5f)*d*scaler;
tmp.xyz.y += (frand()-0.5f)*d*scaler;
tmp.xyz.z += (frand()-0.5f)*d*scaler;
// generate left half
l_node *ll = NULL;
l_node *lr = NULL;
if(!nebl_gen( left, &tmp, depth+1, max_depth, child, &ll, &lr )){
if(child_node != NULL){
nebl_release(child_node);
}
if(ll != NULL){
nebl_release(ll);
}
return 0;
}
// generate right half
l_node *rl = NULL;
l_node *rr = NULL;
if(!nebl_gen( &tmp, right, depth+1, max_depth, child, &rl, &rr )){
if(child_node != NULL){
nebl_release(child_node);
}
if(ll != NULL){
nebl_release(ll);
}
if(rl != NULL){
nebl_release(rl);
}
return 0;
}
// splice the two together
lr->links[LINK_RIGHT] = rl->links[LINK_RIGHT];
lr->links[LINK_RIGHT]->links[LINK_LEFT] = lr;
nebl_delete(rl);
// if we generated a child, stick him on
if(child_node != NULL){
lr->links[LINK_CHILD] = child_node;
}
// return these
*l_left = ll;
*l_right = rr;
return 1;
}
// output top and bottom vectors
// fvec == forward vector (eye viewpoint basically. in world coords)
// pos == world coordinate of the point we're calculating "around"
// w == width of the diff between top and bottom around pos
void nebl_calc_facing_pts_smart( vec3d *top, vec3d *bot, vec3d *fvec, vec3d *pos, float w, float z_add )
{
vec3d uvec, rvec;
vec3d temp;
temp = *pos;
vm_vec_sub( &rvec, &Eye_position, &temp );
vm_vec_normalize( &rvec );
vm_vec_cross(&uvec,fvec,&rvec);
vm_vec_normalize(&uvec);
vm_vec_scale_add( top, &temp, &uvec, w/2.0f );
vm_vec_scale_add( bot, &temp, &uvec, -w/2.0f );
vm_vec_scale_add2( top, &rvec, z_add );
vm_vec_scale_add2( bot, &rvec, z_add );
}
// render a section of the bolt
void nebl_render_section(bolt_type *bi, l_section *a, l_section *b)
{
vertex v[4];
vertex *verts[4] = {&v[0], &v[1], &v[2], &v[3]};
material material_params;
material_set_unlit_emissive(&material_params, bi->texture, Nebl_alpha, 2.0f);
// draw some stuff
for(size_t idx=0; idx<2; idx++){
v[0] = a->vex[idx];
v[0].texture_position.u = 0.0f; v[0].texture_position.v = 0.0f;
v[1] = a->vex[idx+1];
v[1].texture_position.u = 1.0f; v[1].texture_position.v = 0.0f;
v[2] = b->vex[idx+1];
v[2].texture_position.u = 1.0f; v[2].texture_position.v = 1.0f;
v[3] = b->vex[idx];
v[3].texture_position.u = 0.0f; v[3].texture_position.v = 1.0f;
// draw
g3_render_primitives_textured(&material_params, v, 4, PRIM_TYPE_TRIFAN, false);
}
// draw
v[0] = a->vex[2];
v[0].texture_position.u = 0.0f; v[0].texture_position.v = 0.0f;
v[1] = a->vex[0];
v[1].texture_position.u = 1.0f; v[1].texture_position.v = 0.0f;
v[2] = b->vex[0];
v[2].texture_position.u = 1.0f; v[2].texture_position.v = 1.0f;
v[3] = b->vex[2];
v[3].texture_position.u = 0.0f; v[3].texture_position.v = 1.0f;
g3_render_primitives_textured(&material_params, v, 4, PRIM_TYPE_TRIFAN, false);
// draw the glow beam
verts[0] = &a->glow_vex[0];
verts[0]->texture_position.v = 0.0f; verts[0]->texture_position.u = 0.0f;
verts[1] = &a->glow_vex[1];
verts[1]->texture_position.v = 1.0f; verts[1]->texture_position.u = 0.0f;
verts[2] = &b->glow_vex[1];
verts[2]->texture_position.v = 1.0f; verts[2]->texture_position.u = 1.0f;
verts[3] = &b->glow_vex[0];
verts[3]->texture_position.v = 0.0f; verts[3]->texture_position.u = 1.0f;
g3_render_primitives_textured(&material_params, v, 4, PRIM_TYPE_TRIFAN, false);
}
// generate a section
void nebl_generate_section(bolt_type *bi, float width, l_node *a, l_node *b, l_section *c, l_section *cap, int pinch_a, int pinch_b)
{
vec3d dir;
vec3d dir_normal;
matrix m;
size_t idx;
vec3d temp, pt;
vec3d glow_a, glow_b;
vertex tempv;
// direction matrix
vm_vec_sub(&dir, &a->pos, &b->pos);
vm_vec_copy_normalize(&dir_normal, &dir);
vm_vector_2_matrix(&m, &dir_normal, NULL, NULL);
// distance to player
float bang_dist = vm_vec_dist_quick(&Eye_position, &a->pos);
if(bang_dist < Nebl_bang){
Nebl_bang = bang_dist;
}
// rotate the basic section into world
for(idx=0; idx<3; idx++){
memset(&tempv, 0, sizeof(tempv));
// rotate to world
if(pinch_a){
vm_vec_rotate(&pt, &Nebl_ring_pinched[idx], &m);
} else {
vm_vec_copy_scale(&temp, &Nebl_ring[idx], width);
vm_vec_rotate(&pt, &temp, &m);
}
vm_vec_add2(&pt, &a->pos);
g3_transfer_vertex(&c->vex[idx], &pt);
g3_rotate_vertex(&tempv, &pt);
g3_project_vertex(&tempv);
// if first frame, keep track of the average screen pos
if (tempv.codes == 0) {
Nebl_flash_x += tempv.screen.xyw.x;
Nebl_flash_y += tempv.screen.xyw.y;
Nebl_flash_count++;
}
}
// calculate the glow points
nebl_calc_facing_pts_smart(&glow_a, &glow_b, &dir_normal, &a->pos, pinch_a ? 0.5f : width * 6.0f, Nebl_type->b_add);
g3_transfer_vertex(&c->glow_vex[0], &glow_a);
g3_transfer_vertex(&c->glow_vex[1], &glow_b);
// maybe do a cap
if(cap != NULL){
// rotate the basic section into world
for(idx=0; idx<3; idx++){
// rotate to world
if(pinch_b){
vm_vec_rotate(&pt, &Nebl_ring_pinched[idx], &m);
} else {
vm_vec_copy_scale(&temp, &Nebl_ring[idx], width);
vm_vec_rotate(&pt, &temp, &m);
}
vm_vec_add2(&pt, &b->pos);
g3_transfer_vertex(&cap->vex[idx], &pt);
g3_rotate_vertex(&tempv, &pt);
g3_project_vertex(&tempv);
// if first frame, keep track of the average screen pos
if (tempv.codes == 0) {
Nebl_flash_x += tempv.screen.xyw.x;
Nebl_flash_y += tempv.screen.xyw.y;
Nebl_flash_count++;
}
}
// calculate the glow points
nebl_calc_facing_pts_smart(&glow_a, &glow_b, &dir_normal, &b->pos, pinch_b ? 0.5f : width * 6.0f, bi->b_add);
g3_transfer_vertex(&cap->glow_vex[0], &glow_a);
g3_transfer_vertex(&cap->glow_vex[1], &glow_b);
}
}
// render the bolt
void nebl_render(bolt_type *bi, l_node *whee, float width, l_section *prev)
{
l_section start;
l_section end;
l_section child_start;
// bad
if(whee == NULL){
return;
}
// if prev is NULL, we're just starting so we need our start point
if(prev == NULL){
Assert(whee->links[LINK_RIGHT] != NULL);
nebl_generate_section(bi, width, whee, whee->links[LINK_RIGHT], &start, NULL, 1, 0);
} else {
start = *prev;
}
// if we have a child section
if(whee->links[LINK_CHILD]){
// generate section
nebl_generate_section(bi, width * 0.5f, whee, whee->links[LINK_CHILD], &child_start, &end, 0, whee->links[LINK_CHILD]->links[LINK_RIGHT] == NULL ? 1 : 0);
// render
nebl_render_section(bi, &child_start, &end);
// maybe continue
if(whee->links[LINK_CHILD]->links[LINK_RIGHT] != NULL){
nebl_render(bi, whee->links[LINK_CHILD], width * 0.5f, &end);
}
}
// if the next section is an end section
if(whee->links[LINK_RIGHT]->links[LINK_RIGHT] == NULL){
l_section temp;
// generate section
nebl_generate_section(bi, width, whee, whee->links[LINK_RIGHT], &temp, &end, 0, 1);
// render the section
nebl_render_section(bi, &start, &end);
}
// a middle section
else if(whee->links[LINK_RIGHT]->links[LINK_RIGHT] != NULL){
// generate section
nebl_generate_section(bi, width, whee->links[LINK_RIGHT], whee->links[LINK_RIGHT]->links[LINK_RIGHT], &end, NULL, 0, 0);
// render the section
nebl_render_section(bi, &start, &end);
// recurse through him
nebl_render(bi, whee->links[LINK_RIGHT], width, &end);
}
}
// given a valid, complete bolt, jitter him based upon his noise
void nebl_jitter(l_bolt *b)
{
matrix m;
vec3d temp;
float length;
l_node *moveup;
bolt_type *bi = NULL;
// sanity
if(b == NULL){
return;
}
if( b->type >= Bolt_types.size() ){
return;
}
bi = &Bolt_types[b->type];
// get the bolt direction
vm_vec_sub(&temp, &b->strike, &b->start);
length = vm_vec_normalize_quick(&temp);
vm_vector_2_matrix(&m, &temp, NULL, NULL);
// jitter all nodes on the main trunk
moveup = b->head;
while(moveup != NULL){
temp = moveup->pos;
vm_vec_random_in_circle(&moveup->pos, &temp, &m, frand_range(0.0f, length * bi->noise), 0);
// just on the main trunk
moveup = moveup->links[LINK_RIGHT];
}
}
// return the index of a given bolt type by name
size_t nebl_get_bolt_index(char *name)
{
for(size_t idx=0; idx<Bolt_types.size(); idx++){
if(!strcmp(name, Bolt_types[idx].name)){
return idx;
}
}
return (size_t)-1;
}
// return the index of a given storm type by name
size_t nebl_get_storm_index(char *name)
{
if (name == NULL)
return (size_t)-1;
for(size_t idx=0; idx<Storm_types.size(); idx++){
if(!strcmp(name, Storm_types[idx].name)){
return idx;
}
}
return (size_t)-1;
}