worked on 3D model creation

This commit is contained in:
k-a-z-u
2018-02-06 17:34:29 +01:00
parent 0bb1b707de
commit a35e043196
15 changed files with 1442 additions and 1091 deletions

View File

@@ -8,210 +8,401 @@
#include "Cube.h"
#include "FloorplanMesh.h"
/**
* convert an indoor map into a 3D model based on triangles
*/
class ModelFactory {
namespace Ray3D {
private:
/**
* convert an indoor map into a 3D model based on triangles
*/
class ModelFactory {
bool exportCeilings = true;
bool exportObstacles = true;
bool exportWallTops = false;
std::vector<Floorplan::Floor*> exportFloors;
private:
const Floorplan::IndoorMap* map;
bool exportCeilings = true;
bool exportObstacles = true;
bool exportStairs = true;
bool exportHandrails = true;
bool exportDoors = false;
bool exportWallTops = false;
std::vector<Floorplan::Floor*> exportFloors;
public:
/** the to-be-exported map */
const Floorplan::IndoorMap* map;
public:
/** ctor */
ModelFactory(const Floorplan::IndoorMap* map) : map(map) {
}
void setExportCeilings(bool exp) {
this->exportCeilings = exp;
}
/** limit to-be-exported floors */
void setFloors(const std::vector<Floorplan::Floor*> 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<Obstacle3D> triangulize() {
std::vector<Obstacle3D> res;
// get the to-be-exported floors (either "all" or "user defined")
const std::vector<Floorplan::Floor*>& floors = (exportFloors.empty()) ? (map->floors) : (exportFloors);
// process each floor
for (const Floorplan::Floor* f : floors) {
// triangulize the floor itself (floor/ceiling)
if (exportCeilings) {
std::vector<Obstacle3D> tmp = getFloor(f);
res.insert(res.end(), tmp.begin(), tmp.end());
}
// process each obstacle within the floor
for (const Floorplan::FloorObstacle* fo : f->obstacles) {
// handle line obstacles
const Floorplan::FloorObstacleLine* fol = dynamic_cast<const Floorplan::FloorObstacleLine*>(fo);
if (fol) {
if (fol->type == Floorplan::ObstacleType::HANDRAIL) {continue;}
if (exportObstacles) {res.push_back(getTriangles(f, fol));}
}
}
// TODO: remove
//break;
/** ctor */
ModelFactory(const Floorplan::IndoorMap* map) : map(map) {
}
return res;
}
private:
/** convert a floor (floor/ceiling) into triangles */
std::vector<Obstacle3D> getFloor(const Floorplan::Floor* f) {
// 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<std::string, Polygon> 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);
}
}
/** whether or not to export ceilings */
void setExportCeilings(bool exp) {
this->exportCeilings = exp;
}
// 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);
}
}
/** limit to-be-exported floors */
void setFloors(const std::vector<Floorplan::Floor*> floors) {
this->exportFloors = floors;
}
std::vector<Obstacle3D> res;
/** convert floorplan to mesh */
FloorplanMesh getMesh() {
// 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<std::vector<Point3>> polys = it.second.get(f->getStartingZ());
// convert polygons (GL_TRIANGLE_STRIP) to triangles
for (const std::vector<Point3>& 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);
FloorplanMesh mesh;
mesh.elements = triangulize();
return mesh;
}
return res;
private:
}
/** convert a line obstacle to 3D triangles */
Obstacle3D getTriangles(const Floorplan::Floor* f, const Floorplan::FloorObstacleLine* fol) {
/** get all triangles grouped by obstacle */
std::vector<Obstacle3D> triangulize() {
/*
Obstacle3D res(fol->material);
std::vector<Obstacle3D> res;
Point3 p1(fol->from.x, fol->from.y, f->getStartingZ());
Point3 p2(fol->to.x, fol->to.y, f->getStartingZ());
Point3 p3(fol->to.x, fol->to.y, f->getEndingZ());
Point3 p4(fol->from.x, fol->from.y, f->getEndingZ());
// get the to-be-exported floors (either "all" or "user defined")
const std::vector<Floorplan::Floor*>& floors = (exportFloors.empty()) ? (map->floors) : (exportFloors);
Triangle3 t1(p1,p2,p3);
Triangle3 t2(p1,p3,p4);
// process each floor
for (const Floorplan::Floor* f : floors) {
res.triangles.push_back(t1);
res.triangles.push_back(t2);
if (!f->enabled) {continue;}
*/
// triangulize the floor itself (floor/ceiling)
if (exportCeilings) {
std::vector<Obstacle3D> tmp = getFloor(f);
res.insert(res.end(), tmp.begin(), tmp.end());
}
const float thickness_m = fol->thickness_m;
const Point2 from = fol->from;
const Point2 to = fol->to;
const Point2 cen2 = (from+to)/2;
// process each obstacle within the floor
if (f->obstacles.enabled) {
for (const Floorplan::FloorObstacle* fo : f->obstacles) {
std::vector<Obstacle3D> tmp = getObstacle(f, fo);
res.insert(res.end(), tmp.begin(), tmp.end());
}
}
const float rad = std::atan2(to.y - from.y, to.x - from.x);
const float deg = rad * 180 / M_PI;
// stairs
if (f->stairs.enabled) {
for (const Floorplan::Stair* stair : f->stairs) {
// cube's destination center
const Point3 pos(cen2.x, cen2.y, f->atHeight + f->height/2);
if (exportStairs) {res.push_back(getStairs(f, stair));}
// div by 2.01 to prevent overlapps and z-fi
const float sx = from.getDistance(to) / 2.01f;
const float sy = thickness_m / 2.01f;
const float sz = f->height / 2.01f; // prevent overlaps
const Point3 size(sx, sy, sz);
const Point3 rot(0,0,deg);
}
}
// build
Cube cube(pos, size, rot);
// TODO: remove
//break;
// done
Obstacle3D res(Obstacle3D::Type::WALL, fol->material);
res.triangles = cube.getTriangles();
return res;
}
}
return res;
}
};
private:
/** convert a floor (floor/ceiling) into triangles */
std::vector<Obstacle3D> getFloor(const Floorplan::Floor* f) {
std::vector<Obstacle3D> 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<std::string, Polygon> 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);
}
}
}
// 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<std::vector<Point3>> polys = it.second.get(f->getStartingZ());
// convert polygons (GL_TRIANGLE_STRIP) to triangles
for (const std::vector<Point3>& 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<Obstacle3D> getObstacle(const Floorplan::Floor* f, const Floorplan::FloorObstacle* fo, const Floorplan::FloorObstacleDoor* aboveDoor = nullptr) const {
std::vector<Obstacle3D> res;
// handle line obstacles
const Floorplan::FloorObstacleLine* fol = dynamic_cast<const Floorplan::FloorObstacleLine*>(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<const Floorplan::FloorObstacleDoor*>(fo);
if (door) {
if (exportObstacles) {
if (exportDoors) {
res.push_back(getDoor(f, door));
}
//std::vector<Obstacle3D> 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 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 Point3 pos(cen2.x, cen2.y, f->atHeight + door->height/2);
// 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 = door->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(Obstacle3D::Type::DOOR, door->material);
res.triangles = cube.getTriangles();
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<const Floorplan::FloorObstacleLine*>(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<const Floorplan::FloorObstacleLine*> (*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<Triangle3> 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<Triangle3> 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<Floorplan::Quad3> 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