From d0504baff80e6a8f30af25659987c68c8ba8e185 Mon Sep 17 00:00:00 2001 From: danij Date: Mon, 26 Nov 2012 05:54:39 +0000 Subject: [PATCH] Refactor: Merged render/r_sky.c/h render/rend_sky.c/h and switched to C++ --- doomsday/engine/engine.pro | 6 +- doomsday/engine/include/de_render.h | 3 +- doomsday/engine/include/render/r_sky.h | 145 --- doomsday/engine/include/render/rend_sky.h | 47 - doomsday/engine/include/render/sky.h | 136 +++ doomsday/engine/src/def_data.c | 2 +- doomsday/engine/src/gl/gl_main.c | 2 +- doomsday/engine/src/map/dam_main.cpp | 2 +- doomsday/engine/src/map/p_ticker.c | 4 +- doomsday/engine/src/map/r_world.c | 2 +- doomsday/engine/src/render/r_main.c | 3 +- doomsday/engine/src/render/r_sky.c | 580 ------------ doomsday/engine/src/render/rend_list.c | 2 +- doomsday/engine/src/render/rend_main.c | 4 +- doomsday/engine/src/render/rend_sky.c | 519 ---------- doomsday/engine/src/render/sky.cpp | 1046 +++++++++++++++++++++ 16 files changed, 1195 insertions(+), 1308 deletions(-) delete mode 100644 doomsday/engine/include/render/r_sky.h delete mode 100644 doomsday/engine/include/render/rend_sky.h create mode 100644 doomsday/engine/include/render/sky.h delete mode 100644 doomsday/engine/src/render/r_sky.c delete mode 100644 doomsday/engine/src/render/rend_sky.c create mode 100644 doomsday/engine/src/render/sky.cpp diff --git a/doomsday/engine/engine.pro b/doomsday/engine/engine.pro index 6521441e5a..5afa1a9905 100644 --- a/doomsday/engine/engine.pro +++ b/doomsday/engine/engine.pro @@ -272,7 +272,6 @@ DENG_HEADERS += \ include/render/r_lgrid.h \ include/render/r_lumobjs.h \ include/render/r_shadow.h \ - include/render/r_sky.h \ include/render/r_things.h \ include/render/rend_bias.h \ include/render/rend_clip.h \ @@ -287,9 +286,9 @@ DENG_HEADERS += \ include/render/rend_model.h \ include/render/rend_particle.h \ include/render/rend_shadow.h \ - include/render/rend_sky.h \ include/render/rend_sprite.h \ include/render/rendpoly.h \ + include/render/sky.h \ include/render/vignette.h \ include/render/vlight.h \ include/resource/animgroups.h \ @@ -559,7 +558,6 @@ SOURCES += \ src/render/r_lgrid.c \ src/render/r_lumobjs.c \ src/render/r_shadow.c \ - src/render/r_sky.c \ src/render/r_things.c \ src/render/rend_bias.c \ src/render/rend_clip.cpp \ @@ -574,9 +572,9 @@ SOURCES += \ src/render/rend_model.c \ src/render/rend_particle.c \ src/render/rend_shadow.c \ - src/render/rend_sky.c \ src/render/rend_sprite.c \ src/render/rendpoly.cpp \ + src/render/sky.cpp \ src/render/vignette.c \ src/render/vlight.cpp \ src/resource/animgroups.cpp \ diff --git a/doomsday/engine/include/de_render.h b/doomsday/engine/include/de_render.h index 307c952cc8..c9cde935aa 100644 --- a/doomsday/engine/include/de_render.h +++ b/doomsday/engine/include/de_render.h @@ -27,14 +27,12 @@ #include "render/r_lgrid.h" #include "render/r_lumobjs.h" #include "render/r_shadow.h" -#include "render/r_sky.h" #include "render/r_things.h" #include "render/rend_main.h" #include "render/rend_clip.h" #include "render/rend_halo.h" #include "render/rend_list.h" #include "render/rend_particle.h" -#include "render/rend_sky.h" #include "render/rend_sprite.h" #include "render/rend_model.h" #include "render/rend_shadow.h" @@ -45,6 +43,7 @@ #include "render/rend_console.h" #include "render/rend_dynlight.h" #include "render/rendpoly.h" +#include "render/sky.h" #include "render/vignette.h" #include "render/vlight.h" diff --git a/doomsday/engine/include/render/r_sky.h b/doomsday/engine/include/render/r_sky.h deleted file mode 100644 index a94dad7942..0000000000 --- a/doomsday/engine/include/render/r_sky.h +++ /dev/null @@ -1,145 +0,0 @@ -/**\file r_sky.h - *\section License - * License: GPL - * Online License Link: http://www.gnu.org/licenses/gpl.html - * - *\author Copyright © 2003-2012 Jaakko Keränen - *\author Copyright © 2006-2012 Daniel Swanson - * - * 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, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301 USA - */ - -/** - * Sky Management. - */ - -#ifndef LIBDENG_REFRESH_SKY_H -#define LIBDENG_REFRESH_SKY_H - -#include "color.h" -#include "resource/models.h" - -#define MAX_SKY_LAYERS ( 2 ) -#define MAX_SKY_MODELS ( 32 ) - -#define DEFAULT_SKY_HEIGHT ( .666667f ) -#define DEFAULT_SKY_HORIZON_OFFSET ( 0 ) -#define DEFAULT_SKY_SPHERE_XOFFSET ( 0 ) -#define DEFAULT_SKY_SPHERE_MATERIAL ( "Textures:SKY1" ) -#define DEFAULT_SKY_SPHERE_FADEOUT_LIMIT ( .3f ) - -typedef struct skymodel_s { - ded_skymodel_t* def; - modeldef_t* model; - int frame; - int timer; - int maxTimer; - float yaw; -} skymodel_t; - -DENG_EXTERN_C boolean alwaysDrawSphere; -DENG_EXTERN_C boolean skyModelsInited; -DENG_EXTERN_C skymodel_t skyModels[MAX_SKY_MODELS]; - -#ifdef __cplusplus -extern "C" { -#endif - -/// Initialize this module. -void R_SkyInit(void); - -/** - * Precache all resources necessary for rendering the current sky. - */ -void R_SkyPrecache(void); - -/** - * Animate the sky. - */ -void R_SkyTicker(void); - -/** - * Configure the sky based on the specified definition if not @c NULL, - * otherwise, setup using suitable defaults. - */ -void R_SetupSky(ded_sky_t* sky); - -/// @return Unique identifier of the current Sky's first active layer. -int R_SkyFirstActiveLayer(void); - -/// @return Current ambient sky color. -const ColorRawf* R_SkyAmbientColor(void); - -/// @return Horizon offset for the current Sky. -float R_SkyHorizonOffset(void); - -/// @return Height of the current Sky as a factor [0...1] where @c 1 covers the entire view. -float R_SkyHeight(void); - -/// @return @c true if the identified @a layerId of the current Sky is active. -boolean R_SkyLayerActive(int layerId); - -/// @return Fadeout limit for the identified @a layerId of the current Sky. -float R_SkyLayerFadeoutLimit(int layerId); - -/// @return @c true if the identified @a layerId for the current Sky is masked. -boolean R_SkyLayerMasked(int layerId); - -/// @return Material assigned to the identified @a layerId of the current Sky. -material_t* R_SkyLayerMaterial(int layerId); - -/// @return Horizontal offset for the identified @a layerId of the current Sky. -float R_SkyLayerOffset(int layerId); - -/** - * Change the 'active' state for the identified @a layerId of the current Sky. - * \post Sky light color is marked for update (deferred). - */ -void R_SkyLayerSetActive(int layerId, boolean yes); - -/** - * Change the 'masked' state for the identified @a layerId of the current Sky. - * \post Sky light color and layer Material are marked for update (deferred). - */ -void R_SkyLayerSetMasked(int layerId, boolean yes); - -/** - * Change the fadeout limit for the identified @a layerId of the current Sky. - * \post Sky light color is marked for update (deferred). - */ -void R_SkyLayerSetFadeoutLimit(int layerId, float limit); - -/** - * Change the Material assigned to the identified @a layerId of the current Sky. - * \post Sky light color and layer Material are marked for update (deferred). - */ -void R_SkyLayerSetMaterial(int layerId, material_t* material); - -/** - * Change the horizontal offset for the identified @a layerId of the current Sky. - */ -void R_SkyLayerSetOffset(int layerId, float offset); - -/** - * Alternative interface for manipulating Sky (layer) properties by name/id. - */ -void R_SkyParams(int layer, int param, void* data); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif /* LIBDENG_REFRESH_SKY_H */ diff --git a/doomsday/engine/include/render/rend_sky.h b/doomsday/engine/include/render/rend_sky.h deleted file mode 100644 index 6130323362..0000000000 --- a/doomsday/engine/include/render/rend_sky.h +++ /dev/null @@ -1,47 +0,0 @@ -/**\file rend_sky.h - *\section License - * License: GPL - * Online License Link: http://www.gnu.org/licenses/gpl.html - * - *\author Copyright © 2003-2012 Jaakko Keränen - *\author Copyright © 2007-2012 Daniel Swanson - * - * 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, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301 USA - */ - -/** - * Sky Sphere and 3D Models. - * - * This version supports only two sky layers. - * (More would be a waste of resources?) - */ - -#ifndef LIBDENG_RENDER_SKY_H -#define LIBDENG_RENDER_SKY_H - -/// Register the console commands, variables, etc..., of this module. -void Rend_SkyRegister(void); - -/// Initialize this module. -void Rend_SkyInit(void); - -/// Shutdown this module. -void Rend_SkyShutdown(void); - -/// Render the current sky. -void Rend_RenderSky(void); - -#endif /* LIBDENG_RENDER_SKY_H */ diff --git a/doomsday/engine/include/render/sky.h b/doomsday/engine/include/render/sky.h new file mode 100644 index 0000000000..b488fd725d --- /dev/null +++ b/doomsday/engine/include/render/sky.h @@ -0,0 +1,136 @@ +/** + * @file sky.h Sky Sphere and 3D Models + * + * This version supports only two sky layers. (More would be a waste of resources?) + * + * @author Copyright © 2003-2012 Jaakko Keränen + * @author Copyright © 2007-2012 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, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef LIBDENG_RENDER_SKY_H +#define LIBDENG_RENDER_SKY_H + +#include "color.h" +#include "resource/models.h" + +#define MAX_SKY_LAYERS ( 2 ) +#define MAX_SKY_MODELS ( 32 ) + +#define DEFAULT_SKY_HEIGHT ( .666667f ) +#define DEFAULT_SKY_HORIZON_OFFSET ( 0 ) +#define DEFAULT_SKY_SPHERE_XOFFSET ( 0 ) +#define DEFAULT_SKY_SPHERE_MATERIAL ( "Textures:SKY1" ) +#define DEFAULT_SKY_SPHERE_FADEOUT_LIMIT ( .3f ) + +#ifdef __cplusplus +extern "C" { +#endif + +/// Register the console commands, variables, etc..., of this module. +void Sky_Register(void); + +/// Initialize this module. +void Sky_Init(void); + +/// Shutdown this module. +void Sky_Shutdown(void); + +/** + * Cache all resources necessary for rendering the current sky. + */ +void Sky_Cache(void); + +/** + * Animate the sky. + */ +void Sky_Ticker(void); + +/** + * Configure the sky based on the specified definition if not @c NULL, + * otherwise, setup using suitable defaults. + */ +void Sky_Configure(ded_sky_t *sky); + +/// @return Unique identifier of the current Sky's first active layer. +int Sky_FirstActiveLayer(void); + +/// @return Current ambient sky color. +ColorRawf const* Sky_AmbientColor(void); + +/// @return Horizon offset for the current Sky. +float Sky_HorizonOffset(void); + +/// @return Height of the current Sky as a factor [0...1] where @c 1 covers the entire view. +float Sky_Height(void); + +/// @return @c true if the identified @a layerId of the current Sky is active. +boolean Sky_LayerActive(int layerId); + +/// @return Fadeout limit for the identified @a layerId of the current Sky. +float Sky_LayerFadeoutLimit(int layerId); + +/// @return @c true if the identified @a layerId for the current Sky is masked. +boolean Sky_LayerMasked(int layerId); + +/// @return Material assigned to the identified @a layerId of the current Sky. +material_t *Sky_LayerMaterial(int layerId); + +/// @return Horizontal offset for the identified @a layerId of the current Sky. +float Sky_LayerOffset(int layerId); + +/** + * Change the 'active' state for the identified @a layerId of the current Sky. + * @post Sky light color is marked for update (deferred). + */ +void Sky_LayerSetActive(int layerId, boolean yes); + +/** + * Change the 'masked' state for the identified @a layerId of the current Sky. + * @post Sky light color and layer Material are marked for update (deferred). + */ +void Sky_LayerSetMasked(int layerId, boolean yes); + +/** + * Change the fadeout limit for the identified @a layerId of the current Sky. + * @post Sky light color is marked for update (deferred). + */ +void Sky_LayerSetFadeoutLimit(int layerId, float limit); + +/** + * Change the Material assigned to the identified @a layerId of the current Sky. + * @post Sky light color and layer Material are marked for update (deferred). + */ +void Sky_LayerSetMaterial(int layerId, material_t *material); + +/** + * Change the horizontal offset for the identified @a layerId of the current Sky. + */ +void Sky_LayerSetOffset(int layerId, float offset); + +/// Render the current sky. +void Sky_Render(void); + +/** + * Alternative interface for manipulating Sky (layer) properties by name/id. + */ +void R_SkyParams(int layer, int param, void *data); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* LIBDENG_RENDER_SKY_H */ diff --git a/doomsday/engine/src/def_data.c b/doomsday/engine/src/def_data.c index defa86adc2..5f96c12759 100644 --- a/doomsday/engine/src/def_data.c +++ b/doomsday/engine/src/def_data.c @@ -37,7 +37,7 @@ #include "de_misc.h" #include "de_graphics.h" -#include "render/r_sky.h" +#include "render/sky.h" #include "def_data.h" // Helper Routines ------------------------------------------------------- diff --git a/doomsday/engine/src/gl/gl_main.c b/doomsday/engine/src/gl/gl_main.c index ae1c29b827..ba3cbe9313 100644 --- a/doomsday/engine/src/gl/gl_main.c +++ b/doomsday/engine/src/gl/gl_main.c @@ -484,7 +484,7 @@ void GL_Shutdown(void) GL_ShutdownDeferredTask(); FR_Shutdown(); Rend_ModelShutdown(); - Rend_SkyShutdown(); + Sky_Shutdown(); Rend_Reset(); GL_ShutdownRefresh(); diff --git a/doomsday/engine/src/map/dam_main.cpp b/doomsday/engine/src/map/dam_main.cpp index 4f335505be..aa3ffbec83 100644 --- a/doomsday/engine/src/map/dam_main.cpp +++ b/doomsday/engine/src/map/dam_main.cpp @@ -333,7 +333,7 @@ boolean DAM_AttemptMapLoad(Uri const* _uri) skyDef = &mapInfo->sky; } - R_SetupSky(skyDef); + Sky_Configure(skyDef); // Setup accordingly. if(mapInfo) diff --git a/doomsday/engine/src/map/p_ticker.c b/doomsday/engine/src/map/p_ticker.c index 68142be7b1..b05413b9af 100644 --- a/doomsday/engine/src/map/p_ticker.c +++ b/doomsday/engine/src/map/p_ticker.c @@ -34,7 +34,7 @@ #include "de_play.h" #include "de_misc.h" -#include "render/r_sky.h" +#include "render/sky.h" // MACROS ------------------------------------------------------------------ @@ -124,7 +124,7 @@ void P_Ticker(timespan_t time) if(DD_IsSharpTick()) { - R_SkyTicker(); + Sky_Ticker(); // Check all mobjs (always public). GameMap_IterateThinkers(theMap, gx.MobjThinker, 0x1, P_MobjTicker, NULL); diff --git a/doomsday/engine/src/map/r_world.c b/doomsday/engine/src/map/r_world.c index 1c547d0b41..f4aff87313 100644 --- a/doomsday/engine/src/map/r_world.c +++ b/doomsday/engine/src/map/r_world.c @@ -2029,7 +2029,7 @@ const float* R_GetSectorLightColor(const Sector* sector) static float oldRendSkyLight = -1; if(rendSkyLight > .001f && R_SectorContainsSkySurfaces(sector)) { - const ColorRawf* ambientColor = R_SkyAmbientColor(); + const ColorRawf* ambientColor = Sky_AmbientColor(); if(rendSkyLight != oldRendSkyLight || !INRANGE_OF(ambientColor->red, oldSkyAmbientColor[CR], .001f) || !INRANGE_OF(ambientColor->green, oldSkyAmbientColor[CG], .001f) || diff --git a/doomsday/engine/src/render/r_main.c b/doomsday/engine/src/render/r_main.c index 31fd209276..14da209e9e 100644 --- a/doomsday/engine/src/render/r_main.c +++ b/doomsday/engine/src/render/r_main.c @@ -512,7 +512,6 @@ void R_Init(void) R_InitRawTexs(); R_InitSvgs(); R_InitViewWindow(); - R_SkyInit(); Rend_Init(); frameCount = 0; } @@ -1444,7 +1443,7 @@ void Rend_CacheForMap() } // Sky models usually have big skins. - R_SkyPrecache(); + Sky_Cache(); // Precache model skins? if(useModels && precacheSkins) diff --git a/doomsday/engine/src/render/r_sky.c b/doomsday/engine/src/render/r_sky.c deleted file mode 100644 index 4e12edb44f..0000000000 --- a/doomsday/engine/src/render/r_sky.c +++ /dev/null @@ -1,580 +0,0 @@ -/**\file r_sky.c - *\section License - * License: GPL - * Online License Link: http://www.gnu.org/licenses/gpl.html - * - *\author Copyright © 2003-2012 Jaakko Keränen - *\author Copyright © 2006-2012 Daniel Swanson - * - * 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, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301 USA - */ - -#include "de_base.h" -#include "de_console.h" -#include "de_graphics.h" - -#include "map/r_world.h" -#include "render/r_sky.h" -#include "def_data.h" -#include "client/cl_def.h" -#include "m_vector.h" -#include "resource/texture.h" -#include "resource/texturevariant.h" -#include "resource/materialvariant.h" - -/** - * @defgroup skyLayerFlags Sky Layer Flags - * @ingroup flags - * @{ - */ -#define SLF_ACTIVE 0x1 /// Layer is active and will be rendered. -#define SLF_MASKED 0x2 /// Mask this layer's texture. -/**@}*/ - -typedef struct { - int flags; - material_t* material; - float offset; - float fadeoutLimit; -} skylayer_t; - -#define VALID_SKY_LAYERID(val) ((val) > 0 && (val) <= MAX_SKY_LAYERS) - -static void R_SetupSkyModels(ded_sky_t* def); - -boolean alwaysDrawSphere; - -skylayer_t skyLayers[MAX_SKY_LAYERS]; - -skymodel_t skyModels[MAX_SKY_MODELS]; -boolean skyModelsInited; - -static int firstSkyLayer, activeSkyLayers; -static float horizonOffset; -static float height; - -static boolean skyAmbientColorDefined; /// @c true= pre-defined in a MapInfo def. -static boolean needUpdateSkyAmbientColor; /// @c true= update if not pre-defined. -static ColorRawf skyAmbientColor; - -static __inline skylayer_t* skyLayerById(int id) -{ - if(!VALID_SKY_LAYERID(id)) return NULL; - return skyLayers + (id-1); -} - -static void configureDefaultSky(void) -{ - int i; - - skyAmbientColorDefined = false; - needUpdateSkyAmbientColor = true; - V3f_Set(skyAmbientColor.rgb, 1.0f, 1.0f, 1.0f); - - for(i = 0; i < MAX_SKY_LAYERS; ++i) - { - skylayer_t* layer = &skyLayers[i]; - layer->flags = (i == 0? SLF_ACTIVE : 0); - layer->material = Materials_ToMaterial(Materials_ResolveUriCString(DEFAULT_SKY_SPHERE_MATERIAL)); - layer->offset = DEFAULT_SKY_SPHERE_XOFFSET; - layer->fadeoutLimit = DEFAULT_SKY_SPHERE_FADEOUT_LIMIT; - } - - height = DEFAULT_SKY_HEIGHT; - horizonOffset = DEFAULT_SKY_HORIZON_OFFSET; -} - -static void calculateSkyAmbientColor(void) -{ - ColorRawf avgMaterialColor = { 0, 0, 0, 0 }; - ColorRawf bottomCapColor = { 0, 0, 0, 0 }; - ColorRawf topCapColor = { 0, 0, 0, 0 }; - skylayer_t* slayer; - int i, avgCount; - - if(!needUpdateSkyAmbientColor) return; - needUpdateSkyAmbientColor = false; - - V3f_Set(skyAmbientColor.rgb, 1.0f, 1.0f, 1.0f); - if(skyModelsInited && !alwaysDrawSphere) return; - - /** - * \todo Re-implement me by rendering the sky to a low-quality cubemap - * and use that to obtain the lighting characteristics. - */ - avgCount = 0; - for(i = 0, slayer = &skyLayers[firstSkyLayer]; i < MAX_SKY_LAYERS; ++i, slayer++) - { - const materialvariantspecification_t* spec; - const materialsnapshot_t* ms; - - if(!(slayer->flags & SLF_ACTIVE) || !slayer->material) continue; - - /** - * \note Ensure that this specification matches that used when - * preparing the sky for render (see ./engine/src/rend_sky.c - * configureRenderHemisphereStateForLayer) else an unnecessary - * GL texture will be uploaded as a consequence of this call. - */ - spec = Materials_VariantSpecificationForContext(MC_SKYSPHERE, - TSF_NO_COMPRESSION | ((slayer->flags & SLF_MASKED)? TSF_ZEROMASK : 0), - 0, 0, 0, GL_REPEAT, GL_CLAMP_TO_EDGE, 0, -1, -1, false, true, false, false); - ms = Materials_Prepare(slayer->material, spec, false); - - if(MSU_texture(ms, MTU_PRIMARY)) - { - const averagecolor_analysis_t* avgColor = (const averagecolor_analysis_t*) - Texture_AnalysisDataPointer(MSU_texture(ms, MTU_PRIMARY), TA_COLOR); - if(!avgColor) - Con_Error("calculateSkyAmbientColor: Texture id:%u has no TA_COLOR analysis.", Textures_Id(MSU_texture(ms, MTU_PRIMARY))); - - if(i == firstSkyLayer) - { - const Texture* tex = MSU_texture(ms, MTU_PRIMARY); - const averagecolor_analysis_t* avgLineColor = (const averagecolor_analysis_t*) - Texture_AnalysisDataPointer(tex, TA_LINE_TOP_COLOR); - if(!avgLineColor) - Con_Error("calculateSkyAmbientColor: Texture id:%u has no TA_LINE_TOP_COLOR analysis.", Textures_Id(MSU_texture(ms, MTU_PRIMARY))); - V3f_Copy(topCapColor.rgb, avgLineColor->color.rgb); - - avgLineColor = (const averagecolor_analysis_t*) - Texture_AnalysisDataPointer(tex, TA_LINE_BOTTOM_COLOR); - if(!avgLineColor) - Con_Error("calculateSkyAmbientColor: Texture id:%u has no TA_LINE_BOTTOM_COLOR analysis.", Textures_Id(MSU_texture(ms, MTU_PRIMARY))); - V3f_Copy(bottomCapColor.rgb, avgLineColor->color.rgb); - } - - V3f_Sum(avgMaterialColor.rgb, avgMaterialColor.rgb, avgColor->color.rgb); - ++avgCount; - } - } - - if(avgCount != 0) - { - // The caps cover a large amount of the sky sphere, so factor it in too. - vec3f_t capSum; - V3f_Sum(capSum, topCapColor.rgb, bottomCapColor.rgb); - V3f_Sum(skyAmbientColor.rgb, avgMaterialColor.rgb, capSum); - avgCount += 2; // Each cap is another unit. - V3f_Scale(skyAmbientColor.rgb, 1.f / avgCount); - } -} - -void R_SkyInit(void) -{ - firstSkyLayer = 0; - activeSkyLayers = 0; - skyModelsInited = false; - alwaysDrawSphere = false; - needUpdateSkyAmbientColor = true; -} - -void R_SetupSky(ded_sky_t* def) -{ - int i; - - configureDefaultSky(); - if(!def) return; // Go with the defaults. - - height = def->height; - horizonOffset = def->horizonOffset; - - for(i = 1; i <= MAX_SKY_LAYERS; ++i) - { - ded_skylayer_t* sl = &def->layers[i-1]; - - if(!(sl->flags & SLF_ACTIVE)) - { - R_SkyLayerSetActive(i, false); - continue; - } - - R_SkyLayerSetActive(i, true); - R_SkyLayerSetMasked(i, (sl->flags & SLF_MASKED) != 0); - if(sl->material) - { - material_t* mat = Materials_ToMaterial( - Materials_ResolveUri2(sl->material, true/*quiet please*/)); - if(mat) - { - R_SkyLayerSetMaterial(i, mat); - } - else - { - AutoStr* path = Uri_ToString(sl->material); - Con_Message("Warning: Unknown material \"%s\" in sky def %i, using default.\n", Str_Text(path), i); - } - } - R_SkyLayerSetOffset(i, sl->offset); - R_SkyLayerSetFadeoutLimit(i, sl->colorLimit); - } - - if(def->color[CR] > 0 || def->color[CG] > 0 || def->color[CB] > 0) - { - skyAmbientColorDefined = true; - V3f_Set(skyAmbientColor.rgb, def->color[CR], def->color[CG], def->color[CB]); - } - - // Any sky models to setup? Models will override the normal sphere by default. - R_SetupSkyModels(def); -} - -/** - * The sky models are set up using the data in the definition. - */ -static void R_SetupSkyModels(ded_sky_t* def) -{ - ded_skymodel_t* modef; - skymodel_t* sm; - int i; - - // Clear the whole sky models data. - memset(skyModels, 0, sizeof(skyModels)); - - // Normally the sky sphere is not drawn if models are in use. - alwaysDrawSphere = (def->flags & SIF_DRAW_SPHERE) != 0; - - // The normal sphere is used if no models will be set up. - skyModelsInited = false; - - for(i = 0, modef = def->models, sm = skyModels; i < MAX_SKY_MODELS; - ++i, modef++, sm++) - { - // Is the model ID set? - sm->model = Models_Definition(modef->id); - if(!sm->model) continue; - - // There is a model here. - skyModelsInited = true; - - sm->def = modef; - sm->maxTimer = (int) (TICSPERSEC * modef->frameInterval); - sm->yaw = modef->yaw; - sm->frame = sm->model->sub[0].frame; - } -} - -/** - * Prepare all sky model skins. - */ -void R_SkyPrecache(void) -{ - needUpdateSkyAmbientColor = true; - calculateSkyAmbientColor(); - - if(skyModelsInited) - { - int i; - skymodel_t* sky; - for(i = 0, sky = skyModels; i < MAX_SKY_MODELS; ++i, sky++) - { - if(!sky->def) continue; - Models_Cache(sky->model); - } - } -} - -/** - * Animate sky models. - */ -void R_SkyTicker(void) -{ - skymodel_t* sky; - int i; - - if(clientPaused || !skyModelsInited) return; - - for(i = 0, sky = skyModels; i < MAX_SKY_MODELS; ++i, sky++) - { - if(!sky->def) continue; - - // Rotate the model. - sky->yaw += sky->def->yawSpeed / TICSPERSEC; - - // Is it time to advance to the next frame? - if(sky->maxTimer > 0 && ++sky->timer >= sky->maxTimer) - { - sky->timer = 0; - sky->frame++; - - // Execute a console command? - if(sky->def->execute) - Con_Execute(CMDS_SCRIPT, sky->def->execute, true, false); - } - } -} - -int R_SkyFirstActiveLayer(void) -{ - return firstSkyLayer+1; //1-based index. -} - -const ColorRawf* R_SkyAmbientColor(void) -{ - static const ColorRawf white = { 1.0f, 1.0f, 1.0f, 0 }; - if(skyAmbientColorDefined || rendSkyLightAuto) - { - if(!skyAmbientColorDefined) - { - calculateSkyAmbientColor(); - } - return &skyAmbientColor; - } - return &white; -} - -float R_SkyHorizonOffset(void) -{ - return horizonOffset; -} - -float R_SkyHeight(void) -{ - return height; -} - -void R_SkyLayerSetActive(int id, boolean active) -{ - skylayer_t* layer = skyLayerById(id); - if(!layer) - { -#if _DEBUG - Con_Message("Warning:R_SkyLayerSetActive: Invalid layer id #%i, ignoring.\n", id); -#endif - return; - } - - if(active) layer->flags |= SLF_ACTIVE; - else layer->flags &= ~SLF_ACTIVE; - - needUpdateSkyAmbientColor = true; -} - -boolean R_SkyLayerActive(int id) -{ - skylayer_t* layer = skyLayerById(id); - if(!layer) - { -#if _DEBUG - Con_Message("Warning:R_SkyLayerActive: Invalid layer id #%i, returning false.\n", id); -#endif - return false; - } - return !!(layer->flags & SLF_ACTIVE); -} - -void R_SkyLayerSetMasked(int id, boolean masked) -{ - skylayer_t* layer = skyLayerById(id); - if(!layer) - { -#if _DEBUG - Con_Message("Warning:R_SkyLayerSetMasked: Invalid layer id #%i, ignoring.\n", id); -#endif - return; - } - if(masked) layer->flags |= SLF_MASKED; - else layer->flags &= ~SLF_MASKED; - - needUpdateSkyAmbientColor = true; -} - -boolean R_SkyLayerMasked(int id) -{ - skylayer_t* layer = skyLayerById(id); - if(!layer) - { -#if _DEBUG - Con_Message("Warning:R_SkyLayerMasked: Invalid layer id #%i, returning false.\n", id); -#endif - return false; - } - return !!(layer->flags & SLF_MASKED); -} - -material_t* R_SkyLayerMaterial(int id) -{ - skylayer_t* layer = skyLayerById(id); - if(!layer) - { -#if _DEBUG - Con_Message("Warning:R_SkyLayerMaterial: Invalid layer id #%i, returning NULL.\n", id); -#endif - return NULL; - } - return layer->material; -} - -void R_SkyLayerSetMaterial(int id, material_t* mat) -{ - skylayer_t* layer = skyLayerById(id); - if(!layer) - { -#if _DEBUG - Con_Message("Warning:R_SkyLayerSetMaterial: Invalid layer id #%i, ignoring.\n", id); -#endif - return; - } - if(layer->material == mat) return; - - layer->material = mat; - needUpdateSkyAmbientColor = true; -} - -float R_SkyLayerFadeoutLimit(int id) -{ - skylayer_t* layer = skyLayerById(id); - if(!layer) - { -#if _DEBUG - Con_Message("Warning:R_SkyLayerFadeoutLimit: Invalid layer id #%i, returning default.\n", id); -#endif - return DEFAULT_SKY_SPHERE_FADEOUT_LIMIT; - } - return layer->fadeoutLimit; -} - -void R_SkyLayerSetFadeoutLimit(int id, float limit) -{ - skylayer_t* layer = skyLayerById(id); - if(!layer) - { -#if _DEBUG - Con_Message("Warning:R_SkyLayerSetFadeoutLimit: Invalid layer id #%i, ignoring.\n", id); -#endif - return; - } - if(layer->fadeoutLimit == limit) return; - - layer->fadeoutLimit = limit; - needUpdateSkyAmbientColor = true; -} - -float R_SkyLayerOffset(int id) -{ - skylayer_t* layer = skyLayerById(id); - if(!layer) - { -#if _DEBUG - Con_Message("Warning:R_SkyLayerOffset: Invalid layer id #%i, returning default.\n", id); -#endif - return DEFAULT_SKY_SPHERE_XOFFSET; - } - return layer->offset; -} - -void R_SkyLayerSetOffset(int id, float offset) -{ - skylayer_t* layer = skyLayerById(id); - if(!layer) - { -#if _DEBUG - Con_Message("Warning:R_SkyLayerOffset: Invalid layer id #%i, ignoring.\n", id); -#endif - return; - } - if(layer->offset == offset) return; - layer->offset = offset; -} - -static void chooseFirstLayer(void) -{ - int i; - // -1 denotes 'no active layers'. - firstSkyLayer = -1; - activeSkyLayers = 0; - - for(i = 1; i <= MAX_SKY_LAYERS; ++i) - { - if(!R_SkyLayerActive(i)) continue; - - ++activeSkyLayers; - if(firstSkyLayer == -1) - { - firstSkyLayer = i-1; - } - } -} - -static void internalSkyParams(int layer, int param, void* data) -{ - switch(param) - { - case DD_ENABLE: - R_SkyLayerSetActive(layer, true); - chooseFirstLayer(); - break; - - case DD_DISABLE: - R_SkyLayerSetActive(layer, false); - chooseFirstLayer(); - break; - - case DD_MASK: - R_SkyLayerSetMasked(layer, *((int*)data) == DD_YES); - break; - - case DD_MATERIAL: { - materialid_t materialId = *((materialid_t*) data); - R_SkyLayerSetMaterial(layer, Materials_ToMaterial(materialId)); - break; - } - case DD_OFFSET: - R_SkyLayerSetOffset(layer, *((float*) data)); - break; - - case DD_COLOR_LIMIT: - R_SkyLayerSetFadeoutLimit(layer, *((float*) data)); - break; - - default: - Con_Error("R_SkyParams: Bad parameter (%d).\n", param); - break; - } -} - -void R_SkyParams(int layer, int param, void* data) -{ - if(layer == DD_SKY) // The whole sky? - { - switch(param) - { - case DD_HEIGHT: - height = *((float*) data); - break; - - case DD_HORIZON: // Horizon offset angle - horizonOffset = *((float*) data); - break; - - default: { // Operate on all layers. - int i; - for(i = 1; i <= MAX_SKY_LAYERS; ++i) - { - internalSkyParams(i, param, data); - } - break; - } - } - return; - } - - // A specific layer? - if(layer >= 0 && layer < MAX_SKY_LAYERS) - { - internalSkyParams(layer+1, param, data); - } -} diff --git a/doomsday/engine/src/render/rend_list.c b/doomsday/engine/src/render/rend_list.c index a07cf3e167..7c9bdb7ac1 100644 --- a/doomsday/engine/src/render/rend_list.c +++ b/doomsday/engine/src/render/rend_list.c @@ -2163,7 +2163,7 @@ END_PROF( PROF_RL_RENDER_SKYMASK ); glStencilFunc(GL_EQUAL, 1, 0xffffffff); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); - Rend_RenderSky(); + Sky_Render(); if(!devRendSkyAlways) { diff --git a/doomsday/engine/src/render/rend_main.c b/doomsday/engine/src/render/rend_main.c index a90ea8e8c3..a1e0a78f45 100644 --- a/doomsday/engine/src/render/rend_main.c +++ b/doomsday/engine/src/render/rend_main.c @@ -188,7 +188,7 @@ void Rend_Register(void) Rend_DecorRegister(); SB_Register(); LG_Register(); - Rend_SkyRegister(); + Sky_Register(); Rend_ModelRegister(); Rend_ParticleRegister(); Rend_RadioRegister(); @@ -209,7 +209,7 @@ void Rend_Init(void) { C_Init(); RL_Init(); - Rend_SkyInit(); + Sky_Init(); } void Rend_Shutdown(void) diff --git a/doomsday/engine/src/render/rend_sky.c b/doomsday/engine/src/render/rend_sky.c deleted file mode 100644 index 05830d724b..0000000000 --- a/doomsday/engine/src/render/rend_sky.c +++ /dev/null @@ -1,519 +0,0 @@ -/**\file rend_sky.c - *\section License - * License: GPL - * Online License Link: http://www.gnu.org/licenses/gpl.html - * - *\author Copyright © 2003-2012 Jaakko Keränen - *\author Copyright © 2006-2012 Daniel Swanson - * - * 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, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301 USA - */ - -#include - -#include "de_base.h" -#include "de_console.h" -#include "de_graphics.h" -#include "de_render.h" - -#include "render/r_sky.h" -#include "resource/texture.h" -#include "resource/texturevariant.h" -#include "resource/materialvariant.h" - -/** - * @defgroup skySphereRenderFlags Sky Render Flags - * @ingroup flags - * @{ - */ -#define SKYHEMI_UPPER 0x1 -#define SKYHEMI_LOWER 0x2 -#define SKYHEMI_JUST_CAP 0x4 // Just draw the top or bottom cap. -/**@}*/ - -typedef struct { - float pos[3]; -} skyvertex_t; - -typedef struct { - boolean fadeout, texXFlip; - Size2Raw texSize; - float texOffset; - ColorRawf capColor; -} renderhemispherestate_t; - -int skyDetail = 6, skyColumns = 4*6, skyRows = 3; -float skyDistance = 1600; - -static void constructSphere(void); -static void destroySphere(void); -// CVar callback function which marks the sphere as needing to be rebuilt. -static void updateSphere(void); - -static void rebuildHemisphere(void); - -// @c true iff this module has been initialized. -static boolean initedOk = false; - -// Hemisphere geometry used with the sky sphere. -static skyvertex_t* skyVerts; // Crest is up. -static int numSkyVerts; -static boolean needRebuildHemisphere = true; - -// Sphere render state paramaters. Global for performance reasons. -static renderhemispherestate_t rs; - -void Rend_SkyRegister(void) -{ - C_VAR_INT2("rend-sky-detail", &skyDetail, 0, 3, 7, updateSphere); - C_VAR_INT2("rend-sky-rows", &skyRows, 0, 1, 8, updateSphere); - C_VAR_FLOAT("rend-sky-distance", &skyDistance, CVF_NO_MAX, 1, 0); -} - -void Rend_SkyInit(void) -{ - if(novideo || isDedicated || initedOk) return; - initedOk = true; -} - -void Rend_SkyShutdown(void) -{ - if(novideo || isDedicated || !initedOk) return; - destroySphere(); - initedOk = false; -} - -static void renderSkyModels(void) -{ - rendmodelparams_t params; - skymodel_t* sky; - float inter; - int i, c; - - LIBDENG_ASSERT_IN_MAIN_THREAD(); - LIBDENG_ASSERT_GL_CONTEXT_ACTIVE(); - - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - - // Setup basic translation. - glTranslatef(vOrigin[VX], vOrigin[VY], vOrigin[VZ]); - - for(i = 0, sky = skyModels; i < NUM_SKY_MODELS; ++i, sky++) - { - if(!sky->def) - continue; - - if(!R_SkyLayerActive(sky->def->layer+1)) - { - // The model has been assigned to a layer, but the layer is - // not visible. - continue; - } - - inter = (sky->maxTimer > 0 ? sky->timer / (float) sky->maxTimer : 0); - - memset(¶ms, 0, sizeof(params)); - - // Calculate the coordinates for the model. - params.origin[VX] = vOrigin[VX] * -sky->def->coordFactor[VX]; - params.origin[VY] = vOrigin[VZ] * -sky->def->coordFactor[VZ]; - params.origin[VZ] = vOrigin[VY] * -sky->def->coordFactor[VY]; - params.gzt = params.origin[VZ]; - params.distance = 1; - - params.extraYawAngle = params.yawAngleOffset = sky->def->rotate[0]; - params.extraPitchAngle = params.pitchAngleOffset = sky->def->rotate[1]; - params.inter = inter; - params.mf = sky->model; - params.alwaysInterpolate = true; - Rend_ModelSetFrame(sky->model, sky->frame); - params.yaw = sky->yaw; - for(c = 0; c < 4; ++c) - { - params.ambientColor[c] = sky->def->color[c]; - } - params.vLightListIdx = 0; - params.shineTranslateWithViewerPos = true; - - Rend_RenderModel(¶ms); - } - - // We don't want that anything interferes with what was drawn. - //glClear(GL_DEPTH_BUFFER_BIT); - - glMatrixMode(GL_MODELVIEW); - glPopMatrix(); -} - -// Look up the precalculated vertex. -static __inline skyvertex_t* skyVertex(int r, int c) -{ - return skyVerts + (r*skyColumns + c%skyColumns); -} - -static void renderHemisphereCap(void) -{ - int c; - - // Use the appropriate color. - glColor3fv(rs.capColor.rgb); - - // Draw the cap. - glBegin(GL_TRIANGLE_FAN); - for(c = 0; c < skyColumns; ++c) - { - glVertex3fv((const GLfloat*)skyVertex(0, c)->pos); - } - glEnd(); - - // Are we doing a colored fadeout? - if(!rs.fadeout) return; - - // We must fill the background for the top row since it'll be - // partially translucent. - glBegin(GL_TRIANGLE_STRIP); - glVertex3fv((const GLfloat*)skyVertex(0, 0)->pos); - for(c = 0; c < skyColumns; ++c) - { - // One step down. - glVertex3fv((const GLfloat*)skyVertex(1, c)->pos); - // And one step right. - glVertex3fv((const GLfloat*)skyVertex(0, c + 1)->pos); - } - glVertex3fv((const GLfloat*)skyVertex(1, c)->pos); - glEnd(); -} - -static void renderHemisphere(void) -{ -#define WRITESKYVERTEX(r_, c_) { \ - svtx = skyVertex(r_, c_); \ - if(rs.texSize.width != 0) \ - glTexCoord2f((c_) / (float) skyColumns, (r_) / (float) skyRows); \ - if(rs.fadeout) \ - { \ - if((r_) == 0) glColor4f(1, 1, 1, 0); \ - else glColor3f(1, 1, 1); \ - } \ - else \ - { \ - if((r_) == 0) glColor3f(0, 0, 0); \ - else glColor3f(1, 1, 1); \ - } \ - glVertex3fv((const GLfloat*)svtx->pos); \ -} - - const skyvertex_t* svtx; - int r, c; - for(r = 0; r < skyRows; ++r) - { - glBegin(GL_TRIANGLE_STRIP); - WRITESKYVERTEX(r, 0); - WRITESKYVERTEX(r + 1, 0); - for(c = 1; c <= skyColumns; ++c) - { - WRITESKYVERTEX(r, c); - WRITESKYVERTEX(r + 1, c); - } - glEnd(); - } -} - -typedef enum { - HC_NONE = 0, - HC_TOP, - HC_BOTTOM -} hemispherecap_t; - -static void configureRenderHemisphereStateForLayer(int layer, hemispherecap_t setupCap) -{ - // Default state is no texture and no fadeout. - rs.texSize.width = rs.texSize.height = 0; - if(setupCap != HC_NONE) - rs.fadeout = false; - rs.texXFlip = true; - - if(renderTextures != 0) - { - const materialvariantspecification_t* spec; - const materialsnapshot_t* ms; - material_t* mat; - - if(renderTextures == 2) - { - mat = Materials_ToMaterial(Materials_ResolveUriCString("System:gray")); - } - else - { - mat = R_SkyLayerMaterial(layer); - if(!mat) - { - mat = Materials_ToMaterial(Materials_ResolveUriCString("System:missing")); - rs.texXFlip = false; - } - } - assert(mat); - - spec = Materials_VariantSpecificationForContext(MC_SKYSPHERE, - TSF_NO_COMPRESSION | (R_SkyLayerMasked(layer)? TSF_ZEROMASK : 0), - 0, 0, 0, GL_REPEAT, GL_CLAMP_TO_EDGE, 0, -1, -1, false, true, false, false); - ms = Materials_Prepare(mat, spec, true); - - rs.texSize.width = Texture_Width(MSU_texture(ms, MTU_PRIMARY)); - rs.texSize.height = Texture_Height(MSU_texture(ms, MTU_PRIMARY)); - if(rs.texSize.width && rs.texSize.height) - { - rs.texOffset = R_SkyLayerOffset(layer); - GL_BindTexture(MST(ms, MTU_PRIMARY)); - } - else - { - // Disable texturing. - rs.texSize.width = rs.texSize.height = 0; - GL_SetNoTexture(); - } - - if(setupCap != HC_NONE) - { - const averagecolor_analysis_t* avgLineColor = (const averagecolor_analysis_t*) - Texture_AnalysisDataPointer(MSU_texture(ms, MTU_PRIMARY), - (setupCap == HC_TOP? TA_LINE_TOP_COLOR : TA_LINE_BOTTOM_COLOR)); - const float fadeoutLimit = R_SkyLayerFadeoutLimit(layer); - if(!avgLineColor) - Con_Error("configureRenderHemisphereStateForLayer: Texture id:%u has no %s analysis.", Textures_Id(MSU_texture(ms, MTU_PRIMARY)), (setupCap == HC_TOP? "TA_LINE_TOP_COLOR" : "TA_LINE_BOTTOM_COLOR")); - - V3f_Copy(rs.capColor.rgb, avgLineColor->color.rgb); - // Is the colored fadeout in use? - rs.fadeout = (rs.capColor.red >= fadeoutLimit || - rs.capColor.green >= fadeoutLimit || - rs.capColor.blue >= fadeoutLimit); - } - } - else - { - GL_SetNoTexture(); - } - - if(setupCap != HC_NONE && !rs.fadeout) - { - // Default color is black. - V3f_Set(rs.capColor.rgb, 0, 0, 0); - } -} - -/// @param flags @see skySphereRenderFlags -static void renderSkyHemisphere(int flags) -{ - int firstSkyLayer = R_SkyFirstActiveLayer(); - const boolean yflip = !!(flags & SKYHEMI_LOWER); - hemispherecap_t cap = !!(flags & SKYHEMI_LOWER)? HC_BOTTOM : HC_TOP; - - // Rebuild the hemisphere model if necessary. - rebuildHemisphere(); - - if(yflip) - { - // The lower hemisphere must be flipped. - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - glScalef(1.0f, -1.0f, 1.0f); - } - - // First render the cap and the background for fadeouts, if needed. - configureRenderHemisphereStateForLayer(firstSkyLayer, cap); - renderHemisphereCap(); - - if(!(flags & SKYHEMI_JUST_CAP)) - { - int i; - - for(i = firstSkyLayer; i <= MAX_SKY_LAYERS; ++i) - { - if(!R_SkyLayerActive(i)) continue; - if(i != firstSkyLayer) - { - configureRenderHemisphereStateForLayer(i, HC_NONE); - } - - if(rs.texSize.width != 0) - { - glEnable(GL_TEXTURE_2D); - glMatrixMode(GL_TEXTURE); - glPushMatrix(); - glLoadIdentity(); - glTranslatef(rs.texOffset / rs.texSize.width, 0, 0); - glScalef(1024.f / rs.texSize.width * (rs.texXFlip? 1 : -1), yflip? -1 : 1, 1); - if(yflip) glTranslatef(0, -1, 0); - } - - renderHemisphere(); - - if(rs.texSize.width != 0) - { - glMatrixMode(GL_TEXTURE); - glPopMatrix(); - glDisable(GL_TEXTURE_2D); - } - } - } - - if(yflip) - { - glMatrixMode(GL_MODELVIEW); - glPopMatrix(); - } -} - -void Rend_RenderSky(void) -{ - if(novideo || isDedicated || !initedOk) return; - - // Is there a sky to be rendered? - if(!R_SkyFirstActiveLayer()) return; - - // If sky models have been inited, they will be used. - if(!skyModelsInited || alwaysDrawSphere) - { - // We don't want anything written in the depth buffer. - glDisable(GL_DEPTH_TEST); - glDepthMask(GL_FALSE); - - // Disable culling, all triangles face the viewer. - glDisable(GL_CULL_FACE); - - // Setup a proper matrix. - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - glTranslatef(vOrigin[VX], vOrigin[VY], vOrigin[VZ]); - glScalef(skyDistance, skyDistance, skyDistance); - - // Always draw both hemispheres. - renderSkyHemisphere(SKYHEMI_LOWER); - renderSkyHemisphere(SKYHEMI_UPPER); - - glMatrixMode(GL_MODELVIEW); - glPopMatrix(); - - // Restore assumed default GL state. - glEnable(GL_CULL_FACE); - - glDepthMask(GL_TRUE); - glEnable(GL_DEPTH_TEST); - } - - // How about some 3D models? - if(skyModelsInited) - { - // We don't want anything written in the depth buffer. - glDisable(GL_DEPTH_TEST); - glDepthMask(GL_FALSE); - - renderSkyModels(); - - // Restore assumed default GL state. - glDepthMask(GL_TRUE); - glEnable(GL_DEPTH_TEST); - } -} - -static void destroySphere(void) -{ - if(skyVerts) - { - free(skyVerts); - skyVerts = NULL; - } - numSkyVerts = 0; -} - -/** - * The top row (row 0) is the one that's faded out. - * There must be at least 4 columns. The preferable number is 4n, where - * n is 1, 2, 3... There should be at least two rows because the first - * one is always faded. - * - * The total number of triangles per hemisphere can be calculated thus: - * - * Sum: rows * columns * 2 + (hemisphere) - * rows * 2 + (fadeout) - * rows - 2 (cap) - */ -static void constructSphere(void) -{ - const float maxSideAngle = (float) PI / 2 * R_SkyHeight(); - const float horizonOffset = (float) PI / 2 * R_SkyHorizonOffset(); - const float scale = 1; - float realRadius, topAngle, sideAngle; - int c, r; - - if(skyDetail < 1) skyDetail = 1; - if(skyRows < 1) skyRows = 1; - - skyColumns = 4 * skyDetail; - - numSkyVerts = skyColumns * (skyRows + 1); - skyVerts = (skyvertex_t*)realloc(skyVerts, sizeof *skyVerts * numSkyVerts); - if(!skyVerts) - Con_Error("constructSphere: Failed (re)allocation of %lu bytes for SkyVertex list.", (unsigned long) sizeof *skyVerts * numSkyVerts); - - // Calculate the vertices. - for(r = 0; r < skyRows + 1; ++r) - for(c = 0; c < skyColumns; ++c) - { - skyvertex_t* svtx = skyVertex(r, c); - - topAngle = ((c / (float) skyColumns) *2) * PI; - sideAngle = horizonOffset + maxSideAngle * (skyRows - r) / (float) skyRows; - realRadius = scale * cos(sideAngle); - svtx->pos[VX] = realRadius * cos(topAngle); - svtx->pos[VY] = scale * sin(sideAngle); // The height. - svtx->pos[VZ] = realRadius * sin(topAngle); - } -} - -static void rebuildHemisphere(void) -{ - static boolean firstBuild = true; - static float oldHorizonOffset; - static float oldHeight; - - // Rebuild our hemisphere model if any paramaters have changed. - if(firstBuild || R_SkyHorizonOffset() != oldHorizonOffset) - { - oldHorizonOffset = R_SkyHorizonOffset(); - needRebuildHemisphere = true; - } - if(firstBuild || R_SkyHeight() != oldHeight) - { - oldHeight = R_SkyHeight(); - needRebuildHemisphere = true; - } - firstBuild = false; - - if(!needRebuildHemisphere) return; - - // We have work to do... - constructSphere(); - needRebuildHemisphere = false; -} - -/// \note A CVar callback. -static void updateSphere(void) -{ - // Defer this task until render time, when we can be sure we are in correct thread. - needRebuildHemisphere = true; -} diff --git a/doomsday/engine/src/render/sky.cpp b/doomsday/engine/src/render/sky.cpp new file mode 100644 index 0000000000..2d4054065d --- /dev/null +++ b/doomsday/engine/src/render/sky.cpp @@ -0,0 +1,1046 @@ +/** + * @file sky.cpp Sky Sphere and 3D Models + * + * @author Copyright © 2003-2012 Jaakko Keränen + * @author Copyright © 2006-2012 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, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include + +#include "de_base.h" +#include "de_console.h" +#include "de_graphics.h" +#include "de_render.h" + +#include "def_data.h" +#include "client/cl_def.h" +#include "map/r_world.h" +#include "m_vector.h" + +#include "resource/texture.h" +#include "resource/texturevariant.h" +#include "resource/materialvariant.h" + +#include "render/sky.h" + +/** + * @defgroup skySphereRenderFlags Sky Render Flags + * @ingroup flags + * @{ + */ +#define SKYHEMI_UPPER 0x1 +#define SKYHEMI_LOWER 0x2 +#define SKYHEMI_JUST_CAP 0x4 ///< Just draw the top or bottom cap. +/**@}*/ + +/** + * @defgroup skyLayerFlags Sky Layer Flags + * @ingroup flags + * @{ + */ +#define SLF_ACTIVE 0x1 ///< Layer is active and will be rendered. +#define SLF_MASKED 0x2 ///< Mask this layer's texture. +/**@}*/ + +/** + * Logical sky "layer" (for parallax effects). + */ +struct skylayer_t +{ + int flags; + material_t *material; + float offset; + float fadeoutLimit; +}; + +#define VALID_SKY_LAYERID(val) ((val) > 0 && (val) <= MAX_SKY_LAYERS) + +struct skyvertex_t +{ + float pos[3]; +}; + +enum hemispherecap_t +{ + HC_NONE = 0, + HC_TOP, + HC_BOTTOM +}; + +struct skymodel_t +{ + ded_skymodel_t *def; + modeldef_t *model; + int frame; + int timer; + int maxTimer; + float yaw; +}; + +struct renderhemispherestate_t +{ + bool fadeout, texXFlip; + Size2Raw texSize; + float texOffset; + ColorRawf capColor; +}; + +static int skyDetail = 6; +static int skyRows = 3; +static float skyDistance = 1600; + +// CVar callback function which marks the sphere as needing to be rebuilt. +static void updateSphere(); + +void Sky_Register() +{ + C_VAR_INT2("rend-sky-detail", &skyDetail, 0, 3, 7, updateSphere); + C_VAR_INT2("rend-sky-rows", &skyRows, 0, 1, 8, updateSphere); + C_VAR_FLOAT("rend-sky-distance", &skyDistance, CVF_NO_MAX, 1, 0); +} + +static int skyColumns = 4*6; + +static void constructHemisphere(); +static void destroyHemisphere(); + +static void rebuildHemisphere(); +static void setupSkyModels(ded_sky_t *def); + +// @c true iff this module has been initialized. +static bool initedOk = false; + +static bool alwaysDrawSphere; + +static skylayer_t skyLayers[MAX_SKY_LAYERS]; + +// Hemisphere geometry used with the sky sphere. +static skyvertex_t *hemisphereVerts; // Crest is up. +static int numHemisphereVerts; +static bool needRebuildHemisphere = true; + +static skymodel_t skyModels[MAX_SKY_MODELS]; +static bool skyModelsInited; + +// Sphere render state paramaters. Global for performance reasons. +static renderhemispherestate_t rs; + +static int firstSkyLayer, activeSkyLayers; +static float horizonOffset; +static float height; + +static bool skyAmbientColorDefined; /// @c true= pre-defined in a MapInfo def. +static bool needUpdateSkyAmbientColor; /// @c true= update if not pre-defined. +static ColorRawf skyAmbientColor; + +static inline skylayer_t *skyLayerById(int id) +{ + if(!VALID_SKY_LAYERID(id)) return 0; + return skyLayers + (id-1); +} + +static void configureDefaultSky() +{ + skyAmbientColorDefined = false; + needUpdateSkyAmbientColor = true; + V3f_Set(skyAmbientColor.rgb, 1.0f, 1.0f, 1.0f); + + for(int i = 0; i < MAX_SKY_LAYERS; ++i) + { + skylayer_t *layer = &skyLayers[i]; + layer->flags = (i == 0? SLF_ACTIVE : 0); + layer->material = Materials_ToMaterial(Materials_ResolveUriCString(DEFAULT_SKY_SPHERE_MATERIAL)); + layer->offset = DEFAULT_SKY_SPHERE_XOFFSET; + layer->fadeoutLimit = DEFAULT_SKY_SPHERE_FADEOUT_LIMIT; + } + + height = DEFAULT_SKY_HEIGHT; + horizonOffset = DEFAULT_SKY_HORIZON_OFFSET; +} + +materialvariantspecification_t const *Sky_SphereMaterialSpec(bool masked) +{ + return Materials_VariantSpecificationForContext(MC_SKYSPHERE, + TSF_NO_COMPRESSION | (masked? TSF_ZEROMASK : 0), + 0, 0, 0, GL_REPEAT, GL_CLAMP_TO_EDGE, 0, -1, -1, false, true, false, false); +} + +static void calculateSkyAmbientColor() +{ + ColorRawf avgMaterialColor = { 0, 0, 0, 0 }; + ColorRawf bottomCapColor = { 0, 0, 0, 0 }; + ColorRawf topCapColor = { 0, 0, 0, 0 }; + skylayer_t *slayer; + int i, avgCount; + + if(!needUpdateSkyAmbientColor) return; + needUpdateSkyAmbientColor = false; + + V3f_Set(skyAmbientColor.rgb, 1.0f, 1.0f, 1.0f); + if(skyModelsInited && !alwaysDrawSphere) return; + + /** + * @todo Re-implement me by rendering the sky to a low-quality cubemap + * and use that to obtain the lighting characteristics. + */ + avgCount = 0; + for(i = 0, slayer = &skyLayers[firstSkyLayer]; i < MAX_SKY_LAYERS; ++i, slayer++) + { + materialvariantspecification_t const *spec; + materialsnapshot_t const *ms; + + if(!(slayer->flags & SLF_ACTIVE) || !slayer->material) continue; + + spec = Sky_SphereMaterialSpec(!!(slayer->flags & SLF_MASKED)); + ms = Materials_Prepare(slayer->material, spec, false); + + if(MSU_texture(ms, MTU_PRIMARY)) + { + averagecolor_analysis_t const *avgColor = (averagecolor_analysis_t const *) + Texture_AnalysisDataPointer(MSU_texture(ms, MTU_PRIMARY), TA_COLOR); + if(!avgColor) Con_Error("calculateSkyAmbientColor: Texture id:%u has no TA_COLOR analysis.", Textures_Id(MSU_texture(ms, MTU_PRIMARY))); + + if(i == firstSkyLayer) + { + Texture const *tex = MSU_texture(ms, MTU_PRIMARY); + averagecolor_analysis_t const *avgLineColor = (averagecolor_analysis_t const *) Texture_AnalysisDataPointer(tex, TA_LINE_TOP_COLOR); + if(!avgLineColor) Con_Error("calculateSkyAmbientColor: Texture id:%u has no TA_LINE_TOP_COLOR analysis.", Textures_Id(MSU_texture(ms, MTU_PRIMARY))); + + V3f_Copy(topCapColor.rgb, avgLineColor->color.rgb); + + avgLineColor = (averagecolor_analysis_t const *) Texture_AnalysisDataPointer(tex, TA_LINE_BOTTOM_COLOR); + if(!avgLineColor) Con_Error("calculateSkyAmbientColor: Texture id:%u has no TA_LINE_BOTTOM_COLOR analysis.", Textures_Id(MSU_texture(ms, MTU_PRIMARY))); + + V3f_Copy(bottomCapColor.rgb, avgLineColor->color.rgb); + } + + V3f_Sum(avgMaterialColor.rgb, avgMaterialColor.rgb, avgColor->color.rgb); + ++avgCount; + } + } + + if(avgCount != 0) + { + // The caps cover a large amount of the sky sphere, so factor it in too. + vec3f_t capSum; + V3f_Sum(capSum, topCapColor.rgb, bottomCapColor.rgb); + V3f_Sum(skyAmbientColor.rgb, avgMaterialColor.rgb, capSum); + avgCount += 2; // Each cap is another unit. + V3f_Scale(skyAmbientColor.rgb, 1.f / avgCount); + } +} + +void Sky_Init() +{ + if(novideo || isDedicated || initedOk) return; + initedOk = true; + + firstSkyLayer = 0; + activeSkyLayers = 0; + skyModelsInited = false; + alwaysDrawSphere = false; + needUpdateSkyAmbientColor = true; +} + +void Sky_Shutdown() +{ + if(novideo || isDedicated || !initedOk) return; + destroyHemisphere(); + initedOk = false; +} + +void Sky_Configure(ded_sky_t *def) +{ + configureDefaultSky(); + if(!def) return; // Go with the defaults. + + height = def->height; + horizonOffset = def->horizonOffset; + + for(int i = 1; i <= MAX_SKY_LAYERS; ++i) + { + ded_skylayer_t *sl = &def->layers[i-1]; + + if(!(sl->flags & SLF_ACTIVE)) + { + Sky_LayerSetActive(i, false); + continue; + } + + Sky_LayerSetActive(i, true); + Sky_LayerSetMasked(i, (sl->flags & SLF_MASKED) != 0); + if(sl->material) + { + material_t *mat = Materials_ToMaterial(Materials_ResolveUri2(sl->material, true/*quiet please*/)); + if(mat) + { + Sky_LayerSetMaterial(i, mat); + } + else + { + AutoStr *path = Uri_ToString(sl->material); + Con_Message("Warning: Unknown material \"%s\" in sky def %i, using default.\n", Str_Text(path), i); + } + } + Sky_LayerSetOffset(i, sl->offset); + Sky_LayerSetFadeoutLimit(i, sl->colorLimit); + } + + if(def->color[CR] > 0 || def->color[CG] > 0 || def->color[CB] > 0) + { + skyAmbientColorDefined = true; + V3f_Set(skyAmbientColor.rgb, def->color[CR], def->color[CG], def->color[CB]); + } + + // Any sky models to setup? Models will override the normal sphere by default. + setupSkyModels(def); +} + +/** + * The sky models are set up using the data in the definition. + */ +static void setupSkyModels(ded_sky_t *def) +{ + // Clear the whole sky models data. + memset(skyModels, 0, sizeof(skyModels)); + + // Normally the sky sphere is not drawn if models are in use. + alwaysDrawSphere = (def->flags & SIF_DRAW_SPHERE) != 0; + + // The normal sphere is used if no models will be set up. + skyModelsInited = false; + + ded_skymodel_t *modef = def->models; + skymodel_t *sm = skyModels; + for(int i = 0; i < MAX_SKY_MODELS; ++i, modef++, sm++) + { + // Is the model ID set? + sm->model = Models_Definition(modef->id); + if(!sm->model) continue; + + // There is a model here. + skyModelsInited = true; + + sm->def = modef; + sm->maxTimer = (int) (TICSPERSEC * modef->frameInterval); + sm->yaw = modef->yaw; + sm->frame = sm->model->sub[0].frame; + } +} + +void Sky_Cache() +{ + needUpdateSkyAmbientColor = true; + calculateSkyAmbientColor(); + + if(skyModelsInited) + { + skymodel_t* sky = skyModels; + for(int i = 0; i < MAX_SKY_MODELS; ++i, sky++) + { + if(!sky->def) continue; + Models_Cache(sky->model); + } + } +} + +void Sky_Ticker() +{ + if(clientPaused || !skyModelsInited) return; + + skymodel_t *sky = skyModels; + for(int i = 0; i < MAX_SKY_MODELS; ++i, sky++) + { + if(!sky->def) continue; + + // Rotate the model. + sky->yaw += sky->def->yawSpeed / TICSPERSEC; + + // Is it time to advance to the next frame? + if(sky->maxTimer > 0 && ++sky->timer >= sky->maxTimer) + { + sky->timer = 0; + sky->frame++; + + // Execute a console command? + if(sky->def->execute) + Con_Execute(CMDS_SCRIPT, sky->def->execute, true, false); + } + } +} + +int Sky_FirstActiveLayer() +{ + return firstSkyLayer+1; //1-based index. +} + +ColorRawf const *Sky_AmbientColor() +{ + static ColorRawf const white = { 1.0f, 1.0f, 1.0f, 0 }; + if(skyAmbientColorDefined || rendSkyLightAuto) + { + if(!skyAmbientColorDefined) + { + calculateSkyAmbientColor(); + } + return &skyAmbientColor; + } + return &white; +} + +float Sky_HorizonOffset() +{ + return horizonOffset; +} + +float Sky_Height() +{ + return height; +} + +void Sky_LayerSetActive(int id, boolean active) +{ + skylayer_t *layer = skyLayerById(id); + if(!layer) + { +#if _DEBUG + Con_Message("Warning: Sky_LayerSetActive: Invalid layer id #%i, ignoring.\n", id); +#endif + return; + } + + if(active) layer->flags |= SLF_ACTIVE; + else layer->flags &= ~SLF_ACTIVE; + + needUpdateSkyAmbientColor = true; +} + +boolean Sky_LayerActive(int id) +{ + skylayer_t* layer = skyLayerById(id); + if(!layer) + { +#if _DEBUG + Con_Message("Warning: Sky_LayerActive: Invalid layer id #%i, returning false.\n", id); +#endif + return false; + } + return !!(layer->flags & SLF_ACTIVE); +} + +void Sky_LayerSetMasked(int id, boolean masked) +{ + skylayer_t *layer = skyLayerById(id); + if(!layer) + { +#if _DEBUG + Con_Message("Warning: Sky_LayerSetMasked: Invalid layer id #%i, ignoring.\n", id); +#endif + return; + } + if(masked) layer->flags |= SLF_MASKED; + else layer->flags &= ~SLF_MASKED; + + needUpdateSkyAmbientColor = true; +} + +boolean Sky_LayerMasked(int id) +{ + skylayer_t *layer = skyLayerById(id); + if(!layer) + { +#if _DEBUG + Con_Message("Warning: Sky_LayerMasked: Invalid layer id #%i, returning false.\n", id); +#endif + return false; + } + return !!(layer->flags & SLF_MASKED); +} + +material_t *Sky_LayerMaterial(int id) +{ + skylayer_t *layer = skyLayerById(id); + if(!layer) + { +#if _DEBUG + Con_Message("Warning: Sky_LayerMaterial: Invalid layer id #%i, returning NULL.\n", id); +#endif + return 0; + } + return layer->material; +} + +void Sky_LayerSetMaterial(int id, material_t *mat) +{ + skylayer_t *layer = skyLayerById(id); + if(!layer) + { +#if _DEBUG + Con_Message("Warning: Sky_LayerSetMaterial: Invalid layer id #%i, ignoring.\n", id); +#endif + return; + } + if(layer->material == mat) return; + + layer->material = mat; + needUpdateSkyAmbientColor = true; +} + +float Sky_LayerFadeoutLimit(int id) +{ + skylayer_t *layer = skyLayerById(id); + if(!layer) + { +#if _DEBUG + Con_Message("Warning: Sky_LayerFadeoutLimit: Invalid layer id #%i, returning default.\n", id); +#endif + return DEFAULT_SKY_SPHERE_FADEOUT_LIMIT; + } + return layer->fadeoutLimit; +} + +void Sky_LayerSetFadeoutLimit(int id, float limit) +{ + skylayer_t *layer = skyLayerById(id); + if(!layer) + { +#if _DEBUG + Con_Message("Warning: Sky_LayerSetFadeoutLimit: Invalid layer id #%i, ignoring.\n", id); +#endif + return; + } + if(layer->fadeoutLimit == limit) return; + + layer->fadeoutLimit = limit; + needUpdateSkyAmbientColor = true; +} + +float Sky_LayerOffset(int id) +{ + skylayer_t *layer = skyLayerById(id); + if(!layer) + { +#if _DEBUG + Con_Message("Warning: Sky_LayerOffset: Invalid layer id #%i, returning default.\n", id); +#endif + return DEFAULT_SKY_SPHERE_XOFFSET; + } + return layer->offset; +} + +void Sky_LayerSetOffset(int id, float offset) +{ + skylayer_t *layer = skyLayerById(id); + if(!layer) + { +#if _DEBUG + Con_Message("Warning: Sky_LayerOffset: Invalid layer id #%i, ignoring.\n", id); +#endif + return; + } + if(layer->offset == offset) return; + layer->offset = offset; +} + +static void chooseFirstLayer() +{ + // -1 denotes 'no active layers'. + firstSkyLayer = -1; + activeSkyLayers = 0; + + for(int i = 1; i <= MAX_SKY_LAYERS; ++i) + { + if(!Sky_LayerActive(i)) continue; + + ++activeSkyLayers; + if(firstSkyLayer == -1) + { + firstSkyLayer = i-1; + } + } +} + +static void internalSkyParams(int layer, int param, void *data) +{ + switch(param) + { + case DD_ENABLE: + Sky_LayerSetActive(layer, true); + chooseFirstLayer(); + break; + + case DD_DISABLE: + Sky_LayerSetActive(layer, false); + chooseFirstLayer(); + break; + + case DD_MASK: + Sky_LayerSetMasked(layer, *((int *)data) == DD_YES); + break; + + case DD_MATERIAL: { + materialid_t materialId = *((materialid_t *)data); + Sky_LayerSetMaterial(layer, Materials_ToMaterial(materialId)); + break; } + + case DD_OFFSET: + Sky_LayerSetOffset(layer, *((float *)data)); + break; + + case DD_COLOR_LIMIT: + Sky_LayerSetFadeoutLimit(layer, *((float *)data)); + break; + + default: + Con_Error("R_SkyParams: Bad parameter (%d).\n", param); + } +} + +void R_SkyParams(int layer, int param, void *data) +{ + if(layer == DD_SKY) // The whole sky? + { + switch(param) + { + case DD_HEIGHT: + height = *((float *)data); + break; + + case DD_HORIZON: // Horizon offset angle + horizonOffset = *((float *)data); + break; + + default: // Operate on all layers. + for(int i = 1; i <= MAX_SKY_LAYERS; ++i) + { + internalSkyParams(i, param, data); + } + } + return; + } + + // A specific layer? + if(layer >= 0 && layer < MAX_SKY_LAYERS) + { + internalSkyParams(layer+1, param, data); + } +} + +static void renderSkyModels() +{ + LIBDENG_ASSERT_IN_MAIN_THREAD(); + LIBDENG_ASSERT_GL_CONTEXT_ACTIVE(); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + // Setup basic translation. + glTranslatef(vOrigin[VX], vOrigin[VY], vOrigin[VZ]); + + rendmodelparams_t parm; + skymodel_t* sky = skyModels; + for(int i = 0; i < NUM_SKY_MODELS; ++i, sky++) + { + if(!sky->def) continue; + + if(!Sky_LayerActive(sky->def->layer+1)) + { + // The model has been assigned to a layer, but the layer is + // not visible. + continue; + } + + float inter = (sky->maxTimer > 0 ? sky->timer / float(sky->maxTimer) : 0); + + memset(&parm, 0, sizeof(parm)); + + // Calculate the coordinates for the model. + parm.origin[VX] = vOrigin[VX] * -sky->def->coordFactor[VX]; + parm.origin[VY] = vOrigin[VZ] * -sky->def->coordFactor[VZ]; + parm.origin[VZ] = vOrigin[VY] * -sky->def->coordFactor[VY]; + parm.gzt = parm.origin[VZ]; + parm.distance = 1; + + parm.extraYawAngle = parm.yawAngleOffset = sky->def->rotate[0]; + parm.extraPitchAngle = parm.pitchAngleOffset = sky->def->rotate[1]; + parm.inter = inter; + parm.mf = sky->model; + parm.alwaysInterpolate = true; + Rend_ModelSetFrame(sky->model, sky->frame); + parm.yaw = sky->yaw; + for(int c = 0; c < 4; ++c) + { + parm.ambientColor[c] = sky->def->color[c]; + } + parm.vLightListIdx = 0; + parm.shineTranslateWithViewerPos = true; + + Rend_RenderModel(&parm); + } + + // We don't want that anything interferes with what was drawn. + //glClear(GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); +} + +// Look up the precalculated vertex. +static inline skyvertex_t *skyVertex(int r, int c) +{ + return hemisphereVerts + (r * skyColumns + c % skyColumns); +} + +static void renderHemisphereCap() +{ + // Use the appropriate color. + glColor3fv(rs.capColor.rgb); + + // Draw the cap. + glBegin(GL_TRIANGLE_FAN); + for(int c = 0; c < skyColumns; ++c) + { + glVertex3fv((GLfloat const *)skyVertex(0, c)->pos); + } + glEnd(); + + // Are we doing a colored fadeout? + if(!rs.fadeout) return; + + // We must fill the background for the top row since it'll be + // partially translucent. + glBegin(GL_TRIANGLE_STRIP); + glVertex3fv((GLfloat const *)skyVertex(0, 0)->pos); + int c = 0; + for(; c < skyColumns; ++c) + { + // One step down. + glVertex3fv((GLfloat const *)skyVertex(1, c)->pos); + // And one step right. + glVertex3fv((GLfloat const *)skyVertex(0, c + 1)->pos); + } + glVertex3fv((GLfloat const *)skyVertex(1, c)->pos); + glEnd(); +} + +static void renderHemisphere() +{ +#define WRITESKYVERTEX(r_, c_) { \ + svtx = skyVertex(r_, c_); \ + if(rs.texSize.width != 0) \ + glTexCoord2f((c_) / float(skyColumns), (r_) / float(skyRows)); \ + if(rs.fadeout) \ + { \ + if((r_) == 0) glColor4f(1, 1, 1, 0); \ + else glColor3f(1, 1, 1); \ + } \ + else \ + { \ + if((r_) == 0) glColor3f(0, 0, 0); \ + else glColor3f(1, 1, 1); \ + } \ + glVertex3fv((GLfloat const *)svtx->pos); \ +} + + skyvertex_t const *svtx; + for(int r = 0; r < skyRows; ++r) + { + glBegin(GL_TRIANGLE_STRIP); + WRITESKYVERTEX(r, 0); + WRITESKYVERTEX(r + 1, 0); + for(int c = 1; c <= skyColumns; ++c) + { + WRITESKYVERTEX(r, c); + WRITESKYVERTEX(r + 1, c); + } + glEnd(); + } +} + +static void configureRenderHemisphereStateForLayer(int layer, hemispherecap_t setupCap) +{ + // Default state is no texture and no fadeout. + rs.texSize.width = rs.texSize.height = 0; + if(setupCap != HC_NONE) + rs.fadeout = false; + rs.texXFlip = true; + + if(renderTextures != 0) + { + material_t* mat; + + if(renderTextures == 2) + { + mat = Materials_ToMaterial(Materials_ResolveUriCString("System:gray")); + } + else + { + mat = Sky_LayerMaterial(layer); + if(!mat) + { + mat = Materials_ToMaterial(Materials_ResolveUriCString("System:missing")); + rs.texXFlip = false; + } + } + DENG_ASSERT(mat); + + materialvariantspecification_t const *spec = Sky_SphereMaterialSpec(Sky_LayerMasked(layer)); + materialsnapshot_t const *ms = Materials_Prepare(mat, spec, true); + + rs.texSize.width = Texture_Width(MSU_texture(ms, MTU_PRIMARY)); + rs.texSize.height = Texture_Height(MSU_texture(ms, MTU_PRIMARY)); + if(rs.texSize.width && rs.texSize.height) + { + rs.texOffset = Sky_LayerOffset(layer); + GL_BindTexture(MST(ms, MTU_PRIMARY)); + } + else + { + // Disable texturing. + rs.texSize.width = rs.texSize.height = 0; + GL_SetNoTexture(); + } + + if(setupCap != HC_NONE) + { + averagecolor_analysis_t const *avgLineColor = (averagecolor_analysis_t const *) + Texture_AnalysisDataPointer(MSU_texture(ms, MTU_PRIMARY), + (setupCap == HC_TOP? TA_LINE_TOP_COLOR : TA_LINE_BOTTOM_COLOR)); + float const fadeoutLimit = Sky_LayerFadeoutLimit(layer); + if(!avgLineColor) Con_Error("configureRenderHemisphereStateForLayer: Texture id:%u has no %s analysis.", Textures_Id(MSU_texture(ms, MTU_PRIMARY)), (setupCap == HC_TOP? "TA_LINE_TOP_COLOR" : "TA_LINE_BOTTOM_COLOR")); + + V3f_Copy(rs.capColor.rgb, avgLineColor->color.rgb); + // Is the colored fadeout in use? + rs.fadeout = (rs.capColor.red >= fadeoutLimit || + rs.capColor.green >= fadeoutLimit || + rs.capColor.blue >= fadeoutLimit); + } + } + else + { + GL_SetNoTexture(); + } + + if(setupCap != HC_NONE && !rs.fadeout) + { + // Default color is black. + V3f_Set(rs.capColor.rgb, 0, 0, 0); + } +} + +/// @param flags @see skySphereRenderFlags +static void renderHemisphere(int flags) +{ + int firstSkyLayer = Sky_FirstActiveLayer(); + bool const yflip = !!(flags & SKYHEMI_LOWER); + hemispherecap_t cap = !!(flags & SKYHEMI_LOWER)? HC_BOTTOM : HC_TOP; + + // Rebuild the hemisphere model if necessary. + rebuildHemisphere(); + + if(yflip) + { + // The lower hemisphere must be flipped. + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glScalef(1.0f, -1.0f, 1.0f); + } + + // First render the cap and the background for fadeouts, if needed. + configureRenderHemisphereStateForLayer(firstSkyLayer, cap); + renderHemisphereCap(); + + if(!(flags & SKYHEMI_JUST_CAP)) + { + for(int i = firstSkyLayer; i <= MAX_SKY_LAYERS; ++i) + { + if(!Sky_LayerActive(i)) continue; + + if(i != firstSkyLayer) + { + configureRenderHemisphereStateForLayer(i, HC_NONE); + } + + if(rs.texSize.width != 0) + { + glEnable(GL_TEXTURE_2D); + glMatrixMode(GL_TEXTURE); + glPushMatrix(); + glLoadIdentity(); + glTranslatef(rs.texOffset / rs.texSize.width, 0, 0); + glScalef(1024.f / rs.texSize.width * (rs.texXFlip? 1 : -1), yflip? -1 : 1, 1); + if(yflip) glTranslatef(0, -1, 0); + } + + renderHemisphere(); + + if(rs.texSize.width != 0) + { + glMatrixMode(GL_TEXTURE); + glPopMatrix(); + glDisable(GL_TEXTURE_2D); + } + } + } + + if(yflip) + { + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } +} + +static void destroyHemisphere() +{ + if(hemisphereVerts) + { + M_Free(hemisphereVerts); + hemisphereVerts = 0; + } + numHemisphereVerts = 0; +} + +/** + * The top row (row 0) is the one that's faded out. + * There must be at least 4 columns. The preferable number is 4n, where + * n is 1, 2, 3... There should be at least two rows because the first + * one is always faded. + * + * The total number of triangles per hemisphere can be calculated thus: + * + * Sum: rows * columns * 2 + (hemisphere) + * rows * 2 + (fadeout) + * rows - 2 (cap) + */ +static void constructHemisphere() +{ + float const maxSideAngle = (float) PI / 2 * Sky_Height(); + float const horizonOffset = (float) PI / 2 * Sky_HorizonOffset(); + float const scale = 1; + float realRadius, topAngle, sideAngle; + int c, r; + + if(skyDetail < 1) skyDetail = 1; + if(skyRows < 1) skyRows = 1; + + skyColumns = 4 * skyDetail; + + numHemisphereVerts = skyColumns * (skyRows + 1); + hemisphereVerts = (skyvertex_t*) M_Realloc(hemisphereVerts, sizeof(*hemisphereVerts) * numHemisphereVerts); + if(!hemisphereVerts) Con_Error("constructHemisphere: Failed (re)allocation of %lu bytes for hemisphere vertices.", (unsigned long) sizeof(*hemisphereVerts) * numHemisphereVerts); + + // Calculate the vertices. + for(r = 0; r < skyRows + 1; ++r) + { + for(c = 0; c < skyColumns; ++c) + { + skyvertex_t *svtx = skyVertex(r, c); + + topAngle = ((c / float(skyColumns)) *2) * PI; + sideAngle = horizonOffset + maxSideAngle * (skyRows - r) / float(skyRows); + realRadius = scale * cos(sideAngle); + svtx->pos[VX] = realRadius * cos(topAngle); + svtx->pos[VY] = scale * sin(sideAngle); // The height. + svtx->pos[VZ] = realRadius * sin(topAngle); + } + } +} + +static void rebuildHemisphere() +{ + static bool firstBuild = true; + static float oldHorizonOffset; + static float oldHeight; + + // Rebuild our model if any paramaters have changed. + if(firstBuild || Sky_HorizonOffset() != oldHorizonOffset) + { + oldHorizonOffset = Sky_HorizonOffset(); + needRebuildHemisphere = true; + } + if(firstBuild || Sky_Height() != oldHeight) + { + oldHeight = Sky_Height(); + needRebuildHemisphere = true; + } + firstBuild = false; + + if(!needRebuildHemisphere) return; + + // We have work to do... + constructHemisphere(); + needRebuildHemisphere = false; +} + +void Sky_Render() +{ + if(novideo || isDedicated || !initedOk) return; + + // Is there a sky to be rendered? + if(!Sky_FirstActiveLayer()) return; + + // If sky models have been inited, they will be used. + if(!skyModelsInited || alwaysDrawSphere) + { + // We don't want anything written in the depth buffer. + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + + // Disable culling, all triangles face the viewer. + glDisable(GL_CULL_FACE); + + // Setup a proper matrix. + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glTranslatef(vOrigin[VX], vOrigin[VY], vOrigin[VZ]); + glScalef(skyDistance, skyDistance, skyDistance); + + // Always draw both hemispheres. + renderHemisphere(SKYHEMI_LOWER); + renderHemisphere(SKYHEMI_UPPER); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + // Restore assumed default GL state. + glEnable(GL_CULL_FACE); + + glDepthMask(GL_TRUE); + glEnable(GL_DEPTH_TEST); + } + + // How about some 3D models? + if(skyModelsInited) + { + // We don't want anything written in the depth buffer. + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + + renderSkyModels(); + + // Restore assumed default GL state. + glDepthMask(GL_TRUE); + glEnable(GL_DEPTH_TEST); + } +} + +/// @note A CVar callback. +static void updateSphere() +{ + // Defer this task until render time, when we can be sure we are in correct thread. + needRebuildHemisphere = true; +}