pgtools 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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