浏览代码

Merge branch 'dev' of git://github.com/mrdoob/three.js.git into vector-index-accessors

Ben Houston 12 年之前
父节点
当前提交
9eea49592f

+ 8 - 18
build/three.js

@@ -10052,28 +10052,18 @@ THREE.SceneLoader.prototype.parse = function ( json, callbackFinished, url ) {
 
 						var loader = scope.hierarchyHandlerMap[ objJSON.type ][ "loaderObject" ];
 
-						// OBJLoader
-
-						if ( loader.addEventListener ) {
-
-							loader.addEventListener( 'load', create_callback_hierachy( objID, parent, material, objJSON ) );
-							loader.load( get_url( objJSON.url, data.urlBaseType ) );
-
-						} else {
-
-							// ColladaLoader
+						// ColladaLoader
 
-							if ( loader.options ) {
+						if ( loader.options ) {
 
-								loader.load( get_url( objJSON.url, data.urlBaseType ), create_callback_hierachy( objID, parent, material, objJSON ) );
+							loader.load( get_url( objJSON.url, data.urlBaseType ), create_callback_hierachy( objID, parent, material, objJSON ) );
 
-							// UTF8Loader
-
-							} else {
+						// UTF8Loader
+						// OBJLoader
 
-								loader.load( get_url( objJSON.url, data.urlBaseType ), create_callback_hierachy( objID, parent, material, objJSON ), loaderParameters );
+						} else {
 
-							}
+							loader.load( get_url( objJSON.url, data.urlBaseType ), create_callback_hierachy( objID, parent, material, objJSON ), loaderParameters );
 
 						}
 
@@ -10859,7 +10849,7 @@ THREE.SceneLoader.prototype.parse = function ( json, callbackFinished, url ) {
 
 			} else if ( parID === "combine" ) {
 
-				matJSON.parameters[ parID ] = ( matJSON.parameters[ parID ] == "MixOperation" ) ? THREE.MixOperation : THREE.MultiplyOperation;
+				matJSON.parameters[ parID ] = matJSON.parameters[ parID ] in THREE ? THREE[ matJSON.parameters[ parID ] ] : THREE.MultiplyOperation;
 
 			} else if ( parID === "vertexColors" ) {
 

+ 20 - 20
build/three.min.js

@@ -196,26 +196,26 @@ a.bones;d.animation=a.animation;if(void 0!==a.morphTargets){i=0;for(k=a.morphTar
 THREE.SceneLoader.prototype.load=function(a,b){var c=this,d=new XMLHttpRequest;d.onreadystatechange=function(){if(4===d.readyState)if(200===d.status||0===d.status){var e=JSON.parse(d.responseText);c.parse(e,b,a)}else console.error("THREE.SceneLoader: Couldn't load ["+a+"] ["+d.status+"]")};d.open("GET",a,!0);d.send(null)};THREE.SceneLoader.prototype.addGeometryHandler=function(a,b){this.geometryHandlerMap[a]={loaderClass:b}};
 THREE.SceneLoader.prototype.addHierarchyHandler=function(a,b){this.hierarchyHandlerMap[a]={loaderClass:b}};
 THREE.SceneLoader.prototype.parse=function(a,b,c){function d(a,b){return"relativeToHTML"==b?a:p+"/"+a}function e(){f(A.scene,G.objects)}function f(a,b){var c,e,g,i,k,p;for(p in b)if(void 0===A.objects[p]){var l=b[p],q=null;if(l.type&&l.type in n.hierarchyHandlerMap){if(void 0===l.loading){c={type:1,url:1,material:1,position:1,rotation:1,scale:1,visible:1,children:1,properties:1,skin:1,morph:1,mirroredLoop:1,duration:1};e={};for(var t in l)t in c||(e[t]=l[t]);r=A.materials[l.material];l.loading=!0;
-c=n.hierarchyHandlerMap[l.type].loaderObject;c.addEventListener?(c.addEventListener("load",h(p,a,r,l)),c.load(d(l.url,G.urlBaseType))):c.options?c.load(d(l.url,G.urlBaseType),h(p,a,r,l)):c.load(d(l.url,G.urlBaseType),h(p,a,r,l),e)}}else if(void 0!==l.geometry){if(m=A.geometries[l.geometry]){q=!1;r=A.materials[l.material];q=r instanceof THREE.ShaderMaterial;e=l.position;g=l.rotation;i=l.scale;c=l.matrix;k=0;l.material||(r=new THREE.MeshFaceMaterial(A.face_materials[l.geometry]));r instanceof THREE.MeshFaceMaterial&&
-0===r.materials.length&&(r=new THREE.MeshFaceMaterial(A.face_materials[l.geometry]));if(r instanceof THREE.MeshFaceMaterial)for(var y=0;y<r.materials.length;y++)q=q||r.materials[y]instanceof THREE.ShaderMaterial;q&&m.computeTangents();l.skin?q=new THREE.SkinnedMesh(m,r):l.morph?(q=new THREE.MorphAnimMesh(m,r),void 0!==l.duration&&(q.duration=l.duration),void 0!==l.time&&(q.time=l.time),void 0!==l.mirroredLoop&&(q.mirroredLoop=l.mirroredLoop),r.morphNormals&&m.computeMorphNormals()):q=new THREE.Mesh(m,
-r);q.name=p;c?(q.matrixAutoUpdate=!1,q.matrix.set(c[0],c[1],c[2],c[3],c[4],c[5],c[6],c[7],c[8],c[9],c[10],c[11],c[12],c[13],c[14],c[15])):(q.position.set(e[0],e[1],e[2]),k?(q.quaternion.set(k[0],k[1],k[2],k[3]),q.useQuaternion=!0):q.rotation.set(g[0],g[1],g[2]),q.scale.set(i[0],i[1],i[2]));q.visible=l.visible;q.castShadow=l.castShadow;q.receiveShadow=l.receiveShadow;a.add(q);A.objects[p]=q}}else"DirectionalLight"===l.type||"PointLight"===l.type||"AmbientLight"===l.type?(B=void 0!==l.color?l.color:
-16777215,x=void 0!==l.intensity?l.intensity:1,"DirectionalLight"===l.type?(e=l.direction,u=new THREE.DirectionalLight(B,x),u.position.set(e[0],e[1],e[2]),l.target&&(H.push({object:u,targetName:l.target}),u.target=null)):"PointLight"===l.type?(e=l.position,c=l.distance,u=new THREE.PointLight(B,x,c),u.position.set(e[0],e[1],e[2])):"AmbientLight"===l.type&&(u=new THREE.AmbientLight(B)),a.add(u),u.name=p,A.lights[p]=u,A.objects[p]=u):"PerspectiveCamera"===l.type||"OrthographicCamera"===l.type?("PerspectiveCamera"===
-l.type?s=new THREE.PerspectiveCamera(l.fov,l.aspect,l.near,l.far):"OrthographicCamera"===l.type&&(s=new THREE.OrthographicCamera(l.left,l.right,l.top,l.bottom,l.near,l.far)),e=l.position,s.position.set(e[0],e[1],e[2]),a.add(s),s.name=p,A.cameras[p]=s,A.objects[p]=s):(e=l.position,g=l.rotation,i=l.scale,k=0,q=new THREE.Object3D,q.name=p,q.position.set(e[0],e[1],e[2]),k?(q.quaternion.set(k[0],k[1],k[2],k[3]),q.useQuaternion=!0):q.rotation.set(g[0],g[1],g[2]),q.scale.set(i[0],i[1],i[2]),q.visible=void 0!==
-l.visible?l.visible:!1,a.add(q),A.objects[p]=q,A.empties[p]=q);if(q){if(void 0!==l.properties)for(var z in l.properties)q.properties[z]=l.properties[z];void 0!==l.children&&f(q,l.children)}}}function g(a){return function(b,c){A.geometries[a]=b;A.face_materials[a]=c;e();t-=1;n.onLoadComplete();k()}}function h(a,b,c,d){return function(f){var f=f.content?f.content:f.dae?f.scene:f,g=d.position,h=d.rotation,i=d.quaternion,l=d.scale;f.position.set(g[0],g[1],g[2]);i?(f.quaternion.set(i[0],i[1],i[2],i[3]),
-f.useQuaternion=!0):f.rotation.set(h[0],h[1],h[2]);f.scale.set(l[0],l[1],l[2]);c&&f.traverse(function(a){a.material=c});var m=void 0!==d.visible?d.visible:!0;f.traverse(function(a){a.visible=m});b.add(f);f.name=a;A.objects[a]=f;e();t-=1;n.onLoadComplete();k()}}function i(a){return function(b,c){A.geometries[a]=b;A.face_materials[a]=c}}function k(){n.callbackProgress({totalModels:C,totalTextures:z,loadedModels:C-t,loadedTextures:z-F},A);n.onLoadProgress();if(0===t&&0===F){for(var a=0;a<H.length;a++){var c=
-H[a],d=A.objects[c.targetName];d?c.object.target=d:(c.object.target=new THREE.Object3D,A.scene.add(c.object.target));c.object.target.properties.targetInverse=c.object}b(A)}}var n=this,p=THREE.Loader.prototype.extractUrlBase(c),m,r,s,l,q,u,B,x,t,F,C,z,A,H=[],G=a,I;for(I in this.geometryHandlerMap)a=this.geometryHandlerMap[I].loaderClass,this.geometryHandlerMap[I].loaderObject=new a;for(I in this.hierarchyHandlerMap)a=this.hierarchyHandlerMap[I].loaderClass,this.hierarchyHandlerMap[I].loaderObject=
-new a;F=t=0;A={scene:new THREE.Scene,geometries:{},face_materials:{},materials:{},textures:{},objects:{},cameras:{},lights:{},fogs:{},empties:{}};if(G.transform&&(I=G.transform.position,a=G.transform.rotation,c=G.transform.scale,I&&A.scene.position.set(I[0],I[1],I[2]),a&&A.scene.rotation.set(a[0],a[1],a[2]),c&&A.scene.scale.set(c[0],c[1],c[2]),I||a||c))A.scene.updateMatrix(),A.scene.updateMatrixWorld();I=function(a){return function(){F-=a;k();n.onLoadComplete()}};for(var $ in G.fogs)a=G.fogs[$],"linear"===
-a.type?l=new THREE.Fog(0,a.near,a.far):"exp2"===a.type&&(l=new THREE.FogExp2(0,a.density)),a=a.color,l.color.setRGB(a[0],a[1],a[2]),A.fogs[$]=l;for(var D in G.geometries)l=G.geometries[D],l.type in this.geometryHandlerMap&&(t+=1,n.onLoadStart());for(var L in G.objects)l=G.objects[L],l.type&&l.type in this.hierarchyHandlerMap&&(t+=1,n.onLoadStart());C=t;for(D in G.geometries)if(l=G.geometries[D],"cube"===l.type)m=new THREE.CubeGeometry(l.width,l.height,l.depth,l.widthSegments,l.heightSegments,l.depthSegments),
-A.geometries[D]=m;else if("plane"===l.type)m=new THREE.PlaneGeometry(l.width,l.height,l.widthSegments,l.heightSegments),A.geometries[D]=m;else if("sphere"===l.type)m=new THREE.SphereGeometry(l.radius,l.widthSegments,l.heightSegments),A.geometries[D]=m;else if("cylinder"===l.type)m=new THREE.CylinderGeometry(l.topRad,l.botRad,l.height,l.radSegs,l.heightSegs),A.geometries[D]=m;else if("torus"===l.type)m=new THREE.TorusGeometry(l.radius,l.tube,l.segmentsR,l.segmentsT),A.geometries[D]=m;else if("icosahedron"===
-l.type)m=new THREE.IcosahedronGeometry(l.radius,l.subdivisions),A.geometries[D]=m;else if(l.type in this.geometryHandlerMap){L={};for(q in l)"type"!==q&&"url"!==q&&(L[q]=l[q]);this.geometryHandlerMap[l.type].loaderObject.load(d(l.url,G.urlBaseType),g(D),L)}else"embedded"===l.type&&(L=G.embeds[l.id],L.metadata=G.metadata,L&&this.geometryHandlerMap.ascii.loaderObject.createModel(L,i(D),""));for(var y in G.textures)if(D=G.textures[y],D.url instanceof Array){F+=D.url.length;for(q=0;q<D.url.length;q++)n.onLoadStart()}else F+=
-1,n.onLoadStart();z=F;for(y in G.textures){D=G.textures[y];void 0!==D.mapping&&void 0!==THREE[D.mapping]&&(D.mapping=new THREE[D.mapping]);if(D.url instanceof Array){L=D.url.length;l=[];for(q=0;q<L;q++)l[q]=d(D.url[q],G.urlBaseType);q=(q=l[0].endsWith(".dds"))?THREE.ImageUtils.loadCompressedTextureCube(l,D.mapping,I(L)):THREE.ImageUtils.loadTextureCube(l,D.mapping,I(L))}else q=D.url.toLowerCase().endsWith(".dds"),L=d(D.url,G.urlBaseType),l=I(1),q=q?THREE.ImageUtils.loadCompressedTexture(L,D.mapping,
-l):THREE.ImageUtils.loadTexture(L,D.mapping,l),void 0!==THREE[D.minFilter]&&(q.minFilter=THREE[D.minFilter]),void 0!==THREE[D.magFilter]&&(q.magFilter=THREE[D.magFilter]),D.anisotropy&&(q.anisotropy=D.anisotropy),D.repeat&&(q.repeat.set(D.repeat[0],D.repeat[1]),1!==D.repeat[0]&&(q.wrapS=THREE.RepeatWrapping),1!==D.repeat[1]&&(q.wrapT=THREE.RepeatWrapping)),D.offset&&q.offset.set(D.offset[0],D.offset[1]),D.wrap&&(L={repeat:THREE.RepeatWrapping,mirror:THREE.MirroredRepeatWrapping},void 0!==L[D.wrap[0]]&&
-(q.wrapS=L[D.wrap[0]]),void 0!==L[D.wrap[1]]&&(q.wrapT=L[D.wrap[1]]));A.textures[y]=q}var K,J;for(K in G.materials){y=G.materials[K];for(J in y.parameters)"envMap"===J||"map"===J||"lightMap"===J||"bumpMap"===J?y.parameters[J]=A.textures[y.parameters[J]]:"shading"===J?y.parameters[J]="flat"===y.parameters[J]?THREE.FlatShading:THREE.SmoothShading:"side"===J?y.parameters[J]="double"==y.parameters[J]?THREE.DoubleSide:"back"==y.parameters[J]?THREE.BackSide:THREE.FrontSide:"blending"===J?y.parameters[J]=
-y.parameters[J]in THREE?THREE[y.parameters[J]]:THREE.NormalBlending:"combine"===J?y.parameters[J]="MixOperation"==y.parameters[J]?THREE.MixOperation:THREE.MultiplyOperation:"vertexColors"===J?"face"==y.parameters[J]?y.parameters[J]=THREE.FaceColors:y.parameters[J]&&(y.parameters[J]=THREE.VertexColors):"wrapRGB"===J&&(I=y.parameters[J],y.parameters[J]=new THREE.Vector3(I[0],I[1],I[2]));void 0!==y.parameters.opacity&&1>y.parameters.opacity&&(y.parameters.transparent=!0);y.parameters.normalMap?(I=THREE.ShaderUtils.lib.normal,
-D=THREE.UniformsUtils.clone(I.uniforms),q=y.parameters.color,L=y.parameters.specular,l=y.parameters.ambient,$=y.parameters.shininess,D.tNormal.value=A.textures[y.parameters.normalMap],y.parameters.normalScale&&D.uNormalScale.value.set(y.parameters.normalScale[0],y.parameters.normalScale[1]),y.parameters.map&&(D.tDiffuse.value=y.parameters.map,D.enableDiffuse.value=!0),y.parameters.envMap&&(D.tCube.value=y.parameters.envMap,D.enableReflection.value=!0,D.uReflectivity.value=y.parameters.reflectivity),
-y.parameters.lightMap&&(D.tAO.value=y.parameters.lightMap,D.enableAO.value=!0),y.parameters.specularMap&&(D.tSpecular.value=A.textures[y.parameters.specularMap],D.enableSpecular.value=!0),y.parameters.displacementMap&&(D.tDisplacement.value=A.textures[y.parameters.displacementMap],D.enableDisplacement.value=!0,D.uDisplacementBias.value=y.parameters.displacementBias,D.uDisplacementScale.value=y.parameters.displacementScale),D.uDiffuseColor.value.setHex(q),D.uSpecularColor.value.setHex(L),D.uAmbientColor.value.setHex(l),
-D.uShininess.value=$,y.parameters.opacity&&(D.uOpacity.value=y.parameters.opacity),r=new THREE.ShaderMaterial({fragmentShader:I.fragmentShader,vertexShader:I.vertexShader,uniforms:D,lights:!0,fog:!0})):r=new THREE[y.type](y.parameters);A.materials[K]=r}for(K in G.materials)if(y=G.materials[K],y.parameters.materials){J=[];for(q=0;q<y.parameters.materials.length;q++)J.push(A.materials[y.parameters.materials[q]]);A.materials[K].materials=J}e();A.cameras&&G.defaults.camera&&(A.currentCamera=A.cameras[G.defaults.camera]);
-A.fogs&&G.defaults.fog&&(A.scene.fog=A.fogs[G.defaults.fog]);a=G.defaults.bgcolor;A.bgColor=new THREE.Color;A.bgColor.setRGB(a[0],a[1],a[2]);A.bgColorAlpha=G.defaults.bgalpha;n.callbackSync(A);k()};THREE.TextureLoader=function(){THREE.EventDispatcher.call(this);this.crossOrigin=null};THREE.TextureLoader.prototype={constructor:THREE.TextureLoader,load:function(a){var b=this,c=new Image;c.addEventListener("load",function(){var a=new THREE.Texture(c);a.needsUpdate=!0;b.dispatchEvent({type:"load",content:a})},!1);c.addEventListener("error",function(){b.dispatchEvent({type:"error",message:"Couldn't load URL ["+a+"]"})},!1);b.crossOrigin&&(c.crossOrigin=b.crossOrigin);c.src=a}};THREE.Material=function(){THREE.EventDispatcher.call(this);this.id=THREE.MaterialIdCount++;this.name="";this.side=THREE.FrontSide;this.opacity=1;this.transparent=!1;this.blending=THREE.NormalBlending;this.blendSrc=THREE.SrcAlphaFactor;this.blendDst=THREE.OneMinusSrcAlphaFactor;this.blendEquation=THREE.AddEquation;this.depthWrite=this.depthTest=!0;this.polygonOffset=!1;this.alphaTest=this.polygonOffsetUnits=this.polygonOffsetFactor=0;this.overdraw=!1;this.needsUpdate=this.visible=!0};
+c=n.hierarchyHandlerMap[l.type].loaderObject;c.options?c.load(d(l.url,G.urlBaseType),h(p,a,r,l)):c.load(d(l.url,G.urlBaseType),h(p,a,r,l),e)}}else if(void 0!==l.geometry){if(m=A.geometries[l.geometry]){q=!1;r=A.materials[l.material];q=r instanceof THREE.ShaderMaterial;e=l.position;g=l.rotation;i=l.scale;c=l.matrix;k=0;l.material||(r=new THREE.MeshFaceMaterial(A.face_materials[l.geometry]));r instanceof THREE.MeshFaceMaterial&&0===r.materials.length&&(r=new THREE.MeshFaceMaterial(A.face_materials[l.geometry]));
+if(r instanceof THREE.MeshFaceMaterial)for(var y=0;y<r.materials.length;y++)q=q||r.materials[y]instanceof THREE.ShaderMaterial;q&&m.computeTangents();l.skin?q=new THREE.SkinnedMesh(m,r):l.morph?(q=new THREE.MorphAnimMesh(m,r),void 0!==l.duration&&(q.duration=l.duration),void 0!==l.time&&(q.time=l.time),void 0!==l.mirroredLoop&&(q.mirroredLoop=l.mirroredLoop),r.morphNormals&&m.computeMorphNormals()):q=new THREE.Mesh(m,r);q.name=p;c?(q.matrixAutoUpdate=!1,q.matrix.set(c[0],c[1],c[2],c[3],c[4],c[5],
+c[6],c[7],c[8],c[9],c[10],c[11],c[12],c[13],c[14],c[15])):(q.position.set(e[0],e[1],e[2]),k?(q.quaternion.set(k[0],k[1],k[2],k[3]),q.useQuaternion=!0):q.rotation.set(g[0],g[1],g[2]),q.scale.set(i[0],i[1],i[2]));q.visible=l.visible;q.castShadow=l.castShadow;q.receiveShadow=l.receiveShadow;a.add(q);A.objects[p]=q}}else"DirectionalLight"===l.type||"PointLight"===l.type||"AmbientLight"===l.type?(B=void 0!==l.color?l.color:16777215,x=void 0!==l.intensity?l.intensity:1,"DirectionalLight"===l.type?(e=l.direction,
+u=new THREE.DirectionalLight(B,x),u.position.set(e[0],e[1],e[2]),l.target&&(H.push({object:u,targetName:l.target}),u.target=null)):"PointLight"===l.type?(e=l.position,c=l.distance,u=new THREE.PointLight(B,x,c),u.position.set(e[0],e[1],e[2])):"AmbientLight"===l.type&&(u=new THREE.AmbientLight(B)),a.add(u),u.name=p,A.lights[p]=u,A.objects[p]=u):"PerspectiveCamera"===l.type||"OrthographicCamera"===l.type?("PerspectiveCamera"===l.type?s=new THREE.PerspectiveCamera(l.fov,l.aspect,l.near,l.far):"OrthographicCamera"===
+l.type&&(s=new THREE.OrthographicCamera(l.left,l.right,l.top,l.bottom,l.near,l.far)),e=l.position,s.position.set(e[0],e[1],e[2]),a.add(s),s.name=p,A.cameras[p]=s,A.objects[p]=s):(e=l.position,g=l.rotation,i=l.scale,k=0,q=new THREE.Object3D,q.name=p,q.position.set(e[0],e[1],e[2]),k?(q.quaternion.set(k[0],k[1],k[2],k[3]),q.useQuaternion=!0):q.rotation.set(g[0],g[1],g[2]),q.scale.set(i[0],i[1],i[2]),q.visible=void 0!==l.visible?l.visible:!1,a.add(q),A.objects[p]=q,A.empties[p]=q);if(q){if(void 0!==l.properties)for(var z in l.properties)q.properties[z]=
+l.properties[z];void 0!==l.children&&f(q,l.children)}}}function g(a){return function(b,c){A.geometries[a]=b;A.face_materials[a]=c;e();t-=1;n.onLoadComplete();k()}}function h(a,b,c,d){return function(f){var f=f.content?f.content:f.dae?f.scene:f,g=d.position,h=d.rotation,i=d.quaternion,l=d.scale;f.position.set(g[0],g[1],g[2]);i?(f.quaternion.set(i[0],i[1],i[2],i[3]),f.useQuaternion=!0):f.rotation.set(h[0],h[1],h[2]);f.scale.set(l[0],l[1],l[2]);c&&f.traverse(function(a){a.material=c});var m=void 0!==
+d.visible?d.visible:!0;f.traverse(function(a){a.visible=m});b.add(f);f.name=a;A.objects[a]=f;e();t-=1;n.onLoadComplete();k()}}function i(a){return function(b,c){A.geometries[a]=b;A.face_materials[a]=c}}function k(){n.callbackProgress({totalModels:C,totalTextures:z,loadedModels:C-t,loadedTextures:z-F},A);n.onLoadProgress();if(0===t&&0===F){for(var a=0;a<H.length;a++){var c=H[a],d=A.objects[c.targetName];d?c.object.target=d:(c.object.target=new THREE.Object3D,A.scene.add(c.object.target));c.object.target.properties.targetInverse=
+c.object}b(A)}}var n=this,p=THREE.Loader.prototype.extractUrlBase(c),m,r,s,l,q,u,B,x,t,F,C,z,A,H=[],G=a,I;for(I in this.geometryHandlerMap)a=this.geometryHandlerMap[I].loaderClass,this.geometryHandlerMap[I].loaderObject=new a;for(I in this.hierarchyHandlerMap)a=this.hierarchyHandlerMap[I].loaderClass,this.hierarchyHandlerMap[I].loaderObject=new a;F=t=0;A={scene:new THREE.Scene,geometries:{},face_materials:{},materials:{},textures:{},objects:{},cameras:{},lights:{},fogs:{},empties:{}};if(G.transform&&
+(I=G.transform.position,a=G.transform.rotation,c=G.transform.scale,I&&A.scene.position.set(I[0],I[1],I[2]),a&&A.scene.rotation.set(a[0],a[1],a[2]),c&&A.scene.scale.set(c[0],c[1],c[2]),I||a||c))A.scene.updateMatrix(),A.scene.updateMatrixWorld();I=function(a){return function(){F-=a;k();n.onLoadComplete()}};for(var $ in G.fogs)a=G.fogs[$],"linear"===a.type?l=new THREE.Fog(0,a.near,a.far):"exp2"===a.type&&(l=new THREE.FogExp2(0,a.density)),a=a.color,l.color.setRGB(a[0],a[1],a[2]),A.fogs[$]=l;for(var D in G.geometries)l=
+G.geometries[D],l.type in this.geometryHandlerMap&&(t+=1,n.onLoadStart());for(var L in G.objects)l=G.objects[L],l.type&&l.type in this.hierarchyHandlerMap&&(t+=1,n.onLoadStart());C=t;for(D in G.geometries)if(l=G.geometries[D],"cube"===l.type)m=new THREE.CubeGeometry(l.width,l.height,l.depth,l.widthSegments,l.heightSegments,l.depthSegments),A.geometries[D]=m;else if("plane"===l.type)m=new THREE.PlaneGeometry(l.width,l.height,l.widthSegments,l.heightSegments),A.geometries[D]=m;else if("sphere"===l.type)m=
+new THREE.SphereGeometry(l.radius,l.widthSegments,l.heightSegments),A.geometries[D]=m;else if("cylinder"===l.type)m=new THREE.CylinderGeometry(l.topRad,l.botRad,l.height,l.radSegs,l.heightSegs),A.geometries[D]=m;else if("torus"===l.type)m=new THREE.TorusGeometry(l.radius,l.tube,l.segmentsR,l.segmentsT),A.geometries[D]=m;else if("icosahedron"===l.type)m=new THREE.IcosahedronGeometry(l.radius,l.subdivisions),A.geometries[D]=m;else if(l.type in this.geometryHandlerMap){L={};for(q in l)"type"!==q&&"url"!==
+q&&(L[q]=l[q]);this.geometryHandlerMap[l.type].loaderObject.load(d(l.url,G.urlBaseType),g(D),L)}else"embedded"===l.type&&(L=G.embeds[l.id],L.metadata=G.metadata,L&&this.geometryHandlerMap.ascii.loaderObject.createModel(L,i(D),""));for(var y in G.textures)if(D=G.textures[y],D.url instanceof Array){F+=D.url.length;for(q=0;q<D.url.length;q++)n.onLoadStart()}else F+=1,n.onLoadStart();z=F;for(y in G.textures){D=G.textures[y];void 0!==D.mapping&&void 0!==THREE[D.mapping]&&(D.mapping=new THREE[D.mapping]);
+if(D.url instanceof Array){L=D.url.length;l=[];for(q=0;q<L;q++)l[q]=d(D.url[q],G.urlBaseType);q=(q=l[0].endsWith(".dds"))?THREE.ImageUtils.loadCompressedTextureCube(l,D.mapping,I(L)):THREE.ImageUtils.loadTextureCube(l,D.mapping,I(L))}else q=D.url.toLowerCase().endsWith(".dds"),L=d(D.url,G.urlBaseType),l=I(1),q=q?THREE.ImageUtils.loadCompressedTexture(L,D.mapping,l):THREE.ImageUtils.loadTexture(L,D.mapping,l),void 0!==THREE[D.minFilter]&&(q.minFilter=THREE[D.minFilter]),void 0!==THREE[D.magFilter]&&
+(q.magFilter=THREE[D.magFilter]),D.anisotropy&&(q.anisotropy=D.anisotropy),D.repeat&&(q.repeat.set(D.repeat[0],D.repeat[1]),1!==D.repeat[0]&&(q.wrapS=THREE.RepeatWrapping),1!==D.repeat[1]&&(q.wrapT=THREE.RepeatWrapping)),D.offset&&q.offset.set(D.offset[0],D.offset[1]),D.wrap&&(L={repeat:THREE.RepeatWrapping,mirror:THREE.MirroredRepeatWrapping},void 0!==L[D.wrap[0]]&&(q.wrapS=L[D.wrap[0]]),void 0!==L[D.wrap[1]]&&(q.wrapT=L[D.wrap[1]]));A.textures[y]=q}var K,J;for(K in G.materials){y=G.materials[K];
+for(J in y.parameters)"envMap"===J||"map"===J||"lightMap"===J||"bumpMap"===J?y.parameters[J]=A.textures[y.parameters[J]]:"shading"===J?y.parameters[J]="flat"===y.parameters[J]?THREE.FlatShading:THREE.SmoothShading:"side"===J?y.parameters[J]="double"==y.parameters[J]?THREE.DoubleSide:"back"==y.parameters[J]?THREE.BackSide:THREE.FrontSide:"blending"===J?y.parameters[J]=y.parameters[J]in THREE?THREE[y.parameters[J]]:THREE.NormalBlending:"combine"===J?y.parameters[J]=y.parameters[J]in THREE?THREE[y.parameters[J]]:
+THREE.MultiplyOperation:"vertexColors"===J?"face"==y.parameters[J]?y.parameters[J]=THREE.FaceColors:y.parameters[J]&&(y.parameters[J]=THREE.VertexColors):"wrapRGB"===J&&(I=y.parameters[J],y.parameters[J]=new THREE.Vector3(I[0],I[1],I[2]));void 0!==y.parameters.opacity&&1>y.parameters.opacity&&(y.parameters.transparent=!0);y.parameters.normalMap?(I=THREE.ShaderUtils.lib.normal,D=THREE.UniformsUtils.clone(I.uniforms),q=y.parameters.color,L=y.parameters.specular,l=y.parameters.ambient,$=y.parameters.shininess,
+D.tNormal.value=A.textures[y.parameters.normalMap],y.parameters.normalScale&&D.uNormalScale.value.set(y.parameters.normalScale[0],y.parameters.normalScale[1]),y.parameters.map&&(D.tDiffuse.value=y.parameters.map,D.enableDiffuse.value=!0),y.parameters.envMap&&(D.tCube.value=y.parameters.envMap,D.enableReflection.value=!0,D.uReflectivity.value=y.parameters.reflectivity),y.parameters.lightMap&&(D.tAO.value=y.parameters.lightMap,D.enableAO.value=!0),y.parameters.specularMap&&(D.tSpecular.value=A.textures[y.parameters.specularMap],
+D.enableSpecular.value=!0),y.parameters.displacementMap&&(D.tDisplacement.value=A.textures[y.parameters.displacementMap],D.enableDisplacement.value=!0,D.uDisplacementBias.value=y.parameters.displacementBias,D.uDisplacementScale.value=y.parameters.displacementScale),D.uDiffuseColor.value.setHex(q),D.uSpecularColor.value.setHex(L),D.uAmbientColor.value.setHex(l),D.uShininess.value=$,y.parameters.opacity&&(D.uOpacity.value=y.parameters.opacity),r=new THREE.ShaderMaterial({fragmentShader:I.fragmentShader,
+vertexShader:I.vertexShader,uniforms:D,lights:!0,fog:!0})):r=new THREE[y.type](y.parameters);A.materials[K]=r}for(K in G.materials)if(y=G.materials[K],y.parameters.materials){J=[];for(q=0;q<y.parameters.materials.length;q++)J.push(A.materials[y.parameters.materials[q]]);A.materials[K].materials=J}e();A.cameras&&G.defaults.camera&&(A.currentCamera=A.cameras[G.defaults.camera]);A.fogs&&G.defaults.fog&&(A.scene.fog=A.fogs[G.defaults.fog]);a=G.defaults.bgcolor;A.bgColor=new THREE.Color;A.bgColor.setRGB(a[0],
+a[1],a[2]);A.bgColorAlpha=G.defaults.bgalpha;n.callbackSync(A);k()};THREE.TextureLoader=function(){THREE.EventDispatcher.call(this);this.crossOrigin=null};THREE.TextureLoader.prototype={constructor:THREE.TextureLoader,load:function(a){var b=this,c=new Image;c.addEventListener("load",function(){var a=new THREE.Texture(c);a.needsUpdate=!0;b.dispatchEvent({type:"load",content:a})},!1);c.addEventListener("error",function(){b.dispatchEvent({type:"error",message:"Couldn't load URL ["+a+"]"})},!1);b.crossOrigin&&(c.crossOrigin=b.crossOrigin);c.src=a}};THREE.Material=function(){THREE.EventDispatcher.call(this);this.id=THREE.MaterialIdCount++;this.name="";this.side=THREE.FrontSide;this.opacity=1;this.transparent=!1;this.blending=THREE.NormalBlending;this.blendSrc=THREE.SrcAlphaFactor;this.blendDst=THREE.OneMinusSrcAlphaFactor;this.blendEquation=THREE.AddEquation;this.depthWrite=this.depthTest=!0;this.polygonOffset=!1;this.alphaTest=this.polygonOffsetUnits=this.polygonOffsetFactor=0;this.overdraw=!1;this.needsUpdate=this.visible=!0};
 THREE.Material.prototype.setValues=function(a){if(void 0!==a)for(var b in a){var c=a[b];if(void 0===c)console.warn("THREE.Material: '"+b+"' parameter is undefined.");else if(b in this){var d=this[b];d instanceof THREE.Color&&c instanceof THREE.Color?d.copy(c):d instanceof THREE.Color?d.set(c):d instanceof THREE.Vector3&&c instanceof THREE.Vector3?d.copy(c):this[b]=c}}};
 THREE.Material.prototype.clone=function(a){void 0===a&&(a=new THREE.Material);a.name=this.name;a.side=this.side;a.opacity=this.opacity;a.transparent=this.transparent;a.blending=this.blending;a.blendSrc=this.blendSrc;a.blendDst=this.blendDst;a.blendEquation=this.blendEquation;a.depthTest=this.depthTest;a.depthWrite=this.depthWrite;a.polygonOffset=this.polygonOffset;a.polygonOffsetFactor=this.polygonOffsetFactor;a.polygonOffsetUnits=this.polygonOffsetUnits;a.alphaTest=this.alphaTest;a.overdraw=this.overdraw;
 a.visible=this.visible;return a};THREE.Material.prototype.dispose=function(){this.dispatchEvent({type:"dispose"})};THREE.MaterialIdCount=0;THREE.LineBasicMaterial=function(a){THREE.Material.call(this);this.color=new THREE.Color(16777215);this.linewidth=1;this.linejoin=this.linecap="round";this.vertexColors=!1;this.fog=!0;this.setValues(a)};THREE.LineBasicMaterial.prototype=Object.create(THREE.Material.prototype);

