added support for .obj objects within the floorplan

include objects within navmesh calculation
include objects within 3d mesh generation
minor changes/fixes
This commit is contained in:
2018-02-17 17:20:43 +01:00
parent 42a3a47317
commit 8358f45674
15 changed files with 770 additions and 114 deletions

View File

@@ -2,6 +2,7 @@
#define FLOORPLANMESH_H
#include "Obstacle3.h"
#include <fstream>
namespace Ray3D {
@@ -12,11 +13,35 @@ namespace Ray3D {
std::vector<Obstacle3D> elements;
/** 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 toOBJ() {
std::string toOBJsimple() {
int nVerts = 1;
int nObjs = 0;
std::string res;
// write each obstacle
@@ -29,12 +54,87 @@ namespace Ray3D {
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) {
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) + " " + std::to_string(t.p1.y) + " " + std::to_string(t.p1.z) + "\n";
res.obj += "v " + std::to_string(t.p2.x) + " " + std::to_string(t.p2.y) + " " + std::to_string(t.p2.z) + "\n";
res.obj += "v " + std::to_string(t.p3.x) + " " + std::to_string(t.p3.y) + " " + std::to_string(t.p3.z) + "\n";
} else {
res.obj += "v " + std::to_string(t.p1.x) + " " + std::to_string(t.p1.z) + " " + std::to_string(t.p1.y) + "\n";
res.obj += "v " + std::to_string(t.p2.x) + " " + std::to_string(t.p2.z) + " " + std::to_string(t.p2.y) + "\n";
res.obj += "v " + std::to_string(t.p3.x) + " " + std::to_string(t.p3.z) + " " + std::to_string(t.p3.y) + "\n";
}
}
}
// write each obstacle
for (const Obstacle3D& o : elements) {
// create a new group
res += "g elem_" + std::to_string(++nObjs) + "\n";
//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 += "f " + std::to_string(nVerts+0) + " " + std::to_string(nVerts+1) + " " + std::to_string(nVerts+2) + "\n";
res.obj += "f " + std::to_string(nVerts+0) + " " + std::to_string(nVerts+1) + " " + std::to_string(nVerts+2) + "\n";
nVerts += 3;
}
@@ -59,18 +159,6 @@ namespace Ray3D {
faces += obs.triangles.size();
}
// 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
};
res << "element material " << mats.size() << "\n";
res << "property uchar red\n";
@@ -85,11 +173,11 @@ namespace Ray3D {
res << "property float nx\n";
res << "property float ny\n";
res << "property float nz\n";
res << "property int material_index\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";
@@ -107,9 +195,9 @@ namespace Ray3D {
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 << " " << matIdx << " " << mat.r << " " << mat.g << " " << mat.b << " " << mat.a << "\n";
res << tria.p2.x << " " << tria.p2.y << " " << tria.p2.z << " " << n.x << " " << n.y << " " << n.z << " " << matIdx << " " << mat.r << " " << mat.g << " " << mat.b << " " << mat.a <<"\n";
res << tria.p3.x << " " << tria.p3.y << " " << tria.p3.z << " " << n.x << " " << n.y << " " << n.z << " " << matIdx << " " << mat.r << " " << mat.g << " " << mat.b << " " << mat.a <<"\n";
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";
}
}
@@ -131,6 +219,20 @@ namespace Ray3D {
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;}

View File

@@ -9,6 +9,8 @@
#include "Tube.h"
#include "FloorplanMesh.h"
#include "OBJPool.h"
namespace Ray3D {
/**
@@ -25,6 +27,7 @@ namespace Ray3D {
bool exportHandrails = true;
bool exportDoors = true;
bool doorsOpen = true;
bool exportObjects = true;
bool exportWallTops = false;
std::vector<Floorplan::Floor*> exportFloors;
@@ -114,6 +117,8 @@ namespace Ray3D {
/** convert a floor (floor/ceiling) into triangles */
std::vector<Obstacle3D> getFloor(const Floorplan::Floor* f) {
FloorPos fpos(f);
std::vector<Obstacle3D> res;
if (!f->enabled) {return res;}
if (!f->outline.enabled) {return res;}
@@ -157,20 +162,22 @@ namespace Ray3D {
Obstacle3D obs(type, Floorplan::Material::CONCRETE);
// convert them into polygons
std::vector<std::vector<Point3>> polys = it.second.get(f->getStartingZ());
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 for reflection to work with the correct normals
// 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) {tria1 = tria1 - Point3(0,0,0.02);}
if (tria2.getNormal().z < 0) {tria2 = tria2 - Point3(0,0,0.02);}
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);
@@ -208,6 +215,16 @@ namespace Ray3D {
}
}
// handle object obstacles
const Floorplan::FloorObstacleObject* foo = dynamic_cast<const Floorplan::FloorObstacleObject*>(fo);
if (foo) {
if (exportObjects) {
if (!foo->file.empty()) {
res.push_back(getObject(f, foo));
}
}
}
const Floorplan::FloorObstacleDoor* door = dynamic_cast<const Floorplan::FloorObstacleDoor*>(fo);
if (door) {
if (exportObstacles) {
@@ -246,6 +263,8 @@ namespace Ray3D {
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);
@@ -255,8 +274,8 @@ namespace Ray3D {
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 float cenZ = (!aboveDoor) ? (fpos.z1 + fpos.height/2) : (fpos.z2 - (fpos.height - aboveDoor->height) / 2);
const float height = (!aboveDoor) ? (fpos.height) : (fpos.height - aboveDoor->height);
const Point3 pos(cen2.x, cen2.y, cenZ);
// div by 2.01 to prevent overlapps and z-fighting
@@ -276,8 +295,44 @@ namespace Ray3D {
}
/** 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);
obs = obs.rotated_deg( Point3(foo->rot.x, foo->rot.y, foo->rot.z) );
obs = obs.translated(foo->pos + Point3(0,0,fpos.z1));
// std::vector<Triangle3> 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;
@@ -297,7 +352,7 @@ namespace Ray3D {
if (doorsOpen) {deg += (door->swap) ? (-90) : (+90);}
mat = Matrix4::getTranslation(1,0,0); // cube's edge located at 0,0,0
pos = Point3(from.x, from.y, f->atHeight + door->height/2);
pos = Point3(from.x, from.y, fpos.z1 + door->height/2);
const float sx = from.getDistance(to) / 2;
const float sy = thickness_m / 2;
@@ -312,7 +367,7 @@ namespace Ray3D {
} else if (Floorplan::DoorType::REVOLVING == door->type) {
const Point2 cen2 = (from+to)/2;
const Point3 pos(cen2.x, cen2.y, f->atHeight + door->height/2);
const Point3 pos(cen2.x, cen2.y, fpos.z1 + door->height/2);
// outer and inner radius
const float rOuter = from.getDistance(to) / 2;
@@ -350,11 +405,6 @@ namespace Ray3D {
}
return res;
}
@@ -383,6 +433,8 @@ namespace Ray3D {
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;}
@@ -393,8 +445,8 @@ namespace Ray3D {
const Point2 cen2 = (from+to)/2;
// edges
const float z1 = f->atHeight;
const float z2 = f->atHeight + 1.0;
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);
@@ -576,6 +628,15 @@ namespace Ray3D {
}
}
/** 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) {;}
};
};
}

View File

@@ -0,0 +1,108 @@
#ifndef OBJPOOL_H
#define 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>
namespace Ray3D {
/**
* load several named 3D models for quick re-use
*/
class OBJPool {
private:
/** singleton */
OBJPool() {;}
bool initDone = false;
std::unordered_map<std::string, Obstacle3D> cache;
public:
/** singleton access */
static OBJPool& get() {
static OBJPool instance;
return instance;
}
/** data folder */
void init(const std::string& folder) {
initDone = true;
// LINUX ONLY!
DIR* d = opendir(folder.c_str());
if (!d) {throw Exception("OBJPool: folder not found: " + folder);}
struct dirent *dir;
while ((dir = readdir(d)) != NULL) {
const std::string absFile = folder + "/" + dir->d_name;
if (endsWith(absFile, ".obj")) {
std::string name = std::string(dir->d_name);
name = name.substr(0, name.length() - 4); // without extension
load(absFile, name);
}
}
closedir(d);
}
/** 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:
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::Face& face : reader.getData().faces) {
const Triangle3 tria(face.vnt[0].vertex, face.vnt[1].vertex, face.vnt[2].vertex);
obs.triangles.push_back(tria);
}
// store
cache[name] = obs;
}
};
}
#endif // OBJPOOL_H

View File

@@ -0,0 +1,169 @@
#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}) {;}
};
/** internal data */
struct Data {
std::vector<Point3> vertices;
std::vector<Point2> texCoords;
std::vector<Point3> normals;
std::vector<Face> faces;
} 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:
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(const std::string& line) {
if (line.length() < 2) {return;}
const std::vector<std::string> tokens = split(line, ' ');
const std::string token = tokens.front();
if ("v" == token) {parseVertex(tokens);}
if ("vt" == token) {parseTexCoord(tokens);}
if ("vn" == token) {parseNormal(tokens);}
if ("f" == token) {parseFace(tokens);}
}
/** 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 = 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.faces.push_back(face);
}
// sanity check
if (numVertices != 3) {throw "this face is not a triangle!";}
}
};
#endif // OBJREADER_H

View File

@@ -25,6 +25,7 @@ namespace Ray3D {
DOOR,
WALL,
WINDOW,
OBJECT,
};
Type type;
@@ -37,6 +38,27 @@ namespace Ray3D {
/** ctor */
Obstacle3D(Type type, Floorplan::Material mat) : type(type), mat(mat) {;}
/** 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);
}
return copy;
}
};
}