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/GridFactory.h
2018-10-25 11:50:12 +02:00

461 lines
12 KiB
C++
Executable File
Raw Permalink 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_H
#define GRIDFACTORY_H
#include <string>
#include <unordered_set>
#include "../../floorplan/Floor.h"
#include "../../floorplan/Stairs.h"
#include "../../floorplan/PlatformStair.h"
#include "../../geo/Units.h"
#include "../GridNodeBBox.h"
#include "../Grid.h"
#include "../../misc/Debug.h"
template <typename T> class GridFactory {
/** logging name */
static constexpr const char* name = "GridFac";
private:
/** the grid to build into */
Grid<T>& grid;
public:
/** ctor with the grid to fill */
GridFactory(Grid<T>& grid) : grid(grid) {;}
/** add the given floor at the provided height (in cm) */
void addFloor(const Floor& floor, const float z_cm) {
Log::add(name, "adding floor at height " + std::to_string(z_cm), false);
Log::tick();
const int gridSize_cm = grid.getGridSize_cm();
// build grid-points
for(int x_cm = 0; x_cm < floor.getWidth_cm(); x_cm += gridSize_cm) {
for (int y_cm = 0; y_cm < floor.getDepth_cm(); y_cm += gridSize_cm) {
// check intersection with the floorplan
const GridNodeBBox bbox(GridPoint(x_cm, y_cm, z_cm), gridSize_cm);
if (intersects(bbox, floor)) {continue;}
// add to the grid
grid.add(T(x_cm, y_cm, z_cm));
}
}
Log::tock();
connectAdjacent(z_cm);
}
/** 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 float z_cm) {
Log::add(name, "connecting all adjacent nodes at height " + std::to_string(z_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(n1);
}
Log::tock();
}
/** connect the given node with its neighbors */
void connectAdjacent(T& n1) {
const int gridSize_cm = grid.getGridSize_cm();
// square around the node
for (int x = -gridSize_cm; x <= gridSize_cm; x += gridSize_cm) {
for (int y = -gridSize_cm; y <= gridSize_cm; y += gridSize_cm) {
// skip the center (node itself)
if ((x == y) && (x == 0)) {continue;}
// position of the potential neighbor
const int ox = n1.x_cm + x;
const int oy = n1.y_cm + y;
const GridPoint p(ox, oy, n1.z_cm);
// does the grid contain the potential neighbor?
const T* n2 = grid.getNodePtrFor(p);
if (n2 != nullptr) {
grid.connectUniDir(n1, *n2); // UNI-dir connection as EACH node is processed!
}
}
}
}
float align(const float val) {
const float gridSize_cm = grid.getGridSize_cm();
return std::round(val/gridSize_cm) * gridSize_cm;
}
/** shrink the given bbox to be grid-aligned */
BBox2 shrinkAlign(const BBox2& bb) {
const float gridSize_cm = grid.getGridSize_cm();
Point2 p1 = bb.getMin();
Point2 p2 = bb.getMax();
p1.x = std::ceil(p1.x/gridSize_cm)*gridSize_cm;
p1.y = std::ceil(p1.y/gridSize_cm)*gridSize_cm;
p2.x = std::floor(p2.x/gridSize_cm)*gridSize_cm;
p2.y = std::floor(p2.y/gridSize_cm)*gridSize_cm;
BBox2 res; res.add(p1); res.add(p2); return res;
}
/** add a new platform-stair between the two given floors */
void buildPlatformStair(const PlatformStair& s, const float z1_cm, const float z2_cm) {
const float zCenter_cm = align((z2_cm + z1_cm) / 2);
std::vector<int> indices;
// add the platform in the middle
BBox2 bb = shrinkAlign(s.platform);
const int gridSize_cm = grid.getGridSize_cm();
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) {
int idx = grid.add(T(x_cm, y_cm, zCenter_cm));
indices.push_back(idx);
}
}
// connect the plattform in the middle
connectAdjacent(indices);
// TODO: interconnect (x-y) the stair lines???
buildStair(s.s1, z1_cm, zCenter_cm);
buildStair(s.s2, z2_cm, zCenter_cm);
}
void addStairs(const Stairs& stairs, const float z1_cm, const float z2_cm) {
Log::add(name, "adding stairs between " + std::to_string(z1_cm) + " and " + std::to_string(z2_cm), false);
Log::tick();
for (const Stair& s : stairs) { buildStair(s, z1_cm, z2_cm); }
Log::tock();
}
void buildStair(const Stair& s, const float z1_cm, const float z2_cm) {
// potential starting-point for the stair
for (T& n : grid) {
// node lies on the stair's starting edge?
if (n.z_cm == z1_cm && grid.getBBox(n).intersects(s.start)) {
// construct end-point by using the stair's direction
const Point3 end = Point3(n.x_cm, n.y_cm, z2_cm) + Point3(s.dir.x, s.dir.y, 0);
GridPoint gp(end.x, end.y, end.z);
// does such and end-point exist within the grid? -> construct stair-line
if (grid.hasNodeFor(gp)) {
T& n2 = (T&) grid.getNodeFor(gp);
buildStairLine(grid, n, n2);
}
}
}
}
/**
* this method ensures that the start- and end-node of a stair are "terminated":
* they have less than 8 neighbors. it must not be possible to walk on otherwise
* than using the stair.
*
* NOTE: only works for axis aligned stairs!!!!
* NOTE: needs grid.cleanup() during some later timestep!!
*
*/
void removeStairBlocked(Grid<T>& grid, T& _n1, T& _n2) {
Point3 pStart(_n1.x_cm, _n1.y_cm, _n1.z_cm);
Point3 pEnd(_n2.x_cm, _n2.y_cm, _n2.z_cm);
// cleanup the region around the starting node
BBox3 bboxStart;
bboxStart.add( pStart ); // from the start
bboxStart.add( pStart + (pEnd-pStart) * 0.3 ); // 30% to the end
// cleanup the region around the destionation node
BBox3 bboxEnd;
bboxEnd.add( pEnd ); // from the end
bboxEnd.add( pEnd + (pStart-pEnd) * 0.3 ); // 30% to the start
for (T& n : grid) {
if (n == _n1) {continue;} // keep the start itself
if (n == _n2) {continue;} // keep the end itself
Point3 pp(n.x_cm, n.y_cm, n.z_cm);
if (bboxStart.contains(pp)) {grid.remove(n);} // cleanup starting region
else if (bboxEnd.contains(pp)) {grid.remove(n);} // cleanup ending region
}
}
/** build a stair (z-transition) from n1 to n2 */
void buildStairLine(Grid<T>& grid, T& _n1, T& _n2) {
// remove unneccesary nodes in stair-environments
removeStairBlocked(grid, _n1, _n2);
// does not work, deletion takes place at a later time!
// sanity check: both, the starting and the end node should have less than 8 neighbors, otherwise they are not "leading" into the stair
//if (_n1.getNumNeighbors() >= 8) {throw Exception("invalid number of neighbors for a stair-start-node");}
//if (_n2.getNumNeighbors() >= 8) {throw Exception("invalid number of neighbors for a stair-end-node");}
// half the grid size = small steps
const int gridSize_cm = grid.getGridSize_cm() / std::sqrt(2);
// local copies, needed for std::swap to work
T n1 = _n1; T n2 = _n2;
// ensure we work from lower to upper levels
if (n2.z_cm < n1.z_cm) { std::swap(n1, n2); }
// dimensional difference between stairt start and end
const float zDiff = n2.z_cm - n1.z_cm;
const float xDiff = n2.x_cm - n1.x_cm;
const float yDiff = n2.y_cm - n1.y_cm;
int idx1 = n1.getIdx(); // starting node
int idx2 = -1; // next node
const int idx3 = n2.getIdx(); // final node
// move upards
for (int _z = gridSize_cm; _z <= zDiff - gridSize_cm; _z+= gridSize_cm) {
// calculate the percentage of reached upwards-distance
const float percent = _z/zDiff;
// adjust (x,y) accordingly (interpolate)
int x = n1.x_cm + xDiff * percent;
int y = n1.y_cm + yDiff * percent;
int z = n1.z_cm + _z;
// snap (x,y) to the grid???
//x = std::round(x / gridSize_cm) * gridSize_cm;
//y = std::round(y / gridSize_cm) * gridSize_cm;
// create a new node add it to the grid, and connect it with the previous one
idx2 = grid.addUnaligned(T(x,y,z));
grid.connectBiDir(idx1, idx2);
idx1 = idx2;
}
// add the last segment
Assert::isTrue(idx2 != -1, "strange stair issue?!");
grid.connectBiDir(idx2, idx3);
}
/** 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 removeIsolated() {
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();
}
/** remove all nodes not connected to n1 */
void removeIsolated(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();
// 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()) {grid.remove(n2);}
}
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());
}
}
}
}
// /** recursively get all connected nodes and add them to the set */
// void getConnected(const int idx, std::unordered_set<int>& set) {
// // get the node behind idx
// const T& n1 = (T&) grid[idx];
// // add him to the current region
// set.insert(n1.getIdx());
// // get all his (unprocessed) neighbors and add them to the region
// for (const T& n2 : grid.neighbors(n1)) {
// if (set.find(n2.getIdx()) == set.end()) {
// getConnected(n2.getIdx(), set);
// }
// }
// }
// /** recursively get all connected nodes and add them to the set */
// void getConnected(const T& n1, std::unordered_set<int>& set) {
// // add him to the current region
// set.insert(n1.getIdx());
// // get all his (unprocessed) neighbors and add them to the region
// for (const T& n2 : grid.neighbors(n1)) {
// if (set.find(n2.getIdx()) == set.end()) {
// getConnected(n2, set);
// }
// }
// }
private:
/** does the bbox intersect with any of the floor's walls? */
static inline bool intersects(const GridNodeBBox& bbox, const Floor& floor) {
for (const Line2& l : floor.getObstacles()) {
if (bbox.intersects(l)) {return true;}
}
return false;
}
};
#endif // GRIDFACTORY_H