From 0181ff245bf4c22623fd1ad5ab1c318a377d1edc Mon Sep 17 00:00:00 2001 From: "Theunis J. Botha" Date: Fri, 30 Sep 2016 12:56:58 +0200 Subject: [PATCH] game-lib --- game-lib.js | 757 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 757 insertions(+) create mode 100644 game-lib.js diff --git a/game-lib.js b/game-lib.js new file mode 100644 index 0000000..e8d4022 --- /dev/null +++ b/game-lib.js @@ -0,0 +1,757 @@ +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; +} \ No newline at end of file