실제 모션캡처 데이터를 가져다 2D 캔버스 위에 사람 형상으로 그리고, 여러 클립을 끊김 없이 이어 붙여 임의 길이의 연속 동작을 만드는 시스템을 만들어줘. ■ 1. 데이터 CMU Motion Capture Database 의 BVH 를 쓴다. https://raw.githubusercontent.com/una-dinosauria/cmu-mocap/master/data/<3자리>/_.bvh (예: data/002/02_01.bvh) 클립 목록은 같은 저장소의 cmu-mocap-index-text.txt 쓰면 다음 출처 문구를 산출물에 남긴다: "The data used in this project was obtained from mocap.cs.cmu.edu. The database was created with funding from NSF EIA-0196217." ■ 2. BVH → 관절 스트림 변환기 (의존성 없는 순수 파이썬으로) - BVH 계층을 파싱하고 순운동학으로 월드 좌표를 구한다. - 관절 19개로 매핑한다: pel spine chest neck head / L_hip L_knee L_ank L_toe / R_hip R_knee R_ank R_toe / L_sho L_elb L_hnd / R_sho R_elb R_hnd ⚠ 손·발 IK 네 점으로 줄이지 마라. 렌더러가 19개를 그대로 먹는다. 네 점으로 압축하면 척추·골반 회전·무릎 방향·머리를 통째로 버리게 된다. 기어가기 같은 동작은 척추가 전부다. - 리타겟: 대퇴+정강 평균 길이를 목표 리그의 다리 길이(예: 0.87 m)에 맞춰 전체를 한 배율로 스케일. - 좌표계: CMU 는 정면이 +z 다. 목표 규약이 −z 면 yaw 180° 회전. --mirror 로 좌우 반전도 지원. - 머리 보정: BVH 의 Head 는 두개골 밑이다. 목→머리 방향으로 0.125 m 밀어 머리 중심을 만든다. - 바닥 안착: 전 프레임 최저 발목 y 를 0 으로 내리고, 첫 프레임 골반 x·z 를 원점으로. - 30 fps 로 리샘플, 좌표는 소수점 3자리. - 출력 JSON: {v, kind:"mocap", name, fps, n, order:[19개 이름], j:[n*57개 평탄배열], src} - --start --dur --fps --yaw --mirror 옵션. ■ 3. 후처리 도구 - --autoloop 자세가 가장 비슷한 두 프레임을 찾아 그 구간만 남긴다(루프 이음매의 툭 튐 제거). 짧은 구간이 무조건 이기지 않게 길이에 약한 보너스를 준다. - --inplace 골반의 수평 이동을 빼서 제자리 동작으로 만든다. 원래 이동 속도(m/s)를 결과 JSON 의 mps 에 기록한다. 나중에 반드시 필요하다. - --smoothloop N 앞뒤 N 프레임을 교차 혼합해 이음매를 더 부드럽게. ■ 4. 렌더러 — 포즈 하나를 그림으로 - 관절 19개로 뼈는 캡슐, 관절은 원으로 Path2D 에 그린다. - 몸통은 캡처의 척추 곡선을 목표 비율로 다시 샘플링해서 쓴다(골반→목 호길이의 37%, 74% 지점). 배우마다 척추 마디 위치가 달라 그대로 쓰면 가슴이 뭉툭해진다. - 깊이 정렬은 뒤쪽 팔다리 / 몸통 / 앞쪽 팔다리 3층으로. ⚠ 앞뒤 판정에 ±0.10 m 밴드를 둬라. 0 을 기준으로 나누면 옆으로 늘어뜨린 팔이 뒤로 분류돼 인물이 납작해 보인다. ⚠ 원과 캡슐의 감기 방향을 맞춰라(원은 arc(...,0,-2π,true)). 반대면 Path2D 를 한 번에 채울 때 겹치는 자리에 구멍이 뚫린다. ■ 5. 연속 동작 — 여기가 핵심 - 배우 하나를 {seq[], path[], face[]} 로 정의한다. - seq[i] = {clip, at, rate, from, to, loop, pingpong, blend} — at 은 월드 시각이다. 시각 t 에서 at ≤ t 인 마지막 항목이 현재 클립이다. - 이음새 블렌드: 다음 항목의 at 직전 blend 초(기본 0.35) 구간에서는 현재 포즈와 다음 클립의 포즈를 관절별 선형보간한다. 가중치는 smoothstep. 두 포즈를 같은 위치·같은 방향으로 놓고 섞어야 한다(월드 좌표를 그냥 섞으면 안 된다). - path = [{t,x,y,z}] — 월드 위치. 단조 3차 에르미트(Fritsch–Carlson)로 보간한다. 선형이면 웨이포인트마다 속도가 꺾이고, 일반 Catmull–Rom 은 오버슛해서 궤적이 밖으로 부푼다. - face = [{t,yaw}] — 없으면 경로 속도에서 뽑는다: yaw = atan2(-vx, -vz) (정면이 −z 규약). 반드시 지켜야 하는 네 가지: ① path 를 쓰는 클립은 반드시 --inplace 처리본. 안 그러면 클립의 이동과 경로의 이동이 겹쳐 두 배로 간다. ② 클립 rate = 경로속도 / mps. 안 맞추면 발이 지면에서 미끄러진다. mps 가 그 클립의 본래 속도다. ③ 구간마다 속도가 다르면 위상을 이어 붙인다. 경계 시각 t_b 에서 at_{k+1} = t_b − (t_b − at_k)·r_k / r_{k+1}. 이렇게 두면 다리 위상이 정확히 이어져 블렌드조차 필요 없다. 그냥 두면 경계에서 다리가 튄다. ④ yaw 는 언랩해서(계속 누적되는 값으로) 넣는다. 190 다음에 −170 을 쓰면 몸이 먼 길로 한 바퀴 돈다. 270°를 도는 동선이면 값도 270 만큼 늘어나야 한다. ■ 6. 결정성 render(t) 는 t 말고 아무것도 보지 않는다. Math.random() · 현재 시각 금지. 캐시는 파라미터 키로만 걸고 t 로는 걸지 않는다. 어느 프레임부터든 똑같이 다시 그려져야 한다. ■ 7. 검수 — 이걸 통과해야 끝난 것이다 - 걷기 클립을 직선 경로에 태워 10초: 발이 지면에서 미끄러지지 않는다. - 서기 → 걷기 → 앉기 → 서기 네 클립 이어 붙이기: 이음매에서 팔다리가 튀지 않는다. - 속도가 다른 두 구간을 이어 붙이기: 경계에서 다리 위상이 끊기지 않는다. - 방향을 270° 이상 도는 경로: 몸이 반대로 돌지 않는다. - 같은 시각을 두 번 렌더: 픽셀이 같다. ■ 쓸 때 바꿀 것 - 다리 길이 0.87 m — 그리려는 인물 비례에 맞춘다. - 정면 −z — 렌더러 규약이 다르면 --yaw 값을 바꾼다. - 30 fps — 최종 영상 프레임레이트에 맞춘다. - 클립 목록 — 필요한 동작을 CMU 인덱스에서 골라 받는다. 걷기·달리기·기어가기·살금살금· 앉았다 일어서기·점프·손 흔들기·비틀거림 같은 단독 동작과, 악수·하이파이브·부딪힘·다툼 같은 2인 동작이 따로 있다. ■ 안 되는 것 — 프롬프트를 아무리 잘 써도 - 말로 묘사한 새 동작은 합성되지 않는다. 있는 캡처를 고르고 이어 붙이는 것이다. "우아하게 넘어지는 동작"은 그런 캡처가 있어야 나온다. - 손가락·표정·옷·머리카락이 없다. 관절 19개에 그런 건 없다. - 물체와 정밀하게 맞물리는 동작(공구를 쥐고 조이기, 문고리, 악기)은 캡처에도 잘 없고 이어 붙여 만들 수도 없다. - 2인 접촉 동작은 쌍으로 캡처된 클립만 맞는다. 따로 캡처한 두 사람을 붙이면 손이 안 만난다.