pgtools 1.0.0 → 1.0.1

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 +4 -4
  2. data/LICENSE +25 -25
  3. data/bin/bxm_decoder +2 -2
  4. data/bin/bxm_encoder +2 -2
  5. data/bin/clh_convert +2 -2
  6. data/bin/clp_convert +2 -2
  7. data/bin/clw_convert +2 -2
  8. data/bin/dat_creator +2 -2
  9. data/bin/dat_extractor +2 -2
  10. data/bin/dat_ls +2 -2
  11. data/bin/eff_idd_creator +2 -2
  12. data/bin/eff_idd_extractor +2 -2
  13. data/bin/exp_convert_wiiu_pc +2 -2
  14. data/bin/exp_tool +2 -2
  15. data/bin/mot_convert_wiiu_pc +2 -2
  16. data/bin/mot_tool +2 -2
  17. data/bin/pkz_extractor +2 -2
  18. data/bin/scr_creator +2 -2
  19. data/bin/scr_extractor +2 -2
  20. data/bin/wmb_cleanup +2 -2
  21. data/bin/wmb_common_bones +2 -2
  22. data/bin/wmb_convert_pc_switch +2 -2
  23. data/bin/wmb_convert_wiiu_pc +2 -2
  24. data/bin/wmb_export_assimp +2 -2
  25. data/bin/wmb_get_bone_map +2 -2
  26. data/bin/wmb_import_assimp +2 -2
  27. data/bin/wmb_import_nier +2 -2
  28. data/bin/wmb_import_wiiu +2 -2
  29. data/bin/wtb_convert_wiiu_pc +2 -2
  30. data/bin/wtx_creator +2 -2
  31. data/bin/wtx_extractor +2 -2
  32. data/lib/bayonetta/alignment.rb +0 -0
  33. data/lib/bayonetta/bone.rb +0 -0
  34. data/lib/bayonetta/bxm.rb +180 -180
  35. data/lib/bayonetta/clh.rb +159 -159
  36. data/lib/bayonetta/clp.rb +212 -212
  37. data/lib/bayonetta/clw.rb +166 -166
  38. data/lib/bayonetta/dat.rb +261 -261
  39. data/lib/bayonetta/eff.rb +314 -314
  40. data/lib/bayonetta/endianness.rb +0 -0
  41. data/lib/bayonetta/exp.rb +768 -768
  42. data/lib/bayonetta/linalg.rb +416 -416
  43. data/lib/bayonetta/material_database.yaml +2581 -2581
  44. data/lib/bayonetta/mot.rb +763 -763
  45. data/lib/bayonetta/pkz.rb +63 -63
  46. data/lib/bayonetta/scr.rb +0 -0
  47. data/lib/bayonetta/tools/bxm_decoder.rb +23 -23
  48. data/lib/bayonetta/tools/bxm_encoder.rb +37 -37
  49. data/lib/bayonetta/tools/clh_convert.rb +60 -60
  50. data/lib/bayonetta/tools/clp_convert.rb +70 -70
  51. data/lib/bayonetta/tools/clw_convert.rb +60 -60
  52. data/lib/bayonetta/tools/dat_creator.rb +57 -57
  53. data/lib/bayonetta/tools/dat_extractor.rb +94 -94
  54. data/lib/bayonetta/tools/dat_ls.rb +106 -106
  55. data/lib/bayonetta/tools/eff_idd_creator.rb +66 -66
  56. data/lib/bayonetta/tools/eff_idd_extractor.rb +73 -73
  57. data/lib/bayonetta/tools/exp_convert_wiiu_pc.rb +33 -33
  58. data/lib/bayonetta/tools/exp_tool.rb +48 -48
  59. data/lib/bayonetta/tools/mot_convert_wiiu_pc.rb +33 -33
  60. data/lib/bayonetta/tools/mot_tool.rb +0 -0
  61. data/lib/bayonetta/tools/pkz_extractor.rb +75 -75
  62. data/lib/bayonetta/tools/scr_creator.rb +63 -63
  63. data/lib/bayonetta/tools/scr_extractor.rb +78 -78
  64. data/lib/bayonetta/tools/wmb_cleanup.rb +250 -250
  65. data/lib/bayonetta/tools/wmb_common_bones.rb +45 -45
  66. data/lib/bayonetta/tools/wmb_convert_pc_switch.rb +35 -35
  67. data/lib/bayonetta/tools/wmb_convert_wiiu_pc.rb +33 -33
  68. data/lib/bayonetta/tools/wmb_export_assimp.rb +479 -479
  69. data/lib/bayonetta/tools/wmb_get_bone_map.rb +50 -50
  70. data/lib/bayonetta/tools/wmb_import_assimp.rb +735 -735
  71. data/lib/bayonetta/tools/wmb_import_geometry_wiiu_pc.rb +472 -472
  72. data/lib/bayonetta/tools/wmb_import_nier.rb +309 -309
  73. data/lib/bayonetta/tools/wtb_convert_wiiu_pc.rb +95 -95
  74. data/lib/bayonetta/tools/wtb_import_textures.rb +103 -103
  75. data/lib/bayonetta/tools/wtx_creator.rb +69 -69
  76. data/lib/bayonetta/tools/wtx_extractor.rb +85 -85
  77. data/lib/bayonetta/vertex_types.yaml +0 -0
  78. data/lib/bayonetta/vertex_types2.yaml +0 -0
  79. data/lib/bayonetta/vertex_types_nier.yaml +145 -145
  80. data/lib/bayonetta/wmb.rb +2455 -2443
  81. data/lib/bayonetta/wmb3.rb +759 -759
  82. data/lib/bayonetta/wtb.rb +481 -481
  83. data/lib/bayonetta.rb +60 -60
  84. metadata +2 -2
@@ -1,735 +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
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