对 dae
格式的三维模型进行合并,第一个函数对模型类型分类后合并,第二个函数对同一类的模型合并,第三个函数对模型的几何体合并;使用了 threejs
类库和 jQuery
类库的方法。合并后的模型由于数据没有共用,模型体积比合并之前的多个模型体积总和大;合并后的模型在渲染时比合并之前的多个模型减少了渲染的计算和处理次数,渲染速度比较快。下面是参考的代码:
// 模型操作类
var ObjectHelper = function () {
this.objectTag = 'ObjectHelper';
}
// 合并模型
ObjectHelper.prototype.mergeObjectByType = function ( obj, refer ) {
if ( ! obj || ! refer || ! refer.mergeObjects || ! refer.mergeBufferGeometry ) return null;
console.time( '合并模型 ' + obj.name );
var objAll, objCopy, matrixTemp;
objAll = {
'Mesh': [],
'SkinnedMesh': [],
'Line': [],
'LineSegments': [],
'other': []
};
obj.updateMatrixWorld( true );
obj.traverse( function ( child ) {
objCopy = child.clone( false );
matrixTemp = objCopy.matrix.clone();
matrixTemp.getInverse( matrixTemp.clone() );
objCopy.applyMatrix( matrixTemp.clone() );
objCopy.applyMatrix( child.matrixWorld.clone() );
if ( child.type === 'Mesh' ) {
objCopy.name = '[已合并Mesh] ' + objCopy.name;
objAll[ child.type ].push( objCopy );
} else if ( child.type === 'SkinnedMesh' ) {
objCopy.name = '[已合并SkinnedMesh] ' + objCopy.name;
objAll[ child.type ].push( objCopy );
} else if ( child.type === 'Line' ) {
objCopy.name = '[已合并Line] ' + objCopy.name;
objAll[ child.type ].push( objCopy );
} else if ( child.type === 'LineSegments' ) {
objCopy.name = '[已合并LineSegments] ' + objCopy.name;
objAll[ child.type ].push( objCopy );
} else if ( child.type !== 'Group' ) {
objCopy.name = '[未合并] ' + objCopy.name;
objAll[ 'other' ].push( objCopy );
}
} );
var group = new THREE.Group();
for ( var type in objAll ) {
if ( objAll[ type ].length < 1 ) continue;
if ( type === 'other' ) {
for ( var i = 0, l = objAll[ type ].length; i < l; i++ ) {
group.add( objAll[ type ][ i ] );
}
} else {
objCopy = refer.mergeObjects( objAll[ type ], refer );
objCopy.name = objAll[ type ][ 0 ][ 'name' ];
$.extend( objCopy.userData, objAll[ type ][ 0 ][ 'userData' ] );
group.add( objCopy );
}
}
group.name = obj.name;
$.extend( group.userData, obj.userData );
console.timeEnd( '合并模型 ' + obj.name );
return group;
}
// 合并某个类型的所有模型
ObjectHelper.prototype.mergeObjects = function ( objs, refer ) {
if ( ! objs || objs.length < 1 || ! refer || ! refer.mergeBufferGeometry ) return null;
var type = objs[ 0 ].type, geometries = [], materials = {}, materialsArr = [],
groups, materialTemp, geometryTemp, count = 0, countTemp = 0, startTemp,
uvAttr = false, vertices, uvs, geometry = new THREE.BufferGeometry(),
geometry2, midNow;
if ( type === 'Mesh' || type === 'SkinnedMesh' )
materialsArr.push( new THREE.MeshBasicMaterial( { side: THREE.BackSide, name: '默认背面材质' } ) );
for ( var i = 0, l = objs.length; i < l; i++ ) {
materialTemp = objs[ i ].material;
geometryTemp = objs[ i ].geometry.clone();
geometryTemp.applyMatrix( objs[ i ].matrix.clone() );
objs[ i ].geometry.dispose();
if ( geometryTemp.vertices ) {
countTemp = geometryTemp.vertices.length;
if ( objs[i].geometry.faceVertexUvs ) uvAttr = true;
} else {
countTemp = geometryTemp.attributes.position.count;
if ( objs[i].geometry.attributes.uv ) uvAttr = true;
}
if ( geometryTemp.groups.length === 0 ) {
geometries.push( {
geo: geometryTemp,
start: 0,
count: countTemp,
mid: materialTemp.id
} );
count += countTemp;
} else {
groups = geometryTemp.groups;
for ( var j = 0, jl = groups.length; j < jl; j++ ) {
geometries.push( {
geo: geometryTemp,
start: groups[ j ].start,
count: groups[ j ].count,
mid: materialTemp.id ? materialTemp.id : materialTemp[ groups[ j ].materialIndex ].id
} );
count += groups[ j ].count;
}
}
if ( materialTemp.id ) {
materials[ materialTemp.id ] = materialTemp;
if ( materialTemp.transparent ) {
materialTemp.side = THREE.DoubleSide;
if ( materialTemp.opacity === 1 ) materialTemp.opacity = 0.6;
}
} else {
for ( var j = 0, jl = materialTemp.length; j < jl; j++ ) {
materials[ materialTemp[ j ].id ] = materialTemp[ j ];
if ( materialTemp[ j ].transparent ) {
materialTemp[ j ].side = THREE.DoubleSide;
if ( materialTemp[ j ].opacity === 1 ) materialTemp[ j ].opacity = 0.6;
}
}
}
}
vertices = new Float32Array( count * 3 );
uvs = new Float32Array( count * 2 );
geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
if ( uvAttr ) geometry.addAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) );
geometry.computeVertexNormals();
geometry2 = geometry.clone();
count = 0;
while ( geometries.length > 0 ) {
midNow = geometries[ 0 ].mid;
startTemp = count;
countTemp = 0;
for ( var i = 0, l = geometries.length; i < l; i++ ) {
if ( geometries[ i ].mid === midNow ) {
if ( geometries[ i ].geo.isBufferGeometry ) {
refer.mergeBufferGeometry( geometry, count, geometries[ i ].geo, geometries[ i ].start, geometries[ i ].count );
} else {
geometry2.fromGeometry( geometries[ i ].geo );
refer.mergeBufferGeometry( geometry, count, geometry2, geometries[ i ].start, geometries[ i ].count );
}
count += geometries[ i ].count;
countTemp += geometries[ i ].count;
geometries[ i ].geo.dispose();
geometries.splice( i, 1 );
i--;
l--;
}
}
if ( type === 'Mesh' || type === 'SkinnedMesh' ) {
if ( ! materials[ midNow ].transparent )
geometry.addGroup(
startTemp,
countTemp,
0
);
}
console.log( '合并模型:', materials[ midNow ].name );
materialsArr.push( materials[ midNow ] );
materials[ midNow ] = materialsArr.length - 1;
geometry.addGroup(
startTemp,
countTemp,
materials[ midNow ]
);
}
var obj;
if ( materialsArr.length === 1 ) {
obj = new THREE[ type ]( geometry, materialsArr[ 0 ] );
} else {
obj = new THREE[ type ]( geometry, materialsArr );
}
return obj;
}
// 合并几何体
ObjectHelper.prototype.mergeBufferGeometry = function ( geometry1, offset, geometry2, start, count ) {
if (
! geometry1 || ! geometry1.isBufferGeometry || ! geometry2 || ! geometry2.isBufferGeometry
|| offset < 0 || start < 0 || count < 0
) return null;
var attributes1 = geometry1.attributes, attributes2 = geometry2.attributes,
attribute1, attributeArray1, attribute2, attributeArray2, attributeOffset, length,
attributeStart, attributeCount;
for ( var key in attributes1 ) {
if ( attributes2[ key ] === undefined ) continue;
attribute1 = attributes1[ key ];
attributeArray1 = attribute1.array;
attribute2 = attributes2[ key ];
attributeArray2 = attribute2.array;
attributeOffset = attribute1.itemSize * offset;
attributeStart = attribute2.itemSize * start;
attributeCount = attribute2.itemSize * count;
length = Math.min( attributeCount, attributeArray1.length - attributeOffset );
for ( var i = attributeStart, j = attributeOffset, z = 0; z < length; i ++, j ++, z ++ ) {
attributeArray1[ j ] = attributeArray2[ i ];
}
}
return geometry1;
}