diff --git a/doomsday/client/include/world/map.h b/doomsday/client/include/world/map.h index f6dfa627fc..f5de187c97 100644 --- a/doomsday/client/include/world/map.h +++ b/doomsday/client/include/world/map.h @@ -827,11 +827,6 @@ class Map */ void initBias(); - /** - * @todo Replace with a de::Observers-based mechanism. - */ - void updateMissingMaterialsForLinesOfSector(Sector const &sec); - #endif // __CLIENT__ public: diff --git a/doomsday/client/include/world/sector.h b/doomsday/client/include/world/sector.h index 0675321066..de70e52bc0 100644 --- a/doomsday/client/include/world/sector.h +++ b/doomsday/client/include/world/sector.h @@ -513,6 +513,11 @@ class Sector : public de::MapElement */ void markVisible(bool yes = true); + /** + * Perform missing material fixes again for all line sides in the sector. + */ + void fixMissingMaterialsForSides(); + #endif // __CLIENT__ protected: diff --git a/doomsday/client/src/dd_main.cpp b/doomsday/client/src/dd_main.cpp index 2ecfecd75e..cfd40e0a19 100644 --- a/doomsday/client/src/dd_main.cpp +++ b/doomsday/client/src/dd_main.cpp @@ -3026,7 +3026,7 @@ DENG_EXTERN_C void R_SetupMap(int mode, int flags) /// @todo Refactor away. foreach(Sector *sector, map.sectors()) { - map.updateMissingMaterialsForLinesOfSector(*sector); + sector->fixMissingMaterialsForSides(); sector->markReverbDirty(); } #endif diff --git a/doomsday/client/src/world/map.cpp b/doomsday/client/src/world/map.cpp index 0f2c7a1bc0..0d211768f3 100644 --- a/doomsday/client/src/world/map.cpp +++ b/doomsday/client/src/world/map.cpp @@ -2760,186 +2760,6 @@ uint Map::biasLastChangeOnFrame() const return d->bias.lastChangeOnFrame; } -/** - * 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(Line::Side &side, int section) -{ - Material *choice1 = 0, *choice2 = 0; - - Sector *frontSec = side.sectorPtr(); - Sector *backSec = side.back().sectorPtr(); - - if(backSec) - { - // Our first choice is a material in the other sector. - if(section == Line::Side::Bottom) - { - if(frontSec->floor().height() < backSec->floor().height()) - { - choice1 = backSec->floorSurface().materialPtr(); - } - } - else if(section == Line::Side::Top) - { - if(frontSec->ceiling().height() > backSec->ceiling().height()) - { - choice1 = backSec->ceilingSurface().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(frontSec, &side.line(), side.line().vertexOwner(side.sideId()), - false /*next clockwise*/); - if(!other) - // Try the right neighbor. - other = R_FindLineNeighbor(frontSec, &side.line(), side.line().vertexOwner(side.sideId()^1), - true /*next anti-clockwise*/); - - if(other) - { - if(!other->hasBackSector()) - { - // Our choice is clear - the middle material. - choice1 = other->front().middle().materialPtr(); - } - else - { - // Compare the relative heights to decide. - Line::Side &otherSide = other->side(&other->frontSector() == frontSec? Line::Front : Line::Back); - Sector &otherSec = other->side(&other->frontSector() == 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->planeSurface(section == Line::Side::Bottom? Sector::Floor : Sector::Ceiling).materialPtr(); - - // Prefer a non-animated, non-masked material. - if(choice1 && !choice1->isAnimated() && !choice1->isSkyMasked()) - return choice1; - if(choice2 && !choice2->isAnimated() && !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 &App_Materials().find(de::Uri("System", Path("missing"))).material(); -} - -static void addMissingMaterial(Line::Side &side, int 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()) return; - - // Look for and apply a suitable replacement if found. - surface.setMaterial(chooseFixMaterial(side, section), true/* is missing fix */); - - // During map setup we log missing materials. - if(ddMapSetup && verbose) - { - String path = 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.") - << (side.isBack()? "Back" : "Front") << side.line().indexInMap() - << (section == Line::Side::Middle? "middle" : section == Line::Side::Top? "top" : "bottom") - << path; - } -} - -void Map::updateMissingMaterialsForLinesOfSector(Sector const &sec) -{ - foreach(Line::Side *side, sec.sides()) - { - /** - * 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), - * or if there is a midtexture use that instead. - */ - if(side->hasSector() && side->back().hasSector()) - { - Sector const &frontSec = side->sector(); - Sector const &backSec = side->back().sector(); - - // A potential bottom section fix? - if(!(frontSec.floorSurface().hasSkyMaskedMaterial() && - backSec.floorSurface().hasSkyMaskedMaterial())) - { - if(frontSec.floor().height() < backSec.floor().height()) - { - addMissingMaterial(*side, Line::Side::Bottom); - } - else if(frontSec.floor().height() > backSec.floor().height()) - { - addMissingMaterial(side->back(), Line::Side::Bottom); - } - } - - // A potential top section fix? - if(!(frontSec.ceilingSurface().hasSkyMaskedMaterial() && - backSec.ceilingSurface().hasSkyMaskedMaterial())) - { - if(backSec.ceiling().height() < frontSec.ceiling().height()) - { - addMissingMaterial(*side, Line::Side::Top); - } - else if(backSec.ceiling().height() > frontSec.ceiling().height()) - { - addMissingMaterial(side->back(), Line::Side::Top); - } - } - } - else - { - // A potential middle section fix. - addMissingMaterial(*side, Line::Side::Middle); - } - } -} - #endif // __CLIENT__ void Map::update() diff --git a/doomsday/client/src/world/sector.cpp b/doomsday/client/src/world/sector.cpp index dfc334736f..4e88cccce9 100644 --- a/doomsday/client/src/world/sector.cpp +++ b/doomsday/client/src/world/sector.cpp @@ -23,12 +23,15 @@ #include +#include "dd_main.h" // App_Materials(), verbose + #include "Face" #include "BspLeaf" #include "Line" #include "Plane" #include "world/map.h" +#include "world/maputil.h" #include "world/p_object.h" #include "world/p_players.h" @@ -363,8 +366,9 @@ DENG2_OBSERVES(Plane, HeightChange) } #ifdef __CLIENT__ - /// @todo Sector should implement this logic. - self.map().updateMissingMaterialsForLinesOfSector(self); + // A plane move means we must re-apply missing material fixes. + /// @todo optimize: Defer until actually necessary. + self.fixMissingMaterialsForSides(); // We'll need to recalculate environmental audio characteristics. needReverbUpdate = true; @@ -701,6 +705,186 @@ void Sector::markVisible(bool yes) d->visible = yes; } +/** + * 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(Line::Side &side, int section) +{ + Material *choice1 = 0, *choice2 = 0; + + Sector *frontSec = side.sectorPtr(); + Sector *backSec = side.back().sectorPtr(); + + if(backSec) + { + // Our first choice is a material in the other sector. + if(section == Line::Side::Bottom) + { + if(frontSec->floor().height() < backSec->floor().height()) + { + choice1 = backSec->floorSurface().materialPtr(); + } + } + else if(section == Line::Side::Top) + { + if(frontSec->ceiling().height() > backSec->ceiling().height()) + { + choice1 = backSec->ceilingSurface().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(frontSec, &side.line(), side.line().vertexOwner(side.sideId()), + false /*next clockwise*/); + if(!other) + // Try the right neighbor. + other = R_FindLineNeighbor(frontSec, &side.line(), side.line().vertexOwner(side.sideId()^1), + true /*next anti-clockwise*/); + + if(other) + { + if(!other->hasBackSector()) + { + // Our choice is clear - the middle material. + choice1 = other->front().middle().materialPtr(); + } + else + { + // Compare the relative heights to decide. + Line::Side &otherSide = other->side(&other->frontSector() == frontSec? Line::Front : Line::Back); + Sector &otherSec = other->side(&other->frontSector() == 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->planeSurface(section == Line::Side::Bottom? Sector::Floor : Sector::Ceiling).materialPtr(); + + // Prefer a non-animated, non-masked material. + if(choice1 && !choice1->isAnimated() && !choice1->isSkyMasked()) + return choice1; + if(choice2 && !choice2->isAnimated() && !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 &App_Materials().find(de::Uri("System", Path("missing"))).material(); +} + +static void addMissingMaterial(Line::Side &side, int 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()) return; + + // Look for and apply a suitable replacement if found. + surface.setMaterial(chooseFixMaterial(side, section), true/* is missing fix */); + + // During map setup we log missing materials. + if(ddMapSetup && verbose) + { + String path = 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.") + << (side.isBack()? "Back" : "Front") << side.line().indexInMap() + << (section == Line::Side::Middle? "middle" : section == Line::Side::Top? "top" : "bottom") + << path; + } +} + +void Sector::fixMissingMaterialsForSides() +{ + foreach(Line::Side *side, d->sides) + { + /* + * 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), + * or if there is a midtexture use that instead. + */ + if(side->hasSector() && side->back().hasSector()) + { + Sector const &frontSec = side->sector(); + Sector const &backSec = side->back().sector(); + + // A potential bottom section fix? + if(!(frontSec.floorSurface().hasSkyMaskedMaterial() && + backSec.floorSurface().hasSkyMaskedMaterial())) + { + if(frontSec.floor().height() < backSec.floor().height()) + { + addMissingMaterial(*side, Line::Side::Bottom); + } + /*else if(frontSec.floor().height() > backSec.floor().height()) + { + addMissingMaterial(side->back(), Line::Side::Bottom); + }*/ + } + + // A potential top section fix? + if(!(frontSec.ceilingSurface().hasSkyMaskedMaterial() && + backSec.ceilingSurface().hasSkyMaskedMaterial())) + { + if(backSec.ceiling().height() < frontSec.ceiling().height()) + { + addMissingMaterial(*side, Line::Side::Top); + } + /*else if(backSec.ceiling().height() > frontSec.ceiling().height()) + { + addMissingMaterial(side->back(), Line::Side::Top); + }*/ + } + } + else + { + // A potential middle section fix. + addMissingMaterial(*side, Line::Side::Middle); + } + } +} + #endif // __CLIENT__ int Sector::property(DmuArgs &args) const diff --git a/doomsday/client/src/world/world.cpp b/doomsday/client/src/world/world.cpp index 0ec55dd8bf..6f4a537da5 100644 --- a/doomsday/client/src/world/world.cpp +++ b/doomsday/client/src/world/world.cpp @@ -609,7 +609,7 @@ DENG2_PIMPL(World) /// @todo Refactor away: foreach(Sector *sector, map->sectors()) { - map->updateMissingMaterialsForLinesOfSector(*sector); + sector->fixMissingMaterialsForSides(); sector->markReverbDirty(); } #endif