/* DE1: $Id: rend_bias.c 3224 2006-05-25 12:29:51Z skyjake $ * Copyright (C) 2004 Jaakko Keränen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not: http://www.opensource.org/ */ /* * rend_bias.c: Light/Shadow Bias * * Calculating macro-scale lighting on the fly. */ // HEADER FILES ------------------------------------------------------------ #include "de_base.h" #include "de_edit.h" #include "de_system.h" #include "de_graphics.h" #include "de_render.h" #include "de_refresh.h" #include "de_defs.h" #include "de_misc.h" #include "p_sight.h" #include // MACROS ------------------------------------------------------------------ // TYPES ------------------------------------------------------------------- typedef struct affection_s { float intensities[MAX_BIAS_AFFECTED]; int numFound; biasaffection_t *affected; } affection_t; // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- // PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- // PRIVATE FUNCTION PROTOTYPES --------------------------------------------- void SB_EvalPoint(gl_rgba_t *light, vertexillum_t *illum, biasaffection_t *affectedSources, float *point, float *normal); // EXTERNAL DATA DECLARATIONS ---------------------------------------------- // PUBLIC DATA DEFINITIONS ------------------------------------------------- int useBias = false; int numSources = 0; unsigned int currentTimeSB; // PRIVATE DATA DEFINITIONS ------------------------------------------------ static source_t sources[MAX_BIAS_LIGHTS]; static int numSourceDelta = 0; static int useSightCheck = true; static int biasMin = 220; static int biasMax = 255; static int updateAffected = true; static float biasIgnoreLimit = .005f; static int lightSpeed = 130; static unsigned int lastChangeOnFrame; /* * BS_EvalPoint uses these, so they must be set before it is called. */ static biastracker_t trackChanged; static biastracker_t trackApplied; static byte biasColor[3]; static float biasAmount; // CODE -------------------------------------------------------------------- /* * Register console variables for Shadow Bias. */ void SB_Register(void) { C_VAR_INT("rend-bias", &useBias, 0, 0, 1, "1=Enable the experimental shadow bias test setup."); C_VAR_INT("rend-bias-min", &biasMin, 0, 0, 255, "Sector lightlevel that is biased completely to zero."); C_VAR_INT("rend-bias-max", &biasMax, 0, 0, 255, "Sector lightlevel that retains its normal color."); C_VAR_INT("rend-bias-lightspeed", &lightSpeed, 0, 0, 5000, "Milliseconds it takes for light changes to " "become effective."); // Development variables. C_VAR_INT("rend-dev-bias-sight", &useSightCheck, CVF_NO_ARCHIVE, 0, 1, "1=Enable the use of line-of-sight checking with shadow bias."); C_VAR_INT("rend-dev-bias-affected", &updateAffected, CVF_NO_ARCHIVE, 0, 1, "1=Keep track which sources affect which surfaces."); /* C_VAR_INT("rend-dev-bias-solo", &editSelector, CVF_NO_ARCHIVE, -1, 255, "Solo light source.");*/ } /* * Creates a new bias light source and sets the appropriate properties * to the values of the passed parameters. The id of the new light source * is returned unless there are no free sources available. * * @param x X coordinate of the new light. * @param y Y coordinate of the new light. * @param z Z coordinate of the new light. * @param size Size (strength) of the new light. * @param minLight Lower sector light level limit. * @param maxLight Upper sector light level limit. * @param rgb Ptr to float[3], the color for the new light. * * @return int The id of the newly created bias light source else -1. */ int SB_NewSourceAt(float x, float y, float z, float size, int minLight, int maxLight, float *rgb) { source_t *src; if(numSources == MAX_BIAS_LIGHTS) return -1; src = &sources[numSources++]; // New lights are automatically locked. src->flags = BLF_CHANGED | BLF_LOCKED; src->pos[VX] = x; src->pos[VY] = y; src->pos[VZ] = z; SB_SetColor(src->color,rgb); src->primaryIntensity = src->intensity = size; src->sectorLevel[0] = minLight; src->sectorLevel[1] = maxLight; // This'll enforce an update (although the vertices are also // STILL_UNSEEN). src->lastUpdateTime = 0; return numSources; // == index + 1; } /* * Same as above really but for updating an existing source. */ void SB_UpdateSource(int which, float x, float y, float z, float size, int minLight, int maxLight, float *rgb) { source_t *src; if(which < 0 || which >= numSources) return; src = &sources[which]; // Position change? src->pos[VX] = x; src->pos[VY] = y; src->pos[VZ] = z; SB_SetColor(src->color, rgb); src->primaryIntensity = src->intensity = size; src->sectorLevel[0] = minLight; src->sectorLevel[1] = maxLight; } /* * Return a ptr to the bias light source by id. */ source_t *SB_GetSource(int which) { return &sources[which]; } /* * Convert bias light source ptr to index. */ int SB_ToIndex(source_t* source) { if(!source) return -1; else return (source - sources); } /* * Removes the specified bias light source from the level. * * @param which The id of the bias light source to be deleted. */ void SB_Delete(int which) { int i; sector_t *seciter; boolean done; if(which < 0 || which > numSources) return; // Very odd... // Do a memory move. for(i = which; i < numSources; ++i) sources[i].flags |= BLF_CHANGED; if(which < numSources) memmove(&sources[which], &sources[which + 1], sizeof(source_t) * (numSources - which - 1)); sources[numSources - 1].intensity = 0; // Will be one fewer very soon. numSourceDelta--; // Check to see if a mobj had acquired this source for it's own use. // You never know what trick the user might be trying pull :-) done = false; for(i = 0; i < numsectors && !done; ++i) { mobj_t *iter; seciter = SECTOR_PTR(i); for(iter = seciter->thinglist; iter && !done; iter = iter->snext) { if(iter->usingBias && iter->light == which + 1) { iter->light = 0; iter->usingBias = false; done = true; } } } } /* * Removes ALL bias light sources on the level. */ void SB_Clear(void) { int i; sector_t *seciter; while(numSources-- > 0) sources[numSources].flags |= BLF_CHANGED; numSources = 0; // Zero Bias source indices acquired by mobjs. for(i = 0; i < numsectors; ++i) { mobj_t *iter; seciter = SECTOR_PTR(i); for(iter = seciter->thinglist; iter; iter = iter->snext) { if(iter->usingBias) { iter->light = 0; iter->usingBias = false; } } } } /* * Initializes the bias lights according to the loaded Light * definitions. */ void SB_InitForLevel(const char *uniqueId) { ded_light_t *def; int i; // Start with no sources whatsoever. numSources = 0; // Check all the loaded Light definitions for any matches. for(i = 0; i < defs.count.lights.num; ++i) { def = &defs.lights[i]; if(def->state[0] || stricmp(uniqueId, def->level)) continue; if(SB_NewSourceAt(def->offset[VX], def->offset[VY], def->offset[VZ], def->size, def->lightlevel[0], def->lightlevel[1], def->color) == -1) break; } } void SB_SetColor(float *dest, float *src) { float largest = 0; int i; // Amplify the color. for(i = 0; i < 3; ++i) { dest[i] = src[i]; if(largest < dest[i]) largest = dest[i]; } if(largest > 0) { for(i = 0; i < 3; ++i) dest[i] /= largest; } else { // Replace black with white. dest[0] = dest[1] = dest[2] = 1; } } /* * Conversion from HSV to RGB. Everything is [0,1]. */ void HSVtoRGB(float *rgb, float h, float s, float v) { int i; float f, p, q, t; if(s == 0) { // achromatic (grey) rgb[0] = rgb[1] = rgb[2] = v; return; } if(h >= 1) h -= 1; h *= 6; // sector 0 to 5 i = floor(h); f = h - i; // factorial part of h p = v * (1 - s); q = v * (1 - s * f); t = v * (1 - s * (1 - f)); switch(i) { case 0: rgb[0] = v; rgb[1] = t; rgb[2] = p; break; case 1: rgb[0] = q; rgb[1] = v; rgb[2] = p; break; case 2: rgb[0] = p; rgb[1] = v; rgb[2] = t; break; case 3: rgb[0] = p; rgb[1] = q; rgb[2] = v; break; case 4: rgb[0] = t; rgb[1] = p; rgb[2] = v; break; default: rgb[0] = v; rgb[1] = p; rgb[2] = q; break; } } static void SB_WallNormal(rendpoly_t *poly, float normal[3]) { normal[VY] = (poly->vertices[0].pos[VX] - poly->vertices[1].pos[VX]) / poly->length; normal[VX] = (poly->vertices[1].pos[VY] - poly->vertices[0].pos[VY]) / poly->length; normal[VZ] = 0; } static void SB_AddAffected(affection_t *aff, int k, float intensity) { int i, worst; if(aff->numFound < MAX_BIAS_AFFECTED) { aff->affected[aff->numFound].source = k; aff->intensities[aff->numFound] = intensity; aff->numFound++; } else { // Drop the weakest. worst = 0; for(i = 1; i < MAX_BIAS_AFFECTED; ++i) { if(aff->intensities[i] < aff->intensities[worst]) worst = i; } aff->affected[worst].source = k; aff->intensities[worst] = intensity; } } /* * This must be called when a plane that the seg touches is moved, or * when a seg in a polyobj changes position. */ void SB_SegHasMoved(seg_t *seg) { int i; seginfo_t *info = SEG_INFO(seg); // Mark the affected lights changed. for(i = 0; i < MAX_BIAS_AFFECTED && info->affected[i].source >= 0; ++i) { sources[info->affected[i].source].flags |= BLF_CHANGED; } } /* * This must be called when a plane has moved. Set 'theCeiling' to * true if the plane is a ceiling plane. */ void SB_PlaneHasMoved(subsector_t *subsector, int plane) { int i; subsectorinfo_t *subInfo = SUBSECT_INFO(subsector); planeinfo_t *info = &subInfo->plane[plane]; // Mark the affected lights changed. for(i = 0; i < MAX_BIAS_AFFECTED && info->affected[i].source >= 0; ++i) { sources[info->affected[i].source].flags |= BLF_CHANGED; } } /* * This could be enhanced so that only the lights on the right side of * the seg are taken into consideration. */ void SB_UpdateSegAffected(int seg, rendpoly_t *poly) { seginfo_t *info = &seginfo[seg]; int i, k; vec2_t delta; source_t *src; float distance, len, normal[3]; float intensity; affection_t aff; // If the data is already up to date, nothing needs to be done. if(info->updated == lastChangeOnFrame || !updateAffected) return; info->updated = lastChangeOnFrame; aff.affected = info->affected; aff.numFound = 0; memset(aff.affected, -1, sizeof(info->affected)); for(k = 0, src = sources; k < numSources; ++k, ++src) { if(src->intensity <= 0) continue; // Calculate minimum 2D distance to the seg. for(i = 0; i < 2; ++i) { V2_Set(delta, poly->vertices[i].pos[VX] - src->pos[VX], poly->vertices[i].pos[VY] - src->pos[VY]); len = V2_Normalize(delta); if(i == 0 || len < distance) distance = len; } SB_WallNormal(poly, normal); if(M_DotProduct(delta, normal) >= 0) continue; if(distance < 1) distance = 1; intensity = src->intensity/distance; // Is the source is too weak, ignore it entirely. if(intensity < biasIgnoreLimit) continue; SB_AddAffected(&aff, k, intensity); } } static float SB_Dot(source_t *src, float point[3], float normal[3]) { float delta[3]; int i; // Delta vector between source and given point. for(i = 0; i < 3; ++i) delta[i] = src->pos[i] - point[i]; // Calculate the distance. M_Normalize(delta); return M_DotProduct(delta, normal); } static float SB_PlaneDot(source_t *src, float point[3], plane_t *plane) { float normal[3]; normal[VX] = plane->normal[VX]; normal[VY] = plane->normal[VY]; normal[VZ] = plane->normal[VZ]; return SB_Dot(src, point, normal); } /* * This could be enhanced so that only the lights on the right side of * the plane are taken into consideration. */ void SB_UpdateSubsectorAffected(int sub, rendpoly_t *poly) { subsector_t *subsector = SUBSECTOR_PTR(sub); subsectorinfo_t *info = &subsecinfo[sub]; int i, k; vec2_t delta; float point[3]; source_t *src; float distance, len, dot; float intensity; affection_t aff[NUM_PLANES]; // If the data is already up to date, nothing needs to be done. if(info->plane[PLN_FLOOR].updated == lastChangeOnFrame || !updateAffected) return; // For each plane. for(k = 0; k < NUM_PLANES; ++k) { info->plane[k].updated = lastChangeOnFrame; aff[k].affected = info->plane[k].affected; aff[k].numFound = 0; memset(aff[k].affected, -1, sizeof(info->plane[k].affected)); } for(k = 0, src = sources; k < numSources; ++k, ++src) { if(src->intensity <= 0) continue; // Calculate minimum 2D distance to the subsector. // FIXME: This is probably too accurate an estimate. for(i = 0; i < poly->numvertices; ++i) { V2_Set(delta, poly->vertices[i].pos[VX] - src->pos[VX], poly->vertices[i].pos[VY] - src->pos[VY]); len = V2_Length(delta); if(i == 0 || len < distance) distance = len; } if(distance < 1) distance = 1; // For each plane for(i = 0; i < NUM_PLANES; ++i) { // Estimate the effect on this plane. point[VX] = subsector->midpoint.x; point[VY] = subsector->midpoint.y; point[VZ] = FIX2FLT(subsector->sector->planes[k].height); dot = SB_PlaneDot(src, point, &subsector->sector->planes[k]); if(dot <= 0) continue; intensity = /*dot * */ src->intensity / distance; // Is the source is too weak, ignore it entirely. if(intensity < biasIgnoreLimit) continue; SB_AddAffected(&aff[i], k, intensity); } } } /* * Sets/clears a bit in the tracker for the given index. */ void SB_TrackerMark(biastracker_t *tracker, int index) { if(index >= 0) { tracker->changes[index >> 5] |= (1 << (index & 0x1f)); } /*else { tracker->changes[(-index) >> 5] &= ~(1 << ((-index) & 0x1f)); }*/ } /* * Checks if the given index bit is set in the tracker. */ int SB_TrackerCheck(biastracker_t *tracker, int index) { return (tracker->changes[index >> 5] & (1 << (index & 0x1f))) != 0; } /* * Copies changes from src to dest. */ void SB_TrackerApply(biastracker_t *dest, const biastracker_t *src) { int i; for(i = 0; i < sizeof(dest->changes)/sizeof(dest->changes[0]); ++i) { dest->changes[i] |= src->changes[i]; } } /* * Clears changes of src from dest. */ void SB_TrackerClear(biastracker_t *dest, const biastracker_t *src) { int i; for(i = 0; i < sizeof(dest->changes)/sizeof(dest->changes[0]); ++i) { dest->changes[i] &= ~src->changes[i]; } } /* * Tests against trackChanged. */ static boolean SB_ChangeInAffected(biasaffection_t *affected, biastracker_t *changed) { int i; for(i = 0; i < MAX_BIAS_AFFECTED; ++i) { if(affected[i].source < 0) break; if(SB_TrackerCheck(changed, affected[i].source)) return true; } return false; } /* * This is done in the beginning of the frame when a light source has * changed. The planes that the change affects will need to be * re-evaluated. */ void SB_MarkPlaneChanges(planeinfo_t *plane, biastracker_t *allChanges) { int i; SB_TrackerApply(&plane->tracker, allChanges); if(SB_ChangeInAffected(plane->affected, allChanges)) { // Mark the illumination unseen to force an update. for(i = 0; i < plane->numvertices; ++i) plane->illumination[i].flags |= VIF_STILL_UNSEEN; } } /* * Do initial processing that needs to be done before rendering a * frame. Changed lights cause the tracker bits to the set for all * segs and planes. */ void SB_BeginFrame(void) { biastracker_t allChanges; int i, j, k; seginfo_t *sin; source_t *s; if(!useBias) return; // The time that applies on this frame. currentTimeSB = Sys_GetRealTime(); // Check which sources have changed. memset(&allChanges, 0, sizeof(allChanges)); for(i = 0, s = sources; i < numSources; ++i, ++s) { if(s->sectorLevel[1] > 0 || s->sectorLevel[0] > 0) { int minLevel = s->sectorLevel[0]; int maxLevel = s->sectorLevel[1]; sector_t *sector = R_PointInSubsector (FRACUNIT * s->pos[VX], FRACUNIT * s->pos[VY])->sector; float oldIntensity = s->intensity; // The lower intensities are useless for light emission. if(sector->lightlevel >= maxLevel) { s->intensity = s->primaryIntensity; } if(sector->lightlevel >= minLevel && minLevel != maxLevel) { s->intensity = s->primaryIntensity * (sector->lightlevel - minLevel) / (maxLevel - minLevel); } else { s->intensity = 0; } if(s->intensity != oldIntensity) sources[i].flags |= BLF_CHANGED; } if(sources[i].flags & BLF_CHANGED) { SB_TrackerMark(&allChanges, i); sources[i].flags &= ~BLF_CHANGED; // This is used for interpolation. sources[i].lastUpdateTime = currentTimeSB; // Recalculate which sources affect which surfaces. lastChangeOnFrame = framecount; } } // Apply to all segs. for(i = 0; i < numsegs; ++i) { sin = &seginfo[i]; for(j = 0; j < 3; ++j) SB_TrackerApply(&sin->tracker[j], &allChanges); // Everything that is affected by the changed lights will need // an update. if(SB_ChangeInAffected(sin->affected, &allChanges)) { // Mark the illumination unseen to force an update. for(j = 0; j < 3; ++j) for(k = 0; k < 4; ++k) sin->illum[j][k].flags |= VIF_STILL_UNSEEN; } } // Apply to all planes. for(i = 0; i < numsubsectors; ++i) { for(j = 0; j < NUM_PLANES; ++j) SB_MarkPlaneChanges(&subsecinfo[i].plane[j], &allChanges); } } void SB_EndFrame(void) { if(numSourceDelta != 0) { numSources += numSourceDelta; numSourceDelta = 0; } // Update the editor. SBE_EndFrame(); } void SB_AddLight(gl_rgba_t *dest, const byte *color, float howMuch) { int i, new; byte amplified[3], largest; if(color == NULL) { for(i = 0; i < 3; ++i) { amplified[i] = dest->rgba[i]; if(i == 0 || dest->rgba[i] > largest) largest = dest->rgba[i]; } if(largest == 0) // Black! { amplified[0] = amplified[1] = amplified[2] = 255; } else { for(i = 0; i < 3; ++i) { amplified[i] = (byte) (amplified[i] / (float) largest * 255); } } } for(i = 0; i < 3; ++i) { new = dest->rgba[i] + (byte) ((color ? color : amplified)[i] * howMuch); if(new > 255) new = 255; dest->rgba[i] = new; } } #if 0 /* * Color override forces the bias light color to override biased * sectorlight. */ static boolean SB_CheckColorOverride(biasaffection_t *affected) { /* int i; for(i = 0; affected[i].source >= 0 && i < MAX_BIAS_AFFECTED; ++i) { // If the color is completely black, it means no light was // reached from this affected source. if(!(affected[i].rgb[0] | affected[i].rgb[1] | affected[i].rgb[2])) continue; if(sources[affected[i].source].flags & BLF_COLOR_OVERRIDE) return true; }*/ return false; } #endif /* * Poly can be a either a wall or a plane (ceiling or a floor). * * Parameters: * * poly Rendering polygon (wall or plane). * illumination Illumination for each corner of the polygon. * tracker Tracker of all the changed lights for this polygon. * mapElementIndex Index of the seg or subsector in the global arrays. */ void SB_RendPoly(struct rendpoly_s *poly, int plane, sector_t *sector, struct vertexillum_s *illumination, biastracker_t *tracker, int mapElementIndex) { float pos[3]; float normal[3]; int i; boolean forced; biasaffection_t *affected; if(!useBias) return; #if 1 // Apply sectorlight bias. Note: Distance darkening is not used // with bias lights. if(sector->lightlevel > biasMin && biasMax > biasMin) { const byte *sectorColor; biasAmount = (sector->lightlevel - biasMin) / (float) (biasMax - biasMin); if(biasAmount > 1) biasAmount = 1; sectorColor = R_GetSectorLightColor(sector); for(i = 0; i < 3; ++i) biasColor[i] = sectorColor[i]; /* // Planes and the top edge of walls. SB_AddLight(&poly->vertices[i].color, colorOverride ? NULL : sectorColor, applied); if(poly->numvertices == 2) { // The bottom edge of walls. SB_AddLight(&poly->bottomcolor[i], colorOverride ? NULL : sectorColor, applied); } }*/ } else { biasAmount = 0; } #endif memcpy(&trackChanged, tracker, sizeof(trackChanged)); memset(&trackApplied, 0, sizeof(trackApplied)); if(poly->numvertices == 2) { // Has any of the old affected lights changed? affected = seginfo[mapElementIndex].affected; /*forced = SB_ChangeInAffected(affected);*/ forced = false; //seginfo[mapElementIndex].forced; SB_UpdateSegAffected(mapElementIndex, poly); // It's a wall. SB_WallNormal(poly, normal); for(i = 0; i < 4; ++i) { pos[VX] = poly->vertices[i % 2].pos[VX]; pos[VY] = poly->vertices[i % 2].pos[VY]; pos[VZ] = (i >= 2 ? poly->bottom : poly->top); SB_EvalPoint((i >= 2 ? &poly->bottomcolor[i - 2] : &poly->vertices[i].color), &illumination[i], affected, pos, normal); } // colorOverride = SB_CheckColorOverride(affected); } else { affected = subsecinfo[mapElementIndex].plane[plane].affected; /* // Has any of the old affected lights changed? forced = SB_ChangeInAffected(affected); */ forced = false; /*subsecinfo[mapElementIndex].plane[plane].forced*/ SB_UpdateSubsectorAffected(mapElementIndex, poly); // It's a plane. normal[VX] = sector->planes[plane].normal[VX]; normal[VY] = sector->planes[plane].normal[VY]; normal[VZ] = sector->planes[plane].normal[VZ]; for(i = 0; i < poly->numvertices; ++i) { pos[VX] = poly->vertices[i].pos[VX]; pos[VY] = poly->vertices[i].pos[VY]; pos[VZ] = poly->top; SB_EvalPoint(&poly->vertices[i].color, &illumination[i], affected, pos, normal); } // colorOverride = SB_CheckColorOverride(affected); } SB_TrackerClear(tracker, &trackApplied); } /* * Interpolate between current and destination. */ void SB_LerpIllumination(vertexillum_t *illum, gl_rgba_t *result) { int i; if(!(illum->flags & VIF_LERP)) { // We're done with the interpolation, just use the // destination color. memcpy(result->rgba, illum->color.rgba, 4); } else { float inter = (currentTimeSB - illum->updatetime) / (float)lightSpeed; if(inter > 1) { illum->flags &= ~VIF_LERP; memcpy(illum->color.rgba, illum->dest.rgba, 4); memcpy(result->rgba, illum->color.rgba, 4); } else { for(i = 0; i < 3; ++i) { result->rgba[i] = (DGLuint) (illum->color.rgba[i] + (illum->dest.rgba[i] - illum->color.rgba[i]) * inter); } } } } /* * Returns the light contributed by the specified source. */ byte *SB_GetCasted(vertexillum_t *illum, int sourceIndex, biasaffection_t *affectedSources) { int i, k; boolean inUse; for(i = 0; i < MAX_BIAS_AFFECTED; ++i) if(illum->casted[i].source == sourceIndex) return illum->casted[i].rgb; // Choose an array element not used by the affectedSources. for(i = 0; i < MAX_BIAS_AFFECTED; ++i) { inUse = false; for(k = 0; k < MAX_BIAS_AFFECTED; ++k) { if(affectedSources[k].source < 0) break; if(affectedSources[k].source == illum->casted[i].source) { inUse = true; break; } } if(!inUse) { illum->casted[i].source = sourceIndex; memset(illum->casted[i].rgb, 0, 3); return illum->casted[i].rgb; } } Con_Error("SB_GetCasted: No light casted by source %i.\n", sourceIndex); return NULL; } /* * Add ambient light. */ void SB_AmbientLight(float *point, gl_rgba_t *light) { // Add grid light (represents ambient lighting). byte color[3]; LG_Evaluate(point, color); SB_AddLight(light, color, 1.0f); } /* * Applies shadow bias to the given point. If 'forced' is true, new * lighting is calculated regardless of whether the lights affecting * the point have changed. This is needed when there has been world * geometry changes. 'illum' is allowed to be NULL. * * FIXME: Only recalculate the changed lights. The colors contributed * by the others can be saved with the 'affected' array. */ void SB_EvalPoint(gl_rgba_t *light, vertexillum_t *illum, biasaffection_t *affectedSources, float *point, float *normal) { gl_rgba_t new; float dot; float delta[3], surfacePoint[3]; float distance; float level; int i, idx; boolean illuminationChanged = false; unsigned int latestSourceUpdate = 0; source_t *s; byte *casted; // gl_rgba_t Srgba; struct { int index; //int affNum; // Index in affectedSources. source_t *source; biasaffection_t *affection; boolean changed; boolean overrider; } affecting[MAX_BIAS_AFFECTED + 1], *aff; // memcpy(Srgba.rgba, light->rgba, 4); // Vertices that are rendered for the first time need to be fully // evaluated. if(illum->flags & VIF_STILL_UNSEEN) { illuminationChanged = true; illum->flags &= ~VIF_STILL_UNSEEN; } // Determine if any of the affecting lights have changed since // last frame. for(i = 0, aff = affecting; affectedSources[i].source >= 0 && i < MAX_BIAS_AFFECTED; ++i) { idx = affectedSources[i].source; // Is this a valid index? if(idx < 0 || idx >= numSources) continue; aff->index = idx; //aff->affNum = i; aff->source = &sources[idx]; aff->affection = &affectedSources[i]; aff->overrider = (aff->source->flags & BLF_COLOR_OVERRIDE) != 0; if(SB_TrackerCheck(&trackChanged, idx)) { aff->changed = true; illuminationChanged = true; SB_TrackerMark(&trackApplied, idx); // Keep track of the earliest time when an affected source // was changed. if(latestSourceUpdate < sources[idx].lastUpdateTime) { latestSourceUpdate = sources[idx].lastUpdateTime; } } else { aff->changed = false; } // Move to the next. aff++; } aff->source = NULL; if(!illuminationChanged && illum != NULL) { // Reuse the previous value. SB_LerpIllumination(illum, light); SB_AmbientLight(point, light); /* for(i=0; i < 3; i++) light->rgba[i] = (byte)(((Srgba.rgba[i]/ 255.0f)) * light->rgba[i]); light->rgba[3] = Srgba.rgba[3]; */ return; } // Init to black. new.rgba[0] = new.rgba[1] = new.rgba[2] = 0; // Calculate the contribution from each light. for(aff = affecting; aff->source; aff++) { if(illum && !aff->changed) //SB_TrackerCheck(&trackChanged, aff->index)) { // We can reuse the previously calculated value. This can // only be done if this particular light source hasn't // changed. continue; } s = aff->source; if(illum) casted = SB_GetCasted(illum, aff->index, affectedSources); else casted = NULL; for(i = 0; i < 3; ++i) { delta[i] = s->pos[i] - point[i]; surfacePoint[i] = point[i] + delta[i] / 100; } if(useSightCheck && !P_CheckLineSight(s->pos, surfacePoint)) { // LOS fail. if(casted) { // This affecting source does not contribute any light. memset(casted, 0, 3); casted[3] = 1; } continue; } else { distance = M_Normalize(delta); dot = M_DotProduct(delta, normal); // The surface faces away from the light. if(dot <= 0) { if(casted) { memset(casted, 0, 3); casted[3] = 1; } continue; } level = dot * s->intensity / distance; } if(level > 1) level = 1; for(i = 0; i < 3; ++i) { //int v; // The light casted from this source. casted[i] = (byte) (255 * s->color[i] * level); //v = new.rgba[i] + 255 * s->color[i] * level; //if(v > 255) v = 255; //new.rgba[i] = (DGLubyte) v; } casted[3] = 1; // Are we already fully lit? /*if(new.rgba[0] == 255 && new.rgba[1] == 255 && new.rgba[2] == 255) break;*/ } if(illum) { //Con_Message("\n"); boolean willOverride = false; // Combine the casted light from each source. for(aff = affecting; aff->source; aff++) { byte *casted = SB_GetCasted(illum, aff->index, affectedSources); if(aff->overrider && (casted[0] | casted[1] | casted[2]) != 0) willOverride = true; /* if(!casted[3]) { int n; Con_Message("affected: "); for(n = 0; n < MAX_BIAS_AFFECTED; ++n) Con_Message("%i ", affectedSources[n].source); Con_Message("\n"); Con_Error("not updated: s=%i\n", aff->index); } */ /* if(editSelector >= 0 && aff->index != editSelector) continue;*/ /* { int n; printf("affected: "); for(n = 0; n < MAX_BIAS_AFFECTED; ++n) printf("%i ", affectedSources[n].source); printf("casted: "); for(n = 0; n < MAX_BIAS_AFFECTED; ++n) printf("%i ", illum->casted[n].source); printf("%i:(%i %i %i) ", aff->index, casted[0], casted[1], casted[2]); printf("\n"); }*/ for(i = 0; i < 3; ++i) { int v = new.rgba[i] + casted[i]; if(v > 255) v = 255; new.rgba[i] = v; } } /*if(biasAmount > 0) { SB_AddLight(&new, willOverride ? NULL : biasColor, biasAmount); }*/ // Is there a new destination? if(memcmp(illum->dest.rgba, new.rgba, 3)) { if(illum->flags & VIF_LERP) { // Must not lose the half-way interpolation. gl_rgba_t mid; SB_LerpIllumination(illum, &mid); // This is current color at this very moment. memcpy(&illum->color.rgba, &mid, 4); } // This is what we will be interpolating to. memcpy(illum->dest.rgba, new.rgba, 4); illum->flags |= VIF_LERP; illum->updatetime = latestSourceUpdate; } SB_LerpIllumination(illum, light); } else { memcpy(light->rgba, new.rgba, 4); } SB_AmbientLight(point, light); /* for(i=0; i < 3; i++) light->rgba[i] = (byte)(((Srgba.rgba[i]/ 255.0f)) * light->rgba[i]); light->rgba[3] = Srgba.rgba[3]; */ }