跳转至

Minecraft 穿搭资源与实例切换 API

通用几何/骨架组装见 Model Assembly。本页只描述 Minecraft 的资源查找、 发布和句柄接入,Model 及其公共子系统没有模型格式白名单。

资源包定义

文件放在 assets/<namespace>/model_assemblies/<path>.json,定义 ID 为 <namespace>:<path>。 例如 assets/example/model_assemblies/winter.json 对应 example:winter。 来源模型仍需通过已有 model_proxy 配置预加载,见 模型加载。

{
  "body": {
    "id": "body",
    "model": "example:models/character.bbmodel",
    "regions": { "torso": [0, 1, 2] },
    "hide_regions": ["torso"]
  },
  "parts": [
    {
      "id": "shirt",
      "model": "example:models/shirt.glb",
      "bone_mappings": { "shirt_root": "root" }
    },
    {
      "id": "coat",
      "model": "example:models/coat.mmd.zip",
      "model_name": "coat",
      "include_dynamics": true
    }
  ],
  "bind_tolerance": 0.0001
}
字段 默认/要求
body 必需的组件 object,id 默认 body
parts 可选组件数组,保持顺序;每项 id 必需且不可重复
model 每组件必需的资源位置;路由复用已注册模型管线
model_name 可选内部模型名,供需要内部 entry 查找的 Reader 使用
bone_mappings 源骨名到输出骨名的 object,默认 {};身体不可设置别名
regions 区域名到源面索引数组的 object,默认 {}
hide_regions 隐藏区域名数组,默认 [],未知名称报错
hide_meshes 附加隐藏面索引数组,默认 [];索引须为整数
match_body_bones boolean,默认 true
include_dynamics boolean,默认 true
bind_tolerance 有限正数,默认 1e-4

定义先在资源准备阶段读取;纹理完成后在 game thread 发布。错误 JSON 会使该次准备失败, 不会发布半份穿搭定义。来源缺失时定义仍可注册,但 mount 返回 false,下一 tick 重试。 绑定矩阵、隐藏面范围等依赖实际模型的校验在完整来源就绪后执行。 身体与衣服须已在同一绑定空间;这不是自动体型拟合接口。

McModelHandle

包:lib.kasuga.rendering.models.mc.api。

McModelHandle handle = McModelHandle.ofAssembly(
        ResourceLocation.parse("example:winter"), instanceId, entity.position());
if (handle.mount()) {
    handle.scheduleVanillaRenderer();
    handle.markRenderedThisFrame();
}

boolean switched = handle.switchAssembly(ResourceLocation.parse("example:summer"));
方法 行为
static McModelHandle ofAssembly(ResourceLocation id, ResourceLocation instanceId, @Nullable Vec3 pos) 使用全局组装服务,允许早于资源发布创建
boolean switchAssembly(ResourceLocation id) 使用全局服务换装;服务或来源未就绪时返回 false
boolean switchAssembly(McModelAssemblies service, ResourceLocation id) 显式指定服务,适合自定义管线
McModelHandle onInstanceChanged(BiConsumer<ModelInstance, ModelInstance> callback) 新实例完成基础状态恢复后调用,可安装自定义驱动/物理;null 清除回调

换装先解析完整来源、取得缓存模型、挂载新实例,再释放该句柄的旧实例。 来源未就绪时不改变当前穿搭;绑定/回调异常会清理新实例并向调用方抛出,旧实例保留。 回调在公共驱动迁移之前执行,设置的新驱动优先;回调自身的外部副作用由调用方负责。

位置、旋转、缩放与 double 浮动原点、可见性模式、距离限制和 ambient multiplier 保留。 兼容名称的骨骼局部输入、morph 激活值、IK 开关与持久目标复制到新实例。 临时逐帧 IK 目标、物理模拟结果、tick-loop 自定义模块与锚点回调不复制;在回调中重新配置。 手动材质帧如需按组件重映射,也在回调中处理。

公共 ModelPosing、AnimationPlayer、FsmPoseDriver 实现 RebindablePoseDriver。 命名 ModelPosing 会从新 Model 的片段库刷新数据,并保留原时间轴、播放时间、速度和暂停状态; 缺失片段恢复空姿态。外部共享时钟不被重启,跟随该播放器自有时钟的其他目标也保留同一时钟。 普通 AnimationPlayer/FSM 的 rebind 迁移写入目标;需要更新其外部片段数据时由回调接入。 自定义驱动实现同一接口或在回调中创建新驱动。

