Skip to content

Commit f09746a

Browse files
author
Nozomi Miyamori
committed
release version 3.1.0
* Import color variants of a model from NGS1 TMC. * Fix some material mappings.
1 parent aeef2d0 commit f09746a

File tree

5 files changed

+142
-115
lines changed

5 files changed

+142
-115
lines changed

src/ninja_gaiden_tmc/blender_manifest.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
schema_version = "1.0.0"
22

33
id = "ninja_gaiden_tmc"
4-
version = "3.0.0"
4+
version = "3.1.0"
55
name = "Ninja Gaiden Sigma 1 and 2 TMC Format"
66
tagline = "Import Ninja Gaiden Sigma 1 and 2 TMC"
77
maintainer = "Nozomi Miyamori"

src/ninja_gaiden_tmc/ngs1/importer.py

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -235,12 +235,20 @@ def import_tmc(context, tmc, g1tg):
235235
pbsdf.distribution = 'GGX'
236236
pbsdf.inputs['Specular IOR Level'].default_value = 1
237237

238+
base_spec_mix = m.node_tree.nodes.new('ShaderNodeMix')
239+
base_spec_mix.name = 'mtrcol_base_spec'
240+
base_spec_mix.parent = shader_frame
241+
base_spec_mix.data_type = 'RGBA'
242+
base_spec_mix.blend_type = 'MULTIPLY'
243+
base_spec_mix.inputs[0].default_value = .9
244+
m.node_tree.links.new(base_spec_mix.outputs['Result'], pbsdf.inputs['Base Color'])
245+
238246
base_color_mix = m.node_tree.nodes.new('ShaderNodeMix')
239247
base_color_mix.name = 'mtrcol_base_color'
240248
base_color_mix.parent = shader_frame
241249
base_color_mix.data_type = 'RGBA'
242-
base_color_mix.blend_type = 'LIGHTEN'
243-
m.node_tree.links.new(base_color_mix.outputs['Result'], pbsdf.inputs['Base Color'])
250+
base_color_mix.blend_type = 'LINEAR_LIGHT'
251+
m.node_tree.links.new(base_color_mix.outputs['Result'], base_spec_mix.inputs['A'])
244252

245253
spec_mix = m.node_tree.nodes.new('ShaderNodeMix')
246254
spec_mix.name = 'mtrcol_specular'
@@ -296,8 +304,8 @@ def import_tmc(context, tmc, g1tg):
296304
base_color_albedo_uv = uv.uv_map
297305

298306
if t.color_usage == 0:
299-
ti.label = frame.label = 'Diffuse Texture (Soft Light)'
300-
albedo_mix.blend_type = 'SOFT_LIGHT'
307+
ti.label = frame.label = 'Diffuse Texture (Light)'
308+
albedo_mix.blend_type = 'LINEAR_LIGHT'
301309
elif t.color_usage == 1:
302310
ti.label = frame.label = 'Diffuse Texture (Screen)'
303311
albedo_mix.blend_type = 'SCREEN'
@@ -339,14 +347,16 @@ def import_tmc(context, tmc, g1tg):
339347
mix.parent = textures_frame
340348
mix.data_type = 'RGBA'
341349
mix.blend_type = 'ADD'
342-
mix.inputs['Factor'].default_value = 1
350+
mix.inputs['Factor'].default_value = 0
351+
m.node_tree.links.new(ti.outputs['Color'], mix.inputs['Factor'])
343352
if is_shadow:
344-
m.node_tree.links.new(base_color_albedo.outputs['Alpha'], mix.inputs['A'])
345353
mul = m.node_tree.nodes.new('ShaderNodeMath')
346354
mul.operation = 'MULTIPLY'
347355
m.node_tree.links.new(ti.outputs['Color'], mul.inputs[0])
348356
m.node_tree.links.new(ti.outputs['Alpha'], mul.inputs[1])
349357
m.node_tree.links.new(mul.outputs[0], pbsdf.inputs['Alpha'])
358+
m.node_tree.links.new(base_color_albedo.outputs['Alpha'], mix.inputs['A'])
359+
m.node_tree.links.new(base_color_albedo.outputs['Color'], mix.inputs['Factor'])
350360
else:
351361
m.node_tree.links.new(base_color_albedo.outputs['Color'], mix.inputs['A'])
352362
m.node_tree.links.new(ti.outputs['Color'], mix.inputs['B'])
@@ -355,10 +365,36 @@ def import_tmc(context, tmc, g1tg):
355365
case x:
356366
raise Exception(f'Not supported texture map usage: {repr(x)}')
357367

