text: #include "assets/renderable.h"
  
  #include "data/vertex_hashtable.h"
  
  
  void renderable_add_mesh(renderable* r, mesh* m) {
    
    renderable_surface* surface = renderable_surface_new(m);
    
    r->num_surfaces++;
    r->surfaces = realloc(r->surfaces, sizeof(renderable_surface*) *  r->num_surfaces);
    r->surfaces[r->num_surfaces-1] = surface;
    
  }
  
  void renderable_add_model(renderable* r, model* m) {
  
    for(int i = 0; i < m->num_meshes; i++) {
      renderable_add_mesh(r, m->meshes[i]);
    }
    
  }
  
  renderable* renderable_new() {
    
    renderable* r = malloc(sizeof(renderable));
    
    r->material = asset_hndl_new_load(P("$CORANGE/shaders/basic.mat"));
    r->num_surfaces = 0;
    r->surfaces = NULL;
    r->is_rigged = false;
    
    return r;
  
  }
  
  void renderable_delete(renderable* r) {
    
    for(int i = 0; i < r->num_surfaces; i++) {
      renderable_surface_delete( r->surfaces[i] );
    }
    
    free(r);
  
  }
  
  void renderable_set_material(renderable* r, asset_hndl mat) {
    r->material = mat;
  }
  
  model* renderable_to_model(renderable* r) {
  
    if (r->is_rigged) {
      error("Cannot convert rigged renderable to model");
    }  
  
    model* m = model_new();
    
    m->num_meshes = r->num_surfaces;
    m->meshes = realloc(m->meshes, sizeof(mesh*) * m->num_meshes);
    
    for(int i = 0; i < r->num_surfaces; i++) {
      
      renderable_surface* s = r->surfaces[i];
      
      float* vb_data = malloc(sizeof(float) * s->num_verticies * 18);
      uint32_t* ib_data = malloc(sizeof(uint32_t) * s->num_triangles * 3);
      
      glBindBuffer(GL_ARRAY_BUFFER, s->vertex_vbo);
      glGetBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * s->num_verticies * 18, vb_data);
      
      glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s->triangle_vbo);
      glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, sizeof(uint32_t) * s->num_triangles * 3, ib_data);
      
      m->meshes[i] = mesh_new();
      
      mesh* me = m->meshes[i];
      
      me->num_verts = s->num_verticies;
      me->num_triangles = s->num_triangles;
      me->verticies = realloc(me->verticies, sizeof(vertex) * me->num_verts);
      me->triangles = realloc(me->triangles, sizeof(uint32_t) * me->num_triangles * 3);
      
      for(int j = 0; j < me->num_verts; j++) {
        me->verticies[j].position.x = vb_data[(j*18)+0];
        me->verticies[j].position.y = vb_data[(j*18)+1];
        me->verticies[j].position.z = vb_data[(j*18)+2];
        
        me->verticies[j].normal.x = vb_data[(j*18)+3];
        me->verticies[j].normal.y = vb_data[(j*18)+4];
        me->verticies[j].normal.z = vb_data[(j*18)+5];
        
        me->verticies[j].tangent.x = vb_data[(j*18)+6];
        me->verticies[j].tangent.y = vb_data[(j*18)+7];
        me->verticies[j].tangent.z = vb_data[(j*18)+8];
        
        me->verticies[j].binormal.x = vb_data[(j*18)+9];
        me->verticies[j].binormal.y = vb_data[(j*18)+10];
        me->verticies[j].binormal.z = vb_data[(j*18)+11];
        
        me->verticies[j].uvs.x = vb_data[(j*18)+12];
        me->verticies[j].uvs.y = vb_data[(j*18)+13];
        
        me->verticies[j].color.x = vb_data[(j*18)+14];
        me->verticies[j].color.y = vb_data[(j*18)+15];
        me->verticies[j].color.z = vb_data[(j*18)+16];
        me->verticies[j].color.w = vb_data[(j*18)+17];
      }
      
      for(int j = 0; j < me->num_triangles * 3; j++) {
        me->triangles[j] = ib_data[j];
      }
      
      free(vb_data);
      free(ib_data);
    
    }
    
    return m;
  }
  
  renderable_surface* renderable_surface_new(mesh* m) {
  
    renderable_surface* s = malloc(sizeof(renderable_surface));
  
    glGenBuffers(1, &s->vertex_vbo);
    glGenBuffers(1, &s->triangle_vbo);
  
    s->num_verticies = m->num_verts;
    s->num_triangles = m->num_triangles;
    s->bound = mesh_bounding_sphere(m);
    
    /* Position Normal Tangent Binormal Uvs Color      */
    /* 3        3      3       3        2   4     = 18 */
    float* vb_data = malloc(sizeof(float) * m->num_verts * 18);
    
    for(int i = 0; i < m->num_verts; i++) {
    
      vec3 pos = m->verticies[i].position;
      vec3 norm = m->verticies[i].normal;
      vec3 tang = m->verticies[i].tangent;
      vec3 bino = m->verticies[i].binormal;
      vec2 uvs = m->verticies[i].uvs;
      vec4 col = m->verticies[i].color;
      
      vb_data[(i*18)+0] = pos.x;
      vb_data[(i*18)+1] = pos.y;
      vb_data[(i*18)+2] = pos.z;
      
      vb_data[(i*18)+3] = norm.x;
      vb_data[(i*18)+4] = norm.y;
      vb_data[(i*18)+5] = norm.z;
      
      vb_data[(i*18)+6] = tang.x;
      vb_data[(i*18)+7] = tang.y;
      vb_data[(i*18)+8] = tang.z;
      
      vb_data[(i*18)+9] = bino.x;
      vb_data[(i*18)+10] = bino.y;
      vb_data[(i*18)+11] = bino.z;
      
      vb_data[(i*18)+12] = uvs.x;
      vb_data[(i*18)+13] = uvs.y;
      
      vb_data[(i*18)+14] = col.x;
      vb_data[(i*18)+15] = col.y;
      vb_data[(i*18)+16] = col.z;
      vb_data[(i*18)+17] = col.w;
    
    }
    
    glBindBuffer(GL_ARRAY_BUFFER, s->vertex_vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(float) * s->num_verticies * 18, vb_data, GL_STATIC_DRAW);
    
    free(vb_data);
    
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s->triangle_vbo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32_t) * s->num_triangles * 3, m->triangles, GL_STATIC_DRAW);
    
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
  
    return s;
  }
  
  renderable_surface* renderable_surface_new_rigged(mesh* m, vertex_weight* weights) {
  
    renderable_surface* s = malloc(sizeof(renderable_surface));
  
    glGenBuffers(1, &s->vertex_vbo);
    glGenBuffers(1, &s->triangle_vbo);
  
    s->num_verticies = m->num_verts;
    s->num_triangles = m->num_triangles;
    
    /* Position Normal Tangent Binormal Uvs Color WeightIds WeightAmounts      */
    /* 3        3      3       3        2   4     3         3             = 24 */
    float* vb_data = malloc(sizeof(float) * m->num_verts * 24);
    
    for(int i = 0; i < m->num_verts; i++) {
    
      vec3 pos = m->verticies[i].position;
      vec3 norm = m->verticies[i].normal;
      vec3 tang = m->verticies[i].tangent;
      vec3 bino = m->verticies[i].binormal;
      vec2 uvs = m->verticies[i].uvs;
      vec4 col = m->verticies[i].color;
      
      vb_data[(i*24)+0] = pos.x;
      vb_data[(i*24)+1] = pos.y;
      vb_data[(i*24)+2] = pos.z;
      
      vb_data[(i*24)+3] = norm.x;
      vb_data[(i*24)+4] = norm.y;
      vb_data[(i*24)+5] = norm.z;
      
      vb_data[(i*24)+6] = tang.x;
      vb_data[(i*24)+7] = tang.y;
      vb_data[(i*24)+8] = tang.z;
      
      vb_data[(i*24)+9] = bino.x;
      vb_data[(i*24)+10] = bino.y;
      vb_data[(i*24)+11] = bino.z;
      
      vb_data[(i*24)+12] = uvs.x;
      vb_data[(i*24)+13] = uvs.y;
      
      vb_data[(i*24)+14] = col.w;
      vb_data[(i*24)+15] = col.x;
      vb_data[(i*24)+16] = col.y;
      vb_data[(i*24)+17] = col.z;
    
      vb_data[(i*24)+18] = (float)weights[i].bone_ids[0];
      vb_data[(i*24)+19] = (float)weights[i].bone_ids[1];
      vb_data[(i*24)+20] = (float)weights[i].bone_ids[2];
      
      vb_data[(i*24)+21] = weights[i].bone_weights[0];
      vb_data[(i*24)+22] = weights[i].bone_weights[1];
      vb_data[(i*24)+23] = weights[i].bone_weights[2];
    
    }
    
    glBindBuffer(GL_ARRAY_BUFFER, s->vertex_vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(float) * s->num_verticies * 24, vb_data, GL_STATIC_DRAW);
    
    free(vb_data);
    
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s->triangle_vbo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32_t) * s->num_triangles * 3, m->triangles, GL_STATIC_DRAW);
    
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    SDL_GL_CheckError();
    
    return s;
  }
  
  void renderable_surface_delete(renderable_surface* s) {
  
    glDeleteBuffers(1, &s->vertex_vbo);
    glDeleteBuffers(1, &s->triangle_vbo);
    
    free(s);
    
  }
  
  static sphere renderable_surface_bounding_sphere(float* verts, int num_verts, int stride) {
    
    sphere s;
    
    s.center = vec3_zero();
    for(int i = 0; i < num_verts * stride; i += stride) {
      vec3 position = vec3_new(verts[i+0], verts[i+1], verts[i+2]);
      s.center = vec3_add(s.center, position);
    }
    s.center = vec3_div(s.center, num_verts);
    
    s.radius = 0;
    for(int i = 0; i < num_verts * stride; i += stride) {
      vec3 position = vec3_new(verts[i+0], verts[i+1], verts[i+2]);
      s.radius = max(s.radius, vec3_dist(s.center, position));
    }
    
    return s;
    
  }
  
  renderable* bmf_load_file(char* filename) {
  
    renderable* r = malloc(sizeof(renderable));
    
    SDL_RWops* file = SDL_RWFromFile(filename, "rb");
    
    if(file == NULL) {
      error("Could not load file %s", filename);
    }
    
    char magic[4];
    SDL_RWread(file, &magic, 3, 1);
    magic[3] = '\0';
    
    if (strcmp(magic, "BMF") != 0) {
      error("Badly formed bmf file '%s', missing magic number", filename);
    }  
    
    uint32_t version = 0;
    SDL_RWread(file, &version, sizeof(uint32_t), 1);
    
    if (version != 1) {
      error("Only version 1 of bmf format supported. Recieved file of version %i.", version);
    }
      
    SDL_RWread(file, &r->is_rigged, 1, 1);
      
    uint32_t mat_len;
    SDL_RWread(file, &mat_len, sizeof(uint32_t), 1);
      
    fpath material_path;
    SDL_RWread(file, material_path.ptr, mat_len, 1);
    material_path.ptr[mat_len] = '\0';
      
    r->material = asset_hndl_new_load(material_path);
    
    uint32_t num_surfaces;
    SDL_RWread(file, &num_surfaces, sizeof(uint32_t), 1);
    r->num_surfaces = num_surfaces;
    
    r->surfaces = malloc(sizeof(renderable_surface*) * r->num_surfaces);
    
    const int stride = r->is_rigged ? 24 : 18;
    
    for(int i = 0; i < r->num_surfaces; i++) {
      renderable_surface* s = malloc(sizeof(renderable_surface));
      
      uint32_t num_verticies;
      SDL_RWread(file, &num_verticies, sizeof(uint32_t), 1);
      s->num_verticies = num_verticies;
      
      float* vert_data = malloc(sizeof(float) * stride * s->num_verticies);
      SDL_RWread(file, vert_data, sizeof(float) * stride * s->num_verticies, 1);
      
      s->bound = renderable_surface_bounding_sphere(vert_data, s->num_verticies, stride);
      
      glGenBuffers(1, &s->vertex_vbo);
      glBindBuffer(GL_ARRAY_BUFFER, s->vertex_vbo);
      glBufferData(GL_ARRAY_BUFFER, sizeof(float) * s->num_verticies * stride, vert_data, GL_STATIC_DRAW);
      free(vert_data);
      
      uint32_t num_indicies;
      SDL_RWread(file, &num_indicies, sizeof(uint32_t), 1);
      s->num_triangles = num_indicies / 3;
      
      uint32_t* index_data = malloc(sizeof(uint32_t) * num_indicies);
      SDL_RWread(file, index_data, sizeof(uint32_t) * num_indicies, 1);
      glGenBuffers(1, &s->triangle_vbo);
      glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s->triangle_vbo);
      glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32_t) * num_indicies, index_data, GL_STATIC_DRAW);
      free(index_data);
      
      r->surfaces[i] = s;
    }
    
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    SDL_RWclose(file);
    
    SDL_GL_CheckError();
    
    return r;
  }
  
  void bmf_save_file(renderable* r, char* filename) {
    
    SDL_RWops* file = SDL_RWFromFile(filename, "wb");
    
    uint32_t version = 1;
    
    SDL_RWwrite(file, "BMF", 3, 1);
    SDL_RWwrite(file, &version, sizeof(uint32_t), 1);
    SDL_RWwrite(file, &r->is_rigged, 1, 1);
    
    fpath matpath;
    SDL_PathRelative(matpath.ptr, r->material.path.ptr);
    SDL_PathForwardSlashes(matpath.ptr);
    
    uint32_t mat_len = strlen(matpath.ptr);
    SDL_RWwrite(file, &mat_len, sizeof(uint32_t), 1);
    SDL_RWwrite(file, matpath.ptr, mat_len, 1);
    
    uint32_t num_surfaces = r->num_surfaces;
    SDL_RWwrite(file, &num_surfaces, sizeof(uint32_t), 1);
    
    const uint32_t vertsize = r->is_rigged ? 24 : 18;
    
    for(int i = 0; i < r->num_surfaces; i++) {
      renderable_surface* s = r->surfaces[i];
      
      uint32_t num_verticies = s->num_verticies;
      SDL_RWwrite(file, &num_verticies, sizeof(uint32_t), 1);
      
      uint32_t vert_data_size = sizeof(float) * vertsize * num_verticies;
      float* vert_data = calloc(vert_data_size, 1);
      glBindBuffer(GL_ARRAY_BUFFER, s->vertex_vbo);
      glGetBufferSubData(GL_ARRAY_BUFFER, 0, vert_data_size, vert_data);
      SDL_RWwrite(file, vert_data, 1, vert_data_size);
      free(vert_data);
      
      uint32_t num_indicies = s->num_triangles * 3;
      SDL_RWwrite(file, &num_indicies, sizeof(uint32_t), 1);
      
      uint32_t index_data_size = sizeof(uint32_t) * num_indicies;
      uint32_t* index_data = calloc(index_data_size, 1);
      glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s->triangle_vbo);
      glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, index_data_size, index_data);
      SDL_RWwrite(file, index_data, 1, index_data_size);
      free(index_data);
      
    }
    
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    SDL_RWclose(file);
      
  }
  
  static int SDL_RWreadline(SDL_RWops* file, char* buffer, int buffersize) {
    
    char c;
    int status = 0;
    int i = 0;
    while(1) {
      
      status = SDL_RWread(file, &c, 1, 1);
      
      if (status == -1) return -1;
      if (i == buffersize-1) return -1;
      if (status == 0) break;
      
      buffer[i] = c;
      i++;
      
      if (c == '\n') {
        buffer[i] = '\0';
        return i;
      }
    }
    
    if(i > 0) {
      buffer[i] = '\0';
      return i;
    } else {
      return 0;
    }
    
  }
  
  renderable* obj_load_file(char* filename) {
      
    model* obj_model = malloc(sizeof(model));
    obj_model->num_meshes = 0;
    obj_model->meshes = malloc(sizeof(mesh*) * 0);
    
    mesh* active_mesh = NULL;
    
    vertex_list* vert_data = vertex_list_new();
    vertex_list* vert_list = vertex_list_new();
    int_list* tri_list = int_list_new();
    vertex_hashtable* vert_hashes = vertex_hashtable_new(4096);
    
    int num_pos, num_norm, num_tex;
    num_pos = num_norm = num_tex = 0;
    
    int vert_index = 0;
    
    bool has_normal_data = false;
    bool has_texcoord_data = false;
    
    SDL_RWops* file = SDL_RWFromFile(filename, "r");
    
    if(file == NULL) {
      error("Could not load file %s", filename);
    }
    
    char line[1024];
    while(SDL_RWreadline(file, line, 1024)) {
      
      char comment[512 + 1];
      char matlib[512 + 1];
      char object[512 + 1];
      char group[512 + 1];
      char material[512 + 1];
      float px, py, pz, tx, ty, nx, ny, nz;
      int smoothing_group;
      int pi1, ti1, ni1, pi2, ti2, ni2, pi3, ti3, ni3;
      
      if (sscanf(line, "# %512s", comment) == 1) {
        /* Comment, do nothing */
      }
      
      else if (sscanf(line, "mtllib %512s", matlib) == 1) {
        /* Do Nothing */
      }
      
      else if (sscanf(line, "o %512s", object) == 1) {
        /* Do Nothing */
      }
      
      else if (sscanf(line, "v %f %f %f", &px, &py, &pz) == 3) {
      
        while(vert_data->num_items <= num_pos) { vertex_list_push_back(vert_data, vertex_new()); }
        vertex vert = vertex_list_get(vert_data, num_pos);
        vert.position = vec3_new(px, py, pz);
        vertex_list_set(vert_data, num_pos, vert);
        num_pos++;
      }
      
      else if (sscanf(line, "vt %f %f", &tx, &ty) == 2) {
      
        while(vert_data->num_items <= num_tex) { vertex_list_push_back(vert_data, vertex_new()); }
        vertex vert = vertex_list_get(vert_data, num_tex);
        vert.uvs = vec2_new(tx, ty);
        vertex_list_set(vert_data, num_tex, vert);
        num_tex++;
      }
      
      else if (sscanf(line, "vn %f %f %f", &nx, &ny, &nz) == 3) {
      
        while(vert_data->num_items <= num_norm) { vertex_list_push_back(vert_data, vertex_new()); }
        vertex vert = vertex_list_get(vert_data, num_norm);
        vert.normal = vec3_new(nx, ny, nz);
        vertex_list_set(vert_data, num_norm, vert);
        num_norm++;
      }
      
      else if (sscanf(line, "g %512s", group) == 1) {
          
        if (active_mesh != NULL) {
        
          active_mesh->num_verts = vert_index;
          active_mesh->num_triangles = tri_list->num_items / 3;
          
          active_mesh->verticies = malloc(sizeof(vertex) * active_mesh->num_verts);
          for(int i = 0; i < active_mesh->num_verts; i++) {
            active_mesh->verticies[i] = vertex_list_get(vert_list, i);
          }
          
          active_mesh->triangles = malloc(sizeof(int) * active_mesh->num_triangles * 3);
          for(int i = 0; i < active_mesh->num_triangles * 3; i++) {
            active_mesh->triangles[i] = int_list_get(tri_list, i);
          }
        
          obj_model->num_meshes++;
          obj_model->meshes = realloc(obj_model->meshes, sizeof(mesh*) * obj_model->num_meshes);
          obj_model->meshes[obj_model->num_meshes-1] = active_mesh;
          
        }
        
        vert_index = 0;
        
        vertex_hashtable_delete(vert_hashes);
        vertex_list_delete(vert_list);
        int_list_delete(tri_list);
        
        vert_list = vertex_list_new();
        tri_list = int_list_new();
        vert_hashes = vertex_hashtable_new(4096);
        
        active_mesh = malloc(sizeof(mesh));
        
      }
      
      else if (sscanf(line, "usemtl %512s", material) == 1) {
        /* Do Nothing */
      }
      
      else if (sscanf(line, "s %i", &smoothing_group) == 1) {
        /* Smoothing group, do nothing */
      }
      
      else if (sscanf(line, "f %i/%i/%i %i/%i/%i %i/%i/%i", &pi1, &ti1, &ni1, &pi2, &ti2, &ni2, &pi3, &ti3, &ni3) == 9) {
        
        if (active_mesh == NULL) {
          vert_index = 0;
          
          vertex_hashtable_delete(vert_hashes);
          vertex_list_delete(vert_list);
          int_list_delete(tri_list);
          
          vert_list = vertex_list_new();
          tri_list = int_list_new();
          vert_hashes = vertex_hashtable_new(4096);
          
          active_mesh = malloc(sizeof(mesh));
        }
        
        has_normal_data = true;
        has_texcoord_data = true;
        
        /* OBJ file indices start from one, have to subtract one */
        pi1--; ti1--; ni1--; pi2--; ti2--; ni2--; pi3--; ti3--; ni3--;
        
        vertex v1, v2, v3;
        v1.position = vertex_list_get(vert_data, pi1).position;
        v1.uvs = vertex_list_get(vert_data, ti1).uvs;
        v1.normal = vertex_list_get(vert_data, ni1).normal;
        
        v2.position = vertex_list_get(vert_data, pi2).position;
        v2.uvs = vertex_list_get(vert_data, ti2).uvs;
        v2.normal = vertex_list_get(vert_data, ni2).normal;
        
        v3.position = vertex_list_get(vert_data, pi3).position;
        v3.uvs = vertex_list_get(vert_data, ti3).uvs;
        v3.normal = vertex_list_get(vert_data, ni3).normal;
        
        int v1_id = vertex_hashtable_get(vert_hashes, v1);
        if ( v1_id == -1 ) {
          vertex_hashtable_set(vert_hashes, v1, vert_index);
          vertex_list_push_back(vert_list, v1);
          int_list_push_back(tri_list, vert_index);
          vert_index++;
        } else {
          int_list_push_back(tri_list, v1_id);
        }
        
        int v2_id = vertex_hashtable_get(vert_hashes, v2);
        if ( v2_id == -1 ) {
          vertex_hashtable_set(vert_hashes, v2, vert_index);
          vertex_list_push_back(vert_list, v2);
          int_list_push_back(tri_list, vert_index);
          vert_index++;
        } else {
          int_list_push_back(tri_list, v2_id);
        }
        
        int v3_id = vertex_hashtable_get(vert_hashes, v3);
        if ( v3_id == -1 ) {
          vertex_hashtable_set(vert_hashes, v3, vert_index);
          vertex_list_push_back(vert_list, v3);
          int_list_push_back(tri_list, vert_index);
          vert_index++;
        } else {
          int_list_push_back(tri_list, v3_id);
        }
        
      }
      
      else if (sscanf(line, "f %i//%i %i//%i %i//%i", &pi1, &ni1, &pi2, &ni2, &pi3, &ni3) == 6) {
        
        if (active_mesh == NULL) {
          vert_index = 0;
          
          vertex_hashtable_delete(vert_hashes);
          vertex_list_delete(vert_list);
          int_list_delete(tri_list);
          
          vert_list = vertex_list_new();
          tri_list = int_list_new();
          vert_hashes = vertex_hashtable_new(4096);
          
          active_mesh = malloc(sizeof(mesh));
        }
        
        has_normal_data = true;
        has_texcoord_data = false;
        
        /* OBJ file indices start from one, have to subtract one */
        pi1--; ni1--; pi2--; ni2--; pi3--; ni3--;
        
        vertex v1, v2, v3;
        v1.position = vertex_list_get(vert_data, pi1).position;
        v1.uvs = vec2_zero();
        v1.normal = vertex_list_get(vert_data, ni1).normal;
        
        v2.position = vertex_list_get(vert_data, pi2).position;
        v2.uvs = vec2_zero();
        v2.normal = vertex_list_get(vert_data, ni2).normal;
        
        v3.position = vertex_list_get(vert_data, pi3).position;
        v3.uvs = vec2_zero();
        v3.normal = vertex_list_get(vert_data, ni3).normal;
        
        int v1_id = vertex_hashtable_get(vert_hashes, v1);
        if ( v1_id == -1 ) {
          vertex_hashtable_set(vert_hashes, v1, vert_index);
          vertex_list_push_back(vert_list, v1);
          int_list_push_back(tri_list, vert_index);
          vert_index++;
        } else {
          int_list_push_back(tri_list, v1_id);
        }
        
        int v2_id = vertex_hashtable_get(vert_hashes, v2);
        if ( v2_id == -1 ) {
          vertex_hashtable_set(vert_hashes, v2, vert_index);
          vertex_list_push_back(vert_list, v2);
          int_list_push_back(tri_list, vert_index);
          vert_index++;
        } else {
          int_list_push_back(tri_list, v2_id);
        }
        
        int v3_id = vertex_hashtable_get(vert_hashes, v3);
        if ( v3_id == -1 ) {
          vertex_hashtable_set(vert_hashes, v3, vert_index);
          vertex_list_push_back(vert_list, v3);
          int_list_push_back(tri_list, vert_index);
          vert_index++;
        } else {
          int_list_push_back(tri_list, v3_id);
        }
        
      }
      
      else if (sscanf(line, "f %i/%i %i/%i %i/%i", &pi1, &ti1, &pi2, &ti2, &pi3, &ti3) == 6) {
        
        if (active_mesh == NULL) {
          vert_index = 0;
          
          vertex_hashtable_delete(vert_hashes);
          vertex_list_delete(vert_list);
          int_list_delete(tri_list);
          
          vert_list = vertex_list_new();
          tri_list = int_list_new();
          vert_hashes = vertex_hashtable_new(4096);
          
          active_mesh = malloc(sizeof(mesh));
        }
        
        has_normal_data = false;
        has_texcoord_data = true;
        
        /* OBJ file indices start from one, have to subtract one */
        pi1--; ti1--; pi2--; ti2--; pi3--; ti3--;
        
        vertex v1, v2, v3;
        v1.position = vertex_list_get(vert_data, pi1).position;
        v1.uvs = vertex_list_get(vert_data, ti1).uvs;
        v1.normal = vec3_zero();
        
        v2.position = vertex_list_get(vert_data, pi2).position;
        v2.uvs = vertex_list_get(vert_data, ti2).uvs;
        v2.normal = vec3_zero();
        
        v3.position = vertex_list_get(vert_data, pi3).position;
        v3.uvs = vertex_list_get(vert_data, ti3).uvs;
        v3.normal = vec3_zero();
        
        int v1_id = vertex_hashtable_get(vert_hashes, v1);
        if ( v1_id == -1 ) {
          vertex_hashtable_set(vert_hashes, v1, vert_index);
          vertex_list_push_back(vert_list, v1);
          int_list_push_back(tri_list, vert_index);
          vert_index++;
        } else {
          int_list_push_back(tri_list, v1_id);
        }
        
        int v2_id = vertex_hashtable_get(vert_hashes, v2);
        if ( v2_id == -1 ) {
          vertex_hashtable_set(vert_hashes, v2, vert_index);
          vertex_list_push_back(vert_list, v2);
          int_list_push_back(tri_list, vert_index);
          vert_index++;
        } else {
          int_list_push_back(tri_list, v2_id);
        }
        
        int v3_id = vertex_hashtable_get(vert_hashes, v3);
        if ( v3_id == -1 ) {
          vertex_hashtable_set(vert_hashes, v3, vert_index);
          vertex_list_push_back(vert_list, v3);
          int_list_push_back(tri_list, vert_index);
          vert_index++;
        } else {
          int_list_push_back(tri_list, v3_id);
        }
        
      }
      
      else if (sscanf(line, "f %i %i %i", &pi1, &pi2, &pi3) == 3) {
        
        if (active_mesh == NULL) {
          vert_index = 0;
          
          vertex_hashtable_delete(vert_hashes);
          vertex_list_delete(vert_list);
          int_list_delete(tri_list);
          
          vert_list = vertex_list_new();
          tri_list = int_list_new();
          vert_hashes = vertex_hashtable_new(4096);
          
          active_mesh = malloc(sizeof(mesh));
        }
        
        has_normal_data = false;
        has_texcoord_data = false;
        
        /* OBJ file indices start from one, have to subtract one */
        pi1--; pi2--; pi3--;
        
        vertex v1, v2, v3;
        v1.position = vertex_list_get(vert_data, pi1).position;
        v1.uvs = vec2_zero();
        v1.normal = vec3_zero();
        
        v2.position = vertex_list_get(vert_data, pi2).position;
        v2.uvs = vec2_zero();
        v2.normal = vec3_zero();
        
        v3.position = vertex_list_get(vert_data, pi3).position;
        v3.uvs = vec2_zero();
        v3.normal = vec3_zero();
        
        int v1_id = vertex_hashtable_get(vert_hashes, v1);
        if ( v1_id == -1 ) {
          vertex_hashtable_set(vert_hashes, v1, vert_index);
          vertex_list_push_back(vert_list, v1);
          int_list_push_back(tri_list, vert_index);
          vert_index++;
        } else {
          int_list_push_back(tri_list, v1_id);
        }
        
        int v2_id = vertex_hashtable_get(vert_hashes, v2);
        if ( v2_id == -1 ) {
          vertex_hashtable_set(vert_hashes, v2, vert_index);
          vertex_list_push_back(vert_list, v2);
          int_list_push_back(tri_list, vert_index);
          vert_index++;
        } else {
          int_list_push_back(tri_list, v2_id);
        }
        
        int v3_id = vertex_hashtable_get(vert_hashes, v3);
        if ( v3_id == -1 ) {
          vertex_hashtable_set(vert_hashes, v3, vert_index);
          vertex_list_push_back(vert_list, v3);
          int_list_push_back(tri_list, vert_index);
          vert_index++;
        } else {
          int_list_push_back(tri_list, v3_id);
        }
        
      }
      
    }
  
    SDL_RWclose(file);
    
    if (active_mesh == NULL) {
      error("Unable to load file '%s', it appears to be empty.", filename);
    }
    
    active_mesh->num_verts = vert_index;
    active_mesh->num_triangles = tri_list->num_items / 3;
    
    active_mesh->verticies = malloc(sizeof(vertex) * active_mesh->num_verts);
    for(int i = 0; i < active_mesh->num_verts; i++) {
      active_mesh->verticies[i] = vertex_list_get(vert_list, i);
    }
    
    active_mesh->triangles = malloc(sizeof(int) * active_mesh->num_triangles * 3);
    for(int i = 0; i < active_mesh->num_triangles * 3; i++) {
      active_mesh->triangles[i] = int_list_get(tri_list, i);
    }
    
    obj_model->num_meshes++;
    obj_model->meshes = realloc(obj_model->meshes, sizeof(mesh*) * obj_model->num_meshes);
    obj_model->meshes[obj_model->num_meshes-1] = active_mesh;
    
    vertex_hashtable_delete(vert_hashes);
    vertex_list_delete(vert_data);
    vertex_list_delete(vert_list);
    int_list_delete(tri_list);
    
    if (!has_normal_data) {
      model_generate_normals(obj_model);
    }
    
    if (!has_texcoord_data) {
      model_generate_texcoords_cylinder(obj_model);
    }
    
    model_generate_tangents(obj_model);
    
    renderable* renderable = renderable_new();
    renderable_add_model(renderable, obj_model);
    model_delete(obj_model);
    
    fpath mat_file;
    fpath bmf_file;
    fpath fileid;
    
    SDL_PathFileLocation(mat_file.ptr, filename);
    SDL_PathFileLocation(bmf_file.ptr, filename);
    SDL_PathFileName(fileid.ptr, filename);
    
    strcat(mat_file.ptr, fileid.ptr);
    strcat(mat_file.ptr, ".mat");
    
    if (file_exists(mat_file)) {
      renderable->material = asset_hndl_new(mat_file);
    }
    
    strcat(bmf_file.ptr, fileid.ptr);
    strcat(bmf_file.ptr, ".bmf");
    bmf_save_file(renderable, bmf_file.ptr);
    
    return renderable;
  }
  
  static void renderable_add_mesh_rigged(renderable* r, mesh* m, vertex_weight* weights) {
    
    renderable_surface* surface = renderable_surface_new_rigged(m, weights);
    
    r->num_surfaces++;
    r->surfaces = realloc(r->surfaces, sizeof(renderable_surface*) *  r->num_surfaces);
    r->surfaces[r->num_surfaces-1] = surface;
    
  }
  
  enum {
    STATE_LOAD_EMPTY     = 0,
    STATE_LOAD_TRIANGLES = 1,
  };
  
  renderable* smd_load_file(char* filename) {
    
    int state = STATE_LOAD_EMPTY;
    char state_material[1024];
    
    vertex_hashtable* hashes = vertex_hashtable_new(1024);
    vertex_list* vert_list = vertex_list_new();
    int_list* tri_list = int_list_new();
    
    int allocated_weights = 1024;
    vertex_weight* weights = malloc(sizeof(vertex_weight) * allocated_weights);
    
    int vert_index = 0;
    
    SDL_RWops* file = SDL_RWFromFile(filename, "r");
    
    if(file == NULL) {
      error("Could not load file %s", filename);
    }
    
    renderable* r = renderable_new();
    r->is_rigged = true;
    
    char line[1024];
    while(SDL_RWreadline(file, line, 1024)) {
      
      if (strstr(line, "end")) {
        state = STATE_LOAD_EMPTY;
      }
      
      if (state == STATE_LOAD_EMPTY) {
        
        int version;
        if (sscanf(line, "version %i", &version) > 0) {
          if (version != 1) {
            error("Can't load SMD file %s. Don't know how to load version %i\n", filename, version);
          }
        }
        
        if (strstr(line, "triangles")) {
          state = STATE_LOAD_TRIANGLES;
        }
      }
      
      if (state == STATE_LOAD_TRIANGLES) {
        
        int id, l1_id, l2_id, l3_id;
        int num_links = 0;
        float x, y, z, nx, ny, nz, u, v, l1_amount, l2_amount, l3_amount;
        if (sscanf(line, "%i %f %f %f %f %f %f %f %f %i %i %f %i %f %i %f", 
            &id, &x, &y, &z, &nx, &ny, &nz, &u, &v, &num_links, 
            &l1_id, &l1_amount, &l2_id, &l2_amount, &l3_id, &l3_amount) > 9) {
          
          if (num_links > 3) {
            //warning("Loading file '%s'. More than 3 bones rigged to vertex (%i). Ignoring other bones", filename, num_links);
            num_links = 3;
          }
          
          if (num_links == 0) {
            warning("Loading file '%s'. Vertex has no direct bone links", filename);
            num_links = 1;
            l1_id = id;
          }
          
          vertex vert;
          /* Swap y and z axis */
          vert.position = vec3_new(x, z, y);
          vert.normal = vec3_new(nx, nz, ny);
          vert.uvs = vec2_new(u, v);
          vert.color = vec4_one();
          vert.tangent = vec3_zero();
          vert.binormal = vec3_zero();
          
          int vert_pos = vertex_hashtable_get(hashes, vert);
          
          /* Not already in hashtable */
          if (vert_pos == -1) {
            vertex_hashtable_set(hashes, vert, vert_index);
            vert_pos = vert_index;
            vertex_list_push_back(vert_list, vert);
            
            vertex_weight vw;
            switch (num_links) {
              case 3:
                vw.bone_ids[0] = l1_id;
                vw.bone_ids[1] = l2_id;
                vw.bone_ids[2] = l3_id;
                vw.bone_weights[0] = l1_amount;
                vw.bone_weights[1] = l2_amount;
                vw.bone_weights[2] = l3_amount;
              break;
              case 2:
                vw.bone_ids[0] = l1_id;
                vw.bone_ids[1] = l2_id;
                vw.bone_ids[2] = 0;
                vw.bone_weights[0] = l1_amount;
                vw.bone_weights[1] = l2_amount;
                vw.bone_weights[2] = 0;
              break;
              case 1:
                vw.bone_ids[0] = l1_id;
                vw.bone_ids[1] = 0;
                vw.bone_ids[2] = 0;
                vw.bone_weights[0] = 1;
                vw.bone_weights[1] = 0;
                vw.bone_weights[2] = 0;
              break;
              default:
                warning("Loading file %s. Unrigged vertex!", filename);
                vw.bone_ids[0] = 0;
                vw.bone_ids[1] = 0;
                vw.bone_ids[2] = 0;
                vw.bone_weights[0] = 1;
                vw.bone_weights[1] = 0;
                vw.bone_weights[2] = 0;
              break;
            }
            
            float total = vw.bone_weights[0] + vw.bone_weights[1] + vw.bone_weights[2];
            
            if (total != 0) {
              vw.bone_weights[0] /= total;
              vw.bone_weights[1] /= total;
              vw.bone_weights[2] /= total;
            }
            
            while(vert_pos >= allocated_weights) {
              allocated_weights = allocated_weights * 2;
              weights = realloc(weights, sizeof(vertex_weight) * allocated_weights);
            }
  
            weights[vert_pos] = vw;
            
            vert_index++;
          }
          
          int_list_push_back(tri_list, vert_pos);
          
        } else {
          
          if (vert_index == 0) {
            strcpy(state_material, line);
          }
          
          else if (strcmp(state_material, line)) {
            
            strcpy(state_material, line);
            
            mesh* m = malloc(sizeof(mesh));
            m->num_verts = vert_list->num_items;
            m->num_triangles = tri_list->num_items / 3;
            
            m->verticies = malloc(sizeof(vertex) * m->num_verts);
            m->triangles = malloc(sizeof(int) * m->num_triangles * 3);
            
            for(int i = 0; i < m->num_verts; i++) {
              m->verticies[i] = vertex_list_get(vert_list, i);
            }
            
            for(int i = 0; i < m->num_triangles * 3; i+=3) {
              m->triangles[i+0] = int_list_get(tri_list, i+2);
              m->triangles[i+1] = int_list_get(tri_list, i+1);
              m->triangles[i+2] = int_list_get(tri_list, i+0);
            }
            
            mesh_generate_tangents(m);
            renderable_add_mesh_rigged(r, m, weights);
            mesh_delete(m);
            
            vertex_hashtable_delete(hashes);
            vertex_list_delete(vert_list);
            int_list_delete(tri_list);
            
            vert_index = 0;
            hashes = vertex_hashtable_new(1024);
            vert_list = vertex_list_new();
            tri_list = int_list_new();
            
            allocated_weights = 1024;
            weights = realloc(weights, sizeof(vertex_weight) * 1024);
            
          }
          
        }
        
      }
      
    }
    
    SDL_RWclose(file);
    
    mesh* m = malloc(sizeof(mesh));
    m->num_verts = vert_list->num_items;
    m->num_triangles = tri_list->num_items / 3;
    m->verticies = malloc(sizeof(vertex) * m->num_verts);
    m->triangles = malloc(sizeof(int) * m->num_triangles * 3);
    
    for(int i = 0; i < m->num_verts; i++) {
      m->verticies[i] = vertex_list_get(vert_list, i);
    }
    
    for(int i = 0; i < m->num_triangles * 3; i+=3) {
      m->triangles[i+0] = int_list_get(tri_list, i+2);
      m->triangles[i+1] = int_list_get(tri_list, i+1);
      m->triangles[i+2] = int_list_get(tri_list, i+0);
    }
    
    mesh_generate_tangents(m);  
    renderable_add_mesh_rigged(r, m, weights);
    mesh_delete(m);
    
    vertex_hashtable_delete(hashes);
    vertex_list_delete(vert_list);
    int_list_delete(tri_list);
    free(weights);
  
    fpath mat_file;
    fpath bmf_file;
    fpath fileid;
    
    SDL_PathFileLocation(mat_file.ptr, filename);
    SDL_PathFileLocation(bmf_file.ptr, filename);
    SDL_PathFileName(fileid.ptr, filename);
    
    strcat(mat_file.ptr, fileid.ptr);
    strcat(mat_file.ptr, ".mat");
    
    if (file_exists(mat_file)) {
      r->material = asset_hndl_new(mat_file);
    }
    
    strcat(bmf_file.ptr, fileid.ptr);
    strcat(bmf_file.ptr, ".bmf");
    bmf_save_file(r, bmf_file.ptr);
    
    return r;
  }
  
  renderable* ply_load_file(char* filename) {
    
    SDL_RWops* file = SDL_RWFromFile(filename, "r");
    
    if(file == NULL) {
      error("Could not load file %s", filename);
    }
    
    renderable* r = renderable_new();
    
    mesh* curr_mesh = NULL;
    int vert_id = 0;
    int tri_id = 0;
    
    char line[1024];
    while(SDL_RWreadline(file, line, 1024)) {
          
      if (strstr(line, "ply")) {
        
        if (curr_mesh != NULL) {
          mesh_generate_tangents(curr_mesh);
          renderable_add_mesh(r, curr_mesh);
          mesh_delete(curr_mesh);
        }
      
        curr_mesh = mesh_new();
        vert_id = 0;
        tri_id = 0;
      }
      
      int vert_count = 0;
      if (sscanf(line, "element vertex %i", &vert_count)) {
        curr_mesh->num_verts = vert_count;
        curr_mesh->verticies = malloc(sizeof(vertex) * vert_count);
      }
      
      int tri_count = 0;
      if (sscanf(line, "element face %i", &tri_count)) {
        curr_mesh->num_triangles = tri_count;
        curr_mesh->triangles = malloc(sizeof(uint32_t) * tri_count * 3);
      }
      
      vertex v;
      int r, g, b, a;
      if (sscanf(line, "%f %f %f %f %f %f %f %f %i %i %i %i",
        &v.position.x, &v.position.y, &v.position.z,
        &v.normal.x, &v.normal.y, &v.normal.z,
        &v.uvs.x, &v.uvs.y, &r, &g, &b, &a) == 12) {
        
        v.color = vec4_new(
          (float)r / 255.0, (float)g / 255.0, 
          (float)b / 255.0, (float)a / 255.0);
        
        curr_mesh->verticies[vert_id] = v; vert_id++;
      }
      
      int i0, i1, i2;
      if (sscanf(line, "3 %i %i %i", &i0, &i1, &i2) == 3) {
        curr_mesh->triangles[tri_id] = i0; tri_id++;
        curr_mesh->triangles[tri_id] = i1; tri_id++;
        curr_mesh->triangles[tri_id] = i2; tri_id++;
      }
      
    }
    
    SDL_RWclose(file);
    
    if (curr_mesh != NULL) {
      mesh_generate_tangents(curr_mesh);
      renderable_add_mesh(r, curr_mesh);
      mesh_delete(curr_mesh);
    }
    
    fpath mat_file;
    fpath bmf_file;
    fpath fileid;
    
    SDL_PathFileLocation(mat_file.ptr, filename);
    SDL_PathFileLocation(bmf_file.ptr, filename);
    SDL_PathFileName(fileid.ptr, filename);
    
    strcat(mat_file.ptr, fileid.ptr);
    strcat(mat_file.ptr, ".mat");
    
    if (file_exists(mat_file)) {
      r->material = asset_hndl_new(mat_file);
    }
    
    strcat(bmf_file.ptr, fileid.ptr);
    strcat(bmf_file.ptr, ".bmf");
    bmf_save_file(r, bmf_file.ptr);
    
    return r;
    
  }
  
  