通用模型姿态与动画 API¶
运行时接口不要求 Model 来自某种格式,ModelData / BoneData 可以为 null。
原理见 模型子系统边界,IK、继承、固定轴和物理定义见
Skeleton Dynamics。
Model 与格式接入¶
| 接口 | 包名 | 行为 |
|---|---|---|
AnimationLibrary Model.getAnimations() |
lib.kasuga.rendering.models.uml.structure |
Model 共享的命名片段库 |
ModelPosing ModelInstance.getPosing() |
lib.kasuga.rendering.models.uml.dynamic |
延迟创建该实例的通用姿态控制器;取用本身不安装驱动 |
void ModelData.configureSkeleton(Skeleton) |
lib.kasuga.rendering.models.uml.structure.data |
构造 Model 时转换骨架能力,默认空实现 |
void ModelData.configureModel(Model) |
同上 | Model 的公共字段初始化完成后接入通用子系统,默认空实现 |
glTF Converter 将所加载的动画注册到片段库;未加载的轨道不在库中。
glTF 的 weights 轨道与 TRS 共用此入口,支持 STEP、LINEAR 和 CUBIC_SPLINE。
POSITION / NORMAL morph target 按节点实例注册为 GltfAsset.MorphId(nodeIndex, targetIndex);
同一个 mesh 的不同节点独立控制。默认权重依次取 node.weights、mesh.weights、全零,
并烘入静态几何;采样器输出相对默认值的偏移,停止播放后恢复默认形态。
权重允许为负或大于 1,组装与交叉淡入淡出保留其线性含义。自定义采样器可用
Pose.Builder.morphWeight(id, weight) 写入有限的有符号权重;morph(id, value, factor)
继续使用原有的归一化激活语义。
Blockbench Loader 将文件内的动画注册到同一个库。两种格式的未命名片段使用 animation_<文件索引>。
VMD/VPD、通用 AnimationClip 或自定义格式使用 sampler/data 接入,不需要新建模型类型。
骨骼名、morph 标识、材质引用仍须与目标模型匹配;此接口不自动重定向不同拓扑的骨架。
AnimationLibrary¶
包:lib.kasuga.rendering.models.uml.dynamic.animation。
new AnimationLibrary();
<T> AnimationLibrary.Clip<T> register(String name, AnimationSampler<T> sampler, T data);
AnimationLibrary.Clip<?> get(String name);
boolean contains(String name);
Map<String, AnimationLibrary.Clip<?>> clips();
new AnimationLibrary.Clip<T>(String name, AnimationSampler<T> sampler, T data);
float AnimationLibrary.Clip.duration();
Pose AnimationLibrary.Clip.sample(float seconds);
Pose AnimationLibrary.Clip.sample(float seconds, Namespace namespace);
AnimationLibrary.Clip<Pose> AnimationLibrary.pose(Pose pose);
register 的名称非空且不可全为空白,同名注册替换已有绑定;get 未命中返回 null。
clips() 返回不可修改的视图。构建/加载时注册,完成注册后再创建播放器;注册不是线程安全操作。
sampler/data 非 null;外部 data 的不可变性由接入方保证。库不复制原始动画,也不改写其插值方式。
片段的时间和时长为秒,实际启动播放时由公共播放器检查时长是否有限。
AnimationLibrary.SAMPLER 是供 AnimationPlayer/FSM 使用的公共片段适配器。
FSM 的公式 Namespace 透传给原 sampler;命名绑定不会丢失公式上下文。
pose 生成未命名、时长为 0 的常量片段。
import lib.kasuga.rendering.models.uml.dynamic.ModelInstance;
import lib.kasuga.rendering.models.uml.dynamic.fsm.Pose;
import lib.kasuga.rendering.models.uml.math.Transform;
import lib.kasuga.rendering.models.uml.typo.miku_miku_dance.vmd.VmdSampler;
import lib.kasuga.rendering.models.uml.typo.miku_miku_dance.vpd.VpdSampler;
// model 是任意来源的 Model;motion / vpd 是对应 Reader 已读取的数据。
model.getAnimations().register("dance", new VmdSampler(), motion);
model.getAnimations().register("stance", VpdSampler.INSTANCE, vpd);
ModelInstance instance = new ModelInstance(model, null, null, null, null, null);
instance.getPosing().play("dance", true);
// 静态姿态也可以直接使用通用 Pose。
instance.getPosing().pose(new Pose.Builder()
.bone("head", new Transform(), lib.kasuga.rendering.models.uml.dynamic.fsm.ApplyMode.REPLACE)
.ikEnabled("left-arm", false).build());
ModelPosing¶
包:lib.kasuga.rendering.models.uml.dynamic;实现 PoseDriver。
| 方法 | 行为 |
|---|---|
new ModelPosing(ModelInstance instance) |
通常使用 instance.getPosing();不检查模型格式 |
boolean hasClip(String name) |
查询模型片段库 |
String currentClip() |
当前片段名称;静态姿态/停止后为 null |
boolean play(String name, boolean loop) |
从头播放并安装为实例驱动;缺失名称返回 false,保留当前驱动 |
boolean follow(String name, AnimationTimeline timeline) |
跟随外部时间轴并安装驱动;不重复推进共享时钟 |
void pose(Pose pose) |
安装驱动并持续采样常量姿态;pose 不可为 null |
void stop() |
下一次 render sample 将该驱动写过的 bone/morph/IK 通道恢复默认;材质帧保留 |
void pause(), void resume() |
暂停/恢复播放时钟 |
void seek(float seconds) |
跳转到有限、非负秒数 |
void setSpeed(float speed) |
有限、非负倍率;0 冻结时钟 |
boolean isPlaying() |
时钟是否继续推进;静态姿态仍持续采样 |
float currentTime() |
当前时钟秒数;循环时未取模 |
AnimationTimeline timeline() |
当前时间轴,供其他目标跟随 |
void tick(float dt) |
通常由 ModelInstance.animate(dt) 推进 |
void sample(float partialTick) |
通常由实例渲染采样入口调用 |
每实例独立持有播放器、时间轴和 PoseSink;静态姿态、命名片段切换复用同一个 sink,
因此前一姿态中消失的通道会自动清除。播放控制和 tick 串行执行,render sample 消费公共播放器
发布的时钟快照。stop 的重置在采样阶段执行,不能代替采样或实例更新。
当前 PoseDriver 仍是单个槽位;安装 ModelPosing 会替换当前驱动。
公共驱动实现 RebindablePoseDriver,资源切换与时钟保留见 输入迁移。
需要动画分层时,用 FSM 的 State.clip(AnimationLibrary.SAMPLER, clip, loop) 和 State.pose(Pose)。
Pose 的 IK 通道¶
包:lib.kasuga.rendering.models.uml.dynamic.fsm。
new Pose(Map<Object, Pose.Morph> morphs, Map<String, Pose.Bone> bones,
Map<Object, Pose.Frame> frames, Map<String, Boolean> ikEnabled);
Map<String, Boolean> Pose.ikEnabled();
Pose.Builder Pose.Builder.ikEnabled(String chain, boolean enabled);
State<Owner> State.ikEnabled(String chain, boolean enabled);
boolean SkeletonInstance.clearIkEnabled(String chain);
原来的三参数 Pose 构造方法保留,IK map 默认为空。map 的键为 IK 链名,非模型格式或文件骨索引。
VmdSampler 自动把 property track 的 IK 状态写入 Pose,无需在播放后另行写骨架。
FSM JSON 的 pose.ik_enabled 为可选的名称到布尔值的 object,默认 {}。
开关在 cross fade 的 alpha 小于 0.5 时取前一端,否则取后一端;单边通道保留该边的值。
与材质帧一样,BASE/ADDITIVE 使用最后一个值,OVERRIDE 优先;BoneMask 只作用于骨骼变换,
不屏蔽 IK 链开关。PoseSink 忽略未知链;通道消失时只清除自身写过的链,其他手动开关保留。
clearIkEnabled 恢复该链默认开启状态,有显式状态被移除时返回 true。
名称迁移¶
| 原接口 | 当前接口 |
|---|---|
GltfAnimationPoseDriver |
ModelInstance.getPosing() / 通用 ModelPosing |
MmdRagdoll |
lib.kasuga.rendering.models.uml.dynamic.physics.SkeletonRagdoll |
MmdPhysicsScene |
同包的 ModelPhysicsScene |
MmdPhysicsClusterManager |
同包的 ModelPhysicsClusterManager |
SkeletonInstance.getPmxBones() |
格式 Reader 自行维护文件索引映射;实例仅公开通用 Skeleton/Bone |
这些格式专用运行时入口已移除。物理接口的参数和能力保留,引用旧类型的调用方需更新 imports 并重新编译。glTF 的 STEP/LINEAR/CUBIC_SPLINE 继续由 GltfSampler 精确采样。