/* ---- This file is part of SECONDO. Copyright (C) 2019, Faculty of Mathematics and Computer Science, Database Systems for New Applications. SECONDO is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. SECONDO is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SECONDO; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ---- //[<] [\ensuremath{<}] //[>] [\ensuremath{>}] \setcounter{tocdepth}{3} \tableofcontents 1 Plane */ #include "GpPlane.h" using namespace pointcloud2; using namespace std; std::string GpPlane::getCaption(bool plural) const { return plural ? "planes" : "plane"; } unsigned GpPlane::getTupleSize() const { return TUPLE_SIZE_PLANE; } std::unique_ptr>> GpPlane::projectTupleToDual(const std::vector& tuple, const ParamsAnalyzeGeom& params, const Rectangle& boxOfEntireCloud) { assert(tuple.size() == TUPLE_SIZE_PLANE); // support vector (= dt. Stuetzvektor) const SamplePoint& p = tuple[0]; double px = p._coords[0]; double py = p._coords[1]; double pz = p._coords[2]; // vectors (q - p) and (r - p) const SamplePoint& q = tuple[1]; double qpx = q._coords[0] - px; double qpy = q._coords[1] - py; double qpz = q._coords[2] - pz; const SamplePoint& r = tuple[2]; double rpx = r._coords[0] - px; double rpy = r._coords[1] - py; double rpz = r._coords[2] - pz; // normal vector n = (q - p) x (r - p) double nx = qpy * rpz - qpz * rpy; double ny = qpz * rpx - qpx * rpz; double nz = qpx * rpy - qpy * rpx; double nLengthSquare = nx * nx + ny * ny + nz * nz; if (nLengthSquare <= 0.0) return nullptr; // p, q, r are on the same line double nLength = std::sqrt(nLengthSquare); nx /= nLength; ny /= nLength; nz /= nLength; /* to create a dual point for this plane, an obvious idea would be to * use the coefficients of the equation a*x + b*y + c*z = d defining * this plane, where (a,b,c) is the direction and - if (a,b,c) is * normalized - d the length of the normal vector pointing from the * origin to the plane. However, this would put remote planes at a severe * disadvantage: at a high distance from the origin, a small diffusion * in the original points will lead to a very different normal vector * making it impossible for DBSCAN to find an appropriate cluster. (Note * that intersection point of the normal vector and the plane can be at a * very different place from where the pointcloud points are that belong * to this plane!) * * Therefore, rather than the origin, we use different "points of * reference" (i.e. "alternative origins") to express the normal vector. * These points of reference will always be close to the actual points * in the cloud, making the normal vector short and thus enabling DBSCAN * to cluster dual points when there is some diffusion. Obviously, we * will need to enter the respective "point of reference" as part of * the dual point (indices 0-2) along with the normal vector (indices 3-5). * We use a "raster" to get a point of reference in the vicinity of the * first point p in the tuple. * * The fact that, even with no diffusion at all, the same geometric plane * may now be represented by various dual points (due to different points * of reference) is "corrected" later: The planes that are identified as * a result of the DBSCAN in dual space will be matched with the actual * points using the getPredicateForShape and getBboxPredicateForShape * predicates, and those predicates are "universal", i.e. independent * of the various points of reference. */ // get a "point of reference" c in the vicinity of these points // (esp. of point p) double cellExtent = 8.0 * params._minimumObjectExtent; double cx = rasterPos(px, cellExtent); double cy = rasterPos(py, cellExtent); double cz = rasterPos(pz, cellExtent); // equation for this plane: // 1) a*x + b*y + c*z = d if (x,y,z) is relative to the origin, or // 2) a*x + b*y + c*z = dd if (x, y, z) is relative to c // double a = nx; // double b = ny; // double c = nz; double dd = nx * (px - cx) + ny * (py - cy) + nz * (pz - cz); double d = nx * px + ny * py + nz * pz; // we challenge this sphere with one or several additional point from // the same neighborhood in order to avoid noise in dual space for (unsigned i = 0; i < CHALLENGE_COUNT_PLANE ; ++i) { const SamplePoint& point = tuple[EQUATION_COUNT_PLANE + i]; // the test is the same as in getPredicateForShape: double dist = nx * point._coords[0] + ny * point._coords[1] + nz * point._coords[2] - d; if (std::abs(dist) > params._matchTolerance) return nullptr; // challenge failed } /* we do not use a, b, c, d directly in the dual point, since |a|, |b|, * |c| are scaled to be <= 1.0, while dd uses an actual scaling of the * Pointcloud2, and both scalings could be completely different, making * cluster distances in the dimensions a, b, c incommensurable with * cluster distances in d. * * Instead, we simply use the vector from the point of reference (c) to * the plane. Thus, all dimensions are commensurable. */ // create the dual point DbScanPoint dualPoint; dualPoint.initialize(); dualPoint._coords[0] = cx; dualPoint._coords[1] = cy; dualPoint._coords[2] = cz; dualPoint._coords[3] = nx * dd; dualPoint._coords[4] = ny * dd; dualPoint._coords[5] = nz * dd; if (REPORT_DETAILED) { std::cout << "+ dual point for " << getCaption(false) << ": " << dualPoint.toString() << std::endl; } unique_ptr>> results( new vector>()); results->push_back(dualPoint); return results; } double GpPlane::rasterPos(const double pos, const double cellExtent) { return cellExtent * std::floor(pos / cellExtent); } GeomPredicate GpPlane::getPredicateForShape( const PointBase& shape, const ParamsAnalyzeGeom& params) const { // interpret the coords of the dual point: get the "point of reference" c // and the normal vector from c to the plane with length dd (see the // above explanation in projectTupleToDual) double cx = shape._coords[0]; double cy = shape._coords[1]; double cz = shape._coords[2]; double ddnx = shape._coords[3]; double ddny = shape._coords[4]; double ddnz = shape._coords[5]; double dd = std::sqrt(ddnx * ddnx + ddny * ddny + ddnz * ddnz); // equation for this plane: // 1) a*x + b*y + c*z = d if (x,y,z) is relative to the origin, or // 2) a*x + b*y + c*z = dd if (x, y, z) is relative to c double a = ddnx / dd; double b = ddny / dd; double c = ddnz / dd; double d = dd + a * cx + b * cy + c * cz; double tolerance = params._matchTolerance; // the predicate uses a, b, c, d (so it is "universal" and indepentent // from the points of reference that were used for DBSCAN in dual space) GeomPredicate predicate = [a, b, c, d, tolerance](const DbScanPoint& point) { // get the distance of this point from the plane double dist = a * point._coords[0] + b * point._coords[1] + c * point._coords[2] - d; return std::abs(dist) <= tolerance; }; return predicate; } BboxPredicate GpPlane::getBboxPredicateForShape( const PointBase& shape, const ParamsAnalyzeGeom& params) const { // interpret the coords of the dual point: get the "point of reference" c // and the normal vector from c to the plane with length dd (see the // above explanation in projectTupleToDual) double cx = shape._coords[0]; double cy = shape._coords[1]; double cz = shape._coords[2]; double ddnx = shape._coords[3]; double ddny = shape._coords[4]; double ddnz = shape._coords[5]; double dd = std::sqrt(ddnx * ddnx + ddny * ddny + ddnz * ddnz); // equation for this plane: // 1) a*x + b*y + c*z = d if (x,y,z) is relative to the origin, or // 2) a*x + b*y + c*z = dd if (x, y, z) is relative to c double a = ddnx / dd; double b = ddny / dd; double c = ddnz / dd; double d = dd + a * cx + b * cy + c * cz; double tolerance = params._matchTolerance; // the predicate uses a, b, c, d (so it is "universal" and indepentent // from the points of reference that were used for DBSCAN in dual space) BboxPredicate predicate = [a, b, c, d, tolerance](const Rectangle2& bbox) { // determine whether all corners of the bbox are on the same side // of the plane, in which case there is no intersection, and false is // returned; otherwise, the bbox intersects with the plane. size_t combinations = (1 << DIMENSIONS); bool sign = false; for (size_t cornerBits = 0; cornerBits < combinations; ++cornerBits) { double x = (cornerBits & 1) ? bbox.MinD(0) - tolerance : bbox.MaxD(0) + tolerance; double y = (cornerBits & 2) ? bbox.MinD(1) - tolerance : bbox.MaxD(1) + tolerance; double z = (cornerBits & 4) ? bbox.MinD(2) - tolerance : bbox.MaxD(2) + tolerance; double dist = a * x + b * y + c * z - d; if (cornerBits == 0) sign = std::signbit(dist); else if (sign != std::signbit(dist)) return true; } return false; }; return predicate; }