+ 1 - 1
docs/manual/introduction/Creating-a-scene.html

@@ -72,7 +72,7 @@
 
 		<div>To create a cube, we need a <strong>CubeGeometry</strong>. This is an object that contains all the points (<strong>vertices</strong>) and fill (<strong>faces</strong>) of the cube. We'll explore this more in the future.</div>
 
-		<div>In addition to the geometry, we need a material to color it. Three.js comes with several materials, but we'll stick to the <strong>MeshBasicMateria</strong> for now. All materials take an object of properties which will be applied to them. To keep things very simple, we only supply a color attribute of <strong>0x00ff00</strong>, which is green. This works the same way that colors work in CSS or Photoshop (<strong>hex colors</strong>).</div>
+		<div>In addition to the geometry, we need a material to color it. Three.js comes with several materials, but we'll stick to the <strong>MeshBasicMaterial</strong> for now. All materials take an object of properties which will be applied to them. To keep things very simple, we only supply a color attribute of <strong>0x00ff00</strong>, which is green. This works the same way that colors work in CSS or Photoshop (<strong>hex colors</strong>).</div>
 
 		<div>The third thing we need is a <strong>Mesh</strong>. A mesh is an object that takes a geometry, and applies a material to it, which we then can insert to our scene, and move freely around.</div>
 

+ 1 - 1
examples/canvas_geometry_birds.html

@@ -62,7 +62,7 @@
 				this.setWorldSize = function ( width, height, depth ) {
 
 					_width = width;
-					_height = height;vector
+					_height = height;
 					_depth = depth;
 
 				}

+ 399 - 156
examples/js/ShaderDeferred.js

@@ -6,6 +6,114 @@
  */
 
 
+THREE.DeferredShaderChunk = {
+
+	// decode float to vec3
+
+	unpackFloat: [
+
+		"vec3 float_to_vec3( float data ) {",
+
+			"vec3 uncompressed;",
+			"uncompressed.x = fract( data );",
+			"float zInt = floor( data / 255.0 );",
+			"uncompressed.z = fract( zInt / 255.0 );",
+			"uncompressed.y = fract( floor( data - ( zInt * 255.0 ) ) / 255.0 );",
+			"return uncompressed;",
+
+		"}"
+
+	].join("\n"),
+
+	computeVertexPositionVS: [
+
+		"vec2 texCoord = gl_FragCoord.xy / vec2( viewWidth, viewHeight );",
+
+		"vec4 normalDepth = texture2D( samplerNormalDepth, texCoord );",
+		"float z = normalDepth.w;",
+
+		"if ( z == 0.0 ) discard;",
+
+		"vec2 xy = texCoord * 2.0 - 1.0;",
+
+		"vec4 vertexPositionProjected = vec4( xy, z, 1.0 );",
+		"vec4 vertexPositionVS = matProjInverse * vertexPositionProjected;",
+		"vertexPositionVS.xyz /= vertexPositionVS.w;",
+		"vertexPositionVS.w = 1.0;"
+
+	].join("\n"),
+
+	computeNormal: [
+
+		"vec3 normal = normalDepth.xyz * 2.0 - 1.0;"
+
+	].join("\n"),
+
+	unpackColorMap: [
+
+		"vec4 colorMap = texture2D( samplerColor, texCoord );",
+
+		"vec3 albedo = float_to_vec3( abs( colorMap.x ) );",
+		"vec3 specularColor = float_to_vec3( abs( colorMap.y ) );",
+		"float shininess = abs( colorMap.z );",
+		"float wrapAround = sign( colorMap.z );",
+		"float additiveSpecular = sign( colorMap.y );"
+
+	].join("\n"),
+
+	computeDiffuse: [
+
+		"float dotProduct = dot( normal, lightVector );",
+		"float diffuseFull = max( dotProduct, 0.0 );",
+
+		"vec3 diffuse;",
+
+		"if ( wrapAround < 0.0 ) {",
+
+			// wrap around lighting
+
+			"float diffuseHalf = max( 0.5 * dotProduct + 0.5, 0.0 );",
+
+			"const vec3 wrapRGB = vec3( 1.0, 1.0, 1.0 );",
+			"diffuse = mix( vec3( diffuseFull ), vec3( diffuseHalf ), wrapRGB );",
+
+		"} else {",
+
+			// simple lighting
+
+			"diffuse = vec3( diffuseFull );",
+
+		"}"
+
+	].join("\n"),
+
+	computeSpecular: [
+
+		"vec3 halfVector = normalize( lightVector - normalize( vertexPositionVS.xyz ) );",
+		"float dotNormalHalf = max( dot( normal, halfVector ), 0.0 );",
+
+		// simple specular
+
+		//"vec3 specular = specularColor * max( pow( dotNormalHalf, shininess ), 0.0 ) * diffuse;",
+
+		// physically based specular
+
+		"float specularNormalization = ( shininess + 2.0001 ) / 8.0;",
+
+		"vec3 schlick = specularColor + vec3( 1.0 - specularColor ) * pow( 1.0 - dot( lightVector, halfVector ), 5.0 );",
+		"vec3 specular = schlick * max( pow( dotNormalHalf, shininess ), 0.0 ) * diffuse * specularNormalization;"
+
+	].join("\n"),
+
+	combine: [
+
+		"vec3 light = lightIntensity * lightColor;",
+		"gl_FragColor = vec4( light * ( albedo * diffuse + specular ), attenuation );"
+
+	].join("\n")
+
+};
+
 THREE.ShaderDeferred = {
 
 	"color" : {
@@ -21,7 +129,11 @@ THREE.ShaderDeferred = {
 				"specular" :  { type: "c", value: new THREE.Color( 0x111111 ) },
 				"shininess":  { type: "f", value: 30 },
 				"wrapAround": 		{ type: "f", value: 1 },
-				"additiveSpecular": { type: "f", value: 1 }
+				"additiveSpecular": { type: "f", value: 1 },
+
+				"samplerNormalDepth": { type: "t", value: null },
+				"viewWidth": 		{ type: "f", value: 800 },
+				"viewHeight": 		{ type: "f", value: 600 }
 			}
 
 		] ),
