跳转到内容
查看源码

animation自动生成

分类:包 · 清单:use "kernels/";

骨骼、驱动它的动画片段与跟随的网格:片段采样与混合、行走、瞄准、蒙皮。

A skeleton, the clips that pose it and the mesh that follows: sampling and blending clips, walking, aiming, and skinning by the joint palette.

Layer: Simulation. Uses: geometry, mesh.

use "animation.entpkg";
  • kernels/aim.ent — Aiming. Kernels: animation.aim@1.
  • kernels/clip.ent — Clips. Kernels: animation.clock@1, animation.sample@1, animation.blend@1, animation.add@1.
  • kernels/gait.ent — Walking. Kernels: animation.stride@1, animation.travel@1.
  • kernels/skeleton.ent — A skeleton, the clips that pose it, and the mesh that follows.
  • kernels/skin.ent — Skinning. Kernels: animation.palette@1, animation.apply@1, animation.place@1.
  • graphs/pose.esn — One clip playing on one skeleton, as one node.

tests/ holds scenes and their replays, run on every backend: aim.replay, clips.replay. Every Kernel of the package is reached by one of them.

Used by the animation, voxel examples.

Kernel 执行模式 简述
animation.add@1 map A pose laid over another: what add does differently from rest is done to base, by weight. A clip authored as a difference — a flinch, a lean, a breath — plays over whatever the body is already doing, which is what a game blends on top of a walk.
animation.aim@1 map One joint turned to face a point, over whatever the poses already say: a head that follows what it looks at, an arm that reaches, a gun that tracks. The joint’s axis, in its own space (a bone from Blender runs along its y), is turned the shortest way onto the target, and that turn is laid over the pose the joint already had, so the twist the clip gives it stays; weight blends between the two. Every other joint passes through, so this sits between sampling and the palette and the chain below the joint follows it.
animation.apply@1 map The mesh in the pose the palette says: four joints a vertex, weighted. A node sized instances * count(mesh) poses a crowd out of one mesh, each instance reading its own block of the palette.
animation.blend@1 map Two poses of one skeleton, blended: a walk into a run, a turn laid over either. A Kernel or the graph decides mix, so it can move every frame.
animation.clock@1 map The clock a clip is read at, as a row of one second: the simple case, where a clip plays at its own speed from the start of the session.
animation.palette@1 map The palette: each joint’s local transform composed up the parent chain and taken through its inverse bind, which is the matrix a vertex bound to that joint is moved by.
animation.place@1 map Where each instance stands: one Pose a instance, applied to the mesh it posed. A crowd is this over animation.apply@1’s output.
animation.sample@1 map Every joint’s local transform at the moment the clock says, blended between the two frames it falls between. A clip ends where its rows end: repeat wraps round to the start, and otherwise the last frame holds.
animation.stride@1 map A traveling clip played in place: every joint as animation.sample@1 would pose it, looping every frames - 1 frames as the clip closes on itself, and the root with its travel along the ground axes taken out. What stays in the root is what is not travel — a walk’s bob, its sway on an axis ground leaves out — and the travel is what animation.travel@1 gives the scene to carry.
animation.travel@1 map How far a traveling clip’s root moved between two moments, was and at, one row an instance: the travel animation.stride@1 took out of the pose, for the scene to add to where the body stands. It is the difference of where the root had got to at each, so a scene that adds it every evaluation stands where the clip says whatever its frames are, loops and all.
函数 简述
standing(poses: Rows<Pose>, skeleton: Skeleton, block: Int, joint: Int, depth: Int) -> Mat4
traveled(clip: Clip, joints: Int, joint: Int, moment: Float, ground: Vec3) -> Vec3
posing(p: Pose) -> Mat4
mixing(a: Pose, b: Pose, t: Float) -> Pose
名称 写法
Walk struct Walk { matrix: Mat4; joint: Int; }
Joint struct Joint { parent: Int; bind: Mat4; }
Pose struct Pose { at: Vec3; turn: Vec4; grow: Vec3; }
Bound struct Bound { joints: [Int; 4]; weights: Vec4; }
Skeleton type Skeleton = Rows<Joint>;
Clip type Clip = Rows<Pose>;
Palette type Palette = Rows<Mat4>;
名称 接口 说明
pose input mesh: Rows<MeshPoint>
input weights: Rows<Bound>
input skeleton: Skeleton
input clip: Clip
input at: Rows<Float>
param rate: Float = 30.0
param repeat: Bool = true
param depth: Int = 64
output result <- posed.result
output palette <- palette.result
One clip playing on one skeleton, as one node. Place it with node posed: graph “animation.entpkg/graphs/pose.esn” { mesh <- mesh; weights <- weights; skeleton <- skeleton; clip <- walk; at <- clock.result; rate = 30.0; } and hand posed.result to whatever projects it. Blending two clips wants the Kernels themselves: sample each, animation.blend@1, then animation.palette@1.

A pose laid over another: what add does differently from rest is done to base, by weight. A clip authored as a difference — a flinch, a lean, a breath — plays over whatever the body is already doing, which is what a game blends on top of a walk.

