Move the shoulder bag clear of coat and dress skirts
Narrow the bag to 0.06 m and move it outboard to centre x=0.292. The body inner face is now 0.262 m, clearing the coat skirt (0.225) and dress (0.260); the strap lower points start on the same line. The fit report measures the bag body separately from the torso strap and uses a signed inner-face clearance. Run Blender build/bake and Godot import. Python tooling 87/87.
This commit is contained in:
1 parent
bbf4693475
commit
702f54ae53
6 files changed
+44
-10
No files matched your search
+36
-4
@@ -113,6 +113,9 @@ FIT_HEAD_MATERIALS = ("skin",)
|
||||
FIT_CLOTHING_MATERIAL = "cloth"
|
||||
FIT_CLOTHING_BAND = (0.30, 0.52) # hip/skirt band; captures coat/dress flare
|
||||
FIT_HIP_BAND = (0.55, 0.95) # fallback body radius for trousers/vests
|
||||
# Side bags include a strap that crosses the torso, so the full gear bbox cannot
|
||||
# tell where the bag body starts. Measure the body-only y band for this fit.
|
||||
FIT_BODY_BANDS = {"shoulder_bag": (-0.34, -0.16)}
|
||||
GEAR = {
|
||||
"cap": "head", "beanie": "head", "hood": "head", "sunglasses": "head",
|
||||
"firefighter_helmet": "head",
|
||||
@@ -1005,11 +1008,13 @@ def build_gear(name):
|
||||
(0.11 * s, -0.13, 0.18), (0.12 * s, -0.12, -0.08)],
|
||||
0.012, M("plastic_black"), seg=6))
|
||||
elif name == "shoulder_bag":
|
||||
p.append(box("Bag", (0.08, 0.26, 0.20), (0.21, 0.0, -0.22), M("leather"),
|
||||
# Keep the bag's inner face outside the widest skirt flare (dress = 0.26):
|
||||
# 0.292 - 0.06/2 = 0.262. The strap starts/ends on that same outboard line.
|
||||
p.append(box("Bag", (0.06, 0.26, 0.20), (0.292, 0.0, -0.22), M("leather"),
|
||||
bevel=0.025, segs=2))
|
||||
p.append(tube("Strap", [(0.20, 0.0, -0.12), (0.15, -0.12, 0.10), (0.0, -0.13, 0.26),
|
||||
p.append(tube("Strap", [(0.262, 0.0, -0.12), (0.15, -0.12, 0.10), (0.0, -0.13, 0.26),
|
||||
(-0.15, -0.05, 0.34), (-0.16, 0.10, 0.28), (0.0, 0.14, 0.05),
|
||||
(0.20, 0.03, -0.12)], 0.009, M("leather"), seg=6))
|
||||
(0.262, 0.03, -0.12)], 0.009, M("leather"), seg=6))
|
||||
elif name == "scba_tank":
|
||||
p.append(cyl("Tank", 0.085, 0.085, 0.56, (0, 0.21, 0.02), M("steel"), seg=18))
|
||||
p.append(sphere("TopCap", 0.085, (0, 0.21, 0.30), M("steel"), seg=18, rings=8,
|
||||
@@ -1741,6 +1746,23 @@ def character_fit_reference(name):
|
||||
}
|
||||
|
||||
|
||||
def _gear_local_positions(name):
|
||||
path = asset_path("npc_gear", name)
|
||||
if not path.is_file():
|
||||
return []
|
||||
try:
|
||||
_magic, _version, _length, _size, gltf, binary = _read_glb(path)
|
||||
except (struct.error, ValueError, KeyError):
|
||||
return []
|
||||
out = []
|
||||
for mesh in gltf.get("meshes", []):
|
||||
for prim in mesh.get("primitives", []):
|
||||
attrs = prim.get("attributes", {})
|
||||
if "POSITION" in attrs:
|
||||
out.extend(_accessor(gltf, binary, attrs["POSITION"]))
|
||||
return out
|
||||
|
||||
|
||||
def _gear_fit(name, reference):
|
||||
attach = GEAR.get(name)
|
||||
if attach not in ("head", "back", "chest"):
|
||||
@@ -1768,12 +1790,21 @@ def _gear_fit(name, reference):
|
||||
else:
|
||||
# Compare the widest point (|x|) and the corner radial extent with the
|
||||
# skirt flare. The radial value catches bags that sit diagonally out.
|
||||
inner_x = min(abs(lo[0]), abs(hi[0]))
|
||||
if name in FIT_BODY_BANDS:
|
||||
band = FIT_BODY_BANDS[name]
|
||||
body_x = [abs(p[0]) for p in _gear_local_positions(name)
|
||||
if band[0] <= p[1] <= band[1]]
|
||||
if body_x:
|
||||
inner_x = min(body_x)
|
||||
x_extent = max(abs(lo[0]), abs(hi[0]))
|
||||
radial = max(math.hypot(x, z) for x in (lo[0], hi[0]) for z in (lo[2], hi[2]))
|
||||
result.update({
|
||||
"inner_x": round(inner_x, 3),
|
||||
"x_extent": round(x_extent, 3),
|
||||
"radial": round(radial, 3),
|
||||
"x_clearance": round(reference["clothing_radius"] - x_extent, 3),
|
||||
# Positive clearance means the bag's inner face is outside the body.
|
||||
"x_clearance": round(inner_x - reference["clothing_radius"], 3),
|
||||
"radial_clearance": round(reference["clothing_radius"] - radial, 3),
|
||||
})
|
||||
return result
|
||||
@@ -1917,6 +1948,7 @@ def main(argv=None):
|
||||
else:
|
||||
clip = "CLIP" if row["x_clearance"] < 0 else "clear"
|
||||
print(f" {row['name']:20s} {row['attach']:5s} {box} "
|
||||
f"inner_x={row['inner_x']:.3f} "
|
||||
f"clearance x={row['x_clearance']:+.3f} "
|
||||
f"r={row['radial_clearance']:+.3f} {clip}")
|
||||
return 0
|
||||
|
||||
@@ -57,6 +57,8 @@ class BuiltAssetTests(unittest.TestCase):
|
||||
bag = next(row for row in coat["gear"] if row["name"] == "shoulder_bag")
|
||||
self.assertEqual(bag["attach"], "back")
|
||||
self.assertGreaterEqual(bag["x_extent"], 0.2)
|
||||
self.assertGreater(bag["x_clearance"], 0)
|
||||
self.assertGreater(bag["inner_x"], coat["reference"]["clothing_radius"])
|
||||
for character in report.values():
|
||||
for row in character["gear"]:
|
||||
self.assertEqual(len(row["min"]), 3)
|
||||
|
||||
Reference in new issue
Block a user