public static void WriteMorphDataToFile(MCS_Utilities.Morph.MorphData morphData, string filePath, bool refresh = false, bool compress = false) { if (!File.Exists(filePath) || refresh) { //FYI, I first tried a JSON ascii format but the file size was way too big, thus we're using a binary struct format //The blendshape cereal structure takes care of very efficiently packing a blendshape (about 28x byte compression) byte[] bytes = MCS_Utilities.Morph.MorphData.ConvertMorphDataToBytes(morphData); if (compress) { Stream ms = new MemoryStream(bytes); ms.Position = 0; Stream outStream = Compression.CompressStream(ms); outStream.Position = 0; bytes = Compression.StreamToBytes(outStream); ms.Close(); outStream.Close(); } System.IO.File.WriteAllBytes(filePath, bytes); } }
/// <summary> /// Rips out blendshapes from a skinned mesh renderer and figures out where to store it along with creating a manifest file /// </summary> /// <param name="smr"></param> /// <returns>A string path to the manifest file</returns> public static string ExtractBlendshapesFromMesh(SkinnedMeshRenderer smr, string dirPath, int totalProcessed = 0, int totalCount = 0, bool useProgressBar = true, bool generateManifest = true, NameScrubCallback smrScrub = null, NameScrubCallback morphScrub = null, List <string> nameWhitelist = null) { //Extracts all blendshapes out of the mesh, does not remove any of them from the mesh int blendshapeCount = smr.sharedMesh.blendShapeCount; string manifestPath = dirPath + "/manifest.json"; MorphManifest manifest = new MorphManifest(); manifest.name = smr.name; manifest.count = blendshapeCount; manifest.names = new string[blendshapeCount]; if (smrScrub != null) { manifest.name = smrScrub(manifest.name); } if (!Directory.Exists(dirPath)) { DirectoryInfo di = Directory.CreateDirectory(dirPath); } for (int i = 0; i < blendshapeCount; i++) { BlendshapeData bd = new BlendshapeData(); bd.name = smr.sharedMesh.GetBlendShapeName(i); if (morphScrub != null) { bd.name = morphScrub(bd.name); } bd.shapeIndex = i; int vertexCount = smr.sharedMesh.vertexCount; bd.deltaVertices = new Vector3[vertexCount]; bd.deltaNormals = new Vector3[vertexCount]; bd.deltaTangents = new Vector3[vertexCount]; //loads the blendshape data from the blendshape into our blendshapestate struct smr.sharedMesh.GetBlendShapeFrameVertices(bd.shapeIndex, bd.frameIndex, bd.deltaVertices, bd.deltaNormals, bd.deltaTangents); //convert a blendshape name from something like Genesis2Male__FBMHeavy to FBMHeavy int bdIndex = bd.name.LastIndexOf("__"); if (bdIndex > -1) { bd.name = bd.name.Substring(bdIndex + 2); } if (nameWhitelist != null && nameWhitelist.Count > 0) { if (!nameWhitelist.Contains(bd.name)) { continue; } else { UnityEngine.Debug.Log("Matched: " + bd.name); } } float percent = 0f; if (totalCount > 0) { percent = (float)totalProcessed / (float)totalCount; } if (useProgressBar) { //TODO: we need to move this back into editor land, as this function is ONLY used if you're using the production tool suite //EditorUtility.DisplayProgressBar("Extracting Blends", "Blend: " + bd.name, percent); } string relativePath = bd.name + "." + extension; string filePath = dirPath + "/" + relativePath; MCS_Utilities.Morph.MorphData morphData = new MCS_Utilities.Morph.MorphData(); morphData.name = bd.name; morphData.blendshapeData = bd; WriteMorphDataToFile(morphData, filePath, true, false); manifest.names[i] = bd.name; totalProcessed += 1; } if (generateManifest) { Stream fs = File.Create(manifestPath); string json = JsonUtility.ToJson(manifest); byte[] bytes = System.Text.Encoding.UTF8.GetBytes(json); fs.Write(bytes, 0, bytes.Length); fs.Close(); } return(manifestPath); }
protected static char jctKeySeperatorChar = '|'; //used for splitting the name late /// <summary> /// Efficiently packs a morph into a hardcoded binary packed format /// Using a dataset of about 30k verts... /// C# native deserializer - 75ms /// ProtoBuf deserializer - 30ms /// Custom deserializer - 0ms /// </summary> public static byte[] ConvertMorphDataToBytes(MorphData morphData, int version = 1) { //Build the header, it's format is the following, note floats are all 4 bytes (single precision) // quick note, all lengths are of size "int" to keep things simple, saving a few extra bytes isn't worth worrying about it byte[] buffer; /* * * HEADER * * Magic Bytes - Verifies this is the correct file type (int) * Version (float) - This is the DATA STRUCTURE version, if we update the structure, we need to update the version * Length of Name (int) * Length of Delta Verts (int) * Length of Delta Norms (int) * Length of Delta Tans (int) * Length of JCT Keys (int) (about 170+ entries of variable length names) * Length of JCTs (int) * * BODY * * Name (string) * Verts (uint32+3floats) * Norms (uint32+3floats) * Tans (uint32+3floats) * JCT Names (string split on "|") * JCTs (6floats) * */ MorphDataHeaderV1 header = new MorphDataHeaderV1(); header.version = version; byte[] morphNameAsBytes = System.Text.Encoding.UTF8.GetBytes(morphData.name); header.lenName = morphNameAsBytes.Length; Vector3Spatial[] deltaVertices = Vector3Spatial.ConvertVector3ArrayToSpatialArray(morphData.blendshapeData.deltaVertices); Vector3Spatial[] deltaNormals = Vector3Spatial.ConvertVector3ArrayToSpatialArray(morphData.blendshapeData.deltaNormals); Vector3Spatial[] deltaTangents = Vector3Spatial.ConvertVector3ArrayToSpatialArray(morphData.blendshapeData.deltaTangents); header.lenDeltaVertices = (morphData.blendshapeData.deltaVertices != null ? morphData.blendshapeData.deltaVertices.Length : 0); header.lenDeltaNormals = (morphData.blendshapeData.deltaNormals != null ? morphData.blendshapeData.deltaNormals.Length : 0); header.lenDeltaTangents = (morphData.blendshapeData.deltaTangents != null ? morphData.blendshapeData.deltaTangents.Length : 0); header.lenDeltaVerticesPacked = (deltaVertices != null ? deltaVertices.Length : 0); header.lenDeltaNormalsPacked = (deltaNormals != null ? deltaNormals.Length : 0); header.lenDeltaTangentsPacked = (deltaTangents != null ? deltaTangents.Length : 0); header.lenJCTKeys = 0; header.lenJCTs = 0; StringBuilder sbJCTKeys = new StringBuilder(); byte[] jctKeys = null; //only do this if we have jct data in here if (morphData.jctData.boneNames != null && morphData.jctData.boneNames.Length > 0) { header.lenJCTs = (ushort)morphData.jctData.localPositions.Length; for (int i = 0; i < morphData.jctData.boneNames.Length; i++) { sbJCTKeys.Append(morphData.jctData.boneNames[i]); if (i + 1 < morphData.jctData.boneNames.Length) { sbJCTKeys.Append(jctKeySeperator); } } jctKeys = System.Text.Encoding.UTF8.GetBytes(sbJCTKeys.ToString()); header.lenJCTKeys = jctKeys.Length; //6 floats per item, key is the same as the name order header.lenJCTs = ((6 * sizeOfFloat) * morphData.jctData.localPositions.Length); } //now that we know the header, how big should our buffer be? int bufferSize = 0; bufferSize += sizeOfInt; //magic header bufferSize += sizeOfInt; //version bufferSize += sizeOfInt + header.lenName; bufferSize += sizeOfInt + (header.lenDeltaVerticesPacked * sizeOfInt) + (header.lenDeltaVerticesPacked * (3 * sizeOfFloat)); bufferSize += sizeOfInt + (header.lenDeltaNormalsPacked * sizeOfInt) + (header.lenDeltaNormalsPacked * (3 * sizeOfFloat)); bufferSize += sizeOfInt + (header.lenDeltaTangentsPacked * sizeOfInt) + (header.lenDeltaTangentsPacked * (3 * sizeOfFloat)); bufferSize += (sizeOfInt * 3); //packed lengths bufferSize += sizeOfInt + header.lenJCTKeys; bufferSize += sizeOfInt + header.lenJCTs; //bufferSize += sizeOfInt + (6 * sizeOfFloat); buffer = new byte[bufferSize]; byte[] itemBuffer; int pos = 0; //write the header first //magic itemBuffer = System.BitConverter.GetBytes(magicHeader); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; //version itemBuffer = System.BitConverter.GetBytes(header.version); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; //name itemBuffer = System.BitConverter.GetBytes(header.lenName); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; //verts (unpacked) itemBuffer = System.BitConverter.GetBytes(header.lenDeltaVertices); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; //normals itemBuffer = System.BitConverter.GetBytes(header.lenDeltaNormals); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; //tangents itemBuffer = System.BitConverter.GetBytes(header.lenDeltaTangents); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; //packed itemBuffer = System.BitConverter.GetBytes(header.lenDeltaVerticesPacked); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; itemBuffer = System.BitConverter.GetBytes(header.lenDeltaNormalsPacked); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; itemBuffer = System.BitConverter.GetBytes(header.lenDeltaTangentsPacked); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; //JCT keys itemBuffer = System.BitConverter.GetBytes(header.lenJCTKeys); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; //JCTs itemBuffer = System.BitConverter.GetBytes(header.lenJCTs); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; //header is done, now let's write the body //morph name System.Buffer.BlockCopy(morphNameAsBytes, 0, buffer, pos, morphNameAsBytes.Length); pos += morphNameAsBytes.Length; //deltas if (deltaVertices != null) { for (int i = 0; i < deltaVertices.Length; i++) { itemBuffer = System.BitConverter.GetBytes(deltaVertices[i].index); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; itemBuffer = System.BitConverter.GetBytes(deltaVertices[i].x); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; itemBuffer = System.BitConverter.GetBytes(deltaVertices[i].y); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; itemBuffer = System.BitConverter.GetBytes(deltaVertices[i].z); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; } } if (deltaNormals != null) { for (int i = 0; i < deltaNormals.Length; i++) { itemBuffer = System.BitConverter.GetBytes(deltaVertices[i].index); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; itemBuffer = System.BitConverter.GetBytes(deltaNormals[i].x); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; itemBuffer = System.BitConverter.GetBytes(deltaNormals[i].y); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; itemBuffer = System.BitConverter.GetBytes(deltaNormals[i].z); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; } } if (deltaTangents != null) { for (int i = 0; i < deltaTangents.Length; i++) { itemBuffer = System.BitConverter.GetBytes(deltaVertices[i].index); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; itemBuffer = System.BitConverter.GetBytes(deltaTangents[i].x); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; itemBuffer = System.BitConverter.GetBytes(deltaTangents[i].y); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; itemBuffer = System.BitConverter.GetBytes(deltaTangents[i].z); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; } } //JCT if (jctKeys != null) { //JCT keys System.Buffer.BlockCopy(jctKeys, 0, buffer, pos, jctKeys.Length); pos += jctKeys.Length; //JCTs for (int i = 0; i < morphData.jctData.localPositions.Length; i++) { itemBuffer = System.BitConverter.GetBytes(morphData.jctData.localPositions[i].x); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; itemBuffer = System.BitConverter.GetBytes(morphData.jctData.localPositions[i].y); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; itemBuffer = System.BitConverter.GetBytes(morphData.jctData.localPositions[i].z); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; itemBuffer = System.BitConverter.GetBytes(morphData.jctData.worldPositions[i].x); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; itemBuffer = System.BitConverter.GetBytes(morphData.jctData.worldPositions[i].y); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; itemBuffer = System.BitConverter.GetBytes(morphData.jctData.worldPositions[i].z); System.Buffer.BlockCopy(itemBuffer, 0, buffer, pos, itemBuffer.Length); pos += itemBuffer.Length; } } return(buffer); }
public static MorphData ConvertBytesToMorphData(byte[] buffer) { MorphData md = new MorphData(); int pos = 0; //magic bytes int magicHeaderIn = System.BitConverter.ToInt32(buffer, pos); pos += sizeOfInt; if (magicHeader != magicHeaderIn) { throw new Exception("Magic morph header not found, invalid morph file"); } int version = System.BitConverter.ToInt32(buffer, pos); pos += sizeOfInt; switch (version) { case 1: //Version 1 MorphDataHeaderV1 header = new MorphDataHeaderV1(); header.version = version; header.lenName = System.BitConverter.ToInt32(buffer, pos); pos += sizeOfInt; header.lenDeltaVertices = System.BitConverter.ToInt32(buffer, pos); pos += sizeOfInt; header.lenDeltaNormals = System.BitConverter.ToInt32(buffer, pos); pos += sizeOfInt; header.lenDeltaTangents = System.BitConverter.ToInt32(buffer, pos); pos += sizeOfInt; header.lenDeltaVerticesPacked = System.BitConverter.ToInt32(buffer, pos); pos += sizeOfInt; header.lenDeltaNormalsPacked = System.BitConverter.ToInt32(buffer, pos); pos += sizeOfInt; header.lenDeltaTangentsPacked = System.BitConverter.ToInt32(buffer, pos); pos += sizeOfInt; header.lenJCTKeys = System.BitConverter.ToInt32(buffer, pos); pos += sizeOfInt; header.lenJCTs = System.BitConverter.ToInt32(buffer, pos); pos += sizeOfInt; //name byte[] nameBytes = new byte[header.lenName]; System.Buffer.BlockCopy(buffer, pos, nameBytes, 0, header.lenName); md.name = System.Text.Encoding.UTF8.GetString(nameBytes); md.blendshapeData.name = md.name; pos += header.lenName; Vector3Spatial[] deltaVerticesCereal = new Vector3Spatial[header.lenDeltaVerticesPacked]; Vector3Spatial[] deltaNormalsCereal = new Vector3Spatial[header.lenDeltaNormalsPacked]; Vector3Spatial[] deltaTangentsCereal = new Vector3Spatial[header.lenDeltaTangentsPacked]; for (int i = 0; i < header.lenDeltaVerticesPacked; i++) { deltaVerticesCereal[i].index = System.BitConverter.ToInt32(buffer, pos); pos += sizeOfInt; deltaVerticesCereal[i].x = System.BitConverter.ToSingle(buffer, pos); pos += sizeOfFloat; deltaVerticesCereal[i].y = System.BitConverter.ToSingle(buffer, pos); pos += sizeOfFloat; deltaVerticesCereal[i].z = System.BitConverter.ToSingle(buffer, pos); pos += sizeOfFloat; } for (int i = 0; i < header.lenDeltaNormalsPacked; i++) { deltaNormalsCereal[i].index = System.BitConverter.ToInt32(buffer, pos); pos += sizeOfInt; deltaNormalsCereal[i].x = System.BitConverter.ToSingle(buffer, pos); pos += sizeOfFloat; deltaNormalsCereal[i].y = System.BitConverter.ToSingle(buffer, pos); pos += sizeOfFloat; deltaNormalsCereal[i].z = System.BitConverter.ToSingle(buffer, pos); pos += sizeOfFloat; } for (int i = 0; i < header.lenDeltaTangentsPacked; i++) { deltaTangentsCereal[i].index = System.BitConverter.ToInt32(buffer, pos); pos += sizeOfInt; deltaTangentsCereal[i].x = System.BitConverter.ToSingle(buffer, pos); pos += sizeOfFloat; deltaTangentsCereal[i].y = System.BitConverter.ToSingle(buffer, pos); pos += sizeOfFloat; deltaTangentsCereal[i].z = System.BitConverter.ToSingle(buffer, pos); pos += sizeOfFloat; } //blendshape states allow nulls for normals and tangents, but not vertices, by doing this check we reduce the liklihood we need to expand the heap md.blendshapeData.deltaVertices = new Vector3[header.lenDeltaVertices]; Vector3Spatial.ConvertVector3CerealToArrayNonAlloc(deltaVerticesCereal, md.blendshapeData.deltaVertices); if (header.lenDeltaNormalsPacked > 0) { md.blendshapeData.deltaNormals = new Vector3[header.lenDeltaNormals]; Vector3Spatial.ConvertVector3CerealToArrayNonAlloc(deltaNormalsCereal, md.blendshapeData.deltaNormals); } if (header.lenDeltaTangentsPacked > 0) { md.blendshapeData.deltaTangents = new Vector3[header.lenDeltaTangents]; Vector3Spatial.ConvertVector3CerealToArrayNonAlloc(deltaTangentsCereal, md.blendshapeData.deltaTangents); } //finally let's read jcts if we have them if (header.lenJCTKeys > 0) { byte[] JCTKeysBytes = new byte[header.lenJCTKeys]; System.Buffer.BlockCopy(buffer, pos, JCTKeysBytes, 0, header.lenJCTKeys); string JCTKeysString = System.Text.Encoding.UTF8.GetString(JCTKeysBytes); pos += header.lenJCTKeys; md.jctData.boneNames = JCTKeysString.Split(jctKeySeperatorChar); int totalJCTCount = header.lenJCTKeys / (6 * sizeOfFloat); md.jctData.localPositions = new Vector3[totalJCTCount]; md.jctData.worldPositions = new Vector3[totalJCTCount]; if (header.lenJCTs > 0) { for (int i = 0; i < totalJCTCount; i++) { md.jctData.localPositions[i].x = System.BitConverter.ToSingle(buffer, pos); pos += sizeOfFloat; md.jctData.localPositions[i].y = System.BitConverter.ToSingle(buffer, pos); pos += sizeOfFloat; md.jctData.localPositions[i].z = System.BitConverter.ToSingle(buffer, pos); pos += sizeOfFloat; } for (int i = 0; i < totalJCTCount; i++) { md.jctData.worldPositions[i].x = System.BitConverter.ToSingle(buffer, pos); pos += sizeOfFloat; md.jctData.worldPositions[i].y = System.BitConverter.ToSingle(buffer, pos); pos += sizeOfFloat; md.jctData.worldPositions[i].z = System.BitConverter.ToSingle(buffer, pos); pos += sizeOfFloat; } } } break; default: UnityEngine.Debug.LogError("Version: " + version + " is not supported, please verify you have the most up-to-date version of MCS"); throw new Exception("Unknown morph file version"); break; } return(md); }