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