The Allegro Wiki is migrating to github at https://github.com/liballeg/allegro_wiki/wiki

ModelLoader

From Allegro Wiki
Jump to: navigation, search

This is a simple loader and viewer for .obj model files. Usage is simple: Just load with: Model file("file.obj"); And then draw with: file.Draw();

NOTE THAT: When some bad stuff happens, I throw objects of type const char *. If you don't catch them, well, you should probably change the error handling code.

When other bad stuff happens, I don't handle it. The program will likely crash horribly. Notable examples are: A malformed file. 1d or 3d textures. I don't handle those. Because I don't believe in them. If you do, it would be terribly kind of you to fix the code up, make sure the new version works, and apply your fixes to the code on this page, and maybe even put a little note saying that you did so.

By James Lawder Andrews, aka Zaphos, except to the extent that it isn't because it's on a publicly editable website :) This source is public domain; use it as you please.

model.h

<highlightSyntax language="cpp">#ifndef _MODEL_

  1. define _MODEL_
  1. include <alleggl.h>
  2. include <vector>
  3. include <map>
  4. include <string>
  1. include "vec2.h"
  2. include "vec3.h"

struct Index {

  int vertex, texture, normal;

};

class Poly {

  std::vector<Index> indices;
  bool hasTextures, hasNormals;

public:

  void Draw(const std::vector<Vec3f> &v, const std::vector<Vec2f> &t, const std::vector<Vec3f> &n);
  
  friend std::istream& operator>>(std::istream& is, Poly &p);
  friend std::ostream& operator<<(std::ostream& os, Poly &m);

};

struct ltstr {

  bool operator()(const char* s1, const char* s2) const {
     return strcmp(s1, s2) < 0;
  }

};

struct Material {

  Vec3f Ka, Kd, Ks;
  int illum;
  float extentOfShiny;
  GLuint texture;
  bool hasTexture;
  
  std::string name;   // for some unknown reason this is crucial to materials functioning
  
  Material();
  
  void Set();
  
  friend std::ostream& operator<<(std::ostream& os, Material &m);

};

class MaterialLibrary {

  std::map<const char*, Material, ltstr> materials;

public:

  void Load(const char *file);
  Material Get(std::string material);

};

struct MaterialGroup {

  std::vector<Poly> polygons;
  Material material;
  std::string name;
  
  friend std::ostream& operator<<(std::ostream& os, MaterialGroup &m);

};

class Model {

  std::vector<Vec3f> vertices, normals;
  std::vector<Vec2f> textures;
  std::map<const char*, MaterialGroup, ltstr> geometry;
  MaterialLibrary library;

public:

  Model(char *file);
  void Draw();
  void DebugOut();   // creates a text file with model data as was loaded into Model class for verification; is not exhastive debug.
              // Material data output, as of this writing, is basically not there.
  
  ~Model();

};

  1. endif
</highlightSyntax>

model.cpp

<highlightSyntax language="cpp">#include "model.h"

  1. include <fstream>
  2. include <sstream>

using namespace std;

  void Poly::Draw(const vector<Vec3f> &v, const vector<Vec2f> &t, const vector<Vec3f> &n) {
     glBegin(GL_POLYGON);
        for (vector<Index>::iterator i = indices.begin(); i != indices.end(); ++i) {
           if (hasNormals) {
              int index = i->normal-1;
              glNormal3f(n[index].x, n[index].y, n[index].z);
           }
           if (hasTextures) {
              int index = i->texture-1;
              glTexCoord2f(t[index].x, t[index].y); 
           }
           int index = i->vertex-1;
           glVertex3f(v[index].x, v[index].y, v[index].z);
        }
     glEnd();
  }

GLuint makeTexture(const char*file, const char *asking) {

  GLuint toret;
  BITMAP* temp = load_bitmap(file, 0);
  if (!temp) throw (string("Failed to load texture : ") + file + string(" which was required by: ") + asking).c_str();
  toret = allegro_gl_make_masked_texture(temp);
  destroy_bitmap(temp);
  return toret;

}

int GetInt(string s) {

  int n;
  istringstream iss(s, istringstream::in);
  iss >> n;
  return n;

}

bool Whitespace(char c) {

  return (c == ' ' || c == '\t' || c == '\v');

}

void ClearWhitespace(istream &in) {

  while (Whitespace(in.get()));
  in.unget();   

}

std::ostream& operator<<(std::ostream& os, Poly &m) {

  for (vector<Index>::iterator i = m.indices.begin(); i != m.indices.end(); ++i) {
     os << i->vertex << "/" << i->texture << "/" << i->normal << " ";
  }
  
  return os;

}

