Collada cube and mesh export

This commit is contained in:
JannisX11 2021-12-01 11:39:30 +01:00
parent 8da6ae45c5
commit 43608512da
5 changed files with 310 additions and 61 deletions

View File

@ -6,38 +6,27 @@ var codec = new Codec('collada', {
extension: 'dae',
async compile(options = 0) {
let scope = this;
/*
let exporter = new THREE.ColladaExporter();
let animations = [];
let gl_scene = new THREE.Scene();
gl_scene.name = 'blockbench_export'
gl_scene.add(Project.model_3d);
if (!Modes.edit) {
Animator.showDefaultPose();
}
if (options.animations !== false) {
//animations = buildAnimationTracks();
}
let result = await new Promise((resolve, reject) => {
exporter.parse(gl_scene, (result) => {
resolve(result);
}, {
author: settings.username.value || 'Blockbench User',
textureDirectory: 'textures'
});
})
scene.add(Project.model_3d);
*/
let geometries = [];
let root = [];
let effects = [];
let images = [];
let materials = [];
/**
* TODO
* different materials per geo
* animations
* image export
*/
// Structure
let model = {
type: 'COLLADA',
attributes: {
xmlns: 'http://www.collada.org/2005/11/COLLADASchema',
version: '1.4.1',
'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
},
content: [
{
type: 'asset',
@ -45,12 +34,13 @@ var codec = new Codec('collada', {
{
name: 'contributor',
content: [
{type: 'author', content: settings.username.value || 'Blockbench user'},
{type: 'authoring_tool', content: 'Blockbench'},
{type: 'author', content: settings.username.value || 'Blockbench user'}
]
},
{name: 'created', content: new Date().toISOString()},
{name: 'modified', content: new Date().toISOString()},
{name: 'unit', attributes: {name: 'meter', meter: "0.0625"}},
{name: 'up_axis', content: 'Y_UP'}
]
},
@ -93,6 +83,7 @@ var codec = new Codec('collada', {
]
}
// Materials
Texture.all.forEach((texture, i) => {
effects.push({
type: 'effect',
@ -102,20 +93,19 @@ var codec = new Codec('collada', {
content: [
{
type: 'newparam',
attributes: {sid: `Image_0-surface`},
attributes: {sid: `Image_${i}-surface`},
content: {
type: 'surface',
attributes: {type: '2D'},
content: {type: 'init_from', content: `Image_0`}
content: {type: 'init_from', content: `Image_${i}`}
}
},
{
type: 'newparam',
attributes: {sid: `Image_0-sampler`},
attributes: {sid: `Image_${i}-sampler`},
content: {
type: 'sampler2D',
attributes: {type: '2D'},
content: {type: 'source', content: `Image_0-surface`}
content: {type: 'source', content: `Image_${i}-surface`}
}
},
{
@ -125,7 +115,7 @@ var codec = new Codec('collada', {
type: 'lambert',
content: [
{type: 'emission', content: {type: 'color', attributes: {sid: 'emission'}, content: '0 0 0 1'}},
{type: 'diffuse', content: {type: 'texture', attributes: {texture: `Image_0-sampler`, texcoord: 'UVMap'}}},
{type: 'diffuse', content: {type: 'texture', attributes: {texture: `Image_${i}-sampler`, texcoord: 'UVMap'}}},
{type: 'index_of_refraction', content: {type: 'float', attributes: {sid: 'ior'}, content: '1.45'}}
]
}
@ -141,26 +131,92 @@ var codec = new Codec('collada', {
},
content: {
type: 'init_from',
content: `Image_${i}.png`
content: `${texture.name.replace(/\.png$/, '')}.png`
}
})
materials.push({
type: 'material',
attributes: {
id: `Image_${i}-material`,
name: `Image_${i}`,
id: `Material_${i}-material`,
name: `Material_${i}`,
},
content: {name: 'instance_effect', attributes: {url: `#Material_${i}-effect`}}
})
})
// Cube Geometry
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],
}
Cube.all.forEach(cube => {
let positions = [];
let normals = [];
let uv = [];
let vcount = [];
let primitive = [];
function addPosition(x, y, z) {
positions.push(x - cube.origin[0], y - cube.origin[1], z - cube.origin[2]);
}
addPosition(cube.to[0] + cube.inflate, cube.to[1] + cube.inflate, cube.to[2] + cube.inflate);
addPosition(cube.to[0] + cube.inflate, cube.to[1] + cube.inflate, cube.from[2] - cube.inflate);
addPosition(cube.to[0] + cube.inflate, cube.from[1] - cube.inflate, cube.to[2] + cube.inflate);
addPosition(cube.to[0] + cube.inflate, cube.from[1] - cube.inflate, cube.from[2] - cube.inflate);
addPosition(cube.from[0] - cube.inflate, cube.to[1] + cube.inflate, cube.from[2] - cube.inflate);
addPosition(cube.from[0] - cube.inflate, cube.to[1] + cube.inflate, cube.to[2] + cube.inflate);
addPosition(cube.from[0] - cube.inflate, cube.from[1] - cube.inflate, cube.from[2] - cube.inflate);
addPosition(cube.from[0] - cube.inflate, cube.from[1] - cube.inflate, cube.to[2] + cube.inflate);
for (let fkey in cube.faces) {
let face = cube.faces[fkey];
if (face.texture === null) continue;
normals.push(...cube_face_normals[fkey]);
let uv_outputs = [
[face.uv[0] / Project.texture_width, 1 - face.uv[1] / Project.texture_height],
[face.uv[2] / Project.texture_width, 1 - face.uv[1] / Project.texture_height],
[face.uv[2] / Project.texture_width, 1 - face.uv[3] / Project.texture_height],
[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;
}
uv_outputs.forEach(coord => {
uv.push(...coord);
})
vcount.push(4);
let vertices;
switch (fkey) {
case 'north': vertices = [1, 4, 6, 3]; break;
case 'east': vertices = [0, 1, 3, 2]; break;
case 'south': vertices = [5, 0, 2, 7]; break;
case 'west': vertices = [4, 5, 7, 6]; break;
case 'up': vertices = [4, 1, 0, 5]; break;
case 'down': vertices = [7, 2, 3, 6]; break;
}
primitive.push(
vertices[0], (normals.length/3)-1, vcount.length*4 - 4,
vertices[1], (normals.length/3)-1, vcount.length*4 - 3,
vertices[2], (normals.length/3)-1, vcount.length*4 - 2,
vertices[3], (normals.length/3)-1, vcount.length*4 - 1,
)
}
let geometry = {
type: 'geometry',
attributes: {
id: `${cube.uuid}-mesh`,
name: Cube.name
name: cube.name
},
content: [{
type: 'mesh',
@ -171,14 +227,14 @@ var codec = new Codec('collada', {
content: [
{
type: 'float_array',
attributes: {id: `${cube.uuid}-mesh-positions-array`, count: 24},
content: cube.mesh.geometry.attributes.position.array.join(' ')
attributes: {id: `${cube.uuid}-mesh-positions-array`, count: positions.length},
content: positions.join(' ')
},
{
type: 'technique_common',
content: {
type: 'accessor',
attributes: {source: `#${cube.uuid}-mesh-positions-array`, count: 8, stride: 3},
attributes: {source: `#${cube.uuid}-mesh-positions-array`, count: positions.length/3, stride: 3},
content: [
{type: 'param', attributes: {name: 'X', type: 'float'}},
{type: 'param', attributes: {name: 'Y', type: 'float'}},
@ -194,14 +250,14 @@ var codec = new Codec('collada', {
content: [
{
type: 'float_array',
attributes: {id: `${cube.uuid}-mesh-normals-array`, count: 18},
content: cube.mesh.geometry.attributes.normal.array.join(' ')
attributes: {id: `${cube.uuid}-mesh-normals-array`, count: normals.length},
content: normals.join(' ')
},
{
type: 'technique_common',
content: {
type: 'accessor',
attributes: {source: `#${cube.uuid}-mesh-normals-array`, count: 6, stride: 3},
attributes: {source: `#${cube.uuid}-mesh-normals-array`, count: normals.length/3, stride: 3},
content: [
{type: 'param', attributes: {name: 'X', type: 'float'}},
{type: 'param', attributes: {name: 'Y', type: 'float'}},
@ -217,14 +273,14 @@ var codec = new Codec('collada', {
content: [
{
type: 'float_array',
attributes: {id: `${cube.uuid}-mesh-map-0-array`, count: 48},
content: cube.mesh.geometry.attributes.uv.array.join(' ')
attributes: {id: `${cube.uuid}-mesh-map-0-array`, count: uv.length},
content: uv.join(' ')
},
{
type: 'technique_common',
content: {
type: 'accessor',
attributes: {source: `#${cube.uuid}-mesh-map-0-array`, count: 24, stride: 2},
attributes: {source: `#${cube.uuid}-mesh-map-0-array`, count: uv.length/2, stride: 2},
content: [
{type: 'param', attributes: {name: 'S', type: 'float'}},
{type: 'param', attributes: {name: 'T', type: 'float'}},
@ -239,22 +295,22 @@ var codec = new Codec('collada', {
content: [
{
type: 'input',
attributes: {semantic: 'POSITION', id: `${cube.uuid}-mesh-positions`}
attributes: {semantic: 'POSITION', source: `#${cube.uuid}-mesh-positions`}
}
]
},
{
type: 'polylist',
attributes: {
count: 6,
material: ''
material: `Material_${Texture.all.indexOf(cube.faces.north.getTexture())}-material`,
count: 6
},
content: [
{type: 'input', semantic: 'VERTEX', source: `${cube.uuid}-mesh-positions`, offset: 0},
{type: 'input', semantic: 'NORMAL', source: `${cube.uuid}-mesh-normals`, offset: 1},
{type: 'input', semantic: 'TEXCOORD', source: `${cube.uuid}-mesh-map-0`, offset: 2, set: 0},
{type: 'vcount', content: '4 4 4 4 4 4'},
{type: 'p', content: '0 0 0 1 0 1 3 0 2 2 0 3 2 1 4 3 1 5 7 1 6 6 1 7 6 2 8 7 2 9 5 2 10 4 2 11 4 3 12 5 3 13 1 3 14 0 3 15 2 4 16 6 4 17 4 4 18 0 4 19 7 5 20 3 5 21 1 5 22 5 5 23'}
{type: 'input', attributes: {semantic: 'VERTEX', source: `#${cube.uuid}-mesh-vertices`, offset: 0}},
{type: 'input', attributes: {semantic: 'NORMAL', source: `#${cube.uuid}-mesh-normals`, offset: 1}},
{type: 'input', attributes: {semantic: 'TEXCOORD', source: `#${cube.uuid}-mesh-map-0`, offset: 2, set: 0}},
{type: 'vcount', content: vcount.join(' ')},
{type: 'p', content: primitive.join(' ')}
]
}
]
@ -263,8 +319,167 @@ var codec = new Codec('collada', {
geometries.push(geometry);
})
// Mesh Geo
Mesh.all.forEach(mesh => {
let positions = [];
let normals = [];
let uv = [];
let vcount = [];
let primitive = [];
let vertex_keys = [];
function addPosition(x, y, z) {
positions.push(x, y, z);
}
for (let vkey in mesh.vertices) {
addPosition(...mesh.vertices[vkey]);
vertex_keys.push(vkey);
}
let texture;
for (let key in mesh.faces) {
if (mesh.faces[key].texture !== null && mesh.faces[key].vertices.length >= 3) {
let face = mesh.faces[key];
let vertices = face.getSortedVertices();
let tex = mesh.faces[key].getTexture();
texture = tex;
vcount.push(vertices.length);
vertices.forEach(vkey => {
uv.push(face.uv[vkey][0] / Project.texture_width, 1 - face.uv[vkey][1] / Project.texture_height);
})
normals.push(...face.getNormal(true));
vertices.forEach((vkey, vi) => {
primitive.push(
vertex_keys.indexOf(vkey),
(normals.length/3)-1,
(uv.length/2)-vertices.length+vi,
)
})
i++;
}
}
let geometry = {
type: 'geometry',
attributes: {
id: `${mesh.uuid}-mesh`,
name: mesh.name
},
content: [{
type: 'mesh',
content: [
{
type: 'source',
attributes: {id: `${mesh.uuid}-mesh-positions`},
content: [
{
type: 'float_array',
attributes: {id: `${mesh.uuid}-mesh-positions-array`, count: positions.length},
content: positions.join(' ')
},
{
type: 'technique_common',
content: {
type: 'accessor',
attributes: {source: `#${mesh.uuid}-mesh-positions-array`, count: positions.length/3, stride: 3},
content: [
{type: 'param', attributes: {name: 'X', type: 'float'}},
{type: 'param', attributes: {name: 'Y', type: 'float'}},
{type: 'param', attributes: {name: 'Z', type: 'float'}},
]
}
}
]
},
{
type: 'source',
attributes: {id: `${mesh.uuid}-mesh-normals`},
content: [
{
type: 'float_array',
attributes: {id: `${mesh.uuid}-mesh-normals-array`, count: normals.length},
content: normals.join(' ')
},
{
type: 'technique_common',
content: {
type: 'accessor',
attributes: {source: `#${mesh.uuid}-mesh-normals-array`, count: normals.length/3, stride: 3},
content: [
{type: 'param', attributes: {name: 'X', type: 'float'}},
{type: 'param', attributes: {name: 'Y', type: 'float'}},
{type: 'param', attributes: {name: 'Z', type: 'float'}},
]
}
}
]
},
{
type: 'source',
attributes: {id: `${mesh.uuid}-mesh-map-0`},
content: [
{
type: 'float_array',
attributes: {id: `${mesh.uuid}-mesh-map-0-array`, count: uv.length},
content: uv.join(' ')
},
{
type: 'technique_common',
content: {
type: 'accessor',
attributes: {source: `#${mesh.uuid}-mesh-map-0-array`, count: uv.length/2, stride: 2},
content: [
{type: 'param', attributes: {name: 'S', type: 'float'}},
{type: 'param', attributes: {name: 'T', type: 'float'}},
]
}
}
]
},
{
type: 'vertices',
attributes: {id: `${mesh.uuid}-mesh-vertices`},
content: [
{
type: 'input',
attributes: {semantic: 'POSITION', source: `#${mesh.uuid}-mesh-positions`}
}
]
},
{
type: 'polylist',
attributes: {
material: `Material_${Texture.all.indexOf(texture)}-material`,
count: 6
},
content: [
{type: 'input', attributes: {semantic: 'VERTEX', source: `#${mesh.uuid}-mesh-vertices`, offset: 0}},
{type: 'input', attributes: {semantic: 'NORMAL', source: `#${mesh.uuid}-mesh-normals`, offset: 1}},
{type: 'input', attributes: {semantic: 'TEXCOORD', source: `#${mesh.uuid}-mesh-map-0`, offset: 2, set: 0}},
{type: 'vcount', content: vcount.join(' ')},
{type: 'p', content: primitive.join(' ')}
]
}
]
}]
}
geometries.push(geometry);
})
// Object Hierarchy
function processNode(node) {
let position = node.origin.slice();
if (node.parent instanceof Group) position.V3_subtract(node.parent.origin);
let tag = {
name: 'node',
attributes: {
id: node.uuid,
name: node.name,
@ -272,13 +487,44 @@ var codec = new Codec('collada', {
},
content: [
{type: 'scale', attributes: {sid: 'scale'}, content: '1 1 1'},
{type: 'rotate', attributes: {sid: 'rotationX'}, content: '1 0 0 0'},
{type: 'rotate', attributes: {sid: 'rotationY'}, content: '0 1 0 0'},
{type: 'rotate', attributes: {sid: 'rotationZ'}, content: '0 0 1 -7'},
{type: 'translate', attributes: {sid: 'location'}, content: node.origin.join(' ')},
{type: 'instance_geometry', attributes: {url: `#${node.uuid}-mesh`, name: node.name}},
{type: 'translate', attributes: {sid: 'location'}, content: position.join(' ')},
]
}
if (node.rotatable) {
tag.content.push(
{type: 'rotate', attributes: {sid: 'rotationZ'}, content: `0 0 1 ${node.rotation[2]}`},
{type: 'rotate', attributes: {sid: 'rotationY'}, content: `0 1 0 ${node.rotation[1]}`},
{type: 'rotate', attributes: {sid: 'rotationX'}, content: `1 0 0 ${node.rotation[0]}`},
)
}
if (node instanceof Cube || node instanceof Mesh) {
let textures = [];
for (let fkey in node.faces) {
let tex = node.faces[fkey].getTexture();
if (tex instanceof Texture) textures.safePush(tex);
}
tag.content.push({
type: 'instance_geometry',
attributes: {url: `#${node.uuid}-mesh`, name: node.name},
content: {
name: 'bind_material',
content: {
name: 'technique_common',
content: textures.map(tex => {
let index = Texture.all.indexOf(tex);
return {
name: 'instance_material',
attributes: {symbol: `Material_${index}-material`, target: `#Material_${index}-material`},
content: {
name: 'bind_vertex_input',
attributes: {semantic: 'UVMap', input_semantic: 'TEXCOORD', input_set: '0'}
}
}
})
}
}
});
}
if (node instanceof Group) {
node.children.forEach(node => {
tag.content.push(processNode(node));
@ -356,7 +602,7 @@ function compileXML(object) {
output += `>\n`;
let list = object.content instanceof Array ? object.content : [object.content];
list.forEach(node => {
handleObject(node);
if (typeof node == 'object') handleObject(node);
})
depth--;
output += spaces() + `</${type}>\n`;

View File

@ -181,6 +181,8 @@ var codec = new Codec('gltf', {
}
})
codec.buildAnimationTracks = buildAnimationTracks;
BARS.defineActions(function() {
codec.export_action = new Action({
id: 'export_gltf',

View File

@ -91,7 +91,7 @@ var codec = new Codec('obj', {
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 = element.faces[key].rotation || 0;
var rot = face.rotation || 0;
while (rot > 0) {
uv_outputs.splice(0, 0, uv_outputs.pop());
rot -= 90;

View File

@ -410,6 +410,7 @@ class Group extends OutlinerNode {
Group.prototype.type = 'group';
Group.prototype.icon = 'fa fa-folder';
Group.prototype.isParent = true;
Group.prototype.rotatable = true;
Group.prototype.name_regex = () => Format.bone_rig ? 'a-zA-Z0-9_' : false;
Group.prototype.buttons = [
Outliner.buttons.autouv,

View File

@ -862,9 +862,9 @@
"action.export_obj": "Export OBJ Model",
"action.export_obj.desc": "Export a Wavefront OBJ model for rendering",
"action.export_collada": "Export Collada Model (dae)",
"action.export_collada.desc": "Export model and animations as dae file to use in other 3D applications",
"action.export_collada.desc": "Export model and animations as dae file to use it in other 3D applications",
"action.export_gltf": "Export glTF Model",
"action.export_gltf.desc": "Export model and animations as glTF file to use in other 3D applications",
"action.export_gltf.desc": "Export model and animations as glTF file for sharing and rendering",
"action.upload_sketchfab": "Upload to Sketchfab",
"action.upload_sketchfab.desc": "Upload your model to Sketchfab",
"action.share_model": "Share...",