/* * Copyright (C) 2015 Open Source Robotics Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include #include #include #include #include #include #include "SVGLoaderPrivate.hh" #include "SVGLoader.hh" using namespace gazebo; using namespace common; ///////////////////////////////////////////////// // This local helper function transforms a string to its lowecase equivalent std::string lowercase(const std::string &_in) { std::string out = _in; std::transform(out.begin(), out.end(), out.begin(), ::tolower); return out; } ///////////////////////////////////////////////// // This local helper function transforms a C string to its lowecase equivalent std::string lowercase(const char *_in) { std::string ins = _in; return lowercase(ins); } ///////////////////////////////////////////////// // Local helper function that splits a string according to the delimiting char std::vector &split(const std::string &_s, char _delim, std::vector &_elems) { std::stringstream ss(_s); std::string item; while (std::getline(ss, item, _delim)) { _elems.push_back(item); } return _elems; } ///////////////////////////////////////////////// // This local helper function takes in a SVG transformation string // and returns the corresponding transformation matrix ignition::math::Matrix3d ParseTransformMatrixStr( const std::string &_transformStr) { // check for transformation GZ_ASSERT(!_transformStr.empty(), "no data for ParseTransformMatrixStr"); // _transfromStr should not have a closing paren and look like this // matrix(0,0.55669897,-0.55669897,0,194.55441,-149.50402 // we're going to extract the transform type and numbers std::vector tx; split(_transformStr, '(', tx); if (tx.size() < 2) { gzerr << "Invalid path transform: '" << &_transformStr << "'" << std::endl; return ignition::math::Matrix3d::Identity; } std::string transform = tx[0]; std::vector numbers; split(tx[1], ',', numbers); // how to unpack the values into 3x3 matrices // http://www.w3.org/TR/SVG/coords.html#TransformAttribute if (transform.find("matrix") != std::string::npos) { if (numbers.size() != 6) { gzerr << "Unsupported matrix transform with " << numbers.size() << " parameters. Should be 6." << std::endl; return ignition::math::Matrix3d::Identity; } double a = stod(numbers[0]); // 00 double b = stod(numbers[1]); // 10 double c = stod(numbers[2]); // 01 double d = stod(numbers[3]); // 11 double e = stod(numbers[4]); // 02 double f = stod(numbers[5]); // 12 ignition::math::Matrix3d m(a, c, e, b, d, f, 0, 0, 1); return m; } if (transform.find("skewX") != std::string::npos) { if (numbers.size() != 1) { gzerr << "Unsupported skewX transform. Needs 1 parameter only" << std::endl; return ignition::math::Matrix3d::Identity; } double deg = stod(numbers[0]); ignition::math::Angle angle; angle.Degree(deg); // get the tangent of the angle double t = tan(angle.Radian()); ignition::math::Matrix3d m(1, t, 0, 0, 1, 0, 0, 0, 1); return m; } if (transform.find("skewY") != std::string::npos) { if (numbers.size() != 1) { gzerr << "Unsupported skewY transform. Needs 1 parameter only" << std::endl; return ignition::math::Matrix3d::Identity; } double deg = stod(numbers[0]); ignition::math::Angle angle; angle.Degree(deg); // get the tangent of the angle double t = tan(angle.Radian()); ignition::math::Matrix3d m(1, 0, 0, t, 1, 0, 0, 0, 1); return m; } // scale( []) // if y is not provided, it is assumed to be equal to x. if (transform.find("scale") != std::string::npos) { if (numbers.size() == 0 || numbers.size() > 2) { gzerr << "Unsupported scale transform with more than 2 parameters" << std::endl; return ignition::math::Matrix3d::Identity; } double x = stod(numbers[0]); double y = x; if (numbers.size() == 2) { y = stod(numbers[1]); } ignition::math::Matrix3d m(x, 0, 0, 0, y, 0, 0, 0, 1); return m; } // translate( []) // If y is not provided, it is assumed to be zero. if (transform.find("translate") != std::string::npos) { if (numbers.size() == 0 || numbers.size() > 2) { gzerr << "Unsupported translate transform with more than 2 parameters" << std::endl; return ignition::math::Matrix3d::Identity; } double x = stod(numbers[0]); double y = 0; if (numbers.size() == 2) { y = stod(numbers[1]); } ignition::math::Matrix3d m(1, 0, x, 0, 1, y, 0, 0, 1); return m; } // rotate( [ ]) angle in degrees, center x and y // if x, y are not supplied, rotation is about 0, 0 if (transform.find("rotate") != std::string::npos) { if (numbers.size() ==0 || numbers.size() == 2 || numbers.size() > 3 ) { gzerr << "Unsupported rotate transform. Only angle and optional x y" << " are supported" << std::endl; return ignition::math::Matrix3d::Identity; } double deg = stod(numbers[0]); ignition::math::Angle angle; angle.Degree(deg); double a = angle.Radian(); double sina = sin(a); double cosa = cos(a); double x = 0; double y = 0; if (numbers.size() == 3) { x = stod(numbers[1]); y = stod(numbers[2]); } // we apply a translation to x, y, the rotation and the translation -x,-y ignition::math::Matrix3d transToXy(1, 0, x, 0, 1, y, 0, 0, 1); ignition::math::Matrix3d transFromXy(1, 0, -x, 0, 1, -y, 0, 0, 1); ignition::math::Matrix3d rotate(cosa, -sina, 0, sina, cosa, 0, 0, 0, 1); ignition::math::Matrix3d m = transToXy * rotate * transFromXy; return m; } // we have no business being here gzerr << "Unknown transformation: " << transform << std::endl; ignition::math::Matrix3d m = ignition::math::Matrix3d::Identity; return m; } ///////////////////////////////////////////////// // This local helper function interpolates a bezier curve at t (between 0 and 1) ignition::math::Vector2d bezierInterpolate(double _t, const ignition::math::Vector2d &_p0, const ignition::math::Vector2d &_p1, const ignition::math::Vector2d &_p2, const ignition::math::Vector2d &_p3) { double t_1 = 1.0 - _t; double t_1_2 = t_1 * t_1; double t_1_3 = t_1_2 * t_1; double t2 = _t * _t; double t3 = t2 * _t; ignition::math::Vector2d p; p.X(t_1_3 * _p0.X() + 3 * _t * t_1_2 * _p1.X() + 3 * t2 * t_1 * _p2.X() + t3 * _p3.X()); p.Y(t_1_3 * _p0.Y() + 3 * _t * t_1_2 * _p1.Y() + 3 * t2 * t_1 * _p2.Y() + t3 * _p3.Y()); return p; } ///////////////////////////////////////////////// // This helper function adds bezier interpolations to a list of points void cubicBezier(const ignition::math::Vector2d &_p0, const ignition::math::Vector2d &_p1, const ignition::math::Vector2d &_p2, const ignition::math::Vector2d &_p3, double _step, std::vector &_points) { // we don't start at t = 0, but t = step... // so we assume that the first point is there (from the last move) double t = _step; while (t < 1.0) { auto p = bezierInterpolate(t, _p0, _p1, _p2, _p3); _points.push_back(p); t += _step; } // however we close the loop with the last point (t = 1) _points.push_back(_p3); } ///////////////////////////////////////////////// // This helper function computes the square of a number static double Sqr(float _x) { return _x * _x; } ///////////////////////////////////////////////// // This helper function computes the angle between 2 vectors, using acos static float VecAng(float _ux, float _uy, float _vx, float _vy) { double ux = _ux; double uy = _uy; double vx = _vx; double vy = _vy; double uMag = sqrt(ux * ux + uy * uy); double vMag = sqrt(vx * vx + vy * vy); double r = (ux * vx + uy * vy) / (uMag * vMag); if (r < -1.0) { r = -1.0; } else if (r > 1.0) { r = 1.0; } double a = acos(r); if (ux * vy < uy * vx) { return -a; } else { return a; } } ///////////////////////////////////////////////// // This helper function adds arc interpolations to a list of points void arcPath(const ignition::math::Vector2d &_p0, const double _rx, const double _ry, const double _rotxDeg, const size_t _largeArc, const size_t _sweepDirection, const ignition::math::Vector2d &_pEnd, const double _step, std::vector &_points) { // Ported from canvg (https://code.google.com/p/canvg/) double rx = _rx; double ry = _ry; double rotx = _rotxDeg / 180.0 * M_PI; double x1, y1, x2, y2, cx, cy, dx, dy, d; double x1p, y1p, cxp, cyp, s, sa, sb; double ux, uy, vx, vy, a1, da; double px = 0, py = 0, ptanx = 0, ptany = 0, t[6]; double sinrx, cosrx; double hda, kappa; x1 = _p0.X(); y1 = _p0.Y(); x2 = _pEnd.X(); y2 = _pEnd.Y(); dx = x1 - x2; dy = y1 - y2; d = sqrt(dx*dx + dy*dy); if (d < 1e-6 || rx < 1e-6 || ry < 1e-6) { // The arc degenerates to a line _points.push_back(_pEnd); return; } sinrx = sin(rotx); cosrx = cos(rotx); // Convert to center point parameterization. // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes // 1) Compute x1', y1' x1p = cosrx * dx / 2.0 + sinrx * dy / 2.0; y1p = -sinrx * dx / 2.0 + cosrx * dy / 2.0; d = Sqr(x1p) / Sqr(rx) + Sqr(y1p) / Sqr(ry); if (d > 1) { d = sqrt(d); rx *= d; ry *= d; } // 2) Compute cx', cy' s = 0.0; sa = Sqr(rx) * Sqr(ry) - Sqr(rx) * Sqr(y1p) - Sqr(ry) * Sqr(x1p); sb = Sqr(rx) * Sqr(y1p) + Sqr(ry) * Sqr(x1p); if (sa < 0.0) sa = 0.0; if (sb > 0.0) s = sqrt(sa / sb); if (_largeArc == _sweepDirection) { s = -s; } cxp = s * rx * y1p / ry; cyp = s * -ry * x1p / rx; // 3) Compute cx,cy from cx',cy' cx = (x1 + x2) / 2.0 + cosrx * cxp - sinrx * cyp; cy = (y1 + y2) / 2.0 + sinrx * cxp + cosrx * cyp; // 4) Calculate theta1, and delta theta. ux = (x1p - cxp) / rx; uy = (y1p - cyp) / ry; vx = (-x1p - cxp) / rx; vy = (-y1p - cyp) / ry; // initial angle a1 = VecAng(1.0, 0.0, ux, uy); // delta angle da = VecAng(ux, uy, vx, vy); if (_largeArc) { // Choose large arc if (da > 0.0) da = da - 2 * M_PI; else da = 2 * M_PI + da; } // rounding errors for half circles if (M_PI - fabs(da) < 0.001) { if (_sweepDirection) da = M_PI; else da = -M_PI; } // Approximate the arc using cubic spline segments. t[0] = cosrx; t[1] = sinrx; t[2] = -sinrx; t[3] = cosrx; t[4] = cx; t[5] = cy; // Split arc into max 90 degree segments. // The loop assumes an iteration per end point // (including start and end), this +1. size_t ndivs = static_cast(fabs(da) / (M_PI * 0.5) + 1.0); hda = (da / ndivs) / 2.0; kappa = fabs(4.0 / 3.0 * (1.0 - cos(hda)) / sin(hda)); if (da < 0.0) kappa = -kappa; for (size_t i = 0; i <= ndivs; ++i) { double x, y, tanx, tany, a; a = a1 + da * (1.0 * i /ndivs); dx = cos(a); dy = sin(a); // position xform point double pox = dx * rx; double poy = dy * ry; x = pox * t[0] + poy * t[2] + t[4]; y = pox * t[1] + poy * t[3] + t[5]; // tangent xform vec double tx = -dy * rx * kappa; double ty = dx * ry * kappa; tanx = tx * t[0] + ty * t[2]; tany = tx * t[1] + ty * t[3]; if (i > 0) { ignition::math::Vector2d p0(px, py); ignition::math::Vector2d p1(px + ptanx, py + ptany); ignition::math::Vector2d p2(x - tanx, y - tany); ignition::math::Vector2d p3(x, y); cubicBezier(p0, p1, p2, p3, _step, _points); } px = x; py = y; ptanx = tanx; ptany = tany; } } ///////////////////////////////////////////////// SvgError::SvgError(const std::string &_what) : std::runtime_error(_what) { } ///////////////////////////////////////////////// ignition::math::Vector2d SVGLoader::SubpathToPolyline( const std::vector &_subpath, ignition::math::Vector2d _last, std::vector &_polyline) { GZ_ASSERT(_polyline.size() == 0, "polyline not empty"); for (SVGCommand cmd: _subpath) { size_t i = 0; size_t count = cmd.numbers.size(); switch (cmd.cmd) { case 'm': case 'l': while (i < count) { ignition::math::Vector2d p; p.X(cmd.numbers[i+0]); p.Y(cmd.numbers[i+1]); // m and l cmds are relative to the last point p.X() += _last.X(); p.Y() += _last.Y(); _polyline.push_back(p); _last = p; i += 2; } break; case 'M': case 'L': while (i < count) { ignition::math::Vector2d p; p.X(cmd.numbers[i+0]); p.Y(cmd.numbers[i+1]); _polyline.push_back(p); _last = p; i += 2; } break; case 'C': while (i < count) { ignition::math::Vector2d p0 = _last; ignition::math::Vector2d p1, p2, p3; p1.X(cmd.numbers[i+0]); p1.Y(cmd.numbers[i+1]); p2.X(cmd.numbers[i+2]); p2.Y(cmd.numbers[i+3]); p3.X(cmd.numbers[i+4]); p3.Y(cmd.numbers[i+5]); cubicBezier(p0, p1, p2, p3, this->dataPtr->resolution, _polyline); _last = p3; i += 6; } break; case 'c': while (i < count) { ignition::math::Vector2d p0 = _last; ignition::math::Vector2d p1, p2, p3; p1.X(cmd.numbers[i+0] + _last.X()); p1.Y(cmd.numbers[i+1] + _last.Y()); p2.X(cmd.numbers[i+2] + _last.X()); p2.Y(cmd.numbers[i+3] + _last.Y()); p3.X(cmd.numbers[i+4] + _last.X()); p3.Y(cmd.numbers[i+5] + _last.Y()); cubicBezier(p0, p1, p2, p3, this->dataPtr->resolution, _polyline); _last = p3; i += 6; } break; case 'A': while (i < count) { ignition::math::Vector2d p0 = _last; double rx = cmd.numbers[i+0]; double ry = cmd.numbers[i+1]; double xRot = cmd.numbers[i+2]; unsigned int arc(cmd.numbers[i+3]); unsigned int sweep(cmd.numbers[i+4]); ignition::math::Vector2d pEnd; pEnd.X(cmd.numbers[i+5]); pEnd.Y(cmd.numbers[i+6]); arcPath(p0, rx, ry, xRot, arc, sweep, pEnd, this->dataPtr->resolution, _polyline); _last = pEnd; i += 7; } break; case 'a': while (i < count) { ignition::math::Vector2d p0 = _last; double rx = cmd.numbers[i+0]; double ry = cmd.numbers[i+1]; double xRot = cmd.numbers[i+2]; unsigned int arc(cmd.numbers[i+3]); unsigned int sweep(cmd.numbers[i+4]); ignition::math::Vector2d pEnd; pEnd.X(cmd.numbers[i+5] + _last.X()); pEnd.Y(cmd.numbers[i+6] + _last.Y()); arcPath(p0, rx, ry, xRot, arc, sweep, pEnd, this->dataPtr->resolution, _polyline); _last = pEnd; i += 7; } // Z and z indicate closed path. // just add the first point to the list case 'Z': case 'z': { auto &p = _polyline.front(); if (_polyline.back().Distance(p) > 1e-5) { gzerr << "Zz" << _polyline.back().Distance(p) << std::endl; _polyline.push_back(p); } break; } default: gzerr << "Unexpected SVGCommand value: " << cmd.cmd << std::endl; } } return _last; } ///////////////////////////////////////////////// SVGLoader::SVGLoader(unsigned int _samples) { this->dataPtr = new SVGLoaderPrivate(); if (_samples == 0) { std::string m("The number of samples cannot be 0"); SvgError e(m); throw e; } this->dataPtr->resolution = 1.0/_samples; } ///////////////////////////////////////////////// SVGLoader::~SVGLoader() { delete(this->dataPtr); } ///////////////////////////////////////////////// void SVGLoader::SplitSubpaths(const std::vector &_cmds, std::vector< std::vector > &_subpaths) { if (_cmds.empty()) { std::ostringstream os; os << "SVGPath has no commands"; SvgError x(os.str()); throw x; } for (SVGCommand cmd: _cmds) { if (tolower(cmd.cmd) == 'm') { // the path contains a subpath std::vector sub; _subpaths.push_back(sub); } // get a reference to the latest subpath std::vector &subpath = _subpaths.back(); // give the cmd to the latest subpath.push_back(cmd); } } ///////////////////////////////////////////////// void SVGLoader::ExpandCommands( const std::vector< std::vector > &_subpaths, SVGPath &_path) { for (std::vector compressedSubpath :_subpaths) { // add new subpath _path.subpaths.push_back(std::vector()); // get a reference std::vector &subpath = _path.subpaths.back(); // copy the cmds with repeating commands, grouping the numbers for (SVGCommand xCmd : compressedSubpath) { unsigned int numberCount = 0; if (tolower(xCmd.cmd) == 'a') numberCount = 7; if (tolower(xCmd.cmd) == 'c') numberCount = 6; if (tolower(xCmd.cmd) == 'm') numberCount = 2; if (tolower(xCmd.cmd) == 'l') numberCount = 2; if (tolower(xCmd.cmd) == 'v') numberCount = 1; if (tolower(xCmd.cmd) == 'h') numberCount = 1; if (tolower(xCmd.cmd) == 'z') subpath.push_back(xCmd); // group numbers together and repeat the command // for each group unsigned int n = 0; size_t size = xCmd.numbers.size(); while (n < size) { subpath.push_back(SVGCommand()); SVGCommand &cmd = subpath.back(); cmd.cmd = xCmd.cmd; for (size_t i = 0; i < numberCount; ++i) { cmd.numbers.push_back(xCmd.numbers[i+n]); } n += numberCount; } } } } ///////////////////////////////////////////////// void SVGLoader::GetPathCommands(const std::vector &_tokens, SVGPath &_path) { std::vector cmds; std::string lookup = "aAcCmMqQlLvVhHzZ"; char lastCmd = 'x'; std::vector numbers; for (std::string token: _tokens) { // new command? if (lookup.find(token[0]) == std::string::npos) { // its just numbers std::vector numberStrs; split(token, ',', numberStrs); for (std::string numberStr : numberStrs) { double f = atof(numberStr.c_str()); numbers.push_back(f); } } else { if (lastCmd != 'x') { SVGCommand c; c.cmd = lastCmd; c.numbers = numbers; cmds.push_back(c); } // its new command lastCmd = token[0]; numbers.resize(0); } } // the last command if (lastCmd != 'x') { SVGCommand c; c.cmd = lastCmd; c.numbers = numbers; cmds.push_back(c); } // split the commands into sub_paths std::vector< std::vector< SVGCommand> > subpaths; this->SplitSubpaths(cmds, subpaths); this->ExpandCommands(subpaths, _path); // the starting point for the subpath // it is the end point of the previous one ignition::math::Vector2d p; for (std::vector subpath : subpaths) { _path.polylines.push_back(std::vector()); std::vector &polyline = _path.polylines.back(); p = this->SubpathToPolyline(subpath, p, polyline); } // if necessary, apply transform to p and polyline if (_path.transform != ignition::math::Matrix3d::Identity) { // we need to transform all the points in the path for (auto &polyline : _path.polylines) { for (auto &polyPoint : polyline) { // make a 3d vector form the 2d point ignition::math::Vector3d point3(polyPoint.X(), polyPoint.Y(), 1); // matrix multiply to get the new point, then save new coords in place auto transformed = _path.transform * point3; polyPoint.X(transformed.X()); polyPoint.Y(transformed.Y()); } } } } ///////////////////////////////////////////////// void SVGLoader::GetPathAttribs(TiXmlElement *_pElement, SVGPath &_path) { GZ_ASSERT(_pElement, "empty XML element where a path was expected"); _path.transform = ignition::math::Matrix3d::Identity; TiXmlAttribute *pAttrib = _pElement->FirstAttribute(); // this attribute contains a list of coordinates std::vector tokens; while (pAttrib) { std::string name = lowercase(pAttrib->Name()); std::string value = pAttrib->Value(); if (name == "style") { _path.style = value; } else if (name == "id") { _path.id = value; } else if (name == "transform") { _path.transform = ParseTransformMatrixStr(value); } else if (name == "d") { // load in the path parameters split(value, ' ', tokens); } else { gzwarn << "Ignoring attribute \"" << name << "\" in path" << std::endl; } pAttrib = pAttrib->Next(); } // Now that all attributes are loaded, we can compute the values this->GetPathCommands(tokens, _path); } ///////////////////////////////////////////////// void SVGLoader::GetSvgPaths(TiXmlNode *_pParent, std::vector &_paths) { if (!_pParent) return; TiXmlNode *pChild; int t = _pParent->Type(); std::string name; if ( t == TiXmlNode::TINYXML_ELEMENT) { name = lowercase(_pParent->Value()); if (name == "path") { TiXmlElement *element = _pParent->ToElement(); SVGPath p; this->GetPathAttribs(element, p); _paths.push_back(p); } // skip defs node that can contain path // elements that are not actual paths. if (name == "defs") { return; } } for (pChild = _pParent->FirstChild(); pChild != 0; pChild = pChild->NextSibling()) { this->GetSvgPaths(pChild, _paths); } } ///////////////////////////////////////////////// bool SVGLoader::Parse(const std::string &_filename, std::vector &_paths) { try { // load the named file and dump its structure to STDOUT TiXmlDocument doc(_filename.c_str()); if (!doc.LoadFile()) { std::ostringstream os; gzerr << "Failed to load file " << _filename << std::endl; gzerr << os.str() << std::endl; return false; } this->GetSvgPaths(&doc, _paths); return true; } catch(SvgError &e) { gzerr << e.what() << std::endl; } return false; } ///////////////////////////////////////////////// void SVGLoader::DumpPaths(const std::vector &_paths, std::ostream &_out) const { // this prints an html document that allows to debug // SVG parsing issues. The points are generated in // a loop between the header and footer. std::string header = R"***(
Xoff:
Yoff:
Scale:
Your browser does not support the canvas element. )***"; _out << header << std::endl; _out << "var svg = [];" << std::endl; for (SVGPath path : _paths) { _out << "svg.push({name:\"" << path.id; _out << "\", subpaths:[], style: \""; _out << path.style << "\"}); " << std::endl; _out << "svg[svg.length-1].subpaths = ["; char psep = ' '; for (unsigned int i = 0; i < path.polylines.size(); ++i) { std::vector poly = path.polylines[i]; _out << psep << "[" << std::endl; psep = ','; char sep = ' '; for (ignition::math::Vector2d p : poly) { _out << " " << sep << " [" << p.X() << ", " << p.Y() << "]" < &_paths, double _tol, std::vector< std::vector > &_closedPolys, std::vector< std::vector > &_openPolys) { // first we extract all polyline into a vector of line segments std::list > segments; for (auto const &path : _paths) { for (auto const &poly : path.polylines) { ignition::math::Vector2d startPoint = poly[0]; for (unsigned int i =1; i < poly.size(); ++i) { const ignition::math::Vector2d &endPoint = poly[i]; double length = endPoint.Distance(startPoint); if (length < _tol) { gzmsg << "Ignoring short segment (length: " << length << ")" < polyline; auto &s = segments.front(); polyline.push_back(s.first); polyline.push_back(s.second); // remove the segment from the list segments.pop_front(); // this flag will be false if the polyline has no // new segment bool segmentFound = true; // this flag is true when the polyline is closed bool loopClosed = false; while (segmentFound && !loopClosed) { // find the segment in the polyline segmentFound = false; for (auto it = segments.begin(); it != segments.end(); ++it) { auto seg = *it; ignition::math::Vector2d nextPoint; if (Vector2dCompare(polyline.back(), seg.first, _tol)) { nextPoint = seg.second; segmentFound = true; } if (Vector2dCompare(polyline.back(), seg.second, _tol)) { nextPoint = seg.first; segmentFound = true; } if (segmentFound) { // remove the segment from the list of all remaining segments segments.erase(it); // add the new point to the polyline polyline.push_back(nextPoint); // verify if the polyline is closed if (Vector2dCompare(nextPoint, polyline[0], _tol)) { // the loop is closed, we don't need another segment loopClosed = true; } // the segment has been found // get out of the for loop. break; } } } // the new polyline is complete if (loopClosed) { _closedPolys.push_back(polyline); } else { gzmsg << "Line segments that are not part of a closed paths have" << " been found with the current minimum distance of " << _tol << " between 2 points." << std::endl << std::endl; _openPolys.push_back(polyline); } } }