pgtools 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +25 -0
  3. data/bin/bxm_decoder +2 -0
  4. data/bin/bxm_encoder +2 -0
  5. data/bin/clh_convert +2 -0
  6. data/bin/clp_convert +2 -0
  7. data/bin/clw_convert +2 -0
  8. data/bin/dat_creator +2 -0
  9. data/bin/dat_extractor +2 -0
  10. data/bin/dat_ls +2 -0
  11. data/bin/eff_idd_creator +2 -0
  12. data/bin/eff_idd_extractor +2 -0
  13. data/bin/exp_convert_wiiu_pc +2 -0
  14. data/bin/exp_tool +2 -0
  15. data/bin/mot_convert_wiiu_pc +2 -0
  16. data/bin/mot_tool +2 -0
  17. data/bin/pkz_extractor +2 -0
  18. data/bin/scr_creator +2 -0
  19. data/bin/scr_extractor +2 -0
  20. data/bin/wmb_cleanup +2 -0
  21. data/bin/wmb_common_bones +2 -0
  22. data/bin/wmb_convert_pc_switch +2 -0
  23. data/bin/wmb_convert_wiiu_pc +2 -0
  24. data/bin/wmb_export_assimp +2 -0
  25. data/bin/wmb_get_bone_map +2 -0
  26. data/bin/wmb_import_assimp +2 -0
  27. data/bin/wmb_import_nier +2 -0
  28. data/bin/wmb_import_wiiu +2 -0
  29. data/bin/wtb_convert_wiiu_pc +2 -0
  30. data/bin/wtx_creator +2 -0
  31. data/bin/wtx_extractor +2 -0
  32. data/lib/bayonetta/alignment.rb +14 -0
  33. data/lib/bayonetta/bone.rb +55 -0
  34. data/lib/bayonetta/bxm.rb +180 -0
  35. data/lib/bayonetta/clh.rb +159 -0
  36. data/lib/bayonetta/clp.rb +212 -0
  37. data/lib/bayonetta/clw.rb +166 -0
  38. data/lib/bayonetta/dat.rb +261 -0
  39. data/lib/bayonetta/eff.rb +314 -0
  40. data/lib/bayonetta/endianness.rb +53 -0
  41. data/lib/bayonetta/exp.rb +768 -0
  42. data/lib/bayonetta/linalg.rb +416 -0
  43. data/lib/bayonetta/material_database.yaml +2581 -0
  44. data/lib/bayonetta/mot.rb +763 -0
  45. data/lib/bayonetta/pkz.rb +63 -0
  46. data/lib/bayonetta/scr.rb +393 -0
  47. data/lib/bayonetta/tools/bxm_decoder.rb +23 -0
  48. data/lib/bayonetta/tools/bxm_encoder.rb +37 -0
  49. data/lib/bayonetta/tools/clh_convert.rb +60 -0
  50. data/lib/bayonetta/tools/clp_convert.rb +70 -0
  51. data/lib/bayonetta/tools/clw_convert.rb +60 -0
  52. data/lib/bayonetta/tools/dat_creator.rb +57 -0
  53. data/lib/bayonetta/tools/dat_extractor.rb +94 -0
  54. data/lib/bayonetta/tools/dat_ls.rb +106 -0
  55. data/lib/bayonetta/tools/eff_idd_creator.rb +66 -0
  56. data/lib/bayonetta/tools/eff_idd_extractor.rb +73 -0
  57. data/lib/bayonetta/tools/exp_convert_wiiu_pc.rb +33 -0
  58. data/lib/bayonetta/tools/exp_tool.rb +48 -0
  59. data/lib/bayonetta/tools/mot_convert_wiiu_pc.rb +33 -0
  60. data/lib/bayonetta/tools/mot_tool.rb +60 -0
  61. data/lib/bayonetta/tools/pkz_extractor.rb +75 -0
  62. data/lib/bayonetta/tools/scr_creator.rb +63 -0
  63. data/lib/bayonetta/tools/scr_extractor.rb +78 -0
  64. data/lib/bayonetta/tools/wmb_cleanup.rb +250 -0
  65. data/lib/bayonetta/tools/wmb_common_bones.rb +45 -0
  66. data/lib/bayonetta/tools/wmb_convert_pc_switch.rb +35 -0
  67. data/lib/bayonetta/tools/wmb_convert_wiiu_pc.rb +33 -0
  68. data/lib/bayonetta/tools/wmb_export_assimp.rb +479 -0
  69. data/lib/bayonetta/tools/wmb_get_bone_map.rb +50 -0
  70. data/lib/bayonetta/tools/wmb_import_assimp.rb +735 -0
  71. data/lib/bayonetta/tools/wmb_import_geometry_wiiu_pc.rb +472 -0
  72. data/lib/bayonetta/tools/wmb_import_nier.rb +309 -0
  73. data/lib/bayonetta/tools/wtb_convert_wiiu_pc.rb +95 -0
  74. data/lib/bayonetta/tools/wtb_import_textures.rb +103 -0
  75. data/lib/bayonetta/tools/wtx_creator.rb +69 -0
  76. data/lib/bayonetta/tools/wtx_extractor.rb +85 -0
  77. data/lib/bayonetta/vertex_types.yaml +213 -0
  78. data/lib/bayonetta/vertex_types2.yaml +164 -0
  79. data/lib/bayonetta/vertex_types_nier.yaml +145 -0
  80. data/lib/bayonetta/wmb.rb +2443 -0
  81. data/lib/bayonetta/wmb3.rb +759 -0
  82. data/lib/bayonetta/wtb.rb +481 -0
  83. data/lib/bayonetta.rb +60 -0
  84. metadata +254 -0
