This repository has been archived on 2020-04-08. You can view files and clone it, but cannot push or open issues or pull requests.
Files
Indoor/grid/factory/v2/GridFactory.h
2018-10-25 11:50:12 +02:00

710 lines
20 KiB
C++
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* © Copyright 2014 Urheberrechtshinweis
* Alle Rechte vorbehalten / All Rights Reserved
*
* Programmcode ist urheberrechtlich geschuetzt.
* Das Urheberrecht liegt, soweit nicht ausdruecklich anders gekennzeichnet, bei Frank Ebner.
* Keine Verwendung ohne explizite Genehmigung.
* (vgl. § 106 ff UrhG / § 97 UrhG)
*/
#ifndef GRIDFACTORY_V2_H
#define GRIDFACTORY_V2_H
#include <string>
#include <unordered_set>
#include <set>
#include "../../../math/Math.h"
#include "../../../floorplan/v2/Floorplan.h"
#include "Helper.h"
#include "Stairs2.h"
#include "Elevators.h"
#include "../../../geo/Units.h"
#include "../../../geo/Circle2.h"
#include "../../GridNodeBBox.h"
#include "../../Grid.h"
#include "../../../misc/Debug.h"
#include <functional>
#include "../../../floorplan/3D/objects/OBJPool.h"
#include "../../../geo/ConvexHull2.h"
#include "GridFactoryListener.h"
template <typename T> class GridFactory {
/** logging name */
static constexpr const char* name = "GridFac2";
private:
/** the grid to build into */
Grid<T>& grid;
/** calculation helper */
Helper<T> helper;
/** stair builder */
Stairs<T> stairs;
/** elevator builder */
Elevators<T> elevators;
std::vector<GridPoint> withinRemovePolygon;
bool _buildStairs = true;
bool _removeIsolated = true;
bool _addTightToObstacle = false;
bool _abortOnError = true;
public:
/** ctor with the grid to fill */
explicit GridFactory(Grid<T>& grid) : grid(grid), helper(grid), stairs(grid), elevators(grid) {
}
void setAbortOnError(const bool abort) {this->_abortOnError = abort;}
void setAddTightToObstacle(const bool tight) {this->_addTightToObstacle = tight;}
/** whether or not to build stairs */
void setBuildStairs(const bool build) {this->_buildStairs = build;}
/** whether or not to remove isolated nodes */
void setRemoveIsolated(const bool remove) {this->_removeIsolated = remove;}
/** does the given grid-node have a stair-neighbor? */
bool hasStairNeighbor(const T& node) const {
for (const T& n : grid.neighbors(node)) {
if (n.getType() == GridNode::TYPE_STAIR) {return true;}
}
return false;
}
/** build using the given map */
void build(const Floorplan::IndoorMap* map, GridFactoryListener* listener = nullptr) {
Log::add(name, "building grid from IndoorMap", true);
const int total = map->floors.size()*3 + 1;
int cur = 0;
// build all the floors
if (listener) {listener->onGridBuildUpdateMajor("adding floors");}
for (Floorplan::Floor* f : map->floors) {
addFloor(f, listener);
if (listener) {listener->onGridBuildUpdateMajor(total, ++cur);}
}
// build all stairs
if (listener) {listener->onGridBuildUpdateMajor("adding stairs");}
if (_buildStairs) {
for (Floorplan::Floor* f : map->floors) {
buildStairs(map, f, listener);
if (listener) {listener->onGridBuildUpdateMajor(total, ++cur);}
}
}
// build all elevators
if (listener) {listener->onGridBuildUpdateMajor("adding elevators");}
if (_buildStairs) {
for (Floorplan::Floor* f : map->floors) {
buildElevators(f, listener);
if (listener) {listener->onGridBuildUpdateMajor(total, ++cur);}
}
}
// remove nodes within remove-OutlinePolygons
// this must happen AFTER stairs have been added, otherwise stairs might not be connectable due to removed/missing nodes.
// also, within this loop we prevent the deltion of nodes that are a direct neighbor of a stair!
// [otherwise the same thing would happen again!]
if (true) {
for (const GridPoint& gp : withinRemovePolygon) {
T* n = (T*) grid.getNodePtrFor(gp);
// delete if node is present and is no direct neighbor of a stair
if (n && !hasStairNeighbor(*n)) {
grid.remove(*n);
}
}
// remove all nodes that were just marked for removal
grid.cleanup();
}
// remove isolated nodes
if (_removeIsolated) {
if (listener) {listener->onGridBuildUpdateMajor("removing isolated nodes");}
removeIsolatedNodes();
if (listener) {listener->onGridBuildUpdateMajor(total, ++cur);}
}
}
/** get the 2D-bbox for the given floor's outline */
BBox2 getFloorOutlineBBox(const Floorplan::FloorOutline& outline) {
BBox2 bb;
for (Floorplan::FloorOutlinePolygon* poly : outline) {
for (Point2 p : poly->poly.points) {
bb.add(p * 100); // convert m to cm
}
}
return bb;
}
struct PartOfOutline {
bool contained = false; // contained within at-least one polygon?
bool markedForRemove = false; // part of a "to-be-removed" polygon?
bool outdoor = false; // otherwise: indoor
};
/** get the part of outline the given location belongs to. currently: none, indoor, outdoor */
static PartOfOutline isPartOfFloorOutline(const int x_cm, const int y_cm, const Floorplan::FloorOutline& outline) {
// assume the point is not part of the outline
PartOfOutline res;
res.contained = false;
res.markedForRemove = false;
// process every outline polygon
for (Floorplan::FloorOutlinePolygon* poly : outline) {
HelperPoly pol(*poly);
if (pol.contains(Point2(x_cm, y_cm))) {
// mark as "contained"
res.contained = true;
// belongs to a "remove" polygon? -> directly ignore this location!
if (poly->method == Floorplan::OutlineMethod::REMOVE) {
res.markedForRemove = true;
}
// belongs to a "add" polygon? -> remember until all polygons were checked
// [might still belong to a "remove" polygon]
if (poly->outdoor) {
res.outdoor = true; // belonging to an outdoor region overwrites all other belongings
}
}
}
// done
return res;
}
/** add the given floor to the grid */
void addFloor(const Floorplan::Floor* floor, GridFactoryListener* listener = nullptr) {
Log::add(name, "adding floor '" + floor->name + "'", true);
if (listener) {listener->onGridBuildUpdateMinor("adding floor " + floor->name);}
const BBox2 bbox = getFloorOutlineBBox(floor->outline);
const int x1 = helper.align(bbox.getMin().x);
const int x2 = helper.align(bbox.getMax().x);
const int y1 = helper.align(bbox.getMin().y);
const int y2 = helper.align(bbox.getMax().y);
const int z_cm = std::round(floor->atHeight*100);
const int total = (x2-x1) / helper.gridSize();
int cur = 0;
int numNodes = 0;
// all 3D objects within the floor
std::vector<HelperPoly> objObstacles;
for (const Floorplan::FloorObstacle* fo : floor->obstacles) {
// process all object-obstalces
const Floorplan::FloorObstacleObject* foo = dynamic_cast<const Floorplan::FloorObstacleObject*>(fo);
if (foo) {
// get the obstacle
const Floorplan3D::Obstacle3D obs = Floorplan3D::OBJPool::get().getObject(foo->file).scaled(foo->scale).rotated_deg(foo->rot).translated(foo->pos);
// construct its 2D convex hull (in centimter)
HelperPoly poly;
for (const Point2 p : ConvexHull2::get(obs.getPoints2D())) {poly.add(p*100);}
objObstacles.push_back(poly);
}
}
// does any of the obj-obstalces contain the given point?
auto isPartOfObject = [&objObstacles, this] (const GridNodeBBox& bb) {
for (HelperPoly poly : objObstacles) {
//if (!_addTightToObstacle) {
if (poly.contains(bb.getCorner1())) {return true;}
if (poly.contains(bb.getCorner2())) {return true;}
if (poly.contains(bb.getCorner3())) {return true;}
if (poly.contains(bb.getCorner4())) {return true;}
//} else {
// //poly.shrink(1);
// if (poly.contains(bb.getCenter())) {return true;}
//}
}
return false;
};
// build grid-points for floor-outline
for(int x_cm = x1; x_cm < x2; x_cm += helper.gridSize()) {
for (int y_cm = y1; y_cm < y2; y_cm += helper.gridSize()) {
// does the outline-polygon contain this position?
const PartOfOutline part = isPartOfFloorOutline(x_cm, y_cm, floor->outline);
if (!part.contained) {continue;}
// bbox to check intersection with the floorplan
GridNodeBBox bbox(GridPoint(x_cm, y_cm, z_cm), helper.gridSize());
// slightly grow the bbox to ensure even obstacles that are directly aligned to the bbox are hit
bbox.grow(0.1337);
if (!_addTightToObstacle) {
if (intersects(bbox, floor)) {continue;}
}
// intersection with objects?
if (isPartOfObject(bbox)) {continue;}
// add to the grid [once]
T t(x_cm, y_cm, z_cm);
if (grid.hasNodeFor(t)) {continue;}
updateType(t, part, bbox, floor);
grid.add(t);
// node part of remove-region?
// if so, remove >>AFTER<< stairs have been added
if (part.markedForRemove) {
withinRemovePolygon.push_back(t);
}
// debug
++numNodes;
}
if (listener) {listener->onGridBuildUpdateMinor(total, ++cur);}
}
Log::add(name, "added " + std::to_string(numNodes) + " nodes");
// connect the g
connectAdjacent(floor, z_cm);
}
void buildStairs(const Floorplan::IndoorMap* map, const Floorplan::Floor* floor, GridFactoryListener* listener = nullptr) {
stairs.setAbortOnError(_abortOnError);
const int total = floor->stairs.size();
int cur = 0;
// process each stair within the floor
for (const Floorplan::Stair* stair : floor->stairs) {
if (listener) {listener->onGridBuildUpdateMinor("adding " + floor->name + " stair " + std::to_string(cur+1));}
stairs.build(map, floor, stair);
if (listener) {listener->onGridBuildUpdateMinor(total, ++cur);}
}
// cleanup
stairs.finalize();
}
void buildElevators(const Floorplan::Floor* floor, GridFactoryListener* listener = nullptr) {
const int total = floor->elevators.size();
int cur = 0;
// process each elevator within the floor
for (const Floorplan::Elevator* elevator : floor->elevators) {
if (listener) {listener->onGridBuildUpdateMinor("adding " + floor->name + " elevator " + std::to_string(cur+1));}
elevators.build(floor, elevator);
if (listener) {listener->onGridBuildUpdateMinor(total, ++cur);}
}
}
/** connect all neighboring nodes part of the given index-vector */
void connectAdjacent(const std::vector<int>& indices) {
for (const int idx : indices) {
// connect the node with its neighbors
connectAdjacent(grid[idx]);
}
}
/** connect all neighboring nodes located on the given height-plane */
void connectAdjacent(const Floorplan::Floor* floor, const int z_cm) {
Log::add(name, "connecting all adjacent nodes within floor '" + floor->name + "' (atHeight: " + std::to_string(z_cm) + "cm)", false);
Log::tick();
// connect adjacent grid-points
for (T& n1 : grid) {
// not the floor we are looking for? -> skip (ugly.. slow(er))
if (n1.z_cm != z_cm) {continue;}
// connect the node with its neighbors
connectAdjacent(floor, n1);
}
Log::tock();
}
/**
* connect the given node with its neighbors.
* even though a node has a neighbor, it might still be blocked by an obstacle:
* e.g. a 45° wall directly between nodes. a neighbor exists, but is unreachable due to the wall.
* we thus perform an additional intersection check with all obstacles within the floor the node n1 belongs to
*/
void connectAdjacent(const Floorplan::Floor* floor, T& n1) {
const int gridSize_cm = grid.getGridSize_cm();
// square around the node
for (int x = -1; x <= +1; ++x) {
for (int y = -1; y <= +1; ++y) {
// skip the center (node itself)
if ((x == y) && (x == 0)) {continue;}
// position of the potential neighbor
const int ox = n1.x_cm + x * gridSize_cm;
const int oy = n1.y_cm + y * gridSize_cm;
const GridPoint p(ox, oy, n1.z_cm);
// does the grid contain the potential neighbor?
const T* n2 = grid.getNodePtrFor(p);
if (n2 != nullptr) {
if (isBlocked(floor, n1, *n2)) {continue;} // is there a (e.g. small) obstacle between the two?
// NOTE
// if you have two floors at the >same< height, and their outlines overlap, this one is needed!
if (!n1.hasNeighbor(n2->getIdx())) {
grid.connectUniDir(n1, *n2); // UNI-dir connection as EACH node is processed!
}
}
}
}
}
/** add the inverted version of the given z-layer */
void addInverted(const Grid<T>& gIn, const float z_cm) {
// get the original grid's bbox
BBox3 bb = gIn.getBBox();
// ensure we have an outer boundary
bb.grow(gIn.getGridSize_cm() * 2);
const int gridSize_cm = grid.getGridSize_cm();
// build new grid-points
for(int x_cm = bb.getMin().x; x_cm <= bb.getMax().x; x_cm += gridSize_cm) {
for (int y_cm = bb.getMin().y; y_cm < bb.getMax().y; y_cm += gridSize_cm) {
// does the input-grid contain such a point?
GridPoint gp(x_cm, y_cm, z_cm);
if (gIn.hasNodeFor(gp)) {continue;}
// add to the grid
grid.add(T(x_cm, y_cm, z_cm));
}
}
}
// // TODO: how to determine the starting index?!
// // IDEAS: find all segments:
// // start at a random point, add all connected points to the set
// // start at a NEW random point ( not part of the already processed points), add connected points to a new set
// // repeat until all points processed
// // how to handle multiple floor layers?!?!
// // run after all floors AND staircases were added??
// // OR: random start, check segment size, < 50% of all nodes? start again
// void removeIsolatedNodes() {
// Log::add(name, "searching for isolated nodes");
// // get largest connected region
// std::unordered_set<int> set;
// do {
// const int idxStart = rand() % grid.getNumNodes();
// set.clear();
// Log::add(name, "getting connected region starting at " + (std::string) grid[idxStart]);
// getConnected(grid[idxStart], set);
// Log::add(name, "region size is " + std::to_string(set.size()) + " nodes");
// } while (set.size() < 0.5 * grid.getNumNodes());
// // remove all other
// Log::add(name, "removing the isolated nodes");
// for (int i = 0; i < grid.getNumNodes(); ++i) {
// if (set.find(i) == set.end()) {grid.remove(i);}
// }
// // clean the grid
// grid.cleanup();
// }
void removeIsolatedNodes() {
//std::cout << "todo: remove" << std::endl;
//return;
// try to start at the first stair
for (T& n : grid) {
if (n.getType() == GridNode::TYPE_STAIR) {removeIsolatedNodes(n); return;}
}
// no stair found? try to start at the first node
removeIsolatedNodes(grid[0]);
}
/** remove all nodes not connected to n1 */
void removeIsolatedNodes(T& n1) {
// get the connected region around n1
Log::add(name, "getting set of all nodes connected to " + (std::string) n1, false);
Log::tick();
std::unordered_set<int> set;
getConnected(n1, set);
Log::tock();
//const int numToRemove = grid.getNumNodes() - set.size();
//int numRemoved = 0;
// remove all other
Log::add(name, "removing all nodes NOT connected to " + (std::string) n1, false);
Log::tick();
for (T& n2 : grid) {
if (set.find(n2.getIdx()) == set.end()) {
// sanity check
// wouldn't make sense that a stair-node is removed..
// maybe something went wrong elsewhere???
Assert::notEqual(n2.getType(), GridNode::TYPE_STAIR, "detected an isolated stair?!");
Assert::notEqual(n2.getType(), GridNode::TYPE_ELEVATOR, "detected an isolated elevator?!");
//Assert::notEqual(n2.getType(), GridNode::TYPE_DOOR, "detected an isolated door?!");
// proceed ;)
grid.remove(n2);
//++numRemoved;
//std::cout << numRemoved << ":" << numToRemove << std::endl;
}
}
Log::tock();
// clean the grid (physically delete the removed nodes)
grid.cleanup();
}
private:
/** recursively get all connected nodes and add them to the set */
void getConnected(T& n1, std::unordered_set<int>& visited) {
std::unordered_set<int> toVisit;
toVisit.insert(n1.getIdx());
// run while there are new nodes to visit
while(!toVisit.empty()) {
// get the next node
int nextIdx = *toVisit.begin();
toVisit.erase(nextIdx);
visited.insert(nextIdx);
T& next = grid[nextIdx];
// get all his (unprocessed) neighbors and add them to the region
for (const T& n2 : grid.neighbors(next)) {
if (visited.find(n2.getIdx()) == visited.end()) {
toVisit.insert(n2.getIdx());
}
}
}
}
private:
/** as line-obstacles have a thickness, we need 4 lines for the intersection test! */
static std::vector<Line2> getThickLines(const Floorplan::FloorObstacleLine* line) {
//const Line2 base(line->from*100, line->to*100);
const float thickness_m = line->thickness_m;
const Point2 dir = (line->to - line->from); // obstacle's direction
const Point2 perp = dir.perpendicular().normalized(); // perpendicular direction (90 degree)
const Point2 p1 = line->from + perp * thickness_m/2; // start-up
const Point2 p2 = line->from - perp * thickness_m/2; // start-down
const Point2 p3 = line->to + perp * thickness_m/2; // end-up
const Point2 p4 = line->to - perp * thickness_m/2; // end-down
return {
Line2(p1, p2),
Line2(p3, p4),
Line2(p2, p4),
Line2(p1, p3),
};
}
/** does the bbox intersect with any of the floor's walls? */
static inline bool intersects(const GridNodeBBox& bbox, const Floorplan::Floor* floor) {
// process each obstacle
// (obstacles use meter, while the bbox is in centimeter!
for (Floorplan::FloorObstacle* fo : floor->obstacles) {
// depends on the type of obstacle
if (dynamic_cast<Floorplan::FloorObstacleLine*>(fo)) {
const Floorplan::FloorObstacleLine* line = (Floorplan::FloorObstacleLine*) fo;
// old method (does not support thickness)
//const Line2 l2(line->from*100, line->to*100);
//if (bbox.intersects(l2)) {return true;}
const std::vector<Line2> lines = getThickLines(line);
for (const Line2& l : lines) {
if (bbox.intersects(l*100)) {return true;}
}
} else if (dynamic_cast<Floorplan::FloorObstacleCircle*>(fo)) {
const Floorplan::FloorObstacleCircle* circle = (Floorplan::FloorObstacleCircle*) fo;
const float dist = std::min(
bbox.getCorner1().getDistance(circle->center*100),
bbox.getCorner2().getDistance(circle->center*100),
bbox.getCorner3().getDistance(circle->center*100),
bbox.getCorner4().getDistance(circle->center*100)
);
const float threshold = circle->radius * 100;
if (dist < threshold) {return true;}
} else if (dynamic_cast<Floorplan::FloorObstacleDoor*>(fo)) {
// DOORS ARE NOT AN OBSTACLE
} else if (dynamic_cast<Floorplan::FloorObstacleObject*>(fo)) {
// ADDED EARLIER
} else {
throw Exception("TODO: not yet implemented obstacle type");
}
}
return false;
}
/**
* normally, the algorithm will not try to connect two nodes that are neighbors but have an obstacle between them.
* however, especially for 45° obstacles it might happen that a neighbor exists but is blocked by an obstacle.
* we thus need this additional check to ensure everything is fine... even though it needs performance...
*/
static inline bool isBlocked(const Floorplan::Floor* floor, const GridPoint& n1, const GridPoint& n2) {
// (obstacles use meter, while the nodes are in centimeter!
const Point2 p1_m = n1.inMeter().xy();
const Point2 p2_m = n2.inMeter().xy();
const Line2 lineNodes(p1_m, p2_m);
// process each obstacle
for (Floorplan::FloorObstacle* fo : floor->obstacles) {
// depends on the type of obstacle
if (dynamic_cast<Floorplan::FloorObstacleLine*>(fo)) {
const Floorplan::FloorObstacleLine* line = (Floorplan::FloorObstacleLine*) fo;
const Line2 lineObstacle(line->from, line->to);
if (lineObstacle.getSegmentIntersection(lineNodes)) {return true;}
} else if (dynamic_cast<Floorplan::FloorObstacleCircle*>(fo)) {
const Floorplan::FloorObstacleCircle* circle = (Floorplan::FloorObstacleCircle*) fo;
Circle2 circ(circle->center, circle->radius);
if (circ.intersects(lineNodes)) {return true;}
//throw Exception("should not happen");
} else if (dynamic_cast<Floorplan::FloorObstacleDoor*>(fo)) {
// DOORS ARE NOT AN OBSTACLE
} else if (dynamic_cast<Floorplan::FloorObstacleObject*>(fo)) {
// removed earlier
//std::cout << "GridFactory: TODO: Floorplan::FloorObstacleObject" << std::endl;
} else {
throw Exception("TODO: not yet implemented obstacle type");
}
}
return false;
}
/** adjust the given gridNode's type if needed [e.g. from "floor" to "door"] */
static inline void updateType(T& t, const PartOfOutline part, const GridNodeBBox& bbox, const Floorplan::Floor* floor) {
// first, assume the type of the outline polygon
if (part.outdoor) {
t.setType(GridNode::TYPE_OUTDOOR);
} else {
//t.setType(GridNode::TYPE_FLOOR);
}
// hereafter, process each obstacle and mark doors
for (Floorplan::FloorObstacle* fo : floor->obstacles) {
if (dynamic_cast<Floorplan::FloorObstacleDoor*>(fo)) {
const Floorplan::FloorObstacleDoor* door = (Floorplan::FloorObstacleDoor*) fo;
const Line2 l2(door->from*100, door->to*100);
if (bbox.intersects(l2)) {
t.setType(GridNode::TYPE_DOOR);
return; // done
}
}
}
}
};
#endif // GRIDFACTORY_V2_H