aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSyoyo Fujita <syoyo@lighttransport.com>2012-08-19 14:36:48 +0900
committerSyoyo Fujita <syoyo@lighttransport.com>2012-08-19 14:36:48 +0900
commitcf78dd845bd1d63675f271c0411036a6373ca624 (patch)
tree9cc053c4f4a0dd6a73748f8874997dc4c664fa78
downloadtinyobjloader-cf78dd845bd1d63675f271c0411036a6373ca624.tar.gz
Initial commit.
-rw-r--r--README.md4
-rw-r--r--cornell_box.mtl24
-rw-r--r--cornell_box.obj145
-rw-r--r--premake4.lua33
-rw-r--r--test.cc45
-rw-r--r--tiny_obj_loader.cc381
-rw-r--r--tiny_obj_loader.h53
7 files changed, 685 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..431527c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
+tinyobjloader
+=============
+
+Tiny but poweful single file wavefront obj loader written in C++. no dependency except for C++ STL.
diff --git a/cornell_box.mtl b/cornell_box.mtl
new file mode 100644
index 0000000..d3a1c7a
--- /dev/null
+++ b/cornell_box.mtl
@@ -0,0 +1,24 @@
+newmtl white
+Ka 0 0 0
+Kd 1 1 1
+Ks 0 0 0
+
+newmtl red
+Ka 0 0 0
+Kd 1 0 0
+Ks 0 0 0
+
+newmtl green
+Ka 0 0 0
+Kd 0 1 0
+Ks 0 0 0
+
+newmtl blue
+Ka 0 0 0
+Kd 0 0 1
+Ks 0 0 0
+
+newmtl light
+Ka 20 20 20
+Kd 1 1 1
+Ks 0 0 0
diff --git a/cornell_box.obj b/cornell_box.obj
new file mode 100644
index 0000000..a97672d
--- /dev/null
+++ b/cornell_box.obj
@@ -0,0 +1,145 @@
+# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project.
+# original cornell box data
+ # comment
+
+# empty line including some space
+
+
+mtllib cornell_box.mtl
+
+o floor
+usemtl white
+v 552.8 0.0 0.0
+v 0.0 0.0 0.0
+v 0.0 0.0 559.2
+v 549.6 0.0 559.2
+
+v 130.0 0.0 65.0
+v 82.0 0.0 225.0
+v 240.0 0.0 272.0
+v 290.0 0.0 114.0
+
+v 423.0 0.0 247.0
+v 265.0 0.0 296.0
+v 314.0 0.0 456.0
+v 472.0 0.0 406.0
+
+f 1 2 3 4
+f 8 7 6 5
+f 12 11 10 9
+
+o light
+usemtl light
+v 343.0 548.0 227.0
+v 343.0 548.0 332.0
+v 213.0 548.0 332.0
+v 213.0 548.0 227.0
+#f -4 -3 -2 -1
+
+o ceiling
+usemtl white
+v 556.0 548.8 0.0
+v 556.0 548.8 559.2
+v 0.0 548.8 559.2
+v 0.0 548.8 0.0
+f -4 -3 -2 -1
+
+o back_wall
+usemtl white
+v 549.6 0.0 559.2
+v 0.0 0.0 559.2
+v 0.0 548.8 559.2
+v 556.0 548.8 559.2
+f -4 -3 -2 -1
+
+o front_wall
+usemtl blue
+v 549.6 0.0 0
+v 0.0 0.0 0
+v 0.0 548.8 0
+v 556.0 548.8 0
+#f -1 -2 -3 -4
+
+o green_wall
+usemtl green
+v 0.0 0.0 559.2
+v 0.0 0.0 0.0
+v 0.0 548.8 0.0
+v 0.0 548.8 559.2
+f -4 -3 -2 -1
+
+o red_wall
+usemtl red
+v 552.8 0.0 0.0
+v 549.6 0.0 559.2
+v 556.0 548.8 559.2
+v 556.0 548.8 0.0
+f -4 -3 -2 -1
+
+o short_block
+usemtl white
+
+v 130.0 165.0 65.0
+v 82.0 165.0 225.0
+v 240.0 165.0 272.0
+v 290.0 165.0 114.0
+f -4 -3 -2 -1
+
+v 290.0 0.0 114.0
+v 290.0 165.0 114.0
+v 240.0 165.0 272.0
+v 240.0 0.0 272.0
+f -4 -3 -2 -1
+
+v 130.0 0.0 65.0
+v 130.0 165.0 65.0
+v 290.0 165.0 114.0
+v 290.0 0.0 114.0
+f -4 -3 -2 -1
+
+v 82.0 0.0 225.0
+v 82.0 165.0 225.0
+v 130.0 165.0 65.0
+v 130.0 0.0 65.0
+f -4 -3 -2 -1
+
+v 240.0 0.0 272.0
+v 240.0 165.0 272.0
+v 82.0 165.0 225.0
+v 82.0 0.0 225.0
+f -4 -3 -2 -1
+
+o tall_block
+usemtl white
+
+v 423.0 330.0 247.0
+v 265.0 330.0 296.0
+v 314.0 330.0 456.0
+v 472.0 330.0 406.0
+f -4 -3 -2 -1
+
+usemtl white
+v 423.0 0.0 247.0
+v 423.0 330.0 247.0
+v 472.0 330.0 406.0
+v 472.0 0.0 406.0
+f -4 -3 -2 -1
+
+v 472.0 0.0 406.0
+v 472.0 330.0 406.0
+v 314.0 330.0 456.0
+v 314.0 0.0 456.0
+f -4 -3 -2 -1
+
+v 314.0 0.0 456.0
+v 314.0 330.0 456.0
+v 265.0 330.0 296.0
+v 265.0 0.0 296.0
+f -4 -3 -2 -1
+
+v 265.0 0.0 296.0
+v 265.0 330.0 296.0
+v 423.0 330.0 247.0
+v 423.0 0.0 247.0
+f -4 -3 -2 -1
+
diff --git a/premake4.lua b/premake4.lua
new file mode 100644
index 0000000..ad020a6
--- /dev/null
+++ b/premake4.lua
@@ -0,0 +1,33 @@
+lib_sources = {
+ "tiny_obj_loader.cc"
+}
+
+sources = {
+ "test.cc",
+ }
+
+-- premake4.lua
+solution "TinyObjLoaderSolution"
+ configurations { "Release", "Debug" }
+
+ if (os.is("windows")) then
+ platforms { "x32", "x64" }
+ else
+ platforms { "native", "x32", "x64" }
+ end
+
+ -- A project defines one build target
+ project "tinyobjloader"
+ kind "ConsoleApp"
+ language "C++"
+ files { lib_sources, sources }
+
+ configuration "Debug"
+ defines { "DEBUG" } -- -DDEBUG
+ flags { "Symbols" }
+ targetname "test_tinyobjloader_debug"
+
+ configuration "Release"
+ -- defines { "NDEBUG" } -- -NDEBUG
+ flags { "Symbols", "Optimize" }
+ targetname "test_tinyobjloader"
diff --git a/test.cc b/test.cc
new file mode 100644
index 0000000..1a15f5e
--- /dev/null
+++ b/test.cc
@@ -0,0 +1,45 @@
+#include "tiny_obj_loader.h"
+
+#include <cassert>
+#include <iostream>
+
+int
+main(
+ int argc,
+ char **argv)
+{
+ std::string inputfile = "cornell_box.obj";
+ std::vector<tinyobj::shape_t> shapes;
+
+ if (argc > 1) {
+ inputfile = std::string(argv[1]);
+ }
+
+ std::string err = tinyobj::LoadObj(shapes, inputfile.c_str());
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+
+ std::cout << "# of shapes : " << shapes.size() << std::endl;
+
+ for (size_t i = 0; i < shapes.size(); i++) {
+ printf("shape[%ld].name = %s\n", i, shapes[i].name.c_str());
+ printf("shape[%ld].indices: %ld\n", i, shapes[i].mesh.indices.size());
+ assert((shapes[i].mesh.indices.size() % 3) == 0);
+ for (size_t f = 0; f < shapes[i].mesh.indices.size(); f++) {
+ printf(" idx[%ld] = %d\n", f, shapes[i].mesh.indices[f]);
+ }
+
+ printf("shape[%ld].vertices: %ld\n", i, shapes[i].mesh.positions.size());
+ assert((shapes[i].mesh.positions.size() % 3) == 0);
+ for (size_t v = 0; v < shapes[i].mesh.positions.size() / 3; v++) {
+ printf(" v[%ld] = (%f, %f, %f)\n", v,
+ shapes[i].mesh.positions[3*v+0],
+ shapes[i].mesh.positions[3*v+1],
+ shapes[i].mesh.positions[3*v+2]);
+ }
+ }
+
+ return 0;
+}
diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc
new file mode 100644
index 0000000..f1cf661
--- /dev/null
+++ b/tiny_obj_loader.cc
@@ -0,0 +1,381 @@
+//
+// Copyright 2012, Syoyo Fujita.
+//
+// Licensed under 2-clause BSD liecense.
+//
+
+//
+// version 0.9.0: Initial
+//
+
+//
+// @todo { Read .mtl }
+//
+
+#include <cassert>
+
+#include <string>
+#include <vector>
+#include <map>
+#include <fstream>
+#include <sstream>
+
+#include "tiny_obj_loader.h"
+
+namespace tinyobj {
+
+struct vertex_index {
+ int v_idx, vt_idx, vn_idx;
+ vertex_index() {};
+ vertex_index(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {};
+ vertex_index(int vidx, int vtidx, int vnidx) : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {};
+
+};
+// for std::map
+static inline bool operator<(const vertex_index& a, const vertex_index& b)
+{
+ if (a.v_idx != b.v_idx) return (a.v_idx < b.v_idx);
+ if (a.vn_idx != b.vn_idx) return (a.vn_idx < b.vn_idx);
+ if (a.vt_idx != b.vt_idx) return (a.vt_idx < b.vt_idx);
+
+ return false;
+}
+
+struct obj_shape {
+ std::vector<float> v;
+ std::vector<float> vn;
+ std::vector<float> vt;
+};
+
+static inline bool isSpace(const char c) {
+ return (c == ' ') || (c == '\t');
+}
+
+// Make index zero-base, and also support relative index.
+static inline int fixIndex(int idx, int n)
+{
+ int i;
+
+ if (idx > 0) {
+ i = idx - 1;
+ } else if (idx == 0) {
+ i = 0;
+ } else { // negative value = relative
+ i = n + idx;
+ }
+ return i;
+}
+
+static inline float parseFloat(const char*& token)
+{
+ token += strspn(token, " \t");
+ float f = (float)atof(token);
+ token += strcspn(token, " \t\r");
+ return f;
+}
+
+static inline void parseFloat2(
+ float& x, float& y,
+ const char*& token)
+{
+ x = parseFloat(token);
+ y = parseFloat(token);
+}
+
+static inline void parseFloat3(
+ float& x, float& y, float& z,
+ const char*& token)
+{
+ x = parseFloat(token);
+ y = parseFloat(token);
+ z = parseFloat(token);
+}
+
+
+// Parse triples: i, i/j/k, i//k, i/j
+static vertex_index parseTriple(
+ const char* &token,
+ int vsize,
+ int vnsize,
+ int vtsize)
+{
+ vertex_index vi(-1);
+
+ vi.v_idx = fixIndex(atoi(token), vsize);
+ token += strcspn(token, "/ \t\r");
+ if (token[0] != '/') {
+ return vi;
+ }
+ token++;
+
+ // i//k
+ if (token[0] == '/') {
+ token++;
+ vi.vn_idx = fixIndex(atoi(token), vnsize);
+ token += strcspn(token, "/ \t\r");
+ return vi;
+ }
+
+ // i/j/k or i/j
+ vi.vt_idx = fixIndex(atoi(token), vtsize);
+ token += strcspn(token, "/ \t\r");
+ if (token[0] != '/') {
+ return vi;
+ }
+
+ // i/j/k
+ vi.vn_idx = fixIndex(atoi(token), vnsize);
+ token += strcspn(token, "/ \t\r");
+ return vi;
+}
+
+static unsigned int
+updateVertex(
+ std::map<vertex_index, unsigned int>& vertexCache,
+ std::vector<float>& positions,
+ std::vector<float>& normals,
+ std::vector<float>& texcoords,
+ const std::vector<float>& in_positions,
+ const std::vector<float>& in_normals,
+ const std::vector<float>& in_texcoords,
+ const vertex_index& i)
+{
+ const std::map<vertex_index, unsigned int>::iterator it = vertexCache.find(i);
+
+ if (it != vertexCache.end()) {
+ // found cache
+ return it->second;
+ }
+
+ assert(in_positions.size() > (3*i.v_idx+2));
+
+ positions.push_back(in_positions[3*i.v_idx+0]);
+ positions.push_back(in_positions[3*i.v_idx+1]);
+ positions.push_back(in_positions[3*i.v_idx+2]);
+
+ if (i.vn_idx >= 0) {
+ normals.push_back(in_normals[3*i.vn_idx+0]);
+ normals.push_back(in_normals[3*i.vn_idx+1]);
+ normals.push_back(in_normals[3*i.vn_idx+2]);
+ }
+
+ if (i.vt_idx >= 0) {
+ texcoords.push_back(in_texcoords[3*i.vt_idx+0]);
+ texcoords.push_back(in_texcoords[3*i.vt_idx+1]);
+ texcoords.push_back(in_texcoords[3*i.vt_idx+2]);
+ }
+
+ unsigned int idx = positions.size() / 3 - 1;
+ vertexCache[i] = idx;
+
+ return idx;
+}
+
+static bool
+exportFaceGroupToShape(
+ shape_t& shape,
+ const std::vector<float> in_positions,
+ const std::vector<float> in_normals,
+ const std::vector<float> in_texcoords,
+ const std::vector<std::vector<vertex_index> >& faceGroup,
+ const std::string name)
+{
+ if (faceGroup.empty()) {
+ return false;
+ }
+
+ // Flattened version of vertex data
+ std::vector<float> positions;
+ std::vector<float> normals;
+ std::vector<float> texcoords;
+ std::map<vertex_index, unsigned int> vertexCache;
+ std::vector<unsigned int> indices;
+
+ // Flatten vertices and indices
+ for (size_t i = 0; i < faceGroup.size(); i++) {
+ const std::vector<vertex_index>& face = faceGroup[i];
+
+ vertex_index i0 = face[0];
+ vertex_index i1(-1);
+ vertex_index i2 = face[1];
+
+ size_t npolys = face.size();
+
+ // Polygon -> triangle fan conversion
+ for (size_t k = 2; k < npolys; k++) {
+ i1 = i2;
+ i2 = face[k];
+
+ unsigned int v0 = updateVertex(vertexCache, positions, normals, texcoords, in_positions, in_normals, in_texcoords, i0);
+ unsigned int v1 = updateVertex(vertexCache, positions, normals, texcoords, in_positions, in_normals, in_texcoords, i1);
+ unsigned int v2 = updateVertex(vertexCache, positions, normals, texcoords, in_positions, in_normals, in_texcoords, i2);
+
+ indices.push_back(v0);
+ indices.push_back(v1);
+ indices.push_back(v2);
+ }
+
+ }
+
+ //
+ // Construct shape.
+ //
+ shape.name = name;
+ shape.mesh.positions.swap(positions);
+ shape.mesh.normals.swap(normals);
+ shape.mesh.texcoords.swap(texcoords);
+ shape.mesh.indices.swap(indices);
+
+ // @todo { material, name }
+
+ return true;
+
+}
+
+std::string
+LoadObj(
+ std::vector<shape_t>& shapes,
+ const char* filename)
+{
+
+ shapes.clear();
+
+ std::stringstream err;
+
+ std::ifstream ifs(filename);
+ if (!ifs) {
+ err << "Cannot open file [" << filename << "]" << std::endl;
+ return err.str();
+ }
+
+ std::vector<float> v;
+ std::vector<float> vn;
+ std::vector<float> vt;
+ std::vector<std::vector<vertex_index> > faceGroup;
+ std::string name;
+
+ int maxchars = 8192; // Alloc enough size.
+ std::vector<char> buf(maxchars); // Alloc enough size.
+ while (ifs.peek() != -1) {
+ ifs.getline(&buf[0], maxchars);
+
+ std::string linebuf(&buf[0]);
+
+ // Trim newline '\r\n' or '\r'
+ if (linebuf.size() > 0) {
+ if (linebuf[linebuf.size()-1] == '\n') linebuf.erase(linebuf.size()-1);
+ }
+ if (linebuf.size() > 0) {
+ if (linebuf[linebuf.size()-1] == '\n') linebuf.erase(linebuf.size()-1);
+ }
+
+ // Skip if empty line.
+ if (linebuf.empty()) {
+ continue;
+ }
+
+ // Skip leading space.
+ const char* token = linebuf.c_str();
+ token += strspn(token, " \t");
+
+ assert(token);
+ if (token[0] == '\0') continue; // empty line
+
+ if (token[0] == '#') continue; // comment line
+
+ // vertex
+ if (token[0] == 'v' && isSpace((token[1]))) {
+ token += 2;
+ float x, y, z;
+ parseFloat3(x, y, z, token);
+ v.push_back(x);
+ v.push_back(y);
+ v.push_back(z);
+ continue;
+ }
+
+ // normal
+ if (token[0] == 'v' && token[1] == 'n' && isSpace((token[2]))) {
+ token += 3;
+ float x, y, z;
+ parseFloat3(x, y, z, token);
+ vn.push_back(x);
+ vn.push_back(y);
+ vn.push_back(z);
+ continue;
+ }
+
+ // texcoord
+ if (token[0] == 'v' && token[1] == 't' && isSpace((token[2]))) {
+ token += 3;
+ float x, y;
+ parseFloat2(x, y, token);
+ vt.push_back(x);
+ vt.push_back(y);
+ continue;
+ }
+
+ // face
+ if (token[0] == 'f' && isSpace((token[1]))) {
+ token += 2;
+ token += strspn(token, " \t");
+
+ std::vector<vertex_index> face;
+ while (token[0] && (token[1] != '\0')) {
+ vertex_index vi = parseTriple(token, v.size() / 3, vn.size() / 3, vt.size() / 2);
+ face.push_back(vi);
+ int n = strspn(token, " \t");
+ token += n;
+ }
+
+ faceGroup.push_back(face);
+
+ continue;
+ }
+
+ // use mtl
+ if ((0 == strncmp(token, "usemtl", 6)) && isSpace((token[6]))) {
+
+ // flush previous face group.
+ shape_t shape;
+ bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, name);
+ if (ret) {
+ shapes.push_back(shape);
+ }
+
+ faceGroup.clear();
+
+ continue;
+
+ }
+
+ // load mtl
+ if ((0 == strncmp(token, "mtllib", 6)) && isSpace((token[6]))) {
+ continue;
+ }
+
+ // object name
+ if (token[0] == 'o' && isSpace((token[1]))) {
+ char namebuf[4096];
+ token += 2;
+ sscanf(token, "%s", namebuf);
+ name = std::string(namebuf);
+
+ continue;
+ }
+
+ // Ignore unknown command.
+ }
+
+ shape_t shape;
+ bool ret = exportFaceGroupToShape(shape, v, vn, vt, faceGroup, name);
+ if (ret) {
+ shapes.push_back(shape);
+ }
+ faceGroup.clear(); // for safety
+
+ return err.str();
+}
+
+
+};
diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h
new file mode 100644
index 0000000..4f64925
--- /dev/null
+++ b/tiny_obj_loader.h
@@ -0,0 +1,53 @@
+//
+// Copyright 2012, Syoyo Fujita.
+//
+// Licensed under 2-clause BSD liecense.
+//
+#ifndef _TINY_OBJ_LOADER_H
+#define _TINY_OBJ_LOADER_H
+
+#include <string>
+#include <vector>
+
+namespace tinyobj {
+
+typedef struct
+{
+ std::string name;
+
+ float ambient[3];
+ float diffuse[3];
+ float specular[3];
+ float transmittance[3];
+
+ std::string ambient_texname;
+ std::string diffuse_texname;
+ std::string specular_texname;
+} material_t;
+
+typedef struct
+{
+ std::vector<float> positions;
+ std::vector<float> normals;
+ std::vector<float> texcoords;
+ std::vector<unsigned int> indices;
+} mesh_t;
+
+typedef struct
+{
+ std::string name;
+ material_t material;
+ mesh_t mesh;
+} shape_t;
+
+/// Loads .obj from a file.
+/// 'shapes' will be filled with parsed shape data
+/// The function returns error string.
+/// Returns empty string when loading .obj success.
+std::string LoadObj(
+ std::vector<shape_t>& shapes, // [output]
+ const char* filename);
+
+};
+
+#endif // _TINY_OBJ_LOADER_H