// 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.<clay.Mesh>} 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;