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
@@ -21,10 +21,10 @@ live in `tools/blender_assets.py` (`_build_gear()`), the contract in `docs/blend
|
||||
Rebuilt GLB/AO: measured width **0.202 m** (0.64x head), still inside the head band.
|
||||
Also fixed `capture_realism.gd` so `--portrait-type=N` is actually parsed (it was documented
|
||||
but ignored).
|
||||
- [ ] Fix `shoulder_bag` clipping through `npc_coat`/`npc_dress`: the bag body spans `|x|`
|
||||
0.17–0.25 at the hip while the skirt flares to 0.225–0.26, so its inner half is inside the
|
||||
cloth. Move it outboard (or up onto the belt line) until the inner face clears the flare,
|
||||
and keep the strap tube outside the same radius.
|
||||
- [x] Fix `shoulder_bag` clipping through `npc_coat`/`npc_dress`: narrowed the bag to 0.06 m and
|
||||
moved it outboard (centre x=0.292); the body's inner face is now 0.262 m, clearing the coat
|
||||
(0.225) by 0.037 m and the dress (0.260) by 0.002 m. The strap's lower attach points sit on
|
||||
the same outboard line. `fit` now measures the bag body separately from the torso strap.
|
||||
- [ ] Same pass for `cap`/`beanie`/`hood`/`firefighter_helmet`: they must not clip the new
|
||||
brow/ear geometry, and `hood` must still leave the face readable for hoodie avatars.
|
||||
- [ ] Rebuild the gear (`build`) and re-bake AO (`tools/bake_ao.py`), then
|
||||
|
||||
@@ -1001,7 +1001,7 @@
|
||||
"attach": "back",
|
||||
"bounds_godot": {
|
||||
"max": [
|
||||
0.25,
|
||||
0.322,
|
||||
0.348,
|
||||
0.139
|
||||
],
|
||||
@@ -1017,7 +1017,7 @@
|
||||
],
|
||||
"path": "assets/models/blender/npc_gear/shoulder_bag.glb",
|
||||
"triangles": 188,
|
||||
"vertices": 212
|
||||
"vertices": 209
|
||||
},
|
||||
"stanchion": {
|
||||
"bounds_godot": {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 30 KiB |
+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