@@ -0,0 +1,2443 @@
1
+ require 'set'
2
+ require 'digest'
3
+ require 'yaml'
4
+ $material_db = YAML::load_file(File.join( File.dirname(__FILE__), 'material_database.yaml'))
5
+
6
+ module Bayonetta
7
+
8
+ class UByteList < LibBin::Structure
9
+ uint32 :data
10
+
11
+ def self.is_bayo2?(parent)
12
+ if parent.__parent.respond_to?(:is_bayo2?)
13
+ return parent.__parent.is_bayo2?
14
+ elsif parent.__parent.__parent.respond_to?(:is_bayo2?)
15
+ return parent.__parent.__parent.is_bayo2?
16
+ end
17
+ raise "Cannot determine if Bayo2 or not!"
18
+ end
19
+
20
+ def self.inherited(subclass)
21
+ subclass.instance_variable_set(:@fields, @fields.dup)
22
+ end
23
+
24
+ def __convert(input, output, input_big, output_big, parent = nil, index = nil)
25
+ __set_convert_state(input, output,
26
+ input_big && self.class.is_bayo2?(parent) ? false : input_big,
27
+ output_big && self.class.is_bayo2?(parent) ? false : output_big,
28
+ parent, index)
29
+ __convert_fields
30
+ __unset_convert_state
31
+ self
32
+ end
33
+
34
+ def __load(input, input_big, parent = nil, index = nil)
35
+ __set_load_state(input, input_big && self.class.is_bayo2?(parent) ? false : input_big, parent, index)
36
+ __load_fields
37
+ __unset_load_state
38
+ self
39
+ end
40
+
41
+ def __dump(output, output_big, parent = nil, index = nil)
42
+ __set_dump_state(output, output_big && self.class.is_bayo2?(parent) ? false : output_big, parent, index)
43
+ __dump_fields
44
+ __unset_dump_state
45
+ self
46
+ end
47
+
48
+ def initialize
49
+ @data = 0
50
+ end
51
+
52
+ end
53
+
54
+ class Color < UByteList
55
+
56
+ def r
57
+ @data & 0xff
58
+ end
59
+
60
+ def g
61
+ (@data >> 8) & 0xff
62
+ end
63
+
64
+ def b
65
+ (@data >> 16) & 0xff
66
+ end
67
+
68
+ def a
69
+ (@data >> 24) & 0xff
70
+ end
71
+
72
+ def r=(v)
73
+ @data = (@data & 0xffffff00) | (v & 0xff)
74
+ v & 0xff
75
+ end
76
+
77
+ def g=(v)
78
+ @data = (@data & 0xffff00ff) | ((v & 0xff)<<8)
79
+ v & 0xff
80
+ end
81
+
82
+ def b=(v)
83
+ @data = (@data & 0xff00ffff) | ((v & 0xff)<<16)
84
+ v & 0xff
85
+ end
86
+
87
+ def a=(v)
88
+ @data = (@data & 0x00ffffff) | ((v & 0xff)<<24)
89
+ v & 0xff
90
+ end
91
+
92
+ def to_a
93
+ [r, g, b, a]
94
+ end
95
+
96
+ end
97
+
98
+ module VectorAccessor
99
+ def [](i)
100
+ case i
101
+ when 0
102
+ self.x
103
+ when 1
104
+ self.y
105
+ when 2
106
+ self.z
107
+ else
108
+ "Invalid index #{i} for a vector access!"
109
+ end
110
+ end
111
+
112
+ def []=(i,v)
113
+ case i
114
+ when 0
115
+ self.x = v
116
+ when 1
117
+ self.y = v
118
+ when 2
119
+ self.z = v
120
+ else
121
+ "Invalid index #{i} for a vector access!"
122
+ end
123
+ end
124
+
125
+ end
126
+
127
+ class Tangents < UByteList
128
+ include VectorAccessor
129
+
130
+ def clamp(v, max, min)
131
+ if v > max
132
+ v = max
133
+ elsif v < min
134
+ v = min
135
+ end
136
+ v
137
+ end
138
+
139
+ def x
140
+ ((@data & 0xff) - 127.0)/127.0
141
+ end
142
+
143
+ def y
144
+ (((@data >> 8) & 0xff) - 127.0)/127.0
145
+ end
146
+
147
+ def z
148
+ (((@data >> 16) & 0xff) -127.0)/127.0
149
+ end
150
+
151
+ def s
152
+ (((@data >> 24) & 0xff) -127.0)/127.0
153
+ end
154
+
155
+ def x=(v)
156
+ v2 = clamp((v*127.0+127.0).round, 255, 0)
157
+ @data = (@data & 0xffffff00) | v2
158
+ v
159
+ end
160
+
161
+ def y=(v)
162
+ v2 = clamp((v*127.0+127.0).round, 255, 0)
163
+ @data = (@data & 0xffff00ff) | (v2 << 8)
164
+ v
165
+ end
166
+
167
+ def z=(v)
168
+ v2 = clamp((v*127.0+127.0).round, 255, 0)
169
+ @data = (@data & 0xff00ffff) | (v2 << 16)
170
+ v
171
+ end
172
+
173
+ def s=(v)
174
+ v2 = clamp((v*127.0+127.0).round, 255, 0)
175
+ @data = (@data & 0x00ffffff) | (v2 << 24)
176
+ v
177
+ end
178
+
179
+ def normalize(fx, fy, fz)
180
+ nrm = Math::sqrt(fx*fx+fy*fy+fz*fz)
181
+ return [0.0, 0.0, 0.0] if nrm == 0.0
182
+ [fx/nrm, fy/nrm, fz/nrm]
183
+ end
184
+
185
+ def set(x, y, z, s = nil)
186
+ x, y, z = normalize(x, y, z)
187
+ self.x = x
188
+ self.y = y
189
+ self.z = z
190
+ self.s = s if s
191
+ self
192
+ end
193
+
194
+ def to_a
195
+ [x, y, z, s]
196
+ end
197
+
198
+ end
199
+
200
+ class Mapping < LibBin::Structure
201
+ half :u
202
+ half :v
203
+
204
+ def to_a
205
+ [u, v]
206
+ end
207
+ end
208
+
209
+ class FloatMapping < LibBin::Structure
210
+ float :u
211
+ float :v
212
+
213
+ def to_a
214
+ [u, v]
215
+ end
216
+ end
217
+
218
+ class FloatNormal < LibBin::Structure
219
+ include VectorAccessor
220
+ float :x
221
+ float :y
222
+ float :z
223
+
224
+ def to_a
225
+ [x, y, z]
226
+ end
227
+ end
228
+
229
+ class HalfNormal < LibBin::Structure
230
+ include VectorAccessor
231
+ half :x
232
+ half :y
233
+ half :z
234
+ half :dummy
235
+
236
+ def to_a
237
+ [x, y, z]
238
+ end
239
+ end
240
+
241
+ class Normal < LibBin::Structure
242
+ include VectorAccessor
243
+ attr_accessor :normal
244
+ attr_accessor :normal_big_orig
245
+ attr_accessor :normal_small_orig
246
+ attr_accessor :wide
247
+
248
+ def initialize
249
+ @normal = [0.0, 0.0, 0.0]
250
+ @normal_big_orig = nil
251
+ @normal_small_orig = nil
252
+ @wide = false
253
+ end
254
+
255
+ def x
256
+ @normal[0]
257
+ end
258
+
259
+ def y
260
+ @normal[1]
261
+ end
262
+
263
+ def z
264
+ @normal[2]
265
+ end
266
+
267
+ def x=(v)
268
+ @normal_big_orig = nil
269
+ @normal_small_orig = nil
270
+ @normal[0] = v
271
+ end
272
+
273
+ def y=(v)
274
+ @normal_big_orig = nil
275
+ @normal_small_orig = nil
276
+ @normal[1] = v
277
+ end
278
+
279
+ def z=(v)
280
+ @normal_big_orig = nil
281
+ @normal_small_orig = nil
282
+ @normal[2] = v
283
+ end
284
+
285
+ def __size(position, parent, index)
286
+ 4
287
+ end
288
+
289
+ def normalize(fx, fy, fz)
290
+ nrm = Math::sqrt(fx*fx+fy*fy+fz*fz)
291
+ return [0.0, 0.0, 0.0] if nrm == 0.0
292
+ [fx/nrm, fy/nrm, fz/nrm]
293
+ end
294
+
295
+ def decode_big_normal(vs)
296
+ v = vs.unpack("L>").first
297
+ nx = v & ((1<<10)-1)
298
+ ny = (v >> 10) & ((1<<10)-1)
299
+ nz = (v >> 20) & ((1<<10)-1)
300
+ sx = nx & (1<<9)
301
+ sy = ny & (1<<9)
302
+ sz = nz & (1<<9)
303
+ if sx
304
+ nx ^= sx
305
+ nx = -(sx-nx)
306
+ end
307
+ if sy
308
+ ny ^= sy
309
+ ny = -(sy-ny)
310
+ end
311
+ if sz
312
+ nz ^= sz
313
+ nz = -(sz-nz)
314
+ end
315
+
316
+ mag = ((1<<9)-1).to_f
317
+ fx = nx.to_f/mag
318
+ fy = ny.to_f/mag
319
+ fz = nz.to_f/mag
320
+
321
+ normalize(fx, fy, fz)
322
+ end
323
+
324
+ def redecode_wide
325
+ v = normal_small_orig.unpack("L<").first
326
+ nx = v & ((1<<10)-1)
327
+ ny = (v >> 10) & ((1<<10)-1)
328
+ nz = (v >> 20) & ((1<<10)-1)
329
+ sx = nx & (1<<9)
330
+ sy = ny & (1<<9)
331
+ sz = nz & (1<<9)
332
+ if sx
333
+ nx ^= sx
334
+ nx = -(sx-nx)
335
+ end
336
+ if sy
337
+ ny ^= sy
338
+ ny = -(sy-ny)
339
+ end
340
+ if sz
341
+ nz ^= sz
342
+ nz = -(sz-nz)
343
+ end
344
+
345
+ mag = ((1<<9)-1).to_f
346
+ fx = nx.to_f/mag
347
+ fy = ny.to_f/mag
348
+ fz = nz.to_f/mag
349
+ @normals = normalize(fx, fy, fz)
350
+ end
351
+
352
+ def decode_small_normal(v)
353
+ n = v.unpack("c4")
354
+ nx = n[3]
355
+ ny = n[2]
356
+ nz = n[1]
357
+ mag = 127.0
358
+ fx = nx.to_f/mag
359
+ fy = ny.to_f/mag
360
+ fz = nz.to_f/mag
361
+
362
+ normalize(fx, fy, fz)
363
+ end
364
+
365
+ def clamp(v, max, min)
366
+ if v > max
367
+ v = max
368
+ elsif v < min
369
+ v = min
370
+ end
371
+ v
372
+ end
373
+
374
+ def encode_small_normal(normal)
375
+ if @wide
376
+ fx = normal[0]
377
+ fy = normal[1]
378
+ fz = normal[2]
379
+ mag = (1<<9)-1
380
+ nx = (fx*(mag).to_f).to_i
381
+ ny = (fy*(mag).to_f).to_i
382
+ nz = (fz*(mag).to_f).to_i
383
+ nx = clamp(nx, mag, -1-mag)
384
+ ny = clamp(ny, mag, -1-mag)
385
+ nz = clamp(nz, mag, -1-mag)
386
+ mask = (1<<10)-1
387
+ v = 0
388
+ v |= nz & mask
389
+ v <<= 10
390
+ v |= ny & mask
391
+ v <<= 10
392
+ v |= nx & mask
393
+ [v].pack("L<")
394
+ else
395
+ fx = normal[0]
396
+ fy = normal[1]
397
+ fz = normal[2]
398
+ nx = (fx*127.0).to_i
399
+ ny = (fy*127.0).to_i
400
+ nz = (fz*127.0).to_i
401
+ nx = clamp(nx, 127, -128)
402
+ ny = clamp(ny, 127, -128)
403
+ nz = clamp(nz, 127, -128)
404
+ [0, nz, ny, nx].pack("c4")
405
+ end
406
+ end
407
+
408
+ def encode_big_normal(normal)
409
+ fx = normal[0]
410
+ fy = normal[1]
411
+ fz = normal[2]
412
+ mag = (1<<9)-1
413
+ nx = (fx*(mag).to_f).to_i
414
+ ny = (fy*(mag).to_f).to_i
415
+ nz = (fz*(mag).to_f).to_i
416
+ nx = clamp(nx, mag, -1-mag)
417
+ ny = clamp(ny, mag, -1-mag)
418
+ nz = clamp(nz, mag, -1-mag)
419
+ mask = (1<<10)-1
420
+ v = 0
421
+ v |= nz & mask
422
+ v <<= 10
423
+ v |= ny & mask
424
+ v <<= 10
425
+ v |= nx & mask
426
+ [v].pack("L>")
427
+ end
428
+
429
+ def load_normal
430
+ s = __input.read(4)
431
+ if __input_big
432
+ @normal_big_orig = s
433
+ @normal_small_orig = nil
434
+ @normal = decode_big_normal(s)
435
+ else
436
+ @normal_small_orig = s
437
+ @normal_big_orig = nil
438
+ @normal = decode_small_normal(s)
439
+ end
440
+ end
441
+
442
+ def dump_normal
443
+ if __output_big
444
+ s2 = (@normal_big_orig ? @normal_big_orig : encode_big_normal(@normal))
445
+ else
446
+ s2 = (@normal_small_orig ? @normal_small_orig : encode_small_normal(@normal))
447
+ end
448
+ __output.write(s2)
449
+ end
450
+
451
+ def convert_normal
452
+ load_normal
453
+ dump_normal
454
+ end
455
+
456
+ def __convert_fields
457
+ convert_normal
458
+ end
459
+
460
+ def __load_fields
461
+ load_normal
462
+ end
463
+
464
+ def __dump_fields
465
+ dump_normal
466
+ end
467
+
468
+
469
+ def to_a
470
+ [x, y, z]
471
+ end
472
+ end
473
+
474
+ class Position < LibBin::Structure
475
+ include VectorAccessor
476
+ float :x
477
+ float :y
478
+ float :z
479
+
480
+ def -(other)
481
+ b = Position::new
482
+ b.x = @x - other.x
483
+ b.y = @y - other.y
484
+ b.z = @z - other.z
485
+ b
486
+ end
487
+
488
+ def -@
489
+ b = Position::new
490
+ b.x = -@x
491
+ b.y = -@y
492
+ b.z = -@z
493
+ b
494
+ end
495
+
496
+ def +(other)
497
+ b = Position::new
498
+ b.x = @x + other.x
499
+ b.y = @y + other.y
500
+ b.z = @z + other.z
501
+ b
502
+ end
503
+
504
+ def *(scal)
505
+ b = Position::new
506
+ b.x = @x*scal
507
+ b.y = @y*scal
508
+ b.z = @z*scal
509
+ b
510
+ end
511
+
512
+ def to_yaml_properties
513
+ [:@x, :@y, :@z]
514
+ end
515
+
516
+ def to_s
517
+ "<#{@x}, #{@y}, #{@z}>"
518
+ end
519
+
520
+ def to_a
521
+ [x, y, z]
522
+ end
523
+ end
524
+
525
+ class BoneInfos < LibBin::Structure
526
+ register_field :indexes, UByteList
527
+ register_field :weights, UByteList
528
+
529
+ def initialize
530
+ @indexes = UByteList::new
531
+ @weights = UByteList::new
532
+ end
533
+
534
+ def get_indexes_and_weights
535
+ res = []
536
+ 4.times { |i|
537
+ bi = (@indexes.data >> (i*8)) & 0xff
538
+ bw = (@weights.data >> (i*8)) & 0xff
539
+ res.push [bi, bw] if bw > 0
540
+ }
541
+ res
542
+ end
543
+
544
+ def get_indexes
545
+ get_indexes_and_weights.collect { |bi, _| bi }
546
+ end
547
+
548
+ def get_weights
549
+ get_indexes_and_weights.collect { |_, bw| bw }
550
+ end
551
+
552
+ def remap_indexes(map)
553
+ new_bone_info = get_indexes_and_weights.collect { |bi, bw| [map[bi], bw] }
554
+ set_indexes_and_weights(new_bone_info)
555
+ self
556
+ end
557
+
558
+ def set_indexes_and_weights(bone_info)
559
+ raise "Too many bone information #{bone_info.inspect}!" if bone_info.length > 4
560
+ @indexes.data = 0
561
+ @weights.data = 0
562
+ bone_info.each_with_index { |(bi, bw), i|
563
+ raise "Invalid bone index #{bi}!" if bi > 255 || bi < 0
564
+ @indexes.data |= ( bi << (i*8) )
565
+ bw = 0 if bw < 0
566
+ bw = 255 if bw > 255
567
+ @weights.data |= (bw << (i*8) )
568
+ }
569
+ self
570
+ end
571
+
572
+ def to_a
573
+ get_indexes_and_weights
574
+ end
575
+ end
576
+
577
+ class BoneIndexTranslateTable < LibBin::Structure
578
+ int16 :offsets, length: 16
579
+ #attr_accessor :second_levels
580
+ #attr_accessor :third_levels
581
+ attr_reader :table
582
+
583
+ def table=(t)
584
+ @table = t
585
+ encode
586
+ t
587
+ end
588
+
589
+ def __size(position = 0, parent = nil, index = nil)
590
+ sz = super()
591
+ if @second_levels
592
+ @second_levels.each { |e|
593
+ sz += e.__size(position, parent, index)
594
+ }
595
+ end
596
+ if @third_levels
597
+ @third_levels.each { |e|
598
+ sz += e.__size(position, parent, index)
599
+ }
600
+ end
601
+ sz
602
+ end
603
+
604
+ def __convert(input, output, input_big, output_big, parent, index, level = 1)
605
+ __set_convert_state(input, output, input_big, output_big, parent, index)
606
+ __convert_fields
607
+ if level == 1
608
+ @second_levels = []
609
+ @offsets.each { |o|
610
+ if o != -1
611
+ t = self.class::new
612
+ t.__convert(input, output, input_big, output_big, self, nil, level+1)
613
+ @second_levels.push t
614
+ end
615
+ }
616
+ @third_levels = []
617
+ @second_levels.each { |l|
618
+ l.offsets.each { |o|
619
+ if o != -1
620
+ t = self.class::new
621
+ t.__convert(input, output, input_big, output_big, self, nil, level+2)
622
+ @third_levels.push t
623
+ end
624
+ }
625
+ }
626
+ decode
627
+ else
628
+ @second_levels = nil
629
+ @third_levels = nil
630
+ end
631
+ __unset_convert_state
632
+ self
633
+ end
634
+
635
+ def __load(input, input_big, parent, index, level = 1)
636
+ __set_load_state(input, input_big, parent, index)
637
+ __load_fields
638
+ if level == 1
639
+ @second_levels = []
640
+ @offsets.each { |o|
641
+ if o != -1
642
+ t = self.class::new
643
+ t.__load(input, input_big, self, nil, level+1)
644
+ @second_levels.push t
645
+ end
646
+ }
647
+ @third_levels = []
648
+ @second_levels.each { |l|
649
+ l.offsets.each { |o|
650
+ if o != -1
651
+ t = self.class::new
652
+ t.__load(input, input_big, self, nil, level+2)
653
+ @third_levels.push t
654
+ end
655
+ }
656
+ }
657
+ decode
658
+ else
659
+ @second_levels = nil
660
+ @third_levels = nil
661
+ end
662
+ __unset_load_state
663
+ self
664
+ end
665
+
666
+ def decode
667
+ t = (@offsets+@second_levels.collect(&:offsets)+@third_levels.collect(&:offsets)).flatten
668
+ @table = (0x0..0xfff).each.collect { |i|
669
+ index = t[(i & 0xf00)>>8]
670
+ next if index == -1
671
+ index = t[index + ((i & 0xf0)>>4)]
672
+ next if index == -1
673
+ index = t[index + (i & 0xf)]
674
+ next if index == 0xfff
675
+ [i, index]
676
+ }.compact.to_h
677
+ end
678
+ private :decode
679
+
680
+ def encode
681
+ keys = @table.keys.sort
682
+ first_table = 16.times.collect { |i|
683
+ lower = i*0x100
684
+ upper = (i+1)*0x100
685
+ keys.select { |k| k >= lower && k < upper }
686
+ }
687
+ off = 0x0
688
+ @offsets = first_table.collect { |e| e == [] ? -1 : (off += 0x10) }
689
+
690
+ second_table = first_table.select { |e| e != [] }.collect { |e|
691
+ 16.times.collect { |i|
692
+ lower = i*0x10
693
+ upper = (i+1)*0x10
694
+ e.select { |k| (k&0xff) >= lower && (k&0xff) < upper }
695
+ }
696
+ }
697
+ @second_levels = second_table.collect { |st|
698
+ tab = BoneIndexTranslateTable::new
699
+ tab.offsets = st.collect { |e| e == [] ? -1 : (off += 0x10) }
700
+ tab
701
+ }
702
+ @third_levels = []
703
+ second_table.each { |e|
704
+ e.select { |ee| ee != [] }.each { |ee|
705
+ tab = BoneIndexTranslateTable::new
706
+ tab.offsets = [0xfff]*16
707
+ ee.each { |k|
708
+ tab.offsets[k&0xf] = @table[k]
709
+ }
710
+ @third_levels.push tab
711
+ }
712
+ }
713
+ self
714
+ end
715
+ private :encode
716
+
717
+ def __dump(output, output_big, parent, index, level = 1)
718
+ __set_dump_state(output, output_big, parent, index)
719
+ encode if level == 1
720
+ __dump_fields
721
+ if @second_levels
722
+ @second_levels.each { |e|
723
+ e.__dump(output, output_big, self, nil, level+1)
724
+ }
725
+ end
726
+ if @third_levels
727
+ @third_levels.each { |e|
728
+ e.__dump(output, output_big, self, nil, level+2)
729
+ }
730
+ end
731
+ __unset_dump_state
732
+ end
733
+
734
+ end
735
+
736
+ VERTEX_FIELDS = {
737
+ position_t: [ Position, 12 ],
738
+ mapping_t: [ Mapping, 4 ],
739
+ normal_t: [ Normal, 4 ],
740
+ tangents_t: [ Tangents, 4 ],
741
+ bone_infos_t: [ BoneInfos, 8],
742
+ color_t: [ Color, 4],
743
+ fnormal_t: [ FloatNormal, 12 ],
744
+ hnormal_t: [ HalfNormal, 8 ],
745
+ fmapping_t: [ FloatMapping, 8]
746
+ }
747
+
748
+ class WMBFile < LibBin::Structure
749
+ include Alignment
750
+
751
+ VERTEX_TYPES = {}
752
+ VERTEX_TYPES.update( YAML::load_file(File.join( File.dirname(__FILE__), 'vertex_types.yaml')) )
753
+ VERTEX_TYPES.update( YAML::load_file(File.join( File.dirname(__FILE__), 'vertex_types2.yaml')) )
754
+
755
+ class UnknownStruct < LibBin::Structure
756
+ uint8 :u_a1, length: 4
757
+ uint32 :u_b1
758
+ int16 :u_c1, length: 4
759
+ uint8 :u_a2, length: 4
760
+ uint32 :u_b2
761
+ int16 :u_c2, length: 4
762
+ uint8 :u_a3, length: 4
763
+ uint32 :u_b3
764
+ end
765
+
766
+ class Material < LibBin::Structure
767
+ int16 :type
768
+ uint16 :flag
769
+ uint32 :material_data,
770
+ length: '(..\materials_offsets[__index+1] ? ..\materials_offsets[__index+1] - ..\materials_offsets[__index] - 4 : ..\header\offset_meshes_offsets - __position - 4)/4'
771
+
772
+ def __size(position = 0, parent = nil, index = nil)
773
+ return 2 + 2 + @material_data.length * 4
774
+ end
775
+ end
776
+
777
+ class Bayo1Material
778
+ attr_reader :type
779
+ attr_reader :flag
780
+ attr_reader :samplers
781
+ attr_reader :parameters
782
+
783
+ def initialize(m)
784
+ @type = m.type
785
+ @flag = m.flag
786
+ @layout = $material_db[@type][:layout]
787
+ @samplers = {}
788
+ @parameters = {}
789
+ field_count = 0
790
+ @layout.each { |name, t|
791
+ if t == "sampler2D_t" || t == "samplerCUBE_t"
792
+ @samplers[name] = m.material_data[field_count]
793
+ field_count += 1
794
+ else
795
+ @parameters[name] = m.material_data[field_count...(field_count+4)].pack("L4").unpack("F4")
796
+ field_count += 4
797
+ end
798
+ }
799
+ end
800
+
801
+ end
802
+
803
+ class BatchHeader < LibBin::Structure
804
+ int16 :batch_id #Bayo 2
805
+ int16 :mesh_id
806
+ uint16 :flags
807
+ int16 :ex_mat_id
808
+ uint8 :material_id
809
+ uint8 :has_bone_refs
810
+ uint8 :u_e1
811
+ uint8 :u_e2
812
+ uint32 :vertex_start
813
+ uint32 :vertex_end
814
+ int32 :primitive_type
815
+ uint32 :offset_indices
816
+ int32 :num_indices
817
+ int32 :vertex_offset
818
+ int32 :u_f, length: 7
819
+
820
+ def initialize
821
+ @batch_id = 0
822
+ @mesh_id = 0
823
+ @flags = 0x8001
824
+ @ex_mat_id = 0
825
+ @material_id = 0
826
+ @has_bone_refs = 1
827
+ @u_e1 = 0
828
+ @u_e2 = 0
829
+ @vertex_start = 0
830
+ @vertex_end = 0
831
+ @primitive_type = 4
832
+ @offset_indices = 0x100
833
+ @num_indices = 0
834
+ @vertex_offset = 0
835
+ @u_f = [0]*7
836
+ end
837
+ end
838
+
839
+ class Batch < LibBin::Structure
840
+ register_field :header, BatchHeader
841
+ int32 :num_bone_ref, condition: 'header\has_bone_refs != 0'
842
+ uint8 :bone_refs, length: 'num_bone_ref', condition: 'header\has_bone_refs != 0'
843
+ float :unknown, length: 4, condition: 'header\has_bone_refs == 0'
844
+ uint16 :indices, length: 'header\num_indices', offset: '__position + header\offset_indices'
845
+
846
+ def initialize
847
+ @header = BatchHeader::new
848
+ @num_bone_ref = 0
849
+ @bone_refs = []
850
+ @indices = []
851
+ end
852
+
853
+ def duplicate(positions, vertexes, vertexes_ex)
854
+ b = Batch::new
855
+ if header.has_bone_refs != 0
856
+ b.header = @header.dup
857
+ b.num_bone_ref = @num_bone_ref
858
+ b.bone_refs = @bone_refs.dup
859
+ else
860
+ b.unknown = @unknown
861
+ end
862
+ l = vertexes.length
863
+ old_indices_map = vertex_indices.uniq.sort.each_with_index.collect { |vi, i| [vi, l + i] }.to_h
864
+ old_indices_map.each { |vi, nvi| positions[nvi] = positions[vi] } if positions
865
+ old_indices_map.each { |vi, nvi| vertexes[nvi] = vertexes[vi] }
866
+ old_indices_map.each { |vi, nvi| vertexes_ex[nvi] = vertexes_ex[vi] } if vertexes_ex
867
+ b.indices = vertex_indices.collect { |vi| old_indices_map[vi] }
868
+ b.recompute_from_absolute_indices
869
+ b
870
+ end
871
+
872
+ def recompute_from_absolute_indices
873
+ @header.num_indices = @indices.length
874
+ unless @header.num_indices == 0
875
+ sorted_indices = @indices.sort.uniq
876
+ @header.vertex_start = sorted_indices.first
877
+ @header.vertex_end = sorted_indices.last + 1
878
+ if sorted_indices.last > 0xffff
879
+ offset = @header.vertex_offset = @header.vertex_start
880
+ @indices.collect! { |i| i - offset }
881
+ else
882
+ @header.vertex_offset = 0
883
+ end
884
+ end
885
+ self
886
+ end
887
+
888
+ def triangles
889
+ inds = @indices.collect{ |i| i + @header.vertex_offset }
890
+ if @header.primitive_type == 4
891
+ inds.each_slice(3).to_a
892
+ else
893
+ inds.each_cons(3).each_with_index.collect do |(v0, v1, v2), i|
894
+ if i.even?
895
+ [v0, v1, v2]
896
+ else
897
+ [v1, v0, v2]
898
+ end
899
+ end.select { |t| t.uniq.length == 3 }
900
+ end
901
+ end
902
+
903
+ def set_triangles(trs)
904
+ @header.primitive_type = 4
905
+ @indices = trs.flatten
906
+ recompute_from_absolute_indices
907
+ @header.num_indices = @indices.length
908
+ self
909
+ end
910
+
911
+ def filter_vertexes(vertexes)
912
+ vertex_map = vertexes.collect { |i| [i, true] }.to_h
913
+ trs = triangles
914
+ new_trs = trs.select { |tr| vertex_map.include?(tr[0]) && vertex_map.include?(tr[1]) && vertex_map.include?(tr[2]) }
915
+ set_triangles(new_trs)
916
+ end
917
+
918
+ def vertex_indices
919
+ indices.collect { |i| i + @header.vertex_offset }
920
+ end
921
+
922
+ def cleanup_bone_refs(vertexes)
923
+ if header.has_bone_refs != 0
924
+ bone_refs_map = @bone_refs.each_with_index.collect { |b, i| [i, b] }.to_h
925
+ used_bone_refs_indexes = vertex_indices.collect { |vi| vertexes[vi].bone_infos.get_indexes }.flatten.uniq
926
+ new_bone_refs_list = used_bone_refs_indexes.collect{ |i| bone_refs_map[i] }.uniq.sort
927
+ new_bone_refs_reverse_map = new_bone_refs_list.each_with_index.collect { |b, i| [b, i] }.to_h
928
+ translation_map = used_bone_refs_indexes.collect { |ri|
929
+ [ri, new_bone_refs_reverse_map[bone_refs_map[ri]]]
930
+ }.to_h
931
+ vertex_indices.uniq.sort.each { |vi| vertexes[vi].bone_infos.remap_indexes(translation_map) }
932
+ @bone_refs = new_bone_refs_list
933
+ @num_bone_ref = @bone_refs.length
934
+ end
935
+ self
936
+ end
937
+
938
+ def add_ancestors_bone_refs(vertexes, bones)
939
+ if header.has_bone_refs != 0
940
+ bone_refs_map = @bone_refs.each_with_index.collect { |b, i| [i, b] }.to_h
941
+ used_bone_refs_indexes = vertex_indices.collect { |vi| vertexes[vi].bone_infos.get_indexes }.flatten.uniq
942
+ new_bone_refs_list = used_bone_refs_indexes.collect{ |i| bone_refs_map[i] }.uniq.sort
943
+ new_bone_refs_set = Set::new(new_bone_refs_list)
944
+ new_bone_refs_list.each { |bi|
945
+ new_bone_refs_set.merge(bones[bi].parents.collect(&:index))
946
+ }
947
+ new_bone_refs_list = new_bone_refs_set.to_a.sort
948
+ new_bone_refs_reverse_map = new_bone_refs_list.each_with_index.collect { |b, i| [b, i] }.to_h
949
+ translation_map = used_bone_refs_indexes.collect { |ri|
950
+ [ri, new_bone_refs_reverse_map[bone_refs_map[ri]]]
951
+ }.to_h
952
+ vertex_indices.uniq.sort.each { |vi| vertexes[vi].bone_infos.remap_indexes(translation_map) }
953
+ @bone_refs = new_bone_refs_list
954
+ @num_bone_ref = @bone_refs.length
955
+ end
956
+ end
957
+
958
+ def add_previous_bone_refs(vertexes, bones)
959
+ if header.has_bone_refs != 0
960
+ bone_refs_map = @bone_refs.each_with_index.collect { |b, i| [i, b] }.to_h
961
+ used_bone_refs_indexes = vertex_indices.collect { |vi| vertexes[vi].bone_infos.get_indexes }.flatten.uniq
962
+ last_bone = used_bone_refs_indexes.collect{ |i| bone_refs_map[i] }.uniq.max
963
+ new_bone_refs_list = (0..last_bone).to_a
964
+ new_bone_refs_reverse_map = new_bone_refs_list.each_with_index.collect { |b, i| [b, i] }.to_h
965
+ translation_map = used_bone_refs_indexes.collect { |ri|
966
+ [ri, new_bone_refs_reverse_map[bone_refs_map[ri]]]
967
+ }.to_h
968
+ vertex_indices.uniq.sort.each { |vi| vertexes[vi].bone_infos.remap_indexes(translation_map) }
969
+ @bone_refs = new_bone_refs_list
970
+ @num_bone_ref = @bone_refs.length
971
+ end
972
+ end
973
+
974
+ end
975
+
976
+ class MeshHeader < LibBin::Structure
977
+ int16 :id
978
+ int16 :num_batch
979
+ int16 :u_a1
980
+ int16 :u_a2
981
+ uint32 :offset_batch_offsets
982
+ uint32 :u_b
983
+ int32 :u_c, length: 4
984
+ string :name, 32
985
+ float :mat, length: 12
986
+
987
+ def initialize
988
+ @id = 0
989
+ @num_batch = 0
990
+ @u_a1 = 0
991
+ @u_a2 = 1
992
+ @offset_batch_offsets = 128
993
+ @u_b = 0x80000000
994
+ @u_c = [0]*4
995
+ @name = [0]*32
996
+ @mat = [0.0, 0.98, -0.42, 1.64, 1.10, 2.08,
997
+ 0.1, -1.10, -0.12, -0.95, 0.0, 0.0]
998
+ end
999
+ end
1000
+
1001
+ class Mesh < LibBin::Structure
1002
+ register_field :header, MeshHeader
1003
+ uint32 :batch_offsets, length: 'header\num_batch',
1004
+ offset: '__position + header\offset_batch_offsets'
1005
+ register_field :batches, Batch, count: 'header\num_batch', sequence: true,
1006
+ offset: '__position + header\offset_batch_offsets + batch_offsets[__iterator]'
1007
+
1008
+ def initialize
1009
+ @header = MeshHeader::new
1010
+ @batch_offsets = []
1011
+ @batches = []
1012
+ end
1013
+
1014
+ def __size(position = 0, parent = nil, index = nil)
1015
+ sz = @header.offset_batch_offsets
1016
+ sz += @header.num_batch * 4
1017
+ sz = align(sz, 0x20)
1018
+ @header.num_batch.times { |i|
1019
+ sz += @batches[i].__size
1020
+ sz = align(sz, 0x20)
1021
+ }
1022
+ sz
1023
+ end
1024
+
1025
+ def recompute_layout
1026
+ @header.num_batch = @batches.length
1027
+ off = @header.num_batch * 4
1028
+ @batch_offsets = []
1029
+ @header.num_batch.times { |j|
1030
+ off = align(off, 0x20)
1031
+ @batch_offsets.push off
1032
+ off += @batches[j].__size
1033
+ }
1034
+ end
1035
+
1036
+ def duplicate(positions, vertexes, vertexes_ex)
1037
+ m = Mesh::new
1038
+ m.header = @header
1039
+ m.batch_offsets = @batch_offsets
1040
+ m.batches = @batches.collect { |b| b.duplicate(positions, vertexes, vertexes_ex) }
1041
+ m
1042
+ end
1043
+
1044
+ end
1045
+
1046
+ class ShaderName < LibBin::Structure
1047
+ string :name, 16
1048
+ end
1049
+
1050
+ class TexInfo < LibBin::Structure
1051
+ uint32 :id
1052
+ int32 :info
1053
+ end
1054
+
1055
+ class TexInfos < LibBin::Structure
1056
+ int32 :num_tex_infos
1057
+ register_field :tex_infos, TexInfo, length: 'num_tex_infos'
1058
+ end
1059
+
1060
+ class WMBFileHeader < LibBin::Structure
1061
+ uint32 :id
1062
+ int32 :u_a
1063
+ int32 :u_b
1064
+ int32 :num_vertexes
1065
+ int8 :vertex_ex_data_size
1066
+ int8 :vertex_ex_data
1067
+ int16 :u_e
1068
+ int32 :offset_positions
1069
+ uint32 :offset_vertexes
1070
+ uint32 :offset_vertexes_ex_data
1071
+ int32 :u_g, length: 4
1072
+ int32 :num_bones
1073
+ uint32 :offset_bone_hierarchy
1074
+ uint32 :offset_bone_relative_position
1075
+ uint32 :offset_bone_position
1076
+ uint32 :offset_bone_index_translate_table
1077
+ int32 :num_materials
1078
+ uint32 :offset_materials_offsets
1079
+ uint32 :offset_materials
1080
+ int32 :num_meshes
1081
+ uint32 :offset_meshes_offsets
1082
+ uint32 :offset_meshes
1083
+ int32 :u_k
1084
+ int32 :u_l
1085
+ uint32 :offset_u_j
1086
+ uint32 :offset_bone_symmetries
1087
+ uint32 :offset_bone_flags
1088
+ uint32 :offset_shader_names
1089
+ uint32 :offset_tex_infos
1090
+ uint32 :u_m
1091
+ uint32 :u_n
1092
+ end
1093
+
1094
+ register_field :header, WMBFileHeader
1095
+ register_field :positions, Position, length: 'header\num_vertexes', offset: 'header\offset_positions'
1096
+ register_field :vertexes, 'get_vertex_types[0]', length: 'header\num_vertexes', offset: 'header\offset_vertexes'
1097
+ register_field :vertexes_ex_data, 'get_vertex_types[1]', length: 'header\num_vertexes',
1098
+ offset: 'header\offset_vertexes_ex_data'
1099
+ int16 :bone_hierarchy, length: 'header\num_bones', offset: 'header\offset_bone_hierarchy'
1100
+ register_field :bone_relative_positions, Position, length: 'header\num_bones',
1101
+ offset: 'header\offset_bone_relative_position'
1102
+ register_field :bone_positions, Position, length: 'header\num_bones', offset: 'header\offset_bone_position'
1103
+ register_field :bone_index_translate_table, BoneIndexTranslateTable,
1104
+ offset: 'header\offset_bone_index_translate_table'
1105
+ register_field :u_j, UnknownStruct, offset: 'header\offset_u_j'
1106
+ int16 :bone_symmetries, length: 'header\num_bones', offset: 'header\offset_bone_symmetries'
1107
+ int8 :bone_flags, length: 'header\num_bones', offset: 'header\offset_bone_flags'
1108
+ register_field :shader_names, ShaderName, length: 'header\num_materials', offset: 'header\offset_shader_names'
1109
+ register_field :tex_infos, TexInfos, offset: 'header\offset_tex_infos'
1110
+ uint32 :materials_offsets, length: 'header\num_materials', offset: 'header\offset_materials_offsets'
1111
+ register_field :materials, Material, count: 'header\num_materials', sequence: true,
1112
+ offset: 'header\offset_materials + materials_offsets[__iterator]'
1113
+ uint32 :meshes_offsets, length: 'header\num_meshes', offset: 'header\offset_meshes_offsets'
1114
+ register_field :meshes, Mesh, count: 'header\num_meshes', sequence: true,
1115
+ offset: 'header\offset_meshes + meshes_offsets[__iterator]'
1116
+
1117
+ def texture_ids
1118
+ return [] unless @tex_infos
1119
+ @tex_infos.tex_infos.collect { |i| i.id }
1120
+ end
1121
+
1122
+ def materials_textures
1123
+ return {} unless @tex_infos
1124
+ tex_ids = texture_ids
1125
+ @materials.each_with_index.collect { |m, i|
1126
+ [i, m.material_data.select { |t| tex_ids.include?(t) }]
1127
+ }.to_h
1128
+ end
1129
+
1130
+ def get_vertex_types
1131
+ if @vertex_type
1132
+ return [@vertex_type, @vertex_ex_type]
1133
+ else
1134
+ types = VERTEX_TYPES[ [ @header.u_b, @header.vertex_ex_data_size, @header.vertex_ex_data] ]
1135
+ @vertex_type = Class::new(LibBin::Structure)
1136
+ @vertex_size = 0
1137
+ if types[0]
1138
+ types[0].each { |name, type|
1139
+ @vertex_type.register_field(name, VERTEX_FIELDS[type][0])
1140
+ @vertex_size += VERTEX_FIELDS[type][1]
1141
+ }
1142
+ end
1143
+ @vertex_ex_type = Class::new(LibBin::Structure)
1144
+ @vertex_ex_size = 0
1145
+ if types[1]
1146
+ types[1].each { |name, type|
1147
+ @vertex_ex_type.register_field(name, VERTEX_FIELDS[type][0])
1148
+ @vertex_ex_size += VERTEX_FIELDS[type][1]
1149
+ }
1150
+ end
1151
+ return [@vertex_type, @vertex_ex_type]
1152
+ end
1153
+ end
1154
+
1155
+ def get_vertex_fields
1156
+ if @vertex_fields
1157
+ return @vertex_fields
1158
+ else
1159
+ types = VERTEX_TYPES[ [ @header.u_b, @header.vertex_ex_data_size, @header.vertex_ex_data] ]
1160
+ @vertex_fields = []
1161
+ if types[0]
1162
+ types[0].each { |name, type|
1163
+ @vertex_fields.push(name)
1164
+ }
1165
+ end
1166
+ if types[1]
1167
+ types[1].each { |name, type|
1168
+ @vertex_fields.push(name)
1169
+ }
1170
+ end
1171
+ return @vertex_fields
1172
+ end
1173
+ end
1174
+
1175
+ def get_vertex_field(field, vi)
1176
+ if @vertexes[vi].respond_to?(field)
1177
+ return @vertexes[vi].send(field)
1178
+ elsif @vertexes_ex_data && @vertexes_ex_data[vi].respond_to?(field)
1179
+ return @vertexes_ex_data[vi].send(field)
1180
+ elsif field == :position && @positions
1181
+ return @positions[vi]
1182
+ else
1183
+ return nil
1184
+ end
1185
+ end
1186
+
1187
+ def set_vertex_field(field, vi, val)
1188
+ if @vertexes[vi].respond_to?(field)
1189
+ return @vertexes[vi].send(:"#{field}=", val)
1190
+ elsif @vertexes_ex_data && @vertexes_ex_data[vi].respond_to?(field)
1191
+ return @vertexes_ex_data[vi].send(:"#{field}=", val)
1192
+ elsif field == :position && @positions
1193
+ return @positions[vi] = val
1194
+ else
1195
+ raise "Couldn't find field: #{field}!"
1196
+ end
1197
+ end
1198
+
1199
+ def self.convert(input_name, output_name, output_big = false)
1200
+ if input_name.respond_to?(:read) && input_name.respond_to?(:seek)
1201
+ input = input_name
1202
+ else
1203
+ File.open(input_name, "rb") { |f|
1204
+ input = StringIO::new(f.read, "rb")
1205
+ }
1206
+ end
1207
+ input_big = validate_endianness(input)
1208
+
1209
+ if output_name.respond_to?(:write) && output_name.respond_to?(:seek)
1210
+ output = output_name
1211
+ else
1212
+ output = File.open(output_name, "wb")
1213
+ end
1214
+ output.write("\xFB"*input.size)
1215
+ output.rewind
1216
+
1217
+ wmb = self::new
1218
+ wmb.instance_variable_set(:@__was_big, input_big)
1219
+ wmb.__convert(input, output, input_big, output_big)
1220
+
1221
+ input.close unless input_name.respond_to?(:read) && input_name.respond_to?(:seek)
1222
+ output.close unless output_name.respond_to?(:write) && output_name.respond_to?(:seek)
1223
+ wmb
1224
+ end
1225
+
1226
+ def self.load(input_name)
1227
+ if input_name.respond_to?(:read) && input_name.respond_to?(:seek)
1228
+ input = input_name
1229
+ else
1230
+ input = File.open(input_name, "rb")
1231
+ end
1232
+ if is_wmb3?(input)
1233
+ input.close unless input_name.respond_to?(:read) && input_name.respond_to?(:seek)
1234
+ return WMB3File::load(input_name)
1235
+ end
1236
+ input = StringIO::new(input.read, "rb")
1237
+ input_big = validate_endianness(input)
1238
+
1239
+ wmb = self::new
1240
+ wmb.instance_variable_set(:@__was_big, input_big)
1241
+ wmb.__load(input, input_big)
1242
+ input.close unless input_name.respond_to?(:read) && input_name.respond_to?(:seek)
1243
+
1244
+ wmb
1245
+ end
1246
+
1247
+ def was_big?
1248
+ @__was_big
1249
+ end
1250
+
1251
+ def is_bayo2?
1252
+ @header.offset_shader_names != 0 || @header.offset_tex_infos != 0
1253
+ end
1254
+
1255
+ def self.is_wmb3?(input)
1256
+ input.rewind
1257
+ id = input.read(4).unpack("a4").first
1258
+ input.rewind
1259
+ return id == "WMB3".b || id == "3BMW".b
1260
+ end
1261
+
1262
+ def self.validate_endianness(input)
1263
+ input.rewind
1264
+ id = input.read(4).unpack("a4").first
1265
+ case id
1266
+ when "WMB\0".b
1267
+ input_big = false
1268
+ when "\0BMW".b
1269
+ input_big = true
1270
+ else
1271
+ raise "Invalid file type #{id}!"
1272
+ end
1273
+ input.rewind
1274
+ input_big
1275
+ end
1276
+
1277
+ def dump(output_name, output_big = false)
1278
+ if output_name.respond_to?(:write) && output_name.respond_to?(:seek)
1279
+ output = output_name
1280
+ else
1281
+ output = StringIO::new("", "wb")
1282
+ end
1283
+ output.rewind
1284
+
1285
+ __set_dump_state(output, output_big, nil, nil)
1286
+ __dump_fields
1287
+ __unset_dump_state
1288
+
1289
+ sz = output.size
1290
+ sz = align(sz, 0x20)
1291
+ if sz > output.size
1292
+ output.seek(sz-1)
1293
+ output.write("\x00")
1294
+ end
1295
+
1296
+ unless output_name.respond_to?(:write) && output_name.respond_to?(:seek)
1297
+ File.open(output_name, "wb") { |f|
1298
+ f.write output.string
1299
+ }
1300
+ output.close
1301
+ end
1302
+ self
1303
+ end
1304
+
1305
+ def check_normals
1306
+ return self if @__was_big
1307
+ wide_normals = false
1308
+ @vertexes.each { |v|
1309
+ if v.normal.normal_small_orig.bytes.first == 0
1310
+ wide_normals = true
1311
+ end
1312
+ }
1313
+ if wide_normals
1314
+ @vertexes.each { |v|
1315
+ v.normal.wide = true
1316
+ v.normal.redecode_wide
1317
+ }
1318
+ end
1319
+ self
1320
+ end
1321
+
1322
+ def normals_to_wide
1323
+ @vertexes.each { |v|
1324
+ v.normal.wide = true
1325
+ v.normal.normal_big_orig = nil
1326
+ v.normal.normal_small_orig = nil
1327
+ }
1328
+ self
1329
+ end
1330
+
1331
+ def normals_to_narrow
1332
+ @vertexes.each { |v|
1333
+ v.normal.wide = false
1334
+ v.normal.normal_big_orig = nil
1335
+ v.normal.normal_small_orig = nil
1336
+ }
1337
+ self
1338
+ end
1339
+
1340
+ def get_bone_structure
1341
+ bones = @bone_positions.collect { |p|
1342
+ Bone::new(p)
1343
+ }
1344
+ bones.each_with_index { |b, i|
1345
+ if @bone_hierarchy[i] == -1
1346
+ b.parent = nil
1347
+ else
1348
+ b.parent = bones[@bone_hierarchy[i]]
1349
+ bones[@bone_hierarchy[i]].children.push(b)
1350
+ end
1351
+ b.index = i
1352
+ b.relative_position = @bone_relative_positions[i]
1353
+ b.symmetric = @bone_symmetries[i] if @header.offset_bone_symmetries > 0x0
1354
+ b.flag = @bone_flags[i] if @header.offset_bone_flags > 0x0
1355
+ }
1356
+ end
1357
+
1358
+ def recompute_relative_positions
1359
+ @bone_hierarchy.each_with_index { |b, i|
1360
+ if b != -1
1361
+ #puts "bone: #{i} parent: #{b} position: #{@bone_positions[i]} pposition: #{@bone_positions[b]}"
1362
+ @bone_relative_positions[i] = @bone_positions[i] - @bone_positions[b]
1363
+ else
1364
+ @bone_relative_positions[i] = @bone_positions[i]
1365
+ end
1366
+ }
1367
+ self
1368
+ end
1369
+
1370
+ def set_bone_structure(bones)
1371
+ @bone_hierarchy = []
1372
+ @bone_relative_positions = []
1373
+ @bone_positions = []
1374
+ @bone_symmetries = [] if @header.offset_bone_symmetries > 0x0
1375
+ @bone_flags = [] if @header.offset_bone_flags > 0x0
1376
+ bones.each { |b|
1377
+ p_index = -1
1378
+ p_index = b.parent.index if b.parent
1379
+ @bone_hierarchy.push p_index
1380
+ @bone_positions.push b.position
1381
+ rel_position = b.relative_position
1382
+ unless rel_position
1383
+ if b.parent
1384
+ rel_position = b.position - b.parent.position
1385
+ else
1386
+ rel_position = b.position
1387
+ end
1388
+ end
1389
+ @bone_relative_positions.push rel_position
1390
+ @bone_symmetries.push b.symmetric if @header.offset_bone_symmetries > 0x0
1391
+ @bone_flags.push b.flag if @header.offset_bone_flags > 0x0
1392
+ }
1393
+ @header.num_bones = bones.size
1394
+ self
1395
+ end
1396
+
1397
+ def scale(s)
1398
+ if @positions
1399
+ @positions.each { |p|
1400
+ p.x = p.x * s
1401
+ p.y = p.y * s
1402
+ p.z = p.z * s
1403
+ }
1404
+ end
1405
+ if @vertexes && @vertexes.first.respond_to?(:position)
1406
+ @vertexes.each { |v|
1407
+ v.position.x = v.position.x * s
1408
+ v.position.y = v.position.y * s
1409
+ v.position.z = v.position.z * s
1410
+ }
1411
+ end
1412
+ if @vertexes && @vertexes.first.respond_to?(:position2)
1413
+ @vertexes.each { |v|
1414
+ v.position2.x = v.position2.x * s
1415
+ v.position2.y = v.position2.y * s
1416
+ v.position2.z = v.position2.z * s
1417
+ }
1418
+ end
1419
+ if @vertexes_ex_data && @vertexes_ex_data.first.respond_to?(:position2)
1420
+ @vertexes_ex_data.each { |v|
1421
+ v.position2.x = v.position2.x * s
1422
+ v.position2.y = v.position2.y * s
1423
+ v.position2.z = v.position2.z * s
1424
+ }
1425
+ end
1426
+ if @bone_positions
1427
+ @bone_positions.each { |p|
1428
+ p.x = p.x * s
1429
+ p.y = p.y * s
1430
+ p.z = p.z * s
1431
+ }
1432
+ recompute_relative_positions
1433
+ end
1434
+ self
1435
+ end
1436
+
1437
+ def shift(x, y, z)
1438
+ if @positions
1439
+ @positions.each { |p|
1440
+ p.x = p.x + x
1441
+ p.y = p.y + y
1442
+ p.z = p.z + z
1443
+ }
1444
+ end
1445
+ if @vertexes && @vertexes.first.respond_to?(:position)
1446
+ @vertexes.each { |v|
1447
+ v.position.x = v.position.x + x
1448
+ v.position.y = v.position.y + y
1449
+ v.position.z = v.position.z + z
1450
+ }
1451
+ end
1452
+ if @vertexes && @vertexes.first.respond_to?(:position2)
1453
+ @vertexes.each { |v|
1454
+ v.position2.x = v.position2.x + x
1455
+ v.position2.y = v.position2.y + y
1456
+ v.position2.z = v.position2.z + z
1457
+ }
1458
+ end
1459
+ if @vertexes_ex_data && @vertexes_ex_data.first.respond_to?(:position2)
1460
+ @vertexes_ex_data.each { |v|
1461
+ v.position2.x = v.position2.x + x
1462
+ v.position2.y = v.position2.y + y
1463
+ v.position2.z = v.position2.z + z
1464
+ }
1465
+ end
1466
+ if @bone_positions
1467
+ @bone_positions.each { |p|
1468
+ p.x = p.x + x
1469
+ p.y = p.y + y
1470
+ p.z = p.z + z
1471
+ }
1472
+ recompute_relative_positions
1473
+ end
1474
+ self
1475
+ end
1476
+
1477
+ def rotate(*args)
1478
+ if args.length == 2
1479
+ (rx, ry, rz), center = args
1480
+ elsif args.length == 3
1481
+ rx, ry, rz = args
1482
+ center = nil
1483
+ else
1484
+ raise "Invalid arguments for rotate: #{args.inspect}!"
1485
+ end
1486
+ m = Linalg::get_rotation_matrix(rx, ry, rz, center: center)
1487
+ if @positions
1488
+ @positions.each { |p|
1489
+ r = m * Linalg::Vector::new(p.x, p.y, p.z)
1490
+ p.x = r.x
1491
+ p.y = r.y
1492
+ p.z = r.z
1493
+ }
1494
+ end
1495
+ if @vertexes && @vertexes.first.respond_to?(:position)
1496
+ @vertexes.each { |v|
1497
+ r = m * Linalg::Vector::new(v.position.x, v.position.y, v.position.z)
1498
+ v.position.x = r.x
1499
+ v.position.y = r.y
1500
+ v.position.z = r.z
1501
+ }
1502
+ end
1503
+ if @vertexes && @vertexes.first.respond_to?(:position2)
1504
+ @vertexes.each { |v|
1505
+ r = m * Linalg::Vector::new(v.position2.x, v.position2.y, v.position2.z)
1506
+ v.position2.x = r.x
1507
+ v.position2.y = r.y
1508
+ v.position2.z = r.z
1509
+ }
1510
+ end
1511
+ if @vertexes_ex_data && @vertexes_ex_data.first.respond_to?(:position2)
1512
+ @vertexes_ex_data.each { |v|
1513
+ r = m * Linalg::Vector::new(v.position2.x, v.position2.y, v.position2.z)
1514
+ v.position2.x = r.x
1515
+ v.position2.y = r.y
1516
+ v.position2.z = r.z
1517
+ }
1518
+ end
1519
+ if @bone_positions
1520
+ @bone_positions.each { |p|
1521
+ r = m * Linalg::Vector::new(p.x, p.y, p.z)
1522
+ p.x = r.x
1523
+ p.y = r.y
1524
+ p.z = r.z
1525
+ }
1526
+ recompute_relative_positions
1527
+ end
1528
+ if @vertexes
1529
+ @vertexes.each { |v|
1530
+ r = m * Linalg::Vector::new(v.normal.x, v.normal.y, v.normal.z, 0.0)
1531
+ v.normal.x = r.x
1532
+ v.normal.y = r.y
1533
+ v.normal.z = r.z
1534
+ if v.tangents.data != 0xc0c0c0ff
1535
+ r = m * Linalg::Vector::new(v.tangents.x, v.tangents.y, v.tangents.z, 0.0)
1536
+ v.tangents.x = r.x
1537
+ v.tangents.y = r.y
1538
+ v.tangents.z = r.z
1539
+ end
1540
+ }
1541
+ end
1542
+ self
1543
+ end
1544
+
1545
+ def set_pose(pose, exp)
1546
+ table = @bone_index_translate_table.table
1547
+ bones = get_bone_structure
1548
+ tracks = Hash::new { |h,k| h[k] = {} }
1549
+ tracks = bones.collect { |b|
1550
+ [ b.relative_position.x,
1551
+ b.relative_position.y,
1552
+ b.relative_position.z,
1553
+ 0.0, 0.0, 0.0,
1554
+ 0.0,
1555
+ 1.0, 1.0, 1.0,
1556
+ 1.0, 1.0, 1.0 ]
1557
+ }
1558
+ pose.each { |b, ts|
1559
+ bi = table[b]
1560
+ if bi
1561
+ ts.each { |ti, v|
1562
+ tracks[bi][ti] = v
1563
+ }
1564
+ end
1565
+ }
1566
+ if exp
1567
+ exp.apply(tracks, table)
1568
+ end
1569
+ matrices = tracks.each_with_index.collect { |ts, bi|
1570
+ if @header.offset_bone_flags > 0x0
1571
+ order = @bone_flags[bi]
1572
+ else
1573
+ order = nil
1574
+ end
1575
+ m = Linalg::get_translation_matrix(*ts[0..2])
1576
+ pi = @bone_hierarchy[bi]
1577
+ if pi != -1
1578
+ parent_cumulative_scale = tracks[pi][10..12]
1579
+ m = m * Linalg::get_inverse_scaling_matrix(*parent_cumulative_scale)
1580
+ 3.times { |i| ts[10+i] *= parent_cumulative_scale[i] }
1581
+ end
1582
+ 3.times { |i| ts[10+i] *= ts[7+i] }
1583
+ m = m * Linalg::get_rotation_matrix(*ts[3..5], order: order)
1584
+ m = m * Linalg::get_scaling_matrix(*ts[10..12])
1585
+ }
1586
+ multiplied_matrices = []
1587
+ inverse_bind_pose = bones.collect { |b|
1588
+ Linalg::get_translation_matrix(-1 * b.position.x, -1 * b.position.y, -1 * b.position.z)
1589
+ }
1590
+ bones.each { |b|
1591
+ if b.parent
1592
+ multiplied_matrices[b.index] = multiplied_matrices[b.parent.index] * matrices[b.index]
1593
+ else
1594
+ multiplied_matrices[b.index] = matrices[b.index]
1595
+ end
1596
+ }
1597
+ bones.each { |b|
1598
+ v = multiplied_matrices[b.index] * Linalg::Vector::new( 0.0, 0.0, 0.0 )
1599
+ b.position.x = v.x
1600
+ b.position.y = v.y
1601
+ b.position.z = v.z
1602
+ b.relative_position = nil
1603
+ }
1604
+ set_bone_structure(bones)
1605
+ multiplied_matrices = bones.collect { |b|
1606
+ multiplied_matrices[b.index] * inverse_bind_pose[b.index]
1607
+ }
1608
+ vertex_usage = get_vertex_usage
1609
+ vertex_usage.each { |vi, bs|
1610
+ bone_refs = bs.first.bone_refs
1611
+ bone_infos = get_vertex_field(:bone_infos, vi)
1612
+ indexes_and_weights = bone_infos.get_indexes_and_weights
1613
+ vertex_matrix = Linalg::get_zero_matrix
1614
+ indexes_and_weights.each { |bi, bw|
1615
+ i = bone_refs[bi]
1616
+ vertex_matrix = vertex_matrix + multiplied_matrices[i] * (bw.to_f/255.to_f)
1617
+ }
1618
+ normal_matrix = vertex_matrix.inverse.transpose
1619
+ vp = get_vertex_field(:position, vi)
1620
+ new_vp = vertex_matrix * Linalg::Vector::new(vp.x, vp.y, vp.z)
1621
+ vp.x = new_vp.x
1622
+ vp.y = new_vp.y
1623
+ vp.z = new_vp.z
1624
+ n = get_vertex_field(:normal, vi)
1625
+ new_n = (normal_matrix * Linalg::Vector::new(n.x, n.y, n.z, 0.0)).normalize
1626
+ n.x = new_n.x
1627
+ n.y = new_n.y
1628
+ n.z = new_n.z
1629
+ t = get_vertex_field(:tangents, vi)
1630
+ new_t = (normal_matrix * Linalg::Vector::new(t.x, t.y, t.z, 0.0)).normalize
1631
+ t.x = new_t.x
1632
+ t.y = new_t.y
1633
+ t.z = new_t.z
1634
+ p2 = get_vertex_field(:position2, vi)
1635
+ if p2
1636
+ new_p2 = vertex_matrix * Linalg::Vector::new(p2.x, p2.y, p2.z)
1637
+ p2.x = new_p2.x
1638
+ p2.y = new_p2.y
1639
+ p2.z = new_p2.z
1640
+ end
1641
+ }
1642
+ self
1643
+ end
1644
+
1645
+ def restrict_bones(used_bones)
1646
+ bones = get_bone_structure
1647
+ used_bones_array = used_bones.to_a.sort
1648
+ bone_map = used_bones_array.each_with_index.collect.to_h
1649
+ new_bones = used_bones_array.collect { |bi|
1650
+ b = bones[bi].dup
1651
+ b.index = bone_map[b.index]
1652
+ b
1653
+ }
1654
+ new_bones.each { |b|
1655
+ b.parent = new_bones[bone_map[b.parent.index]] if b.parent
1656
+ }
1657
+ set_bone_structure(new_bones)
1658
+
1659
+ table = @bone_index_translate_table.table
1660
+ new_table = table.select { |k,v|
1661
+ used_bones.include? v
1662
+ }
1663
+ new_table = new_table.collect { |k, v| [k, bone_map[v]] }.to_h
1664
+ @bone_index_translate_table.table = new_table
1665
+ @meshes.each_with_index { |m, i|
1666
+ m.batches.each_with_index { |b, j|
1667
+ b.bone_refs.collect! { |bi|
1668
+ new_bi = bone_map[bi]
1669
+ raise "Bone #{bi} was deleted bu is still used by mesh #{i} batch #{j}!" unless new_bi
1670
+ new_bi
1671
+ }
1672
+ }
1673
+ }
1674
+ self
1675
+ end
1676
+ private :restrict_bones
1677
+
1678
+ def remap_bones(bone_map)
1679
+ raise "Global index specified multiple times!" unless bone_map.values.uniq.size == bone_map.size
1680
+ local_to_global = @bone_index_translate_table.table.invert
1681
+ unknown_bones = bone_map.keys - local_to_global.keys
1682
+ raise "Unknown bones: #{unknown_bones}!" unless unknown_bones.size == 0
1683
+ global_tt = {}
1684
+ bone_map.each { |k, v|
1685
+ global_tt[local_to_global.delete(k)] = v
1686
+ }
1687
+ puts global_tt
1688
+ table = local_to_global.invert
1689
+ new_global_indexes = bone_map.values - table.keys
1690
+ raise "Global indexes: #{bone_map.values - new_global_indexes} still in use!" unless new_global_indexes.size == bone_map.size
1691
+ bone_map.each { |k, v|
1692
+ table[v] = k
1693
+ }
1694
+ @bone_symmetries.collect! { |k|
1695
+ global_tt[k] ? global_tt[k] : k
1696
+ } if @bone_symmetries
1697
+ @bone_index_translate_table.table = table
1698
+ self
1699
+ end
1700
+
1701
+ def order_bones
1702
+ arr = @bone_index_translate_table.table.sort
1703
+ old_local_to_new_local = {}
1704
+ arr.each_with_index { |(_, old_local), new_local|
1705
+ old_local_to_new_local[old_local] = new_local
1706
+ }
1707
+ new_local_to_old_local = old_local_to_new_local.invert
1708
+ bones = get_bone_structure
1709
+ new_bones = bones.size.times.collect { |new_bi|
1710
+ b = bones[new_local_to_old_local[new_bi]]
1711
+ b.index = new_bi
1712
+ b
1713
+ }
1714
+ new_bones.each { |b|
1715
+ raise "Invali hierarchy: #{b.parent.index} >= #{b.index} !" if b.parent && b.parent.index >= b.index
1716
+ }
1717
+ set_bone_structure(new_bones)
1718
+ new_table = @bone_index_translate_table.table.collect { |k, v| [k, old_local_to_new_local[v]] }.to_h
1719
+ @bone_index_translate_table.table = new_table
1720
+ @meshes.each_with_index { |m, i|
1721
+ m.batches.each_with_index { |b, j|
1722
+ b.bone_refs.collect! { |bi|
1723
+ old_local_to_new_local[bi]
1724
+ }
1725
+ }
1726
+ }
1727
+ self
1728
+ end
1729
+
1730
+ def delete_meshes(list)
1731
+ kept_meshes = @meshes.size.times.to_a - list
1732
+ @meshes = kept_meshes.collect { |i|
1733
+ @meshes[i]
1734
+ }
1735
+ @header.num_meshes = @meshes.size
1736
+ self
1737
+ end
1738
+
1739
+ def split_meshes(list)
1740
+ kept_meshes = @meshes.size.times.to_a - list
1741
+ split_meshes = @meshes.size.times.to_a - kept_meshes
1742
+ new_meshes = []
1743
+ split_meshes.each { |i|
1744
+ @meshes[i].batches.each_with_index { |b, j|
1745
+ new_mesh = @meshes[i].dup
1746
+ new_mesh.header = @meshes[i].header.dup
1747
+ new_mesh.header.name = @meshes[i].header.name.tr("\x00","") + ("_%02d" % j)
1748
+ new_mesh.batches = [b]
1749
+ new_meshes.push new_mesh
1750
+ }
1751
+ }
1752
+ @meshes = kept_meshes.collect { |i|
1753
+ @meshes[i]
1754
+ }
1755
+ @meshes += new_meshes
1756
+ @header.num_meshes = @meshes.size
1757
+ self
1758
+ end
1759
+
1760
+ def dummy_meshes(list)
1761
+ list.map { |i| m = @meshes[i]
1762
+ m.header.num_batch = 1
1763
+ m.batches = [m.batches.first]
1764
+ m.batches[0].set_triangles([m.batches[0].triangles.first[0]]*3)
1765
+ }
1766
+ self
1767
+ end
1768
+
1769
+ def duplicate_meshes(list)
1770
+ @meshes += list.collect { |i|
1771
+ @meshes[i].duplicate(@positions, @vertexes, @vertexes_ex_data)
1772
+ }
1773
+ @header.num_meshes = @meshes.size
1774
+ @header.num_vertexes = @vertexes.size
1775
+ self
1776
+ end
1777
+
1778
+ def swap_meshes(hash)
1779
+ hash.each { |k, v|
1780
+ raise "Mesh #{k} was not found in the model!" unless @meshes[k]
1781
+ raise "Mesh #{v} was not found in the model!" unless @meshes[v]
1782
+ tmp = @meshes[k]
1783
+ @meshes[k] = @meshes[v]
1784
+ @meshes[v] = tmp
1785
+ }
1786
+ self
1787
+ end
1788
+
1789
+ def move_meshes(positions)
1790
+ raise "Invalid positions!" unless positions.size > 0
1791
+ positions.each { |k, v|
1792
+ raise "Mesh #{k} was not found in the model!" unless @meshes[k]
1793
+ raise "Invalid target position #{v}!" unless v >= 0 && v < @meshes.length
1794
+ }
1795
+ raise "Duplicate mesh found!" unless positions.keys.uniq.size == positions.size
1796
+ raise "Duplicate target position found!" unless positions.values.uniq.size == positions.size
1797
+ m_p = positions.to_a.sort { |(m1, _), (m2, _)| m2 <=> m1 }
1798
+ m_a = m_p.collect { |m, p|
1799
+ [@meshes.delete_at(m), p]
1800
+ }.sort { |(_, p1), (_, p2)| p1 <=> p2 }
1801
+ m_a.each { |m, p|
1802
+ @meshes.insert(p, m)
1803
+ }
1804
+ self
1805
+ end
1806
+
1807
+ def merge_meshes(hash)
1808
+ hash.each { |k, vs|
1809
+ raise "Mesh #{k} was not found in the model!" unless @meshes[k]
1810
+ vs = [vs].flatten
1811
+ vs.each { |v|
1812
+ raise "Mesh #{v} was not found in the model!" unless @meshes[v]
1813
+ @meshes[k].batches += @meshes[v].batches
1814
+ }
1815
+ @meshes[k].header.num_batch = @meshes[k].batches.length
1816
+ }
1817
+ self
1818
+ end
1819
+
1820
+ def delete_batches(hash)
1821
+ hash.each { |k, list|
1822
+ raise "Mesh #{k} was not found in the model!" unless @meshes[k]
1823
+ list = case list
1824
+ when Enumerable
1825
+ list.to_a
1826
+ else
1827
+ [list]
1828
+ end
1829
+ kept_batches = @meshes[k].batches.length.times.to_a - list
1830
+ @meshes[k].batches = kept_batches.collect { |i|
1831
+ raise "Batch #{i} was not found in mesh #{k}!" unless @meshes[k].batches[i]
1832
+ @meshes[k].batches[i]
1833
+ }
1834
+ @meshes[k].header.num_batch = @meshes[k].batches.length
1835
+ }
1836
+ self
1837
+ end
1838
+
1839
+ def delete_bones(list)
1840
+ used_bones = (@header.num_bones.times.to_a - list)
1841
+ restrict_bones(used_bones)
1842
+ self
1843
+ end
1844
+
1845
+ def cleanup_textures(input_name, overwrite)
1846
+ if File.exist?(input_name.gsub(".wmb",".wtb"))
1847
+ wtb = WTBFile::new(File::new(input_name.gsub(".wmb",".wtb"), "rb"))
1848
+ if overwrite
1849
+ output_name = input_name.gsub(".wmb",".wtb")
1850
+ else
1851
+ output_name = "wtb_output/#{File.basename(input_name, ".wmb")}.wtb"
1852
+ end
1853
+ wtp = false
1854
+ elsif File.exist?(input_name.gsub(".wmb",".wta"))
1855
+ wtb = WTBFile::new(File::new(input_name.gsub(".wmb",".wta"), "rb"), true, File::new(input_name.gsub(".wmb",".wtp"), "rb"))
1856
+ if overwrite
1857
+ output_name = input_name.gsub(".wmb",".wta")
1858
+ else
1859
+ output_name = "wtb_output/#{File.basename(input_name, ".wmb")}.wta"
1860
+ end
1861
+ wtp = true
1862
+ else
1863
+ raise "Could not find texture file!"
1864
+ end
1865
+
1866
+ available_textures = {}
1867
+ digests = []
1868
+ wtb.each.with_index { |(info, t), i|
1869
+ if @tex_info #Bayo 2
1870
+ digest = Digest::SHA1.hexdigest(t.read)
1871
+ available_textures[info[2]] = digest
1872
+ digests.push( digest )
1873
+ else #Bayo 1
1874
+ digest = Digest::SHA1.hexdigest(t.read)
1875
+ available_textures[i] = digest
1876
+ digests.push( digest )
1877
+ end
1878
+ t.rewind
1879
+ }
1880
+ used_textures_digest_map = {}
1881
+ used_texture_digests = Set[]
1882
+ @materials.each { |m|
1883
+ m.material_data[0..4].each { |tex_id|
1884
+ if available_textures.key?(tex_id)
1885
+ digest = available_textures[tex_id]
1886
+ used_textures_digest_map[tex_id] = digest
1887
+ used_texture_digests.add(digest)
1888
+ end
1889
+ }
1890
+ }
1891
+ index_list = digests.each_with_index.collect { |d,i| [i,d] }.select { |i, d|
1892
+ used_texture_digests.delete?(d)
1893
+ }.collect { |i,d| i }
1894
+ new_wtb = WTBFile::new(nil, wtb.big, wtp)
1895
+ j = 0
1896
+ digest_to_tex_id_map = {}
1897
+ wtb.each.with_index { |(info, t), i|
1898
+ if index_list.include?(i)
1899
+ new_wtb.push( t, info[1], info[2])
1900
+ if @tex_info
1901
+ digest_to_tex_id_map[digests[i]] = info[2]
1902
+ else
1903
+ digest_to_tex_id_map[digests[i]] = j
1904
+ end
1905
+ j += 1
1906
+ end
1907
+ }
1908
+ new_wtb.dump(output_name)
1909
+ @materials.each { |m|
1910
+ m.material_data[0..4].each_with_index { |tex_id, i|
1911
+ if available_textures.key?(tex_id)
1912
+ digest = available_textures[tex_id]
1913
+ m.material_data[i] = digest_to_tex_id_map[digest]
1914
+ end
1915
+ }
1916
+ }
1917
+ end
1918
+
1919
+ def advanced_materials
1920
+ if is_bayo2?
1921
+ materials
1922
+ else
1923
+ materials.collect { |m|
1924
+ if $material_db[m.type][:layout]
1925
+ Bayo1Material::new(m)
1926
+ else
1927
+ m
1928
+ end
1929
+ }
1930
+ end
1931
+ end
1932
+
1933
+ def cleanup_materials
1934
+ used_materials = Set[]
1935
+ @meshes.each { |m|
1936
+ m.batches.each { |b|
1937
+ if @tex_infos #Bayo 2
1938
+ used_materials.add(b.header.ex_mat_id)
1939
+ else #Bayo 1
1940
+ used_materials.add(b.header.material_id)
1941
+ end
1942
+ }
1943
+ }
1944
+ materials = @header.num_materials.times.to_a
1945
+ kept_materials = materials & used_materials.to_a
1946
+ correspondance_table = kept_materials.each_with_index.to_h
1947
+ @materials.select!.with_index { |_, i| used_materials.include?(i) }
1948
+ @header.num_materials = used_materials.size
1949
+ if @shader_names
1950
+ @shader_names.select!.with_index { |_, i| used_materials.include?(i) }
1951
+ end
1952
+ @meshes.each { |m|
1953
+ m.batches.each { |b|
1954
+ if @tex_infos
1955
+ b.header.ex_mat_id = correspondance_table[b.header.ex_mat_id]
1956
+ else
1957
+ b.header.material_id = correspondance_table[b.header.material_id]
1958
+ end
1959
+ }
1960
+ }
1961
+ self
1962
+ end
1963
+
1964
+ def cleanup_material_sizes
1965
+ raise "Unsupported for Bayonetta 2!" if @shader_names
1966
+ @materials.each { |m|
1967
+ type = m.type
1968
+ if $material_db.key?(type) && $material_db[type][:size]
1969
+ size = $material_db[type][:size]
1970
+ else
1971
+ warn "Unknown material type #{m.type}!"
1972
+ next
1973
+ end
1974
+ data_number = (size - 4)/4
1975
+ m.material_data = m.material_data.first(data_number)
1976
+ }
1977
+ self
1978
+ end
1979
+
1980
+ def maximize_material_sizes
1981
+ raise "Unsupported for Bayonetta 2!" if @shader_names
1982
+ max_size_mat = $material_db.select { |k, v| v[:size] }.max_by { |k, v|
1983
+ v[:size]
1984
+ }
1985
+ max_data_number = (max_size_mat[1][:size] - 4)/4
1986
+ @materials.each { |m|
1987
+ m.material_data = m.material_data + [0]*(max_data_number - m.material_data.size)
1988
+ }
1989
+ self
1990
+ end
1991
+
1992
+ def cleanup_bone_refs
1993
+ @meshes.each { |m|
1994
+ m.batches.each { |b|
1995
+ b.cleanup_bone_refs(@vertexes)
1996
+ }
1997
+ }
1998
+ self
1999
+ end
2000
+
2001
+ def add_ancestors_bone_refs
2002
+ @meshes.each { |m|
2003
+ m.batches.each { |b|
2004
+ b.add_ancestors_bone_refs(@vertexes, get_bone_structure)
2005
+ }
2006
+ }
2007
+ self
2008
+ end
2009
+
2010
+ def add_previous_bone_refs
2011
+ @meshes.each { |m|
2012
+ m.batches.each { |b|
2013
+ b.add_previous_bone_refs(@vertexes, get_bone_structure)
2014
+ }
2015
+ }
2016
+ self
2017
+ end
2018
+
2019
+ def cleanup_bones
2020
+ used_bones = Set[]
2021
+ @meshes.each { |m|
2022
+ m.batches.each { |b|
2023
+ used_bones.merge b.bone_refs
2024
+ }
2025
+ }
2026
+ bones = get_bone_structure
2027
+ used_bones.to_a.each { |bi|
2028
+ used_bones.merge bones[bi].parents.collect(&:index)
2029
+ }
2030
+ restrict_bones(used_bones)
2031
+ self
2032
+ end
2033
+
2034
+ def dump_bones(list = nil)
2035
+ bone_struct = Struct::new(:index, :parent, :relative_position, :position, :global_index, :symmetric, :flag)
2036
+ table = @bone_index_translate_table.table.invert
2037
+ list = (0...@header.num_bones) unless list
2038
+ list.collect { |bi|
2039
+ bone_struct::new(bi, @bone_hierarchy[bi], @bone_relative_positions[bi], @bone_positions[bi], table[bi], @header.offset_bone_symmetries > 0x0 ? @bone_symmetries[bi] : -1, @header.offset_bone_flags > 0x0 ? @bone_flags[bi] : 5)
2040
+ }
2041
+ end
2042
+
2043
+ def import_bones( list )
2044
+ table = @bone_index_translate_table.table
2045
+ @header.num_bones += list.length
2046
+ list.each { |b|
2047
+ table[b[:global_index]] = b[:index]
2048
+ @bone_hierarchy.push b[:parent]
2049
+ @bone_relative_positions.push b[:relative_position]
2050
+ @bone_positions.push b[:position]
2051
+ @bone_symmetries.push b[:symmetric] if @header.offset_bone_symmetries > 0x0
2052
+ @bone_flags.push b[:flag] if @header.offset_bone_flags > 0x0
2053
+ }
2054
+ @bone_index_translate_table.table = table
2055
+ self
2056
+ end
2057
+
2058
+ def remove_triangle_strips
2059
+ @meshes.each { |m|
2060
+ m.batches.each { |b|
2061
+ b.set_triangles(b.triangles)
2062
+ }
2063
+ }
2064
+ end
2065
+
2066
+ def revert_triangles( mesh_list )
2067
+ mesh_list.each { |mesh_index|
2068
+ @meshes[mesh_index].batches.each { |b|
2069
+ b.set_triangles( b.triangles.collect { |n1, n2, n3|
2070
+ [n1, n3, n2]
2071
+ })
2072
+ }
2073
+ }
2074
+ end
2075
+
2076
+ def cleanup_vertexes
2077
+ used_vertex_indexes = []
2078
+ @meshes.each { |m|
2079
+ m.batches.each { |b|
2080
+ used_vertex_indexes += b.vertex_indices
2081
+ }
2082
+ }
2083
+ used_vertex_indexes = used_vertex_indexes.sort.uniq
2084
+ @vertexes = used_vertex_indexes.collect { |i| @vertexes[i] }
2085
+ @vertexes_ex_data = used_vertex_indexes.collect { |i| @vertexes_ex_data[i] } if @vertexes_ex_data
2086
+ @header.num_vertexes = @vertexes.size
2087
+ vertex_map = used_vertex_indexes.each_with_index.to_h
2088
+ @meshes.each { |m|
2089
+ m.batches.each { |b|
2090
+ b.indices.collect! { |i|
2091
+ vertex_map[i + b.header.vertex_offset]
2092
+ }
2093
+ b.recompute_from_absolute_indices
2094
+ }
2095
+ }
2096
+ self
2097
+ end
2098
+
2099
+ def get_vertex_usage
2100
+ vertex_usage = Hash::new { |h, k| h[k] = [] }
2101
+ @meshes.each { |m|
2102
+ m.batches.each { |b|
2103
+ b.vertex_indices.each { |i|
2104
+ vertex_usage[i].push(b)
2105
+ }
2106
+ }
2107
+ }
2108
+ vertex_usage.each { |k,v| v.uniq! }
2109
+ vertex_usage
2110
+ end
2111
+
2112
+ # Duplicate vertexes used by several batches
2113
+ def normalize_vertex_usage
2114
+ vertex_usage = get_vertex_usage
2115
+ vertex_usage.select! { |k, v| v.length > 1 }
2116
+ batches = Set::new
2117
+ vertex_usage.each { |vi, blist|
2118
+ batches.merge blist[1..-1]
2119
+ }
2120
+ batches.each { |b|
2121
+ new_batch = b.duplicate(@positions, @vertexes, @vertexes_ex_data)
2122
+ b.header = new_batch.header
2123
+ b.indices = new_batch.indices
2124
+ }
2125
+ @header.num_vertexes = @vertexes.size
2126
+ self
2127
+ end
2128
+
2129
+ def remove_duplicate_vertexes
2130
+ vertex_indices = @header.num_vertexes.times
2131
+ equivalent_vertex_indices = vertex_indices.group_by { |indx|
2132
+ data = get_vertex_fields.collect { |f|
2133
+ get_vertex_field(f, indx).to_a
2134
+ }
2135
+ }
2136
+ indx_map = {}
2137
+ equivalent_vertex_indices.each { |k, group|
2138
+ group.each { |indx|
2139
+ indx_map[indx] = group.first
2140
+ }
2141
+ }
2142
+ @meshes.each { |m|
2143
+ m.batches.each { |b|
2144
+ b.indices = b.vertex_indices.collect { |i|
2145
+ indx_map[i]
2146
+ }
2147
+ b.recompute_from_absolute_indices
2148
+ }
2149
+ }
2150
+ end
2151
+
2152
+ def copy_vertex_properties(vertex_hash, **options)
2153
+ vertex_usage = nil
2154
+ vertex_usage = get_vertex_usage if options[:bone_infos]
2155
+ vertex_hash.each { |ivi, ovis|
2156
+ ovis = [ovis].flatten
2157
+ ovis.each { |ovi|
2158
+ iv = @vertexes[ivi]
2159
+ ov = @vertexes[ovi]
2160
+ if options[:position]
2161
+ if @positions
2162
+ @positions[ovi].x = @positions[ivi].x
2163
+ @positions[ovi].y = @positions[ivi].y
2164
+ @positions[ovi].z = @positions[ivi].z
2165
+ end
2166
+ if ov.respond_to?(:position)
2167
+ ov.position.x = iv.position.x
2168
+ ov.position.y = iv.position.y
2169
+ ov.position.z = iv.position.z
2170
+ end
2171
+ if ov.respond_to?(:position2)
2172
+ ov.position2.x = iv.position2.x
2173
+ ov.position2.y = iv.position2.y
2174
+ ov.position2.z = iv.position2.z
2175
+ end
2176
+ if @vertexes_ex_data
2177
+ if @vertexes_ex_data[ovi].respond_to?(:position2)
2178
+ @vertexes_ex_data[ovi].position2.x = @vertexes_ex_data[ivi].position2.x
2179
+ @vertexes_ex_data[ovi].position2.y = @vertexes_ex_data[ivi].position2.y
2180
+ @vertexes_ex_data[ovi].position2.z = @vertexes_ex_data[ivi].position2.z
2181
+ end
2182
+ end
2183
+ end
2184
+ if options[:mapping]
2185
+ if ov.respond_to?(:mapping)
2186
+ ov.mapping.u = iv.mapping.u
2187
+ ov.mapping.v = iv.mapping.v
2188
+ end
2189
+ if ov.respond_to?(:mapping2)
2190
+ ov.mapping2.u = iv.mapping2.u
2191
+ ov.mapping2.v = iv.mapping2.v
2192
+ end
2193
+ if @vertexes_ex_data && @vertexes_ex_data[ovi].respond_to?(:mapping2)
2194
+ @vertexes_ex_data[ovi].mapping2.u = @vertexes_ex_data[ivi].mapping2.u
2195
+ @vertexes_ex_data[ovi].mapping2.v = @vertexes_ex_data[ivi].mapping2.v
2196
+ end
2197
+ end
2198
+ if options[:normal]
2199
+ ov.normal = iv.normal
2200
+ end
2201
+ if options[:tangents]
2202
+ ov.tangents = iv.tangents
2203
+ end
2204
+ if options[:color]
2205
+ if ov.respond_to?(:color)
2206
+ ov.color = iv.color
2207
+ end
2208
+ if @vertexes_ex_data && @vertexes_ex_data[ovi].respond_to?(:color)
2209
+ @vertexes_ex_data[ovi].color = @vertexes_ex_data[ivi].color
2210
+ end
2211
+ end
2212
+ if options[:bone_infos] && ov.respond_to?(:bone_infos)
2213
+ input_batches = vertex_usage[ivi]
2214
+ raise "Unormalized vertex #{ivi} , normalize first, and recompute vertex numbers!" if input_batches.length > 1
2215
+ raise "Unused vertex #{ivi}!" if input_batches.length == 0
2216
+ output_batches = vertex_usage[ovi]
2217
+ raise "Unormalized vertex #{ovi} , normalize first, and recompute vertex numbers!" if output_batches.length > 1
2218
+ raise "Unused vertex #{ovi}!" if output_batches.length == 0
2219
+ input_batch = input_batches.first
2220
+ output_batch = output_batches.first
2221
+ input_bone_indexes_and_weights = iv.bone_infos.get_indexes_and_weights
2222
+ output_bone_indexes_and_weights = input_bone_indexes_and_weights.collect { |bi, bw|
2223
+ [input_batch.bone_refs[bi], bw]
2224
+ }.collect { |bi, bw|
2225
+ new_bi = output_batch.bone_refs.find_index(bi)
2226
+ unless new_bi
2227
+ new_bi = output_batch.bone_refs.length
2228
+ output_batch.bone_refs.push(bi)
2229
+ output_batch.num_bone_ref = output_batch.bone_refs.length
2230
+ end
2231
+ [new_bi, bw]
2232
+ }
2233
+ ov.bone_infos.set_indexes_and_weights( output_bone_indexes_and_weights )
2234
+ end
2235
+ }
2236
+ }
2237
+ self
2238
+ end
2239
+
2240
+ def renumber_batches
2241
+ @meshes.each_with_index { |m, i|
2242
+ m.header.id = i
2243
+ m.batches.each { |b|
2244
+ b.header.mesh_id = i
2245
+ }
2246
+ }
2247
+ self
2248
+ end
2249
+
2250
+ def remove_batch_vertex_offsets
2251
+ @meshes.each { |m|
2252
+ m.batches.each { |b|
2253
+ b.indices = b.vertex_indices
2254
+ b.recompute_from_absolute_indices
2255
+ }
2256
+ }
2257
+ self
2258
+ end
2259
+
2260
+ def fix_ex_data
2261
+ @vertexes.each_with_index { |v, i|
2262
+ if @vertexes_ex_data[i].respond_to?(:color)
2263
+ @vertexes_ex_data[i].color.data = 0xffc0c0c0
2264
+ end
2265
+ if @vertexes_ex_data[i].respond_to?(:mapping2)
2266
+ @vertexes_ex_data[i].mapping2.u = v.mapping.u
2267
+ @vertexes_ex_data[i].mapping2.v = v.mapping.v
2268
+ end
2269
+ }
2270
+ end
2271
+
2272
+ def copy_uv12(mesh_list)
2273
+ raise "No UV2 in model!" unless @vertexes_ex_data[0].respond_to?(:mapping2)
2274
+ mesh_list.each { |i|
2275
+ @meshes[i].batches.each { |b|
2276
+ b.vertex_indices.each { |vi|
2277
+ @vertexes_ex_data[vi].mapping2.u = @vertexes[vi].mapping.u
2278
+ @vertexes_ex_data[vi].mapping2.v = @vertexes[vi].mapping.v
2279
+ }
2280
+ }
2281
+ }
2282
+ end
2283
+
2284
+ def reverse_tangents_byte_order(mesh_list)
2285
+ raise "Vertex don't have tangents information!" unless @vertexes[0].respond_to?(:tangents)
2286
+ vertex_indices = []
2287
+ mesh_list.each { |i|
2288
+ @meshes[i].batches.each { |b|
2289
+ vertex_indices += b.vertex_indices
2290
+ }
2291
+ }
2292
+ vertex_indices.uniq!
2293
+ vertex_indices.each { |i|
2294
+ @vertexes[i].tangents.data = [@vertexes[i].tangents.data].pack("L<").unpack("L>").first
2295
+ }
2296
+ end
2297
+
2298
+ def recompute_batch_tangents(batch)
2299
+ vertex_indices = batch.vertex_indices.uniq
2300
+ tangents = vertex_indices.collect { |i| [i, Linalg::Vector::new(0, 0, 0, 0)] }.to_h
2301
+ indices = batch.triangles.flatten
2302
+ inconsistentuvs = 0
2303
+ # https://stackoverflow.com/a/66918075
2304
+ indices.each_with_index { |i, l|
2305
+ j = indices[(l + 1) % 3 + (l / 3) * 3]
2306
+ k = indices[(l + 2) % 3 + (l / 3) * 3]
2307
+ n = Linalg::Vector::new(*@vertexes[i].normal.to_a, 0.0)
2308
+ pi = Linalg::Vector::new(*@vertexes[i].position.to_a, 0.0)
2309
+ mi = Linalg::Vector::new(*@vertexes[i].mapping.to_a, 0.0, 0.0)
2310
+ pj = Linalg::Vector::new(*@vertexes[j].position.to_a, 0.0)
2311
+ mj = Linalg::Vector::new(*@vertexes[j].mapping.to_a, 0.0, 0.0)
2312
+ pk = Linalg::Vector::new(*@vertexes[k].position.to_a, 0.0)
2313
+ mk = Linalg::Vector::new(*@vertexes[k].mapping.to_a, 0.0, 0.0)
2314
+ v1 = pj - pi
2315
+ v2 = pk - pi
2316
+ t1 = mj - mi
2317
+ t2 = mk - mi
2318
+ uv2xArea = t1.x * t2.y - t1.y * t2.x
2319
+ next if (uv2xArea.abs < 9.5367431640625e-07)
2320
+ flip = uv2xArea > 0.0 ? 1.0 : -1.0
2321
+ inconsistentuvs += 1 if (tangents[i].w != 0 && tangents[i].w != flip)
2322
+ tangents[i].w = flip
2323
+ c = v1.x * n.x + v1.y * n.y + v1.z * n.z
2324
+ v1 -= n * (v1.dot(n));
2325
+ v2 -= n * (v2.dot(n));
2326
+ s = ((v1 * t2.y - v2 * t1.y) * flip).normalize
2327
+ ac = v1.dot(v2) / (v1.length * v2.length)
2328
+ ac = (ac > 1.0 ? 1.0 : (ac < -1.0 ? -1.0 : ac))
2329
+ angle = Math.acos(ac);
2330
+ tangents[i] += s*angle
2331
+ }
2332
+ warn "Found #{inconsistentuvs} inconsistent UVs" if inconsistentuvs > 0
2333
+ tangents.each { |i, t|
2334
+ t.normalize!
2335
+ @vertexes[i].tangents.set(t.x, t.y, t.z, -t.w)
2336
+ }
2337
+ end
2338
+
2339
+ def recompute_tangents(mesh_list)
2340
+ raise "Vertex don't have tangents information!" unless @vertexes[0].respond_to?(:tangents)
2341
+ mesh_list.each { |i|
2342
+ @meshes[i].batches.each { |b|
2343
+ recompute_batch_tangents(b)
2344
+ }
2345
+ }
2346
+ end
2347
+
2348
+ def recompute_layout
2349
+ get_vertex_types
2350
+
2351
+ if is_bayo2?
2352
+ last_offset = 0xc0
2353
+ else
2354
+ last_offset = 0x80
2355
+ end
2356
+
2357
+ @header.num_vertexes = @vertexes.size if @vertexes
2358
+
2359
+ if @header.offset_positions > 0x0
2360
+ last_offset = @header.offset_positions = align(last_offset, 0x20)
2361
+ last_offset += @header.num_vertexes * 12
2362
+ end
2363
+
2364
+ if @header.offset_vertexes > 0x0
2365
+ last_offset = @header.offset_vertexes = align(last_offset, 0x20)
2366
+ last_offset += @header.num_vertexes * @vertex_size
2367
+ end
2368
+ if @header.offset_vertexes_ex_data > 0x0
2369
+ last_offset += 0x20 if is_bayo2?
2370
+ last_offset = @header.offset_vertexes_ex_data = align(last_offset, 0x20)
2371
+ last_offset += @header.num_vertexes * @vertex_ex_size
2372
+ end
2373
+ if @header.offset_bone_relative_position > 0x0
2374
+ last_offset = @header.offset_bone_hierarchy = align(last_offset, 0x20)
2375
+ last_offset += @header.num_bones * 2
2376
+ end
2377
+ if @header.offset_bone_relative_position > 0x0
2378
+ last_offset = @header.offset_bone_relative_position = align(last_offset, 0x20)
2379
+ last_offset += @header.num_bones * 12
2380
+ end
2381
+ if @header.offset_bone_position > 0x0
2382
+ last_offset = @header.offset_bone_position = align(last_offset, 0x20)
2383
+ last_offset += @header.num_bones * 12
2384
+ end
2385
+ if @header.offset_bone_index_translate_table > 0x0
2386
+ last_offset = @header.offset_bone_index_translate_table = align(last_offset, 0x20)
2387
+ last_offset += @bone_index_translate_table.__size
2388
+ end
2389
+ if @header.offset_u_j > 0x0
2390
+ last_offset = @header.offset_u_j = align(last_offset, 0x20)
2391
+ last_offset += @u_j.__size
2392
+ end
2393
+ if @header.offset_bone_symmetries > 0x0
2394
+ last_offset = @header.offset_bone_symmetries = align(last_offset, 0x20)
2395
+ last_offset += @header.num_bones * 2
2396
+ end
2397
+ if @header.offset_bone_flags > 0x0
2398
+ last_offset = @header.offset_bone_flags = align(last_offset, 0x20)
2399
+ last_offset += @header.num_bones
2400
+ end
2401
+ if @header.offset_shader_names > 0x0
2402
+ last_offset = @header.offset_shader_names = align(last_offset, 0x20)
2403
+ last_offset += @header.num_materials * 16
2404
+ end
2405
+ if @header.offset_tex_infos > 0x0
2406
+ last_offset = @header.offset_tex_infos = align(last_offset, 0x20)
2407
+ last_offset += 4 + @tex_infos.num_tex_infos * 8
2408
+ end
2409
+
2410
+ last_offset = @header.offset_materials_offsets = align(last_offset, 0x20)
2411
+ off = 0
2412
+ @materials_offsets = []
2413
+ @header.num_materials.times { |i|
2414
+ @materials_offsets.push off
2415
+ off += @materials[i].__size
2416
+ off = align(off, 0x4)
2417
+ }
2418
+
2419
+ last_offset += 4*@header.num_materials
2420
+ last_offset = @header.offset_materials = align(last_offset, 0x20)
2421
+
2422
+ last_offset += @materials.collect(&:__size).reduce(&:+)
2423
+ # Pad last material with zeros
2424
+ pad_count = (align(last_offset, 0x20) - last_offset)/4
2425
+ @materials.last.material_data += [0]*pad_count
2426
+ last_offset = @header.offset_meshes_offsets = align(last_offset, 0x20)
2427
+
2428
+ off = 0
2429
+ @meshes_offsets = []
2430
+ @header.num_meshes.times { |i|
2431
+ @meshes[i].recompute_layout
2432
+ @meshes_offsets.push off
2433
+ off += @meshes[i].__size
2434
+ off = align(off, 0x20)
2435
+ }
2436
+
2437
+ last_offset += 4*@header.num_meshes
2438
+ last_offset = @header.offset_meshes = align(last_offset, 0x20)
2439
+ end
2440
+
2441
+ end
2442
+
2443
+ end