Add eye whites and pupils to every base and archive the 0.9.9 milestone
This commit is contained in:
1 parent
c70a523f0f
commit
a0dbf75806
22 files changed
+129
-34
No files matched your search
@@ -1663,6 +1663,81 @@ def _add_quaternius_mouth(obj):
|
||||
print(f" mouth placed at y {front + 0.003:+.3f} z {height:.3f}")
|
||||
|
||||
|
||||
def _add_quaternius_eye_whites(obj):
|
||||
"""Give the eye quads a sclera and a pupil.
|
||||
|
||||
Every base ships one dark quad per eye, so the face reads as two dark
|
||||
rectangles at portrait distance. The quads are rewired instead of textured: the
|
||||
existing faces become the sclera (light), and a shrunken copy in front of each
|
||||
becomes the pupil (dark). Two draw calls per head, no new UV work.
|
||||
"""
|
||||
eye = next((i for i, m in enumerate(obj.data.materials)
|
||||
if m is not None and _base_name(m.name).lower() == "eye"), None)
|
||||
if eye is None:
|
||||
return
|
||||
sclera = obj.data.materials[eye]
|
||||
sclera.name = "sclera"
|
||||
sclera.use_nodes = True
|
||||
bsdf = sclera.node_tree.nodes.get("Principled BSDF")
|
||||
if bsdf is not None:
|
||||
# Linear values: 0.62 reads as an off-white sclera, not a glowing one.
|
||||
bsdf.inputs["Base Color"].default_value = (0.62, 0.60, 0.55, 1.0)
|
||||
bsdf.inputs["Roughness"].default_value = 0.45
|
||||
sclera.use_backface_culling = False
|
||||
pupil = bpy.data.materials.new("pupil")
|
||||
pupil.use_nodes = True
|
||||
pupil_bsdf = pupil.node_tree.nodes.get("Principled BSDF")
|
||||
if pupil_bsdf is not None:
|
||||
pupil_bsdf.inputs["Base Color"].default_value = (0.012, 0.010, 0.014, 1.0)
|
||||
pupil_bsdf.inputs["Roughness"].default_value = 0.35
|
||||
# A single-sided pupil can vanish depending on the exported winding; the extra
|
||||
# draw cost on two 0.02 m quads is irrelevant.
|
||||
pupil.use_backface_culling = False
|
||||
|
||||
# Collect the eye faces before adding anything, then inset a pupil per face.
|
||||
faces = [p for p in obj.data.polygons if p.material_index == eye]
|
||||
if not faces:
|
||||
return
|
||||
obj.data.materials.append(pupil)
|
||||
pupil_index = len(obj.data.materials) - 1
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
bm.faces.ensure_lookup_table()
|
||||
# Hold face *references*: adding vertices invalidates the index tables.
|
||||
targets = [bm.faces[i] for i in range(len(bm.faces))
|
||||
if bm.faces[i].material_index == eye]
|
||||
# Shrink toward the *eye* centre, not each triangle's own centre: the quad is two
|
||||
# triangles, and per-face insets produced two offset wedges instead of a pupil.
|
||||
eye_centre = {}
|
||||
for face in targets:
|
||||
side = 1 if face.calc_center_median().x >= 0.0 else -1
|
||||
eye_centre.setdefault(side, []).append(face.calc_center_median())
|
||||
side_centre = {side: sum(points, Vector()) / len(points)
|
||||
for side, points in eye_centre.items()}
|
||||
new_faces = []
|
||||
for face in targets:
|
||||
centre = side_centre[1 if face.calc_center_median().x >= 0.0 else -1]
|
||||
# Push the pupil in front of the sclera so it cannot z-fight, then shrink it
|
||||
# about its own centre. Blender -Y is the face, so "in front" is -Y.
|
||||
# Push the pupil out along the sclera's own normal: offsetting in world -Y
|
||||
# only worked while the eye plane was axis-aligned, and this one is tilted.
|
||||
nudge = face.normal.normalized() * 0.0032
|
||||
verts = []
|
||||
for vert in face.verts:
|
||||
offset = vert.co - centre
|
||||
verts.append(bm.verts.new(centre
|
||||
+ Vector((offset.x * 0.74,
|
||||
offset.y * 0.74,
|
||||
offset.z * 0.70))
|
||||
+ nudge))
|
||||
new_face = bm.faces.new(verts)
|
||||
new_face.material_index = pupil_index
|
||||
new_faces.append(new_face)
|
||||
bm.to_mesh(obj.data)
|
||||
bm.free()
|
||||
print(f" eye whites: {len(faces)} sclera quads, {len(new_faces)} pupil quads")
|
||||
|
||||
|
||||
def _base_name(name):
|
||||
"""Strip Blender's `.001` duplicate suffix."""
|
||||
head, sep, tail = name.rpartition(".")
|
||||
@@ -2075,6 +2150,7 @@ def _quaternius_tpose_glb(base, overrides):
|
||||
if source.get("donor_head"):
|
||||
_transplant_worker_head(obj)
|
||||
_add_quaternius_mouth(obj)
|
||||
_add_quaternius_eye_whites(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