FelipeBaltazar Posted April 6, 2017 Share Posted April 6, 2017 Hello! I need to export my objects to obj. I found an example that I could adapt. But I can not incorporate the position of the object in the scene. Any idea? note: I'm a beginner*** BABYLON.Mesh.prototype.exportOBJ = function() { var output=[]; output.push("mtllib savedFile.mtl"); for(var i2 = 0; i2< selectedItems.length; i2++) { output.push("g object"+i2); output.push("o object_"+i2); output.push("usemtl material"+i2); var g = window[selectedItem[i2]].geometry; console.log(selectedItem[i2]); trunkVerts = g.getVerticesData('position'); trunkNormals = g.getVerticesData('normal'); trunkUV = g.getVerticesData('uv'); trunkFaces = g.getIndices(); for(var i=0;i<trunkVerts.length;i+=3){ output.push("v "+trunkVerts[i]+" "+trunkVerts[i+1]+" "+trunkVerts[i+2]); } for(i=0;i<trunkNormals.length;i+=3){ output.push("vn "+trunkNormals[i]+" "+trunkNormals[i+1]+" "+trunkNormals[i+2]); } for(i=0;i<trunkUV.length;i+=2){ output.push("vt "+trunkUV[i]+" "+trunkUV[i+1]); } for(i=0;i<trunkFaces.length;i+=3) { output.push( "f "+(trunkFaces[i+2]+1)+"/"+(trunkFaces[i+2]+1)+"/"+(trunkFaces[i+2]+1)+ " "+(trunkFaces[i+1]+1)+"/"+(trunkFaces[i+1]+1)+"/"+(trunkFaces[i+1]+1)+ " "+(trunkFaces[i]+1)+"/"+(trunkFaces[i]+1)+"/"+(trunkFaces[i]+1) ); } } var text = output.join("\n"); var fileBlob; try{ fileBlob=new Blob([text]); }catch(e){ var blobBuilder=window.BlobBuilder || window.MozBlobBuilder || window.WebKitBlobBuilder; var bb=new blobBuilder(); bb.append([text]); fileBlob=bb.getBlob(); } var URL=window.URL || window.webkitURL; var link=URL.createObjectURL(fileBlob); return link; } Quote Link to comment Share on other sites More sharing options...
GameMonetize Posted April 6, 2017 Share Posted April 6, 2017 Hey did you see this: https://github.com/BabylonJS/Babylon.js/tree/master/dist/preview release/serializers ? We already provide an exporter for obj jschwuch 1 Quote Link to comment Share on other sites More sharing options...
FelipeBaltazar Posted April 12, 2017 Author Share Posted April 12, 2017 Hi! Thanks for reply! I used this code to inspire me, but the world position is wrong. how can i incorporate this? using matrix maybe? Quote Link to comment Share on other sites More sharing options...
BitOfGold Posted April 13, 2017 Share Posted April 13, 2017 OBJ always contain world coordinates. The obj exporter does not take into account the position of the mesh. You can bake a translation into vertices position like this: var matrix = BABYLON.Matrix.Translation(mesh.position.x,mesh.position.y,mesh.position.z); mesh.bakeTransformIntoVertices(matrix); FelipeBaltazar 1 Quote Link to comment Share on other sites More sharing options...
FelipeBaltazar Posted April 13, 2017 Author Share Posted April 13, 2017 Worked fine! This is what I needed. Thanks! Quote Link to comment Share on other sites More sharing options...
jschwuch Posted April 13, 2017 Share Posted April 13, 2017 @Deltakosh is this documented anywhere? And can I export a whole scene to .obj or another format so I can get the my scene from babylon to Blender? This would be great! Quote Link to comment Share on other sites More sharing options...
GameMonetize Posted April 13, 2017 Share Posted April 13, 2017 Unfortunately, obj are only meshes (so no light, no camera, etc..) Quote Link to comment Share on other sites More sharing options...
jschwuch Posted April 18, 2017 Share Posted April 18, 2017 But I could export all meshes of my scene to one obj file without merging them? That would be great for rendering my scenes in blender and giving the renders to customers. Quote Link to comment Share on other sites More sharing options...
GameMonetize Posted April 18, 2017 Share Posted April 18, 2017 I don't know to be fair But you can try if this is supported by obj file format (I guess it is not but it is worth trying) Quote Link to comment Share on other sites More sharing options...
BitOfGold Posted April 18, 2017 Share Posted April 18, 2017 I think it is possiblehttps://www.gamedev.net/topic/563768-multiple-objects-in-a-obj/ Quote Link to comment Share on other sites More sharing options...
FelipeBaltazar Posted April 18, 2017 Author Share Posted April 18, 2017 I did this in my example. "Selecteditems" is an array of meshes. But I still have problems with the indexes of the faces. On 06/04/2017 at 10:55 AM, FelipeBaltazar said: Hello! I need to export my objects to obj. I found an example that I could adapt. But I can not incorporate the position of the object in the scene. Any idea? note: I'm a beginner*** BABYLON.Mesh.prototype.exportOBJ = function() { var output=[]; output.push("mtllib savedFile.mtl"); for(var i2 = 0; i2< selectedItems.length; i2++) { output.push("g object"+i2); output.push("o object_"+i2); output.push("usemtl material"+i2); var g = selectedItem[i2].geometry; trunkVerts = g.getVerticesData('position'); trunkNormals = g.getVerticesData('normal'); trunkUV = g.getVerticesData('uv'); trunkFaces = g.getIndices(); for(var i=0;i<trunkVerts.length;i+=3){ output.push("v "+trunkVerts[i]+" "+trunkVerts[i+1]+" "+trunkVerts[i+2]); } for(i=0;i<trunkNormals.length;i+=3){ output.push("vn "+trunkNormals[i]+" "+trunkNormals[i+1]+" "+trunkNormals[i+2]); } for(i=0;i<trunkUV.length;i+=2){ output.push("vt "+trunkUV[i]+" "+trunkUV[i+1]); } for(i=0;i<trunkFaces.length;i+=3) { output.push( "f "+(trunkFaces[i+2]+1)+"/"+(trunkFaces[i+2]+1)+"/"+(trunkFaces[i+2]+1)+ " "+(trunkFaces[i+1]+1)+"/"+(trunkFaces[i+1]+1)+"/"+(trunkFaces[i+1]+1)+ " "+(trunkFaces[i]+1)+"/"+(trunkFaces[i]+1)+"/"+(trunkFaces[i]+1) ); } } var text = output.join("\n"); var fileBlob; try{ fileBlob=new Blob([text]); }catch(e){ var blobBuilder=window.BlobBuilder || window.MozBlobBuilder || window.WebKitBlobBuilder; var bb=new blobBuilder(); bb.append([text]); fileBlob=bb.getBlob(); } var URL=window.URL || window.webkitURL; var link=URL.createObjectURL(fileBlob); return link; } jschwuch 1 Quote Link to comment Share on other sites More sharing options...
jschwuch Posted April 19, 2017 Share Posted April 19, 2017 Thx! @FelipeBaltazar, this was a great point to start at. I changed some things because I'm abusing BJS when it comes to worldMatrices (I'm setting them directly) so this code exports as if transform where baked into the vertices. I also fixed the problems with the indices. Problem was, that obj doesn't start counting from 1 again when a new object begins, so we have to keep track of how many vertices we already exported. Here's my version: function exportOBJ(selectedItems) { var output = []; output.push("mtllib savedFile.mtl"); var v = 1; for (var j = 0; j < selectedItems.length; j++) { if (selectedItems[j].isEnabled() && selectedItems[j].material && selectedItems[j].material.alpha && selectedItems[j].visibility) { output.push("g object" + j); output.push("o object_" + j); output.push("usemtl " + selectedItems[j].material.id); var g = selectedItems[j].geometry; var rotM = selectedItems[j]._worldMatrix.clone(); rotM.m[12] = 0; rotM.m[13] = 0; rotM.m[14] = 0; trunkVerts = g.getVerticesData('position'); trunkNormals = g.getVerticesData('normal'); trunkUV = g.getVerticesData('uv'); trunkFaces = g.getIndices(); var curV = 0; for (var i = 0; i < trunkVerts.length; i += 3) { var vert = new V3(trunkVerts[i], trunkVerts[i + 1], trunkVerts[i + 2]); vert = V3.TransformCoordinates(vert, selectedItems[j]._worldMatrix); output.push("v " + vert.x + " " + vert.y + " " + vert.z); curV++; } if (trunkNormals) { for (var i = 0; i < trunkNormals.length; i += 3) { var vert = new V3(trunkNormals[i], trunkNormals[i + 1], trunkNormals[i + 2]); vert = V3.TransformCoordinates(vert, rotM); output.push("vn " + vert.x + " " + vert.y + " " + vert.z); } } if (trunkUV) { for (var i = 0; i < trunkUV.length; i += 2) { output.push("vt " + trunkUV[i] + " " + trunkUV[i + 1]); } } for (var i = 0; i < trunkFaces.length; i += 3) { if (!(trunkFaces[i] == undefined) && !(trunkFaces[i + 1] == undefined) && !(trunkFaces[i + 2] == undefined)) { output.push( "f " + (parseInt(trunkFaces[i]) + v) + " " + (parseInt(trunkFaces[i + 1]) + v) + " " + (parseInt(trunkFaces[i + 2]) + v) ); } } v += curV; } } var text = output.join("\n"); var fileBlob; try { fileBlob = new Blob([text]); } catch (e) { var blobBuilder = window.BlobBuilder || window.MozBlobBuilder || window.WebKitBlobBuilder; var bb = new blobBuilder(); bb.append([text]); fileBlob = bb.getBlob(); } var URL = window.URL || window.webkitURL; var link = URL.createObjectURL(fileBlob); return link; } Oh, and I'm only exporting visible meshes with material and material.alpha != 0 because I need this. Big thx to everyone for pointing me in the right direction FelipeBaltazar 1 Quote Link to comment Share on other sites More sharing options...
FelipeBaltazar Posted April 20, 2017 Author Share Posted April 20, 2017 @jschwuch great! Dammit! I forget that detail on indices loop. My bad! You applied the global position with mesh matrix.... What is the difference of your example and the "bake" of babylon? var matrix = BABYLON.Matrix.Translation(mesh.position.x,mesh.position.y,mesh.position.z);mesh.bakeTransformIntoVertices(matrix); Thx Quote Link to comment Share on other sites More sharing options...
jschwuch Posted April 20, 2017 Share Posted April 20, 2017 Hi! The bake of bjs applies the transformations you set to the vertices. So you change the meshes. Additionally the baking seems to update the worldMatrix from the rotations and translations set on the mesh. But because i never set rotation and translation in my project but the world Matrix directly the baking doesn't work for me. If you're ok with changing your meshes you can instead use the baking. It just doesn't work out for me Quote Link to comment Share on other sites More sharing options...
FelipeBaltazar Posted April 20, 2017 Author Share Posted April 20, 2017 @jschwuch Woa! That's great! This was exactly what I needed! I'll work on the materials now! The "babylon.objSerializer.js" already has this, but not for more than one mesh. Thank you very much for the explanations! part of code: //Exports the material(s) of a mesh in .MTL file format (text) OBJExport.MTL = function (mesh) { var output = []; var m = mesh.material; output.push("newmtl mat1"); output.push(" Ns " + m.specularPower.toFixed(4)); output.push(" Ni 1.5000"); output.push(" d " + m.alpha.toFixed(4)); output.push(" Tr 0.0000"); output.push(" Tf 1.0000 1.0000 1.0000"); output.push(" illum 2"); output.push(" Ka " + m.ambientColor.r.toFixed(4) + " " + m.ambientColor.g.toFixed(4) + " " + m.ambientColor.b.toFixed(4)); output.push(" Kd " + m.diffuseColor.r.toFixed(4) + " " + m.diffuseColor.g.toFixed(4) + " " + m.diffuseColor.b.toFixed(4)); output.push(" Ks " + m.specularColor.r.toFixed(4) + " " + m.specularColor.g.toFixed(4) + " " + m.specularColor.b.toFixed(4)); output.push(" Ke " + m.emissiveColor.r.toFixed(4) + " " + m.emissiveColor.g.toFixed(4) + " " + m.emissiveColor.b.toFixed(4)); //TODO: uv scale, offset, wrap //TODO: UV mirrored in Blender? second UV channel? lightMap? reflection textures? var uvscale = ""; if (m.ambientTexture) { output.push(" map_Ka " + uvscale + m.ambientTexture.name); } if (m.diffuseTexture) { output.push(" map_Kd " + uvscale + m.diffuseTexture.name); //TODO: alpha testing, opacity in diffuse texture alpha channel (diffuseTexture.hasAlpha -> map_d) } if (m.specularTexture) { output.push(" map_Ks " + uvscale + m.specularTexture.name); /* TODO: glossiness = specular highlight component is in alpha channel of specularTexture. (???) if (m.useGlossinessFromSpecularMapAlpha) { output.push(" map_Ns "+uvscale + m.specularTexture.name); } */ } /* TODO: emissive texture not in .MAT format (???) if (m.emissiveTexture) { output.push(" map_d "+uvscale+m.emissiveTexture.name); } */ if (m.bumpTexture) { output.push(" map_bump -imfchan z " + uvscale + m.bumpTexture.name); } if (m.opacityTexture) { output.push(" map_d " + uvscale + m.opacityTexture.name); } var text = output.join("\n"); return (text); }; Quote Link to comment Share on other sites More sharing options...
GameMonetize Posted April 20, 2017 Share Posted April 20, 2017 Do you want to contribute it back to the repo? Like here: https://github.com/BabylonJS/Babylon.js/tree/master/serializers/src/OBJ FelipeBaltazar 1 Quote Link to comment Share on other sites More sharing options...
FelipeBaltazar Posted April 24, 2017 Author Share Posted April 24, 2017 @Deltakosh Done! But, i have not solved the materials yet https://github.com/felipebaltazar/Babylon.js/blob/b683beced1e42b907d67581d9e0b916a4d01b969/serializers/src/OBJ/babylon.objSerializer.ts Quote Link to comment Share on other sites More sharing options...
GameMonetize Posted April 24, 2017 Share Posted April 24, 2017 We need to cleanup code and tabs a bit FelipeBaltazar 1 Quote Link to comment Share on other sites More sharing options...
jschwuch Posted April 25, 2017 Share Posted April 25, 2017 Hi @FelipeBaltazar, I really think we shouldn't modify the meshes when exporting the scene as I don't think people expect this to happen. Maybe we could add a flag if the function should bake the vertices or calculate the coordinates from worldMatrix defaulting to calculating the coordinates. I would be highly irritated if my meshes change when exporting a scene and it could cause major problems for people who build their meshes from scratch. FelipeBaltazar 1 Quote Link to comment Share on other sites More sharing options...
FelipeBaltazar Posted April 25, 2017 Author Share Posted April 25, 2017 @Deltakosh sorry... im a complete newbie in development (in english too... sorry again) https://github.com/felipebaltazar/Babylon.js/blob/8efbe08b024f13017330ade4e993ff325e860caf/serializers/src/OBJ/babylon.objSerializer.ts @jschwuch You are absolutely right! I made this option, "selectable". globalposition define this. public static OBJ(mesh: Mesh[], materials?: boolean, matlibname?: string, globalposition?:boolean) In your scene, yours meshes will remain normal. Because this: var lastMatrix = BABYLON.Matrix.Translation(-(mesh[j].position.x),-(mesh[j].position.y),-(mesh[j].position.z)); I bring back de original matrix... if(globalposition){ mesh.bakeTransformIntoVertices(lastMatrix); } thks everyone Quote Link to comment Share on other sites More sharing options...
damian2taylor Posted April 26, 2017 Share Posted April 26, 2017 Hey guys, I've been working on a .obj file exporter for a few weeks, a while after getting started with BabylonJS at my new company. I took inspiration from the current babylon.objSerializer.js file, but I haven't focused on materials for now. My version also works on a full scene (that contains an array of meshes), not on a single mesh! Actually, I am trying to develop a robust serializer that could adapt to any kind of .obj file, as its structure is not always correctly followed by graphic designers. @FelipeBaltazar, I corrected your code here: OBJExport.OBJ = function (scene, materials, matlibname) { var output = []; var sceneMeshes = scene.meshes; var v = 1; if (materials) { if (!matlibname) { matlibname = 'mat'; } output.push("mtllib " + matlibname + ".mtl"); } // Loop on each mesh of meshes array and extract their info for (var j = 0; j < sceneMeshes.length; j++) { var m = sceneMeshes[j]; // Go to next lap if the subMesh is not defined if (m.subMeshes == undefined) continue; output.push("g gr" + j); if (materials) { output.push("usemtl " + matlibname); } // Translate current mesh so that its global position is taken into account when exported var newMatrix = BABYLON.Matrix.Translation(m.position.x, m.position.y, m.position.z); m.bakeTransformIntoVertices(newMatrix); // CAUTION: this function doesn't work (no error) if meshes[j].subMeshes is undefined! var meshPositions = m.getVerticesData('position'); var meshNormals = m.getVerticesData('normal'); var meshUvs = m.getVerticesData('uv'); var meshIndices = m.getIndices(); curV = 0; // The mesh is reset to its initial position for avoiding problems var lastMatrix = BABYLON.Matrix.Translation(-(m.position.x), -(m.position.y), -(m.position.z)); m.bakeTransformIntoVertices(lastMatrix); //TODO: submeshes (groups) //TODO: smoothing groups (s 1, s off); // Vertices position, UV coordinates and normal coordinates (of every mesh) writing for (var i = 0; i < meshPositions.length; i += 3) { output.push("v " + meshPositions[i] + " " + meshPositions[i + 1] + " " + meshPositions[i + 2]); curV++; } for (var i = 0; i < meshNormals.length; i += 3) { output.push("vn " + meshNormals[i] + " " + meshNormals[i + 1] + " " + meshNormals[i + 2]); } for (var i = 0; i < meshUvs.length; i += 2) { output.push("vt " + meshUvs[i] + " " + meshUvs[i + 1]); } for (var i = 0; i < meshIndices.length; i += 3) { output.push( "f " + (meshIndices[i + 2] + v) + "/" + (meshIndices[i + 2] + v) + "/" + (meshIndices[i + 2] + v) + " " + (meshIndices[i + 1] + v) + "/" + (meshIndices[i + 1] + v) + "/" + (meshIndices[i + 1] + v) + " " + (meshIndices[i] + v) + "/" + (meshIndices[i] + v) + "/" + (meshIndices[i] + v) ); } v += curV; } var text = output.join("\n"); return text; } We can do without the g geometry: the vertices data can be accessed directly from the current mesh! You should also check the structure of a .obj file, it is pretty well detailed here: https://en.wikipedia.org/wiki/Wavefront_.obj_file. Normally, there is only one object (o) in a single file. The groups (g) shall be declared after v, vt and vn declarations. They are actually groups of faces (f), which are usually separated when they are covered in different materials (usemtl). Actually, I'm working right now on an optimized version that takes into account the .obj format specification, specifying that vertices shall be declared only once, even if they appear more than once (in multiple faces or even groups of faces for instance) so that the final .obj file is lighter. I should post it quite soon, I'm trying to optimize it right now because it's processing very long arrays, so the loading is much slower! Finally, I'm facing an issue with a .obj file imported with BABYLON.SceneLoader.ImportMesh() function. I don't know why, but its first mesh is weird and has no subMesh defined (by default, a mesh's subMesh is defined as the mesh itself). That causes no error in my case but corrupts the exported .obj file when using bakeTransformIntoVertices() function. The problem is precisely located in babylon.max.js, in Mesh.prototype.bakeTransformIntoVertices = function (transform) (l. 19620), at var submeshes = this.subMeshes.splice(0); declaration. Please don't hesitate to comment, improve and integrate my solution. I'd like to contribute on GitHub but I'm kinda new to it. My ID is the same as in this forum! Thanks PS: @Deltakosh, shouldn't we write getVerticesData.PositionKind instead of getVerticesData('position')? EDIT: I'm sorry, I just realized the importance of v and curV variables! I don't really understand their use, could you help me on this? Commenting the code could be useful. Thanks! Quote Link to comment Share on other sites More sharing options...
GameMonetize Posted April 27, 2017 Share Posted April 27, 2017 Clearly! Quote Link to comment Share on other sites More sharing options...
FelipeBaltazar Posted April 27, 2017 Author Share Posted April 27, 2017 @damian2taylor This is fantastic! Are great points, congratulations! Please, if you finish part of materials, tell me plz! I'm working on it too. Quote Link to comment Share on other sites More sharing options...
damian2taylor Posted April 28, 2017 Share Posted April 28, 2017 Hey, I've been working on materials, I've corrected some major bugs, even if there is still much to be done: OBJExport.MTL = function (scene) { var sceneMeshes = scene.meshes; var output = []; // Loop on each mesh of sceneMeshes array and extract their info for (var j = 0; j < sceneMeshes.length; j++) { var mesh = sceneMeshes[j]; if (!mesh.subMeshes) { continue; } if (!mesh.material) { //console.warn("Mesh n°" + j + " has no material!"); continue; } var m = mesh.material; // If the current material is not written yet, add it if (output.indexOf("newmtl " + m.name) == -1) // Is m.name enough to distinguish a mesh from another? { output.push("newmtl " + m.name); output.push(" Ns " + parseFloat(m.specularPower).toFixed(4)); output.push(" Ni 1.5000"); output.push(" d " + parseFloat(m.alpha).toFixed(4)); output.push(" Tr 0.0000"); output.push(" Tf 1.0000 1.0000 1.0000"); output.push(" illum 2"); output.push(" Ka " + parseFloat(m.ambientColor.r).toFixed(4) + " " + parseFloat(m.ambientColor.g).toFixed(4) + " " + parseFloat(m.ambientColor.b).toFixed(4)); output.push(" Kd " + parseFloat(m.diffuseColor.r).toFixed(4) + " " + parseFloat(m.diffuseColor.g).toFixed(4) + " " + parseFloat(m.diffuseColor.b).toFixed(4)); output.push(" Ks " + parseFloat(m.specularColor.r).toFixed(4) + " " + parseFloat(m.specularColor.g).toFixed(4) + " " + parseFloat(m.specularColor.b).toFixed(4)); output.push(" Ke " + parseFloat(m.emissiveColor.r).toFixed(4) + " " + parseFloat(m.emissiveColor.g).toFixed(4) + " " + parseFloat(m.emissiveColor.b).toFixed(4)); //TODO: uv scale, offset, wrap //TODO: UV mirrored in Blender? second UV channel? lightMap? reflection textures? var uvscale = ""; if (m.ambientTexture) { var filename = m.ambientTexture.name.slice(m.ambientTexture.name.lastIndexOf('/') + 1); output.push(" map_Ka " + uvscale + filename); } if (m.diffuseTexture) { var filename = m.diffuseTexture.name.slice(m.diffuseTexture.name.lastIndexOf('/') + 1); output.push(" map_Kd " + uvscale + filename); //TODO: alpha testing, opacity in diffuse texture alpha channel (diffuseTexture.hasAlpha -> map_d) } if (m.specularTexture) { var filename = m.specularTexture.name.slice(m.specularTexture.name.lastIndexOf('/') + 1); output.push(" map_Ks " + uvscale + filename); /* TODO: glossiness = specular highlight component is in alpha channel of specularTexture. (???) if (m.useGlossinessFromSpecularMapAlpha) { output.push(" map_Ns "+uvscale + filename); } */ } /* TODO: emissive texture not in .MAT format (???) if (m.emissiveTexture) { output.push(" map_d "+uvscale+m.emissiveTexture.name); } */ if (m.bumpTexture) { var filename = m.bumpTexture.name.slice(m.bumpTexture.name.lastIndexOf('/') + 1); output.push(" map_bump -imfchan z " + uvscale + filename); } if (m.opacityTexture) { var filename = m.opacityTexture.name.slice(m.opacityTexture.name.lastIndexOf('/') + 1); output.push(" map_d " + uvscale + filename); } output.push("\n"); } } var text = output.join("\n"); return text; }; I also did it for a whole scene in which there are meshes. However, multiple materials can actually be used in a single mesh, meaning that multiple 'usemtl' instructions can be used in a single group (g). I guess this is where subMeshes and even multi materials come up... It's working pretty well but not for all .obj files. I think it is because of the .obj loader (babylon.objFileLoader.js) that is not working robust enough to support weakly designed .obj files to import. It dosn't handle negative face indices for instance. Again, don't hesitate to comment and improve my solution, I'm not very experienced with BabylonJS (for now!). FelipeBaltazar 1 Quote Link to comment Share on other sites More sharing options...
FelipeBaltazar Posted April 29, 2017 Author Share Posted April 29, 2017 wow! Thks! Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.