|
@@ -1,839 +1,839 @@
|
|
-"""Convert Wavefront OBJ / MTL files into Three.js
|
|
|
|
-
|
|
|
|
--------------------------
|
|
|
|
-How to use this converter
|
|
|
|
--------------------------
|
|
|
|
-
|
|
|
|
-python convert_obj_threejs.py -i filename.obj -o filename.js [-a center|top|bottom]
|
|
|
|
-
|
|
|
|
-Note: by default, model is centered (middle of bounding box goes to 0,0,0).
|
|
|
|
-
|
|
|
|
---------------------------------------------------
|
|
|
|
-How to use generated JS file in your HTML document
|
|
|
|
---------------------------------------------------
|
|
|
|
-
|
|
|
|
- <script type="text/javascript" src="Three.js"></script>
|
|
|
|
- <script type="text/javascript" src="ModelName.js"></script>
|
|
|
|
-
|
|
|
|
- ...
|
|
|
|
-
|
|
|
|
- <script type="text/javascript">
|
|
|
|
- ...
|
|
|
|
-
|
|
|
|
- var normalizeUVsFlag = 1; // set to 1 if canvas render has missing materials
|
|
|
|
- var geometry = new ModelName( path_to_textures );
|
|
|
|
- var mesh = new THREE.Mesh( geometry, geometry.materials, 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 cannot have more than 65,536 vertices
|
|
|
|
- (this comes from WebGL using just 16-bit indices,
|
|
|
|
- could be worked around by expanding indexed
|
|
|
|
- faces into full vertex definitions)
|
|
|
|
-
|
|
|
|
- - texture coordinates can be wrong in canvas renderer
|
|
|
|
- (there is crude normalization, but it doesn't
|
|
|
|
- work for all cases)
|
|
|
|
-
|
|
|
|
- - everything is using smoothing
|
|
|
|
- (if you want flat shading for whole mesh,
|
|
|
|
- don't export normals, then Three will
|
|
|
|
- compute own normals)
|
|
|
|
-
|
|
|
|
-----------------------------------------------
|
|
|
|
-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
|
|
|
|
-
|
|
|
|
-# #####################################################
|
|
|
|
-# Configuration
|
|
|
|
-# #####################################################
|
|
|
|
-ALIGN = "center" # center bottom top none
|
|
|
|
-
|
|
|
|
-# default colors for debugging (each material gets one distinct color):
|
|
|
|
-# white, red, green, blue, yellow, cyan, magenta
|
|
|
|
-COLORS = [0xffeeeeee, 0xffee0000, 0xff00ee00, 0xff0000ee, 0xffeeee00, 0xff00eeee, 0xffee00ee]
|
|
|
|
-
|
|
|
|
-# #####################################################
|
|
|
|
-# Templates
|
|
|
|
-# #####################################################
|
|
|
|
-TEMPLATE_FILE = u"""\
|
|
|
|
-// Converted from: %(fname)s
|
|
|
|
-// vertices: %(nvertex)d
|
|
|
|
-// faces: %(nface)d
|
|
|
|
-// materials: %(nmaterial)d
|
|
|
|
-//
|
|
|
|
-// This file was generated by "convert_obj_treejs.py"
|
|
|
|
-
|
|
|
|
-var %(name)s = function ( urlbase ) {
|
|
|
|
- var scope = this;
|
|
|
|
-
|
|
|
|
- THREE.Geometry.call(this);
|
|
|
|
-
|
|
|
|
- var materials = [%(materials)s];
|
|
|
|
-
|
|
|
|
- init_materials();
|
|
|
|
-
|
|
|
|
- var normals = [%(normals)s];
|
|
|
|
-
|
|
|
|
-%(vertices)s
|
|
|
|
-
|
|
|
|
-%(uvs)s
|
|
|
|
-
|
|
|
|
-%(faces)s
|
|
|
|
-
|
|
|
|
- this.computeCentroids();
|
|
|
|
- this.computeNormals();
|
|
|
|
-
|
|
|
|
- function material_color( mi ) {
|
|
|
|
- var m = materials[mi];
|
|
|
|
- if( m.col_diffuse )
|
|
|
|
- return (m.col_diffuse[0]*255 << 16) + (m.col_diffuse[1]*255 << 8) + m.col_diffuse[2]*255;
|
|
|
|
- else if ( m.a_dbg_color )
|
|
|
|
- return m.a_dbg_color;
|
|
|
|
- else
|
|
|
|
- return 0xffeeeeee;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- function v( x, y, z ) {
|
|
|
|
- scope.vertices.push( new THREE.Vertex( new THREE.Vector3( x, y, z ) ) );
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- function f3( a, b, c, material ) {
|
|
|
|
- var color = material_color(material);
|
|
|
|
- scope.faces.push( new THREE.Face3( a, b, c, null, new THREE.Color(color), material ) );
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- function f4( a, b, c, d, material ) {
|
|
|
|
- var color = material_color(material);
|
|
|
|
- scope.faces.push( new THREE.Face4( a, b, c, d, null, new THREE.Color(color), material ) );
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- function f3n( a, b, c, material, n1, n2, n3 ) {
|
|
|
|
- var color = material_color(material);
|
|
|
|
- var n1x = normals[n1][0];
|
|
|
|
- var n1y = normals[n1][1];
|
|
|
|
- var n1z = normals[n1][2];
|
|
|
|
- var n2x = normals[n2][0];
|
|
|
|
- var n2y = normals[n2][1];
|
|
|
|
- var n2z = normals[n2][2];
|
|
|
|
- var n3x = normals[n3][0];
|
|
|
|
- var n3y = normals[n3][1];
|
|
|
|
- var n3z = normals[n3][2];
|
|
|
|
- scope.faces.push( new THREE.Face3( a, b, c,
|
|
|
|
- [new THREE.Vector3( n1x, n1y, n1z ), new THREE.Vector3( n2x, n2y, n2z ), new THREE.Vector3( n3x, n3y, n3z )],
|
|
|
|
- new THREE.Color(color), material ) );
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- function f4n( a, b, c, d, material, n1, n2, n3, n4 ) {
|
|
|
|
- var color = material_color(material);
|
|
|
|
- var n1x = normals[n1][0];
|
|
|
|
- var n1y = normals[n1][1];
|
|
|
|
- var n1z = normals[n1][2];
|
|
|
|
- var n2x = normals[n2][0];
|
|
|
|
- var n2y = normals[n2][1];
|
|
|
|
- var n2z = normals[n2][2];
|
|
|
|
- var n3x = normals[n3][0];
|
|
|
|
- var n3y = normals[n3][1];
|
|
|
|
- var n3z = normals[n3][2];
|
|
|
|
- var n4x = normals[n4][0];
|
|
|
|
- var n4y = normals[n4][1];
|
|
|
|
- var n4z = normals[n4][2];
|
|
|
|
- scope.faces.push( new THREE.Face4( a, b, c, d,
|
|
|
|
- [new THREE.Vector3( n1x, n1y, n1z ), new THREE.Vector3( n2x, n2y, n2z ), new THREE.Vector3( n3x, n3y, n3z ), new THREE.Vector3( n4x, n4y, n4z )],
|
|
|
|
- new THREE.Color(color), material ) );
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- function uv( u1, v1, u2, v2, u3, v3, u4, v4 ) {
|
|
|
|
- var uv = [];
|
|
|
|
- uv.push( new THREE.UV( u1, v1 ) );
|
|
|
|
- uv.push( new THREE.UV( u2, v2 ) );
|
|
|
|
- uv.push( new THREE.UV( u3, v3 ) );
|
|
|
|
- if ( u4 && v4 ) uv.push( new THREE.UV( u4, v4 ) );
|
|
|
|
- scope.uvs.push( uv );
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- function init_materials() {
|
|
|
|
- scope.materials = [];
|
|
|
|
- for(var i=0; i<materials.length; ++i) {
|
|
|
|
- scope.materials[i] = create_material( materials[i], urlbase );
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- function is_pow2( n ) {
|
|
|
|
- var l = Math.log(n) / Math.LN2;
|
|
|
|
- return Math.floor(l) == l;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- function nearest_pow2(n) {
|
|
|
|
- var l = Math.log(n) / Math.LN2;
|
|
|
|
- return Math.pow( 2, Math.round(l) );
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- function create_material( m ) {
|
|
|
|
- var material;
|
|
|
|
-
|
|
|
|
- if( m.map_diffuse && urlbase ) {
|
|
|
|
- var texture = document.createElement( 'canvas' );
|
|
|
|
-
|
|
|
|
- material = new THREE.MeshBitmapUVMappingMaterial( texture );
|
|
|
|
- var image = new Image();
|
|
|
|
-
|
|
|
|
- image.onload = function () {
|
|
|
|
-
|
|
|
|
- if ( !is_pow2(this.width) || !is_pow2(this.height) ) {
|
|
|
|
-
|
|
|
|
- var w = nearest_pow2( this.width );
|
|
|
|
- var h = nearest_pow2( this.height );
|
|
|
|
- material.bitmap.width = w;
|
|
|
|
- material.bitmap.height = h;
|
|
|
|
- material.bitmap.getContext("2d").drawImage( this, 0, 0, w, h );
|
|
|
|
- }
|
|
|
|
- else {
|
|
|
|
- material.bitmap = this;
|
|
|
|
- }
|
|
|
|
- material.loaded = 1;
|
|
|
|
-
|
|
|
|
- };
|
|
|
|
- image.src = urlbase + "/" + m.map_diffuse;
|
|
|
|
- }
|
|
|
|
- else if( m.col_diffuse ) {
|
|
|
|
- var color = (m.col_diffuse[0]*255 << 16) + (m.col_diffuse[1]*255 << 8) + m.col_diffuse[2]*255;
|
|
|
|
- material = new THREE.MeshColorFillMaterial( color, m.transparency );
|
|
|
|
- }
|
|
|
|
- else if( m.a_dbg_color ) {
|
|
|
|
- material = new THREE.MeshColorFillMaterial( m.a_dbg_color );
|
|
|
|
- }
|
|
|
|
- else {
|
|
|
|
- material = new THREE.MeshFaceColorFillMaterial( );
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return material;
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-%(name)s.prototype = new THREE.Geometry();
|
|
|
|
-%(name)s.prototype.constructor = %(name)s;
|
|
|
|
-"""
|
|
|
|
-
|
|
|
|
-TEMPLATE_VERTEX = "\tv(%f,%f,%f);"
|
|
|
|
-
|
|
|
|
-TEMPLATE_UV3 = "\tuv(%f,%f,%f,%f,%f,%f);"
|
|
|
|
-TEMPLATE_UV4 = "\tuv(%f,%f,%f,%f,%f,%f,%f,%f);"
|
|
|
|
-
|
|
|
|
-TEMPLATE_FACE3 = "\tf3(%d,%d,%d,%d);"
|
|
|
|
-TEMPLATE_FACE4 = "\tf4(%d,%d,%d,%d,%d);"
|
|
|
|
-
|
|
|
|
-TEMPLATE_FACE3N = "\tf3n(%d,%d,%d, %d, %d,%d,%d);"
|
|
|
|
-TEMPLATE_FACE4N = "\tf4n(%d,%d,%d,%d, %d, %d,%d,%d,%d);"
|
|
|
|
-
|
|
|
|
-TEMPLATE_N = "[%f,%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].capitalize()
|
|
|
|
-
|
|
|
|
-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_uv(f, uvs):
|
|
|
|
- ui = f['uv']
|
|
|
|
- if len(ui) == 3:
|
|
|
|
- return TEMPLATE_UV3 % (uvs[ui[0]-1][0], 1.0 - uvs[ui[0]-1][1],
|
|
|
|
- uvs[ui[1]-1][0], 1.0 - uvs[ui[1]-1][1],
|
|
|
|
- uvs[ui[2]-1][0], 1.0 - uvs[ui[2]-1][1])
|
|
|
|
- elif len(ui) == 4:
|
|
|
|
- return TEMPLATE_UV4 % (uvs[ui[0]-1][0], 1.0 - uvs[ui[0]-1][1],
|
|
|
|
- uvs[ui[1]-1][0], 1.0 - uvs[ui[1]-1][1],
|
|
|
|
- uvs[ui[2]-1][0], 1.0 - uvs[ui[2]-1][1],
|
|
|
|
- uvs[ui[3]-1][0], 1.0 - uvs[ui[3]-1][1])
|
|
|
|
- return ""
|
|
|
|
-
|
|
|
|
-def generate_face(f):
|
|
|
|
- vi = f['vertex']
|
|
|
|
- if f["normal"]:
|
|
|
|
- ni = f['normal']
|
|
|
|
- if len(vi) == 3:
|
|
|
|
- return TEMPLATE_FACE3N % (vi[0]-1, vi[1]-1, vi[2]-1, f['material'], ni[0]-1, ni[1]-1, ni[2]-1)
|
|
|
|
- elif len(vi) == 4:
|
|
|
|
- return TEMPLATE_FACE4N % (vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1, f['material'], ni[0]-1, ni[1]-1, ni[2]-1, n[3]-1)
|
|
|
|
- else:
|
|
|
|
- if len(vi) == 3:
|
|
|
|
- return TEMPLATE_FACE3 % (vi[0]-1, vi[1]-1, vi[2]-1, f['material'])
|
|
|
|
- elif len(vi) == 4:
|
|
|
|
- return TEMPLATE_FACE4 % (vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1, f['material'])
|
|
|
|
- return ""
|
|
|
|
-
|
|
|
|
-def generate_normal(n):
|
|
|
|
- return TEMPLATE_N % (n[0], n[1], n[2])
|
|
|
|
-
|
|
|
|
-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%x" % COLORS[i]
|
|
|
|
- else:
|
|
|
|
- return "0x%x" % (int(0xffffff * random.random()) + 0xff000000)
|
|
|
|
-
|
|
|
|
-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
|
|
|
|
-
|
|
|
|
-# #####################################################
|
|
|
|
-# API
|
|
|
|
-# #####################################################
|
|
|
|
-def convert(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)
|
|
|
|
-
|
|
|
|
- random.seed(42) # to get well defined color order for materials
|
|
|
|
-
|
|
|
|
- uv_string = ""
|
|
|
|
- if len(uvs)>0:
|
|
|
|
- uv_string = "\n".join([generate_uv(f, uvs) for f in faces])
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- mtl = {}
|
|
|
|
- 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):
|
|
|
|
- mtl = parse_mtl(fname)
|
|
|
|
-
|
|
|
|
- else:
|
|
|
|
- print "Couldn't find [%s]" % fname
|
|
|
|
-
|
|
|
|
- if not mtl:
|
|
|
|
- # if there is no specified MTL or if loading failed,
|
|
|
|
- # generate default materials with debug colors
|
|
|
|
- mtl = generate_mtl(materials)
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- text = TEMPLATE_FILE % {
|
|
|
|
- "name" : get_name(outfile),
|
|
|
|
- "vertices" : "\n".join([generate_vertex(v) for v in vertices]),
|
|
|
|
- "faces" : "\n".join([generate_face(f) for f in faces]),
|
|
|
|
- "uvs" : uv_string,
|
|
|
|
- "normals" : ",".join(generate_normal(n) for n in normals),
|
|
|
|
-
|
|
|
|
- "materials" : generate_materials(mtl, materials),
|
|
|
|
-
|
|
|
|
- "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))
|
|
|
|
-
|
|
|
|
-# #############################################################################
|
|
|
|
-# Helpers
|
|
|
|
-# #############################################################################
|
|
|
|
-def usage():
|
|
|
|
- print "Usage: %s -i filename.obj -o filename.js [-a center|top|bottom]" % 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:", ["help", "input=", "output=", "align="])
|
|
|
|
-
|
|
|
|
- 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
|
|
|
|
-
|
|
|
|
- if infile == "" or outfile == "":
|
|
|
|
- usage()
|
|
|
|
- sys.exit(2)
|
|
|
|
-
|
|
|
|
- print "Converting [%s] into [%s] ..." % (infile, outfile)
|
|
|
|
- convert(infile, outfile)
|
|
|
|
-
|
|
|
|
|
|
+"""Convert Wavefront OBJ / MTL files into Three.js
|
|
|
|
+
|
|
|
|
+-------------------------
|
|
|
|
+How to use this converter
|
|
|
|
+-------------------------
|
|
|
|
+
|
|
|
|
+python convert_obj_threejs.py -i filename.obj -o filename.js [-a center|top|bottom]
|
|
|
|
+
|
|
|
|
+Note: by default, model is centered (middle of bounding box goes to 0,0,0).
|
|
|
|
+
|
|
|
|
+--------------------------------------------------
|
|
|
|
+How to use generated JS file in your HTML document
|
|
|
|
+--------------------------------------------------
|
|
|
|
+
|
|
|
|
+ <script type="text/javascript" src="Three.js"></script>
|
|
|
|
+ <script type="text/javascript" src="ModelName.js"></script>
|
|
|
|
+
|
|
|
|
+ ...
|
|
|
|
+
|
|
|
|
+ <script type="text/javascript">
|
|
|
|
+ ...
|
|
|
|
+
|
|
|
|
+ var normalizeUVsFlag = 1; // set to 1 if canvas render has missing materials
|
|
|
|
+ var geometry = new ModelName( path_to_textures );
|
|
|
|
+ var mesh = new THREE.Mesh( geometry, geometry.materials, 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 cannot have more than 65,536 vertices
|
|
|
|
+ (this comes from WebGL using just 16-bit indices,
|
|
|
|
+ could be worked around by expanding indexed
|
|
|
|
+ faces into full vertex definitions)
|
|
|
|
+
|
|
|
|
+ - texture coordinates can be wrong in canvas renderer
|
|
|
|
+ (there is crude normalization, but it doesn't
|
|
|
|
+ work for all cases)
|
|
|
|
+
|
|
|
|
+ - everything is using smoothing
|
|
|
|
+ (if you want flat shading for whole mesh,
|
|
|
|
+ don't export normals, then Three will
|
|
|
|
+ compute own normals)
|
|
|
|
+
|
|
|
|
+----------------------------------------------
|
|
|
|
+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
|
|
|
|
+
|
|
|
|
+# #####################################################
|
|
|
|
+# Configuration
|
|
|
|
+# #####################################################
|
|
|
|
+ALIGN = "center" # center bottom top none
|
|
|
|
+
|
|
|
|
+# default colors for debugging (each material gets one distinct color):
|
|
|
|
+# white, red, green, blue, yellow, cyan, magenta
|
|
|
|
+COLORS = [0xffeeeeee, 0xffee0000, 0xff00ee00, 0xff0000ee, 0xffeeee00, 0xff00eeee, 0xffee00ee]
|
|
|
|
+
|
|
|
|
+# #####################################################
|
|
|
|
+# Templates
|
|
|
|
+# #####################################################
|
|
|
|
+TEMPLATE_FILE = u"""\
|
|
|
|
+// Converted from: %(fname)s
|
|
|
|
+// vertices: %(nvertex)d
|
|
|
|
+// faces: %(nface)d
|
|
|
|
+// materials: %(nmaterial)d
|
|
|
|
+//
|
|
|
|
+// This file was generated by "convert_obj_treejs.py"
|
|
|
|
+
|
|
|
|
+var %(name)s = function ( urlbase ) {
|
|
|
|
+ var scope = this;
|
|
|
|
+
|
|
|
|
+ THREE.Geometry.call(this);
|
|
|
|
+
|
|
|
|
+ var materials = [%(materials)s];
|
|
|
|
+
|
|
|
|
+ init_materials();
|
|
|
|
+
|
|
|
|
+ var normals = [%(normals)s];
|
|
|
|
+
|
|
|
|
+%(vertices)s
|
|
|
|
+
|
|
|
|
+%(uvs)s
|
|
|
|
+
|
|
|
|
+%(faces)s
|
|
|
|
+
|
|
|
|
+ this.computeCentroids();
|
|
|
|
+ this.computeNormals();
|
|
|
|
+
|
|
|
|
+ function material_color( mi ) {
|
|
|
|
+ var m = materials[mi];
|
|
|
|
+ if( m.col_diffuse )
|
|
|
|
+ return (m.col_diffuse[0]*255 << 16) + (m.col_diffuse[1]*255 << 8) + m.col_diffuse[2]*255;
|
|
|
|
+ else if ( m.a_dbg_color )
|
|
|
|
+ return m.a_dbg_color;
|
|
|
|
+ else
|
|
|
|
+ return 0xffeeeeee;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function v( x, y, z ) {
|
|
|
|
+ scope.vertices.push( new THREE.Vertex( new THREE.Vector3( x, y, z ) ) );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function f3( a, b, c, material ) {
|
|
|
|
+ var color = material_color(material);
|
|
|
|
+ scope.faces.push( new THREE.Face3( a, b, c, null, new THREE.Color(color), material ) );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function f4( a, b, c, d, material ) {
|
|
|
|
+ var color = material_color(material);
|
|
|
|
+ scope.faces.push( new THREE.Face4( a, b, c, d, null, new THREE.Color(color), material ) );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function f3n( a, b, c, material, n1, n2, n3 ) {
|
|
|
|
+ var color = material_color(material);
|
|
|
|
+ var n1x = normals[n1][0];
|
|
|
|
+ var n1y = normals[n1][1];
|
|
|
|
+ var n1z = normals[n1][2];
|
|
|
|
+ var n2x = normals[n2][0];
|
|
|
|
+ var n2y = normals[n2][1];
|
|
|
|
+ var n2z = normals[n2][2];
|
|
|
|
+ var n3x = normals[n3][0];
|
|
|
|
+ var n3y = normals[n3][1];
|
|
|
|
+ var n3z = normals[n3][2];
|
|
|
|
+ scope.faces.push( new THREE.Face3( a, b, c,
|
|
|
|
+ [new THREE.Vector3( n1x, n1y, n1z ), new THREE.Vector3( n2x, n2y, n2z ), new THREE.Vector3( n3x, n3y, n3z )],
|
|
|
|
+ new THREE.Color(color), material ) );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function f4n( a, b, c, d, material, n1, n2, n3, n4 ) {
|
|
|
|
+ var color = material_color(material);
|
|
|
|
+ var n1x = normals[n1][0];
|
|
|
|
+ var n1y = normals[n1][1];
|
|
|
|
+ var n1z = normals[n1][2];
|
|
|
|
+ var n2x = normals[n2][0];
|
|
|
|
+ var n2y = normals[n2][1];
|
|
|
|
+ var n2z = normals[n2][2];
|
|
|
|
+ var n3x = normals[n3][0];
|
|
|
|
+ var n3y = normals[n3][1];
|
|
|
|
+ var n3z = normals[n3][2];
|
|
|
|
+ var n4x = normals[n4][0];
|
|
|
|
+ var n4y = normals[n4][1];
|
|
|
|
+ var n4z = normals[n4][2];
|
|
|
|
+ scope.faces.push( new THREE.Face4( a, b, c, d,
|
|
|
|
+ [new THREE.Vector3( n1x, n1y, n1z ), new THREE.Vector3( n2x, n2y, n2z ), new THREE.Vector3( n3x, n3y, n3z ), new THREE.Vector3( n4x, n4y, n4z )],
|
|
|
|
+ new THREE.Color(color), material ) );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function uv( u1, v1, u2, v2, u3, v3, u4, v4 ) {
|
|
|
|
+ var uv = [];
|
|
|
|
+ uv.push( new THREE.UV( u1, v1 ) );
|
|
|
|
+ uv.push( new THREE.UV( u2, v2 ) );
|
|
|
|
+ uv.push( new THREE.UV( u3, v3 ) );
|
|
|
|
+ if ( u4 && v4 ) uv.push( new THREE.UV( u4, v4 ) );
|
|
|
|
+ scope.uvs.push( uv );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function init_materials() {
|
|
|
|
+ scope.materials = [];
|
|
|
|
+ for(var i=0; i<materials.length; ++i) {
|
|
|
|
+ scope.materials[i] = create_material( materials[i], urlbase );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function is_pow2( n ) {
|
|
|
|
+ var l = Math.log(n) / Math.LN2;
|
|
|
|
+ return Math.floor(l) == l;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function nearest_pow2(n) {
|
|
|
|
+ var l = Math.log(n) / Math.LN2;
|
|
|
|
+ return Math.pow( 2, Math.round(l) );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function create_material( m ) {
|
|
|
|
+ var material;
|
|
|
|
+
|
|
|
|
+ if( m.map_diffuse && urlbase ) {
|
|
|
|
+ var texture = document.createElement( 'canvas' );
|
|
|
|
+
|
|
|
|
+ material = new THREE.MeshBitmapUVMappingMaterial( texture );
|
|
|
|
+ var image = new Image();
|
|
|
|
+
|
|
|
|
+ image.onload = function () {
|
|
|
|
+
|
|
|
|
+ if ( !is_pow2(this.width) || !is_pow2(this.height) ) {
|
|
|
|
+
|
|
|
|
+ var w = nearest_pow2( this.width );
|
|
|
|
+ var h = nearest_pow2( this.height );
|
|
|
|
+ material.bitmap.width = w;
|
|
|
|
+ material.bitmap.height = h;
|
|
|
|
+ material.bitmap.getContext("2d").drawImage( this, 0, 0, w, h );
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ material.bitmap = this;
|
|
|
|
+ }
|
|
|
|
+ material.loaded = 1;
|
|
|
|
+
|
|
|
|
+ };
|
|
|
|
+ image.src = urlbase + "/" + m.map_diffuse;
|
|
|
|
+ }
|
|
|
|
+ else if( m.col_diffuse ) {
|
|
|
|
+ var color = (m.col_diffuse[0]*255 << 16) + (m.col_diffuse[1]*255 << 8) + m.col_diffuse[2]*255;
|
|
|
|
+ material = new THREE.MeshColorFillMaterial( color, m.transparency );
|
|
|
|
+ }
|
|
|
|
+ else if( m.a_dbg_color ) {
|
|
|
|
+ material = new THREE.MeshColorFillMaterial( m.a_dbg_color );
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ material = new THREE.MeshFaceColorFillMaterial( );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return material;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+%(name)s.prototype = new THREE.Geometry();
|
|
|
|
+%(name)s.prototype.constructor = %(name)s;
|
|
|
|
+"""
|
|
|
|
+
|
|
|
|
+TEMPLATE_VERTEX = "\tv(%f,%f,%f);"
|
|
|
|
+
|
|
|
|
+TEMPLATE_UV3 = "\tuv(%f,%f,%f,%f,%f,%f);"
|
|
|
|
+TEMPLATE_UV4 = "\tuv(%f,%f,%f,%f,%f,%f,%f,%f);"
|
|
|
|
+
|
|
|
|
+TEMPLATE_FACE3 = "\tf3(%d,%d,%d,%d);"
|
|
|
|
+TEMPLATE_FACE4 = "\tf4(%d,%d,%d,%d,%d);"
|
|
|
|
+
|
|
|
|
+TEMPLATE_FACE3N = "\tf3n(%d,%d,%d, %d, %d,%d,%d);"
|
|
|
|
+TEMPLATE_FACE4N = "\tf4n(%d,%d,%d,%d, %d, %d,%d,%d,%d);"
|
|
|
|
+
|
|
|
|
+TEMPLATE_N = "[%f,%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].capitalize()
|
|
|
|
+
|
|
|
|
+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_uv(f, uvs):
|
|
|
|
+ ui = f['uv']
|
|
|
|
+ if len(ui) == 3:
|
|
|
|
+ return TEMPLATE_UV3 % (uvs[ui[0]-1][0], 1.0 - uvs[ui[0]-1][1],
|
|
|
|
+ uvs[ui[1]-1][0], 1.0 - uvs[ui[1]-1][1],
|
|
|
|
+ uvs[ui[2]-1][0], 1.0 - uvs[ui[2]-1][1])
|
|
|
|
+ elif len(ui) == 4:
|
|
|
|
+ return TEMPLATE_UV4 % (uvs[ui[0]-1][0], 1.0 - uvs[ui[0]-1][1],
|
|
|
|
+ uvs[ui[1]-1][0], 1.0 - uvs[ui[1]-1][1],
|
|
|
|
+ uvs[ui[2]-1][0], 1.0 - uvs[ui[2]-1][1],
|
|
|
|
+ uvs[ui[3]-1][0], 1.0 - uvs[ui[3]-1][1])
|
|
|
|
+ return ""
|
|
|
|
+
|
|
|
|
+def generate_face(f):
|
|
|
|
+ vi = f['vertex']
|
|
|
|
+ if f["normal"]:
|
|
|
|
+ ni = f['normal']
|
|
|
|
+ if len(vi) == 3:
|
|
|
|
+ return TEMPLATE_FACE3N % (vi[0]-1, vi[1]-1, vi[2]-1, f['material'], ni[0]-1, ni[1]-1, ni[2]-1)
|
|
|
|
+ elif len(vi) == 4:
|
|
|
|
+ return TEMPLATE_FACE4N % (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)
|
|
|
|
+ else:
|
|
|
|
+ if len(vi) == 3:
|
|
|
|
+ return TEMPLATE_FACE3 % (vi[0]-1, vi[1]-1, vi[2]-1, f['material'])
|
|
|
|
+ elif len(vi) == 4:
|
|
|
|
+ return TEMPLATE_FACE4 % (vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1, f['material'])
|
|
|
|
+ return ""
|
|
|
|
+
|
|
|
|
+def generate_normal(n):
|
|
|
|
+ return TEMPLATE_N % (n[0], n[1], n[2])
|
|
|
|
+
|
|
|
|
+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%x" % COLORS[i]
|
|
|
|
+ else:
|
|
|
|
+ return "0x%x" % (int(0xffffff * random.random()) + 0xff000000)
|
|
|
|
+
|
|
|
|
+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
|
|
|
|
+
|
|
|
|
+# #####################################################
|
|
|
|
+# API
|
|
|
|
+# #####################################################
|
|
|
|
+def convert(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)
|
|
|
|
+
|
|
|
|
+ random.seed(42) # to get well defined color order for materials
|
|
|
|
+
|
|
|
|
+ uv_string = ""
|
|
|
|
+ if len(uvs)>0:
|
|
|
|
+ uv_string = "\n".join([generate_uv(f, uvs) for f in faces])
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ mtl = {}
|
|
|
|
+ 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):
|
|
|
|
+ mtl = parse_mtl(fname)
|
|
|
|
+
|
|
|
|
+ else:
|
|
|
|
+ print "Couldn't find [%s]" % fname
|
|
|
|
+
|
|
|
|
+ if not mtl:
|
|
|
|
+ # if there is no specified MTL or if loading failed,
|
|
|
|
+ # generate default materials with debug colors
|
|
|
|
+ mtl = generate_mtl(materials)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ text = TEMPLATE_FILE % {
|
|
|
|
+ "name" : get_name(outfile),
|
|
|
|
+ "vertices" : "\n".join([generate_vertex(v) for v in vertices]),
|
|
|
|
+ "faces" : "\n".join([generate_face(f) for f in faces]),
|
|
|
|
+ "uvs" : uv_string,
|
|
|
|
+ "normals" : ",".join(generate_normal(n) for n in normals),
|
|
|
|
+
|
|
|
|
+ "materials" : generate_materials(mtl, materials),
|
|
|
|
+
|
|
|
|
+ "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))
|
|
|
|
+
|
|
|
|
+# #############################################################################
|
|
|
|
+# Helpers
|
|
|
|
+# #############################################################################
|
|
|
|
+def usage():
|
|
|
|
+ print "Usage: %s -i filename.obj -o filename.js [-a center|top|bottom]" % 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:", ["help", "input=", "output=", "align="])
|
|
|
|
+
|
|
|
|
+ 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
|
|
|
|
+
|
|
|
|
+ if infile == "" or outfile == "":
|
|
|
|
+ usage()
|
|
|
|
+ sys.exit(2)
|
|
|
|
+
|
|
|
|
+ print "Converting [%s] into [%s] ..." % (infile, outfile)
|
|
|
|
+ convert(infile, outfile)
|
|
|
|
+
|
|
|
|
|