Last active
May 30, 2020 01:41
-
-
Save mtao/b04256f2c2e7f596db1566186811178e to your computer and use it in GitHub Desktop.
some relatively naive performance attempts on igl's mesh loader
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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