@@ -38,7 +150,25 @@ THREE.ShaderDeferred = {
 			THREE.ShaderChunk[ "color_pars_fragment" ],
 			THREE.ShaderChunk[ "map_pars_fragment" ],
 			THREE.ShaderChunk[ "lightmap_pars_fragment" ],
-			THREE.ShaderChunk[ "envmap_pars_fragment" ],
+
+			"#ifdef USE_ENVMAP",
+
+				"varying vec3 vWorldPosition;",
+
+				"uniform float reflectivity;",
+				"uniform samplerCube envMap;",
+				"uniform float flipEnvMap;",
+				"uniform int combine;",
+
+				"uniform bool useRefract;",
+				"uniform float refractionRatio;",
+
+				"uniform sampler2D samplerNormalDepth;",
+				"uniform float viewHeight;",
+				"uniform float viewWidth;",
+
+			"#endif",
+
 			THREE.ShaderChunk[ "fog_pars_fragment" ],
 			THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
 			THREE.ShaderChunk[ "specularmap_pars_fragment" ],
@@ -63,11 +193,61 @@ THREE.ShaderDeferred = {
 				THREE.ShaderChunk[ "specularmap_fragment" ],
 				THREE.ShaderChunk[ "lightmap_fragment" ],
 				THREE.ShaderChunk[ "color_fragment" ],
-				THREE.ShaderChunk[ "envmap_fragment" ],
-				THREE.ShaderChunk[ "shadowmap_fragment" ],
 
-				THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
+				"#ifdef USE_ENVMAP",
+
+					"vec2 texCoord = gl_FragCoord.xy / vec2( viewWidth, viewHeight );",
+					"vec4 normalDepth = texture2D( samplerNormalDepth, texCoord );",
+					"vec3 normal = normalDepth.xyz * 2.0 - 1.0;",
+
+					"vec3 reflectVec;",
+
+					"vec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );",
+
+					"if ( useRefract ) {",
+
+						"reflectVec = refract( cameraToVertex, normal, refractionRatio );",
+
+					"} else { ",
+
+						"reflectVec = reflect( cameraToVertex, normal );",
+
+					"}",
+
+					"#ifdef DOUBLE_SIDED",
+
+						"float flipNormal = ( -1.0 + 2.0 * float( gl_FrontFacing ) );",
+						"vec4 cubeColor = textureCube( envMap, flipNormal * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );",
+
+					"#else",
+
+						"vec4 cubeColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );",
+
+					"#endif",
 
+					"#ifdef GAMMA_INPUT",
+
+						"cubeColor.xyz *= cubeColor.xyz;",
+
+					"#endif",
+
+					"if ( combine == 1 ) {",
+
+						"gl_FragColor.xyz = mix( gl_FragColor.xyz, cubeColor.xyz, specularStrength * reflectivity );",
+
+					"} else if ( combine == 2 ) {",
+
+						"gl_FragColor.xyz += cubeColor.xyz * specularStrength * reflectivity;",
+
+					"} else {",
+
+						"gl_FragColor.xyz = mix( gl_FragColor.xyz, gl_FragColor.xyz * cubeColor.xyz, specularStrength * reflectivity );",
+
+					"}",
+
+				"#endif",
+
+				THREE.ShaderChunk[ "shadowmap_fragment" ],
 				THREE.ShaderChunk[ "fog_fragment" ],
 
 				//
@@ -104,6 +284,8 @@ THREE.ShaderDeferred = {
 
 				"}",
 
+				"gl_FragColor.y *= additiveSpecular;",
+
 				// shininess
 
 				"gl_FragColor.z = wrapAround * shininess;",
@@ -120,12 +302,17 @@ THREE.ShaderDeferred = {
 
 			THREE.ShaderChunk[ "map_pars_vertex" ],
 			THREE.ShaderChunk[ "lightmap_pars_vertex" ],
-			THREE.ShaderChunk[ "envmap_pars_vertex" ],
 			THREE.ShaderChunk[ "color_pars_vertex" ],
 			THREE.ShaderChunk[ "morphtarget_pars_vertex" ],
 			THREE.ShaderChunk[ "skinning_pars_vertex" ],
 			THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
 
+			"#ifdef USE_ENVMAP",
+
+				"varying vec3 vWorldPosition;",
+
+			"#endif",
+
 			"void main() {",
 
 				THREE.ShaderChunk[ "map_vertex" ],
@@ -139,9 +326,14 @@ THREE.ShaderDeferred = {
 				THREE.ShaderChunk[ "default_vertex" ],
 
 				THREE.ShaderChunk[ "worldpos_vertex" ],
-				THREE.ShaderChunk[ "envmap_vertex" ],
 				THREE.ShaderChunk[ "shadowmap_vertex" ],
 
+				"#ifdef USE_ENVMAP",
+
+					"vWorldPosition = worldPosition.xyz;",
+
+				"#endif",
+
 			"}"
 
 		].join("\n")
@@ -282,11 +474,11 @@ THREE.ShaderDeferred = {
 
 			samplerNormalDepth: { type: "t", value: null },
 			samplerColor: 		{ type: "t", value: null },
-			matView: 		{ type: "m4", value: new THREE.Matrix4() },
 			matProjInverse: { type: "m4", value: new THREE.Matrix4() },
 			viewWidth: 		{ type: "f", value: 800 },
 			viewHeight: 	{ type: "f", value: 600 },
-			lightPos: 		{ type: "v3", value: new THREE.Vector3( 0, 0, 0 ) },
+
+			lightPositionVS:{ type: "v3", value: new THREE.Vector3( 0, 0, 0 ) },
 			lightColor: 	{ type: "c", value: new THREE.Color( 0x000000 ) },
 			lightIntensity: { type: "f", value: 1.0 },
 			lightRadius: 	{ type: "f", value: 1.0 }
@@ -295,9 +487,6 @@ THREE.ShaderDeferred = {
 
 		fragmentShader : [
 
-			"varying vec3 lightView;",
-			"varying vec4 clipPos;",
-
 			"uniform sampler2D samplerColor;",
 			"uniform sampler2D samplerNormalDepth;",
 
@@ -307,110 +496,154 @@ THREE.ShaderDeferred = {
 			"uniform float viewWidth;",
 
 			"uniform vec3 lightColor;",
+			"uniform vec3 lightPositionVS;",
 
 			"uniform mat4 matProjInverse;",
 
-			"vec3 float_to_vec3( float data ) {",
-
-				"vec3 uncompressed;",
-				"uncompressed.x = fract( data );",
-				"float zInt = floor( data / 255.0 );",
-				"uncompressed.z = fract( zInt / 255.0 );",
-				"uncompressed.y = fract( floor( data - ( zInt * 255.0 ) ) / 255.0 );",
-				"return uncompressed;",
-
-			"}",
+			THREE.DeferredShaderChunk[ "unpackFloat" ],
 
 			"void main() {",
 
-				"vec2 texCoord = gl_FragCoord.xy / vec2( viewWidth, viewHeight );",
-
-				"vec4 normalDepth = texture2D( samplerNormalDepth, texCoord );",
+				THREE.DeferredShaderChunk[ "computeVertexPositionVS" ],
 
-				"float z = normalDepth.w;",
-				"float lightZ = clipPos.z / clipPos.w;",
+				// bail out early when pixel outside of light sphere
 
-				//"if ( z == 0.0 || lightZ > z ) discard;",
+				"vec3 lightVector = lightPositionVS - vertexPositionVS.xyz;",
+				"float distance = length( lightVector );",
 
-				"float x = texCoord.x * 2.0 - 1.0;",
-				"float y = texCoord.y * 2.0 - 1.0;",
+				"if ( distance > lightRadius ) discard;",
 
-				"vec4 projectedPos = vec4( x, y, z, 1.0 );",
+				THREE.DeferredShaderChunk[ "computeNormal" ],
+				THREE.DeferredShaderChunk[ "unpackColorMap" ],
 
-				"vec4 viewPos = matProjInverse * projectedPos;",
-				"viewPos.xyz /= viewPos.w;",
-				"viewPos.w = 1.0;",
+				// compute light
 
-				"vec3 lightDir = lightView - viewPos.xyz;",
-				"float dist = length( lightDir );",
+				"lightVector = normalize( lightVector );",
 
-				"if ( dist > lightRadius ) discard;",
+				THREE.DeferredShaderChunk[ "computeDiffuse" ],
+				THREE.DeferredShaderChunk[ "computeSpecular" ],
 
-				"lightDir = normalize( lightDir );",
+				// combine
 
 				"float cutoff = 0.3;",
-				"float denom = dist/lightRadius + 1.0;",
+				"float denom = distance / lightRadius + 1.0;",
 				"float attenuation = 1.0 / ( denom * denom );",
 				"attenuation = ( attenuation - cutoff ) / ( 1.0 - cutoff );",
 				"attenuation = max( attenuation, 0.0 );",
 				"attenuation *= attenuation;",
 
-				// normal
+				THREE.DeferredShaderChunk[ "combine" ],
 
-				"vec3 normal = normalDepth.xyz * 2.0 - 1.0;",
+			"}"
 
-				// color
+		].join("\n"),
 
-				"vec4 colorMap = texture2D( samplerColor, texCoord );",
+		vertexShader : [
+
+			"void main() { ",
+
+				// sphere proxy needs real position
+
+				"vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
+				"gl_Position = projectionMatrix * mvPosition;",
 
-				"vec3 albedo = float_to_vec3( abs( colorMap.x ) );",
-				"vec3 specularColor = float_to_vec3( abs( colorMap.y ) );",
-				"float shininess = abs( colorMap.z );",
-				"float wrapAround = sign( colorMap.z );",
+			"}"
 
-				// light
+		].join("\n")
 
-				"vec3 diffuse;",
+	},
 
-				"float diffuseFull = max( dot( normal, lightDir ), 0.0 );",
+	"spotLight" : {
 
-				"if ( wrapAround < 0.0 ) {",
+		uniforms: {
 
-					// wrap around lighting
+			samplerNormalDepth: { type: "t", value: null },
+			samplerColor: 		{ type: "t", value: null },
+			matProjInverse: { type: "m4", value: new THREE.Matrix4() },
+			viewWidth: 		{ type: "f", value: 800 },
+			viewHeight: 	{ type: "f", value: 600 },
 
-					"float diffuseHalf = max( 0.5 + 0.5 * dot( normal, lightDir ), 0.0 );",
+			lightPositionVS :{ type: "v3", value: new THREE.Vector3( 0, 1, 0 ) },
+			lightDirectionVS:{ type: "v3", value: new THREE.Vector3( 0, 1, 0 ) },
+			lightColor: 	{ type: "c", value: new THREE.Color( 0x000000 ) },
+			lightIntensity: { type: "f", value: 1.0 },
+			lightDistance: 	{ type: "f", value: 1.0 },
+			lightAngle: 	{ type: "f", value: 1.0 }
 
-					"const vec3 wrapRGB = vec3( 0.6, 0.2, 0.2 );",
-					"diffuse = mix( vec3( diffuseFull ), vec3( diffuseHalf ), wrapRGB );",
+		},
 
-				"} else {",
+		fragmentShader : [
 
-					// simple lighting
+			"uniform vec3 lightPositionVS;",
+			"uniform vec3 lightDirectionVS;",
 
-					"diffuse = vec3( diffuseFull );",
+			"uniform sampler2D samplerColor;",
+			"uniform sampler2D samplerNormalDepth;",
 
-				"}",
+			"uniform float viewHeight;",
+			"uniform float viewWidth;",
+
+		    "uniform float lightAngle;"+
+			"uniform float lightIntensity;"+
+			"uniform vec3 lightColor;",
 
-				// specular
+			"uniform mat4 matProjInverse;",
 
-				"vec3 halfVector = normalize( lightDir - normalize( viewPos.xyz ) );",
-				"float dotNormalHalf = max( dot( normal, halfVector ), 0.0 );",
+			THREE.DeferredShaderChunk[ "unpackFloat" ],
 
-				// simple specular
+			"void main() {",
 
-				//"vec3 specular = specularIntensity * max( pow( dotNormalHalf, shininess ), 0.0 ) * diffuse;",
+				THREE.DeferredShaderChunk[ "computeVertexPositionVS" ],
+				THREE.DeferredShaderChunk[ "computeNormal" ],
+				THREE.DeferredShaderChunk[ "unpackColorMap" ],
 
-				// physically based specular
+				// compute light
 
-				"float specularNormalization = ( shininess + 2.0001 ) / 8.0;",
+				"vec3 lightVector = normalize( lightPositionVS.xyz - vertexPositionVS.xyz );",
 
-				"vec3 schlick = specularColor + vec3( 1.0 - specularColor ) * pow( 1.0 - dot( lightDir, halfVector ), 5.0 );",
-				"vec3 specular = schlick * max( pow( dotNormalHalf, shininess ), 0.0 ) * diffuse * specularNormalization;",
+				"float rho = dot( lightDirectionVS, lightVector );",
+				"float rhoMax = cos( lightAngle * 0.5 );",
 
-				// combine
+				"if ( rho > rhoMax ) {",
+
+					"float theta = rhoMax + 0.0001;",
+					"float phi = rhoMax + 0.05;",
+					"float falloff = 4.0;",
+
+					"float spot = 0.0;",
 
-				"vec3 light = lightIntensity * lightColor;",
-				"gl_FragColor = vec4( light * ( albedo * diffuse + specular ), attenuation );",
+					"if ( rho >= phi ) {",
+
+						"spot = 1.0;",
+
+					"} else if ( rho <= theta ) {",
+
+						"spot = 0.0;",
+
+					"} else { ",
+
+						"spot = pow( ( rho - theta ) / ( phi - theta ), falloff );",
+
+					"}",
+
+
+					THREE.DeferredShaderChunk[ "computeDiffuse" ],
+
+					"diffuse *= spot;",
+
+					THREE.DeferredShaderChunk[ "computeSpecular" ],
+
+					// combine
+
+					"const float attenuation = 1.0;",
+
+					THREE.DeferredShaderChunk[ "combine" ],
+
+					"return;",
+
+				"}",
+
+				"gl_FragColor = vec4( 0.0 );",
 
 			"}"
 
@@ -418,17 +651,11 @@ THREE.ShaderDeferred = {
 
 		vertexShader : [
 
-			"varying vec3 lightView;",
-			"varying vec4 clipPos;",
-			"uniform vec3 lightPos;",
-			"uniform mat4 matView;",
-
 			"void main() { ",
 
-				"vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
-				"gl_Position = projectionMatrix * mvPosition;",
-				"lightView = vec3( matView * vec4( lightPos, 1.0 ) );",
-				"clipPos = gl_Position;",
+				// full screen quad proxy
+
+				"gl_Position = vec4( sign( position.xy ), 0.0, 1.0 );",
 
 			"}"
 
@@ -442,11 +669,11 @@ THREE.ShaderDeferred = {
 
 			samplerNormalDepth: { type: "t", value: null },
 			samplerColor: 		{ type: "t", value: null },
-			matView: 		{ type: "m4", value: new THREE.Matrix4() },
 			matProjInverse: { type: "m4", value: new THREE.Matrix4() },
 			viewWidth: 		{ type: "f", value: 800 },
 			viewHeight: 	{ type: "f", value: 600 },
-			lightDir: 		{ type: "v3", value: new THREE.Vector3( 0, 1, 0 ) },
+
+			lightDirectionVS: { type: "v3", value: new THREE.Vector3( 0, 1, 0 ) },
 			lightColor: 	{ type: "c", value: new THREE.Color( 0x000000 ) },
 			lightIntensity: { type: "f", value: 1.0 }
 
@@ -454,9 +681,6 @@ THREE.ShaderDeferred = {
 
 		fragmentShader : [
 
-			"varying vec3 lightView;",
-			"varying vec4 clipPos;",
-
 			"uniform sampler2D samplerColor;",
 			"uniform sampler2D samplerNormalDepth;",
 
@@ -466,95 +690,128 @@ THREE.ShaderDeferred = {
 			"uniform float viewWidth;",
 
 			"uniform vec3 lightColor;",
+			"uniform vec3 lightDirectionVS;",
 
 			"uniform mat4 matProjInverse;",
 
-			"vec3 float_to_vec3( float data ) {",
+			THREE.DeferredShaderChunk[ "unpackFloat" ],
 
-				"vec3 uncompressed;",
-				"uncompressed.x = fract( data );",
-				"float zInt = floor( data / 255.0 );",
-				"uncompressed.z = fract( zInt / 255.0 );",
-				"uncompressed.y = fract( floor( data - ( zInt * 255.0 ) ) / 255.0 );",
-				"return uncompressed;",
+			"void main() {",
 
-			"}",
+				THREE.DeferredShaderChunk[ "computeVertexPositionVS" ],
+				THREE.DeferredShaderChunk[ "computeNormal" ],
+				THREE.DeferredShaderChunk[ "unpackColorMap" ],
 
-			"void main() {",
+				// compute light
 
-				"vec2 texCoord = gl_FragCoord.xy / vec2( viewWidth, viewHeight );",
+				"vec3 lightVector = lightDirectionVS;",
 
-				"vec4 normalDepth = texture2D( samplerNormalDepth, texCoord );",
-				"float z = normalDepth.w;",
+				THREE.DeferredShaderChunk[ "computeDiffuse" ],
+				THREE.DeferredShaderChunk[ "computeSpecular" ],
 
-				"if ( z == 0.0 ) discard;",
+				// combine
 
-				"float x = texCoord.x * 2.0 - 1.0;",
-				"float y = texCoord.y * 2.0 - 1.0;",
+				"const float attenuation = 1.0;",
 
-				"vec4 projectedPos = vec4( x, y, z, 1.0 );",
+				THREE.DeferredShaderChunk[ "combine" ],
 
-				"vec4 viewPos = matProjInverse * projectedPos;",
-				"viewPos.xyz /= viewPos.w;",
-				"viewPos.w = 1.0;",
+			"}"
 
-				"vec3 dirVector = normalize( lightView );",
+		].join("\n"),
 
-				// normal
+		vertexShader : [
 
-				"vec3 normal = normalDepth.xyz * 2.0 - 1.0;",
+			"void main() { ",
 
-				// color
+				// full screen quad proxy
 
-				"vec4 colorMap = texture2D( samplerColor, texCoord );",
-				"vec3 albedo = float_to_vec3( abs( colorMap.x ) );",
-				"vec3 specularColor = float_to_vec3( abs( colorMap.y ) );",
-				"float shininess = abs( colorMap.z );",
-				"float wrapAround = sign( colorMap.z );",
+				"gl_Position = vec4( sign( position.xy ), 0.0, 1.0 );",
 
-				"vec3 diffuse;",
+			"}"
 
-				"float dotProduct = dot( normal, dirVector );",
+		].join("\n")
 
-				"float diffuseFull = max( dotProduct, 0.0 );",
+	},
 
-				// wrap around lighting
+	"hemisphereLight" : {
 
-				"if ( wrapAround < 0.0 ) {",
+		uniforms: {
 
-					"float diffuseHalf = max( 0.5 + 0.5 * dotProduct, 0.0 );",
+			samplerNormalDepth: { type: "t", value: null },
+			samplerColor: 		{ type: "t", value: null },
+			matProjInverse: { type: "m4", value: new THREE.Matrix4() },
+			viewWidth: 		{ type: "f", value: 800 },
+			viewHeight: 	{ type: "f", value: 600 },
 
-					"const vec3 wrapRGB = vec3( 0.2, 0.2, 0.2 );",
-					"diffuse = mix( vec3 ( diffuseFull ), vec3( diffuseHalf ), wrapRGB );",
+			lightDirectionVS: { type: "v3", value: new THREE.Vector3( 0, 1, 0 ) },
+			lightColorSky: 	  { type: "c", value: new THREE.Color( 0x000000 ) },
+			lightColorGround: { type: "c", value: new THREE.Color( 0x000000 ) },
+			lightIntensity:   { type: "f", value: 1.0 }
 
-				// simple lighting
+		},
 
-				"} else {",
+		fragmentShader : [
 
-					"diffuse = vec3 ( diffuseFull );",
+			"uniform sampler2D samplerColor;",
+			"uniform sampler2D samplerNormalDepth;",
 
-				"}",
+			"uniform float lightRadius;",
+			"uniform float lightIntensity;",
+			"uniform float viewHeight;",
+			"uniform float viewWidth;",
+
+			"uniform vec3 lightColorSky;",
+			"uniform vec3 lightColorGround;",
+			"uniform vec3 lightDirectionVS;",
+
+			"uniform mat4 matProjInverse;",
+
+			THREE.DeferredShaderChunk[ "unpackFloat" ],
+
+			"void main() {",
+
+				THREE.DeferredShaderChunk[ "computeVertexPositionVS" ],
+				THREE.DeferredShaderChunk[ "computeNormal" ],
+				THREE.DeferredShaderChunk[ "unpackColorMap" ],
+
+				// compute light
+
+				"vec3 lightVector = lightDirectionVS;",
+
+				// diffuse
+
+				"float dotProduct = dot( normal, lightVector );",
+				"float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;",
+
+				"vec3 hemiColor = mix( lightColorGround, lightColorSky, hemiDiffuseWeight );",
+
+				"vec3 diffuse = hemiColor;",
 
-				// specular
+				// specular (sky light)
 
-				"vec3 halfVector = normalize( dirVector - normalize( viewPos.xyz ) );",
-				"float dotNormalHalf = max( dot( normal, halfVector ), 0.0 );",
+				"vec3 hemiHalfVectorSky = normalize( lightVector - vertexPositionVS.xyz );",
+				"float hemiDotNormalHalfSky = 0.5 * dot( normal, hemiHalfVectorSky ) + 0.5;",
+				"float hemiSpecularWeightSky = max( pow( hemiDotNormalHalfSky, shininess ), 0.0 );",
 
-				// simple specular
+				// specular (ground light)
 
-				//"vec3 specular = specularColor * max( pow( dotNormalHalf, shininess ), 0.0 ) * diffuse;",
+				"vec3 lVectorGround = -lightVector;",
 
-				// physically based specular
+				"vec3 hemiHalfVectorGround = normalize( lVectorGround - vertexPositionVS.xyz );",
+				"float hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;",
+				"float hemiSpecularWeightGround = max( pow( hemiDotNormalHalfGround, shininess ), 0.0 );",
+
+				"float dotProductGround = dot( normal, lVectorGround );",
 
 				"float specularNormalization = ( shininess + 2.0001 ) / 8.0;",
 
-				"vec3 schlick = specularColor + vec3( 1.0 - specularColor ) * pow( 1.0 - dot( dirVector, halfVector ), 5.0 );",
-				"vec3 specular = schlick * max( pow( dotNormalHalf, shininess ), 0.0 ) * diffuse * specularNormalization;",
+				"vec3 schlickSky = specularColor + vec3( 1.0 - specularColor ) * pow( 1.0 - dot( lightVector, hemiHalfVectorSky ), 5.0 );",
+				"vec3 schlickGround = specularColor + vec3( 1.0 - specularColor ) * pow( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 5.0 );",
+				"vec3 specular = hemiColor * specularNormalization * ( schlickSky * hemiSpecularWeightSky * max( dotProduct, 0.0 ) + schlickGround * hemiSpecularWeightGround * max( dotProductGround, 0.0 ) );",
 
 				// combine
 
-				"vec3 light = lightIntensity * lightColor;",
-				"gl_FragColor = vec4( light * ( albedo * diffuse + specular ), 1.0 );",
+				"gl_FragColor = vec4( lightIntensity * ( albedo * diffuse + specular ), 1.0 );",
 
 			"}"
 
@@ -562,17 +819,11 @@ THREE.ShaderDeferred = {
 
 		vertexShader : [
 
-			"varying vec3 lightView;",
-			"varying vec4 clipPos;",
-			"uniform vec3 lightDir;",
-			"uniform mat4 matView;",
-
 			"void main() { ",
 
-				"vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
-				"gl_Position = projectionMatrix * mvPosition;",
-				"lightView = vec3( matView * vec4( lightDir, 0.0 ) );",
-				"clipPos = gl_Position;",
+				// full screen quad proxy
+
+				"gl_Position = vec4( sign( position.xy ), 0.0, 1.0 );",
 
 			"}"
 
@@ -597,16 +848,7 @@ THREE.ShaderDeferred = {
 			"uniform float viewHeight;",
 			"uniform float viewWidth;",
 
-			"vec3 float_to_vec3( float data ) {",
-
-				"vec3 uncompressed;",
-				"uncompressed.x = fract( data );",
-				"float zInt = floor( data / 255.0 );",
-				"uncompressed.z = fract( zInt / 255.0 );",
-				"uncompressed.y = fract( floor( data - ( zInt * 255.0 ) ) / 255.0 );",
-				"return uncompressed;",
-
-			"}",
+			THREE.DeferredShaderChunk[ "unpackFloat" ],
 
 			"void main() {",
 
@@ -625,8 +867,9 @@ THREE.ShaderDeferred = {
 
 			"void main() { ",
 
-				"vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
-				"gl_Position = projectionMatrix * mvPosition;",
+				// full screen quad proxy
+
+				"gl_Position = vec4( sign( position.xy ), 0.0, 1.0 );",
 
 			"}"
 

+ 6 - 2
examples/js/loaders/OBJLoader.js

@@ -12,14 +12,18 @@ THREE.OBJLoader.prototype = {
 
 	constructor: THREE.OBJLoader,
 
-	load: function ( url ) {
+	load: function ( url, callback ) {
 
 		var scope = this;
 		var request = new XMLHttpRequest();
 
 		request.addEventListener( 'load', function ( event ) {
 
-			scope.dispatchEvent( { type: 'load', content: scope.parse( event.target.responseText ) } );
+			var hierarchy = scope.parse( event.target.responseText );
+
+			scope.dispatchEvent( { type: 'load', content: hierarchy } );
+
+			if ( callback ) callback( hierarchy );
 
 		}, false );
 

+ 6 - 14
examples/js/loaders/STLLoader.js

@@ -35,7 +35,11 @@ THREE.STLLoader.prototype = {
 
 		request.addEventListener( 'load', function ( event ) {
 
-			scope.dispatchEvent( { type: 'load', content: scope.parse( event.target.responseText ) } );
+			var geometry = scope.parse( event.target.responseText );
+
+			scope.dispatchEvent( { type: 'load', content: geometry } );
+
+			if ( callback ) callback( geometry );
 
 		}, false );
 
@@ -51,16 +55,6 @@ THREE.STLLoader.prototype = {
 
 		}, false );
 
-		if ( callback ) {
-
-			scope.addEventListener( 'load', function ( event ) {
-
-				callback( event.content );
-
-			} );
-
-		}
-
 		request.open( 'GET', url, true );
 		request.send( null );
 
@@ -91,9 +85,7 @@ THREE.STLLoader.prototype = {
 
 			while( ( result = patternVertex.exec( text ) ) != null ) {
 
-				geometry.vertices.push(
-					new THREE.Vector3( result[ 1 ], result[ 3 ], result[ 5 ] )
-				);
+				geometry.vertices.push(	new THREE.Vector3( result[ 1 ], result[ 3 ], result[ 5 ] ) );
 
 			}
 

+ 5 - 11
examples/js/loaders/VTKLoader.js

@@ -19,7 +19,11 @@ THREE.VTKLoader.prototype = {
 
 		request.addEventListener( 'load', function ( event ) {
 
-			scope.dispatchEvent( { type: 'load', content: scope.parse( event.target.responseText ) } );
+			var geometry = scope.parse( event.target.responseText );
+
+			scope.dispatchEvent( { type: 'load', content: geometry } );
+
+			if ( callback ) callback( geometry );
 
 		}, false );
 
@@ -35,16 +39,6 @@ THREE.VTKLoader.prototype = {
 
 		}, false );
 
-		if ( callback ) {
-
-			scope.addEventListener( 'load', function ( event ) {
-
-				callback( event.content );
-
-			} );
-
-		}
-
 		request.open( 'GET', url, true );
 		request.send( null );
 

+ 1 - 0
examples/js/renderers/CSS3DRenderer.js

@@ -170,6 +170,7 @@ THREE.CSS3DRenderer = function () {
 					_tmpMatrix.copy( camera.matrixWorldInverse );
 					_tmpMatrix.transpose();
 					_tmpMatrix.extractPosition( object.matrixWorld );
+					_tmpMatrix.scale( object.scale );
 
 					_tmpMatrix.elements[ 3 ] = 0;
 					_tmpMatrix.elements[ 7 ] = 0;

+ 316 - 105
examples/js/renderers/WebGLDeferredRenderer.js

@@ -7,14 +7,14 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 
 	var _this = this;
 
-	var width = parameters.width;
-	var height = parameters.height;
-	var scale = parameters.scale;
+	var fullWidth = parameters.width !== undefined ? parameters.width : 800;
+	var fullHeight = parameters.height !== undefined ? parameters.height : 600;
+	var currentScale = parameters.scale !== undefined ? parameters.scale : 1;
 
-	var scaledWidth = Math.floor( scale * width );
-	var scaledHeight = Math.floor( scale * height );
+	var scaledWidth = Math.floor( currentScale * fullWidth );
+	var scaledHeight = Math.floor( currentScale * fullHeight );
 
-	var brightness = parameters.brightness !== undefined ?  parameters.brightness : 1;
+	var brightness = parameters.brightness !== undefined ? parameters.brightness : 1;
 	var antialias = parameters.antialias !== undefined ? parameters.antialias : false;
 
 	this.renderer = parameters.renderer;
@@ -22,7 +22,7 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 	if ( this.renderer === undefined ) {
 
 		this.renderer = new THREE.WebGLRenderer( { alpha: false, antialias: false } );
-		this.renderer.setSize( width, height );
+		this.renderer.setSize( fullWidth, fullHeight );
 		this.renderer.setClearColorHex( 0x000000, 0 );
 
 		this.renderer.autoClear = false;
@@ -37,6 +37,11 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 
 	//
 
+	var positionVS = new THREE.Vector3();
+	var directionVS = new THREE.Vector3();
+
+	//
+
 	var geometryLightSphere = new THREE.SphereGeometry( 1, 16, 8 );
 	var geometryLightPlane = new THREE.PlaneGeometry( 2, 2 );
 
@@ -49,7 +54,9 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 
 	var emissiveLightShader = THREE.ShaderDeferred[ "emissiveLight" ];
 	var pointLightShader = THREE.ShaderDeferred[ "pointLight" ];
+	var spotLightShader = THREE.ShaderDeferred[ "spotLight" ];
 	var directionalLightShader = THREE.ShaderDeferred[ "directionalLight" ];
+	var hemisphereLightShader = THREE.ShaderDeferred[ "hemisphereLight" ];
 
 	var compositeShader = THREE.ShaderDeferred[ "composite" ];
 
@@ -63,7 +70,10 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 	//
 
 	var lightSceneFullscreen, lightSceneProxy;
-	var lightMaterials = [];
+
+	//
+
+	var resizableMaterials = [];
 
 	//
 
@@ -80,9 +90,6 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 
 	} );
 
-	var defaultNormalDepthMaterialWireframe = defaultNormalDepthMaterial.clone();
-	defaultNormalDepthMaterialWireframe.wireframe = true;
-
 	//
 
 	var initDeferredMaterials = function ( object ) {
@@ -127,7 +134,6 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 
 	};
 
-
 	var createDeferredMaterials = function ( originalMaterial ) {
 
 		var deferredMaterials = {};
@@ -143,7 +149,7 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 		// 	morphs
 
 		var uniforms = THREE.UniformsUtils.clone( colorShader.uniforms );
-		var defines = { "USE_MAP": !! originalMaterial.map, "GAMMA_INPUT": true };
+		var defines = { "USE_MAP": !! originalMaterial.map, "USE_ENVMAP": !! originalMaterial.envMap, "GAMMA_INPUT": true };
 
 		var material = new THREE.ShaderMaterial( {
 
@@ -181,6 +187,23 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 
 		uniforms.map.value = originalMaterial.map;
 
+		if ( originalMaterial.envMap ) {
+
+			uniforms.envMap.value = originalMaterial.envMap;
+			uniforms.useRefract.value = originalMaterial.envMap.mapping instanceof THREE.CubeRefractionMapping;
+			uniforms.refractionRatio.value = originalMaterial.refractionRatio;
+			uniforms.combine.value = originalMaterial.combine;
+			uniforms.reflectivity.value = originalMaterial.reflectivity;
+			uniforms.flipEnvMap.value = ( originalMaterial.envMap instanceof THREE.WebGLRenderTargetCube ) ? 1 : -1;
+
+			uniforms.samplerNormalDepth.value = compNormalDepth.renderTarget2;
+			uniforms.viewWidth.value = scaledWidth;
+			uniforms.viewHeight.value = scaledHeight;
+
+			resizableMaterials.push( material );
+
+		}
+
 		material.vertexColors = originalMaterial.vertexColors;
 		material.morphTargets = originalMaterial.morphTargets;
 		material.morphNormals = originalMaterial.morphNormals;
@@ -250,18 +273,9 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 
 			} );
 
-			if ( originalMaterial.morphTargets ) {
-
-				normalDepthMaterial.morphTargets = originalMaterial.morphTargets;
-				normalDepthMaterial.morphNormals = originalMaterial.morphNormals;
-
-			}
-
-			if ( originalMaterial.skinning ) {
-
-				normalDepthMaterial.skinning = originalMaterial.skinning;
-
-			}
+			normalDepthMaterial.morphTargets = originalMaterial.morphTargets;
+			normalDepthMaterial.morphNormals = originalMaterial.morphNormals;
+			normalDepthMaterial.skinning = originalMaterial.skinning;
 
 			if ( originalMaterial.bumpMap ) {
 
@@ -275,22 +289,62 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 
 			}
 
-			normalDepthMaterial.wireframe = originalMaterial.wireframe;
-
-			deferredMaterials.normalDepthMaterial = normalDepthMaterial;
-
 		} else {
 
-			deferredMaterials.normalDepthMaterial = originalMaterial.wireframe ? defaultNormalDepthMaterialWireframe : defaultNormalDepthMaterial;
+			var normalDepthMaterial = defaultNormalDepthMaterial.clone();
 
 		}
 
+		normalDepthMaterial.wireframe = originalMaterial.wireframe;
+		normalDepthMaterial.vertexColors = originalMaterial.vertexColors;
+
+		deferredMaterials.normalDepthMaterial = normalDepthMaterial;
+
+		//
+
 		deferredMaterials.transparent = originalMaterial.transparent;
 
 		return deferredMaterials;
 
 	};
 
+	var updatePointLightProxy = function ( lightProxy ) {
+
+		var light = lightProxy.properties.originalLight;
+		var uniforms = lightProxy.material.uniforms;
+
+		// skip infinite pointlights
+		// right now you can't switch between infinite and finite pointlights
+		// it's just too messy as they use different proxies
+
+		var distance = light.distance;
+
+		if ( distance > 0 ) {
+
+			lightProxy.scale.set( 1, 1, 1 ).multiplyScalar( distance );
+			uniforms[ "lightRadius" ].value = distance;
+
+			positionVS.copy( light.matrixWorld.getPosition() );
+			camera.matrixWorldInverse.multiplyVector3( positionVS );
+			uniforms[ "lightPositionVS" ].value.copy( positionVS );
+
+			lightProxy.position.copy( light.matrixWorld.getPosition() );
+
+		} else {
+
+			uniforms[ "lightRadius" ].value = Infinity;
+
+		}
+
+		// linear space colors
+
+		var intensity = light.intensity * light.intensity;
+
+		uniforms[ "lightIntensity" ].value = intensity;
+		uniforms[ "lightColor" ].value.copyGammaToLinear( light.color );
+
+	};
+
 	var createDeferredPointLight = function ( light ) {
 
 		// setup light material
@@ -312,16 +366,14 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 		// infinite pointlights use full-screen quad proxy
 		// regular pointlights use sphere proxy
 
-		var distance, geometry;
+		var  geometry;
 
 		if ( light.distance > 0 ) {
 
-			distance = light.distance;
 			geometry = geometryLightSphere;
 
 		} else {
 
-			distance = Infinity;
 			geometry = geometryLightPlane;
 
 			materialLight.depthTest = false;
@@ -329,15 +381,6 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 
 		}
 
-		// linear space
-
-		var intensity = light.intensity * light.intensity;
-
-		materialLight.uniforms[ "lightPos" ].value = light.position;
-		materialLight.uniforms[ "lightRadius" ].value = distance;
-		materialLight.uniforms[ "lightIntensity" ].value = intensity;
-		materialLight.uniforms[ "lightColor" ].value.copyGammaToLinear( light.color );
-
 		materialLight.uniforms[ "viewWidth" ].value = scaledWidth;
 		materialLight.uniforms[ "viewHeight" ].value = scaledHeight;
 
@@ -348,12 +391,78 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 
 		var meshLight = new THREE.Mesh( geometry, materialLight );
 
-		if ( light.distance > 0 ) {
+		// keep reference for color and intensity updates
 
-			meshLight.position = light.position;
-			meshLight.scale.multiplyScalar( distance );
+		meshLight.properties.originalLight = light;
 
-		}
+		// keep reference for size reset
+
+		resizableMaterials.push( materialLight );
+
+		// sync proxy uniforms to the original light
+
+		updatePointLightProxy( meshLight );
+
+		return meshLight;
+
+	};
+
+	var updateSpotLightProxy = function ( lightProxy ) {
+
+		var light = lightProxy.properties.originalLight;
+		var uniforms = lightProxy.material.uniforms;
+
+		positionVS.copy( light.matrixWorld.getPosition() );
+		camera.matrixWorldInverse.multiplyVector3( positionVS );
+
+		directionVS.copy( light.matrixWorld.getPosition() );
+		directionVS.subSelf( light.target.matrixWorld.getPosition() );
+		directionVS.normalize();
+		camera.matrixWorldInverse.rotateAxis( directionVS );
+
+		uniforms[ "lightPositionVS" ].value.copy( positionVS );
+		uniforms[ "lightDirectionVS" ].value.copy( directionVS );
+
+		uniforms[ "lightAngle" ].value = light.angle;
+		uniforms[ "lightDistance" ].value = light.distance;
+
+		// linear space colors
+
+		var intensity = light.intensity * light.intensity;
+
+		uniforms[ "lightIntensity" ].value = intensity;
+		uniforms[ "lightColor" ].value.copyGammaToLinear( light.color );
+
+	};
+
+	var createDeferredSpotLight = function ( light ) {
+
+		// setup light material
+
+		var uniforms = THREE.UniformsUtils.clone( spotLightShader.uniforms );
+
+		var materialLight = new THREE.ShaderMaterial( {
+
+			uniforms:       uniforms,
+			vertexShader:   spotLightShader.vertexShader,
+			fragmentShader: spotLightShader.fragmentShader,
+
+			blending:		THREE.AdditiveBlending,
+			depthWrite:		false,
+			depthTest:		false,
+			transparent:	true
+
+		} );
+
+		uniforms[ "viewWidth" ].value = scaledWidth;
+		uniforms[ "viewHeight" ].value = scaledHeight;
+
+		uniforms[ 'samplerColor' ].value = compColor.renderTarget2;
+		uniforms[ 'samplerNormalDepth' ].value = compNormalDepth.renderTarget2;
+
+		// create light proxy mesh
+
+		var meshLight = new THREE.Mesh( geometryLightPlane, materialLight );
 
 		// keep reference for color and intensity updates
 
@@ -361,19 +470,46 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 
 		// keep reference for size reset
 
-		lightMaterials.push( materialLight );
+		resizableMaterials.push( materialLight );
+
+		// sync proxy uniforms to the original light
+
+		updateSpotLightProxy( meshLight );
 
 		return meshLight;
 
 	};
 
+	var updateDirectionalLightProxy = function ( lightProxy ) {
+
+		var light = lightProxy.properties.originalLight;
+		var uniforms = lightProxy.material.uniforms;
+
+		directionVS.copy( light.matrixWorld.getPosition() );
+		directionVS.subSelf( light.target.matrixWorld.getPosition() );
+		directionVS.normalize();
+		camera.matrixWorldInverse.rotateAxis( directionVS );
+
+		uniforms[ "lightDirectionVS" ].value.copy( directionVS );
+
+		// linear space colors
+
+		var intensity = light.intensity * light.intensity;
+
+		uniforms[ "lightIntensity" ].value = intensity;
+		uniforms[ "lightColor" ].value.copyGammaToLinear( light.color );
+
+	};
+
 	var createDeferredDirectionalLight = function ( light ) {
 
 		// setup light material
 
+		var uniforms = THREE.UniformsUtils.clone( directionalLightShader.uniforms );
+
 		var materialLight = new THREE.ShaderMaterial( {
 
-			uniforms:       THREE.UniformsUtils.clone( directionalLightShader.uniforms ),
+			uniforms:       uniforms,
 			vertexShader:   directionalLightShader.vertexShader,
 			fragmentShader: directionalLightShader.fragmentShader,
 
@@ -384,19 +520,77 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 
 		} );
 
-		// linear space
+		uniforms[ "viewWidth" ].value = scaledWidth;
+		uniforms[ "viewHeight" ].value = scaledHeight;
+
+		uniforms[ 'samplerColor' ].value = compColor.renderTarget2;
+		uniforms[ 'samplerNormalDepth' ].value = compNormalDepth.renderTarget2;
+
+		// create light proxy mesh
+
+		var meshLight = new THREE.Mesh( geometryLightPlane, materialLight );
+
+		// keep reference for color and intensity updates
+
+		meshLight.properties.originalLight = light;
+
+		// keep reference for size reset
+
+		resizableMaterials.push( materialLight );
+
+		// sync proxy uniforms to the original light
+
+		updateDirectionalLightProxy( meshLight );
+
+		return meshLight;
+
+	};
+
+	var updateHemisphereLightProxy = function ( lightProxy ) {
+
+		var light = lightProxy.properties.originalLight;
+		var uniforms = lightProxy.material.uniforms;
+
+		directionVS.copy( light.matrixWorld.getPosition() );
+		directionVS.normalize();
+		camera.matrixWorldInverse.rotateAxis( directionVS );
+
+		uniforms[ "lightDirectionVS" ].value.copy( directionVS );
+
+		// linear space colors
 
 		var intensity = light.intensity * light.intensity;
 
-		materialLight.uniforms[ "lightDir" ].value = light.position;
-		materialLight.uniforms[ "lightIntensity" ].value = intensity;
-		materialLight.uniforms[ "lightColor" ].value.copyGammaToLinear( light.color );
+		uniforms[ "lightIntensity" ].value = intensity;
+		uniforms[ "lightColorSky" ].value.copyGammaToLinear( light.color );
+		uniforms[ "lightColorGround" ].value.copyGammaToLinear( light.groundColor );
 
-		materialLight.uniforms[ "viewWidth" ].value = scaledWidth;
-		materialLight.uniforms[ "viewHeight" ].value = scaledHeight;
+	};
 
-		materialLight.uniforms[ 'samplerColor' ].value = compColor.renderTarget2;
-		materialLight.uniforms[ 'samplerNormalDepth' ].value = compNormalDepth.renderTarget2;
+	var createDeferredHemisphereLight = function ( light ) {
+
+		// setup light material
+
+		var uniforms = THREE.UniformsUtils.clone( hemisphereLightShader.uniforms );
+
+		var materialLight = new THREE.ShaderMaterial( {
+
+			uniforms:       uniforms,
+			vertexShader:   hemisphereLightShader.vertexShader,
+			fragmentShader: hemisphereLightShader.fragmentShader,
+
+			blending:		THREE.AdditiveBlending,
+			depthWrite:		false,
+			depthTest:		false,
+			transparent:	true
+
+		} );
+
+		uniforms[ "viewWidth" ].value = scaledWidth;
+		uniforms[ "viewHeight" ].value = scaledHeight;
+
+		uniforms[ 'samplerColor' ].value = compColor.renderTarget2;
+		uniforms[ 'samplerNormalDepth' ].value = compNormalDepth.renderTarget2;
 
 		// create light proxy mesh
 
@@ -408,7 +602,11 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 
 		// keep reference for size reset
 
-		lightMaterials.push( materialLight );
+		resizableMaterials.push( materialLight );
+
+		// sync proxy uniforms to the original light
+
+		updateHemisphereLightProxy( meshLight );
 
 		return meshLight;
 
@@ -429,7 +627,6 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 
 		} );
 
-
 		materialLight.uniforms[ "viewWidth" ].value = scaledWidth;
 		materialLight.uniforms[ "viewHeight" ].value = scaledHeight;
 
@@ -441,7 +638,7 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 
 		// keep reference for size reset
 
-		lightMaterials.push( materialLight );
+		resizableMaterials.push( materialLight );
 
 		return meshLight;
 
@@ -467,11 +664,21 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 
 			}
 
+		} else if ( object instanceof THREE.SpotLight ) {
+
+			var meshLight = createDeferredSpotLight( object );
+			lightSceneFullscreen.add( meshLight );
+
 		} else if ( object instanceof THREE.DirectionalLight ) {
 
 			var meshLight = createDeferredDirectionalLight( object );
 			lightSceneFullscreen.add( meshLight );
 
+		} else if ( object instanceof THREE.HemisphereLight ) {
+
+			var meshLight = createDeferredHemisphereLight( object );
+			lightSceneFullscreen.add( meshLight );
+
 		}
 
 		object.properties.deferredInitialized = true;
@@ -541,41 +748,47 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 
 	};
 
-	this.setSize = function ( width, height ) {
+	this.setScale = function ( scale ) {
 
-		this.renderer.setSize( width, height );
+		currentScale = scale;
 
-		scaledWidth = Math.floor( scale * width );
-		scaledHeight = Math.floor( scale * height );
+		scaledWidth = Math.floor( currentScale * fullWidth );
+		scaledHeight = Math.floor( currentScale * fullHeight );
 
-		compColor.setSize( scaledWidth, scaledHeight );
 		compNormalDepth.setSize( scaledWidth, scaledHeight );
+		compColor.setSize( scaledWidth, scaledHeight );
 		compLight.setSize( scaledWidth, scaledHeight );
 		compFinal.setSize( scaledWidth, scaledHeight );
 
-		compNormalDepth.renderTarget2.shareDepthFrom = compColor.renderTarget2;
-		compLight.renderTarget2.shareDepthFrom = compColor.renderTarget2;
+		compColor.renderTarget2.shareDepthFrom = compNormalDepth.renderTarget2;
+		compLight.renderTarget2.shareDepthFrom = compNormalDepth.renderTarget2;
 
-		for ( var i = 0, il = lightMaterials.length; i < il; i ++ ) {
+		for ( var i = 0, il = resizableMaterials.length; i < il; i ++ ) {
 
-			var uniforms = lightMaterials[ i ].uniforms;
+			var uniforms = resizableMaterials[ i ].uniforms;
 
 			uniforms[ "viewWidth" ].value = scaledWidth;
 			uniforms[ "viewHeight" ].value = scaledHeight;
 
-			uniforms[ 'samplerColor' ].value = compColor.renderTarget2;
+			if ( uniforms[ 'samplerColor' ] ) uniforms[ 'samplerColor' ].value = compColor.renderTarget2;
+			if ( uniforms[ 'samplerNormalDepth' ] ) uniforms[ 'samplerNormalDepth' ].value = compNormalDepth.renderTarget2;
 
-			if ( uniforms[ 'samplerNormalDepth' ] ) {
+		}
 
-				uniforms[ 'samplerNormalDepth' ].value = compNormalDepth.renderTarget2;
+		compositePass.uniforms[ 'samplerLight' ].value = compLight.renderTarget2;
 
-			}
+		effectFXAA.uniforms[ 'resolution' ].value.set( 1 / fullWidth, 1 / fullHeight );
 
-		}
+	};
 
-		compositePass.uniforms[ 'samplerLight' ].value = compLight.renderTarget2;
+	this.setSize = function ( width, height ) {
+
+		fullWidth = width;
+		fullHeight = height;
+
+		this.renderer.setSize( fullWidth, fullHeight );
 
-		effectFXAA.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height );
+		this.setScale( currentScale );
 
 	};
 
@@ -592,25 +805,23 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 
 		if ( originalLight ) {
 
-			if ( uniforms[ "lightColor" ] ) uniforms[ "lightColor" ].value.copyGammaToLinear( originalLight.color );
-			if ( uniforms[ "lightIntensity" ] ) uniforms[ "lightIntensity" ].value = originalLight.intensity * originalLight.intensity;
-
 			lightProxy.visible = originalLight.visible;
 
 			if ( originalLight instanceof THREE.PointLight ) {
 
-				var distance = originalLight.distance;
+				updatePointLightProxy( lightProxy );
 
-				// skip infinite pointlights
-				// right now you can't switch between infinite and finite pointlights
-				// it's just too messy as they use different proxies
+			} else if ( originalLight instanceof THREE.SpotLight ) {
 
-				if ( distance > 0 ) {
+				updateSpotLightProxy( lightProxy );
 
-					lightProxy.scale.set( 1, 1, 1 ).multiplyScalar( distance );
-					if ( uniforms[ "lightRadius" ] ) uniforms[ "lightRadius" ].value = distance;
+			} else if ( originalLight instanceof THREE.DirectionalLight ) {
 
-				}
+				updateDirectionalLightProxy( lightProxy );
+
+			} else if ( originalLight instanceof THREE.HemisphereLight ) {
+
+				updateHemisphereLightProxy( lightProxy );
 
 			}
 
@@ -653,9 +864,9 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 		this.renderer.autoUpdateScene = false;
 		scene.updateMatrixWorld();
 
-		// 1) g-buffer color pass
+		// 1) g-buffer normals + depth pass
 
-		scene.traverse( setMaterialColor );
+		scene.traverse( setMaterialNormalDepth );
 
 		// clear shared depth buffer
 
@@ -665,22 +876,22 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 		// write 1 to shared stencil buffer
 		// for non-background pixels
 
-		gl.enable( gl.STENCIL_TEST );
+		//gl.enable( gl.STENCIL_TEST );
 		gl.stencilOp( gl.REPLACE, gl.REPLACE, gl.REPLACE );
 		gl.stencilFunc( gl.ALWAYS, 1, 0xffffffff );
 		gl.clearStencil( 0 );
 
-		compColor.render();
+		compNormalDepth.render();
 
 		// just touch foreground pixels (stencil == 1)
-		// both in normalDepth and light passes
+		// both in color and light passes
 
 		gl.stencilFunc( gl.EQUAL, 1, 0xffffffff );
 		gl.stencilOp( gl.KEEP, gl.KEEP, gl.KEEP );
 
-		// 2) g-buffer normals + depth pass
+		// 2) g-buffer color pass
 
-		scene.traverse( setMaterialNormalDepth );
+		scene.traverse( setMaterialColor );
 
 		// must use clean slate depth buffer
 		// otherwise there are z-fighting glitches
@@ -690,7 +901,7 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 		this.renderer.autoClearDepth = true;
 		this.renderer.autoClearStencil = false;
 
-		compNormalDepth.render();
+		compColor.render();
 
 		// 3) light pass
 
@@ -759,14 +970,6 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 		rtLight.generateMipmaps = false;
 		rtFinal.generateMipmaps = false;
 
-		// color composer
-
-		passColor = new THREE.RenderPass();
-		passColor.clear = true;
-
-		compColor = new THREE.EffectComposer( _this.renderer, rtColor );
-		compColor.addPass( passColor );
-
 		// normal + depth composer
 
 		passNormalDepth = new THREE.RenderPass();
@@ -775,7 +978,15 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 		compNormalDepth = new THREE.EffectComposer( _this.renderer, rtNormalDepth );
 		compNormalDepth.addPass( passNormalDepth );
 
-		compNormalDepth.renderTarget2.shareDepthFrom = compColor.renderTarget2;
+		// color composer
+
+		passColor = new THREE.RenderPass();
+		passColor.clear = true;
+
+		compColor = new THREE.EffectComposer( _this.renderer, rtColor );
+		compColor.addPass( passColor );
+
+		compColor.renderTarget2.shareDepthFrom = compNormalDepth.renderTarget2;
 
 		// light composer
 
@@ -789,7 +1000,7 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 		compLight.addPass( passLightFullscreen );
 		compLight.addPass( passLightProxy );
 
-		compLight.renderTarget2.shareDepthFrom = compColor.renderTarget2;
+		compLight.renderTarget2.shareDepthFrom = compNormalDepth.renderTarget2;
 
 		// final composer
 
@@ -802,7 +1013,7 @@ THREE.WebGLDeferredRenderer = function ( parameters ) {
 		// FXAA
 
 		effectFXAA = new THREE.ShaderPass( THREE.FXAAShader );
-		effectFXAA.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height );
+		effectFXAA.uniforms[ 'resolution' ].value.set( 1 / fullWidth, 1 / fullHeight );
 		effectFXAA.renderToScreen = true;
 
 		//

+ 15 - 1
src/extras/GeometryUtils.js

@@ -1031,7 +1031,21 @@ THREE.GeometryUtils = {
 		geometry.faces = faces;
 		geometry.faceVertexUvs = faceVertexUvs;
 
-	}
+	},
+	
+	setMaterialIndex: function ( geometry, index, startFace, endFace ){
+		
+		var faces = geometry.faces;
+		var start = startFace || 0;
+		var end = endFace || faces.length - 1;
+		
+		for ( var i = start; i <= end; i ++ ) {
+		
+			faces[i].materialIndex = index;
+
+		}
+		
+    }
 
 };
 

+ 8 - 18
src/loaders/SceneLoader.js

@@ -208,28 +208,18 @@ THREE.SceneLoader.prototype.parse = function ( json, callbackFinished, url ) {
 
 						var loader = scope.hierarchyHandlerMap[ objJSON.type ][ "loaderObject" ];
 
-						// OBJLoader
-
-						if ( loader.addEventListener ) {
-
-							loader.addEventListener( 'load', create_callback_hierachy( objID, parent, material, objJSON ) );
-							loader.load( get_url( objJSON.url, data.urlBaseType ) );
-
-						} else {
-
-							// ColladaLoader
+						// ColladaLoader
 
-							if ( loader.options ) {
+						if ( loader.options ) {
 
-								loader.load( get_url( objJSON.url, data.urlBaseType ), create_callback_hierachy( objID, parent, material, objJSON ) );
+							loader.load( get_url( objJSON.url, data.urlBaseType ), create_callback_hierachy( objID, parent, material, objJSON ) );
 
-							// UTF8Loader
-
-							} else {
+						// UTF8Loader
+						// OBJLoader
 
-								loader.load( get_url( objJSON.url, data.urlBaseType ), create_callback_hierachy( objID, parent, material, objJSON ), loaderParameters );
+						} else {
 
-							}
+							loader.load( get_url( objJSON.url, data.urlBaseType ), create_callback_hierachy( objID, parent, material, objJSON ), loaderParameters );
 
 						}
 
@@ -1015,7 +1005,7 @@ THREE.SceneLoader.prototype.parse = function ( json, callbackFinished, url ) {
 
 			} else if ( parID === "combine" ) {
 
-				matJSON.parameters[ parID ] = ( matJSON.parameters[ parID ] == "MixOperation" ) ? THREE.MixOperation : THREE.MultiplyOperation;
+				matJSON.parameters[ parID ] = matJSON.parameters[ parID ] in THREE ? THREE[ matJSON.parameters[ parID ] ] : THREE.MultiplyOperation;
 
 			} else if ( parID === "vertexColors" ) {
 

+ 454 - 0
utils/exporters/blender/2.65/scripts/addons/io_mesh_threejs/__init__.py

@@ -0,0 +1,454 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# ################################################################
+# Init
+# ################################################################
+
+
+bl_info = {
+    "name": "three.js format",
+    "author": "mrdoob, kikko, alteredq, remoe, pxf, n3tfr34k",
+    "version": (1, 4, 0),
+    "blender": (2, 6, 5),
+    "api": 35622,
+    "location": "File > Import-Export",
+    "description": "Import-Export three.js meshes",
+    "warning": "",
+    "wiki_url": "https://github.com/mrdoob/three.js/tree/master/utils/exporters/blender",
+    "tracker_url": "https://github.com/mrdoob/three.js/issues",
+    "category": "Import-Export"}
+
+# To support reload properly, try to access a package var,
+# if it's there, reload everything
+
+import bpy
+
+if "bpy" in locals():
+    import imp
+    if "export_threejs" in locals():
+        imp.reload(export_threejs)
+    if "import_threejs" in locals():
+        imp.reload(import_threejs)
+
+from bpy.props import *
+from bpy_extras.io_utils import ExportHelper, ImportHelper
+
+# ################################################################
+# Custom properties
+# ################################################################
+
+bpy.types.Object.THREE_castShadow = bpy.props.BoolProperty()
+bpy.types.Object.THREE_receiveShadow = bpy.props.BoolProperty()
+bpy.types.Object.THREE_doubleSided = bpy.props.BoolProperty()
+bpy.types.Object.THREE_exportGeometry = bpy.props.BoolProperty(default = True)
+
+bpy.types.Material.THREE_useVertexColors = bpy.props.BoolProperty()
+bpy.types.Material.THREE_depthWrite = bpy.props.BoolProperty(default = True)
+bpy.types.Material.THREE_depthTest = bpy.props.BoolProperty(default = True)
+
+THREE_material_types = [("Basic", "Basic", "Basic"), ("Phong", "Phong", "Phong"), ("Lambert", "Lambert", "Lambert")]
+bpy.types.Material.THREE_materialType = EnumProperty(name = "Material type", description = "Material type", items = THREE_material_types, default = "Lambert")
+
+THREE_blending_types = [("NoBlending", "NoBlending", "NoBlending"), ("NormalBlending", "NormalBlending", "NormalBlending"),
+                        ("AdditiveBlending", "AdditiveBlending", "AdditiveBlending"), ("SubtractiveBlending", "SubtractiveBlending", "SubtractiveBlending"),
+                        ("MultiplyBlending", "MultiplyBlending", "MultiplyBlending"), ("AdditiveAlphaBlending", "AdditiveAlphaBlending", "AdditiveAlphaBlending")]
+bpy.types.Material.THREE_blendingType = EnumProperty(name = "Blending type", description = "Blending type", items = THREE_blending_types, default = "NormalBlending")
+
+class OBJECT_PT_hello( bpy.types.Panel ):
+
+    bl_label = "THREE"
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
+    bl_context = "object"
+
+    def draw(self, context):
+        layout = self.layout
+        obj = context.object
+
+        row = layout.row()
+        row.label(text="Selected object: " + obj.name )
+
+        row = layout.row()
+        row.prop( obj, "THREE_exportGeometry", text="Export geometry" )
+
+        row = layout.row()
+        row.prop( obj, "THREE_castShadow", text="Casts shadow" )
+
+        row = layout.row()
+        row.prop( obj, "THREE_receiveShadow", text="Receives shadow" )
+
+        row = layout.row()
+        row.prop( obj, "THREE_doubleSided", text="Double sided" )
+
+class MATERIAL_PT_hello( bpy.types.Panel ):
+
+    bl_label = "THREE"
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
+    bl_context = "material"
+
+    def draw(self, context):
+        layout = self.layout
+        mat = context.material
+
+        row = layout.row()
+        row.label(text="Selected material: " + mat.name )
+
+        row = layout.row()
+        row.prop( mat, "THREE_materialType", text="Material type" )
+
+        row = layout.row()
+        row.prop( mat, "THREE_blendingType", text="Blending type" )
+
+        row = layout.row()
+        row.prop( mat, "THREE_useVertexColors", text="Use vertex colors" )
+
+        row = layout.row()
+        row.prop( mat, "THREE_depthWrite", text="Enable depth writing" )
+
+        row = layout.row()
+        row.prop( mat, "THREE_depthTest", text="Enable depth testing" )
+
+
+# ################################################################
+# Importer
+# ################################################################
+
+class ImportTHREEJS(bpy.types.Operator, ImportHelper):
+    '''Load a Three.js ASCII JSON model'''
+
+    bl_idname = "import.threejs"
+    bl_label = "Import Three.js"
+
+    filename_ext = ".js"
+    filter_glob = StringProperty(default="*.js", options={'HIDDEN'})
+
+    option_flip_yz = BoolProperty(name="Flip YZ", description="Flip YZ", default=True)
+    recalculate_normals = BoolProperty(name="Recalculate normals", description="Recalculate vertex normals", default=True)
+    option_worker = BoolProperty(name="Worker", description="Old format using workers", default=False)
+
+    def execute(self, context):
+        import io_mesh_threejs.import_threejs
+        return io_mesh_threejs.import_threejs.load(self, context, **self.properties)
+
+
+    def draw(self, context):
+        layout = self.layout
+
+        row = layout.row()
+        row.prop(self.properties, "option_flip_yz")
+
+        row = layout.row()
+        row.prop(self.properties, "recalculate_normals")
+
+        row = layout.row()
+        row.prop(self.properties, "option_worker")
+
+
+# ################################################################
+# Exporter - settings
+# ################################################################
+
+SETTINGS_FILE_EXPORT = "threejs_settings_export.js"
+
+import os
+import json
+
+def file_exists(filename):
+    """Return true if file exists and 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_settings_fullpath():
+    return os.path.join(bpy.app.tempdir, SETTINGS_FILE_EXPORT)
+
+def save_settings_export(properties):
+
+    settings = {
+    "option_export_scene" : properties.option_export_scene,
+    "option_embed_meshes" : properties.option_embed_meshes,
+    "option_url_base_html" : properties.option_url_base_html,
+    "option_copy_textures" : properties.option_copy_textures,
+
+    "option_lights" : properties.option_lights,
+    "option_cameras" : properties.option_cameras,
+
+    "option_animation_morph" : properties.option_animation_morph,
+    "option_animation_skeletal" : properties.option_animation_skeletal,
+
+    "option_frame_step" : properties.option_frame_step,
+    "option_all_meshes" : properties.option_all_meshes,
+
+    "option_flip_yz"      : properties.option_flip_yz,
+
+    "option_materials"       : properties.option_materials,
+    "option_normals"         : properties.option_normals,
+    "option_colors"          : properties.option_colors,
+    "option_uv_coords"       : properties.option_uv_coords,
+    "option_faces"           : properties.option_faces,
+    "option_vertices"        : properties.option_vertices,
+
+    "option_skinning"        : properties.option_skinning,
+    "option_bones"           : properties.option_bones,
+
+    "option_vertices_truncate" : properties.option_vertices_truncate,
+    "option_scale"        : properties.option_scale,
+
+    "align_model"         : properties.align_model
+    }
+
+    fname = get_settings_fullpath()
+    f = open(fname, "w")
+    json.dump(settings, f)
+
+def restore_settings_export(properties):
+
+    settings = {}
+
+    fname = get_settings_fullpath()
+    if file_exists(fname):
+        f = open(fname, "r")
+        settings = json.load(f)
+
+    properties.option_vertices = settings.get("option_vertices", True)
+    properties.option_vertices_truncate = settings.get("option_vertices_truncate", False)
+    properties.option_faces = settings.get("option_faces", True)
+    properties.option_normals = settings.get("option_normals", True)
+
+    properties.option_colors = settings.get("option_colors", True)
+    properties.option_uv_coords = settings.get("option_uv_coords", True)
+    properties.option_materials = settings.get("option_materials", True)
+
+    properties.option_skinning = settings.get("option_skinning", True)
+    properties.option_bones = settings.get("option_bones", True)
+
+    properties.align_model = settings.get("align_model", "None")
+
+    properties.option_scale = settings.get("option_scale", 1.0)
+    properties.option_flip_yz = settings.get("option_flip_yz", True)
+
+    properties.option_export_scene = settings.get("option_export_scene", False)
+    properties.option_embed_meshes = settings.get("option_embed_meshes", True)
+    properties.option_url_base_html = settings.get("option_url_base_html", False)
+    properties.option_copy_textures = settings.get("option_copy_textures", False)
+
+    properties.option_lights = settings.get("option_lights", False)
+    properties.option_cameras = settings.get("option_cameras", False)
+
+    properties.option_animation_morph = settings.get("option_animation_morph", False)
+    properties.option_animation_skeletal = settings.get("option_animation_skeletal", False)
+
+    properties.option_frame_step = settings.get("option_frame_step", 1)
+    properties.option_all_meshes = settings.get("option_all_meshes", True)
+
+# ################################################################
+# Exporter
+# ################################################################
+
+class ExportTHREEJS(bpy.types.Operator, ExportHelper):
+    '''Export selected object / scene for Three.js (ASCII JSON format).'''
+
+    bl_idname = "export.threejs"
+    bl_label = "Export Three.js"
+
+    filename_ext = ".js"
+
+    option_vertices = BoolProperty(name = "Vertices", description = "Export vertices", default = True)
+    option_vertices_deltas = BoolProperty(name = "Deltas", description = "Delta vertices", default = False)
+    option_vertices_truncate = BoolProperty(name = "Truncate", description = "Truncate vertices", default = False)
+
+    option_faces = BoolProperty(name = "Faces", description = "Export faces", default = True)
+    option_faces_deltas = BoolProperty(name = "Deltas", description = "Delta faces", default = False)
+
+    option_normals = BoolProperty(name = "Normals", description = "Export normals", default = True)
+
+    option_colors = BoolProperty(name = "Colors", description = "Export vertex colors", default = True)
+    option_uv_coords = BoolProperty(name = "UVs", description = "Export texture coordinates", default = True)
+    option_materials = BoolProperty(name = "Materials", description = "Export materials", default = True)
+
+    option_skinning = BoolProperty(name = "Skinning", description = "Export skin data", default = True)
+    option_bones = BoolProperty(name = "Bones", description = "Export bones", default = True)
+
+    align_types = [("None","None","None"), ("Center","Center","Center"), ("Bottom","Bottom","Bottom"), ("Top","Top","Top")]
+    align_model = EnumProperty(name = "Align model", description = "Align model", items = align_types, default = "None")
+
+    option_scale = FloatProperty(name = "Scale", description = "Scale vertices", min = 0.01, max = 1000.0, soft_min = 0.01, soft_max = 1000.0, default = 1.0)
+    option_flip_yz = BoolProperty(name = "Flip YZ", description = "Flip YZ", default = True)
+
+    option_export_scene = BoolProperty(name = "Scene", description = "Export scene", default = False)
+    option_embed_meshes = BoolProperty(name = "Embed meshes", description = "Embed meshes", default = True)
+    option_copy_textures = BoolProperty(name = "Copy textures", description = "Copy textures", default = False)
+    option_url_base_html = BoolProperty(name = "HTML as url base", description = "Use HTML as url base ", default = False)
+
+    option_lights = BoolProperty(name = "Lights", description = "Export default scene lights", default = False)
+    option_cameras = BoolProperty(name = "Cameras", description = "Export default scene cameras", default = False)
+
+    option_animation_morph = BoolProperty(name = "Morph animation", description = "Export animation (morphs)", default = False)
+    option_animation_skeletal = BoolProperty(name = "Skeletal animation", description = "Export animation (skeletal)", default = False)
+
+    option_frame_step = IntProperty(name = "Frame step", description = "Animation frame step", min = 1, max = 1000, soft_min = 1, soft_max = 1000, default = 1)
+    option_all_meshes = BoolProperty(name = "All meshes", description = "All meshes (merged)", default = True)
+
+    def invoke(self, context, event):
+        restore_settings_export(self.properties)
+        return ExportHelper.invoke(self, context, event)
+
+    @classmethod
+    def poll(cls, context):
+        return context.active_object != None
+
+    def execute(self, context):
+        print("Selected: " + context.active_object.name)
+
+        if not self.properties.filepath:
+            raise Exception("filename not set")
+
+        save_settings_export(self.properties)
+
+        filepath = self.filepath
+
+        import io_mesh_threejs.export_threejs
+        return io_mesh_threejs.export_threejs.save(self, context, **self.properties)
+
+    def draw(self, context):
+        layout = self.layout
+
+        row = layout.row()
+        row.label(text="Geometry:")
+
+        row = layout.row()
+        row.prop(self.properties, "option_vertices")
+        # row = layout.row()
+        # row.enabled = self.properties.option_vertices
+        # row.prop(self.properties, "option_vertices_deltas")
+        row.prop(self.properties, "option_vertices_truncate")
+        layout.separator()
+
+        row = layout.row()
+        row.prop(self.properties, "option_faces")
+        row = layout.row()
+        row.enabled = self.properties.option_faces
+        # row.prop(self.properties, "option_faces_deltas")
+        layout.separator()
+
+        row = layout.row()
+        row.prop(self.properties, "option_normals")
+        layout.separator()
+
+        row = layout.row()
+        row.prop(self.properties, "option_bones")
+        row.prop(self.properties, "option_skinning")
+        layout.separator()
+
+        row = layout.row()
+        row.label(text="Materials:")
+
+        row = layout.row()
+        row.prop(self.properties, "option_uv_coords")
+        row.prop(self.properties, "option_colors")
+        row = layout.row()
+        row.prop(self.properties, "option_materials")
+        layout.separator()
+
+        row = layout.row()
+        row.label(text="Settings:")
+
+        row = layout.row()
+        row.prop(self.properties, "align_model")
+        row = layout.row()
+        row.prop(self.properties, "option_flip_yz")
+        row.prop(self.properties, "option_scale")
+        layout.separator()
+
+        row = layout.row()
+        row.label(text="--------- Experimental ---------")
+        layout.separator()
+
+        row = layout.row()
+        row.label(text="Scene:")
+
+        row = layout.row()
+        row.prop(self.properties, "option_export_scene")
+        row.prop(self.properties, "option_embed_meshes")
+
+        row = layout.row()
+        row.prop(self.properties, "option_lights")
+        row.prop(self.properties, "option_cameras")
+        layout.separator()
+
+        row = layout.row()
+        row.label(text="Animation:")
+
+        row = layout.row()
+        row.prop(self.properties, "option_animation_morph")
+        row = layout.row()
+        row.prop(self.properties, "option_animation_skeletal")
+        row = layout.row()
+        row.prop(self.properties, "option_frame_step")
+        layout.separator()
+
+        row = layout.row()
+        row.label(text="Settings:")
+
+        row = layout.row()
+        row.prop(self.properties, "option_all_meshes")
+
+        row = layout.row()
+        row.prop(self.properties, "option_copy_textures")
+
+        row = layout.row()
+        row.prop(self.properties, "option_url_base_html")
+
+        layout.separator()
+
+
+# ################################################################
+# Common
+# ################################################################
+
+def menu_func_export(self, context):
+    default_path = bpy.data.filepath.replace(".blend", ".js")
+    self.layout.operator(ExportTHREEJS.bl_idname, text="Three.js (.js)").filepath = default_path
+
+def menu_func_import(self, context):
+    self.layout.operator(ImportTHREEJS.bl_idname, text="Three.js (.js)")
+
+def register():
+    bpy.utils.register_module(__name__)
+    bpy.types.INFO_MT_file_export.append(menu_func_export)
+    bpy.types.INFO_MT_file_import.append(menu_func_import)
+
+def unregister():
+    bpy.utils.unregister_module(__name__)
+    bpy.types.INFO_MT_file_export.remove(menu_func_export)
+    bpy.types.INFO_MT_file_import.remove(menu_func_import)
+
+if __name__ == "__main__":
+    register()

+ 2409 - 0
utils/exporters/blender/2.65/scripts/addons/io_mesh_threejs/export_threejs.py

@@ -0,0 +1,2409 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+"""
+Blender exporter for Three.js (ASCII JSON format).
+
+TODO
+    - binary format
+"""
+
+import bpy
+import mathutils
+
+import shutil
+import os
+import os.path
+import math
+import operator
+import random
+
+# #####################################################
+# Configuration
+# #####################################################
+
+DEFAULTS = {
+"bgcolor" : [0, 0, 0],
+"bgalpha" : 1.0,
+
+"position" : [0, 0, 0],
+"rotation" : [-math.pi/2, 0, 0],
+"scale"    : [1, 1, 1],
+
+"camera"  :
+    {
+        "name" : "default_camera",
+        "type" : "PerspectiveCamera",
+        "near" : 1,
+        "far"  : 10000,
+        "fov"  : 60,
+        "aspect": 1.333,
+        "position" : [0, 0, 10],
+        "target"   : [0, 0, 0]
+    },
+
+"light" :
+ {
+    "name"       : "default_light",
+    "type"       : "DirectionalLight",
+    "direction"  : [0, 1, 1],
+    "color"      : [1, 1, 1],
+    "intensity"  : 0.8
+ }
+}
+
+# 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]
+
+
+# skinning
+MAX_INFLUENCES = 2
+
+
+# #####################################################
+# Templates - scene
+# #####################################################
+
+TEMPLATE_SCENE_ASCII = """\
+{
+
+"metadata" :
+{
+	"formatVersion" : 3.2,
+	"type" 			: "scene",
+	"sourceFile"    : "%(fname)s",
+	"generatedBy"   : "Blender 2.65 Exporter",
+	"objects"       : %(nobjects)s,
+	"geometries"    : %(ngeometries)s,
+	"materials"     : %(nmaterials)s,
+	"textures"      : %(ntextures)s
+},
+
+"urlBaseType" : %(basetype)s,
+
+%(sections)s
+
+"transform" :
+{
+	"position"  : %(position)s,
+	"rotation"  : %(rotation)s,
+	"scale"     : %(scale)s
+},
+
+"defaults" :
+{
+	"bgcolor" : %(bgcolor)s,
+	"bgalpha" : %(bgalpha)f,
+	"camera"  : %(defcamera)s
+}
+
+}
+"""
+
+TEMPLATE_SECTION = """
+"%s" :
+{
+%s
+},
+"""
+
+TEMPLATE_OBJECT = """\
+	%(object_id)s : {
+		"geometry"  : %(geometry_id)s,
+		"groups"    : [ %(group_id)s ],
+		"material"  : %(material_id)s,
+		"position"  : %(position)s,
+		"rotation"  : %(rotation)s,
+		"quaternion": %(quaternion)s,
+		"scale"     : %(scale)s,
+		"visible"       : %(visible)s,
+		"castShadow"    : %(castShadow)s,
+		"receiveShadow" : %(receiveShadow)s,
+		"doubleSided"   : %(doubleSided)s
+	}"""
+
+TEMPLATE_EMPTY = """\
+	%(object_id)s : {
+		"groups"    : [ %(group_id)s ],
+		"position"  : %(position)s,
+		"rotation"  : %(rotation)s,
+		"quaternion": %(quaternion)s,
+		"scale"     : %(scale)s
+	}"""
+
+TEMPLATE_GEOMETRY_LINK = """\
+	%(geometry_id)s : {
+		"type" : "ascii",
+		"url"  : %(model_file)s
+	}"""
+
+TEMPLATE_GEOMETRY_EMBED = """\
+	%(geometry_id)s : {
+		"type" : "embedded",
+		"id"  : %(embed_id)s
+	}"""
+
+TEMPLATE_TEXTURE = """\
+	%(texture_id)s : {
+		"url": %(texture_file)s%(extras)s
+	}"""
+
+TEMPLATE_MATERIAL_SCENE = """\
+	%(material_id)s : {
+		"type": %(type)s,
+		"parameters": { %(parameters)s }
+	}"""
+
+TEMPLATE_CAMERA_PERSPECTIVE = """\
+	%(camera_id)s : {
+		"type"  : "PerspectiveCamera",
+		"fov"   : %(fov)f,
+		"aspect": %(aspect)f,
+		"near"  : %(near)f,
+		"far"   : %(far)f,
+		"position": %(position)s,
+		"target"  : %(target)s
+	}"""
+
+TEMPLATE_CAMERA_ORTHO = """\
+	%(camera_id)s : {
+		"type"  : "OrthographicCamera",
+		"left"  : %(left)f,
+		"right" : %(right)f,
+		"top"   : %(top)f,
+		"bottom": %(bottom)f,
+		"near"  : %(near)f,
+		"far"   : %(far)f,
+		"position": %(position)s,
+		"target"  : %(target)s
+	}"""
+
+TEMPLATE_LIGHT_DIRECTIONAL = """\
+	%(light_id)s : {
+		"type"       : "DirectionalLight",
+		"direction"  : %(direction)s,
+		"color"      : %(color)d,
+		"intensity"  : %(intensity).2f
+	}"""
+
+TEMPLATE_LIGHT_POINT = """\
+	%(light_id)s : {
+		"type"       : "PointLight",
+		"position"   : %(position)s,
+		"color"      : %(color)d,
+		"intensity"  : %(intensity).3f
+	}"""
+
+TEMPLATE_VEC4 = '[ %g, %g, %g, %g ]'
+TEMPLATE_VEC3 = '[ %g, %g, %g ]'
+TEMPLATE_VEC2 = '[ %g, %g ]'
+TEMPLATE_STRING = '"%s"'
+TEMPLATE_HEX = "0x%06x"
+
+# #####################################################
+# Templates - model
+# #####################################################
+
+TEMPLATE_FILE_ASCII = """\
+{
+
+	"metadata" :
+	{
+		"formatVersion" : 3.1,
+		"generatedBy"   : "Blender 2.65 Exporter",
+		"vertices"      : %(nvertex)d,
+		"faces"         : %(nface)d,
+		"normals"       : %(nnormal)d,
+		"colors"        : %(ncolor)d,
+		"uvs"           : [%(nuvs)s],
+		"materials"     : %(nmaterial)d,
+		"morphTargets"  : %(nmorphTarget)d,
+		"bones"         : %(nbone)d
+	},
+
+%(model)s
+
+}
+"""
+
+TEMPLATE_MODEL_ASCII = """\
+	"scale" : %(scale)f,
+
+	"materials" : [%(materials)s],
+
+	"vertices" : [%(vertices)s],
+
+	"morphTargets" : [%(morphTargets)s],
+
+	"normals" : [%(normals)s],
+
+	"colors" : [%(colors)s],
+
+	"uvs" : [%(uvs)s],
+
+	"faces" : [%(faces)s],
+
+	"bones" : [%(bones)s],
+
+	"skinIndices" : [%(indices)s],
+
+	"skinWeights" : [%(weights)s],
+
+	"animation" : {%(animation)s}
+"""
+
+TEMPLATE_VERTEX = "%g,%g,%g"
+TEMPLATE_VERTEX_TRUNCATE = "%d,%d,%d"
+
+TEMPLATE_N = "%g,%g,%g"
+TEMPLATE_UV = "%g,%g"
+TEMPLATE_C = "%d"
+
+# #####################################################
+# Utils
+# #####################################################
+
+def veckey3(x,y,z):
+    return round(x, 6), round(y, 6), round(z, 6)
+
+def veckey3d(v):
+    return veckey3(v.x, v.y, v.z)
+
+def veckey2d(v):
+    return round(v[0], 6), round(v[1], 6)
+
+def get_faces(obj):
+    if hasattr(obj, "tessfaces"):
+        return obj.tessfaces
+    else:
+        return obj.faces
+
+def get_normal_indices(v, normals, mesh):
+    n = []
+    mv = mesh.vertices
+
+    for i in v:
+        normal = mv[i].normal
+        key = veckey3d(normal)
+
+        n.append( normals[key] )
+
+    return n
+
+def get_uv_indices(face_index, uvs, mesh, layer_index):
+    uv = []
+    uv_layer = mesh.tessface_uv_textures[layer_index].data
+    for i in uv_layer[face_index].uv:
+        uv.append( uvs[veckey2d(i)] )
+    return uv
+
+def get_color_indices(face_index, colors, mesh):
+    c = []
+    color_layer = mesh.tessface_vertex_colors.active.data
+    face_colors = color_layer[face_index]
+    face_colors = face_colors.color1, face_colors.color2, face_colors.color3, face_colors.color4
+    for i in face_colors:
+        c.append( colors[hexcolor(i)] )
+    return c
+
+def rgb2int(rgb):
+    color = (int(rgb[0]*255) << 16) + (int(rgb[1]*255) << 8) + int(rgb[2]*255);
+    return color
+
+# #####################################################
+# Utils - files
+# #####################################################
+
+def write_file(fname, content):
+    out = open(fname, "w")
+    out.write(content)
+    out.close()
+
+def ensure_folder_exist(foldername):
+    """Create folder (with whole path) if it doesn't exist yet."""
+
+    if not os.access(foldername, os.R_OK|os.W_OK|os.X_OK):
+        os.makedirs(foldername)
+
+def ensure_extension(filepath, extension):
+    if not filepath.lower().endswith(extension):
+        filepath += extension
+    return filepath
+
+def generate_mesh_filename(meshname, filepath):
+    normpath = os.path.normpath(filepath)
+    path, ext = os.path.splitext(normpath)
+    return "%s.%s%s" % (path, meshname, ext)
+
+
+# #####################################################
+# Utils - alignment
+# #####################################################
+
+def bbox(vertices):
+    """Compute bounding box of vertex array.
+    """
+
+    if len(vertices)>0:
+        minx = maxx = vertices[0].co.x
+        miny = maxy = vertices[0].co.y
+        minz = maxz = vertices[0].co.z
+
+        for v in vertices[1:]:
+            if v.co.x < minx:
+                minx = v.co.x
+            elif v.co.x > maxx:
+                maxx = v.co.x
+
+            if v.co.y < miny:
+                miny = v.co.y
+            elif v.co.y > maxy:
+                maxy = v.co.y
+
+            if v.co.z < minz:
+                minz = v.co.z
+            elif v.co.z > maxz:
+                maxz = v.co.z
+
+        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 range(len(vertices)):
+        vertices[i].co.x += t[0]
+        vertices[i].co.y += t[1]
+        vertices[i].co.z += 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])
+
+    return [-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])
+
+    return [-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])
+
+    return [-cx,-cy,-cz]
+
+# #####################################################
+# Elements rendering
+# #####################################################
+
+def hexcolor(c):
+    return ( int(c[0] * 255) << 16  ) + ( int(c[1] * 255) << 8 ) + int(c[2] * 255)
+
+def generate_vertices(vertices, option_vertices_truncate, option_vertices):
+    if not option_vertices:
+        return ""
+
+    return ",".join(generate_vertex(v, option_vertices_truncate) for v in vertices)
+
+def generate_vertex(v, option_vertices_truncate):
+    if not option_vertices_truncate:
+        return TEMPLATE_VERTEX % (v.co.x, v.co.y, v.co.z)
+    else:
+        return TEMPLATE_VERTEX_TRUNCATE % (v.co.x, v.co.y, v.co.z)
+
+def generate_normal(n):
+    return TEMPLATE_N % (n[0], n[1], n[2])
+
+def generate_vertex_color(c):
+    return TEMPLATE_C % c
+
+def generate_uv(uv):
+    return TEMPLATE_UV % (uv[0], uv[1])
+
+# #####################################################
+# Model exporter - faces
+# #####################################################
+
+def setBit(value, position, on):
+    if on:
+        mask = 1 << position
+        return (value | mask)
+    else:
+        mask = ~(1 << position)
+        return (value & mask)
+
+def generate_faces(normals, uv_layers, colors, meshes, option_normals, option_colors, option_uv_coords, option_materials, option_faces):
+
+    if not option_faces:
+        return "", 0
+
+    vertex_offset = 0
+    material_offset = 0
+
+    chunks = []
+    for mesh, object in meshes:
+
+        vertexUV = len(mesh.uv_textures) > 0
+        vertexColors = len(mesh.vertex_colors) > 0
+
+        mesh_colors = option_colors and vertexColors
+        mesh_uvs = option_uv_coords and vertexUV
+
+        if vertexUV:
+            active_uv_layer = mesh.uv_textures.active
+            if not active_uv_layer:
+                mesh_extract_uvs = False
+
+        if vertexColors:
+            active_col_layer = mesh.vertex_colors.active
+            if not active_col_layer:
+                mesh_extract_colors = False
+
+        for i, f in enumerate(get_faces(mesh)):
+            face = generate_face(f, i, normals, uv_layers, colors, mesh, option_normals, mesh_colors, mesh_uvs, option_materials, vertex_offset, material_offset)
+            chunks.append(face)
+
+        vertex_offset += len(mesh.vertices)
+
+        material_count = len(mesh.materials)
+        if material_count == 0:
+            material_count = 1
+
+        material_offset += material_count
+
+    return ",".join(chunks), len(chunks)
+
+def generate_face(f, faceIndex, normals, uv_layers, colors, mesh, option_normals, option_colors, option_uv_coords, option_materials, vertex_offset, material_offset):
+    isTriangle = ( len(f.vertices) == 3 )
+
+    if isTriangle:
+        nVertices = 3
+    else:
+        nVertices = 4
+
+    hasMaterial = option_materials
+
+    hasFaceUvs = False # not supported in Blender
+    hasFaceVertexUvs = option_uv_coords
+
+    hasFaceNormals = False # don't export any face normals (as they are computed in engine)
+    hasFaceVertexNormals = option_normals
+
+    hasFaceColors = False       # not supported in Blender
+    hasFaceVertexColors = option_colors
+
+    faceType = 0
+    faceType = setBit(faceType, 0, not isTriangle)
+    faceType = setBit(faceType, 1, hasMaterial)
+    faceType = setBit(faceType, 2, hasFaceUvs)
+    faceType = setBit(faceType, 3, hasFaceVertexUvs)
+    faceType = setBit(faceType, 4, hasFaceNormals)
+    faceType = setBit(faceType, 5, hasFaceVertexNormals)
+    faceType = setBit(faceType, 6, hasFaceColors)
+    faceType = setBit(faceType, 7, hasFaceVertexColors)
+
+    faceData = []
+
+    # order is important, must match order in JSONLoader
+
+    # face type
+    # vertex indices
+    # material index
+    # face uvs index
+    # face vertex uvs indices
+    # face color index
+    # face vertex colors indices
+
+    faceData.append(faceType)
+
+    # must clamp in case on polygons bigger than quads
+
+    for i in range(nVertices):
+        index = f.vertices[i] + vertex_offset
+        faceData.append(index)
+
+    if hasMaterial:
+        index = f.material_index + material_offset
+        faceData.append( index )
+
+    if hasFaceVertexUvs:
+        for layer_index, uvs in enumerate(uv_layers):
+            uv = get_uv_indices(faceIndex, uvs, mesh, layer_index)
+            for i in range(nVertices):
+                index = uv[i]
+                faceData.append(index)
+
+    if hasFaceVertexNormals:
+        n = get_normal_indices(f.vertices, normals, mesh)
+        for i in range(nVertices):
+            index = n[i]
+            faceData.append(index)
+
+    if hasFaceVertexColors:
+        c = get_color_indices(faceIndex, colors, mesh)
+        for i in range(nVertices):
+            index = c[i]
+            faceData.append(index)
+
+    return ",".join( map(str, faceData) )
+
+
+# #####################################################
+# Model exporter - normals
+# #####################################################
+
+def extract_vertex_normals(mesh, normals, count):
+    for f in get_faces(mesh):
+        for v in f.vertices:
+
+            normal = mesh.vertices[v].normal
+            key = veckey3d(normal)
+
+            if key not in normals:
+                normals[key] = count
+                count += 1
+
+    return count
+
+def generate_normals(normals, option_normals):
+    if not option_normals:
+        return ""
+
+    chunks = []
+    for key, index in sorted(normals.items(), key = operator.itemgetter(1)):
+        chunks.append(key)
+
+    return ",".join(generate_normal(n) for n in chunks)
+
+# #####################################################
+# Model exporter - vertex colors
+# #####################################################
+
+def extract_vertex_colors(mesh, colors, count):
+    color_layer = mesh.tessface_vertex_colors.active.data
+
+    for face_index, face in enumerate(get_faces(mesh)):
+
+        face_colors = color_layer[face_index]
+        face_colors = face_colors.color1, face_colors.color2, face_colors.color3, face_colors.color4
+
+        for c in face_colors:
+            key = hexcolor(c)
+            if key not in colors:
+                colors[key] = count
+                count += 1
+
+    return count
+
+def generate_vertex_colors(colors, option_colors):
+    if not option_colors:
+        return ""
+
+    chunks = []
+    for key, index in sorted(colors.items(), key=operator.itemgetter(1)):
+        chunks.append(key)
+
+    return ",".join(generate_vertex_color(c) for c in chunks)
+
+# #####################################################
+# Model exporter - UVs
+# #####################################################
+
+def extract_uvs(mesh, uv_layers, counts):
+    for index, layer in enumerate(mesh.tessface_uv_textures):
+
+        if len(uv_layers) <= index:
+            uvs = {}
+            count = 0
+            uv_layers.append(uvs)
+            counts.append(count)
+        else:
+            uvs = uv_layers[index]
+            count = counts[index]
+
+        uv_layer = layer.data
+
+        for face_index, face in enumerate(get_faces(mesh)):
+
+            for uv_index, uv in enumerate(uv_layer[face_index].uv):
+
+                key = veckey2d(uv)
+                if key not in uvs:
+                    uvs[key] = count
+                    count += 1
+
+        counts[index] = count
+
+    return counts
+
+def generate_uvs(uv_layers, option_uv_coords):
+    if not option_uv_coords:
+        return "[]"
+
+    layers = []
+    for uvs in uv_layers:
+        chunks = []
+        for key, index in sorted(uvs.items(), key=operator.itemgetter(1)):
+            chunks.append(key)
+        layer = ",".join(generate_uv(n) for n in chunks)
+        layers.append(layer)
+
+    return ",".join("[%s]" % n for n in layers)
+
+# ##############################################################################
+# Model exporter - bones
+# (only the first armature will exported)
+# ##############################################################################
+
+def generate_bones(option_bones, flipyz):
+
+    if not option_bones or len(bpy.data.armatures) == 0:
+        return "", 0
+
+    hierarchy = []
+
+    armature = bpy.data.armatures[0]
+
+    TEMPLATE_BONE = '{"parent":%d,"name":"%s","pos":[%g,%g,%g],"rotq":[0,0,0,1]}'
+
+    for bone in armature.bones:
+        if bone.parent == None:
+            if flipyz:
+                joint = TEMPLATE_BONE % (-1, bone.name, bone.head.x, bone.head.z, -bone.head.y)
+                hierarchy.append(joint)
+            else:
+                joint = TEMPLATE_BONE % (-1, bone.name, bone.head.x, bone.head.y, bone.head.z)
+                hierarchy.append(joint)
+        else:
+            index = i = 0
+            for parent in armature.bones:
+                if parent.name == bone.parent.name:
+                    index = i
+                i += 1
+
+            position = bone.head_local - bone.parent.head_local
+
+            if flipyz:
+                joint = TEMPLATE_BONE % (index, bone.name, position.x, position.z, -position.y)
+                hierarchy.append(joint)
+            else:
+                joint = TEMPLATE_BONE % (index, bone.name, position.x, position.y, position.z)
+                hierarchy.append(joint)
+
+    bones_string = ",".join(hierarchy)
+
+    return bones_string, len(armature.bones)
+
+
+# ##############################################################################
+# Model exporter - skin indices and weights
+# ##############################################################################
+
+def generate_indices_and_weights(meshes, option_skinning):
+
+    if not option_skinning or len(bpy.data.armatures) == 0:
+        return "", ""
+
+    indices = []
+    weights = []
+
+    armature = bpy.data.armatures[0]
+
+    for mesh, object in meshes:
+
+        i = 0
+        mesh_index = -1
+
+        # find the original object
+
+        for obj in bpy.data.objects:
+            if obj.name == mesh.name or obj == object:
+                mesh_index = i
+            i += 1
+
+        if mesh_index == -1:
+            print("generate_indices: couldn't find object for mesh", mesh.name)
+            continue
+
+        object = bpy.data.objects[mesh_index]
+
+        for vertex in mesh.vertices:
+
+            # sort bones by influence
+
+            bone_array = []
+
+            for group in vertex.groups:
+                index = group.group
+                weight = group.weight
+
+                bone_array.append( (index, weight) )
+
+            bone_array.sort(key = operator.itemgetter(1), reverse=True)
+
+            # select first N bones
+
+            for i in range(MAX_INFLUENCES):
+
+                if i < len(bone_array):
+                    bone_proxy = bone_array[i]
+
+                    index = bone_proxy[0]
+                    weight = bone_proxy[1]
+
+                    for j, bone in enumerate(armature.bones):
+                        if object.vertex_groups[index].name == bone.name:
+                            indices.append('%d' % j)
+                            weights.append('%g' % weight)
+                            break
+
+                else:
+                    indices.append('0')
+                    weights.append('0')
+
+
+    indices_string = ",".join(indices)
+    weights_string = ",".join(weights)
+
+    return indices_string, weights_string
+
+
+# ##############################################################################
+# Model exporter - skeletal animation
+# (only the first action will exported)
+# ##############################################################################
+
+def generate_animation(option_animation_skeletal, option_frame_step, flipyz):
+
+    if not option_animation_skeletal or len(bpy.data.actions) == 0 or len(bpy.data.armatures) == 0:
+        return ""
+
+    # TODO: Add scaling influences
+
+    action = bpy.data.actions[0]
+    armature = bpy.data.armatures[0]
+
+    parents = []
+    parent_index = -1
+
+    fps = bpy.data.scenes[0].render.fps
+
+    end_frame = action.frame_range[1]
+    start_frame = action.frame_range[0]
+
+    frame_length = end_frame - start_frame
+
+    TEMPLATE_KEYFRAME_FULL  = '{"time":%g,"pos":[%g,%g,%g],"rot":[%g,%g,%g,%g],"scl":[1,1,1]}'
+    TEMPLATE_KEYFRAME       = '{"time":%g,"pos":[%g,%g,%g],"rot":[%g,%g,%g,%g]}'
+    TEMPLATE_KEYFRAME_POS   = '{"time":%g,"pos":[%g,%g,%g]}'
+    TEMPLATE_KEYFRAME_ROT   = '{"time":%g,"rot":[%g,%g,%g,%g]}'
+
+    for hierarchy in armature.bones:
+
+        keys = []
+
+        for frame in range(int(start_frame), int(end_frame / option_frame_step) + 1):
+
+            pos, pchange = position(hierarchy, frame * option_frame_step)
+            rot, rchange = rotation(hierarchy, frame * option_frame_step)
+
+            if flipyz:
+                px, py, pz = pos.x, pos.z, -pos.y
+                rx, ry, rz, rw = rot.x, rot.z, -rot.y, rot.w
+            else:
+                px, py, pz = pos.x, pos.y, pos.z
+                rx, ry, rz, rw = rot.x, rot.y, rot.z, rot.w
+
+            # START-FRAME: needs pos, rot and scl attributes (required frame)
+
+            if frame == int(start_frame):
+
+                time = (frame * option_frame_step - start_frame) / fps
+                keyframe = TEMPLATE_KEYFRAME_FULL % (time, px, py, pz, rx, ry, rz, rw)
+                keys.append(keyframe)
+
+            # END-FRAME: needs pos, rot and scl attributes with animation length (required frame)
+
+            elif frame == int(end_frame / option_frame_step):
+
+                time = frame_length / fps
+                keyframe = TEMPLATE_KEYFRAME_FULL % (time, px, py, pz, rx, ry, rz, rw)
+                keys.append(keyframe)
+
+            # MIDDLE-FRAME: needs only one of the attributes, can be an empty frame (optional frame)
+
+            elif pchange == True or rchange == True:
+
+                time = (frame * option_frame_step - start_frame) / fps
+
+                if pchange == True and rchange == True:
+                    keyframe = TEMPLATE_KEYFRAME % (time, px, py, pz, rx, ry, rz, rw)
+                elif pchange == True:
+                    keyframe = TEMPLATE_KEYFRAME_POS % (time, px, py, pz)
+                elif rchange == True:
+                    keyframe = TEMPLATE_KEYFRAME_ROT % (time, rx, ry, rz, rw)
+
+                keys.append(keyframe)
+
+        keys_string = ",".join(keys)
+        parent = '{"parent":%d,"keys":[%s]}' % (parent_index, keys_string)
+        parent_index += 1
+        parents.append(parent)
+
+    hierarchy_string = ",".join(parents)
+    animation_string = '"name":"%s","fps":%d,"length":%g,"hierarchy":[%s]' % (action.name, fps, (frame_length / fps), hierarchy_string)
+
+    return animation_string
+
+def handle_position_channel(channel, frame, position):
+
+    change = False
+
+    if channel.array_index in [0, 1, 2]:
+        for keyframe in channel.keyframe_points:
+            if keyframe.co[0] == frame:
+                change = True
+
+        value = channel.evaluate(frame)
+
+        if channel.array_index == 0:
+            position.x = value
+
+        if channel.array_index == 1:
+            position.y = value
+
+        if channel.array_index == 2:
+            position.z = value
+
+    return change
+
+def position(bone, frame):
+
+    position = mathutils.Vector((0,0,0))
+    change = False
+
+    action = bpy.data.actions[0]
+    ngroups = len(action.groups)
+
+
+
+    if ngroups > 0:
+
+        index = 0
+
+        for i in range(ngroups):
+            if action.groups[i].name == bone.name:
+                index = i
+
+        for channel in action.groups[index].channels:
+            if "location" in channel.data_path:
+                hasChanged = handle_position_channel(channel, frame, position)
+                change = change or hasChanged
+
+    else:
+
+        bone_label = '"%s"' % bone.name
+
+        for channel in action.fcurves:
+            data_path = channel.data_path
+            if bone_label in data_path and "location" in data_path:
+                hasChanged = handle_position_channel(channel, frame, position)
+                change = change or hasChanged
+
+    position = position * bone.matrix_local.inverted()
+
+    if bone.parent == None:
+
+        position.x += bone.head.x
+        position.y += bone.head.y
+        position.z += bone.head.z
+
+    else:
+
+        parent = bone.parent
+
+        parentInvertedLocalMatrix = parent.matrix_local.inverted()
+        parentHeadTailDiff = parent.tail_local - parent.head_local
+
+        position.x += (bone.head * parentInvertedLocalMatrix).x + parentHeadTailDiff.x
+        position.y += (bone.head * parentInvertedLocalMatrix).y + parentHeadTailDiff.y
+        position.z += (bone.head * parentInvertedLocalMatrix).z + parentHeadTailDiff.z
+
+    return position, change
+
+def handle_rotation_channel(channel, frame, rotation):
+
+    change = False
+
+    if channel.array_index in [0, 1, 2, 3]:
+
+        for keyframe in channel.keyframe_points:
+            if keyframe.co[0] == frame:
+                change = True
+
+        value = channel.evaluate(frame)
+
+        if channel.array_index == 1:
+            rotation.x = value
+
+        elif channel.array_index == 2:
+            rotation.y = value
+
+        elif channel.array_index == 3:
+            rotation.z = value
+
+        elif channel.array_index == 0:
+            rotation.w = value
+
+    return change
+
+def rotation(bone, frame):
+
+    # TODO: calculate rotation also from rotation_euler channels
+
+    rotation = mathutils.Vector((0,0,0,1))
+
+    change = False
+
+    action = bpy.data.actions[0]
+    ngroups = len(action.groups)
+
+    # animation grouped by bones
+
+    if ngroups > 0:
+
+        index = 0
+
+        for i in range(ngroups):
+            if action.groups[i].name == bone.name:
+                index = i
+
+        for channel in action.groups[index].channels:
+            if "quaternion" in channel.data_path:
+                hasChanged = handle_rotation_channel(channel, frame, rotation)
+                change = change or hasChanged
+
+    # animation in raw fcurves
+
+    else:
+
+        bone_label = '"%s"' % bone.name
+
+        for channel in action.fcurves:
+            data_path = channel.data_path
+            if bone_label in data_path and "quaternion" in data_path:
+                hasChanged = handle_rotation_channel(channel, frame, rotation)
+                change = change or hasChanged
+
+    rot3 = rotation.to_3d()
+    rotation.xyz = rot3 * bone.matrix_local.inverted()
+
+    return rotation, change
+
+# #####################################################
+# Model exporter - 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]
+        return COLORS[i]
+    else:
+        #return "0x%06x" % int(0xffffff * random.random())
+        return int(0xffffff * random.random())
+
+def generate_mtl(materials):
+    """Generate dummy materials.
+    """
+
+    mtl = {}
+    for m in materials:
+        index = materials[m]
+        mtl[m] = {
+            "DbgName": m,
+            "DbgIndex": index,
+            "DbgColor": generate_color(index),
+            "vertexColors" : False
+        }
+    return mtl
+
+def value2string(v):
+    if type(v) == str and v[0:2] != "0x":
+        return '"%s"' % v
+    elif type(v) == bool:
+        return str(v).lower()
+    elif type(v) == list:
+        return "[%s]" % (", ".join(value2string(x) for x in v))
+    return str(v)
+
+def generate_materials(mtl, materials, draw_type):
+    """Generate JS array of materials objects
+    """
+
+    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]['DbgName'] = m
+        mtl[m]['DbgIndex'] = index
+        mtl[m]['DbgColor'] = generate_color(index)
+
+        if draw_type in [ "BOUNDS", "WIRE" ]:
+            mtl[m]['wireframe'] = True
+            mtl[m]['DbgColor'] = 0xff0000
+
+        mtl_raw = ",\n".join(['\t\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)]), len(mtl_array)
+
+def extract_materials(mesh, scene, option_colors, option_copy_textures, filepath):
+    world = scene.world
+
+    materials = {}
+    for m in mesh.materials:
+        if m:
+            materials[m.name] = {}
+            material = materials[m.name]
+
+            material['colorDiffuse'] = [m.diffuse_intensity * m.diffuse_color[0],
+                                        m.diffuse_intensity * m.diffuse_color[1],
+                                        m.diffuse_intensity * m.diffuse_color[2]]
+
+            material['colorSpecular'] = [m.specular_intensity * m.specular_color[0],
+                                         m.specular_intensity * m.specular_color[1],
+                                         m.specular_intensity * m.specular_color[2]]
+
+            material['colorAmbient'] = [m.ambient * material['colorDiffuse'][0],
+                                        m.ambient * material['colorDiffuse'][1],
+                                        m.ambient * material['colorDiffuse'][2]]
+
+            material['transparency'] = m.alpha
+
+            # not sure about mapping values to Blinn-Phong shader
+            # Blender uses INT from [1, 511] with default 0
+            # http://www.blender.org/documentation/blender_python_api_2_54_0/bpy.types.Material.html#bpy.types.Material.specular_hardness
+
+            material["specularCoef"] = m.specular_hardness
+
+            textures = guess_material_textures(m)
+
+            handle_texture('diffuse', textures, material, filepath, option_copy_textures)
+            handle_texture('light', textures, material, filepath, option_copy_textures)
+            handle_texture('normal', textures, material, filepath, option_copy_textures)
+            handle_texture('specular', textures, material, filepath, option_copy_textures)
+            handle_texture('bump', textures, material, filepath, option_copy_textures)
+
+            material["vertexColors"] = m.THREE_useVertexColors and option_colors
+
+            # can't really use this reliably to tell apart Phong from Lambert
+            # as Blender defaults to non-zero specular color
+            #if m.specular_intensity > 0.0 and (m.specular_color[0] > 0 or m.specular_color[1] > 0 or m.specular_color[2] > 0):
+            #    material['shading'] = "Phong"
+            #else:
+            #    material['shading'] = "Lambert"
+
+            if textures['normal']:
+                material['shading'] = "Phong"
+            else:
+                material['shading'] = m.THREE_materialType
+
+            material['blending'] = m.THREE_blendingType
+            material['depthWrite'] = m.THREE_depthWrite
+            material['depthTest'] = m.THREE_depthTest
+            material['transparent'] = m.use_transparency
+
+    return materials
+
+def generate_materials_string(mesh, scene, option_colors, draw_type, option_copy_textures, filepath, offset):
+
+    random.seed(42) # to get well defined color order for debug materials
+
+    materials = {}
+    if mesh.materials:
+        for i, m in enumerate(mesh.materials):
+            mat_id = i + offset
+            if m:
+                materials[m.name] = mat_id
+            else:
+                materials["undefined_dummy_%0d" % mat_id] = mat_id
+
+
+    if not materials:
+        materials = { 'default': 0 }
+
+    # default dummy materials
+
+    mtl = generate_mtl(materials)
+
+    # extract real materials from the mesh
+
+    mtl.update(extract_materials(mesh, scene, option_colors, option_copy_textures, filepath))
+
+    return generate_materials(mtl, materials, draw_type)
+
+def handle_texture(id, textures, material, filepath, option_copy_textures):
+
+    if textures[id]:
+        texName     = 'map%s'       % id.capitalize()
+        repeatName  = 'map%sRepeat' % id.capitalize()
+        wrapName    = 'map%sWrap'   % id.capitalize()
+
+        slot = textures[id]['slot']
+        texture = textures[id]['texture']
+        image = texture.image
+        fname = extract_texture_filename(image)
+        material[texName] = fname
+
+        if option_copy_textures:
+            save_image(image, fname, filepath)
+
+        if texture.repeat_x != 1 or texture.repeat_y != 1:
+            material[repeatName] = [texture.repeat_x, texture.repeat_y]
+
+        if texture.extension == "REPEAT":
+            wrap_x = "repeat"
+            wrap_y = "repeat"
+
+            if texture.use_mirror_x:
+                wrap_x = "mirror"
+            if texture.use_mirror_y:
+                wrap_y = "mirror"
+
+            material[wrapName] = [wrap_x, wrap_y]
+
+        if slot.use_map_normal:
+            if slot.normal_factor != 1.0:
+                if id == "bump":
+                    material['mapBumpScale'] = slot.normal_factor
+                else:
+                    material['mapNormalFactor'] = slot.normal_factor
+
+
+# #####################################################
+# ASCII model generator
+# #####################################################
+
+def generate_ascii_model(meshes, morphs,
+                         scene,
+                         option_vertices,
+                         option_vertices_truncate,
+                         option_faces,
+                         option_normals,
+                         option_uv_coords,
+                         option_materials,
+                         option_colors,
+                         option_bones,
+                         option_skinning,
+                         align_model,
+                         flipyz,
+                         option_scale,
+                         option_copy_textures,
+                         filepath,
+                         option_animation_morph,
+                         option_animation_skeletal,
+                         option_frame_step):
+
+    vertices = []
+
+    vertex_offset = 0
+    vertex_offsets = []
+
+    nnormal = 0
+    normals = {}
+
+    ncolor = 0
+    colors = {}
+
+    nuvs = []
+    uv_layers = []
+
+    nmaterial = 0
+    materials = []
+
+    for mesh, object in meshes:
+
+        vertexUV = len(mesh.uv_textures) > 0
+        vertexColors = len(mesh.vertex_colors) > 0
+
+        mesh_extract_colors = option_colors and vertexColors
+        mesh_extract_uvs = option_uv_coords and vertexUV
+
+        if vertexUV:
+            active_uv_layer = mesh.uv_textures.active
+            if not active_uv_layer:
+                mesh_extract_uvs = False
+
+        if vertexColors:
+            active_col_layer = mesh.vertex_colors.active
+            if not active_col_layer:
+                mesh_extract_colors = False
+
+        vertex_offsets.append(vertex_offset)
+        vertex_offset += len(vertices)
+
+        vertices.extend(mesh.vertices[:])
+
+        if option_normals:
+            nnormal = extract_vertex_normals(mesh, normals, nnormal)
+
+        if mesh_extract_colors:
+            ncolor = extract_vertex_colors(mesh, colors, ncolor)
+
+        if mesh_extract_uvs:
+            nuvs = extract_uvs(mesh, uv_layers, nuvs)
+
+        if option_materials:
+            mesh_materials, nmaterial = generate_materials_string(mesh, scene, mesh_extract_colors, object.draw_type, option_copy_textures, filepath, nmaterial)
+            materials.append(mesh_materials)
+
+
+    morphTargets_string = ""
+    nmorphTarget = 0
+
+    if option_animation_morph:
+        chunks = []
+        for i, morphVertices in enumerate(morphs):
+            morphTarget = '{ "name": "%s_%06d", "vertices": [%s] }' % ("animation", i, morphVertices)
+            chunks.append(morphTarget)
+
+        morphTargets_string = ",\n\t".join(chunks)
+        nmorphTarget = len(morphs)
+
+    if align_model == 1:
+        center(vertices)
+    elif align_model == 2:
+        bottom(vertices)
+    elif align_model == 3:
+        top(vertices)
+
+    faces_string, nfaces = generate_faces(normals, uv_layers, colors, meshes, option_normals, option_colors, option_uv_coords, option_materials, option_faces)
+
+    bones_string, nbone = generate_bones(option_bones, flipyz)
+    indices_string, weights_string = generate_indices_and_weights(meshes, option_skinning)
+
+    materials_string = ",\n\n".join(materials)
+
+    model_string = TEMPLATE_MODEL_ASCII % {
+    "scale" : option_scale,
+
+    "uvs"       : generate_uvs(uv_layers, option_uv_coords),
+    "normals"   : generate_normals(normals, option_normals),
+    "colors"    : generate_vertex_colors(colors, option_colors),
+
+    "materials" : materials_string,
+
+    "vertices" : generate_vertices(vertices, option_vertices_truncate, option_vertices),
+
+    "faces"    : faces_string,
+
+    "morphTargets" : morphTargets_string,
+
+    "bones"     : bones_string,
+    "indices"   : indices_string,
+    "weights"   : weights_string,
+    "animation" : generate_animation(option_animation_skeletal, option_frame_step, flipyz)
+    }
+
+    text = TEMPLATE_FILE_ASCII % {
+    "nvertex"   : len(vertices),
+    "nface"     : nfaces,
+    "nuvs"      : ",".join("%d" % n for n in nuvs),
+    "nnormal"   : nnormal,
+    "ncolor"    : ncolor,
+    "nmaterial" : nmaterial,
+    "nmorphTarget": nmorphTarget,
+    "nbone"     : nbone,
+
+    "model"     : model_string
+    }
+
+
+    return text, model_string
+
+
+# #####################################################
+# Model exporter - export single mesh
+# #####################################################
+
+def extract_meshes(objects, scene, export_single_model, option_scale, flipyz):
+
+    meshes = []
+
+    for object in objects:
+
+        if object.type == "MESH" and object.THREE_exportGeometry:
+
+            # collapse modifiers into mesh
+
+            mesh = object.to_mesh(scene, True, 'RENDER')
+
+            if not mesh:
+                raise Exception("Error, could not get mesh data from object [%s]" % object.name)
+
+            # preserve original name
+
+            mesh.name = object.name
+
+            if export_single_model:
+
+                if flipyz:
+
+                    # that's what Blender's native export_obj.py does to flip YZ
+
+                    X_ROT = mathutils.Matrix.Rotation(-math.pi/2, 4, 'X')
+                    mesh.transform(X_ROT * object.matrix_world)
+
+                else:
+                    mesh.transform(object.matrix_world)
+
+            mesh.calc_normals()
+            mesh.calc_tessface()
+            mesh.transform(mathutils.Matrix.Scale(option_scale, 4))
+            meshes.append([mesh, object])
+
+    return meshes
+
+def generate_mesh_string(objects, scene,
+                option_vertices,
+                option_vertices_truncate,
+                option_faces,
+                option_normals,
+                option_uv_coords,
+                option_materials,
+                option_colors,
+                option_bones,
+                option_skinning,
+                align_model,
+                flipyz,
+                option_scale,
+                export_single_model,
+                option_copy_textures,
+                filepath,
+                option_animation_morph,
+                option_animation_skeletal,
+                option_frame_step):
+
+    meshes = extract_meshes(objects, scene, export_single_model, option_scale, flipyz)
+
+    morphs = []
+
+    if option_animation_morph:
+
+        original_frame = scene.frame_current # save animation state
+
+        scene_frames = range(scene.frame_start, scene.frame_end + 1, option_frame_step)
+
+        for index, frame in enumerate(scene_frames):
+            scene.frame_set(frame, 0.0)
+
+            anim_meshes = extract_meshes(objects, scene, export_single_model, option_scale, flipyz)
+
+            frame_vertices = []
+
+            for mesh, object in anim_meshes:
+                frame_vertices.extend(mesh.vertices[:])
+
+            if index == 0:
+                if align_model == 1:
+                    offset = center(frame_vertices)
+                elif align_model == 2:
+                    offset = bottom(frame_vertices)
+                elif align_model == 3:
+                    offset = top(frame_vertices)
+                else:
+                    offset = False
+            else:
+                if offset:
+                    translate(frame_vertices, offset)
+
+            morphVertices = generate_vertices(frame_vertices, option_vertices_truncate, option_vertices)
+            morphs.append(morphVertices)
+
+            # remove temp meshes
+
+            for mesh, object in anim_meshes:
+                bpy.data.meshes.remove(mesh)
+
+        scene.frame_set(original_frame, 0.0) # restore animation state
+
+
+    text, model_string = generate_ascii_model(meshes, morphs,
+                                scene,
+                                option_vertices,
+                                option_vertices_truncate,
+                                option_faces,
+                                option_normals,
+                                option_uv_coords,
+                                option_materials,
+                                option_colors,
+                                option_bones,
+                                option_skinning,
+                                align_model,
+                                flipyz,
+                                option_scale,
+                                option_copy_textures,
+                                filepath,
+                                option_animation_morph,
+                                option_animation_skeletal,
+                                option_frame_step)
+
+    # remove temp meshes
+
+    for mesh, object in meshes:
+        bpy.data.meshes.remove(mesh)
+
+    return text, model_string
+
+def export_mesh(objects,
+                scene, filepath,
+                option_vertices,
+                option_vertices_truncate,
+                option_faces,
+                option_normals,
+                option_uv_coords,
+                option_materials,
+                option_colors,
+                option_bones,
+                option_skinning,
+                align_model,
+                flipyz,
+                option_scale,
+                export_single_model,
+                option_copy_textures,
+                option_animation_morph,
+                option_animation_skeletal,
+                option_frame_step):
+
+    """Export single mesh"""
+
+    text, model_string = generate_mesh_string(objects,
+                scene,
+                option_vertices,
+                option_vertices_truncate,
+                option_faces,
+                option_normals,
+                option_uv_coords,
+                option_materials,
+                option_colors,
+                option_bones,
+                option_skinning,
+                align_model,
+                flipyz,
+                option_scale,
+                export_single_model,
+                option_copy_textures,
+                filepath,
+                option_animation_morph,
+                option_animation_skeletal,
+                option_frame_step)
+
+    write_file(filepath, text)
+
+    print("writing", filepath, "done")
+
+
+# #####################################################
+# Scene exporter - render elements
+# #####################################################
+
+def generate_vec4(vec):
+    return TEMPLATE_VEC4 % (vec[0], vec[1], vec[2], vec[3])
+
+def generate_vec3(vec):
+    return TEMPLATE_VEC3 % (vec[0], vec[1], vec[2])
+
+def generate_vec2(vec):
+    return TEMPLATE_VEC2 % (vec[0], vec[1])
+
+def generate_hex(number):
+    return TEMPLATE_HEX % number
+
+def generate_string(s):
+    return TEMPLATE_STRING % s
+
+def generate_string_list(src_list):
+    return ", ".join(generate_string(item) for item in src_list)
+
+def generate_section(label, content):
+    return TEMPLATE_SECTION % (label, content)
+
+def get_mesh_filename(mesh):
+    object_id = mesh["data"]["name"]
+    filename = "%s.js" % sanitize(object_id)
+    return filename
+
+def generate_material_id_list(materials):
+    chunks = []
+    for material in materials:
+        chunks.append(material.name)
+
+    return chunks
+
+def generate_group_id_list(obj):
+    chunks = []
+
+    for group in bpy.data.groups:
+        if obj.name in group.objects:
+            chunks.append(group.name)
+
+    return chunks
+
+def generate_bool_property(property):
+    if property:
+        return "true"
+    return "false"
+
+# #####################################################
+# Scene exporter - objects
+# #####################################################
+
+def generate_objects(data):
+    chunks = []
+
+    for obj in data["objects"]:
+
+        if obj.type == "MESH" and obj.THREE_exportGeometry:
+            object_id = obj.name
+
+            if len(obj.modifiers) > 0:
+                geo_name = obj.name
+            else:
+                geo_name = obj.data.name
+
+            geometry_id = "geo_%s" % geo_name
+
+            material_ids = generate_material_id_list(obj.material_slots)
+            group_ids = generate_group_id_list(obj)
+
+            position, quaternion, scale = obj.matrix_world.decompose()
+            rotation = quaternion.to_euler("XYZ")
+
+            # use empty material string for multi-material objects
+            # this will trigger use of MeshFaceMaterial in SceneLoader
+
+            material_string = ""
+            if len(material_ids) == 1:
+                material_string = generate_string_list(material_ids)
+
+            group_string = ""
+            if len(group_ids) > 0:
+                group_string = generate_string_list(group_ids)
+
+            castShadow = obj.THREE_castShadow
+            receiveShadow = obj.THREE_receiveShadow
+            doubleSided = obj.THREE_doubleSided
+
+            visible = True
+
+            geometry_string = generate_string(geometry_id)
+
+            object_string = TEMPLATE_OBJECT % {
+            "object_id"   : generate_string(object_id),
+            "geometry_id" : geometry_string,
+            "group_id"    : group_string,
+            "material_id" : material_string,
+
+            "position"    : generate_vec3(position),
+            "rotation"    : generate_vec3(rotation),
+            "quaternion"  : generate_vec4(quaternion),
+            "scale"       : generate_vec3(scale),
+
+            "castShadow"  : generate_bool_property(castShadow),
+            "receiveShadow"  : generate_bool_property(receiveShadow),
+            "doubleSided"  : generate_bool_property(doubleSided),
+            "visible"      : generate_bool_property(visible)
+            }
+            chunks.append(object_string)
+
+        elif obj.type == "EMPTY" or (obj.type == "MESH" and not obj.THREE_exportGeometry):
+
+            object_id = obj.name
+            group_ids = generate_group_id_list(obj)
+
+            position, quaternion, scale = obj.matrix_world.decompose()
+            rotation = quaternion.to_euler("XYZ")
+
+            group_string = ""
+            if len(group_ids) > 0:
+                group_string = generate_string_list(group_ids)
+
+            object_string = TEMPLATE_EMPTY % {
+            "object_id"   : generate_string(object_id),
+            "group_id"    : group_string,
+
+            "position"    : generate_vec3(position),
+            "rotation"    : generate_vec3(rotation),
+            "quaternion"  : generate_vec4(quaternion),
+            "scale"       : generate_vec3(scale)
+            }
+            chunks.append(object_string)
+
+    return ",\n\n".join(chunks), len(chunks)
+
+# #####################################################
+# Scene exporter - geometries
+# #####################################################
+
+def generate_geometries(data):
+    chunks = []
+
+    geo_set = set()
+
+    for obj in data["objects"]:
+        if obj.type == "MESH" and obj.THREE_exportGeometry:
+
+            if len(obj.modifiers) > 0:
+                name = obj.name
+            else:
+                name = obj.data.name
+
+            if name not in geo_set:
+
+                geometry_id = "geo_%s" % name
+
+                if data["embed_meshes"]:
+
+                    embed_id = "emb_%s" % name
+
+                    geometry_string = TEMPLATE_GEOMETRY_EMBED % {
+                    "geometry_id" : generate_string(geometry_id),
+                    "embed_id"  : generate_string(embed_id)
+                    }
+
+                else:
+
+                    model_filename = os.path.basename(generate_mesh_filename(name, data["filepath"]))
+
+                    geometry_string = TEMPLATE_GEOMETRY_LINK % {
+                    "geometry_id" : generate_string(geometry_id),
+                    "model_file"  : generate_string(model_filename)
+                    }
+
+                chunks.append(geometry_string)
+
+                geo_set.add(name)
+
+    return ",\n\n".join(chunks), len(chunks)
+
+# #####################################################
+# Scene exporter - textures
+# #####################################################
+
+def generate_textures_scene(data):
+    chunks = []
+
+    # TODO: extract just textures actually used by some objects in the scene
+
+    for texture in bpy.data.textures:
+
+        if texture.type == 'IMAGE' and texture.image:
+
+            img = texture.image
+
+            texture_id = img.name
+            texture_file = extract_texture_filename(img)
+
+            if data["copy_textures"]:
+                save_image(img, texture_file, data["filepath"])
+
+            extras = ""
+
+            if texture.repeat_x != 1 or texture.repeat_y != 1:
+                extras += ',\n        "repeat": [%g, %g]' % (texture.repeat_x, texture.repeat_y)
+
+            if texture.extension == "REPEAT":
+                wrap_x = "repeat"
+                wrap_y = "repeat"
+
+                if texture.use_mirror_x:
+                    wrap_x = "mirror"
+                if texture.use_mirror_y:
+                    wrap_y = "mirror"
+
+                extras += ',\n        "wrap": ["%s", "%s"]' % (wrap_x, wrap_y)
+
+            texture_string = TEMPLATE_TEXTURE % {
+            "texture_id"   : generate_string(texture_id),
+            "texture_file" : generate_string(texture_file),
+            "extras"       : extras
+            }
+            chunks.append(texture_string)
+
+    return ",\n\n".join(chunks), len(chunks)
+
+def extract_texture_filename(image):
+    fn = bpy.path.abspath(image.filepath)
+    fn = os.path.normpath(fn)
+    fn_strip = os.path.basename(fn)
+    return fn_strip
+
+def save_image(img, name, fpath):
+    dst_dir = os.path.dirname(fpath)
+    dst_path = os.path.join(dst_dir, name)
+
+    ensure_folder_exist(dst_dir)
+
+    if img.packed_file:
+        img.save_render(dst_path)
+
+    else:
+        src_path = bpy.path.abspath(img.filepath)
+        shutil.copy(src_path, dst_dir)
+
+# #####################################################
+# Scene exporter - materials
+# #####################################################
+
+def extract_material_data(m, option_colors):
+    world = bpy.context.scene.world
+
+    material = { 'name': m.name }
+
+    material['colorDiffuse'] = [m.diffuse_intensity * m.diffuse_color[0],
+                                m.diffuse_intensity * m.diffuse_color[1],
+                                m.diffuse_intensity * m.diffuse_color[2]]
+
+    material['colorSpecular'] = [m.specular_intensity * m.specular_color[0],
+                                 m.specular_intensity * m.specular_color[1],
+                                 m.specular_intensity * m.specular_color[2]]
+
+    material['colorAmbient'] = [m.ambient * material['colorDiffuse'][0],
+                                m.ambient * material['colorDiffuse'][1],
+                                m.ambient * material['colorDiffuse'][2]]
+
+    material['transparency'] = m.alpha
+
+    # not sure about mapping values to Blinn-Phong shader
+    # Blender uses INT from [1,511] with default 0
+    # http://www.blender.org/documentation/blender_python_api_2_54_0/bpy.types.Material.html#bpy.types.Material.specular_hardness
+
+    material["specularCoef"] = m.specular_hardness
+
+    material["vertexColors"] = m.THREE_useVertexColors and option_colors
+
+    material['mapDiffuse'] = ""
+    material['mapLight'] = ""
+    material['mapSpecular'] = ""
+    material['mapNormal'] = ""
+    material['mapBump'] = ""
+
+    material['mapNormalFactor'] = 1.0
+    material['mapBumpScale'] = 1.0
+
+    textures = guess_material_textures(m)
+
+    if textures['diffuse']:
+        material['mapDiffuse'] = textures['diffuse']['texture'].image.name
+
+    if textures['light']:
+        material['mapLight'] = textures['light']['texture'].image.name
+
+    if textures['specular']:
+        material['mapSpecular'] = textures['specular']['texture'].image.name
+
+    if textures['normal']:
+        material['mapNormal'] = textures['normal']['texture'].image.name
+        if textures['normal']['slot'].use_map_normal:
+            material['mapNormalFactor'] = textures['normal']['slot'].normal_factor
+
+    if textures['bump']:
+        material['mapBump'] = textures['bump']['texture'].image.name
+        if textures['normal']['slot'].use_map_normal:
+            material['mapBumpScale'] = textures['normal']['slot'].normal_factor
+
+    material['shading'] = m.THREE_materialType
+    material['blending'] = m.THREE_blendingType
+    material['depthWrite'] = m.THREE_depthWrite
+    material['depthTest'] = m.THREE_depthTest
+    material['transparent'] = m.use_transparency
+
+    return material
+
+def guess_material_textures(material):
+    textures = {
+        'diffuse' : None,
+        'light'   : None,
+        'normal'  : None,
+        'specular': None,
+        'bump'    : None
+    }
+
+    # just take first textures of each, for the moment three.js materials can't handle more
+    # assume diffuse comes before lightmap, normalmap has checked flag
+
+    for i in range(len(material.texture_slots)):
+        slot = material.texture_slots[i]
+        if slot:
+            texture = slot.texture
+            if slot.use and texture and texture.type == 'IMAGE':
+
+                # normal map in Blender UI: textures => image sampling => normal map
+
+                if texture.use_normal_map:
+                    textures['normal'] = { "texture": texture, "slot": slot }
+
+                # bump map in Blender UI: textures => influence => geometry => normal
+
+                elif slot.use_map_normal:
+                    textures['bump'] = { "texture": texture, "slot": slot }
+
+                elif slot.use_map_specular or slot.use_map_hardness:
+                    textures['specular'] = { "texture": texture, "slot": slot }
+
+                else:
+                    if not textures['diffuse'] and not slot.blend_type == 'MULTIPLY':
+                        textures['diffuse'] = { "texture": texture, "slot": slot }
+
+                    else:
+                        textures['light'] = { "texture": texture, "slot": slot }
+
+                if textures['diffuse'] and textures['normal'] and textures['light'] and textures['specular'] and textures['bump']:
+                    break
+
+    return textures
+
+def generate_material_string(material):
+
+    material_id = material["name"]
+
+    # default to Lambert
+
+    shading = material.get("shading", "Lambert")
+
+    # normal and bump mapped materials must use Phong
+    # to get all required parameters for normal shader
+
+    if material['mapNormal'] or material['mapBump']:
+        shading = "Phong"
+
+    type_map = {
+    "Lambert"   : "MeshLambertMaterial",
+    "Phong"     : "MeshPhongMaterial"
+    }
+
+    material_type = type_map.get(shading, "MeshBasicMaterial")
+
+    parameters = '"color": %d' % rgb2int(material["colorDiffuse"])
+    parameters += ', "opacity": %.2g' % material["transparency"]
+
+    if shading == "Phong":
+        parameters += ', "ambient": %d' % rgb2int(material["colorAmbient"])
+        parameters += ', "specular": %d' % rgb2int(material["colorSpecular"])
+        parameters += ', "shininess": %.1g' % material["specularCoef"]
+
+    colorMap = material['mapDiffuse']
+    lightMap = material['mapLight']
+    specularMap = material['mapSpecular']
+    normalMap = material['mapNormal']
+    bumpMap = material['mapBump']
+    normalMapFactor = material['mapNormalFactor']
+    bumpMapScale = material['mapBumpScale']
+
+    if colorMap:
+        parameters += ', "map": %s' % generate_string(colorMap)
+    if lightMap:
+        parameters += ', "lightMap": %s' % generate_string(lightMap)
+    if specularMap:
+        parameters += ', "specularMap": %s' % generate_string(specularMap)
+    if normalMap:
+        parameters += ', "normalMap": %s' % generate_string(normalMap)
+    if bumpMap:
+        parameters += ', "bumpMap": %s' % generate_string(bumpMap)
+
+    if normalMapFactor != 1.0:
+        parameters += ', "normalMapFactor": %g' % normalMapFactor
+
+    if bumpMapScale != 1.0:
+        parameters += ', "bumpMapScale": %g' % bumpMapScale
+
+    if material['vertexColors']:
+        parameters += ', "vertexColors": "vertex"'
+
+    if material['transparent']:
+        parameters += ', "transparent": true'
+
+    parameters += ', "blending": "%s"' % material['blending']
+
+    if not material['depthWrite']:
+        parameters += ', "depthWrite": false'
+
+    if not material['depthTest']:
+        parameters += ', "depthTest": false'
+
+
+    material_string = TEMPLATE_MATERIAL_SCENE % {
+    "material_id" : generate_string(material_id),
+    "type"        : generate_string(material_type),
+    "parameters"  : parameters
+    }
+
+    return material_string
+
+def generate_materials_scene(data):
+    chunks = []
+
+    # TODO: extract just materials actually used by some objects in the scene
+
+    for m in bpy.data.materials:
+        material = extract_material_data(m, data["use_colors"])
+        material_string = generate_material_string(material)
+        chunks.append(material_string)
+
+    return ",\n\n".join(chunks), len(chunks)
+
+# #####################################################
+# Scene exporter - cameras
+# #####################################################
+
+def generate_cameras(data):
+    chunks = []
+
+    if data["use_cameras"]:
+
+        cams = bpy.data.objects
+        cams = [ob for ob in cams if (ob.type == 'CAMERA' and ob.select)]
+
+        if not cams:
+            camera = DEFAULTS["camera"]
+
+            if camera["type"] == "PerspectiveCamera":
+
+                camera_string = TEMPLATE_CAMERA_PERSPECTIVE % {
+                "camera_id" : generate_string(camera["name"]),
+                "fov"       : camera["fov"],
+                "aspect"    : camera["aspect"],
+                "near"      : camera["near"],
+                "far"       : camera["far"],
+                "position"  : generate_vec3(camera["position"]),
+                "target"    : generate_vec3(camera["target"])
+                }
+
+            elif camera["type"] == "OrthographicCamera":
+
+                camera_string = TEMPLATE_CAMERA_ORTHO % {
+                "camera_id" : generate_string(camera["name"]),
+                "left"      : camera["left"],
+                "right"     : camera["right"],
+                "top"       : camera["top"],
+                "bottom"    : camera["bottom"],
+                "near"      : camera["near"],
+                "far"       : camera["far"],
+                "position"  : generate_vec3(camera["position"]),
+                "target"    : generate_vec3(camera["target"])
+                }
+
+            chunks.append(camera_string)
+
+        else:
+
+            for cameraobj in cams:
+                camera = bpy.data.cameras[cameraobj.name]
+
+                # TODO:
+                #   Support more than perspective camera
+                #   Calculate a target/lookat
+                #   Get correct aspect ratio
+                if camera.id_data.type == "PERSP":
+
+                    camera_string = TEMPLATE_CAMERA_PERSPECTIVE % {
+                    "camera_id" : generate_string(camera.name),
+                    "fov"       : (camera.angle / 3.14) * 180.0,
+                    "aspect"    : 1.333,
+                    "near"      : camera.clip_start,
+                    "far"       : camera.clip_end,
+                    "position"  : generate_vec3([cameraobj.location[0], -cameraobj.location[1], cameraobj.location[2]]),
+                    "target"    : generate_vec3([0, 0, 0])
+                    }
+
+                chunks.append(camera_string)
+
+    return ",\n\n".join(chunks), len(chunks)
+
+# #####################################################
+# Scene exporter - lights
+# #####################################################
+
+def generate_lights(data):
+    chunks = []
+
+    if data["use_lights"]:
+
+        lights = data.get("lights", [])
+        if not lights:
+            lights.append(DEFAULTS["light"])
+
+        for light in lights:
+
+            if light["type"] == "DirectionalLight":
+                light_string = TEMPLATE_LIGHT_DIRECTIONAL % {
+                "light_id"      : generate_string(light["name"]),
+                "direction"     : generate_vec3(light["direction"]),
+                "color"         : rgb2int(light["color"]),
+                "intensity"     : light["intensity"]
+                }
+
+            elif light["type"] == "PointLight":
+                light_string = TEMPLATE_LIGHT_POINT % {
+                "light_id"      : generate_string(light["name"]),
+                "position"      : generate_vec3(light["position"]),
+                "color"         : rgb2int(light["color"]),
+                "intensity"     : light["intensity"]
+                }
+
+            chunks.append(light_string)
+
+    return ",\n\n".join(chunks), len(chunks)
+
+# #####################################################
+# Scene exporter - embedded meshes
+# #####################################################
+
+def generate_embeds(data):
+
+    if data["embed_meshes"]:
+
+        chunks = []
+
+        for e in data["embeds"]:
+
+            embed = '"emb_%s": {%s}' % (e, data["embeds"][e])
+            chunks.append(embed)
+
+        return ",\n\n".join(chunks)
+
+    return ""
+
+# #####################################################
+# Scene exporter - generate ASCII scene
+# #####################################################
+
+def generate_ascii_scene(data):
+
+    objects, nobjects = generate_objects(data)
+    geometries, ngeometries = generate_geometries(data)
+    textures, ntextures = generate_textures_scene(data)
+    materials, nmaterials = generate_materials_scene(data)
+    lights, nlights = generate_lights(data)
+    cameras, ncameras = generate_cameras(data)
+
+    embeds = generate_embeds(data)
+
+    if nlights > 0:
+        if nobjects > 0:
+            objects = objects + ",\n\n" + lights
+        else:
+            objects = lights
+        nobjects += nlights
+
+    if ncameras > 0:
+        if nobjects > 0:
+            objects = objects + ",\n\n" + cameras
+        else:
+            objects = cameras
+        nobjects += ncameras
+
+    basetype = "relativeTo"
+
+    if data["base_html"]:
+        basetype += "HTML"
+    else:
+        basetype += "Scene"
+
+    sections = [
+    ["objects",    objects],
+    ["geometries", geometries],
+    ["textures",   textures],
+    ["materials",  materials],
+    ["embeds",     embeds]
+    ]
+
+    chunks = []
+    for label, content in sections:
+        if content:
+            chunks.append(generate_section(label, content))
+
+    sections_string = "\n".join(chunks)
+
+    default_camera = ""
+    if data["use_cameras"]:
+        cams = [ob for ob in bpy.data.objects if (ob.type == 'CAMERA' and ob.select)]
+        if not cams:
+            default_camera = "default_camera"
+        else:
+            default_camera = cams[0].name
+
+    parameters = {
+    "fname"     : data["source_file"],
+
+    "sections"  : sections_string,
+
+    "bgcolor"   : generate_vec3(DEFAULTS["bgcolor"]),
+    "bgalpha"   : DEFAULTS["bgalpha"],
+    "defcamera" :  generate_string(default_camera),
+
+    "nobjects"      : nobjects,
+    "ngeometries"   : ngeometries,
+    "ntextures"     : ntextures,
+    "basetype"      : generate_string(basetype),
+    "nmaterials"    : nmaterials,
+
+    "position"      : generate_vec3(DEFAULTS["position"]),
+    "rotation"      : generate_vec3(DEFAULTS["rotation"]),
+    "scale"         : generate_vec3(DEFAULTS["scale"])
+    }
+
+    text = TEMPLATE_SCENE_ASCII % parameters
+
+    return text
+
+def export_scene(scene, filepath, flipyz, option_colors, option_lights, option_cameras, option_embed_meshes, embeds, option_url_base_html, option_copy_textures):
+
+    source_file = os.path.basename(bpy.data.filepath)
+
+    # objects are contained in scene and linked groups
+    objects = []
+
+    # get scene objects
+    sceneobjects = scene.objects
+    for obj in sceneobjects:
+      objects.append(obj)
+
+    # get linked group objcts
+    for group in bpy.data.groups:
+       for object in group.objects:
+          objects.append(object)
+
+    scene_text = ""
+    data = {
+    "scene"        : scene,
+    "objects"      : objects,
+    "embeds"       : embeds,
+    "source_file"  : source_file,
+    "filepath"     : filepath,
+    "flipyz"       : flipyz,
+    "use_colors"   : option_colors,
+    "use_lights"   : option_lights,
+    "use_cameras"  : option_cameras,
+    "embed_meshes" : option_embed_meshes,
+    "base_html"    : option_url_base_html,
+    "copy_textures": option_copy_textures
+    }
+    scene_text += generate_ascii_scene(data)
+
+    write_file(filepath, scene_text)
+
+# #####################################################
+# Main
+# #####################################################
+
+def save(operator, context, filepath = "",
+         option_flip_yz = True,
+         option_vertices = True,
+         option_vertices_truncate = False,
+         option_faces = True,
+         option_normals = True,
+         option_uv_coords = True,
+         option_materials = True,
+         option_colors = True,
+         option_bones = True,
+         option_skinning = True,
+         align_model = 0,
+         option_export_scene = False,
+         option_lights = False,
+         option_cameras = False,
+         option_scale = 1.0,
+         option_embed_meshes = True,
+         option_url_base_html = False,
+         option_copy_textures = False,
+         option_animation_morph = False,
+         option_animation_skeletal = False,
+         option_frame_step = 1,
+         option_all_meshes = True):
+
+    #print("URL TYPE", option_url_base_html)
+
+    filepath = ensure_extension(filepath, '.js')
+
+    scene = context.scene
+
+    if scene.objects.active:
+        bpy.ops.object.mode_set(mode='OBJECT')
+
+    if option_all_meshes:
+        sceneobjects = scene.objects
+    else:
+        sceneobjects = context.selected_objects
+
+    # objects are contained in scene and linked groups
+    objects = []
+
+    # get scene objects
+    for obj in sceneobjects:
+      objects.append(obj)
+
+    # get objects in linked groups
+    for group in bpy.data.groups:
+       for object in group.objects:
+          objects.append(object)
+
+    if option_export_scene:
+
+        geo_set = set()
+        embeds = {}
+
+        for object in objects:
+            if object.type == "MESH" and object.THREE_exportGeometry:
+
+                # create extra copy of geometry with applied modifiers
+                # (if they exist)
+
+                if len(object.modifiers) > 0:
+                    name = object.name
+
+                # otherwise can share geometry
+
+                else:
+                    name = object.data.name
+
+                if name not in geo_set:
+
+                    if option_embed_meshes:
+
+                        text, model_string = generate_mesh_string([object], scene,
+                                                        option_vertices,
+                                                        option_vertices_truncate,
+                                                        option_faces,
+                                                        option_normals,
+                                                        option_uv_coords,
+                                                        option_materials,
+                                                        option_colors,
+                                                        option_bones,
+                                                        option_skinning,
+                                                        False,          # align_model
+                                                        option_flip_yz,
+                                                        option_scale,
+                                                        False,          # export_single_model
+                                                        False,          # option_copy_textures
+                                                        filepath,
+                                                        option_animation_morph,
+                                                        option_animation_skeletal,
+                                                        option_frame_step)
+
+                        embeds[name] = model_string
+
+                    else:
+
+                        fname = generate_mesh_filename(name, filepath)
+                        export_mesh([object], scene,
+                                    fname,
+                                    option_vertices,
+                                    option_vertices_truncate,
+                                    option_faces,
+                                    option_normals,
+                                    option_uv_coords,
+                                    option_materials,
+                                    option_colors,
+                                    option_bones,
+                                    option_skinning,
+                                    False,          # align_model
+                                    option_flip_yz,
+                                    option_scale,
+                                    False,          # export_single_model
+                                    option_copy_textures,
+                                    option_animation_morph,
+                                    option_animation_skeletal,
+                                    option_frame_step)
+
+                    geo_set.add(name)
+
+        export_scene(scene, filepath,
+                     option_flip_yz,
+                     option_colors,
+                     option_lights,
+                     option_cameras,
+                     option_embed_meshes,
+                     embeds,
+                     option_url_base_html,
+                     option_copy_textures)
+
+    else:
+
+        export_mesh(objects, scene, filepath,
+                    option_vertices,
+                    option_vertices_truncate,
+                    option_faces,
+                    option_normals,
+                    option_uv_coords,
+                    option_materials,
+                    option_colors,
+                    option_bones,
+                    option_skinning,
+                    align_model,
+                    option_flip_yz,
+                    option_scale,
+                    True,            # export_single_model
+                    option_copy_textures,
+                    option_animation_morph,
+                    option_animation_skeletal,
+                    option_frame_step)
+
+    return {'FINISHED'}

+ 633 - 0
utils/exporters/blender/2.65/scripts/addons/io_mesh_threejs/import_threejs.py

@@ -0,0 +1,633 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+"""
+Blender importer for Three.js (ASCII JSON format).
+
+"""
+
+import os
+import time
+import json
+import bpy
+import mathutils
+from mathutils.geometry import tessellate_polygon
+from bpy_extras.image_utils import load_image
+
+# #####################################################
+# Generators
+# #####################################################
+def setColor(c, t):
+    c.r = t[0]
+    c.g = t[1]
+    c.b = t[2]
+
+def create_texture(filename, modelpath):
+    name = filename
+    texture = bpy.data.textures.new(name, type='IMAGE')
+
+    image = load_image(filename, modelpath)
+    has_data = False
+
+    if image:
+        texture.image = image
+        has_data = image.has_data
+
+    return texture
+
+def create_materials(data, modelpath):
+    materials = []
+    materials_data = data.get("materials", [])
+
+    for i, m in enumerate(materials_data):
+
+        name = m.get("DbgName", "material_%d" % i)
+
+        colorAmbient = m.get("colorAmbient", None)
+        colorDiffuse = m.get("colorDiffuse", None)
+        colorSpecular = m.get("colorSpecular", None)
+        alpha = m.get("transparency", 1.0)
+        specular_hardness = m.get("specularCoef", 0)
+
+        mapDiffuse = m.get("mapDiffuse", None)
+        mapLightmap = m.get("mapLightmap", None)
+
+        vertexColorsType = m.get("vertexColors", False)
+
+        useVertexColors = False
+        if vertexColorsType:
+            useVertexColors = True
+
+        material = bpy.data.materials.new(name)
+
+        material.THREE_useVertexColors = useVertexColors
+
+        if colorDiffuse:
+            setColor(material.diffuse_color, colorDiffuse)
+            material.diffuse_intensity = 1.0
+
+        if colorSpecular:
+            setColor(material.specular_color, colorSpecular)
+            material.specular_intensity = 1.0
+
+        if alpha < 1.0:
+            material.alpha = alpha
+            material.use_transparency = True
+
+        if specular_hardness:
+            material.specular_hardness = specular_hardness
+
+        if mapDiffuse:
+            texture = create_texture(mapDiffuse, modelpath)
+            mtex = material.texture_slots.add()
+            mtex.texture = texture
+            mtex.texture_coords = 'UV'
+            mtex.use = True
+            mtex.use_map_color_diffuse = True
+
+            material.active_texture = texture
+
+        materials.append(material)
+
+    return materials
+
+def create_mesh_object(name, vertices, materials, face_data, flipYZ, recalculate_normals):
+
+    faces         = face_data["faces"]
+    vertexNormals = face_data["vertexNormals"]
+    vertexColors  = face_data["vertexColors"]
+    vertexUVs     = face_data["vertexUVs"]
+    faceMaterials = face_data["materials"]
+    faceColors    = face_data["faceColors"]
+
+    edges = []
+
+    # Create a new mesh
+
+    me = bpy.data.meshes.new(name)
+    me.from_pydata(vertices, edges, faces)
+
+    # Handle normals
+
+    if not recalculate_normals:
+        me.update(calc_edges = True)
+
+    if face_data["hasVertexNormals"]:
+
+        print("setting vertex normals")
+
+        for fi in range(len(faces)):
+
+            if vertexNormals[fi]:
+
+                #print("setting face %i with %i vertices" % (fi, len(normals[fi])))
+
+                # if me.update() is called after setting vertex normals
+                # setting face.use_smooth overrides these normals
+                #  - this fixes weird shading artefacts (seems to come from sharing
+                #    of vertices between faces, didn't find a way how to set vertex normals
+                #    per face use of vertex as opposed to per vertex),
+                #  - probably this just overrides all custom vertex normals
+                #  - to preserve vertex normals from the original data
+                #    call me.update() before setting them
+
+                me.faces[fi].use_smooth = True
+
+                if not recalculate_normals:
+                    for j in range(len(vertexNormals[fi])):
+
+                        vertexNormal = vertexNormals[fi][j]
+
+                        x = vertexNormal[0]
+                        y = vertexNormal[1]
+                        z = vertexNormal[2]
+
+                        if flipYZ:
+                            tmp = y
+                            y = -z
+                            z = tmp
+
+                            # flip normals (this make them look consistent with the original before export)
+
+                            #x = -x
+                            #y = -y
+                            #z = -z
+
+                        vi = me.faces[fi].vertices[j]
+
+                        me.vertices[vi].normal.x = x
+                        me.vertices[vi].normal.y = y
+                        me.vertices[vi].normal.z = z
+
+    if recalculate_normals:
+        me.update(calc_edges = True)
+
+    # Handle colors
+
+    if face_data["hasVertexColors"]:
+
+        print("setting vertex colors")
+
+        me.vertex_colors.new("vertex_color_layer_0")
+
+        for fi in range(len(faces)):
+
+            if vertexColors[fi]:
+
+                face_colors = me.vertex_colors[0].data[fi]
+                face_colors = face_colors.color1, face_colors.color2, face_colors.color3, face_colors.color4
+
+                for vi in range(len(vertexColors[fi])):
+
+                    r = vertexColors[fi][vi][0]
+                    g = vertexColors[fi][vi][1]
+                    b = vertexColors[fi][vi][2]
+
+                    face_colors[vi].r = r
+                    face_colors[vi].g = g
+                    face_colors[vi].b = b
+
+    elif face_data["hasFaceColors"]:
+
+        print("setting vertex colors from face colors")
+
+        me.vertex_colors.new("vertex_color_layer_0")
+
+        for fi in range(len(faces)):
+
+            if faceColors[fi]:
+
+                r = faceColors[fi][0]
+                g = faceColors[fi][1]
+                b = faceColors[fi][2]
+
+                face_colors = me.vertex_colors[0].data[fi]
+                face_colors = face_colors.color1, face_colors.color2, face_colors.color3, face_colors.color4
+
+                for vi in range(len(faces[fi])):
+
+                    face_colors[vi].r = r
+                    face_colors[vi].g = g
+                    face_colors[vi].b = b
+
+    # Handle uvs
+
+    if face_data["hasVertexUVs"]:
+
+        print("setting vertex uvs")
+
+        for li, layer in enumerate(vertexUVs):
+
+            me.uv_textures.new("uv_layer_%d" % li)
+
+            for fi in range(len(faces)):
+
+                if layer[fi]:
+
+                    uv_face = me.uv_textures[li].data[fi]
+                    face_uvs = uv_face.uv1, uv_face.uv2, uv_face.uv3, uv_face.uv4
+
+                    for vi in range(len(layer[fi])):
+
+                        u = layer[fi][vi][0]
+                        v = layer[fi][vi][1]
+
+                        face_uvs[vi].x = u
+                        face_uvs[vi].y = v
+
+                    active_texture = materials[faceMaterials[fi]].active_texture
+
+                    if active_texture:
+                        uv_face.image = active_texture.image
+
+
+    # Handle materials # 1
+
+    if face_data["hasMaterials"]:
+
+
+        print("setting materials (mesh)")
+
+        for m in materials:
+
+            me.materials.append(m)
+
+        print("setting materials (faces)")
+
+        for fi in range(len(faces)):
+
+            if faceMaterials[fi] >= 0:
+
+                me.faces[fi].material_index = faceMaterials[fi]
+
+    # Create a new object
+
+    ob = bpy.data.objects.new(name, me)
+    ob.data = me                                # link the mesh data to the object
+
+
+    scene = bpy.context.scene                   # get the current scene
+    scene.objects.link(ob)                      # link the object into the scene
+
+    ob.location = scene.cursor_location         # position object at 3d-cursor
+
+
+# #####################################################
+# Faces
+# #####################################################
+
+def extract_faces(data):
+
+    result = {
+    "faces"         : [],
+    "materials"     : [],
+    "faceUVs"       : [],
+    "vertexUVs"     : [],
+    "faceNormals"   : [],
+    "vertexNormals" : [],
+    "faceColors"    : [],
+    "vertexColors"  : [],
+
+    "hasVertexNormals"  : False,
+    "hasVertexUVs"      : False,
+    "hasVertexColors"   : False,
+    "hasFaceColors"     : False,
+    "hasMaterials"      : False
+    }
+
+    faces = data.get("faces", [])
+    normals = data.get("normals", [])
+    colors = data.get("colors", [])
+
+    offset = 0
+    zLength = len(faces)
+
+    # disregard empty arrays
+
+    nUvLayers = 0
+
+    for layer in data["uvs"]:
+
+        if len(layer) > 0:
+            nUvLayers += 1
+            result["faceUVs"].append([])
+            result["vertexUVs"].append([])
+
+
+    while ( offset < zLength ):
+
+        type = faces[ offset ]
+        offset += 1
+
+        isQuad          	= isBitSet( type, 0 )
+        hasMaterial         = isBitSet( type, 1 )
+        hasFaceUv           = isBitSet( type, 2 )
+        hasFaceVertexUv     = isBitSet( type, 3 )
+        hasFaceNormal       = isBitSet( type, 4 )
+        hasFaceVertexNormal = isBitSet( type, 5 )
+        hasFaceColor	    = isBitSet( type, 6 )
+        hasFaceVertexColor  = isBitSet( type, 7 )
+
+        #print("type", type, "bits", isQuad, hasMaterial, hasFaceUv, hasFaceVertexUv, hasFaceNormal, hasFaceVertexNormal, hasFaceColor, hasFaceVertexColor)
+
+        result["hasVertexUVs"] = result["hasVertexUVs"] or hasFaceVertexUv
+        result["hasVertexNormals"] = result["hasVertexNormals"] or hasFaceVertexNormal
+        result["hasVertexColors"] = result["hasVertexColors"] or hasFaceVertexColor
+        result["hasFaceColors"] = result["hasFaceColors"] or hasFaceColor
+        result["hasMaterials"] = result["hasMaterials"] or hasMaterial
+
+        # vertices
+
+        if isQuad:
+
+            a = faces[ offset ]
+            offset += 1
+
+            b = faces[ offset ]
+            offset += 1
+
+            c = faces[ offset ]
+            offset += 1
+
+            d = faces[ offset ]
+            offset += 1
+
+            face = [a, b, c, d]
+
+            nVertices = 4
+
+        else:
+
+            a = faces[ offset ]
+            offset += 1
+
+            b = faces[ offset ]
+            offset += 1
+
+            c = faces[ offset ]
+            offset += 1
+
+            face = [a, b, c]
+
+            nVertices = 3
+
+        result["faces"].append(face)
+
+        # material
+
+        if hasMaterial:
+
+            materialIndex = faces[ offset ]
+            offset += 1
+
+        else:
+
+            materialIndex = -1
+
+        result["materials"].append(materialIndex)
+
+        # uvs
+
+        for i in range(nUvLayers):
+
+            faceUv = None
+
+            if hasFaceUv:
+
+                uvLayer = data["uvs"][ i ]
+
+                uvIndex = faces[ offset ]
+                offset += 1
+
+                u = uvLayer[ uvIndex * 2 ]
+                v = uvLayer[ uvIndex * 2 + 1 ]
+
+                faceUv = [u, v]
+
+            result["faceUVs"][i].append(faceUv)
+
+
+            if hasFaceVertexUv:
+
+                uvLayer = data["uvs"][ i ]
+
+                vertexUvs = []
+
+                for j in range(nVertices):
+
+                    uvIndex = faces[ offset ]
+                    offset += 1
+
+                    u = uvLayer[ uvIndex * 2 ]
+                    v = uvLayer[ uvIndex * 2 + 1 ]
+
+                    vertexUvs.append([u, v])
+
+            result["vertexUVs"][i].append(vertexUvs)
+
+
+        if hasFaceNormal:
+
+            normalIndex = faces[ offset ] * 3
+            offset += 1
+
+            x = normals[ normalIndex ]
+            y = normals[ normalIndex + 1 ]
+            z = normals[ normalIndex + 2 ]
+
+            faceNormal = [x, y, z]
+
+        else:
+
+            faceNormal = None
+
+        result["faceNormals"].append(faceNormal)
+
+
+        if hasFaceVertexNormal:
+
+            vertexNormals = []
+
+            for j in range(nVertices):
+
+                normalIndex = faces[ offset ] * 3
+                offset += 1
+
+                x = normals[ normalIndex ]
+                y = normals[ normalIndex + 1 ]
+                z = normals[ normalIndex + 2 ]
+
+                vertexNormals.append( [x, y, z] )
+
+
+        else:
+
+            vertexNormals = None
+
+        result["vertexNormals"].append(vertexNormals)
+
+
+        if hasFaceColor:
+
+            colorIndex = faces[ offset ]
+            offset += 1
+
+            faceColor = hexToTuple( colors[ colorIndex ] )
+
+        else:
+
+            faceColor = None
+
+        result["faceColors"].append(faceColor)
+
+
+        if hasFaceVertexColor:
+
+            vertexColors = []
+
+            for j in range(nVertices):
+
+                colorIndex = faces[ offset ]
+                offset += 1
+
+                color = hexToTuple( colors[ colorIndex ] )
+                vertexColors.append( color )
+
+        else:
+
+            vertexColors = None
+
+        result["vertexColors"].append(vertexColors)
+
+
+    return result
+
+# #####################################################
+# Utils
+# #####################################################
+
+def hexToTuple( hexColor ):
+    r = (( hexColor >> 16 ) & 0xff) / 255.0
+    g = (( hexColor >> 8 ) & 0xff) / 255.0
+    b = ( hexColor & 0xff) / 255.0
+    return (r, g, b)
+
+def isBitSet(value, position):
+    return value & ( 1 << position )
+
+def splitArray(data, chunkSize):
+    result = []
+    chunk = []
+    for i in range(len(data)):
+        if i > 0 and i % chunkSize == 0:
+            result.append(chunk)
+            chunk = []
+        chunk.append(data[i])
+    result.append(chunk)
+    return result
+
+
+def extract_json_string(text):
+    marker_begin = "var model ="
+    marker_end = "postMessage"
+
+    start = text.find(marker_begin) + len(marker_begin)
+    end = text.find(marker_end)
+    end = text.rfind("}", start, end)
+    return text[start:end+1].strip()
+
+def get_name(filepath):
+    return os.path.splitext(os.path.basename(filepath))[0]
+
+def get_path(filepath):
+    return os.path.dirname(filepath)
+
+# #####################################################
+# Parser
+# #####################################################
+
+def load(operator, context, filepath, option_flip_yz = True, recalculate_normals = True, option_worker = False):
+
+    print('\nimporting %r' % filepath)
+
+    time_main = time.time()
+
+    print("\tparsing JSON file...")
+
+    time_sub = time.time()
+
+    file = open(filepath, 'rU')
+    rawcontent = file.read()
+    file.close()
+
+    if option_worker:
+        json_string = extract_json_string(rawcontent)
+    else:
+        json_string = rawcontent
+    data = json.loads( json_string )
+
+    time_new = time.time()
+
+    print('parsing %.4f sec' % (time_new - time_sub))
+
+    time_sub = time_new
+
+    # flip YZ
+
+    vertices = splitArray(data["vertices"], 3)
+
+    if option_flip_yz:
+        vertices[:] = [(v[0], -v[2], v[1]) for v in vertices]
+
+    # extract faces
+
+    face_data = extract_faces(data)
+
+    # deselect all
+
+    bpy.ops.object.select_all(action='DESELECT')
+
+    nfaces = len(face_data["faces"])
+    nvertices = len(vertices)
+    nnormals = len(data.get("normals", [])) / 3
+    ncolors = len(data.get("colors", [])) / 3
+    nuvs = len(data.get("uvs", [])) / 2
+    nmaterials = len(data.get("materials", []))
+
+    print('\tbuilding geometry...\n\tfaces:%i, vertices:%i, vertex normals: %i, vertex uvs: %i, vertex colors: %i, materials: %i ...' % (
+        nfaces, nvertices, nnormals, nuvs, ncolors, nmaterials ))
+
+    # Create materials
+
+    materials = create_materials(data, get_path(filepath))
+
+    # Create new obj
+
+    create_mesh_object(get_name(filepath), vertices, materials, face_data, option_flip_yz, recalculate_normals)
+
+    scene = bpy.context.scene
+    scene.update()
+
+    time_new = time.time()
+
+    print('finished importing: %r in %.4f sec.' % (filepath, (time_new - time_main)))
+    return {'FINISHED'}
+
+
+if __name__ == "__main__":
+    register()