hi
we are trying to migrate our App from using babylonjs version 4.0.3 to 4.1.0 (there was some changes with GLtf file that break it for us). We were mostly able to make most of it work but we still have some problems with exporting scene to OBJ.
Currently we are using custom OBJ exporter based on Babylon’s default one. Changes are mostly in MTL file. Standard workflow is that we load GLB/GLtf models, then user can change materials and at any moment it can be exported to OBJ with currently selected materials configuration. Example product with babylon 4.0.3.
Issue is that on exported obj file materials/textures are flipped (files seem to be ok). We tried to remove flipSide method call, reverse indices, normals, invert scale at MTL entries etc. but we wasn’t able to make it work properly.
Below is code of our exporter and also images how it should look like and how it looks now (It’s visible on Sixteen3 tag). If it is required I can try to create PG with our code but it’s quite big…
I will be thankful for any tip how we can to make it work.
export const exportOBJ = (mesh, materials, matlibname, globalposition) => {
const output = [“# Mimeeq.com exporter”];let v = 1;
if (materials) {
if (!matlibname) {
matlibname = ‘mat’;
}
output.push("mtllib " + matlibname + “.mtl”);
}mesh.forEach((item, index) => {
output.push("# mesh: " + item.name);item.flipFaces(true); const mName = item.name.replace(".", "_").replace("_exp", ""); output.push("g object" + index); output.push("o " + mName + "-" + index); // Uses the position of the item in the scene, to the file (this back to normal in the end) let lastMatrix = null; if (globalposition && item.position) { const newMatrix = Matrix.Translation(item.position.x, item.position.y, item.position.z); lastMatrix = Matrix.Translation(-(item.position.x), -(item.position.y), -(item.position.z)); item.bakeTransformIntoVertices(newMatrix); } const mat = item.material; if (materials && mat) { if (mat.subMaterials) { const subMaterial = mat.subMaterials; subMaterial.forEach(sm => { logger("EXP mtl name " + sm.name); output.push("usemtl " + removeSpaces(sm.id)); }) } else { logger("exp mtl name " + removeSpaces(mat.id)); output.push("usemtl " + removeSpaces(mat.id)); } } const g = item.geometry; if (!g) { Tools.Warn("No geometry is present on the mesh"); return; } const trunkVerts = g.getVerticesData(VertexBuffer.PositionKind); const trunkNormals = g.getVerticesData(VertexBuffer.NormalKind); const trunkUV = g.getVerticesData(VertexBuffer.UVKind); const trunkFaces = g.getIndices(); let curV = 0; if (!trunkVerts || !trunkFaces) { Tools.Warn("There are no position vertices or indices on the mesh!"); return; } for (let i = 0; i < trunkVerts.length; i += 3) { output.push("v " + trunkVerts[i] + " " + trunkVerts[i + 1] + " " + trunkVerts[i + 2]); curV += 1; } if (trunkNormals != null) { // IMPORTANT! recalculate normals VertexData.ComputeNormals(trunkVerts, g.getIndices(), trunkNormals); g.setVerticesData( VertexBuffer.NormalKind, trunkNormals ); for (let i = 0; i < trunkNormals.length; i += 3) { output.push("vn " + trunkNormals[i] + " " + trunkNormals[i + 1] + " " + trunkNormals[i + 2]); } } if (trunkUV != null) { for (let i = 0; i < trunkUV.length; i += 2) { output.push("vt " + trunkUV[i] + " " + trunkUV[i + 1]); } } for (let i = 0; i < trunkFaces.length; i += 3) { const indices = [String(trunkFaces[i + 2] + v), String(trunkFaces[i + 1] + v), String(trunkFaces[i] + v)]; const blanks = ["", "", ""]; const facePositions = indices; const faceUVs = trunkUV != null ? indices : blanks; const faceNormals = trunkNormals != null ? indices : blanks; output.push(`f ${facePositions[0]}/${faceUVs[0]}/${faceNormals[0]} ${facePositions[1]}/${faceUVs[1]}/${faceNormals[1]} ${facePositions[2]}/${faceUVs[2]}/${faceNormals[2]}`); } // back de previous matrix, to not change the original mesh in the scene if (globalposition && lastMatrix) { item.bakeTransformIntoVertices(lastMatrix); } v += curV
});
output.push(“# OBJ END”);return output.join(“\n”);
};const prepareMTL = (m, output) => {
if (!m) {
return;
}
const newmtl =newmtl ${removeSpaces(m.id)}
;
if (output.includes(newmtl)) {
return;
}output.push(newmtl);
output.push(" Ni 1.5000");
output.push(d ${m.alpha.toFixed(4)}
);
output.push(Tr ${m.alpha.toFixed(4)}
);
output.push(" illum 2");if (m.ambientColor) {
output.push(Ka ${m.ambientColor.r.toFixed(4)} ${m.ambientColor.g.toFixed(4)} ${m.ambientColor.b.toFixed(4)}
);
} else {
output.push(" Ka 0.0000 0.0000 0.0000");
}if (m.emissiveColor) {
output.push(Ke ${m.emissiveColor.r.toFixed(4)} ${m.emissiveColor.g.toFixed(4)} ${m.emissiveColor.b.toFixed(4)}
);
}if (m.albedoColor) {
output.push(Kd ${m.albedoColor.r.toFixed(4)} ${m.albedoColor.g.toFixed(4)} ${m.albedoColor.b.toFixed(4)}
);
} else {
output.push(" Kd 0.0000 0.0000 0.0000");
}if (m.metallic) {
output.push(Ks ${m.metallic.toFixed(4)} ${m.metallic.toFixed(4)} ${m.metallic.toFixed(4)}
);
} else {
output.push(" Ks 0.0000 0.0000 0.0000");
}if (m.roughness) {
output.push(Ns ${m.roughness.toFixed(4)}
);
} else {
output.push(" Ns 0.5000");
}const outputPath = " textures\";
if (m.albedoTexture) {
const scale =-s ${m.albedoTexture.uScale} ${m.albedoTexture.vScale} 1
;
const offset =-o ${m.albedoTexture.uOffset} ${m.albedoTexture.vOffset} 1
;
output.push(map_Kd${scale}${offset}${outputPath}${renameToPNG(getFileName(m.albedoTexture.name))}
);
}if (m.bumpTexture) {
const scale =-s ${m.bumpTexture.uScale} ${m.bumpTexture.vScale} 1
;
const offset =-o ${m.bumpTexture.uOffset} ${m.bumpTexture.vOffset} 1
;output.push(` map_Bump${scale}${offset} -bm ${m.bumpTexture.level.toFixed(4)}${outputPath}${renameToPNG(getFileName(m.bumpTexture.name))}`); output.push(` norm${scale}${offset} -bm ${m.bumpTexture.level.toFixed(4)}${outputPath}${renameToPNG(getFileName(m.bumpTexture.name))}`);
}
if (m.opacityTexture) {
const scale =-s ${m.opacityTexture.uScale} ${m.opacityTexture.vScale} 1
;
const offset =-o ${m.opacityTexture.uOffset} ${m.opacityTexture.vOffset} 1
;output.push(` map_d${scale}${offset}${outputPath}${renameToPNG(getFileName(m.opacityTexture.name))}`);
}
};export const exportMTL = (mesh) => {
const output = ;if (!Array.isArray(mesh)) {
return null;
}mesh.forEach(o => {
if(o.material) {
if (o.material.getClassName() === “MultiMaterial”) {
const {subMaterials} = o.material;subMaterials.forEach(sm => { prepareMTL(sm, output); }) } else { prepareMTL(o.material, output); } }
});
const text = output.join(“\n”);
return text;
};