源模型替换、删除或穿搭资源重载会撤掉借用旧资源的实例。句柄保留基础状态,随后 mount() 自动重新解析并挂载;注册表中的穿搭定义在来源暂缺期间保留。 destroy() 回收当前实例及发布订阅,释放句柄持有的待恢复状态。 全局 Actor 的 instanceId 应由宿主唯一管理;同一服务不应让两个句柄同时管理同一穿搭/instanceId。

程序注册与自定义服务

import lib.kasuga.rendering.models.uml.loaders.assembly.ModelAssemblyDefinition;
import lib.kasuga.rendering.models.mc.api.McModelAssemblies;
import lib.kasuga.rendering.models.mc.registry.PipelineRegistry;

var definition = ModelAssemblyDefinition.builder("body",
        new McModelAssemblies.Reference(bodyLocation))
        .part("shirt", new McModelAssemblies.Reference(shirtLocation),
                part -> part.mapBone("shirt_root", "root"))
        .build();
var service = PipelineRegistry.assemblies(); // 客户端内置管线初始化前为 null
if (service != null) service.register(outfitId, definition);

PipelineRegistry.ASSEMBLY 的值为 assembly,其管线没有格式 Reader,接受派生公共 Model。 普通 of(modelLoc, ...) 继续按文件扩展名路由;穿搭定义使用 ofAssembly,不需要伪造文件扩展名。

McModelAssemblies 完整接口:

new McModelAssemblies(
    ModelPipeLine<?, ?, ResourceLocation, ResourceLocation, ?> output,
    Function<McModelAssemblies.Reference, McModelAssemblies.PublishedSource> sources,
    ModelAssemblyCache cache);

new McModelAssemblies.Reference(ResourceLocation model);
new McModelAssemblies.Reference(ResourceLocation model, @Nullable String modelName);
new McModelAssemblies.PublishedSource(
    ModelPipeLine<?, ?, ResourceLocation, ResourceLocation, ?> pipeline, ResourceLocation key);

void register(ResourceLocation id, ModelAssemblyDefinition<McModelAssemblies.Reference> definition);
boolean unregister(ResourceLocation id);
void replaceResourceDefinitions(Map<ResourceLocation, ModelAssemblyDefinition<McModelAssemblies.Reference>> next);
Map<ResourceLocation, ModelAssemblyDefinition<McModelAssemblies.Reference>> definitions();
@Nullable ModelAssembly resolve(ResourceLocation id);
@Nullable ModelInstance createAndBind(ResourceLocation id, ResourceLocation instanceId, @Nullable Transform root);
McModelHandle handle(ResourceLocation id, ResourceLocation instanceId);
ModelPipeLine<?, ?, ResourceLocation, ResourceLocation, ?> pipeline();
ModelAssemblyCache.Stats cacheStats();
void close();

程序定义优先于同名资源包定义,并跨资源 reload 保留。unregister 移除程序覆盖,回收该定义 的实例;若有资源包定义则恢复它,返回值表示是否移除了程序定义。 replaceResourceDefinitions 应用已成功解析的完整资源快照,清理派生模型及组装缓存。 close 幂等,回收派生实例、缓存引用和来源发布订阅,关闭后调用解析/注册接口会报错。 服务操作与模型发布/渲染挂载须在 game thread 串行执行;基础缓存的独立同步语义仍见通用 API。

默认来源适配复用 KasugaModelPipelines.publishedSource(Reference);资源 reload 正在进行时不解析 旧发布代的资源。自定义来源函数可接入任意已发布 Model 管线,不限制其格式。

通用资源定义与发布生命周期

以下接口在 render-core,不依赖 Minecraft:

static <K> ModelAssemblyDefinition.Builder<K> ModelAssemblyDefinition.builder(String bodyId, K source);
static <K> ModelAssemblyDefinition.Builder<K> ModelAssemblyDefinition.builder(
        String bodyId, K source, Consumer<ModelAssemblyDefinition.PartBuilder> configure);
