// ReSharper disable once InconsistentNaming // data is object[] { bool exportAttachments, string materialReference, string modelName, bool onlyOneLOD, bool skipCollision } public void Write(ICLIFlags flags, Chunked chunked, Stream output, List <byte> LODs, object[] data, FindLogic.Combo.ModelInfoNew modelInfo) { byte?flagLOD = null; if (flags is ExtractFlags extractFlags) { flagLOD = extractFlags.LOD; } IChunk chunk = chunked.FindNextChunk("MNRM").Value; if (chunk == null) { return; } MNRM model = (MNRM)chunk; chunk = chunked.FindNextChunk("CLDM").Value; CLDM materials = null; if (chunk != null) { materials = (CLDM)chunk; } chunk = chunked.FindNextChunk("lksm").Value; lksm skeleton = null; if (chunk != null) { skeleton = (lksm)chunk; } chunk = chunked.FindNextChunk("PRHM").Value; PRHM hardpoints = null; if (chunk != null) { hardpoints = (PRHM)chunk; } HTLC cloth = chunked.FindNextChunk("HTLC").Value as HTLC; short[] hierarchy = (short[])skeleton?.Hierarchy.Clone(); Dictionary <int, HTLC.ClothNode> nodeMap = new Dictionary <int, HTLC.ClothNode>(); if (cloth != null) { uint clothIndex = 0; foreach (HTLC.ClothNode[] nodeCollection in cloth.Nodes) { if (nodeCollection == null) { continue; } int nodeIndex = 0; foreach (HTLC.ClothNode node in nodeCollection) { int parentRaw = node.VerticalParent; if (cloth.NodeBones[clothIndex].ContainsKey(nodeIndex) && cloth.NodeBones[clothIndex].ContainsKey(parentRaw)) { if (cloth.NodeBones[clothIndex][nodeIndex] != -1) { hierarchy[cloth.NodeBones[clothIndex][nodeIndex]] = cloth.NodeBones[clothIndex][parentRaw]; if (cloth.NodeBones[clothIndex][parentRaw] == -1) { HTLC.ClothNodeWeight weightedBone = node.Bones.Aggregate((i1, i2) => i1.Weight > i2.Weight ? i1 : i2); hierarchy[cloth.NodeBones[clothIndex][nodeIndex]] = weightedBone.Bone; } } } else { if (cloth.NodeBones[clothIndex].ContainsKey(nodeIndex)) { if (cloth.NodeBones[clothIndex][nodeIndex] != -1) { hierarchy[cloth.NodeBones[clothIndex][nodeIndex]] = -1; HTLC.ClothNodeWeight weightedBone = node.Bones.Aggregate((i1, i2) => i1.Weight > i2.Weight ? i1 : i2); hierarchy[cloth.NodeBones[clothIndex][nodeIndex]] = weightedBone.Bone; } } } if (cloth.NodeBones[clothIndex].ContainsKey(nodeIndex)) { if (cloth.NodeBones[clothIndex][nodeIndex] != -1) { nodeMap[cloth.NodeBones[clothIndex][nodeIndex]] = node; } } nodeIndex++; } clothIndex++; } } using (BinaryWriter writer = new BinaryWriter(output)) { writer.Write((ushort)1); // version major writer.Write((ushort)5); // version minor if (data.Length > 1 && data[1] is string && ((string)data[1]).Length > 0) { writer.Write((string)data[1]); } else { writer.Write((byte)0); } if (data.Length > 2 && data[2] is string && ((string)data[2]).Length > 0) { writer.Write((string)data[2]); } else { writer.Write((byte)0); } if (skeleton == null) { writer.Write((ushort)0); // number of bones } else { writer.Write(skeleton.Data.bonesAbs); } // ReSharper disable once InconsistentNaming Dictionary <byte, List <int> > LODMap = new Dictionary <byte, List <int> >(); uint sz = 0; uint lookForLod = 0; if (model.Submeshes.Any(x => x.lod == flagLOD)) { lookForLod = (byte)flagLOD; } else if (flagLOD != null) { SubmeshDescriptor nextLowest = model.Submeshes.Where(p => p.lod < flagLOD).OrderBy(x => x.lod).LastOrDefault(); if (nextLowest.verticesToDraw == 0 && nextLowest.indexCount == 0) // not real mesh { SubmeshDescriptor nextHighest = model.Submeshes.Where(p => p.lod > flagLOD).OrderBy(x => x.lod).FirstOrDefault(); lookForLod = nextHighest.lod; } else { lookForLod = nextLowest.lod; } } for (int i = 0; i < model.Submeshes.Length; ++i) { SubmeshDescriptor submesh = model.Submeshes[i]; if (data.Length > 4 && data[4] is bool && (bool)data[4]) { if (submesh.flags == SubmeshFlags.COLLISION_MESH) { continue; } } if (lookForLod > 0 && submesh.lod != lookForLod && submesh.lod != 255) { continue; } if (!LODMap.ContainsKey(submesh.lod)) { LODMap.Add(submesh.lod, new List <int>()); } sz++; LODMap[submesh.lod].Add(i); } writer.Write(sz); writer.Write(hardpoints?.HardPoints.Length ?? 0); if (skeleton != null) { for (int i = 0; i < skeleton.Data.bonesAbs; ++i) { writer.Write(IdToString("bone", skeleton.IDs[i])); short parent = hierarchy[i]; if (parent == -1) { parent = (short)i; } writer.Write(parent); Matrix3x4 bone = skeleton.Matrices34[i]; Quaternion rot = new Quaternion(bone[0, 0], bone[0, 1], bone[0, 2], bone[0, 3]); Vector3 scl = new Vector3(bone[1, 0], bone[1, 1], bone[1, 2]); Vector3 pos = new Vector3(bone[2, 0], bone[2, 1], bone[2, 2]); if (nodeMap.ContainsKey(i)) { HTLC.ClothNode thisNode = nodeMap[i]; pos.X = thisNode.X; pos.Y = thisNode.Y; pos.Z = thisNode.Z; } writer.Write(pos.X); writer.Write(pos.Y); writer.Write(pos.Z); writer.Write(scl.X); writer.Write(scl.Y); writer.Write(scl.Z); writer.Write(rot.X); writer.Write(rot.Y); writer.Write(rot.Z); writer.Write(rot.W); } } foreach (KeyValuePair <byte, List <int> > kv in LODMap) { foreach (int i in kv.Value) { SubmeshDescriptor submesh = model.Submeshes[i]; ModelVertex[] vertex = model.Vertices[i]; ModelVertex[] normal = model.Normals[i]; ModelUV[][] uv = model.TextureCoordinates[i]; ModelIndice[] index = model.Indices[i]; ModelBoneData[] bones = model.Bones[i]; writer.Write($"Submesh_{i}.{kv.Key}.{materials.Materials[submesh.material]:X16}"); writer.Write(materials.Materials[submesh.material]); writer.Write((byte)uv.Length); writer.Write(vertex.Length); writer.Write(index.Length); for (int j = 0; j < vertex.Length; ++j) { writer.Write(vertex[j].x); writer.Write(vertex[j].y); writer.Write(vertex[j].z); writer.Write(-normal[j].x); writer.Write(-normal[j].y); writer.Write(-normal[j].z); foreach (ModelUV[] t in uv) { writer.Write(t[j].u); writer.Write(t[j].v); } if (skeleton != null && bones != null && bones[j].boneIndex != null && bones[j].boneWeight != null) { writer.Write((byte)4); writer.Write(skeleton.Lookup[bones[j].boneIndex[0]]); writer.Write(skeleton.Lookup[bones[j].boneIndex[1]]); writer.Write(skeleton.Lookup[bones[j].boneIndex[2]]); writer.Write(skeleton.Lookup[bones[j].boneIndex[3]]); writer.Write(bones[j].boneWeight[0]); writer.Write(bones[j].boneWeight[1]); writer.Write(bones[j].boneWeight[2]); writer.Write(bones[j].boneWeight[3]); } else { // bone -> size + index + weight writer.Write((byte)0); } } List <ModelIndiceModifiable> indexNew = new List <ModelIndiceModifiable>(); foreach (ModelIndice indice in index) { indexNew.Add(new ModelIndiceModifiable { v1 = indice.v1, v2 = indice.v2, v3 = indice.v3 }); } foreach (ModelIndiceModifiable indice in indexNew) { writer.Write((byte)3); writer.Write(indice.v1); writer.Write(indice.v2); writer.Write(indice.v3); } } } if (hardpoints != null) { // attachments foreach (PRHM.HardPoint hp in hardpoints.HardPoints) { writer.Write(IdToString("hardpoint", GUID.Index(hp.HardPointGUID))); Matrix4 mat = hp.Matrix.ToOpenTK(); Vector3 pos = mat.ExtractTranslation(); Quaternion rot = mat.ExtractRotation(); writer.Write(pos.X); writer.Write(pos.Y); writer.Write(pos.Z); writer.Write(rot.X); writer.Write(rot.Y); writer.Write(rot.Z); writer.Write(rot.W); } // extension 1.1 foreach (PRHM.HardPoint hp in hardpoints.HardPoints) { writer.Write(IdToString("bone", GUID.Index(hp.GUIDx012))); } } // ext 1.3: cloth writer.Write(0); // ext 1.4: embedded refpose if (skeleton != null) { for (int i = 0; i < skeleton.Data.bonesAbs; ++i) { writer.Write(IdToString("bone", skeleton.IDs[i])); short parent = hierarchy[i]; writer.Write(parent); Matrix3x4 bone = skeleton.Matrices34Inverted[i]; Quaternion3D quat = new Quaternion3D(bone[0, 3], bone[0, 0], bone[0, 1], bone[0, 2]); Vector3D rot = C3D.ToEulerAngles(quat); // ReSharper disable CompareOfFloatsByEqualityOperator if (rot.X == -3.14159274f && rot.Y == 0 && rot.Z == 0) { rot = new Vector3D(0, 3.14159274f, 3.14159274f); // effectively the same but you know, eulers. } // ReSharper restore CompareOfFloatsByEqualityOperator Vector3 scl = new Vector3(bone[1, 0], bone[1, 1], bone[1, 2]); Vector3 pos = new Vector3(bone[2, 0], bone[2, 1], bone[2, 2]); writer.Write(pos.X); writer.Write(pos.Y); writer.Write(pos.Z); writer.Write(scl.X); writer.Write(scl.Y); writer.Write(scl.Z); writer.Write(rot.X); writer.Write(rot.Y); writer.Write(rot.Z); } } // ext 1.5: guid writer.Write(GUID.Index(modelInfo.GUID)); // ext 1.6: cloth 2.0 if (cloth == null) { writer.Write(0); } else { writer.Write(cloth.Descriptors.Length); for (int i = 0; i < cloth.Descriptors.Length; i++) { var desc = cloth.Descriptors[i]; writer.Write(desc.Name); writer.Write(cloth.Nodes[i].Length); foreach (HTLC.ClothNode clothNode in cloth.Nodes[i]) { writer.Write(clothNode.Bones.Length); foreach (HTLC.ClothNodeWeight clothNodeWeight in clothNode.Bones) { writer.Write(clothNodeWeight.Bone); writer.Write(clothNodeWeight.Weight); } } } } } }
public bool Write(Chunked chunked, Stream output, List <byte> LODs, Dictionary <ulong, List <ImageLayer> > layers, object[] opts) { culture.NumberFormat.NumberDecimalSeparator = "."; System.Threading.Thread.CurrentThread.CurrentCulture = culture; IChunk chunk = chunked.FindNextChunk("MNRM").Value; if (chunk == null) { return(false); } MNRM model = (MNRM)chunk; chunk = chunked.FindNextChunk("CLDM").Value; CLDM materials = null; if (chunk != null) { materials = (CLDM)chunk; } chunk = chunked.FindNextChunk("lksm").Value; lksm skeleton = null; if (chunk != null) { skeleton = (lksm)chunk; } //Console.Out.WriteLine("Writing ASCII"); using (StreamWriter writer = new StreamWriter(output)) { if (skeleton != null) { writer.WriteLine(skeleton.Data.bonesAbs); for (int i = 0; i < skeleton.Data.bonesAbs; ++i) { writer.WriteLine("bone_{0:X4}", skeleton.IDs[i]); writer.WriteLine(skeleton.Hierarchy[i]); OpenTK.Vector3 bonePos = skeleton.Matrices[i].ExtractTranslation(); writer.WriteLine("{0:0.000000} {1:0.000000} {2:0.000000}", bonePos.X, bonePos.Y, bonePos.Z); } } else { writer.WriteLine("0"); } Dictionary <byte, List <int> > LODMap = new Dictionary <byte, List <int> >(); uint sz = 0; uint lookForLod = 0; bool lodOnly = false; if (opts.Length > 3 && opts[3] != null && opts[3].GetType() == typeof(bool) && (bool)opts[3] == true) { lodOnly = true; } for (int i = 0; i < model.Submeshes.Length; ++i) { SubmeshDescriptor submesh = model.Submeshes[i]; if (opts.Length > 4 && opts[4] != null && opts[4].GetType() == typeof(bool) && (bool)opts[4] == true) { if (submesh.flags == SubmeshFlags.COLLISION_MESH) { continue; } } if (LODs != null && !LODs.Contains(submesh.lod)) { continue; } if (lodOnly && lookForLod > 0 && submesh.lod != lookForLod) { continue; } if (!LODMap.ContainsKey(submesh.lod)) { LODMap.Add(submesh.lod, new List <int>()); } lookForLod = submesh.lod; sz++; LODMap[submesh.lod].Add(i); } writer.WriteLine(sz); foreach (KeyValuePair <byte, List <int> > kv in LODMap) { //Console.Out.WriteLine("Writing LOD {0}", kv.Key); foreach (int i in kv.Value) { SubmeshDescriptor submesh = model.Submeshes[i]; ModelVertex[] vertex = model.Vertices[i]; ModelVertex[] normal = model.Normals[i]; ModelUV[][] uv = model.TextureCoordinates[i]; ModelIndice[] index = model.Indices[i]; ModelBoneData[] bones = model.Bones[i]; ulong materialKey = submesh.material; if (materials != null) { materialKey = materials.Materials[submesh.material]; } writer.WriteLine("Submesh_{0}.{1}.{2:X16}", i, kv.Key, materialKey); writer.WriteLine(uv.Length); if (layers.ContainsKey(materialKey)) { List <ImageLayer> materialLayers = layers[materialKey]; uint count = 0; HashSet <ulong> done = new HashSet <ulong>(); for (int j = 0; j < materialLayers.Count; ++j) { if (done.Add(materialLayers[j].Key)) { count += 1; } } writer.WriteLine(count); done.Clear(); for (int j = 0; j < materialLayers.Count; ++j) { if (done.Add(materialLayers[j].Key)) { writer.WriteLine($"{GUID.LongKey(materialLayers[j].Key):X12}.dds"); writer.WriteLine(0); } } } else { writer.WriteLine(uv.Length); for (int j = 0; j < uv.Length; ++j) { writer.WriteLine("{0:X16}_UV{1}.dds", materialKey, j); writer.WriteLine(j); } } writer.WriteLine(vertex.Length); for (int j = 0; j < vertex.Length; ++j) { writer.WriteLine("{0} {1} {2}", vertex[j].x, vertex[j].y, vertex[j].z); writer.WriteLine("{0} {1} {2}", -normal[j].x, -normal[j].y, -normal[j].z); writer.WriteLine("255 255 255 255"); for (int k = 0; k < uv.Length; ++k) { writer.WriteLine("{0:0.######} {1:0.######}", uv[k][j].u, uv[k][j].v); } if (skeleton != null && skeleton.Data.bonesAbs > 0) { if (bones != null && bones[j].boneIndex != null && bones[j].boneWeight != null) { writer.WriteLine("{0} {1} {2} {3}", skeleton.Lookup[bones[j].boneIndex[0]], skeleton.Lookup[bones[j].boneIndex[1]], skeleton.Lookup[bones[j].boneIndex[2]], skeleton.Lookup[bones[j].boneIndex[3]]); writer.WriteLine("{0:0.######} {1:0.######} {2:0.######} {3:0.######}", bones[j].boneWeight[0], bones[j].boneWeight[1], bones[j].boneWeight[2], bones[j].boneWeight[3]); } else { writer.WriteLine("0 0 0 0"); writer.WriteLine("0 0 0 0"); } } } writer.WriteLine(index.Length); for (int j = 0; j < index.Length; ++j) { writer.WriteLine("{0} {1} {2}", index[j].v1, index[j].v2, index[j].v3); } } } writer.WriteLine(""); } return(true); }
public bool Write(Chunked chunked, Stream output, List <byte> LODs, Dictionary <ulong, List <ImageLayer> > layers, object[] data) { IChunk chunk = chunked.FindNextChunk("MNRM").Value; if (chunk == null) { return(false); } MNRM model = (MNRM)chunk; chunk = chunked.FindNextChunk("CLDM").Value; CLDM materials = null; if (chunk != null) { materials = (CLDM)chunk; } chunk = chunked.FindNextChunk("lksm").Value; lksm skeleton = null; if (chunk != null) { skeleton = (lksm)chunk; } chunk = chunked.FindNextChunk("PRHM").Value; PRHM hardpoints = null; if (chunk != null) { hardpoints = (PRHM)chunk; } //Console.Out.WriteLine("Writing OWMDL"); using (BinaryWriter writer = new BinaryWriter(output)) { writer.Write((ushort)1); // version major writer.Write((ushort)1); // version minor if (data.Length > 1 && data[1] != null && data[1].GetType() == typeof(string) && ((string)data[1]).Length > 0) { writer.Write((string)data[1]); } else { writer.Write((byte)0); } if (data.Length > 2 && data[2] != null && data[2].GetType() == typeof(string) && ((string)data[2]).Length > 0) { writer.Write((string)data[2]); } else { writer.Write((byte)0); } if (skeleton == null) { writer.Write((ushort)0); // number of bones } else { writer.Write(skeleton.Data.bonesAbs); } Dictionary <byte, List <int> > LODMap = new Dictionary <byte, List <int> >(); uint sz = 0; uint lookForLod = 0; bool lodOnly = false; if (data.Length > 3 && data[3] != null && data[3].GetType() == typeof(bool) && (bool)data[3] == true) { lodOnly = true; } for (int i = 0; i < model.Submeshes.Length; ++i) { SubmeshDescriptor submesh = model.Submeshes[i]; if (data.Length > 4 && data[4] != null && data[4].GetType() == typeof(bool) && (bool)data[4] == true) { if ((SubmeshFlags)submesh.flags == SubmeshFlags.COLLISION_MESH) { continue; } } if (LODs != null && !LODs.Contains(submesh.lod)) { continue; } if (lodOnly && lookForLod > 0 && submesh.lod != lookForLod) { continue; } if (!LODMap.ContainsKey(submesh.lod)) { LODMap.Add(submesh.lod, new List <int>()); } lookForLod = submesh.lod; sz++; LODMap[submesh.lod].Add(i); } writer.Write(sz); if (hardpoints != null) { writer.Write(hardpoints.HardPoints.Length); } else { writer.Write((int)0); // number of attachments } if (skeleton != null) { for (int i = 0; i < skeleton.Data.bonesAbs; ++i) { writer.Write(IdToString("bone", skeleton.IDs[i])); short parent = skeleton.Hierarchy[i]; if (parent == -1) { parent = (short)i; } writer.Write(parent); Matrix3x4 bone = skeleton.Matrices34[i]; Quaternion rot = new Quaternion(bone[0, 0], bone[0, 1], bone[0, 2], bone[0, 3]); Vector3 scl = new Vector3(bone[1, 0], bone[1, 1], bone[1, 2]); Vector3 pos = new Vector3(bone[2, 0], bone[2, 1], bone[2, 2]); writer.Write(pos.X); writer.Write(pos.Y); writer.Write(pos.Z); writer.Write(scl.X); writer.Write(scl.X); writer.Write(scl.X); writer.Write(rot.X); writer.Write(rot.Y); writer.Write(rot.Z); writer.Write(rot.W); } } foreach (KeyValuePair <byte, List <int> > kv in LODMap) { //Console.Out.WriteLine("Writing LOD {0}", kv.Key); foreach (int i in kv.Value) { SubmeshDescriptor submesh = model.Submeshes[i]; ModelVertex[] vertex = model.Vertices[i]; ModelVertex[] normal = model.Normals[i]; ModelUV[][] uv = model.TextureCoordinates[i]; ModelIndice[] index = model.Indices[i]; ModelBoneData[] bones = model.Bones[i]; writer.Write($"Submesh_{i}.{kv.Key}.{materials.Materials[submesh.material]:X16}"); writer.Write(materials.Materials[submesh.material]); writer.Write((byte)uv.Length); writer.Write(vertex.Length); writer.Write(index.Length); for (int j = 0; j < vertex.Length; ++j) { writer.Write(vertex[j].x); writer.Write(vertex[j].y); writer.Write(vertex[j].z); writer.Write(-normal[j].x); writer.Write(-normal[j].y); writer.Write(-normal[j].z); for (int k = 0; k < uv.Length; ++k) { writer.Write((float)uv[k][j].u); writer.Write((float)uv[k][j].v); } if (skeleton != null && bones != null && bones[j].boneIndex != null && bones[j].boneWeight != null) { writer.Write((byte)4); writer.Write(skeleton.Lookup[bones[j].boneIndex[0]]); writer.Write(skeleton.Lookup[bones[j].boneIndex[1]]); writer.Write(skeleton.Lookup[bones[j].boneIndex[2]]); writer.Write(skeleton.Lookup[bones[j].boneIndex[3]]); writer.Write(bones[j].boneWeight[0]); writer.Write(bones[j].boneWeight[1]); writer.Write(bones[j].boneWeight[2]); writer.Write(bones[j].boneWeight[3]); } else { // bone -> size + index + weight writer.Write((byte)0); } } for (int j = 0; j < index.Length; ++j) { writer.Write((byte)3); writer.Write((int)index[j].v1); writer.Write((int)index[j].v2); writer.Write((int)index[j].v3); } } } if (hardpoints != null) { // attachments for (int i = 0; i < hardpoints.HardPoints.Length; ++i) { PRHM.HardPoint hp = hardpoints.HardPoints[i]; writer.Write(IdToString("attachment_", hp.id)); Matrix4 mat = hp.matrix.ToOpenTK(); Vector3 pos = mat.ExtractTranslation(); Quaternion rot = mat.ExtractRotation(); writer.Write(pos.X); writer.Write(pos.Y); writer.Write(pos.Z); writer.Write(rot.X); writer.Write(rot.Y); writer.Write(rot.Z); writer.Write(rot.W); } // extension 1.1 for (int i = 0; i < hardpoints.HardPoints.Length; ++i) { PRHM.HardPoint hp = hardpoints.HardPoints[i]; writer.Write(IdToString("bone", hp.id)); } } } return(true); }
public bool Write(Chunked chunked, Stream stream, List <byte> LODs, Dictionary <ulong, List <ImageLayer> > layers, object[] opts) { IChunk chunk = chunked.FindNextChunk("MNRM").Value; if (chunk == null) { return(false); } MNRM model = (MNRM)chunk; chunk = chunked.FindNextChunk("CLDM").Value; CLDM materials = null; if (chunk != null) { materials = (CLDM)chunk; } chunk = chunked.FindNextChunk("lksm").Value; lksm skeleton = null; if (chunk != null) { skeleton = (lksm)chunk; } //Console.Out.WriteLine("Writing BIN"); using (BinaryWriter writer = new BinaryWriter(stream)) { writer.Write((uint)323232); writer.Write((ushort)2); writer.Write((ushort)99); WriteString(writer, "XNAaraL"); writer.Write((uint)5); WriteString(writer, "OVERWATCH"); WriteString(writer, "BLIZZARD"); WriteString(writer, "NULL"); writer.Write((uint)180); // hash writer.Write((uint)1); // items // item 1 writer.Write((uint)1); // type; 1 = pose; 2 = flags; 255 = padding // pose writer.Write((uint)0); // size pow 4 writer.Write((uint)0); // op info; bone count /* * pose data is always ASCII. * Each line is: * for each bone: * boneName:rotx roty rotz posx posy posz scalex scaley scalez */ if (skeleton != null) { writer.Write((uint)skeleton.Data.bonesAbs); for (int i = 0; i < skeleton.Data.bonesAbs; ++i) { WriteString(writer, $"bone_{skeleton.IDs[i]:X4}"); short parent = skeleton.Hierarchy[i]; if (parent == -1) { parent = (short)i; } writer.Write(parent); OpenTK.Vector3 bonePos = skeleton.Matrices[i].ExtractTranslation(); writer.Write(bonePos.X); writer.Write(bonePos.Y); writer.Write(bonePos.Z); } } else { writer.Write((uint)0); } Dictionary <byte, List <int> > LODMap = new Dictionary <byte, List <int> >(); uint sz = 0; uint lookForLod = 0; bool lodOnly = false; if (opts.Length > 3 && opts[3] != null && opts[3].GetType() == typeof(bool) && (bool)opts[3] == true) { lodOnly = true; } for (int i = 0; i < model.Submeshes.Length; ++i) { SubmeshDescriptor submesh = model.Submeshes[i]; if (opts.Length > 4 && opts[4] != null && opts[4].GetType() == typeof(bool) && (bool)opts[4] == true) { if (submesh.flags == SubmeshFlags.COLLISION_MESH) { continue; } } if (LODs != null && !LODs.Contains(submesh.lod)) { continue; } if (lodOnly && lookForLod > 0 && submesh.lod != lookForLod) { continue; } if (!LODMap.ContainsKey(submesh.lod)) { LODMap.Add(submesh.lod, new List <int>()); } lookForLod = submesh.lod; sz++; LODMap[submesh.lod].Add(i); } writer.Write(sz); foreach (KeyValuePair <byte, List <int> > kv in LODMap) { //Console.Out.WriteLine("Writing LOD {0}", kv.Key); foreach (int i in kv.Value) { SubmeshDescriptor submesh = model.Submeshes[i]; ModelVertex[] vertex = model.Vertices[i]; ModelVertex[] normal = model.Normals[i]; ModelUV[][] uv = model.TextureCoordinates[i]; ModelIndice[] index = model.Indices[i]; ModelBoneData[] bones = model.Bones[i]; ulong materialKey = submesh.material; if (materials != null) { materialKey = materials.Materials[submesh.material]; } WriteString(writer, $"Submesh_{i}.{kv.Key}.{materialKey:X16}"); writer.Write((uint)uv.Length); if (layers.ContainsKey(materialKey)) { List <ImageLayer> materialLayers = layers[materialKey]; uint count = 0; HashSet <ulong> done = new HashSet <ulong>(); for (int j = 0; j < materialLayers.Count; ++j) { if (done.Add(materialLayers[j].Key)) { count += 1; } } writer.Write(count); done.Clear(); for (int j = 0; j < materialLayers.Count; ++j) { if (done.Add(materialLayers[j].Key)) { writer.Write($"{GUID.LongKey(materialLayers[j].Key):X12}.dds"); writer.Write((uint)0); } } } else { writer.Write((uint)uv.Length); for (int j = 0; j < uv.Length; ++j) { writer.Write($"{materialKey:X16}_UV{j}.dds"); writer.Write((uint)j); } } writer.Write((uint)vertex.Length); for (int j = 0; j < vertex.Length; ++j) { writer.Write(vertex[j].x); writer.Write(vertex[j].y); writer.Write(vertex[j].z); writer.Write(-normal[j].x); writer.Write(-normal[j].y); writer.Write(-normal[j].z); writer.Write((byte)255); writer.Write((byte)255); writer.Write((byte)255); writer.Write((byte)255); for (int k = 0; k < uv.Length; ++k) { writer.Write((float)uv[k][j].u); writer.Write((float)uv[k][j].v); } if (skeleton != null && skeleton.Data.bonesAbs > 0) { if (bones != null && bones[j].boneIndex != null && bones[j].boneWeight != null) { writer.Write(skeleton.Lookup[bones[j].boneIndex[0]]); writer.Write(skeleton.Lookup[bones[j].boneIndex[1]]); writer.Write(skeleton.Lookup[bones[j].boneIndex[2]]); writer.Write(skeleton.Lookup[bones[j].boneIndex[3]]); writer.Write(bones[j].boneWeight[0]); writer.Write(bones[j].boneWeight[1]); writer.Write(bones[j].boneWeight[2]); writer.Write(bones[j].boneWeight[3]); } else { writer.Write((ushort)0); writer.Write((ushort)0); writer.Write((ushort)0); writer.Write((ushort)0); writer.Write(0.0f); writer.Write(0.0f); writer.Write(0.0f); writer.Write(0.0f); } } } writer.Write((uint)index.Length); for (int j = 0; j < index.Length; ++j) { writer.Write((uint)index[j].v1); writer.Write((uint)index[j].v2); writer.Write((uint)index[j].v3); } } } } return(true); }
// ReSharper disable once InconsistentNaming public bool Write(Chunked chunked, Stream output, List <byte> LODs, Dictionary <ulong, List <ImageLayer> > layers, object[] data) { IChunk chunk = chunked.FindNextChunk("MNRM").Value; if (chunk == null) { return(false); } MNRM model = (MNRM)chunk; chunk = chunked.FindNextChunk("CLDM").Value; CLDM materials = null; if (chunk != null) { materials = (CLDM)chunk; } chunk = chunked.FindNextChunk("lksm").Value; lksm skeleton = null; if (chunk != null) { skeleton = (lksm)chunk; } chunk = chunked.FindNextChunk("PRHM").Value; PRHM hardpoints = null; if (chunk != null) { hardpoints = (PRHM)chunk; } short[] hierarchy = (short[])skeleton?.Hierarchy.Clone(); Dictionary <int, HTLC.ClothNode> nodeMap = new Dictionary <int, HTLC.ClothNode>(); if (chunked.FindNextChunk("HTLC").Value is HTLC cloth) { uint clothIndex = 0; foreach (HTLC.ClothNode[] nodeCollection in cloth.Nodes) { if (nodeCollection == null) { continue; } int nodeIndex = 0; foreach (HTLC.ClothNode node in nodeCollection) { int parentRaw = node.VerticalParent; if (cloth.NodeBones[clothIndex].ContainsKey(nodeIndex) && cloth.NodeBones[clothIndex].ContainsKey(parentRaw)) { if (cloth.NodeBones[clothIndex][nodeIndex] != -1) { hierarchy[cloth.NodeBones[clothIndex][nodeIndex]] = cloth.NodeBones[clothIndex][parentRaw]; if (cloth.NodeBones[clothIndex][parentRaw] == -1) { HTLC.ClothNodeWeight weightedBone = node.Bones.Aggregate((i1, i2) => i1.Weight > i2.Weight ? i1 : i2); hierarchy[cloth.NodeBones[clothIndex][nodeIndex]] = weightedBone.Bone; } } } else { if (cloth.NodeBones[clothIndex].ContainsKey(nodeIndex)) // if on main skele { if (cloth.NodeBones[clothIndex][nodeIndex] != -1) { hierarchy[cloth.NodeBones[clothIndex][nodeIndex]] = -1; HTLC.ClothNodeWeight weightedBone = node.Bones.Aggregate((i1, i2) => i1.Weight > i2.Weight ? i1 : i2); hierarchy[cloth.NodeBones[clothIndex][nodeIndex]] = weightedBone.Bone; } } } if (cloth.NodeBones[clothIndex].ContainsKey(nodeIndex)) { // good code: if (cloth.NodeBones[clothIndex][nodeIndex] != -1) { nodeMap[cloth.NodeBones[clothIndex][nodeIndex]] = node; } } nodeIndex++; } clothIndex++; } } using (BinaryWriter writer = new BinaryWriter(output)) { writer.Write((ushort)1); // version major writer.Write((ushort)4); // version minor if (data.Length > 1 && data[1] is string && ((string)data[1]).Length > 0) { writer.Write((string)data[1]); } else { writer.Write((byte)0); } if (data.Length > 2 && data[2] is string && ((string)data[2]).Length > 0) { writer.Write((string)data[2]); } else { writer.Write((byte)0); } if (skeleton == null) { writer.Write((ushort)0); // number of bones } else { writer.Write(skeleton.Data.bonesAbs); } // ReSharper disable once InconsistentNaming Dictionary <byte, List <int> > LODMap = new Dictionary <byte, List <int> >(); uint sz = 0; uint lookForLod = 0; bool lodOnly = data.Length > 3 && data[3] is bool && (bool)data[3]; for (int i = 0; i < model.Submeshes.Length; ++i) { SubmeshDescriptor submesh = model.Submeshes[i]; if (data.Length > 4 && data[4] is bool && (bool)data[4]) { if (submesh.flags == SubmeshFlags.COLLISION_MESH) { continue; } } if (LODs != null && !LODs.Contains(submesh.lod)) { continue; } if (lodOnly && lookForLod > 0 && submesh.lod != lookForLod) { continue; } if (!LODMap.ContainsKey(submesh.lod)) { LODMap.Add(submesh.lod, new List <int>()); } lookForLod = submesh.lod; sz++; LODMap[submesh.lod].Add(i); } //long meshCountPos = writer.BaseStream.Position; writer.Write(sz); writer.Write(hardpoints?.HardPoints.Length ?? 0); if (skeleton != null) { for (int i = 0; i < skeleton.Data.bonesAbs; ++i) { writer.Write(IdToString("bone", skeleton.IDs[i])); short parent = hierarchy[i]; if (parent == -1) { parent = (short)i; } writer.Write(parent); Matrix3x4 bone = skeleton.Matrices34[i]; Quaternion rot = new Quaternion(bone[0, 0], bone[0, 1], bone[0, 2], bone[0, 3]); Vector3 scl = new Vector3(bone[1, 0], bone[1, 1], bone[1, 2]); Vector3 pos = new Vector3(bone[2, 0], bone[2, 1], bone[2, 2]); if (nodeMap.ContainsKey(i)) { HTLC.ClothNode thisNode = nodeMap[i]; pos.X = thisNode.X; pos.Y = thisNode.Y; pos.Z = thisNode.Z; } writer.Write(pos.X); writer.Write(pos.Y); writer.Write(pos.Z); writer.Write(scl.X); writer.Write(scl.Y); writer.Write(scl.Z); writer.Write(rot.X); writer.Write(rot.Y); writer.Write(rot.Z); writer.Write(rot.W); } } foreach (KeyValuePair <byte, List <int> > kv in LODMap) { foreach (int i in kv.Value) { SubmeshDescriptor submesh = model.Submeshes[i]; ModelVertex[] vertex = model.Vertices[i]; ModelVertex[] normal = model.Normals[i]; ModelUV[][] uv = model.TextureCoordinates[i]; ModelIndice[] index = model.Indices[i]; ModelBoneData[] bones = model.Bones[i]; writer.Write($"Submesh_{i}.{kv.Key}.{materials.Materials[submesh.material]:X16}"); writer.Write(materials.Materials[submesh.material]); writer.Write((byte)uv.Length); writer.Write(vertex.Length); writer.Write(index.Length); for (int j = 0; j < vertex.Length; ++j) { writer.Write(vertex[j].x); writer.Write(vertex[j].y); writer.Write(vertex[j].z); writer.Write(-normal[j].x); writer.Write(-normal[j].y); writer.Write(-normal[j].z); foreach (ModelUV[] t in uv) { writer.Write(t[j].u); writer.Write(t[j].v); } if (skeleton != null && bones != null && bones[j].boneIndex != null && bones[j].boneWeight != null) { writer.Write((byte)4); writer.Write(skeleton.Lookup[bones[j].boneIndex[0]]); writer.Write(skeleton.Lookup[bones[j].boneIndex[1]]); writer.Write(skeleton.Lookup[bones[j].boneIndex[2]]); writer.Write(skeleton.Lookup[bones[j].boneIndex[3]]); writer.Write(bones[j].boneWeight[0]); writer.Write(bones[j].boneWeight[1]); writer.Write(bones[j].boneWeight[2]); writer.Write(bones[j].boneWeight[3]); } else { // bone -> size + index + weight writer.Write((byte)0); } } List <ModelIndiceModifiable> indexNew = new List <ModelIndiceModifiable>(); foreach (ModelIndice indice in index) { indexNew.Add(new ModelIndiceModifiable { v1 = indice.v1, v2 = indice.v2, v3 = indice.v3 }); } foreach (ModelIndiceModifiable indice in indexNew) { writer.Write((byte)3); writer.Write(indice.v1); writer.Write(indice.v2); writer.Write(indice.v3); } } } if (hardpoints != null) { // attachments foreach (PRHM.HardPoint hp in hardpoints.HardPoints) { writer.Write(IdToString("attachment_", GUID.Index(hp.HardPointGUID))); Matrix4 mat = hp.Matrix.ToOpenTK(); Vector3 pos = mat.ExtractTranslation(); Quaternion rot = mat.ExtractRotation(); writer.Write(pos.X); writer.Write(pos.Y); writer.Write(pos.Z); writer.Write(rot.X); writer.Write(rot.Y); writer.Write(rot.Z); writer.Write(rot.W); } // extension 1.1 foreach (PRHM.HardPoint hp in hardpoints.HardPoints) { writer.Write(IdToString("bone", GUID.Index(hp.GUIDx012))); } } // ext 1.3: cloth writer.Write(0); // ext 1.4: embedded refpose if (skeleton != null) { for (int i = 0; i < skeleton.Data.bonesAbs; ++i) { writer.Write(IdToString("bone", skeleton.IDs[i])); short parent = hierarchy[i]; // if (parent == -1) { // parent = (short)i; // } writer.Write(parent); Matrix3x4 bone = skeleton.Matrices34Inverted[i]; // Quaternion3D quat = new Quaternion3D(bone[0, 0], bone[0, 1], bone[0, 2], bone[0, 3]); // why are they different Quaternion3D quat = new Quaternion3D(bone[0, 3], bone[0, 0], bone[0, 1], bone[0, 2]); Vector3D rot = C3D.ToEulerAngles(quat); if (rot.X == -3.14159274f && rot.Y == 0 && rot.Z == 0) { rot = new Vector3D(0, 3.14159274f, 3.14159274f); // effectively the same but you know, eulers. } Vector3 scl = new Vector3(bone[1, 0], bone[1, 1], bone[1, 2]); Vector3 pos = new Vector3(bone[2, 0], bone[2, 1], bone[2, 2]); writer.Write(pos.X); writer.Write(pos.Y); writer.Write(pos.Z); writer.Write(scl.X); writer.Write(scl.Y); writer.Write(scl.Z); writer.Write(rot.X); writer.Write(rot.Y); writer.Write(rot.Z); } } } return(true); }
public bool Write(Chunked chunked, Stream output, List <byte> LODs, Dictionary <ulong, List <ImageLayer> > layers, object[] opts) { IChunk chunk = chunked.FindNextChunk("MNRM").Value; if (chunk == null) { return(false); } MNRM model = (MNRM)chunk; chunk = chunked.FindNextChunk("CLDM").Value; CLDM materials = null; if (chunk != null) { materials = (CLDM)chunk; } NumberFormatInfo numberFormatInfo = new NumberFormatInfo(); numberFormatInfo.NumberDecimalSeparator = "."; using (StreamWriter writer = new StreamWriter(output)) { uint faceOffset = 1; if (opts.Length > 1 && opts[1] != null && opts[1].GetType() == typeof(string) && ((string)opts[1]).Length > 0) { writer.WriteLine("mtllib {0}", (string)opts[1]); } Dictionary <byte, List <int> > LODMap = new Dictionary <byte, List <int> >(); for (int i = 0; i < model.Submeshes.Length; ++i) { SubmeshDescriptor submesh = model.Submeshes[i]; if (opts.Length > 4 && opts[4] != null && opts[4].GetType() == typeof(bool) && (bool)opts[4] == true) { if (submesh.flags == SubmeshFlags.COLLISION_MESH) { continue; } } if (LODs != null && !LODs.Contains(submesh.lod)) { continue; } if (!LODMap.ContainsKey(submesh.lod)) { LODMap.Add(submesh.lod, new List <int>()); } LODMap[submesh.lod].Add(i); } foreach (KeyValuePair <byte, List <int> > kv in LODMap) { //Console.Out.WriteLine("Writing LOD {0}", kv.Key); writer.WriteLine("o Submesh_{0}", kv.Key); foreach (int i in kv.Value) { SubmeshDescriptor submesh = model.Submeshes[i]; if (materials != null) { writer.WriteLine("g Material_{0:X16}", materials.Materials[submesh.material]); writer.WriteLine("usemtl {0:X16}", materials.Materials[submesh.material]); } else { writer.WriteLine("g Material_{0}", i); } ModelVertex[] vertex = model.Vertices[i]; ModelVertex[] normal = model.Normals[i]; ModelUV[][] uvs = model.TextureCoordinates[i]; ModelUV[] uv = uvs[0]; ModelIndice[] index = model.Indices[i]; for (int j = 0; j < vertex.Length; ++j) { writer.WriteLine("v {0} {1} {2}", vertex[j].x, vertex[j].y, vertex[j].z); } for (int j = 0; j < vertex.Length; ++j) { writer.WriteLine("vt {0} {1}", uv[j].u.ToString("0.######", numberFormatInfo), uv[j].v.ToString("0.######", numberFormatInfo)); } if (uvs.Length > 1) { for (int j = 0; j < uvs.Length; ++j) { for (int k = 0; k < vertex.Length; ++k) { writer.WriteLine("vt{0} {0} {1}", j, uvs[j][k].u.ToString("0.######", numberFormatInfo), uvs[j][k].v.ToString("0.######", numberFormatInfo)); } } } for (int j = 0; j < vertex.Length; ++j) { writer.WriteLine("vn {0} {1} {2}", normal[j].x, normal[j].y, normal[j].z); } writer.WriteLine(""); for (int j = 0; j < index.Length; ++j) { writer.WriteLine("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}", index[j].v1 + faceOffset, index[j].v2 + faceOffset, index[j].v3 + faceOffset); } faceOffset += (uint)vertex.Length; writer.WriteLine(""); } if (opts.Length > 3 && opts[3] != null && opts[3].GetType() == typeof(bool) && (bool)opts[3] == true) { break; } } } return(true); }