From f6e68c4b86deb63d2a3a69e0e473ad618a774a20 Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Tue, 24 Feb 2015 22:34:17 -0700 Subject: [PATCH] _sunlight2 (sky light, light dome) support from q3map2 _sunlight penumbra (deviance) from q3map2 light penumbra from q3map2 --- include/light/entities.h | 10 +- include/light/light.h | 13 +- light/entities.c | 353 ++++++++++++++++++++++++++++++++++----- light/light.c | 5 +- light/ltface.c | 55 +++--- man/light.1 | 28 +++- 6 files changed, 387 insertions(+), 77 deletions(-) diff --git a/include/light/entities.h b/include/light/entities.h index f7006b6a..fff42e61 100644 --- a/include/light/entities.h +++ b/include/light/entities.h @@ -71,7 +71,6 @@ typedef struct entity_s { /* worldspawn only */ vec_t dirtdepth; int dirtmode; - int sunlight_dirt; int minlight_dirt; /* worldspawn, light entities */ @@ -79,10 +78,18 @@ typedef struct entity_s { vec_t dirtgain; int dirt; + /* light entities: q3map2 penumbra */ + vec_t deviance; + int num_samples; + char target[MAX_ENT_VALUE]; char targetname[MAX_ENT_VALUE]; struct epair_s *epairs; const struct entity_s *targetent; + + qboolean generated; // if true, don't write to the bsp + + struct entity_s *next; } entity_t; /* @@ -101,7 +108,6 @@ typedef struct entity_s { */ extern entity_t *entities; -extern int num_entities; entity_t *FindEntityWithKeyPair(const char *key, const char *value); const char *ValueForKey(const entity_t *ent, const char *key); diff --git a/include/light/light.h b/include/light/light.h index 732837b9..4e21a791 100644 --- a/include/light/light.h +++ b/include/light/light.h @@ -84,6 +84,14 @@ typedef struct { vec3_t offset; } modelinfo_t; +typedef struct sun_s { + vec3_t sunvec; + lightsample_t sunlight; + struct sun_s *next; + qboolean dirt; + float anglescale; +} sun_t; + /* tracelist is a null terminated array of BSP models to use for LOS tests */ extern const dmodel_t *const *tracelist; @@ -101,8 +109,8 @@ extern const vec3_t vec3_white; extern qboolean addminlight; extern lightsample_t minlight; -extern lightsample_t sunlight; -extern vec3_t sunvec; + +extern sun_t *suns; /* dirt */ @@ -114,7 +122,6 @@ extern float dirtScale; extern float dirtGain; extern qboolean globalDirt; // apply dirt to all lights (unless they override it)? -extern qboolean sunlightDirt; // apply dirt to sunlight? extern qboolean minlightDirt; // apply dirt to minlight? extern qboolean dirtModeSetOnCmdline; diff --git a/light/entities.c b/light/entities.c index 836c2a81..3f423358 100644 --- a/light/entities.c +++ b/light/entities.c @@ -24,8 +24,18 @@ #include entity_t *entities; -int num_entities; -static int max_entities; +static entity_t *entities_tail; +static int num_entities; +static int num_lights; + +/* temporary storage for sunlight settings before the sun_t objects are + created. */ +static lightsample_t sunlight = { 0, { 255, 255, 255 } }; +static lightsample_t sunlight2 = { 0, { 255, 255, 255 } }; +static int sunlight_dirt = 0; +static int sunlight2_dirt = 0; +static vec3_t sunvec = { 0, 0, -1 }; /* defaults to straight down */ +static vec_t sun_deviance = 0; /* * ============================================================================ @@ -81,20 +91,19 @@ LightStyleForTargetname(const char *targetname) static void MatchTargets(void) { - int i, j; entity_t *entity; const entity_t *target; - for (i = 0, entity = entities; i < num_entities; i++, entity++) { + for (entity = entities; entity; entity = entity->next) { if (!entity->target[0]) continue; - for (j = 0, target = entities; j < num_entities; j++, target++) { + for (target = entities; target; target = target->next) { if (!strcmp(target->targetname, entity->target)) { entity->targetent = target; break; } } - if (j == num_entities) { + if (target == NULL) { logprint("WARNING: entity at (%s) (%s) has unmatched " "target (%s)\n", VecStr(entity->origin), entity->classname, entity->target); @@ -114,10 +123,9 @@ MatchTargets(void) static void SetupSpotlights(void) { - int i; entity_t *entity; - for (i = 0, entity = entities; i < num_entities; i++, entity++) { + for (entity = entities; entity; entity = entity->next) { if (strncmp(entity->classname, "light", 5)) continue; if (entity->targetent) { @@ -181,6 +189,14 @@ CheckEntityFields(entity_t *entity) { if (!entity->light.light) entity->light.light = DEFAULTLIGHTLEVEL; + + /* ydnar: get deviance and samples */ + if (entity->deviance < 0.0f || entity->num_samples < 1) { + entity->deviance = 0.0f; + entity->num_samples = 1; + } + entity->light.light /= entity->num_samples; + if (entity->atten <= 0.0) entity->atten = 1.0; if (entity->anglescale < 0 || entity->anglescale > 1.0) @@ -245,6 +261,188 @@ CheckEntityFields(entity_t *entity) } } +/* + * ============= + * Dirt_ResolveFlag + * + * Resolves a dirt flag (0=default, 1=enable, -1=disable) to a boolean + * ============= + */ +static qboolean +Dirt_ResolveFlag(int dirtInt) +{ + if (dirtInt == 1) return true; + else if (dirtInt == -1) return false; + else return globalDirt; +} + +/* + * ============= + * AddSun + * ============= + */ +static void +AddSun(vec3_t sunvec, lightsample_t sunlight, float anglescale, int dirtInt) +{ + sun_t *sun = malloc(sizeof(sun_t)); + memset(sun, 0, sizeof(*sun)); + VectorCopy(sunvec, sun->sunvec); + VectorNormalize(sun->sunvec); + VectorScale(sun->sunvec, -16384, sun->sunvec); + sun->sunlight = sunlight; + sun->anglescale = anglescale; + sun->dirt = Dirt_ResolveFlag(dirtInt); + + // add to list + sun->next = suns; + suns = sun; + + // printf( "sun is using vector %f %f %f light %f color %f %f %f anglescale %f dirt %d resolved to %d\n", + // sun->sunvec[0], sun->sunvec[1], sun->sunvec[2], sun->sunlight.light, + // sun->sunlight.color[0], sun->sunlight.color[1], sun->sunlight.color[2], + // anglescale, + // dirtInt, + // (int)sun->dirt); +} + +/* + * ============= + * SetupSuns + * + * Creates a sun_t object for the "_sunlight" worldspawn key, + * optionall many suns if the "_sunlight_penumbra" key is used. + * + * From q3map2 + * ============= + */ +static void +SetupSuns() +{ + int i; + int sun_num_samples = 100; + + if (sun_deviance == 0) { + sun_num_samples = 1; + } else { + logprint("using _sunlight_penumbra of %f degrees from worldspawn.\n", sun_deviance); + } + + VectorNormalize(sunvec); + + //printf( "input sunvec %f %f %f. deviance is %f, %d samples\n",sunvec[0],sunvec[1], sunvec[2], sun_deviance, sun_num_samples); + + /* set photons */ + sunlight.light /= sun_num_samples; + + for ( i = 0; i < sun_num_samples; i++ ) + { + vec3_t direction; + + /* calculate sun direction */ + if ( i == 0 ) { + VectorCopy( sunvec, direction ); + } + else + { + vec_t da, de; + vec_t d = sqrt( sunvec[ 0 ] * sunvec[ 0 ] + sunvec[ 1 ] * sunvec[ 1 ] ); + vec_t angle = atan2( sunvec[ 1 ], sunvec[ 0 ] ); + vec_t elevation = atan2( sunvec[ 2 ], d ); + + /* jitter the angles (loop to keep random sample within sun->deviance steridians) */ + do + { + da = ( Random() * 2.0f - 1.0f ) * DEG2RAD(sun_deviance); + de = ( Random() * 2.0f - 1.0f ) * DEG2RAD(sun_deviance); + } + while ( ( da * da + de * de ) > ( sun_deviance * sun_deviance ) ); + angle += da; + elevation += de; + + /* create new vector */ + direction[ 0 ] = cos( angle ) * cos( elevation ); + direction[ 1 ] = sin( angle ) * cos( elevation ); + direction[ 2 ] = sin( elevation ); + } + + //printf( "sun %d is using vector %f %f %f\n", i, direction[0], direction[1], direction[2]); + + AddSun(direction, sunlight, sun_anglescale, sunlight_dirt); + } +} + +/* + * ============= + * SetupSkyDome + * + * Setup a dome of suns for the "_sunlight2" worldspawn key. + * + * From q3map2 + * ============= + */ +static void +SetupSkyDome() +{ + int i, j, numSuns; + int angleSteps, elevationSteps; + float angle, elevation; + float angleStep, elevationStep; + float step, start; + vec3_t direction; + const int iterations = 8; + + /* dummy check */ + if ( sunlight2.light <= 0.0f || iterations < 2 ) { + return; + } + + /* calculate some stuff */ + step = 2.0f / ( iterations - 1 ); + start = -1.0f; + + /* setup */ + elevationSteps = iterations - 1; + angleSteps = elevationSteps * 4; + angle = 0.0f; + elevationStep = DEG2RAD( 90.0f / iterations ); /* skip elevation 0 */ + angleStep = DEG2RAD( 360.0f / angleSteps ); + + /* calc individual sun brightness */ + numSuns = angleSteps * elevationSteps + 1; + logprint("using %d suns for _sunlight2. total light: %f color: %f %f %f\n", numSuns, sunlight2.light, sunlight2.color[0], sunlight2.color[1], sunlight2.color[2]); + sunlight2.light /= numSuns; + + /* iterate elevation */ + elevation = elevationStep * 0.5f; + angle = 0.0f; + for ( i = 0, elevation = elevationStep * 0.5f; i < elevationSteps; i++ ) + { + /* iterate angle */ + for ( j = 0; j < angleSteps; j++ ) + { + /* create sun */ + direction[ 0 ] = cos( angle ) * cos( elevation ); + direction[ 1 ] = sin( angle ) * cos( elevation ); + direction[ 2 ] = -sin( elevation ); + + AddSun(direction, sunlight2, 0.0, sunlight2_dirt); + + /* move */ + angle += angleStep; + } + + /* move */ + elevation += elevationStep; + angle += angleStep / elevationSteps; + } + + /* create vertical sun */ + VectorSet( direction, 0.0f, 0.0f, 1.0f ); + + AddSun(direction, sunlight2, 0.0, sunlight2_dirt); +} + +#if 0 /* * Quick count of entities. * Assumes correct syntax, etc. @@ -287,6 +485,62 @@ CountEntities(const char *entitystring) pos++; } } +#endif + +/* + * ============= + * Entities_Insert + * + * Adds the entity to the linked list + * ============= + */ +static void +Entities_Insert(entity_t *entity) +{ + /* Insert it into the tail end of the list */ + if (num_entities == 0) { + entities = entity; + entities_tail = entity; + } else { + entities_tail->next = entity; + entities_tail = entity; + } + entity->next = NULL; + num_entities++; +} + +/* + * ============= + * JitterEntity + * + * Creates jittered copies of the light if specified using the "_samples" and "_deviance" keys. + * + * From q3map2 + * ============= + */ +static void +JitterEntity(entity_t *entity) +{ + int j; + + /* jitter the light */ + for ( j = 1; j < entity->num_samples; j++ ) + { + /* create a light */ + entity_t *light2 = malloc( sizeof( *entity ) ); + memcpy( light2, entity, sizeof( *entity ) ); + + light2->generated = true; // don't write generated light to bsp + + /* add to list */ + Entities_Insert(light2); + + /* jitter it */ + light2->origin[ 0 ] = entity->origin[ 0 ] + ( Random() * 2.0f - 1.0f ) * entity->deviance; + light2->origin[ 1 ] = entity->origin[ 1 ] + ( Random() * 2.0f - 1.0f ) * entity->deviance; + light2->origin[ 2 ] = entity->origin[ 2 ] + ( Random() * 2.0f - 1.0f ) * entity->deviance; + } +} /* * ================== @@ -301,19 +555,12 @@ LoadEntities(const bsp2_t *bsp) char key[MAX_ENT_KEY]; epair_t *epair; vec3_t vec; - int memsize, num_lights; - - /* Count the entities and allocate memory */ - max_entities = CountEntities(bsp->dentdata); - memsize = max_entities * sizeof(*entities); - entities = malloc(memsize); - if (!entities) - Error("%s: allocation of %d bytes failed\n", __func__, memsize); - memset(entities, 0, memsize); /* start parsing */ num_entities = 0; - num_lights = 0; + entities = NULL; + entities_tail = NULL; + num_lights = 0; data = bsp->dentdata; /* go through all the entities */ @@ -325,10 +572,10 @@ LoadEntities(const bsp2_t *bsp) if (com_token[0] != '{') Error("%s: found %s when expecting {", __func__, com_token); - if (num_entities == max_entities) - Error("%s: Internal Error - exceeded max_entities", __func__); - entity = &entities[num_entities]; - num_entities++; + /* Allocate a new entity */ + entity = (entity_t *)malloc(sizeof(entity_t)); + memset(entity, 0, sizeof(*entity)); + Entities_Insert(entity); /* Init some fields... */ entity->anglescale = -1; @@ -398,11 +645,14 @@ LoadEntities(const bsp2_t *bsp) else if (!strcmp(key, "_sun_mangle")) { scan_vec3(vec, com_token, "_sun_mangle"); vec_from_mangle(sunvec, vec); - VectorNormalize(sunvec); - VectorScale(sunvec, -16384, sunvec); } else if (!strcmp(key, "_sunlight_color")) { scan_vec3(sunlight.color, com_token, "_sunlight_color"); normalize_color_format(sunlight.color); + } else if (!strcmp(key, "_sunlight2")) + sunlight2.light = atof(com_token); + else if (!strcmp(key, "_sunlight2_color") || !strcmp(key, "_sunlight_color2")) { + scan_vec3(sunlight2.color, com_token, key); + normalize_color_format(sunlight2.color); } else if (!strcmp(key, "_minlight_color")) { scan_vec3(minlight.color, com_token, "_minlight_color"); normalize_color_format(minlight.color); @@ -413,7 +663,9 @@ LoadEntities(const bsp2_t *bsp) else if (!strcmp(key, "_dirtmode")) entity->dirtmode = atoi(com_token); else if (!strcmp(key, "_sunlight_dirt")) - entity->sunlight_dirt = atoi(com_token); + sunlight_dirt = atoi(com_token); + else if (!strcmp(key, "_sunlight2_dirt")) + sunlight2_dirt = atoi(com_token); else if (!strcmp(key, "_minlight_dirt")) entity->minlight_dirt = atoi(com_token); else if (!strcmp(key, "_dirtscale")) @@ -428,6 +680,15 @@ LoadEntities(const bsp2_t *bsp) dirty = true; } } + else if (!strcmp(key, "_sunlight_penumbra")) { + sun_deviance = atof(com_token); + } + else if (!strcmp(key, "_deviance")) { + entity->deviance = atof(com_token); + } + else if (!strcmp(key, "_samples")) { + entity->num_samples = atoi(com_token); + } } /* @@ -436,6 +697,8 @@ LoadEntities(const bsp2_t *bsp) if (!strncmp(entity->classname, "light", 5)) { CheckEntityFields(entity); num_lights++; + + JitterEntity(entity); } if (!strcmp(entity->classname, "light")) { if (entity->targetname[0] && !entity->style) { @@ -483,16 +746,19 @@ LoadEntities(const bsp2_t *bsp) logprint("Global dirtmapping enabled in worldspawn.\n"); } - if (entity->sunlight_dirt == 1) { - sunlightDirt = true; + if (sunlight_dirt == 1) { dirty = true; logprint("Sunlight dirtmapping enabled in worldspawn.\n"); - } else if (entity->sunlight_dirt == -1) { - sunlightDirt = false; + } else if (sunlight_dirt == -1) { logprint("Sunlight dirtmapping disabled in worldspawn.\n"); - } else { - sunlightDirt = globalDirt; - } + } + + if (sunlight2_dirt == 1) { + dirty = true; + logprint("Sunlight2 dirtmapping enabled in worldspawn.\n"); + } else if (sunlight2_dirt == -1) { + logprint("Sunlight2 dirtmapping disabled in worldspawn.\n"); + } if (entity->minlight_dirt == 1) { minlightDirt = true; @@ -508,7 +774,8 @@ LoadEntities(const bsp2_t *bsp) } if (!VectorCompare(sunlight.color, vec3_white) || - !VectorCompare(minlight.color, vec3_white)) { + !VectorCompare(minlight.color, vec3_white) || + !VectorCompare(sunlight2.color, vec3_white)) { if (!write_litfile) { write_litfile = true; logprint("Colored light entities detected: " @@ -519,6 +786,8 @@ LoadEntities(const bsp2_t *bsp) logprint("%d entities read, %d are lights.\n", num_entities, num_lights); MatchTargets(); SetupSpotlights(); + SetupSuns(); + SetupSkyDome(); } const char * @@ -537,10 +806,8 @@ FindEntityWithKeyPair(const char *key, const char *value) { entity_t *ent; epair_t *ep; - int i; - for (i = 0; i < num_entities; i++) { - ent = &entities[i]; + for (ent = entities; ent; ent = ent->next) { for (ep = ent->epairs; ep; ep = ep->next) if (!strcmp(ep->key, key)) { if (!strcmp(ep->value, value)) @@ -561,17 +828,18 @@ GetVectorForKey(const entity_t *ent, const char *key, vec3_t vec) } static size_t -Get_EntityStringSize(const entity_t *entities, int num_entities) +Get_EntityStringSize(const entity_t *entities) { const entity_t *entity; const epair_t *epair; size_t size; - int i; size = 0; - for (i = 0, entity = entities; i < num_entities; i++, entity++) { + for (entity = entities; entity; entity = entity->next) { if (!entity->epairs) continue; + if (entity->generated) + continue; size += 2; /* "{\n" */ for (epair = entity->epairs; epair; epair = epair->next) { /* 6 extra chars for quotes, space and newline */ @@ -597,7 +865,6 @@ WriteEntitiesToString(bsp2_t *bsp) const epair_t *epair; size_t space, length; char *pos; - int i; if (bsp->dentdata) free(bsp->dentdata); @@ -605,7 +872,7 @@ WriteEntitiesToString(bsp2_t *bsp) /* FIXME - why are we printing this here? */ logprint("%i switchable light styles\n", numlighttargets); - bsp->entdatasize = Get_EntityStringSize(entities, num_entities); + bsp->entdatasize = Get_EntityStringSize(entities); bsp->dentdata = malloc(bsp->entdatasize); if (!bsp->dentdata) Error("%s: allocation of %d bytes failed\n", __func__, @@ -613,9 +880,11 @@ WriteEntitiesToString(bsp2_t *bsp) space = bsp->entdatasize; pos = bsp->dentdata; - for (i = 0, entity = entities; i < num_entities; i++, entity++) { + for (entity = entities; entity; entity = entity->next) { if (!entity->epairs) continue; + if (entity->generated) + continue; length = snprintf(pos, space, "{\n"); pos += length; diff --git a/light/light.c b/light/light.c index 425525c2..a48353f1 100644 --- a/light/light.c +++ b/light/light.c @@ -32,8 +32,7 @@ const vec3_t vec3_white = { 255, 255, 255 }; qboolean addminlight = false; lightsample_t minlight = { 0, { 255, 255, 255 } }; -lightsample_t sunlight = { 0, { 255, 255, 255 } }; -vec3_t sunvec = { 0, 0, 16384 }; /* defaults to straight down */ +sun_t *suns = NULL; /* dirt */ qboolean dirty = false; @@ -44,7 +43,6 @@ float dirtScale = 1.0f; float dirtGain = 1.0f; qboolean globalDirt = false; -qboolean sunlightDirt = false; qboolean minlightDirt = false; qboolean dirtSetOnCmdline = false; @@ -278,7 +276,6 @@ main(int argc, const char **argv) } else if ( !strcmp( argv[ i ], "-dirty" ) ) { dirty = true; globalDirt = true; - sunlightDirt = true; minlightDirt = true; logprint( "Dirtmapping enabled globally\n" ); } else if ( !strcmp( argv[ i ], "-dirtdebug" ) || !strcmp( argv[ i ], "-debugdirt" ) ) { diff --git a/light/ltface.c b/light/ltface.c index 6720069b..dff3f0cf 100644 --- a/light/ltface.c +++ b/light/ltface.c @@ -731,12 +731,17 @@ Dirt_GetScaleFactor(vec_t occlusion, const entity_t *entity) return 1.0f; /* should this light be affected by dirt? */ - if (entity && entity->dirt == -1) { - usedirt = false; - } else if (entity && entity->dirt == 1) { - usedirt = true; + if (entity) { + if (entity->dirt == -1) { + usedirt = false; + } else if (entity->dirt == 1) { + usedirt = true; + } else { + usedirt = globalDirt; + } } else { - usedirt = globalDirt; + /* no entity is provided, assume the caller wants dirt */ + usedirt = true; } /* if not, quit */ @@ -847,7 +852,9 @@ LightFace_Entity(const entity_t *entity, const lightsample_t *light, Light_Add(sample, add, light->color); /* Check if we really hit, ignore tiny lights */ - if (!hit && sample->light >= 1) + /* ericw -- don't ignore tiny lights if the light was split up + for a penumbra; the small light values are important in that case */ + if (!hit && (sample->light >= 1 || entity->num_samples > 1)) hit = true; } @@ -861,8 +868,7 @@ LightFace_Entity(const entity_t *entity, const lightsample_t *light, * ============= */ static void -LightFace_Sky(const lightsample_t *light, const vec3_t vector, - const lightsurf_t *lightsurf, lightmap_t *lightmaps) +LightFace_Sky(const sun_t *sun, const lightsurf_t *lightsurf, lightmap_t *lightmaps) { const modelinfo_t *modelinfo = lightsurf->modelinfo; const plane_t *plane = &lightsurf->plane; @@ -876,16 +882,16 @@ LightFace_Sky(const lightsample_t *light, const vec3_t vector, lightmap_t *lightmap; /* Don't bother if surface facing away from sun */ - if (DotProduct(vector, plane->normal) < -ANGLE_EPSILON) + if (DotProduct(sun->sunvec, plane->normal) < -ANGLE_EPSILON) return; /* if sunlight is set, use a style 0 light map */ lightmap = Lightmap_ForStyle(lightmaps, 0); - VectorCopy(vector, incoming); + VectorCopy(sun->sunvec, incoming); VectorNormalize(incoming); angle = DotProduct(incoming, plane->normal); - angle = (1.0 - sun_anglescale) + sun_anglescale * angle; + angle = (1.0 - sun->anglescale) + sun->anglescale * angle; /* Check each point... */ hit = false; @@ -894,13 +900,13 @@ LightFace_Sky(const lightsample_t *light, const vec3_t vector, surfpoint = lightsurf->points[0]; for (i = 0; i < lightsurf->numpoints; i++, sample++, surfpoint += 3) { vec_t value; - if (!TestSky(surfpoint, vector, shadowself)) + if (!TestSky(surfpoint, sun->sunvec, shadowself)) continue; - value = angle * light->light; - if (sunlightDirt) + value = angle * sun->sunlight.light; + if (sun->dirt) value *= Dirt_GetScaleFactor(lightsurf->occlusion[i], NULL); - Light_Add(sample, value, light->color); - if (!hit && sample->light >= 1) + Light_Add(sample, value, sun->sunlight.color); + if (!hit/* && (sample->light >= 1)*/) hit = true; } @@ -945,7 +951,7 @@ LightFace_Min(const lightsample_t *light, /* Cast rays for local minlight entities */ shadowself = modelinfo->shadowself ? modelinfo->model : NULL; - for (i = 0, entity = entities; i < num_entities; i++, entity++) { + for (entity = entities; entity; entity = entity->next) { if (entity->formula != LF_LOCALMIN) continue; @@ -1292,6 +1298,7 @@ LightFace(bsp2_dface_t *face, const modelinfo_t *modelinfo, const entity_t *entity; lightsample_t *sample; lightsurf_t lightsurf; + sun_t *sun; /* One extra lightmap is allocated to simplify handling overflow */ lightmap_t lightmaps[MAXLIGHTMAPS + 1]; @@ -1317,14 +1324,15 @@ LightFace(bsp2_dface_t *face, const modelinfo_t *modelinfo, */ /* positive lights */ - for (i = 0, entity = entities; i < num_entities; i++, entity++) { + for (entity = entities; entity; entity = entity->next) { if (entity->formula == LF_LOCALMIN) continue; if (entity->light.light > 0) LightFace_Entity(entity, &entity->light, &lightsurf, lightmaps); } - if (sunlight.light > 0) - LightFace_Sky(&sunlight, sunvec, &lightsurf, lightmaps); + for ( sun = suns; sun; sun = sun->next ) + if (sun->sunlight.light > 0) + LightFace_Sky (sun, &lightsurf, lightmaps); /* minlight - Use the greater of global or model minlight. */ if (modelinfo->minlight.light > minlight.light) @@ -1333,14 +1341,15 @@ LightFace(bsp2_dface_t *face, const modelinfo_t *modelinfo, LightFace_Min(&minlight, &lightsurf, lightmaps); /* negative lights */ - for (i = 0, entity = entities; i < num_entities; i++, entity++) { + for (entity = entities; entity; entity = entity->next) { if (entity->formula == LF_LOCALMIN) continue; if (entity->light.light < 0) LightFace_Entity(entity, &entity->light, &lightsurf, lightmaps); } - if (sunlight.light < 0) - LightFace_Sky(&sunlight, sunvec, &lightsurf, lightmaps); + for ( sun = suns; sun; sun = sun->next ) + if (sun->sunlight.light < 0) + LightFace_Sky (sun, &lightsurf, lightmaps); /* replace lightmaps with AO for debugging */ if (dirtDebug) diff --git a/man/light.1 b/man/light.1 index 03864262..b5f4f98b 100644 --- a/man/light.1 +++ b/man/light.1 @@ -96,20 +96,37 @@ pitch specifies the angle from 90 (straight up) to -90 (straight down). Roll has no effect, so use any value (e.g. 0). Default is straight down ("0 -90 0"). +.IP "\fB""_sunlight_penumbra"" ""n""\fP" +Specifies the penumbra width, in degrees, of sunlight. +Useful values are 3-4 for a gentle soft edge, or 10-20+ for more diffuse +sunlight. Default is 0. + .IP "\fB""_sunlight_color"" ""r g b""\fP" Specify red(r), green(g) and blue(b) components for the colour of the sunlight. RGB component values are between 0 and 255 (between 0 and 1 is also accepted). Default is white light ("255 255 255"). +.IP "\fB""_sunlight2"" ""n""\fP" +Set the brightness of a large dome of lights positioned around the map (16K unit +radius). Useful for simulating higly diffused light (e.g. cloudy skies) +in outdoor areas. Default 0. + +.IP "\fB""_sunlight_color2"" ""r g b""\fP | \fB""_sunlight2_color"" ""r g b""\fP" +Specifies the colour of _sunlight2, same format as "_sunlight_color". Default is +white light ("255 255 255"). + .IP "\fB""_dirt"" ""n""\fP" 1 enables dirtmapping (ambient occlusion) on all lights, borrowed from q3map2. This adds shadows to corners and crevices. You can override the global setting for specific lights with the -"_dirt" light entitiy key or "_sunlight_nodirt" and "_minlight_nodirt" worldspawn keys. +"_dirt" light entitiy key or "_sunlight_dirt", "_sunlight2_dirt", and "_minlight_dirt" worldspawn keys. Default is no dirtmapping (-1). .IP "\fB""_sunlight_dirt"" ""n""\fP" 1 enables dirtmapping (ambient occlusion) on sunlight, -1 to disable (making it illuminate the dirtmapping shadows). Default is to use the value of "_dirt". + +.IP "\fB""_sunlight2_dirt"" ""n""\fP" +1 enables dirtmapping (ambient occlusion) on sunlight2, -1 to disable. Default is to use the value of "_dirt". .IP "\fB""_minlight_dirt"" ""n""\fP" 1 enables dirtmapping (ambient occlusion) on minlight, -1 to disable. Default is to use the value of "_dirt". @@ -232,9 +249,14 @@ keys in the worldspawn section. .IP "\fB""_dirt"" ""n""\fP" Overrides the worldspawn setting of "_dirt" for this particular light. -1 to disable dirtmapping (ambient occlusion) for this light, making it illuminate the dirtmapping shadows. 1 to enable ambient occlusion for this light. Default is to defer to the worldspawn setting. +.IP "\fB""_deviance"" ""n""\fP" +Convert the light into a sphere of randomly positioned lights with radius "n" (in world units). Useful to shadows cast by the light a wider penumbra. "_samples" must also be set to a number greater than 1. Default is 0. -.SH AUTHOR -Written by Kevin Shanahan (aka Tyrann) +.IP "\fB""_samples"" ""n""\fP" +Number of lights to use for "_deviance". Use 16 as a starting point, more for higher quality. Default 1. + +.SH AUTHOR +Written by Kevin Shanahan (aka Tyrann) .br Based on source provided by id Software .br