2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
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
10 * $Logfile: /Freespace2/code/Lighting/Lighting.cpp $
15 * Code to calculate dynamic lighting on a vertex.
18 * Revision 1.3 2002/06/17 06:33:09 relnev
19 * ryan's struct patch for gcc 2.95
21 * Revision 1.2 2002/06/09 04:41:22 relnev
22 * added copyright header
24 * Revision 1.1.1.1 2002/05/03 03:28:09 root
28 * 5 6/22/99 2:22p Dave
29 * Doh. Fixed a type bug.
31 * 4 6/18/99 5:16p Dave
32 * Added real beam weapon lighting. Fixed beam weapon sounds. Added MOTD
33 * dialog to PXO screen.
35 * 3 5/09/99 6:00p Dave
36 * Lots of cool new effects. E3 build tweaks.
38 * 2 10/07/98 10:53a Dave
41 * 1 10/07/98 10:49a Dave
43 * 37 4/12/98 9:56a John
44 * Made lighting detail flags work. Made explosions cast light on
47 * 36 4/10/98 5:20p John
48 * Changed RGB in lighting structure to be ubytes. Removed old
49 * not-necessary 24 bpp software stuff.
51 * 35 4/04/98 5:17p John
52 * Added sun stuff. Made Glide optionally use 8-bpp textures. (looks
55 * 34 3/14/98 3:07p Adam
56 * re-added some lighting changes
58 * 33 3/13/98 4:10p John
59 * Put back in Adam's old lighting values.
61 * 32 3/12/98 8:42a John
62 * Checked in Adam's new lighting values.
63 * Checked in changes to timing models code
65 * 31 2/26/98 5:42p John
67 * 30 2/26/98 3:25p John
68 * Added code to turn on/off lighting. Made lighting used dist*dist
71 * 29 2/19/98 10:51p John
72 * Enabled colored lighting for hardware (Glide)
74 * 28 2/13/98 5:00p John
75 * Made lighting push functions return number of releveent lights.
77 * 27 1/29/98 3:36p Johnson
78 * JAS: Fixed some problems with pre_player_entry.
80 * 26 1/29/98 3:16p Allender
81 * fix compiler warnings
83 * 25 1/29/98 8:14a John
84 * Added support for RGB lighting
86 * 24 1/23/98 5:08p John
87 * Took L out of vertex structure used B (blue) instead. Took all small
88 * fireballs out of fireball types and used particles instead. Fixed some
89 * debris explosion things. Restructured fireball code. Restructured
90 * some lighting code. Made dynamic lighting on by default. Made groups
91 * of lasers only cast one light. Made fireballs not cast light.
93 * 23 1/10/98 1:14p John
94 * Added explanation to debug console commands
96 * 22 1/02/98 11:53a Adam
97 * Changed lighting mode to darken. Changed ambient and reflect to .75
98 * and .50 to compensate.
100 * 21 1/02/98 11:28a Adam
101 * Changed default ambient to 0.55f from 0.45f.
103 * 20 12/21/97 4:33p John
104 * Made debug console functions a class that registers itself
105 * automatically, so you don't need to add the function to
106 * debugfunctions.cpp.
108 * 19 12/17/97 7:53p John
109 * Fixed a bug where gunpoint for flashes were in world coordinates,
110 * should have been object.
112 * 18 12/17/97 5:11p John
113 * Added brightening back into fade table. Added code for doing the fast
114 * dynamic gun flashes and thruster flashes.
116 * 17 12/12/97 3:02p John
117 * First Rev of Ship Shadows
119 * 16 11/07/97 7:24p John
120 * changed lighting to take two ranges.
121 * In textest, added test code to draw nebulas
123 * 15 11/04/97 9:19p John
124 * Optimized dynamic lighting more.
126 * 14 10/29/97 5:05p John
127 * Changed dynamic lighting to only rotate and calculate lighting for
128 * point lights that are close to an object. Changed lower framerate cap
131 * 13 9/24/97 12:37p Mike
132 * Crank ambient lighting from 0.2 to 0.6
134 * 12 9/17/97 9:44a John
135 * fixed bug with lighting set to default
137 * 11 9/11/97 5:36p Jasen
138 * Changed ambient and reflective lighting values to give a bit more
139 * realism to the game. Yeah, yeah, I know I am not a programmer.
141 * 10 4/24/97 2:58p John
143 * 9 4/24/97 11:49a John
144 * added new lighting commands to console.
146 * 8 4/23/97 5:26p John
147 * First rev of new debug console stuff.
149 * 7 4/22/97 3:14p John
150 * upped the ambient light
152 * 6 4/17/97 6:06p John
153 * New code/data for v19 of BSPGEN with smoothing and zbuffer
156 * 5 4/08/97 5:18p John
157 * First rev of decent (dynamic, correct) lighting in FreeSpace.
159 * 4 2/17/97 5:18p John
160 * Added a bunch of RCS headers to a bunch of old files that don't have
163 * 3 1/30/97 9:35a Hoffoss
164 * Added header for files.
174 #include "lighting.h"
176 #include "systemvars.h"
178 #define MAX_LIGHTS 256
179 #define MAX_LIGHT_LEVELS 16
181 #define LT_DIRECTIONAL 0 // A light like a sun
182 #define LT_POINT 1 // A point light, like an explosion
183 #define LT_TUBE 2 // A tube light, like a fluorescent light
185 typedef struct light {
186 int type; // What type of light this is
187 vector vec; // location in world space of a point light or the direction of a directional light or the first point on the tube for a tube light
188 vector vec2; // second point on a tube light
189 vector local_vec; // rotated light vector
190 vector local_vec2; // rotated 2nd light vector for a tube light
191 float intensity; // How bright the light is.
192 float rad1, rad1_squared; // How big of an area a point light affect. Is equal to l->intensity / MIN_LIGHT;
193 float rad2, rad2_squared; // How big of an area a point light affect. Is equal to l->intensity / MIN_LIGHT;
194 float r,g,b; // The color components of the light
195 int ignore_objnum; // Don't light this object. Used to optimize weapons casting light on parents.
196 int affected_objnum; // for "unique lights". ie, lights which only affect one object (trust me, its useful)
199 light Lights[MAX_LIGHTS];
202 light *Relevent_lights[MAX_LIGHTS][MAX_LIGHT_LEVELS];
203 int Num_relevent_lights[MAX_LIGHT_LEVELS];
204 int Num_light_levels = 0;
206 #define MAX_STATIC_LIGHTS 10
207 light * Static_light[MAX_STATIC_LIGHTS];
208 int Static_light_count = 0;
210 static int Light_in_shadow = 0; // If true, this means we're in a shadow
212 #define LM_BRIGHTEN 0
215 #define MIN_LIGHT 0.03f // When light drops below this level, ignore it. Must be non-zero! (1/32)
218 int Lighting_off = 0;
220 // For lighting values, 0.75 is full intensity
222 #if 1 // ADAM'S new stuff
223 int Lighting_mode = LM_BRIGHTEN;
224 #define AMBIENT_LIGHT_DEFAULT 0.15f //0.10f
225 #define REFLECTIVE_LIGHT_DEFAULT 0.75f //0.90f
227 int Lighting_mode = LM_DARKEN;
228 #define AMBIENT_LIGHT_DEFAULT 0.75f //0.10f
229 #define REFLECTIVE_LIGHT_DEFAULT 0.50f //0.90f
232 float Ambient_light = AMBIENT_LIGHT_DEFAULT;
233 float Reflective_light = REFLECTIVE_LIGHT_DEFAULT;
235 int Lighting_flag = 1;
237 DCF(light,"Changes lighting parameters")
240 dc_get_arg(ARG_STRING);
241 if ( !strcmp( Dc_arg, "ambient" )) {
242 dc_get_arg(ARG_FLOAT);
243 if ( (Dc_arg_float < 0.0f) || (Dc_arg_float > 1.0f) ) {
246 Ambient_light = Dc_arg_float;
248 } else if ( !strcmp( Dc_arg, "reflect" )) {
249 dc_get_arg(ARG_FLOAT);
250 if ( (Dc_arg_float < 0.0f) || (Dc_arg_float > 1.0f) ) {
253 Reflective_light = Dc_arg_float;
255 } else if ( !strcmp( Dc_arg, "default" )) {
256 Lighting_mode = LM_BRIGHTEN;
257 Ambient_light = AMBIENT_LIGHT_DEFAULT;
258 Reflective_light = REFLECTIVE_LIGHT_DEFAULT;
260 } else if ( !strcmp( Dc_arg, "mode" )) {
261 dc_get_arg(ARG_STRING);
262 if ( !strcmp(Dc_arg, "light") ) {
263 Lighting_mode = LM_BRIGHTEN;
264 } else if ( !strcmp(Dc_arg, "darken")) {
265 Lighting_mode = LM_DARKEN;
269 } else if ( !strcmp( Dc_arg, "dynamic" )) {
270 dc_get_arg(ARG_TRUE|ARG_FALSE|ARG_NONE);
271 if ( Dc_arg_type & ARG_TRUE ) Lighting_flag = 1;
272 else if ( Dc_arg_type & ARG_FALSE ) Lighting_flag = 0;
273 else if ( Dc_arg_type & ARG_NONE ) Lighting_flag ^= 1;
274 } else if ( !strcmp( Dc_arg, "on" ) ) {
276 } else if ( !strcmp( Dc_arg, "off" ) ) {
279 // print usage, not stats
285 dc_printf( "Usage: light keyword\nWhere keyword can be in the following forms:\n" );
286 dc_printf( "light on|off Turns all lighting on/off\n" );
287 dc_printf( "light default Resets lighting to all default values\n" );
288 dc_printf( "light ambient X Where X is the ambient light between 0 and 1.0\n" );
289 dc_printf( "light reflect X Where X is the material reflectiveness between 0 and 1.0\n" );
290 dc_printf( "light dynamic [bool] Toggles dynamic lighting on/off\n" );
291 dc_printf( "light mode [light|darken] Changes the lighting mode.\n" );
292 dc_printf( " Where 'light' means the global light adds light.\n");
293 dc_printf( " and 'darken' means the global light subtracts light.\n");
294 Dc_status = 0; // don't print status if help is printed. Too messy.
298 dc_printf( "Ambient light is set to %.2f\n", Ambient_light );
299 dc_printf( "Reflective light is set to %.2f\n", Reflective_light );
300 dc_printf( "Dynamic lighting is: %s\n", (Lighting_flag?"on":"off") );
301 switch( Lighting_mode ) {
302 case LM_BRIGHTEN: dc_printf( "Lighting mode is: light\n" ); break;
303 case LM_DARKEN: dc_printf( "Lighting mode is: darken\n" ); break;
304 default: dc_printf( "Lighting mode is: UNKNOWN\n" ); break;
313 // reset static (sun) lights
314 for(idx=0; idx<MAX_STATIC_LIGHTS; idx++){
315 Static_light[idx] = NULL;
317 Static_light_count = 0;
320 light_filter_reset();
323 // Rotates the light into the current frame of reference
324 void light_rotate(light * l)
328 // Rotate the light direction into local coodinates
329 vm_vec_rotate(&l->local_vec, &l->vec, &Light_matrix );
334 // Rotate the point into local coordinates
335 vm_vec_sub(&tempv, &l->vec, &Light_base );
336 vm_vec_rotate(&l->local_vec, &tempv, &Light_matrix );
342 // Rotate the point into local coordinates
343 vm_vec_sub(&tempv, &l->vec, &Light_base );
344 vm_vec_rotate(&l->local_vec, &tempv, &Light_matrix );
346 // Rotate the point into local coordinates
347 vm_vec_sub(&tempv, &l->vec2, &Light_base );
348 vm_vec_rotate(&l->local_vec2, &tempv, &Light_matrix );
353 Int3(); // Invalid light type
357 // Sets the ambient lighting level.
359 void light_set_ambient(float ambient_light)
363 void light_add_directional( vector *dir, float intensity, float r, float g, float b )
367 if ( Lighting_off ) return;
369 if ( Num_lights >= MAX_LIGHTS ) return;
371 l = &Lights[Num_lights++];
373 l->type = LT_DIRECTIONAL;
375 if ( Lighting_mode == LM_BRIGHTEN ) {
376 vm_vec_copy_scale( &l->vec, dir, -1.0f );
378 vm_vec_copy_scale( &l->vec, dir, 1.0f );
384 l->intensity = intensity;
387 l->rad1_squared = l->rad1*l->rad1;
388 l->rad2_squared = l->rad2*l->rad2;
389 l->ignore_objnum = -1;
390 l->affected_objnum = -1;
392 SDL_assert( Num_light_levels <= 1 );
393 // Relevent_lights[Num_relevent_lights[Num_light_levels-1]++][Num_light_levels-1] = l;
395 if(Static_light_count < MAX_STATIC_LIGHTS){
396 Static_light[Static_light_count++] = l;
401 void light_add_point( vector * pos, float rad1, float rad2, float intensity, float r, float g, float b, int ignore_objnum )
405 if ( Lighting_off ) return;
407 if (!Lighting_flag) return;
409 // if ( key_pressed(SDLK_LSHIFT) ) return;
411 if ( Num_lights >= MAX_LIGHTS ) {
412 mprintf(( "Out of lights!\n" ));
416 l = &Lights[Num_lights++];
423 l->intensity = intensity;
426 l->rad1_squared = l->rad1*l->rad1;
427 l->rad2_squared = l->rad2*l->rad2;
428 l->ignore_objnum = ignore_objnum;
429 l->affected_objnum = -1;
431 SDL_assert( Num_light_levels <= 1 );
432 // Relevent_lights[Num_relevent_lights[Num_light_levels-1]++][Num_light_levels-1] = l;
435 void light_add_point_unique( vector * pos, float rad1, float rad2, float intensity, float r, float g, float b, int affected_objnum)
439 if ( Lighting_off ) return;
441 if (!Lighting_flag) return;
443 // if ( key_pressed(SDLK_LSHIFT) ) return;
445 if ( Num_lights >= MAX_LIGHTS ) {
446 mprintf(( "Out of lights!\n" ));
450 l = &Lights[Num_lights++];
457 l->intensity = intensity;
460 l->rad1_squared = l->rad1*l->rad1;
461 l->rad2_squared = l->rad2*l->rad2;
462 l->ignore_objnum = -1;
463 l->affected_objnum = affected_objnum;
465 SDL_assert( Num_light_levels <= 1 );
468 // for now, tube lights only affect one ship (to keep the filter stuff simple)
469 void light_add_tube(vector *p0, vector *p1, float r1, float r2, float intensity, float r, float g, float b, int affected_objnum)
473 if ( Lighting_off ) return;
475 if (!Lighting_flag) return;
477 // if ( key_pressed(SDLK_LSHIFT) ) return;
479 if ( Num_lights >= MAX_LIGHTS ) {
480 mprintf(( "Out of lights!\n" ));
484 l = &Lights[Num_lights++];
492 l->intensity = intensity;
495 l->rad1_squared = l->rad1*l->rad1;
496 l->rad2_squared = l->rad2*l->rad2;
497 l->ignore_objnum = -1;
498 l->affected_objnum = affected_objnum;
500 SDL_assert( Num_light_levels <= 1 );
503 // Reset the list of lights to point to all lights.
504 void light_filter_reset()
509 if ( Lighting_off ) return;
511 Num_light_levels = 1;
513 int n = Num_light_levels-1;
514 Num_relevent_lights[n] = 0;
517 for (i=0; i<Num_lights; i++, l++ ) {
518 Relevent_lights[Num_relevent_lights[n]++][n] = l;
523 // Makes a list of only the lights that will affect
524 // the sphere specified by 'pos' and 'rad' and 'objnum'
525 int light_filter_push( int objnum, vector *pos, float rad )
530 if ( Lighting_off ) return 0;
532 light_filter_reset();
535 n1 = Num_light_levels-1;
536 n2 = Num_light_levels;
538 SDL_assert( Num_light_levels < MAX_LIGHT_LEVELS );
540 Num_relevent_lights[n2] = 0;
542 for (i=0; i<Num_relevent_lights[n1]; i++ ) {
543 l = Relevent_lights[i][n1];
547 //Relevent_lights[Num_relevent_lights[n2]++][n2] = l;
551 // if this is a "unique" light source, it only affects one guy
552 if(l->affected_objnum >= 0){
553 if(objnum == l->affected_objnum){
555 float dist_squared, max_dist_squared;
556 vm_vec_sub( &to_light, &l->vec, pos );
557 dist_squared = vm_vec_mag_squared(&to_light);
559 max_dist_squared = l->rad2+rad;
560 max_dist_squared *= max_dist_squared;
562 if ( dist_squared < max_dist_squared ) {
563 Relevent_lights[Num_relevent_lights[n2]++][n2] = l;
567 // otherwise check all relevant objects
569 // if ( (l->ignore_objnum<0) || (l->ignore_objnum != objnum) ) {
571 float dist_squared, max_dist_squared;
572 vm_vec_sub( &to_light, &l->vec, pos );
573 dist_squared = vm_vec_mag_squared(&to_light);
575 max_dist_squared = l->rad2+rad;
576 max_dist_squared *= max_dist_squared;
578 if ( dist_squared < max_dist_squared ) {
579 Relevent_lights[Num_relevent_lights[n2]++][n2] = l;
586 // hmm. this could probably be more optimal
588 // all tubes are "unique" light sources for now
589 if((l->affected_objnum >= 0) && (objnum == l->affected_objnum)){
590 Relevent_lights[Num_relevent_lights[n2]++][n2] = l;
595 Int3(); // Invalid light type
599 return Num_relevent_lights[n2];
602 int is_inside( vector *min, vector *max, vector * p0, float rad )
604 float *origin = (float *)&p0->xyz.x;
605 float *minB = (float *)min;
606 float *maxB = (float *)max;
609 for (i=0; i<3; i++ ) {
610 if ( origin[i] < minB[i] - rad ) {
612 } else if (origin[i] > maxB[i] + rad ) {
620 int light_filter_push_box( vector *min, vector *max )
625 if ( Lighting_off ) return 0;
628 n1 = Num_light_levels-1;
629 n2 = Num_light_levels;
632 // static int mll = -1;
633 // if ( Num_light_levels > mll ) {
634 // mll = Num_light_levels;
635 // mprintf(( "Max level = %d\n", mll ));
638 SDL_assert( Num_light_levels < MAX_LIGHT_LEVELS );
640 Num_relevent_lights[n2] = 0;
642 for (i=0; i<Num_relevent_lights[n1]; i++ ) {
643 l = Relevent_lights[i][n1];
647 Int3(); //Relevent_lights[Num_relevent_lights[n2]++][n2] = l;
652 if ( is_inside( min, max, &l->local_vec, l->rad2 ) ) {
653 Relevent_lights[Num_relevent_lights[n2]++][n2] = l;
659 if ( is_inside(min, max, &l->local_vec, l->rad2) || is_inside(min, max, &l->local_vec2, l->rad2) ) {
660 Relevent_lights[Num_relevent_lights[n2]++][n2] = l;
665 Int3(); // Invalid light type
669 return Num_relevent_lights[n2];
672 void light_filter_pop()
674 if ( Lighting_off ) return;
677 SDL_assert( Num_light_levels > 0 );
680 int l_num_points=0, l_num_lights=0;
683 void light_rotate_all()
688 if ( Lighting_off ) return;
690 int n = Num_light_levels-1;
692 for (i=0; i<Num_relevent_lights[n]; i++ ) {
693 l = Relevent_lights[i][n];
697 for(i=0; i<Static_light_count; i++){
698 light_rotate(Static_light[i]);
702 // for (i=0; i<Num_lights; i++, l++ ) {
707 // return the # of global light sources
708 int light_get_global_count()
710 return Static_light_count;
713 int light_get_global_dir(vector *pos, int n)
715 if((n > MAX_STATIC_LIGHTS) || (n > Static_light_count-1)){
719 if ( Static_light[n] == NULL ) {
724 *pos = Static_light[n]->vec;
726 if ( Lighting_mode != LM_DARKEN ) {
727 vm_vec_scale( pos, -1.0f );
734 void light_set_shadow( int state )
736 Light_in_shadow = state;
740 ubyte light_apply( vector *pos, vector * norm, float static_light_level )
746 if (Detail.lighting==0) {
748 return ubyte(fl2i(static_light_level*255.0f));
751 if ( Lighting_off ) return 191;
753 // Factor in ambient light
754 lval = Ambient_light;
756 // Factor in light from suns if there are any
757 if ( !Light_in_shadow ){
758 // apply all sun lights
759 for(idx=0; idx<Static_light_count; idx++){
763 if(Static_light[idx] == NULL){
767 // calculate light from surface normal
768 ltmp = -vm_vec_dot(&Static_light[idx]->local_vec, norm )*Static_light[idx]->intensity*Reflective_light; // reflective light
770 switch(Lighting_mode) {
786 // At this point, l must be between 0 and 0.75 (0.75-1.0 is for dynamic light only)
789 } else if ( lval > 0.75f ) {
793 lval *= static_light_level;
795 int n = Num_light_levels-1;
797 l_num_lights += Num_relevent_lights[n];
800 for (i=0; i<Num_relevent_lights[n]; i++ ) {
801 l = Relevent_lights[i][n];
805 vm_vec_sub( &to_light, &l->local_vec, pos );
806 dot = vm_vec_dot(&to_light, norm );
808 dist = vm_vec_mag_squared(&to_light);
809 if ( dist < l->rad1_squared ) {
810 lval += l->intensity*dot;
811 } else if ( dist < l->rad2_squared ) {
813 float x = dist - l->rad1_squared;
814 float d = l->rad2_squared - l->rad1_squared;
815 float ltmp = (1.0f - x / d )*dot*l->intensity;
824 return ubyte(fl2i(lval*255.0f));
828 void light_apply_rgb( ubyte *param_r, ubyte *param_g, ubyte *param_b, vector *pos, vector * norm, float static_light_level )
831 float rval, gval, bval;
834 if (Detail.lighting==0) {
836 ubyte s = ubyte(fl2i(static_light_level*255.0f));
843 if ( Lighting_off ) {
850 // Factor in ambient light
851 rval = Ambient_light;
852 gval = Ambient_light;
853 bval = Ambient_light;
855 // Factor in light from sun if there is one
856 if ( !Light_in_shadow ){
857 // apply all sun lights
858 for(idx=0; idx<Static_light_count; idx++){
862 if(Static_light[idx] == NULL){
866 // calculate light from surface normal
867 ltmp = -vm_vec_dot(&Static_light[idx]->local_vec, norm )*Static_light[idx]->intensity*Reflective_light; // reflective light
869 switch(Lighting_mode) {
872 rval += Static_light[idx]->r * ltmp;
873 gval += Static_light[idx]->g * ltmp;
874 bval += Static_light[idx]->b * ltmp;
879 rval -= ltmp; if ( rval < 0.0f ) rval = 0.0f;
880 gval -= ltmp; if ( gval < 0.0f ) gval = 0.0f;
881 bval -= ltmp; if ( bval < 0.0f ) bval = 0.0f;
888 // At this point, l must be between 0 and 0.75 (0.75-1.0 is for dynamic light only)
891 } else if ( rval > 0.75f ) {
896 } else if ( gval > 0.75f ) {
901 } else if ( bval > 0.75f ) {
905 rval *= static_light_level;
906 gval *= static_light_level;
907 bval *= static_light_level;
909 int n = Num_light_levels-1;
911 l_num_lights += Num_relevent_lights[n];
917 for (i=0; i<Num_relevent_lights[n]; i++ ) {
918 l = Relevent_lights[i][n];
924 vm_vec_sub( &to_light, &l->local_vec, pos );
929 if(vm_vec_dist_to_line(pos, &l->local_vec, &l->local_vec2, &temp, &dist) != 0){
932 vm_vec_sub(&to_light, &temp, pos);
933 dist *= dist; // since we use radius squared
941 dot = vm_vec_dot(&to_light, norm);
944 // indicating that we already calculated the distance (vm_vec_dist_to_line(...) does this for us)
946 dist = vm_vec_mag_squared(&to_light);
948 if ( dist < l->rad1_squared ) {
950 ratio = l->intensity*dot;
955 } else if ( dist < l->rad2_squared ) {
958 float x = dist - l->rad1_squared;
959 float d = l->rad2_squared - l->rad1_squared;
960 ratio = (1.0f - x / d)*dot*l->intensity;
970 if ( gval > m ) m = gval;
971 if ( bval > m ) m = bval;
983 } else if ( rval > 1.0f ) {
988 } else if ( gval > 1.0f ) {
993 } else if ( bval > 1.0f ) {
997 *param_r = ubyte(fl2i(rval*255.0f));
998 *param_g = ubyte(fl2i(gval*255.0f));
999 *param_b = ubyte(fl2i(bval*255.0f));
1004 float light_apply( vector *pos, vector * norm )
1008 light_apply_rgb( &r, &g, &b, pos, norm );
1009 return (r+g+b) / 3.0f;
1011 return light_apply_ramp( pos, norm );