FelipeBaltazar

Babylon objects to .obj file

Recommended Posts

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;
}


 

Share this post


Link to post
Share on other sites

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);
 

Share this post


Link to post
Share on other sites

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;
}


 

 

Share this post


Link to post
Share on other sites

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 :)

Share this post


Link to post
Share on other sites

@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

 

Share this post


Link to post
Share on other sites

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 ;)

 

 

Share this post


Link to post
Share on other sites

@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);
        };

 

Share this post


Link to post
Share on other sites

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. ;)

Share this post


Link to post
Share on other sites

@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

Share this post


Link to post
Share on other sites

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'):P

 

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!

Share this post


Link to post
Share on other sites

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!).

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.