rest is the frame the difference is measured from, which is the additive clip’s own first frame: sample it at zero.

执行模式 map
需要能力 field.read, field.write
输入 base: Rows<Pose>, add: Rows<Pose>, rest: Rows<Pose>, weight: Rows<Float>
参数 —
输出 result: Rows<Pose>

One joint turned to face a point, over whatever the poses already say: a head that follows what it looks at, an arm that reaches, a gun that tracks. The joint’s axis, in its own space (a bone from Blender runs along its y), is turned the shortest way onto the target, and that turn is laid over the pose the joint already had, so the twist the clip gives it stays; weight blends between the two. Every other joint passes through, so this sits between sampling and the palette and the chain below the joint follows it.

target is in the skin’s space, one row an instance: a crowd sized instances * count(skeleton) has each instance look at its own.

执行模式 map
需要能力 field.read, field.write
输入 poses: Rows<Pose>, skeleton: Rows<Joint>, target: Rows<Vec3>, weight: Rows<Float>
参数 joint: Int = 0, axis: Vec3 = [0.0, 1.0, 0.0], depth: Int = 64
输出 result: Rows<Pose>
源码 packages/animation.entpkg/kernels/aim.ent

The mesh in the pose the palette says: four joints a vertex, weighted. A node sized instances * count(mesh) poses a crowd out of one mesh, each instance reading its own block of the palette.

A vertex bound to nothing — all four weights zero, which is what a model that mixes skinned and unskinned meshes writes — is left where it is.

执行模式 map
需要能力 field.read, field.write
输入 mesh: Rows<MeshPoint>, weights: Rows<Bound>, palette: Rows<Mat4>, skeleton: Rows<Joint>
参数 —
输出 result: Rows<MeshPoint>

Two poses of one skeleton, blended: a walk into a run, a turn laid over either. A Kernel or the graph decides mix, so it can move every frame.

mix of one row is the whole skeleton. A row a joint is a mask: an upper body running while the legs walk is the same blend with ones down the arms and zeros down the legs.

执行模式 map
需要能力 field.read, field.write
输入 first: Rows<Pose>, second: Rows<Pose>, mix: Rows<Float>
参数 —
输出 result: Rows<Pose>

The clock a clip is read at, as a row of one second: the simple case, where a clip plays at its own speed from the start of the session.

执行模式 map
需要能力 field.write, time.read
输入 —
参数 speed: Float = 1.0
输出 result: Rows<Float>

The palette: each joint’s local transform composed up the parent chain and taken through its inverse bind, which is the matrix a vertex bound to that joint is moved by.

执行模式 map
需要能力 field.read, field.write
输入 poses: Rows<Pose>, skeleton: Rows<Joint>
参数 depth: Int = 64
输出 result: Rows<Mat4>

Where each instance stands: one Pose a instance, applied to the mesh it posed. A crowd is this over animation.apply@1’s output.

执行模式 map
需要能力 field.read, field.write
输入 mesh: Rows<MeshPoint>, places: Rows<Pose>, one: Rows<MeshPoint>
参数 —
输出 result: Rows<MeshPoint>

Every joint’s local transform at the moment the clock says, blended between the two frames it falls between. A clip ends where its rows end: repeat wraps round to the start, and otherwise the last frame holds.

A node sized instances * count(skeleton) poses a crowd instead of one: element e is joint e % joints of instance e / joints, and each instance reads its own row of at, so a hundred of them walk the same clip out of step. Sized for one — which is what a node with no domain of its own is — it is the same Kernel doing the same thing.

执行模式 map
需要能力 field.read, field.write
输入 skeleton: Rows<Joint>, clip: Rows<Pose>, at: Rows<Float>
参数 rate: Float = 30.0, repeat: Bool = True
输出 result: Rows<Pose>

A traveling clip played in place: every joint as animation.sample@1 would pose it, looping every frames - 1 frames as the clip closes on itself, and the root with its travel along the ground axes taken out. What stays in the root is what is not travel — a walk’s bob, its sway on an axis ground leaves out — and the travel is what animation.travel@1 gives the scene to carry.

执行模式 map
需要能力 field.read, field.write
输入 skeleton: Rows<Joint>, clip: Rows<Pose>, at: Rows<Float>
参数 rate: Float = 30.0, joint: Int = 0, ground: Vec3 = [1.0, 0.0, 1.0]
输出 result: Rows<Pose>
源码 packages/animation.entpkg/kernels/gait.ent

How far a traveling clip’s root moved between two moments, was and at, one row an instance: the travel animation.stride@1 took out of the pose, for the scene to add to where the body stands. It is the difference of where the root had got to at each, so a scene that adds it every evaluation stands where the clip says whatever its frames are, loops and all.

执行模式 map
需要能力 field.read, field.write
输入 skeleton: Rows<Joint>, clip: Rows<Pose>, at: Rows<Float>, was: Rows<Float>
参数 rate: Float = 30.0, joint: Int = 0, ground: Vec3 = [1.0, 0.0, 1.0]
输出 result: Rows<Vec3>
源码 packages/animation.entpkg/kernels/gait.ent

aim.replay, clips.replay