368+
try:
369+
V = tmc.extmcol.color_variants
370+
except AttributeError:
371+
pass
372+
else:
373+
for var in V:
374+
C = bpy.data.collections.new(tmc_name)
375+
collection_top.children.link(C)
376+
M = { m: m for m in objgeo_params_to_material.values() }
377+
for c in var:
378+
i = c.mtrcol_chunk_index
379+
for m in M:
380+
if m['mtrcol'] == i:
381+
M[m] = new_m = m.copy()
382+
set_material_parameters(new_m, c)
383+
for i, mo in enumerate(mesh_objs):
384+
o = mo.copy()
385+
C.objects.link(o)
386+
o.parent = armature_obj
387+
for j, ms in enumerate(o.material_slots):
388+
ms.material = M[ms.material]
389+
390+
358391
def set_material_parameters(material, mtrcol_chunk):
359392
n = material.node_tree.nodes['Principled BSDF']
360393
n.inputs['Metallic'].default_value = min(math.log10(1+Vector(mtrcol_chunk.specular_power[:3]).length), 1)
361-
n.inputs['IOR'].default_value = max(min(math.log10(1+mtrcol_chunk.ior), 4), 1)
394+
n.inputs['IOR'].default_value = max(min(math.log10(1+mtrcol_chunk.specular_power[3]), 4), 1)
395+
396+
n = material.node_tree.nodes['mtrcol_base_spec']
397+
n.inputs['B'].default_value = mtrcol_chunk.specular
362398

363399
n = material.node_tree.nodes['mtrcol_base_color']
364400
n.inputs['B'].default_value = mtrcol_chunk.emission

src/ninja_gaiden_tmc/ngs2/importer.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -267,14 +267,14 @@ def import_tmc(context, tmc):
267267
base_spec_mix.parent = shader_frame
268268
base_spec_mix.data_type = 'RGBA'
269269
base_spec_mix.blend_type = 'MULTIPLY'
270+
base_spec_mix.inputs[0].default_value = 1
270271
m.node_tree.links.new(base_spec_mix.outputs['Result'], pbsdf.inputs['Base Color'])
271272

272273
base_color_mix = m.node_tree.nodes.new('ShaderNodeMix')
273274
base_color_mix.name = 'mtrcol_base_color'
274275
base_color_mix.parent = shader_frame
275276
base_color_mix.data_type = 'RGBA'
276277
base_color_mix.blend_type = 'SCREEN'
277-
m.node_tree.links.new(base_color_mix.outputs['Result'], base_spec_mix.inputs['Factor'])
278278
m.node_tree.links.new(base_color_mix.outputs['Result'], base_spec_mix.inputs['A'])
279279

280280
spec_mix = m.node_tree.nodes.new('ShaderNodeMix')
@@ -331,8 +331,8 @@ def import_tmc(context, tmc):
331331
base_color_albedo_uv = uv.uv_map
332332

333333
if t.color_usage == 0:
334-
ti.label = frame.label = 'Diffuse Texture (Soft Light)'
335-
albedo_mix.blend_type = 'SOFT_LIGHT'
334+
ti.label = frame.label = 'Diffuse Texture (Light)'
335+
albedo_mix.blend_type = 'LINEAR_LIGHT'
336336
elif t.color_usage == 1:
337337
ti.label = frame.label = 'Diffuse Texture (Screen)'
338338
albedo_mix.blend_type = 'SCREEN'
@@ -374,14 +374,16 @@ def import_tmc(context, tmc):
374374
mix.parent = textures_frame
375375
mix.data_type = 'RGBA'
376376
mix.blend_type = 'ADD'
377-
mix.inputs['Factor'].default_value = 1
377+
mix.inputs['Factor'].default_value = 0
378+
m.node_tree.links.new(ti.outputs['Color'], mix.inputs['Factor'])
378379
if is_shadow:
379-
m.node_tree.links.new(base_color_albedo.outputs['Alpha'], mix.inputs['A'])
380380
mul = m.node_tree.nodes.new('ShaderNodeMath')
381381
mul.operation = 'MULTIPLY'
382382
m.node_tree.links.new(ti.outputs['Color'], mul.inputs[0])
383383
m.node_tree.links.new(ti.outputs['Alpha'], mul.inputs[1])
384384
m.node_tree.links.new(mul.outputs[0], pbsdf.inputs['Alpha'])
385+
m.node_tree.links.new(base_color_albedo.outputs['Alpha'], mix.inputs['A'])
386+
m.node_tree.links.new(base_color_albedo.outputs['Color'], mix.inputs['Factor'])
385387
else:
386388
m.node_tree.links.new(base_color_albedo.outputs['Color'], mix.inputs['A'])
387389
m.node_tree.links.new(ti.outputs['Color'], mix.inputs['B'])
@@ -395,12 +397,12 @@ def import_tmc(context, tmc):
395397
except AttributeError:
396398
pass
397399
else:
398-
for mtrc in V:
400+
for var in V:
399401
C = bpy.data.collections.new(tmc_name)
400402
collection_top.children.link(C)
401403
M = { m: m.copy() for m in objgeo_params_to_material.values() }
402404
for m in M.values():
403-
set_material_parameters(m, mtrc[m["mtrcol"]])
405+
set_material_parameters(m, var[m["mtrcol"]])
404406
for i, mo in enumerate(mesh_objs):
405407
o = mo.copy()
406408
C.objects.link(o)

