Roll out seven modular outfit bases: eleven distinct outfits across twelve roles
This commit is contained in:
1 parent
3b9e3b61d8
commit
b18236f9b4
27 files changed
+267
-88
No files matched your search
+106
-6
@@ -95,7 +95,7 @@ CHARACTERS = {
|
||||
"base": "hoodie",
|
||||
"roles": ["tourist", "confused_visitor", "intoxicated", "guest"]},
|
||||
"npc_jacket": {"layer": "jacket", "hair": "short", "gear": ["lanyard", "clipboard"],
|
||||
"base": "casual",
|
||||
"base": "casual_2",
|
||||
"roles": ["authorized_staff", "protester", "guest"]},
|
||||
"npc_coat": {"layer": "coat", "hair": "short", "base": "suit",
|
||||
"gear": ["sunglasses", "shoulder_bag", "phone"],
|
||||
@@ -107,10 +107,10 @@ CHARACTERS = {
|
||||
"slots": {"Worker_Yellow": {"split": True, "z": 1.65,
|
||||
"above": "hair", "below": "keep"}},
|
||||
"roles": ["delivery"]},
|
||||
"npc_dress": {"layer": "dress", "hair": "long", "build": "slim", "base": "casual",
|
||||
"npc_dress": {"layer": "dress", "hair": "long", "build": "slim", "base": "king",
|
||||
"gear": ["shoulder_bag"],
|
||||
"roles": ["performer"]},
|
||||
"npc_hoodie": {"layer": "hood", "hair": "short", "gear": ["cup"], "base": "hoodie",
|
||||
"npc_hoodie": {"layer": "hood", "hair": "short", "gear": ["cup"], "base": "punk",
|
||||
"roles": ["trespasser", "mental_crisis", "violent_threat"]},
|
||||
"npc_firefighter": {"layer": "responder", "hair": "buzz", "build": "broad",
|
||||
"base": "worker",
|
||||
@@ -122,12 +122,12 @@ CHARACTERS = {
|
||||
"roles": ["player_guard", "venue_security"]},
|
||||
# Crowd-variety prototypes (character development): the three Quaternius
|
||||
# sources are spread across the crowd pools so a crowd never shows one mesh.
|
||||
"npc_crowd_a": {"layer": "none", "hair": "short", "build": "slim", "base": "casual",
|
||||
"npc_crowd_a": {"layer": "none", "hair": "long", "build": "slim", "base": "beach",
|
||||
"gear": ["sunglasses"], "roles": ["tourist", "guest"]},
|
||||
"npc_crowd_b": {"layer": "none", "hair": "buzz", "build": "broad", "base": "casual",
|
||||
"npc_crowd_b": {"layer": "none", "hair": "short", "build": "broad", "base": "farmer",
|
||||
"beard": True, "gear": ["shoulder_bag"],
|
||||
"roles": ["confused_visitor", "guest"]},
|
||||
"npc_crowd_c": {"layer": "coat", "hair": "long", "base": "hoodie",
|
||||
"npc_crowd_c": {"layer": "coat", "hair": "short", "base": "adventurer",
|
||||
"gear": ["umbrella"], "roles": ["intoxicated", "guest"]},
|
||||
"npc_crowd_d": {"layer": "hood", "hair": "short", "build": "slim",
|
||||
"base": "worker",
|
||||
@@ -1513,6 +1513,61 @@ QUATERNIUS_SOURCES = {
|
||||
"Eye": "keep", "Eyebrows": "keep"},
|
||||
"notes": "hoodie + shorts + white shoes (the hoodie covers the shoe uppers)",
|
||||
},
|
||||
"adventurer": {
|
||||
"file": "Adventurer.glb",
|
||||
"slots": {"Skin": "skin", "Suit": "cloth", "Suit_legs": "pants",
|
||||
"Hair": "hair", "Grey": "accessory", "Black": "accessory",
|
||||
"LightGreen": "keep", "Gold": "keep", "Brown": "keep", "White": "keep"},
|
||||
"notes": "adventurer jacket + trousers, boots, satchel; gold buckles fixed",
|
||||
},
|
||||
"beach": {
|
||||
"file": "Beach.glb",
|
||||
"slots": {"Skin": "skin", "Skin_legs": "skin", "LightBrown": "cloth",
|
||||
"Red_Dark_legs": "pants", "White_legs": "pants",
|
||||
"Red_Dark": "accessory", "Hair": "hair", "Earrings": "keep"},
|
||||
"notes": "beach shirt and shorts, bare legs, flip-flops",
|
||||
},
|
||||
"casual_2": {
|
||||
"file": "Casual_2.glb",
|
||||
"slots": {"Skin": "skin", "LightBrown": "cloth", "LightBlue_legs": "pants",
|
||||
"Red_Dark": "accessory", "White": "accessory", "Hair": "hair",
|
||||
"Skin_Darker": "keep", "Eye": "keep", "Eyebrows": "keep"},
|
||||
"notes": "second casual outfit: jersey, jeans, sneakers",
|
||||
},
|
||||
"farmer": {
|
||||
"file": "Farmer.glb",
|
||||
"slots": {"Skin": "skin", "Brown": "cloth", "LightBlue.001": "pants",
|
||||
"Brown.001": "accessory", "Brown2": "accessory",
|
||||
"LightBlue": "keep", "Beige": "keep", "Beige.001": "keep",
|
||||
"Eyebrows": "keep"},
|
||||
"patch_slots": ["hair"],
|
||||
"notes": "farmer shirt + overalls + straw hat; no hair shell, so the slot is patched",
|
||||
},
|
||||
"king": {
|
||||
"file": "King.glb",
|
||||
"slots": {"Skin": "skin", "Metal": "cloth", "Metal_legs": "pants",
|
||||
"DarkBrown_legs": "pants", "Metal.001": "accessory",
|
||||
"Hair_White": "hair", "Gold": "keep", "Metal_Dark": "keep",
|
||||
"Metal_Dark_legs": "keep", "Blue": "keep"},
|
||||
"notes": "royal armour and cape with a gold crown; stage-performer look",
|
||||
},
|
||||
"punk": {
|
||||
"file": "Punk.glb",
|
||||
"slots": {"Skin": "skin", "Black": "cloth", "LightBlue_legs": "pants",
|
||||
"Black.001": "accessory", "Red": "hair",
|
||||
"White": "keep", "Earrings": "keep", "Red_Dark": "keep",
|
||||
"Eye": "keep", "Eyebrows": "keep"},
|
||||
"notes": "punk jacket and jeans; the mohawk is the `Red` material, so it is the hair slot",
|
||||
},
|
||||
"swat": {
|
||||
"file": "Swat.glb",
|
||||
"slots": {"Skin": "skin", "Swat_Black": "cloth", "Swat_legs": "pants",
|
||||
"Swat_Black_legs": "pants", "Swat_Black.001": "accessory",
|
||||
"Swat": "keep", "Swat.001": "keep", "Swat_Black.002": "keep",
|
||||
"Visor": "keep"},
|
||||
"patch_slots": ["hair"],
|
||||
"notes": "tactical uniform with helmet and visor; no hair shell, so the slot is patched",
|
||||
},
|
||||
"suit": {
|
||||
"file": "Suit.glb",
|
||||
"slots": {"Skin": "skin", "Suit": "cloth", "Suit_legs": "pants",
|
||||
@@ -1845,6 +1900,50 @@ def _apply_hair_part(obj, style, base):
|
||||
print(f" hair: {style} scale {sx}/{sy}/{sz} about {part['anchor']}")
|
||||
|
||||
|
||||
## Slots a source cannot paint (a helmeted head has no hair) still have to exist: the
|
||||
## runtime tints five slots and the audit requires all five as materials. A 4 cm
|
||||
## triangle is buried inside the skull or the pelvis for each missing one, so the
|
||||
## material is used instead of being dropped by the exporter, and nothing shows.
|
||||
SLOT_PATCH_ANCHOR = {"hair": ("Head", 0.0, 0.0, 1.66),
|
||||
"skin": ("Head", 0.0, 0.0, 1.60),
|
||||
"pants": ("Hips", 0.0, 0.0, 0.90),
|
||||
"cloth": ("Chest", 0.0, 0.0, 1.30),
|
||||
"accessory": ("Hips", 0.0, 0.0, 0.86)}
|
||||
|
||||
|
||||
def _patch_missing_slots(obj, slots):
|
||||
"""Buried triangles that keep an unpaintable contract slot present."""
|
||||
present = set()
|
||||
for poly in obj.data.polygons:
|
||||
material = obj.data.materials[poly.material_index]
|
||||
if material is not None:
|
||||
present.add(_base_name(material.name))
|
||||
patched = []
|
||||
for slot in slots:
|
||||
if slot in present or slot not in SLOT_PATCH_ANCHOR:
|
||||
continue
|
||||
bone, x, y, z = SLOT_PATCH_ANCHOR[slot]
|
||||
material = bpy.data.materials.get(slot) or bpy.data.materials.new(slot)
|
||||
if material.name not in [m.name for m in obj.data.materials if m]:
|
||||
obj.data.materials.append(material)
|
||||
index = [m.name for m in obj.data.materials].index(material.name)
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
before = len(obj.data.vertices)
|
||||
cube = bmesh.ops.create_cube(bm, size=0.04)
|
||||
bmesh.ops.translate(bm, verts=cube["verts"], vec=Vector((x, y, z)))
|
||||
bm.to_mesh(obj.data)
|
||||
bm.free()
|
||||
for poly in obj.data.polygons:
|
||||
if all(v >= before for v in poly.vertices):
|
||||
poly.material_index = index
|
||||
group = obj.vertex_groups.get(bone) or obj.vertex_groups.new(name=bone)
|
||||
group.add(list(range(before, len(obj.data.vertices))), 1.0, "REPLACE")
|
||||
patched.append(slot)
|
||||
if patched:
|
||||
print(f" patched hidden slots: {', '.join(patched)}")
|
||||
|
||||
|
||||
def _base_name(name):
|
||||
"""Strip Blender's `.001` duplicate suffix."""
|
||||
head, sep, tail = name.rpartition(".")
|
||||
@@ -2266,6 +2365,7 @@ def _quaternius_tpose_glb(base, overrides, hair_style="short", parts=()):
|
||||
_add_quaternius_mouth(obj)
|
||||
_add_quaternius_eye_whites(obj)
|
||||
_quaternius_slot_map(obj, slots)
|
||||
_patch_missing_slots(obj, list(source.get("patch_slots", [])))
|
||||
_apply_hair_part(obj, hair_style, base)
|
||||
|
||||
# Upstream UVs are degenerate (every loop at 0,0), so an AO bake would write
|
||||
|
||||
Reference in new issue
Block a user