forked from pz4kybsvg/Conception
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
155 lines
5.6 KiB
155 lines
5.6 KiB
#include "drake/geometry/read_obj.h"
|
|
|
|
#include <fmt/format.h>
|
|
#include <tiny_obj_loader.h>
|
|
|
|
#include "drake/common/drake_assert.h"
|
|
#include "drake/common/text_logging.h"
|
|
|
|
static_assert(std::is_same_v<tinyobj::real_t, double>,
|
|
"tinyobjloader must be compiled in double-precision mode");
|
|
|
|
namespace drake {
|
|
namespace geometry {
|
|
namespace internal {
|
|
namespace {
|
|
|
|
// TODO(SeanCurtis-TRI) Move this tinyobj->fcl code into its own library that
|
|
// can be built and tested separately.
|
|
|
|
//
|
|
// Convert vertices from tinyobj format to FCL format.
|
|
//
|
|
// Vertices from tinyobj are in a vector of floating-points like this:
|
|
// attrib.vertices = {c0,c1,c2, c3,c4,c5, c6,c7,c8,...}
|
|
// = {x, y, z, x, y, z, x, y, z,...}
|
|
// We will convert to a vector of Vector3d for FCL like this:
|
|
// vertices = {{c0,c1,c2}, {c3,c4,c5}, {c6,c7,c8},...}
|
|
// = { v0, v1, v2, ...}
|
|
//
|
|
// The size of `attrib.vertices` is three times the number of vertices.
|
|
//
|
|
std::vector<Eigen::Vector3d> TinyObjToFclVertices(
|
|
const tinyobj::attrib_t& attrib, const double scale) {
|
|
int num_coords = attrib.vertices.size();
|
|
DRAKE_DEMAND(num_coords % 3 == 0);
|
|
std::vector<Eigen::Vector3d> vertices;
|
|
vertices.reserve(num_coords / 3);
|
|
|
|
auto iter = attrib.vertices.begin();
|
|
while (iter != attrib.vertices.end()) {
|
|
// We increment `iter` three times for x, y, and z coordinates.
|
|
double x = *(iter++) * scale;
|
|
double y = *(iter++) * scale;
|
|
double z = *(iter++) * scale;
|
|
vertices.emplace_back(x, y, z);
|
|
}
|
|
|
|
return vertices;
|
|
}
|
|
|
|
//
|
|
// Returns the `mesh`'s faces re-encoded in a format consistent with what
|
|
// fcl::Convex expects.
|
|
//
|
|
// A tinyobj mesh has an integer array storing the number of vertices of
|
|
// each polygonal face.
|
|
// mesh.num_face_vertices = {n0,n1,n2,...}
|
|
// face0 has n0 vertices.
|
|
// face1 has n1 vertices.
|
|
// face2 has n2 vertices.
|
|
// ...
|
|
// A tinyobj mesh has a vector of vertices that belong to the faces.
|
|
// mesh.indices = {v0_0, v0_1,..., v0_n0-1,
|
|
// v1_0, v1_1,..., v1_n1-1,
|
|
// v2_0, v2_1,..., v2_n2-1,
|
|
// ...}
|
|
// face0 has vertices v0_0, v0_1,...,v0_n0-1.
|
|
// face1 has vertices v1_0, v1_1,...,v1_n1-1.
|
|
// face2 has vertices v2_0, v2_1,...,v2_n2-1.
|
|
// ...
|
|
// For fcl::Convex, faces are encoded as an array of integers in this format.
|
|
// faces = { n0, v0_0,v0_1,...,v0_n0-1,
|
|
// n1, v1_0,v1_1,...,v1_n1-1,
|
|
// n2, v2_0,v2_1,...,v2_n2-1,
|
|
// ...}
|
|
// where ni is the number of vertices of facei.
|
|
//
|
|
// The actual number of faces returned will be equal to:
|
|
// mesh.num_face_vertices.size() which *cannot* be easily inferred from the
|
|
// *size* of the returned vector.
|
|
std::vector<int> TinyObjToFclFaces(const tinyobj::mesh_t& mesh) {
|
|
std::vector<int> faces;
|
|
faces.reserve(mesh.indices.size() + mesh.num_face_vertices.size());
|
|
auto iter = mesh.indices.begin();
|
|
for (int num : mesh.num_face_vertices) {
|
|
faces.push_back(num);
|
|
std::for_each(iter, iter + num, [&faces](const tinyobj::index_t& index) {
|
|
faces.push_back(index.vertex_index);
|
|
});
|
|
iter += num;
|
|
}
|
|
|
|
return faces;
|
|
}
|
|
} // namespace
|
|
|
|
std::tuple<std::shared_ptr<std::vector<Eigen::Vector3d>>,
|
|
std::shared_ptr<std::vector<int>>, int>
|
|
ReadObjFile(const std::string& filename, double scale, bool triangulate) {
|
|
tinyobj::attrib_t attrib;
|
|
std::vector<tinyobj::shape_t> shapes;
|
|
std::vector<tinyobj::material_t> materials;
|
|
std::string warn;
|
|
std::string err;
|
|
|
|
// Tinyobj doesn't infer the search directory from the directory containing
|
|
// the obj file. We have to provide that directory; of course, this assumes
|
|
// that the material library reference is relative to the obj directory.
|
|
const size_t pos = filename.find_last_of('/');
|
|
const std::string obj_folder = filename.substr(0, pos + 1);
|
|
const char* mtl_basedir = obj_folder.c_str();
|
|
|
|
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err,
|
|
filename.c_str(), mtl_basedir, triangulate);
|
|
if (!ret || !err.empty()) {
|
|
throw std::runtime_error("Error parsing file '" + filename + "' : " + err);
|
|
}
|
|
if (!warn.empty()) {
|
|
drake::log()->warn("Warning parsing file '{}' : {}", filename, warn);
|
|
}
|
|
|
|
if (shapes.size() == 0) {
|
|
throw std::runtime_error(
|
|
fmt::format("The file parsed contains no objects; only OBJs with "
|
|
"a single object are supported. The file could be "
|
|
"corrupt, empty, or not an OBJ file. File name: '{}'",
|
|
filename));
|
|
} else if (shapes.size() > 1) {
|
|
throw std::runtime_error(
|
|
fmt::format("The OBJ file contains multiple objects; only OBJs with "
|
|
"a single object are supported: File name: '{}'",
|
|
filename));
|
|
}
|
|
|
|
auto vertices = std::make_shared<std::vector<Eigen::Vector3d>>(
|
|
TinyObjToFclVertices(attrib, scale));
|
|
|
|
// We will have `faces.size()` larger than the number of faces. For each
|
|
// face_i, the vector `faces` contains both the number and indices of its
|
|
// vertices:
|
|
// faces = { n0, v0_0,v0_1,...,v0_n0-1,
|
|
// n1, v1_0,v1_1,...,v1_n1-1,
|
|
// n2, v2_0,v2_1,...,v2_n2-1,
|
|
// ...}
|
|
// where n_i is the number of vertices of face_i.
|
|
//
|
|
int num_faces = static_cast<int>(shapes[0].mesh.num_face_vertices.size());
|
|
auto faces =
|
|
std::make_shared<std::vector<int>>(TinyObjToFclFaces(shapes[0].mesh));
|
|
return {vertices, faces, num_faces};
|
|
}
|
|
} // namespace internal
|
|
} // namespace geometry
|
|
} // namespace drake
|