diff --git a/doomsday/client/include/map/gamemap.h b/doomsday/client/include/map/gamemap.h index e33829862b..d7e9f59548 100644 --- a/doomsday/client/include/map/gamemap.h +++ b/doomsday/client/include/map/gamemap.h @@ -31,6 +31,10 @@ #include "p_particle.h" #include "Polyobj" +#ifdef __CLIENT__ +# include "render/r_lgrid.h" +#endif + class Vertex; class Line; class Sector; @@ -76,6 +80,11 @@ struct clpolyobj_s; class GameMap { public: +#ifdef __CLIENT__ + /// Required light grid is missing. @ingroup errors + DENG2_ERROR(MissingLightGridError); +#endif + typedef QList Vertexes; typedef QList Sectors; typedef QList Lines; @@ -603,6 +612,27 @@ class GameMap #ifdef __CLIENT__ void buildSurfaceLists(); + + /** + * Returns @c true iff a LightGrid has been initialized for the map. + * + * @see lightGrid() + */ + bool hasLightGrid(); + + /** + * Provides access to the light grid for the map. + * + * @see hasLightGrid() + */ + de::LightGrid &lightGrid(); + + /** + * Initialize the ambient lighting grid (smoothed sector lighting). + * If the grid has already been initialized calling this will perform + * a full update. + */ + void initLightGrid(); #endif bool buildBsp(); diff --git a/doomsday/client/include/map/sector.h b/doomsday/client/include/map/sector.h index 280f4c20af..ed0323a8e0 100644 --- a/doomsday/client/include/map/sector.h +++ b/doomsday/client/include/map/sector.h @@ -43,7 +43,6 @@ class Surface; */ ///@{ #define SIF_VISIBLE 0x1 ///< Sector is visible on this frame. -#define SIF_LIGHT_CHANGED 0x2 // Flags to clear before each frame. #define SIF_FRAME_CLEAR SIF_VISIBLE diff --git a/doomsday/client/include/network/net_main.h b/doomsday/client/include/network/net_main.h index 7454abb6f2..a5cd984c97 100644 --- a/doomsday/client/include/network/net_main.h +++ b/doomsday/client/include/network/net_main.h @@ -251,7 +251,11 @@ int Net_TimeDelta(byte now, byte then); void Net_Update(void); void Net_ResetTimer(void); void Net_Ticker(timespan_t time); -void Net_Drawer(void); + +/** + * Does drawing for the engine's HUD, not just the net. + */ +void Net_Drawer(void); boolean Net_IsLocalPlayer(int pNum); diff --git a/doomsday/client/include/render/r_lgrid.h b/doomsday/client/include/render/r_lgrid.h index 8f9a97f889..2e25920e37 100644 --- a/doomsday/client/include/render/r_lgrid.h +++ b/doomsday/client/include/render/r_lgrid.h @@ -1,4 +1,4 @@ -/** @file r_lgrid.h +/** @file render/r_lgrid.h Light Grid (Smoothed ambient sector lighting). * * @authors Copyright © 2003-2013 Jaakko Keränen * @authors Copyright © 2006-2013 Daniel Swanson @@ -17,51 +17,108 @@ * http://www.gnu.org/licenses */ -/** - * Smoothed ambient sector lighting. - */ - -#ifndef LIBDENG_REFRESH_LIGHT_GRID_H -#define LIBDENG_REFRESH_LIGHT_GRID_H +#ifndef DENG_RENDER_LIGHT_GRID_H +#define DENG_RENDER_LIGHT_GRID_H #include "dd_types.h" -#include "map/p_mapdata.h" -#ifdef __cplusplus -extern "C" { -#endif +#include -void LG_Register(void); -void LG_InitForMap(void); -void LG_Update(void); - -/** - * Called when a setting is changed which affects the lightgrid. - */ -void LG_MarkAllForUpdate(void); +class GameMap; +class Sector; -void LG_SectorChanged(Sector* sector); +namespace de { /** - * Calculate the light color for a 3D point in the world. + * Simple global illumination method utilizing a 2D grid of light levels, achieving + * smoothed ambient sector lighting. * - * @param point 3D point. - * @param destColor Evaluated color of the point (return value). + * @ingroup render */ -void LG_Evaluate(coord_t const point[3], float destColor[3]); +class LightGrid +{ +public: + /** + * Two dimensioned reference to a block in the grid [X, Y]. + */ + typedef Vector2i Ref; -/** - * Calculate the light level for a 3D point in the world. - * - * @param point 3D point. - * @return Evaluated light level of the point. - */ -float LG_EvaluateLightLevel(coord_t const point[3]); + /** + * Linear reference to a block in the grid (X + Y * Grid Width). + */ + typedef int Index; + +public: + /** + * Construct and initialize a new LightGrid for the specifed map. + * + * @note Initialization may take some time depending on the complexity of the map + * (world dimensions, number of sectors) and should therefore be done "off-line". + */ + LightGrid(GameMap &map); + + /** + * Register the console comands and variables of this module. + */ + static void consoleRegister(); + + /** + * Update the grid by finding the strongest light source in each grid block. + * Should be called periodically to update/refresh the grid contents (e.g., when + * beginning a new render frame). + */ + void update(); + + /** + * To be called when an engine variable which affects the lightgrid changes. + */ + void markAllForUpdate(); + + /** + * Returns the dimensions of the light grid in blocks. For informational purposes. + */ + Vector2i const &dimensions() const; + + /** + * Returns the size of grid block in map coordinate space units. For informational + * purposes. + */ + coord_t blockSize() const; + + /** + * Determine the ambient light color for a point in the map coordinate space. + * + * @param point 3D point. + * + * @return Evaluated color at the specified point. + */ + Vector3f evaluate(Vector3d const &point); + + /** + * Determine the ambient light level for a point in the map coordinate space. + * + * @param point 3D point. + * + * @return Evaluated light level at the specified point. + */ + float evaluateLightLevel(Vector3d const &point); + + /** + * To be called when the ambient lighting properties in the sector change. + * + * @todo Replace with internal de::Observers based mechanism. + */ + void sectorChanged(Sector §or); + + /** + * Draw the light grid debug visual. + */ + void drawDebugVisual(); -void LG_Debug(void); +private: + DENG2_PRIVATE(d) +}; -#ifdef __cplusplus -} // extern "C" -#endif +} // namespace de -#endif // LIBDENG_REFRESH_LIGHT_GRID_H +#endif // DENG_RENDER_LIGHT_GRID_H diff --git a/doomsday/client/src/map/gamemap.cpp b/doomsday/client/src/map/gamemap.cpp index eed1d18eea..1b1cddf6b8 100644 --- a/doomsday/client/src/map/gamemap.cpp +++ b/doomsday/client/src/map/gamemap.cpp @@ -20,6 +20,8 @@ #include +#include + #include "de_base.h" #include "de_console.h" #include "de_play.h" @@ -59,6 +61,8 @@ DENG2_PIMPL(GameMap) #ifdef __CLIENT__ SurfaceSet decoratedSurfaces; SurfaceSet glowingSurfaces; + + QScopedPointer lightGrid; #endif coord_t skyFix[2]; // [floor, ceiling] @@ -690,6 +694,36 @@ void GameMap::buildSurfaceLists() } } +bool GameMap::hasLightGrid() +{ + return !d->lightGrid.isNull(); +} + +LightGrid &GameMap::lightGrid() +{ + if(!d->lightGrid.isNull()) + { + return *d->lightGrid; + } + /// @throw MissingLightGrid Attempted with no LightGrid initialized. + throw MissingLightGridError("GameMap::lightGrid", "No light grid is initialized"); +} + +void GameMap::initLightGrid() +{ + // Disabled? + if(!Con_GetInteger("rend-bias-grid")) + return; + + // Time to initialize the LightGrid? + if(d->lightGrid.isNull()) + { + d->lightGrid.reset(new LightGrid(*this)); + } + // Perform a full update right away. + d->lightGrid->update(); +} + #endif // __CLIENT__ de::Uri GameMap::uri() const diff --git a/doomsday/client/src/map/p_data.cpp b/doomsday/client/src/map/p_data.cpp index 13560ded8d..de54d2fb3e 100644 --- a/doomsday/client/src/map/p_data.cpp +++ b/doomsday/client/src/map/p_data.cpp @@ -228,8 +228,7 @@ DENG_EXTERN_C boolean P_LoadMap(char const *uriCString) R_InitShadowProjectionListsForMap(); // Projected mobj shadows. VL_InitForMap(); // Converted vlights (from lumobjs) management. - // Initialize the lighting grid. - LG_InitForMap(); + theMap->initLightGrid(); R_InitRendPolyPools(); #endif diff --git a/doomsday/client/src/map/p_dmu.cpp b/doomsday/client/src/map/p_dmu.cpp index 1dd2d51384..1c06918a1c 100644 --- a/doomsday/client/src/map/p_dmu.cpp +++ b/doomsday/client/src/map/p_dmu.cpp @@ -833,15 +833,13 @@ static void updateSector(Sector §or, bool forceUpdate = false) sector._lightColor[1] != sector._oldLightColor[1] || sector._lightColor[2] != sector._oldLightColor[2])) { - sector._frameFlags |= SIF_LIGHT_CHANGED; sector._oldLightLevel = sector._lightLevel; sector._oldLightColor = sector._lightColor; - LG_SectorChanged(§or); - } - else - { - sector._frameFlags &= ~SIF_LIGHT_CHANGED; + if(theMap->hasLightGrid()) + { + theMap->lightGrid().sectorChanged(sector); + } } #endif } diff --git a/doomsday/client/src/network/net_main.cpp b/doomsday/client/src/network/net_main.cpp index fd0ec5373a..6ce95a311c 100644 --- a/doomsday/client/src/network/net_main.cpp +++ b/doomsday/client/src/network/net_main.cpp @@ -741,18 +741,9 @@ void Net_DrawDemoOverlay(void) #endif // __CLIENT__ -/** - * Does drawing for the engine's HUD, not just the net. - */ -void Net_Drawer(void) +void Net_Drawer() { #ifdef __CLIENT__ - // Draw the Shadow Bias Editor HUD (if it is active). - SBE_DrawHUD(); - - // Draw lightgrid debug display. - LG_Debug(); - // Draw the blockmap debug display. Rend_BlockmapDebug(); diff --git a/doomsday/client/src/render/r_lgrid.cpp b/doomsday/client/src/render/r_lgrid.cpp index 4930ca82e6..6ff4e526b8 100644 --- a/doomsday/client/src/render/r_lgrid.cpp +++ b/doomsday/client/src/render/r_lgrid.cpp @@ -1,4 +1,4 @@ -/** @file r_lgrid.cpp Light Grid (Large-Scale FakeRadio). +/** @file render/r_lgrid.cpp Light Grid (Large-Scale FakeRadio). * * @authors Copyright © 2006-2013 Jaakko Keränen * @authors Copyright © 2006-2013 Daniel Swanson @@ -18,296 +18,528 @@ * http://www.gnu.org/licenses */ -/** - * Light Grid (Large-Scale FakeRadio). - * - * Very simple global illumination method utilizing a 2D grid of light levels. - */ +#include +#include -// HEADER FILES ------------------------------------------------------------ +#include +#include -#include -#include +#include +#include -#include "de_base.h" #include "de_console.h" -#include "de_render.h" -#include "de_graphics.h" -#include "de_misc.h" -#include "de_play.h" -#include "gl/sys_opengl.h" +#include "BspLeaf" #include "map/gamemap.h" +#include "map/p_maputil.h" // P_IsPointInBspLeaf +#include "map/p_players.h" // viewPlayer + +#include "render/rend_main.h" #include "render/r_lgrid.h" using namespace de; -// MACROS ------------------------------------------------------------------ +static int lgEnabled = false; + +static int lgShowDebug = false; +static float lgDebugSize = 1.5f; + +static int lgBlockSize = 31; +static int lgMXSample = 1; ///< 5 samples per block + +void LightGrid::consoleRegister() // static +{ + C_VAR_INT ("rend-bias-grid", &lgEnabled, 0, 0, 1); + C_VAR_INT ("rend-bias-grid-debug", &lgShowDebug, CVF_NO_ARCHIVE, 0, 1); + C_VAR_FLOAT ("rend-bias-grid-debug-size", &lgDebugSize, 0, .1f, 100); + C_VAR_INT ("rend-bias-grid-blocksize", &lgBlockSize, 0, 8, 1024); + C_VAR_INT ("rend-bias-grid-multisample", &lgMXSample, 0, 0, 7); +} + +/** + * Determines the Z-axis bias scale factor for the given @a sector. + */ +static int biasForSector(Sector const §or) +{ + int const height = int(sector.ceiling().height() - sector.floor().height()); + bool hasSkyFloor = sector.floorSurface().hasSkyMaskedMaterial(); + bool hasSkyCeil = sector.ceilingSurface().hasSkyMaskedMaterial(); + + if(hasSkyFloor && !hasSkyCeil) + { + return -height / 6; + } + if(!hasSkyFloor && hasSkyCeil) + { + return height / 6; + } + if(height > 100) + { + return (height - 100) / 2; + } + return 0; +} + +class LightBlock +{ +public: + enum Flag + { + /// Grid block sector light has changed. + Changed = 0x1, + + /// Contributes light to some other block. + Contributor = 0x2, + + AllFlags = Changed | Contributor + }; + Q_DECLARE_FLAGS(Flags, Flag) + +public: + /** + * Construct a new light block. + * + * @param Primary lighting contributor for the block. Can be @c 0 + * (to create a "null-block"). + */ + LightBlock(Sector *sector = 0); + + /** + * Returns the @em primary sector attributed to the light block + * (contributing sectors are not linked). + */ + Sector §or() const; + + /** + * Returns a copy of the flags of the light block. + */ + Flags flags() const; + + /** + * Change the flags of the light block. + * + * @param flagsToChange Flags to change the value of. + * @param operation Logical operation to perform on the flags. + */ + void setFlags(Flags flagsToChange, FlagOp operation = SetFlags); + + /** + * Evaluate the ambient color for the light block. + */ + Vector3f evaluate(/*coord_t height*/) const; + + void markChanged(bool isContributor = false); -#define GRID_BLOCK(x, y) (&grid[(y)*lgBlockWidth + (x)]) + /** + * Apply the sector's lighting to the block. + */ + void applySector(Vector3f const &color, float level, int bias, float factor); -#define GBF_CHANGED 0x1 // Grid block sector light has changed. -#define GBF_CONTRIBUTOR 0x2 // Contributes light to a changed block. + /** + * Provides immutable access to the "raw" color (i.e., non-biased) for the + * block. Primarily intended for debugging. + */ + Vector3f const &rawColorRef() const; -BEGIN_PROF_TIMERS() - PROF_GRID_UPDATE -END_PROF_TIMERS() +private: + DENG2_PRIVATE(d) +}; -// TYPES ------------------------------------------------------------------- +Q_DECLARE_OPERATORS_FOR_FLAGS(LightBlock::Flags) -typedef struct gridblock_s { +DENG2_PIMPL_NOREF(LightBlock) +{ Sector *sector; - byte flags; + Flags flags; - // Positive bias means that the light is shining in the floor of - // the sector. - char bias; + /// Positive bias means that the light is shining in the floor of the sector. + char bias; - // Color of the light: - float rgb[3]; - float oldRGB[3]; // Used instead of rgb if the lighting in this - // block has changed and we haven't yet done a - // a full grid update. -} gridblock_t; + /// Color of the light: + Vector3f color; -// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + /// Used instead of @var rgb if the lighting in this block has changed + /// and a full grid update is needed. + Vector3f oldColor; -// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + Instance(Sector *sector) : sector(sector), bias(0) + {} +}; -// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- +LightBlock::LightBlock(Sector *sector) + : d(new Instance(sector)) +{} -// EXTERNAL DATA DECLARATIONS ---------------------------------------------- +Sector &LightBlock::sector() const +{ + DENG_ASSERT(d->sector != 0); + return *d->sector; +} -// PUBLIC DATA DEFINITIONS ------------------------------------------------- +LightBlock::Flags LightBlock::flags() const +{ + return d->flags; +} -// PRIVATE DATA DEFINITIONS ------------------------------------------------ +void LightBlock::setFlags(LightBlock::Flags flagsToChange, FlagOp operation) +{ + if(!d->sector) return; + applyFlagOperation(d->flags, flagsToChange, operation); +} -int lgEnabled = false; -static boolean lgInited; -static boolean needsUpdate = true; +Vector3f LightBlock::evaluate(/*coord_t height*/) const +{ + // If not attributed to a sector, the color is always black. + if(!d->sector) return Vector3f(0, 0, 0); -static int lgShowDebug = false; -static float lgDebugSize = 1.5f; + /** + * Biased light dimming disabled because this does not work well enough. + * The problem is that two points on a given surface may be determined to + * be in different blocks and as the height is taken from the block linked + * sector this results in very uneven lighting. + * + * Biasing is a good idea but the plane heights must come from the sector + * at the exact X|Y coordinates of the sample point, not the "quantized" + * references in the light grid. -ds + */ -static int lgBlockSize = 31; -static coord_t lgOrigin[3]; -static int lgBlockWidth; -static int lgBlockHeight; -static gridblock_t *grid; + /* + coord_t dz = 0; + if(_bias < 0) + { + // Calculate Z difference to the ceiling. + dz = d->sector->ceiling().height() - height; + } + else if(_bias > 0) + { + // Calculate Z difference to the floor. + dz = height - d->sector->floor().height(); + } + dz -= 50; + if(dz < 0) + dz = 0;*/ -static int lgMXSample = 1; // Default is mode 1 (5 samples per block) + // If we are awaiting an updated value use the old color. + Vector3f color = d->flags.testFlag(Changed)? d->oldColor : d->color; -// CODE -------------------------------------------------------------------- + // Biased ambient light causes a dimming on the Z axis. + /*if(dz && _bias) + { + float dimming = 1 - (dz * (float) de::abs(d->bias)) / 35000.0f; + if(dimming < .5f) + dimming = .5f; -/** - * Registers console variables. - */ -void LG_Register(void) + color *= dimming; + } + */ + + return color; +} + +void LightBlock::markChanged(bool isContributor) +{ + if(!d->sector) return; + + if(isContributor) + { + // Changes by contributor sectors are simply flagged until an update. + d->flags |= Contributor; + return; + } + + // The color will be recalculated. + d->flags |= Changed; + d->flags |= Contributor; + + if(!d->flags & Changed) + { + // Remember the color in case we receive any queries before the update. + d->oldColor = d->color; + } + + // Init to black in preparation for the update. + d->color = Vector3f(0, 0, 0); +} + +void LightBlock::applySector(Vector3f const &color, float level, int bias, float factor) { - C_VAR_INT("rend-bias-grid", &lgEnabled, 0, 0, 1); + if(!d->sector) return; + + // Apply a bias to the light level. + level -= (0.95f - level); + if(level < 0) + level = 0; + + level *= factor; + + if(level <= 0) + return; - C_VAR_INT("rend-bias-grid-debug", &lgShowDebug, CVF_NO_ARCHIVE, 0, 1); + for(int i = 0; i < 3; ++i) + { + float c = de::clamp(0.f, color[i] * level, 1.f); - C_VAR_FLOAT("rend-bias-grid-debug-size", &lgDebugSize, 0, .1f, 100); + if(d->color[i] + c > 1) + { + d->color[i] = 1; + } + else + { + d->color[i] += c; + } + } - C_VAR_INT("rend-bias-grid-blocksize", &lgBlockSize, 0, 8, 1024); + // Influenced by the source bias. + d->bias = de::clamp(-0x80, int(d->bias * (1 - factor) + bias * factor), 0x7f); +} - C_VAR_INT("rend-bias-grid-multisample", &lgMXSample, 0, 0, 7); +/** + * Provides immutable access to the "raw" color (i.e., non-biased) for the + * block. Primarily intended for debugging. + */ +Vector3f const &LightBlock::rawColorRef() const +{ + return d->color; } /** - * Determines if the index (x, y) is in the bitfield. + * Determines if the index for the specified map point is in the bitfield. */ -static boolean HasIndexBit(int x, int y, uint *bitfield) +static bool hasIndexBit(LightGrid::Ref const &gref, int gridWidth, uint *bitfield) { - uint index = x + y * lgBlockWidth; + uint index = gref.x + gref.y * gridWidth; // Assume 32-bit uint. return (bitfield[index >> 5] & (1 << (index & 0x1f))) != 0; } /** - * Sets the index (x, y) in the bitfield. + * Sets the index for the specified map point in the bitfield. * Count is incremented when a zero bit is changed to one. */ -static void AddIndexBit(int x, int y, uint *bitfield, int *count) +static void addIndexBit(LightGrid::Ref const &gref, int gridWidth, uint *bitfield, int *count) { - uint index = x + y * lgBlockWidth; + uint index = gref.x + gref.y * gridWidth; // Assume 32-bit uint. - if(!HasIndexBit(index, 0, bitfield)) + if(!hasIndexBit(LightGrid::Ref(index, 0), gridWidth, bitfield)) { (*count)++; } bitfield[index >> 5] |= (1 << (index & 0x1f)); } -/** - * Initialize the light grid for the current map. - */ -void LG_InitForMap(void) +typedef QVector Blocks; + +/// The special null-block. +static LightBlock nullBlock; + +/// Returns @c true iff @a block is the special "null-block". +static inline bool isNullBlock(LightBlock const &block) { + return &block == &nullBlock; +} + +DENG2_PIMPL(LightGrid) { - uint startTime = Timer_RealMilliseconds(); - -#define MSFACTORS 7 - typedef struct lgsamplepoint_s { - coord_t origin[3]; - } lgsamplepoint_t; - // Diagonal in maze arrangement of natural numbers. - // Up to 65 samples per-block(!) - static int multisample[] = {1, 5, 9, 17, 25, 37, 49, 65}; - - coord_t max[3]; - coord_t width, height; - int i = 0; - int a, b, x, y; - int count; - int changedCount; - size_t bitfieldSize; - uint *indexBitfield = 0; - uint *contributorBitfield = 0; - gridblock_t *block; - int *sampleResults = 0; - int n, size, numSamples, center, best; - coord_t off[2]; - lgsamplepoint_t *samplePoints = 0, sample; - - Sector **ssamples; - Sector **blkSampleSectors; - GameMap *map = theMap; - - if(!lgEnabled || !map) - { - lgInited = false; - return; - } + /// Map for which we provide an ambient lighting grid. + GameMap ↦ - lgInited = true; + /// Origin of the grid in the map coordinate space. + Vector2d origin; - // Allocate the grid. - map->bounds(&lgOrigin[0], &max[0]); + /// Size of a block (box axes) in map coordinate space units. + int blockSize; - width = max[VX] - lgOrigin[VX]; - height = max[VY] - lgOrigin[VY]; + /// Dimensions of the grid in blocks. + Ref dimensions; - lgBlockWidth = ROUND(width / lgBlockSize) + 1; - lgBlockHeight = ROUND(height / lgBlockSize) + 1; + /// The grid of LightBlocks. + Blocks grid; - // Clamp the multisample factor. - if(lgMXSample > MSFACTORS) - lgMXSample = MSFACTORS; - else if(lgMXSample < 0) - lgMXSample = 0; + /// Set to @a true when a full update is needed. + bool needUpdate; - numSamples = multisample[lgMXSample]; + Instance(Public *i, GameMap &map) + : Base(i), + map(map), + needUpdate(false) + {} - // Allocate memory for sample points array. - samplePoints = (lgsamplepoint_t *) M_Malloc(sizeof(lgsamplepoint_t) * numSamples); + ~Instance() + { + qDeleteAll(grid); + } /** - * It would be possible to only allocate memory for the unique - * sample results. And then select the appropriate sample in the loop - * for initializing the grid instead of copying the previous results in - * the loop for acquiring the sample points. - * - * Calculate with the equation (number of unique sample points): - * - * ((1 + lgBlockHeight * lgMXSample) * (1 + lgBlockWidth * lgMXSample)) + - * (size % 2 == 0? numBlocks : 0) - * OR - * - * We don't actually need to store the ENTIRE sample points array. It - * would be sufficent to only store the results from the start of the - * previous row to current col index. This would save a bit of memory. - * - * However until lightgrid init is finalized it would be rather silly - * to optimize this much further. + * Convert a light grid reference to a grid index. */ + inline Index toIndex(int x, int y) { return y * dimensions.x + x; } - // Allocate memory for all the sample results. - ssamples = (Sector **) M_Malloc(sizeof(Sector*) * - ((lgBlockWidth * lgBlockHeight) * numSamples)); + /// @copydoc toIndex + inline Index toIndex(Ref const &gref) { return toIndex(gref.x, gref.y); } - // Determine the size^2 of the samplePoint array plus its center. - size = center = 0; - if(numSamples > 1) + /** + * Convert a point in the map coordinate space to a light grid reference. + */ + Ref toRef(Vector3d const &point) { - float f = sqrt(float(numSamples)); + int x = de::round((point.x - origin.x) / blockSize); + int y = de::round((point.y - origin.y) / blockSize); - if(de::ceil(f) != de::floor(f)) - { - size = sqrt(float(numSamples -1)); - center = 0; - } - else - { - size = (int) f; - center = size+1; - } + return Ref(de::clamp(1, x, dimensions.x - 2), + de::clamp(1, y, dimensions.y - 2)); } - // Construct the sample point offset array. - // This way we can use addition only during calculation of: - // (lgBlockHeight*lgBlockWidth)*numSamples - if(center == 0) + /** + * Retrieve the block at the specified light grid index. If no block exists + * at this index the special "null-block" is returned. + */ + LightBlock &lightBlock(Index idx) { - // Zero is the center so do that first. - samplePoints[0].origin[VX] = lgBlockSize / 2; - samplePoints[0].origin[VY] = lgBlockSize / 2; + DENG_ASSERT(idx >= 0 && idx < grid.size()); + LightBlock *block = grid[idx]; + if(block) return *block; + return nullBlock; } - if(numSamples > 1) + /** + * Retrieve the block at the specified light grid reference. + */ + LightBlock &lightBlock(Ref const &gref) { return lightBlock(toIndex(gref)); } + + /// @copydoc lightBlock + inline LightBlock &lightBlock(int x, int y) { return lightBlock(Ref(x, y)); } + + /** + * Same as @ref lightBlock except @a point is in the map coordinate space. + */ + inline LightBlock &lightBlock(Vector3d const &point) { - coord_t bSize = (coord_t) lgBlockSize / (size-1); + return lightBlock(toRef(point)); + } + + /** + * Fully (re)-initialize the light grid. + */ + void initialize() + { + // Diagonal in maze arrangement of natural numbers. + // Up to 65 samples per-block(!) + static int const MSFACTORS = 7; + static int multisample[] = {1, 5, 9, 17, 25, 37, 49, 65}; + + de::Time begunAt; + + LOG_AS("LightGrid::initialize"); + + // Determine the origin of the grid in the map coordinate space. + origin = Vector2d(map.bounds().min); + + // Once initialized the blocksize cannot be changed (requires a full grid + // update) so remember this value. + blockSize = lgBlockSize; + + // Determine the dimensions of the grid (in blocks) + Vector2d mapDimensions = Vector2d(map.bounds().max) - Vector2d(map.bounds().min); + dimensions = Vector2i(de::round(mapDimensions.x / blockSize) + 1, + de::round(mapDimensions.y / blockSize) + 1); + + // Determine how many sector samples per block. + int numSamples = multisample[de::clamp(0, lgMXSample, MSFACTORS)]; + + // Allocate memory for sample points data. + Vector2d *samplePoints = new Vector2d[numSamples]; + int *sampleResults = new int[numSamples]; + + /** + * It would be possible to only allocate memory for the unique + * sample results. And then select the appropriate sample in the loop + * for initializing the grid instead of copying the previous results in + * the loop for acquiring the sample points. + * + * Calculate with the equation (number of unique sample points): + * + * ((1 + lgBlockHeight * lgMXSample) * (1 + lgBlockWidth * lgMXSample)) + + * (size % 2 == 0? numBlocks : 0) + * OR + * + * We don't actually need to store the ENTIRE sample points array. It + * would be sufficent to only store the results from the start of the + * previous row to current col index. This would save a bit of memory. + * + * However until lightgrid init is finalized it would be rather silly + * to optimize this much further. + */ + + // Allocate memory for all the sample results. + Sector **ssamples = (Sector **) M_Malloc(sizeof(Sector *) * ((dimensions.x * dimensions.y) * numSamples)); + + // Determine the size^2 of the samplePoint array plus its center. + int size = 0, center = 0; + if(numSamples > 1) + { + float f = sqrt(float(numSamples)); + + if(std::ceil(f) != std::floor(f)) + { + size = sqrt(float(numSamples - 1)); + center = 0; + } + else + { + size = (int) f; + center = size+1; + } + } - // Is there an offset? + // Construct the sample point offset array. + // This way we can use addition only during calculation of: + // (dimensions.y * dimensions.x) * numSamples if(center == 0) - n = 1; - else - n = 0; + { + // Zero is the center so do that first. + samplePoints[0] = Vector2d(blockSize / 2, blockSize / 2); + } - for(y = 0; y < size; ++y) - for(x = 0; x < size; ++x, ++n) + if(numSamples > 1) + { + coord_t bSize = (coord_t) blockSize / (size - 1); + + // Is there an offset? + int n = (center == 0? 1 : 0); + + for(int y = 0; y < size; ++y) + for(int x = 0; x < size; ++x, ++n) { - samplePoints[n].origin[VX] = ROUND(x * bSize); - samplePoints[n].origin[VY] = ROUND(y * bSize); + samplePoints[n] = + Vector2d(de::round(x * bSize), de::round(y * bSize)); } - } + } -/* -#if _DEBUG - for(n = 0; n < numSamples; ++n) - Con_Message(" %i of %i %i(%f %f)", - n, numSamples, (n == center)? 1 : 0, - samplePoints[n].pos[VX], samplePoints[n].pos[VY]); -#endif -*/ - - // Acquire the sectors at ALL the sample points. - for(y = 0; y < lgBlockHeight; ++y) - { - off[VY] = y * lgBlockSize; - for(x = 0; x < lgBlockWidth; ++x) + Vector2d samplePoint; + + // Acquire the sectors at ALL the sample points. + for(int y = 0; y < dimensions.y; ++y) + for(int x = 0; x < dimensions.x; ++x) { - int blk = (x + y * lgBlockWidth); - int idx; + Index const blk = toIndex(x, y); - off[VX] = x * lgBlockSize; + Vector2d off(x * blockSize, y * blockSize); - n = 0; + int n = 0; if(center == 0) - { // Center point is not considered with the term 'size'. + { + // Center point is not considered with the term 'size'. // Sample this point and place at index 0 (at the start // of the samples for this block). - idx = blk * (numSamples); + int idx = blk * (numSamples); - sample.origin[VX] = lgOrigin[VX] + off[VX] + samplePoints[0].origin[VX]; - sample.origin[VY] = lgOrigin[VY] + off[VY] + samplePoints[0].origin[VY]; + samplePoint = origin + off + samplePoints[0]; - BspLeaf *bspLeaf = map->bspLeafAtPoint(sample.origin); - if(P_IsPointInBspLeaf(sample.origin, *bspLeaf)) + BspLeaf *bspLeaf = map.bspLeafAtPoint(samplePoint); + if(P_IsPointInBspLeaf(samplePoint, *bspLeaf)) ssamples[idx] = bspLeaf->sectorPtr(); else ssamples[idx] = 0; @@ -315,51 +547,48 @@ void LG_InitForMap(void) n++; // Offset the index in the samplePoints array bellow. } - count = blk * size; - for(b = 0; b < size; ++b) + int count = blk * size; + for(int b = 0; b < size; ++b) { - i = (b + count) * size; - for(a = 0; a < size; ++a, ++n) - { - idx = a + i; + int i = (b + count) * size; - if(center == 0) - idx += blk + 1; + for(int a = 0; a < size; ++a, ++n) + { + int idx = a + i + (center == 0? blk + 1 : 0); if(numSamples > 1 && ((x > 0 && a == 0) || (y > 0 && b == 0))) { // We have already sampled this point. // Get the previous result. - int prevX, prevY, prevA, prevB; - int previdx; + Ref prev(x, y); + Ref prevB(a, b); + int prevIdx; - prevX = x; prevY = y; prevA = a; prevB = b; if(x > 0 && a == 0) { - prevA = size -1; - prevX--; + prevB.x = size -1; + prev.x--; } if(y > 0 && b == 0) { - prevB = size -1; - prevY--; + prevB.y = size -1; + prev.y--; } - previdx = prevA + (prevB + (prevX + prevY * lgBlockWidth) * size) * size; + prevIdx = prevB.x + (prevB.y + toIndex(prev) * size) * size; if(center == 0) - previdx += (prevX + prevY * lgBlockWidth) + 1; + prevIdx += toIndex(prev) + 1; - ssamples[idx] = ssamples[previdx]; + ssamples[idx] = ssamples[prevIdx]; } else { // We haven't sampled this point yet. - sample.origin[VX] = lgOrigin[VX] + off[VX] + samplePoints[n].origin[VX]; - sample.origin[VY] = lgOrigin[VY] + off[VY] + samplePoints[n].origin[VY]; + samplePoint = origin + off + samplePoints[n]; - BspLeaf *bspLeaf = map->bspLeafAtPoint(sample.origin); - if(P_IsPointInBspLeaf(sample.origin, *bspLeaf)) + BspLeaf *bspLeaf = map.bspLeafAtPoint(samplePoint); + if(P_IsPointInBspLeaf(samplePoint, *bspLeaf)) ssamples[idx] = bspLeaf->sectorPtr(); else ssamples[idx] = 0; @@ -367,634 +596,342 @@ void LG_InitForMap(void) } } } - } - // We're done with the samplePoints block. - M_Free(samplePoints); - // Bitfields for marking affected blocks. Make sure each bit is in a word. - bitfieldSize = 4 * (31 + lgBlockWidth * lgBlockHeight) / 32; - indexBitfield = (unsigned int *) M_Calloc(bitfieldSize); - contributorBitfield = (unsigned int *) M_Calloc(bitfieldSize); + // We're done with the samplePoints block. + delete[] samplePoints; samplePoints = 0; - // \todo It would be possible to only allocate memory for the grid - // blocks that are going to be in use. + // Bitfields for marking affected blocks. Make sure each bit is in a word. + size_t bitfieldSize = 4 * (31 + dimensions.x * dimensions.y) / 32; - // Allocate memory for the entire grid. - grid = (gridblock_t* ) Z_Calloc(sizeof(gridblock_t) * lgBlockWidth * lgBlockHeight, - PU_MAPSTATIC, NULL); + uint *indexBitfield = (uint *) M_Calloc(bitfieldSize); + uint *contributorBitfield = (uint *) M_Calloc(bitfieldSize); - Con_Message("LG_InitForMap: %i x %i grid (%lu bytes).", - lgBlockWidth, lgBlockHeight, - (unsigned long) (sizeof(gridblock_t) * lgBlockWidth * lgBlockHeight)); + // Allocate memory used for the collection of the sample results. + Sector **blkSampleSectors = (Sector **) M_Malloc(sizeof(Sector *) * numSamples); - // Allocate memory used for the collection of the sample results. - blkSampleSectors = (Sector **) M_Malloc(sizeof(Sector*) * numSamples); - if(numSamples > 1) - sampleResults = (int *) M_Calloc(sizeof(int) * numSamples); + /* + * Initialize the light block grid. + */ + grid.fill(NULL, dimensions.x * dimensions.y); + int numBlocks = 0; - // Initialize the grid. - for(block = grid, y = 0; y < lgBlockHeight; ++y) - { - off[VY] = y * lgBlockSize; - for(x = 0; x < lgBlockWidth; ++x, ++block) + for(int y = 0; y < dimensions.y; ++y) + for(int x = 0; x < dimensions.x; ++x) { - off[VX] = x * lgBlockSize; + Vector2d off(x * blockSize, y * blockSize); /** * Pick the sector at each of the sample points. - * \todo We don't actually need the blkSampleSectors array + * @todo We don't actually need the blkSampleSectors array * anymore. Now that ssamples stores the results consecutively * a simple index into ssamples would suffice. * However if the optimization to save memory is implemented as * described in the comments above we WOULD still require it. * Therefore, for now I'm making use of it to clarify the code. */ - n = (x + y * lgBlockWidth) * numSamples; - for(i = 0; i < numSamples; ++i) - blkSampleSectors[i] = ssamples[i + n]; + Index idx = toIndex(x, y) * numSamples; + for(int i = 0; i < numSamples; ++i) + { + blkSampleSectors[i] = ssamples[i + idx]; + } - block->sector = NULL; + Sector *sector = 0; if(numSamples == 1) { - block->sector = blkSampleSectors[center]; + sector = blkSampleSectors[center]; } else - { // Pick the sector which had the most hits. - best = -1; - memset(sampleResults, 0, sizeof(int) * numSamples); - for(i = 0; i < numSamples; ++i) - if(blkSampleSectors[i]) - for(a = 0; a < numSamples; ++a) - if(blkSampleSectors[a] == blkSampleSectors[i] && - blkSampleSectors[a]) - { - sampleResults[a]++; - if(sampleResults[a] > best) - best = i; - } + { + // Pick the sector which had the most hits. + int best = -1; + std::memset(sampleResults, 0, sizeof(int) * numSamples); + + for(int i = 0; i < numSamples; ++i) + { + if(!blkSampleSectors[i]) continue; + + for(int k = 0; k < numSamples; ++k) + { + if(blkSampleSectors[k] == blkSampleSectors[i] && blkSampleSectors[k]) + { + sampleResults[k]++; + if(sampleResults[k] > best) + best = i; + } + } + } if(best != -1) - { // Favour the center sample if its a draw. + { + // Favour the center sample if its a draw. if(sampleResults[best] == sampleResults[center] && blkSampleSectors[center]) - block->sector = blkSampleSectors[center]; + sector = blkSampleSectors[center]; else - block->sector = blkSampleSectors[best]; + sector = blkSampleSectors[best]; } } + + if(!sector) + continue; + + // Insert a new light block in the grid. + grid[toIndex(x, y)] = new LightBlock(sector); + + // There is now one more block. + numBlocks++; } - } - // We're done with sector samples completely. - M_Free(ssamples); - // We're done with the sample results arrays. - M_Free(blkSampleSectors); - if(numSamples > 1) - M_Free(sampleResults); - - // Find the blocks of all sectors. - foreach(Sector *sector, theMap->sectors()) - { - count = changedCount = 0; - if(sector->sideCount()) + LOG_INFO("%i light blocks (%u bytes).") + << numBlocks << (sizeof(LightBlock) * numBlocks); + + // We're done with sector samples completely. + M_Free(ssamples); + // We're done with the sample results arrays. + M_Free(blkSampleSectors); + + delete[] sampleResults; + + // Find the blocks of all sectors. + foreach(Sector *sector, theMap->sectors()) { - // Clear the bitfields. - std::memset(indexBitfield, 0, bitfieldSize); - std::memset(contributorBitfield, 0, bitfieldSize); + int count = 0, changedCount = 0; - for(block = grid, y = 0; y < lgBlockHeight; ++y) + if(sector->sideCount()) { - for(x = 0; x < lgBlockWidth; ++x, ++block) + // Clear the bitfields. + std::memset(indexBitfield, 0, bitfieldSize); + std::memset(contributorBitfield, 0, bitfieldSize); + + for(int y = 0; y < dimensions.y; ++y) + for(int x = 0; x < dimensions.x; ++x) { - if(block->sector == sector) + LightBlock &block = lightBlock(x, y); + if(isNullBlock(block) || &block.sector() != sector) + continue; + + /// @todo Determine min/max a/b before going into the loop. + for(int b = -2; b <= 2; ++b) { - // \todo Determine min/max a/b before going into the loop. - for(b = -2; b <= 2; ++b) + if(y + b < 0 || y + b >= dimensions.y) + continue; + + for(int a = -2; a <= 2; ++a) { - if(y + b < 0 || y + b >= lgBlockHeight) + if(x + a < 0 || x + a >= dimensions.x) continue; - for(a = -2; a <= 2; ++a) - { - if(x + a < 0 || x + a >= lgBlockWidth) - continue; - - AddIndexBit(x + a, y + b, indexBitfield, - &changedCount); - } + addIndexBit(Ref(x + a, y + b), dimensions.x, + indexBitfield, &changedCount); } } } - } - // Determine contributor blocks. Contributors are the blocks that are - // close enough to contribute light to affected blocks. - for(y = 0; y < lgBlockHeight; ++y) - { - for(x = 0; x < lgBlockWidth; ++x) + // Determine contributor blocks. Contributors are the blocks that are + // close enough to contribute light to affected blocks. + for(int y = 0; y < dimensions.y; ++y) + for(int x = 0; x < dimensions.x; ++x) { - if(!HasIndexBit(x, y, indexBitfield)) + if(!hasIndexBit(Ref(x, y), dimensions.x, indexBitfield)) continue; // Add the contributor blocks. - for(b = -2; b <= 2; ++b) + for(int b = -2; b <= 2; ++b) { - if(y + b < 0 || y + b >= lgBlockHeight) + if(y + b < 0 || y + b >= dimensions.y) continue; - for(a = -2; a <= 2; ++a) + for(int a = -2; a <= 2; ++a) { - if(x + a < 0 || x + a >= lgBlockWidth) + if(x + a < 0 || x + a >= dimensions.x) continue; - if(!HasIndexBit(x + a, y + b, indexBitfield)) + if(!hasIndexBit(Ref(x + a, y + b), dimensions.x, indexBitfield)) { - AddIndexBit(x + a, y + b, contributorBitfield, - &count); + addIndexBit(Ref(x + a, y + b), dimensions.x, contributorBitfield, &count); } } } } } - } - -/*if _DEBUG -Con_Message(" Sector %i: %i / %i", theMap->sectorIndex(s), changedCount, count); -#endif*/ - Sector::LightGridData &lgData = sector->_lightGridData; - lgData.changedBlockCount = changedCount; - lgData.blockCount = changedCount + count; + // LOG_DEBUG(" Sector %i: %i / %i") << theMap->sectorIndex(s) << changedCount << count; - if(lgData.blockCount > 0) - { - lgData.blocks = (ushort *) Z_Malloc(sizeof(*lgData.blocks) * lgData.blockCount, PU_MAPSTATIC, 0); + Sector::LightGridData &lgData = sector->_lightGridData; + lgData.changedBlockCount = changedCount; + lgData.blockCount = changedCount + count; - for(x = 0, a = 0, b = changedCount; x < lgBlockWidth * lgBlockHeight; - ++x) + if(lgData.blockCount > 0) { - if(HasIndexBit(x, 0, indexBitfield)) - lgData.blocks[a++] = x; - else if(HasIndexBit(x, 0, contributorBitfield)) - lgData.blocks[b++] = x; - } + lgData.blocks = (ushort *) Z_Malloc(sizeof(*lgData.blocks) * lgData.blockCount, PU_MAPSTATIC, 0); + + int a = 0, b = changedCount; + for(int x = 0; x < dimensions.x * dimensions.y; ++x) + { + if(hasIndexBit(Ref(x, 0), dimensions.x, indexBitfield)) + lgData.blocks[a++] = x; + else if(hasIndexBit(Ref(x, 0), dimensions.x, contributorBitfield)) + lgData.blocks[b++] = x; + } - DENG_ASSERT(a == changedCount); - //DENG_ASSERT(b == info->blockCount); + DENG_ASSERT(a == changedCount); + //DENG_ASSERT(b == blockCount); + } } - } - M_Free(indexBitfield); - M_Free(contributorBitfield); + M_Free(indexBitfield); + M_Free(contributorBitfield); - // How much time did we spend? - VERBOSE(Con_Message - ("LG_InitForMap: Done in %.2f seconds.", - (Timer_RealMilliseconds() - startTime) / 1000.0f)); + self.markAllForUpdate(); + + // How much time did we spend? + LOG_INFO(String("LightGrid::initialize: Done in %1 seconds.").arg(begunAt.since(), 0, 'g', 2)); + } +}; + +LightGrid::LightGrid(GameMap &map) + : d(new Instance(this, map)) +{ + d->initialize(); } -/** - * Apply the sector's lighting to the block. - */ -static void LG_ApplySector(gridblock_t *block, Vector3f const &color, - float level, float factor, int bias) +Vector2i const &LightGrid::dimensions() const { - // Apply a bias to the light level. - level -= (0.95f - level); - if(level < 0) - level = 0; + return d->dimensions; +} - level *= factor; +coord_t LightGrid::blockSize() const +{ + return d->blockSize; +} - if(level <= 0) - return; +Vector3f LightGrid::evaluate(Vector3d const &point) +{ + LightBlock &block = d->lightBlock(point); + Vector3f color = block.evaluate(); + // Apply light range compression. for(int i = 0; i < 3; ++i) { - float c = color[i] * level; - - c = de::clamp(0.f, c, 1.f); - - if(block->rgb[i] + c > 1) - { - block->rgb[i] = 1; - } - else - { - block->rgb[i] += c; - } + color[i] += Rend_LightAdaptationDelta(color[i]); } - // Influenced by the source bias. - int i = block->bias * (1 - factor) + bias * factor; - i = de::clamp(-0x80, i, 0x7f); - block->bias = i; + return color; } -/** - * Called when a sector has changed its light level. - */ -void LG_SectorChanged(Sector *sector) +float LightGrid::evaluateLightLevel(Vector3d const &point) { - if(!lgInited) return; - if(!sector) return; + Vector3f color = evaluate(point); + /// @todo Do not do this at evaluation time; store into another grid. + return (color.x + color.y + color.z) / 3; +} - Sector::LightGridData &lgData = sector->_lightGridData; +void LightGrid::sectorChanged(Sector §or) +{ + Sector::LightGridData &lgData = sector._lightGridData; if(!lgData.changedBlockCount && !lgData.blockCount) return; // Mark changed blocks and contributors. for(uint i = 0; i < lgData.changedBlockCount; ++i) { - ushort n = lgData.blocks[i]; - - // The color will be recalculated. - if(!(grid[n].flags & GBF_CHANGED)) - { - std::memcpy(grid[n].oldRGB, grid[n].rgb, sizeof(grid[n].oldRGB)); - } - - for(int j = 0; j < 3; ++j) - { - grid[n].rgb[j] = 0; - } - - grid[n].flags |= GBF_CHANGED | GBF_CONTRIBUTOR; + d->lightBlock(lgData.blocks[i]).markChanged(); } for(uint i = 0; i < lgData.blockCount; ++i) { - grid[lgData.blocks[i]].flags |= GBF_CONTRIBUTOR; + d->lightBlock(lgData.blocks[i]).markChanged(true /* is-contributor */); } - needsUpdate = true; + d->needUpdate = true; } -void LG_MarkAllForUpdate() +void LightGrid::markAllForUpdate() { - if(!lgInited || !theMap) - return; - // Mark all blocks and contributors. - foreach(Sector *sector, theMap->sectors()) + foreach(Sector *sector, d->map.sectors()) { - LG_SectorChanged(sector); + sectorChanged(*sector); } } -#if 0 -/* - * Determines whether it is necessary to recalculate the lighting of a - * grid block. Updates are needed when there has been a light level - * or color change in a sector that affects the block. - */ -static boolean LG_BlockNeedsUpdate(int x, int y) +void LightGrid::update() { - // First check the block itself. - gridblock_t *block = GRID_BLOCK(x, y); - Sector *blockSector; - int a, b; - int limitA[2]; - int limitB; - - blockSector = block->sector; - if(!blockSector) - { - // The sector needs to be determined. - return true; - } - - if(SECT_INFO(blockSector)->flags & SIF_LIGHT_CHANGED) - { - return true; - } - - // Check neighbor blocks as well. - // Determine neighbor boundaries. Y coordinate. - if(y >= 2) - { - b = y - 2; - } - else - { - b = 0; - } - if(y <= lgBlockHeight - 3) - { - limitB = y + 2; - } - else - { - limitB = lgBlockHeight - 1; - } - - // X coordinate. - if(x >= 2) - { - limitA[0] = x - 2; - } - else - { - limitA[0] = 0; - } - if(x <= lgBlockWidth - 3) - { - limitA[1] = x + 2; - } - else - { - limitA[1] = lgBlockWidth - 1; - } - - // Iterate through neighbors. - for(; b <= limitB; ++b) + static float const factors[5 * 5] = { - a = limitA[0]; - block = GRID_BLOCK(a, b); - - for(; a <= limitA[1]; ++a, ++block) - { - if(!a && !b) continue; - - if(block->sector == blockSector) - continue; - - if(SECT_INFO(block->sector)->flags & SIF_LIGHT_CHANGED) - { - return true; - } - } - } - - return false; -} -#endif - -/** - * Update the grid by finding the strongest light source in each grid - * block. - */ -void LG_Update(void) -{ - static const float factors[5 * 5] = - { - .1f, .2f, .25f, .2f, .1f, - .2f, .4f, .5f, .4f, .2f, - .25f, .5f, 1.f, .5f, .25f, - .2f, .4f, .5f, .4f, .2f, - .1f, .2f, .25f, .2f, .1f + .1f, .2f, .25f, .2f, .1f, + .2f, .4f, .5f, .4f, .2f, + .25f, .5f, 1.f, .5f, .25f, + .2f, .4f, .5f, .4f, .2f, + .1f, .2f, .25f, .2f, .1f }; - gridblock_t *block, *lastBlock, *other; - int x, y, a, b; - Sector *sector; - int bias; - int height; - -#ifdef DD_PROFILE - static int i; - - if(++i > 40) - { - i = 0; - PRINT_PROF( PROF_GRID_UPDATE ); - } -#endif - - if(!lgInited || !needsUpdate) + if(!d->needUpdate) return; -BEGIN_PROF( PROF_GRID_UPDATE ); - -#if 0 - for(block = grid, y = 0; y < lgBlockHeight; ++y) + for(int y = 0; y < d->dimensions.y; ++y) + for(int x = 0; x < d->dimensions.x; ++x) { - for(x = 0; x < lgBlockWidth; ++x, block++) - { - if(LG_BlockNeedsUpdate(x, y)) - { - block->flags |= GBF_CHANGED; - - // Clear to zero (will be recalculated). - memset(block->rgb, 0, sizeof(float) * 3); - - // Mark contributors. - for(b = -2; b <= 2; ++b) - { - if(y + b < 0 || y + b >= lgBlockHeight) - continue; + LightBlock &block = d->lightBlock(x, y); - for(a = -2; a <= 2; ++a) - { - if(x + a < 0 || x + a >= lgBlockWidth) - continue; + // No contribution? + if(!block.flags().testFlag(LightBlock::Contributor)) + continue; - GRID_BLOCK(x + a, y + b)->flags |= GBF_CONTRIBUTOR; - } - } - } - else - { - block->flags &= ~GBF_CHANGED; - } - } - } -#endif + // Determine the ambient light properties of the sector at this block. + Sector §or = block.sector(); + Vector3f const &color = Rend_SectorLightColor(sector); + float const level = sector.lightLevel(); + int const bias = biasForSector(sector); - for(block = grid, y = 0; y < lgBlockHeight; ++y) - { - for(x = 0; x < lgBlockWidth; ++x, ++block) + /// @todo Calculate min/max for a and b. + for(int a = -2; a <= 2; ++a) + for(int b = -2; b <= 2; ++b) { - // Unused blocks can't contribute. - if(!(block->flags & GBF_CONTRIBUTOR) || !block->sector) + if(x + a < 0 || y + b < 0 || + x + a > d->dimensions.x - 1 || y + b > d->dimensions.y - 1) continue; - // Determine the color of the ambient light in this sector. - sector = block->sector; - Vector3f const &color = Rend_SectorLightColor(*sector); - height = (int) (sector->ceiling().height() - sector->floor().height()); - - bool isSkyFloor = sector->ceilingSurface().hasSkyMaskedMaterial(); - bool isSkyCeil = sector->floorSurface().hasSkyMaskedMaterial(); - - if(isSkyFloor && !isSkyCeil) - { - bias = -height / 6; - } - else if(!isSkyFloor && isSkyCeil) - { - bias = height / 6; - } - else if(height > 100) - { - bias = (height - 100) / 2; - } - else - { - bias = 0; - } - - // \todo Calculate min/max for a and b. - for(a = -2; a <= 2; ++a) - { - for(b = -2; b <= 2; ++b) - { - if(x + a < 0 || y + b < 0 || x + a > lgBlockWidth - 1 || - y + b > lgBlockHeight - 1) continue; + LightBlock &other = d->lightBlock(x + a, y + b); - other = GRID_BLOCK(x + a, y + b); + if(!other.flags().testFlag(LightBlock::Changed)) + continue; - if(other->flags & GBF_CHANGED) - { - LG_ApplySector(other, color, sector->lightLevel(), - factors[(b + 2)*5 + a + 2]/8, bias); - } - } - } + other.applySector(color, level, bias, factors[(b + 2) * 5 + a + 2] / 8); } } // Clear all changed and contribution flags. - lastBlock = &grid[lgBlockWidth * lgBlockHeight]; - for(block = grid; block != lastBlock; ++block) + foreach(LightBlock *block, d->grid) { - block->flags = 0; + if(!block) continue; + block->setFlags(LightBlock::AllFlags, UnsetFlags); } - needsUpdate = false; - -END_PROF( PROF_GRID_UPDATE ); + d->needUpdate = false; } -void LG_Evaluate(coord_t const point[3], float color[3]) -{ - int x, y, i; - //float dz = 0, dimming; - gridblock_t* block; - - if(!lgInited) - { - memset(color, 0, sizeof(float) * 3); - return; - } - - x = ROUND((point[VX] - lgOrigin[VX]) / lgBlockSize); - y = ROUND((point[VY] - lgOrigin[VY]) / lgBlockSize); - x = MINMAX_OF(1, x, lgBlockWidth - 2); - y = MINMAX_OF(1, y, lgBlockHeight - 2); - - block = &grid[y * lgBlockWidth + x]; - - /** - * danij: Biased light dimming disabled because this does not work - * well enough. The problem is that two points on a given surface - * may be determined to be in different blocks and as the height is - * taken from the block-linked sector this results in very uneven - * lighting. - * - * Biasing the dimming is a good idea but the heights must be taken - * from the BSP Leaf which contains the surface and not the block. - */ - if(block->sector) - { - /*if(block->bias < 0) - { - // Calculate Z difference to the ceiling. - dz = block->sector->ceiling().height() - point[VZ]; - } - else if(block->bias > 0) - { - // Calculate Z difference to the floor. - dz = point[VZ] - block->sector->floor().height(); - } - - dz -= 50; - if(dz < 0) - dz = 0;*/ - - if(block->flags & GBF_CHANGED) - { // We are waiting for an updated value, for now use the old. - color[CR] = block->oldRGB[CR]; - color[CG] = block->oldRGB[CG]; - color[CB] = block->oldRGB[CB]; - } - else - { - color[CR] = block->rgb[CR]; - color[CG] = block->rgb[CG]; - color[CB] = block->rgb[CB]; - } - } - else - { // The block has no sector!? - // Must be an error in the lightgrid covering sector determination. - // Init to black. - color[CR] = color[CG] = color[CB] = 0; - } - - // Biased ambient light causes a dimming in the Z direction. - /*if(dz && block->bias) - { - if(block->bias < 0) - dimming = 1 - (dz * (float) -block->bias) / 35000.0f; - else - dimming = 1 - (dz * (float) block->bias) / 35000.0f; - - if(dimming < .5f) - dimming = .5f; - - for(i = 0; i < 3; ++i) - { - // Apply the dimming - color[i] *= dimming; - - // Add the light range compression factor - color[i] += Rend_LightAdaptationDelta(color[i]); - } - } - else*/ - { - // Just add the light range compression factor - for(i = 0; i < 3; ++i) - color[i] += Rend_LightAdaptationDelta(color[i]); - } -} - -float LG_EvaluateLightLevel(coord_t const point[3]) -{ - float color[3]; - LG_Evaluate(point, color); - /// @todo Do not do this at evaluation time; store into another grid. - return (color[CR] + color[CG] + color[CB]) / 3; -} +#include "de_graphics.h" +#include "de_render.h" +#include "gl/sys_opengl.h" -/** - * Draw the grid in 2D HUD mode. - */ -void LG_Debug(void) +void LightGrid::drawDebugVisual() { - static int blink = 0; + static Vector3f const red(1, 0, 0); + static int blink = 0; - gridblock_t* block; - int x, y; - int vx, vy; - size_t vIdx = 0, blockIdx; - ddplayer_t* ddpl = (viewPlayer? &viewPlayer->shared : NULL); - - if(!lgInited || !lgShowDebug) - return; + // Disabled? + if(!lgShowDebug) return; DENG_ASSERT_IN_MAIN_THREAD(); DENG_ASSERT_GL_CONTEXT_ACTIVE(); - if(ddpl) + // Determine the grid reference of the view player. + Ref viewGRef; + if(viewPlayer) { blink++; - vx = ROUND((ddpl->mo->origin[VX] - lgOrigin[VX]) / lgBlockSize); - vy = ROUND((ddpl->mo->origin[VY] - lgOrigin[VY]) / lgBlockSize); - vx = MINMAX_OF(1, vx, lgBlockWidth - 2); - vy = MINMAX_OF(1, vy, lgBlockHeight - 2); - vIdx = vy * lgBlockWidth + vx; + viewGRef = d->toRef(viewPlayer->shared.mo->origin); } // Go into screen projection mode. @@ -1003,26 +940,30 @@ void LG_Debug(void) glLoadIdentity(); glOrtho(0, DENG_WINDOW->width(), DENG_WINDOW->height(), 0, -1, 1); - for(y = 0; y < lgBlockHeight; ++y) + for(int y = 0; y < d->dimensions.y; ++y) { glBegin(GL_QUADS); - for(x = 0; x < lgBlockWidth; ++x, ++block) + for(int x = 0; x < d->dimensions.x; ++x) { - blockIdx = (lgBlockHeight - 1 - y) * lgBlockWidth + x; - block = &grid[blockIdx]; + Ref gref(x, d->dimensions.y - 1 - y); + bool const isViewGRef = (viewPlayer && viewGRef == gref); - if(ddpl && vIdx == blockIdx && (blink & 16)) + Vector3f const *color; + if(isViewGRef && (blink & 16)) { - glColor3f(1, 0, 0); + color = &red; } else { - if(!block->sector) + LightBlock &block = d->lightBlock(gref); + if(isNullBlock(block)) continue; - glColor3fv(block->rgb); + color = &block.rawColorRef(); } + glColor3f(color->x, color->y, color->z); + glVertex2f(x * lgDebugSize, y * lgDebugSize); glVertex2f(x * lgDebugSize + lgDebugSize, y * lgDebugSize); glVertex2f(x * lgDebugSize + lgDebugSize, diff --git a/doomsday/client/src/render/r_main.cpp b/doomsday/client/src/render/r_main.cpp index b6fb5508e8..b5a588fc17 100644 --- a/doomsday/client/src/render/r_main.cpp +++ b/doomsday/client/src/render/r_main.cpp @@ -831,7 +831,9 @@ void R_BeginWorldFrame() if(!freezeRLs) { - LG_Update(); + // Initialize and/or update the LightGrid. + theMap->initLightGrid(); + SB_BeginFrame(); LO_BeginWorldFrame(); R_ClearObjlinksForFrame(); // Zeroes the links. diff --git a/doomsday/client/src/render/r_things.cpp b/doomsday/client/src/render/r_things.cpp index 60c7762446..0eaeaf8389 100644 --- a/doomsday/client/src/render/r_things.cpp +++ b/doomsday/client/src/render/r_things.cpp @@ -536,11 +536,10 @@ float R_ShadowStrength(mobj_t *mo) return 0; // Sample the ambient light level at the mobj's position. - if(useBias) + if(useBias && theMap->hasLightGrid()) { // Evaluate in the light grid. - vec3d_t point; V3d_Set(point, mo->origin[VX], mo->origin[VY], mo->origin[VZ]); - ambientLightLevel = LG_EvaluateLightLevel(point); + ambientLightLevel = theMap->lightGrid().evaluateLightLevel(mo->origin); } else { @@ -913,13 +912,11 @@ void getLightingParams(coord_t x, coord_t y, coord_t z, BspLeaf *bspLeaf, { collectaffectinglights_params_t lparams; - if(useBias) + if(useBias && theMap->hasLightGrid()) { - vec3d_t point; - // Evaluate the position in the light grid. - V3d_Set(point, x, y, z); - LG_Evaluate(point, ambientColor); + Vector3f tmp = theMap->lightGrid().evaluate(Vector3d(x, y, z)); + V3f_Set(ambientColor, tmp.x, tmp.y, tmp.z); } else { diff --git a/doomsday/client/src/render/rend_bias.cpp b/doomsday/client/src/render/rend_bias.cpp index e23d9add83..3faf5d2f9d 100644 --- a/doomsday/client/src/render/rend_bias.cpp +++ b/doomsday/client/src/render/rend_bias.cpp @@ -736,37 +736,13 @@ void SB_EndFrame() SBE_EndFrame(); } -void SB_AddLight(float dest[4], float const *color, float howMuch) +static void addLight(float dest[4], Vector3f const &color, float howMuch = 1.0f) { - float amplified[3]; - - if(!color) - { - float largest = 0; - - for(int i = 0; i < 3; ++i) - { - amplified[i] = dest[i]; - if(i == 0 || amplified[i] > largest) - largest = amplified[i]; - } - - if(largest == 0) // Black! - { - amplified[0] = amplified[1] = amplified[2] = 1; - } - else - { - for(int i = 0; i < 3; ++i) - { - amplified[i] = amplified[i] / largest; - } - } - } + DENG_ASSERT(dest != 0); for(int i = 0; i < 3; ++i) { - float newval = dest[i] + ((color ? color : amplified)[i] * howMuch); + float newval = dest[i] + (color[i] * howMuch); if(newval > 1) newval = 1; @@ -775,35 +751,13 @@ void SB_AddLight(float dest[4], float const *color, float howMuch) } } -#if 0 -/** - * Color override forces the bias light color to override biased - * sectorlight. - */ -static boolean SB_CheckColorOverride(biasaffection_t *affected) -{ - DENG_ASSERT(affected); - - for(int 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 - void SB_RendPoly(struct ColorRawf_s *rcolors, BiasSurface *bsuf, struct rvertex_s const *rvertices, size_t numVertices, Vector3f const &surfaceNormal, float sectorLightLevel, de::MapElement const *mapElement, int elmIdx) { + DENG_ASSERT(bsuf != 0); + // Apply sectorlight bias. Note: Distance darkening is not used // with bias lights. if(sectorLightLevel > biasMin && biasMax > biasMin) @@ -876,9 +830,9 @@ void SB_RendPoly(struct ColorRawf_s *rcolors, BiasSurface *bsuf, /** * Interpolate between current and destination. */ -void SB_LerpIllumination(vertexillum_t *illum, float *result) +static void lerpIllumination(vertexillum_t *illum, float *result) { - DENG_ASSERT(illum); + DENG_ASSERT(illum != 0); if(!(illum->flags & VIF_LERP)) { @@ -960,17 +914,11 @@ float *SB_GetCasted(vertexillum_t *illum, int sourceIndex, return 0; } -/** - * Add ambient light. - */ -void SB_AmbientLight(Vector3d const &point, float *light) +static Vector3f ambientLight(Vector3d const &point) { - // Add grid light (represents ambient lighting). - coord_t pointV1[3] = { point.x, point.y, point.z }; - float color[3]; - - LG_Evaluate(pointV1, color); - SB_AddLight(light, color, 1.0f); + if(theMap->hasLightGrid()) + return theMap->lightGrid().evaluate(point); + return Vector3f(0, 0, 0); } /** @@ -1053,8 +1001,11 @@ void SB_EvalPoint(float light[4], vertexillum_t *illum, if(!illuminationChanged && illum != NULL) { // Reuse the previous value. - SB_LerpIllumination(illum, light); - SB_AmbientLight(point, light); + lerpIllumination(illum, light); + + // Add ambient lighting. + addLight(light, ambientLight(point)); + return; } @@ -1089,7 +1040,6 @@ void SB_EvalPoint(float light[4], vertexillum_t *illum, // This affecting source does not contribute any light. casted[CR] = casted[CG] = casted[CB] = 0; } - continue; } @@ -1116,64 +1066,21 @@ void SB_EvalPoint(float light[4], vertexillum_t *illum, // The light casted from this source. casted[i] = s->color[i] * level; } - - // Are we already fully lit? - /*if(!(newColor[CR] < 1 && newColor[CG] < 1 && new.rgba[2] < 1)) - break;*/ } if(illum) { - // bool willOverride = false; - // Combine the casted light from each source. for(aff = affecting; aff->source; aff++) { float *casted = SB_GetCasted(illum, aff->index, affectedSources); -/* if(aff->overrider && - (casted[CR] > 0 || casted[CG] > 0 || casted[CB] > 0)) - willOverride = true; -*/ - /* - if(!(casted[3] > 0)) - { - int n; - Con_Message("affected: "); - for(n = 0; n < MAX_BIAS_AFFECTED; ++n) - Con_Message(" - %i", affectedSources[n].source); - 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:(%g %g %g) ", - aff->index, casted[CR], casted[CG], casted[CB]); - printf("\n"); - }*/ - for(uint i = 0; i < 3; ++i) { newColor[i] = MINMAX_OF(0, newColor[i] + casted[i], 1); } } - /*if(biasAmount > 0) - { - SB_AddLight(&new, willOverride ? NULL : biasColor, biasAmount); - }*/ - // Is there a new destination? if(!(illum->dest[CR] < newColor[CR] + COLOR_CHANGE_THRESHOLD && illum->dest[CR] > newColor[CR] - COLOR_CHANGE_THRESHOLD) || @@ -1185,7 +1092,7 @@ void SB_EvalPoint(float light[4], vertexillum_t *illum, if(illum->flags & VIF_LERP) { // Must not lose the half-way interpolation. - float mid[3]; SB_LerpIllumination(illum, mid); + float mid[3]; lerpIllumination(illum, mid); // This is current color at this very moment. illum->color[CR] = mid[CR]; @@ -1202,7 +1109,7 @@ void SB_EvalPoint(float light[4], vertexillum_t *illum, illum->updatetime = latestSourceUpdate; } - SB_LerpIllumination(illum, light); + lerpIllumination(illum, light); } else { @@ -1211,7 +1118,8 @@ void SB_EvalPoint(float light[4], vertexillum_t *illum, light[CB] = newColor[CB]; } - SB_AmbientLight(point, light); + // Add ambient lighting. + addLight(light, ambientLight(point)); #undef COLOR_CHANGE_THRESHOLD } diff --git a/doomsday/client/src/render/rend_main.cpp b/doomsday/client/src/render/rend_main.cpp index 92448b41ab..2969a73db5 100644 --- a/doomsday/client/src/render/rend_main.cpp +++ b/doomsday/client/src/render/rend_main.cpp @@ -173,6 +173,13 @@ static Vector3f currentSectorLightColor; static float currentSectorLightLevel; static bool firstBspLeaf; // No range checking for the first one. +static void markLightGridForFullUpdate() +{ + if(!theMap) return; + if(!theMap->hasLightGrid()) return; + theMap->lightGrid().markAllForUpdate(); +} + void Rend_Register() { C_VAR_FLOAT ("rend-camera-fov", &fieldOfView, 0, 1, 179); @@ -188,8 +195,8 @@ void Rend_Register() C_VAR_FLOAT ("rend-light-bright", &dynlightFactor, 0, 0, 1); C_VAR_FLOAT2("rend-light-compression", &lightRangeCompression, 0, -1, 1, Rend_UpdateLightModMatrix); C_VAR_FLOAT ("rend-light-fog-bright", &dynlightFogBright, 0, 0, 1); - C_VAR_FLOAT2("rend-light-sky", &rendSkyLight, 0, 0, 1, LG_MarkAllForUpdate); - C_VAR_BYTE2 ("rend-light-sky-auto", &rendSkyLightAuto, 0, 0, 1, LG_MarkAllForUpdate); + C_VAR_FLOAT2("rend-light-sky", &rendSkyLight, 0, 0, 1, markLightGridForFullUpdate); + C_VAR_BYTE2 ("rend-light-sky-auto", &rendSkyLightAuto, 0, 0, 1, markLightGridForFullUpdate); C_VAR_FLOAT ("rend-light-wall-angle", &rendLightWallAngle, CVF_NO_MAX, 0, 0); C_VAR_BYTE ("rend-light-wall-angle-smooth", &rendLightWallAngleSmooth, 0, 0, 1); @@ -223,7 +230,7 @@ void Rend_Register() LO_Register(); Rend_DecorRegister(); SB_Register(); - LG_Register(); + LightGrid::consoleRegister(); Sky_Register(); Rend_ModelRegister(); Rend_ParticleRegister(); @@ -412,8 +419,8 @@ Vector3f const &Rend_SectorLightColor(Sector const §or) skyLightColor[i] = skyLightColor[i] + (1 - rendSkyLight) * (1.f - skyLightColor[i]); } - // When the sky light color changes we must update the lightgrid. - LG_MarkAllForUpdate(); + // When the sky light color changes we must update the light grid. + markLightGridForFullUpdate(); oldSkyAmbientColor = Vector3f(ambientColor->rgb); } diff --git a/doomsday/client/src/render/rend_particle.cpp b/doomsday/client/src/render/rend_particle.cpp index 4f7a4922c5..2958a59020 100644 --- a/doomsday/client/src/render/rend_particle.cpp +++ b/doomsday/client/src/render/rend_particle.cpp @@ -458,9 +458,10 @@ static void setupModelParamsForParticle(rendmodelparams_t* params, { collectaffectinglights_params_t lparams; - if(useBias) + if(useBias && theMap->hasLightGrid()) { - LG_Evaluate(params->origin, params->ambientColor); + Vector3f tmp = theMap->lightGrid().evaluate(params->origin); + V3f_Set(params->ambientColor, tmp.x, tmp.y, tmp.z); } else { diff --git a/doomsday/client/src/render/sprite.cpp b/doomsday/client/src/render/sprite.cpp index ac803e02d0..573a40268b 100644 --- a/doomsday/client/src/render/sprite.cpp +++ b/doomsday/client/src/render/sprite.cpp @@ -329,10 +329,11 @@ static void setupPSpriteParams(rendpspriteparams_t *params, vispsprite_t *spr) { collectaffectinglights_params_t lparams; - if(useBias) + if(useBias && theMap->hasLightGrid()) { // Evaluate the position in the light grid. - LG_Evaluate(spr->origin, params->ambientColor); + Vector3f tmp = theMap->lightGrid().evaluate(spr->origin); + V3f_Set(params->ambientColor, tmp.x, tmp.y, tmp.z); } else { @@ -719,9 +720,10 @@ static void setupModelParamsForVisPSprite(rendmodelparams_t *params, vispsprite_ { collectaffectinglights_params_t lparams; - if(useBias) + if(useBias && theMap->hasLightGrid()) { - LG_Evaluate(params->origin, params->ambientColor); + Vector3f tmp = theMap->lightGrid().evaluate(params->origin); + V3f_Set(params->ambientColor, tmp.x, tmp.y, tmp.z); } else { diff --git a/doomsday/client/src/ui/legacywidget.cpp b/doomsday/client/src/ui/legacywidget.cpp index b35edc8d7b..d52300b69b 100644 --- a/doomsday/client/src/ui/legacywidget.cpp +++ b/doomsday/client/src/ui/legacywidget.cpp @@ -28,6 +28,7 @@ #include "dd_main.h" #include "dd_loop.h" #include "sys_system.h" +#include "edit_bias.h" #include "map/gamemap.h" #include "network/net_main.h" #include "render/r_main.h" @@ -164,7 +165,16 @@ void LegacyWidget::draw() if(drawGame) { - // Debug information. + // Shadow Bias Editor HUD (if it is active). + SBE_DrawHUD(); + + /* + * Draw debug information. + */ + if(theMap && theMap->hasLightGrid()) + { + theMap->lightGrid().drawDebugVisual(); + } Net_Drawer(); S_Drawer(); diff --git a/doomsday/server/include/server_dummies.h b/doomsday/server/include/server_dummies.h index ea68b990c1..780996616e 100644 --- a/doomsday/server/include/server_dummies.h +++ b/doomsday/server/include/server_dummies.h @@ -75,8 +75,6 @@ DENG_EXTERN_C void Models_Init(); DENG_EXTERN_C void Models_Shutdown(); DENG_EXTERN_C void Models_CacheForState(int stateIndex); -//DENG_EXTERN_C void LG_SectorChanged(Sector* sector); - DENG_EXTERN_C void Cl_InitPlayers(void); DENG_EXTERN_C void UI_Init();