mirror of https://github.com/axmolengine/axmol.git
548 lines
17 KiB
C++
548 lines
17 KiB
C++
|
/*******************************************************************************
|
||
|
* Author : Angus Johnson *
|
||
|
* Date : 26 October 2022 *
|
||
|
* Website : http://www.angusj.com *
|
||
|
* Copyright : Angus Johnson 2010-2022 *
|
||
|
* Purpose : FAST rectangular clipping *
|
||
|
* License : http://www.boost.org/LICENSE_1_0.txt *
|
||
|
*******************************************************************************/
|
||
|
|
||
|
#include <cmath>
|
||
|
#include "clipper.h"
|
||
|
#include "clipper.rectclip.h"
|
||
|
|
||
|
namespace Clipper2Lib {
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Miscellaneous methods
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
inline PointInPolygonResult Path1ContainsPath2(Path64 path1, Path64 path2)
|
||
|
{
|
||
|
PointInPolygonResult result = PointInPolygonResult::IsOn;
|
||
|
for(const Point64& pt : path2)
|
||
|
{
|
||
|
result = PointInPolygon(pt, path1);
|
||
|
if (result != PointInPolygonResult::IsOn) break;
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
inline bool GetLocation(const Rect64& rec,
|
||
|
const Point64& pt, Location& loc)
|
||
|
{
|
||
|
if (pt.x == rec.left && pt.y >= rec.top && pt.y <= rec.bottom)
|
||
|
{
|
||
|
loc = Location::Left;
|
||
|
return false;
|
||
|
}
|
||
|
else if (pt.x == rec.right && pt.y >= rec.top && pt.y <= rec.bottom)
|
||
|
{
|
||
|
loc = Location::Right;
|
||
|
return false;
|
||
|
}
|
||
|
else if (pt.y == rec.top && pt.x >= rec.left && pt.x <= rec.right)
|
||
|
{
|
||
|
loc = Location::Top;
|
||
|
return false;
|
||
|
}
|
||
|
else if (pt.y == rec.bottom && pt.x >= rec.left && pt.x <= rec.right)
|
||
|
{
|
||
|
loc = Location::Bottom;
|
||
|
return false;
|
||
|
}
|
||
|
else if (pt.x < rec.left) loc = Location::Left;
|
||
|
else if (pt.x > rec.right) loc = Location::Right;
|
||
|
else if (pt.y < rec.top) loc = Location::Top;
|
||
|
else if (pt.y > rec.bottom) loc = Location::Bottom;
|
||
|
else loc = Location::Inside;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
Point64 GetIntersectPoint64(const Point64& ln1a, const Point64& ln1b,
|
||
|
const Point64& ln2a, const Point64& ln2b)
|
||
|
{
|
||
|
// see http://astronomy.swin.edu.au/~pbourke/geometry/lineline2d/
|
||
|
if (ln1b.x == ln1a.x)
|
||
|
{
|
||
|
if (ln2b.x == ln2a.x) return Point64(); // parallel lines
|
||
|
double m2 = static_cast<double>(ln2b.y - ln2a.y) / (ln2b.x - ln2a.x);
|
||
|
double b2 = ln2a.y - m2 * ln2a.x;
|
||
|
return Point64(ln1a.x, static_cast<int64_t>(std::round(m2 * ln1a.x + b2)));
|
||
|
}
|
||
|
else if (ln2b.x == ln2a.x)
|
||
|
{
|
||
|
double m1 = static_cast<double>(ln1b.y - ln1a.y) / (ln1b.x - ln1a.x);
|
||
|
double b1 = ln1a.y - m1 * ln1a.x;
|
||
|
return Point64(ln2a.x, static_cast<int64_t>(std::round(m1 * ln2a.x + b1)));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
double m1 = static_cast<double>(ln1b.y - ln1a.y) / (ln1b.x - ln1a.x);
|
||
|
double b1 = ln1a.y - m1 * ln1a.x;
|
||
|
double m2 = static_cast<double>(ln2b.y - ln2a.y) / (ln2b.x - ln2a.x);
|
||
|
double b2 = ln2a.y - m2 * ln2a.x;
|
||
|
if (std::fabs(m1 - m2) > 1.0E-15)
|
||
|
{
|
||
|
double x = (b2 - b1) / (m1 - m2);
|
||
|
return Point64(x, m1 * x + b1);
|
||
|
}
|
||
|
else
|
||
|
return Point64((ln1a.x + ln1b.x) * 0.5, (ln1a.y + ln1b.y) * 0.5);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
inline bool GetIntersection(const Path64& rectPath,
|
||
|
const Point64& p, const Point64& p2, Location& loc, Point64& ip)
|
||
|
{
|
||
|
// gets the intersection closest to 'p'
|
||
|
// when Result = false, loc will remain unchanged
|
||
|
switch (loc)
|
||
|
{
|
||
|
case Location::Left:
|
||
|
if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true))
|
||
|
ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[3]);
|
||
|
else if (p.y < rectPath[0].y &&
|
||
|
SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true))
|
||
|
{
|
||
|
ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[1]);
|
||
|
loc = Location::Top;
|
||
|
}
|
||
|
else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true))
|
||
|
{
|
||
|
ip = GetIntersectPoint64(p, p2, rectPath[2], rectPath[3]);
|
||
|
loc = Location::Bottom;
|
||
|
}
|
||
|
else return false;
|
||
|
break;
|
||
|
|
||
|
case Location::Top:
|
||
|
if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true))
|
||
|
ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[1]);
|
||
|
else if (p.x < rectPath[0].x &&
|
||
|
SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true))
|
||
|
{
|
||
|
ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[3]);
|
||
|
loc = Location::Left;
|
||
|
}
|
||
|
else if (p.x > rectPath[1].x &&
|
||
|
SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true))
|
||
|
{
|
||
|
ip = GetIntersectPoint64(p, p2, rectPath[1], rectPath[2]);
|
||
|
loc = Location::Right;
|
||
|
}
|
||
|
else return false;
|
||
|
break;
|
||
|
|
||
|
case Location::Right:
|
||
|
if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true))
|
||
|
ip = GetIntersectPoint64(p, p2, rectPath[1], rectPath[2]);
|
||
|
else if (p.y < rectPath[0].y &&
|
||
|
SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true))
|
||
|
{
|
||
|
ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[1]);
|
||
|
loc = Location::Top;
|
||
|
}
|
||
|
else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true))
|
||
|
{
|
||
|
ip = GetIntersectPoint64(p, p2, rectPath[2], rectPath[3]);
|
||
|
loc = Location::Bottom;
|
||
|
}
|
||
|
else return false;
|
||
|
break;
|
||
|
|
||
|
case Location::Bottom:
|
||
|
if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true))
|
||
|
ip = GetIntersectPoint64(p, p2, rectPath[2], rectPath[3]);
|
||
|
else if (p.x < rectPath[3].x &&
|
||
|
SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true))
|
||
|
{
|
||
|
ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[3]);
|
||
|
loc = Location::Left;
|
||
|
}
|
||
|
else if (p.x > rectPath[2].x &&
|
||
|
SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true))
|
||
|
{
|
||
|
ip = GetIntersectPoint64(p, p2, rectPath[1], rectPath[2]);
|
||
|
loc = Location::Right;
|
||
|
}
|
||
|
else return false;
|
||
|
break;
|
||
|
|
||
|
default: // loc == rInside
|
||
|
if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true))
|
||
|
{
|
||
|
ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[3]);
|
||
|
loc = Location::Left;
|
||
|
}
|
||
|
else if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true))
|
||
|
{
|
||
|
ip = GetIntersectPoint64(p, p2, rectPath[0], rectPath[1]);
|
||
|
loc = Location::Top;
|
||
|
}
|
||
|
else if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true))
|
||
|
{
|
||
|
ip = GetIntersectPoint64(p, p2, rectPath[1], rectPath[2]);
|
||
|
loc = Location::Right;
|
||
|
}
|
||
|
else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true))
|
||
|
{
|
||
|
ip = GetIntersectPoint64(p, p2, rectPath[2], rectPath[3]);
|
||
|
loc = Location::Bottom;
|
||
|
}
|
||
|
else return false;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
inline Location GetAdjacentLocation(Location loc, bool isClockwise)
|
||
|
{
|
||
|
int delta = (isClockwise) ? 1 : 3;
|
||
|
return static_cast<Location>((static_cast<int>(loc) + delta) % 4);
|
||
|
}
|
||
|
|
||
|
inline bool HeadingClockwise(Location prev, Location curr)
|
||
|
{
|
||
|
return (static_cast<int>(prev) + 1) % 4 == static_cast<int>(curr);
|
||
|
}
|
||
|
|
||
|
inline bool AreOpposites(Location prev, Location curr)
|
||
|
{
|
||
|
return abs(static_cast<int>(prev) - static_cast<int>(curr)) == 2;
|
||
|
}
|
||
|
|
||
|
inline bool IsClockwise(Location prev, Location curr,
|
||
|
Point64 prev_pt, Point64 curr_pt, Point64 rect_mp)
|
||
|
{
|
||
|
if (AreOpposites(prev, curr))
|
||
|
return CrossProduct(prev_pt, rect_mp, curr_pt) < 0;
|
||
|
else
|
||
|
return HeadingClockwise(prev, curr);
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
// RectClip64
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
void RectClip::AddCorner(Location prev, Location curr)
|
||
|
{
|
||
|
if (HeadingClockwise(prev, curr))
|
||
|
result_.push_back(rectPath_[static_cast<int>(prev)]);
|
||
|
else
|
||
|
result_.push_back(rectPath_[static_cast<int>(curr)]);
|
||
|
}
|
||
|
|
||
|
void RectClip::AddCorner(Location& loc, bool isClockwise)
|
||
|
{
|
||
|
if (isClockwise)
|
||
|
{
|
||
|
result_.push_back(rectPath_[static_cast<int>(loc)]);
|
||
|
loc = GetAdjacentLocation(loc, true);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
loc = GetAdjacentLocation(loc, false);
|
||
|
result_.push_back(rectPath_[static_cast<int>(loc)]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void RectClip::GetNextLocation(const Path64& path,
|
||
|
Location& loc, int& i, int highI)
|
||
|
{
|
||
|
switch (loc)
|
||
|
{
|
||
|
case Location::Left:
|
||
|
while (i <= highI && path[i].x <= rect_.left) ++i;
|
||
|
if (i > highI) break;
|
||
|
else if (path[i].x >= rect_.right) loc = Location::Right;
|
||
|
else if (path[i].y <= rect_.top) loc = Location::Top;
|
||
|
else if (path[i].y >= rect_.bottom) loc = Location::Bottom;
|
||
|
else loc = Location::Inside;
|
||
|
break;
|
||
|
|
||
|
case Location::Top:
|
||
|
while (i <= highI && path[i].y <= rect_.top) ++i;
|
||
|
if (i > highI) break;
|
||
|
else if (path[i].y >= rect_.bottom) loc = Location::Bottom;
|
||
|
else if (path[i].x <= rect_.left) loc = Location::Left;
|
||
|
else if (path[i].x >= rect_.right) loc = Location::Right;
|
||
|
else loc = Location::Inside;
|
||
|
break;
|
||
|
|
||
|
case Location::Right:
|
||
|
while (i <= highI && path[i].x >= rect_.right) ++i;
|
||
|
if (i > highI) break;
|
||
|
else if (path[i].x <= rect_.left) loc = Location::Left;
|
||
|
else if (path[i].y <= rect_.top) loc = Location::Top;
|
||
|
else if (path[i].y >= rect_.bottom) loc = Location::Bottom;
|
||
|
else loc = Location::Inside;
|
||
|
break;
|
||
|
|
||
|
case Location::Bottom:
|
||
|
while (i <= highI && path[i].y >= rect_.bottom) ++i;
|
||
|
if (i > highI) break;
|
||
|
else if (path[i].y <= rect_.top) loc = Location::Top;
|
||
|
else if (path[i].x <= rect_.left) loc = Location::Left;
|
||
|
else if (path[i].x >= rect_.right) loc = Location::Right;
|
||
|
else loc = Location::Inside;
|
||
|
break;
|
||
|
|
||
|
case Location::Inside:
|
||
|
while (i <= highI)
|
||
|
{
|
||
|
if (path[i].x < rect_.left) loc = Location::Left;
|
||
|
else if (path[i].x > rect_.right) loc = Location::Right;
|
||
|
else if (path[i].y > rect_.bottom) loc = Location::Bottom;
|
||
|
else if (path[i].y < rect_.top) loc = Location::Top;
|
||
|
else { result_.push_back(path[i]); ++i; continue; }
|
||
|
break; //inner loop
|
||
|
}
|
||
|
break;
|
||
|
} //switch
|
||
|
}
|
||
|
|
||
|
Path64 RectClip::Execute(const Path64& path)
|
||
|
{
|
||
|
if (rect_.IsEmpty() || path.size() < 3) return Path64();
|
||
|
|
||
|
result_.clear();
|
||
|
start_locs_.clear();
|
||
|
int i = 0, highI = static_cast<int>(path.size()) - 1;
|
||
|
Location prev = Location::Inside, loc;
|
||
|
Location crossing_loc = Location::Inside;
|
||
|
Location first_cross_ = Location::Inside;
|
||
|
if (!GetLocation(rect_, path[highI], loc))
|
||
|
{
|
||
|
i = highI - 1;
|
||
|
while (i >= 0 && !GetLocation(rect_, path[i], prev)) --i;
|
||
|
if (i < 0) return path;
|
||
|
if (prev == Location::Inside) loc = Location::Inside;
|
||
|
i = 0;
|
||
|
}
|
||
|
Location starting_loc = loc;
|
||
|
|
||
|
///////////////////////////////////////////////////
|
||
|
while (i <= highI)
|
||
|
{
|
||
|
prev = loc;
|
||
|
Location crossing_prev = crossing_loc;
|
||
|
|
||
|
GetNextLocation(path, loc, i, highI);
|
||
|
|
||
|
if (i > highI) break;
|
||
|
Point64 ip, ip2;
|
||
|
Point64 prev_pt = (i) ? path[static_cast<size_t>(i - 1)] : path[highI];
|
||
|
|
||
|
crossing_loc = loc;
|
||
|
if (!GetIntersection(rectPath_, path[i], prev_pt, crossing_loc, ip))
|
||
|
{
|
||
|
// ie remaining outside
|
||
|
|
||
|
if (crossing_prev == Location::Inside)
|
||
|
{
|
||
|
bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], mp_);
|
||
|
do {
|
||
|
start_locs_.push_back(prev);
|
||
|
prev = GetAdjacentLocation(prev, isClockw);
|
||
|
} while (prev != loc);
|
||
|
crossing_loc = crossing_prev; // still not crossed
|
||
|
}
|
||
|
else if (prev != Location::Inside && prev != loc)
|
||
|
{
|
||
|
bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], mp_);
|
||
|
do {
|
||
|
AddCorner(prev, isClockw);
|
||
|
} while (prev != loc);
|
||
|
}
|
||
|
++i;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////
|
||
|
// we must be crossing the rect boundary to get here
|
||
|
////////////////////////////////////////////////////
|
||
|
|
||
|
if (loc == Location::Inside) // path must be entering rect
|
||
|
{
|
||
|
if (first_cross_ == Location::Inside)
|
||
|
{
|
||
|
first_cross_ = crossing_loc;
|
||
|
start_locs_.push_back(prev);
|
||
|
}
|
||
|
else if (prev != crossing_loc)
|
||
|
{
|
||
|
bool isClockw = IsClockwise(prev, crossing_loc, prev_pt, path[i], mp_);
|
||
|
do {
|
||
|
AddCorner(prev, isClockw);
|
||
|
} while (prev != crossing_loc);
|
||
|
}
|
||
|
}
|
||
|
else if (prev != Location::Inside)
|
||
|
{
|
||
|
// passing right through rect. 'ip' here will be the second
|
||
|
// intersect pt but we'll also need the first intersect pt (ip2)
|
||
|
loc = prev;
|
||
|
GetIntersection(rectPath_, prev_pt, path[i], loc, ip2);
|
||
|
if (crossing_prev != Location::Inside)
|
||
|
AddCorner(crossing_prev, loc);
|
||
|
|
||
|
if (first_cross_ == Location::Inside)
|
||
|
{
|
||
|
first_cross_ = loc;
|
||
|
start_locs_.push_back(prev);
|
||
|
}
|
||
|
|
||
|
loc = crossing_loc;
|
||
|
result_.push_back(ip2);
|
||
|
if (ip == ip2)
|
||
|
{
|
||
|
// it's very likely that path[i] is on rect
|
||
|
GetLocation(rect_, path[i], loc);
|
||
|
AddCorner(crossing_loc, loc);
|
||
|
crossing_loc = loc;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
else // path must be exiting rect
|
||
|
{
|
||
|
loc = crossing_loc;
|
||
|
if (first_cross_ == Location::Inside)
|
||
|
first_cross_ = crossing_loc;
|
||
|
}
|
||
|
|
||
|
result_.push_back(ip);
|
||
|
|
||
|
} //while i <= highI
|
||
|
///////////////////////////////////////////////////
|
||
|
|
||
|
if (first_cross_ == Location::Inside)
|
||
|
{
|
||
|
if (starting_loc == Location::Inside) return path;
|
||
|
Rect64 tmp_rect = Bounds(path);
|
||
|
if (tmp_rect.Contains(rect_) &&
|
||
|
Path1ContainsPath2(path, rectPath_) !=
|
||
|
PointInPolygonResult::IsOutside) return rectPath_;
|
||
|
else
|
||
|
return Path64();
|
||
|
}
|
||
|
|
||
|
if (loc != Location::Inside &&
|
||
|
(loc != first_cross_ || start_locs_.size() > 2))
|
||
|
{
|
||
|
if (start_locs_.size() > 0)
|
||
|
{
|
||
|
prev = loc;
|
||
|
for (auto loc2 : start_locs_)
|
||
|
{
|
||
|
if (prev == loc2) continue;
|
||
|
AddCorner(prev, HeadingClockwise(prev, loc2));
|
||
|
prev = loc2;
|
||
|
}
|
||
|
loc = prev;
|
||
|
}
|
||
|
if (loc != first_cross_)
|
||
|
AddCorner(loc, HeadingClockwise(loc, first_cross_));
|
||
|
}
|
||
|
|
||
|
if (result_.size() < 3) return Path64();
|
||
|
|
||
|
// tidy up duplicates and collinear segments
|
||
|
Path64 res;
|
||
|
res.reserve(result_.size());
|
||
|
size_t k = 0; highI = static_cast<int>(result_.size()) - 1;
|
||
|
Point64 prev_pt = result_[highI];
|
||
|
res.push_back(result_[0]);
|
||
|
Path64::const_iterator cit;
|
||
|
for (cit = result_.cbegin() + 1; cit != result_.cend(); ++cit)
|
||
|
{
|
||
|
if (CrossProduct(prev_pt, res[k], *cit))
|
||
|
{
|
||
|
prev_pt = res[k++];
|
||
|
res.push_back(*cit);
|
||
|
}
|
||
|
else
|
||
|
res[k] = *cit;
|
||
|
}
|
||
|
|
||
|
if (k < 2) return Path64();
|
||
|
// and a final check for collinearity
|
||
|
else if (!CrossProduct(res[0], res[k - 1], res[k])) res.pop_back();
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
Paths64 RectClipLines::Execute(const Path64& path)
|
||
|
{
|
||
|
result_.clear();
|
||
|
Paths64 result;
|
||
|
if (rect_.IsEmpty() || path.size() == 0) return result;
|
||
|
|
||
|
int i = 1, highI = static_cast<int>(path.size()) - 1;
|
||
|
|
||
|
Location prev = Location::Inside, loc;
|
||
|
Location crossing_loc = Location::Inside;
|
||
|
if (!GetLocation(rect_, path[0], loc))
|
||
|
{
|
||
|
while (i <= highI && !GetLocation(rect_, path[i], prev)) ++i;
|
||
|
if (i > highI) {
|
||
|
result.push_back(path);
|
||
|
return result;
|
||
|
}
|
||
|
if (prev == Location::Inside) loc = Location::Inside;
|
||
|
i = 1;
|
||
|
}
|
||
|
if (loc == Location::Inside) result_.push_back(path[0]);
|
||
|
|
||
|
///////////////////////////////////////////////////
|
||
|
while (i <= highI)
|
||
|
{
|
||
|
prev = loc;
|
||
|
GetNextLocation(path, loc, i, highI);
|
||
|
if (i > highI) break;
|
||
|
Point64 ip, ip2;
|
||
|
Point64 prev_pt = path[static_cast<size_t>(i - 1)];
|
||
|
|
||
|
crossing_loc = loc;
|
||
|
if (!GetIntersection(rectPath_, path[i], prev_pt, crossing_loc, ip))
|
||
|
{
|
||
|
// ie remaining outside
|
||
|
++i;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////
|
||
|
// we must be crossing the rect boundary to get here
|
||
|
////////////////////////////////////////////////////
|
||
|
|
||
|
if (loc == Location::Inside) // path must be entering rect
|
||
|
{
|
||
|
result_.push_back(ip);
|
||
|
}
|
||
|
else if (prev != Location::Inside)
|
||
|
{
|
||
|
// passing right through rect. 'ip' here will be the second
|
||
|
// intersect pt but we'll also need the first intersect pt (ip2)
|
||
|
crossing_loc = prev;
|
||
|
GetIntersection(rectPath_, prev_pt, path[i], crossing_loc, ip2);
|
||
|
result_.push_back(ip2);
|
||
|
result_.push_back(ip);
|
||
|
result.push_back(result_);
|
||
|
result_.clear();
|
||
|
}
|
||
|
else // path must be exiting rect
|
||
|
{
|
||
|
result_.push_back(ip);
|
||
|
result.push_back(result_);
|
||
|
result_.clear();
|
||
|
}
|
||
|
} //while i <= highI
|
||
|
///////////////////////////////////////////////////
|
||
|
|
||
|
if (result_.size() > 1)
|
||
|
result.push_back(result_);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
} // namespace
|