public void Write(Stream stream) { System.Threading.Thread.CurrentThread.CurrentCulture = _culture; teModelChunk_Skeleton skeleton = ChunkedData.GetChunk <teModelChunk_Skeleton>(); teModelChunk_Cloth cloth = ChunkedData.GetChunk <teModelChunk_Cloth>(); if (skeleton == null) { return; } short[] hierarchy; Dictionary <int, teModelChunk_Cloth.ClothNode> clothNodeMap = null; if (cloth != null) { hierarchy = cloth.CreateFakeHierarchy(skeleton, out clothNodeMap); } else { hierarchy = skeleton.Hierarchy; } using (StreamWriter writer = new StreamWriter(stream, Encoding.Default, 512)) { writer.WriteLine("{0}", skeleton.Header.BonesAbs); writer.WriteLine("version 1"); writer.WriteLine("nodes"); for (int i = 0; i < skeleton.Header.BonesAbs; ++i) { writer.WriteLine("{0} \"bone_{1:X4}\" {2}", i, skeleton.IDs[i], hierarchy[i]); } writer.WriteLine("end"); writer.WriteLine("skeleton"); writer.WriteLine("time 0"); for (int i = 0; i < skeleton.Header.BonesAbs; ++i) { OverwatchModel.GetRefPoseTransform(i, hierarchy, skeleton, clothNodeMap, out teVec3 scale, out teQuat quat, out teVec3 pos); teVec3 rot = quat.ToEulerAngles(); writer.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0} {1:0.000000} {2:0.000000} {3:0.000000} {4:0.000000} {5:0.000000} {6:0.000000} {7:0.000000} {8:0.000000} {9:0.000000}", i, pos.X, pos.Y, pos.Z, rot.X, rot.Y, rot.Z, scale.X, scale.Y, scale.Z)); } } }
public void Parse(ICLIFlags toolFlags) { var flags = toolFlags as ExtractFlags; var testGuids = flags?.Positionals.Skip(3).Select(x => uint.Parse(x, System.Globalization.NumberStyles.HexNumber)); foreach (var guid in Program.TrackedFiles[0xC]) { if (!(testGuids ?? throw new InvalidDataException()).Contains(teResourceGUID.Index(guid))) { continue; } using (Stream file = IO.OpenFile(guid)) using (BinaryReader reader = new BinaryReader(file)) { teChunkedData chunk = new teChunkedData(reader); teModelChunk_STU stuChunk = chunk.GetChunk <teModelChunk_STU>(); var hitboxes = stuChunk.StructuredData.m_CB4D298D; var complex = hitboxes.Select(x => x.m_B7C8314A).OfType <STU_B3800E70>().First(); var lines = new List <string> { "ply", "format ascii 1.0", $"element vertex {complex.m_88FCECD7.Length}", "property float x", "property float y", "property float z", "end_header" }; lines.AddRange(complex.m_88FCECD7.Select(x => $"{x.X} {x.Y} {x.Z}")); // vertex File.WriteAllText(@"F:\Test.ply", string.Join("\n", lines)); } } }
public void Write(Stream stream) { teModelChunk_RenderMesh renderMesh = _data.GetChunk <teModelChunk_RenderMesh>(); teModelChunk_Model model = _data.GetChunk <teModelChunk_Model>(); teModelChunk_Skeleton skeleton = _data.GetChunk <teModelChunk_Skeleton>(); teModelChunk_Cloth cloth = _data.GetChunk <teModelChunk_Cloth>(); teModelChunk_STU stu = _data.GetChunk <teModelChunk_STU>(); using (BinaryWriter writer = new BinaryWriter(stream)) { writer.Write((ushort)1); writer.Write((ushort)6); if (ModelLookFileName == null) // mat ref { writer.Write((byte)0); } else { writer.Write(ModelLookFileName); } if (Name == null) // model name { writer.Write((byte)0); } else { writer.Write(Name); } short[] hierarchy = null; Dictionary <int, teModelChunk_Cloth.ClothNode> clothNodeMap = null; if (skeleton != null) { if (cloth != null) { hierarchy = cloth.CreateFakeHierarchy(skeleton, out clothNodeMap); } else { hierarchy = skeleton.Hierarchy; } writer.Write(skeleton.Header.BonesAbs); } else { writer.Write((ushort)0); } if (renderMesh == null) { writer.Write(0u); writer.Write(0); return; } teModelChunk_RenderMesh.Submesh[] submeshes = renderMesh.Submeshes.Where(x => x.Descriptor.LOD == TargetLod || x.Descriptor.LOD == -1).ToArray(); writer.Write((uint)submeshes.Length); if (stu?.StructuredData.m_hardPoints != null) { writer.Write(stu.StructuredData.m_hardPoints.Length); } else { writer.Write(0); // hardpoints } if (skeleton != null) { for (int i = 0; i < skeleton.Header.BonesAbs; ++i) { writer.Write(GetBoneName(skeleton.IDs[i])); short parent = hierarchy[i]; if (parent == -1) { parent = (short)i; } writer.Write(parent); skeleton.GetWorldSpace(i, out teVec3 scale, out teQuat rotation, out teVec3 translation); writer.Write(translation); writer.Write(scale); writer.Write(rotation); } } int submeshIdx = 0; foreach (teModelChunk_RenderMesh.Submesh submesh in submeshes) { writer.Write($"Submesh_{submeshIdx}.{model.Materials[submesh.Descriptor.Material]:X16}"); writer.Write(model.Materials[submesh.Descriptor.Material]); writer.Write(submesh.UVCount); writer.Write(submesh.Vertices.Length); writer.Write(submesh.Indices.Length / 3); for (int j = 0; j < submesh.Vertices.Length; j++) { writer.Write(submesh.Vertices[j]); writer.Write(-submesh.Normals[j].X); writer.Write(-submesh.Normals[j].Y); writer.Write(-submesh.Normals[j].Z); foreach (teVec2 uv in submesh.UV[j]) { writer.Write(uv); } if (skeleton != null && submesh.BoneIndices[j] != null) { writer.Write((byte)4); writer.Write(skeleton.Lookup[submesh.BoneIndices[j][0]]); writer.Write(skeleton.Lookup[submesh.BoneIndices[j][1]]); writer.Write(skeleton.Lookup[submesh.BoneIndices[j][2]]); writer.Write(skeleton.Lookup[submesh.BoneIndices[j][3]]); writer.Write(submesh.BoneWeights[j][0]); writer.Write(submesh.BoneWeights[j][1]); writer.Write(submesh.BoneWeights[j][2]); writer.Write(submesh.BoneWeights[j][3]); } else { writer.Write((byte)0); } writer.Write(submesh.Color1.ElementAtOrDefault(j)); writer.Write(submesh.Color2.ElementAtOrDefault(j)); } for (int j = 0; j < submesh.Descriptor.IndicesToDraw; j += 3) { writer.Write((byte)3); writer.Write((int)submesh.Indices[j]); writer.Write((int)submesh.Indices[j + 1]); writer.Write((int)submesh.Indices[j + 2]); } submeshIdx++; } if (stu?.StructuredData.m_hardPoints != null) { foreach (STUModelHardpoint hardPoint in stu.StructuredData.m_hardPoints) { writer.Write(IdToString("hardpoint", teResourceGUID.Index(hardPoint.m_EDF0511C))); Matrix parentMat = Matrix.Identity; if (hardPoint.m_FF592924 != 0 && skeleton != null) { int?boneIdx = null; var hardPointBoneID = teResourceGUID.Index(hardPoint.m_FF592924); for (int i = 0; i < skeleton.IDs.Length; i++) { var currBoneID = skeleton.IDs[i]; if (currBoneID == hardPointBoneID) { boneIdx = i; break; } } if (boneIdx == null) { parentMat = Matrix.Identity; Logger.Debug("OverwatchModel", $"Hardpoint {teResourceGUID.AsString(hardPoint.m_EDF0511C)} is attached to bone {teResourceGUID.AsString(hardPoint.m_FF592924)} which doesn't exist on model {teResourceGUID.AsString(GUID)}"); } else { parentMat = skeleton.GetWorldSpace(boneIdx.Value); } } Matrix hardPointMat = Matrix.RotationQuaternion(hardPoint.m_rotation) * Matrix.Translation(hardPoint.m_position); hardPointMat = hardPointMat * parentMat; hardPointMat.Decompose(out Vector3 _, out Quaternion rot, out Vector3 pos); writer.Write(pos); writer.Write(rot); } foreach (STUModelHardpoint modelHardpoint in stu.StructuredData.m_hardPoints) { writer.Write(IdToString("bone", teResourceGUID.Index(modelHardpoint.m_FF592924))); } } // ext 1.3: old cloth writer.Write(0); // ext 1.4: embedded refpose if (skeleton != null) { for (int i = 0; i < skeleton.Header.BonesAbs; ++i) { writer.Write(IdToString("bone", skeleton.IDs[i])); short parent = hierarchy[i]; writer.Write(parent); GetRefPoseTransform(i, hierarchy, skeleton, clothNodeMap, out teVec3 scale, out teQuat quat, out teVec3 translation); teVec3 rot = quat.ToEulerAngles(); writer.Write(translation); writer.Write(scale); writer.Write(rot); } } writer.Write(teResourceGUID.Index(GUID)); } }