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,735 @@
1
+ require 'assimp-ffi'
2
+ require 'rbconfig'
3
+ require 'optparse'
4
+ require 'pathname'
5
+ require 'set'
6
+ require_relative '../../bayonetta.rb'
7
+ require 'yaml'
8
+ require 'shellwords'
9
+ include Bayonetta
10
+
11
+ $is_win = (RbConfig::CONFIG['host_os'] =~ /mswin/)
12
+
13
+ $options = {}
14
+
15
+ OptionParser.new do |opts|
16
+ opts.banner = "Usage: wmb_import_assim.rb target_file source_file [options]"
17
+
18
+ opts.on("-bFILE", "--bone-map=FILE", "Bone map") do |bone_map|
19
+ $options[:bone_map] = bone_map
20
+ end
21
+
22
+ opts.on("-u", "--update-bones", "Update recognized bone positions") do |update_bones|
23
+ $options[:update_bones] = update_bones
24
+ end
25
+
26
+ # opts.on("-t", "--[no-]import-textures", "Import textures also") do |import_textures|
27
+ # $options[:import_textures] = import_textures
28
+ # end
29
+
30
+ opts.on("-t", "--[no-]transform-meshes", "Apply node transformation to meshes, conflicts with --group-batch-by-name") do |transform|
31
+ $options[:transform] = transform
32
+ $options[:group] = false
33
+ end
34
+
35
+ opts.on("--swap-mesh-y-z", "Use to swap the mesh if it is not aligned to the skeleton") do |swap|
36
+ $options[:swap] = swap
37
+ end
38
+
39
+ opts.on("--[no-]sort-bones", "Sorts the bone alphanumerically, WARNING: may cause issue if the order doesn't respect the hierarchy!") do |sort|
40
+ $options[:sort] = sort
41
+ end
42
+
43
+ opts.on("-o", "--[no-]overwrite", "Overwrite destination files") do |overwrite|
44
+ $options[:overwrite] = overwrite
45
+ end
46
+
47
+ opts.on("-g", "--[no-]group-batch-by-name", "Try grouping batches using their names, conflicts with --transform-meshes") do |group|
48
+ $options[:group] = group
49
+ $options[:transform] = false
50
+ end
51
+
52
+ opts.on("-f", "--filter-bones=REJECT_LIST", "Don't import all bones") do |filter_bones|
53
+ $options[:filter_bones] = eval(filter_bones)
54
+ end
55
+
56
+ opts.on("-a", "--[no-]auto-map", "Auto map bones") do |auto_map|
57
+ $options[:auto_map] = auto_map
58
+ end
59
+
60
+ opts.on("-s", "--[no-]list-skeleton", "Display a the skeleton") do |skeleton|
61
+ $options[:skeleton] = skeleton
62
+ end
63
+
64
+ opts.on("-l", "--[no-]list", "List source content") do |list|
65
+ $options[:list] = list
66
+ end
67
+
68
+ opts.on("-v", "--[no-]verbose", "Enable logging") do |verbose|
69
+ $options[:verbose] = verbose
70
+ end
71
+
72
+ opts.on("-r", "--[no-]use-root-bone", "Use the skeleton root as a bone") do |root|
73
+ $options[:root] = root
74
+ end
75
+
76
+ opts.on("--[no-]print-transform", "Print transform matrix when listing") do |print|
77
+ $options[:print_transform] = print
78
+ end
79
+
80
+ opts.on("--bone-prefix=STRING", "Change the bone prefix") do |prefix|
81
+ $options[:bone_prefix] = prefix
82
+ end
83
+
84
+ opts.on("-h", "--help", "Prints this help") do
85
+ puts opts
86
+ exit
87
+ end
88
+
89
+ end.parse!
90
+
91
+ $mesh_prefix = /mesh_(\d\d)_/
92
+ $batch_prefix = /batch_(\d\d)_/
93
+ if $options[:bone_prefix]
94
+ $bone_prefix = /#{$options[:bone_prefix]}(\d\d\d)/
95
+ else
96
+ $bone_prefix = /bone_(\d\d\d)/
97
+ end
98
+ $skeleton_prefix = /skeleton/
99
+
100
+ target = ARGV[0]
101
+ source = ARGV[1]
102
+
103
+ if $options[:list]
104
+
105
+ source = target unless source
106
+
107
+ property_store = Assimp::PropertyStore::new
108
+ property_store.import_fbx_preserve_pivots = Assimp::FALSE
109
+ scene = Assimp::import_file(source, flags: [:JoinIdenticalVertices, :CalcTangentSpace], props: property_store)
110
+
111
+ puts "Found #{scene.num_meshes} meshes."
112
+ puts "Found #{scene.num_textures} embedded textures."
113
+ puts "Found #{scene.num_materials} materials."
114
+
115
+ scene.root_node.each_node_with_depth { |n, d|
116
+ puts " "*d + n.name + (n.num_meshes > 0 ? " (#{n.num_meshes} mesh#{n.num_meshes > 1 ? "es" : ""})" : "")
117
+ puts n.transformation if $options[:print_transform]
118
+ }
119
+ puts "-----------------------------------------------"
120
+ scene.meshes.each { |m|
121
+ puts m.name
122
+ puts "num_vertices: #{m.num_vertices}"
123
+ puts "num_faces: #{m.num_faces}"
124
+ puts "num_bones: #{m.num_bones}"
125
+ m.bones.each { |b|
126
+ puts " "+b.name
127
+ }
128
+ puts "normals: #{!m[:normals].null?}"
129
+ puts "tangents: #{!m[:tangents].null?}"
130
+ puts "bitangents: #{!m[:bitangents].null?}"
131
+ m.colors.each_with_index { |c, i|
132
+ puts "color #{i}" if c
133
+ }
134
+ m.texture_coords.each_with_index { |t, i|
135
+ puts "uv #{i} (#{m.num_uv_components[i]})" if t
136
+ }
137
+ }
138
+ exit
139
+ end
140
+
141
+ def get_used_bone_set(scene)
142
+ known_bones = Set::new
143
+ scene.meshes.each { |m|
144
+ m.bones.each { |b|
145
+ known_bones.add(b.name)
146
+ }
147
+ }
148
+ known_bones
149
+ end
150
+
151
+ def find_skeleton(scene)
152
+
153
+ known_bones = get_used_bone_set(scene)
154
+
155
+ raise "Model uses no bones!" if known_bones.size == 0
156
+
157
+ skeleton = nil
158
+ potential_roots = nil
159
+
160
+ Assimp::Node.define_method(:eql?) do |other|
161
+ self.class == other.class &&
162
+ self.pointer == other.pointer
163
+ end
164
+
165
+ Assimp::Node.define_method(:hash) do
166
+ self.pointer.address.hash
167
+ end
168
+
169
+ scene.root_node.each_node { |n|
170
+ if known_bones.include?(n.name)
171
+ potential_roots = n.ancestors unless potential_roots
172
+ potential_roots &= n.ancestors
173
+ end
174
+ }
175
+
176
+ Assimp::Node.remove_method :eql?
177
+ Assimp::Node.remove_method :hash
178
+
179
+ skeleton = potential_roots.find { |n| n.name.match($skeleton_prefix) }
180
+
181
+ if !skeleton
182
+ potential_roots.reverse.each { |n|
183
+ if n.children.find { |c| c.name.match($bone_prefix) }
184
+ skeleton = n
185
+ break
186
+ end
187
+ }
188
+ end
189
+
190
+ if !skeleton
191
+ skeleton = potential_roots.first
192
+ end
193
+
194
+ skeleton
195
+
196
+ end
197
+
198
+ if $options[:skeleton]
199
+
200
+ source = target unless source
201
+
202
+ property_store = Assimp::PropertyStore::new
203
+ property_store.import_fbx_preserve_pivots = Assimp::FALSE
204
+ scene = Assimp::import_file(source, flags: [:JoinIdenticalVertices, :CalcTangentSpace], props: property_store)
205
+
206
+ skeleton = find_skeleton(scene)
207
+ skeleton.each_node_with_depth { |n, d|
208
+ puts " "*d + n.name
209
+ }
210
+ exit
211
+ end
212
+
213
+ def scene_bones(scene)
214
+ skeleton = find_skeleton(scene)
215
+ bones = []
216
+ skeleton.children.each { |c|
217
+ bones += c.each_node_with_depth.collect.to_a
218
+ }
219
+ #this doesn't always work
220
+ bones.sort! { |(n1, d1), (n2, d2)|
221
+ n1.name <=> n2.name
222
+ } if $options[:sort]
223
+ bones.collect! { |n, d| n }
224
+ bones = [skeleton] + bones if $options[:root]
225
+ bones
226
+ end
227
+
228
+ def find_bone_mapping(wmb, scene)
229
+ tt1 = wmb.bone_index_translate_table.table
230
+
231
+ common_mapping = {}
232
+
233
+ global_bone_names = tt1.keys
234
+ scene_bones = scene_bones(scene)
235
+
236
+ if $options[:auto_map]
237
+ scene_bones.each { |n|
238
+ data = n.name.match($bone_prefix)
239
+ if data
240
+ bone_number = data[1].to_i
241
+ if global_bone_names.include?(bone_number)
242
+ common_mapping[n.name] = bone_number
243
+ end
244
+ end
245
+ }
246
+ elsif $options[:bone_map]
247
+ common_mapping.merge! YAML::load_file( $options[:bone_map] )
248
+ else
249
+ common_mapping = {}
250
+ end
251
+
252
+ mapping = {}
253
+ scene_bones.each { |n|
254
+ mapping[n.name] = tt1[common_mapping[n.name]]
255
+ }
256
+ node_mapping = scene_bones.collect { |n| [n.name, n] }.to_h
257
+ [mapping, common_mapping, node_mapping]
258
+ end
259
+
260
+ def get_new_bones(wmb, mapping, node_mapping)
261
+ bones_wmb = wmb.get_bone_structure
262
+ if $options[:update_bones]
263
+ common_bones = mapping.reject { |k,v| v.nil? }
264
+ common_bones.each { |k, v|
265
+ p = node_mapping[k].world_transformation * Assimp::Vector3D::new
266
+ pos = bones_wmb[v].position
267
+ pos.x, pos.y, pos.z = p.x, p.y, p.z
268
+ }
269
+ end
270
+
271
+ mapping[-1] = -1
272
+ missing_bones = mapping.select { |k,v| v.nil? }.collect { |k,v| k }
273
+
274
+ if $options[:filter_bones]
275
+ missing_bones -= $options[:filter_bones]
276
+ end
277
+
278
+ new_bone_index = bones_wmb.size
279
+ new_bone_indexes = []
280
+
281
+ missing_bones.each { |bi|
282
+ mapping[bi] = new_bone_index
283
+ new_bone_indexes.push(new_bone_index)
284
+ p = node_mapping[bi].world_transformation * Assimp::Vector3D::new
285
+ pos = Position::new
286
+ pos.x, pos.y, pos.z = p.x, p.y, p.z
287
+ b = Bone::new(pos)
288
+ b.index = new_bone_index
289
+ parent_name = nil
290
+ if node_mapping[bi].parent
291
+ parent_name = node_mapping[bi].parent.name
292
+ parent_name = nil unless node_mapping.include?(parent_name)
293
+ end
294
+ b.parent = bones_wmb[mapping[parent_name]] if parent_name
295
+ b.symmetric = -1
296
+ b.flag = 5
297
+ bones_wmb.push b
298
+ new_bone_index += 1
299
+ }
300
+ [bones_wmb, missing_bones, new_bone_indexes]
301
+ end
302
+
303
+ def update_translate_table(wmb, common_mapping, missing_bones, new_bone_indexes)
304
+ missing_bones_count = new_bone_indexes.length
305
+ raise "Too many bones to add: #{missing_bones.inspect}!" if missing_bones_count > 0x100
306
+ (align(missing_bones_count, 0x10) - missing_bones_count).times {
307
+ new_bone_indexes.push(0xfff)
308
+ }
309
+
310
+ tt = wmb.bone_index_translate_table.table
311
+ used_indexes = tt.keys
312
+ start_index = nil
313
+
314
+ (0x250..(0x1000-new_bone_indexes.size)).step(0x10) { |s_index|
315
+ if (used_indexes & (s_index..(s_index+new_bone_indexes.size)).to_a) == []
316
+ start_index = s_index
317
+ break
318
+ end
319
+ }
320
+ raise "No room available in translate table!" unless start_index
321
+ new_tt = wmb.bone_index_translate_table.table.dup
322
+ new_bone_indexes.each_with_index { |ind, i|
323
+ new_tt[i+start_index] = ind
324
+ common_mapping[missing_bones[i]] = i + start_index if i < missing_bones.length && missing_bones[i]
325
+ }
326
+ wmb.bone_index_translate_table.table = new_tt
327
+ if wmb.bone_symmetries
328
+ (-missing_bones_count..-1).each { |i|
329
+ symmetric = common_mapping[wmb.bone_symmetries[i]]
330
+ symmetric = -1 unless symmetric
331
+ wmb.bone_symmetries[i] = symmetric
332
+ }
333
+ end
334
+ end
335
+
336
+ def merge_bones(wmb, scene)
337
+ mapping, common_mapping, node_mapping = find_bone_mapping(wmb, scene)
338
+
339
+ bones_wmb, missing_bones, new_bone_indexes = get_new_bones(wmb, mapping, node_mapping)
340
+
341
+ wmb.set_bone_structure(bones_wmb)
342
+
343
+ update_translate_table(wmb, common_mapping, missing_bones, new_bone_indexes)
344
+
345
+ [common_mapping, mapping]
346
+ end
347
+
348
+ def get_mesh_mapping(scene)
349
+ if $options[:group]
350
+ mesh_mapping = Hash::new { |h, k| h[k] = [] }
351
+ scene.meshes.sort { |m1, m2| m1.name <=> m2.name }.each { |m|
352
+ data = m.name.match($batch_prefix)
353
+ if data
354
+ mesh_name = m.name.gsub(data[0], "")
355
+ else
356
+ mesh_name = m.name
357
+ end
358
+ data = mesh_name.match(/_(\d\d)/)
359
+ if data
360
+ mesh_name = mesh_name.gsub(data[0], "")
361
+ end
362
+ mesh_mapping[mesh_name].push(m)
363
+ }
364
+ else
365
+ mesh_nodes = scene.root_node.each_node.select{ |n| n.children.find { |c| c.num_meshes > 0 } }.to_a
366
+ mesh_mapping = mesh_nodes.collect { |n|
367
+ batches = []
368
+ n.children.each { |c|
369
+ batches += c.meshes
370
+ }
371
+ [n, batches.collect{ |num| scene.meshes[num] }]
372
+ }.sort { |(n1, _), (n2, _)| n1.name <=> n2.name }.to_h
373
+ end
374
+ mesh_mapping
375
+ end
376
+
377
+ def create_new_meshes(wmb, mesh_mapping)
378
+ new_meshes = mesh_mapping.each_with_index.collect { |(m, _), i|
379
+ new_mesh = WMBFile::Mesh::new
380
+ if $options[:group]
381
+ mesh_name = m
382
+ else
383
+ mesh_name = m.name
384
+ end
385
+ data = mesh_name.match($mesh_prefix)
386
+ if data
387
+ name = mesh_name.gsub(data[0], "")
388
+ mesh_name = name if name != ""
389
+ end
390
+ new_mesh.header.name = mesh_name
391
+ new_mesh.header.id = i + wmb.header.num_meshes
392
+ new_mesh
393
+ }
394
+ end
395
+
396
+ def set_fields(wmb, bone_mapping, batch, new_indices, transform_matrix)
397
+ recompute_tangents = false
398
+ bone_refs = {}
399
+ bone_refs = batch.bones.sort { |b1, b2|
400
+ b1.name <=> b2.name
401
+ }.collect(&:name).uniq.each_with_index.collect { |b, i|
402
+ [b, i]
403
+ }.to_h
404
+ fields = wmb.get_vertex_fields
405
+
406
+ _, rotation, _ = transform_matrix.decompose
407
+
408
+ fields.each do |field|
409
+ case field
410
+ when :position
411
+ vertices = batch.vertices
412
+ new_indices.each_with_index { |target_index, index|
413
+ p = Position::new
414
+ o_p = vertices[index]
415
+ o_p = transform_matrix * o_p
416
+ p.x = o_p.x
417
+ p.y = o_p.y
418
+ p.z = o_p.z
419
+ wmb.set_vertex_field(field, target_index, p)
420
+ }
421
+ when :normal
422
+ normals = batch.normals
423
+ new_indices.each_with_index { |target_index, index|
424
+ n = Normal::new
425
+ o_n = normals[index]
426
+ o_n = rotation * o_n
427
+ n.x = o_n.x
428
+ n.y = o_n.y
429
+ n.z = o_n.z
430
+ wmb.set_vertex_field(field, target_index, n)
431
+ }
432
+ when :tangents
433
+ tangents = batch.tangents
434
+ bitangents = batch.bitangents
435
+ normals = batch.normals
436
+ new_indices.each_with_index { |target_index, index|
437
+ t = Tangents::new
438
+ o_t = tangents[index]
439
+ if o_t
440
+ o_t = rotation * o_t
441
+ o_n = normals[index]
442
+ o_n = rotation * o_n
443
+ o_b = bitangents[index]
444
+ o_b = rotation * o_b
445
+ n_o_b = (o_n ^ o_t)
446
+ if (n_o_b + o_b).length > 1
447
+ s = -1.0
448
+ else
449
+ s = 1.0
450
+ end
451
+ if o_t.x.nan? || o_t.y.nan? || o_t.z.nan?
452
+ t.set(0, 0, 0, 1)
453
+ else
454
+ t.set(o_t.x, o_t.y, o_t.z, s)
455
+ end
456
+ else
457
+ warn "Invalid mapping for batch: #{batch.name}, tangents will be recomputed!" unless recompute_tangents
458
+ recompute_tangents = true
459
+ t.set(0, 0, 0, 1)
460
+ end
461
+ wmb.set_vertex_field(field, target_index, t)
462
+ }
463
+ when :mapping, :mapping2, :mapping3, :mapping4, :mapping5
464
+ mapping_index = 0
465
+ mapping_index = 1 if field == :mapping2
466
+ mapping_index = 2 if field == :mapping3
467
+ mapping_index = 3 if field == :mapping4
468
+ mapping_index = 4 if field == :mapping5
469
+ mapping_index = 0 if batch.num_uv_components[mapping_index] < 2
470
+ texture_coords = batch.texture_coords[mapping_index]
471
+ raise "No texture coordinate found!" unless texture_coords
472
+ new_indices.each_with_index { |target_index, index|
473
+ m = Mapping::new
474
+ o_m = texture_coords[index]
475
+ m.u = o_m.x
476
+ m.v = o_m.y
477
+ wmb.set_vertex_field(field, target_index, m)
478
+ }
479
+ when :color, :color2
480
+ color_index = 0
481
+ color_index = 1 if field == :color2
482
+ colors = batch.colors[color_index]
483
+ if colors
484
+ new_indices.each_with_index { |target_index, index|
485
+ c = Color::new
486
+ o_c = colors[index]
487
+ c.r = (o_c.r * 255.0).round.clamp(0, 255)
488
+ c.g = (o_c.g * 255.0).round.clamp(0, 255)
489
+ c.b = (o_c.b * 255.0).round.clamp(0, 255)
490
+ c.a = (o_c.a < 0 ? 255 : (c.a * 255.0).round.clamp(0, 255))
491
+ wmb.set_vertex_field(field, target_index, c)
492
+ }
493
+ else
494
+ c = Color::new
495
+ c.r = 0xc0
496
+ c.g = 0xc0
497
+ c.b = 0xc0
498
+ c.a = 0xff
499
+ new_indices.each_with_index { |target_index, _|
500
+ wmb.set_vertex_field(field, target_index, c)
501
+ }
502
+ end
503
+ when :bone_infos
504
+ bone_infos = new_indices.size.times.collect {
505
+ []
506
+ }
507
+ batch.bones.each { |bone|
508
+ bone_index = bone_refs[bone.name]
509
+ raise "Missing bone: #{bone.name}!" unless bone_index
510
+ bone.weights.each { |vtxweight|
511
+ vertex_id = vtxweight.vertex_id
512
+ weight = vtxweight.weight
513
+ bone_infos[vertex_id].push [bone_index, weight]
514
+ }
515
+ }
516
+ bone_infos = bone_infos.collect { |bone_info|
517
+ b_i = bone_info.sort { |(_, w1), (_, w2)| w1 <=> w2 }.reverse.first(4).reject { |_, w| w <= 0.0 }
518
+ if b_i.length == 0
519
+ warn "Invalid rigging for batch: #{batch.name}, orphan vertex!"
520
+ else
521
+ sum = b_i.reduce(0.0) { |memo, (_, w)| memo + w }
522
+ b_i.collect! { |ind, w| [ind, (w*255.0/sum).round.clamp(0, 255)] }
523
+ sum = b_i.reduce(0) { |memo, (_, w)| memo + w }
524
+ if sum != 255
525
+ diff = 255 - sum
526
+ b_i.first[1] += diff
527
+ end
528
+ end
529
+ b_i
530
+ }
531
+
532
+ new_indices.each_with_index { |target_index, index|
533
+ bi = BoneInfos::new
534
+ bi.set_indexes_and_weights(bone_infos[index])
535
+ wmb.set_vertex_field(field, target_index, bi)
536
+ }
537
+ else
538
+ raise "Unknow field in wmb file #{field.inspect}!"
539
+ end
540
+ end
541
+ [bone_refs, recompute_tangents]
542
+ end
543
+
544
+ def merge_geometry(wmb, scene, bone_mapping)
545
+ mesh_mapping = get_mesh_mapping(scene)
546
+
547
+ new_meshes = create_new_meshes(wmb, mesh_mapping)
548
+
549
+ vertex_types = wmb.get_vertex_types
550
+
551
+ mesh_mapping.each_with_index { |(n, batches), i|
552
+ if $options[:transform]
553
+ matrix = n.world_transformation
554
+ else
555
+ matrix = Assimp::Matrix4x4::identity
556
+ end
557
+ if $options[:swap]
558
+ rot = Assimp::Matrix4x4::new
559
+ rot.a1 = 1.0
560
+ rot.b3 = -1.0
561
+ rot.c2 = 1.0
562
+ rot.d4 = 1.0
563
+ matrix = rot * matrix
564
+ end
565
+
566
+ batches.each_with_index { |batch, j|
567
+ first_vertex_index = wmb.vertexes.length
568
+
569
+ num_vertices = batch.num_vertices
570
+ indices = (0...num_vertices)
571
+ new_indices = (first_vertex_index...(first_vertex_index + num_vertices)).to_a
572
+
573
+ wmb.vertexes += num_vertices.times.collect {
574
+ vertex_types[0]::new
575
+ }
576
+ if wmb.vertexes_ex_data
577
+ wmb.vertexes_ex_data += num_vertices.times.collect {
578
+ vertex_types[1]::new
579
+ }
580
+ end
581
+
582
+ wmb.header.num_vertexes += num_vertices
583
+
584
+ bone_refs, recompute_tangents = set_fields(wmb, bone_mapping, batch, new_indices, matrix)
585
+
586
+ b = WMBFile::Batch::new
587
+ b.header.material_id = wmb.header.num_materials + batch.material_index
588
+ b.header.mesh_id = new_meshes[i].header.id
589
+ indice_array = []
590
+ batch.faces.each { |f|
591
+ indice_array += f.indices.collect { |ind| new_indices[ind] }
592
+ }
593
+ b.header.num_indices = indice_array.length
594
+ b.indices = indice_array
595
+ b.recompute_from_absolute_indices
596
+ b.bone_refs = bone_refs.collect { |name, _| bone_mapping[name] }
597
+ b.num_bone_ref = b.bone_refs.length
598
+ new_meshes[i].batches.push b
599
+ new_meshes[i].header.num_batch += 1
600
+ if recompute_tangents
601
+ wmb.recompute_batch_tangents(b)
602
+ end
603
+ }
604
+ }
605
+ wmb.meshes += new_meshes
606
+ wmb.header.num_meshes += new_meshes.length
607
+
608
+ end
609
+
610
+ def get_new_tex_list(scene)
611
+ texture_set = Set::new
612
+ scene.materials.each { |m|
613
+ m.properties.each { |p|
614
+ if p.key == Assimp::MATKEY_TEXTURE
615
+ texture_set.add p.data
616
+ end
617
+ }
618
+ }
619
+ texture_set.to_a.sort
620
+ end
621
+
622
+ def merge_materials(wmb, scene, tex)
623
+ old_tex_count = tex.each.count
624
+ new_tex_list = get_new_tex_list(scene)
625
+ new_tex_map = new_tex_list.each_with_index.collect { |t, i| [t, i+old_tex_count] }.to_h
626
+
627
+ mat_offset = wmb.materials_offsets.last + wmb.materials.last.__size
628
+ new_materials = []
629
+ new_materials_offsets = []
630
+ scene.materials.each_with_index { |mat, i|
631
+ new_materials_offsets.push(mat_offset + i*0x124)
632
+ m = WMBFile::Material::new
633
+ m.type = 0x0
634
+ m.flag = 0x0
635
+ m.material_data = [0x0]*(0x120/4)
636
+ mat.properties.select { |p|
637
+ p.key == Assimp::MATKEY_TEXTURE
638
+ }.collect { |p|
639
+ new_tex_map[p.data]
640
+ }.each_with_index { |tex, j|
641
+ m.material_data[j] = tex
642
+ }
643
+ m.material_data[0] = (m.material_data[0] ? m.material_data[0] : 0x80000000)
644
+ m.material_data[1] = (m.material_data[1] ? m.material_data[1] : 0x80000000)
645
+ new_materials.push(m)
646
+ }
647
+ wmb.header.num_materials += new_materials.size
648
+ wmb.materials += new_materials
649
+ wmb.materials_offsets += new_materials_offsets
650
+
651
+ new_tex_list
652
+ end
653
+
654
+ def convert_windows_path(path)
655
+ # we are on linux but were given a windows path, hypothesis: linux on windows
656
+ if path.include?("\\")
657
+ res = ""
658
+ copy = path.dup
659
+ if path.start_with?("\\\\")
660
+ res << "//"
661
+ copy = copy[2..-1]
662
+ elsif path.start_with?("\\")
663
+ res << "/"
664
+ copy = copy[2..-1]
665
+ elsif m = path.match(/([A-Za-z]):\\/) #linux on windows
666
+ res << "/mnt/#{m[1].downcase}/"
667
+ copy = copy[3..-1]
668
+ end
669
+ res << copy.gsub("\\", "/")
670
+ else
671
+ res = path
672
+ end
673
+ res
674
+ end
675
+
676
+ def add_textures(tex, path, new_tex_list)
677
+ new_tex_list.each { |tex_path|
678
+ extension = File.extname(tex_path)
679
+ tex_path = convert_windows_path(tex_path) unless $is_win
680
+ old_tex_path = Pathname.new(tex_path).absolute? ? tex_path : File.join(path, tex_path)
681
+ if extension.downcase != ".dds"
682
+ tex_path = Pathname.new(tex_path).absolute? ? File.join(File.dirname(tex_path), File.basename(tex_path,extension)) + ".dds" :
683
+ File.join(path, File.join(File.dirname(tex_path), File.basename(tex_path,extension))) + ".dds"
684
+ `convert -define dds:compression=dxt5 #{Shellwords.escape old_tex_path} #{Shellwords.escape tex_path}`
685
+ else
686
+ tex_path = old_tex_path
687
+ end
688
+ tex.push File::new(tex_path, "rb")
689
+ }
690
+ end
691
+
692
+ raise "Invalid file: #{target}" unless target && File::file?(target)
693
+ raise "Invalid file: #{source}" unless source && File::file?(source)
694
+
695
+ Dir.mkdir("wmb_output") unless Dir.exist?("wmb_output")
696
+ Dir.mkdir("wtb_output") unless Dir.exist?("wtb_output")
697
+
698
+ wmb = WMBFile::load(target)
699
+
700
+ tex_file_name = target.gsub(/wmb\z/,"wtb")
701
+ tex = WTBFile::new(File::new(tex_file_name, "rb"))
702
+
703
+ if $options[:verbose]
704
+ log = Assimp::LogStream::stderr
705
+ log.attach
706
+ Assimp::LogStream::verbose(1)
707
+ end
708
+
709
+ property_store = Assimp::PropertyStore::new
710
+ property_store.import_fbx_preserve_pivots = Assimp::FALSE
711
+ scene = Assimp::import_file(source, flags: [:JoinIdenticalVertices, :FlipWindingOrder, :Triangulate, :FlipUVs], props: property_store)
712
+
713
+
714
+ common_mapping, bone_mapping = merge_bones(wmb, scene)
715
+
716
+ merge_geometry(wmb, scene, bone_mapping)
717
+
718
+ new_tex_list = merge_materials(wmb, scene, tex)
719
+
720
+ add_textures(tex, File.dirname(source), new_tex_list)
721
+
722
+ wmb.recompute_relative_positions
723
+ wmb.recompute_layout
724
+
725
+ File::open(File.join("wmb_output", "#{File::basename(source,File::extname(source))}_#{File::basename(target,".wmb")}_bone_map.yaml"), "w") { |f|
726
+ f.write YAML::dump(common_mapping)
727
+ }
728
+
729
+ if $options[:overwrite]
730
+ wmb.dump(target)
731
+ tex.dump(tex_file_name)
732
+ else
733
+ wmb.dump(File.join("wmb_output", File.basename(target)))
734
+ tex.dump(File.join("wtb_output", File.basename(tex_file_name)))
735
+ end