#ifndef MODELFACTORY_H #define MODELFACTORY_H #include "../../../floorplan/v2/Floorplan.h" #include "../../../geo/Triangle3.h" #include "ModelFactoryHelper.h" #include "Obstacle3.h" #include "Cube.h" #include "Tube.h" #include "FloorplanMesh.h" namespace Ray3D { /** * convert an indoor map into a 3D model based on triangles */ class ModelFactory { private: bool exportCeilings = true; bool exportObstacles = true; bool exportStairs = true; bool exportHandrails = true; bool exportDoors = true; bool doorsOpen = true; bool exportWallTops = false; std::vector exportFloors; /** the to-be-exported map */ const Floorplan::IndoorMap* map; public: /** ctor */ ModelFactory(const Floorplan::IndoorMap* map) : map(map) { } /** whether or not to export ceilings */ void setExportCeilings(bool exp) { this->exportCeilings = exp; } /** limit to-be-exported floors */ void setFloors(const std::vector floors) { this->exportFloors = floors; } /** convert floorplan to mesh */ FloorplanMesh getMesh() { FloorplanMesh mesh; mesh.elements = triangulize(); return mesh; } private: /** get all triangles grouped by obstacle */ std::vector triangulize() { std::vector res; // get the to-be-exported floors (either "all" or "user defined") const std::vector& floors = (exportFloors.empty()) ? (map->floors) : (exportFloors); // process each floor for (const Floorplan::Floor* f : floors) { if (!f->enabled) {continue;} // triangulize the floor itself (floor/ceiling) if (exportCeilings) { std::vector tmp = getFloor(f); res.insert(res.end(), tmp.begin(), tmp.end()); } // process each obstacle within the floor if (f->obstacles.enabled) { for (const Floorplan::FloorObstacle* fo : f->obstacles) { std::vector tmp = getObstacle(f, fo); res.insert(res.end(), tmp.begin(), tmp.end()); } } // stairs if (f->stairs.enabled) { for (const Floorplan::Stair* stair : f->stairs) { if (exportStairs) {res.push_back(getStairs(f, stair));} } } // TODO: remove //break; } return res; } private: /** convert a floor (floor/ceiling) into triangles */ std::vector getFloor(const Floorplan::Floor* f) { std::vector res; if (!f->enabled) {return res;} if (!f->outline.enabled) {return res;} // floor uses an outline based on "add" and "remove" areas // we need to create the apropriate triangles to model the polygon // including all holes (remove-areas) // process all "add" regions by type // [this allows for overlaps of the same type] std::unordered_map types; for (Floorplan::FloorOutlinePolygon* fop : f->outline) { if (fop->method == Floorplan::OutlineMethod::ADD) { if (fop->outdoor) { types["outdoor"].add(fop->poly); } else { types["indoor"].add(fop->poly); } } } // remove the "remove" regions from EVERY "add" region added within the previous step for (Floorplan::FloorOutlinePolygon* fop : f->outline) { if (fop->method == Floorplan::OutlineMethod::REMOVE) { for (auto& it : types) { it.second.remove(fop->poly); } } // allow for overlapping outdoor/indoor regions -> outdoor wins [remove outdoor part from indoor parts] if (fop->outdoor) { types["indoor"].remove(fop->poly); } } // create an obstacle for each type (indoor, outdoor) for (auto& it : types) { // TODO: variable type? Obstacle3D::Type type = (it.first == "indoor") ? (Obstacle3D::Type::GROUND_INDOOR) : (Obstacle3D::Type::GROUND_OUTDOOR); Obstacle3D obs(type, Floorplan::Material::CONCRETE); // convert them into polygons std::vector> polys = it.second.get(f->getStartingZ()); // convert polygons (GL_TRIANGLE_STRIP) to triangles for (const std::vector& pts : polys) { for (int i = 0; i < (int)pts.size() - 2; ++i) { // floor must be double-sided for reflection to work with the correct normals Triangle3 tria1 (pts[i+0], pts[i+1], pts[i+2]); Triangle3 tria2 (pts[i+2], pts[i+1], pts[i+0]); // ensure the triangle with the normal pointing downwards (towards bulding's cellar) // is below the triangle that points upwards (towards the sky) if (tria1.getNormal().z < 0) {tria1 = tria1 - Point3(0,0,0.02);} if (tria2.getNormal().z < 0) {tria2 = tria2 - Point3(0,0,0.02);} // add both obs.triangles.push_back(tria1); obs.triangles.push_back(tria2); } } res.push_back(obs); } return res; } /** * @brief build the given obstacle * @param f the floor * @param fo the obstacle * @param aboveDoor whether to place this obstacle ABOVE the given door (overwrite) * @return */ std::vector getObstacle(const Floorplan::Floor* f, const Floorplan::FloorObstacle* fo, const Floorplan::FloorObstacleDoor* aboveDoor = nullptr) const { std::vector res; // handle line obstacles const Floorplan::FloorObstacleLine* fol = dynamic_cast(fo); if (fol) { if (exportObstacles) { if (fol->type != Floorplan::ObstacleType::HANDRAIL || exportHandrails) { res.push_back(getObstacleLine(f, fol, aboveDoor)); } } } const Floorplan::FloorObstacleDoor* door = dynamic_cast(fo); if (door) { if (exportObstacles) { if (exportDoors) { res.push_back(getDoor(f, door)); } //std::vector tmp = getDoorAbove(f, door); //res.insert(res.end(), tmp.begin(), tmp.end()); res.push_back(getDoorAbove(f, door)); } } return res; } /** convert a line obstacle to 3D triangles */ Obstacle3D getObstacleLine(const Floorplan::Floor* f, const Floorplan::FloorObstacleLine* fol, const Floorplan::FloorObstacleDoor* aboveDoor = nullptr) const { switch (fol->type) { case Floorplan::ObstacleType::HANDRAIL: return getHandrail(f, fol); case Floorplan::ObstacleType::WINDOW: return getWindow(f, fol, aboveDoor); case Floorplan::ObstacleType::WALL: return getWall(f, fol, aboveDoor); default: throw Exception("invalid obstacle type"); } } Obstacle3D getWindow(const Floorplan::Floor* f, const Floorplan::FloorObstacleLine* fol, const Floorplan::FloorObstacleDoor* aboveDoor) const { return getWall(f, fol, aboveDoor); } Obstacle3D getWall(const Floorplan::Floor* f, const Floorplan::FloorObstacleLine* fol, const Floorplan::FloorObstacleDoor* aboveDoor) const { const float thickness_m = fol->thickness_m; const Point2 from = (!aboveDoor) ? (fol->from) : (aboveDoor->from); const Point2 to = (!aboveDoor) ? (fol->to) : (aboveDoor->to); const Point2 cen2 = (from+to)/2; const float rad = std::atan2(to.y - from.y, to.x - from.x); const float deg = rad * 180 / M_PI; // cube's destination center const float cenZ = (!aboveDoor) ? (f->atHeight + f->height/2) : (f->getEndingZ() - (f->height - aboveDoor->height) / 2); const float height = (!aboveDoor) ? (f->height) : (f->height - aboveDoor->height); const Point3 pos(cen2.x, cen2.y, cenZ); // div by 2.01 to prevent overlapps and z-fighting const float sx = from.getDistance(to) / 2; const float sy = thickness_m / 2; const float sz = height / 2.01f; // prevent overlaps const Point3 size(sx, sy, sz); const Point3 rot(0,0,deg); // build Cube cube(pos, size, rot); // done Obstacle3D res(getType(fol), fol->material); res.triangles = cube.getTriangles(); return res; } Obstacle3D getDoor(const Floorplan::Floor* f, const Floorplan::FloorObstacleDoor* door) const { const float thickness_m = 0.10; // TODO?? const Point2 from = door->from; const Point2 to = door->to; const float rad = std::atan2(to.y - from.y, to.x - from.x); float deg = rad * 180 / M_PI; // div by 2.01 to prevent overlapps and z-fighting const Point3 rot(0,0,deg); Point3 pos; Matrix4 mat = Matrix4::identity(); Obstacle3D res(Obstacle3D::Type::DOOR, door->material); // normal door? (non-spinner) if (Floorplan::DoorType::SWING == door->type) { if (doorsOpen) {deg += (door->swap) ? (-90) : (+90);} mat = Matrix4::getTranslation(1,0,0); // cube's edge located at 0,0,0 pos = Point3(from.x, from.y, f->atHeight + door->height/2); const float sx = from.getDistance(to) / 2; const float sy = thickness_m / 2; const float sz = door->height / 2.01f; // prevent overlaps const Point3 size(sx, sy, sz); Cube cube; cube.transform(mat); cube.transform(pos, size, rot); res.triangles = cube.getTriangles(); } else if (Floorplan::DoorType::REVOLVING == door->type) { const Point2 cen2 = (from+to)/2; const Point3 pos(cen2.x, cen2.y, f->atHeight + door->height/2); // outer and inner radius const float rOuter = from.getDistance(to) / 2; const float rInner = rOuter - 0.1; const float sz = door->height / 2.01f; // prevent overlaps const Point3 size(1, 1, sz); // around the doors Tube tube; tube.addSegment(0+40-90, 180-40-90, rInner, rOuter, 1, true, true); tube.addSegment(180+40-90, 360-40-90, rInner, rOuter, 1, true, true); tube.transform(pos, Point3(1,1,1), rot); res.triangles = tube.getTriangles(); // the doors const int numDoors = 3; Cube cube; cube.transform(Matrix4::getTranslation(1,0,0)); for (int i = 0; i < numDoors; ++i) { const int deg = 45 + (360*i / numDoors); Cube c1 = cube .transformed(Matrix4::getScale(rInner/2-0.05, thickness_m/2, sz)) .transformed(Matrix4::getTranslation(0.04, 0, 0)) .transformed(Matrix4::getRotationDeg(0,0,deg)) .transformed(Matrix4::getTranslation(pos.x, pos.y, pos.z)); //pos, Point3(rInner/2-0.05, thickness_m/2, sz), Point3(0,0,deg)); std::vector t1 = c1.getTriangles(); res.triangles.insert(res.triangles.end(), t1.begin(), t1.end()); } } else { throw "unsupported door type"; } return res; } /** get the missing part/gap, above the given door */ Obstacle3D getDoorAbove(const Floorplan::Floor* f, const Floorplan::FloorObstacleDoor* door) const { // find the element above the door (= a connected element) auto comp = [door] (const Floorplan::FloorObstacle* obs) { if (obs == door) {return false;} const Floorplan::FloorObstacleLine* line = dynamic_cast(obs); if (!line) {return false;} return (line->from == door->from || line->to == door->from || line->from == door->to || line->to == door->to); }; auto it = std::find_if(f->obstacles.begin(), f->obstacles.end(), comp); const Floorplan::FloorObstacleLine* line = dynamic_cast (*it); if (!line) { throw Exception("did not find a matching element to place above the door"); } // get the obstacle to place above the door return getObstacleLine(f, line, door); } Obstacle3D getHandrail(const Floorplan::Floor* f, const Floorplan::FloorObstacleLine* fol) const { // target Obstacle3D res(getType(fol), fol->material); if (!exportHandrails) {return res;} const float thickness_m = 0.05; const Point2 from = fol->from; const Point2 to = fol->to; const Point2 cen2 = (from+to)/2; // edges const float z1 = f->atHeight; const float z2 = f->atHeight + 1.0; Point3 p1 = Point3(from.x, from.y, z1); Point3 p2 = Point3(to.x, to.y, z1); Point3 p3 = Point3(from.x, from.y, z2); Point3 p4 = Point3(to.x, to.y, z2); const float rad = std::atan2(to.y - from.y, to.x - from.x); const float deg = rad * 180 / M_PI; // cube's destination center const Point3 pUp(cen2.x, cen2.y, z2); const float sx = from.getDistance(to) / 2; const float sy = thickness_m / 2; const float sz = thickness_m / 2; const Point3 size(sx, sy, sz); const Point3 rot(0,0,deg); // upper bar const Cube cubeUpper(pUp, size, rot); const std::vector tmp = cubeUpper.getTriangles(); res.triangles.insert(res.triangles.end(), tmp.begin(), tmp.end()); const Point3 d1 = p2-p1; const Point3 d2 = p4-p3; const int numBars = d2.length() / 0.75f; for (int i = 1; i < numBars; ++i) { const Point3 s = p1 + d1 * i / numBars; const Point3 e = p3 + d2 * i / numBars; const Point3 c = (s+e)/2; const Point3 size(thickness_m/2, thickness_m/2, s.getDistance(e)/2 - thickness_m); const Cube cube(c, size, rot); const std::vector tmp = cube.getTriangles(); res.triangles.insert(res.triangles.end(), tmp.begin(), tmp.end()); } // done return res; } /** convert a line obstacle to 3D triangles */ Obstacle3D getStairs(const Floorplan::Floor* f, const Floorplan::Stair* s) { Obstacle3D res(Obstacle3D::Type::STAIR, Floorplan::Material::CONCRETE); std::vector quads = Floorplan::getQuads(s->getParts(), f); for (const Floorplan::Quad3& quad : quads) { const Triangle3 t1(quad.p1, quad.p2, quad.p3); const Triangle3 t2(quad.p3, quad.p4, quad.p1); res.triangles.push_back(t1); res.triangles.push_back(t2); } return res; } static Obstacle3D::Type getType(const Floorplan::FloorObstacleLine* l) { switch (l->type) { case Floorplan::ObstacleType::WALL: return Obstacle3D::Type::WALL; case Floorplan::ObstacleType::WINDOW: return Obstacle3D::Type::WINDOW; case Floorplan::ObstacleType::HANDRAIL: return Obstacle3D::Type::HANDRAIL; default: return Obstacle3D::Type::UNKNOWN; } } }; } #endif // MODELFACTORY_H