Flipped materials after export to OBJ

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

This sounds similar to what @sguerrero was experiencing a few days ago, although she was using the Editor. I’m not certain if they are related, but you might want to check out @Drigax’s post here to see if that helps.

2 Likes

Have you had any luck with this? the easiest way to get support for an issue is to create a playground, so anyone else can take a look or hook up a debugger to the engine.

Thanks

Hi
No we wasn’t able to find any solution for that issue. And since I was lately focused on different project we decided to temporarily drop this functionality.

However I will try to find some time and create playground

Thanks

wt., 11 sie 2020, 18:16 użytkownik Nicholas Barlow via Babylon.js <babylonjs@discoursemail.com> napisał:

Hi

Hmm, OBJ files are using rightHandedSystem while default in Babylon (basically for every other format) is leftHandedSystem. So this might be the issue? Try using

scene.useRightHandedSystem = true;

2 Likes

Hi @Marcin_Zamelski, the solution is to reverse the scaling around the appropriate axis. In @tbunker and @sguerrero’s case we had to scale the root mesh/node with new BABYLON.Vector3(-1, 1, 1).

1 Like