Skip to content

Commit 4f664ea

Browse files
Preserve neck morph data from vanilla models
1 parent 65ccb85 commit 4f664ea

File tree

5 files changed

+174
-7
lines changed

5 files changed

+174
-7
lines changed

xivModdingFramework/Models/DataContainers/LevelOfDetail.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ public EMeshType GetMeshType(int offset)
9797

9898
/// <summary>
9999
/// Unknown Usage
100+
/// This appears to be multiple individual byte values, with the first 2 being related to neck morph data
100101
/// </summary>
101102
public int Unknown7 { get; set; }
102103

xivModdingFramework/Models/DataContainers/MdlModelData.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ public class MdlModelData
172172
/// </summary>
173173
public byte BgCrestChangeMaterialIndex { get; set; }
174174

175-
public byte Unknown12 { get; set; }
175+
public byte NeckMorphTableSize { get; set; }
176176

177177
/// <summary>
178178
/// Unknown Usage
@@ -241,7 +241,7 @@ public static MdlModelData Read(BinaryReader br)
241241
BgChangeMaterialIndex = br.ReadByte(),
242242
BgCrestChangeMaterialIndex = br.ReadByte(),
243243

244-
Unknown12 = br.ReadByte(),
244+
NeckMorphTableSize = br.ReadByte(),
245245
BoneSetSize = br.ReadInt16(),
246246

247247
Unknown13 = br.ReadInt16(),
@@ -278,7 +278,7 @@ public void Write(BinaryWriter br)
278278
br.Write((byte)Flags3);
279279
br.Write(BgChangeMaterialIndex);
280280
br.Write(BgCrestChangeMaterialIndex);
281-
br.Write(Unknown12);
281+
br.Write(NeckMorphTableSize);
282282
br.Write(BoneSetSize);
283283
br.Write(Unknown13);
284284
br.Write(Unknown14);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// xivModdingFramework
2+
// Copyright © 2018 Rafael Gonzalez - All Rights Reserved
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
using SharpDX;
18+
using System.Collections.Generic;
19+
20+
namespace xivModdingFramework.Models.DataContainers
21+
{
22+
/// <summary>
23+
/// Class representing a neck morph table entry in a model, which modifies a vertex on the neck seam of a character's body when active.
24+
/// Typically 10 of these entries exist on a head mesh.
25+
/// It is unclear how each entry relates to individual vertex on the body mesh.
26+
/// </summary>
27+
public class NeckMorphEntry
28+
{
29+
/// <summary>
30+
/// Relative adjustment of the Position of the vertex.
31+
/// Its unclear how this is applied, but it seems affected by the referenced bones.
32+
/// </summary>
33+
public Vector3 PositionAdjust { get; set; }
34+
35+
/// <summary>
36+
/// This value is unclear but preserving it is important.
37+
/// If it is incorrect, the vertex adjustments disappear when viewed from certain camera angles.
38+
/// For all known working examples, it just has the value 0x00006699.
39+
/// </summary>
40+
public uint Unknown { get; set; }
41+
42+
/// <summary>
43+
/// Relative adjustment of the Normal of the vertex.
44+
/// Its unclear how this is applied.
45+
/// </summary>
46+
public Vector3 NormalAdjust { get; set; }
47+
48+
/// <summary>
49+
/// A list of bones, stored as indexes in to MdlPathData.BoneList.
50+
/// For all known working examples, the referenced bones are ["j_kubi", "j_sebo_c"].
51+
/// If the incorrect bones are referenced, the neck behavior becomes erratic.
52+
/// NOTE: In the model file these are actually stored as indexes in to BoneSet 0.
53+
/// </summary>
54+
public List<short> Bones { get; set; }
55+
}
56+
}

xivModdingFramework/Models/DataContainers/XivMdl.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,5 +141,10 @@ public class XivMdl
141141
/// This happens when the sum of all LoD mesh counts is less than the model data mesh count
142142
/// </remarks>
143143
public List<MeshData> ExtraMeshData { get; set; }
144+
145+
/// <summary>
146+
/// This data is present on heads and seems to affect the shape of the neck on the body mesh
147+
/// </summary>
148+
public List<NeckMorphEntry> NeckMorphTable { get; set; }
144149
}
145150
}

xivModdingFramework/Models/FileTypes/Mdl.cs

Lines changed: 109 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -894,7 +894,7 @@ public static XivMdl GetXivMdl(byte[] mdlData, string mdlPath = "")
894894

895895
#endregion
896896

897-
#region Part Bone Sets & Padding
897+
#region Part Bone Sets
898898
// Bone index for Parts
899899
var partBoneSet = new BoneSet
900900
{
@@ -908,7 +908,43 @@ public static XivMdl GetXivMdl(byte[] mdlData, string mdlPath = "")
908908
}
909909

