diff --git a/doomsday/apps/client/include/client/clientsubsector.h b/doomsday/apps/client/include/client/clientsubsector.h index 6f7c1afaef..cc1eb4e206 100644 --- a/doomsday/apps/client/include/client/clientsubsector.h +++ b/doomsday/apps/client/include/client/clientsubsector.h @@ -119,6 +119,8 @@ class ClientSubsector : public Subsector, public de::LightGrid::IBlockLightSourc */ de::LoopResult forAllEdgeRings(std::function func) const; + void fixSurfacesMissingMaterials(); + //- Audio environment ------------------------------------------------------------------- /** diff --git a/doomsday/apps/client/include/world/line.h b/doomsday/apps/client/include/world/line.h index a326e15669..bc6d259390 100644 --- a/doomsday/apps/client/include/world/line.h +++ b/doomsday/apps/client/include/world/line.h @@ -359,12 +359,6 @@ class Line : public world::MapElement */ de::LoopResult forAllSurfaces(std::function func) const; - /** - * Do as in the original DOOM if the texture has not been defined - extend the - * floor/ceiling to fill the space (unless it is skymasked). - */ - void fixSurfacesMissingMaterials(); - /** * Update the tangent space normals of the side's surfaces according to the points * defined by the Line's vertices. If no Sections are defined this is a no-op. diff --git a/doomsday/apps/client/src/client/clientsubsector.cpp b/doomsday/apps/client/src/client/clientsubsector.cpp index c8042246bc..147b5a847a 100644 --- a/doomsday/apps/client/src/client/clientsubsector.cpp +++ b/doomsday/apps/client/src/client/clientsubsector.cpp @@ -20,9 +20,9 @@ #include "client/clientsubsector.h" -#include "Surface" -#include "world/blockmap.h" #include "world/map.h" +#include "world/maputil.h" +#include "world/blockmap.h" #include "world/p_object.h" #include "world/p_players.h" #include "world/surface.h" @@ -37,8 +37,13 @@ #include "WallEdge" #include "Face" +#include "dd_main.h" // verbose + +#include +#include +#include +#include -#include #include #include #include @@ -1173,15 +1178,223 @@ DENG2_PIMPL(ClientSubsector) } } + static bool materialHasAnimatedTextureLayers(Material const &mat) + { + for (dint i = 0; i < mat.layerCount(); ++i) + { + MaterialLayer const &layer = mat.layer(i); + if (!layer.is() && !layer.is()) + { + if(layer.isAnimated()) return true; + } + } + return false; + } + /** - * @todo Optimize: Target and process only the dependent surfaces -ds + * Given a side section, look at the neighbouring surfaces and pick the best choice of + * material used on those surfaces to be applied to "this" surface. + * + * Material on back neighbour plane has priority. Non-animated materials are preferred. + * Sky materials are ignored. + */ + static Material *chooseFixMaterial(LineSide &side, dint section) + { + Material *choice1 = nullptr, *choice2 = nullptr; + + Sector *frontSec = side.sectorPtr(); + Sector *backSec = side.back().sectorPtr(); + + if (backSec) + { + // Our first choice is a material in the other sector. + if (section == LineSide::Bottom) + { + if (frontSec->floor().height() < backSec->floor().height()) + { + choice1 = backSec->floor().surface().materialPtr(); + } + } + else if (section == LineSide::Top) + { + if (frontSec->ceiling().height() > backSec->ceiling().height()) + { + choice1 = backSec->ceiling().surface().materialPtr(); + } + } + + // In the special case of sky mask on the back plane, our best + // choice is always this material. + if (choice1 && choice1->isSkyMasked()) + { + return choice1; + } + } + else + { + // Our first choice is a material on an adjacent wall section. + // Try the left neighbor first. + Line *other = R_FindLineNeighbor(side.line(), *side.line().vertexOwner(side.sideId()), + Clockwise, frontSec); + if (!other) + { + // Try the right neighbor. + other = R_FindLineNeighbor(side.line(), *side.line().vertexOwner(side.sideId()^1), + Anticlockwise, frontSec); + } + + if (other) + { + if (!other->back().hasSector()) + { + // Our choice is clear - the middle material. + choice1 = other->front().middle().materialPtr(); + } + else + { + // Compare the relative heights to decide. + LineSide &otherSide = other->side(&other->front().sector() == frontSec ? Line::Front : Line::Back); + Sector &otherSec = other->side(&other->front().sector() == frontSec ? Line::Back : Line::Front).sector(); + + if (otherSec.ceiling().height() <= frontSec->floor().height()) + choice1 = otherSide.top().materialPtr(); + else if (otherSec.floor().height() >= frontSec->ceiling().height()) + choice1 = otherSide.bottom().materialPtr(); + else if (otherSec.ceiling().height() < frontSec->ceiling().height()) + choice1 = otherSide.top().materialPtr(); + else if (otherSec.floor().height() > frontSec->floor().height()) + choice1 = otherSide.bottom().materialPtr(); + // else we'll settle for a plane material. + } + } + } + + // Our second choice is a material from this sector. + choice2 = frontSec->plane(section == LineSide::Bottom ? Sector::Floor : Sector::Ceiling) + .surface().materialPtr(); + + // Prefer a non-animated, non-masked material. + if (choice1 && !materialHasAnimatedTextureLayers(*choice1) && !choice1->isSkyMasked()) + return choice1; + if (choice2 && !materialHasAnimatedTextureLayers(*choice2) && !choice2->isSkyMasked()) + return choice2; + + // Prefer a non-masked material. + if (choice1 && !choice1->isSkyMasked()) + return choice1; + if (choice2 && !choice2->isSkyMasked()) + return choice2; + + // At this point we'll accept anything if it means avoiding HOM. + if (choice1) return choice1; + if (choice2) return choice2; + + // We'll assign the special "missing" material... + return &Materials::get().material(de::Uri("System", Path("missing"))); + } + + static void addMissingMaterial(LineSide &side, dint section) + { + // Sides without sections need no fixing. + if (!side.hasSections()) return; + // ...nor those of self-referencing lines. + if (side.line().isSelfReferencing()) return; + // ...nor those of "one-way window" lines. + if (!side.back().hasSections() && side.back().hasSector()) return; + + // A material must actually be missing to qualify for fixing. + Surface &surface = side.surface(section); + if (surface.hasMaterial() && !surface.hasFixMaterial()) + return; + + Material *oldMaterial = surface.materialPtr(); + + // Look for and apply a suitable replacement (if found). + surface.setMaterial(chooseFixMaterial(side, section), true/* is missing fix */); + + if (oldMaterial == surface.materialPtr()) + return; + + // We'll need to recalculate reverb. + if (HEdge *hedge = side.leftHEdge()) + { + if (hedge->hasFace() && hedge->face().hasMapElement()) + { + auto &subsec = hedge->face().mapElementAs() + .subsector().as(); + subsec.markReverbDirty(); + subsec.markVisPlanesDirty(); + } + } + + // During map setup we log missing materials. + if (::ddMapSetup && ::verbose) + { + String const surfaceMaterialPath = surface.hasMaterial() ? surface.material().manifest().composeUri().asText() : ""; + + LOG_WARNING( "%s of Line #%d is missing a material for the %s section." + "\n %s was chosen to complete the definition.") + << Line::sideIdAsText(side.sideId()).upperFirstChar() + << side.line().indexInMap() + << LineSide::sectionIdAsText(section) + << surfaceMaterialPath; + } + } + + /** + * Do as in the original DOOM if the texture has not been defined - extend the + * floor/ceiling to fill the space (unless it is skymasked). + * + * @todo Optimize: Process only the mapping-affected surfaces -ds */ void fixSurfacesMissingMaterials() { - self.sector().forAllSides([] (LineSide &side) + self.forAllEdgeRings([this] (EdgeRing const &ring) { - side.fixSurfacesMissingMaterials(); - side.back().fixSurfacesMissingMaterials(); + ClientSubsector const &frontSubsec = ring.owner(); + SubsectorCirculator it(&ring.firstHEdge()); + do + { + if (it->hasMapElement()) // BSP errors may fool the circulator wrt interior edges -ds + { + LineSide &side = it->mapElementAs().lineSide(); + if (ring.hasBackSubsector()) + { + auto const &backSubsec = ring.backSubsector().as(); + + // A potential bottom section fix? + if (!(frontSubsec.hasSkyFloor() && backSubsec.hasSkyFloor())) + { + if (frontSubsec.visFloor().height() < backSubsec.visFloor().height()) + { + addMissingMaterial(side, LineSide::Bottom); + } + else if (side.bottom().hasFixMaterial()) + { + side.bottom().setMaterial(0); + } + } + + // A potential top section fix? + if (!(frontSubsec.hasSkyCeiling() && backSubsec.hasSkyCeiling())) + { + if (frontSubsec.visCeiling().height() > backSubsec.visCeiling().height()) + { + addMissingMaterial(side, LineSide::Top); + } + else if (side.top().hasFixMaterial()) + { + side.top().setMaterial(0); + } + } + } + else if (!side.back().hasSector()) + { + // A potential middle section fix. + addMissingMaterial(side, LineSide::Middle); + } + } + } while (&it.next() != &ring.firstHEdge()); return LoopContinue; }); } @@ -1507,6 +1720,11 @@ LoopResult ClientSubsector::forAllEdgeRings(std::functionfixSurfacesMissingMaterials(); +} + bool ClientSubsector::hasSkyPlane(dint planeIndex) const { if (planeIndex < 0) diff --git a/doomsday/apps/client/src/dd_main.cpp b/doomsday/apps/client/src/dd_main.cpp index a12ce48991..4de54f686a 100644 --- a/doomsday/apps/client/src/dd_main.cpp +++ b/doomsday/apps/client/src/dd_main.cpp @@ -94,6 +94,9 @@ #include "world/clientserverworld.h" #include "world/map.h" #include "world/p_players.h" +# ifdef __CLIENT__ +# include "client/clientsubsector.h" +#endif #include "ui/infine/infinesystem.h" #include "ui/nativeui.h" @@ -2370,9 +2373,9 @@ DENG_EXTERN_C void R_SetupMap(dint mode, dint flags) /// @todo Refactor away. map.forAllSectors([] (Sector §or) { - sector.forAllSides([] (LineSide &side) + sector.forAllSubsectors([] (world::Subsector &subsec) { - side.fixSurfacesMissingMaterials(); + subsec.as().fixSurfacesMissingMaterials(); return LoopContinue; }); return LoopContinue; diff --git a/doomsday/apps/client/src/world/base/clientserverworld.cpp b/doomsday/apps/client/src/world/base/clientserverworld.cpp index 057fac405c..81f992c640 100644 --- a/doomsday/apps/client/src/world/base/clientserverworld.cpp +++ b/doomsday/apps/client/src/world/base/clientserverworld.cpp @@ -588,9 +588,9 @@ DENG2_PIMPL(ClientServerWorld) /// @todo Refactor away: map->forAllSectors([] (Sector §or) { - sector.forAllSides([] (LineSide &side) + sector.forAllSubsectors([] (Subsector &subsec) { - side.fixSurfacesMissingMaterials(); + subsec.as().fixSurfacesMissingMaterials(); return LoopContinue; }); return LoopContinue; diff --git a/doomsday/apps/client/src/world/base/line.cpp b/doomsday/apps/client/src/world/base/line.cpp index b226d1783c..c0883311d8 100644 --- a/doomsday/apps/client/src/world/base/line.cpp +++ b/doomsday/apps/client/src/world/base/line.cpp @@ -20,32 +20,26 @@ #include "world/line.h" -#include "dd_main.h" // App_Materials(), verbose - -#include "Face" -#include "HEdge" - #include "world/map.h" -#include "ConvexSubspace" -#include "Sector" -#include "Subsector" -#include "Surface" -#include "Vertex" -#include "world/maputil.h" +#include "world/maputil.h" // R_FindLineNeighbor()... +#include "world/convexsubspace.h" +#include "world/sector.h" +#include "world/surface.h" +#include "world/vertex.h" #ifdef __CLIENT__ # include "world/lineowner.h" # include "render/r_main.h" // levelFullBright # include "render/rend_fakeradio.h" - -# include "client/clientsubsector.h" #endif +#include "Face" +#include "HEdge" +#include "dd_main.h" // App_World() + #include -#include -#include -#include #include + #include #include #include @@ -679,211 +673,6 @@ void Line::Side::setShadowVisCount(dint newCount) #ifdef __CLIENT__ -static bool materialHasAnimatedTextureLayers(Material const &mat) -{ - for (dint i = 0; i < mat.layerCount(); ++i) - { - MaterialLayer const &layer = mat.layer(i); - if (!layer.is() && !layer.is()) - { - if(layer.isAnimated()) return true; - } - } - return false; -} - -/** - * Given a side section, look at the neighbouring surfaces and pick the best choice of material - * used on those surfaces to be applied to "this" surface. - * - * Material on back neighbour plane has priority. Non-animated materials are preferred. Sky - * materials are ignored. - */ -static Material *chooseFixMaterial(LineSide &side, dint section) -{ - Material *choice1 = nullptr, *choice2 = nullptr; - - Sector *frontSec = side.sectorPtr(); - Sector *backSec = side.back().sectorPtr(); - - if (backSec) - { - // Our first choice is a material in the other sector. - if (section == LineSide::Bottom) - { - if (frontSec->floor().height() < backSec->floor().height()) - { - choice1 = backSec->floor().surface().materialPtr(); - } - } - else if (section == LineSide::Top) - { - if (frontSec->ceiling().height() > backSec->ceiling().height()) - { - choice1 = backSec->ceiling().surface().materialPtr(); - } - } - - // In the special case of sky mask on the back plane, our best - // choice is always this material. - if (choice1 && choice1->isSkyMasked()) - { - return choice1; - } - } - else - { - // Our first choice is a material on an adjacent wall section. - // Try the left neighbor first. - Line *other = R_FindLineNeighbor(side.line(), *side.line().vertexOwner(side.sideId()), - Clockwise, frontSec); - if (!other) - { - // Try the right neighbor. - other = R_FindLineNeighbor(side.line(), *side.line().vertexOwner(side.sideId()^1), - Anticlockwise, frontSec); - } - - if (other) - { - if (!other->back().hasSector()) - { - // Our choice is clear - the middle material. - choice1 = other->front().middle().materialPtr(); - } - else - { - // Compare the relative heights to decide. - LineSide &otherSide = other->side(&other->front().sector() == frontSec ? Line::Front : Line::Back); - Sector &otherSec = other->side(&other->front().sector() == frontSec ? Line::Back : Line::Front).sector(); - - if (otherSec.ceiling().height() <= frontSec->floor().height()) - choice1 = otherSide.top().materialPtr(); - else if (otherSec.floor().height() >= frontSec->ceiling().height()) - choice1 = otherSide.bottom().materialPtr(); - else if (otherSec.ceiling().height() < frontSec->ceiling().height()) - choice1 = otherSide.top().materialPtr(); - else if (otherSec.floor().height() > frontSec->floor().height()) - choice1 = otherSide.bottom().materialPtr(); - // else we'll settle for a plane material. - } - } - } - - // Our second choice is a material from this sector. - choice2 = frontSec->plane(section == LineSide::Bottom ? Sector::Floor : Sector::Ceiling) - .surface().materialPtr(); - - // Prefer a non-animated, non-masked material. - if (choice1 && !materialHasAnimatedTextureLayers(*choice1) && !choice1->isSkyMasked()) - return choice1; - if (choice2 && !materialHasAnimatedTextureLayers(*choice2) && !choice2->isSkyMasked()) - return choice2; - - // Prefer a non-masked material. - if (choice1 && !choice1->isSkyMasked()) - return choice1; - if (choice2 && !choice2->isSkyMasked()) - return choice2; - - // At this point we'll accept anything if it means avoiding HOM. - if (choice1) return choice1; - if (choice2) return choice2; - - // We'll assign the special "missing" material... - return &world::Materials::get().material(de::Uri("System", Path("missing"))); -} - -static void addMissingMaterial(LineSide &side, dint section) -{ - // Sides without sections need no fixing. - if (!side.hasSections()) return; - // ...nor those of self-referencing lines. - if (side.line().isSelfReferencing()) return; - // ...nor those of "one-way window" lines. - if (!side.back().hasSections() && side.back().hasSector()) return; - - // A material must actually be missing to qualify for fixing. - Surface &surface = side.surface(section); - if (surface.hasMaterial() && !surface.hasFixMaterial()) - return; - - Material *oldMaterial = surface.materialPtr(); - - // Look for and apply a suitable replacement (if found). - surface.setMaterial(chooseFixMaterial(side, section), true/* is missing fix */); - - if (oldMaterial == surface.materialPtr()) - return; - - // We'll need to recalculate reverb. - if (HEdge *hedge = side.leftHEdge()) - { - if (hedge->hasFace() && hedge->face().hasMapElement()) - { - auto &subsec = hedge->face().mapElementAs() - .subsector().as(); - subsec.markReverbDirty(); - subsec.markVisPlanesDirty(); - } - } - - // During map setup we log missing materials. - if (ddMapSetup && verbose) - { - String const surfaceMaterialPath = surface.hasMaterial() ? surface.material().manifest().composeUri().asText() : ""; - - LOG_WARNING( "%s of Line #%d is missing a material for the %s section." - "\n %s was chosen to complete the definition.") - << Line::sideIdAsText(side.sideId()).upperFirstChar() - << side.line().indexInMap() - << LineSide::sectionIdAsText(section) - << surfaceMaterialPath; - } -} - -void Line::Side::fixSurfacesMissingMaterials() -{ - if (hasSector() && back().hasSector()) - { - Sector const &frontSec = sector(); - Sector const &backSec = back().sector(); - - // A potential bottom section fix? - if (!(frontSec.floor().surface().hasSkyMaskedMaterial() && - backSec .floor().surface().hasSkyMaskedMaterial())) - { - if (frontSec.floor().height() < backSec.floor().height()) - { - addMissingMaterial(*this, LineSide::Bottom); - } - else if (bottom().hasFixMaterial()) - { - bottom().setMaterial(0); - } - } - - // A potential top section fix? - if (!(frontSec.ceiling().surface().hasSkyMaskedMaterial() && - backSec .ceiling().surface().hasSkyMaskedMaterial())) - { - if (backSec.ceiling().height() < frontSec.ceiling().height()) - { - addMissingMaterial(*this, LineSide::Top); - } - else if (top().hasFixMaterial()) - { - top().setMaterial(0); - } - } - } - else - { - // A potential middle section fix. - addMissingMaterial(*this, LineSide::Middle); - } -} - shadowcorner_t const &Line::Side::radioCornerTop(bool right) const { return d->radioData.topCorners[dint(right)];