// TODO test import Geometry from '../Geometry'; import Mesh from '../Mesh'; import Node from '../Node'; import BoundingBox from '../math/BoundingBox'; import vec3 from '../glmatrix/vec3'; import mat4 from '../glmatrix/mat4'; /** * @namespace clay.util.mesh */ var meshUtil = { /** * Merge multiple meshes to one. * Note that these meshes must have the same material * * @param {Array.} meshes * @param {boolean} applyWorldTransform * @return {clay.Mesh} * @memberOf clay.util.mesh */ merge: function (meshes, applyWorldTransform) { if (! meshes.length) { return; } var templateMesh = meshes[0]; var templateGeo = templateMesh.geometry; var material = templateMesh.material; var geometry = new Geometry({ dynamic: false }); geometry.boundingBox = new BoundingBox(); var attributeNames = templateGeo.getEnabledAttributes(); for (var i = 0; i < attributeNames.length; i++) { var name = attributeNames[i]; var attr = templateGeo.attributes[name]; // Extend custom attributes if (!geometry.attributes[name]) { geometry.attributes[name] = attr.clone(false); } } var inverseTransposeMatrix = mat4.create(); // Initialize the array data and merge bounding box var nVertex = 0; var nFace = 0; for (var k = 0; k < meshes.length; k++) { var currentGeo = meshes[k].geometry; if (currentGeo.boundingBox) { currentGeo.boundingBox.applyTransform(applyWorldTransform ? meshes[k].worldTransform : meshes[k].localTransform); geometry.boundingBox.union(currentGeo.boundingBox); } nVertex += currentGeo.vertexCount; nFace += currentGeo.triangleCount; } for (var n = 0; n < attributeNames.length; n++) { var name = attributeNames[n]; var attrib = geometry.attributes[name]; attrib.init(nVertex); } if (nVertex >= 0xffff) { geometry.indices = new Uint32Array(nFace * 3); } else { geometry.indices = new Uint16Array(nFace * 3); } var vertexOffset = 0; var indicesOffset = 0; var useIndices = templateGeo.isUseIndices(); for (var mm = 0; mm < meshes.length; mm++) { var mesh = meshes[mm]; var currentGeo = mesh.geometry; var nVertex = currentGeo.vertexCount; var matrix = applyWorldTransform ? mesh.worldTransform.array : mesh.localTransform.array; mat4.invert(inverseTransposeMatrix, matrix); mat4.transpose(inverseTransposeMatrix, inverseTransposeMatrix); for (var nn = 0; nn < attributeNames.length; nn++) { var name = attributeNames[nn]; var currentAttr = currentGeo.attributes[name]; var targetAttr = geometry.attributes[name]; // Skip the unused attributes; if (!currentAttr.value.length) { continue; } var len = currentAttr.value.length; var size = currentAttr.size; var offset = vertexOffset * size; var count = len / size; for (var i = 0; i < len; i++) { targetAttr.value[offset + i] = currentAttr.value[i]; } // Transform position, normal and tangent if (name === 'position') { vec3.forEach(targetAttr.value, size, offset, count, vec3.transformMat4, matrix); } else if (name === 'normal' || name === 'tangent') { vec3.forEach(targetAttr.value, size, offset, count, vec3.transformMat4, inverseTransposeMatrix); } } if (useIndices) { var len = currentGeo.indices.length; for (var i = 0; i < len; i++) { geometry.indices[i + indicesOffset] = currentGeo.indices[i] + vertexOffset; } indicesOffset += len; } vertexOffset += nVertex; } return new Mesh({ material: material, geometry: geometry }); }, /** * Split mesh into sub meshes, each mesh will have maxJointNumber joints. * @param {clay.Mesh} mesh * @param {number} maxJointNumber * @param {boolean} inPlace * @return {clay.Node} * * @memberOf clay.util.mesh */ // FIXME, Have issues on some models splitByJoints: function (mesh, maxJointNumber, inPlace) { var geometry = mesh.geometry; var skeleton = mesh.skeleton; var material = mesh.material; var joints = mesh.joints; if (!geometry || !skeleton || !joints.length) { return; } if (joints.length < maxJointNumber) { return mesh; } var indices = geometry.indices; var faceLen = geometry.triangleCount; var rest = faceLen; var isFaceAdded = []; var jointValues = geometry.attributes.joint.value; for (var i = 0; i < faceLen; i++) { isFaceAdded[i] = false; } var addedJointIdxPerFace = []; var buckets = []; var getJointByIndex = function (idx) { return joints[idx]; }; while (rest > 0) { var bucketTriangles = []; var bucketJointReverseMap = []; var bucketJoints = []; var subJointNumber = 0; for (var i = 0; i < joints.length; i++) { bucketJointReverseMap[i] = -1; } for (var f = 0; f < faceLen; f++) { if (isFaceAdded[f]) { continue; } var canAddToBucket = true; var addedNumber = 0; for (var i = 0; i < 3; i++) { var idx = indices[f * 3 + i]; for (var j = 0; j < 4; j++) { var jointIdx = jointValues[idx * 4 + j]; if (jointIdx >= 0) { if (bucketJointReverseMap[jointIdx] === -1) { if (subJointNumber < maxJointNumber) { bucketJointReverseMap[jointIdx] = subJointNumber; bucketJoints[subJointNumber++] = jointIdx; addedJointIdxPerFace[addedNumber++] = jointIdx; } else { canAddToBucket = false; } } } } } if (!canAddToBucket) { // Reverse operation for (var i = 0; i < addedNumber; i++) { bucketJointReverseMap[addedJointIdxPerFace[i]] = -1; bucketJoints.pop(); subJointNumber--; } } else { bucketTriangles.push(indices.subarray(f * 3, (f + 1) * 3)); isFaceAdded[f] = true; rest--; } } buckets.push({ triangles: bucketTriangles, joints: bucketJoints.map(getJointByIndex), jointReverseMap: bucketJointReverseMap }); } var root = new Node({ name: mesh.name }); var attribNames = geometry.getEnabledAttributes(); attribNames.splice(attribNames.indexOf('joint'), 1); // Map from old vertex index to new vertex index var newIndices = []; for (var b = 0; b < buckets.length; b++) { var bucket = buckets[b]; var jointReverseMap = bucket.jointReverseMap; var subJointNumber = bucket.joints.length; var subGeo = new Geometry(); var subMesh = new Mesh({ name: [mesh.name, i].join('-'), // DON'T clone material. material: material, geometry: subGeo, skeleton: skeleton, joints: bucket.joints.slice() }); var nVertex = 0; var nVertex2 = geometry.vertexCount; for (var i = 0; i < nVertex2; i++) { newIndices[i] = -1; } // Count sub geo number for (var f = 0; f < bucket.triangles.length; f++) { var face = bucket.triangles[f]; for (var i = 0; i < 3; i++) { var idx = face[i]; if (newIndices[idx] === -1) { newIndices[idx] = nVertex; nVertex++; } } } for (var a = 0; a < attribNames.length; a++) { var attribName = attribNames[a]; var subAttrib = subGeo.attributes[attribName]; subAttrib.init(nVertex); } subGeo.attributes.joint.value = new Float32Array(nVertex * 4); if (nVertex > 0xffff) { subGeo.indices = new Uint32Array(bucket.triangles.length * 3); } else { subGeo.indices = new Uint16Array(bucket.triangles.length * 3); } var indicesOffset = 0; nVertex = 0; for (var i = 0; i < nVertex2; i++) { newIndices[i] = -1; } for (var f = 0; f < bucket.triangles.length; f++) { var triangle = bucket.triangles[f]; for (var i = 0; i < 3; i++) { var idx = triangle[i]; if (newIndices[idx] === -1) { newIndices[idx] = nVertex; for (var a = 0; a < attribNames.length; a++) { var attribName = attribNames[a]; var attrib = geometry.attributes[attribName]; var subAttrib = subGeo.attributes[attribName]; var size = attrib.size; for (var j = 0; j < size; j++) { subAttrib.value[nVertex * size + j] = attrib.value[idx * size + j]; } } for (var j = 0; j < 4; j++) { var jointIdx = geometry.attributes.joint.value[idx * 4 + j]; var offset = nVertex * 4 + j; if (jointIdx >= 0) { subGeo.attributes.joint.value[offset] = jointReverseMap[jointIdx]; } else { subGeo.attributes.joint.value[offset] = -1; } } nVertex++; } subGeo.indices[indicesOffset++] = newIndices[idx]; } } subGeo.updateBoundingBox(); root.add(subMesh); } var children = mesh.children(); for (var i = 0; i < children.length; i++) { root.add(children[i]); } root.position.copy(mesh.position); root.rotation.copy(mesh.rotation); root.scale.copy(mesh.scale); if (inPlace) { if (mesh.getParent()) { var parent = mesh.getParent(); parent.remove(mesh); parent.add(root); } } return root; } }; export default meshUtil;