1 /** @file maputil.cpp  World map utilities.
2  *
3  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2006-2014 Daniel Swanson <danij@dengine.net>
5  *
6  * @par License
7  * GPL: http://www.gnu.org/licenses/gpl.html
8  *
9  * <small>This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by the
11  * Free Software Foundation; either version 2 of the License, or (at your
12  * option) any later version. This program is distributed in the hope that it
13  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15  * Public License for more details. You should have received a copy of the GNU
16  * General Public License along with this program; if not, write to the Free
17  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18  * 02110-1301 USA</small>
19  */
20 
21 #include "world/maputil.h"
22 
23 #include "Line"
24 #include "Plane"
25 #include "Sector"
26 
27 #ifdef __CLIENT__
28 #  include "Surface"
29 #  include "world/lineowner.h"
30 
31 #  include "MaterialVariantSpec"
32 
33 #  include "render/rend_main.h" // Rend_MapSurfacematerialSpec
34 #  include "MaterialAnimator"
35 #  include "WallEdge"
36 #endif
37 
38 using namespace de;
39 
lineopening_s(Line const & line)40 lineopening_s::lineopening_s(Line const &line)
41 {
42     if(!line.back().hasSector())
43     {
44         top = bottom = range = lowFloor = 0;
45         return;
46     }
47 
48     Sector const *frontSector = line.front().sectorPtr();
49     Sector const *backSector  = line.back().sectorPtr();
50     DENG_ASSERT(frontSector != 0);
51 
52     if(backSector && backSector->ceiling().height() < frontSector->ceiling().height())
53     {
54         top = backSector->ceiling().height();
55     }
56     else
57     {
58         top = frontSector->ceiling().height();
59     }
60 
61     if(backSector && backSector->floor().height() > frontSector->floor().height())
62     {
63         bottom = backSector->floor().height();
64     }
65     else
66     {
67         bottom = frontSector->floor().height();
68     }
69 
70     range = top - bottom;
71 
72     // Determine the "low floor".
73     if(backSector && frontSector->floor().height() > backSector->floor().height())
74     {
75         lowFloor = float( backSector->floor().height() );
76     }
77     else
78     {
79         lowFloor = float( frontSector->floor().height() );
80     }
81 }
82 
operator =(lineopening_s const & other)83 lineopening_s &lineopening_s::operator = (lineopening_s const &other)
84 {
85     top      = other.top;
86     bottom   = other.bottom;
87     range    = other.range;
88     lowFloor = other.lowFloor;
89     return *this;
90 }
91 
92 #ifdef __CLIENT__
93 
94 /**
95  * Same as @ref R_OpenRange() but works with the "visual" (i.e., smoothed) plane
96  * height coordinates rather than the "sharp" coordinates.
97  *
98  * @param side      Line side to find the open range for.
99  *
100  * Return values:
101  * @param bottom    Bottom Z height is written here. Can be @c nullptr.
102  * @param top       Top Z height is written here. Can be @c nullptr.
103  *
104  * @return Height of the open range.
105  *
106  * @todo fixme: Should use the visual plane heights of subsectors.
107  */
visOpenRange(LineSide const & side,coord_t * retBottom=nullptr,coord_t * retTop=nullptr)108 static coord_t visOpenRange(LineSide const &side, coord_t *retBottom = nullptr, coord_t *retTop = nullptr)
109 {
110     Sector const *frontSec = side.sectorPtr();
111     Sector const *backSec  = side.back().sectorPtr();
112 
113     coord_t bottom;
114     if(backSec && backSec->floor().heightSmoothed() > frontSec->floor().heightSmoothed())
115     {
116         bottom = backSec->floor().heightSmoothed();
117     }
118     else
119     {
120         bottom = frontSec->floor().heightSmoothed();
121     }
122 
123     coord_t top;
124     if(backSec && backSec->ceiling().heightSmoothed() < frontSec->ceiling().heightSmoothed())
125     {
126         top = backSec->ceiling().heightSmoothed();
127     }
128     else
129     {
130         top = frontSec->ceiling().heightSmoothed();
131     }
132 
133     if(retBottom) *retBottom = bottom;
134     if(retTop)    *retTop    = top;
135 
136     return top - bottom;
137 }
138 
139 /// @todo fixme: Should use the visual plane heights of subsectors.
R_SideBackClosed(LineSide const & side,bool ignoreOpacity)140 bool R_SideBackClosed(LineSide const &side, bool ignoreOpacity)
141 {
142     if(!side.hasSections()) return false;
143     if(!side.hasSector()) return false;
144     if(side.line().isSelfReferencing()) return false; // Never.
145 
146     if(side.considerOneSided()) return true;
147 
148     Sector const &frontSec = side.sector();
149     Sector const &backSec  = side.back().sector();
150 
151     if(backSec.floor().heightSmoothed()   >= backSec.ceiling().heightSmoothed())  return true;
152     if(backSec.ceiling().heightSmoothed() <= frontSec.floor().heightSmoothed())   return true;
153     if(backSec.floor().heightSmoothed()   >= frontSec.ceiling().heightSmoothed()) return true;
154 
155     // Perhaps a middle material completely covers the opening?
156     //if(side.middle().hasMaterial())
157 
158     if (MaterialAnimator *matAnimator = side.middle().materialAnimator())
159     {
160         //MaterialAnimator &matAnimator = static_cast<ClientMaterial &>(side.middle().material())
161         //        .getAnimator(Rend_MapSurfaceMaterialSpec());
162 
163         // Ensure we have up to date info about the material.
164         matAnimator->prepare();
165 
166         if(ignoreOpacity || (matAnimator->isOpaque() && !side.middle().blendMode() && side.middle().opacity() >= 1))
167         {
168             // Stretched middles always cover the opening.
169             if(side.isFlagged(SDF_MIDDLE_STRETCH))
170                 return true;
171 
172             if(side.leftHEdge()) // possibility of degenerate BSP leaf
173             {
174                 coord_t openRange, openBottom, openTop;
175                 openRange = visOpenRange(side, &openBottom, &openTop);
176                 if(matAnimator->dimensions().y >= openRange)
177                 {
178                     // Possibly; check the placement.
179                     WallEdge edge(WallSpec::fromMapSide(side, LineSide::Middle),
180                                   *side.leftHEdge(), Line::From);
181 
182                     return (edge.isValid() && edge.top().z() > edge.bottom().z()
183                             && edge.top().z() >= openTop && edge.bottom().z() <= openBottom);
184                 }
185             }
186         }
187     }
188 
189     return false;
190 }
191 
R_FindLineNeighbor(Line const & line,LineOwner const & own,ClockDirection direction,Sector const * sector,binangle_t * diff)192 Line *R_FindLineNeighbor(Line const &line, LineOwner const &own, ClockDirection direction,
193     Sector const *sector, binangle_t *diff)
194 {
195     LineOwner const *cown = (direction == Anticlockwise ? own.prev() : own.next());
196     Line *other = &cown->line();
197 
198     if(other == &line)
199         return nullptr;
200 
201     if(diff)
202     {
203         *diff += (direction == Anticlockwise ? cown->angle() : own.angle());
204     }
205 
206     if(!other->back().hasSector() || !other->isSelfReferencing())
207     {
208         if(sector)  // Must one of the sectors match?
209         {
210             if(other->front().sectorPtr() == sector ||
211                (other->back().hasSector() && other->back().sectorPtr() == sector))
212                 return other;
213         }
214         else
215         {
216             return other;
217         }
218     }
219 
220     // Not suitable, try the next.
221     DENG2_ASSERT(cown);
222     return R_FindLineNeighbor(line, *cown, direction, sector, diff);
223 }
224 
225 /**
226  * @param side  Line side for which to determine covered opening status.
227  *
228  * @return  @c true iff there is a "middle" material on @a side which
229  * completely covers the open range.
230  *
231  * @todo fixme: Should use the visual plane heights of subsectors.
232  */
middleMaterialCoversOpening(LineSide const & side)233 static bool middleMaterialCoversOpening(LineSide const &side)
234 {
235     if(!side.hasSector()) return false; // Never.
236 
237     if(!side.hasSections()) return false;
238     //if(!side.middle().hasMaterial()) return false;
239 
240     // Stretched middles always cover the opening.
241     if(side.isFlagged(SDF_MIDDLE_STRETCH))
242         return true;
243 
244     MaterialAnimator *matAnimator = side.middle().materialAnimator();
245             //.as<ClientMaterial>().getAnimator(Rend_MapSurfaceMaterialSpec());
246     if (!matAnimator) return false;
247 
248     // Ensure we have up to date info about the material.
249     matAnimator->prepare();
250 
251     if(matAnimator->isOpaque() && !side.middle().blendMode() && side.middle().opacity() >= 1)
252     {
253         if(side.leftHEdge())  // possibility of degenerate BSP leaf.
254         {
255             coord_t openRange, openBottom, openTop;
256             openRange = visOpenRange(side, &openBottom, &openTop);
257             if(matAnimator->dimensions().y >= openRange)
258             {
259                 // Possibly; check the placement.
260                 WallEdge edge(WallSpec::fromMapSide(side, LineSide::Middle), *side.leftHEdge(), Line::From);
261 
262                 return (edge.isValid()
263                         && edge.top   ().z() > edge.bottom().z()
264                         && edge.top   ().z() >= openTop
265                         && edge.bottom().z() <= openBottom);
266             }
267         }
268     }
269 
270     return false;
271 }
272 
273 /// @todo fixme: Should use the visual plane heights of subsectors.
R_FindSolidLineNeighbor(Line const & line,LineOwner const & own,ClockDirection direction,Sector const * sector,binangle_t * diff)274 Line *R_FindSolidLineNeighbor(Line const &line, LineOwner const &own, ClockDirection direction,
275     Sector const *sector, binangle_t *diff)
276 {
277     DENG2_ASSERT(sector);
278 
279     LineOwner const *cown = (direction == Anticlockwise ? own.prev() : own.next());
280     Line *other = &cown->line();
281 
282     if (other == &line) return nullptr;
283 
284     if (diff)
285     {
286         *diff += (direction == Anticlockwise ? cown->angle() : own.angle());
287     }
288 
289     if (!((other->isBspWindow()) && other->front().sectorPtr() != sector)
290         && !other->isSelfReferencing())
291     {
292         if (!other->front().hasSector() || !other->back().hasSector())
293             return other;
294 
295         if (   other->front().sector().floor  ().heightSmoothed() >= sector->ceiling().heightSmoothed()
296             || other->front().sector().ceiling().heightSmoothed() <= sector->floor  ().heightSmoothed()
297             || other->back().sector().floor  ().heightSmoothed() >= sector->ceiling().heightSmoothed()
298             || other->back().sector().ceiling().heightSmoothed() <= sector->floor  ().heightSmoothed()
299             || other->back().sector().ceiling().heightSmoothed() <= other->back().sector().floor().heightSmoothed())
300             return other;
301 
302         // Both front and back MUST be open by this point.
303 
304         // Perhaps a middle material completely covers the opening?
305         // We should not give away the location of false walls (secrets).
306         LineSide &otherSide = other->side(other->front().sectorPtr() == sector ? Line::Front : Line::Back);
307         if (middleMaterialCoversOpening(otherSide))
308             return other;
309     }
310 
311     // Not suitable, try the next.
312     DENG2_ASSERT(cown);
313     return R_FindSolidLineNeighbor(line, *cown, direction, sector, diff);
314 }
315 
316 #endif  // __CLIENT__
317