From 460327f39cd0483c0b9e76b617811131f14c2e3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaakko=20Ker=C3=A4nen?= Date: Tue, 29 Dec 2015 21:09:58 +0200 Subject: [PATCH] Renderer|Client: Environment cube map textures Added render::Environment and an instance of it owned by RenderSystem. RenderSystem now deletes subsystems in reverse order compared to construction, to ensure appropriate dependency order. Added a new asset type "texture.reflect.(map-id)" for specifying which reflection cube maps to use in which map. IssueID #2105 --- doomsday/apps/client/include/render/environ.h | 54 +++++ .../apps/client/include/render/rendersystem.h | 13 +- doomsday/apps/client/src/clientapp.cpp | 18 +- doomsday/apps/client/src/render/environ.cpp | 205 ++++++++++++++++++ .../apps/client/src/render/modelrenderer.cpp | 65 +++--- .../apps/client/src/render/rendersystem.cpp | 7 + .../libdoomsday/include/doomsday/world/map.h | 2 + doomsday/apps/libdoomsday/src/world/map.cpp | 10 +- 8 files changed, 316 insertions(+), 58 deletions(-) create mode 100644 doomsday/apps/client/include/render/environ.h create mode 100644 doomsday/apps/client/src/render/environ.cpp diff --git a/doomsday/apps/client/include/render/environ.h b/doomsday/apps/client/include/render/environ.h new file mode 100644 index 0000000000..68510a5483 --- /dev/null +++ b/doomsday/apps/client/include/render/environ.h @@ -0,0 +1,54 @@ +/** @file environ.h Environment rendering. + * + * @authors Copyright (c) 2015 Jaakko Keränen + * + * @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_ENVIRON_H +#define DENG_CLIENT_RENDER_ENVIRON_H + +#include + +class BspLeaf; + +namespace render { + +/** + * Environmental rendering and texture maps. + */ +class Environment +{ +public: + Environment(); + + de::GLTexture const &defaultReflection() const; + + /** + * Determines the reflection cube map suitable for an object at a particular + * position in the current map. + * + * @param sector Sector. + * + * @return Reflection cube map. + */ + de::GLTexture const &reflectionInBspLeaf(BspLeaf const *bspLeaf) const; + +private: + DENG2_PRIVATE(d) +}; + +} // namespace render + +#endif // DENG_CLIENT_RENDER_ENVIRON_H diff --git a/doomsday/apps/client/include/render/rendersystem.h b/doomsday/apps/client/include/render/rendersystem.h index b4a2a631b2..e002b6d77c 100644 --- a/doomsday/apps/client/include/render/rendersystem.h +++ b/doomsday/apps/client/include/render/rendersystem.h @@ -35,6 +35,7 @@ class AngleClipper; class ModelRenderer; class SkyDrawable; +namespace render { class Environment; } /** * Renderer subsystems, draw lists, etc... @ingroup render @@ -47,21 +48,19 @@ class RenderSystem : public de::System // System. void timeChanged(de::Clock const &); + SettingsRegister &settings(); + SettingsRegister &appearanceSettings(); + void glInit(); void glDeinit(); de::GLShaderBank &shaders(); de::ImageBank &images(); de::GLUniform const &uMapTime() const; - - SettingsRegister &settings(); - SettingsRegister &appearanceSettings(); - - AngleClipper &angleClipper() const; - + render::Environment &environment(); ModelRenderer &modelRenderer(); - SkyDrawable &sky(); + AngleClipper &angleClipper() const; /** * Provides access to the central map geometry buffer. diff --git a/doomsday/apps/client/src/clientapp.cpp b/doomsday/apps/client/src/clientapp.cpp index 312320ad75..026dff4247 100644 --- a/doomsday/apps/client/src/clientapp.cpp +++ b/doomsday/apps/client/src/clientapp.cpp @@ -235,14 +235,14 @@ DENG2_PIMPL(ClientApp) } updater.reset(); - delete world; //delete infineSys; + delete inputSys; + delete resourceSys; delete winSys; - delete svLink; - delete rendSys; delete audioSys; - delete resourceSys; - delete inputSys; + delete rendSys; + delete world; + delete svLink; delete menuBar; clientAppSingleton = 0; } @@ -443,6 +443,10 @@ void ClientApp::initialize() } #endif + // Create the world system. + d->world = new ClientServerWorld; + addSystem(*d->world); + // Create the render system. d->rendSys = new RenderSystem; addSystem(*d->rendSys); @@ -477,10 +481,6 @@ void ClientApp::initialize() //d->infineSys = new InFineSystem; //addSystem(*d->infineSys); - // Create the world system. - d->world = new ClientServerWorld; - addSystem(*d->world); - // Finally, run the bootstrap script. scriptSystem().importModule("bootstrap"); diff --git a/doomsday/apps/client/src/render/environ.cpp b/doomsday/apps/client/src/render/environ.cpp new file mode 100644 index 0000000000..6a5d2f1851 --- /dev/null +++ b/doomsday/apps/client/src/render/environ.cpp @@ -0,0 +1,205 @@ +/** @file environ.cpp Environment rendering. + * + * @authors Copyright (c) 2015 Jaakko Keränen + * + * @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 "render/environ.h" +#include "world/map.h" +#include "world/bspleaf.h" +#include "world/convexsubspace.h" +#include "clientapp.h" + +#include +#include +#include + +using namespace de; + +static String const DEF_PATH ("path"); +static String const DEF_INTERIOR_PATH("interior.path"); +static String const DEF_EXTERIOR_PATH("exterior.path"); + +namespace render { + +DENG2_PIMPL(Environment) +, DENG2_OBSERVES(filesys::AssetObserver, Availability) +, DENG2_OBSERVES(World, MapChange) +{ + enum { Interior, Exterior }; + + filesys::AssetObserver observer { "texture\\.reflect\\..*" }; + LoopCallback mainCall; + + struct EnvMaps + { + Path interior; + Path exterior; + }; + QHash maps; + + /// Currently loaded reflection textures. + GLTexture reflectionTextures[2]; + + Instance(Public *i) : Base(i) + { + observer.audienceForAvailability() += this; + World::get().audienceForMapChange() += this; + + // Reflection cube maps use mipmapping for blurred reflections. + for(auto &tex : reflectionTextures) + { + tex.setMinFilter(gl::Linear, gl::MipLinear); + tex.setWrap(gl::ClampToEdge, gl::ClampToEdge); + } + } + + ~Instance() + { + World::get().audienceForMapChange() -= this; + + release(); + } + + void assetAvailabilityChanged(String const &identifier, + filesys::AssetObserver::Event event) + { + LOG_RES_MSG("Texture asset \"%s\" is now %s") + << identifier + << (event == filesys::AssetObserver::Added? "available" : + "unavailable"); + // Register available reflection maps. + String const mapId = identifier.substr(16).toLower(); + switch(event) + { + case filesys::AssetObserver::Added: + addMapsFromAsset(mapId, App::asset(identifier)); + break; + + case filesys::AssetObserver::Removed: + maps.remove(mapId); + break; + } + } + + void addMapsFromAsset(String const &mapId, Package::Asset const &asset) + { + EnvMaps env; + + if(asset.has(DEF_PATH)) + { + env.interior = env.exterior = asset.absolutePath(DEF_PATH); + } + if(asset.has(DEF_INTERIOR_PATH)) + { + env.interior = asset.absolutePath(DEF_INTERIOR_PATH); + } + if(asset.has(DEF_EXTERIOR_PATH)) + { + env.exterior = asset.absolutePath(DEF_EXTERIOR_PATH); + } + + // All done. + maps.insert(mapId, env); + } + + void release() + { + for(auto &tex : reflectionTextures) + { + tex.clear(); + } + } + + void loadCubeMap(GLTexture &tex, String const &path) + { + try + { + ImageFile const &imgFile = App::rootFolder().locate(path); + + LOG_GL_MSG("Loading reflection cube map %s") << imgFile.description(); + + Image img = imgFile.image(); + Image::Size const size(img.width() / 6, img.height()); + tex.setImage(gl::NegativeX, img.subImage(Rectanglei(0*size.x, 0, size.x, size.y))); + tex.setImage(gl::PositiveZ, img.subImage(Rectanglei(1*size.x, 0, size.x, size.y))); + tex.setImage(gl::PositiveX, img.subImage(Rectanglei(2*size.x, 0, size.x, size.y))); + tex.setImage(gl::NegativeZ, img.subImage(Rectanglei(3*size.x, 0, size.x, size.y))); + tex.setImage(gl::NegativeY, img.subImage(Rectanglei(4*size.x, 0, size.x, size.y))); + tex.setImage(gl::PositiveY, img.subImage(Rectanglei(5*size.x, 0, size.x, size.y))); + tex.generateMipmap(); + } + catch(Error const &er) + { + LOG_GL_WARNING("Failed to load reflection cube map from path \"%s\": %s") + << path << er.asText(); + } + } + + void worldMapChanged() + { + mainCall.enqueue([this] () { loadTexturesForCurrentMap(); }); + } + + void loadTexturesForCurrentMap() + { + DENG2_ASSERT_IN_MAIN_THREAD(); + + release(); + + String const mapId = ClientApp::world().map().id().toLower(); + + // Check which reflection maps are available for the new map. + auto found = maps.constFind(mapId); + if(found != maps.constEnd()) + { + EnvMaps const &env = found.value(); + DENG2_ASSERT(env.interior.isEmpty() && env.exterior.isEmpty()); + + if(!env.exterior.isEmpty()) + { + loadCubeMap(reflectionTextures[Exterior], env.exterior); + } + + loadCubeMap(reflectionTextures[Interior], env.interior); + } + } +}; + +Environment::Environment() + : d(new Instance(this)) +{} + +GLTexture const &Environment::defaultReflection() const +{ + return d->reflectionTextures[Instance::Interior]; +} + +GLTexture const &Environment::reflectionInBspLeaf(BspLeaf const *bspLeaf) const +{ + if(!bspLeaf || !bspLeaf->hasSubspace()) + { + return defaultReflection(); + } + + Sector const §or = bspLeaf->subspace().sector(); + if(sector.hasSkyMaskedPlane()) + { + return d->reflectionTextures[Instance::Exterior]; + } + return d->reflectionTextures[Instance::Interior]; +} + +} // namespace render diff --git a/doomsday/apps/client/src/render/modelrenderer.cpp b/doomsday/apps/client/src/render/modelrenderer.cpp index 6ca07bdd3f..f633226738 100644 --- a/doomsday/apps/client/src/render/modelrenderer.cpp +++ b/doomsday/apps/client/src/render/modelrenderer.cpp @@ -20,6 +20,7 @@ #include "render/stateanimator.h" #include "render/rend_main.h" #include "render/vissprite.h" +#include "render/environ.h" #include "gl/gl_main.h" #include "world/p_players.h" #include "world/clientmobjthinkerdata.h" @@ -31,7 +32,6 @@ #include #include #include -#include #include @@ -121,7 +121,6 @@ DENG2_PIMPL(ModelRenderer) GLUniform uFogRange { "uFogRange", GLUniform::Vec4 }; GLUniform uFogColor { "uFogColor", GLUniform::Vec4 }; - GLTexture reflectionCube; Matrix4f inverseLocal; ///< Translation ignored, this is used for light vectors. int lightCount = 0; @@ -136,43 +135,6 @@ DENG2_PIMPL(ModelRenderer) // The default shader is used whenever a model does not specifically // request another one. loadProgram(SHADER_DEFAULT); - - // Prepare a generic reflection cube map. -#if 1 - /// @todo Use assets for getting the cubemap. - String const cubePath = "/home/cubemap.jpg"; - if(App::rootFolder().has(cubePath)) - { - Image img = App::rootFolder().locate(cubePath).image(); - Image::Size size(img.width() / 6, img.height()); - reflectionCube.setMinFilter(gl::Linear, gl::MipLinear); - reflectionCube.setWrap(gl::ClampToEdge, gl::ClampToEdge); - reflectionCube.setImage(gl::NegativeX, img.subImage(Rectanglei(0*size.x, 0, size.x, size.y))); - reflectionCube.setImage(gl::PositiveZ, img.subImage(Rectanglei(1*size.x, 0, size.x, size.y))); - reflectionCube.setImage(gl::PositiveX, img.subImage(Rectanglei(2*size.x, 0, size.x, size.y))); - reflectionCube.setImage(gl::NegativeZ, img.subImage(Rectanglei(3*size.x, 0, size.x, size.y))); - reflectionCube.setImage(gl::NegativeY, img.subImage(Rectanglei(4*size.x, 0, size.x, size.y))); - reflectionCube.setImage(gl::PositiveY, img.subImage(Rectanglei(5*size.x, 0, size.x, size.y))); - reflectionCube.generateMipmap(); - } -#endif -#if 0 - QImage img(QSize(256, 256), QImage::Format_ARGB32); - img.fill(Qt::red); - reflectionCube.setImage(gl::NegativeX, img); - img.fill(Qt::green); - reflectionCube.setImage(gl::PositiveZ, img); - img.fill(Qt::magenta); - reflectionCube.setImage(gl::PositiveX, img); - img.fill(Qt::cyan); - reflectionCube.setImage(gl::NegativeZ, img); - img.fill(Qt::yellow); - reflectionCube.setImage(gl::NegativeY, img); - img.fill(Qt::blue); - reflectionCube.setImage(gl::PositiveY, img); -#endif - - uReflectionTex = reflectionCube; } void deinit() @@ -183,7 +145,7 @@ DENG2_PIMPL(ModelRenderer) atlasPool.clear(); unloadProgram(*programs[SHADER_DEFAULT]); - reflectionCube.clear(); + //reflectionCube.clear(); } Atlas *makeAtlas(MultiAtlas &) override @@ -719,6 +681,24 @@ DENG2_PIMPL(ModelRenderer) uEyePos = inverseLocal * relativeEyePos; } + void setReflectionForBspLeaf(BspLeaf const *bspLeaf) + { + uReflectionTex = ClientApp::renderSystem().environment(). + reflectionInBspLeaf(bspLeaf); + } + + void setReflectionForObject(mobj_t const *object) + { + if(object) + { + setReflectionForBspLeaf(&Mobj_BspLeafAtOrigin(*object)); + } + else + { + uReflectionTex = ClientApp::renderSystem().environment().defaultReflection(); + } + } + template // generic to accommodate psprites and vispsprites void draw(Params const &p) { @@ -773,6 +753,9 @@ void ModelRenderer::render(vissprite_t const &spr) auto const *mobjData = (p.object? &THINKER_DATA(p.object->thinker, ClientMobjThinkerData) : nullptr); + // Use the reflection cube map appropriate for the object's location. + d->setReflectionForObject(p.object); + d->setupPose((spr.pose.origin + spr.pose.srvo).xzy(), p.model->offset, spr.pose.viewAligned? spr.pose.yawAngleOffset : spr.pose.yaw, @@ -792,6 +775,8 @@ void ModelRenderer::render(vispsprite_t const &pspr) { auto const &p = pspr.data.model2; + d->setReflectionForBspLeaf(pspr.bspLeaf); + // Walk bobbing is specified using angle offsets. float yaw = vang + p.yawAngleOffset; float pitch = vpitch + p.pitchAngleOffset; diff --git a/doomsday/apps/client/src/render/rendersystem.cpp b/doomsday/apps/client/src/render/rendersystem.cpp index 4773fb1ccc..3d21752c65 100644 --- a/doomsday/apps/client/src/render/rendersystem.cpp +++ b/doomsday/apps/client/src/render/rendersystem.cpp @@ -26,6 +26,7 @@ #include #include #include "clientapp.h" +#include "render/environ.h" #include "render/rend_main.h" #include "render/rend_halo.h" #include "render/angleclipper.h" @@ -53,6 +54,7 @@ DENG2_PIMPL(RenderSystem) Binder binder; Record renderModule; + render::Environment environ; ModelRenderer models; SkyDrawable sky; SettingsRegister settings; @@ -420,6 +422,11 @@ GLUniform const &RenderSystem::uMapTime() const return d->uMapTime; } +render::Environment &RenderSystem::environment() +{ + return d->environ; +} + ModelRenderer &RenderSystem::modelRenderer() { return d->models; diff --git a/doomsday/apps/libdoomsday/include/doomsday/world/map.h b/doomsday/apps/libdoomsday/include/doomsday/world/map.h index a66b7ccaf1..2e7c6cfe1f 100644 --- a/doomsday/apps/libdoomsday/include/doomsday/world/map.h +++ b/doomsday/apps/libdoomsday/include/doomsday/world/map.h @@ -42,6 +42,8 @@ class LIBDOOMSDAY_PUBLIC Map explicit Map(res::MapManifest *manifest = nullptr); virtual ~Map(); + de::String id() const; + /** * Returns @c true if a resource manifest is associated with the map. * diff --git a/doomsday/apps/libdoomsday/src/world/map.cpp b/doomsday/apps/libdoomsday/src/world/map.cpp index a03b1db59a..2b2432c08c 100644 --- a/doomsday/apps/libdoomsday/src/world/map.cpp +++ b/doomsday/apps/libdoomsday/src/world/map.cpp @@ -36,7 +36,7 @@ DENG2_PIMPL(Map) if(manifest) manifest->audienceForDeletion() -= this; DENG2_FOR_PUBLIC_AUDIENCE2(Deletion, i) i->mapBeingDeleted(self); } - + void recordBeingDeleted(Record &record) { // The manifest is not owned by us, it may be deleted by others. @@ -59,6 +59,12 @@ Map::Map(res::MapManifest *manifest) : d(new Instance(this)) Map::~Map() {} +String Map::id() const +{ + if(!hasManifest()) return ""; + return manifest().gets("id"); +} + bool Map::hasManifest() const { return d->manifest != nullptr; @@ -80,7 +86,7 @@ void Map::setManifest(res::MapManifest *newManifest) if(d->manifest) d->manifest->audienceForDeletion() -= d; d->manifest = newManifest; - + if(d->manifest) d->manifest->audienceForDeletion() += d; }