ousnius / BodySlide-and-Outfit-Studio

BodySlide and Outfit Studio, a tool to convert, create, and customize outfits and bodies for Bethesda games.
GNU General Public License v3.0
286 stars 63 forks source link

Import from .obj - Undesired Shape Translation #432

Closed GrafPanzer closed 2 years ago

GrafPanzer commented 2 years ago

I've just read the conversation at Issue #429, and want to bring this related issue up in case it hasn't already been addressed. My apologies if I'm retreading old ground.

Game: Fallout 4 Problem: Copying global-to-skin transforms from a reference model to an imported .obj generates undesired translation of the target mesh.

How to reproduce:

  1. Import an .obj (freshly exported from Blender) to OS and skin it to a reference model: a. File > New Project > From Template [CBBE Body] > Next > From File [select .obj] > Finish > Yes (copy ref's global-to-skin transforms) b. Check properties = Transforms remain at origin c. Copy bone weights > Okay d. Check properties = Transforms match reference
  2. File > Load Reference > From Template [CBBE Body] > Finish. Shape jumps to a position above the reference model (transforms still match reference).

Alternate step 2. a. Save as .nif b. File > New Project > From Template [CBBE Body] > Next > From File [select saved .nif] > Finish. Shape jumps to a position above the reference model (transforms still match reference).

How to work around:

  1. Import an .obj (freshly exported from Blender) to OS and skin it to a reference model: a. File > New Project > From Template [CBBE Body] > Next > From File [select .obj] > Finish > No [do not copy ref's global-to-skin transforms]. Transforms remain at origin. c. Copy bone weights > Okay. Transforms remain at origin.
  2. Load another reference and reskin the .obj shape a. File > Load Reference > From Template [CBBE Body] > Finish. Shape remains in place, transforms remain at origin. b. Copy bone weights > Okay [leave the two boxes checked at the bottom of the dialog]. Shape remains in place, transforms now match reference.
sts1skj commented 2 years ago

This bug is in OutfitProject::ImportOBJ, at the end of the function, in the block if (copyBaseSkinTrans). The minimal code fix would be to just delete this block.

The "global-to-skin transform" is typically stored in three places inside Outfit Studio:

Here's the explanation for each step in your reproduction procedure:

1.a. If you pick "Yes" when ImportOBJ asks whether you want to copy the reference's global-to-skin transforms, this triggers the bug. The bug stores the new (copied from ref) global-to-skin transform in AnimSkin. But, since commit 94ccd2d, OBJ and FBX files are imported unskinned. There should be no information stored in AnimSkin, because the shape is not skinned. Storing the information there gets the transforms for the shape out of sync: AnimSkin thinks the shape is skinned with a non-identity transform, while NifFile thinks the shape is unskinned with an identity transform The vertex coordinates are not adjusted to match the copied transform. The Mesh is initialized from the NifFile, so it displays where it's supposed to.

1.b. Shape Properties can show you one of two different transforms, depending on whether the shape is skinned or not. It asks NifFile if the shape is skinned, not AnimSkin, and NifFile says no. So it displays the unskinned shape's node-to-parent transform from NifFile, which is still the identity.

1.c. Copy Bone Weights asks AnimSkin, not NifFile, if the shape is skinned, and AnimSkin says yes. The reference shape and the new shape have the same transforms according to AnimSkin, since step 1.a. made them that way, so Copy Bone Weights thinks everything is great, and it doesn't even display the option to set the global-to-skin transform from the reference. The act of copying bone weights to a shape forces it to become skinned (since unskinned shapes do not have bones or bone weights). By the time the operation is finished, NifFile believes the new shape is skinned too. But NifFile's global-to-skin transform is never set, so it remains just the identity transform. The Mesh is not updated either, so it still displays in the same location, even though its global-to-skin transform in AnimSkin says that it should be somewhere else.

1.d. Now Shape Properties asks NifFile if the shape is skinned, and it says yes. Shape Properties then displays the global-to-skin transform from AnimSkin, which matches the reference shape's. But the NifFile's transform is still the identity, and the displayed Mesh's transforms still haven't been updated.

  1. Loading a reference is one of many possible operations that causes all the Mesh data to be reloaded, in the function wxGLPanel::AddMeshFromNif. For shapes that are skinned (according to NifFile), this function sets the Mesh's transforms from AnimSkin. Suddenly the shape is shown lifted up. But the NifFile's transform is still the identity.

For step 2, anything that triggers AddMeshFromNif will trigger the mesh to move. For example, using the flip-edge tool to flip a triangle edge will do it. But the NifFile's transform is still the identity.

Alternate 2.a. Saving to Nif causes the transform to be copied from AnimSkin to NifFile. Now only Mesh still has the identity transform. So the shape appears in the right place, but the transforms in AnimSkin, NifFile, and the actual file are all wrong (since they were copied from the reference without adjusting the vertex coordinates to match).

Alternate 2.b. Reloading everything causes all the transforms to be reset from the new NifFile.

The workaround, as you point out, is to answer "No" when ImportOBJ or ImportFBX asks you if you want to copy the reference's global-to-skin transform.

One of the reasons this is all so complicated is that, starting with Fallout 4, the NIF file (and thus NifFile) does not store the global-to-skin transform directly. Instead, it has to be inferred. The code for calculating it is actually quite complex. That's a big reason why AnimSkin is the primary storage location for this information inside Outfit Studio, not NifFile. But in Skyrim and older games, the global-to-skin transform is stored in the NIF file (and thus NifFile). So messy.

In bug report #429 , I wrote a long message that concluded with a suggested solution to that bug. Fixing that bug would incidentally fix this bug, #432, because the option to copy the global-to-skin transform on OBJ/FBX import would be gone; I suggested a completely different way of selecting this important transform.

Incidentally, if you follow the workaround suggested in your first message here (not copying the ref's global-to-skin transform), and then save the result to NIF, the data will be damaged, just as reported in #429 . You really want the global-to-skin transform to be set to something good before you save, with all the vertex coordinates offset to match.

GrafPanzer commented 2 years ago

Great, I'm glad this is already fixed in the fix to #429. Until that's released, it's good to know that my workaround is valid. Thanks for the rundown on what's happening in the background. That mess is a good lesson in how painful backward compatibility can be to program, I guess. Thanks again.