Skip to content

Instantly share code, notes, and snippets.

@jcarletto27
Last active September 25, 2023 16:34
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jcarletto27/35b19f811c657ec73b0238d78413f88d to your computer and use it in GitHub Desktop.
Save jcarletto27/35b19f811c657ec73b0238d78413f88d to your computer and use it in GitHub Desktop.
Three.js OBJExporter, updated to work with bufferGeometry. Adds support for bones and morph data.
THREE.OBJExporter = function () {};
THREE.OBJExporter.prototype = {
constructor: THREE.OBJExporter,
parse: function (object) {
var output = '';
var indexVertex = 0;
var indexVertexUvs = 0;
var indexNormals = 0;
var vertex = new THREE.Vector3();
var normal = new THREE.Vector3();
var uv = new THREE.Vector2();
var i,
j,
l,
m,
face = [];
var parseMesh = function (mesh) {
var nbVertex = 0;
var nbNormals = 0;
var nbVertexUvs = 0;
var geometry = mesh.geometry;
var normalMatrixWorld = new THREE.Matrix3();
if (geometry instanceof THREE.Geometry) {
geometry = new THREE.BufferGeometry().setFromObject(mesh);
}
if (geometry instanceof THREE.BufferGeometry) {
var vertices = geometry.getAttribute('position');
var normals = geometry.getAttribute('normal');
var uvs = geometry.getAttribute('uv');
var indices = geometry.getIndex();
var skinIndices = geometry.getAttribute('skinIndex');
var weights = geometry.getAttribute('skinWeight');
var morphVector;
// name of the mesh object
output += 'o ' + mesh.name + '\n';
// vertices
if (vertices !== undefined) {
for (i = 0, l = vertices.count; i < l; i++, nbVertex++) {
if (skinIndices == undefined) {
vertex.x = vertices.getX(i);
vertex.y = vertices.getY(i);
vertex.z = vertices.getZ(i);
//vertex.applyMatrix4(mesh.matrixWorld);
output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n';
} else {
vertex.x = vertices.getX(i);
vertex.y = vertices.getY(i);
vertex.z = vertices.getZ(i);
//vertex.applyMatrix4(mesh.matrixWorld);
skinIndex = [];
skinIndex[0] = skinIndices.getX(i);
skinIndex[1] = skinIndices.getY(i);
skinIndex[2] = skinIndices.getZ(i);
skinIndex[3] = skinIndices.getW(i);
skinWeight = [];
skinWeight[0] = weights.getX(i);
skinWeight[1] = weights.getY(i);
skinWeight[2] = weights.getZ(i);
skinWeight[3] = weights.getW(i);
inverses = [];
inverses[0] = mesh.skeleton.boneInverses[skinIndex[0]];
inverses[1] = mesh.skeleton.boneInverses[skinIndex[1]];
inverses[2] = mesh.skeleton.boneInverses[skinIndex[2]];
inverses[3] = mesh.skeleton.boneInverses[skinIndex[3]];
skinMatrices = [];
skinMatrices[0] = mesh.skeleton.bones[skinIndex[0]].matrixWorld;
skinMatrices[1] = mesh.skeleton.bones[skinIndex[1]].matrixWorld;
skinMatrices[2] = mesh.skeleton.bones[skinIndex[2]].matrixWorld;
skinMatrices[3] = mesh.skeleton.bones[skinIndex[3]].matrixWorld;
var finalVector = new THREE.Vector4();
if (geometry.morphTargetInfluences !== undefined) {
var morphAttributes = geometry.morphAttributes.position;
//console.log("Mesh Name:" + mesh.name);
morphMatricesX = [];
morphMatricesY = [];
morphMatricesZ = [];
morphMatricesInfluence = [];
var morphLength = geometry.morphAttributes.position.length;
//console.log("Morph Length " + morphLength);
for (var mt = 0; mt < morphLength; mt++) {
//collect the needed vertex info
morphMatricesX[mt] = morphAttributes[mt].getX(i);
morphMatricesY[mt] = morphAttributes[mt].getY(i);
morphMatricesZ[mt] = morphAttributes[mt].getZ(i);
morphMatricesInfluence[mt] = mesh.morphTargetInfluences[mt];
}
}
if (geometry.morphTargetInfluences !== undefined) {
morphVector = new THREE.Vector4(vertex.x, vertex.y, vertex.z);
var morphLength = geometry.morphAttributes.position.length;
for (var mt = 0; mt < morphLength; mt++) {
//not pretty, but it gets the job done
morphVector.lerp(new THREE.Vector4(morphMatricesX[mt], morphMatricesY[mt], morphMatricesZ[mt], 1), morphMatricesInfluence[mt]);
}
}
for (var k = 0; k < 4; k++) {
if (geometry.morphTargetInfluences !== undefined) {
var tempVector = new THREE.Vector4(morphVector.x, morphVector.y, morphVector.z);
} else {
var tempVector = new THREE.Vector4(vertex.x, vertex.y, vertex.z);
}
tempVector.multiplyScalar(skinWeight[k]);
//the inverse takes the vector into local bone space
//which is then transformed to the appropriate world space
tempVector.applyMatrix4(inverses[k]).applyMatrix4(skinMatrices[k]);
finalVector.add(tempVector);
}
// transform the vertex to export format
output += 'v ' + finalVector.x + ' ' + finalVector.y + ' ' + finalVector.z + '\n';
}
}
}
// uvs
if (uvs !== undefined) {
for (i = 0, l = uvs.count; i < l; i++, nbVertexUvs++) {
uv.x = uvs.getX(i);
uv.y = uvs.getY(i);
// transform the uv to export format
output += 'vt ' + uv.x + ' ' + uv.y + '\n';
}
}
// normals
if (normals !== undefined) {
normalMatrixWorld.getNormalMatrix(mesh.matrixWorld);
for (i = 0, l = normals.count; i < l; i++, nbNormals++) {
normal.x = normals.getX(i);
normal.y = normals.getY(i);
normal.z = normals.getZ(i);
// transfrom the normal to world space
normal.applyMatrix3(normalMatrixWorld);
// transform the normal to export format
output += 'vn ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n';
}
}
// faces
if (indices !== null) {
for (i = 0, l = indices.count; i < l; i += 3) {
for (m = 0; m < 3; m++) {
j = indices.getX(i + m) + 1;
face[m] = (indexVertex + j) + '/' + (uvs ? (indexVertexUvs + j) : '') + '/' + (indexNormals + j);
}
// transform the face to export format
output += 'f ' + face.join(' ') + "\n";
}
} else {
for (i = 0, l = vertices.count; i < l; i += 3) {
for (m = 0; m < 3; m++) {
j = i + m + 1;
face[m] = (indexVertex + j) + '/' + (uvs ? (indexVertexUvs + j) : '') + '/' + (indexNormals + j);
}
// transform the face to export format
output += 'f ' + face.join(' ') + "\n";
}
}
} else {
console.warn('THREE.OBJExporter.parseMesh(): geometry type unsupported', geometry);
}
// update index
indexVertex += nbVertex;
indexVertexUvs += nbVertexUvs;
indexNormals += nbNormals;
};
var parseLine = function (line) {
var nbVertex = 0;
var geometry = line.geometry;
var type = line.type;
if (geometry instanceof THREE.Geometry) {
geometry = new THREE.BufferGeometry().setFromObject(line);
}
if (geometry instanceof THREE.BufferGeometry) {
// shortcuts
var vertices = geometry.getAttribute('position');
var indices = geometry.getIndex();
// name of the line object
output += 'o ' + line.name + '\n';
if (vertices !== undefined) {
for (i = 0, l = vertices.count; i < l; i++, nbVertex++) {
vertex.x = vertices.getX(i);
vertex.y = vertices.getY(i);
vertex.z = vertices.getZ(i);
// transfrom the vertex to world space
vertex.applyMatrix4(line.matrixWorld);
// transform the vertex to export format
output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n';
}
}
if (type === 'Line') {
output += 'l ';
for (j = 1, l = vertices.count; j <= l; j++) {
output += (indexVertex + j) + ' ';
}
output += '\n';
}
if (type === 'LineSegments') {
for (j = 1, k = j + 1, l = vertices.count; j < l; j += 2, k = j + 1) {
output += 'l ' + (indexVertex + j) + ' ' + (indexVertex + k) + '\n';
}
}
} else {
console.warn('THREE.OBJExporter.parseLine(): geometry type unsupported', geometry);
}
// update index
indexVertex += nbVertex;
};
object.traverse(function (child) {
if (child instanceof THREE.Mesh) {
parseMesh(child);
}
if (child instanceof THREE.Line) {
parseLine(child);
}
});
return output;
}
};
@perelman
Copy link

