Lift and scale head gear for the Quaternius head so the cap stops reading as a visor
This commit is contained in:
1 parent
529d15ee7a
commit
a290fe83c8
24 files changed
+96
-44
No files matched your search
+39
-20
@@ -152,7 +152,11 @@ HEAD_BAND = (1.55, 1.81) # face/headgear height band in Godot metres
|
||||
# Quaternius roster has a 0.20 m head whose centre sits higher (see
|
||||
# `build_gear`). 0.64 = 0.201 / 0.315.
|
||||
HEAD_GEAR_SCALE = 0.64
|
||||
HEAD_GEAR_LIFT = 0.07
|
||||
# The gear origin is the Head *bone* (Godot y 1.587 on the Quaternius rig), but the
|
||||
# gear is authored around the head *centre* (1.62 in the procedural library). The
|
||||
# Quaternius head centre sits at 1.69, so head gear needs +0.103 or the cap brim
|
||||
# lands on the brow and reads as a black visor across the eyes.
|
||||
HEAD_GEAR_LIFT = 0.103
|
||||
FIT_HEAD_MATERIALS = ("skin",)
|
||||
FIT_CLOTHING_MATERIAL = "cloth"
|
||||
FIT_CLOTHING_BAND = (0.30, 0.52) # hip/skirt band; captures coat/dress flare
|
||||
@@ -1529,14 +1533,21 @@ QUATERNIUS_SOURCES = {
|
||||
## remapped, because the Worker's vest *is* its torso and its hat *is* its crown.
|
||||
WORKER_HEAD_DONOR = {
|
||||
"source": "Casual_Hoodie.glb",
|
||||
# donor material -> contract slot name. The hoodie's eyes are not used: this
|
||||
# head's eye quads are large enough to read as one dark bar, which is the
|
||||
# "unibrow" the owner saw. The brows and hair carry the face instead.
|
||||
# donor material -> contract slot name.
|
||||
"parts": {"Eyebrows": "Brows", "Hair": "hair"},
|
||||
# Lift the brows clear of the eye line (the two heads differ in mesh density).
|
||||
"lift": {"Brows": 0.018},
|
||||
# our material -> height rule for the faces that go away
|
||||
"remove": {"Eyebrows": None, "Eye": None, "Worker_Yellow": 1.60},
|
||||
# The three upstream heads carry the same-sized eye quads (x +-0.057), but the
|
||||
# Worker's face is flatter, so unchecked they sit proud of the skin and merge
|
||||
# into one dark bar -- the "unibrow" the owner saw. Set them back into the
|
||||
# socket so the skin crops their edges, and lift the brows clear of them.
|
||||
"offset": {"Brows": (0.0, 0.0, 0.020)},
|
||||
# Our material -> the height below which its faces go away. `Eyebrows` is the
|
||||
# bar *and* the forehead shell, so only the bar is cut: removing the whole
|
||||
# material opened the skull and the interior rendered as a black band across
|
||||
# the brows. `Eye` goes entirely (its quads read as one dark mass).
|
||||
# `Eye` is deliberately *not* removed: this head has open sockets and the eye
|
||||
# quads are the only thing filling them, so deleting them showed the inside of
|
||||
# the head as a black band. They stay; the donor brows sit above them.
|
||||
"remove": {"Eyebrows": 1.715, "Worker_Yellow": 1.60},
|
||||
"bone": "Head",
|
||||
}
|
||||
|
||||
@@ -1577,16 +1588,6 @@ def _transplant_worker_head(obj):
|
||||
bm.to_mesh(obj.data)
|
||||
bm.free()
|
||||
print(f" removed {len(doomed)} Worker head faces")
|
||||
# Free the slot names so the donor's materials can take them. `pop` removes a
|
||||
# slot by index and repoints the polygons that used it.
|
||||
for index in range(len(obj.data.materials) - 1, -1, -1):
|
||||
slot = obj.data.materials[index]
|
||||
if slot is None or slot.name not in spec["remove"]:
|
||||
continue
|
||||
if any(poly.material_index == index for poly in obj.data.polygons):
|
||||
continue
|
||||
obj.data.materials.pop(index=index)
|
||||
|
||||
donor_path = QUATERNIUS_SRC / spec["source"]
|
||||
if not donor_path.is_file():
|
||||
print(f" head donor {donor_path.name} missing, Worker head left bare")
|
||||
@@ -1629,7 +1630,7 @@ def _transplant_worker_head(obj):
|
||||
m.name = slot
|
||||
remap[index] = len(kept)
|
||||
kept.append(m)
|
||||
for slot_name, lift in spec.get("lift", {}).items():
|
||||
for slot_name, rule in spec.get("narrow", {}).items():
|
||||
local = [i for i, m in enumerate(candidate.data.materials)
|
||||
if m is not None and m.name == slot_name]
|
||||
if not local:
|
||||
@@ -1638,8 +1639,26 @@ def _transplant_worker_head(obj):
|
||||
bm.from_mesh(candidate.data)
|
||||
bm.faces.ensure_lookup_table()
|
||||
moved = {v for f in bm.faces if f.material_index in local for v in f.verts}
|
||||
centre = float(rule["centre"])
|
||||
factor = float(rule["scale"])
|
||||
for v in moved:
|
||||
v.co.z += float(lift)
|
||||
box = centre if v.co.x >= 0.0 else -centre
|
||||
v.co.x = box + (v.co.x - box) * factor
|
||||
bm.to_mesh(candidate.data)
|
||||
bm.free()
|
||||
|
||||
for slot_name, offset in spec.get("offset", {}).items():
|
||||
local = [i for i, m in enumerate(candidate.data.materials)
|
||||
if m is not None and m.name == slot_name]
|
||||
if not local:
|
||||
continue
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(candidate.data)
|
||||
bm.faces.ensure_lookup_table()
|
||||
moved = {v for f in bm.faces if f.material_index in local for v in f.verts}
|
||||
delta = Vector(offset)
|
||||
for v in moved:
|
||||
v.co += delta
|
||||
bm.to_mesh(candidate.data)
|
||||
bm.free()
|
||||
face_slots = [remap.get(poly.material_index, 0)
|
||||
|
||||
Reference in new issue
Block a user