public bool Create(SKNFile file, Logger logger) { // This function converts the handedness of the DirectX style input data // into the handedness OpenGL expects. // So, vector inputs have their Z value negated and quaternion inputs have their // Z and W values negated. List<float> vertexPositions = new List<float>(); List<float> vertexNormals = new List<float>(); List<float> vertexTextureCoordinates = new List<float>(); for (int i = 0; i < file.numVertices; ++i) { vertexPositions.Add(file.vertices[i].position[0]); vertexPositions.Add(file.vertices[i].position[1]); vertexPositions.Add(-file.vertices[i].position[2]); vertexNormals.Add(file.vertices[i].normal[0]); vertexNormals.Add(file.vertices[i].normal[1]); vertexNormals.Add(-file.vertices[i].normal[2]); vertexTextureCoordinates.Add(file.vertices[i].texCoords[0]); vertexTextureCoordinates.Add(file.vertices[i].texCoords[1]); } List<uint> iData = new List<uint>(); for (int i = 0; i < numIndices; ++i) { iData.Add((uint)file.indices[i]); } return Create(vertexPositions, vertexNormals, vertexTextureCoordinates, iData, logger); }
// // Helper Functions. // (Because nested Try/Catch looks nasty in one function block.) // private static bool ReadBinary(MemoryStream input, ref SKNFile data, Logger logger) { bool result = true; try { BinaryReader myFile = new BinaryReader(input); result = ReadData(myFile, ref data, logger); myFile.Close(); } catch (Exception e) { logger.Error("Unable to open binary reader."); logger.Error(e.Message); result = false; } return(result); }
// // Helper Functions. // (Because nested Try/Catch looks nasty in one function block.) // private static bool ReadBinary(MemoryStream input, ref SKNFile data, Logger logger) { bool result = true; try { BinaryReader myFile = new BinaryReader(input); result = ReadData(myFile, ref data, logger); myFile.Close(); } catch(Exception e) { logger.Error("Unable to open binary reader."); logger.Error(e.Message); result = false; } return result; }
/// <summary> /// Read in binary .skn file from RAF. /// </summary> /// <param name="file">The file.</param> /// <param name="data">The contents of the file are stored in here.</param> /// <returns></returns> public static bool Read(RAFFileListEntry file, ref SKNFile data, Logger logger) { bool result = true; logger.Event("Reading skn: " + file.FileName); try { // Get the data from the archive MemoryStream myInput = new MemoryStream(file.GetContent()); result = ReadBinary(myInput, ref data, logger); myInput.Close(); } catch (Exception e) { logger.Error("Unable to open memory stream: " + file.FileName); logger.Error(e.Message); result = false; } return(result); }
/// <summary> /// Read in binary .skn file from RAF. /// </summary> /// <param name="file">The file.</param> /// <param name="data">The contents of the file are stored in here.</param> /// <returns></returns> public static bool Read(IFileEntry file, ref SKNFile data, Logger logger) { bool result = true; logger.Event("Reading skn: " + file.FileName); try { // Get the data from the archive MemoryStream myInput = new MemoryStream( file.GetContent() ); result = ReadBinary(myInput, ref data, logger); myInput.Close(); } catch(Exception e) { logger.Error("Unable to open memory stream: " + file.FileName); logger.Error(e.Message); result = false; } return result; }
private static bool ReadData(BinaryReader file, ref SKNFile data, Logger logger) { bool result = true; try { // File Header Information. data.magic = file.ReadInt32(); data.version = file.ReadInt16(); data.numObjects = file.ReadInt16(); if (data.version == 1 || data.version == 2) { // Contains material headers. data.numMaterialHeaders = file.ReadInt32(); for (int i = 0; i < data.numMaterialHeaders; ++i) { // Read in the headers. SKNMaterial header = new SKNMaterial(); header.name = new String(file.ReadChars(SKNMaterial.MATERIAL_NAME_SIZE)); header.startVertex = file.ReadInt32(); header.numVertices = file.ReadInt32(); header.startIndex = file.ReadInt32(); header.numIndices = file.ReadInt32(); data.materialHeaders.Add(header); } // Read in model data. data.numIndices = file.ReadInt32(); data.numVertices = file.ReadInt32(); for (int i = 0; i < data.numIndices; ++i) { data.indices.Add(file.ReadInt16()); } for (int i = 0; i < data.numVertices; ++i) { SKNVertex vertex = new SKNVertex(); vertex.position[0] = file.ReadSingle(); // x vertex.position[1] = file.ReadSingle(); // y vertex.position[2] = file.ReadSingle(); // z for (int j = 0; j < SKNVertex.BONE_INDEX_SIZE; ++j) { int bone = (int)file.ReadByte(); vertex.boneIndex[j] = bone; } vertex.weights[0] = file.ReadSingle(); vertex.weights[1] = file.ReadSingle(); vertex.weights[2] = file.ReadSingle(); vertex.weights[3] = file.ReadSingle(); vertex.normal[0] = file.ReadSingle(); // x vertex.normal[1] = file.ReadSingle(); // y vertex.normal[2] = file.ReadSingle(); // z vertex.texCoords[0] = file.ReadSingle(); // u vertex.texCoords[1] = file.ReadSingle(); // v data.vertices.Add(vertex); } // Data exclusive to version two. if (data.version == 2) { data.endTab.Add(file.ReadInt32()); data.endTab.Add(file.ReadInt32()); data.endTab.Add(file.ReadInt32()); } } // Unknown Version else { logger.Error("Unknown skn version: " + data.version); result = false; } } catch (Exception e) { logger.Error("Skn reading error."); logger.Error(e.Message); result = false; } logger.Event("Magic: " + data.magic); logger.Event("Version: " + data.version); logger.Event("Number of Objects: " + data.numObjects); logger.Event("Number of Material Headers: " + data.numMaterialHeaders); logger.Event("Number of Vertices: " + data.numVertices); logger.Event("Number of Indices: " + data.numIndices); return(result); }
private static bool ReadData(BinaryReader file, ref SKNFile data, Logger logger) { bool result = true; try { // File Header Information. data.magic = file.ReadInt32(); data.version = file.ReadInt16(); data.numObjects = file.ReadInt16(); if (data.version == 1 || data.version == 2) { // Contains material headers. data.numMaterialHeaders = file.ReadInt32(); for (int i = 0; i < data.numMaterialHeaders; ++i) { // Read in the headers. SKNMaterial header = new SKNMaterial(); header.name = new String(file.ReadChars(SKNMaterial.MATERIAL_NAME_SIZE)); header.startVertex = file.ReadInt32(); header.numVertices = file.ReadInt32(); header.startIndex = file.ReadInt32(); header.numIndices = file.ReadInt32(); data.materialHeaders.Add(header); } // Read in model data. data.numIndices = file.ReadInt32(); data.numVertices = file.ReadInt32(); for (int i = 0; i < data.numIndices; ++i) { data.indices.Add(file.ReadInt16()); } for (int i = 0; i < data.numVertices; ++i) { SKNVertex vertex = new SKNVertex(); vertex.position[0] = file.ReadSingle(); // x vertex.position[1] = file.ReadSingle(); // y vertex.position[2] = file.ReadSingle(); // z for (int j = 0; j < SKNVertex.BONE_INDEX_SIZE; ++j) { int bone = (int)file.ReadByte(); vertex.boneIndex[j] = bone; } vertex.weights[0] = file.ReadSingle(); vertex.weights[1] = file.ReadSingle(); vertex.weights[2] = file.ReadSingle(); vertex.weights[3] = file.ReadSingle(); vertex.normal[0] = file.ReadSingle(); // x vertex.normal[1] = file.ReadSingle(); // y vertex.normal[2] = file.ReadSingle(); // z vertex.texCoords[0] = file.ReadSingle(); // u vertex.texCoords[1] = file.ReadSingle(); // v data.vertices.Add(vertex); } // Data exclusive to version two. if (data.version == 2) { data.endTab.Add(file.ReadInt32()); data.endTab.Add(file.ReadInt32()); data.endTab.Add(file.ReadInt32()); } } // Unknown Version else { logger.Error("Unknown skn version: " + data.version); result = false; } } catch(Exception e) { logger.Error("Skn reading error."); logger.Error(e.Message); result = false; } logger.Event("Magic: " + data.magic); logger.Event("Version: " + data.version); logger.Event("Number of Objects: " + data.numObjects); logger.Event("Number of Material Headers: " + data.numMaterialHeaders); logger.Event("Number of Vertices: " + data.numVertices); logger.Event("Number of Indices: " + data.numIndices); return result; }
/// <summary> /// Loads data from SKN and SKL files into OpenGL. /// </summary> /// <param name="skn">The .skn data.</param> /// <param name="skl">The .skl data.</param> /// <returns></returns> public bool Create(SKNFile skn, SKLFile skl, Dictionary<String, ANMFile> anms, Logger logger) { bool result = true; // This function converts the handedness of the DirectX style input data // into the handedness OpenGL expects. // So, vector inputs have their Z value negated and quaternion inputs have their // Z and W values negated. // Vertex Data List<float> vertexPositions = new List<float>(); List<float> vertexNormals = new List<float>(); List<float> vertexTextureCoordinates = new List<float>(); List<float> vertexBoneIndices = new List<float>(); List<float> vertexBoneWeights = new List<float>(); List<uint> indices = new List<uint>(); // Animation data. List<OpenTK.Quaternion> boneOrientations = new List<OpenTK.Quaternion>(); List<OpenTK.Vector3> bonePositions = new List<OpenTK.Vector3>(); List<float> boneScales = new List<float>(); List<int> boneParents = new List<int>(); List<String> boneNames = new List<String>(); // Bones are not always in order between the ANM and SKL files. Dictionary<String, int> boneNameToID = new Dictionary<String, int>(); Dictionary<int, String> boneIDToName = new Dictionary<int, String>(); for (int i = 0; i < skn.numVertices; ++i) { // Position Information vertexPositions.Add(skn.vertices[i].position[0]); vertexPositions.Add(skn.vertices[i].position[1]); vertexPositions.Add(-skn.vertices[i].position[2]); // Normal Information vertexNormals.Add(skn.vertices[i].normal[0]); vertexNormals.Add(skn.vertices[i].normal[1]); vertexNormals.Add(-skn.vertices[i].normal[2]); // Tex Coords Information vertexTextureCoordinates.Add(skn.vertices[i].texCoords[0]); vertexTextureCoordinates.Add(skn.vertices[i].texCoords[1]); // Bone Index Information for (int j = 0; j < SKNVertex.BONE_INDEX_SIZE; ++j) { vertexBoneIndices.Add(skn.vertices[i].boneIndex[j]); } // Bone Weight Information vertexBoneWeights.Add(skn.vertices[i].weights[0]); vertexBoneWeights.Add(skn.vertices[i].weights[1]); vertexBoneWeights.Add(skn.vertices[i].weights[2]); vertexBoneWeights.Add(skn.vertices[i].weights[3]); } // Animation data for (int i = 0; i < skl.numBones; ++i) { Quaternion orientation = Quaternion.Identity; if (skl.version == 0) { // Version 0 SKLs contain a quaternion. orientation.X = skl.bones[i].orientation[0]; orientation.Y = skl.bones[i].orientation[1]; orientation.Z = -skl.bones[i].orientation[2]; orientation.W = -skl.bones[i].orientation[3]; } else { // Other SKLs contain a rotation matrix. // Create a matrix from the orientation values. Matrix4 transform = Matrix4.Identity; transform.M11 = skl.bones[i].orientation[0]; transform.M21 = skl.bones[i].orientation[1]; transform.M31 = skl.bones[i].orientation[2]; transform.M12 = skl.bones[i].orientation[4]; transform.M22 = skl.bones[i].orientation[5]; transform.M32 = skl.bones[i].orientation[6]; transform.M13 = skl.bones[i].orientation[8]; transform.M23 = skl.bones[i].orientation[9]; transform.M33 = skl.bones[i].orientation[10]; // Convert the matrix to a quaternion. orientation = OpenTKExtras.Matrix4.CreateQuatFromMatrix(transform); orientation.Z = -orientation.Z; orientation.W = -orientation.W; } boneOrientations.Add(orientation); // Create a vector from the position values. Vector3 position = Vector3.Zero; position.X = skl.bones[i].position[0]; position.Y = skl.bones[i].position[1]; position.Z = -skl.bones[i].position[2]; bonePositions.Add(position); boneNames.Add(skl.bones[i].name); boneNameToID[skl.bones[i].name] = i; boneIDToName[i] = skl.bones[i].name; boneScales.Add(skl.bones[i].scale); boneParents.Add(skl.bones[i].parentID); } // // Version 0 SKL files are similar to the animation files. // The bone positions and orientations are relative to their parent. // So, we need to compute their absolute location by hand. // if (skl.version == 0) { // // This algorithm is a little confusing since it's indexing identical data from // the SKL file and the local variable List<>s. The indexing scheme works because // the List<>s are created in the same order as the data in the SKL files. // for (int i = 0; i < skl.numBones; ++i) { // Only update non root bones. if (skl.bones[i].parentID != -1) { // Determine the parent bone. int parentBoneID = skl.bones[i].parentID; // Update orientation. // Append quaternions for rotation transform B * A. boneOrientations[i] = boneOrientations[parentBoneID] * boneOrientations[i]; Vector3 localPosition = Vector3.Zero; localPosition.X = skl.bones[i].position[0]; localPosition.Y = skl.bones[i].position[1]; localPosition.Z = skl.bones[i].position[2]; // Update position. bonePositions[i] = bonePositions[parentBoneID] + Vector3.Transform(localPosition, boneOrientations[parentBoneID]); } } } // Depending on the version of the model, the look ups change. if (skl.version == 2 || skl.version == 0) { for (int i = 0; i < vertexBoneIndices.Count; ++i) { // I don't know why things need remapped, but they do. // Sanity if (vertexBoneIndices[i] < skl.boneIDs.Count) { vertexBoneIndices[i] = skl.boneIDs[(int)vertexBoneIndices[i]]; } } } // Add the animations. foreach (var animation in anms) { if (animations.ContainsKey(animation.Key) == false) { // Create the OpenGL animation wrapper. GLAnimation glAnimation = new GLAnimation(); glAnimation.playbackFPS = animation.Value.playbackFPS; glAnimation.numberOfBones = animation.Value.numberOfBones; glAnimation.numberOfFrames = animation.Value.numberOfFrames; // Convert ANMBone to GLBone. foreach (ANMBone bone in animation.Value.bones) { GLBone glBone = new GLBone(); if (animation.Value.version == 4 && skl.boneIDMap.Count > 0) { // Version 4 ANM files contain a hash value to represent the bone ID/name. // We need to use the map from the SKL file to match the ANM bone with the correct // SKL bone. if (skl.boneIDMap.ContainsKey(bone.id)) { int sklID = (int)skl.boneIDMap[bone.id]; glBone.name = boneIDToName[sklID]; } } else { glBone.name = bone.name; } // Convert ANMFrame to Matrix4. foreach (ANMFrame frame in bone.frames) { Matrix4 transform = Matrix4.Identity; Quaternion quat = new Quaternion(frame.orientation[0], frame.orientation[1], -frame.orientation[2], -frame.orientation[3]); transform = Matrix4.Rotate(quat); transform.M41 = frame.position[0]; transform.M42 = frame.position[1]; transform.M43 = -frame.position[2]; glBone.frames.Add(transform); } glAnimation.bones.Add(glBone); } glAnimation.timePerFrame = 1.0f / (float)animation.Value.playbackFPS; // Store the animation. animations.Add(animation.Key, glAnimation); } } // Index Information for (int i = 0; i < skn.numIndices; ++i) { indices.Add((uint)skn.indices[i]); } this.numIndices = indices.Count; // // Compute the final animation transforms. // foreach (var animation in animations) { // This is sort of a mess. // We need to make sure "parent" bones are always updated before their "children". The SKL file contains // bones ordered in this manner. However, ANM files do not always do this. So, we sort the bones in the ANM to match the ordering in // the SKL file. animation.Value.bones.Sort( (a, b) => { if (boneNameToID.ContainsKey(a.name) && boneNameToID.ContainsKey(b.name)) { return boneNameToID[a.name].CompareTo(boneNameToID[b.name]); } else if (boneNameToID.ContainsKey(a.name) == false) { return 1; } else { return -1; } }); } // Create the binding transform. (The SKL initial transform.) GLAnimation bindingBones = new GLAnimation(); for (int i = 0; i < boneOrientations.Count; ++i) { GLBone bone = new GLBone(); bone.name = boneNames[i]; bone.parent = boneParents[i]; bone.transform = Matrix4.Rotate(boneOrientations[i]); bone.transform.M41 = bonePositions[i].X; bone.transform.M42 = bonePositions[i].Y; bone.transform.M43 = bonePositions[i].Z; bone.transform = Matrix4.Invert(bone.transform); bindingBones.bones.Add(bone); } // Convert animations into absolute space. foreach (var animation in animations) { foreach (var bone in animation.Value.bones) { // Sanity. if (boneNameToID.ContainsKey(bone.name)) { int id = boneNameToID[bone.name]; bone.parent = bindingBones.bones[id].parent; // For each frame... for (int i = 0; i < bone.frames.Count; ++i) { Matrix4 parentTransform = Matrix4.Identity; if (bone.parent >= 0) { if (bone.parent < animation.Value.bones.Count) { GLBone parent = animation.Value.bones[bone.parent]; parentTransform = parent.frames[i]; } } bone.frames[i] = bone.frames[i] * parentTransform; } } } } // Multiply the animation transforms by the binding transform. foreach (var animation in animations) { foreach (var bone in animation.Value.bones) { // Sanity. if (boneNameToID.ContainsKey(bone.name)) { int id = boneNameToID[bone.name]; GLBone bindingBone = bindingBones.bones[id]; for (int i = 0; i < bone.frames.Count; ++i) { bone.frames[i] = bindingBone.transform * bone.frames[i]; } } } } // Create the OpenGL objects. result = Create(vertexPositions, vertexNormals, vertexTextureCoordinates, vertexBoneIndices, vertexBoneWeights, indices, logger); return result; }