moved from ray3 to floorplan/3D
worked on new wall models refactoring
This commit is contained in:
156
floorplan/3D/Builder.h
Normal file
156
floorplan/3D/Builder.h
Normal file
@@ -0,0 +1,156 @@
|
||||
#ifndef FLOORPLAN_3D_BUILDER_H
|
||||
#define FLOORPLAN_3D_BUILDER_H
|
||||
|
||||
#include "../v2/Floorplan.h"
|
||||
|
||||
#include "FloorplanMesh.h"
|
||||
#include "Obstacle3.h"
|
||||
|
||||
#include "Outline.h"
|
||||
#include "Stairs.h"
|
||||
#include "Handrails.h"
|
||||
#include "Objects.h"
|
||||
#include "Pillars.h"
|
||||
|
||||
#include "Walls.h"
|
||||
#include "WallsViaCubes.h"
|
||||
#include "WallsViaCuttedQuads.h"
|
||||
|
||||
namespace Floorplan3D {
|
||||
|
||||
class Builder {
|
||||
|
||||
/** the to-be-exported map */
|
||||
const Floorplan::IndoorMap* map;
|
||||
|
||||
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 exportPillars = true;
|
||||
bool exportWallTops = false;
|
||||
bool center = true;
|
||||
|
||||
//Walls* walls = new WallsViaCubes();
|
||||
//Walls* walls = new WallsViaCuttedQuads();
|
||||
|
||||
public:
|
||||
|
||||
|
||||
/** ctor */
|
||||
Builder(const Floorplan::IndoorMap* map) : map(map) {
|
||||
|
||||
}
|
||||
|
||||
/** get the created mesh */
|
||||
FloorplanMesh getMesh() {
|
||||
|
||||
FloorplanMesh mesh;
|
||||
mesh.elements = triangulize();
|
||||
|
||||
if (center) {
|
||||
BBox3 bb = mesh.getBBox();
|
||||
mesh -= Point3(bb.getCenter().x, bb.getCenter().y, 0);
|
||||
}
|
||||
|
||||
return mesh;
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/** get all triangles grouped by obstacle */
|
||||
std::vector<Obstacle3D> triangulize() {
|
||||
|
||||
// TODO: filtering??
|
||||
std::vector<Floorplan::Floor*> floors = map->floors;
|
||||
|
||||
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) {
|
||||
|
||||
if (!f->enabled) {continue;}
|
||||
|
||||
// triangulize the floor itself (floor/ceiling)
|
||||
if (exportCeilings) {
|
||||
Outline out;
|
||||
const std::vector<Obstacle3D> tmp = out.get(f);
|
||||
res.insert(res.end(), tmp.begin(), tmp.end());
|
||||
}
|
||||
|
||||
// process each obstacle within the floor
|
||||
if (f->obstacles.enabled) {
|
||||
|
||||
if (1 == 1) {
|
||||
const std::vector<Obstacle3D> tmp = getWalls(f);
|
||||
res.insert(res.end(), tmp.begin(), tmp.end());
|
||||
}
|
||||
|
||||
if (exportHandrails) {
|
||||
Handrails rails;
|
||||
const std::vector<Obstacle3D> tmp = rails.getHandrails(f);
|
||||
res.insert(res.end(), tmp.begin(), tmp.end());
|
||||
}
|
||||
|
||||
if (exportObjects) {
|
||||
Objects objs;
|
||||
const std::vector<Obstacle3D> tmp = objs.getObjects(f);
|
||||
res.insert(res.end(), tmp.begin(), tmp.end());
|
||||
}
|
||||
|
||||
if (exportPillars) {
|
||||
Pillars pillars;
|
||||
const std::vector<Obstacle3D> tmp = pillars.getPillars(f);
|
||||
res.insert(res.end(), tmp.begin(), tmp.end());
|
||||
}
|
||||
|
||||
// for (const Floorplan::FloorObstacle* fo : f->obstacles) {
|
||||
// std::vector<Obstacle3D> tmp = getWalls(f);
|
||||
// res.insert(res.end(), tmp.begin(), tmp.end());
|
||||
// }
|
||||
}
|
||||
|
||||
// // stairs
|
||||
if (f->stairs.enabled && exportStairs) {
|
||||
Stairs stairs;
|
||||
const std::vector<Obstacle3D> tmp = stairs.getStairs(f);
|
||||
res.insert(res.end(), tmp.begin(), tmp.end());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** just get all walls */
|
||||
std::vector<Obstacle3D> getWalls(const Floorplan::Floor* f) {
|
||||
|
||||
WallsViaCuttedQuads walls;
|
||||
|
||||
for (const Floorplan::FloorObstacle* obs : f->obstacles) {
|
||||
const Floorplan::FloorObstacleLine* line = dynamic_cast<const Floorplan::FloorObstacleLine*>(obs);
|
||||
if (line) {walls.add(f, line, nullptr);}
|
||||
}
|
||||
|
||||
return walls.get();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FLOORPLAN_3D_BUILDER_H
|
||||
334
floorplan/3D/FloorplanMesh.h
Normal file
334
floorplan/3D/FloorplanMesh.h
Normal file
@@ -0,0 +1,334 @@
|
||||
#ifndef FLOORPLAN_3D_FLOORPLANMESH_H
|
||||
#define FLOORPLAN_3D_FLOORPLANMESH_H
|
||||
|
||||
#include "Obstacle3.h"
|
||||
#include "../../geo/BBox3.h"
|
||||
#include <fstream>
|
||||
|
||||
namespace Floorplan3D {
|
||||
|
||||
/**
|
||||
* meshed version of the floorplan
|
||||
*/
|
||||
struct FloorplanMesh {
|
||||
|
||||
std::vector<Obstacle3D> elements;
|
||||
|
||||
BBox3 getBBox() const {
|
||||
BBox3 bb;
|
||||
for (const Obstacle3D& o : elements) {
|
||||
for (const Triangle3& t : o.triangles) {
|
||||
bb.add(t.p1);
|
||||
bb.add(t.p2);
|
||||
bb.add(t.p3);
|
||||
}
|
||||
}
|
||||
return bb;
|
||||
}
|
||||
|
||||
void operator -= (const Point3 p) {
|
||||
for (Obstacle3D& o : elements) {
|
||||
for (Triangle3& t : o.triangles) {
|
||||
t -= p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** export as OBJ file */
|
||||
void exportOBJsimple(const std::string& file) {
|
||||
std::ofstream out(file.c_str());
|
||||
out << toOBJsimple();
|
||||
out.close();
|
||||
}
|
||||
|
||||
/** export as OBJ file */
|
||||
void exportOBJcomplex(const std::string& file, const std::string& nameOnly) {
|
||||
std::ofstream outOBJ((file+".obj").c_str());
|
||||
std::ofstream outMTL((file+".mtl").c_str());
|
||||
OBJData data = toOBJ(nameOnly);
|
||||
outOBJ << data.obj;
|
||||
outMTL << data.mtl;
|
||||
outOBJ.close();
|
||||
outMTL.close();
|
||||
}
|
||||
|
||||
/** export as PLY file */
|
||||
void exportPLY(const std::string& file) {
|
||||
std::ofstream out(file.c_str());
|
||||
out << toPLY();
|
||||
out.close();
|
||||
}
|
||||
|
||||
/** DEBUG: convert to .obj file code for exporting */
|
||||
std::string toOBJsimple() {
|
||||
|
||||
int nVerts = 1;
|
||||
std::string res;
|
||||
|
||||
// write each obstacle
|
||||
for (const Obstacle3D& o : elements) {
|
||||
|
||||
// write the vertices
|
||||
for (const Triangle3& t : o.triangles) {
|
||||
res += "v " + std::to_string(t.p1.x) + " " + std::to_string(t.p1.y) + " " + std::to_string(t.p1.z) + "\n";
|
||||
res += "v " + std::to_string(t.p2.x) + " " + std::to_string(t.p2.y) + " " + std::to_string(t.p2.z) + "\n";
|
||||
res += "v " + std::to_string(t.p3.x) + " " + std::to_string(t.p3.y) + " " + std::to_string(t.p3.z) + "\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// write each obstacle
|
||||
for (const Obstacle3D& o : elements) {
|
||||
|
||||
// write the faces
|
||||
for (size_t i = 0; i < o.triangles.size(); ++i) {
|
||||
res += "f " + std::to_string(nVerts+0) + " " + std::to_string(nVerts+1) + " " + std::to_string(nVerts+2) + "\n";
|
||||
nVerts += 3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// done
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
struct OBJData {
|
||||
std::string obj;
|
||||
std::string mtl;
|
||||
};
|
||||
|
||||
/** DEBUG: convert to .obj file code for exporting */
|
||||
OBJData toOBJ(const std::string name) {
|
||||
|
||||
const BBox3 bb = getBBox();
|
||||
const float ox = bb.getCenter().x;
|
||||
const float oy = bb.getCenter().y;
|
||||
|
||||
bool swapYZ = true;
|
||||
int nVerts = 1;
|
||||
int nObjs = 0;
|
||||
OBJData res;
|
||||
|
||||
// write material file
|
||||
for (size_t idx = 0; idx < mats.size(); ++idx) {
|
||||
const Material& mat = mats[idx];
|
||||
res.mtl += "newmtl mat_" + std::to_string(idx) + "\n";
|
||||
res.mtl += "Ka 0.000 0.000 0.000 \n"; // ambient
|
||||
res.mtl += "Kd " + std::to_string(mat.r/255.0f) + " " + std::to_string(mat.g/255.0f) + " " + std::to_string(mat.b/255.0f) + "\n";
|
||||
res.mtl += "Ks 0.000 0.000 0.000 \n";
|
||||
res.mtl += "d " + std::to_string(mat.a/255.0f) + "\n"; // alpha
|
||||
res.mtl += "Tr " + std::to_string(1.0f-mat.a/255.0f) + "\n"; // inv-alpha
|
||||
res.mtl += "illum 2 \n";
|
||||
res.mtl += "\n";
|
||||
}
|
||||
|
||||
|
||||
// use material file
|
||||
res.obj += "mtllib " + name + ".mtl" + "\n";
|
||||
|
||||
// write each obstacle
|
||||
for (const Obstacle3D& o : elements) {
|
||||
|
||||
// write the vertices
|
||||
for (const Triangle3& t : o.triangles) {
|
||||
if (!swapYZ) {
|
||||
res.obj += "v " + std::to_string(t.p1.x-ox) + " " + std::to_string(t.p1.y-oy) + " " + std::to_string(t.p1.z) + "\n";
|
||||
res.obj += "v " + std::to_string(t.p2.x-ox) + " " + std::to_string(t.p2.y-oy) + " " + std::to_string(t.p2.z) + "\n";
|
||||
res.obj += "v " + std::to_string(t.p3.x-ox) + " " + std::to_string(t.p3.y-oy) + " " + std::to_string(t.p3.z) + "\n";
|
||||
} else {
|
||||
res.obj += "v " + std::to_string(t.p1.x-ox) + " " + std::to_string(t.p1.z) + " " + std::to_string(t.p1.y-oy) + "\n";
|
||||
res.obj += "v " + std::to_string(t.p3.x-ox) + " " + std::to_string(t.p3.z) + " " + std::to_string(t.p3.y-oy) + "\n";
|
||||
res.obj += "v " + std::to_string(t.p2.x-ox) + " " + std::to_string(t.p2.z) + " " + std::to_string(t.p2.y-oy) + "\n";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// write each obstacle
|
||||
for (const Obstacle3D& o : elements) {
|
||||
|
||||
// create a new group
|
||||
//res.obj += "g elem_" + std::to_string(++nObjs) + "\n";
|
||||
|
||||
// create a new object
|
||||
res.obj += "o elem_" + std::to_string(++nObjs) + "\n";
|
||||
|
||||
// group's material
|
||||
res.obj += "usemtl mat_" + std::to_string(getMaterial(o)) + "\n";
|
||||
|
||||
// write the group's faces
|
||||
for (size_t i = 0; i < o.triangles.size(); ++i) {
|
||||
res.obj += "f " + std::to_string(nVerts+0) + " " + std::to_string(nVerts+1) + " " + std::to_string(nVerts+2) + "\n";
|
||||
nVerts += 3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// done
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
/** convert to .ply file format */
|
||||
std::string toPLY() const {
|
||||
|
||||
std::stringstream res;
|
||||
res << "ply\n";
|
||||
res << "format ascii 1.0\n";
|
||||
|
||||
int faces = 0;
|
||||
int vertices = 0;
|
||||
for (const Obstacle3D& obs : elements) {
|
||||
vertices += obs.triangles.size() * 3;
|
||||
faces += obs.triangles.size();
|
||||
}
|
||||
|
||||
|
||||
res << "element material " << mats.size() << "\n";
|
||||
res << "property uchar red\n";
|
||||
res << "property uchar green\n";
|
||||
res << "property uchar blue\n";
|
||||
res << "property uchar alpha\n";
|
||||
|
||||
res << "element vertex " << vertices << "\n";
|
||||
res << "property float x\n";
|
||||
res << "property float y\n";
|
||||
res << "property float z\n";
|
||||
res << "property float nx\n";
|
||||
res << "property float ny\n";
|
||||
res << "property float nz\n";
|
||||
res << "property uchar red\n";
|
||||
res << "property uchar green\n";
|
||||
res << "property uchar blue\n";
|
||||
res << "property uchar alpha\n";
|
||||
res << "property int material_index\n";
|
||||
|
||||
res << "element face " << faces << "\n";
|
||||
res << "property list uchar int vertex_indices\n";
|
||||
|
||||
res << "end_header\n";
|
||||
|
||||
|
||||
|
||||
for (const Material& mat : mats) {
|
||||
res << mat.r << " " << mat.g << " " << mat.b << " " << mat.a << "\n";
|
||||
}
|
||||
|
||||
for (const Obstacle3D& obs : elements) {
|
||||
const int matIdx = getMaterial(obs);
|
||||
const Material& mat = mats[matIdx];
|
||||
for (const Triangle3& tria : obs.triangles) {
|
||||
const Point3 n = cross(tria.p2-tria.p1, tria.p3-tria.p1).normalized();
|
||||
res << tria.p1.x << " " << tria.p1.y << " " << tria.p1.z << " " << n.x << " " << n.y << " " << n.z << " " << mat.r << " " << mat.g << " " << mat.b << " " << mat.a << " " << matIdx << "\n";
|
||||
res << tria.p2.x << " " << tria.p2.y << " " << tria.p2.z << " " << n.x << " " << n.y << " " << n.z << " " << mat.r << " " << mat.g << " " << mat.b << " " << mat.a << " " << matIdx << "\n";
|
||||
res << tria.p3.x << " " << tria.p3.y << " " << tria.p3.z << " " << n.x << " " << n.y << " " << n.z << " " << mat.r << " " << mat.g << " " << mat.b << " " << mat.a << " " << matIdx << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
int vidx = 0;
|
||||
for (const Obstacle3D& obs : elements) {
|
||||
for (const Triangle3& tria : obs.triangles) {
|
||||
(void) tria;
|
||||
res << "3 " << (vidx+0) << " " << (vidx+1) << " " << (vidx+2) << "\n";
|
||||
vidx += 3;
|
||||
}
|
||||
}
|
||||
|
||||
// done
|
||||
return res.str();
|
||||
|
||||
}
|
||||
|
||||
struct Material {
|
||||
int r, g, b, a;
|
||||
Material(int r, int g, int b, int a) : r(r), g(g), b(b), a(a) {;}
|
||||
};
|
||||
|
||||
|
||||
// // material
|
||||
// std::vector<Material> mats = {
|
||||
|
||||
// Material(0,128,0,255), // ground outdoor
|
||||
// Material(64,64,64,255), // ground outdoor
|
||||
// Material(255,96,96,255), // stair
|
||||
|
||||
// Material(128,128,128,255), // concrete
|
||||
// Material(64,128,255,64), // glass
|
||||
// Material(200,200,200,255), // default
|
||||
|
||||
// };
|
||||
|
||||
// int getMaterial(const Obstacle3D& o) const {
|
||||
// if (o.type == Obstacle3D::Type::GROUND_OUTDOOR) {return 0;}
|
||||
// if (o.type == Obstacle3D::Type::GROUND_INDOOR) {return 1;}
|
||||
// if (o.type == Obstacle3D::Type::STAIR) {return 2;}
|
||||
|
||||
// if (o.mat == Floorplan::Material::CONCRETE) {return 3;}
|
||||
// if (o.mat == Floorplan::Material::GLASS) {return 4;}
|
||||
// return 5;
|
||||
// }
|
||||
|
||||
std::vector<Material> mats = {
|
||||
|
||||
Material(255,0,0,255), // error
|
||||
|
||||
Material(0,128,0,255), // ground outdoor
|
||||
Material(64,64,64,255), // ground outdoor
|
||||
Material(105,105,105,255), // stair
|
||||
Material(220,220,220,255), // handrail
|
||||
|
||||
Material(200,200,255,96), // door (glass)
|
||||
Material(140,140,140,255), // door (wood)
|
||||
|
||||
Material(135,135,135,255), // concrete
|
||||
Material(240,240,255,96), // glass
|
||||
Material(170,170,255,96), // glass (metallized)
|
||||
Material(170,120,60,255), // wood
|
||||
Material(200,200,200,255), // drywall
|
||||
|
||||
Material(255,255,255,255), // object
|
||||
|
||||
Material(235,235,235,255), // default
|
||||
|
||||
};
|
||||
|
||||
int getMaterial(const Obstacle3D& o) const {
|
||||
|
||||
if (o.type == Floorplan3D::Obstacle3D::Type::ERROR) {return 0;}
|
||||
|
||||
if (o.type == Floorplan3D::Obstacle3D::Type::GROUND_OUTDOOR) {return 1;}
|
||||
if (o.type == Floorplan3D::Obstacle3D::Type::GROUND_INDOOR) {return 2;}
|
||||
if (o.type == Floorplan3D::Obstacle3D::Type::STAIR) {return 3;}
|
||||
if (o.type == Floorplan3D::Obstacle3D::Type::HANDRAIL) {return 4;}
|
||||
if (o.type == Floorplan3D::Obstacle3D::Type::OBJECT) {return 12;}
|
||||
|
||||
if (o.type == Floorplan3D::Obstacle3D::Type::DOOR && o.mat == Floorplan::Material::GLASS) {return 5;}
|
||||
if (o.type == Floorplan3D::Obstacle3D::Type::DOOR) {return 6;}
|
||||
|
||||
if (o.mat == Floorplan::Material::CONCRETE) {return 7;}
|
||||
if (o.mat == Floorplan::Material::GLASS) {return 8;}
|
||||
if (o.mat == Floorplan::Material::METALLIZED_GLAS) {return 9;}
|
||||
|
||||
if (o.mat == Floorplan::Material::WOOD) {return 10;}
|
||||
if (o.mat == Floorplan::Material::DRYWALL) {return 11;}
|
||||
|
||||
return 12;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Color getColor(const Obstacle3D& o) const {
|
||||
// if (o.type == Obstacle3D::Type::GROUND_OUTDOOR) {return Color(0,128,0,255);}
|
||||
// if (o.type == Obstacle3D::Type::GROUND_INDOOR) {return Color(64,64,64,255);}
|
||||
// if (o.mat == Floorplan::Material::CONCRETE) {return Color(128,128,128,255);}
|
||||
// if (o.mat == Floorplan::Material::GLASS) {return Color(128,128,255,64);}
|
||||
// return Color(200,200,200,255);
|
||||
// }
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FLOORPLAN_3D_FLOORPLANMESH_H
|
||||
83
floorplan/3D/Handrails.h
Normal file
83
floorplan/3D/Handrails.h
Normal file
@@ -0,0 +1,83 @@
|
||||
#ifndef FLOORPLAN_3D_HANDRAILS_H
|
||||
#define FLOORPLAN_3D_HANDRAILS_H
|
||||
|
||||
#include "Obstacle3.h"
|
||||
#include "misc.h"
|
||||
|
||||
namespace Floorplan3D {
|
||||
|
||||
class Handrails {
|
||||
|
||||
public:
|
||||
|
||||
std::vector<Obstacle3D> getHandrails(const Floorplan::Floor* f) {
|
||||
std::vector<Obstacle3D> res;
|
||||
for (const Floorplan::FloorObstacle* o: f->obstacles) {
|
||||
const Floorplan::FloorObstacleLine* line = dynamic_cast<const Floorplan::FloorObstacleLine*>(o);
|
||||
if (line && line->type == Floorplan::ObstacleType::HANDRAIL) {
|
||||
res.push_back(getHandrail(f, line));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Obstacle3D getHandrail(const Floorplan::Floor* f, const Floorplan::FloorObstacleLine* fol) const {
|
||||
|
||||
FloorPos fpos(f);
|
||||
|
||||
// target
|
||||
Obstacle3D res(getType(fol), fol->material);
|
||||
|
||||
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<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;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FLOORPLAN_3D_HANDRAILS_H
|
||||
52
floorplan/3D/Objects.h
Normal file
52
floorplan/3D/Objects.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#ifndef FLOORPLAN_3D_OBJECTS_H
|
||||
#define FLOORPLAN_3D_OBJECTS_H
|
||||
|
||||
#include "Obstacle3.h"
|
||||
#include "misc.h"
|
||||
#include "objects/OBJPool.h"
|
||||
|
||||
namespace Floorplan3D {
|
||||
|
||||
class Objects {
|
||||
|
||||
public:
|
||||
|
||||
std::vector<Obstacle3D> getObjects(const Floorplan::Floor* f) {
|
||||
std::vector<Obstacle3D> res;
|
||||
for (const Floorplan::FloorObstacle* o: f->obstacles) {
|
||||
const Floorplan::FloorObstacleObject* obj = dynamic_cast<const Floorplan::FloorObstacleObject*>(o);
|
||||
if (obj) {
|
||||
res.push_back(getObject(f, obj));
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
return obs;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FLOORPLAN_3D_OBJECTS_H
|
||||
127
floorplan/3D/Obstacle3.h
Normal file
127
floorplan/3D/Obstacle3.h
Normal file
@@ -0,0 +1,127 @@
|
||||
#ifndef FLOORPLAN_3D_OBSTACLE3_H
|
||||
#define FLOORPLAN_3D_OBSTACLE3_H
|
||||
|
||||
#include <vector>
|
||||
#include "../../geo/Triangle3.h"
|
||||
#include "../../geo/Sphere3.h"
|
||||
|
||||
#include "../v2/Floorplan.h"
|
||||
|
||||
namespace Floorplan3D {
|
||||
|
||||
/**
|
||||
* 3D obstacle
|
||||
* based on multiple triangles
|
||||
* has a material and a type
|
||||
*/
|
||||
struct Obstacle3D {
|
||||
|
||||
enum class Type {
|
||||
UNKNOWN,
|
||||
GROUND_INDOOR,
|
||||
GROUND_OUTDOOR,
|
||||
STAIR,
|
||||
HANDRAIL,
|
||||
DOOR,
|
||||
WALL,
|
||||
WINDOW,
|
||||
OBJECT,
|
||||
ERROR,
|
||||
};
|
||||
|
||||
Type type;
|
||||
Floorplan::Material mat;
|
||||
std::vector<Triangle3> triangles;
|
||||
|
||||
/** empty ctor */
|
||||
Obstacle3D() : type(Type::UNKNOWN), mat() {;}
|
||||
|
||||
/** ctor */
|
||||
Obstacle3D(Type type, Floorplan::Material mat) : type(type), mat(mat) {;}
|
||||
|
||||
|
||||
/** append a new triangle. REFERENCE ONLY VALID UNTIL NEXT ADD */
|
||||
Triangle3& addTriangle(const Point3 p1, const Point3 p2, const Point3 p3) {
|
||||
triangles.push_back(Triangle3(p1, p2, p3));
|
||||
return triangles.back();
|
||||
}
|
||||
|
||||
/** append a new quad by splitting into two triangles */
|
||||
void addQuad(const Point3 p1, const Point3 p2, const Point3 p3, const Point3 p4) {
|
||||
addTriangle(p1, p2, p3);
|
||||
addTriangle(p1, p3, p4);
|
||||
}
|
||||
|
||||
/** reverse all faces (CW<->CCW) */
|
||||
void reverseFaces() {
|
||||
for (Triangle3& t : triangles) {
|
||||
t.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** scaled copy */
|
||||
Obstacle3D scaled(const Point3 scale) const {
|
||||
Obstacle3D copy = *this;
|
||||
for (Triangle3& tria : copy.triangles) {
|
||||
tria *= scale;
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
/** translated copy */
|
||||
Obstacle3D translated(const Point3 pos) const {
|
||||
Obstacle3D copy = *this;
|
||||
for (Triangle3& tria : copy.triangles) {
|
||||
tria += pos;
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
/** rotated [around (0,0,0)] copy */
|
||||
Obstacle3D rotated_deg(const Point3 rot) const {
|
||||
Obstacle3D copy = *this;
|
||||
for (Triangle3& tria : copy.triangles) {
|
||||
// tria.p1 = tria.p1.rot(rot.x/180.0f*M_PI, rot.y/180.0f*M_PI, rot.z/180.0f*M_PI);
|
||||
// tria.p2 = tria.p2.rot(rot.x/180.0f*M_PI, rot.y/180.0f*M_PI, rot.z/180.0f*M_PI);
|
||||
// tria.p3 = tria.p3.rot(rot.x/180.0f*M_PI, rot.y/180.0f*M_PI, rot.z/180.0f*M_PI);
|
||||
tria.rotate_deg(rot);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
/** get all triangle-edge-points (x,y) within the obstacle */
|
||||
std::vector<Point2> getPoints2D() const {
|
||||
std::vector<Point2> res;
|
||||
for (const Triangle3& tria : triangles) {
|
||||
res.push_back(tria.p1.xy());
|
||||
res.push_back(tria.p2.xy());
|
||||
res.push_back(tria.p3.xy());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/** get all triangle-edge-points (x,y,z) within the obstacle */
|
||||
std::vector<Point3> getPoints3D() const {
|
||||
std::vector<Point3> res;
|
||||
for (const Triangle3& tria : triangles) {
|
||||
res.push_back(tria.p1);
|
||||
res.push_back(tria.p2);
|
||||
res.push_back(tria.p3);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/** perform sanity checks */
|
||||
bool isValid() const {
|
||||
for (const Triangle3& t : triangles) {
|
||||
if (!t.isValid()) {return false;}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FLOORPLAN_3D_OBSTACLE3_H
|
||||
99
floorplan/3D/Outline.h
Normal file
99
floorplan/3D/Outline.h
Normal file
@@ -0,0 +1,99 @@
|
||||
#ifndef FLOORPLAN_3D_OUTLINE_H
|
||||
#define FLOORPLAN_3D_OUTLINE_H
|
||||
|
||||
#include "Obstacle3.h"
|
||||
#include "misc.h"
|
||||
#include <unordered_map>
|
||||
#include "../../geo/GPCPolygon2.h"
|
||||
|
||||
namespace Floorplan3D {
|
||||
|
||||
class Outline {
|
||||
|
||||
public:
|
||||
|
||||
/** convert a floor (floor/ceiling) into triangles */
|
||||
std::vector<Obstacle3D> get(const Floorplan::Floor* f) {
|
||||
|
||||
FloorPos fpos(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, GPCPolygon2> 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<std::vector<Point3>> polys = it.second.get(fpos.z1);
|
||||
|
||||
// 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
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FLOORPLAN_3D_OUTLINE_H
|
||||
51
floorplan/3D/Pillars.h
Normal file
51
floorplan/3D/Pillars.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef FLOORPLAN_3D_PILLARS_H
|
||||
#define FLOORPLAN_3D_PILLARS_H
|
||||
|
||||
#include "Obstacle3.h"
|
||||
#include "misc.h"
|
||||
#include "primitives/Cylinder.h"
|
||||
|
||||
namespace Floorplan3D {
|
||||
|
||||
class Pillars {
|
||||
|
||||
public:
|
||||
|
||||
std::vector<Obstacle3D> getPillars(const Floorplan::Floor* f) {
|
||||
std::vector<Obstacle3D> res;
|
||||
for (const Floorplan::FloorObstacle* o: f->obstacles) {
|
||||
const Floorplan::FloorObstacleCircle* circ = dynamic_cast<const Floorplan::FloorObstacleCircle*>(o);
|
||||
if (circ) {
|
||||
res.push_back(getPillar(f, circ));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
Obstacle3D getPillar(const Floorplan::Floor* f, const Floorplan::FloorObstacleCircle* foc) {
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FLOORPLAN_3D_PILLARS_H
|
||||
82
floorplan/3D/Stairs.h
Normal file
82
floorplan/3D/Stairs.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#ifndef FLOORPLAN_3D_STAIRS_H
|
||||
#define FLOORPLAN_3D_STAIRS_H
|
||||
|
||||
#include "Obstacle3.h"
|
||||
#include "primitives/Cube.h"
|
||||
|
||||
namespace Floorplan3D {
|
||||
|
||||
class Stairs {
|
||||
|
||||
public:
|
||||
|
||||
std::vector<Obstacle3D> getStairs(const Floorplan::Floor* f) {
|
||||
std::vector<Obstacle3D> res;
|
||||
for (const Floorplan::Stair* stair : f->stairs) {
|
||||
res.push_back(getStair(f, stair));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Obstacle3D getStair(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) {
|
||||
|
||||
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<Triangle3> 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<Triangle3> tmp = cube.getTriangles();
|
||||
res.triangles.insert(res.triangles.end(), tmp.begin(), tmp.end());
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FLOORPLAN_3D_STAIRS_H
|
||||
22
floorplan/3D/Walls.h
Normal file
22
floorplan/3D/Walls.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef FLOORPLAN_3D_WALLS_H
|
||||
#define FLOORPLAN_3D_WALLS_H
|
||||
|
||||
#include "Obstacle3.h"
|
||||
|
||||
namespace Floorplan3D {
|
||||
|
||||
class Walls {
|
||||
|
||||
public:
|
||||
|
||||
virtual void clear() = 0;
|
||||
|
||||
virtual void add(const Floorplan::Floor* f, const Floorplan::FloorObstacleLine* fol, const Floorplan::FloorObstacleDoor* aboveDoor) = 0;
|
||||
|
||||
virtual const std::vector<Obstacle3D>& get() = 0;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FLOORPLAN_3D_WALLS_H
|
||||
69
floorplan/3D/WallsViaCubes.h
Normal file
69
floorplan/3D/WallsViaCubes.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#ifndef FLOORPLAN_3D_WALLSVIACUBE_H
|
||||
#define FLOORPLAN_3D_WALLSVIACUBE_H
|
||||
|
||||
#include "Walls.h"
|
||||
#include "misc.h"
|
||||
#include "primitives/Cube.h"
|
||||
|
||||
namespace Floorplan3D {
|
||||
|
||||
/**
|
||||
* simply use one 3D cube per wall
|
||||
* if walls intersect in the 2D view, cubes will also intersect
|
||||
*/
|
||||
class WallsViaCubes : public Walls {
|
||||
|
||||
Cube::Part cubeParts = (Cube::Part) 63; // leftright,topbottom,rearfront
|
||||
|
||||
std::vector<Obstacle3D> vec;
|
||||
|
||||
public:
|
||||
|
||||
void clear() override {
|
||||
vec.clear();
|
||||
}
|
||||
|
||||
void add(const Floorplan::Floor* f, const Floorplan::FloorObstacleLine* fol, const Floorplan::FloorObstacleDoor* aboveDoor) override {
|
||||
|
||||
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();
|
||||
vec.push_back(res);
|
||||
|
||||
}
|
||||
|
||||
const std::vector<Obstacle3D>& get() override {
|
||||
return vec;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FLOORPLAN_3D_WALLSVIACUBE_H
|
||||
303
floorplan/3D/WallsViaCuttedQuads.h
Normal file
303
floorplan/3D/WallsViaCuttedQuads.h
Normal file
@@ -0,0 +1,303 @@
|
||||
#ifndef FLOORPLAN_3D_WALLSVIACUTTEDQUADS_H
|
||||
#define FLOORPLAN_3D_WALLSVIACUTTEDQUADS_H
|
||||
|
||||
#include "../../geo/Line2.h"
|
||||
#include "../../geo/Polygon2.h"
|
||||
#include "Walls.h"
|
||||
#include "misc.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace Floorplan3D {
|
||||
|
||||
/**
|
||||
* interpret walls als quads (polygons)
|
||||
* intersect them with each other to prevent overlaps
|
||||
*/
|
||||
class WallsViaCuttedQuads : public Walls {
|
||||
|
||||
private:
|
||||
|
||||
struct Wall {
|
||||
|
||||
/** algorithms set error flag here */
|
||||
bool error = false;
|
||||
|
||||
/** original line from floorplan */
|
||||
const Floorplan::FloorObstacleLine* line;
|
||||
|
||||
/** outlines after applying thickness */
|
||||
Line2 l1;
|
||||
Line2 l2;
|
||||
|
||||
Wall(const Floorplan::FloorObstacleLine* line) : line(line) {
|
||||
|
||||
const Point2 from = line->from;
|
||||
const Point2 to = line->to;
|
||||
const Point2 dir = (to-from).normalized();
|
||||
const Point2 dirP = dir.perpendicular();
|
||||
const float w = line->thickness_m;
|
||||
const float w2 = w/2;
|
||||
|
||||
const Point2 p1 = from + dirP * w2;
|
||||
const Point2 p2 = from - dirP * w2;
|
||||
const Point2 p3 = to - dirP * w2;
|
||||
const Point2 p4 = to + dirP * w2;
|
||||
|
||||
l1 = Line2(p1, p4);
|
||||
l2 = Line2(p2, p3);
|
||||
|
||||
}
|
||||
|
||||
/** get points for CCW wall outline (p1->p2->p3->p4) */
|
||||
Point2 getP1() const {return l1.p1;}
|
||||
Point2 getP2() const {return l1.p2;}
|
||||
Point2 getP3() const {return l2.p2;}
|
||||
Point2 getP4() const {return l2.p1;}
|
||||
|
||||
Point2& getP1() {return l1.p1;}
|
||||
Point2& getP2() {return l1.p2;}
|
||||
Point2& getP3() {return l2.p2;}
|
||||
Point2& getP4() {return l2.p1;}
|
||||
|
||||
struct CutRes {
|
||||
Point2 p;
|
||||
Line2* l1; // affected line within this wall
|
||||
Line2* l2; // affected line within the other wall
|
||||
CutRes(Point2 p, Line2* l1, Line2* l2) : p(p), l1(l1), l2(l2) {;}
|
||||
};
|
||||
|
||||
/** get all intersecting points between the two walls */
|
||||
std::vector<CutRes> getIntersections(Wall& o, bool limit = true) {
|
||||
std::vector<CutRes> res;
|
||||
Point2 p;
|
||||
if (intersects(l1, o.l1, limit, p)) {res.push_back(CutRes(p,&l1, &o.l1));}
|
||||
if (intersects(l1, o.l2, limit, p)) {res.push_back(CutRes(p,&l1, &o.l2));}
|
||||
if (intersects(l2, o.l1, limit, p)) {res.push_back(CutRes(p,&l2, &o.l1));}
|
||||
if (intersects(l2, o.l2, limit, p)) {res.push_back(CutRes(p,&l2, &o.l2));}
|
||||
return res;
|
||||
}
|
||||
|
||||
/** is this wall directly attached to the given wall? */
|
||||
bool directlyConnectedTo(const Wall& o) const {
|
||||
const float d = 0.001;
|
||||
return
|
||||
(line->from.eq( o.line->from, d)) ||
|
||||
(line->to.eq( o.line->from, d)) ||
|
||||
(line->from.eq( o.line->to, d)) ||
|
||||
(line->to.eq( o.line->to, d));
|
||||
}
|
||||
|
||||
/** does this wall end within the given wall? */
|
||||
bool endsWithin(const Wall& o) const {
|
||||
return o.containsPoint(line->from) || o.containsPoint(line->to);
|
||||
}
|
||||
|
||||
/** does this wall contain the given point */
|
||||
bool containsPoint(const Point2 p) const {
|
||||
return polygonContainsPoint({getP1(), getP2(), getP3(), getP4()}, p);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
void cropLine(Line2* l, Point2 p) {
|
||||
|
||||
// determine which line-end to crop
|
||||
if (p.getDistance(l->p1) < p.getDistance(l->p2)) {
|
||||
l->p1 = p;
|
||||
} else {
|
||||
l->p2 = p;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void cutInMiddle(Point2& pa1, Point2& pa2, Point2& pb1, Point2& pb2) {
|
||||
|
||||
// line from pa1->pa2, and pb1->pb2
|
||||
// intersection expected near pa2|pb1
|
||||
|
||||
Line2 l1(pa1, pa2); l1 = l1.longerAtEnd(10);
|
||||
Line2 l2(pb1, pb2); l2 = l2.longerAtStart(10);
|
||||
|
||||
Point2 pi;
|
||||
if (intersects(l1, l2, true, pi)) {
|
||||
pa2 = pi; // replace end of line1
|
||||
pb1 = pi; // replace start of line2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::vector<Wall> walls;
|
||||
const Floorplan::Floor* floor;
|
||||
std::vector<Obstacle3D> obs;
|
||||
|
||||
public:
|
||||
|
||||
|
||||
void clear() override {
|
||||
walls.clear();
|
||||
}
|
||||
|
||||
void add(const Floorplan::Floor* f, const Floorplan::FloorObstacleLine* fol, const Floorplan::FloorObstacleDoor* aboveDoor) override {
|
||||
if (fol->type != Floorplan::ObstacleType::WALL) {return;}
|
||||
if (aboveDoor) {return;}
|
||||
this->floor = f;
|
||||
walls.push_back(Wall(fol));
|
||||
}
|
||||
|
||||
virtual const std::vector<Obstacle3D>& get() override {
|
||||
std::vector<Wall> tmp = walls;
|
||||
tmp = cutConnected(tmp);
|
||||
tmp = cutProtruding(tmp);
|
||||
obs = toObstacle(tmp, floor);
|
||||
return obs;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/** convert all walls to obstacles */
|
||||
std::vector<Obstacle3D> toObstacle(const std::vector<Wall>& walls, const Floorplan::Floor* f) {
|
||||
std::vector<Obstacle3D> res;
|
||||
for (const Wall& w : walls) {
|
||||
res.push_back(toObstacle(w, f));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/** convert one wall into an obstacle */
|
||||
Obstacle3D toObstacle(const Wall& wall, const Floorplan::Floor* f) {
|
||||
|
||||
FloorPos fp(f);
|
||||
|
||||
Obstacle3D::Type type = (wall.error) ? (Obstacle3D::Type::ERROR) : (getType(wall.line));
|
||||
Obstacle3D obs(type, wall.line->material);
|
||||
|
||||
const float z1 = fp.z1;
|
||||
const float z2 = fp.z2;
|
||||
|
||||
const Point3 p1 = Point3(wall.getP1().x, wall.getP1().y, z1);
|
||||
const Point3 p2 = Point3(wall.getP2().x, wall.getP2().y, z1);
|
||||
const Point3 p3 = Point3(wall.getP3().x, wall.getP3().y, z1);
|
||||
const Point3 p4 = Point3(wall.getP4().x, wall.getP4().y, z1);
|
||||
|
||||
const Point3 p1u = Point3(wall.getP1().x, wall.getP1().y, z2);
|
||||
const Point3 p2u = Point3(wall.getP2().x, wall.getP2().y, z2);
|
||||
const Point3 p3u = Point3(wall.getP3().x, wall.getP3().y, z2);
|
||||
const Point3 p4u = Point3(wall.getP4().x, wall.getP4().y, z2);
|
||||
|
||||
obs.addQuad(p1, p2, p2u, p1u);
|
||||
obs.addQuad(p2, p3, p3u, p2u);
|
||||
obs.addQuad(p3, p4, p4u, p3u);
|
||||
obs.addQuad(p4, p1, p1u, p4u);
|
||||
|
||||
obs.addQuad(p1u, p2u, p3u, p4u);
|
||||
obs.addQuad(p4, p3, p2, p1);
|
||||
|
||||
obs.reverseFaces();
|
||||
|
||||
return obs;
|
||||
|
||||
}
|
||||
|
||||
/** cut off walls ending within another wall */
|
||||
std::vector<Wall> cutProtruding(std::vector<Wall> walls) {
|
||||
|
||||
// if one wall ends within another one cut it off
|
||||
|
||||
for (size_t i = 0; i < walls.size(); ++i) {
|
||||
Wall& w1 = walls[i];
|
||||
|
||||
for (size_t j = i+1; j < walls.size(); ++j) {
|
||||
Wall& w2 = walls[j];
|
||||
|
||||
if (i == j) {continue;}
|
||||
|
||||
// if the two walls are directly connected (share one node) -> ignore this case here!
|
||||
if (w1.directlyConnectedTo(w2)) {continue;}
|
||||
|
||||
// not the case we are looking for?
|
||||
if (!w1.endsWithin(w2) && !w2.endsWithin(w1)) {continue;}
|
||||
|
||||
// get all intersection points between the two walls
|
||||
std::vector<Wall::CutRes> isects = w1.getIntersections(w2);
|
||||
|
||||
// this should be 0 (no intersections) or 2 (one for each outline)
|
||||
if (!isects.empty() && isects.size() != 2) {
|
||||
w1.error = true;
|
||||
w2.error = true;
|
||||
std::cout << "detected strange wall intersection" << std::endl;
|
||||
}
|
||||
|
||||
int cut = 0;
|
||||
|
||||
// check the (2) detected intersections
|
||||
for (const auto isect : isects) {
|
||||
|
||||
// if one of the line-ends p1/p2 from wall1 ends within wall2, crop it by setting it to the intersection
|
||||
if (w2.containsPoint(isect.l1->p1)) {isect.l1->p1 = isect.p; ++cut;}
|
||||
if (w2.containsPoint(isect.l1->p2)) {isect.l1->p2 = isect.p; ++cut;}
|
||||
|
||||
// if one of the line-ends p1/p2 from wall2 ends within wall1, crop it by setting it to the intersection
|
||||
if (w1.containsPoint(isect.l2->p1)) {isect.l2->p1 = isect.p; ++cut;}
|
||||
if (w1.containsPoint(isect.l2->p2)) {isect.l2->p2 = isect.p; ++cut;}
|
||||
|
||||
}
|
||||
|
||||
// 2 lines should have been cut. if not, potential issue!
|
||||
if (cut != 2) {
|
||||
w1.error = true;
|
||||
w2.error = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return walls;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** if two walls share one node, cut both ends in the middle (like 45 degree) */
|
||||
std::vector<Wall> cutConnected(std::vector<Wall> walls) {
|
||||
|
||||
for (size_t i = 0; i < walls.size(); ++i) {
|
||||
Wall& w1 = walls[i];
|
||||
|
||||
for (size_t j = i+1; j < walls.size(); ++j) {
|
||||
Wall& w2 = walls[j];
|
||||
|
||||
if (i == j) {continue;}
|
||||
|
||||
// if the two walls are note directly connected -> ignore
|
||||
if (!w1.directlyConnectedTo(w2)) {continue;}
|
||||
|
||||
const float d = 0.001;
|
||||
if (w1.line->to.eq(w2.line->from, d)) {
|
||||
cutInMiddle(w1.getP1(), w1.getP2(), w2.getP1(), w2.getP2());
|
||||
cutInMiddle(w1.getP4(), w1.getP3(), w2.getP4(), w2.getP3());
|
||||
} else if (w1.line->to.eq(w2.line->to, d)) {
|
||||
cutInMiddle(w1.getP1(), w1.getP2(), w2.getP3(), w2.getP4());
|
||||
cutInMiddle(w1.getP4(), w1.getP3(), w2.getP2(), w2.getP1());
|
||||
} else if (w1.line->from.eq(w2.line->to, d)) {
|
||||
cutInMiddle(w1.getP3(), w1.getP4(), w2.getP3(), w2.getP4());
|
||||
cutInMiddle(w1.getP2(), w1.getP1(), w2.getP2(), w2.getP1());
|
||||
} else if (w1.line->from.eq(w2.line->from, d)) {
|
||||
cutInMiddle(w1.getP3(), w1.getP4(), w2.getP1(), w2.getP2());
|
||||
cutInMiddle(w1.getP2(), w1.getP1(), w2.getP4(), w2.getP3());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return walls;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FLOORPLAN_3D_WALLSVIACUTTEDQUADS_H
|
||||
33
floorplan/3D/misc.h
Normal file
33
floorplan/3D/misc.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef FLOORPLAN_3D_MISC_H
|
||||
#define FLOORPLAN_3D_MISC_H
|
||||
|
||||
#include "Obstacle3.h"
|
||||
|
||||
namespace Floorplan3D {
|
||||
|
||||
/** 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) {;}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // FLOORPLAN_3D_MISC_H
|
||||
130
floorplan/3D/objects/MTLReader.h
Normal file
130
floorplan/3D/objects/MTLReader.h
Normal file
@@ -0,0 +1,130 @@
|
||||
#ifndef MTLREADER_H
|
||||
#define MTLREADER_H
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include "../../../geo/Point2.h"
|
||||
#include "../../../geo/Point3.h"
|
||||
|
||||
/**
|
||||
* prase .mtl files
|
||||
*/
|
||||
class MTLReader {
|
||||
|
||||
public:
|
||||
|
||||
struct Material {
|
||||
std::string textureFile = "";
|
||||
Point3 diffuse = Point3(1,1,1);
|
||||
float alpha = 1.0;
|
||||
};
|
||||
|
||||
Material* cur = nullptr;
|
||||
std::unordered_map<std::string, Material> map;
|
||||
|
||||
/** ctor. use readXYZ() */
|
||||
MTLReader() {
|
||||
;
|
||||
}
|
||||
|
||||
/** read .obj from the given file */
|
||||
void readFile(const std::string& file) {
|
||||
std::ifstream is(file);
|
||||
std::string line;
|
||||
while(getline(is, line)) {parseLine(line);}
|
||||
is.close();
|
||||
}
|
||||
|
||||
/** read obj from the given data string (.obj file contents) */
|
||||
void readData(const std::string& data) {
|
||||
std::stringstream is(data);
|
||||
std::string line;
|
||||
while(getline(is, line)) {parseLine(line);}
|
||||
}
|
||||
|
||||
/** get the given material */
|
||||
const Material& getMaterial(const std::string& mat) const {
|
||||
const auto& it = map.find(mat);
|
||||
if (it == map.end()) {throw Exception("material not available");}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
template<typename Out>
|
||||
void split(const std::string &s, char delim, Out result) {
|
||||
std::stringstream ss(s);
|
||||
std::string item;
|
||||
while (std::getline(ss, item, delim)) {
|
||||
*(result++) = item;
|
||||
}
|
||||
}
|
||||
|
||||
void replaceAll(std::string& str, const std::string& from, const std::string& to) {
|
||||
size_t start_pos = 0;
|
||||
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
|
||||
size_t end_pos = start_pos + from.length();
|
||||
str.replace(start_pos, end_pos, to);
|
||||
start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
|
||||
}
|
||||
}
|
||||
|
||||
/** remove empty strings from the vector */
|
||||
std::vector<std::string> nonEmpty(const std::vector<std::string>& src) {
|
||||
std::vector<std::string> res;
|
||||
for (const std::string& s : src) {
|
||||
if (!s.empty()) {res.push_back(s);}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<std::string> split(const std::string &s, char delim) {
|
||||
std::vector<std::string> elems;
|
||||
split(s, delim, std::back_inserter(elems));
|
||||
return elems;
|
||||
}
|
||||
|
||||
/** parse one line of the .obj file */
|
||||
void parseLine(std::string line) {
|
||||
|
||||
if (line.length() < 2) {return;}
|
||||
|
||||
// remove leading "#"
|
||||
while (line[0] == ' ' || line[0] == '\t') {
|
||||
line.erase(line.begin());
|
||||
}
|
||||
|
||||
// remove other linebreaks
|
||||
replaceAll(line, "\r", "");
|
||||
|
||||
const std::vector<std::string> tokens = nonEmpty(split(line, ' '));
|
||||
const std::string token = tokens.front();
|
||||
|
||||
if ("newmtl" == token) {
|
||||
const std::string id = tokens[1];
|
||||
map[id] = Material();
|
||||
cur = &map[id];
|
||||
} else if ("map_Ka" == token) {
|
||||
const std::string texFile = tokens[1];
|
||||
cur->textureFile = texFile;
|
||||
} else if ("map_Kd" == token) {
|
||||
const std::string texFile = tokens[1];
|
||||
cur->textureFile = texFile;
|
||||
} else if ("Kd" == token) {
|
||||
cur->diffuse.x = std::stof(tokens[1]);
|
||||
cur->diffuse.y = std::stof(tokens[2]);
|
||||
cur->diffuse.z = std::stof(tokens[3]);
|
||||
} else if ("d" == token) {
|
||||
cur->alpha = std::stof(tokens[1]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // MTLREADER_H
|
||||
135
floorplan/3D/objects/OBJPool.h
Normal file
135
floorplan/3D/objects/OBJPool.h
Normal file
@@ -0,0 +1,135 @@
|
||||
#ifndef FLOORPLAN_3D_OBJPOOL_H
|
||||
#define FLOORPLAN_3D_OBJPOOL_H
|
||||
|
||||
#include <vector>
|
||||
#include "../../../geo/Triangle3.h"
|
||||
#include <unordered_map>
|
||||
|
||||
#include "OBJReader.h"
|
||||
#include "../Obstacle3.h"
|
||||
|
||||
// LINUX ONLY
|
||||
//#include <dirent.h>
|
||||
//#include <stdio.h>
|
||||
#include "../../../data/File.h"
|
||||
|
||||
|
||||
#include "../../../misc/Debug.h"
|
||||
|
||||
|
||||
namespace Floorplan3D {
|
||||
|
||||
/**
|
||||
* load several named 3D models for quick re-use
|
||||
*/
|
||||
class OBJPool {
|
||||
|
||||
private:
|
||||
|
||||
static constexpr const char* name = "OBJ-Pool";
|
||||
|
||||
/** singleton */
|
||||
OBJPool() {;}
|
||||
|
||||
bool initDone = false;
|
||||
std::unordered_map<std::string, Obstacle3D> cache;
|
||||
|
||||
public:
|
||||
|
||||
/** singleton access */
|
||||
static OBJPool& get() {
|
||||
static OBJPool instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/** init with *.obj data folder */
|
||||
void init(const std::string& folder) {
|
||||
|
||||
scanFolder(folder);
|
||||
initDone = true;
|
||||
|
||||
}
|
||||
|
||||
/** init with multiple *.obj data folder */
|
||||
void init(const std::initializer_list<std::string>& folders) {
|
||||
|
||||
for (const std::string& folder : folders) {
|
||||
try {
|
||||
scanFolder(folder);
|
||||
} catch (...) {;}
|
||||
}
|
||||
initDone = true;
|
||||
|
||||
}
|
||||
|
||||
/** get all triangles for the given object (if known) */
|
||||
const Obstacle3D& getObject(const std::string& name) {
|
||||
|
||||
// ensure the cache is initialized
|
||||
if (!initDone) {
|
||||
throw Exception("OBJPool: not initialized. call init(folder) first");
|
||||
}
|
||||
|
||||
static Obstacle3D empty;
|
||||
|
||||
// find the entry
|
||||
const auto& it = cache.find(name);
|
||||
if (it == cache.end()) {return empty;}
|
||||
return it->second;
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/** scan the given folder for all *.obj files */
|
||||
void scanFolder(const std::string& folder) {
|
||||
|
||||
FS::File d(folder);
|
||||
if (!d.exists()) {
|
||||
throw Exception("OBJPool: folder not found: " + folder);
|
||||
}
|
||||
|
||||
for (const FS::File& f : d.listFiles()) {
|
||||
std::string name = f.getFilename();
|
||||
if (endsWith(name, ".obj")) {
|
||||
//std::string name = entry.path().filename().string();
|
||||
name = name.substr(0, name.length() - 4); // without extension
|
||||
load(f.getPath(), name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inline bool endsWith(std::string const & value, std::string const & ending) {
|
||||
if (ending.size() > value.size()) return false;
|
||||
return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
|
||||
}
|
||||
|
||||
/** load the given .obj file into the cache */
|
||||
void load(const std::string& absName, const std::string& name) {
|
||||
|
||||
OBJReader reader;
|
||||
reader.readFile(absName);
|
||||
//reader.readFile("/mnt/vm/paper/diss/code/IndoorMap/res/mdl/" + file + ".obj"); // todo
|
||||
|
||||
// create triangles
|
||||
Obstacle3D obs;
|
||||
for (const OBJReader::Object& obj : reader.getData().objects) {
|
||||
for (const OBJReader::Face& face : obj.faces) {
|
||||
const Triangle3 tria(face.vnt[0].vertex, face.vnt[1].vertex, face.vnt[2].vertex);
|
||||
obs.triangles.push_back(tria);
|
||||
}
|
||||
}
|
||||
|
||||
Log::add(this->name, "loaded: " + absName + " [" + std::to_string(obs.triangles.size()) + " triangles]", true);
|
||||
|
||||
// store
|
||||
cache[name] = obs;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FLOORPLAN_3D_OBJPOOL_H
|
||||
213
floorplan/3D/objects/OBJReader.h
Normal file
213
floorplan/3D/objects/OBJReader.h
Normal file
@@ -0,0 +1,213 @@
|
||||
#ifndef OBJREADER_H
|
||||
#define OBJREADER_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include "../../../geo/Point2.h"
|
||||
#include "../../../geo/Point3.h"
|
||||
|
||||
/**
|
||||
* prase .obj files
|
||||
*/
|
||||
class OBJReader {
|
||||
|
||||
public:
|
||||
|
||||
/** group vertex+normal+texture */
|
||||
struct VNT {
|
||||
int idxVertex;
|
||||
int idxNormal;
|
||||
int idxTexture;
|
||||
Point3 vertex;
|
||||
Point3 normal;
|
||||
Point2 texture;
|
||||
};
|
||||
|
||||
/** one triangle */
|
||||
struct Face {
|
||||
VNT vnt[3];
|
||||
Face(VNT v1, VNT v2, VNT v3) : vnt{v1,v2,v3} {;}
|
||||
};
|
||||
|
||||
/** one object within the file */
|
||||
struct Object {
|
||||
std::string material;
|
||||
std::string name;
|
||||
std::vector<Face> faces;
|
||||
};
|
||||
|
||||
/** internal data */
|
||||
struct Data {
|
||||
std::vector<Point3> vertices;
|
||||
std::vector<Point2> texCoords;
|
||||
std::vector<Point3> normals;
|
||||
std::vector<std::string> materialFiles;
|
||||
std::vector<Object> objects;
|
||||
Object& curObj() {
|
||||
if (objects.empty()) {objects.push_back(Object());}
|
||||
return objects.back();
|
||||
}
|
||||
} data;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor. use readXYZ() */
|
||||
OBJReader() {
|
||||
;
|
||||
}
|
||||
|
||||
/** read .obj from the given file */
|
||||
void readFile(const std::string& file) {
|
||||
std::ifstream is(file);
|
||||
std::string line;
|
||||
while(getline(is, line)) {parseLine(line);}
|
||||
is.close();
|
||||
}
|
||||
|
||||
/** read obj from the given data string (.obj file contents) */
|
||||
void readData(const std::string& data) {
|
||||
std::stringstream is(data);
|
||||
std::string line;
|
||||
while(getline(is, line)) {parseLine(line);}
|
||||
}
|
||||
|
||||
/** get the parsed data */
|
||||
const Data& getData() const {return data;}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
void replaceAll(std::string& str, const std::string& from, const std::string& to) {
|
||||
size_t start_pos = 0;
|
||||
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
|
||||
size_t end_pos = start_pos + from.length();
|
||||
str.replace(start_pos, end_pos, to);
|
||||
start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
|
||||
}
|
||||
}
|
||||
|
||||
/** remove empty strings from the vector */
|
||||
std::vector<std::string> nonEmpty(const std::vector<std::string>& src) {
|
||||
std::vector<std::string> res;
|
||||
for (const std::string& s : src) {
|
||||
if (!s.empty()) {res.push_back(s);}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename Out>
|
||||
void split(const std::string &s, char delim, Out result) {
|
||||
std::stringstream ss(s);
|
||||
std::string item;
|
||||
while (std::getline(ss, item, delim)) {
|
||||
*(result++) = item;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> split(const std::string &s, char delim) {
|
||||
std::vector<std::string> elems;
|
||||
split(s, delim, std::back_inserter(elems));
|
||||
return elems;
|
||||
}
|
||||
|
||||
/** parse one line of the .obj file */
|
||||
void parseLine(std::string line) {
|
||||
|
||||
if (line.length() < 2) {return;}
|
||||
|
||||
// remove other linebreaks
|
||||
replaceAll(line, "\r", "");
|
||||
|
||||
const std::vector<std::string> tokens = nonEmpty(split(line, ' '));
|
||||
const std::string token = tokens.front();
|
||||
|
||||
if ("mtllib" == token) {data.materialFiles.push_back(tokens[1]);}
|
||||
if ("usemtl" == token) {data.curObj().material = tokens[1];}
|
||||
if ("v" == token) {parseVertex(tokens);}
|
||||
if ("vt" == token) {parseTexCoord(tokens);}
|
||||
if ("vn" == token) {parseNormal(tokens);}
|
||||
if ("f" == token) {parseFace(tokens);}
|
||||
if ("g" == token) {newObject(tokens[1]);}
|
||||
if ("o" == token) {newObject(tokens[1]);}
|
||||
|
||||
}
|
||||
|
||||
/** allocate a new object */
|
||||
void newObject(const std::string& name) {
|
||||
Object o;
|
||||
o.name = name;
|
||||
data.objects.push_back(o);
|
||||
}
|
||||
|
||||
/** parse one vertex from the tokenizer */
|
||||
void parseVertex(const std::vector<std::string>& t) {
|
||||
const float x = std::stof(t[1]);
|
||||
const float y = std::stof(t[2]);
|
||||
const float z = std::stof(t[3]);
|
||||
data.vertices.push_back(Point3(x,y,z));
|
||||
}
|
||||
|
||||
/** parse one texture-coordinate from the tokenizer */
|
||||
void parseTexCoord(const std::vector<std::string>& t) {
|
||||
const float u = std::stof(t[1]);
|
||||
const float v = std::stof(t[2]);
|
||||
data.texCoords.push_back(Point2(u, -v));
|
||||
}
|
||||
|
||||
/** parse one normal from the tokenizer */
|
||||
void parseNormal(const std::vector<std::string>& t) {
|
||||
const float x = std::stof(t[1]);
|
||||
const float y = std::stof(t[2]);
|
||||
const float z = std::stof(t[3]);
|
||||
data.normals.push_back(Point3(x,y,z));
|
||||
}
|
||||
|
||||
/** parse one face from the tokenizer */
|
||||
void parseFace(const std::vector<std::string>& t) {
|
||||
|
||||
std::vector<VNT> indices;
|
||||
|
||||
int numVertices = 0;
|
||||
for (size_t i = 1; i < t.size(); ++i) {
|
||||
|
||||
// one V/T/N
|
||||
const std::string entry = t[i];
|
||||
const std::vector<std::string> vtn = split(entry, '/');
|
||||
|
||||
++numVertices;
|
||||
const std::string v = vtn[0];
|
||||
const std::string vt = (vtn.size() > 1) ? (vtn[1]) : ("");
|
||||
const std::string vn = (vtn.size() > 2) ? (vtn[2]) : ("");
|
||||
//const std::string vt = t2.getToken('/', false);
|
||||
//const std::string vn = t2.getToken('/', false);
|
||||
|
||||
// create a new vertex/normal/texture combination
|
||||
VNT vnt;
|
||||
vnt.idxVertex = (std::stoi(v) - 1);
|
||||
vnt.idxNormal = (vn.empty()) ? (-1) : (std::stoi(vn) - 1);
|
||||
vnt.idxTexture = (vt.empty()) ? (-1) : (std::stoi(vt) - 1);
|
||||
|
||||
if (vnt.idxVertex >= 0) {vnt.vertex = data.vertices[vnt.idxVertex];}
|
||||
if (vnt.idxNormal >= 0) {vnt.normal = data.normals[vnt.idxNormal];}
|
||||
if (vnt.idxTexture >= 0) {vnt.texture = data.texCoords[vnt.idxTexture];}
|
||||
|
||||
indices.push_back(vnt);
|
||||
|
||||
}
|
||||
|
||||
// this will both, create normal triangles and triangulate polygons
|
||||
// see: http://www.mathopenref.com/polygontriangles.html
|
||||
for (int i = 1; i < (int) indices.size()-1; ++i) {
|
||||
Face face(indices[0], indices[1], indices[i+1]);
|
||||
data.curObj().faces.push_back(face);
|
||||
}
|
||||
|
||||
// sanity check
|
||||
// if (numVertices != 3) {throw "this face is not a triangle!";}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // OBJREADER_H
|
||||
166
floorplan/3D/primitives/Cube.h
Normal file
166
floorplan/3D/primitives/Cube.h
Normal file
@@ -0,0 +1,166 @@
|
||||
#ifndef FLOORPLAN_3D_CUBE_H
|
||||
#define FLOORPLAN_3D_CUBE_H
|
||||
|
||||
|
||||
#include "../../../math/Matrix4.h"
|
||||
#include "Mesh.h"
|
||||
|
||||
namespace Floorplan3D {
|
||||
|
||||
class Cube : public Mesh {
|
||||
|
||||
private:
|
||||
|
||||
/** ctor */
|
||||
Cube() {
|
||||
//unitCube(true);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
|
||||
enum Part {
|
||||
CUBE_TOP = 1,
|
||||
CUBE_BOTTOM = 2,
|
||||
CUBE_LEFT = 4,
|
||||
CUBE_RIGHT = 8,
|
||||
CUBE_FRONT = 16,
|
||||
CUBE_BACK = 32,
|
||||
};
|
||||
|
||||
// Cube (const Point3 p1, const Point3 p2, const Point3 p3, const Point3 p4, const float h) {
|
||||
//// const Point3 ph(0,0,h);
|
||||
//// addQuad(p1+ph, p2+ph, p3+ph, p4+ph); // top
|
||||
//// addQuad(p4, p3, p2, p1); // bottom
|
||||
//// addQuad(p3+ph, p2+ph, p2, p3); // right
|
||||
//// addQuad(p1+ph, p4+ph, p4, p1); // left
|
||||
//// addQuad(p2+ph, p1+ph, p1, p2); // front
|
||||
//// addQuad(p4+ph, p3+ph, p3, p4); // back
|
||||
// addQuad();
|
||||
// }
|
||||
|
||||
/** ctor with position, size and rotation */
|
||||
Cube(const Point3 pos, const Point3 size, const Point3 rot_deg, const Part parts = (Part)63) {
|
||||
unitCube(parts);
|
||||
transform(pos, size, rot_deg);
|
||||
}
|
||||
|
||||
|
||||
/** get a transformed version */
|
||||
Cube transformed(const Matrix4& mat) const {
|
||||
Cube res = *this;
|
||||
res.transform(mat);
|
||||
return res;
|
||||
}
|
||||
|
||||
/** get a transformed version */
|
||||
Cube transformed(const Point3 pos, const Point3 size, const Point3 rot_deg) const {
|
||||
Cube res = *this;
|
||||
res.transform(pos, size, rot_deg);
|
||||
return res;
|
||||
}
|
||||
|
||||
static Cube unit(const Part parts = (Part) 63) {
|
||||
Cube cube;
|
||||
cube.unitCube(parts);
|
||||
return cube;
|
||||
}
|
||||
|
||||
/** cube from 8 vertices (upper 4, lower 4) */
|
||||
static Cube fromVertices(const Point3 pt1, const Point3 pt2, const Point3 pt3, const Point3 pt4, const Point3 pb1, const Point3 pb2, const Point3 pb3, const Point3 pb4) {
|
||||
Cube cube;
|
||||
cube.addQuad(pt1, pt2, pt3, pt4); // top
|
||||
cube.addQuad(pb4, pb3, pb2, pb1); // bottom
|
||||
cube.addQuad(pt3, pt2, pb2, pb3); // right
|
||||
cube.addQuad(pt1, pt4, pb4, pb1); // left
|
||||
cube.addQuad(pt2, pt1, pb1, pb2); // front
|
||||
cube.addQuad(pt4, pt3, pb3, pb4); // back
|
||||
return cube;
|
||||
}
|
||||
|
||||
/** cube from 8 vertices (upper 4, lower 4) */
|
||||
static Cube fromBottomAndHeight(const Point3 pb1, const Point3 pb2, const Point3 pb3, const Point3 pb4, const float h) {
|
||||
const Point3 ph(0,0,h);
|
||||
return Cube::fromVertices(pb1+ph, pb2+ph, pb3+ph, pb4+ph, pb1, pb2, pb3, pb4);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
/** build unit-cube faces */
|
||||
void unitCube(const Part parts) {
|
||||
|
||||
const float s = 1.0f;
|
||||
|
||||
|
||||
|
||||
// left?
|
||||
if (parts & CUBE_LEFT) {
|
||||
addQuad(
|
||||
Point3(+s, -s, -s),
|
||||
Point3(+s, -s, +s),
|
||||
Point3(-s, -s, +s),
|
||||
Point3(-s, -s, -s)
|
||||
);
|
||||
}
|
||||
|
||||
// right?
|
||||
if (parts & CUBE_RIGHT) {
|
||||
addQuad(
|
||||
Point3(-s, +s, -s),
|
||||
Point3(-s, +s, +s),
|
||||
Point3(+s, +s, +s),
|
||||
Point3(+s, +s, -s)
|
||||
);
|
||||
}
|
||||
|
||||
// front
|
||||
if (parts & CUBE_FRONT) {
|
||||
addQuad(
|
||||
Point3(-s, -s, -s),
|
||||
Point3(-s, -s, +s),
|
||||
Point3(-s, +s, +s),
|
||||
Point3(-s, +s, -s)
|
||||
);
|
||||
}
|
||||
|
||||
// back
|
||||
if (parts & CUBE_BACK) {
|
||||
addQuad(
|
||||
Point3(+s, +s, -s),
|
||||
Point3(+s, +s, +s),
|
||||
Point3(+s, -s, +s),
|
||||
Point3(+s, -s, -s)
|
||||
);
|
||||
}
|
||||
|
||||
// top
|
||||
if (parts & CUBE_TOP) {
|
||||
addQuad(
|
||||
Point3(+s, +s, +s),
|
||||
Point3(-s, +s, +s),
|
||||
Point3(-s, -s, +s),
|
||||
Point3(+s, -s, +s)
|
||||
);
|
||||
}
|
||||
|
||||
// bottom
|
||||
if (parts & CUBE_BOTTOM) {
|
||||
addQuad(
|
||||
Point3(+s, -s, -s),
|
||||
Point3(-s, -s, -s),
|
||||
Point3(-s, +s, -s),
|
||||
Point3(+s, +s, -s)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FLOORPLAN_3D_CUBE_H
|
||||
82
floorplan/3D/primitives/Cylinder.h
Normal file
82
floorplan/3D/primitives/Cylinder.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#ifndef FLOORPLAN_3D_CYLINDER_H
|
||||
#define FLOORPLAN_3D_CYLINDER_H
|
||||
|
||||
|
||||
#include "../../../math/Matrix4.h"
|
||||
#include "Mesh.h"
|
||||
|
||||
namespace Floorplan3D {
|
||||
|
||||
/** walled cylinder */
|
||||
class Cylinder : public Mesh {
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
Cylinder() {
|
||||
;
|
||||
}
|
||||
|
||||
/** get a transformed version */
|
||||
Cylinder transformed(const Matrix4& mat) const {
|
||||
Cylinder res = *this;
|
||||
res.transform(mat);
|
||||
return res;
|
||||
}
|
||||
|
||||
/** build */
|
||||
void add(const float rOuter, const float h, bool topAndBottom) {
|
||||
|
||||
const int tiles = 8;
|
||||
const float deg_per_tile = 360.0f / tiles;
|
||||
const float rad_per_tile = deg_per_tile / 180.0f * M_PI;
|
||||
|
||||
for (int i = 0; i < tiles; ++i) {
|
||||
|
||||
const float startRad = (i+0) * rad_per_tile;
|
||||
const float endRad = (i+1) * rad_per_tile;
|
||||
|
||||
const float xo0 = std::cos(startRad) * rOuter;
|
||||
const float yo0 = std::sin(startRad) * rOuter;
|
||||
const float xo1 = std::cos(endRad) * rOuter;
|
||||
const float yo1 = std::sin(endRad) * rOuter;
|
||||
const float cx = 0;
|
||||
const float cy = 0;
|
||||
|
||||
// outer
|
||||
addQuad(
|
||||
Point3(xo0, yo0, -h),
|
||||
Point3(xo1, yo1, -h),
|
||||
Point3(xo1, yo1, +h),
|
||||
Point3(xo0, yo0, +h)
|
||||
);
|
||||
|
||||
if (topAndBottom) {
|
||||
|
||||
// top
|
||||
addTriangle(
|
||||
Point3(cx, cy, h),
|
||||
Point3(xo0, yo0, h),
|
||||
Point3(xo1, yo1, h)
|
||||
);
|
||||
|
||||
// bottom
|
||||
addTriangle(
|
||||
Point3(cx, cy, -h),
|
||||
Point3(xo1, yo1, -h),
|
||||
Point3(xo0, yo0, -h)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FLOORPLAN_3D_CYLINDER_H
|
||||
69
floorplan/3D/primitives/Mesh.h
Normal file
69
floorplan/3D/primitives/Mesh.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#ifndef FLOORPLAN_3D_MESH_H
|
||||
#define FLOORPLAN_3D_MESH_H
|
||||
|
||||
#include <vector>
|
||||
#include "../../../geo/Triangle3.h"
|
||||
#include "../../../math/Matrix4.h"
|
||||
|
||||
namespace Floorplan3D {
|
||||
|
||||
class Mesh {
|
||||
|
||||
protected:
|
||||
|
||||
std::vector<Triangle3> trias;
|
||||
|
||||
public:
|
||||
|
||||
/** get the mesh's triangles */
|
||||
const std::vector<Triangle3>& getTriangles() const {
|
||||
return trias;
|
||||
}
|
||||
|
||||
void transform(const Point3 pos, Point3 size, Point3 rot_deg) {
|
||||
const Matrix4 mRot = Matrix4::getRotationDeg(rot_deg.x, rot_deg.y, rot_deg.z);
|
||||
const Matrix4 mSize = Matrix4::getScale(size.x, size.y, size.z);
|
||||
const Matrix4 mPos = Matrix4::getTranslation(pos.x, pos.y, pos.z);
|
||||
const Matrix4 mat = mPos * mRot * mSize;
|
||||
transform(mat);
|
||||
}
|
||||
|
||||
void translate(const Point3 pos) {
|
||||
const Matrix4 mPos = Matrix4::getTranslation(pos.x, pos.y, pos.z);
|
||||
transform(mPos);
|
||||
}
|
||||
|
||||
void transform(const Matrix4& mat) {
|
||||
|
||||
for (Triangle3& tria : trias) {
|
||||
|
||||
Vector4 v1(tria.p1.x, tria.p1.y, tria.p1.z, 1);
|
||||
Vector4 v2(tria.p2.x, tria.p2.y, tria.p2.z, 1);
|
||||
Vector4 v3(tria.p3.x, tria.p3.y, tria.p3.z, 1);
|
||||
|
||||
v1 = mat*v1;
|
||||
v2 = mat*v2;
|
||||
v3 = mat*v3;
|
||||
|
||||
tria.p1 = Point3(v1.x, v1.y, v1.z);
|
||||
tria.p2 = Point3(v2.x, v2.y, v2.z);
|
||||
tria.p3 = Point3(v3.x, v3.y, v3.z);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void addQuad(Point3 p1, Point3 p2, Point3 p3, Point3 p4) {
|
||||
trias.push_back( Triangle3(p1,p2,p3) );
|
||||
trias.push_back( Triangle3(p1,p3,p4) );
|
||||
}
|
||||
|
||||
void addTriangle(Point3 p1, Point3 p2, Point3 p3) {
|
||||
trias.push_back( Triangle3(p1,p2,p3) );
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FLOORPLAN_3D_MESH_H
|
||||
122
floorplan/3D/primitives/Tube.h
Normal file
122
floorplan/3D/primitives/Tube.h
Normal file
@@ -0,0 +1,122 @@
|
||||
#ifndef FLOORPLAN_3D_TUBE_H
|
||||
#define FLOORPLAN_3D_TUBE_H
|
||||
|
||||
|
||||
#include "../../../math/Matrix4.h"
|
||||
#include "Mesh.h"
|
||||
|
||||
namespace Floorplan3D {
|
||||
|
||||
/** walled cylinder */
|
||||
class Tube : public Mesh {
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
Tube() {
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
/** get a transformed version */
|
||||
Tube transformed(const Matrix4& mat) const {
|
||||
Tube res = *this;
|
||||
res.transform(mat);
|
||||
return res;
|
||||
}
|
||||
|
||||
/** build */
|
||||
void addSegment(const float from_deg, const float to_deg, const float rInner, const float rOuter, const float h, bool closeSides, bool topAndBottom) {
|
||||
|
||||
const int tiles = 32;
|
||||
const float deg_per_tile = 360.0f / tiles;
|
||||
const float rad_per_tile = deg_per_tile / 180.0f * M_PI;
|
||||
const int startTile = std::round(from_deg / deg_per_tile);
|
||||
const int endTile = std::round(to_deg / deg_per_tile);
|
||||
|
||||
for (int i = startTile; i < endTile; ++i) {
|
||||
|
||||
const float startRad = (i+0) * rad_per_tile;
|
||||
const float endRad = (i+1) * rad_per_tile;
|
||||
|
||||
const float xo0 = std::cos(startRad) * rOuter;
|
||||
const float yo0 = std::sin(startRad) * rOuter;
|
||||
const float xo1 = std::cos(endRad) * rOuter;
|
||||
const float yo1 = std::sin(endRad) * rOuter;
|
||||
|
||||
const float xi0 = std::cos(startRad) * rInner;
|
||||
const float yi0 = std::sin(startRad) * rInner;
|
||||
const float xi1 = std::cos(endRad) * rInner;
|
||||
const float yi1 = std::sin(endRad) * rInner;
|
||||
|
||||
if (closeSides) {
|
||||
|
||||
// close start of segment
|
||||
if (i == startTile) {
|
||||
addQuad(
|
||||
Point3(xi0, yi0, -h),
|
||||
Point3(xo0, yo0, -h),
|
||||
Point3(xo0, yo0, +h),
|
||||
Point3(xi0, yi0, +h)
|
||||
);
|
||||
}
|
||||
|
||||
// close end of segment
|
||||
if (i == endTile-1) {
|
||||
addQuad(
|
||||
Point3(xi1, yi1, +h),
|
||||
Point3(xo1, yo1, +h),
|
||||
Point3(xo1, yo1, -h),
|
||||
Point3(xi1, yi1, -h)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// outer
|
||||
addQuad(
|
||||
Point3(xo0, yo0, -h),
|
||||
Point3(xo1, yo1, -h),
|
||||
Point3(xo1, yo1, +h),
|
||||
Point3(xo0, yo0, +h)
|
||||
);
|
||||
|
||||
// innser
|
||||
addQuad(
|
||||
Point3(xi0, yi0, +h),
|
||||
Point3(xi1, yi1, +h),
|
||||
Point3(xi1, yi1, -h),
|
||||
Point3(xi0, yi0, -h)
|
||||
);
|
||||
|
||||
if (topAndBottom) {
|
||||
|
||||
// top
|
||||
addQuad(
|
||||
Point3(xi0, yi0, h),
|
||||
Point3(xo0, yo0, h),
|
||||
Point3(xo1, yo1, h),
|
||||
Point3(xi1, yi1, h)
|
||||
);
|
||||
|
||||
// bottom
|
||||
addQuad(
|
||||
Point3(xi1, yi1, -h),
|
||||
Point3(xo1, yo1, -h),
|
||||
Point3(xo0, yo0, -h),
|
||||
Point3(xi0, yi0, -h)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FLOORPLAN_3D_TUBE_H
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "Floorplan.h"
|
||||
#include "../../geo/BBox2.h"
|
||||
#include "../../wifi/estimate/ray3/ModelFactory.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
@@ -147,12 +146,13 @@ namespace Floorplan {
|
||||
res.push_back(Issue(Type::ERR, floor, "' door is too narrow: " + std::to_string(len_m) + " meter from " + door->from.asString() + " to " + door->to.asString()));
|
||||
}
|
||||
|
||||
try {
|
||||
Ray3D::ModelFactory fac(map);
|
||||
fac.getDoorAbove(floor, door);
|
||||
} catch (Exception e) {
|
||||
res.push_back(Issue(Type::ERR, floor, std::string(e.what()) + "[from" + door->from.asString() + " to " + door->to.asString() + "]"));
|
||||
}
|
||||
#warning "TODO!"
|
||||
// try {
|
||||
// Ray3D::ModelFactory fac(map);
|
||||
// fac.getDoorAbove(floor, door);
|
||||
// } catch (Exception e) {
|
||||
// res.push_back(Issue(Type::ERR, floor, std::string(e.what()) + "[from" + door->from.asString() + " to " + door->to.asString() + "]"));
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user