ModelAssemblyDefinition.Builder<K> part(String id, K source);
ModelAssemblyDefinition.Builder<K> part(String id, K source, Consumer<ModelAssemblyDefinition.PartBuilder> configure);
ModelAssemblyDefinition.Builder<K> bindTolerance(float tolerance);
ModelAssemblyDefinition<K> build();
@Nullable ModelAssembly.Request ModelAssemblyDefinition.resolve(Function<? super K, ModelAssembly.Source> sources);

PartBuilder 提供 mapBone(String,String)、matchBodyBones(boolean)、includeDynamics(boolean)、 hideMeshes(int...)、region(String,int...) 和 hideRegions(String...)。 此资源定义的 hideRegions 在调用时解析,所以先定义 region 再隐藏它。 定义可直接构造为 ModelAssemblyDefinition(List<Part<K>>, float);嵌套 Part<K> 的参数依次是 id, source, Map<String,String> boneMappings, Set<Integer> hiddenMeshes, matchBodyBones, includeDynamics。 任意 K 可由宿主提供;解析来源缺失返回 null,不生成部分几何。

new ModelAssemblyRegistry<K, I>(Function<? super K, ModelAssembly.Source> sources, ModelAssemblyCache cache);
void register(I id, ModelAssemblyDefinition<K> definition);
ModelAssemblyDefinition<K> definition(I id);
Map<I, ModelAssemblyDefinition<K>> definitions();
boolean remove(I id);
@Nullable ModelAssembly resolve(I id);
Set<I> invalidate(Model source);
void clearResolved();
ModelAssemblyCache.Stats cacheStats();

registry 保持资源定义与最新完整结果,返回集合视图只读;来源更新时调用 invalidate,其返回 依赖该旧 Model 的定义 ID,宿主应据此撤掉派生实例。暂缺来源不会生成半份结果。

ModelPipeLine 增加:

方法 行为
Model getModel(StorageIdentifierType key) 未发布时为 null
long modelRevision(StorageIdentifierType key) 当前发布版本;不存在时为 0
StorageIdentifierType modelKeyOf(ModelInstance instance) 查找注册键,未登记为 null;绑定后保留键以便常数时间检查
AutoCloseable onModelChanged(Consumer<ModelChange<StorageIdentifierType>> listener) game thread 通知,在旧实例 close 前执行;关闭订阅即注销
void publishModels(Map<StorageIdentifierType,Model>) 合并发布;相同对象跳过;替换时回收该键的实例
void replaceModels(Map<StorageIdentifierType,Model>) 完整替换,删除快照中缺失的模型
boolean removeModel(StorageIdentifierType key) 移除该键和实例;不存在返回 false
Builder.buildForPublishedModels() 无 SourceManager/Loader 的派生管线,仍需 bridge 和 backend

ModelChange<K> 为 key, previous, current, revision,删除时 current 为 null。 listener 异常不会跳过其他 listener 或旧实例回收,错误在清理完成后汇总抛出。 实例按资源键登记,共享同一个 Model 的不同键互不回收;相同键/instanceId 的 createInstance 幂等。 纯发布管线调用 prepare/load 会报错;SourceManager/Loader 管线的旧 build 方法继续要求 Reader。

ModelPublicationBatch<K>.publish(prepared) 发布一个资源管理者的完整代:prepared 的键为 ModelPipeLine<?,?,K,?,?>,值为 Map<K,Model>。下一代缺失的旧受管键会删除,其他发布者的键保留。 所有受管管线都有清理机会,即使较早的一条管线清理失败。

驱动和输入迁移

interface RebindablePoseDriver extends PoseDriver { void rebind(ModelInstance fresh); }
void ModelPosing.rebind(ModelInstance fresh);
ModelInstance ModelPosing.model();
void AnimationPlayer<T>.rebind(ModelInstance fresh, AnimationSampler<T> sampler, T data);
void AnimationPlayback<T,R>.retarget(AnimationSource<T,R> source, T data);
void SkeletonInstance.copyPoseInputsTo(SkeletonInstance target);
void MorphInstance<Id>.copyInputsTo(MorphInstance<Id> target);

骨骼迁移只复制命名局部变换、IK 开关及持久目标;根变换和浮动原点由宿主另行迁移。 morph 迁移复制标识对应的激活值/因子,不复制结果缓存或源结构对象。目标不存在的名称忽略。 AnimationPlayback.retarget 保持时钟对象/所有权;自有时钟按新片段时长更新,外部时钟不修改。 这些控制操作须与 tick 串行,render sample 消费既有快照机制。