Browse Source

Testing workflow with branches - this should merge changes from "master" branch to "materials".

unknown 14 years ago
parent
commit
ed6f2ccd79
1 changed files with 1194 additions and 1194 deletions
  1. 1194 1194
      utils/exporters/convert_obj_threejs_slim.py

+ 1194 - 1194
utils/exporters/convert_obj_threejs_slim.py

@@ -1,1195 +1,1195 @@
-"""Convert Wavefront OBJ / MTL files into Three.js (slim models version, to be used with web worker based ascii / binary loader)
-
--------------------------
-How to use this converter
--------------------------
-
-python convert_obj_threejs_slim.py -i infile.obj -o outfile.js [-a center|top|bottom] [-s smooth|flat] [-t ascii|binary]
-
-Notes: 
-
-    - by default, model is centered (middle of bounding box goes to 0,0,0),
-      uses smooth shading (if there were vertex normals in the original 
-      model) and is in ASCII format.
- 
-    - binary conversion will create two files: 
-        outfile.js  (materials)
-        outfile.bin (binary buffers)
-    
---------------------------------------------------
-How to use generated JS file in your HTML document
---------------------------------------------------
-
-    <script type="text/javascript" src="Three.js"></script>
-    
-    ...
-    
-    <script type="text/javascript">
-        ...
-        
-        var loader = new THREE.Loader();
-        
-        // load ascii model
-        loader.loadAscii( "Model_slim.js", function( geometry ) { createScene( geometry) }, path_to_textures );
-
-        // load binary model
-        loader.loadBinary( "Model_bin.js", function( geometry ) { createScene( geometry) }, path_to_textures );
-
-        function createScene( geometry ) {
-            
-            var normalizeUVsFlag = 1; // set to 1 if canvas render has missing materials
-            var mesh = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial(), normalizeUVsFlag );
-            
-        }
-        
-        ...
-    </script>
-    
--------------------------------------
-Parsers based on formats descriptions
--------------------------------------
-
-    http://en.wikipedia.org/wiki/Obj
-    http://en.wikipedia.org/wiki/Material_Template_Library
-    
--------------------
-Current limitations
--------------------
-
-    - for the moment, only diffuse color and texture are used 
-      (will need to extend shaders / renderers / materials in Three)
-     
-    - models can have more than 65,536 vertices,
-      but in most cases it will not work well with browsers,
-      which currently seem to have troubles with handling
-      large JS files
-       
-    - texture coordinates can be wrong in canvas renderer
-      (there is crude normalization, but it doesn't
-       work for all cases)
-       
-    - smoothing can be turned on/off only for the whole mesh
-
----------------------------------------------- 
-How to get proper OBJ + MTL files with Blender
-----------------------------------------------
-
-    0. Remove default cube (press DEL and ENTER)
-    
-    1. Import / create model
-    
-    2. Select all meshes (Select -> Select All by Type -> Mesh)
-    
-    3. Export to OBJ (File -> Export -> Wavefront .obj) [*]
-        - enable following options in exporter
-            Material Groups
-            Rotate X90
-            Apply Modifiers
-            High Quality Normals
-            Copy Images
-            Selection Only
-            Objects as OBJ Objects
-            UVs
-            Normals
-            Materials
-            Edges
-            
-        - select empty folder
-        - give your exported file name with "obj" extension
-        - click on "Export OBJ" button
-        
-    4. Your model is now all files in this folder (OBJ, MTL, number of images)
-        - this converter assumes all files staying in the same folder,
-          (OBJ / MTL files use relative paths)
-          
-        - for WebGL, textures must be power of 2 sized
-
-    [*] If OBJ export fails (Blender 2.54 beta), patch your Blender installation 
-        following instructions here:
-            
-            http://www.blendernation.com/2010/09/12/blender-2-54-beta-released/
-            
-------
-Author
-------
-AlteredQualia http://alteredqualia.com
-
-"""
-
-import fileinput
-import operator
-import random
-import os.path
-import getopt
-import sys
-import struct
-import math
-
-# #####################################################
-# Configuration
-# #####################################################
-ALIGN = "center"    # center bottom top none
-SHADING = "smooth"  # smooth flat 
-TYPE = "ascii"      # ascii binary
-
-# default colors for debugging (each material gets one distinct color): 
-# white, red, green, blue, yellow, cyan, magenta
-COLORS = [0xeeeeee, 0xee0000, 0x00ee00, 0x0000ee, 0xeeee00, 0x00eeee, 0xee00ee]
-
-# #####################################################
-# Templates
-# #####################################################
-TEMPLATE_FILE_ASCII = u"""\
-// Converted from: %(fname)s
-//  vertices: %(nvertex)d
-//  faces: %(nface)d 
-//  materials: %(nmaterial)d
-//
-//  Generated with OBJ -> Three.js converter
-//  http://github.com/alteredq/three.js/blob/master/utils/exporters/convert_obj_threejs_slim.py
-
-
-var model = {
-    'materials': [%(materials)s],
-
-    'normals': [%(normals)s],
-
-    'vertices': [%(vertices)s],
-
-    'uvs': [%(uvs)s],
-
-    'triangles': [%(triangles)s],
-    'triangles_n': [%(triangles_n)s],
-    'triangles_uv': [%(triangles_uv)s],
-    'triangles_n_uv': [%(triangles_n_uv)s],
-
-    'quads': [%(quads)s],
-    'quads_n': [%(quads_n)s],
-    'quads_uv': [%(quads_uv)s],
-    'quads_n_uv': [%(quads_n_uv)s],
-
-    'end': (new Date).getTime()
-    }
-    
-postMessage( model );
-"""
-
-TEMPLATE_FILE_BIN = u"""\
-// Converted from: %(fname)s
-//  vertices: %(nvertex)d
-//  faces: %(nface)d 
-//  materials: %(nmaterial)d
-//
-//  Generated with OBJ -> Three.js converter
-//  http://github.com/alteredq/three.js/blob/master/utils/exporters/convert_obj_threejs_slim.py
-
-
-var model = {
-    'materials': [%(materials)s],
-
-    'buffers': '%(buffers)s',
-
-    'end': (new Date).getTime()
-    }
-    
-postMessage( model );
-"""
-
-TEMPLATE_VERTEX = "%f,%f,%f"
-
-TEMPLATE_UV_TRI = "%f,%f,%f,%f,%f,%f"
-TEMPLATE_UV_QUAD = "%f,%f,%f,%f,%f,%f,%f,%f"
-
-TEMPLATE_TRI = "%d,%d,%d,%d"
-TEMPLATE_QUAD = "%d,%d,%d,%d,%d"
-
-TEMPLATE_TRI_UV = "%d,%d,%d,%d,%d,%d,%d"
-TEMPLATE_QUAD_UV = "%d,%d,%d,%d,%d,%d,%d,%d,%d"
-
-TEMPLATE_TRI_N = "%d,%d,%d,%d,%d,%d,%d"
-TEMPLATE_QUAD_N = "%d,%d,%d,%d,%d,%d,%d,%d,%d"
-
-TEMPLATE_TRI_N_UV = "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d"
-TEMPLATE_QUAD_N_UV = "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d"
-
-TEMPLATE_N = "%f,%f,%f"
-TEMPLATE_UV = "%f,%f"
-
-# #####################################################
-# Utils
-# #####################################################
-def file_exists(filename):
-    """Return true if file exists and is accessible for reading.
-    
-    Should be safer than just testing for existence due to links and 
-    permissions magic on Unix filesystems.
-    
-    @rtype: boolean
-    """
-    
-    try:
-        f = open(filename, 'r')
-        f.close()
-        return True
-    except IOError:
-        return False
-
-    
-def get_name(fname):
-    """Create model name based of filename ("path/fname.js" -> "fname").
-    """
-    
-    return os.path.basename(fname).split(".")[0]
-
-def bbox(vertices):
-    """Compute bounding box of vertex array.
-    """
-    
-    if len(vertices)>0:
-        minx = maxx = vertices[0][0]
-        miny = maxy = vertices[0][1]
-        minz = maxz = vertices[0][2]
-        
-        for v in vertices[1:]:
-            if v[0]<minx:
-                minx = v[0]
-            elif v[0]>maxx:
-                maxx = v[0]
-            
-            if v[1]<miny:
-                miny = v[1]
-            elif v[1]>maxy:
-                maxy = v[1]
-
-            if v[2]<minz:
-                minz = v[2]
-            elif v[2]>maxz:
-                maxz = v[2]
-
-        return { 'x':[minx,maxx], 'y':[miny,maxy], 'z':[minz,maxz] }
-    
-    else:
-        return { 'x':[0,0], 'y':[0,0], 'z':[0,0] }
-
-def translate(vertices, t):
-    """Translate array of vertices by vector t.
-    """
-    
-    for i in xrange(len(vertices)):
-        vertices[i][0] += t[0]
-        vertices[i][1] += t[1]
-        vertices[i][2] += t[2]
-        
-def center(vertices):
-    """Center model (middle of bounding box).
-    """
-    
-    bb = bbox(vertices)
-    
-    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
-    cy = bb['y'][0] + (bb['y'][1] - bb['y'][0])/2.0
-    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
-    
-    translate(vertices, [-cx,-cy,-cz])
-
-def top(vertices):
-    """Align top of the model with the floor (Y-axis) and center it around X and Z.
-    """
-    
-    bb = bbox(vertices)
-    
-    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
-    cy = bb['y'][1]
-    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
-    
-    translate(vertices, [-cx,-cy,-cz])
-    
-def bottom(vertices):
-    """Align bottom of the model with the floor (Y-axis) and center it around X and Z.
-    """
-    
-    bb = bbox(vertices)
-    
-    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
-    cy = bb['y'][0] 
-    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
-    
-    translate(vertices, [-cx,-cy,-cz])
-
-def normalize(v):
-    """Normalize 3d vector"""
-    
-    l = math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2])
-    v[0] /= l
-    v[1] /= l
-    v[2] /= l
-
-# #####################################################
-# MTL parser
-# #####################################################
-def parse_mtl(fname):
-    """Parse MTL file.
-    """
-    
-    materials = {}
-    
-    for line in fileinput.input(fname):
-        chunks = line.split()
-        if len(chunks) > 0:
-            
-            # Material start
-            # newmtl identifier
-            if chunks[0] == "newmtl" and len(chunks) == 2:
-                identifier = chunks[1]
-                if not identifier in materials:
-                    materials[identifier] = {}
-
-            # Diffuse color
-            # Kd 1.000 1.000 1.000
-            if chunks[0] == "Kd" and len(chunks) == 4:
-                materials[identifier]["col_diffuse"] = [float(chunks[1]), float(chunks[2]), float(chunks[3])]
-
-            # Ambient color
-            # Ka 1.000 1.000 1.000
-            if chunks[0] == "Ka" and len(chunks) == 4:
-                materials[identifier]["col_ambient"] = [float(chunks[1]), float(chunks[2]), float(chunks[3])]
-
-            # Specular color
-            # Ks 1.000 1.000 1.000
-            if chunks[0] == "Ks" and len(chunks) == 4:
-                materials[identifier]["col_specular"] = [float(chunks[1]), float(chunks[2]), float(chunks[3])]
-
-            # Specular coefficient
-            # Ns 154.000
-            if chunks[0] == "Ns" and len(chunks) == 2:
-                materials[identifier]["specular_coef"] = float(chunks[1])
-
-            # Transparency
-            # Tr 0.9 or d 0.9
-            if (chunks[0] == "Tr" or chunks[0] == "d") and len(chunks) == 2:
-                materials[identifier]["transparency"] = float(chunks[1])
-
-            # Optical density
-            # Ni 1.0
-            if chunks[0] == "Ni" and len(chunks) == 2:                
-                materials[identifier]["optical_density"] = float(chunks[1])
-
-            # Diffuse texture
-            # map_Kd texture_diffuse.jpg
-            if chunks[0] == "map_Kd" and len(chunks) == 2:                
-                materials[identifier]["map_diffuse"] = chunks[1]
-
-            # Ambient texture
-            # map_Ka texture_ambient.jpg
-            if chunks[0] == "map_Ka" and len(chunks) == 2:
-                materials[identifier]["map_ambient"] = chunks[1]
-
-            # Specular texture
-            # map_Ks texture_specular.jpg
-            if chunks[0] == "map_Ks" and len(chunks) == 2:
-                materials[identifier]["map_specular"] = chunks[1]
-
-            # Alpha texture
-            # map_d texture_alpha.png
-            if chunks[0] == "map_d" and len(chunks) == 2:
-                materials[identifier]["map_alpha"] = chunks[1]
-
-            # Bump texture
-            # map_bump texture_bump.jpg or bump texture_bump.jpg
-            if (chunks[0] == "map_bump" or chunks[0] == "bump") and len(chunks) == 2:
-                materials[identifier]["map_bump"] = chunks[1]
-
-            # Illumination
-            # illum 2
-            #
-            # 0. Color on and Ambient off
-            # 1. Color on and Ambient on
-            # 2. Highlight on
-            # 3. Reflection on and Ray trace on
-            # 4. Transparency: Glass on, Reflection: Ray trace on
-            # 5. Reflection: Fresnel on and Ray trace on
-            # 6. Transparency: Refraction on, Reflection: Fresnel off and Ray trace on
-            # 7. Transparency: Refraction on, Reflection: Fresnel on and Ray trace on
-            # 8. Reflection on and Ray trace off
-            # 9. Transparency: Glass on, Reflection: Ray trace off
-            # 10. Casts shadows onto invisible surfaces
-            if chunks[0] == "illum" and len(chunks) == 2:
-                materials[identifier]["illumination"] = int(chunks[1])
-
-    return materials
-    
-# #####################################################
-# OBJ parser
-# #####################################################
-def parse_vertex(text):
-    """Parse text chunk specifying single vertex.
-    
-    Possible formats:
-        vertex index
-        vertex index / texture index
-        vertex index / texture index / normal index
-        vertex index / / normal index
-    """
-    
-    v = 0
-    t = 0
-    n = 0
-    
-    chunks = text.split("/")
-    
-    v = int(chunks[0])
-    if len(chunks) > 1:
-        if chunks[1]:
-            t = int(chunks[1])
-    if len(chunks) > 2:
-        if chunks[2]:
-            n = int(chunks[2])
-            
-    return { 'v':v, 't':t, 'n':n }
-    
-def parse_obj(fname):
-    """Parse OBJ file.
-    """
-    
-    vertices = []
-    normals = []
-    uvs = []
-    
-    faces = []
-    
-    materials = {}
-    mcounter = 0
-    mcurrent = 0
-    
-    mtllib = ""
-    
-    # current face state
-    group = 0
-    object = 0
-    smooth = 0
-    
-    for line in fileinput.input(fname):
-        chunks = line.split()
-        if len(chunks) > 0:
-            
-            # Vertices as (x,y,z) coordinates
-            # v 0.123 0.234 0.345
-            if chunks[0] == "v" and len(chunks) == 4:
-                x = float(chunks[1])
-                y = float(chunks[2])
-                z = float(chunks[3])
-                vertices.append([x,y,z])
-
-            # Normals in (x,y,z) form; normals might not be unit
-            # vn 0.707 0.000 0.707
-            if chunks[0] == "vn" and len(chunks) == 4:
-                x = float(chunks[1])
-                y = float(chunks[2])
-                z = float(chunks[3])
-                normals.append([x,y,z])
-
-            # Texture coordinates in (u,v[,w]) coordinates, w is optional
-            # vt 0.500 -1.352 [0.234]
-            if chunks[0] == "vt" and len(chunks) >= 3:
-                u = float(chunks[1])
-                v = float(chunks[2])
-                w = 0
-                if len(chunks)>3:
-                    w = float(chunks[3])
-                uvs.append([u,v,w])
-
-            # Face
-            if chunks[0] == "f" and len(chunks) >= 4:
-                vertex_index = []
-                uv_index = []
-                normal_index = []
-                
-                for v in chunks[1:]:
-                    vertex = parse_vertex(v)
-                    if vertex['v']:
-                        vertex_index.append(vertex['v'])
-                    if vertex['t']:
-                        uv_index.append(vertex['t'])
-                    if vertex['n']:
-                        normal_index.append(vertex['n'])
-                
-                faces.append({
-                    'vertex':vertex_index, 
-                    'uv':uv_index,
-                    'normal':normal_index,
-                    
-                    'material':mcurrent,
-                    'group':group, 
-                    'object':object, 
-                    'smooth':smooth,
-                    })
-    
-            # Group
-            if chunks[0] == "g" and len(chunks) == 2:
-                group = chunks[1]
-
-            # Object
-            if chunks[0] == "o" and len(chunks) == 2:
-                object = chunks[1]
-
-            # Materials definition
-            if chunks[0] == "mtllib" and len(chunks) == 2:
-                mtllib = chunks[1]
-                
-            # Material
-            if chunks[0] == "usemtl" and len(chunks) == 2:
-                material = chunks[1]
-                if not material in materials:
-                    mcurrent = mcounter
-                    materials[material] = mcounter
-                    mcounter += 1
-                else:
-                    mcurrent = materials[material]
-
-            # Smooth shading
-            if chunks[0] == "s" and len(chunks) == 2:
-                smooth = chunks[1]
-
-    return faces, vertices, uvs, normals, materials, mtllib
-    
-# #####################################################
-# Generator
-# #####################################################
-def generate_vertex(v):
-    return TEMPLATE_VERTEX % (v[0], v[1], v[2])
-    
-def generate_triangle(f):
-    v = f['vertex']
-    return TEMPLATE_TRI % (v[0]-1, v[1]-1, v[2]-1, 
-                           f['material'])
-
-def generate_triangle_uv(f):
-    v = f['vertex']
-    uv = f['uv']
-    return TEMPLATE_TRI_UV % (v[0]-1, v[1]-1, v[2]-1, 
-                              f['material'], 
-                              uv[0]-1, uv[1]-1, uv[2]-1)
-
-def generate_triangle_n(f):
-    v = f['vertex']
-    n = f['normal']
-    return TEMPLATE_TRI_N % (v[0]-1, v[1]-1, v[2]-1, 
-                             f['material'], 
-                             n[0]-1, n[1]-1, n[2]-1)
-
-def generate_triangle_n_uv(f):
-    v = f['vertex']
-    n = f['normal']
-    uv = f['uv']
-    return TEMPLATE_TRI_N_UV % (v[0]-1, v[1]-1, v[2]-1, 
-                                f['material'], 
-                                n[0]-1, n[1]-1, n[2]-1, 
-                                uv[0]-1, uv[1]-1, uv[2]-1)
-
-def generate_quad(f):
-    vi = f['vertex']
-    return TEMPLATE_QUAD % (vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1, 
-                            f['material'])
-
-def generate_quad_uv(f):
-    v = f['vertex']
-    uv = f['uv']
-    return TEMPLATE_QUAD_UV % (v[0]-1, v[1]-1, v[2]-1, v[3]-1, 
-                               f['material'], 
-                               uv[0]-1, uv[1]-1, uv[2]-1, uv[3]-1)
-
-def generate_quad_n(f):
-    v = f['vertex']
-    n = f['normal']
-    return TEMPLATE_QUAD_N % (v[0]-1, v[1]-1, v[2]-1, v[3]-1, 
-                              f['material'],
-                              n[0]-1, n[1]-1, n[2]-1, n[3]-1)
-
-def generate_quad_n_uv(f):
-    v = f['vertex']
-    n = f['normal']
-    uv = f['uv']
-    return TEMPLATE_QUAD_N_UV % (v[0]-1, v[1]-1, v[2]-1, v[3]-1, 
-                                 f['material'],
-                                 n[0]-1, n[1]-1, n[2]-1, n[3]-1,
-                                 uv[0]-1, uv[1]-1, uv[2]-1, uv[3]-1)
-
-def generate_normal(n):
-    return TEMPLATE_N % (n[0], n[1], n[2])
-
-def generate_uv(uv):
-    return TEMPLATE_UV % (uv[0], 1.0 - uv[1])
-
-# #####################################################
-# Materials
-# #####################################################
-def generate_color(i):
-    """Generate hex color corresponding to integer.
-    
-    Colors should have well defined ordering.
-    First N colors are hardcoded, then colors are random 
-    (must seed random number  generator with deterministic value 
-    before getting colors).
-    """
-    
-    if i < len(COLORS):
-        return "0x%06x" % COLORS[i]
-    else:
-        return "0x%06x" % int(0xffffff * random.random())
-        
-def value2string(v):
-    if type(v)==str and v[0] != "0":
-        return '"%s"' % v
-    return str(v)
-    
-def generate_materials(mtl, materials):
-    """Generate JS array of materials objects
-    
-    JS material objects are basically prettified one-to-one 
-    mappings of MTL properties in JSON format.
-    """
-    
-    mtl_array = []
-    for m in mtl:
-        index = materials[m]
-        
-        # add debug information
-        #  materials should be sorted according to how
-        #  they appeared in OBJ file (for the first time)
-        #  this index is identifier used in face definitions
-        mtl[m]['a_dbg_name'] = m
-        mtl[m]['a_dbg_index'] = index
-        mtl[m]['a_dbg_color'] = generate_color(index)
-        
-        mtl_raw = ",\n".join(['\t"%s" : %s' % (n, value2string(v)) for n,v in sorted(mtl[m].items())])
-        mtl_string = "\t{\n%s\n\t}" % mtl_raw
-        mtl_array.append([index, mtl_string])
-        
-    return ",\n\n".join([m for i,m in sorted(mtl_array)])
-
-def generate_mtl(materials):
-    """Generate dummy materials (if there is no MTL file).
-    """
-    
-    mtl = {}
-    for m in materials:
-        index = materials[m]
-        mtl[m] = {
-            'a_dbg_name': m,
-            'a_dbg_index': index,
-            'a_dbg_color': generate_color(index)
-        }
-    return mtl
-    
-def generate_materials_string(materials, mtllib):
-    """Generate final materials string.
-    """
-
-    random.seed(42) # to get well defined color order for materials
-    
-    # default materials with debug colors for when
-    # there is no specified MTL / MTL loading failed,
-    # or if there were no materials / null materials
-    if not materials:
-        materials = { 'default':0 }
-    mtl = generate_mtl(materials)
-    
-    if mtllib:
-        # create full pathname for MTL (included from OBJ)
-        path = os.path.dirname(infile)
-        fname = os.path.join(path, mtllib)
-        
-        if file_exists(fname):
-            # override default materials with real ones from MTL
-            # (where they exist, otherwise keep defaults)
-            mtl.update(parse_mtl(fname))
-        
-        else:
-            print "Couldn't find [%s]" % fname
-    
-    return generate_materials(mtl, materials)
-    
-# #####################################################
-# Faces
-# #####################################################
-def is_triangle_flat(f):
-    return len(f['vertex'])==3 and not (f["normal"] and SHADING == "smooth") and not f['uv']
-    
-def is_triangle_flat_uv(f):
-    return len(f['vertex'])==3 and not (f["normal"] and SHADING == "smooth") and len(f['uv'])==3
-
-def is_triangle_smooth(f):
-    return len(f['vertex'])==3 and f["normal"] and SHADING == "smooth" and not f['uv']
-    
-def is_triangle_smooth_uv(f):
-    return len(f['vertex'])==3 and f["normal"] and SHADING == "smooth" and len(f['uv'])==3
-
-def is_quad_flat(f):
-    return len(f['vertex'])==4 and not (f["normal"] and SHADING == "smooth") and not f['uv']
-    
-def is_quad_flat_uv(f):
-    return len(f['vertex'])==4 and not (f["normal"] and SHADING == "smooth") and len(f['uv'])==4
-
-def is_quad_smooth(f):
-    return len(f['vertex'])==4 and f["normal"] and SHADING == "smooth" and not f['uv']
-
-def is_quad_smooth_uv(f):
-    return len(f['vertex'])==4 and f["normal"] and SHADING == "smooth" and len(f['uv'])==4
-
-def sort_faces(faces):
-    data = {
-    'triangles_flat': [],
-    'triangles_flat_uv': [],
-    'triangles_smooth': [],
-    'triangles_smooth_uv': [],
-    
-    'quads_flat': [],
-    'quads_flat_uv': [],
-    'quads_smooth': [],
-    'quads_smooth_uv': []
-    }
-    
-    for f in faces:
-        if is_triangle_flat(f):
-            data['triangles_flat'].append(f)
-        elif is_triangle_flat_uv(f):
-            data['triangles_flat_uv'].append(f)
-        elif is_triangle_smooth(f):
-            data['triangles_smooth'].append(f)
-        elif is_triangle_smooth_uv(f):
-            data['triangles_smooth_uv'].append(f)
-        
-        elif is_quad_flat(f):
-            data['quads_flat'].append(f)
-        elif is_quad_flat_uv(f):
-            data['quads_flat_uv'].append(f)
-        elif is_quad_smooth(f):
-            data['quads_smooth'].append(f)
-        elif is_quad_smooth_uv(f):
-            data['quads_smooth_uv'].append(f)
-
-    return data
-
-# #####################################################
-# API - ASCII converter
-# #####################################################
-def convert_ascii(infile, outfile):
-    """Convert infile.obj to outfile.js
-    
-    Here is where everything happens. If you need to automate conversions,
-    just import this file as Python module and call this method.
-    """
-    
-    if not file_exists(infile):
-        print "Couldn't find [%s]" % infile
-        return
-        
-    faces, vertices, uvs, normals, materials, mtllib = parse_obj(infile)
-    
-    if ALIGN == "center":
-        center(vertices)
-    elif ALIGN == "bottom":
-        bottom(vertices)
-    elif ALIGN == "top":
-        top(vertices)
-    
-    normals_string = ""
-    if SHADING == "smooth":
-        normals_string = ",".join(generate_normal(n) for n in normals)
-        
-    sfaces = sort_faces(faces)
-    
-    text = TEMPLATE_FILE_ASCII % {
-    "name"          : get_name(outfile),
-    "vertices"      : ",".join(generate_vertex(v) for v in vertices),
-    "triangles"     : ",".join(generate_triangle(f) for f in sfaces['triangles_flat']),
-    "triangles_n"   : ",".join(generate_triangle_n(f) for f in sfaces['triangles_smooth']),
-    "triangles_uv"  : ",".join(generate_triangle_uv(f) for f in sfaces['triangles_flat_uv']),
-    "triangles_n_uv": ",".join(generate_triangle_n_uv(f) for f in sfaces['triangles_smooth_uv']),
-    "quads"         : ",".join(generate_quad(f) for f in sfaces['quads_flat']),
-    "quads_n"       : ",".join(generate_quad_n(f) for f in sfaces['quads_smooth']),
-    "quads_uv"      : ",".join(generate_quad_uv(f) for f in sfaces['quads_flat_uv']),
-    "quads_n_uv"    : ",".join(generate_quad_n_uv(f) for f in sfaces['quads_smooth_uv']),
-    "uvs"           : ",".join(generate_uv(uv) for uv in uvs),
-    "normals"       : normals_string,
-    
-    "materials" : generate_materials_string(materials, mtllib),
-    
-    "fname"     : infile,
-    "nvertex"   : len(vertices),
-    "nface"     : len(faces),
-    "nmaterial" : len(materials)
-    }
-    
-    out = open(outfile, "w")
-    out.write(text)
-    out.close()
-    
-    print "%d vertices, %d faces, %d materials" % (len(vertices), len(faces), len(materials))
-        
-    
-# #############################################################################
-# API - Binary converter
-# #############################################################################
-def convert_binary(infile, outfile):
-    """Convert infile.obj to outfile.js + outfile.bin    
-    """
-    
-    if not file_exists(infile):
-        print "Couldn't find [%s]" % infile
-        return
-    
-    binfile = get_name(outfile) + ".bin"
-    
-    faces, vertices, uvs, normals, materials, mtllib = parse_obj(infile)
-    
-    if ALIGN == "center":
-        center(vertices)
-    elif ALIGN == "bottom":
-        bottom(vertices)
-    elif ALIGN == "top":
-        top(vertices)    
-    
-    sfaces = sort_faces(faces)
-    
-    # ###################
-    # generate JS file
-    # ###################
-    
-    text = TEMPLATE_FILE_BIN % {
-    "name"       : get_name(outfile),
-    
-    "materials" : generate_materials_string(materials, mtllib),
-    "buffers"   : binfile,
-    
-    "fname"     : infile,
-    "nvertex"   : len(vertices),
-    "nface"     : len(faces),
-    "nmaterial" : len(materials)
-    }
-    
-    out = open(outfile, "w")
-    out.write(text)
-    out.close()
-    
-    # ###################
-    # generate BIN file
-    # ###################
-    
-    if SHADING == "smooth":
-        nnormals = len(normals)
-    else:
-        nnormals = 0
-        
-    buffer = []
-
-    # header
-    # ------
-    header_bytes  = struct.calcsize('<8s')
-    header_bytes += struct.calcsize('<BBBBBBBB')
-    header_bytes += struct.calcsize('<IIIIIIIIIII')
-    
-    # signature
-    signature = struct.pack('<8s', 'Three.js')
-    
-    # metadata (all data is little-endian)
-    vertex_coordinate_bytes = 4
-    normal_coordinate_bytes = 1
-    uv_coordinate_bytes = 4
-    
-    vertex_index_bytes = 4
-    normal_index_bytes = 4
-    uv_index_bytes = 4
-    material_index_bytes = 2
-    
-    # header_bytes            unsigned char   1
-    
-    # vertex_coordinate_bytes unsigned char   1
-    # normal_coordinate_bytes unsigned char   1
-    # uv_coordinate_bytes     unsigned char   1
-    
-    # vertex_index_bytes      unsigned char   1
-    # normal_index_bytes      unsigned char   1
-    # uv_index_bytes          unsigned char   1
-    # material_index_bytes    unsigned char   1
-    bdata = struct.pack('<BBBBBBBB', header_bytes,
-                               vertex_coordinate_bytes, 
-                               normal_coordinate_bytes,
-                               uv_coordinate_bytes,
-                               vertex_index_bytes, 
-                               normal_index_bytes,
-                               uv_index_bytes,
-                               material_index_bytes)
-
-    # nvertices       unsigned int    4
-    # nnormals        unsigned int    4
-    # nuvs            unsigned int    4
-    
-    # ntri_flat       unsigned int    4
-    # ntri_smooth     unsigned int    4
-    # ntri_flat_uv    unsigned int    4
-    # ntri_smooth_uv  unsigned int    4
-    
-    # nquad_flat      unsigned int    4
-    # nquad_smooth    unsigned int    4
-    # nquad_flat_uv   unsigned int    4
-    # nquad_smooth_uv unsigned int    4    
-    ndata = struct.pack('<IIIIIIIIIII', len(vertices), 
-                               nnormals,
-                               len(uvs),
-                               len(sfaces['triangles_flat']), 
-                               len(sfaces['triangles_smooth']),
-                               len(sfaces['triangles_flat_uv']), 
-                               len(sfaces['triangles_smooth_uv']),
-                               len(sfaces['quads_flat']),
-                               len(sfaces['quads_smooth']),
-                               len(sfaces['quads_flat_uv']),
-                               len(sfaces['quads_smooth_uv']))
-    buffer.append(signature)
-    buffer.append(bdata)
-    buffer.append(ndata)
-        
-    # 1. vertices
-    # ------------
-    # x float   4
-    # y float   4
-    # z float   4
-    for v in vertices:
-        data = struct.pack('<fff', v[0], v[1], v[2]) 
-        buffer.append(data)
-
-    # 2. normals
-    # ---------------
-    # x signed char 1
-    # y signed char 1
-    # z signed char 1
-    if SHADING == "smooth":
-        for n in normals:
-            normalize(n)
-            data = struct.pack('<bbb', math.floor(n[0]*127+0.5),
-                                       math.floor(n[1]*127+0.5),
-                                       math.floor(n[2]*127+0.5))
-            buffer.append(data)
-    
-    # 3. uvs
-    # -----------
-    # u float   4
-    # v float   4
-    for uv in uvs:
-        data = struct.pack('<ff', uv[0], 1.0-uv[1])
-        buffer.append(data)
-    
-    # 4. flat triangles
-    # ------------------
-    # a unsigned int   4
-    # b unsigned int   4
-    # c unsigned int   4
-    # m unsigned short 2
-    for f in sfaces['triangles_flat']:
-        vi = f['vertex']
-        data = struct.pack('<IIIH', 
-                            vi[0]-1, vi[1]-1, vi[2]-1, 
-                            f['material'])
-        buffer.append(data)
-
-    # 5. smooth triangles
-    # -------------------
-    # a  unsigned int   4
-    # b  unsigned int   4
-    # c  unsigned int   4
-    # m  unsigned short 2
-    # na unsigned int   4
-    # nb unsigned int   4
-    # nc unsigned int   4
-    for f in sfaces['triangles_smooth']:
-        vi = f['vertex']
-        ni = f['normal']
-        data = struct.pack('<IIIHIII', 
-                            vi[0]-1, vi[1]-1, vi[2]-1, 
-                            f['material'], 
-                            ni[0]-1, ni[1]-1, ni[2]-1)
-        buffer.append(data)
-
-    # 6. flat triangles uv
-    # --------------------
-    # a  unsigned int    4
-    # b  unsigned int    4
-    # c  unsigned int    4
-    # m  unsigned short  2
-    # ua unsigned int    4
-    # ub unsigned int    4
-    # uc unsigned int    4
-    for f in sfaces['triangles_flat_uv']:
-        vi = f['vertex']
-        ui = f['uv']
-        data = struct.pack('<IIIHIII', 
-                            vi[0]-1, vi[1]-1, vi[2]-1, 
-                            f['material'], 
-                            ui[0]-1, ui[1]-1, ui[2]-1)
-        buffer.append(data)
-
-    # 7. smooth triangles uv
-    # ----------------------
-    # a  unsigned int    4
-    # b  unsigned int    4
-    # c  unsigned int    4
-    # m  unsigned short  2
-    # na unsigned int    4
-    # nb unsigned int    4
-    # nc unsigned int    4
-    # ua unsigned int    4
-    # ub unsigned int    4
-    # uc unsigned int    4
-    for f in sfaces['triangles_smooth_uv']:
-        vi = f['vertex']
-        ni = f['normal']
-        ui = f['uv']
-        data = struct.pack('<IIIHIIIIII', 
-                            vi[0]-1, vi[1]-1, vi[2]-1, 
-                            f['material'], 
-                            ni[0]-1, ni[1]-1, ni[2]-1,
-                            ui[0]-1, ui[1]-1, ui[2]-1)
-        buffer.append(data)
-
-    # 8. flat quads
-    # ------------------
-    # a unsigned int   4
-    # b unsigned int   4
-    # c unsigned int   4
-    # d unsigned int   4
-    # m unsigned short 2
-    for f in sfaces['quads_flat']:
-        vi = f['vertex']
-        data = struct.pack('<IIIIH', 
-                            vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1, 
-                            f['material'])
-        buffer.append(data)
-            
-    # 9. smooth quads
-    # -------------------
-    # a  unsigned int   4
-    # b  unsigned int   4
-    # c  unsigned int   4
-    # d  unsigned int   4
-    # m  unsigned short 2
-    # na unsigned int   4
-    # nb unsigned int   4
-    # nc unsigned int   4
-    # nd unsigned int   4
-    for f in sfaces['quads_smooth']:
-        vi = f['vertex']
-        ni = f['normal']
-        data = struct.pack('<IIIIHIIII', 
-                            vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1, 
-                            f['material'], 
-                            ni[0]-1, ni[1]-1, ni[2]-1, ni[3]-1)
-        buffer.append(data)
-    
-    # 10. flat quads uv
-    # ------------------
-    # a unsigned int   4
-    # b unsigned int   4
-    # c unsigned int   4
-    # d unsigned int   4
-    # m unsigned short 2
-    # ua unsigned int  4
-    # ub unsigned int  4
-    # uc unsigned int  4
-    # ud unsigned int  4
-    for f in sfaces['quads_flat_uv']:
-        vi = f['vertex']
-        ui = f['uv']
-        data = struct.pack('<IIIIHIIII', 
-                            vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1, 
-                            f['material'],
-                            ui[0]-1, ui[1]-1, ui[2]-1, ui[3]-1)
-        buffer.append(data)
-
-    # 11. smooth quads uv
-    # -------------------
-    # a  unsigned int   4
-    # b  unsigned int   4
-    # c  unsigned int   4
-    # d  unsigned int   4
-    # m  unsigned short 2
-    # na unsigned int   4
-    # nb unsigned int   4
-    # nc unsigned int   4
-    # nd unsigned int   4
-    # ua unsigned int   4
-    # ub unsigned int   4
-    # uc unsigned int   4
-    # ud unsigned int   4
-    for f in sfaces['quads_smooth_uv']:
-        vi = f['vertex']
-        ni = f['normal']
-        ui = f['uv']
-        data = struct.pack('<IIIIHIIIIIIII', 
-                            vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1, 
-                            f['material'], 
-                            ni[0]-1, ni[1]-1, ni[2]-1, ni[3]-1,
-                            ui[0]-1, ui[1]-1, ui[2]-1, ui[3]-1)
-        buffer.append(data)
-
-    path = os.path.dirname(outfile)
-    fname = os.path.join(path, binfile)
-
-    out = open(fname, "wb")
-    out.write("".join(buffer))
-    out.close()
-
-# #############################################################################
-# Helpers
-# #############################################################################
-def usage():
-    print "Usage: %s -i filename.obj -o filename.js [-a center|top|bottom] [-s flat|smooth] [-t binary|ascii]" % os.path.basename(sys.argv[0])
-        
-# #####################################################
-# Main
-# #####################################################
-if __name__ == "__main__":
-    
-    # get parameters from the command line
-    try:
-        opts, args = getopt.getopt(sys.argv[1:], "hi:o:a:s:t:", ["help", "input=", "output=", "align=", "shading=", "type="])
-    
-    except getopt.GetoptError:
-        usage()
-        sys.exit(2)
-        
-    infile = outfile = ""
-    
-    for o, a in opts:
-        if o in ("-h", "--help"):
-            usage()
-            sys.exit()
-        
-        elif o in ("-i", "--input"):
-            infile = a
-
-        elif o in ("-o", "--output"):
-            outfile = a
-
-        elif o in ("-a", "--align"):
-            if a in ("top", "bottom", "center"):
-                ALIGN = a
-
-        elif o in ("-s", "--shading"):
-            if a in ("flat", "smooth"):
-                SHADING = a
-                
-        elif o in ("-t", "--type"):
-            if a in ("binary", "ascii"):
-                TYPE = a
-
-    if infile == "" or outfile == "":
-        usage()
-        sys.exit(2)
-    
-    print "Converting [%s] into [%s] ..." % (infile, outfile)
-    
-    if TYPE == "ascii":
-        convert_ascii(infile, outfile)
-    elif TYPE == "binary":
-        convert_binary(infile, outfile)
+"""Convert Wavefront OBJ / MTL files into Three.js (slim models version, to be used with web worker based ascii / binary loader)
+
+-------------------------
+How to use this converter
+-------------------------
+
+python convert_obj_threejs_slim.py -i infile.obj -o outfile.js [-a center|top|bottom] [-s smooth|flat] [-t ascii|binary]
+
+Notes: 
+
+    - by default, model is centered (middle of bounding box goes to 0,0,0),
+      uses smooth shading (if there were vertex normals in the original 
+      model) and is in ASCII format.
+ 
+    - binary conversion will create two files: 
+        outfile.js  (materials)
+        outfile.bin (binary buffers)
+    
+--------------------------------------------------
+How to use generated JS file in your HTML document
+--------------------------------------------------
+
+    <script type="text/javascript" src="Three.js"></script>
+    
+    ...
+    
+    <script type="text/javascript">
+        ...
+        
+        var loader = new THREE.Loader();
+        
+        // load ascii model
+        loader.loadAscii( "Model_slim.js", function( geometry ) { createScene( geometry) }, path_to_textures );
+
+        // load binary model
+        loader.loadBinary( "Model_bin.js", function( geometry ) { createScene( geometry) }, path_to_textures );
+
+        function createScene( geometry ) {
+            
+            var normalizeUVsFlag = 1; // set to 1 if canvas render has missing materials
+            var mesh = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial(), normalizeUVsFlag );
+            
+        }
+        
+        ...
+    </script>
+    
+-------------------------------------
+Parsers based on formats descriptions
+-------------------------------------
+
+    http://en.wikipedia.org/wiki/Obj
+    http://en.wikipedia.org/wiki/Material_Template_Library
+    
+-------------------
+Current limitations
+-------------------
+
+    - for the moment, only diffuse color and texture are used 
+      (will need to extend shaders / renderers / materials in Three)
+     
+    - models can have more than 65,536 vertices,
+      but in most cases it will not work well with browsers,
+      which currently seem to have troubles with handling
+      large JS files
+       
+    - texture coordinates can be wrong in canvas renderer
+      (there is crude normalization, but it doesn't
+       work for all cases)
+       
+    - smoothing can be turned on/off only for the whole mesh
+
+---------------------------------------------- 
+How to get proper OBJ + MTL files with Blender
+----------------------------------------------
+
+    0. Remove default cube (press DEL and ENTER)
+    
+    1. Import / create model
+    
+    2. Select all meshes (Select -> Select All by Type -> Mesh)
+    
+    3. Export to OBJ (File -> Export -> Wavefront .obj) [*]
+        - enable following options in exporter
+            Material Groups
+            Rotate X90
+            Apply Modifiers
+            High Quality Normals
+            Copy Images
+            Selection Only
+            Objects as OBJ Objects
+            UVs
+            Normals
+            Materials
+            Edges
+            
+        - select empty folder
+        - give your exported file name with "obj" extension
+        - click on "Export OBJ" button
+        
+    4. Your model is now all files in this folder (OBJ, MTL, number of images)
+        - this converter assumes all files staying in the same folder,
+          (OBJ / MTL files use relative paths)
+          
+        - for WebGL, textures must be power of 2 sized
+
+    [*] If OBJ export fails (Blender 2.54 beta), patch your Blender installation 
+        following instructions here:
+            
+            http://www.blendernation.com/2010/09/12/blender-2-54-beta-released/
+            
+------
+Author
+------
+AlteredQualia http://alteredqualia.com
+
+"""
+
+import fileinput
+import operator
+import random
+import os.path
+import getopt
+import sys
+import struct
+import math
+
+# #####################################################
+# Configuration
+# #####################################################
+ALIGN = "center"    # center bottom top none
+SHADING = "smooth"  # smooth flat 
+TYPE = "ascii"      # ascii binary
+
+# default colors for debugging (each material gets one distinct color): 
+# white, red, green, blue, yellow, cyan, magenta
+COLORS = [0xeeeeee, 0xee0000, 0x00ee00, 0x0000ee, 0xeeee00, 0x00eeee, 0xee00ee]
+
+# #####################################################
+# Templates
+# #####################################################
+TEMPLATE_FILE_ASCII = u"""\
+// Converted from: %(fname)s
+//  vertices: %(nvertex)d
+//  faces: %(nface)d 
+//  materials: %(nmaterial)d
+//
+//  Generated with OBJ -> Three.js converter
+//  http://github.com/alteredq/three.js/blob/master/utils/exporters/convert_obj_threejs_slim.py
+
+
+var model = {
+    'materials': [%(materials)s],
+
+    'normals': [%(normals)s],
+
+    'vertices': [%(vertices)s],
+
+    'uvs': [%(uvs)s],
+
+    'triangles': [%(triangles)s],
+    'triangles_n': [%(triangles_n)s],
+    'triangles_uv': [%(triangles_uv)s],
+    'triangles_n_uv': [%(triangles_n_uv)s],
+
+    'quads': [%(quads)s],
+    'quads_n': [%(quads_n)s],
+    'quads_uv': [%(quads_uv)s],
+    'quads_n_uv': [%(quads_n_uv)s],
+
+    'end': (new Date).getTime()
+    }
+    
+postMessage( model );
+"""
+
+TEMPLATE_FILE_BIN = u"""\
+// Converted from: %(fname)s
+//  vertices: %(nvertex)d
+//  faces: %(nface)d 
+//  materials: %(nmaterial)d
+//
+//  Generated with OBJ -> Three.js converter
+//  http://github.com/alteredq/three.js/blob/master/utils/exporters/convert_obj_threejs_slim.py
+
+
+var model = {
+    'materials': [%(materials)s],
+
+    'buffers': '%(buffers)s',
+
+    'end': (new Date).getTime()
+    }
+    
+postMessage( model );
+"""
+
+TEMPLATE_VERTEX = "%f,%f,%f"
+
+TEMPLATE_UV_TRI = "%f,%f,%f,%f,%f,%f"
+TEMPLATE_UV_QUAD = "%f,%f,%f,%f,%f,%f,%f,%f"
+
+TEMPLATE_TRI = "%d,%d,%d,%d"
+TEMPLATE_QUAD = "%d,%d,%d,%d,%d"
+
+TEMPLATE_TRI_UV = "%d,%d,%d,%d,%d,%d,%d"
+TEMPLATE_QUAD_UV = "%d,%d,%d,%d,%d,%d,%d,%d,%d"
+
+TEMPLATE_TRI_N = "%d,%d,%d,%d,%d,%d,%d"
+TEMPLATE_QUAD_N = "%d,%d,%d,%d,%d,%d,%d,%d,%d"
+
+TEMPLATE_TRI_N_UV = "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d"
+TEMPLATE_QUAD_N_UV = "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d"
+
+TEMPLATE_N = "%f,%f,%f"
+TEMPLATE_UV = "%f,%f"
+
+# #####################################################
+# Utils
+# #####################################################
+def file_exists(filename):
+    """Return true if file exists and is accessible for reading.
+    
+    Should be safer than just testing for existence due to links and 
+    permissions magic on Unix filesystems.
+    
+    @rtype: boolean
+    """
+    
+    try:
+        f = open(filename, 'r')
+        f.close()
+        return True
+    except IOError:
+        return False
+
+    
+def get_name(fname):
+    """Create model name based of filename ("path/fname.js" -> "fname").
+    """
+    
+    return os.path.basename(fname).split(".")[0]
+
+def bbox(vertices):
+    """Compute bounding box of vertex array.
+    """
+    
+    if len(vertices)>0:
+        minx = maxx = vertices[0][0]
+        miny = maxy = vertices[0][1]
+        minz = maxz = vertices[0][2]
+        
+        for v in vertices[1:]:
+            if v[0]<minx:
+                minx = v[0]
+            elif v[0]>maxx:
+                maxx = v[0]
+            
+            if v[1]<miny:
+                miny = v[1]
+            elif v[1]>maxy:
+                maxy = v[1]
+
+            if v[2]<minz:
+                minz = v[2]
+            elif v[2]>maxz:
+                maxz = v[2]
+
+        return { 'x':[minx,maxx], 'y':[miny,maxy], 'z':[minz,maxz] }
+    
+    else:
+        return { 'x':[0,0], 'y':[0,0], 'z':[0,0] }
+
+def translate(vertices, t):
+    """Translate array of vertices by vector t.
+    """
+    
+    for i in xrange(len(vertices)):
+        vertices[i][0] += t[0]
+        vertices[i][1] += t[1]
+        vertices[i][2] += t[2]
+        
+def center(vertices):
+    """Center model (middle of bounding box).
+    """
+    
+    bb = bbox(vertices)
+    
+    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
+    cy = bb['y'][0] + (bb['y'][1] - bb['y'][0])/2.0
+    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
+    
+    translate(vertices, [-cx,-cy,-cz])
+
+def top(vertices):
+    """Align top of the model with the floor (Y-axis) and center it around X and Z.
+    """
+    
+    bb = bbox(vertices)
+    
+    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
+    cy = bb['y'][1]
+    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
+    
+    translate(vertices, [-cx,-cy,-cz])
+    
+def bottom(vertices):
+    """Align bottom of the model with the floor (Y-axis) and center it around X and Z.
+    """
+    
+    bb = bbox(vertices)
+    
+    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
+    cy = bb['y'][0] 
+    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
+    
+    translate(vertices, [-cx,-cy,-cz])
+
+def normalize(v):
+    """Normalize 3d vector"""
+    
+    l = math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2])
+    v[0] /= l
+    v[1] /= l
+    v[2] /= l
+
+# #####################################################
+# MTL parser
+# #####################################################
+def parse_mtl(fname):
+    """Parse MTL file.
+    """
+    
+    materials = {}
+    
+    for line in fileinput.input(fname):
+        chunks = line.split()
+        if len(chunks) > 0:
+            
+            # Material start
+            # newmtl identifier
+            if chunks[0] == "newmtl" and len(chunks) == 2:
+                identifier = chunks[1]
+                if not identifier in materials:
+                    materials[identifier] = {}
+
+            # Diffuse color
+            # Kd 1.000 1.000 1.000
+            if chunks[0] == "Kd" and len(chunks) == 4:
+                materials[identifier]["col_diffuse"] = [float(chunks[1]), float(chunks[2]), float(chunks[3])]
+
+            # Ambient color
+            # Ka 1.000 1.000 1.000
+            if chunks[0] == "Ka" and len(chunks) == 4:
+                materials[identifier]["col_ambient"] = [float(chunks[1]), float(chunks[2]), float(chunks[3])]
+
+            # Specular color
+            # Ks 1.000 1.000 1.000
+            if chunks[0] == "Ks" and len(chunks) == 4:
+                materials[identifier]["col_specular"] = [float(chunks[1]), float(chunks[2]), float(chunks[3])]
+
+            # Specular coefficient
+            # Ns 154.000
+            if chunks[0] == "Ns" and len(chunks) == 2:
+                materials[identifier]["specular_coef"] = float(chunks[1])
+
+            # Transparency
+            # Tr 0.9 or d 0.9
+            if (chunks[0] == "Tr" or chunks[0] == "d") and len(chunks) == 2:
+                materials[identifier]["transparency"] = float(chunks[1])
+
+            # Optical density
+            # Ni 1.0
+            if chunks[0] == "Ni" and len(chunks) == 2:                
+                materials[identifier]["optical_density"] = float(chunks[1])
+
+            # Diffuse texture
+            # map_Kd texture_diffuse.jpg
+            if chunks[0] == "map_Kd" and len(chunks) == 2:                
+                materials[identifier]["map_diffuse"] = chunks[1]
+
+            # Ambient texture
+            # map_Ka texture_ambient.jpg
+            if chunks[0] == "map_Ka" and len(chunks) == 2:
+                materials[identifier]["map_ambient"] = chunks[1]
+
+            # Specular texture
+            # map_Ks texture_specular.jpg
+            if chunks[0] == "map_Ks" and len(chunks) == 2:
+                materials[identifier]["map_specular"] = chunks[1]
+
+            # Alpha texture
+            # map_d texture_alpha.png
+            if chunks[0] == "map_d" and len(chunks) == 2:
+                materials[identifier]["map_alpha"] = chunks[1]
+
+            # Bump texture
+            # map_bump texture_bump.jpg or bump texture_bump.jpg
+            if (chunks[0] == "map_bump" or chunks[0] == "bump") and len(chunks) == 2:
+                materials[identifier]["map_bump"] = chunks[1]
+
+            # Illumination
+            # illum 2
+            #
+            # 0. Color on and Ambient off
+            # 1. Color on and Ambient on
+            # 2. Highlight on
+            # 3. Reflection on and Ray trace on
+            # 4. Transparency: Glass on, Reflection: Ray trace on
+            # 5. Reflection: Fresnel on and Ray trace on
+            # 6. Transparency: Refraction on, Reflection: Fresnel off and Ray trace on
+            # 7. Transparency: Refraction on, Reflection: Fresnel on and Ray trace on
+            # 8. Reflection on and Ray trace off
+            # 9. Transparency: Glass on, Reflection: Ray trace off
+            # 10. Casts shadows onto invisible surfaces
+            if chunks[0] == "illum" and len(chunks) == 2:
+                materials[identifier]["illumination"] = int(chunks[1])
+
+    return materials
+    
+# #####################################################
+# OBJ parser
+# #####################################################
+def parse_vertex(text):
+    """Parse text chunk specifying single vertex.
+    
+    Possible formats:
+        vertex index
+        vertex index / texture index
+        vertex index / texture index / normal index
+        vertex index / / normal index
+    """
+    
+    v = 0
+    t = 0
+    n = 0
+    
+    chunks = text.split("/")
+    
+    v = int(chunks[0])
+    if len(chunks) > 1:
+        if chunks[1]:
+            t = int(chunks[1])
+    if len(chunks) > 2:
+        if chunks[2]:
+            n = int(chunks[2])
+            
+    return { 'v':v, 't':t, 'n':n }
+    
+def parse_obj(fname):
+    """Parse OBJ file.
+    """
+    
+    vertices = []
+    normals = []
+    uvs = []
+    
+    faces = []
+    
+    materials = {}
+    mcounter = 0
+    mcurrent = 0
+    
+    mtllib = ""
+    
+    # current face state
+    group = 0
+    object = 0
+    smooth = 0
+    
+    for line in fileinput.input(fname):
+        chunks = line.split()
+        if len(chunks) > 0:
+            
+            # Vertices as (x,y,z) coordinates
+            # v 0.123 0.234 0.345
+            if chunks[0] == "v" and len(chunks) == 4:
+                x = float(chunks[1])
+                y = float(chunks[2])
+                z = float(chunks[3])
+                vertices.append([x,y,z])
+
+            # Normals in (x,y,z) form; normals might not be unit
+            # vn 0.707 0.000 0.707
+            if chunks[0] == "vn" and len(chunks) == 4:
+                x = float(chunks[1])
+                y = float(chunks[2])
+                z = float(chunks[3])
+                normals.append([x,y,z])
+
+            # Texture coordinates in (u,v[,w]) coordinates, w is optional
+            # vt 0.500 -1.352 [0.234]
+            if chunks[0] == "vt" and len(chunks) >= 3:
+                u = float(chunks[1])
+                v = float(chunks[2])
+                w = 0
+                if len(chunks)>3:
+                    w = float(chunks[3])
+                uvs.append([u,v,w])
+
+            # Face
+            if chunks[0] == "f" and len(chunks) >= 4:
+                vertex_index = []
+                uv_index = []
+                normal_index = []
+                
+                for v in chunks[1:]:
+                    vertex = parse_vertex(v)
+                    if vertex['v']:
+                        vertex_index.append(vertex['v'])
+                    if vertex['t']:
+                        uv_index.append(vertex['t'])
+                    if vertex['n']:
+                        normal_index.append(vertex['n'])
+                
+                faces.append({
+                    'vertex':vertex_index, 
+                    'uv':uv_index,
+                    'normal':normal_index,
+                    
+                    'material':mcurrent,
+                    'group':group, 
+                    'object':object, 
+                    'smooth':smooth,
+                    })
+    
+            # Group
+            if chunks[0] == "g" and len(chunks) == 2:
+                group = chunks[1]
+
+            # Object
+            if chunks[0] == "o" and len(chunks) == 2:
+                object = chunks[1]
+
+            # Materials definition
+            if chunks[0] == "mtllib" and len(chunks) == 2:
+                mtllib = chunks[1]
+                
+            # Material
+            if chunks[0] == "usemtl" and len(chunks) == 2:
+                material = chunks[1]
+                if not material in materials:
+                    mcurrent = mcounter
+                    materials[material] = mcounter
+                    mcounter += 1
+                else:
+                    mcurrent = materials[material]
+
+            # Smooth shading
+            if chunks[0] == "s" and len(chunks) == 2:
+                smooth = chunks[1]
+
+    return faces, vertices, uvs, normals, materials, mtllib
+    
+# #####################################################
+# Generator
+# #####################################################
+def generate_vertex(v):
+    return TEMPLATE_VERTEX % (v[0], v[1], v[2])
+    
+def generate_triangle(f):
+    v = f['vertex']
+    return TEMPLATE_TRI % (v[0]-1, v[1]-1, v[2]-1, 
+                           f['material'])
+
+def generate_triangle_uv(f):
+    v = f['vertex']
+    uv = f['uv']
+    return TEMPLATE_TRI_UV % (v[0]-1, v[1]-1, v[2]-1, 
+                              f['material'], 
+                              uv[0]-1, uv[1]-1, uv[2]-1)
+
+def generate_triangle_n(f):
+    v = f['vertex']
+    n = f['normal']
+    return TEMPLATE_TRI_N % (v[0]-1, v[1]-1, v[2]-1, 
+                             f['material'], 
+                             n[0]-1, n[1]-1, n[2]-1)
+
+def generate_triangle_n_uv(f):
+    v = f['vertex']
+    n = f['normal']
+    uv = f['uv']
+    return TEMPLATE_TRI_N_UV % (v[0]-1, v[1]-1, v[2]-1, 
+                                f['material'], 
+                                n[0]-1, n[1]-1, n[2]-1, 
+                                uv[0]-1, uv[1]-1, uv[2]-1)
+
+def generate_quad(f):
+    vi = f['vertex']
+    return TEMPLATE_QUAD % (vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1, 
+                            f['material'])
+
+def generate_quad_uv(f):
+    v = f['vertex']
+    uv = f['uv']
+    return TEMPLATE_QUAD_UV % (v[0]-1, v[1]-1, v[2]-1, v[3]-1, 
+                               f['material'], 
+                               uv[0]-1, uv[1]-1, uv[2]-1, uv[3]-1)
+
+def generate_quad_n(f):
+    v = f['vertex']
+    n = f['normal']
+    return TEMPLATE_QUAD_N % (v[0]-1, v[1]-1, v[2]-1, v[3]-1, 
+                              f['material'],
+                              n[0]-1, n[1]-1, n[2]-1, n[3]-1)
+
+def generate_quad_n_uv(f):
+    v = f['vertex']
+    n = f['normal']
+    uv = f['uv']
+    return TEMPLATE_QUAD_N_UV % (v[0]-1, v[1]-1, v[2]-1, v[3]-1, 
+                                 f['material'],
+                                 n[0]-1, n[1]-1, n[2]-1, n[3]-1,
+                                 uv[0]-1, uv[1]-1, uv[2]-1, uv[3]-1)
+
+def generate_normal(n):
+    return TEMPLATE_N % (n[0], n[1], n[2])
+
+def generate_uv(uv):
+    return TEMPLATE_UV % (uv[0], 1.0 - uv[1])
+
+# #####################################################
+# Materials
+# #####################################################
+def generate_color(i):
+    """Generate hex color corresponding to integer.
+    
+    Colors should have well defined ordering.
+    First N colors are hardcoded, then colors are random 
+    (must seed random number  generator with deterministic value 
+    before getting colors).
+    """
+    
+    if i < len(COLORS):
+        return "0x%06x" % COLORS[i]
+    else:
+        return "0x%06x" % int(0xffffff * random.random())
+        
+def value2string(v):
+    if type(v)==str and v[0] != "0":
+        return '"%s"' % v
+    return str(v)
+    
+def generate_materials(mtl, materials):
+    """Generate JS array of materials objects
+    
+    JS material objects are basically prettified one-to-one 
+    mappings of MTL properties in JSON format.
+    """
+    
+    mtl_array = []
+    for m in mtl:
+        index = materials[m]
+        
+        # add debug information
+        #  materials should be sorted according to how
+        #  they appeared in OBJ file (for the first time)
+        #  this index is identifier used in face definitions
+        mtl[m]['a_dbg_name'] = m
+        mtl[m]['a_dbg_index'] = index
+        mtl[m]['a_dbg_color'] = generate_color(index)
+        
+        mtl_raw = ",\n".join(['\t"%s" : %s' % (n, value2string(v)) for n,v in sorted(mtl[m].items())])
+        mtl_string = "\t{\n%s\n\t}" % mtl_raw
+        mtl_array.append([index, mtl_string])
+        
+    return ",\n\n".join([m for i,m in sorted(mtl_array)])
+
+def generate_mtl(materials):
+    """Generate dummy materials (if there is no MTL file).
+    """
+    
+    mtl = {}
+    for m in materials:
+        index = materials[m]
+        mtl[m] = {
+            'a_dbg_name': m,
+            'a_dbg_index': index,
+            'a_dbg_color': generate_color(index)
+        }
+    return mtl
+    
+def generate_materials_string(materials, mtllib):
+    """Generate final materials string.
+    """
+
+    random.seed(42) # to get well defined color order for materials
+    
+    # default materials with debug colors for when
+    # there is no specified MTL / MTL loading failed,
+    # or if there were no materials / null materials
+    if not materials:
+        materials = { 'default':0 }
+    mtl = generate_mtl(materials)
+    
+    if mtllib:
+        # create full pathname for MTL (included from OBJ)
+        path = os.path.dirname(infile)
+        fname = os.path.join(path, mtllib)
+        
+        if file_exists(fname):
+            # override default materials with real ones from MTL
+            # (where they exist, otherwise keep defaults)
+            mtl.update(parse_mtl(fname))
+        
+        else:
+            print "Couldn't find [%s]" % fname
+    
+    return generate_materials(mtl, materials)
+    
+# #####################################################
+# Faces
+# #####################################################
+def is_triangle_flat(f):
+    return len(f['vertex'])==3 and not (f["normal"] and SHADING == "smooth") and not f['uv']
+    
+def is_triangle_flat_uv(f):
+    return len(f['vertex'])==3 and not (f["normal"] and SHADING == "smooth") and len(f['uv'])==3
+
+def is_triangle_smooth(f):
+    return len(f['vertex'])==3 and f["normal"] and SHADING == "smooth" and not f['uv']
+    
+def is_triangle_smooth_uv(f):
+    return len(f['vertex'])==3 and f["normal"] and SHADING == "smooth" and len(f['uv'])==3
+
+def is_quad_flat(f):
+    return len(f['vertex'])==4 and not (f["normal"] and SHADING == "smooth") and not f['uv']
+    
+def is_quad_flat_uv(f):
+    return len(f['vertex'])==4 and not (f["normal"] and SHADING == "smooth") and len(f['uv'])==4
+
+def is_quad_smooth(f):
+    return len(f['vertex'])==4 and f["normal"] and SHADING == "smooth" and not f['uv']
+
+def is_quad_smooth_uv(f):
+    return len(f['vertex'])==4 and f["normal"] and SHADING == "smooth" and len(f['uv'])==4
+
+def sort_faces(faces):
+    data = {
+    'triangles_flat': [],
+    'triangles_flat_uv': [],
+    'triangles_smooth': [],
+    'triangles_smooth_uv': [],
+    
+    'quads_flat': [],
+    'quads_flat_uv': [],
+    'quads_smooth': [],
+    'quads_smooth_uv': []
+    }
+    
+    for f in faces:
+        if is_triangle_flat(f):
+            data['triangles_flat'].append(f)
+        elif is_triangle_flat_uv(f):
+            data['triangles_flat_uv'].append(f)
+        elif is_triangle_smooth(f):
+            data['triangles_smooth'].append(f)
+        elif is_triangle_smooth_uv(f):
+            data['triangles_smooth_uv'].append(f)
+        
+        elif is_quad_flat(f):
+            data['quads_flat'].append(f)
+        elif is_quad_flat_uv(f):
+            data['quads_flat_uv'].append(f)
+        elif is_quad_smooth(f):
+            data['quads_smooth'].append(f)
+        elif is_quad_smooth_uv(f):
+            data['quads_smooth_uv'].append(f)
+
+    return data
+
+# #####################################################
+# API - ASCII converter
+# #####################################################
+def convert_ascii(infile, outfile):
+    """Convert infile.obj to outfile.js
+    
+    Here is where everything happens. If you need to automate conversions,
+    just import this file as Python module and call this method.
+    """
+    
+    if not file_exists(infile):
+        print "Couldn't find [%s]" % infile
+        return
+        
+    faces, vertices, uvs, normals, materials, mtllib = parse_obj(infile)
+    
+    if ALIGN == "center":
+        center(vertices)
+    elif ALIGN == "bottom":
+        bottom(vertices)
+    elif ALIGN == "top":
+        top(vertices)
+    
+    normals_string = ""
+    if SHADING == "smooth":
+        normals_string = ",".join(generate_normal(n) for n in normals)
+        
+    sfaces = sort_faces(faces)
+    
+    text = TEMPLATE_FILE_ASCII % {
+    "name"          : get_name(outfile),
+    "vertices"      : ",".join(generate_vertex(v) for v in vertices),
+    "triangles"     : ",".join(generate_triangle(f) for f in sfaces['triangles_flat']),
+    "triangles_n"   : ",".join(generate_triangle_n(f) for f in sfaces['triangles_smooth']),
+    "triangles_uv"  : ",".join(generate_triangle_uv(f) for f in sfaces['triangles_flat_uv']),
+    "triangles_n_uv": ",".join(generate_triangle_n_uv(f) for f in sfaces['triangles_smooth_uv']),
+    "quads"         : ",".join(generate_quad(f) for f in sfaces['quads_flat']),
+    "quads_n"       : ",".join(generate_quad_n(f) for f in sfaces['quads_smooth']),
+    "quads_uv"      : ",".join(generate_quad_uv(f) for f in sfaces['quads_flat_uv']),
+    "quads_n_uv"    : ",".join(generate_quad_n_uv(f) for f in sfaces['quads_smooth_uv']),
+    "uvs"           : ",".join(generate_uv(uv) for uv in uvs),
+    "normals"       : normals_string,
+    
+    "materials" : generate_materials_string(materials, mtllib),
+    
+    "fname"     : infile,
+    "nvertex"   : len(vertices),
+    "nface"     : len(faces),
+    "nmaterial" : len(materials)
+    }
+    
+    out = open(outfile, "w")
+    out.write(text)
+    out.close()
+    
+    print "%d vertices, %d faces, %d materials" % (len(vertices), len(faces), len(materials))
+        
+    
+# #############################################################################
+# API - Binary converter
+# #############################################################################
+def convert_binary(infile, outfile):
+    """Convert infile.obj to outfile.js + outfile.bin    
+    """
+    
+    if not file_exists(infile):
+        print "Couldn't find [%s]" % infile
+        return
+    
+    binfile = get_name(outfile) + ".bin"
+    
+    faces, vertices, uvs, normals, materials, mtllib = parse_obj(infile)
+    
+    if ALIGN == "center":
+        center(vertices)
+    elif ALIGN == "bottom":
+        bottom(vertices)
+    elif ALIGN == "top":
+        top(vertices)    
+    
+    sfaces = sort_faces(faces)
+    
+    # ###################
+    # generate JS file
+    # ###################
+    
+    text = TEMPLATE_FILE_BIN % {
+    "name"       : get_name(outfile),
+    
+    "materials" : generate_materials_string(materials, mtllib),
+    "buffers"   : binfile,
+    
+    "fname"     : infile,
+    "nvertex"   : len(vertices),
+    "nface"     : len(faces),
+    "nmaterial" : len(materials)
+    }
+    
+    out = open(outfile, "w")
+    out.write(text)
+    out.close()
+    
+    # ###################
+    # generate BIN file
+    # ###################
+    
+    if SHADING == "smooth":
+        nnormals = len(normals)
+    else:
+        nnormals = 0
+        
+    buffer = []
+
+    # header
+    # ------
+    header_bytes  = struct.calcsize('<8s')
+    header_bytes += struct.calcsize('<BBBBBBBB')
+    header_bytes += struct.calcsize('<IIIIIIIIIII')
+    
+    # signature
+    signature = struct.pack('<8s', 'Three.js')
+    
+    # metadata (all data is little-endian)
+    vertex_coordinate_bytes = 4
+    normal_coordinate_bytes = 1
+    uv_coordinate_bytes = 4
+    
+    vertex_index_bytes = 4
+    normal_index_bytes = 4
+    uv_index_bytes = 4
+    material_index_bytes = 2
+    
+    # header_bytes            unsigned char   1
+    
+    # vertex_coordinate_bytes unsigned char   1
+    # normal_coordinate_bytes unsigned char   1
+    # uv_coordinate_bytes     unsigned char   1
+    
+    # vertex_index_bytes      unsigned char   1
+    # normal_index_bytes      unsigned char   1
+    # uv_index_bytes          unsigned char   1
+    # material_index_bytes    unsigned char   1
+    bdata = struct.pack('<BBBBBBBB', header_bytes,
+                               vertex_coordinate_bytes, 
+                               normal_coordinate_bytes,
+                               uv_coordinate_bytes,
+                               vertex_index_bytes, 
+                               normal_index_bytes,
+                               uv_index_bytes,
+                               material_index_bytes)
+
+    # nvertices       unsigned int    4
+    # nnormals        unsigned int    4
+    # nuvs            unsigned int    4
+    
+    # ntri_flat       unsigned int    4
+    # ntri_smooth     unsigned int    4
+    # ntri_flat_uv    unsigned int    4
+    # ntri_smooth_uv  unsigned int    4
+    
+    # nquad_flat      unsigned int    4
+    # nquad_smooth    unsigned int    4
+    # nquad_flat_uv   unsigned int    4
+    # nquad_smooth_uv unsigned int    4    
+    ndata = struct.pack('<IIIIIIIIIII', len(vertices), 
+                               nnormals,
+                               len(uvs),
+                               len(sfaces['triangles_flat']), 
+                               len(sfaces['triangles_smooth']),
+                               len(sfaces['triangles_flat_uv']), 
+                               len(sfaces['triangles_smooth_uv']),
+                               len(sfaces['quads_flat']),
+                               len(sfaces['quads_smooth']),
+                               len(sfaces['quads_flat_uv']),
+                               len(sfaces['quads_smooth_uv']))
+    buffer.append(signature)
+    buffer.append(bdata)
+    buffer.append(ndata)
+        
+    # 1. vertices
+    # ------------
+    # x float   4
+    # y float   4
+    # z float   4
+    for v in vertices:
+        data = struct.pack('<fff', v[0], v[1], v[2]) 
+        buffer.append(data)
+
+    # 2. normals
+    # ---------------
+    # x signed char 1
+    # y signed char 1
+    # z signed char 1
+    if SHADING == "smooth":
+        for n in normals:
+            normalize(n)
+            data = struct.pack('<bbb', math.floor(n[0]*127+0.5),
+                                       math.floor(n[1]*127+0.5),
+                                       math.floor(n[2]*127+0.5))
+            buffer.append(data)
+    
+    # 3. uvs
+    # -----------
+    # u float   4
+    # v float   4
+    for uv in uvs:
+        data = struct.pack('<ff', uv[0], 1.0-uv[1])
+        buffer.append(data)
+    
+    # 4. flat triangles
+    # ------------------
+    # a unsigned int   4
+    # b unsigned int   4
+    # c unsigned int   4
+    # m unsigned short 2
+    for f in sfaces['triangles_flat']:
+        vi = f['vertex']
+        data = struct.pack('<IIIH', 
+                            vi[0]-1, vi[1]-1, vi[2]-1, 
+                            f['material'])
+        buffer.append(data)
+
+    # 5. smooth triangles
+    # -------------------
+    # a  unsigned int   4
+    # b  unsigned int   4
+    # c  unsigned int   4
+    # m  unsigned short 2
+    # na unsigned int   4
+    # nb unsigned int   4
+    # nc unsigned int   4
+    for f in sfaces['triangles_smooth']:
+        vi = f['vertex']
+        ni = f['normal']
+        data = struct.pack('<IIIHIII', 
+                            vi[0]-1, vi[1]-1, vi[2]-1, 
+                            f['material'], 
+                            ni[0]-1, ni[1]-1, ni[2]-1)
+        buffer.append(data)
+
+    # 6. flat triangles uv
+    # --------------------
+    # a  unsigned int    4
+    # b  unsigned int    4
+    # c  unsigned int    4
+    # m  unsigned short  2
+    # ua unsigned int    4
+    # ub unsigned int    4
+    # uc unsigned int    4
+    for f in sfaces['triangles_flat_uv']:
+        vi = f['vertex']
+        ui = f['uv']
+        data = struct.pack('<IIIHIII', 
+                            vi[0]-1, vi[1]-1, vi[2]-1, 
+                            f['material'], 
+                            ui[0]-1, ui[1]-1, ui[2]-1)
+        buffer.append(data)
+
+    # 7. smooth triangles uv
+    # ----------------------
+    # a  unsigned int    4
+    # b  unsigned int    4
+    # c  unsigned int    4
+    # m  unsigned short  2
+    # na unsigned int    4
+    # nb unsigned int    4
+    # nc unsigned int    4
+    # ua unsigned int    4
+    # ub unsigned int    4
+    # uc unsigned int    4
+    for f in sfaces['triangles_smooth_uv']:
+        vi = f['vertex']
+        ni = f['normal']
+        ui = f['uv']
+        data = struct.pack('<IIIHIIIIII', 
+                            vi[0]-1, vi[1]-1, vi[2]-1, 
+                            f['material'], 
+                            ni[0]-1, ni[1]-1, ni[2]-1,
+                            ui[0]-1, ui[1]-1, ui[2]-1)
+        buffer.append(data)
+
+    # 8. flat quads
+    # ------------------
+    # a unsigned int   4
+    # b unsigned int   4
+    # c unsigned int   4
+    # d unsigned int   4
+    # m unsigned short 2
+    for f in sfaces['quads_flat']:
+        vi = f['vertex']
+        data = struct.pack('<IIIIH', 
+                            vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1, 
+                            f['material'])
+        buffer.append(data)
+            
+    # 9. smooth quads
+    # -------------------
+    # a  unsigned int   4
+    # b  unsigned int   4
+    # c  unsigned int   4
+    # d  unsigned int   4
+    # m  unsigned short 2
+    # na unsigned int   4
+    # nb unsigned int   4
+    # nc unsigned int   4
+    # nd unsigned int   4
+    for f in sfaces['quads_smooth']:
+        vi = f['vertex']
+        ni = f['normal']
+        data = struct.pack('<IIIIHIIII', 
+                            vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1, 
+                            f['material'], 
+                            ni[0]-1, ni[1]-1, ni[2]-1, ni[3]-1)
+        buffer.append(data)
+    
+    # 10. flat quads uv
+    # ------------------
+    # a unsigned int   4
+    # b unsigned int   4
+    # c unsigned int   4
+    # d unsigned int   4
+    # m unsigned short 2
+    # ua unsigned int  4
+    # ub unsigned int  4
+    # uc unsigned int  4
+    # ud unsigned int  4
+    for f in sfaces['quads_flat_uv']:
+        vi = f['vertex']
+        ui = f['uv']
+        data = struct.pack('<IIIIHIIII', 
+                            vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1, 
+                            f['material'],
+                            ui[0]-1, ui[1]-1, ui[2]-1, ui[3]-1)
+        buffer.append(data)
+
+    # 11. smooth quads uv
+    # -------------------
+    # a  unsigned int   4
+    # b  unsigned int   4
+    # c  unsigned int   4
+    # d  unsigned int   4
+    # m  unsigned short 2
+    # na unsigned int   4
+    # nb unsigned int   4
+    # nc unsigned int   4
+    # nd unsigned int   4
+    # ua unsigned int   4
+    # ub unsigned int   4
+    # uc unsigned int   4
+    # ud unsigned int   4
+    for f in sfaces['quads_smooth_uv']:
+        vi = f['vertex']
+        ni = f['normal']
+        ui = f['uv']
+        data = struct.pack('<IIIIHIIIIIIII', 
+                            vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1, 
+                            f['material'], 
+                            ni[0]-1, ni[1]-1, ni[2]-1, ni[3]-1,
+                            ui[0]-1, ui[1]-1, ui[2]-1, ui[3]-1)
+        buffer.append(data)
+
+    path = os.path.dirname(outfile)
+    fname = os.path.join(path, binfile)
+
+    out = open(fname, "wb")
+    out.write("".join(buffer))
+    out.close()
+
+# #############################################################################
+# Helpers
+# #############################################################################
+def usage():
+    print "Usage: %s -i filename.obj -o filename.js [-a center|top|bottom] [-s flat|smooth] [-t binary|ascii]" % os.path.basename(sys.argv[0])
+        
+# #####################################################
+# Main
+# #####################################################
+if __name__ == "__main__":
+    
+    # get parameters from the command line
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], "hi:o:a:s:t:", ["help", "input=", "output=", "align=", "shading=", "type="])
+    
+    except getopt.GetoptError:
+        usage()
+        sys.exit(2)
+        
+    infile = outfile = ""
+    
+    for o, a in opts:
+        if o in ("-h", "--help"):
+            usage()
+            sys.exit()
+        
+        elif o in ("-i", "--input"):
+            infile = a
+
+        elif o in ("-o", "--output"):
+            outfile = a
+
+        elif o in ("-a", "--align"):
+            if a in ("top", "bottom", "center"):
+                ALIGN = a
+
+        elif o in ("-s", "--shading"):
+            if a in ("flat", "smooth"):
+                SHADING = a
+                
+        elif o in ("-t", "--type"):
+            if a in ("binary", "ascii"):
+                TYPE = a
+
+    if infile == "" or outfile == "":
+        usage()
+        sys.exit(2)
+    
+    print "Converting [%s] into [%s] ..." % (infile, outfile)
+    
+    if TYPE == "ascii":
+        convert_ascii(infile, outfile)
+    elif TYPE == "binary":
+        convert_binary(infile, outfile)