diff --git a/hippolyzer/lib/base/colladatools.py b/hippolyzer/lib/base/colladatools.py index d4f589b..15b1bf4 100644 --- a/hippolyzer/lib/base/colladatools.py +++ b/hippolyzer/lib/base/colladatools.py @@ -24,7 +24,6 @@ from lxml import etree import numpy as np import transformations -from hippolyzer.lib.base.datatypes import Vector3 from hippolyzer.lib.base.helpers import get_resource_filename from hippolyzer.lib.base.serialization import BufferReader from hippolyzer.lib.base.mesh import ( @@ -311,39 +310,11 @@ def fix_weird_bind_matrices(skin_seg: SkinSegmentDict) -> None: if not skin_seg['joint_names']: return - scale_fixup = Vector3(1, 1, 1) - angle_fixup = Vector3(0, 0, 0) - have_fixups = False - - # Totally non-scientific method of detecting odd bind matrices based on squinting very, - # very hard at a random sample of assets. - for joint_name, joint_inv in zip(skin_seg['joint_names'], skin_seg['inverse_bind_matrix']): - if not joint_name.startswith("m"): - # We can't make very good guesses based on collision volume scales and rotations, - # skip anything but the "m" joints. - continue - joint_mat = llsd_to_mat4(joint_inv) - joint_scale, _, joint_angle, _, _ = transformations.decompose_matrix(joint_mat) - # If the scale component of an mJointName joint isn't roughly <1,1,1>, we likely have - # scaling applied to the inverse bind matrices rather than the bind matrix. Figure out - # what the fixup should be so that we can reverse it. - if abs(3.0 - sum(joint_scale)) > 0.5: - scale_fixup = Vector3(1, 1, 1) / Vector3(*joint_scale) - have_fixups = True - # I wouldn't expect mJointName joints to be rotated at all in their inverse bind matrices. - # Is this a rotation that should've been applied to the bind shape matrix instead? - # In any event, all joints are likely rotated by this amount, so calculate the inverse. - if abs(sum(joint_angle)) > 0.05: - angle_fixup = -Vector3(*joint_angle) - have_fixups = True - - if have_fixups: - LOG.warning(f"Detected weird matrices in mesh! {scale_fixup!r}, {angle_fixup!r}") - # The magnitude of the scales in the inverse bind matrices look very strange. - # The bind matrix itself is probably messed up as well, try to fix it. - # TODO: put this back in, the previous logic was totally wrong-headed. - # DON'T MESS WITH INVERSE TRANSLATION!!!! Only bind shape gets its translation scaled. - pass + # TODO: calculate the correct inverse bind matrix scale & rotations from avatar_skeleton.xml + # definitions. If the rotation and scale factors are the same across all inverse bind matrices then + # they can be moved over to the bind shape matrix to keep Blender happy. + # Maybe add a scaled / rotated empty as a parent for the armature instead? + return def main(): diff --git a/hippolyzer/lib/base/gltftools.py b/hippolyzer/lib/base/gltftools.py index 8282084..9163d7a 100644 --- a/hippolyzer/lib/base/gltftools.py +++ b/hippolyzer/lib/base/gltftools.py @@ -5,6 +5,8 @@ WIP LLMesh -> glTF converter, for testing eventual glTF -> LLMesh conversion log # * Simple tests # * Round-tripping skinning data from Blender-compatible glTF back to LLMesh (maybe through rig retargeting?) # * Panda3D-glTF viewer for LLMesh? The glTFs seem to work fine in Panda3D-glTF's `gltf-viewer`. +# * Check if skew and projection components of transform matrices are ignored in practice as the spec requires. +# I suppose this would render some real assets impossible to represent with glTF. import dataclasses import math diff --git a/hippolyzer/lib/base/mesh.py b/hippolyzer/lib/base/mesh.py index 8eae647..39cdc9f 100644 --- a/hippolyzer/lib/base/mesh.py +++ b/hippolyzer/lib/base/mesh.py @@ -281,6 +281,17 @@ class VertexWeights(se.SerializableBase): @classmethod def deserialize(cls, reader: se.Reader, ctx=None): + # NOTE: normally you'd want to do something like arrange this into a nicely + # aligned byte array with zero padding so that you could vectorize the decoding. + # In cases where having a vertex with no weights is semantically equivalent to + # having a vertex _with_ weights of a value of 0.0 that's fine. This isn't the case + # in LL's implementation of mesh: + # + # https://bitbucket.org/lindenlab/viewer/src/d31a83fb946c49a38376ea3b312b5380d0c8c065/indra/llmath/llvolume.cpp#lines-2560:2628 + # + # Consider the difference between handling of b"\x00\x00\x00\xFF" and b"\xFF" with the above logic. + # To simplify round-tripping while preserving those semantics, we don't do a vectorized decode. + # I had a vectorized numpy version, but those requirements made everything a bit of a mess. influence_list = [] for _ in range(cls.INFLUENCE_LIMIT): joint_idx = reader.read_bytes(1)[0] @@ -357,7 +368,7 @@ LOD_SEGMENT_SERIALIZER = SegmentSerializer({ se.QuantizedNumPyArray(se.NumPyArray(se.BytesGreedy(), LE_U16, 2), 0.0, 1.0), Vector2, ), - # Normals have a static domain between -1 and 1, so just use that. + # Normals have a static domain between -1 and 1, so we just use that rather than 0.0 - 1.0. "Normal": VecListAdapter( se.QuantizedNumPyArray(se.NumPyArray(se.BytesGreedy(), LE_U16, 3), -1.0, 1.0), Vector3, diff --git a/tests/base/test_mesh.py b/tests/base/test_mesh.py index 1e72f72..5e087aa 100644 --- a/tests/base/test_mesh.py +++ b/tests/base/test_mesh.py @@ -40,6 +40,8 @@ class TestMesh(unittest.TestCase): writer.write(serializer, reader.read(serializer)) second_buf = writer.copy_buffer() self.assertEqual(first_buf, second_buf) + # Dates may not round-trip correctly, but length should always be the same + self.assertEqual(len(first_buf), len(self.slm_bytes)) def test_serialize_raw_segments(self): serializer = LLMeshSerializer(include_raw_segments=True)