feat(skirt-weights): back pin zone, seam tie ramp, ride t-gate, spatial smoothing
Wrap final recipe + pugu arc (pairs with ariki-game b8c439d6c): - SKIRT_PIN_BACK: the pin/snap/seam zone now mirrors to the BACK centerline — the back hem wedged open at full split exactly like the front did. - Seam is a RAMP (own thigh at the zone edge -> 50/50 at the centerline): a flat 50/50 band stepped 1.0->0.5 and read as a cut at full split. - SKIRT_RIDE_T_LO: keeps the belly rope pure pelvis (ride skips the top band). - SKIRT_SMOOTH_W/K: same-side kNN Laplacian weight smoothing — kills the stretch-notch triangles at the hem at run; centerline snap stays crisp, rope band skipped.
This commit is contained in:
@@ -209,6 +209,10 @@ RIDE_T_LO = _envf("SKIRT_RIDE_T_LO", 0.0)
|
|||||||
# that is already there" (Ozan: "can you just put the bone where the error
|
# that is already there" (Ozan: "can you just put the bone where the error
|
||||||
# persists"). 0 (default) = off.
|
# persists"). 0 (default) = off.
|
||||||
PIN_FRONT = _envf("SKIRT_PIN_FRONT", 0.0)
|
PIN_FRONT = _envf("SKIRT_PIN_FRONT", 0.0)
|
||||||
|
# BACK half of the pin zone (2026-08-23, wrap run-from-behind: back hem wedges
|
||||||
|
# opened at full split). Same window/snap/seam as the front, measured off the
|
||||||
|
# BACK centerline. 0 (default) = back unpinned (approved-garment safe).
|
||||||
|
PIN_BACK = _envf("SKIRT_PIN_BACK", 0.0)
|
||||||
PIN_AZ_LO = _envf("SKIRT_PIN_AZ_LO", 20.0)
|
PIN_AZ_LO = _envf("SKIRT_PIN_AZ_LO", 20.0)
|
||||||
PIN_AZ_HI = _envf("SKIRT_PIN_AZ_HI", 70.0)
|
PIN_AZ_HI = _envf("SKIRT_PIN_AZ_HI", 70.0)
|
||||||
PIN_T_LO = _envf("SKIRT_PIN_T_LO", -0.4)
|
PIN_T_LO = _envf("SKIRT_PIN_T_LO", -0.4)
|
||||||
@@ -231,6 +235,16 @@ PIN_SNAP = _envf("SKIRT_PIN_SNAP", 0.0)
|
|||||||
# between the legs, and parks at the midpoint BETWEEN them (never inside a
|
# between the legs, and parks at the midpoint BETWEEN them (never inside a
|
||||||
# leg). 2026-08-23: flat 50/50 band → ramp (its 1.0→0.5 step read as a cut).
|
# leg). 2026-08-23: flat 50/50 band → ramp (its 1.0→0.5 step read as a cut).
|
||||||
PIN_SEAM = _envf("SKIRT_PIN_SEAM", 0.0) # degrees each side of front center
|
PIN_SEAM = _envf("SKIRT_PIN_SEAM", 0.0) # degrees each side of front center
|
||||||
|
# SPATIAL WEIGHT SMOOTHING (2026-08-23, Ozan run review: "running creates too
|
||||||
|
# many small triangles at the bottom — can't we smooth those stretch
|
||||||
|
# triangles"): stretch notches are NEIGHBOURING verts with divergent weight
|
||||||
|
# vectors shearing apart mid-stride. Laplacian pass after the main loop: each
|
||||||
|
# vert blends SMOOTH_W toward the mean of its SMOOTH_K nearest rest-position
|
||||||
|
# neighbors — SAME snap side only (the centerline snap/tie discontinuity must
|
||||||
|
# survive) and the top band is skipped (rope stays pure pelvis). 0 = off
|
||||||
|
# (default; approved recipes reproduce bit-identical).
|
||||||
|
SMOOTH_W = _envf("SKIRT_SMOOTH_W", 0.0)
|
||||||
|
SMOOTH_K = int(_envf("SKIRT_SMOOTH_K", 4.0))
|
||||||
MAX_INFLUENCES = 4
|
MAX_INFLUENCES = 4
|
||||||
|
|
||||||
COMP_FMT = {5120: "b", 5121: "B", 5122: "h", 5123: "H", 5125: "I", 5126: "f"}
|
COMP_FMT = {5120: "b", 5121: "B", 5122: "h", 5123: "H", 5125: "I", 5126: "f"}
|
||||||
@@ -514,7 +528,7 @@ def main():
|
|||||||
# pants/SOLID4 branches — those pack and `continue`, so a post-pass after
|
# pants/SOLID4 branches — those pack and `continue`, so a post-pass after
|
||||||
# them never sees the hem verts (first attempt changed nothing, measured).
|
# them never sees the hem verts (first attempt changed nothing, measured).
|
||||||
pinned = False
|
pinned = False
|
||||||
if PIN_FRONT > 0.0 and tube_fwd is not None:
|
if (PIN_FRONT > 0.0 or PIN_BACK > 0.0) and tube_fwd is not None:
|
||||||
fwd_ang = math.atan2(tube_fwd[1], tube_fwd[0])
|
fwd_ang = math.atan2(tube_fwd[1], tube_fwd[0])
|
||||||
rel = abs((math.atan2(v[2] - pelvis[2], v[0] - pelvis[0])
|
rel = abs((math.atan2(v[2] - pelvis[2], v[0] - pelvis[0])
|
||||||
- fwd_ang + math.pi) % (2 * math.pi) - math.pi)
|
- fwd_ang + math.pi) % (2 * math.pi) - math.pi)
|
||||||
@@ -523,25 +537,32 @@ def main():
|
|||||||
# the left while walking"): a HARD pin boundary tears a wedge where
|
# the left while walking"): a HARD pin boundary tears a wedge where
|
||||||
# the pinned sector hugs the leg and the free midline hangs. Fade
|
# the pinned sector hugs the leg and the free midline hangs. Fade
|
||||||
# the share to 0 across PIN_AZ_FADE degrees on both sides instead.
|
# the share to 0 across PIN_AZ_FADE degrees on both sides instead.
|
||||||
|
# 2026-08-23 BACK zone (Ozan, run from behind: "the back side has
|
||||||
|
# triangles, extra stuff laying around — back of leg pushes
|
||||||
|
# cloth"): the same wedge opened at the BACK hem center at full
|
||||||
|
# split. d = angular distance to the NEAREST centerline (front or
|
||||||
|
# back); the back half pins with PIN_BACK, ties with the same seam.
|
||||||
fade = math.radians(PIN_AZ_FADE)
|
fade = math.radians(PIN_AZ_FADE)
|
||||||
strength = (smoothstep((rel - (math.radians(PIN_AZ_LO) - fade)) / fade)
|
d = rel if rel <= math.pi / 2 else math.pi - rel
|
||||||
* (1.0 - smoothstep((rel - math.radians(PIN_AZ_HI)) / fade)))
|
zone = PIN_FRONT if rel <= math.pi / 2 else PIN_BACK
|
||||||
if strength > 0.0 and PIN_T_LO <= t_rel <= PIN_T_HI:
|
strength = (smoothstep((d - (math.radians(PIN_AZ_LO) - fade)) / fade)
|
||||||
share = PIN_FRONT * strength
|
* (1.0 - smoothstep((d - math.radians(PIN_AZ_HI)) / fade)))
|
||||||
|
if strength > 0.0 and zone > 0.0 and PIN_T_LO <= t_rel <= PIN_T_HI:
|
||||||
|
share = zone * strength
|
||||||
hip_half = max(0.02, abs(origin["thigh_l"][0] - pelvis[0]))
|
hip_half = max(0.02, abs(origin["thigh_l"][0] - pelvis[0]))
|
||||||
side = max(-1.0, min(1.0, (v[0] - pelvis[0]) / hip_half))
|
side = max(-1.0, min(1.0, (v[0] - pelvis[0]) / hip_half))
|
||||||
sign_l = 1.0 if origin["thigh_l"][0] >= pelvis[0] else -1.0
|
sign_l = 1.0 if origin["thigh_l"][0] >= pelvis[0] else -1.0
|
||||||
f_l = 0.5 + 0.5 * side * sign_l
|
f_l = 0.5 + 0.5 * side * sign_l
|
||||||
if PIN_SNAP > 0.0:
|
if PIN_SNAP > 0.0:
|
||||||
f_l = smoothstep((side * sign_l + 0.05) / 0.10)
|
f_l = smoothstep((side * sign_l + 0.05) / 0.10)
|
||||||
if PIN_SEAM > 0.0 and rel < math.radians(PIN_SEAM):
|
if PIN_SEAM > 0.0 and d < math.radians(PIN_SEAM):
|
||||||
# TIE the flap ends (2026-08-23, Ozan: "a sharp cut, two
|
# TIE the flap ends (2026-08-23, Ozan: "a sharp cut, two
|
||||||
# separate lines at full split — tie those end surfaces"):
|
# separate lines at full split — tie those end surfaces"):
|
||||||
# a FLAT 50/50 seam band steps 1.0→0.5 at its boundary and
|
# a FLAT 50/50 seam band steps 1.0→0.5 at its boundary and
|
||||||
# the step reads as a cut. Ramp f_l from the flap's own
|
# the step reads as a cut. Ramp f_l from the flap's own
|
||||||
# thigh at the zone edge to 0.5 at the centerline instead —
|
# thigh at the zone edge to 0.5 at the centerline instead —
|
||||||
# the stretch distributes across the whole seam, no step.
|
# the stretch distributes across the whole seam, no step.
|
||||||
u = rel / math.radians(PIN_SEAM)
|
u = d / math.radians(PIN_SEAM)
|
||||||
f_own = 1.0 if (side * sign_l) >= 0.0 else 0.0
|
f_own = 1.0 if (side * sign_l) >= 0.0 else 0.0
|
||||||
f_l = 0.5 + (f_own - 0.5) * u
|
f_l = 0.5 + (f_own - 0.5) * u
|
||||||
w[slot["pelvis"]] = 1.0 - share
|
w[slot["pelvis"]] = 1.0 - share
|
||||||
@@ -802,6 +823,36 @@ def main():
|
|||||||
out_j.append(tuple(j for j, _ in top4))
|
out_j.append(tuple(j for j, _ in top4))
|
||||||
out_w.append(tuple(x for _, x in top4))
|
out_w.append(tuple(x for _, x in top4))
|
||||||
|
|
||||||
|
# ── spatial weight smoothing post-pass (see the SKIRT_SMOOTH_W header) ──
|
||||||
|
if SMOOTH_W > 0.0:
|
||||||
|
dense = [{j: x for j, x in zip(js, ws) if x > 0.0}
|
||||||
|
for js, ws in zip(out_j, out_w)]
|
||||||
|
side_of = [1.0 if p[0] >= pelvis[0] else -1.0 for p in pos]
|
||||||
|
t_rel_of = [(y_top - p[1]) / span if span > 1e-9 else 0.0 for p in pos]
|
||||||
|
for i in range(len(pos)):
|
||||||
|
if t_rel_of[i] < PIN_T_LO: # rope / top band: keep pure pelvis
|
||||||
|
continue
|
||||||
|
d2 = sorted(((pos[i][0] - pos[j][0]) ** 2
|
||||||
|
+ (pos[i][1] - pos[j][1]) ** 2
|
||||||
|
+ (pos[i][2] - pos[j][2]) ** 2, j)
|
||||||
|
for j in range(len(pos))
|
||||||
|
if j != i and side_of[j] == side_of[i])[:SMOOTH_K]
|
||||||
|
acc = {}
|
||||||
|
for _, j in d2:
|
||||||
|
for b, x in dense[j].items():
|
||||||
|
acc[b] = acc.get(b, 0.0) + x / SMOOTH_K
|
||||||
|
blended = {b: (1.0 - SMOOTH_W) * dense[i].get(b, 0.0) + SMOOTH_W * acc.get(b, 0.0)
|
||||||
|
for b in set(dense[i]) | set(acc)}
|
||||||
|
top4 = sorted(blended.items(), key=lambda kv: -kv[1])[:MAX_INFLUENCES]
|
||||||
|
total = sum(x for _, x in top4) or 1.0
|
||||||
|
top4 = [(j, x / total) for j, x in top4]
|
||||||
|
while len(top4) < MAX_INFLUENCES:
|
||||||
|
top4.append((0, 0.0))
|
||||||
|
out_j[i] = tuple(j for j, _ in top4)
|
||||||
|
out_w[i] = tuple(x for _, x in top4)
|
||||||
|
log(f"spatial smooth: W={SMOOTH_W} K={SMOOTH_K} "
|
||||||
|
f"(same-side only, top band skipped)")
|
||||||
|
|
||||||
# ── gates FIRST: never leave a broken garment on disk ──
|
# ── gates FIRST: never leave a broken garment on disk ──
|
||||||
n = len(pos)
|
n = len(pos)
|
||||||
log(f"waistband (pelvis only): {stats['waistband']} verts")
|
log(f"waistband (pelvis only): {stats['waistband']} verts")
|
||||||
|
|||||||
Reference in New Issue
Block a user