Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions UnityPy/helpers/MeshHelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ def decompress_compressed_mesh(self):
shape=(uvDim,),
)
setattr(self, f"m_UV{uv_channel}", m_UV)
uvSrcOffset = uvDim * m_VertexCount
uvSrcOffset += uvDim * m_VertexCount
else:
self.m_UV0 = unpack_floats(m_CompressedMesh.m_UV, 0, m_VertexCount * 2, shape=(2,))
if m_CompressedMesh.m_UV.m_NumItems >= m_VertexCount * 4:
Expand Down Expand Up @@ -608,7 +608,7 @@ def decompress_compressed_mesh(self):
# we read three weights, but they don't add up to one. calculate the fourth one, and read
# missing bone index. continue with next vertex.
elif j == 3: #
boneWeights[vertexIndex][j] = 1 - sum
boneWeights[vertexIndex][j] = (31 - sum) / 31
boneIndices[vertexIndex][j] = next(boneIndicesIterator)

vertexIndex += 1
Expand Down
110 changes: 110 additions & 0 deletions tests/test_compressed_mesh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from types import SimpleNamespace

import pytest

from UnityPy.classes.generated import (
AABB,
ChannelInfo,
CompressedMesh,
Mesh,
PackedBitVector,
Vector3f,
VertexData,
)
from UnityPy.helpers.MeshHelper import MeshHandler

VERSION = (2021, 3, 0, 0)


def pack_ints(values, bit_size):
data = bytearray((len(values) * bit_size + 7) // 8)
for i, value in enumerate(values):
for bit in range(bit_size):
if value >> bit & 1:
pos = i * bit_size + bit
data[pos // 8] |= 1 << (pos % 8)
return PackedBitVector(m_Data=list(data), m_NumItems=len(values), m_BitSize=bit_size)


def pack_floats(values, bit_size, start=0.0, range_=1.0):
top = (1 << bit_size) - 1
packed = pack_ints([round((value - start) / range_ * top) for value in values], bit_size)
packed.m_Start = start
packed.m_Range = range_
return packed


def compressed_mesh(vertex_count, m_UVInfo=0, **vectors):
names = (
"m_BoneIndices",
"m_NormalSigns",
"m_Normals",
"m_TangentSigns",
"m_Tangents",
"m_Triangles",
"m_UV",
"m_Weights",
)
compressed = CompressedMesh(
m_Vertices=pack_floats([0.0] * vertex_count * 3, 8),
m_UVInfo=m_UVInfo,
**{name: vectors.get(name, PackedBitVector(m_Data=[], m_NumItems=0)) for name in names},
)
mesh = Mesh(
m_BindPose=[],
m_CompressedMesh=compressed,
m_IndexBuffer=[],
m_IndexFormat=0,
m_LocalAABB=AABB(m_Center=Vector3f(0, 0, 0), m_Extent=Vector3f(1, 1, 1)),
m_MeshCompression=1,
m_Name="mesh",
m_SubMeshes=[],
m_VertexData=VertexData(
m_DataSize=b"",
m_VertexCount=0,
m_Channels=[ChannelInfo(dimension=0, format=0, offset=0, stream=0)],
),
)
mesh.object_reader = SimpleNamespace(version=VERSION)
handler = MeshHandler(mesh)
handler.process()
return handler


def test_compressed_bone_weights():
# vertex 0: three weights that do not reach 31/31, so the fourth weight is implied
# vertex 1: one weight of 31/31, the remaining influences are empty
handler = compressed_mesh(
2,
m_Weights=pack_ints([10, 10, 5, 31], 5),
m_BoneIndices=pack_ints([0, 1, 2, 3, 7], 4),
)
expected = [(10 / 31, 10 / 31, 5 / 31, 6 / 31), (1.0, 0, 0, 0)]
assert len(handler.m_BoneWeights) == len(expected)
for weights, expected_weights in zip(handler.m_BoneWeights, expected):
assert weights == pytest.approx(expected_weights)
assert sum(weights) == pytest.approx(1.0)
assert handler.m_BoneIndices == [(0, 1, 2, 3), (7, 0, 0, 0)]


def test_compressed_uv_sets():
# sets 0, 1 and 3 with 2, 3 and 2 components, stored back to back in m_UV
uv_sets = {
0: [(0.125, 0.25), (0.375, 0.5)],
1: [(0.5, 0.625, 0.75), (0.875, 1.0, 0.0)],
3: [(0.0625, 0.1875), (0.3125, 0.4375)],
}
uv_info = 0
for channel, values in uv_sets.items():
uv_info |= (4 | (len(values[0]) - 1)) << (channel * 4)
handler = compressed_mesh(
2,
m_UVInfo=uv_info,
m_UV=pack_floats([v for values in uv_sets.values() for uv in values for v in uv], 16),
)
for channel, values in uv_sets.items():
decoded = getattr(handler, f"m_UV{channel}")
assert len(decoded) == len(values)
for uv, expected in zip(decoded, values):
assert uv == pytest.approx(expected, abs=1e-4)
assert handler.m_UV2 is None
Loading