function GameLib3d( Q, THREE, BlenderNode, config, apiUrl, editorUrl, scenes, worlds ) { this.Q = Q; this.THREE = THREE; this.BlenderNode = BlenderNode; this.editor = null; this.config = config; if (typeof apiUrl == 'undefined') { apiUrl = this.config.api16.url; } this.apiUrl = apiUrl; if (typeof editorUrl == 'undefined') { editorUrl = this.config.editor.url; } this.editorUrl = editorUrl; /** * I disabled MultiMaterial for now - since it has 2 issues so far * 1. Raycasting doesn't work for culled faces (https://github.com/mrdoob/three.js/issues/9694) * 2. Warnings in console - three js breaks when intersecting with faces for which uvs arent defined * @type {boolean} forceMultiMaterial */ this.forceMultiMaterial = false; this.textureLoader = new THREE.TextureLoader(); } /** * @param blenderTexture BlenderNode.Texture * @param threeMaterial * @param threeMaterialMapType * @returns {promise.promise|jQuery.promise|*|promise|Promise} */ GameLib3d.prototype.loadMap = function(blenderTexture, threeMaterial, threeMaterialMapType) { var q = this.Q.defer(); var imagePath = null; if (blenderTexture && blenderTexture.image && blenderTexture.image.apiPath && blenderTexture.image.filename) { /** * Load the image from API here if apiPath is defined */ imagePath = this.apiUrl + blenderTexture.image.uploadPath + '/' + blenderTexture.image.filename; } if (blenderTexture && blenderTexture.image && blenderTexture.image.uploadPath && blenderTexture.image.filename) { /** * Else, load from upload source */ imagePath = this.editorUrl + blenderTexture.image.uploadPath + '/' + blenderTexture.image.filename; } if (imagePath) { this.textureLoader.load( imagePath, function(texture) { /** * onLoad */ threeMaterial[threeMaterialMapType] = texture; threeMaterial[threeMaterialMapType].name = blenderTexture.name; threeMaterial[threeMaterialMapType].anisotropy = blenderTexture.anisotropy; threeMaterial[threeMaterialMapType].encoding = blenderTexture.encoding; threeMaterial[threeMaterialMapType].flipY = blenderTexture.flipY; // threeMaterial[threeMaterialMapType].format = blenderTexture.format; threeMaterial[threeMaterialMapType].generateMipmaps = blenderTexture.generateMipmaps; threeMaterial[threeMaterialMapType].magFilter = blenderTexture.magFilter; threeMaterial[threeMaterialMapType].minFilter = blenderTexture.minFilter; threeMaterial[threeMaterialMapType].mapping = blenderTexture.mapping; threeMaterial[threeMaterialMapType].mipmaps = blenderTexture.mipmaps; threeMaterial[threeMaterialMapType].offset = new THREE.Vector2( blenderTexture.offset.x, blenderTexture.offset.y ); threeMaterial[threeMaterialMapType].premultiplyAlpha = blenderTexture.premultiplyAlpha; threeMaterial[threeMaterialMapType].textureType = blenderTexture.textureType; threeMaterial[threeMaterialMapType].wrapS = blenderTexture.wrapS; threeMaterial[threeMaterialMapType].wrapT = blenderTexture.wrapT; threeMaterial[threeMaterialMapType].unpackAlignment = blenderTexture.unpackAlignment; threeMaterial.needsUpdate = true; q.resolve(true); }, function(xhr) { /** * onProgress */ if (this.editor) { this.editor.setServerStatus(Math.round(xhr.loaded / xhr.total * 100) + '% complete', 'success'); } }, function(error) { /** * onError */ console.log("an error occurred while trying to load the image : " + imagePath); q.resolve(null); } ); } else { q.resolve(null); } return q.promise; }; /** * Creates a THREE Mesh from BlenderNode.Mesh * @param mesh BlenderNode.Mesh * @param geometry THREE.Geometry * @param material THREE.Material */ GameLib3d.prototype.createThreeMesh = function(mesh, geometry, material) { var threeMesh = null; if (mesh.meshType == this.BlenderNode.Mesh.TYPE_NORMAL) { threeMesh = new this.THREE.Mesh(geometry, material); } if (mesh.meshType == this.BlenderNode.Mesh.TYPE_SKINNED) { var bones = mesh.skeleton.bones; var skinIndices = mesh.skinIndices; var skinWeights = mesh.skinWeights; var threeBones = []; for (var bi = 0; bi < bones.length; bi++) { var bone = new this.THREE.Bone(); bone.name = bones[bi].name; bone.position.x = bones[bi].position.x; bone.position.y = bones[bi].position.y; bone.position.z = bones[bi].position.z; bone.rotation.x = bones[bi].rotation.x; bone.rotation.y = bones[bi].rotation.y; bone.rotation.z = bones[bi].rotation.z; bone.quaternion.x = bones[bi].quaternion.x; bone.quaternion.y = bones[bi].quaternion.y; bone.quaternion.z = bones[bi].quaternion.z; bone.quaternion.w = bones[bi].quaternion.w; bone.scale.x = bones[bi].scale.x; bone.scale.y = bones[bi].scale.y; bone.scale.z = bones[bi].scale.z; bone.up.x = bones[bi].up.x; bone.up.y = bones[bi].up.y; bone.up.z = bones[bi].up.z; threeBones.push(bone); } /** * Setup the bone relationships */ for (var br = 0; br < bones.length; br++) { for (var cbi = 0; cbi < bones[br].childBoneIds.length; cbi++) { threeBones[br].add(threeBones[bones[br].childBoneIds[cbi]]); } } /** * Setup bones (indexes) */ for (var si = 0; si < skinIndices.length; si++) { geometry.skinIndices.push( new this.THREE.Vector4( skinIndices[si].x, skinIndices[si].y, skinIndices[si].z, skinIndices[si].w ) ); } /** * Setup bones (weights) */ for (var sw = 0; sw < skinWeights.length; sw++) { geometry.skinWeights.push( new this.THREE.Vector4( skinWeights[sw].x, skinWeights[sw].y, skinWeights[sw].z, skinWeights[sw].w ) ); } threeMesh = new this.THREE.SkinnedMesh(geometry, material); var skeleton = new this.THREE.Skeleton(threeBones); skeleton.useVertexTexture = mesh.skeleton.useVertexTexture; for (var i = 0; i < bones.length; i++) { if (bones[i].parentBoneId === null) { threeMesh.add(threeBones[i]); break; } } threeMesh.bind(skeleton); threeMesh.pose(); threeMesh.skeleton.skeletonHelper = new this.THREE.SkeletonHelper(threeMesh); threeMesh.skeleton.skeletonHelper.material.linewidth = 5; } if (threeMesh == null) { console.log('cannot handle meshes of type ' + mesh.meshType + ' yet.'); } mesh.threeMeshId = threeMesh.id; return threeMesh; }; GameLib3d.prototype.loadMaps = function(blenderMaterial, blenderMaps, threeMaterial) { var textureMaps = []; for (var ti = 0; ti < blenderMaps.length; ti++) { var map = blenderMaps[ti]; if (blenderMaterial.maps.hasOwnProperty(map)) { var blenderTexture = blenderMaterial.maps[map]; if ( blenderTexture && blenderTexture.image && blenderTexture.image.filename && blenderTexture.image.uploadPath ) { var threeMap = null; if (map == 'alpha') { threeMap = 'alhpaMap'; } if (map == 'ao') { threeMap = 'aoMap'; } if (map == 'bump') { threeMap = 'bumpMap'; } if (map == 'displacement') { threeMap = 'displacementMap'; } if (map == 'emissive') { threeMap = 'emissiveMap'; } if (map == 'environment') { threeMap = 'envMap'; } if (map == 'light') { threeMap = 'lightMap'; } if (map == 'specular') { threeMap = 'specularMap'; } if (map == 'diffuse') { threeMap = 'map'; } if (map == 'roughness') { threeMap = 'roughnessMap'; } if (map == 'metalness') { threeMap = 'metalnessMap'; } if (threeMap == null) { console.warn("unsupported map type : " + map); } textureMaps.push(this.loadMap(blenderMaterial.maps[map], threeMaterial, threeMap)); } } } return textureMaps; }; /** * Creates a this.THREE Material from a this.BlenderNode.Material * @param blenderMaterial this.BlenderNode.Material */ GameLib3d.prototype.createThreeMaterial = function(blenderMaterial) { var defer = this.Q.defer(); var threeMaterial = null; var blenderMaps = []; if (blenderMaterial.materialType == this.BlenderNode.Material.TYPE_MESH_STANDARD) { threeMaterial = new this.THREE.MeshStandardMaterial({ name: blenderMaterial.name, opacity: blenderMaterial.opacity, transparent: blenderMaterial.transparent, blending: blenderMaterial.blending, blendSrc: blenderMaterial.blendSrc, blendDst: blenderMaterial.blendDst, blendEquation: blenderMaterial.blendEquation, depthTest: blenderMaterial.depthTest, depthFunc: blenderMaterial.depthFunc, depthWrite: blenderMaterial.depthWrite, polygonOffset: blenderMaterial.polygonOffset, polygonOffsetFactor: blenderMaterial.polygonOffsetFactor, polygonOffsetUnits: blenderMaterial.polygonOffsetUnits, alphaTest: blenderMaterial.alphaTest, clippingPlanes: blenderMaterial.clippingPlanes, clipShadows: blenderMaterial.clipShadows, overdraw: blenderMaterial.overdraw, visible: blenderMaterial.visible, side: blenderMaterial.side, color: new this.THREE.Color( blenderMaterial.color.r, blenderMaterial.color.g, blenderMaterial.color.b ), roughness: blenderMaterial.roughness, metalness: blenderMaterial.metalness, lightMapIntensity: blenderMaterial.lightMapIntensity, aoMapIntensity: blenderMaterial.aoMapIntensity, emissive: new this.THREE.Color( blenderMaterial.emissive.r, blenderMaterial.emissive.g, blenderMaterial.emissive.b ), emissiveIntensity: blenderMaterial.emissiveIntensity, bumpScale: blenderMaterial.bumpScale, normalScale: blenderMaterial.normalScale, displacementScale: blenderMaterial.displacementScale, refractionRatio: blenderMaterial.refractionRatio, fog: blenderMaterial.fog, shading: blenderMaterial.shading, wireframe: blenderMaterial.wireframe, wireframeLinewidth: blenderMaterial.wireframeLineWidth, wireframeLinecap: blenderMaterial.wireframeLineCap, wireframeLinejoin: blenderMaterial.wireframeLineJoin, vertexColors: blenderMaterial.vertexColors, skinning: blenderMaterial.skinning, morphTargets: blenderMaterial.morphTargets, morphNormals: blenderMaterial.morphNormals }); blenderMaps.push( 'diffuse', 'light', 'ao', 'emissive', 'bump', 'normal', 'displacement', 'roughness', 'metalness', 'alpha', 'environment' ); } else if (blenderMaterial.materialType == this.BlenderNode.Material.TYPE_MESH_PHONG) { threeMaterial = new this.THREE.MeshPhongMaterial({ name: blenderMaterial.name, opacity: blenderMaterial.opacity, transparent: blenderMaterial.transparent, blending: blenderMaterial.blending, blendSrc: blenderMaterial.blendSrc, blendDst: blenderMaterial.blendDst, blendEquation: blenderMaterial.blendEquation, depthTest: blenderMaterial.depthTest, depthFunc: blenderMaterial.depthFunc, depthWrite: blenderMaterial.depthWrite, polygonOffset: blenderMaterial.polygonOffset, polygonOffsetFactor: blenderMaterial.polygonOffsetFactor, polygonOffsetUnits: blenderMaterial.polygonOffsetUnits, alphaTest: blenderMaterial.alphaTest, clippingPlanes: blenderMaterial.clippingPlanes, clipShadows: blenderMaterial.clipShadows, overdraw: blenderMaterial.overdraw, visible: blenderMaterial.visible, side: blenderMaterial.side, color: new this.THREE.Color( blenderMaterial.color.r, blenderMaterial.color.g, blenderMaterial.color.b ), specular: new this.THREE.Color( blenderMaterial.specular.r, blenderMaterial.specular.g, blenderMaterial.specular.b ), shininess: blenderMaterial.shininess, lightMapIntensity: blenderMaterial.lightMapIntensity, aoMapIntensity: blenderMaterial.aoMapIntensity, emissive: new this.THREE.Color( blenderMaterial.emissive.r, blenderMaterial.emissive.g, blenderMaterial.emissive.b ), emissiveIntensity: blenderMaterial.emissiveIntensity, bumpScale: blenderMaterial.bumpScale, normalScale: blenderMaterial.normalScale, displacementScale: blenderMaterial.displacementScale, combine: blenderMaterial.combine, refractionRatio: blenderMaterial.refractionRatio, fog: blenderMaterial.fog, shading: blenderMaterial.shading, wireframe: blenderMaterial.wireframe, wireframeLinewidth: blenderMaterial.wireframeLineWidth, wireframeLinecap: blenderMaterial.wireframeLineCap, wireframeLinejoin: blenderMaterial.wireframeLineJoin, vertexColors: blenderMaterial.vertexColors, skinning: blenderMaterial.skinning, morphTargets: blenderMaterial.morphTargets, morphNormals: blenderMaterial.morphNormals }); blenderMaps.push( 'diffuse', 'light', 'ao', 'emissive', 'bump', 'normal', 'displacement', 'specular', 'alpha', 'environment' ); } else { console.log("material type is not implemented yet: " + blenderMaterial.materialType + " - material indexes could be screwed up"); } if (blenderMaps.length > 0) { var textureMaps = this.loadMaps(blenderMaterial, blenderMaps, threeMaterial); Q.all(textureMaps).then( function(){ defer.resolve(threeMaterial); } ).catch( function(error){ console.log(error); defer.reject(error); } ) } else { defer.resolve(threeMaterial); } return defer.promise; }; /** * Loads a this.BlenderNode.Scene object into a ThreeJS Scene object * @param blenderScene this.BlenderNode.Scene * @param onLoaded callback when all meshes have loaded * @param computeNormals set to true to compute new face and vertex normals during load */ GameLib3d.prototype.loadScene = function(blenderScene, onLoaded, computeNormals) { console.log("loading scene " + blenderScene.name); var meshQ = []; for (var m = 0; m < blenderScene.meshes.length; m++) { var mesh = blenderScene.meshes[m]; console.log("loading mesh " + mesh.name); var geometry = new this.THREE.Geometry(); var vertices = mesh.vertices; var faces = mesh.faces; var faceVertexUvs = mesh.faceVertexUvs; var materials = mesh.materials; /** * Setup vertices */ for (var v = 0; v < vertices.length; v++) { geometry.vertices.push( new this.THREE.Vector3( vertices[v].position.x, vertices[v].position.y, vertices[v].position.z ) ) } /** * Setup faces */ for (var f = 0; f < faces.length; f++) { var face = new this.THREE.Face3( faces[f].v0, faces[f].v1, faces[f].v2, new this.THREE.Vector3( faces[f].normal.x, faces[f].normal.y, faces[f].normal.z ), new this.THREE.Color( faces[f].color.r, faces[f].color.g, faces[f].color.b ), faces[f].materialIndex ); face.vertexColors = [ new this.THREE.Color( faces[f].vertexColors[0].r, faces[f].vertexColors[0].g, faces[f].vertexColors[0].b ), new this.THREE.Color( faces[f].vertexColors[1].r, faces[f].vertexColors[1].g, faces[f].vertexColors[1].b ), new this.THREE.Color( faces[f].vertexColors[2].r, faces[f].vertexColors[2].g, faces[f].vertexColors[2].b ) ]; face.normal = new this.THREE.Vector3( faces[f].normal.x, faces[f].normal.y, faces[f].normal.z ); face.vertexNormals = [ new this.THREE.Vector3( faces[f].vertexNormals[0].x, faces[f].vertexNormals[0].y, faces[f].vertexNormals[0].z ), new this.THREE.Vector3( faces[f].vertexNormals[1].x, faces[f].vertexNormals[1].y, faces[f].vertexNormals[1].z ), new this.THREE.Vector3( faces[f].vertexNormals[2].x, faces[f].vertexNormals[2].y, faces[f].vertexNormals[2].z ) ]; geometry.faces.push(face); } geometry.faceVertexUvs = []; /** * Setup face UVs */ for (var fm = 0; fm < faceVertexUvs.length; fm++) { var faceMaterialVertexUvs = faceVertexUvs[fm]; geometry.faceVertexUvs[fm] = []; for (var fuv = 0; fuv < faceMaterialVertexUvs.length; fuv++) { geometry.faceVertexUvs[fm][fuv] = []; geometry.faceVertexUvs[fm][fuv].push( new this.THREE.Vector2( faceMaterialVertexUvs[fuv][0].x, faceMaterialVertexUvs[fuv][0].y ), new this.THREE.Vector2( faceMaterialVertexUvs[fuv][1].x, faceMaterialVertexUvs[fuv][1].y ), new this.THREE.Vector2( faceMaterialVertexUvs[fuv][2].x, faceMaterialVertexUvs[fuv][2].y ) ); } } /** * Re-calculate normals (if we have to) * @type {Array} */ if (computeNormals) { geometry.computeFaceNormals(); geometry.computeVertexNormals(); } var threeMaterialLoaders = []; /** * Setup materials */ for (var mi = 0; mi < materials.length; mi++) { threeMaterialLoaders.push(this.createThreeMaterial(materials[mi])); } var result = this.Q.all(threeMaterialLoaders).then( function(gl3d, mesh, geometry) { return function(materials) { console.log("loaded all materials maps (" + materials.length + ")"); var material = null; if (materials.length > 1 && gl3d.forceMultiMaterial) { material = new gl3d.THREE.MultiMaterial(materials); /** * If I don't set the side of the MultiMaterial to DoubleSide, raycasting breaks */ //material.side = THREE.DoubleSide; } else { material = materials[0]; } var threeMesh = gl3d.createThreeMesh(mesh, geometry, material); threeMesh.name = mesh.name; threeMesh.position.x = mesh.position.x; threeMesh.position.y = mesh.position.y; threeMesh.position.z = mesh.position.z; threeMesh.rotation.x = mesh.rotation.x; threeMesh.rotation.y = mesh.rotation.y; threeMesh.rotation.z = mesh.rotation.z; threeMesh.scale.x = mesh.scale.x; threeMesh.scale.y = mesh.scale.y; threeMesh.scale.z = mesh.scale.z; threeMesh.quaternion.x = mesh.quaternion.x; threeMesh.quaternion.y = mesh.quaternion.y; threeMesh.quaternion.z = mesh.quaternion.z; threeMesh.quaternion.w = mesh.quaternion.w; return threeMesh; }; }(this, mesh, geometry) ).catch(function(error){ console.log(error); }); meshQ.push(result); } this.Q.all(meshQ).then(function(threeMeshes){ console.log("all meshes have loaded"); if (typeof onLoaded != 'undefined') { var threeLights = []; for (var sli = 0; sli < blenderScene.lights.length; sli++) { var blenderLight = blenderScene.lights[sli]; var light = null; if (blenderLight.lightType == 'AmbientLight') { light = new THREE.AmbientLight(blenderLight.color, blenderLight.intensity); } if (blenderLight.lightType == 'DirectionalLight') { light = new THREE.DirectionalLight(blenderLight.color, blenderLight.intensity); } if (blenderLight.lightType == 'PointLight') { light = new THREE.PointLight(blenderLight.color, blenderLight.intensity); light.distance = blenderLight.distance; light.decay = blenderLight.decay; } if (blenderLight.lightType == 'SpotLight') { light = new THREE.SpotLight(blenderLight.color, blenderLight.intensity); light.distance = blenderLight.distance; light.angle = blenderLight.angle; light.penumbra = blenderLight.penumbra; light.decay = blenderLight.decay; } light.position.x = blenderLight.position.x; light.position.y = blenderLight.position.y; light.position.z = blenderLight.position.z; light.rotation.x = blenderLight.rotation.x; light.rotation.y = blenderLight.rotation.y; light.rotation.z = blenderLight.rotation.z; // light.scale.x = blenderLight.scale.x; // light.scale.y = blenderLight.scale.y; // light.scale.z = blenderLight.scale.z; if (light == null) { console.warn('Does not support lights of type :' + blenderLight.lightType + ', not imported'); } else { light.name = blenderLight.name; threeLights.push(light); } } onLoaded(threeMeshes, threeLights); } }).catch(function(error){ console.log(error); }); }; if (typeof module !== 'undefined') { module.exports = GameLib3d; }