std::ostream& operator<<(std::ostream& os, Material &m) {

  // unwritten; do as needed.
  
  return os;

}

std::ostream& operator<<(std::ostream& os, MaterialGroup &m) {

  os << m.name << endl;
  os << m.material << endl;
  for (std::vector<Poly>::iterator i = m.polygons.begin(); i != m.polygons.end(); ++i) {
     os << "f " << *i << endl;
  }
  
  return os;

}

std::istream& operator>>(std::istream& is, Poly &p) {

  string values;
  
  char c;
  do {
     string vert, tex, norm;
     
     if (!(is >> values))
        break;
     unsigned int p1 = values.find('/');
     unsigned int p2 = values.find('/', p1+1);
     if (p1 == string::npos) {    // if there's no slash...
        p.hasTextures = p.hasNormals = false;
        vert = values;
     }
     else {
        if (p2 == string::npos) { // if there's only one slash
           p.hasTextures = true;
           p.hasNormals = false;
           
           vert = values.substr( 0, p1);
           tex = values.substr( p1+1, string::npos);
        }
        else {
           p.hasTextures = p.hasNormals = true;
           
           if (p2==p1+1) {      // if textures are skipped.
              p.hasTextures = false;
              
              vert = values.substr(0,p1);
              norm = values.substr(p2+1, string::npos);
           }
           else {
              vert = values.substr(0, p1);
              tex = values.substr(p1+1, p2);
              norm = values.substr(p2+1, string::npos);
           }
        }
     }
     
     Index ind;
     ind.vertex = (GetInt(vert));
     if (p.hasTextures) ind.texture = (GetInt(tex));
     if (p.hasNormals) ind.normal = (GetInt(norm));
     p.indices.push_back(ind);
     
     // check if we're getting text or just another index group
     is >> c;
     is.unget();
  } while( !((c>='a'&&c<='z') || (c>='A'&&c<='Z')) );
  return is;

}

  Material::Material() : Ka(1,1,1), Kd(1,1,1), Ks(1,1,1), illum(2), extentOfShiny(1), hasTexture(false) {}   // default material
  
  void Material::Set() {
     float params[3];
     
     if (illum>0) {
        glEnable(GL_LIGHTING);
        glMaterialfv(GL_FRONT, GL_AMBIENT, Ka.Array(params));
        glMaterialfv(GL_FRONT, GL_DIFFUSE, Kd.Array(params));
        if (illum==1) glMaterialfv(GL_FRONT, GL_SPECULAR, Ks.Array(params));
        else glMaterialfv(GL_FRONT, GL_SPECULAR, Vec3f(0,0,0).Array(params));
        glMaterialf(GL_FRONT, GL_SHININESS, extentOfShiny);
     }
     else {
        glDisable(GL_LIGHTING);
        glColor3fv(Kd.Array(params));
     }
     if (hasTexture) {
        glBindTexture(GL_TEXTURE_2D, texture);
     }
  }
  void MaterialLibrary::Load(const char *file) {
     ifstream in(file);
     
     if (!in.is_open())
        throw (string("Couldn't open material file: ") + file).c_str();
     
     string t, currentMaterial = "default";
     while ( in >> t ) {
        if (t=="newmtl") {
           ClearWhitespace(in);
           getline(in, currentMaterial);
           materials[currentMaterial.c_str()].name = currentMaterial;
        }
        else if (t=="Ka") {
           in >> materials[currentMaterial.c_str()].Ka;
        }
        else if (t=="Kd") {
           in >> materials[currentMaterial.c_str()].Kd;
        }
        else if (t=="Ks") {
           in >> materials[currentMaterial.c_str()].Ks;
        }
        else if (t=="illum") {
           in >> materials[currentMaterial.c_str()].illum;
        }
        else if (t=="Ns") {
           in >> materials[currentMaterial.c_str()].extentOfShiny;
        }
        else if (t=="map_Kd") {
           ClearWhitespace(in);
           getline(in, t);
           materials[currentMaterial.c_str()].texture = makeTexture(t.c_str(), file);
           materials[currentMaterial.c_str()].hasTexture = true;
        }
     }
  }
  Material MaterialLibrary::Get(string material) {
     return materials[material.c_str()];
  }
  
  Model::Model(char *file) {
     ifstream in(file);
     
     if (!in.is_open())
        throw (string("Couldn't open material file: ") + file).c_str();
     
     string t, currentMaterial = "temp";
     while ( in >> t ) {
        if (t=="v") {
           Vec3f vert;
           in >> vert;
           vertices.push_back(vert);
        }
        else if (t=="vn") {
           Vec3f norm;
           in >> norm;
           normals.push_back(norm);
        }
        else if (t=="vt") {
           Vec2f tex;
           in >> tex;
           textures.push_back(tex);
        }
        else if (t=="f") {
           Poly poly;
           in >> poly;
           geometry[currentMaterial.c_str()].polygons.push_back(poly);
        }
        else if (t=="mtllib") {   // load some new materials from a lib file
           ClearWhitespace(in);
           getline(in, t);
           library.Load(t.c_str());
        }
        else if (t=="usemtl") {
           ClearWhitespace(in);
           getline(in, currentMaterial);
           geometry[currentMaterial.c_str()].name = currentMaterial;
           geometry[currentMaterial.c_str()].material = library.Get(currentMaterial.c_str());
        }
     }
  }
  void Model::Draw() {
     for (map<const char*, MaterialGroup, ltstr>::iterator mtl = geometry.begin(); mtl != geometry.end(); ++mtl) {
        mtl->second.material.Set();
        for (vector<Poly>::iterator i = mtl->second.polygons.begin(); i != mtl->second.polygons.end(); ++i) {
           i->Draw(vertices, textures, normals);
        }
     }
  }
  
  void Model::DebugOut() {
     ofstream out("thefile.txt");
     
     for (std::vector<Vec3f>::iterator i = vertices.begin(); i != vertices.end(); ++i) {
        out << "v " << *i << endl;
     }
     out << endl;
     
     for (std::vector<Vec3f>::iterator i = normals.begin(); i != normals.end(); ++i) {
        out << "vn " << *i << endl;
     }
     out << endl;
     
     for (std::vector<Vec2f>::iterator i = textures.begin(); i != textures.end(); ++i) {
        out << "vt " << *i << endl;
     }
     out << endl;
     
     for (map<const char*, MaterialGroup, ltstr>::iterator mtl = geometry.begin(); mtl != geometry.end(); ++mtl) {
        out << mtl->second;
     }
  }
  
  Model::~Model() {
     vector<GLuint> textures;
     for (map<const char*, MaterialGroup, ltstr>::iterator mtl = geometry.begin(); mtl != geometry.end(); ++mtl) {
        if (mtl->second.material.hasTexture)
           textures.push_back(mtl->second.material.texture);
     }
     GLuint * texArray = new GLuint[textures.size()];
     
     int p = 0;
     for (vector<GLuint>::iterator i = textures.begin(); i != textures.end(); ++i, p++) {
        texArray[p] = *i;
     }
     
     glDeleteTextures(textures.size(), texArray);
     delete [] texArray;
  }
