blockbench/js/io/formats/obj.js
2022-01-22 21:42:08 +01:00

490 lines
14 KiB
JavaScript

(function() {
var _obj_export;
function getMtlFace(obj, index) {
//if (index % 2 == 1) index--;
var key = Canvas.face_order[index];
var tex = obj.faces[key].getTexture()
if (tex === null) {
return false
} else if (!tex || typeof tex === 'string') {
return 'usemtl none'
} else {
return 'usemtl m_' + tex.id;
}
}
const cube_face_normals = {
north: [0, 0, -1],
east: [1, 0, 0],
south: [0, 0, 1],
west: [-1, 0, 0],
up: [0, 1, 0],
down: [0, -1, 0],
}
var codec = new Codec('obj', {
name: 'OBJ Wavefront Model',
extension: 'obj',
compile(options) {
if (!options) options = 0;
var old_scene_position = new THREE.Vector3().copy(scene.position);
scene.position.set(0,0,0);
/**
Based on: three.js obj exporter, MIT license
https://github.com/mrdoob/three.js/blob/dev/examples/js/exporters/OBJExporter.js
*/
let materials = {};
let output = ['# Made in Blockbench '+appVersion];
let indexVertex = 0;
let indexVertexUvs = 0;
let indexNormals = 0;
const vertex = new THREE.Vector3();
const color = new THREE.Color();
const normal = new THREE.Vector3();
const uv = new THREE.Vector2();
const face = [];
let face_export_mode = Settings.get('obj_face_export_mode');
output.push('mtllib ' + (options.mtl_name||'materials.mtl') +'\n');
var parseMesh = function ( mesh ) {
var nbVertex = 0;
var nbVertexUvs = 0;
var nbNormals = 0;
var geometry = mesh.geometry;
var element = OutlinerNode.uuids[mesh.name];
const normalMatrixWorld = new THREE.Matrix3();
if (!element) return;
if (element.export === false) return;
normalMatrixWorld.getNormalMatrix( mesh.matrixWorld );
if (element instanceof Cube) {
output.push(`o ${element.name||'cube'}`)
function addVertex(x, y, z) {
vertex.set(x - element.origin[0], y - element.origin[1], z - element.origin[2]);
vertex.applyMatrix4( mesh.matrixWorld ).divideScalar(16);
output.push('v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z);
nbVertex++;
}
addVertex(element.to[0] + element.inflate, element.to[1] + element.inflate, element.to[2] + element.inflate);
addVertex(element.to[0] + element.inflate, element.to[1] + element.inflate, element.from[2] - element.inflate);
addVertex(element.to[0] + element.inflate, element.from[1] - element.inflate, element.to[2] + element.inflate);
addVertex(element.to[0] + element.inflate, element.from[1] - element.inflate, element.from[2] - element.inflate);
addVertex(element.from[0] - element.inflate, element.to[1] + element.inflate, element.from[2] - element.inflate);
addVertex(element.from[0] - element.inflate, element.to[1] + element.inflate, element.to[2] + element.inflate);
addVertex(element.from[0] - element.inflate, element.from[1] - element.inflate, element.from[2] - element.inflate);
addVertex(element.from[0] - element.inflate, element.from[1] - element.inflate, element.to[2] + element.inflate);
for (let key in element.faces) {
if (element.faces[key].texture !== null) {
let face = element.faces[key];
let uv_outputs = [];
uv_outputs.push(`vt ${face.uv[0] / Project.texture_width} ${1 - face.uv[1] / Project.texture_height}`);
uv_outputs.push(`vt ${face.uv[2] / Project.texture_width} ${1 - face.uv[1] / Project.texture_height}`);
uv_outputs.push(`vt ${face.uv[2] / Project.texture_width} ${1 - face.uv[3] / Project.texture_height}`);
uv_outputs.push(`vt ${face.uv[0] / Project.texture_width} ${1 - face.uv[3] / Project.texture_height}`);
var rot = face.rotation || 0;
while (rot > 0) {
uv_outputs.splice(0, 0, uv_outputs.pop());
rot -= 90;
}
output.push(...uv_outputs);
nbVertexUvs += 4;
}
}
for (let key in element.faces) {
if (element.faces[key].texture !== null) {
normal.fromArray(cube_face_normals[key]);
normal.applyMatrix3( normalMatrixWorld ).normalize();
output.push('vn ' + normal.x + ' ' + normal.y + ' ' + normal.z );
nbNormals += 1;
}
}
let mtl;
let i = 0;
for (let key in element.faces) {
if (element.faces[key].texture !== null) {
let tex = element.faces[key].getTexture()
if (tex && tex.uuid && !materials[tex.id]) {
materials[tex.id] = tex;
}
let mtl_new = (!tex || typeof tex === 'string')
? 'none'
: 'm_' + tex.id;
if (mtl_new != mtl) {
mtl = mtl_new;
output.push('usemtl '+mtl);
}
let vertices;
switch (key) {
case 'north': vertices = [2, 5, 7, 4]; break;
case 'east': vertices = [1, 2, 4, 3]; break;
case 'south': vertices = [6, 1, 3, 8]; break;
case 'west': vertices = [5, 6, 8, 7]; break;
case 'up': vertices = [5, 2, 1, 6]; break;
case 'down': vertices = [8, 3, 4, 7]; break;
}
if (face_export_mode == 'tris') {
output.push('f '+[
`${vertices[2] + indexVertex}/${i*4 + 3 + indexVertexUvs}/${i+1+indexNormals}`,
`${vertices[1] + indexVertex}/${i*4 + 2 + indexVertexUvs}/${i+1+indexNormals}`,
`${vertices[0] + indexVertex}/${i*4 + 1 + indexVertexUvs}/${i+1+indexNormals}`,
].join(' '));
output.push('f '+[
`${vertices[3] + indexVertex}/${i*4 + 4 + indexVertexUvs}/${i+1+indexNormals}`,
`${vertices[2] + indexVertex}/${i*4 + 3 + indexVertexUvs}/${i+1+indexNormals}`,
`${vertices[0] + indexVertex}/${i*4 + 1 + indexVertexUvs}/${i+1+indexNormals}`,
].join(' '));
} else {
output.push('f '+[
`${vertices[3] + indexVertex}/${i*4 + 4 + indexVertexUvs}/${i+1+indexNormals}`,
`${vertices[2] + indexVertex}/${i*4 + 3 + indexVertexUvs}/${i+1+indexNormals}`,
`${vertices[1] + indexVertex}/${i*4 + 2 + indexVertexUvs}/${i+1+indexNormals}`,
`${vertices[0] + indexVertex}/${i*4 + 1 + indexVertexUvs}/${i+1+indexNormals}`,
].join(' '));
}
i++;
}
}
} else if (element instanceof Mesh) {
output.push(`o ${element.name||'mesh'}`)
let vertex_keys = [];
function addVertex(x, y, z) {
vertex.set(x, y, z);
vertex.applyMatrix4( mesh.matrixWorld ).divideScalar(16);
output.push('v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z);
nbVertex++;
}
for (let vkey in element.vertices) {
addVertex(...element.vertices[vkey]);
vertex_keys.push(vkey);
}
let mtl;
let i = 0;
let vertexnormals = [];
let faces = [];
for (let key in element.faces) {
if (element.faces[key].texture !== null && element.faces[key].vertices.length >= 3) {
let face = element.faces[key];
let vertices = face.getSortedVertices().slice();
let tex = element.faces[key].getTexture();
vertices.forEach(vkey => {
output.push(`vt ${face.uv[vkey][0] / Project.texture_width} ${1 - face.uv[vkey][1] / Project.texture_height}`);
nbVertexUvs += 1;
})
normal.fromArray(face.getNormal(true));
normal.applyMatrix3( normalMatrixWorld ).normalize();
vertexnormals.push('vn ' + normal.x + ' ' + normal.y + ' ' + normal.z );
nbNormals += 1;
if (tex && tex.uuid && !materials[tex.id]) {
materials[tex.id] = tex;
}
let mtl_new = (!tex || typeof tex === 'string')
? 'none'
: 'm_' + tex.id;
if (mtl_new != mtl) {
mtl = mtl_new;
faces.push('usemtl '+mtl);
}
if (face_export_mode == 'quads' && vertices.length == 3) vertices.push(vertices[0]);
if (face_export_mode == 'tris' && vertices.length == 4) {
let triplets_a = [];
vertices.slice(0, 3).forEach((vkey, vi) => {
let triplet = [
vertex_keys.indexOf(vkey) + 1 + indexVertex,
nbVertexUvs - vertices.length + vi + 1 + indexVertexUvs,
i+1+indexNormals,
]
triplets_a.push(triplet.join('/'));
})
faces.push('f ' + triplets_a.join(' '));
let triplets_b = [];
[vertices[0], vertices[2], vertices[3]].forEach((vkey, vi) => {
let triplet = [
vertex_keys.indexOf(vkey) + 1 + indexVertex,
nbVertexUvs - vertices.length + (vi ? 1 : 0) + vi + 1 + indexVertexUvs,
i+1+indexNormals,
]
triplets_b.push(triplet.join('/'));
})
faces.push('f ' + triplets_b.join(' '));
} else {
let triplets = [];
vertices.forEach(vkey => {
let triplet = [
vertex_keys.indexOf(vkey) + 1 + indexVertex,
nbVertexUvs - vertices.length + vertices.indexOf(vkey) + 1 + indexVertexUvs,
i+1+indexNormals,
]
triplets.push(triplet.join('/'));
})
faces.push('f ' + triplets.join(' '));
}
i++;
}
}
output.push(...vertexnormals);
output.push(...faces);
} else {
const vertices = geometry.getAttribute( 'position' );
const normals = geometry.getAttribute( 'normal' );
const uvs = geometry.getAttribute( 'uv' );
const indices = geometry.getIndex();
output.push('o ' + mesh.name); // name of the mesh material
if ( mesh.material && mesh.material.name ) {
output.push('usemtl ' + mesh.material.name);
} // vertices
if ( vertices !== undefined ) {
for ( let i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) {
vertex.x = vertices.getX( i );
vertex.y = vertices.getY( i );
vertex.z = vertices.getZ( i ); // transform the vertex to world space
vertex.applyMatrix4( mesh.matrixWorld ).divideScalar(16); // transform the vertex to export format
output.push('v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z);
}
} // uvs
if ( uvs !== undefined ) {
for ( let 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.push('vt ' + uv.x + ' ' + uv.y);
}
} // normals
if ( normals !== undefined ) {
normalMatrixWorld.getNormalMatrix( mesh.matrixWorld );
for ( let i = 0, l = normals.count; i < l; i ++, nbNormals ++ ) {
normal.x = normals.getX( i );
normal.y = normals.getY( i );
normal.z = normals.getZ( i ); // transform the normal to world space
normal.applyMatrix3( normalMatrixWorld ).normalize(); // transform the normal to export format
output.push('vn ' + normal.x + ' ' + normal.y + ' ' + normal.z );
}
}
// material
for (let key in element.faces) {
let tex = element.faces[key].getTexture()
if (tex && tex.uuid && !materials[tex.id]) {
materials[tex.id] = tex
}
}
// faces
if ( indices !== null ) {
for ( let i = 0, l = indices.count; i < l; i += 3 ) {
let f_mat = getMtlFace(element, geometry.groups[ Math.floor(i / 6) ].materialIndex)
if (f_mat) {
if (i % 2 === 0) {
output.push(f_mat)
}
for ( let m = 0; m < 3; m ++ ) {
const j = indices.getX( i + m ) + 1;
face[ m ] = indexVertex + j + ( normals || uvs ? '/' + ( uvs ? indexVertexUvs + j : '' ) + ( normals ? '/' + ( indexNormals + j ) : '' ) : '' );
} // transform the face to export format
output.push('f ' + face.join( ' ' ) );
}
}
} else {
for ( let i = 0, l = vertices.count; i < l; i += 3 ) {
for ( let m = 0; m < 3; m ++ ) {
const j = i + m + 1;
face[ m ] = indexVertex + j + ( normals || uvs ? '/' + ( uvs ? indexVertexUvs + j : '' ) + ( normals ? '/' + ( indexNormals + j ) : '' ) : '' );
} // transform the face to export format
output.push('f ' + face.join( ' ' ) );
}
}
}
// update index
indexVertex += nbVertex;
indexVertexUvs += nbVertexUvs;
indexNormals += nbNormals;
};
scene.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) parseMesh( child );
} );
// mtl output
var mtlOutput = '# Made in Blockbench '+appVersion+'\n';;
for (var key in materials) {
if (materials.hasOwnProperty(key) && materials[key]) {
var tex = materials[key];
mtlOutput += 'newmtl m_' +key+ '\n'
mtlOutput += `map_Kd ${tex.name} \n`;
}
}
mtlOutput += 'newmtl none'
scene.position.copy(old_scene_position)
output = output.join('\n');
_obj_export = {
obj: output,
mtl: mtlOutput,
images: materials
}
this.dispatchEvent('compile', {model: output, mtl: mtlOutput, images: materials});
return options.all_files ? _obj_export : output;
},
write(content, path) {
var scope = this;
var mtl_path = path.replace(/\.obj$/, '.mtl')
content = this.compile({mtl_name: pathToName(mtl_path, true)})
Blockbench.writeFile(path, {content}, path => scope.afterSave(path));
Blockbench.writeFile(mtl_path, {content: _obj_export.mtl});
//let existing_names = [];
for (var key in _obj_export.images) {
var texture = _obj_export.images[key]
if (texture && !texture.error) {
var name = texture.name;
if (name.substr(-4) !== '.png') {
name += '.png';
}
/*
while (existing_names.includes(name)) {
name = name.substr(0, name.length-4);
let match = name.match(/\d+$/);
let number = match ? parseInt(match[0])+1 : 2;
name = name.replace(/\d+$/, '');
name = name + number + '.png';
}
existing_names.push(name);
*/
var image_path = path.split(osfs);
image_path.splice(-1, 1, name);
Blockbench.writeFile(image_path.join(osfs), {
content: texture.source,
savetype: 'image'
})
}
}
},
export() {
var scope = this;
if (isApp) {
Blockbench.export({
resource_id: 'obj',
type: this.name,
extensions: [this.extension],
name: this.fileName(),
custom_writer: (a, b) => scope.write(a, b),
})
} else {
var archive = new JSZip();
var content = this.compile()
archive.file((Project.name||'model')+'.obj', content)
archive.file('materials.mtl', _obj_export.mtl)
for (var key in _obj_export.images) {
var texture = _obj_export.images[key]
if (texture && !texture.error && texture.mode === 'bitmap') {
archive.file(pathToName(texture.name) + '.png', texture.source.replace('data:image/png;base64,', ''), {base64: true});
}
}
archive.generateAsync({type: 'blob'}).then(content => {
Blockbench.export({
type: 'Zip Archive',
extensions: ['zip'],
name: 'assets',
content: content,
savetype: 'zip'
}, path => scope.afterDownload(path));
})
}
}
})
BARS.defineActions(function() {
codec.export_action = new Action({
id: 'export_obj',
icon: 'icon-objects',
category: 'file',
click: function () {
codec.export()
}
})
})
})()