lecodes-assets 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +130 -6
  2. package/dist/cli.js +5358 -2149
  3. package/dist/index.js +5456 -2140
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -22,7 +22,9 @@ lecodes assets convert hero.fbx --ktx2 # + textures
22
22
  lecodes assets probe hero.fbx | hero.glb # skeleton / clips / meshes / textures / bind sanity / world size
23
23
  lecodes assets doctor hero.glb [--fix] [--ktx2] [--dump] # the old tools/glb-reduce
24
24
  lecodes assets doctor hero.glb --fix-scale # bake a unit fix (see Scale below)
25
+ lecodes assets doctor props/*.glb --lightmap-uv -o props/ # + TEXCOORD_1 lightmap UVs (xatlas) for `lecodes lightmap bake`
25
26
  lecodes assets retarget fox.glb --from wolf.fbx --map auto # clips from a DIFFERENT skeleton (map from skeleton shape)
27
+ lecodes assets avatar girl.glb --meta SK_Girl.fbx.meta # a humanoid's bones in the Unity/VRM canon (vs Unity's own map)
26
28
  lecodes assets unpack pack.unitypackage -o out [--only re …] # stream a .unitypackage: unity-index.txt + assets (+ .meta)
27
29
  lecodes assets convert Barrel_v1.FBX --list-nodes # multi-variant prop file: what is in it
28
30
  lecodes assets convert Barrel_v1.FBX --keep Barrel_v1_LD1 -o barrel.glb # one variant, re-origined on the ground
@@ -175,10 +177,111 @@ that decides quality is what the two REFERENCE poses are and what happens where
175
177
  against node space (Maya "actor" exports, ×100) is rescaled; a donor facing the other way is turned
176
178
  (facing is read from the skeleton shape, so `joint1…` rigs work too); non-uniform scales are refused.
177
179
 
178
- What it does not do yet: foot locking on the target beyond what the donor's feet do (an in-place
179
- donor still treadmills), stride warping when a leg is far too short for the donor's reach (reported
180
- as "out of reach"), torso pitch to compensate for mismatched front/hind leg ratios, bipeds/arms (the
181
- auto-map is quadruped-shaped; a humanoid map is best written by hand or by name).
180
+ - **Second pass on proportions** (humanoid plan phase 3, 2026-08-27; the quadruped path shares the
181
+ planting and the root-motion switch):
182
+ - **A humanoid foot = contact + orientation** (2026-08-27). The leg's IK effector is the ankle,
183
+ but its goal comes from the BALL of the foot (the canonical `Toes` bone): the ball goes where
184
+ the donor's ball goes (leg and foot each scaled by their own ratio, stance-corrected), the foot
185
+ keeps the donor's rotation on the target's own flat bind, and the ankle follows from the two.
186
+ Rigs put the ankle at different places over the ball — a boot's foot bone dives 56° to the ball,
187
+ X-bot's 27°, both soles flat: driving the ball with the foot as an IK link let the solver pick
188
+ the foot's pitch (50° off the donor in the swing), transferring the ankle's position AND the
189
+ foot's rotation lifted the boot 10 cm off the ground at toe-off. Now the stance foot is flat,
190
+ toe-off has the heel up and the toes on the ground, and the report says `contact <ball>` when
191
+ it differs from the effector. What the two feet's shapes cost at full extension (a few cm) is
192
+ silent; `reach` reports what the LEG cannot do.
193
+ - **Foot planting** (default; `--no-plant`). While a donor foot stands still on its ground (under
194
+ 15 % of a leg length per second, within 6 % of a leg length of its stance height, ≥ 3 frames),
195
+ the target's foot goal is held at ONE world point — the mean of where the FK would have put it
196
+ over the span, which splits the slide between landing and lift-off; a 4-frame ease at each end
197
+ hides both halves, a span across a cycle's seam is one plant held modulo the clip's travel. A
198
+ span whose mean sits more than 5 % of a leg length from some frame's FK goal is a shuffle, not a
199
+ plant, and is left alone. Per-leg ratios and the hips' own sway otherwise slide a planted foot a
200
+ few cm per step. The planted point is the contact (the ball rolls under a rising heel; the ankle
201
+ moves). Reported per leg (`planted N frames`) and per clip as spans.
202
+ - **Where the travel goes** (`--root-motion auto|hips|root|none`). A rig with a root ABOVE the hips
203
+ gets the donor's horizontal travel on the root and the vertical bob on the hips (`root`, the
204
+ default when such a root exists — a character controller reads the root's travel and must not
205
+ bob with it); `hips` puts everything on the hips as the donor had it; `none` strips the travel
206
+ for an in-place clip (feet still planted in the travelling frame, so they slide back under the
207
+ body like a treadmill). A donor whose hips bob relative to their own root (MoCap Central:
208
+ `root` travels, `pelvis` bobs) has that bob re-applied on the target's hips.
209
+ - **Hands as effectors** (`--ik hands`): the same ratio-scaled goal as a foot, measured from the
210
+ upper arm, chain capped at upper + lower arm, for clips where the hands meet or hold.
211
+ - **Twist helpers** (`--twist N`, default 0.5; `off` = ride rigidly). A helper at fraction `t`
212
+ along its limb bone takes `f·(t·twistOut − (1 − t)·twistIn)` about the bone's axis on top of
213
+ riding it — near the joint it counter-rotates the bone's own axial turn (the shoulder skin stays
214
+ with the clavicle), near the far end it follows the child's (the wrist follows the hand's
215
+ pronation). Helpers come from the avatar's `twist` map (shape classifier or `.avatar.json`).
216
+ - The stance correction inside a foot goal (where THIS rig rests its foot under its hip, against
217
+ where the donor does) is shrunk — never the donor's vector — when it would put the goal past the
218
+ leg's length (a running push-off on a rig whose hips sit higher above its feet); that removed
219
+ the "out of reach" residue on Humanoid_F.
220
+
221
+ What it does not do yet: stride warping when a leg is far too short for the donor's reach (reported
222
+ as "out of reach"), torso pitch to compensate for mismatched front/hind leg ratios; for humanoids
223
+ the knee/elbow bend-plane roll of the UPPER arm and the legs —
224
+ [docs/humanoid-retarget-plan.md](../../docs/humanoid-retarget-plan.md).
225
+
226
+ ### Humanoids — avatars
227
+
228
+ A humanoid rig is described by its **avatar**: its bones named in the Unity/VRM humanoid canon
229
+ (`Hips`, `Spine`/`Chest`/`UpperChest`, `Neck`, `Head`, `Jaw`, `LeftEye`…, `LeftShoulder`/`LeftUpperArm`/
230
+ `LeftLowerArm`/`LeftHand`, `LeftUpperLeg`/`LeftLowerLeg`/`LeftFoot`/`LeftToes`, `Left Thumb Proximal`…
231
+ — 55 bones). An avatar belongs to ONE rig; a humanoid map is two avatars joined on the canon, so
232
+ each rig is mapped once and any pair retargets.
233
+
234
+ ```
235
+ lecodes assets avatar girl.glb # read the avatar off the skeleton's shape
236
+ lecodes assets avatar girl.glb --meta SK_Girl.fbx.meta -o girl.avatar.json # check vs Unity's map, write Unity's
237
+ lecodes assets retarget girl.glb --from Swat.fbx --auto-map # both rigs' avatars from shape → map.json
238
+ lecodes assets retarget girl.glb --from Swat.fbx --avatar girl.avatar.json --map auto --clips Walking
239
+ ```
240
+
241
+ `avatar` reads the structure off the bind pose (`src/retarget/humanoid.ts`): the hips are the joint
242
+ two chains hang from that reach the ground on opposite sides; the hands are the lateral joints fanning
243
+ out into short chains (fingers) — a weapon socket or IK target on the hand is shed; the spine is the
244
+ centre path from the hips to where the arms meet; the head is the last branch above the arms (hair
245
+ rigs branch below it, so a `head` name breaks that tie); the toes say which way is forward, and so
246
+ which side is left. Spine bones: 1 → `Spine`, 2 → `+Chest`, 3 → `+UpperChest`, more → first /
247
+ arc-middle / arm-attach with the rest **bridged**; a second neck bone and finger metacarpals bridge
248
+ too. **Twist/roll helpers** (leaf children on their parent's axis — `upperarm_twist_01_l`,
249
+ `calf_twist_02_r`) are recorded as `twist: { bone: canonical }`, never mapped. Names only break ties
250
+ (thumb vs index when both are named; a `jaw` child) and the report says when they did. Everything
251
+ else (hair, cloth, `ik_*`, bag, breasts) is listed as "not part of the body" and keeps its bind.
252
+
253
+ **A person's word rides in the avatar** (`.avatar.json` `transfer`, written by the character studio,
254
+ read by `retarget --avatar`): `align: { <canonical bone>: "keep" | "align" }` — this bone is BUILT
255
+ that way (keep the bind, whatever the angle) or POSED (align to the donor, the 8° threshold
256
+ notwithstanding; a standing leg too); `roll: { Left?, Right? }` — degrees added to the measured palm
257
+ roll per arm, for a finger that lands almost right; and the transfer settings (`rootMotion`,
258
+ `plant`, `hands`, `twist`) the CLI takes as defaults — flags still win. The report lists
259
+ `under 8° kept` bones with their angle so the decision can be checked, and `avatar transfer: …`
260
+ says what the file asked for.
261
+
262
+ **The humanoid transfer** (`retarget` with both avatars — `--map auto`, `--avatar`, or a map.json
263
+ that carries `avatars`) keeps the world-delta machinery above and decides the reference by canonical
264
+ ROLE: hips, spine, neck, head, face and (standing) legs keep their bind; shoulder → upper arm →
265
+ forearm → hand and every phalanx align to the donor's bind directions with explicit references (the
266
+ hand to its finger bases), but only past an **8° threshold** — under it a mismatch is joint placement
267
+ and stays, over it is posture (an A-pose against a T-pose, a bent elbow) and turns. Direction fixes
268
+ two of three degrees of freedom; the **palm roll** — where the palm faces, the thing no direction can
269
+ see — is read off the knuckle line (index base → little base) across the forearm, and the forearm
270
+ chain turns until the target's line lies where the donor's does. Metacarpals ride the hand rigidly.
271
+ The donor is referenced to its bind (its clips are authored on it); a target `root` above the pelvis
272
+ that shares the donor's hips takes the hips' travel only. An identity retarget is exact
273
+ (`tests/humanoidRetarget.test.ts`, key reduction off); Mixamo Walking on the zoo lands arm directions
274
+ within 3° and palms within 10° on every frame.
275
+
276
+ A Unity model importer's `.meta` carries Unity's own humanoid map: `--meta` diffs the shape result
277
+ against it — the zoo in the plan agrees on every bone but the Chest/UpperChest picks on a 5-bone spine
278
+ — and `-o` with `--meta` writes Unity's map as the `.avatar.json`. `retarget --avatar` / `--donor-avatar`
279
+ accept either file; without them both rigs classify from shape (a biped↔quadruped pair is refused).
280
+
281
+ Bind space: IBMs live in the MESH's geometry space. When an exporter parks the Z-up→Y-up turn on a
282
+ geometry helper node (Female Runner's `Humanoid_F`) or ×100 on the mesh node (Maya "actor"), `rig.ts`
283
+ folds that node's world transform in front of `inv(IBM)` — kept only when it brings the bind closer to
284
+ the node rest pose.
182
285
 
183
286
  ### Donors whose clips are not in the FBX
184
287
 
@@ -195,6 +298,24 @@ lecodes assets retarget fox.glb --from Cat.fbx --map fox-from-cat.map.json --a
195
298
  Pass the donor's locomotion clips even when you do not want them in the output: `--donor-ref auto`
196
299
  takes the donor's neutral posture from a walk/idle, and without one it falls back to the bind pose.
197
300
 
301
+ **Skeleton-only clip files** — the other pack convention (Female Runner, MoCap Central, Mixamo
302
+ "without skin"): each clip is an FBX of nodes + curves and NO mesh, so it has no skin to classify
303
+ and no bind to measure from. `--donor-rig <model.fbx|.glb>` names the rig it was animated on; the
304
+ clips bind to it by bone name (exact, then without the `namespace:` prefix — `rebindClips` in
305
+ `rig.ts`) and the model's bind, skin and avatar carry the motion:
306
+
307
+ ```
308
+ lecodes assets retarget vex.glb --from MCU_af_Loco_Walk_Fwd.FBX --donor-rig rig_MCUnity.fbx --map auto -o vex-mc-walk.glb
309
+ ```
310
+
311
+ Whether a clip file IS the character's own rig (copy the tracks by name, the Mixamo flow) or only
312
+ shares its naming convention (two Unreal-style rigs — retarget) is decided by `sameSkeleton`:
313
+ ≥ 80 % of the names must match AND every matched bone ≥ 5 cm must keep its local offset's
314
+ DIRECTION (the child in its parent's frame — fixed by the rig, unlike the node rotations, which
315
+ a clip file carries posed at its first frame). A Mixamo character against another's clip stays
316
+ within ~20°; MoCap Central's rig against IdaFaber's turns the pelvis by 88° — a copied clip would
317
+ lay the character down.
318
+
198
319
  ### What a short spine cannot borrow
199
320
 
200
321
  Rotations transfer; positions follow from the TARGET's own bones. Leg IK covers that for feet, but
@@ -315,8 +436,11 @@ vendor/ufbx/ ufbx.h / ufbx.c (MIT)
315
436
  src/wasm/fbxReader.js generated single-file Emscripten module (tracked; rebuild with build-wasm.sh)
316
437
  src/fbx/ read.ts (wasm boundary + IR) · toDocument.ts · convert.ts (merge, write)
317
438
  src/textures/ resolveImage.ts (sniff, TGA/BMP decode, PNG encode, external lookup)
318
- src/doctor/ doctor.ts (analyze/apply/report) · reduceGlbBones · transcodeTextures · transcodeKtx2 · normalizeScale · validateGlb (mirror of viewer's)
319
- src/retarget/ rig.ts (hierarchy + bind pose + clip sampling) · retarget.ts (reference poses, world-delta transfer, leg IK, bridges) · automap.ts (skeleton classification + chain pairing) · unityAnim.ts (Unity .anim YAML reader)
439
+ src/doctor/ doctor.ts (analyze/apply/report) · reduceGlbBones · transcodeTextures · transcodeKtx2 · normalizeScale · lightmapUv (xatlas wasm) · validateGlb (mirror of viewer's)
440
+ src/retarget/ rig.ts (hierarchy + bind pose + clip sampling) · retarget.ts (reference poses, world-delta transfer, leg IK, bridges) · automap.ts (quadruped classification + chain pairing) · humanoid.ts (humanoid canon, biped classifier, Unity .meta avatars, avatar↔avatar map) · unityAnim.ts (Unity .anim YAML reader)
441
+ tools/humanoid-survey.ts bind-pose survey of the humanoid zoo (arm drop / elbow / facing / proportions per rig)
442
+ tools/humanoid-phase3.ts every zoo clip onto every zoo target: planted frames, reach, twist, root motion (--strips for filmstrips)
443
+ src/retarget/ghost.ts a rig's skeleton alone with one clip — the character studio's donor "ghost"
320
444
  src/unity/ unpack.ts (.unitypackage streaming) · materials.ts (.mat → glTF materials, ORM packing) · png.ts · scene.ts (.unity → level JSON)
321
445
  src/props.ts --list-nodes / --keep / --split (subtree carving + re-origin)
322
446
  src/mergeSkins.ts --merge-skins (skinned meshes onto one skeleton) · duplicateNodeNames