-
-
Notifications
You must be signed in to change notification settings - Fork 185
Expand file tree
/
Copy pathMeshHelper.py
More file actions
702 lines (609 loc) · 26.9 KB
/
MeshHelper.py
File metadata and controls
702 lines (609 loc) · 26.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
from __future__ import annotations
import math
import struct
from typing import TYPE_CHECKING, List, Optional, Tuple, Union, cast
from ..classes.generated import (
ChannelInfo,
Mesh,
SpriteRenderData,
StreamInfo,
Vector2f,
Vector3f,
Vector4f,
)
from ..enums.MeshTopology import MeshTopology
from ..enums.VertexFormat import (
VERTEX_CHANNEL_FORMAT_STRUCT_TYPE_MAP,
VERTEX_FORMAT_2017_STRUCT_TYPE_MAP,
VERTEX_FORMAT_STRUCT_TYPE_MAP,
VertexChannelFormat,
VertexFormat,
VertexFormat2017,
)
from .PackedBitVector import unpack_floats, unpack_ints
from .ResourceReader import get_resource_data
try:
from UnityPy import UnityPyBoost
except ImportError:
UnityPyBoost = None
Tuple2f = Tuple[float, float]
Tuple3f = Tuple[float, float, float]
Tuple4f = Tuple[float, float, float, float]
def vector_list_to_tuples(
data: Union[List[Vector2f], List[Vector3f], List[Vector4f]],
) -> List[tuple]:
if isinstance(data[0], Vector2f):
return [(v.x, v.y) for v in data]
elif isinstance(data[0], Vector3f):
if TYPE_CHECKING:
data = cast(List[Vector3f], data)
return [(v.x, v.y, v.z) for v in data]
elif isinstance(data[0], Vector4f):
if TYPE_CHECKING:
data = cast(List[Vector4f], data)
return [(v.x, v.y, v.z, v.w) for v in data]
else:
raise ValueError("Unknown vector type")
def lists_to_tuples(data: List[list]) -> List[tuple]:
return [tuple(v) for v in data]
def zeros(m: int, n: int) -> List[list]:
return [[0] * n for _ in range(m)]
def normalize(*vector: float) -> Tuple[float, ...]:
length = math.sqrt(sum(v**2 for v in vector))
if length > 0.00001:
inv_norm = 1.0 / length
return tuple(v * inv_norm for v in vector)
return (0,) * len(vector)
class MeshHandler:
src: Union[Mesh, SpriteRenderData]
endianess: str = "<"
version: Tuple[int, int, int, int]
m_VertexCount: int = 0
m_Vertices: Optional[List[Tuple3f]] = None
# normals can be stored as Tuple4f,
# in such cases the 4th dimension is always 0 and can be discarded
m_Normals: Optional[Union[List[Tuple3f], List[Tuple4f]]] = None
m_Colors: Optional[List[Tuple4f]] = None
m_UV0: Optional[List[Tuple2f]] = None
m_UV1: Optional[List[Tuple2f]] = None
m_UV2: Optional[List[Tuple2f]] = None
m_UV3: Optional[List[Tuple2f]] = None
m_UV4: Optional[List[Tuple2f]] = None
m_UV5: Optional[List[Tuple2f]] = None
m_UV6: Optional[List[Tuple2f]] = None
m_UV7: Optional[List[Tuple2f]] = None
m_Tangents: Optional[List[Tuple4f]] = None
m_BoneIndices: Optional[List[Tuple[int, int, int, int]]] = None
m_BoneWeights: Optional[List[Tuple4f]] = None
m_IndexBuffer: Optional[List[int]] = None
m_Use16BitIndices: bool = True
def __init__(
self,
src: Union[Mesh, SpriteRenderData],
version: Optional[Tuple[int, int, int, int]] = None,
endianess: str = "<",
):
self.src = src
self.endianess = endianess
if version is not None:
self.version = version
elif not isinstance(src, SpriteRenderData) and src.object_reader is not None:
self.version = src.object_reader.version
else:
raise ValueError("No version provided and no object reader found")
def process(self):
mesh = self.src
vertex_data = mesh.m_VertexData
assert vertex_data is not None
m_Channels: list[ChannelInfo]
m_Streams: list[StreamInfo]
if self.version[0] < 4:
assert (
vertex_data.m_Streams_0_ is not None
and vertex_data.m_Streams_1_ is not None
and vertex_data.m_Streams_2_ is not None
and vertex_data.m_Streams_3_ is not None
)
m_Streams = [
vertex_data.m_Streams_0_,
vertex_data.m_Streams_1_,
vertex_data.m_Streams_2_,
vertex_data.m_Streams_3_,
]
assert all(stream is not None for stream in m_Streams)
m_Channels = self.get_channels(m_Streams)
elif self.version[0] == 4:
assert vertex_data.m_Streams is not None and vertex_data.m_Channels is not None
m_Streams = vertex_data.m_Streams
m_Channels = vertex_data.m_Channels
else:
assert vertex_data.m_Channels is not None
m_Channels = vertex_data.m_Channels
m_Streams = self.get_streams(m_Channels, vertex_data.m_VertexCount)
if (
isinstance(mesh, Mesh) and mesh.m_StreamData and mesh.m_StreamData.path
# and mesh.m_VertexData
# and mesh.m_VertexData.m_VertexCount
):
stream_data = mesh.m_StreamData
assert mesh.object_reader, "No object reader assigned to the input Mesh!"
data = get_resource_data(
stream_data.path,
mesh.object_reader.assets_file,
stream_data.offset,
stream_data.size,
)
vertex_data.m_DataSize = data
# try to copy data directly from mesh
if isinstance(mesh, Mesh):
if mesh.m_Use16BitIndices is not None:
self.m_Use16BitIndices = bool(mesh.m_Use16BitIndices)
elif (
(self.version >= (2017, 4))
or
# version == (2017, 3, 1) & patched - px string
(self.version[:2] == (2017, 3) and mesh.m_MeshCompression == 0)
):
self.m_Use16BitIndices = mesh.m_IndexFormat == 0
self.copy_from_mesh()
elif isinstance(mesh, SpriteRenderData):
self.copy_from_spriterenderdata()
else:
raise ValueError(f"Unknown mesh type {type(mesh)}")
if self.m_IndexBuffer:
raw_indices = bytes(self.m_IndexBuffer)
if self.m_Use16BitIndices:
char = "H"
index_size = 2
else:
char = "I"
index_size = 4
self.m_IndexBuffer = cast(
List[int],
struct.unpack(f"<{len(raw_indices) // index_size}{char}", raw_indices),
)
if self.version >= (3, 5):
self.read_vertex_data(m_Channels, m_Streams)
if isinstance(mesh, Mesh) and self.version >= (2, 6):
self.decompress_compressed_mesh()
if self.m_VertexCount == 0 and self.m_Vertices:
self.m_VertexCount = len(self.m_Vertices)
def copy_from_mesh(self):
"""Copy data from mesh to handler if it's not already set."""
mesh = self.src
if TYPE_CHECKING:
assert isinstance(mesh, Mesh)
if self.m_IndexBuffer is None and mesh.m_IndexBuffer:
self.m_IndexBuffer = mesh.m_IndexBuffer
if self.m_Vertices is None and mesh.m_Vertices:
self.m_Vertices = vector_list_to_tuples(mesh.m_Vertices)
if self.m_Normals is None and mesh.m_Normals:
self.m_Normals = vector_list_to_tuples(mesh.m_Normals)
if self.m_Tangents is None and mesh.m_Tangents:
self.m_Tangents = vector_list_to_tuples(mesh.m_Tangents)
if self.m_UV0 is None and mesh.m_UV:
self.m_UV0 = vector_list_to_tuples(mesh.m_UV)
if self.m_UV1 is None and mesh.m_UV1:
self.m_UV1 = vector_list_to_tuples(mesh.m_UV1)
if self.m_Colors is None and mesh.m_Colors:
self.m_Colors = [
(color.r / 255.0, color.g / 255.0, color.b / 255.0, color.a / 255.0) for color in mesh.m_Colors
]
if self.m_BoneWeights is None and mesh.m_Skin:
# BoneInfluence == BoneWeight in terms of usage in UnityPy due to int simplification
self.m_BoneIndices = [
(skin.boneIndex_0_, skin.boneIndex_1_, skin.boneIndex_2_, skin.boneIndex_3_) for skin in mesh.m_Skin
]
self.m_BoneWeights = [
(skin.weight_0_, skin.weight_1_, skin.weight_2_, skin.weight_3_) for skin in mesh.m_Skin
]
def copy_from_spriterenderdata(self):
rd = self.src
if TYPE_CHECKING:
assert isinstance(rd, SpriteRenderData)
if self.m_IndexBuffer is None:
if rd.m_IndexBuffer:
self.m_IndexBuffer = rd.m_IndexBuffer
elif rd.indices:
self.m_IndexBuffer = rd.indices
if self.m_Vertices is None and rd.vertices:
vertices = rd.vertices
self.m_Vertices = [(v.pos.x, v.pos.y, v.pos.z) for v in vertices]
if vertices[0].uv is not None:
self.m_UV0 = [(v.uv.x, v.uv.y) for v in vertices] # type: ignore
# if self.m_BindPose is None and rd.m_BindPose:
# self.m_BindPose = rd.m_BindPose
def get_streams(self, m_Channels: list[ChannelInfo], m_VertexCount: int) -> list[StreamInfo]:
streamCount = 1 + max(x.stream for x in m_Channels)
m_Streams: list[StreamInfo] = []
offset = 0
for s in range(streamCount):
chnMask = 0
stride = 0
for chn, m_Channel in enumerate(m_Channels):
if m_Channel.stream == s:
if m_Channel.dimension > 0:
chnMask |= 1 << chn
component_size = self.get_channel_component_size(m_Channel)
stride += (m_Channel.dimension & 0xF) * component_size
m_Streams.append(
StreamInfo(
channelMask=chnMask,
offset=offset,
stride=stride,
dividerOp=0,
frequency=0,
)
)
offset += m_VertexCount * stride
offset = (offset + (16 - 1)) & ~(16 - 1)
return m_Streams
def get_channels(self, m_Streams: list[StreamInfo]) -> list[ChannelInfo]:
m_Channels = [
ChannelInfo(
dimension=0,
format=0,
offset=0,
stream=0,
)
for _ in range(6)
]
for s, m_Stream in enumerate(m_Streams):
channelMask = m_Stream.channelMask # uint
offset = 0
for i in range(6):
if channelMask & (1 << i):
m_Channel = m_Channels[i]
m_Channel.stream = s
m_Channel.offset = offset
if i in [0, 1]:
# 0 - kShaderChannelVertex
# 1 - kShaderChannelNormal
m_Channel.format = 0 # kChannelFormatFloat
m_Channel.dimension = 3
elif i == 2: # kShaderChannelColor
m_Channel.format = 2 # kChannelFormatColor
m_Channel.dimension = 4
elif i in [3, 4]:
# 3 - kShaderChannelTexCoord0
# 4 - kShaderChannelTexCoord1
m_Channel.format = 0 # kChannelFormatFloat
m_Channel.dimension = 2
elif i == 5: # kShaderChannelTangent
m_Channel.format = 0 # kChannelFormatFloat
m_Channel.dimension = 4
component_size = self.get_channel_component_size(m_Channel)
offset += m_Channel.dimension * component_size
return m_Channels
def read_vertex_data(self, m_Channels: list[ChannelInfo], m_Streams: list[StreamInfo]) -> None:
m_VertexData = self.src.m_VertexData
if m_VertexData is None:
return
# could be empty for fully compressed meshes
# in that case data will be read from CompressedMesh via decompress_compressed_mesh
# also avoids a crash in UnityPyBoost.unpack_vertexdata with empty data
if m_VertexData.m_VertexCount == 0 or not m_VertexData.m_DataSize:
return
self.m_VertexCount = m_VertexCount = m_VertexData.m_VertexCount
# m_VertexDataRaw = m_VertexData.m_DataSize
for chn, m_Channel in enumerate(m_Channels):
if m_Channel.dimension == 0:
continue
m_Stream = m_Streams[m_Channel.stream]
# m_StreamData = m_VertexDataRaw[
# m_Stream.offset : m_Stream.offset + m_VertexCount * m_Stream.stride
# ]
channelMask = bin(m_Stream.channelMask)[::-1]
if channelMask[chn] == "1":
if (
self.version[0] < 2018 and chn == 2 and m_Channel.format == 2
): # kShaderChannelColor && kChannelFormatColor
# new instance to not modify the original
m_Channel = ChannelInfo(
dimension=4,
format=2,
offset=m_Channel.offset,
stream=m_Channel.stream,
)
component_dtype = self.get_channel_dtype(m_Channel)
component_byte_size = self.get_channel_component_size(m_Channel)
# channel_byte_size = m_Channel.dimension * component_byte_size
swap = self.endianess == "<" and component_byte_size > 1
channel_dimension = m_Channel.dimension & 0xF
if UnityPyBoost:
componentBytes = UnityPyBoost.unpack_vertexdata(
m_VertexData.m_DataSize,
component_byte_size,
m_VertexCount,
m_Stream.offset,
m_Stream.stride,
m_Channel.offset,
channel_dimension,
swap,
)
else:
componentBytes = bytearray(m_VertexCount * channel_dimension * component_byte_size)
vertexBaseOffset = m_Stream.offset + m_Channel.offset
for v in range(m_VertexCount):
vertexOffset = vertexBaseOffset + m_Stream.stride * v
for d in range(channel_dimension):
componentOffset = vertexOffset + component_byte_size * d
vertexDataSrc = componentOffset
componentDataSrc = component_byte_size * (v * channel_dimension + d)
buff = m_VertexData.m_DataSize[vertexDataSrc : vertexDataSrc + component_byte_size]
if swap: # swap bytes
buff = buff[::-1]
componentBytes[componentDataSrc : componentDataSrc + component_byte_size] = buff
component_data = list(struct.iter_unpack(f">{channel_dimension}{component_dtype}", componentBytes))
self.assign_channel_vertex_data(chn, component_data)
def assign_channel_vertex_data(self, channel: int, component_data: list):
if self.version[0] >= 2018:
if channel == 0: # kShaderChannelVertex
self.m_Vertices = component_data
elif channel == 1: # kShaderChannelNormal
self.m_Normals = component_data
elif channel == 2: # kShaderChannelTangent
self.m_Tangents = component_data
elif channel == 3: # kShaderChannelColor
self.m_Colors = component_data
elif channel == 4: # kShaderChannelTexCoord0
self.m_UV0 = component_data
elif channel == 5: # kShaderChannelTexCoord1
self.m_UV1 = component_data
elif channel == 6: # kShaderChannelTexCoord2
self.m_UV2 = component_data
elif channel == 7: # kShaderChannelTexCoord3
self.m_UV3 = component_data
elif channel == 8: # kShaderChannelTexCoord4
self.m_UV4 = component_data
elif channel == 9: # kShaderChannelTexCoord5
self.m_UV5 = component_data
elif channel == 10: # kShaderChannelTexCoord6
self.m_UV6 = component_data
elif channel == 11: # kShaderChannelTexCoord7
self.m_UV7 = component_data
# 2018.2 and up
elif channel == 12: # kShaderChannelBlendWeight
self.m_BoneWeights = component_data
elif channel == 13: # kShaderChannelBlendIndices
self.m_BoneIndices = component_data
else:
raise ValueError(f"Unknown channel {channel}")
else:
if channel == 0: # kShaderChannelVertex
self.m_Vertices = component_data
elif channel == 1: # kShaderChannelNormal
self.m_Normals = component_data
elif channel == 2: # kShaderChannelColor
self.m_Colors = component_data
elif channel == 3: # kShaderChannelTexCoord0
self.m_UV0 = component_data
elif channel == 4: # kShaderChannelTexCoord1
self.m_UV1 = component_data
elif channel == 5:
if self.version[0] >= 5: # kShaderChannelTexCoord2
self.m_UV2 = component_data
else: # kShaderChannelTangent
self.m_Tangents = component_data
elif channel == 6: # kShaderChannelTexCoord3
self.m_UV3 = component_data
elif channel == 7: # kShaderChannelTangent
self.m_Tangents = component_data
else:
raise ValueError(f"Unknown channel {channel}")
def get_channel_dtype(self, m_Channel: ChannelInfo):
if self.version[0] < 2017:
format = VertexChannelFormat(m_Channel.format)
component_dtype = VERTEX_CHANNEL_FORMAT_STRUCT_TYPE_MAP[format]
elif self.version[0] < 2019:
format = VertexFormat2017(m_Channel.format)
component_dtype = VERTEX_FORMAT_2017_STRUCT_TYPE_MAP[format]
else:
format = VertexFormat(m_Channel.format)
component_dtype = VERTEX_FORMAT_STRUCT_TYPE_MAP[format]
return component_dtype
def get_channel_component_size(self, m_Channel: ChannelInfo):
dtype = self.get_channel_dtype(m_Channel)
return struct.Struct(dtype).size
def decompress_compressed_mesh(self):
# TODO: m_Triangles????
version = self.version
assert isinstance(self.src, Mesh)
m_CompressedMesh = self.src.m_CompressedMesh
# Vertex
self.m_VertexCount = m_VertexCount = m_CompressedMesh.m_Vertices.m_NumItems // 3
if m_CompressedMesh.m_Vertices.m_NumItems > 0:
self.m_Vertices = unpack_floats(m_CompressedMesh.m_Vertices, shape=(3,))
# UV
if m_CompressedMesh.m_UV.m_NumItems > 0:
m_UVInfo = m_CompressedMesh.m_UVInfo
if m_UVInfo is not None and m_UVInfo != 0:
kInfoBitsPerUV = 4
kUVDimensionMask = 3
kUVChannelExists = 4
kMaxTexCoordShaderChannels = 8
uvSrcOffset = 0
for uv_channel in range(kMaxTexCoordShaderChannels):
texCoordBits = m_UVInfo >> (uv_channel * kInfoBitsPerUV)
texCoordBits &= (1 << kInfoBitsPerUV) - 1
if (texCoordBits & kUVChannelExists) != 0:
uvDim = 1 + int(texCoordBits & kUVDimensionMask)
m_UV = unpack_floats(
m_CompressedMesh.m_UV,
uvSrcOffset,
m_VertexCount * uvDim,
shape=(uvDim,),
)
setattr(self, f"m_UV{uv_channel}", m_UV)
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:
self.m_UV1 = unpack_floats(
m_CompressedMesh.m_UV,
m_VertexCount * 2,
m_VertexCount * 2,
shape=(2,),
)
# BindPose
if version[0] < 5: # 5.0 down
m_BindPoses = m_CompressedMesh.m_BindPoses
if m_BindPoses and m_BindPoses.m_NumItems > 0:
self.m_BindPose = unpack_floats(
m_BindPoses,
shape=(
4,
4,
),
)
# Normal
if m_CompressedMesh.m_Normals.m_NumItems > 0:
normalData = unpack_floats(m_CompressedMesh.m_Normals, shape=(2,))
signs = unpack_ints(m_CompressedMesh.m_NormalSigns)
normals = zeros(self.m_VertexCount, 3)
for srcNrm, sign, dstNrm in zip(normalData, signs, normals):
x, y = srcNrm
zsqr = 1 - x * x - y * y
if zsqr >= 0:
z = math.sqrt(zsqr)
dstNrm[:] = x, y, z
else:
z = 0
dstNrm[:] = normalize(x, y, z)
if sign == 0:
dstNrm[2] *= -1
self.m_Normals = lists_to_tuples(normals)
# Tangent
if m_CompressedMesh.m_Tangents.m_NumItems > 0:
tangentData = unpack_floats(m_CompressedMesh.m_Tangents, shape=(2,))
signs = unpack_ints(m_CompressedMesh.m_TangentSigns, shape=(2,))
tangents = zeros(self.m_VertexCount, 4)
for srcTan, (sign_z, sign_w), dstTan in zip(tangentData, signs, tangents):
x, y = srcTan
zsqr = 1 - x * x - y * y
z = 0
w = 0
if zsqr >= 0:
z = math.sqrt(zsqr)
else:
x, y, z = normalize(x, y, z)
if sign_z == 0:
z = -z
w = 1.0 if sign_w > 0 else -1.0
dstTan[:] = x, y, z, w
self.m_Tangents = lists_to_tuples(tangents)
# FloatColor
if version[0] >= 5: # 5.0 and up
m_FloatColors = m_CompressedMesh.m_FloatColors
if m_FloatColors and m_FloatColors.m_NumItems > 0:
self.m_Colors = unpack_floats(m_FloatColors, shape=(4,))
# Skin
if m_CompressedMesh.m_Weights.m_NumItems > 0:
weightsData = unpack_ints(m_CompressedMesh.m_Weights)
boneIndicesData = unpack_ints(m_CompressedMesh.m_BoneIndices)
vertexIndex = 0
j = 0
sum = 0
boneWeights = zeros(self.m_VertexCount, 4)
boneIndices = zeros(self.m_VertexCount, 4)
boneIndicesIterator = iter(boneIndicesData)
for weight, boneIndex in zip(weightsData, boneIndicesIterator):
# read bone index and weight
boneWeights[vertexIndex][j] = weight / 31
boneIndices[vertexIndex][j] = boneIndex
j += 1
sum += weight
# the weights add up to one, continue with the next vertex.
if sum >= 31:
j = 4
# set weights and boneIndices to 0,
# already done on init
vertexIndex += 1
j = 0
sum = 0
# 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
boneIndices[vertexIndex][j] = next(boneIndicesIterator)
vertexIndex += 1
j = 0
sum = 0
self.m_BoneWeights = lists_to_tuples(boneWeights)
self.m_BoneIndices = lists_to_tuples(boneIndices)
# IndexBuffer
if m_CompressedMesh.m_Triangles.m_NumItems > 0: #
self.m_IndexBuffer = unpack_ints(m_CompressedMesh.m_Triangles)
# Color
if m_CompressedMesh.m_Colors and m_CompressedMesh.m_Colors.m_NumItems > 0:
rgba_colors = unpack_ints(m_CompressedMesh.m_Colors)
self.m_Colors = [
(
((rgba >> 24) & 0xFF) / 255,
((rgba >> 16) & 0xFF) / 255,
((rgba >> 8) & 0xFF) / 255,
(rgba & 0xFF) / 255,
)
for rgba in rgba_colors
]
def get_triangles(self) -> List[List[Tuple[int, ...]]]:
assert self.m_IndexBuffer is not None
assert self.src.m_SubMeshes is not None
submeshes: List[List[Tuple[int, ...]]] = []
for m_SubMesh in self.src.m_SubMeshes:
firstIndex = m_SubMesh.firstByte // 2
if not self.m_Use16BitIndices:
firstIndex //= 2
indexCount = m_SubMesh.indexCount
topology = m_SubMesh.topology
triangles: List[Tuple[int, ...]]
if topology == MeshTopology.Triangles:
triangles = [
tuple(self.m_IndexBuffer[i : i + 3]) for i in range(firstIndex, firstIndex + indexCount, 3)
]
elif self.version[0] < 4 or topology == MeshTopology.TriangleStrip:
triangles = [()] * (indexCount - 2)
triIndex = 0
for i in range(firstIndex, firstIndex + indexCount - 2):
a, b, c = self.m_IndexBuffer[i : i + 3]
# skip degenerates
if a == b or a == c or b == c:
continue
# do the winding flip-flop of strips
if (i - firstIndex) & 1:
triangles[triIndex] = (b, a, c)
else:
triangles[triIndex] = (a, b, c)
triIndex += 1
triangles = triangles[:triIndex]
m_SubMesh.indexCount = len(triangles) * 3
elif topology == MeshTopology.Quads:
# one quad is two triangles, so // 4 * 2 = // 2
triangles = [()] * (indexCount // 2)
triIndex = 0
for i in range(firstIndex, firstIndex + indexCount, 4):
a, b, c, d = self.m_IndexBuffer[i : i + 4]
triangles[triIndex] = (a, b, c)
triangles[triIndex + 1] = (a, c, d)
triIndex += 2
else:
raise ValueError("Failed getting triangles. Submesh topology is lines or points.")
submeshes.append(triangles)
return submeshes
# COMPRESSION_BIT_SIZES = {
# "high": {
# "vertex": 10,
# "uv": 8,
# "normal": 6,
# },
# "medium": {
# "vertex": 16,
# "uv": 10,
# "normal": 8
# },
# "low": {
# "vertex": 20,
# "uv": 16,
# "normal": 8
# },
# }