Merge branch 'collada' into next
This commit is contained in:
commit
8495784dba
@ -70,6 +70,7 @@
|
||||
<script src="lib/three.min.js"></script>
|
||||
<script src="lib/three_custom.js"></script>
|
||||
<script src="lib/GLTFExporter.js"></script>
|
||||
<script src="lib/ColladaExporter.js"></script>
|
||||
<script src="lib/CanvasFrame.js"></script>
|
||||
<script src="lib/fik.min.js"></script>
|
||||
<script src="lib/molang.umd.js"></script>
|
||||
@ -137,6 +138,7 @@
|
||||
<script src="js/io/formats/bedrock_old.js"></script>
|
||||
<script src="js/io/formats/obj.js"></script>
|
||||
<script src="js/io/formats/gltf.js"></script>
|
||||
<script src="js/io/formats/collada.js"></script>
|
||||
<script src="js/io/formats/modded_entity.js"></script>
|
||||
<script src="js/io/formats/optifine_jem.js"></script>
|
||||
<script src="js/io/formats/optifine_jpm.js"></script>
|
||||
|
@ -199,15 +199,16 @@ class Keyframe {
|
||||
})
|
||||
return arr;
|
||||
}
|
||||
getFixed(data_point = 0) {
|
||||
getFixed(data_point = 0, do_quaternion = true) {
|
||||
if (this.channel === 'rotation') {
|
||||
let fix = this.animator.group.mesh.fix_rotation;
|
||||
return new THREE.Quaternion().setFromEuler(new THREE.Euler(
|
||||
let euler = new THREE.Euler(
|
||||
fix.x - Math.degToRad(this.calc('x', data_point)),
|
||||
fix.y - Math.degToRad(this.calc('y', data_point)),
|
||||
fix.z + Math.degToRad(this.calc('z', data_point)),
|
||||
'ZYX'
|
||||
));
|
||||
)
|
||||
return do_quaternion ? new THREE.Quaternion().setFromEuler(euler) : euler;
|
||||
} else if (this.channel === 'position') {
|
||||
let fix = this.animator.group.mesh.fix_position;
|
||||
return new THREE.Vector3(
|
||||
|
@ -601,6 +601,7 @@ const MenuBar = {
|
||||
'export_minecraft_skin',
|
||||
'export_gltf',
|
||||
'export_obj',
|
||||
'export_collada',
|
||||
'upload_sketchfab',
|
||||
'share_model',
|
||||
]},
|
||||
|
806
js/io/formats/collada.js
Normal file
806
js/io/formats/collada.js
Normal file
@ -0,0 +1,806 @@
|
||||
(function() {
|
||||
|
||||
function arrangeArray(array) {
|
||||
return array.map(v => Math.roundTo(v, 6)).join(' ');
|
||||
}
|
||||
|
||||
|
||||
var codec = new Codec('collada', {
|
||||
name: 'Collada Model',
|
||||
extension: 'dae',
|
||||
compile(options = 0) {
|
||||
let scope = this;
|
||||
let geometries = [];
|
||||
let root = [];
|
||||
let effects = [];
|
||||
let images = [];
|
||||
let materials = [];
|
||||
|
||||
// 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',
|
||||
content: [
|
||||
{
|
||||
name: 'contributor',
|
||||
content: [
|
||||
{type: 'author', content: settings.username.value || 'Blockbench user'},
|
||||
{type: 'authoring_tool', content: 'Blockbench'},
|
||||
]
|
||||
},
|
||||
{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'}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'library_effects',
|
||||
content: effects
|
||||
},
|
||||
{
|
||||
type: 'library_images',
|
||||
content: images
|
||||
},
|
||||
{
|
||||
type: 'library_materials',
|
||||
content: materials
|
||||
},
|
||||
{
|
||||
type: 'library_geometries',
|
||||
content: geometries
|
||||
},
|
||||
{
|
||||
type: 'library_visual_scenes',
|
||||
content: [{
|
||||
type: 'visual_scene',
|
||||
attributes: {
|
||||
id: 'Scene',
|
||||
name: 'Scene',
|
||||
},
|
||||
content: root
|
||||
}]
|
||||
},
|
||||
{
|
||||
type: 'scene',
|
||||
content: [{
|
||||
type: 'instance_visual_scene',
|
||||
attributes: {
|
||||
url: '#Scene'
|
||||
}
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// Materials
|
||||
Texture.all.forEach((texture, i) => {
|
||||
effects.push({
|
||||
type: 'effect',
|
||||
attributes: {id: `Material_${i}-effect`},
|
||||
content: {
|
||||
type: 'profile_COMMON',
|
||||
content: [
|
||||
{
|
||||
type: 'newparam',
|
||||
attributes: {sid: `Image_${i}-surface`},
|
||||
content: {
|
||||
type: 'surface',
|
||||
attributes: {type: '2D'},
|
||||
content: {type: 'init_from', content: `Image_${i}`}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'newparam',
|
||||
attributes: {sid: `Image_${i}-sampler`},
|
||||
content: {
|
||||
type: 'sampler2D',
|
||||
content: {type: 'source', content: `Image_${i}-surface`}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'technique',
|
||||
attributes: {sid: 'common'},
|
||||
content: {
|
||||
type: 'lambert',
|
||||
content: [
|
||||
{type: 'emission', content: {type: 'color', attributes: {sid: 'emission'}, content: '0 0 0 1'}},
|
||||
{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'}}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
images.push({
|
||||
type: 'image',
|
||||
attributes: {
|
||||
id: `Image_${i}`,
|
||||
name: `Image_${i}`,
|
||||
},
|
||||
content: {
|
||||
type: 'init_from',
|
||||
content: `${texture.name.replace(/\.png$/, '')}.png`
|
||||
}
|
||||
})
|
||||
materials.push({
|
||||
type: 'material',
|
||||
attributes: {
|
||||
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[3], (normals.length/3)-1, vcount.length*4 - 1,
|
||||
vertices[2], (normals.length/3)-1, vcount.length*4 - 2,
|
||||
vertices[1], (normals.length/3)-1, vcount.length*4 - 3,
|
||||
vertices[0], (normals.length/3)-1, vcount.length*4 - 4,
|
||||
)
|
||||
}
|
||||
|
||||
let geometry = {
|
||||
type: 'geometry',
|
||||
attributes: {
|
||||
id: `${cube.uuid}-mesh`,
|
||||
name: cube.name
|
||||
},
|
||||
content: {
|
||||
type: 'mesh',
|
||||
content: [
|
||||
{
|
||||
type: 'source',
|
||||
attributes: {id: `${cube.uuid}-mesh-positions`},
|
||||
content: [
|
||||
{
|
||||
type: 'float_array',
|
||||
attributes: {id: `${cube.uuid}-mesh-positions-array`, count: positions.length},
|
||||
content: arrangeArray(positions)
|
||||
},
|
||||
{
|
||||
type: 'technique_common',
|
||||
content: {
|
||||
type: 'accessor',
|
||||
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'}},
|
||||
{type: 'param', attributes: {name: 'Z', type: 'float'}},
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'source',
|
||||
attributes: {id: `${cube.uuid}-mesh-normals`},
|
||||
content: [
|
||||
{
|
||||
type: 'float_array',
|
||||
attributes: {id: `${cube.uuid}-mesh-normals-array`, count: normals.length},
|
||||
content: arrangeArray(normals)
|
||||
},
|
||||
{
|
||||
type: 'technique_common',
|
||||
content: {
|
||||
type: 'accessor',
|
||||
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'}},
|
||||
{type: 'param', attributes: {name: 'Z', type: 'float'}},
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'source',
|
||||
attributes: {id: `${cube.uuid}-mesh-map-0`},
|
||||
content: [
|
||||
{
|
||||
type: 'float_array',
|
||||
attributes: {id: `${cube.uuid}-mesh-map-0-array`, count: uv.length},
|
||||
content: arrangeArray(uv)
|
||||
},
|
||||
{
|
||||
type: 'technique_common',
|
||||
content: {
|
||||
type: 'accessor',
|
||||
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'}},
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'vertices',
|
||||
attributes: {id: `${cube.uuid}-mesh-vertices`},
|
||||
content: [
|
||||
{
|
||||
type: 'input',
|
||||
attributes: {semantic: 'POSITION', source: `#${cube.uuid}-mesh-positions`}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
let j = 0;
|
||||
let last_tex;
|
||||
let render_groups = [];
|
||||
for (let fkey in cube.faces) {
|
||||
let face = cube.faces[fkey];
|
||||
if (face.texture !== null) {
|
||||
let vcount_here = vcount[j];
|
||||
let p_here = primitive.slice(j * 12, j * 12 + 12);
|
||||
if (last_tex && face.texture === last_tex) {
|
||||
render_groups.last().vcount.push(vcount_here);
|
||||
render_groups.last().primitive.push(...p_here);
|
||||
|
||||
} else {
|
||||
render_groups.push({
|
||||
texture: face.getTexture(),
|
||||
vcount: [vcount_here],
|
||||
primitive: p_here,
|
||||
})
|
||||
last_tex = face.texture;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
}
|
||||
render_groups.forEach(render_group => {
|
||||
geometry.content.content.push({
|
||||
type: 'polylist',
|
||||
attributes: {
|
||||
material: `Material_${Texture.all.indexOf(render_group.texture)}-material`,
|
||||
count: 6
|
||||
},
|
||||
content: [
|
||||
{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: arrangeArray(render_group.vcount)},
|
||||
{type: 'p', content: arrangeArray(render_group.primitive)}
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
geometries.push(geometry);
|
||||
})
|
||||
|
||||
// Mesh Geo
|
||||
Mesh.all.forEach(mesh => {
|
||||
|
||||
let positions = [];
|
||||
let normals = [];
|
||||
let uv = [];
|
||||
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;
|
||||
|
||||
|
||||
let j = 0;
|
||||
let last_tex;
|
||||
let render_groups = [];
|
||||
let primitive_count = 0;
|
||||
for (let key in mesh.faces) {
|
||||
if (mesh.faces[key].vertices.length >= 3) {
|
||||
let face = mesh.faces[key];
|
||||
let vertices = face.getSortedVertices();
|
||||
let tex = mesh.faces[key].getTexture();
|
||||
|
||||
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));
|
||||
|
||||
let face_primitives = [];
|
||||
vertices.forEach((vkey, vi) => {
|
||||
face_primitives.push(
|
||||
vertex_keys.indexOf(vkey),
|
||||
(normals.length/3)-1,
|
||||
(uv.length/2)-vertices.length+vi,
|
||||
)
|
||||
})
|
||||
|
||||
if (last_tex && face.texture === last_tex) {
|
||||
render_groups.last().vcount.push(vertices.length);
|
||||
render_groups.last().primitive.push(...face_primitives);
|
||||
|
||||
} else {
|
||||
render_groups.push({
|
||||
texture: face.getTexture(),
|
||||
vcount: [vertices.length],
|
||||
primitive: face_primitives,
|
||||
})
|
||||
last_tex = face.texture;
|
||||
}
|
||||
primitive_count += face.vertices.length;
|
||||
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: arrangeArray(positions)
|
||||
},
|
||||
{
|
||||
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: arrangeArray(normals)
|
||||
},
|
||||
{
|
||||
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: arrangeArray(uv)
|
||||
},
|
||||
{
|
||||
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`}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
render_groups.forEach(render_group => {
|
||||
geometry.content.content.push({
|
||||
type: 'polylist',
|
||||
attributes: {
|
||||
material: `Material_${Texture.all.indexOf(render_group.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: arrangeArray(render_group.vcount)},
|
||||
{type: 'p', content: arrangeArray(render_group.primitive)}
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
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,
|
||||
type: 'NODE'
|
||||
},
|
||||
content: [
|
||||
{type: 'scale', attributes: {sid: 'scale'}, content: '1 1 1'},
|
||||
{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));
|
||||
})
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
Outliner.root.forEach(node => {
|
||||
root.push(processNode(node))
|
||||
})
|
||||
|
||||
/*
|
||||
let compiled_animations = Codecs.gltf.buildAnimationTracks();
|
||||
if (compiled_animations.length) {
|
||||
let animations_tag = {
|
||||
type: 'library_animations',
|
||||
content: []
|
||||
}
|
||||
let animation_clips_tag = {
|
||||
type: 'library_animation_clips',
|
||||
content: []
|
||||
}
|
||||
compiled_animations.forEach(anim_obj => {
|
||||
let anim_tag = {
|
||||
type: 'animation',
|
||||
attributes: {
|
||||
id: `animation-${anim_obj.name}`,
|
||||
name: anim_obj.name
|
||||
},
|
||||
content: []
|
||||
}
|
||||
anim_obj.tracks.forEach(track => {
|
||||
let group = OutlinerNode.uuids[track.group_uuid];
|
||||
let collada_channel = track.channel;
|
||||
if (collada_channel == 'position') collada_channel = 'location';
|
||||
let track_name = `${group.name}_${collada_channel}`
|
||||
|
||||
let track_tag = {
|
||||
type: 'animation',
|
||||
attributes: {id: `${group.name}`, name: group.name},
|
||||
content: [
|
||||
{
|
||||
type: 'source',
|
||||
attributes: {id: track_name+'-input'},
|
||||
content: [
|
||||
{
|
||||
type: 'float_array',
|
||||
attributes: {id: track_name+'-input-array', count: track.times.length},
|
||||
content: arrangeArray(track.times)
|
||||
},
|
||||
{
|
||||
type: 'technique_common',
|
||||
content: {
|
||||
type: 'accessor',
|
||||
attributes: {source: '#'+track_name+'-input-array', count: track.times.length, stride: 1},
|
||||
content: {type: 'param', attributes: {name: 'TIME', type: 'float'}}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'source',
|
||||
attributes: {id: track_name+'-output'},
|
||||
content: [
|
||||
{
|
||||
type: 'float_array',
|
||||
attributes: {id: track_name+'-output-array', count: track.values.length},
|
||||
content: arrangeArray(track.values)
|
||||
},
|
||||
{
|
||||
type: 'technique_common',
|
||||
content: {
|
||||
type: 'accessor',
|
||||
attributes: {source: '#'+track_name+'-output-array', count: track.values.length, 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: 'sampler',
|
||||
attributes: {id: `${track_name}-sampler`},
|
||||
content: [
|
||||
{type: 'input', attributes: {semantic: 'INPUT', source: '#'+track_name+'-input'}},
|
||||
{type: 'input', attributes: {semantic: 'OUTPUT', source: '#'+track_name+'-output'}},
|
||||
//{type: 'input', attributes: {semantic: 'INTERPOLATION', source: '#'+track_name+'-interpolation'}},
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
track_tag.content.push({
|
||||
type: 'channel',
|
||||
attributes: {source: `#${track_name}-sampler`, target: `${group.uuid}/${collada_channel}`}
|
||||
})
|
||||
|
||||
anim_tag.content.push(track_tag)
|
||||
})
|
||||
animations_tag.content.push(anim_tag)
|
||||
|
||||
let animation_clip_tag = {
|
||||
type: 'animation_clip',
|
||||
attributes: {
|
||||
id: anim_obj.name,
|
||||
name: anim_obj.name
|
||||
}
|
||||
}
|
||||
})
|
||||
model.content.push(animations_tag);
|
||||
}*/
|
||||
|
||||
scope.dispatchEvent('compile', {model, options});
|
||||
|
||||
|
||||
if (options.raw) {
|
||||
return model
|
||||
} else {
|
||||
return compileXML(model)
|
||||
}
|
||||
},
|
||||
write(content, path) {
|
||||
var scope = this;
|
||||
|
||||
content = this.compile();
|
||||
Blockbench.writeFile(path, {content}, path => scope.afterSave(path));
|
||||
|
||||
Texture.all.forEach(tex => {
|
||||
if (tex.error == 1) return;
|
||||
var name = tex.name;
|
||||
if (name.substr(-4).toLowerCase() !== '.png') {
|
||||
name += '.png';
|
||||
}
|
||||
var image_path = path.split(osfs);
|
||||
image_path.splice(-1, 1, name);
|
||||
Blockbench.writeFile(image_path.join(osfs), {
|
||||
content: tex.source,
|
||||
savetype: 'image'
|
||||
})
|
||||
})
|
||||
},
|
||||
export() {
|
||||
var scope = this;
|
||||
if (isApp) {
|
||||
Blockbench.export({
|
||||
resource_id: 'dae',
|
||||
type: this.name,
|
||||
extensions: [this.extension],
|
||||
startpath: this.startPath(),
|
||||
content: this.compile(),
|
||||
name: this.fileName(),
|
||||
custom_writer: (a, b) => scope.write(a, b),
|
||||
}, path => this.afterDownload(path))
|
||||
|
||||
} else {
|
||||
var archive = new JSZip();
|
||||
var content = this.compile()
|
||||
|
||||
archive.file((Project.name||'model')+'.obj', content)
|
||||
|
||||
Texture.all.forEach(tex => {
|
||||
if (texture.error == 1) return;
|
||||
var name = texture.name;
|
||||
if (name.substr(-4).toLowerCase() !== '.png') {
|
||||
name += '.png';
|
||||
}
|
||||
archive.file(name, 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_collada',
|
||||
icon: 'fas.fa-sync-alt',
|
||||
category: 'file',
|
||||
click: function () {
|
||||
codec.export()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
})()
|
||||
|
||||
function compileXML(object) {
|
||||
let depth = 0;
|
||||
let output = '<?xml version="1.0" encoding="utf-8"?>\n';
|
||||
|
||||
function spaces() {
|
||||
let s = '';
|
||||
for (let i = 0; i < depth; i++) {
|
||||
s += ' ';
|
||||
}
|
||||
return s;
|
||||
}
|
||||
function handleObject(object) {
|
||||
let type = object.type || object.name;
|
||||
let head = `<${type}`;
|
||||
if (object.attributes) {
|
||||
for (let key in object.attributes) {
|
||||
head += ` ${key}="${object.attributes[key]}"`
|
||||
}
|
||||
}
|
||||
output += spaces() + head;
|
||||
if (typeof object.content == 'string') {
|
||||
output += '>' + object.content + `</${type}>\n`;
|
||||
} else if (typeof object.content == 'object') {
|
||||
depth++;
|
||||
output += `>\n`;
|
||||
let list = object.content instanceof Array ? object.content : [object.content];
|
||||
list.forEach(node => {
|
||||
if (typeof node == 'object') handleObject(node);
|
||||
})
|
||||
depth--;
|
||||
output += spaces() + `</${type}>\n`;
|
||||
} else {
|
||||
output += '/>\n';
|
||||
}
|
||||
}
|
||||
handleObject(object);
|
||||
|
||||
return output;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
(function() {
|
||||
|
||||
function buildAnimationTracks() {
|
||||
function buildAnimationTracks(do_quaternions = true) {
|
||||
let anims = [];
|
||||
Animator.animations.forEach(animation => {
|
||||
|
||||
@ -53,7 +53,7 @@ function buildAnimationTracks() {
|
||||
keyframes.sort((a, b) => a.time - b.time)
|
||||
|
||||
// Sampling rotation steps that exceed 180 degrees
|
||||
if (channel === 'rotation' && !contains_script) {
|
||||
if (channel === 'rotation' && !contains_script && do_quaternions) {
|
||||
let original_keyframes = keyframes.slice();
|
||||
original_keyframes.forEach((kf, i) => {
|
||||
let next = original_keyframes[i+1]
|
||||
@ -101,10 +101,10 @@ function buildAnimationTracks() {
|
||||
}
|
||||
times.push(kf.time);
|
||||
Timeline.time = kf.time;
|
||||
kf.getFixed().toArray(values, values.length);
|
||||
kf.getFixed(0, do_quaternions).toArray(values, values.length);
|
||||
})
|
||||
let trackType = THREE.VectorKeyframeTrack;
|
||||
if (channel === 'rotation') {
|
||||
if (channel === 'rotation' && do_quaternions) {
|
||||
trackType = THREE.QuaternionKeyframeTrack;
|
||||
channel = 'quaternion';
|
||||
} else if (channel == 'position') {
|
||||
@ -113,6 +113,8 @@ function buildAnimationTracks() {
|
||||
})
|
||||
}
|
||||
let track = new trackType(animator.group.mesh.uuid+'.'+channel, times, values, interpolation);
|
||||
track.group_uuid = animator.group.uuid;
|
||||
track.channel = channel;
|
||||
tracks.push(track);
|
||||
}
|
||||
}
|
||||
@ -181,6 +183,8 @@ var codec = new Codec('gltf', {
|
||||
}
|
||||
})
|
||||
|
||||
codec.buildAnimationTracks = buildAnimationTracks;
|
||||
|
||||
BARS.defineActions(function() {
|
||||
codec.export_action = new Action({
|
||||
id: 'export_gltf',
|
||||
|
@ -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;
|
||||
|
@ -873,10 +873,16 @@ new NodePreviewController(Cube, {
|
||||
let j = 0;
|
||||
mesh.geometry.faces = [];
|
||||
mesh.geometry.clearGroups();
|
||||
let last_tex;
|
||||
Canvas.face_order.forEach((fkey, i) => {
|
||||
if (cube.faces[fkey].texture !== null) {
|
||||
indices.push(0 + i*4, 2 + i*4, 1 + i*4, 2 + i*4, 3 + i*4, 1 + i*4);
|
||||
mesh.geometry.addGroup(j*6, 6, j)
|
||||
if (last_tex && cube.faces[fkey].texture === last_tex) {
|
||||
mesh.geometry.groups[mesh.geometry.groups.length-1].count += 6;
|
||||
} else {
|
||||
mesh.geometry.addGroup(j*6, 6, j)
|
||||
last_tex = cube.faces[fkey].texture;
|
||||
}
|
||||
mesh.geometry.faces.push(fkey)
|
||||
j++;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -880,8 +880,10 @@
|
||||
"action.export_minecraft_skin.desc": "Export the Minecraft skin as a PNG texture",
|
||||
"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 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...",
|
||||
|
491
lib/ColladaExporter.js
Normal file
491
lib/ColladaExporter.js
Normal file
@ -0,0 +1,491 @@
|
||||
( function () {
|
||||
|
||||
/**
|
||||
* https://github.com/gkjohnson/collada-exporter-js
|
||||
*
|
||||
* Usage:
|
||||
* const exporter = new ColladaExporter();
|
||||
*
|
||||
* const data = exporter.parse(mesh);
|
||||
*
|
||||
* Format Definition:
|
||||
* https://www.khronos.org/collada/
|
||||
*/
|
||||
|
||||
class ColladaExporter {
|
||||
|
||||
parse( object, onDone, options = {} ) {
|
||||
|
||||
options = Object.assign( {
|
||||
version: '1.4.1',
|
||||
author: null,
|
||||
textureDirectory: '',
|
||||
upAxis: 'Y_UP',
|
||||
unitName: null,
|
||||
unitMeter: null
|
||||
}, options );
|
||||
|
||||
if ( options.upAxis.match( /^[XYZ]_UP$/ ) === null ) {
|
||||
|
||||
console.error( 'ColladaExporter: Invalid upAxis: valid values are X_UP, Y_UP or Z_UP.' );
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
if ( options.unitName !== null && options.unitMeter === null ) {
|
||||
|
||||
console.error( 'ColladaExporter: unitMeter needs to be specified if unitName is specified.' );
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
if ( options.unitMeter !== null && options.unitName === null ) {
|
||||
|
||||
console.error( 'ColladaExporter: unitName needs to be specified if unitMeter is specified.' );
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
if ( options.textureDirectory !== '' ) {
|
||||
|
||||
options.textureDirectory = `${options.textureDirectory}/`.replace( /\\/g, '/' ).replace( /\/+/g, '/' );
|
||||
|
||||
}
|
||||
|
||||
const version = options.version;
|
||||
|
||||
if ( version !== '1.4.1' && version !== '1.5.0' ) {
|
||||
|
||||
console.warn( `ColladaExporter : Version ${version} not supported for export. Only 1.4.1 and 1.5.0.` );
|
||||
return null;
|
||||
|
||||
} // Convert the urdf xml into a well-formatted, indented format
|
||||
|
||||
|
||||
function format( urdf ) {
|
||||
|
||||
const IS_END_TAG = /^<\//;
|
||||
const IS_SELF_CLOSING = /(\?>$)|(\/>$)/;
|
||||
const HAS_TEXT = /<[^>]+>[^<]*<\/[^<]+>/;
|
||||
|
||||
const pad = ( ch, num ) => num > 0 ? ch + pad( ch, num - 1 ) : '';
|
||||
|
||||
let tagnum = 0;
|
||||
return urdf.match( /(<[^>]+>[^<]+<\/[^<]+>)|(<[^>]+>)/g ).map( tag => {
|
||||
|
||||
if ( ! HAS_TEXT.test( tag ) && ! IS_SELF_CLOSING.test( tag ) && IS_END_TAG.test( tag ) ) {
|
||||
|
||||
tagnum --;
|
||||
|
||||
}
|
||||
|
||||
const res = `${pad( ' ', tagnum )}${tag}`;
|
||||
|
||||
if ( ! HAS_TEXT.test( tag ) && ! IS_SELF_CLOSING.test( tag ) && ! IS_END_TAG.test( tag ) ) {
|
||||
|
||||
tagnum ++;
|
||||
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
} ).join( '\n' );
|
||||
|
||||
} // Convert an image into a png format for saving
|
||||
|
||||
|
||||
function base64ToBuffer( str ) {
|
||||
|
||||
const b = atob( str );
|
||||
const buf = new Uint8Array( b.length );
|
||||
|
||||
for ( let i = 0, l = buf.length; i < l; i ++ ) {
|
||||
|
||||
buf[ i ] = b.charCodeAt( i );
|
||||
|
||||
}
|
||||
|
||||
return buf;
|
||||
|
||||
}
|
||||
|
||||
let canvas, ctx;
|
||||
|
||||
function imageToData( image, ext ) {
|
||||
|
||||
canvas = canvas || document.createElement( 'canvas' );
|
||||
ctx = ctx || canvas.getContext( '2d' );
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
ctx.drawImage( image, 0, 0 ); // Get the base64 encoded data
|
||||
|
||||
const base64data = canvas.toDataURL( `image/${ext}`, 1 ).replace( /^data:image\/(png|jpg);base64,/, '' ); // Convert to a uint8 array
|
||||
|
||||
return base64ToBuffer( base64data );
|
||||
|
||||
} // gets the attribute array. Generate a new array if the attribute is interleaved
|
||||
|
||||
|
||||
const getFuncs = [ 'getX', 'getY', 'getZ', 'getW' ];
|
||||
|
||||
function attrBufferToArray( attr ) {
|
||||
|
||||
if ( attr.isInterleavedBufferAttribute ) {
|
||||
|
||||
// use the typed array constructor to save on memory
|
||||
const arr = new attr.array.constructor( attr.count * attr.itemSize );
|
||||
const size = attr.itemSize;
|
||||
|
||||
for ( let i = 0, l = attr.count; i < l; i ++ ) {
|
||||
|
||||
for ( let j = 0; j < size; j ++ ) {
|
||||
|
||||
arr[ i * size + j ] = attr[ getFuncs[ j ] ]( i );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return arr;
|
||||
|
||||
} else {
|
||||
|
||||
return attr.array;
|
||||
|
||||
}
|
||||
|
||||
} // Returns an array of the same type starting at the `st` index,
|
||||
// and `ct` length
|
||||
|
||||
|
||||
function subArray( arr, st, ct ) {
|
||||
|
||||
if ( Array.isArray( arr ) ) return arr.slice( st, st + ct ); else return new arr.constructor( arr.buffer, st * arr.BYTES_PER_ELEMENT, ct );
|
||||
|
||||
} // Returns the string for a geometry's attribute
|
||||
|
||||
|
||||
function getAttribute( attr, name, params, type ) {
|
||||
|
||||
const array = attrBufferToArray( attr );
|
||||
const res = `<source id="${name}">` + `<float_array id="${name}-array" count="${array.length}">` + array.join( ' ' ) + '</float_array>' + '<technique_common>' + `<accessor source="#${name}-array" count="${Math.floor( array.length / attr.itemSize )}" stride="${attr.itemSize}">` + params.map( n => `<param name="${n}" type="${type}" />` ).join( '' ) + '</accessor>' + '</technique_common>' + '</source>';
|
||||
return res;
|
||||
|
||||
} // Returns the string for a node's transform information
|
||||
|
||||
|
||||
let transMat;
|
||||
|
||||
function getTransform( o ) {
|
||||
|
||||
// ensure the object's matrix is up to date
|
||||
// before saving the transform
|
||||
o.updateMatrix();
|
||||
transMat = transMat || new THREE.Matrix4();
|
||||
transMat.copy( o.matrix );
|
||||
transMat.transpose();
|
||||
return `<matrix>${transMat.toArray().join( ' ' )}</matrix>`;
|
||||
|
||||
} // Process the given piece of geometry into the geometry library
|
||||
// Returns the mesh id
|
||||
|
||||
|
||||
function processGeometry( g ) {
|
||||
|
||||
let info = geometryInfo.get( g );
|
||||
|
||||
if ( ! info ) {
|
||||
|
||||
// convert the geometry to bufferGeometry if it isn't already
|
||||
const bufferGeometry = g;
|
||||
|
||||
if ( bufferGeometry.isBufferGeometry !== true ) {
|
||||
|
||||
throw new Error( 'THREE.ColladaExporter: Geometry is not of type THREE.BufferGeometry.' );
|
||||
|
||||
}
|
||||
|
||||
const meshid = `Mesh${libraryGeometries.length + 1}`;
|
||||
const indexCount = bufferGeometry.index ? bufferGeometry.index.count * bufferGeometry.index.itemSize : bufferGeometry.attributes.position.count;
|
||||
const groups = bufferGeometry.groups != null && bufferGeometry.groups.length !== 0 ? bufferGeometry.groups : [ {
|
||||
start: 0,
|
||||
count: indexCount,
|
||||
materialIndex: 0
|
||||
} ];
|
||||
const gname = g.name ? ` name="${g.name}"` : '';
|
||||
let gnode = `<geometry id="${meshid}"${gname}><mesh>`; // define the geometry node and the vertices for the geometry
|
||||
|
||||
const posName = `${meshid}-position`;
|
||||
const vertName = `${meshid}-vertices`;
|
||||
gnode += getAttribute( bufferGeometry.attributes.position, posName, [ 'X', 'Y', 'Z' ], 'float' );
|
||||
gnode += `<vertices id="${vertName}"><input semantic="POSITION" source="#${posName}" /></vertices>`; // NOTE: We're not optimizing the attribute arrays here, so they're all the same length and
|
||||
// can therefore share the same triangle indices. However, MeshLab seems to have trouble opening
|
||||
// models with attributes that share an offset.
|
||||
// MeshLab Bug#424: https://sourceforge.net/p/meshlab/bugs/424/
|
||||
// serialize normals
|
||||
|
||||
let triangleInputs = `<input semantic="VERTEX" source="#${vertName}" offset="0" />`;
|
||||
|
||||
if ( 'normal' in bufferGeometry.attributes ) {
|
||||
|
||||
const normName = `${meshid}-normal`;
|
||||
gnode += getAttribute( bufferGeometry.attributes.normal, normName, [ 'X', 'Y', 'Z' ], 'float' );
|
||||
triangleInputs += `<input semantic="NORMAL" source="#${normName}" offset="0" />`;
|
||||
|
||||
} // serialize uvs
|
||||
|
||||
|
||||
if ( 'uv' in bufferGeometry.attributes ) {
|
||||
|
||||
const uvName = `${meshid}-texcoord`;
|
||||
gnode += getAttribute( bufferGeometry.attributes.uv, uvName, [ 'S', 'T' ], 'float' );
|
||||
triangleInputs += `<input semantic="TEXCOORD" source="#${uvName}" offset="0" set="0" />`;
|
||||
|
||||
} // serialize lightmap uvs
|
||||
|
||||
|
||||
if ( 'uv2' in bufferGeometry.attributes ) {
|
||||
|
||||
const uvName = `${meshid}-texcoord2`;
|
||||
gnode += getAttribute( bufferGeometry.attributes.uv2, uvName, [ 'S', 'T' ], 'float' );
|
||||
triangleInputs += `<input semantic="TEXCOORD" source="#${uvName}" offset="0" set="1" />`;
|
||||
|
||||
} // serialize colors
|
||||
|
||||
|
||||
if ( 'color' in bufferGeometry.attributes ) {
|
||||
|
||||
const colName = `${meshid}-color`;
|
||||
gnode += getAttribute( bufferGeometry.attributes.color, colName, [ 'X', 'Y', 'Z' ], 'uint8' );
|
||||
triangleInputs += `<input semantic="COLOR" source="#${colName}" offset="0" />`;
|
||||
|
||||
}
|
||||
|
||||
let indexArray = null;
|
||||
|
||||
if ( bufferGeometry.index ) {
|
||||
|
||||
indexArray = attrBufferToArray( bufferGeometry.index );
|
||||
|
||||
} else {
|
||||
|
||||
indexArray = new Array( indexCount );
|
||||
|
||||
for ( let i = 0, l = indexArray.length; i < l; i ++ ) indexArray[ i ] = i;
|
||||
|
||||
}
|
||||
|
||||
for ( let i = 0, l = groups.length; i < l; i ++ ) {
|
||||
|
||||
const group = groups[ i ];
|
||||
const subarr = subArray( indexArray, group.start, group.count );
|
||||
const polycount = subarr.length / 3;
|
||||
gnode += `<triangles material="MESH_MATERIAL_${group.materialIndex}" count="${polycount}">`;
|
||||
gnode += triangleInputs;
|
||||
gnode += `<p>${subarr.join( ' ' )}</p>`;
|
||||
gnode += '</triangles>';
|
||||
|
||||
}
|
||||
|
||||
gnode += '</mesh></geometry>';
|
||||
libraryGeometries.push( gnode );
|
||||
info = {
|
||||
meshid: meshid,
|
||||
bufferGeometry: bufferGeometry
|
||||
};
|
||||
geometryInfo.set( g, info );
|
||||
|
||||
}
|
||||
|
||||
return info;
|
||||
|
||||
} // Process the given texture into the image library
|
||||
// Returns the image library
|
||||
|
||||
|
||||
function processTexture( tex ) {
|
||||
|
||||
let texid = imageMap.get( tex );
|
||||
|
||||
if ( texid == null ) {
|
||||
|
||||
texid = `image-${libraryImages.length + 1}`;
|
||||
const ext = 'png';
|
||||
const name = tex.name || texid;
|
||||
let imageNode = `<image id="${texid}" name="${name}">`;
|
||||
|
||||
if ( version === '1.5.0' ) {
|
||||
|
||||
imageNode += `<init_from><ref>${options.textureDirectory}${name}.${ext}</ref></init_from>`;
|
||||
|
||||
} else {
|
||||
|
||||
// version image node 1.4.1
|
||||
imageNode += `<init_from>${options.textureDirectory}${name}.${ext}</init_from>`;
|
||||
|
||||
}
|
||||
|
||||
imageNode += '</image>';
|
||||
libraryImages.push( imageNode );
|
||||
imageMap.set( tex, texid );
|
||||
textures.push( {
|
||||
directory: options.textureDirectory,
|
||||
name,
|
||||
ext,
|
||||
data: imageToData( tex.image, ext ),
|
||||
original: tex
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
return texid;
|
||||
|
||||
} // Process the given material into the material and effect libraries
|
||||
// Returns the material id
|
||||
|
||||
|
||||
function processMaterial( m ) {
|
||||
|
||||
let matid = materialMap.get( m );
|
||||
|
||||
if ( matid == null ) {
|
||||
|
||||
matid = `Mat${libraryEffects.length + 1}`;
|
||||
let type = 'phong';
|
||||
|
||||
if ( m.isMeshLambertMaterial === true ) {
|
||||
|
||||
type = 'lambert';
|
||||
|
||||
} else if ( m.isMeshBasicMaterial === true ) {
|
||||
|
||||
type = 'constant';
|
||||
|
||||
if ( m.map !== null ) {
|
||||
|
||||
// The Collada spec does not support diffuse texture maps with the
|
||||
// constant shader type.
|
||||
// mrdoob/three.js#15469
|
||||
console.warn( 'ColladaExporter: Texture maps not supported with THREE.MeshBasicMaterial.' );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const emissive = m.emissive ? m.emissive : new THREE.Color( 0, 0, 0 );
|
||||
const diffuse = m.color ? m.color : new THREE.Color( 0, 0, 0 );
|
||||
const specular = m.specular ? m.specular : new THREE.Color( 1, 1, 1 );
|
||||
const shininess = m.shininess || 0;
|
||||
const reflectivity = m.reflectivity || 0; // Do not export and alpha map for the reasons mentioned in issue (#13792)
|
||||
// in three.js alpha maps are black and white, but collada expects the alpha
|
||||
// channel to specify the transparency
|
||||
|
||||
let transparencyNode = '';
|
||||
|
||||
if ( m.transparent === true ) {
|
||||
|
||||
transparencyNode += '<transparent>' + ( m.map ? '<texture texture="diffuse-sampler"></texture>' : '<float>1</float>' ) + '</transparent>';
|
||||
|
||||
if ( m.opacity < 1 ) {
|
||||
|
||||
transparencyNode += `<transparency><float>${m.opacity}</float></transparency>`;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const techniqueNode = `<technique sid="common"><${type}>` + '<emission>' + ( m.emissiveMap ? '<texture texture="emissive-sampler" texcoord="TEXCOORD" />' : `<color sid="emission">${emissive.r} ${emissive.g} ${emissive.b} 1</color>` ) + '</emission>' + ( type !== 'constant' ? '<diffuse>' + ( m.map ? '<texture texture="diffuse-sampler" texcoord="TEXCOORD" />' : `<color sid="diffuse">${diffuse.r} ${diffuse.g} ${diffuse.b} 1</color>` ) + '</diffuse>' : '' ) + ( type !== 'constant' ? '<bump>' + ( m.normalMap ? '<texture texture="bump-sampler" texcoord="TEXCOORD" />' : '' ) + '</bump>' : '' ) + ( type === 'phong' ? `<specular><color sid="specular">${specular.r} ${specular.g} ${specular.b} 1</color></specular>` + '<shininess>' + ( m.specularMap ? '<texture texture="specular-sampler" texcoord="TEXCOORD" />' : `<float sid="shininess">${shininess}</float>` ) + '</shininess>' : '' ) + `<reflective><color>${diffuse.r} ${diffuse.g} ${diffuse.b} 1</color></reflective>` + `<reflectivity><float>${reflectivity}</float></reflectivity>` + transparencyNode + `</${type}></technique>`;
|
||||
const effectnode = `<effect id="${matid}-effect">` + '<profile_COMMON>' + ( m.map ? '<newparam sid="diffuse-surface"><surface type="2D">' + `<init_from>${processTexture( m.map )}</init_from>` + '</surface></newparam>' + '<newparam sid="diffuse-sampler"><sampler2D><source>diffuse-surface</source></sampler2D></newparam>' : '' ) + ( m.specularMap ? '<newparam sid="specular-surface"><surface type="2D">' + `<init_from>${processTexture( m.specularMap )}</init_from>` + '</surface></newparam>' + '<newparam sid="specular-sampler"><sampler2D><source>specular-surface</source></sampler2D></newparam>' : '' ) + ( m.emissiveMap ? '<newparam sid="emissive-surface"><surface type="2D">' + `<init_from>${processTexture( m.emissiveMap )}</init_from>` + '</surface></newparam>' + '<newparam sid="emissive-sampler"><sampler2D><source>emissive-surface</source></sampler2D></newparam>' : '' ) + ( m.normalMap ? '<newparam sid="bump-surface"><surface type="2D">' + `<init_from>${processTexture( m.normalMap )}</init_from>` + '</surface></newparam>' + '<newparam sid="bump-sampler"><sampler2D><source>bump-surface</source></sampler2D></newparam>' : '' ) + techniqueNode + ( m.side === THREE.DoubleSide ? '<extra><technique profile="THREEJS"><double_sided sid="double_sided" type="int">1</double_sided></technique></extra>' : '' ) + '</profile_COMMON>' + '</effect>';
|
||||
const materialName = m.name ? ` name="${m.name}"` : '';
|
||||
const materialNode = `<material id="${matid}"${materialName}><instance_effect url="#${matid}-effect" /></material>`;
|
||||
libraryMaterials.push( materialNode );
|
||||
libraryEffects.push( effectnode );
|
||||
materialMap.set( m, matid );
|
||||
|
||||
}
|
||||
|
||||
return matid;
|
||||
|
||||
} // Recursively process the object into a scene
|
||||
|
||||
|
||||
function processObject( o ) {
|
||||
|
||||
let node = `<node name="${o.name}">`;
|
||||
node += getTransform( o );
|
||||
|
||||
if ( o.isMesh === true && o.geometry !== null ) {
|
||||
|
||||
// function returns the id associated with the mesh and a "BufferGeometry" version
|
||||
// of the geometry in case it's not a geometry.
|
||||
const geomInfo = processGeometry( o.geometry );
|
||||
const meshid = geomInfo.meshid;
|
||||
const geometry = geomInfo.bufferGeometry; // ids of the materials to bind to the geometry
|
||||
|
||||
let matids = null;
|
||||
let matidsArray; // get a list of materials to bind to the sub groups of the geometry.
|
||||
// If the amount of subgroups is greater than the materials, than reuse
|
||||
// the materials.
|
||||
|
||||
const mat = o.material || new THREE.MeshBasicMaterial();
|
||||
const materials = Array.isArray( mat ) ? mat : [ mat ];
|
||||
|
||||
if ( geometry.groups.length > materials.length ) {
|
||||
|
||||
matidsArray = new Array( geometry.groups.length );
|
||||
|
||||
} else {
|
||||
|
||||
matidsArray = new Array( materials.length );
|
||||
|
||||
}
|
||||
|
||||
matids = matidsArray.fill().map( ( v, i ) => processMaterial( materials[ i % materials.length ] ) );
|
||||
node += `<instance_geometry url="#${meshid}">` + ( matids != null ? '<bind_material><technique_common>' + matids.map( ( id, i ) => `<instance_material symbol="MESH_MATERIAL_${i}" target="#${id}" >` + '<bind_vertex_input semantic="TEXCOORD" input_semantic="TEXCOORD" input_set="0" />' + '</instance_material>' ).join( '' ) + '</technique_common></bind_material>' : '' ) + '</instance_geometry>';
|
||||
|
||||
}
|
||||
|
||||
o.children.forEach( c => node += processObject( c ) );
|
||||
node += '</node>';
|
||||
return node;
|
||||
|
||||
}
|
||||
|
||||
const geometryInfo = new WeakMap();
|
||||
const materialMap = new WeakMap();
|
||||
const imageMap = new WeakMap();
|
||||
const textures = [];
|
||||
const libraryImages = [];
|
||||
const libraryGeometries = [];
|
||||
const libraryEffects = [];
|
||||
const libraryMaterials = [];
|
||||
const libraryVisualScenes = processObject( object );
|
||||
const specLink = version === '1.4.1' ? 'http://www.collada.org/2005/11/COLLADASchema' : 'https://www.khronos.org/collada/';
|
||||
let dae = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>' + `<COLLADA xmlns="${specLink}" version="${version}">` + '<asset>' + ( '<contributor>' + '<authoring_tool>three.js Collada Exporter</authoring_tool>' + ( options.author !== null ? `<author>${options.author}</author>` : '' ) + '</contributor>' + `<created>${new Date().toISOString()}</created>` + `<modified>${new Date().toISOString()}</modified>` + ( options.unitName !== null ? `<unit name="${options.unitName}" meter="${options.unitMeter}" />` : '' ) + `<up_axis>${options.upAxis}</up_axis>` ) + '</asset>';
|
||||
dae += `<library_images>${libraryImages.join( '' )}</library_images>`;
|
||||
dae += `<library_effects>${libraryEffects.join( '' )}</library_effects>`;
|
||||
dae += `<library_materials>${libraryMaterials.join( '' )}</library_materials>`;
|
||||
dae += `<library_geometries>${libraryGeometries.join( '' )}</library_geometries>`;
|
||||
dae += `<library_visual_scenes><visual_scene id="Scene" name="scene">${libraryVisualScenes}</visual_scene></library_visual_scenes>`;
|
||||
dae += '<scene><instance_visual_scene url="#Scene"/></scene>';
|
||||
dae += '</COLLADA>';
|
||||
const res = {
|
||||
data: format( dae ),
|
||||
textures
|
||||
};
|
||||
|
||||
if ( typeof onDone === 'function' ) {
|
||||
|
||||
requestAnimationFrame( () => onDone( res ) );
|
||||
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
THREE.ColladaExporter = ColladaExporter;
|
||||
|
||||
} )();
|
Loading…
x
Reference in New Issue
Block a user