</highlightSyntax>


vec3.h

<highlightSyntax language="cpp">// stripped & modified version of vec3 found at awiki.strangesoft.net

  1. ifndef __VEC3__
  2. define __VEC3__
  1. include <cmath>
  2. include <iostream>

template<typename Val> struct Vec3 {

       Val x,y,z;
  
       Vec3(Val xx = Val(), Val yy = Val(), Val zz = Val())
       : x(xx), y(yy), z(zz)
       { }
  
  float* Array(float vals[3]) {   // plop the x, y, z values into an array; useful for opengl functionality (glMaterial)
     vals[0] = x;
     vals[1] = y;
     vals[2] = z;
     return vals;
  }

};

template<typename Val> std::ostream & operator<< ( std::ostream & os, const Vec3<Val> & rhs ) {

  os << rhs.x << " " << rhs.y << " " << rhs.z;
  
  return os;

}

template<typename Val> std::istream& operator>>(std::istream& is, Vec3<Val>& rhs) {

   is >> rhs.x >> rhs.y >> rhs.z;
   return is;

}

typedef Vec3<float> Vec3f; typedef Vec3<double> Vec3d;

typedef Vec3<int> Vec3i;

  1. endif
</highlightSyntax>

vec2.h

<highlightSyntax language="cpp">// stripped & modified version of vec3 found at awiki.strangesoft.net

  1. ifndef __VEC2__
  2. define __VEC2__
  1. include <cmath>
  2. include <iostream>

template<typename Val> struct Vec2 {

       Val x,y;
  
       Vec2(Val xx = Val(), Val yy = Val())
       : x(xx), y(yy)
       { }

};

template<typename Val> std::ostream& operator<<(std::ostream& os, const Vec2<Val>& rhs) {

  return os << rhs.x << " " << rhs.y;

}

template<typename Val> std::istream& operator>>(std::istream& is, Vec2<Val>& rhs) {

   is >> rhs.x >> rhs.y;
   return is;

}

//A couple of handy typedefs

typedef Vec2<float> Vec2f; typedef Vec2<double> Vec2d;

typedef Vec2<int> Vec2i;

  1. endif
</highlightSyntax>