1 /** @file walledge.cpp  Wall Edge Geometry.
2  *
3  * @authors Copyright © 2011-2015 Daniel Swanson <danij@dengine.net>
4  *
5  * @par License
6  * GPL: http://www.gnu.org/licenses/gpl.html
7  *
8  * <small>This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by the
10  * Free Software Foundation; either version 2 of the License, or (at your
11  * option) any later version. This program is distributed in the hope that it
12  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
14  * Public License for more details. You should have received a copy of the GNU
15  * General Public License along with this program; if not, write to the Free
16  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17  * 02110-1301 USA</small>
18  */
19 
20 #include "de_base.h"
21 #include "render/walledge.h"
22 
23 #include "BspLeaf"
24 #include "ConvexSubspace"
25 #include "Sector"
26 
27 #include "world/lineowner.h"
28 #include "world/p_players.h"
29 #include "world/maputil.h"
30 #include "world/surface.h"
31 #include "client/clientsubsector.h"
32 #include "MaterialAnimator"
33 #include "render/rend_main.h"  /// devRendSkyMode @todo remove me
34 
35 #include "Face"
36 
37 #include <QtAlgorithms>
38 
39 using namespace de;
40 using namespace world;
41 
42 /**
43  * Determines whether normal smoothing should be performed for the given pair of
44  * map surfaces (which are assumed to share an edge).
45  *
46  * Yes if the angle between the two surfaces is less than 45 degrees.
47  * @todo Should be user customizable with a Material property. -ds
48  *
49  * @param sufA       The "left"  map surface which shares an edge with @a sufB.
50  * @param sufB       The "right" map surface which shares an edge with @a sufA.
51  * @param angleDiff  Angle difference (i.e., normal delta) between the two surfaces.
52  */
shouldSmoothNormals(Surface & sufA,Surface & sufB,binangle_t angleDiff)53 static bool shouldSmoothNormals(Surface &sufA, Surface &sufB, binangle_t angleDiff)
54 {
55     DENG2_UNUSED2(sufA, sufB);
56     return INRANGE_OF(angleDiff, BANG_180, BANG_45);
57 }
58 
Event()59 WallEdge::Event::Event()
60     : IHPlane::IIntercept(0)
61     , _owner(nullptr)
62 {}
63 
Event(WallEdge & owner,ddouble distance)64 WallEdge::Event::Event(WallEdge &owner, ddouble distance)
65     : WorldEdge::Event()
66     , IHPlane::IIntercept(distance)
67     , _owner(&owner)
68 {}
69 
operator =(Event const & other)70 WallEdge::Event &WallEdge::Event::operator = (Event const &other)
71 {
72     _owner    = other._owner;
73     _distance = other._distance;
74     return *this;
75 }
76 
operator <(Event const & other) const77 bool WallEdge::Event::operator < (Event const &other) const
78 {
79     return distance() < other.distance();
80 }
81 
distance() const82 ddouble WallEdge::Event::distance() const
83 {
84     return IHPlane::IIntercept::distance();
85 }
86 
origin() const87 Vector3d WallEdge::Event::origin() const
88 {
89     return _owner->pOrigin() + _owner->pDirection() * distance();
90 }
91 
lineSideOffset(LineSideSegment & seg,dint edge)92 static inline coord_t lineSideOffset(LineSideSegment &seg, dint edge)
93 {
94     return seg.lineSideOffset() + (edge? seg.length() : 0);
95 }
96 
97 QList<WallEdge::Impl *> WallEdge::recycledImpls;
98 
99 struct WallEdge::Impl : public IHPlane
100 {
101     WallEdge *self = nullptr;
102 
103     WallSpec spec;
104     dint edge = 0;
105 
106     HEdge *wallHEdge = nullptr;
107 
108     /// The half-plane which partitions the surface coordinate space.
109     Partition hplane;
110 
111     Vector3d pOrigin;
112     Vector3d pDirection;
113 
114     coord_t lo = 0, hi = 0;
115 
116     /// Events for the special termination points are allocated with "this".
117     Event bottom;
118     Event top;
119 
120     /**
121      * Special-purpose array whose memory is never freed while the array exists.
122      * `WallEdge::Impl`s are recycled, so it would be a waste of time to keep
123      * allocating and freeing the event arrays.
124      */
125     struct EventArray : private QVector<Event>
126     {
127         using Base = QVector<Event>;
128 
129         int size = 0;
130 
131     public:
EventArrayWallEdge::Impl::EventArray132         EventArray() {}
133 
134         using Base::at;
135         using Base::begin;
136         using Base::iterator;
137         using Base::const_iterator;
138 
isEmptyWallEdge::Impl::EventArray139         inline bool isEmpty() const
140         {
141             return size == 0;
142         }
143 
clearWallEdge::Impl::EventArray144         inline void clear()
145         {
146             size = 0;
147         }
148 
appendWallEdge::Impl::EventArray149         void append(Event const &event)
150         {
151             if (size < Base::size())
152             {
153                 (*this)[size++] = event;
154             }
155             else
156             {
157                 Base::append(event);
158                 ++size;
159             }
160         }
161 
lastWallEdge::Impl::EventArray162         inline Event &last()
163         {
164             return (*this)[size - 1];
165         }
166 
endWallEdge::Impl::EventArray167         inline Base::iterator end()
168         {
169             return Base::begin() + size;
170         }
171 
endWallEdge::Impl::EventArray172         inline Base::const_iterator end() const
173         {
174             return Base::begin() + size;
175         }
176     };
177 
178     /// All events along the partition line.
179     EventArray events;
180     bool needSortEvents = false;
181 
182     Vector2f materialOrigin;
183 
184     Vector3f normal;
185     bool needUpdateNormal = true;
186 
ImplWallEdge::Impl187     Impl() {}
188 
deinitWallEdge::Impl189     void deinit()
190     {
191         self = nullptr;
192         edge = 0;
193         wallHEdge = nullptr;
194         lo = hi = 0;
195         events.clear();
196         needSortEvents = false;
197         needUpdateNormal = true;
198     }
199 
initWallEdge::Impl200     void init(WallEdge *i, WallSpec const &wallSpec, HEdge &hedge, dint edge)
201     {
202         self = i;
203 
204         spec       = wallSpec;
205         this->edge = edge;
206         wallHEdge  = &hedge;
207         bottom     = Event(*self, 0);
208         top        = Event(*self, 1);
209 
210         // Determine the map space Z coordinates of the wall section.
211         LineSideSegment &seg   = lineSideSegment();
212         Line const &line       = seg.line();
213         bool const unpegBottom = (line.flags() & DDLF_DONTPEGBOTTOM) != 0;
214         bool const unpegTop    = (line.flags() & DDLF_DONTPEGTOP)    != 0;
215 
216         ConvexSubspace const &space = (line.definesPolyobj() ? line.polyobj().bspLeaf().subspace()
217                                                              : wallHEdge->face().mapElementAs<world::ConvexSubspace>());
218         auto const &subsec = space.subsector().as<world::ClientSubsector>();
219 
220         /*
221          * For reference, see "Texture aligment" in Doomwiki.org:
222          * https://doomwiki.org/wiki/Texture_alignment
223          */
224 
225         if (seg.lineSide().considerOneSided()
226             || // Mapping errors may result in a line segment missing a back face.
227                (!line.definesPolyobj() && !wallHEdge->twin().hasFace()))
228         {
229             if (spec.section == LineSide::Middle)
230             {
231                 lo = subsec.visFloor().heightSmoothed();
232                 hi = subsec.visCeiling().heightSmoothed();
233             }
234             else
235             {
236                 lo = hi = subsec.visFloor().heightSmoothed();
237             }
238 
239             materialOrigin = seg.lineSide().middle().originSmoothed();
240             if (unpegBottom)
241             {
242                 materialOrigin.y -= hi - lo;
243             }
244         }
245         else
246         {
247             // Two sided.
248             auto const &backSubsec =
249                 line.definesPolyobj() ? subsec
250                                       : wallHEdge->twin().face().mapElementAs<world::ConvexSubspace>()
251                                             .subsector().as<world::ClientSubsector>();
252 
253             Plane const *ffloor = &subsec.visFloor();
254             Plane const *fceil  = &subsec.visCeiling();
255             Plane const *bfloor = &backSubsec.visFloor();
256             Plane const *bceil  = &backSubsec.visCeiling();
257 
258             switch (spec.section)
259             {
260             case LineSide::Top:
261                 // Self-referencing lines only ever get a middle.
262                 if (!line.isSelfReferencing())
263                 {
264                     // Can't go over front ceiling (would induce geometry flaws).
265                     if (bceil->heightSmoothed() < ffloor->heightSmoothed())
266                         lo = ffloor->heightSmoothed();
267                     else
268                         lo = bceil->heightSmoothed();
269 
270                     hi = fceil->heightSmoothed();
271 
272                     if (spec.flags.testFlag(WallSpec::SkyClip)
273                         && fceil->surface().hasSkyMaskedMaterial()
274                         && bceil->surface().hasSkyMaskedMaterial())
275                     {
276                         hi = lo;
277                     }
278 
279                     materialOrigin = seg.lineSide().middle().originSmoothed();
280                     if (!unpegTop)
281                     {
282                         // Align with normal middle texture.
283                         materialOrigin.y -= fceil->heightSmoothed() - bceil->heightSmoothed();
284                     }
285                 }
286                 break;
287 
288             case LineSide::Bottom:
289                 // Self-referencing lines only ever get a middle.
290                 if (!line.isSelfReferencing())
291                 {
292                     const double bceilZ  = bceil->heightSmoothed();
293                     const double bfloorZ = bfloor->heightSmoothed();
294                     const double fceilZ  = fceil->heightSmoothed();
295                     const double ffloorZ = ffloor->heightSmoothed();
296 
297                     const bool raiseToBackFloor =
298                         (   fceil->surface().hasSkyMaskedMaterial()
299                          && bceil->surface().hasSkyMaskedMaterial()
300                          && fceilZ < bceilZ
301                          && bfloorZ > fceilZ);
302 
303                     coord_t t = bfloorZ;
304 
305                     lo = ffloorZ;
306 
307                     // Can't go over the back ceiling, would induce polygon flaws.
308                     if (bfloorZ > bceilZ)
309                         t = bceilZ;
310 
311                     // Can't go over front ceiling, would induce polygon flaws.
312                     // In the special case of a sky masked upper we must extend the bottom
313                     // section up to the height of the back floor.
314                     if (t > fceilZ && !raiseToBackFloor)
315                         t = fceilZ;
316 
317                     hi = t;
318 
319                     if (spec.flags.testFlag(WallSpec::SkyClip)
320                         && ffloor->surface().hasSkyMaskedMaterial()
321                         && bfloor->surface().hasSkyMaskedMaterial())
322                     {
323                         lo = hi;
324                     }
325 
326                     materialOrigin = seg.lineSide().bottom().originSmoothed();
327                     if (bfloor->heightSmoothed() > fceil->heightSmoothed())
328                     {
329                         materialOrigin.y -= (raiseToBackFloor? t : fceilZ)
330                                           - bfloorZ;
331                     }
332 
333                     if (unpegBottom)
334                     {
335                         // Align with normal middle texture.
336                         materialOrigin.y += (raiseToBackFloor ? t : de::max(fceilZ, bceilZ)) - bfloorZ;
337                     }
338                 }
339                 break;
340 
341             case LineSide::Middle: {
342                 LineSide const &lineSide = seg.lineSide();
343                 Surface const &middle    = lineSide.middle();
344 
345                 const bool isExtendedMasked = middle.hasMaterial() &&
346                                               !middle.materialAnimator()->isOpaque() &&
347                                               !lineSide.top().hasMaterial() &&
348                                               lineSide.sector().ceiling().surface().material().isSkyMasked();
349 
350                 if (!line.isSelfReferencing() && ffloor == &subsec.sector().floor())
351                 {
352                     lo = de::max(bfloor->heightSmoothed(), ffloor->heightSmoothed());
353                 }
354                 else
355                 {
356                     // Use the unmapped heights for positioning purposes.
357                     lo = lineSide.sector().floor().heightSmoothed();
358                 }
359 
360                 if (!line.isSelfReferencing() && fceil == &subsec.sector().ceiling())
361                 {
362                     hi = de::min(bceil->heightSmoothed(), fceil->heightSmoothed());
363                 }
364                 else
365                 {
366                     // Use the unmapped heights for positioning purposes.
367                     hi = lineSide.back().sector().ceiling().heightSmoothed();
368                 }
369 
370                 materialOrigin = Vector2f(middle.originSmoothed().x, 0);
371 
372                 // Perform clipping.
373                 if (middle.hasMaterial()
374                     && !seg.lineSide().isFlagged(SDF_MIDDLE_STRETCH))
375                 {
376                     coord_t openBottom, openTop;
377                     if (!line.isSelfReferencing())
378                     {
379                         openBottom = lo;
380                         openTop    = hi;
381                     }
382                     else
383                     {
384                         openBottom = ffloor->heightSmoothed();
385                         openTop    = fceil->heightSmoothed();
386                     }
387 
388                     if (openTop > openBottom)
389                     {
390                         if (unpegBottom)
391                         {
392                             lo += middle.originSmoothed().y;
393                             hi = lo + middle.material().height();
394                         }
395                         else
396                         {
397                             hi += middle.originSmoothed().y;
398                             lo = hi - middle.material().height();
399                         }
400 
401                         if (hi > openTop)
402                         {
403                             materialOrigin.y = hi - openTop;
404                         }
405 
406                         // Clip it?
407                         bool const clipBottom = !(!(devRendSkyMode || P_IsInVoid(viewPlayer)) && ffloor->surface().hasSkyMaskedMaterial() && bfloor->surface().hasSkyMaskedMaterial());
408                         bool const clipTop    = !(!(devRendSkyMode || P_IsInVoid(viewPlayer)) && fceil->surface().hasSkyMaskedMaterial()  && bceil->surface().hasSkyMaskedMaterial());
409                         if (clipTop || clipBottom)
410                         {
411                             if (clipBottom && lo < openBottom)
412                                 lo = openBottom;
413 
414                             if (clipTop && hi > openTop)
415                                 hi = openTop;
416                         }
417 
418                         if (!clipTop)
419                         {
420                             materialOrigin.y = 0;
421                         }
422                     }
423                 }
424 
425                 // Icarus map01: force fields use a masked middle texture that expands above the sector
426                 if (isExtendedMasked)
427                 {
428                     if (hi - lo < middle.material().height() + middle.originSmoothed().y)
429                     {
430                         hi = lo + middle.material().height() + middle.originSmoothed().y;
431                     }
432                 }
433 
434                 break; }
435             }
436         }
437         materialOrigin += Vector2f(::lineSideOffset(seg, edge), 0);
438 
439         pOrigin    = Vector3d(self->origin(), lo);
440         pDirection = Vector3d(0, 0, hi - lo);
441     }
442 
lineSideSegmentWallEdge::Impl443     inline LineSideSegment &lineSideSegment()
444     {
445         return wallHEdge->mapElementAs<LineSideSegment>();
446     }
447 
verifyValidWallEdge::Impl448     void verifyValid() const
449     {
450         if(!self->isValid())
451         {
452             /// @throw InvalidError  Invalid range geometry was specified.
453             throw InvalidError("WallEdge::verifyValid", "Range geometry is not valid (top < bottom)");
454         }
455     }
456 
toEventIndexWallEdge::Impl457     EventIndex toEventIndex(ddouble distance) const
458     {
459         for (EventIndex i = 0; i < events.size; ++i)
460         {
461             if (de::fequal(events.at(i).distance(), distance))
462                 return i;
463         }
464         return InvalidIndex;
465     }
466 
haveEventWallEdge::Impl467     inline bool haveEvent(ddouble distance) const
468     {
469         return toEventIndex(distance) != InvalidIndex;
470     }
471 
createEventWallEdge::Impl472     Event &createEvent(ddouble distance)
473     {
474         return *intercept(distance);
475     }
476 
477     // Implements IHPlane
configureWallEdge::Impl478     void configure(Partition const &newPartition)
479     {
480         hplane = newPartition;
481     }
482 
483     // Implements IHPlane
partitionWallEdge::Impl484     Partition const &partition() const
485     {
486         return hplane;
487     }
488 
489     // Implements IHPlane
interceptWallEdge::Impl490     Event *intercept(ddouble distance)
491     {
492         events.append(Event(*self, distance));
493 
494         // We'll need to resort the events.
495         needSortEvents = true;
496 
497         return &events.last();
498     }
499 
500     // Implements IHPlane
sortAndMergeInterceptsWallEdge::Impl501     void sortAndMergeIntercepts()
502     {
503         if (needSortEvents)
504         {
505             qSort(events.begin(), events.end(), [] (WorldEdge::Event const &a, WorldEdge::Event const &b) {
506                 return a < b;
507             });
508             needSortEvents = false;
509         }
510     }
511 
512     // Implements IHPlane
clearInterceptsWallEdge::Impl513     void clearIntercepts()
514     {
515         events.clear();
516 
517         // An empty event list is logically sorted.
518         needSortEvents = false;
519     }
520 
521     // Implements IHPlane
atWallEdge::Impl522     Event const &at(EventIndex index) const
523     {
524         if(index >= 0 && index < interceptCount())
525         {
526             return events.at(index);
527         }
528         /// @throw UnknownInterceptError The specified intercept index is not valid.
529         throw UnknownInterceptError("WallEdge::at", String("Index '%1' does not map to a known intercept (count: %2)")
530                                                         .arg(index).arg(interceptCount()));
531     }
532 
533     // Implements IHPlane
interceptCountWallEdge::Impl534     dint interceptCount() const
535     {
536         return events.size;
537     }
538 
539 #ifdef DENG2_DEBUG
printInterceptsWallEdge::Impl540     void printIntercepts() const
541     {
542         EventIndex index = 0;
543         foreach (Event const &icpt, events)
544         {
545             LOGDEV_MAP_MSG(" %u: >%1.2f ") << (index++) << icpt.distance();
546         }
547     }
548 #endif
549 
550     /**
551      * Ensure all intercepts do not exceed the specified closed range.
552      */
assertInterceptsInRangeWallEdge::Impl553     void assertInterceptsInRange(ddouble low, ddouble hi) const
554     {
555 #ifdef DENG2_DEBUG
556         foreach (Event const &icpt, events)
557         {
558             DENG2_ASSERT(icpt.distance() >= low && icpt.distance() <= hi);
559         }
560 #else
561         DENG2_UNUSED2(low, hi);
562 #endif
563     }
564 
distanceToWallEdge::Impl565     inline ddouble distanceTo(coord_t worldHeight) const
566     {
567         return (worldHeight - lo) / (hi - lo);
568     }
569 
addNeighborInterceptsWallEdge::Impl570     void addNeighborIntercepts(ddouble bottom, ddouble top)
571     {
572         ClockDirection const direction = edge ? Clockwise : Anticlockwise;
573 
574         HEdge const *hedge = wallHEdge;
575         while ((hedge = &SubsectorCirculator::findBackNeighbor(*hedge, direction)) != wallHEdge)
576         {
577             // Stop if there is no space on the back side.
578             if (!hedge->hasFace() || !hedge->hasMapElement())
579                 break;
580 
581             auto const &backSpace = hedge->face().mapElementAs<ConvexSubspace>();
582             auto const &subsec    = backSpace.subsector().as<world::ClientSubsector>();
583 
584             if (subsec.hasWorldVolume())
585             {
586                 for (dint i = 0; i < subsec.visPlaneCount(); ++i)
587                 {
588                     Plane const &plane = subsec.visPlane(i);
589 
590                     if (plane.heightSmoothed() > bottom && plane.heightSmoothed() < top)
591                     {
592                         ddouble distance = distanceTo(plane.heightSmoothed());
593                         if (!haveEvent(distance))
594                         {
595                             createEvent(distance);
596 
597                             // Have we reached the div limit?
598                             if (interceptCount() == WALLEDGE_MAX_INTERCEPTS)
599                                 return;
600                         }
601                     }
602 
603                     // Clip a range bound to this height?
604                     if (plane.isSectorFloor() && plane.heightSmoothed() > bottom)
605                         bottom = plane.heightSmoothed();
606                     else if (plane.isSectorCeiling() && plane.heightSmoothed() < top)
607                         top = plane.heightSmoothed();
608 
609                     // All clipped away?
610                     if (bottom >= top)
611                         return;
612                 }
613             }
614             else
615             {
616                 // A neighbor with zero volume -- the potential division is at the height
617                 // of the back ceiling. This is because elsewhere we automatically fix the
618                 // case of a floor above a ceiling by lowering the floor.
619                 ddouble z = subsec.visCeiling().heightSmoothed();
620                 if(z > bottom && z < top)
621                 {
622                     ddouble distance = distanceTo(z);
623                     if(!haveEvent(distance))
624                     {
625                         createEvent(distance);
626                         return; // All clipped away.
627                     }
628                 }
629             }
630         }
631     }
632 
633     /**
634      * Determines whether the wall edge should be intercepted with neighboring
635      * planes from other subsectors.
636      */
shouldInterceptNeighborsWallEdge::Impl637     bool shouldInterceptNeighbors()
638     {
639         if(spec.flags & WallSpec::NoEdgeDivisions)
640             return false;
641 
642         if(de::fequal(hi, lo))
643             return false;
644 
645         // Subsector-internal edges won't be intercepted. This is because such an
646         // edge only ever produces middle wall sections, which, do not support
647         // divisions in any case (they become vissprites).
648         if(Subsector::isInternalEdge(wallHEdge))
649             return false;
650 
651         return true;
652     }
653 
prepareEventsWallEdge::Impl654     void prepareEvents()
655     {
656         DENG2_ASSERT(self->isValid());
657 
658         needSortEvents = false;
659 
660         // The first event is the bottom termination event.
661         // The last event is the top termination event.
662         events.append(bottom);
663         events.append(top);
664 
665         // Add intecepts for neighbor planes?
666         if(shouldInterceptNeighbors())
667         {
668             configure(Partition(Vector2d(0, hi - lo)));
669 
670             // Add intercepts (the "divisions") in ascending distance order.
671             addNeighborIntercepts(lo, hi);
672 
673             // Sorting may be required. This shouldn't take too long...
674             // There seldom are more than two or three intercepts.
675             sortAndMergeIntercepts();
676         }
677 
678 #ifdef DENG2_DEBUG
679         // Sanity check.
680         assertInterceptsInRange(0, 1);
681 #endif
682     }
683 
684     /**
685      * Find the neighbor surface for the edge which we will use to calculate the
686      * "blend" properties (e.g., smoothed edge normal).
687      *
688      * @todo: Use the half-edge rings instead of LineOwners.
689      */
findBlendNeighborWallEdge::Impl690     Surface *findBlendNeighbor(binangle_t &diff)
691     {
692         diff = 0;
693 
694         // Are we not blending?
695         if(spec.flags.testFlag(WallSpec::NoEdgeNormalSmoothing))
696             return nullptr;
697 
698         LineSide const &lineSide = lineSideSegment().lineSide();
699 
700         // Polyobj lines have no owner rings.
701         if(lineSide.line().definesPolyobj())
702             return nullptr;
703 
704         ClockDirection const direction = (edge? Anticlockwise : Clockwise);
705         LineOwner const &farVertOwner  = *lineSide.line().vertexOwner(lineSide.sideId() ^ edge);
706         Line *neighbor;
707         if(R_SideBackClosed(lineSide))
708         {
709             neighbor = R_FindSolidLineNeighbor(lineSide.line(), farVertOwner, direction,
710                                                lineSide.sectorPtr(), &diff);
711         }
712         else
713         {
714             neighbor = R_FindLineNeighbor(lineSide.line(), farVertOwner, direction,
715                                           lineSide.sectorPtr(), &diff);
716         }
717 
718         // No suitable line neighbor?
719         if(!neighbor) return nullptr;
720 
721         // Choose the correct side of the neighbor (determined by which vertex is shared).
722         LineSide *otherSide;
723         if(&neighbor->vertex(edge ^ 1) == &lineSide.vertex(edge))
724             otherSide = &neighbor->front();
725         else
726             otherSide = &neighbor->back();
727 
728         // We can only blend if the neighbor has a surface.
729         if(!otherSide->hasSections()) return nullptr;
730 
731         /// @todo Do not assume the neighbor is the middle section of @var otherSide.
732         return &otherSide->middle();
733     }
734 
735     /**
736      * Determine the (possibly smoothed) edge normal.
737      * @todo Cache the smoothed normal value somewhere...
738      */
updateNormalWallEdge::Impl739     void updateNormal()
740     {
741         needUpdateNormal = false;
742 
743         LineSide &lineSide = lineSideSegment().lineSide();
744         Surface &surface   = lineSide.surface(spec.section);
745 
746         binangle_t angleDiff;
747         Surface *blendSurface = findBlendNeighbor(angleDiff);
748 
749         if(blendSurface && shouldSmoothNormals(surface, *blendSurface, angleDiff))
750         {
751             // Average normals.
752             normal = Vector3f(surface.normal() + blendSurface->normal()) / 2;
753         }
754         else
755         {
756             normal = surface.normal();
757         }
758     }
759 };
760 
WallEdge(WallSpec const & spec,HEdge & hedge,int edge)761 WallEdge::WallEdge(WallSpec const &spec, HEdge &hedge, int edge)
762     : WorldEdge((edge? hedge.twin() : hedge).origin())
763     , d(getRecycledImpl())
764 {
765     d->init(this, spec, hedge, edge);
766 }
767 
~WallEdge()768 WallEdge::~WallEdge()
769 {
770     recycleImpl(d);
771 }
772 
pOrigin() const773 Vector3d const &WallEdge::pOrigin() const
774 {
775     return d->pOrigin;
776 }
777 
pDirection() const778 Vector3d const &WallEdge::pDirection() const
779 {
780     return d->pDirection;
781 }
782 
materialOrigin() const783 Vector2f WallEdge::materialOrigin() const
784 {
785     return d->materialOrigin;
786 }
787 
normal() const788 Vector3f WallEdge::normal() const
789 {
790     if(d->needUpdateNormal)
791     {
792         d->updateNormal();
793     }
794     return d->normal;
795 }
796 
spec() const797 WallSpec const &WallEdge::spec() const
798 {
799     return d->spec;
800 }
801 
lineSideSegment() const802 LineSideSegment &WallEdge::lineSideSegment() const
803 {
804     return d->lineSideSegment();
805 }
806 
lineSideOffset() const807 coord_t WallEdge::lineSideOffset() const
808 {
809     return ::lineSideOffset(d->lineSideSegment(), d->edge);
810 }
811 
divisionCount() const812 dint WallEdge::divisionCount() const
813 {
814     if (!isValid()) return 0;
815     if (d->events.isEmpty())
816     {
817         d->prepareEvents();
818     }
819     return d->interceptCount() - 2;
820 }
821 
firstDivision() const822 WallEdge::EventIndex WallEdge::firstDivision() const
823 {
824     return divisionCount()? 1 : InvalidIndex;
825 }
826 
lastDivision() const827 WallEdge::EventIndex WallEdge::lastDivision() const
828 {
829     return divisionCount()? (d->interceptCount() - 2) : InvalidIndex;
830 }
831 
at(EventIndex index) const832 WallEdge::Event const &WallEdge::at(EventIndex index) const
833 {
834     return d->events.at(index);
835 }
836 
isValid() const837 bool WallEdge::isValid() const
838 {
839     return d->hi > d->lo;
840 }
841 
first() const842 WallEdge::Event const &WallEdge::first() const
843 {
844     return d->bottom;
845 }
846 
last() const847 WallEdge::Event const &WallEdge::last() const
848 {
849     return d->top;
850 }
851 
getRecycledImpl()852 WallEdge::Impl *WallEdge::getRecycledImpl() // static
853 {
854     if (recycledImpls.isEmpty())
855     {
856         return new Impl;
857     }
858     return recycledImpls.takeLast();
859 }
860 
recycleImpl(Impl * d)861 void WallEdge::recycleImpl(Impl *d) // static
862 {
863     d->deinit();
864     recycledImpls.append(d);
865 }
866