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