mirror of
https://github.com/JannisX11/blockbench.git
synced 2024-11-21 01:13:37 +08:00
4ff4c3fd6a
Closes #1257
490 lines
14 KiB
JavaScript
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()
|
|
}
|
|
})
|
|
})
|
|
|
|
})()
|