pgtools 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +25 -0
  3. data/bin/bxm_decoder +2 -0
  4. data/bin/bxm_encoder +2 -0
  5. data/bin/clh_convert +2 -0
  6. data/bin/clp_convert +2 -0
  7. data/bin/clw_convert +2 -0
  8. data/bin/dat_creator +2 -0
  9. data/bin/dat_extractor +2 -0
  10. data/bin/dat_ls +2 -0
  11. data/bin/eff_idd_creator +2 -0
  12. data/bin/eff_idd_extractor +2 -0
  13. data/bin/exp_convert_wiiu_pc +2 -0
  14. data/bin/exp_tool +2 -0
  15. data/bin/mot_convert_wiiu_pc +2 -0
  16. data/bin/mot_tool +2 -0
  17. data/bin/pkz_extractor +2 -0
  18. data/bin/scr_creator +2 -0
  19. data/bin/scr_extractor +2 -0
  20. data/bin/wmb_cleanup +2 -0
  21. data/bin/wmb_common_bones +2 -0
  22. data/bin/wmb_convert_pc_switch +2 -0
  23. data/bin/wmb_convert_wiiu_pc +2 -0
  24. data/bin/wmb_export_assimp +2 -0
  25. data/bin/wmb_get_bone_map +2 -0
  26. data/bin/wmb_import_assimp +2 -0
  27. data/bin/wmb_import_nier +2 -0
  28. data/bin/wmb_import_wiiu +2 -0
  29. data/bin/wtb_convert_wiiu_pc +2 -0
  30. data/bin/wtx_creator +2 -0
  31. data/bin/wtx_extractor +2 -0
  32. data/lib/bayonetta/alignment.rb +14 -0
  33. data/lib/bayonetta/bone.rb +55 -0
  34. data/lib/bayonetta/bxm.rb +180 -0
  35. data/lib/bayonetta/clh.rb +159 -0
  36. data/lib/bayonetta/clp.rb +212 -0
  37. data/lib/bayonetta/clw.rb +166 -0
  38. data/lib/bayonetta/dat.rb +261 -0
  39. data/lib/bayonetta/eff.rb +314 -0
  40. data/lib/bayonetta/endianness.rb +53 -0
  41. data/lib/bayonetta/exp.rb +768 -0
  42. data/lib/bayonetta/linalg.rb +416 -0
  43. data/lib/bayonetta/material_database.yaml +2581 -0
  44. data/lib/bayonetta/mot.rb +763 -0
  45. data/lib/bayonetta/pkz.rb +63 -0
  46. data/lib/bayonetta/scr.rb +393 -0
  47. data/lib/bayonetta/tools/bxm_decoder.rb +23 -0
  48. data/lib/bayonetta/tools/bxm_encoder.rb +37 -0
  49. data/lib/bayonetta/tools/clh_convert.rb +60 -0
  50. data/lib/bayonetta/tools/clp_convert.rb +70 -0
  51. data/lib/bayonetta/tools/clw_convert.rb +60 -0
  52. data/lib/bayonetta/tools/dat_creator.rb +57 -0
  53. data/lib/bayonetta/tools/dat_extractor.rb +94 -0
  54. data/lib/bayonetta/tools/dat_ls.rb +106 -0
  55. data/lib/bayonetta/tools/eff_idd_creator.rb +66 -0
  56. data/lib/bayonetta/tools/eff_idd_extractor.rb +73 -0
  57. data/lib/bayonetta/tools/exp_convert_wiiu_pc.rb +33 -0
  58. data/lib/bayonetta/tools/exp_tool.rb +48 -0
  59. data/lib/bayonetta/tools/mot_convert_wiiu_pc.rb +33 -0
  60. data/lib/bayonetta/tools/mot_tool.rb +60 -0
  61. data/lib/bayonetta/tools/pkz_extractor.rb +75 -0
  62. data/lib/bayonetta/tools/scr_creator.rb +63 -0
  63. data/lib/bayonetta/tools/scr_extractor.rb +78 -0
  64. data/lib/bayonetta/tools/wmb_cleanup.rb +250 -0
  65. data/lib/bayonetta/tools/wmb_common_bones.rb +45 -0
  66. data/lib/bayonetta/tools/wmb_convert_pc_switch.rb +35 -0
  67. data/lib/bayonetta/tools/wmb_convert_wiiu_pc.rb +33 -0
  68. data/lib/bayonetta/tools/wmb_export_assimp.rb +479 -0
  69. data/lib/bayonetta/tools/wmb_get_bone_map.rb +50 -0
  70. data/lib/bayonetta/tools/wmb_import_assimp.rb +735 -0
  71. data/lib/bayonetta/tools/wmb_import_geometry_wiiu_pc.rb +472 -0
  72. data/lib/bayonetta/tools/wmb_import_nier.rb +309 -0
  73. data/lib/bayonetta/tools/wtb_convert_wiiu_pc.rb +95 -0
  74. data/lib/bayonetta/tools/wtb_import_textures.rb +103 -0
  75. data/lib/bayonetta/tools/wtx_creator.rb +69 -0
  76. data/lib/bayonetta/tools/wtx_extractor.rb +85 -0
  77. data/lib/bayonetta/vertex_types.yaml +213 -0
  78. data/lib/bayonetta/vertex_types2.yaml +164 -0
  79. data/lib/bayonetta/vertex_types_nier.yaml +145 -0
  80. data/lib/bayonetta/wmb.rb +2443 -0
  81. data/lib/bayonetta/wmb3.rb +759 -0
  82. data/lib/bayonetta/wtb.rb +481 -0
  83. data/lib/bayonetta.rb +60 -0
  84. metadata +254 -0
@@ -0,0 +1,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