#ifndef MODELFACTORY_H #define MODELFACTORY_H #include "../../../floorplan/v2/Floorplan.h" #include "../../../geo/Triangle3.h" #include "../../../geo/GPCPolygon2.h" #include "Obstacle3.h" #include "Cube.h" #include "Tube.h" #include "Cylinder.h" #include "FloorplanMesh.h" #include "OBJPool.h" namespace Ray3D { /** * convert an indoor map into a 3D model based on triangles */ class ModelFactory { public: bool exportCeilings = true; bool exportObstacles = true; bool exportStairs = true; bool fancyStairs = true; bool exportHandrails = true; bool exportDoors = true; bool exportAboveDoors = true; bool doorsOpen = false; bool exportObjects = true; bool exportWallTops = false; Cube::Part cubeParts = (Cube::Part) 63; // leftright,topbottom,rearfront 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; } public: /** convert a floor (floor/ceiling) into triangles */ std::vector getFloor(const Floorplan::Floor* f) { FloorPos fpos(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(fpos.z1); // 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 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) {std::swap(tria1, tria2);} // tria2 = ceiling of previous floor tria2 -= Point3(0,0,fpos.fh); // 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)); } } } // handle circle obstacles const Floorplan::FloorObstacleCircle* foc = dynamic_cast(fo); if (foc) { if (exportObstacles) { res.push_back(getPillar(f, foc)); } } // handle object obstacles const Floorplan::FloorObstacleObject* foo = dynamic_cast(fo); if (foo) { if (exportObjects) { if (!foo->file.empty()) { res.push_back(getObject(f, foo)); } } } 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()); if (exportAboveDoors) { 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 getPillar(const Floorplan::Floor* f, const Floorplan::FloorObstacleCircle* foc) const { FloorPos fpos(f); // attributes const float r = foc->radius; const float h = (foc->height > 0) ? (foc->height) : (fpos.height); // use either floor's height or user height const Point3 pos(foc->center.x, foc->center.y, fpos.z1 + h/2); // build Cylinder cyl; cyl.add(r, h/2, true); cyl.translate(pos); // done Obstacle3D res(getType(foc), foc->material); res.triangles = cyl.getTriangles(); return res; } Obstacle3D getWall(const Floorplan::Floor* f, const Floorplan::FloorObstacleLine* fol, const Floorplan::FloorObstacleDoor* aboveDoor) const { FloorPos fpos(f); 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 _height = (fol->height_m > 0) ? (fol->height_m) : (fpos.height); // use either floor's height or user height const double height = (!aboveDoor) ? (_height) : (fpos.height - aboveDoor->height); const double cenZ = (!aboveDoor) ? (fpos.z1 + height/2) : (fpos.z1 + aboveDoor->height + height/2);// (fpos.z2 - (fpos.height - aboveDoor->height) / 2); 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, cubeParts); // done Obstacle3D res(getType(fol), fol->material); res.triangles = cube.getTriangles(); return res; } /** 3D Obstacle from .obj 3D mesh */ Obstacle3D getObject(const Floorplan::Floor* f, const Floorplan::FloorObstacleObject* foo) const { FloorPos fpos(f); const std::string& name = foo->file; Obstacle3D obs = OBJPool::get().getObject(name); // perform sanity checks if (!obs.isValid()) { throw std::runtime_error("invalid obstacle-data detected"); } // apply scaling/rotation/translation obs = obs.scaled(foo->scale); obs = obs.rotated_deg( Point3(foo->rot.x, foo->rot.y, foo->rot.z) ); obs = obs.translated(foo->pos + Point3(0,0,fpos.z1)); obs.type = Obstacle3D::Type::OBJECT; // std::vector trias; // for (const OBJReader::Face& face : reader.getData().faces) { // Point3 p1 = face.vnt[0].vertex; // Point3 p2 = face.vnt[1].vertex; // Point3 p3 = face.vnt[2].vertex; // p1 = p1.rot(foo->rot.x/180.0f*M_PI, foo->rot.y/180.0f*M_PI, foo->rot.z/180.0f*M_PI); // p2 = p2.rot(foo->rot.x/180.0f*M_PI, foo->rot.y/180.0f*M_PI, foo->rot.z/180.0f*M_PI); // p3 = p3.rot(foo->rot.x/180.0f*M_PI, foo->rot.y/180.0f*M_PI, foo->rot.z/180.0f*M_PI); // p1 += foo->pos; p1.z += fpos.z1; // p2 += foo->pos; p2.z += fpos.z1; // p3 += foo->pos; p3.z += fpos.z1; // const Triangle3 tria(p1, p2, p3); // trias.push_back(tria); // } // // done // Obstacle3D res(Obstacle3D::Type::OBJECT, Floorplan::Material::WOOD); // res.triangles = trias; // return res; return obs; } Obstacle3D getDoor(const Floorplan::Floor* f, const Floorplan::FloorObstacleDoor* door) const { FloorPos fpos(f); 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, fpos.z1 + 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::unit(cubeParts); 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, fpos.z1 + 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::unit(); 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); // there is absolutely nothing attached directly to the door... if (it == f->obstacles.end()) { throw Exception("did not find a matching element to place above the door"); } // try to convert the element directly connected to the door into a line-obstacle const Floorplan::FloorObstacleLine* line = dynamic_cast (*it); // there seems to be no wall (but something else?!) attached to the door. fishy.. if (!line) { throw Exception("did not find a matching wall 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 { FloorPos fpos(f); // 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 = fpos.z1; const float z2 = fpos.z1 + 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; } 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) { if (quad.isLeveled()) { const float h = 0.2; const Point3 ph(0,0,h); const Cube cube = Cube::fromBottomAndHeight(quad.p1-ph, quad.p2-ph, quad.p3-ph, quad.p4-ph, 0.2); const std::vector tmp = cube.getTriangles(); res.triangles.insert(res.triangles.end(), tmp.begin(), tmp.end()); } else { const Point3 dir1 = quad.p3 - quad.p2; const Point3 dir2 = quad.p4 - quad.p1; float stepH = 0.20; const float totalH = quad.p3.z - quad.p1.z; const int numStairs = std::round(totalH / stepH); stepH = totalH / numStairs; for (int i = 0; i < numStairs; ++i) { //const float y1 = quad.p1.z + (stepH * i); //const float y2 = y1 + stepH; Point3 p1b = quad.p1 + dir1 * (i+0) / numStairs; p1b.z -= stepH; Point3 p2b = quad.p2 + dir2 * (i+0) / numStairs; p2b.z -= stepH; const Point3 p3t = quad.p2 + dir2 * (i+1) / numStairs; const Point3 p4t = quad.p1 + dir1 * (i+1) / numStairs; const Point3 p1t(p1b.x, p1b.y, p4t.z); const Point3 p2t(p2b.x, p2b.y, p3t.z); const Point3 p3b(p3t.x, p3t.y, p2b.z+stepH); const Point3 p4b(p4t.x, p4t.y, p1b.z+stepH); const Cube cube = Cube::fromVertices(p1t, p2t, p3t, p4t, p1b, p2b, p3b, p4b); const std::vector tmp = cube.getTriangles(); res.triangles.insert(res.triangles.end(), tmp.begin(), tmp.end()); } } } 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) { if (!fancyStairs || quad.isLeveled()) { 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); } else { const Point3 dir1 = quad.p3 - quad.p2; const Point3 dir2 = quad.p4 - quad.p1; const float stepH = 0.20; const float totalH = quad.p3.z - quad.p1.z; const int numStairs = std::round(totalH / stepH); for (int i = 0; i < numStairs; ++i) { //const float y1 = quad.p1.z + (stepH * i); //const float y2 = y1 + stepH; const Point3 p1 = quad.p1 + dir1 * (i+0) / numStairs; const Point3 p2 = quad.p2 + dir2 * (i+0) / numStairs; const Point3 p3 = quad.p2 + dir2 * (i+1) / numStairs; const Point3 p4 = quad.p1 + dir1 * (i+1) / numStairs; //const Point3 p14(p4.x, p4.y, p1.z); //const Point3 p23(p3.x, p3.y, p2.z); const Point3 p14(p1.x, p1.y, p4.z); const Point3 p23(p2.x, p2.y, p3.z); // up const Triangle3 t1(p1, p2, p23); const Triangle3 t2(p23, p14, p1); res.triangles.push_back(t1); res.triangles.push_back(t2); // group const Triangle3 t3(p14, p23, p3); const Triangle3 t4(p3, p4, p14); res.triangles.push_back(t3); res.triangles.push_back(t4); // side s const Triangle3 s1(p2, p3, p23); const Triangle3 s2(p1, p14, p4); res.triangles.push_back(s1); res.triangles.push_back(s2); // facing down const Triangle3 d1(quad.p1, quad.p3, quad.p2); const Triangle3 d2(quad.p3, quad.p1, quad.p4); res.triangles.push_back(d1); res.triangles.push_back(d2); } } } 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; } } static Obstacle3D::Type getType(const Floorplan::FloorObstacleCircle* c) { (void) c; return Obstacle3D::Type::WALL; } /** used to model ceiling thickness */ struct FloorPos { float fh; float z1; float z2; float height; FloorPos(const Floorplan::Floor* f) : fh(0.01), z1(f->getStartingZ()), z2(f->getEndingZ()-fh), height(z2-z1) {;} }; }; } #endif // MODELFACTORY_H