Hi there.
Thanks for the gist.
Works great.
I am having troubles adding morphNormals into the export process, in order to fully support morph targets export.

Will be happy to hear if you have a direction.

@EmmanueleVilla
Copy link

EmmanueleVilla commented Oct 11, 2020

Hey, I'm trying to export a humanoid model, but I have some problems:
First of all, the attributes name are skinIndex0 and skinWeigth0, so I had to change the code to:

var skinIndices = geometry.getAttribute('skinIndex0');
var weights = geometry.getAttribute('skinWeight0');

otherwise the exported model is in T pose.

But, after this change, the exported model is a mess:
Schermata 2020-10-11 alle 21 40 29
Do you have any idea why?

@josepmy
Copy link

josepmy commented Oct 31, 2020

Do you have any idea why?

How you did it get working?

@EmmanueleVilla
Copy link

Do you have any idea why?

How you did it get working?

What do you mean "working"? My example doesn't work at all, the exported model is a complete random mess

@josepmy
Copy link

josepmy commented Nov 1, 2020 via email

@EmmanueleVilla
Copy link

I know, I ask about how you config it to arrive to this mesh, I can get as T pose fairly but no more.

El 1 nov 2020, a las 9:45, Emmanuele Villa @.***> escribió: @EmmanueleVilla commented on this gist. Do you have any idea why? How you did it get working? What do you mean "working"? My example doesn't work at all, the exported model is a complete random mess — You are receiving this because you commented. Reply to this email directly, view it on GitHub https://gist.github.com/35b19f811c657ec73b0238d78413f88d#gistcomment-3512061, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABPMFVQU6KKUCXP77MWB2P3SNUN2NANCNFSM4SL7JYSQ.

I've changed the two lines that I wrote in my first comment, from skinWeigth to skinWeight0

@Wirpy
Copy link

Wirpy commented Sep 25, 2023

Does this work ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment