Skip to content

Commit a45f272

Browse files
committed
Traktor: Updated skills for skinned animated meshes.
1 parent d75b2c9 commit a45f272

3 files changed

Lines changed: 71 additions & 30 deletions

File tree

data/Source/System/Skills/Create Animated Character.xdi

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,10 @@
22
<object type="traktor.mcp.SkillAsset">
33
<name>create-animated-character</name>
44
<description>Build a skinned, animated character entity (skeleton, weights, clips, state graph, CharacterComponent).</description>
5-
<whenToUse><![CDATA[Asked to create a playable/animated character or creature with a skeleton and animations.]]></whenToUse>
5+
<whenToUse><![CDATA[Asked to create a playable/animated character or creature with a skeleton and animations. To EDIT or extend an existing rig instead — add joints (forearms, hands, feet), re-skin part of the mesh, or add joints to existing animation takes — use the edit-skinned-rig skill.]]></whenToUse>
66
<body><![CDATA[# Create a skinned, animated character entity
77
8-
Build a skinned character ("{{name}}", colour "{{color}}", ~{{height}} m tall) with a skeleton, vertex weights, animation clips, an optional Idle↔Walk state graph, and a physics `CharacterComponent`.
9-
10-
## Pipeline
11-
model `.tmd` (geometry + joints + weights + animations) → `SkeletonAsset` + one `AnimationAsset` per clip → runtime Skeleton/Animation. Entity = `SkeletonComponentData` + `AnimatedMeshComponentData` + `CharacterComponentData` (+ a state-graph pose controller).
12-
13-
## Steps
14-
1. **One model carries geometry, skeleton and animations** — call `create_mesh_from_geometry` with:
15-
- `meshType: "skinned"`
16-
- `joints`: `[{name, parent (index; -1 = root), translation (LOCAL = global − parent-global), rotation [0,0,0,1], length}]`
17-
- `jointIndices`: per-position joint index (rigid single-bone bind, weight 1)
18-
- `animations`: `[{name, keyframes:[{time, pose:[[tx,ty,tz,qx,qy,qz,qw] × jointCount]}]}]`
19-
- `material`, `triangulate: true`. The returned guid is stable across re-runs.
20-
2. Give it colour: bind a simple/unlit material with `set_mesh_material_shader` and set its albedo, or pass `maps` for textures.
21-
3. `build_asset` the mesh.
22-
4. **Animation assets**: create one `traktor.animation.SkeletonAsset` (`fileName` → the `.tmd`) and one `traktor.animation.AnimationAsset` per clip (`fileName` → `.tmd`, `take` = clip name; empty `take` = clip 0). `build_asset` each.
23-
5. **Entity components**:
24-
- `traktor.animation.SkeletonComponentData { skeleton: <SkeletonAsset guid>, poseController }`
25-
- `traktor.animation.AnimatedMeshComponentData { mesh: <mesh guid> }`
26-
- `traktor.physics.CharacterComponentData { radius, height, step, jumpImpulse, ... }`
27-
6. **Idle/Walk state graph** (optional): a `traktor.animation.StateGraph` with `StateNodeAnimation` states (`animation` = AnimationAsset guid), `StateTransition`s (`condition` string, leading `!` inverts; `moment` = "Immediately"), and `rootState`. Author it with **`import_instance_from_xml`** (see Gotchas). `build_asset` → `RtStateGraph`. Point the pose controller at it: `traktor.animation.AnimationGraphPoseControllerData { stateGraph: <StateGraph guid> }`.
28-
29-
## Gotchas (hard-won)
30-
- **Winding**: Traktor's front face is **clockwise** — the right-handed face normal points *opposite* the outward surface normal. Auto-orient every quad: if `dot(cross(p1−p0, p2−p0), outwardNormal) > 0`, reverse the vertex order.
31-
- **Weights**: the skinned converter keeps the top-4 influences and normalizes them (a single-bone vertex is left unnormalized = 1). Use rigid single-bone binds unless you need blending.
32-
- **Every keyframe must set EVERY joint**: `model::Pose` returns identity for any joint absent from a keyframe, so a non-moving joint must still carry its **bind** local transform in each keyframe or it snaps to the origin.
33-
- **Drive the graph from script with `poseController:setParameterValue(name, bool)`** — `setState(...)` is a no-op stub. For script setup (`import(traktor)`, passing component types directly to `getComponent`), see the *traktor-lua-scripting* skill.
34-
- A `"moving"` transition and a `"!moving"` transition share one boolean parameter `moving`.
35-
- The editor state-graph view relies on shared object identity (`ref=` ids); only `import_instance_from_xml` produces those — `set_instance_member` leaves the graph unwired.
36-
]]></body>
8+
Build a skinned character ("{{name}}", colour "{{color}}", ~{{height}} m tall) with a skeleton, vertex weights, animation clips, an optional Idle↔Walk state graph, and a physics `CharacterComponent`.]]></body>
379
<engineVersion/>
3810
<published>true</published>
3911
<parameters>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<object type="traktor.mcp.SkillAsset">
3+
<name>edit-skinned-rig</name>
4+
<description>Add or modify joints, geometry, skin weights and animation-take poses on an existing skinned character in place, without regenerating it.</description>
5+
<whenToUse><![CDATA[Asked to extend or edit an existing skinned/animated character or creature — add joints (e.g. forearms, hands, feet), re-skin part of the mesh, or add joints to existing animation takes — while preserving the current geometry and animation clips. For building a character from scratch, use create-animated-character instead.]]></whenToUse>
6+
<body><![CDATA[# Edit an existing skinned rig in place
7+
8+
Extend or modify the skinned character "{{mesh}}" — add joints, re-skin geometry, and/or add joints to its animation takes — **without regenerating it**, so existing geometry and clips are preserved. This is the in-place counterpart to *create-animated-character* (use that to build one from scratch); it relies on the `model_*` tools, which read/write an existing `.tmd` directly.
9+
10+
## Golden rule
11+
Edit the model **in place** (open → mutate → save `overwriteSource`). Do NOT rebuild via `create_mesh_from_geometry`: that path cannot read back existing keyframe poses, so it silently destroys hand-made animation. Only the `model_*` tools can extend a rig non-destructively.
12+
13+
## Pipeline
14+
`model_open {meshAsset}` → `model_get_elements` (read) / `model_edit` (mutate) / `model_apply_operation` → `model_save {overwriteSource}` → `build_asset` the mesh + SkeletonAsset + every AnimationAsset.
15+
16+
## 1. Open and map the existing rig
17+
- `model_open { "meshAsset": "{{mesh}}" }` → `handle`. `model_inspect` for counts + joint/animation lists.
18+
- `model_get_elements` kinds `joints`, `positions`, `vertices`, `polygons`, and `pose` (needs `animation`+`keyFrame`). A box-man is **one rigid box per joint**: 24 vertices (6 faces × 4) + 6 quads, each vertex weight 1.0 to a single joint. Box *k* usually occupies positions/vertices `24k..24k+23` and polygons `12k..12k+11` (verify).
19+
20+
## 2. Add joints (`model_edit` `addJoint`)
21+
- Joints are **appended** at the end (indices keep growing); existing pose/vertex data indexed 0..n-1 stays valid.
22+
- `translation` is **LOCAL** = `worldChild − worldParent`; `rotation` `[0,0,0,1]`; `parent` = an existing joint index.
23+
- Make new joints **children of the joints already animated** (forearm→upper-arm, foot→shin) so existing takes still carry them along — *after* you key them (step 4).
24+
25+
## 3. Re-skin so new joints deform the mesh (optional)
26+
Rigid box-man workflow, all additive:
27+
- `removePolygons {indices}` for the box(es) you're replacing. Orphaned positions/vertices are harmless (unreferenced = not drawn).
28+
- Append new boxes: `addPosition` (8 corners), `addNormal` (reuse 6 face normals ±X/±Y/±Z), `addVertex` (24: `position`,`normal`,`jointInfluences:[{joint,weight:1.0}]`), `addPolygon` (6 quads, `material:0`, `normal:-1`).
29+
- **Winding**: Traktor's front face is **clockwise seen from outside** — the triangle's right-hand normal must point *inward*. Auto-orient each face: if `dot(cross(p1−p0, p2−p0), outwardNormal) > 0`, reverse the vertex order.
30+
- Polygons store `normal:-1`; lighting uses per-vertex normals, so give each vertex the true **outward** normal.
31+
- `model_apply_operation` `Triangulate` after adding quads.
32+
- Indices are deterministic (appends start at the current counts). Add in batches positions→normals→vertices→polygons and check the returned counts between batches; generate the arrays with a script to avoid index/winding mistakes.
33+
34+
## 4. Add joints to the animation takes — REQUIRED if you added joints
35+
**Every keyframe must carry an entry for every joint.** A joint absent from a keyframe is baked as **identity**, not bind — verified in engine source (`Model/Pose.cpp` `getJointTransform` returns `Transform::identity()`; `AnimationPipeline.cpp` bakes `pose.getJointTransform(k)` per skeleton joint). Identity local = zero offset from parent, so the limb **collapses onto its parent's origin** at runtime.
36+
- For each take and each keyframe, `model_edit` `setPoseJoint {animation, keyFrame, joint, translation, rotation}` for each new joint, in **ascending joint order** (the pose is a dense array indexed by joint id; ascending appends fill it with no gaps).
37+
- For a new joint that shouldn't move on its own, pass its **bind local** transform (the joint's `translation`, rotation `[0,0,0,1]`). Motion then comes entirely from the animated parent. To actually articulate it, key real rotations instead.
38+
- `setPoseJoint` only edits the named joint, so joints 0..n-1 keep their exact curves. Read a pose back with `model_get_elements kind=pose` to confirm (`total` should reach the skeleton joint count).
39+
40+
## 5. Verify, then commit
41+
- Save to a **throwaway** asset first: `model_save { meshAsset: { path:"Models/X_check", fileName:"Models/X_check.tmd", meshType:"skinned" } }` → `build_asset` → `render_mesh_preview` to check shape/winding (this renders the **bind pose** only).
42+
- When good, `model_save { overwriteSource: true }` to write the real `.tmd`. Delete the temp instance (`delete_instance`) and its file.
43+
44+
## 6. Rebuild
45+
`build_asset` (rebuild) the mesh, the `SkeletonAsset`, and **each** `AnimationAsset` — they all import from the `.tmd`, so new joints and pose entries only reach runtime after rebuilding.
46+
47+
## Gotchas (hard-won)
48+
- **Absent pose joint → identity → limb snaps to origin** (see step 4). This is the #1 trap; render preview won't catch it because it only shows bind pose.
49+
- **Skeleton↔take matching is by joint NAME** in the pipeline (`findJointIndex`); a rename or typo logs `No such joint` and drops it. Keep names stable and consistent between skeleton and takes.
50+
- **No `removeAnimation`/`removeKeyFrame`** in the Model API — you can only add/edit poses and keyframes.
51+
- **Winding/weights** as in step 3 — same rules as *create-animated-character*.
52+
- `render_mesh_preview` needs a MeshAsset and shows only the static bind pose; there is no MCP tool to render an animated frame, and `Traktor.Run.App` does not link the animation/database classes — so runtime animation must be checked by launching the game from the editor.
53+
]]></body>
54+
<engineVersion/>
55+
<published>true</published>
56+
<parameters>
57+
<item type="traktor.mcp.SkillParameter">
58+
<name>mesh</name>
59+
<description>MeshAsset path or guid of the character to edit</description>
60+
<defaultValue>Models/Humanoid</defaultValue>
61+
</item>
62+
</parameters>
63+
</object>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<object type="traktor.db.LocalInstanceMeta" version="2">
3+
<guid>{CF00F128-A47D-45C2-9E3B-12DE334E00FB}</guid>
4+
<primaryType>traktor.mcp.SkillAsset</primaryType>
5+
<blobs/>
6+
</object>

0 commit comments

Comments
 (0)