910910
xivMdl.PartBoneSets = partBoneSet;
911+
#endregion
912+
913+
#region Neck Morph Data
914+
// Neck morph data (appears on face models new in Patch 7.1)
915+
xivMdl.NeckMorphTable = new List<NeckMorphEntry>();
916+
for (var i = 0; i < xivMdl.ModelData.NeckMorphTableSize; ++i)
917+
{
918+
var neckMorphDataEntry = new NeckMorphEntry
919+
{
920+
PositionAdjust = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()),
921+
Unknown = br.ReadUInt32(),
922+
NormalAdjust = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()),
923+
Bones = new List<short>()
924+
};
925+
byte[] neckBoneTable = br.ReadBytes(4);
926+
// Weird code alert:
927+
// - Most vanilla heads legitimately have a zero value in the second slot of the table.
928+
// - Female Hrothgar heads legitimately have a zero value in the first slot of the table.
929+
// - Values in the third and fourth slot seem to be unused, and also seem to use 0 as a padding or null value.
930+
// - However, at least one vanilla model seems to have a non-zero value in the third slot of the table.
931+
// Therefore, only in the third and fourth slot is a zero value treated as an early list terminator.
932+
// This means the table should always contain at least two bones.
933+
for (int j = 0; j < neckBoneTable.Length; ++j)
934+
{
935+
int boneset0_index = neckBoneTable[j];
936+
if (j >= 2 && boneset0_index == 0) break; // Treat this case as an early list terminator.
937+
// Resolve the bone to an index in the bone path table here, to make the in-memory representation a little more normal
938+
if (xivMdl.MeshBoneSets.Count > 0 && xivMdl.MeshBoneSets[0].BoneIndices.Count() > boneset0_index)
939+
{
940+
neckMorphDataEntry.Bones.Add(xivMdl.MeshBoneSets[0].BoneIndices[boneset0_index]);
941+
}
942+
}
943+
xivMdl.NeckMorphTable.Add(neckMorphDataEntry);
944+
}
945+
#endregion
911946

