Give every Quaternius head a visible mouth and queue the prop and gear fit pass
This commit is contained in:
1 parent
30d145079a
commit
9fd48243ad
32 files changed
+146
-36
No files matched your search
@@ -1587,6 +1587,65 @@ def _purge_unused_materials(obj):
|
||||
bpy.data.materials.remove(material)
|
||||
|
||||
|
||||
def _add_quaternius_mouth(obj):
|
||||
"""Give every Quaternius head a readable mouth.
|
||||
|
||||
None of the three upstream heads carries a mouth material: the face is skin
|
||||
only, so at gameplay distance the characters read as brow-and-eye masks. A
|
||||
small flat lip block is placed on the measured skin surface, which is why it
|
||||
works for all three bases without per-source tuning.
|
||||
"""
|
||||
# This runs before the slot map, so the material is still the upstream `Skin`.
|
||||
skin = next((i for i, m in enumerate(obj.data.materials)
|
||||
if m is not None and _base_name(m.name).lower() == "skin"), None)
|
||||
if skin is None:
|
||||
return
|
||||
band = []
|
||||
for poly in obj.data.polygons:
|
||||
if poly.material_index != skin:
|
||||
continue
|
||||
for index in poly.vertices:
|
||||
co = obj.data.vertices[index].co
|
||||
if 1.600 <= co.z <= 1.632 and abs(co.x) <= 0.035:
|
||||
band.append(co.y)
|
||||
if not band:
|
||||
return
|
||||
front = min(band) # Blender -Y is the face; the lip sits on the surface
|
||||
height = 1.616
|
||||
material = bpy.data.materials.new("mouth")
|
||||
material.use_nodes = True
|
||||
bsdf = material.node_tree.nodes.get("Principled BSDF")
|
||||
if bsdf is not None:
|
||||
# Blender node colours are linear; 0.16 linear reads as mauve once
|
||||
# Tonemapped in Godot, so the lip is set well below the skin value.
|
||||
bsdf.inputs["Base Color"].default_value = (0.055, 0.018, 0.02, 1.0)
|
||||
bsdf.inputs["Roughness"].default_value = 0.6
|
||||
else:
|
||||
print(" mouth: no Principled node, colour left at default")
|
||||
material.diffuse_color = (0.055, 0.018, 0.02, 1.0)
|
||||
bm = bmesh.new()
|
||||
bmesh.ops.create_cube(bm, size=1.0)
|
||||
for vert in bm.verts:
|
||||
vert.co.x *= 0.058
|
||||
vert.co.y *= 0.012
|
||||
vert.co.z *= 0.011
|
||||
vert.co += Vector((0.0, front + 0.003, height))
|
||||
mouth = bpy.data.objects.new("Mouth", bpy.data.meshes.new("Mouth"))
|
||||
bm.to_mesh(mouth.data)
|
||||
bm.free()
|
||||
mouth.data.materials.append(material)
|
||||
bpy.context.scene.collection.objects.link(mouth)
|
||||
for o in bpy.context.selected_objects:
|
||||
o.select_set(False)
|
||||
obj.select_set(True)
|
||||
mouth.select_set(True)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.join()
|
||||
group = obj.vertex_groups.get("Head") or obj.vertex_groups.new(name="Head")
|
||||
group.add([v.index for v in obj.data.vertices if not v.groups], 1.0, "REPLACE")
|
||||
print(f" mouth placed at y {front + 0.003:+.3f} z {height:.3f}")
|
||||
|
||||
|
||||
def _base_name(name):
|
||||
"""Strip Blender's `.001` duplicate suffix."""
|
||||
head, sep, tail = name.rpartition(".")
|
||||
@@ -1998,6 +2057,7 @@ def _quaternius_tpose_glb(base, overrides):
|
||||
slots.update(overrides)
|
||||
if source.get("donor_head"):
|
||||
_transplant_worker_head(obj)
|
||||
_add_quaternius_mouth(obj)
|
||||
_quaternius_slot_map(obj, slots)
|
||||
|
||||
# Upstream UVs are degenerate (every loop at 0,0), so an AO bake would write
|
||||
|
||||
Reference in new issue
Block a user