src/ninja_gaiden_tmc/tcmlib/ngs1/parser.py

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ def __init__(self, data, ldata = b''):
2727
tbl = self._metadata[o:p].cast('I')
2828

2929
for t, c in zip(tbl, self._chunks):
30+
if not c:
31+
continue
32+
3033
match t:
3134
case 0x8000_0001:
3235
self.mdlgeo = MdlGeoParser(c)
@@ -39,20 +42,20 @@ def __init__(self, data, ldata = b''):
3942
self.mdlinfo = MdlInfoParser(c)
4043
case 0x8000_0010:
4144
self.hielay = HieLayParser(c)
42-
case 0x0000_0000:
43-
#self.collide = COLLIDEParser(c)
44-
pass
4545
case 0x0000_0001:
4646
self.obj_type_info = OBJ_TYPE_INFOParser(c)
47+
case 0x0000_0015:
48+
self.extmcol = EXTMCOLParser(c)
4749

4850
def close(self):
4951
super().close()
50-
(x := getattr(self, 'vtxlay', None)) and x.close()
51-
(x := getattr(self, 'idxlay', None)) and x.close()
52+
if x := getattr(self, 'vtxlay', None): x.close()
53+
if x := getattr(self, 'idxlay', None): x.close()
5254
self.mdlgeo.close()
5355
self.mtrcol.close()
5456
self.mdlinfo.close()
5557
self.hielay.close()
58+
if x := getattr(self, 'extmcol', None): x.close()
5659

5760
class TMCMetaData(NamedTuple):
5861
unknown0x0: int
@@ -293,22 +296,25 @@ def __init__(self, data, ldata = b''):
293296

294297
@staticmethod
295298
def _make_chunk(c):
296-
mtrcol_chunk_index, xref_count = struct.unpack_from('< iI', c, 0x50)
299+
xref_count, = struct.unpack_from('< I', c, 0x54)
297300
xrefs = struct.unpack_from(f'<' + xref_count*'iI', c, 0x58)
298-
xrefs = tuple(xrefs[i:i+2] for i in range(0, len(xrefs), 2))
299301
return MtrColChunk(
300302
struct.unpack_from('< 4f', c),
301303
struct.unpack_from('< 4f', c, 0x10),
302-
struct.unpack_from('< 4f', c, 0x20),
303-
struct.unpack_from('< 4f', c, 0x30),
304-
*struct.unpack_from('< 4f', c, 0x40),
305-
mtrcol_chunk_index, xrefs)
304+
(*struct.unpack_from('< 3f', c, 0x20), *struct.unpack_from('< f', c, 0x40)),
305+
struct.unpack_from('< 3f', c, 0x30),
306+
*struct.unpack_from('< 4f i', c, 0x40),
307+
tuple(xrefs[i:i+2] for i in range(0, len(xrefs), 2))
308+
)
306309

310+
# Cf. EXTMCOL
307311
class MtrColChunk(NamedTuple):
308312
emission: tuple[float]
309313
specular: tuple[float]
310314
specular_power: tuple[float]
311315
unknown0x30: tuple[float]
316+
# 0x40-0x44 is ior in MtrCol, 0x2c-0x30 is ior in EXTMCOL,
317+
# but the game move ior in MtrCol to 0x2c-0x30 after loading the data.
312318
ior: float
313319
specular_glow_power: float
314320
diffuse_glow_power: float
@@ -395,6 +401,8 @@ class HieLayChunk(NamedTuple):
395401
#address0x8?
396402
children: tuple[int]
397403

404+
# NGS1 specific data below.
405+
398406
class OBJ_TYPE_INFOParser:
399407
def __init__(self, data):
400408
(
@@ -410,3 +418,25 @@ class OBJ_TYPE(IntEnum):
410418
SUP = 4
411419
OPT = 5
412420
WPB = 7
421+
422+
class EXTMCOLParser(ContainerParser):
423+
def __init__(self, data):
424+
super().__init__(b'EXTMCOL', data)
425+
self.metadata = EXTMCOLMetaData(*struct.unpack_from('< II', self._metadata))
426+
m = self.metadata.variant_count
427+
n = self.metadata.element_count
428+
self.chunks = tuple(
429+
MtrColChunk(
430+
struct.unpack_from('< 4f', c),
431+
struct.unpack_from('< 4f', c, 0x10),
432+
struct.unpack_from('< 4f', c, 0x20),
433+
struct.unpack_from('< 4f', c, 0x30),
434+
*struct.unpack_from('< 4f i', c, 0x40),
435+
tuple()
436+
) for c in self._chunks
437+
)
438+
self.color_variants = tuple( self.chunks[i*n:(i+1)*n] for i in range(m) )
439+
440+
class EXTMCOLMetaData(NamedTuple):
441+
variant_count: int
442+
element_count: int

0 commit comments

Comments
 (0)