947+
#region Padding
912948
// Padding
913949
xivMdl.PaddingSize = br.ReadByte();
914950
xivMdl.PaddedBytes = br.ReadBytes(xivMdl.PaddingSize);
@@ -2987,8 +3023,10 @@ public static byte[] MakeUncompressedMdlFile(TTModel ttModel, XivMdl ogMdl, Acti
29873023
basicModelBlock.Add(bgChangeIdx);
29883024
basicModelBlock.Add(crestChangeIdx);
29893025

2990-
// More Unknowns
2991-
basicModelBlock.Add(ogModelData.Unknown12);
3026+
// Using neck morph data from original modal
3027+
// The field currently named LevelOfDetail.Unknown7 also contains this number, and also gets copied from the original model
3028+
var neckMorphTableSizePointer = basicModelBlock.Count; // we want to reset this to 0 later if the neck data cannot be preserved
3029+
basicModelBlock.Add(ogModelData.NeckMorphTableSize);
29923030

29933031
// We fix this pointer later after bone table is done.
29943032
var boneSetSizePointer = basicModelBlock.Count;
@@ -3537,6 +3575,64 @@ public static byte[] MakeUncompressedMdlFile(TTModel ttModel, XivMdl ogMdl, Acti
35373575

35383576
#endregion
35393577

3578+
// Neck Morph Data
3579+
#region Neck Morph Data
3580+
var neckMorphDataBlock = new List<byte>();
3581+
// Preserve the original model's neck morph data if present -- but update the bone references inside of it
3582+
// Bone references are made via BoneSet[0]
3583+
for (int i = 0; i < ogMdl.NeckMorphTable.Count; ++i)
3584+
{
3585+
// Extract the original data (except the bone list)
3586+
var positionAdjust = ogMdl.NeckMorphTable[i].PositionAdjust;
3587+
var unknown = ogMdl.NeckMorphTable[i].Unknown;
3588+
var normalAdjust = ogMdl.NeckMorphTable[i].NormalAdjust;
3589+
var bones = new List<byte>();
3590+
3591+
// Look up the originally referenced bone by name, and map it to the same bone in the imported model
3592+
for (int j = 0; j < ogMdl.NeckMorphTable[i].Bones.Count; ++j)
3593+
{
3594+
string ogBoneName = ogMdl.PathData.BoneList[ogMdl.NeckMorphTable[i].Bones[j]];
3595+
int boneset0Index = -1;
3596+
3597+
if (ttModel.MeshGroups.Count > 0)
3598+
{
3599+
boneset0Index = ttModel.MeshGroups[0].Bones.FindIndex(x => x == ogBoneName);
3600+
}
3601+
3602+
// If a bone can't be located in the new model, just discard all of the neck morph data
3603+
if (boneset0Index == -1 || boneset0Index > 255)
3604+
{
3605+
loggingFunction(true, "Could not match bones in neck morph data, so the data was discarded!");
3606+
// Reset the table size to 0 in the model header and drop the data
3607+
// (Two bytes are also cleared later on when writing the LODs)
3608+
basicModelBlock[neckMorphTableSizePointer] = 0;
3609+
neckMorphDataBlock = new List<byte>();
3610+
break;
3611+
}
3612+
3613+
bones.Add((byte)boneset0Index);
3614+
}
3615+
3616+
// Serialize
3617+
neckMorphDataBlock.AddRange(BitConverter.GetBytes(positionAdjust.X));
3618+
neckMorphDataBlock.AddRange(BitConverter.GetBytes(positionAdjust.Y));
3619+
neckMorphDataBlock.AddRange(BitConverter.GetBytes(positionAdjust.Z));
3620+
neckMorphDataBlock.AddRange(BitConverter.GetBytes(unknown));
3621+
neckMorphDataBlock.AddRange(BitConverter.GetBytes(normalAdjust.X));
3622+
neckMorphDataBlock.AddRange(BitConverter.GetBytes(normalAdjust.Y));
3623+
neckMorphDataBlock.AddRange(BitConverter.GetBytes(normalAdjust.Z));
3624+
3625+
// Bone list is always 4 bytes -- pad with zeroes
3626+
for (int j = 0; j < 4; ++j)
3627+
{
3628+
if (j < bones.Count)
3629+
neckMorphDataBlock.Add(bones[j]);
3630+
else
3631+
neckMorphDataBlock.Add(0);
3632+
}
3633+
}
3634+
#endregion
3635+
35403636
// Padding
35413637
#region Padding Data Block
35423638

@@ -3692,7 +3788,7 @@ public static byte[] MakeUncompressedMdlFile(TTModel ttModel, XivMdl ogMdl, Acti
36923788
// This is the offset to the beginning of the vertex data
36933789
var combinedDataBlockSize = _MdlHeaderSize + vertexInfoBlock.Count + pathInfoBlock.Count + basicModelBlock.Count + unknownDataBlock0.Length + (60 * ogMdl.LoDList.Count) + extraMeshesBlock.Count + meshDataBlock.Count +
36943790
attributePathDataBlock.Count + (unknownDataBlock1?.Length ?? 0) + meshPartDataBlock.Count + unknownDataBlock2.Length + matPathOffsetDataBlock.Count + bonePathOffsetDataBlock.Count +
3695-
boneSetsBlock.Count + FullShapeDataBlock.Count + partBoneSetsBlock.Count + paddingDataBlock.Count + boundingBoxDataBlock.Count + boneBoundingBoxDataBlock.Count;
3791+
boneSetsBlock.Count + FullShapeDataBlock.Count + partBoneSetsBlock.Count + neckMorphDataBlock.Count + paddingDataBlock.Count + boundingBoxDataBlock.Count + boneBoundingBoxDataBlock.Count;
36963792

36973793
var lodDataBlock = new List<byte>();
36983794
List<int> indexStartInjectPointers = new List<int>();
@@ -3735,6 +3831,14 @@ public static byte[] MakeUncompressedMdlFile(TTModel ttModel, XivMdl ogMdl, Acti
37353831
lodDataBlock.AddRange(BitConverter.GetBytes(ogMdl.LoDList[0].Unknown6));
37363832
lodDataBlock.AddRange(BitConverter.GetBytes(ogMdl.LoDList[0].Unknown7));
37373833

3834+
// If the neck morph data was discarded -- clear the morph counts here too
3835+
// (The first 2 bytes of what is currently named "Unknown7" seem to refer to the size of this table)
3836+
if (ogMdl.NeckMorphTable.Count > 0 && neckMorphDataBlock.Count == 0)
3837+
{
3838+
lodDataBlock[lodDataBlock.Count - 4] = 0;
3839+
lodDataBlock[lodDataBlock.Count - 3] = 0;
3840+
}
3841+
37383842
// Vertex & Index Sizes
37393843
lodDataBlock.AddRange(BitConverter.GetBytes(vertexDataSize));
37403844
lodDataBlock.AddRange(BitConverter.GetBytes(indexDataSize));
@@ -3771,6 +3875,7 @@ public static byte[] MakeUncompressedMdlFile(TTModel ttModel, XivMdl ogMdl, Acti
37713875
modelDataBlock.AddRange(boneSetsBlock);
37723876
modelDataBlock.AddRange(FullShapeDataBlock);
37733877
modelDataBlock.AddRange(partBoneSetsBlock);
3878+
modelDataBlock.AddRange(neckMorphDataBlock);
37743879
modelDataBlock.AddRange(paddingDataBlock);
37753880
modelDataBlock.AddRange(boundingBoxDataBlock);
37763881
modelDataBlock.AddRange(boneBoundingBoxDataBlock);

0 commit comments

Comments
 (0)