Skip to content

Instantly share code, notes, and snippets.

@mtao
Last active May 30, 2020 01:41
Show Gist options
  • Save mtao/b04256f2c2e7f596db1566186811178e to your computer and use it in GitHub Desktop.
Save mtao/b04256f2c2e7f596db1566186811178e to your computer and use it in GitHub Desktop.
some relatively naive performance attempts on igl's mesh loader
compiled with `clang++ -O3 -std=c++17 -march=native igl_mesh_read_test.cpp -o igl_mesh_read_test`
Hit something unexpected (unless i'm doing something stupid). In particular, the cost of pre-reading a file to pre-allocate the `vector<vector<double>>` data outweighs the cost of reading in a mesh. Changing the data type that the loader assumes is of size 3 to `vector<array<double,3>` increased performance. I thought that increasing the size of the heap objects being allocated would make pre-sizing more important but it didn't; in fact my preliminary tests said that it made results even slower. My methodology of using `reserve()` then `.push_back` could be faulty (perhaps `resize()` -> `value[idx] = val` will actually work better?
Format of the results are "igl base impl || vector file scan || array file scan || just with array"
File /home/mtao/Downloads/20131013_Venus2.obj took 2576 || 2733 || 2701 || 2503.33 ms/load (7728 || 8200 || 8105 || 7510ms total)
File /home/mtao/git/quartet/meshes/block.obj took 61 || 61 || 51 || 50.3333 ms/load (184 || 184 || 155 || 151ms total)
File /home/mtao/git/quartet/meshes/bunny_watertight.obj took 120 || 128 || 124 || 117.333 ms/load (360 || 385 || 374 || 352ms total)
File /home/mtao/git/quartet/meshes/cow.obj took 10 || 11 || 11 || 10 ms/load (31 || 33 || 33 || 30ms total)
File /home/mtao/git/quartet/meshes/cube.obj took 0 || 0 || 0 || 0 ms/load (0 || 0 || 0 || 0ms total)
File /home/mtao/git/quartet/meshes/dragon.obj took 173 || 183 || 176 || 166.333 ms/load (521 || 549 || 529 || 499ms total)
File /home/mtao/git/quartet/meshes/fandisk.obj took 23 || 24 || 23 || 22 ms/load (69 || 72 || 71 || 66ms total)
File /home/mtao/git/quartet/meshes/joint.obj took 0 || 1 || 0 || 0.333333 ms/load (0 || 3 || 2 || 1ms total)
File /home/mtao/git/quartet/meshes/sphere.obj took 0 || 0 || 0 || 0 ms/load (0 || 0 || 0 || 0ms total)
#include <map>
#include <type_traits>
#include "readOBJ.h"
int main(int argc, char* argv[]) {
std::map<std::string, double> elapsed_time;
std::map<std::string, double> elapsed_time_prescan;
std::map<std::string, double> elapsed_time_prescan_array;
std::map<std::string, double> elapsed_time_array;
int iteration_count = 3;
auto clear = [](auto&&... vec) {
((vec.clear(), 0) && ...);
((vec.shrink_to_fit(), 0) && ...);
};
auto run = [argc, argv, clear, iteration_count](
auto func, std::map<std::string, double>& elapsed_time,
auto&& truth) {
Timer timer;
using truth_type = std::decay_t<decltype(truth)>;
using vec3_type =
std::conditional_t<truth_type::value, std::vector<double>,
std::array<double, 3>>;
using vec2_type =
std::conditional_t<truth_type::value, std::vector<double>,
std::array<double, 2>>;
std::vector<vec3_type> V;
std::vector<std::vector<double>> TC;
std::vector<vec3_type> N;
std::vector<std::vector<int>> F;
std::vector<std::vector<int>> FTC;
std::vector<std::vector<int>> FN;
for (int i = 0; i < iteration_count; ++i) {
for (int index = 1; index < argc; ++index) {
std::string filename(argv[index]);
clear(V, TC, N, F, FTC, FN);
std::cerr << "Loading " << filename << "for the " << i
<< "th time" << std::endl;
timer.start();
func(filename, V, TC, N, F, FTC, FN);
timer.stop();
auto [it, didit] = elapsed_time.try_emplace(filename, 0);
it->second += timer.getElapsedTimeInMilliSec();
}
}
};
run([](auto&&... args) { readOBJ(args...); }, elapsed_time,
std::true_type{});
run([](auto&&... args) { readOBJ_prescan(args...); }, elapsed_time_prescan,
std::true_type{});
run([](auto&&... args) { readOBJ_prescan_array(args...); },
elapsed_time_prescan_array, std::false_type{});
run([](auto&&... args) { readOBJ_array(args...); }, elapsed_time_array,
std::false_type{});
for (auto&& [fn, time] : elapsed_time) {
double time2 = elapsed_time_prescan[fn];
double time3 = elapsed_time_prescan_array[fn];
double time4 = elapsed_time_array[fn];
std::cout << "File " << fn << " took ";
for (int v : {time, time2, time3}) {
std::cout << v / iteration_count << " || ";
}
std::cout << time4 / iteration_count;
std::cout << " ms/load (";
for (int v : {time, time2, time3}) {
std::cout << v << " || ";
}
std::cout << time4;
std::cout << "ms total)" << std::endl;
}
}
// This file is part of libigl, a simple c++ geometry processing library.
//
// Copyright (C) 2013 Alec Jacobson <[email protected]>
//
// This Source Code Form is subject to the terms of the Mozilla Public License
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
// obtain one at http://mozilla.org/MPL/2.0/.
#ifndef IGL_READOBJ_H
#define IGL_READOBJ_H
// History:
// return type changed from void to bool Alec 18 Sept 2011
// added pure vector of vectors version that has much more support Alec 31 Oct
// 2011
// This file is torn from
// https://github.com/libigl/libigl/blob/master/include/igl/readOBJ.h
#include <string>
#include <vector>
#include <cstdio>
#include <cassert>
#include <iostream>
#include <cstdio>
#include <fstream>
#include <sstream>
#include <iterator>
#include <chrono>
#include <optional>
#include <cstring>
#include <array>
struct Timer {
using clock_t = std::chrono::high_resolution_clock;
using ms_t = std::chrono::milliseconds;
using dur_t = std::chrono::duration<double>;
using time_pt_t = std::chrono::time_point<clock_t>;
const time_pt_t& start() { return _start.emplace(clock_t::now()); }
const time_pt_t& stop() { return _stop.emplace(clock_t::now()); }
dur_t getElapsedTime() const { return *_stop - *_start; }
double getElapsedTimeInMilliSec() const { return std::chrono::duration_cast<ms_t>(getElapsedTime()).count(); }
std::optional<time_pt_t> _start;
std::optional<time_pt_t> _stop;
};
template <typename Scalar, typename Index>
bool readOBJ_array(
FILE * obj_file,
std::vector<std::array<Scalar, 3> > & V,
std::vector<std::vector<Scalar> > & TC,
std::vector<std::array<Scalar, 3> > & N,
std::vector<std::vector<Index > > & F,
std::vector<std::vector<Index > > & FTC,
std::vector<std::vector<Index > > & FN)
{
Timer timer;
// File open was successful so clear outputs
V.clear();
TC.clear();
N.clear();
F.clear();
FTC.clear();
FN.clear();
// variables and constants to assist parsing the .obj file
// Constant strings to compare against
std::string v("v");
std::string vn("vn");
std::string vt("vt");
std::string f("f");
std::string tic_tac_toe("#");
#ifndef IGL_LINE_MAX
# define IGL_LINE_MAX 2048
#endif
int line_no = 1;
char line[IGL_LINE_MAX];
line_no = 1;
timer.start();
while (fgets(line, IGL_LINE_MAX, obj_file) != NULL)
{
char type[IGL_LINE_MAX];
// Read first word containing type
if(sscanf(line, "%s",type) == 1)
{
// Get pointer to rest of line right after type
char * l = &line[strlen(type)];
if(type == v)
{
std::istringstream ls(&line[1]);
std::array<Scalar,3> vertex;
std::copy(std::istream_iterator<Scalar >(ls), std::istream_iterator<Scalar >(), vertex.begin());
if (vertex.size() < 3)
{
fprintf(stderr,
"Error: readOBJ() vertex on line %d should have at least 3 coordinates",
line_no);
fclose(obj_file);
return false;
}
V.push_back(vertex);
}else if(type == vn)
{
// TODO: figure out why vertices and normals are read differently?
int count;
std::array<Scalar,3> normal;
if constexpr(std::is_same_v<Scalar,double>) {
count =
sscanf(l,"%lf %lf %lf\n",&normal[0],&normal[1],&normal[2]);
} else if constexpr(std::is_same_v<Scalar, float>) {
count =
sscanf(l,"%lf %lf %lf\n",&normal[0],&normal[1],&normal[2]);
} else {
std::array<double,3> x;
sscanf(l,"%lf %lf %lf\n",&x[0],&x[1],&x[2]);
std::copy(x.begin(),x.end(), normal.begin());
}
if(count != 3)
{
fprintf(stderr,
"Error: readOBJ() normal on line %d should have 3 coordinates",
line_no);
fclose(obj_file);
return false;
}
N.push_back(normal);
}else if(type == vt)
{
std::array<double,3> x;
int count =
sscanf(l,"%lf %lf %lf\n",&x[0],&x[1],&x[2]);
if(count != 2 && count != 3)
{
fprintf(stderr,
"Error: readOBJ() texture coords on line %d should have 2 "
"or 3 coordinates (%d)",
line_no,count);
fclose(obj_file);
return false;
}
std::vector<Scalar> tex(count);
std::copy(x.begin(),x.begin()+count,tex.begin());
TC.push_back(tex);
}else if(type == f)
{
const auto & shift = [&V](const int i)->int
{
return i<0 ? i+V.size() : i-1;
};
const auto & shift_t = [&TC](const int i)->int
{
return i<0 ? i+TC.size() : i-1;
};
const auto & shift_n = [&N](const int i)->int
{
return i<0 ? i+N.size() : i-1;
};
std::vector<Index > f;
std::vector<Index > ftc;
std::vector<Index > fn;
// Read each "word" after type
char word[IGL_LINE_MAX];
int offset;
while(sscanf(l,"%s%n",word,&offset) == 1)
{
// adjust offset
l += offset;
// Process word
long int i,it,in;
if(sscanf(word,"%ld/%ld/%ld",&i,&it,&in) == 3)
{
f.push_back(shift(i));
ftc.push_back(shift_t(it));
fn.push_back(shift_n(in));
}else if(sscanf(word,"%ld/%ld",&i,&it) == 2)
{
f.push_back(shift(i));
ftc.push_back(shift_t(it));
}else if(sscanf(word,"%ld//%ld",&i,&in) == 2)
{
f.push_back(shift(i));
fn.push_back(shift_n(in));
}else if(sscanf(word,"%ld",&i) == 1)
{
f.push_back(shift(i));
}else
{
fprintf(stderr,
"Error: readOBJ() face on line %d has invalid element format\n",
line_no);
fclose(obj_file);
return false;
}
}
if(
(f.size()>0 && fn.size() == 0 && ftc.size() == 0) ||
(f.size()>0 && fn.size() == f.size() && ftc.size() == 0) ||
(f.size()>0 && fn.size() == 0 && ftc.size() == f.size()) ||
(f.size()>0 && fn.size() == f.size() && ftc.size() == f.size()))
{
// No matter what add each type to lists so that lists are the
// correct lengths
F.push_back(f);
FTC.push_back(ftc);
FN.push_back(fn);
}else
{
fprintf(stderr,
"Error: readOBJ() face on line %d has invalid format\n", line_no);
fclose(obj_file);
return false;
}
}else if(strlen(type) >= 1 && (type[0] == '#' ||
type[0] == 'g' ||
type[0] == 's' ||
strcmp("usemtl",type)==0 ||
strcmp("mtllib",type)==0))
{
//ignore comments or other shit
}else
{
//ignore any other lines
fprintf(stderr,
"Warning: readOBJ() ignored non-comment line %d:\n %s",
line_no,
line);
}
}else
{
// ignore empty line
}
line_no++;
}
timer.stop();
printf("File parse time %f\n", timer.getElapsedTimeInMilliSec());
fclose(obj_file);
assert(F.size() == FN.size());
assert(F.size() == FTC.size());
return true;
}
template <typename Scalar, typename Index>
bool readOBJ_prescan_array(
FILE * obj_file,
std::vector<std::array<Scalar, 3> > & V,
std::vector<std::vector<Scalar> > & TC,
std::vector<std::array<Scalar, 3> > & N,
std::vector<std::vector<Index > > & F,
std::vector<std::vector<Index > > & FTC,
std::vector<std::vector<Index > > & FN)
{
Timer timer;
// File open was successful so clear outputs
V.clear();
TC.clear();
N.clear();
F.clear();
FTC.clear();
FN.clear();
// variables and constants to assist parsing the .obj file
// Constant strings to compare against
std::string v("v");
std::string vn("vn");
std::string vt("vt");
std::string f("f");
std::string tic_tac_toe("#");
#ifndef IGL_LINE_MAX
# define IGL_LINE_MAX 2048
#endif
int line_no = 1;
char line[IGL_LINE_MAX];
{ // quickly go through the entire file and size up the vectors
timer.start();
size_t vsize = 0, tcsize = 0, nsize = 0, fsize = 0, ftcsize=0, fnsize = 0;
std::fpos_t start_pos;
{
int errcode = std::fgetpos(obj_file, &start_pos);
if(0 != errcode) {
fprintf(stderr,
"Error: readOBJ() could not read a file position with errno %d", errcode);
return false;
}
}
while (fgets(line, IGL_LINE_MAX, obj_file) != NULL)
{
char type[IGL_LINE_MAX];
// Read first word containing type
if(sscanf(line, "%s",type) == 1)
{
// Get pointer to rest of line right after type
char * l = &line[strlen(type)];
if(type == v)
{
vsize++;
}else if(type == vn)
{
nsize++;
}else if(type == vt)
{
tcsize++;
}else if(type == f)
{
fsize++;
ftcsize++;
fnsize++;
}else if(strlen(type) >= 1 && (type[0] == '#' ||
type[0] == 'g' ||
type[0] == 's' ||
strcmp("usemtl",type)==0 ||
strcmp("mtllib",type)==0))
{
//ignore comments or other shit
}else
{
//ignore any other lines
fprintf(stderr,
"Warning: readOBJ() ignored non-comment line %d:\n %s",
line_no,
line);
}
}else
{
// ignore empty line
}
line_no++;
}
V.reserve(vsize);
TC.reserve(tcsize);
N.reserve(nsize);
F.reserve(fsize);
FTC.reserve(ftcsize);
FN.reserve(fnsize);
// reset the mesh position
int errcode = std::fsetpos(obj_file, &start_pos);
if(0 != errcode) {
fprintf(stderr,
"Error: readOBJ() could not reset a file position with errno %d", errcode);
return false;
}
timer.stop();
printf("Prescan time %f\n", timer.getElapsedTimeInMilliSec());
}
line_no = 1;
timer.start();
while (fgets(line, IGL_LINE_MAX, obj_file) != NULL)
{
char type[IGL_LINE_MAX];
// Read first word containing type
if(sscanf(line, "%s",type) == 1)
{
// Get pointer to rest of line right after type
char * l = &line[strlen(type)];
if(type == v)
{
std::istringstream ls(&line[1]);
std::array<Scalar,3> vertex;
std::copy(std::istream_iterator<Scalar >(ls), std::istream_iterator<Scalar >(), vertex.begin());
if (vertex.size() < 3)
{
fprintf(stderr,
"Error: readOBJ() vertex on line %d should have at least 3 coordinates",
line_no);
fclose(obj_file);
return false;
}
V.push_back(vertex);
}else if(type == vn)
{
// TODO: figure out why vertices and normals are read differently?
int count;
std::array<Scalar,3> normal;
if constexpr(std::is_same_v<Scalar,double>) {
count =
sscanf(l,"%lf %lf %lf\n",&normal[0],&normal[1],&normal[2]);
} else if constexpr(std::is_same_v<Scalar, float>) {
count =
sscanf(l,"%lf %lf %lf\n",&normal[0],&normal[1],&normal[2]);
} else {
std::array<double,3> x;
sscanf(l,"%lf %lf %lf\n",&x[0],&x[1],&x[2]);
std::copy(x.begin(),x.end(), normal.begin());
}
if(count != 3)
{
fprintf(stderr,
"Error: readOBJ() normal on line %d should have 3 coordinates",
line_no);
fclose(obj_file);
return false;
}
N.push_back(normal);
}else if(type == vt)
{
std::array<double,3> x;
int count =
sscanf(l,"%lf %lf %lf\n",&x[0],&x[1],&x[2]);
if(count != 2 && count != 3)
{
fprintf(stderr,
"Error: readOBJ() texture coords on line %d should have 2 "
"or 3 coordinates (%d)",
line_no,count);
fclose(obj_file);
return false;
}
std::vector<Scalar> tex(count);
std::copy(x.begin(),x.begin()+count,tex.begin());
TC.push_back(tex);
}else if(type == f)
{
const auto & shift = [&V](const int i)->int
{
return i<0 ? i+V.size() : i-1;
};
const auto & shift_t = [&TC](const int i)->int
{
return i<0 ? i+TC.size() : i-1;
};
const auto & shift_n = [&N](const int i)->int
{
return i<0 ? i+N.size() : i-1;
};
std::vector<Index > f;
std::vector<Index > ftc;
std::vector<Index > fn;
// Read each "word" after type
char word[IGL_LINE_MAX];
int offset;
while(sscanf(l,"%s%n",word,&offset) == 1)
{
// adjust offset
l += offset;
// Process word
long int i,it,in;
if(sscanf(word,"%ld/%ld/%ld",&i,&it,&in) == 3)
{
f.push_back(shift(i));
ftc.push_back(shift_t(it));
fn.push_back(shift_n(in));
}else if(sscanf(word,"%ld/%ld",&i,&it) == 2)
{
f.push_back(shift(i));
ftc.push_back(shift_t(it));
}else if(sscanf(word,"%ld//%ld",&i,&in) == 2)
{
f.push_back(shift(i));
fn.push_back(shift_n(in));
}else if(sscanf(word,"%ld",&i) == 1)
{
f.push_back(shift(i));
}else
{
fprintf(stderr,
"Error: readOBJ() face on line %d has invalid element format\n",
line_no);
fclose(obj_file);
return false;
}
}
if(
(f.size()>0 && fn.size() == 0 && ftc.size() == 0) ||
(f.size()>0 && fn.size() == f.size() && ftc.size() == 0) ||
(f.size()>0 && fn.size() == 0 && ftc.size() == f.size()) ||
(f.size()>0 && fn.size() == f.size() && ftc.size() == f.size()))
{
// No matter what add each type to lists so that lists are the
// correct lengths
F.push_back(f);
FTC.push_back(ftc);
FN.push_back(fn);
}else
{
fprintf(stderr,
"Error: readOBJ() face on line %d has invalid format\n", line_no);
fclose(obj_file);
return false;
}
}else if(strlen(type) >= 1 && (type[0] == '#' ||
type[0] == 'g' ||
type[0] == 's' ||
strcmp("usemtl",type)==0 ||
strcmp("mtllib",type)==0))
{
//ignore comments or other shit
}else
{
//ignore any other lines
fprintf(stderr,
"Warning: readOBJ() ignored non-comment line %d:\n %s",
line_no,
line);
}
}else
{
// ignore empty line
}
line_no++;
}
timer.stop();
printf("File parse time %f\n", timer.getElapsedTimeInMilliSec());
fclose(obj_file);
assert(F.size() == FN.size());
assert(F.size() == FTC.size());
return true;
}
template <typename Scalar, typename Index>
bool readOBJ_prescan(
FILE * obj_file,
std::vector<std::vector<Scalar > > & V,
std::vector<std::vector<Scalar > > & TC,
std::vector<std::vector<Scalar > > & N,
std::vector<std::vector<Index > > & F,
std::vector<std::vector<Index > > & FTC,
std::vector<std::vector<Index > > & FN)
{
Timer timer;
// File open was successful so clear outputs
V.clear();
TC.clear();
N.clear();
F.clear();
FTC.clear();
FN.clear();
// variables and constants to assist parsing the .obj file
// Constant strings to compare against
std::string v("v");
std::string vn("vn");
std::string vt("vt");
std::string f("f");
std::string tic_tac_toe("#");
#ifndef IGL_LINE_MAX
# define IGL_LINE_MAX 2048
#endif
int line_no = 1;
char line[IGL_LINE_MAX];
{ // quickly go through the entire file and size up the vectors
timer.start();
size_t vsize = 0, tcsize = 0, nsize = 0, fsize = 0, ftcsize=0, fnsize = 0;
std::fpos_t start_pos;
{
int errcode = std::fgetpos(obj_file, &start_pos);
if(0 != errcode) {
fprintf(stderr,
"Error: readOBJ() could not read a file position with errno %d", errcode);
return false;
}
}
while (fgets(line, IGL_LINE_MAX, obj_file) != NULL)
{
char type[IGL_LINE_MAX];
// Read first word containing type
if(sscanf(line, "%s",type) == 1)
{
// Get pointer to rest of line right after type
char * l = &line[strlen(type)];
if(type == v)
{
vsize++;
}else if(type == vn)
{
nsize++;
}else if(type == vt)
{
tcsize++;
}else if(type == f)
{
fsize++;
ftcsize++;
fnsize++;
}else if(strlen(type) >= 1 && (type[0] == '#' ||
type[0] == 'g' ||
type[0] == 's' ||
strcmp("usemtl",type)==0 ||
strcmp("mtllib",type)==0))
{
//ignore comments or other shit
}else
{
//ignore any other lines
fprintf(stderr,
"Warning: readOBJ() ignored non-comment line %d:\n %s",
line_no,
line);
}
}else
{
// ignore empty line
}
line_no++;
}
V.reserve(vsize);
TC.reserve(tcsize);
N.reserve(nsize);
F.reserve(fsize);
FTC.reserve(ftcsize);
FN.reserve(fnsize);
// reset the mesh position
int errcode = std::fsetpos(obj_file, &start_pos);
if(0 != errcode) {
fprintf(stderr,
"Error: readOBJ() could not reset a file position with errno %d", errcode);
return false;
}
timer.stop();
printf("Prescan time %f\n", timer.getElapsedTimeInMilliSec());
}
line_no = 1;
timer.start();
while (fgets(line, IGL_LINE_MAX, obj_file) != NULL)
{
char type[IGL_LINE_MAX];
// Read first word containing type
if(sscanf(line, "%s",type) == 1)
{
// Get pointer to rest of line right after type
char * l = &line[strlen(type)];
if(type == v)
{
std::istringstream ls(&line[1]);
std::vector<Scalar > vertex{std::istream_iterator<Scalar >(ls), std::istream_iterator<Scalar >()};
if (vertex.size() < 3)
{
fprintf(stderr,
"Error: readOBJ() vertex on line %d should have at least 3 coordinates",
line_no);
fclose(obj_file);
return false;
}
V.push_back(vertex);
}else if(type == vn)
{
double x[3];
int count =
sscanf(l,"%lf %lf %lf\n",&x[0],&x[1],&x[2]);
if(count != 3)
{
fprintf(stderr,
"Error: readOBJ() normal on line %d should have 3 coordinates",
line_no);
fclose(obj_file);
return false;
}
std::vector<Scalar > normal(count);
for(int i = 0;i<count;i++)
{
normal[i] = x[i];
}
N.push_back(normal);
}else if(type == vt)
{
double x[3];
int count =
sscanf(l,"%lf %lf %lf\n",&x[0],&x[1],&x[2]);
if(count != 2 && count != 3)
{
fprintf(stderr,
"Error: readOBJ() texture coords on line %d should have 2 "
"or 3 coordinates (%d)",
line_no,count);
fclose(obj_file);
return false;
}
std::vector<Scalar > tex(count);
for(int i = 0;i<count;i++)
{
tex[i] = x[i];
}
TC.push_back(tex);
}else if(type == f)
{
const auto & shift = [&V](const int i)->int
{
return i<0 ? i+V.size() : i-1;
};
const auto & shift_t = [&TC](const int i)->int
{
return i<0 ? i+TC.size() : i-1;
};
const auto & shift_n = [&N](const int i)->int
{
return i<0 ? i+N.size() : i-1;
};
std::vector<Index > f;
std::vector<Index > ftc;
std::vector<Index > fn;
// Read each "word" after type
char word[IGL_LINE_MAX];
int offset;
while(sscanf(l,"%s%n",word,&offset) == 1)
{
// adjust offset
l += offset;
// Process word
long int i,it,in;
if(sscanf(word,"%ld/%ld/%ld",&i,&it,&in) == 3)
{
f.push_back(shift(i));
ftc.push_back(shift_t(it));
fn.push_back(shift_n(in));
}else if(sscanf(word,"%ld/%ld",&i,&it) == 2)
{
f.push_back(shift(i));
ftc.push_back(shift_t(it));
}else if(sscanf(word,"%ld//%ld",&i,&in) == 2)
{
f.push_back(shift(i));
fn.push_back(shift_n(in));
}else if(sscanf(word,"%ld",&i) == 1)
{
f.push_back(shift(i));
}else
{
fprintf(stderr,
"Error: readOBJ() face on line %d has invalid element format\n",
line_no);
fclose(obj_file);
return false;
}
}
if(
(f.size()>0 && fn.size() == 0 && ftc.size() == 0) ||
(f.size()>0 && fn.size() == f.size() && ftc.size() == 0) ||
(f.size()>0 && fn.size() == 0 && ftc.size() == f.size()) ||
(f.size()>0 && fn.size() == f.size() && ftc.size() == f.size()))
{
// No matter what add each type to lists so that lists are the
// correct lengths
F.push_back(f);
FTC.push_back(ftc);
FN.push_back(fn);
}else
{
fprintf(stderr,
"Error: readOBJ() face on line %d has invalid format\n", line_no);
fclose(obj_file);
return false;
}
}else if(strlen(type) >= 1 && (type[0] == '#' ||
type[0] == 'g' ||
type[0] == 's' ||
strcmp("usemtl",type)==0 ||
strcmp("mtllib",type)==0))
{
//ignore comments or other shit
}else
{
//ignore any other lines
fprintf(stderr,
"Warning: readOBJ() ignored non-comment line %d:\n %s",
line_no,
line);
}
}else
{
// ignore empty line
}
line_no++;
}
timer.stop();
printf("File parse time %f\n", timer.getElapsedTimeInMilliSec());
fclose(obj_file);
assert(F.size() == FN.size());
assert(F.size() == FTC.size());
return true;
}
template <typename Scalar, typename Index>
bool readOBJ(
FILE * obj_file,
std::vector<std::vector<Scalar > > & V,
std::vector<std::vector<Scalar > > & TC,
std::vector<std::vector<Scalar > > & N,
std::vector<std::vector<Index > > & F,
std::vector<std::vector<Index > > & FTC,
std::vector<std::vector<Index > > & FN)
{
Timer timer;
// File open was successful so clear outputs
V.clear();
TC.clear();
N.clear();
F.clear();
FTC.clear();
FN.clear();
// variables and constants to assist parsing the .obj file
// Constant strings to compare against
std::string v("v");
std::string vn("vn");
std::string vt("vt");
std::string f("f");
std::string tic_tac_toe("#");
#ifndef IGL_LINE_MAX
# define IGL_LINE_MAX 2048
#endif
int line_no = 1;
char line[IGL_LINE_MAX];
#ifdef PRESCAN_FILE
{ // quickly go through the entire file and size up the vectors
timer.start();
size_t vsize = 0, tcsize = 0, nsize = 0, fsize = 0, ftcsize=0, fnsize = 0;
std::fpos_t start_pos;
{
int errcode = std::fgetpos(obj_file, &start_pos);
if(0 != errcode) {
fprintf(stderr,
"Error: readOBJ() could not read a file position with errno %d", errcode);
return false;
}
}
while (fgets(line, IGL_LINE_MAX, obj_file) != NULL)
{
char type[IGL_LINE_MAX];
// Read first word containing type
if(sscanf(line, "%s",type) == 1)
{
// Get pointer to rest of line right after type
char * l = &line[strlen(type)];
if(type == v)
{
vsize++;
}else if(type == vn)
{
nsize++;
}else if(type == vt)
{
tcsize++;
}else if(type == f)
{
fsize++;
ftcsize++;
fnsize++;
}else if(strlen(type) >= 1 && (type[0] == '#' ||
type[0] == 'g' ||
type[0] == 's' ||
strcmp("usemtl",type)==0 ||
strcmp("mtllib",type)==0))
{
//ignore comments or other shit
}else
{
//ignore any other lines
fprintf(stderr,
"Warning: readOBJ() ignored non-comment line %d:\n %s",
line_no,
line);
}
}else
{
// ignore empty line
}
line_no++;
}
V.reserve(vsize);
TC.reserve(tcsize);
N.reserve(nsize);
F.reserve(fsize);
FTC.reserve(ftcsize);
FN.reserve(fnsize);
// reset the mesh position
int errcode = std::fsetpos(obj_file, &start_pos);
if(0 != errcode) {
fprintf(stderr,
"Error: readOBJ() could not reset a file position with errno %d", errcode);
return false;
}
timer.stop();
printf("Prescan time %f\n", timer.getElapsedTimeInMilliSec());
}
#endif //PRESCAN_FILE
line_no = 1;
timer.start();
while (fgets(line, IGL_LINE_MAX, obj_file) != NULL)
{
char type[IGL_LINE_MAX];
// Read first word containing type
if(sscanf(line, "%s",type) == 1)
{
// Get pointer to rest of line right after type
char * l = &line[strlen(type)];
if(type == v)
{
std::istringstream ls(&line[1]);
std::vector<Scalar > vertex{std::istream_iterator<Scalar >(ls), std::istream_iterator<Scalar >()};
if (vertex.size() < 3)
{
fprintf(stderr,
"Error: readOBJ() vertex on line %d should have at least 3 coordinates",
line_no);
fclose(obj_file);
return false;
}
V.push_back(vertex);
}else if(type == vn)
{
double x[3];
int count =
sscanf(l,"%lf %lf %lf\n",&x[0],&x[1],&x[2]);
if(count != 3)
{
fprintf(stderr,
"Error: readOBJ() normal on line %d should have 3 coordinates",
line_no);
fclose(obj_file);
return false;
}
std::vector<Scalar > normal(count);
for(int i = 0;i<count;i++)
{
normal[i] = x[i];
}
N.push_back(normal);
}else if(type == vt)
{
double x[3];
int count =
sscanf(l,"%lf %lf %lf\n",&x[0],&x[1],&x[2]);
if(count != 2 && count != 3)
{
fprintf(stderr,
"Error: readOBJ() texture coords on line %d should have 2 "
"or 3 coordinates (%d)",
line_no,count);
fclose(obj_file);
return false;
}
std::vector<Scalar > tex(count);
for(int i = 0;i<count;i++)
{
tex[i] = x[i];
}
TC.push_back(tex);
}else if(type == f)
{
const auto & shift = [&V](const int i)->int
{
return i<0 ? i+V.size() : i-1;
};
const auto & shift_t = [&TC](const int i)->int
{
return i<0 ? i+TC.size() : i-1;
};
const auto & shift_n = [&N](const int i)->int
{
return i<0 ? i+N.size() : i-1;
};
std::vector<Index > f;
std::vector<Index > ftc;
std::vector<Index > fn;
// Read each "word" after type
char word[IGL_LINE_MAX];
int offset;
while(sscanf(l,"%s%n",word,&offset) == 1)
{
// adjust offset
l += offset;
// Process word
long int i,it,in;
if(sscanf(word,"%ld/%ld/%ld",&i,&it,&in) == 3)
{
f.push_back(shift(i));
ftc.push_back(shift_t(it));
fn.push_back(shift_n(in));
}else if(sscanf(word,"%ld/%ld",&i,&it) == 2)
{
f.push_back(shift(i));
ftc.push_back(shift_t(it));
}else if(sscanf(word,"%ld//%ld",&i,&in) == 2)
{
f.push_back(shift(i));
fn.push_back(shift_n(in));
}else if(sscanf(word,"%ld",&i) == 1)
{
f.push_back(shift(i));
}else
{
fprintf(stderr,
"Error: readOBJ() face on line %d has invalid element format\n",
line_no);
fclose(obj_file);
return false;
}
}
if(
(f.size()>0 && fn.size() == 0 && ftc.size() == 0) ||
(f.size()>0 && fn.size() == f.size() && ftc.size() == 0) ||
(f.size()>0 && fn.size() == 0 && ftc.size() == f.size()) ||
(f.size()>0 && fn.size() == f.size() && ftc.size() == f.size()))
{
// No matter what add each type to lists so that lists are the
// correct lengths
F.push_back(f);
FTC.push_back(ftc);
FN.push_back(fn);
}else
{
fprintf(stderr,
"Error: readOBJ() face on line %d has invalid format\n", line_no);
fclose(obj_file);
return false;
}
}else if(strlen(type) >= 1 && (type[0] == '#' ||
type[0] == 'g' ||
type[0] == 's' ||
strcmp("usemtl",type)==0 ||
strcmp("mtllib",type)==0))
{
//ignore comments or other shit
}else
{
//ignore any other lines
fprintf(stderr,
"Warning: readOBJ() ignored non-comment line %d:\n %s",
line_no,
line);
}
}else
{
// ignore empty line
}
line_no++;
}
timer.stop();
printf("File parse time %f\n", timer.getElapsedTimeInMilliSec());
fclose(obj_file);
assert(F.size() == FN.size());
assert(F.size() == FTC.size());
return true;
}
template <typename Scalar, typename Index>
bool readOBJ_prescan(
const std::string obj_file_name,
std::vector<std::vector<Scalar > > & V,
std::vector<std::vector<Scalar > > & TC,
std::vector<std::vector<Scalar > > & N,
std::vector<std::vector<Index > > & F,
std::vector<std::vector<Index > > & FTC,
std::vector<std::vector<Index > > & FN)
{
// Open file, and check for error
FILE * obj_file = fopen(obj_file_name.c_str(),"r");
if(NULL==obj_file)
{
fprintf(stderr,"IOError: %s could not be opened...\n",
obj_file_name.c_str());
return false;
}
return readOBJ_prescan(obj_file,V,TC,N,F,FTC,FN);
}
template <typename Scalar, typename Index>
bool readOBJ(
const std::string obj_file_name,
std::vector<std::vector<Scalar > > & V,
std::vector<std::vector<Scalar > > & TC,
std::vector<std::vector<Scalar > > & N,
std::vector<std::vector<Index > > & F,
std::vector<std::vector<Index > > & FTC,
std::vector<std::vector<Index > > & FN)
{
// Open file, and check for error
FILE * obj_file = fopen(obj_file_name.c_str(),"r");
if(NULL==obj_file)
{
fprintf(stderr,"IOError: %s could not be opened...\n",
obj_file_name.c_str());
return false;
}
return readOBJ(obj_file,V,TC,N,F,FTC,FN);
}
template <typename Scalar, typename Index>
bool readOBJ_prescan_array(
const std::string obj_file_name,
std::vector<std::array<Scalar,3 > > & V,
std::vector<std::vector<Scalar > > & TC,
std::vector<std::array<Scalar,3 > > & N,
std::vector<std::vector<Index > > & F,
std::vector<std::vector<Index > > & FTC,
std::vector<std::vector<Index > > & FN)
{
// Open file, and check for error
FILE * obj_file = fopen(obj_file_name.c_str(),"r");
if(NULL==obj_file)
{
fprintf(stderr,"IOError: %s could not be opened...\n",
obj_file_name.c_str());
return false;
}
return readOBJ_prescan_array(obj_file,V,TC,N,F,FTC,FN);
}
template <typename Scalar, typename Index>
bool readOBJ_array(
const std::string obj_file_name,
std::vector<std::array<Scalar,3 > > & V,
std::vector<std::vector<Scalar > > & TC,
std::vector<std::array<Scalar,3 > > & N,
std::vector<std::vector<Index > > & F,
std::vector<std::vector<Index > > & FTC,
std::vector<std::vector<Index > > & FN)
{
// Open file, and check for error
FILE * obj_file = fopen(obj_file_name.c_str(),"r");
if(NULL==obj_file)
{
fprintf(stderr,"IOError: %s could not be opened...\n",
obj_file_name.c_str());
return false;
}
return readOBJ_array(obj_file,V,TC,N,F,FTC,FN);
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment