From 7fa23c2347cd5580dd2a7581a5ad093f8cd2a4eb Mon Sep 17 00:00:00 2001 From: danij Date: Thu, 5 Sep 2013 23:13:15 +0100 Subject: [PATCH] Refactor|Map Renderer|Client: Cleaned up dynamic light projection --- doomsday/client/client.pro | 2 + doomsday/client/include/de_render.h | 7 +- doomsday/client/include/render/projector.h | 131 +++++++ doomsday/client/include/render/r_shadow.h | 9 +- doomsday/client/include/render/rend_main.h | 25 +- doomsday/client/src/render/projector.cpp | 429 +++++++++++++++++++++ doomsday/client/src/render/r_shadow.cpp | 26 +- doomsday/client/src/render/rend_main.cpp | 343 ++++++++++------ 8 files changed, 828 insertions(+), 144 deletions(-) create mode 100644 doomsday/client/include/render/projector.h create mode 100644 doomsday/client/src/render/projector.cpp diff --git a/doomsday/client/client.pro b/doomsday/client/client.pro index b3e0bbc869..c0156b6f0f 100644 --- a/doomsday/client/client.pro +++ b/doomsday/client/client.pro @@ -284,6 +284,7 @@ DENG_HEADERS += \ include/render/lightgrid.h \ include/render/lumobj.h \ include/render/materialcontext.h \ + include/render/projector.h \ include/render/r_draw.h \ include/render/r_main.h \ include/render/r_shadow.h \ @@ -622,6 +623,7 @@ SOURCES += \ src/render/huecirclevisual.cpp \ src/render/lightgrid.cpp \ src/render/lumobj.cpp \ + src/render/projector.cpp \ src/render/r_draw.cpp \ src/render/r_fakeradio.cpp \ src/render/r_main.cpp \ diff --git a/doomsday/client/include/de_render.h b/doomsday/client/include/de_render.h index c68f4f42ec..97e6d6b1cf 100644 --- a/doomsday/client/include/de_render.h +++ b/doomsday/client/include/de_render.h @@ -25,16 +25,17 @@ #include "render/r_main.h" #ifdef __CLIENT__ -#include "render/rend_main.h" -#include "render/r_draw.h" -#include "render/r_things.h" #include "render/lightgrid.h" #include "render/lumobj.h" +#include "render/projector.h" +#include "render/r_draw.h" #include "render/r_shadow.h" +#include "render/r_things.h" #include "render/rend_clip.h" #include "render/rend_halo.h" #include "render/rend_list.h" #include "render/rend_particle.h" +#include "render/rend_main.h" #include "render/rend_model.h" #include "render/rend_shadow.h" #include "render/rend_fakeradio.h" diff --git a/doomsday/client/include/render/projector.h b/doomsday/client/include/render/projector.h new file mode 100644 index 0000000000..1b0a93e068 --- /dev/null +++ b/doomsday/client/include/render/projector.h @@ -0,0 +1,131 @@ +/** @file projector.h Texture coordinate projector and projection lists. + * + * @todo: This module should be replaced with a system which decentralizes the + * projection lists such that these can be managed piecewise and possibly used + * for drawing multiple frames. + * + * @authors Copyright © 2003-2013 Jaakko Keränen + * @authors Copyright © 2006-2013 Daniel Swanson + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * 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, see: + * http://www.gnu.org/licenses + */ + +#ifndef DENG_CLIENT_RENDER_PROJECTOR_H +#define DENG_CLIENT_RENDER_PROJECTOR_H + +#include +#include + +#include "api_gl.h" // DGLuint + +#include "world/map.h" + +class BspLeaf; + +/** + * Dynlight stores a luminous object => surface projection. + */ +struct dynlight_t +{ + DGLuint texture; + de::Vector2f topLeft, bottomRight; + de::Vector4f color; +}; + +/** + * To be called to initialize the projector when the current map changes. + */ +void Rend_ProjectorInitForMap(de::Map &map); + +/** + * To be called at the start of a render frame to clear the projection lists + * to prepare for subsequent drawing. + */ +void Rend_ProjectorReset(); + +/** + * @defgroup projectLightFlags Flags for Rend_ProjectLumobjs + * @ingroup flags + */ +///@{ +#define PLF_SORT_LUMINOSITY_DESC 0x1 ///< Sort by descending luminosity, brightest to dullest. +#define PLF_TEX_FLOOR 0x4 ///< Prefer the "floor" slot when picking textures. +#define PLF_TEX_CEILING 0x8 ///< Prefer the "ceiling" slot when picking textures. +///@} + +/** + * Project all lumobjs affecting the given quad (world space), calculate + * coordinates (in texture space) then store into a new list of projections. + * + * @pre The coordinates of the given quad must be contained wholly within + * the BSP leaf specified. This is due to an optimization within the lumobj + * management which separates them according to their position in the BSP. + * + * @param flags @ref projectLightFlags + * @param bspLeaf BspLeaf within which the quad wholly resides. + * @param blendFactor Multiplied with projection alpha. + * @param topLeft Top left coordinates of the surface being projected to. + * @param bottomRight Bottom right coordinates of the surface being projected to. + * @param tangentMatrix Normalized tangent space matrix of the surface being projected to. + * @param lmap Semantic identifier of the lightmap to use. + * + * Return values: + * @param listIdx If projected to, the identifier of the resultant list + * (1-based) is written here. If a projection list already + * exists it will be reused. + */ +void Rend_ProjectLumobjs(int flags, BspLeaf *bspLeaf, float blendFactor, + de::Vector3d const &topLeft, de::Vector3d const &bottomRight, + de::Matrix3f const &tangentMatrix, /*Lumobj::LightmapSemantic lightmap,*/ + uint &listIdx); + +/** + * Project all plane glows affecting the given quad (world space), calculate + * coordinates (in texture space) then store into a new list of projections. + * + * @pre The coordinates of the given quad must be contained wholly within + * the BSP leaf specified. This is due to an optimization within the lumobj + * management which separates them according to their position in the BSP. + * + * @param flags @ref projectLightFlags + * @param bspLeaf BspLeaf within which the quad wholly resides. + * @param blendFactor Multiplied with projection alpha. + * @param topLeft Top left coordinates of the surface being projected to. + * @param bottomRight Bottom right coordinates of the surface being projected to. + * @param tangentMatrix Normalized tangent space matrix of the surface being projected to. + * + * Return values: + * @param listIdx If projected to, the identifier of the resultant list + * (1-based) is written here. If a projection list already + * exists it will be reused. + */ +void Rend_ProjectPlaneGlows(int flags, BspLeaf *bspLeaf, float blendFactor, + de::Vector3d const &topLeft, de::Vector3d const &bottomRight, + de::Matrix3f const &tangentMatrix, uint &listIdx); + +/** + * Iterate over projections in the identified surface-projection list, making + * a callback for each visited. Iteration ends when all selected projections + * have been visited or a callback returns non-zero. + * + * @param listIdx Unique identifier of the list to process. + * @param callback Callback to make for each visited projection. + * @param context Passed to the callback. + * + * @return @c 0 iff iteration completed wholly. + */ +int Rend_IterateProjectionList(uint listIdx, int (*callback) (dynlight_t const *, void *), + void *context = 0); + +#endif // DENG_CLIENT_RENDER_PROJECTOR_H diff --git a/doomsday/client/include/render/r_shadow.h b/doomsday/client/include/render/r_shadow.h index f7323292ee..0edb0ccc2f 100644 --- a/doomsday/client/include/render/r_shadow.h +++ b/doomsday/client/include/render/r_shadow.h @@ -63,11 +63,14 @@ float R_ShadowAttenuationFactor(coord_t distance); * @param bottomRight Bottom right coordinates of the surface being projected to. * @param tangentMatrix Normalized tangent space matrix of the surface being projected to. * - * @return Projection list identifier if surface is lit else @c 0. + * Return values: + * @param listIdx If projected to, the identifier of the resultant list + * (1-based) is written here. If a projection list already + * exists it will be reused. */ -uint R_ProjectShadowsToSurface(BspLeaf *bspLeaf, float blendFactor, +void Rend_ProjectMobjShadows(BspLeaf *bspLeaf, float blendFactor, de::Vector3d const &topLeft, de::Vector3d const &bottomRight, - de::Matrix3f const &tangentMatrix); + de::Matrix3f const &tangentMatrix, uint &listIdx); /** * Iterate over projections in the identified shadow-projection list, making diff --git a/doomsday/client/include/render/rend_main.h b/doomsday/client/include/render/rend_main.h index eaea634866..887127a58b 100644 --- a/doomsday/client/include/render/rend_main.h +++ b/doomsday/client/include/render/rend_main.h @@ -67,8 +67,9 @@ DENG_EXTERN_C int useBias; DENG_EXTERN_C int useDynLights; DENG_EXTERN_C float dynlightFactor, dynlightFogBright; +DENG_EXTERN_C int rendMaxLumobjs; -DENG_EXTERN_C int useWallGlow; +DENG_EXTERN_C int useGlowOnWalls; DENG_EXTERN_C float glowFactor, glowHeightFactor; DENG_EXTERN_C int glowHeightMax; @@ -82,6 +83,9 @@ DENG_EXTERN_C int useShinySurfaces; DENG_EXTERN_C float detailFactor, detailScale; DENG_EXTERN_C byte devRendSkyAlways; +DENG_EXTERN_C byte rendInfoLums; +DENG_EXTERN_C byte devDrawLums; + DENG_EXTERN_C byte freezeRLs; void Rend_Register(); @@ -155,6 +159,25 @@ void Rend_DrawLightModMatrix(); */ de::Vector3f const &Rend_SectorLightColor(Sector const §or); +/** + * Blend the given light value with the luminous object's color, applying any + * applicable global modifiers and returns the result. + * + * @param color Source light color. + * @param light Strength of the light on the illumination point. + * + * @return Calculated result. + */ +de::Vector3f Rend_LuminousColor(de::Vector3f const &color, float light); + +/** + * Given an @a intensity determine the height of the plane glow, applying any + * applicable global modifiers. + * + * @return Calculated result. + */ +coord_t Rend_PlaneGlowHeight(float intensity); + /** * Selects a Material for the given map @a surface considering the current map * renderer configuration. diff --git a/doomsday/client/src/render/projector.cpp b/doomsday/client/src/render/projector.cpp new file mode 100644 index 0000000000..73b0e85a1b --- /dev/null +++ b/doomsday/client/src/render/projector.cpp @@ -0,0 +1,429 @@ +/** @file projector.cpp Texture coordinate projector and projection lists. + * + * @authors Copyright © 2003-2013 Jaakko Keränen + * @authors Copyright © 2006-2013 Daniel Swanson + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * 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, see: + * http://www.gnu.org/licenses + */ + +#include + +#include "de_platform.h" +#include "de_graphics.h" +#include "de_render.h" + +#include "BspLeaf" +#include "world/p_objlink.h" + +#include "render/projector.h" + +using namespace de; + +struct ListNode +{ + ListNode *next, *nextUsed; + dynlight_t projection; +}; + +struct List +{ + ListNode *head; + bool sortByLuma; ///< @c true= Sort from brightest to darkest. +}; + +// Projection list nodes. +static ListNode *firstNode, *cursorNode; + +// Projection lists. +static uint listCount, cursorList; +static List *lists; + +/** + * Find/create a new projection list. + * + * @param listIdx Address holding the list index to retrieve. If the referenced + * list index is non-zero return the associated list. Otherwise + * allocate a new list and write it's index back to this address. + * + * @param sortByLuma @c true= The list should maintain luma-sorted order. + * + * @return ProjectionList associated with the (possibly newly attributed) index. + */ +static List *newList(uint *listIdx, bool sortByLuma) +{ + DENG_ASSERT(listIdx != 0); + + // Do we need to allocate a list? + if(!(*listIdx)) + { + // Do we need to allocate more lists? + if(++cursorList >= listCount) + { + listCount *= 2; + if(!listCount) listCount = 2; + + lists = (List *) Z_Realloc(lists, listCount * sizeof(*lists), PU_MAP); + } + + List *list = &lists[cursorList-1]; + list->head = 0; + list->sortByLuma = sortByLuma; + + *listIdx = cursorList; + } + + return lists + ((*listIdx) - 1); // 1-based index. +} + +static ListNode *newListNode() +{ + ListNode *node; + + // Do we need to allocate mode nodes? + if(!cursorNode) + { + node = (ListNode *) Z_Malloc(sizeof(*node), PU_APPSTATIC, NULL); + + // Link the new node to the list. + node->nextUsed = firstNode; + firstNode = node; + } + else + { + node = cursorNode; + cursorNode = cursorNode->nextUsed; + } + + node->next = 0; + return node; +} + +static ListNode *newProjection(DGLuint texture, Vector2f const &topLeft, + Vector2f const &bottomRight, Vector3f const &color, float alpha) +{ + DENG_ASSERT(texture != 0); + + ListNode *node = newListNode(); + + dynlight_t &dyn = node->projection; + dyn.texture = texture; + dyn.topLeft = topLeft; + dyn.bottomRight = bottomRight; + dyn.color = Vector4f(color, de::clamp(0.f, alpha, 1.f)); + + return node; +} + +/// Average color * alpha. +static inline float dynlightLuminosity(dynlight_t *dyn) +{ + DENG_ASSERT(dyn != 0); + return (dyn->color.x + dyn->color.y + dyn->color.z) / 3 * dyn->color.w; +} + +static ListNode *linkNodeInList(ListNode *node, List *list) +{ + DENG_ASSERT(node && list); + + if(list->head && list->sortByLuma) + { + float luma = dynlightLuminosity(&node->projection); + ListNode *iter = list->head, *last = iter; + do + { + // Is this brighter than that being added? + if(dynlightLuminosity(&iter->projection) > luma) + { + last = iter; + iter = iter->next; + } + else + { + // Insert it here. + node->next = last->next; + last->next = node; + return node; + } + } while(iter); + } + + node->next = list->head; + list->head = node; + return node; +} + +/** + * Construct a new surface projection (and a list, if one has not already been + * constructed for the referenced index). + * + * @param listIdx Address holding the list index to retrieve. If the referenced + * list index is non-zero return the associated list. Otherwise + * allocate a new list and write it's index back to this address. + * @param sortByLuma @c true= The list should maintain luma-sorted order. + * @param texture GL identifier to texture attributed to the new projection. + * @param topLeft GL texture coordinates for the top left point in texture space. + * @param bottomRight GL texture coordinates for the bottom right point in texture space. + * @param color RGB color attributed to the new projection. + * @param alpha Alpha attributed to the new projection. + */ +static inline void newProjection(uint *listIdx, bool sortByLuma, DGLuint texture, + Vector2f const &topLeft, Vector2f const &bottomRight, Vector3f const &color, + float alpha) +{ + linkNodeInList(newProjection(texture, topLeft, bottomRight, color, alpha), + newList(listIdx, sortByLuma)); +} + +static Lumobj::LightmapSemantic semanticFromFlags(int flags) +{ + if(flags & PLF_TEX_FLOOR) return Lumobj::Down; + if(flags & PLF_TEX_CEILING) return Lumobj::Up; + return Lumobj::Side; +} + +/// Returns the texture variant specification for lightmaps. +static texturevariantspecification_t &lightmapSpec() +{ + return GL_TextureVariantSpec(TC_MAPSURFACE_LIGHTMAP, 0, 0, 0, 0, + GL_CLAMP, GL_CLAMP, 1, -1, -1, + false, false, false, true); +} + +static DGLuint prepareLightmap(Texture *tex = 0) +{ + if(tex) + { + if(TextureVariant *variant = tex->prepareVariant(lightmapSpec())) + { + return variant->glName(); + } + // Dang... + } + // Prepare the default/fallback lightmap instead. + return GL_PrepareLSTexture(LST_DYNAMIC); +} + +void Rend_ProjectorInitForMap(Map &map) +{ + static bool firstTime = true; + if(firstTime) + { + firstNode = 0; + cursorNode = 0; + firstTime = false; + } + + // All memory for the lists is allocated from Zone so we can "forget" it. + lists = 0; + listCount = 0; + cursorList = 0; +} + +void Rend_ProjectorReset() +{ + // Start reusing nodes. + cursorNode = firstNode; + + // Clear the lists. + cursorList = 0; + if(listCount) + { + std::memset(lists, 0, listCount * sizeof *lists); + } +} + +struct project_params_t +{ + uint *listIdx; ///< Index of any resulting projection list. + int flags; ///< @ref lightProjectionFlags. + float blendFactor; ///< Multiplied with projection alpha. + Vector3d const *topLeft; ///< Top left vertex of the surface. + Vector3d const *bottomRight; ///< Bottom right vertex of the surface. + Matrix3f const *tangentMatrix; ///< Normalized surface tangent space vectors. +}; + +/** + * Project the given lumobj onto the surface. + * + * @param lum Lumobj representing the light being projected. + * @param parm project_params_t parameters. + */ +static void projectLumobj(Lumobj &lum, project_params_t &parm) +{ + // Has this already been occluded? + if(R_ViewerLumobjIsHidden(lum.indexInMap())) + return; + + // No lightmap texture? + DGLuint tex = prepareLightmap(lum.lightmap(semanticFromFlags(parm.flags))); + if(!tex) return; + + Vector3d lumCenter = lum.origin(); + lumCenter.z += lum.zOffset(); + + // On the right side? + Vector3d topLeftToLum = *parm.topLeft - lumCenter; + if(topLeftToLum.dot(parm.tangentMatrix->column(2)) > 0.f) + return; + + // Calculate 3D distance between surface and lumobj. + Vector3d pointOnPlane = + R_ClosestPointOnPlane(parm.tangentMatrix->column(2)/*normal*/, *parm.topLeft, lumCenter); + + coord_t distToLum = (lumCenter - pointOnPlane).length(); + if(distToLum <= 0 || distToLum > lum.radius()) + return; + + // Calculate the final surface light attribution factor. + float luma = 1.5f - 1.5f * distToLum / lum.radius(); + + // Fade out as distance from viewer increases? + if(lum.maxDistance() > 0) + { + luma *= lum.attenuation(R_ViewerLumobjDistance(lum.indexInMap())); + } + + // Would this be seen? + if(luma * parm.blendFactor < OMNILIGHT_SURFACE_LUMINOSITY_ATTRIBUTION_MIN) + return; + + // Project, counteracting aspect correction slightly. + Vector2f s, t; + float const scale = 1.0f / ((2.f * lum.radius()) - distToLum); + if(!R_GenerateTexCoords(s, t, pointOnPlane, scale, scale * 1.08f, + *parm.topLeft, *parm.bottomRight, *parm.tangentMatrix)) + return; + + // Write to the projection list. + newProjection(parm.listIdx, (parm.flags & PLF_SORT_LUMINOSITY_DESC) != 0, + tex, Vector2f(s[0], t[0]), Vector2f(s[1], t[1]), + Rend_LuminousColor(lum.color(), luma), parm.blendFactor); +} + +static int projectLumobjWorker(void *lum, void *context) +{ + projectLumobj(*static_cast(lum), *static_cast(context)); + return false; // Continue iteration. +} + +void Rend_ProjectLumobjs(int flags, BspLeaf *bspLeaf, float blendFactor, + Vector3d const &topLeft, Vector3d const &bottomRight, Matrix3f const &tangentMatrix, + uint &listIdx) +{ + DENG_ASSERT(bspLeaf != 0); + + if(blendFactor < OMNILIGHT_SURFACE_LUMINOSITY_ATTRIBUTION_MIN) + return; + + project_params_t parm; zap(parm); + parm.listIdx = &listIdx; + parm.flags = flags; + parm.blendFactor = blendFactor; + parm.topLeft = &topLeft; + parm.bottomRight = &bottomRight; + parm.tangentMatrix = &tangentMatrix; + + R_IterateBspLeafContacts(*bspLeaf, OT_LUMOBJ, projectLumobjWorker, &parm); +} + +/** + * Project a plane glow onto the surface. + * + * @param surface Glowing plane to project the light from. + * @param origin Origin of the glow in the map coordinate space. + * @param parm project_params_t parameters. + */ +static void projectGlow(Surface &surface, Vector3d const &origin, + project_params_t &parm) +{ + // Is the material glowing at this moment? + Vector3f color; + float intensity = surface.glow(color); + if(intensity < .05f) + return; + + coord_t glowHeight = Rend_PlaneGlowHeight(intensity); + if(glowHeight < 2) + return; // Not too small! + + // Calculate coords. + float bottom, top; + if(surface.normal().z < 0) + { + // Cast downward. + bottom = (origin.z - parm.topLeft->z) / glowHeight; + top = bottom + (parm.topLeft->z - parm.bottomRight->z) / glowHeight; + } + else + { + // Cast upward. + top = (parm.bottomRight->z - origin.z) / glowHeight; + bottom = top + (parm.topLeft->z - parm.bottomRight->z) / glowHeight; + } + + // Above/below on the Z axis? + if(!(bottom <= 1 || top >= 0)) + return; + + // Write to the projection list. + newProjection(parm.listIdx, (parm.flags & PLF_SORT_LUMINOSITY_DESC) != 0, + GL_PrepareLSTexture(LST_GRADIENT), + Vector2f(0, bottom), Vector2f(1, top), + Rend_LuminousColor(color, intensity), parm.blendFactor); +} + +void Rend_ProjectPlaneGlows(int flags, BspLeaf *bspLeaf, float blendFactor, + Vector3d const &topLeft, Vector3d const &bottomRight, Matrix3f const &tangentMatrix, + uint &listIdx) +{ + DENG_ASSERT(bspLeaf != 0); + + if(bottomRight.z >= topLeft.z) + return; + + if(blendFactor < OMNILIGHT_SURFACE_LUMINOSITY_ATTRIBUTION_MIN) + return; + + project_params_t parm; zap(parm); + parm.listIdx = &listIdx; + parm.flags = flags; + parm.blendFactor = blendFactor; + parm.topLeft = &topLeft; + parm.bottomRight = &bottomRight; + parm.tangentMatrix = &tangentMatrix; + + for(int i = 0; i < bspLeaf->sector().planeCount(); ++i) + { + Plane &plane = bspLeaf->visPlane(i); + Vector3d pointOnPlane(bspLeaf->cluster().center(), plane.visHeight()); + + projectGlow(plane.surface(), pointOnPlane, parm); + } +} + +int Rend_IterateProjectionList(uint listIdx, int (*callback) (dynlight_t const *, void *), + void *context) +{ + if(callback && listIdx != 0 && listIdx <= listCount) + { + ListNode *node = lists[listIdx - 1].head; + while(node) + { + if(int result = callback(&node->projection, context)) + return result; + node = node->next; + } + } + return 0; // Continue iteration. +} diff --git a/doomsday/client/src/render/r_shadow.cpp b/doomsday/client/src/render/r_shadow.cpp index 3b42ff272b..a49ba72c9a 100644 --- a/doomsday/client/src/render/r_shadow.cpp +++ b/doomsday/client/src/render/r_shadow.cpp @@ -324,27 +324,23 @@ void R_InitShadowProjectionListsForNewFrame() } } -uint R_ProjectShadowsToSurface(BspLeaf *bspLeaf, float blendFactor, +void Rend_ProjectMobjShadows(BspLeaf *bspLeaf, float blendFactor, Vector3d const &topLeft, Vector3d const &bottomRight, - Matrix3f const &tangentMatrix) + Matrix3f const &tangentMatrix, uint &listIdx) { DENG_ASSERT(bspLeaf != 0); - // Early test of the external blend factor for quick rejection. - if(blendFactor < SHADOW_SURFACE_LUMINOSITY_ATTRIBUTION_MIN) return 0; - - projectshadowonsurfaceiteratorparams_t p; - - p.listIdx = 0; - p.spParams.blendFactor = blendFactor; - p.spParams.v1 = topLeft; - p.spParams.v2 = bottomRight; - p.spParams.tangentMatrix = tangentMatrix; + if(blendFactor < SHADOW_SURFACE_LUMINOSITY_ATTRIBUTION_MIN) + return; - R_IterateBspLeafContacts(*bspLeaf, OT_MOBJ, RIT_ProjectShadowToSurfaceIterator, (void *)&p); + projectshadowonsurfaceiteratorparams_t parm; zap(parm); + parm.listIdx = listIdx; + parm.spParams.blendFactor = blendFactor; + parm.spParams.v1 = topLeft; + parm.spParams.v2 = bottomRight; + parm.spParams.tangentMatrix = tangentMatrix; - // Did we produce a projection list? - return p.listIdx; + R_IterateBspLeafContacts(*bspLeaf, OT_MOBJ, RIT_ProjectShadowToSurfaceIterator, &parm); } int R_IterateShadowProjections(uint listIdx, int (*callback) (shadowprojection_t const *, void *), diff --git a/doomsday/client/src/render/rend_main.cpp b/doomsday/client/src/render/rend_main.cpp index eec9bfccc3..7158bebc20 100644 --- a/doomsday/client/src/render/rend_main.cpp +++ b/doomsday/client/src/render/rend_main.cpp @@ -104,7 +104,7 @@ int useDynLights = true; float dynlightFactor = .5f; float dynlightFogBright = .15f; -int useWallGlow = true; +int useGlowOnWalls = true; float glowFactor = .5f; float glowHeightFactor = 3; // Glow height as a multiplier. int glowHeightMax = 100; // 100 is the default (0-1024). @@ -159,6 +159,8 @@ byte rendLightWallAngleSmooth = true; float rendSkyLight = .2f; // Intensity factor. byte rendSkyLightAuto = true; +int rendMaxLumobjs; ///< Max lumobjs per viewer, per frame. @c 0= no maximum. + int extraLight; // Bumped light from gun blasts. float extraLightDelta; @@ -181,9 +183,13 @@ byte devNoTexFix; byte devSectorIndices; ///< @c 1= Draw map sector indicies. +byte rendInfoLums; ///< @c 1= Print lumobj debug info to the console. +byte devDrawLums; ///< @c 1= Draw lumobj origin debug display. + static void Rend_DrawBoundingBoxes(Map &map); static void Rend_DrawSoundOrigins(Map &map); static void drawAllSurfaceTangentVectors(Map &map); +static void drawLumobjs(Map &map); static void drawSectors(Map &map); static void drawVertexes(Map &map); @@ -208,6 +214,22 @@ static void markLightGridForFullUpdate() } } +static int unlinkMobjLumobjWorker(thinker_t *th, void *) +{ + Mobj_UnlinkLumobjs(reinterpret_cast(th)); + return false; // Continue iteration. +} + +static void unlinkMobjLumobjs() +{ + if(App_World().hasMap()) + { + Map &map = App_World().map(); + map.thinkers().iterate(reinterpret_cast(gx.MobjThinker), 0x1, + unlinkMobjLumobjWorker); + } +} + void Rend_Register() { C_VAR_INT ("rend-bias", &useBias, 0, 0, 1); @@ -216,14 +238,17 @@ void Rend_Register() C_VAR_FLOAT ("rend-glow", &glowFactor, 0, 0, 2); C_VAR_INT ("rend-glow-height", &glowHeightMax, 0, 0, 1024); C_VAR_FLOAT ("rend-glow-scale", &glowHeightFactor, 0, 0.1f, 10); - C_VAR_INT ("rend-glow-wall", &useWallGlow, 0, 0, 1); + C_VAR_INT ("rend-glow-wall", &useGlowOnWalls, 0, 0, 1); - C_VAR_INT2 ("rend-light", &useDynLights, 0, 0, 1, LO_UnlinkMobjLumobjs); + C_VAR_BYTE ("rend-info-lums", &rendInfoLums, 0, 0, 1); + + C_VAR_INT2 ("rend-light", &useDynLights, 0, 0, 1, unlinkMobjLumobjs); C_VAR_INT2 ("rend-light-ambient", &ambientLight, 0, 0, 255, Rend_UpdateLightModMatrix); C_VAR_FLOAT ("rend-light-attenuation", &rendLightDistanceAttenuation, CVF_NO_MAX, 0, 0); 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_INT ("rend-light-num", &rendMaxLumobjs, CVF_NO_MAX, 0, 0); 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); @@ -242,7 +267,8 @@ void Rend_Register() C_VAR_INT ("rend-dev-sky", &devRendSkyMode, CVF_NO_ARCHIVE, 0, 1); C_VAR_BYTE ("rend-dev-sky-always", &devRendSkyAlways, CVF_NO_ARCHIVE, 0, 1); C_VAR_BYTE ("rend-dev-freeze", &freezeRLs, CVF_NO_ARCHIVE, 0, 1); - C_VAR_INT ("rend-dev-cull-leafs", &devNoCulling, CVF_NO_ARCHIVE,0,1); + C_VAR_BYTE ("rend-dev-lums", &devDrawLums, CVF_NO_ARCHIVE, 0, 1); + C_VAR_INT ("rend-dev-cull-leafs", &devNoCulling, CVF_NO_ARCHIVE, 0, 1); C_VAR_INT ("rend-dev-mobj-bbox", &devMobjBBox, CVF_NO_ARCHIVE, 0, 1); C_VAR_BYTE ("rend-dev-mobj-show-vlights", &devMobjVLights, CVF_NO_ARCHIVE, 0, 1); C_VAR_INT ("rend-dev-polyobj-bbox", &devPolyobjBBox, CVF_NO_ARCHIVE, 0, 1); @@ -257,7 +283,7 @@ void Rend_Register() C_VAR_BYTE ("rend-dev-soundorigins", &devSoundOrigins, CVF_NO_ARCHIVE, 0, 7); RL_Register(); - LO_Register(); + Lumobj::consoleRegister(); Rend_DecorRegister(); BiasIllum::consoleRegister(); BiasSurface::consoleRegister(); @@ -315,7 +341,11 @@ void Rend_Shutdown() /// World/map renderer reset. void Rend_Reset() { - LO_Clear(); // Free lumobj stuff. + R_ClearViewData(); + if(App_World().hasMap()) + { + App_World().map().removeAllLumobjs(); + } if(dlBBox) { GL_DeleteLists(dlBBox, 1); @@ -476,6 +506,22 @@ Vector3f const &Rend_SectorLightColor(Sector const §or) return sector.lightColor(); } +Vector3f Rend_LuminousColor(Vector3f const &color, float light) +{ + light = de::clamp(0.f, light, 1.f) * dynlightFactor; + + // In fog additive blending is used; the normal fog color is way too bright. + if(usingFog) light *= dynlightFogBright; + + // Multiply light with (ambient) color. + return color * light; +} + +coord_t Rend_PlaneGlowHeight(float intensity) +{ + return de::clamp(0, GLOW_HEIGHT_MAX * intensity * glowHeightFactor, glowHeightMax); +} + Material *Rend_ChooseMapSurfaceMaterial(Surface const &surface) { switch(renderTextures) @@ -630,13 +676,13 @@ void Rend_AddMaskedPoly(Vector3f const *rvertices, Vector4f const *rcolors, * The dynlights will have already been sorted so that the brightest * and largest of them is first in the list. So grab that one. */ - LO_IterateProjections(lightListIdx, RIT_FirstDynlightIterator, (void *)&dyn); + Rend_IterateProjectionList(lightListIdx, RIT_FirstDynlightIterator, (void *)&dyn); VS_WALL(vis)->modTex = dyn->texture; - VS_WALL(vis)->modTexCoord[0][0] = dyn->s[0]; - VS_WALL(vis)->modTexCoord[0][1] = dyn->t[0]; - VS_WALL(vis)->modTexCoord[1][0] = dyn->s[1]; - VS_WALL(vis)->modTexCoord[1][1] = dyn->t[1]; + VS_WALL(vis)->modTexCoord[0][0] = dyn->topLeft.x; + VS_WALL(vis)->modTexCoord[0][1] = dyn->topLeft.y; + VS_WALL(vis)->modTexCoord[1][0] = dyn->bottomRight.x; + VS_WALL(vis)->modTexCoord[1][1] = dyn->bottomRight.y; for(int c = 0; c < 4; ++c) { VS_WALL(vis)->modColor[c] = dyn->color[c]; @@ -658,12 +704,12 @@ static void quadTexCoords(Vector2f *tc, Vector3f const *rverts, tc[0].y = tc[3].y + (rverts[3].z - rverts[2].z); } -static void quadLightCoords(Vector2f *tc, float const s[2], float const t[2]) +static void quadLightCoords(Vector2f *tc, Vector2f const &topLeft, Vector2f const &bottomRight) { - tc[1].x = tc[0].x = s[0]; - tc[1].y = tc[3].y = t[0]; - tc[3].x = tc[2].x = s[1]; - tc[2].y = tc[0].y = t[1]; + tc[1].x = tc[0].x = topLeft.x; + tc[1].y = tc[3].y = topLeft.y; + tc[3].x = tc[2].x = bottomRight.x; + tc[2].y = tc[0].y = bottomRight.y; } static float shinyVertical(float dy, float dx) @@ -809,7 +855,7 @@ static bool renderWorldPoly(Vector3f *posCoords, uint numVertices, Vector2f *modCoords = 0; DGLuint modTex = 0; - float modTexSt[2][2] = {{ 0, 0 }, { 0, 0 }}; + Vector2f modTexSt[2]; // [topLeft, bottomRight] Vector4f modColor; if(!skyMaskedMaterial) @@ -837,15 +883,13 @@ static bool renderWorldPoly(Vector3f *posCoords, uint numVertices, if(useLights && RL_IsMTexLights()) { dynlight_t *dyn = 0; - LO_IterateProjections(p.lightListIdx, RIT_FirstDynlightIterator, (void *)&dyn); - - modTex = dyn->texture; - modCoords = R_AllocRendTexCoords(realNumVertices); - modColor = dyn->color; - modTexSt[0][0] = dyn->s[0]; - modTexSt[0][1] = dyn->s[1]; - modTexSt[1][0] = dyn->t[0]; - modTexSt[1][1] = dyn->t[1]; + Rend_IterateProjectionList(p.lightListIdx, RIT_FirstDynlightIterator, (void *)&dyn); + + modTex = dyn->texture; + modCoords = R_AllocRendTexCoords(realNumVertices); + modColor = dyn->color; + modTexSt[0] = dyn->topLeft; + modTexSt[1] = dyn->bottomRight; } } } @@ -898,8 +942,8 @@ static bool renderWorldPoly(Vector3f *posCoords, uint numVertices, float const width = p.texBR->x - p.texTL->x; float const height = p.texBR->y - p.texTL->y; - modCoords[i] = Vector2f(((p.texBR->x - vtx.x) / width * modTexSt[0][0]) + (delta.x / width * modTexSt[0][1]), - ((p.texBR->y - vtx.y) / height * modTexSt[1][0]) + (delta.y / height * modTexSt[1][1])); + modCoords[i] = Vector2f(((p.texBR->x - vtx.x) / width * modTexSt[0].x) + (delta.x / width * modTexSt[1].x), + ((p.texBR->y - vtx.y) / height * modTexSt[0].y) + (delta.y / height * modTexSt[1].y)); } } } @@ -1329,72 +1373,10 @@ static void wallSectionLightLevelDeltas(WallEdge const &leftEdge, WallEdge const } } -/** - * Project lights for the given map @a surface. - * - * @pre currentBspLeaf is set to the BSP leaf which "contains" the surface. - * - * @param surface Map surface to project lights onto. - * @param glowStrength Surface glow strength (glow scale factor(s) are assumed to - * have already been applied). - * @param topLeft Top left coordinates for the conceptual quad used for projection. - * @param bottomRight Bottom right coordinates for the conceptual quad used for projection. - * @param sortProjections @c true= instruct the projection algorithm to sort the resultant - * projections by descending luminosity. Default = @c false. - * - * @return Identifier for the resultant light projection list; otherwise @c 0. - * - * @see LO_ProjectToSurface() - */ -static uint projectSurfaceLights(Surface &surface, float glowStrength, - Vector3d const &topLeft, Vector3d const &bottomRight, bool sortProjections = false) -{ - BspLeaf *leaf = currentBspLeaf; - DENG_ASSERT(!isNullLeaf(leaf)); - - // Is light projection disabled? - if(glowStrength >= 1) return 0; - if(!useDynLights && !useWallGlow) return 0; - - return LO_ProjectToSurface(0 | (sortProjections? PLF_SORT_LUMINOSITY_DESC : 0), leaf, - 1, topLeft, bottomRight, surface.tangentMatrix()); -} - -/** - * Project shadows for the given map @a surface. - * - * @pre currentBspLeaf is set to the BSP leaf which "contains" the surface. - * - * @param surface Map surface to project shadows onto. - * @param glowStrength Surface glow strength (glow scale factor(s) are assumed to - * have already been applied). - * @param topLeft Top left coordinates for the conceptual quad used for projection. - * @param bottomRight Bottom right coordinates for the conceptual quad used for projection. - * - * @return Identifier for the resultant shadow projection list; otherwise @c 0. - * - * @see R_ProjectShadowsToSurface() - */ -static uint projectSurfaceShadows(Surface &surface, float glowStrength, - Vector3d const &topLeft, Vector3d const &bottomRight) -{ - BspLeaf *leaf = currentBspLeaf; - DENG_ASSERT(!isNullLeaf(leaf)); - - // Is shadow projection disabled? - if(glowStrength >= 1) return 0; - if(!Rend_MobjShadowsEnabled()) return 0; - - // Glow inversely diminishes shadow strength. - float const shadowStrength = 1 - glowStrength; - - return R_ProjectShadowsToSurface(leaf, shadowStrength, topLeft, bottomRight, - surface.tangentMatrix()); -} - static void writeWallSection(HEdge &hedge, int section, bool *retWroteOpaque = 0, coord_t *retBottomZ = 0, coord_t *retTopZ = 0) { + BspLeaf *leaf = currentBspLeaf; DENG_ASSERT(!isNullLeaf(currentBspLeaf)); DENG_ASSERT(hedge.mapElement() != 0); @@ -1510,19 +1492,45 @@ static void writeWallSection(HEdge &hedge, int section, side.chooseSurfaceTintColors(wallSpec.section, &parm.surfaceColor, &parm.wall.surfaceColor2); } - // Project dynamic Lights? - if(!wallSpec.flags.testFlag(WallSpec::NoDynLights) && !skyMasked) + if(!skyMasked && parm.glowing < 1) { - parm.lightListIdx = - projectSurfaceLights(surface, parm.glowing, texQuad[0], texQuad[1], - wallSpec.flags.testFlag(WallSpec::SortDynLights)); - } + Vector3d const &topLeft = texQuad[0]; + Vector3d const &bottomRight = texQuad[1]; - // Project dynamic shadows? - if(!wallSpec.flags.testFlag(WallSpec::NoDynShadows) && !skyMasked) - { - parm.shadowListIdx = - projectSurfaceShadows(surface, parm.glowing, texQuad[0], texQuad[1]); + // Project lights? + if(!wallSpec.flags.testFlag(WallSpec::NoDynLights)) + { + bool const doSort = wallSpec.flags.testFlag(WallSpec::SortDynLights); + float const blendFactor = 1; + + if(useDynLights) + { + Rend_ProjectLumobjs(0 | (doSort? PLF_SORT_LUMINOSITY_DESC : 0), + leaf, blendFactor, + topLeft, bottomRight, surface.tangentMatrix(), + parm.lightListIdx); + } + + if(useGlowOnWalls) + { + Rend_ProjectPlaneGlows(0 | (doSort? PLF_SORT_LUMINOSITY_DESC : 0), + leaf, blendFactor, + topLeft, bottomRight, surface.tangentMatrix(), + parm.lightListIdx); + } + } + + // Project dynamic shadows? + if(!wallSpec.flags.testFlag(WallSpec::NoDynShadows) + && Rend_MobjShadowsEnabled()) + { + // Glow inversely diminishes shadow strength. + float const blendFactor = 1 - parm.glowing; + + Rend_ProjectMobjShadows(leaf, blendFactor, + topLeft, bottomRight, surface.tangentMatrix(), + parm.shadowListIdx); + } } /* @@ -1759,26 +1767,33 @@ static void writeLeafPlane(Plane &plane) parm.glowing *= glowFactor; // Global scale factor. } - // Dynamic lights? - if(parm.glowing < 1 && !(!useDynLights && !useWallGlow)) + if(parm.glowing < 1) { - /// @ref projectLightFlags - int plFlags = (PLF_NO_PLANE | (plane.isSectorFloor()? PLF_TEX_FLOOR : PLF_TEX_CEILING)); + Vector3d const &topLeft = *parm.texTL; + Vector3d const &bottomRight = *parm.texBR; - parm.lightListIdx = - LO_ProjectToSurface(plFlags, leaf, 1, *parm.texTL, *parm.texBR, - surface.tangentMatrix()); - } + // Dynamic lights? + if(useDynLights) + { + /// @ref projectLightFlags + float const blendFactor = 1; - // Mobj shadows? - if(plane.isSectorFloor() && parm.glowing < 1 && Rend_MobjShadowsEnabled()) - { - // Glowing planes inversely diminish shadow strength. - float blendFactor = 1 - parm.glowing; + Rend_ProjectLumobjs(0 | (plane.isSectorFloor()? PLF_TEX_FLOOR : PLF_TEX_CEILING), + leaf, blendFactor, + topLeft, bottomRight, surface.tangentMatrix(), + parm.lightListIdx); + } + + // Mobj shadows? + if(plane.isSectorFloor() && Rend_MobjShadowsEnabled()) + { + // Glow inversely diminishes shadow strength. + float const blendFactor = 1 - parm.glowing; - parm.shadowListIdx = - R_ProjectShadowsToSurface(leaf, blendFactor, *parm.texTL, *parm.texBR, - surface.tangentMatrix()); + Rend_ProjectMobjShadows(leaf, blendFactor, topLeft, bottomRight, + surface.tangentMatrix(), + parm.shadowListIdx); + } } } @@ -2316,6 +2331,33 @@ static void occludeLeaf(bool frontFacing) } while((hedge = &hedge->next()) != base); } +static void clipLeafLumobjs() +{ + BspLeaf *bspLeaf = currentBspLeaf; + DENG_ASSERT(!isNullLeaf(bspLeaf)); + + foreach(Lumobj *lum, bspLeaf->lumobjs()) + { + R_ViewerClipLumobj(lum); + } +} + +/** + * In the situation where a BSP leaf contains both lumobjs and a polyobj, lumobjs + * must be clipped more carefully. Here we check if the line of sight intersects any + * of the polyobj half-edges facing the viewer. + */ +static void clipLeafLumobjsBySight() +{ + BspLeaf *bspLeaf = currentBspLeaf; + DENG_ASSERT(!isNullLeaf(bspLeaf)); + + foreach(Lumobj *lum, bspLeaf->lumobjs()) + { + R_ViewerClipLumobjBySight(lum, bspLeaf); + } +} + /// If not front facing this is no-op. static void clipFrontFacingWalls(HEdge *hedge) { @@ -2432,7 +2474,7 @@ static void drawCurrentLeaf() */ occludeLeaf(false /* back facing */); - LO_ClipInBspLeaf(*leaf); + clipLeafLumobjs(); occludeLeaf(true /* front facing */); clipLeafFrontFacingWalls(); @@ -2440,7 +2482,7 @@ static void drawCurrentLeaf() if(leaf->polyobjCount()) { // Polyobjs don't obstruct - clip lights with another algorithm. - LO_ClipInBspLeafBySight(*leaf); + clipLeafLumobjsBySight(); } // Mark particle generators in the sector visible. @@ -2552,7 +2594,7 @@ void Rend_RenderMap(Map &map) // Make vissprites of all the visible decorations. Rend_DecorProject(); - LO_BeginFrame(); + R_BeginFrame(); // Clear particle generator visibilty info. Rend_ParticleInitForNewFrame(); @@ -2593,7 +2635,7 @@ void Rend_RenderMap(Map &map) // Draw various debugging displays: drawAllSurfaceTangentVectors(map); - LO_DrawLumobjs(); // Lumobjs. + drawLumobjs(map); Rend_DrawBoundingBoxes(map); // Mobj bounding boxes. drawSectors(map); drawVertexes(map); @@ -3353,6 +3395,63 @@ static void drawAllSurfaceTangentVectors(Map &map) glEnable(GL_CULL_FACE); } +static void drawLumobjs(Map &map) +{ + DENG_UNUSED(map); + + static float const black[4] = { 0, 0, 0, 0 }; + + if(!devDrawLums) return; + + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + + for(int i = 0; i < map.lumobjCount(); ++i) + { + Lumobj *lum = map.lumobj(i); + + if(rendMaxLumobjs > 0 && R_ViewerLumobjIsHidden(i)) + continue; + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + glTranslated(lum->origin().x, lum->origin().z + lum->zOffset(), lum->origin().y); + + glBegin(GL_LINES); + { + glColor4fv(black); + glVertex3f(-lum->radius(), 0, 0); + glColor4f(lum->color().x, lum->color().y, lum->color().z, 1); + glVertex3f(0, 0, 0); + glVertex3f(0, 0, 0); + glColor4fv(black); + glVertex3f(lum->radius(), 0, 0); + + glVertex3f(0, -lum->radius(), 0); + glColor4f(lum->color().x, lum->color().y, lum->color().z, 1); + glVertex3f(0, 0, 0); + glVertex3f(0, 0, 0); + glColor4fv(black); + glVertex3f(0, lum->radius(), 0); + + glVertex3f(0, 0, -lum->radius()); + glColor4f(lum->color().x, lum->color().y, lum->color().z, 1); + glVertex3f(0, 0, 0); + glVertex3f(0, 0, 0); + glColor4fv(black); + glVertex3f(0, 0, lum->radius()); + } + glEnd(); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } + + glEnable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); +} + static void drawSoundOrigin(Vector3d const &origin, char const *label, Vector3d const &eye) { coord_t const MAX_SOUNDORIGIN_DIST = 384; ///< Maximum distance from origin to eye in map coordinates.