A Bevy plugin for loading, morphing, rigging, and animating MakeHuman-based 3D humanoid characters at runtime.
- Template-based morphing — hundreds of MakeHuman shape keys are baked into a small set of runtime morph targets, preserving GPU instancing and batching across characters
- Auto-rigging — skeletal rigs are built automatically from MakeHuman rig/weight data and fitted to each character's morphed shape
- Skeleton LOD — reduce bone counts at runtime by merging child bone subtrees into their parents, with automatic weight rebinding and inverse bindpose recomputation
- Mesh LOD — use MakeHuman's lower-poly proxy meshes with Bevy's
VisibilityRangefor distance-based mesh switching - Animation retargeting — import glTF animation clips and retarget them to arbitrary character shapes
- Stitched meshes — split a character into multiple mesh pieces (head, body, clothing) with continuous normals across seam cuts
- Skeleton LOD filtering — use
SkeletonLodFilterto restrict which LOD levels are spawned per character, saving memory when certain detail levels are unnecessary - Ragdoll physics — optional integration with avian3d
- Asset loaders — native Bevy loaders for
.mhclo,.obj,.target,.macro, rig configs, and other MakeHuman data formats - Custom assets — build your own meshes and morph targets in Blender via MPFB
use bevy::prelude::*;
use humentity::prelude::*;
fn main() {
App::new()
.add_plugins((DefaultPlugins, HumentityPlugin))
.insert_resource(SkeletonLodConfig(vec![
BoneMergeConfig::full().without_children_of(&["foot.L", "foot.R"]),
]))
.add_systems(Startup, load_assets)
.add_systems(Update, attach_mesh)
.add_observer(spawn_character)
.run();
}
fn load_assets(asset_server: Res<AssetServer>, mut commands: Commands) {
load_and_insert_humentity_assets(
&mut commands,
&asset_server,
"base.obj",
"basemesh_vertex_groups.json",
"target.json",
"macro.macro",
"targets",
"rigs/rig.default.json",
"rigs/weights.default.json",
"skeletons/default.glb",
);
}
fn spawn_character(
_trigger: On<MorphsReady>,
mut commands: Commands,
asset_server: Res<AssetServer>,
mut mesh_builder: ResMut<MhcloMeshBuilder>,
mut template_assets: ResMut<Assets<CharacterTemplate>>,
mut shape_assets: ResMut<Assets<CharacterShapeAsset>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// 1. Define a template and trigger a mesh build
let mut morphs = MorphTargets::default();
morphs.insert("gender", 0.0);
let template = template_assets.add(CharacterTemplate::new([
CharacterMorphShape::new("woman", morphs),
]));
let mesh = asset_server.load::<MhcloAsset>("proxymeshes/basemesh/basemesh.proxy");
mesh_builder.trigger(LoadAssetMeshJob::Single {
part: mesh.clone(),
template_handle: template.clone(),
skeleton_lod: 0,
});
// 2. Spawn the character entity using the template
let mut weights = MorphTargets::default();
weights.insert("woman", 1.0);
let mat = materials.add(StandardMaterial::from_color(Color::WHITE));
commands.spawn((
CharacterShape(shape_assets.add(CharacterShapeAsset::new(template, weights))),
InheritedVisibility::default(),
children![(
CharacterPart { mesh, skeleton_lod: 0 },
Name::new("body"),
MeshMaterial3d(mat),
)],
));
}
/// Once the skeleton is fitted and the mesh build is complete, insert Mesh3d on each part.
fn attach_mesh(
parts: Query<(Entity, &ChildOf, &CharacterPart, Option<&SkinnedMesh>), Without<Mesh3d>>,
characters: Query<&CharacterShape>,
shape_assets: Res<Assets<CharacterShapeAsset>>,
cached_meshes: Res<CachedMhcloMeshHandles>,
mut commands: Commands,
) {
for (entity, parent, part, skm) in &parts {
let Ok(shape) = characters.get(parent.parent()) else {
continue;
};
let Some(asset) = shape_assets.get(&shape.0) else {
continue;
};
let Some(skm) = skm else {
continue;
};
if let Some(handle) =
cached_meshes.get(&(part.mesh.clone(), asset.template.clone(), part.skeleton_lod))
{
commands
.entity(entity)
.insert((Mesh3d(handle.clone()), skm.clone()));
}
}
}-
Configure — Insert a
SkeletonLodConfigresource with a list ofBoneMergeConfigentries. Each entry defines one LOD level by specifying which bone subtrees to remove and merge into their parents. -
Load — Call
load_and_insert_humentity_assetsto load the MakeHuman basemesh, vertex groups, morph targets, rig config, and reference rig as ECS resources. -
Morph — Create a
CharacterTemplatewith one or moreCharacterMorphShapeentries. Each shape maps named MakeHuman morphs to float weights. The template system bakes hundreds of underlying MakeHuman shape keys into a compact set of morph targets. -
Build — Trigger a
LoadAssetMeshJob(single or stitched) via theMhcloMeshBuilderresource. The job loads OBJ data and morph targets, then spawns a background thread to build the final mesh with the correct bone weights for the requested skeleton LOD level. The result is sent back over a channel and cached inCachedMhcloMeshHandles. This is non-blocking — the main thread continues while the mesh is built asynchronously. -
Spawn — Create a
CharacterShapeentity withCharacterPartchildren. EachCharacterPartspecifies which mesh handle to use and which skeleton LOD level it targets. -
Rig — The plugin automatically spawns skeleton entities for each LOD level, fits bone transforms to the morphed shape, computes inverse bindposes, and sets up
SkinnedMeshon each part. -
Activate — Trigger
EnableSkeletonLodto enable a specific LOD level. All other levels start disabled viaSkeletonLodDisabled.
| Example | Description |
|---|---|
lod.rs |
Distance-based mesh and skeleton LOD with VisibilityRange |
stress_test.rs |
Benchmarks 576 animated characters (24x24 grid) |
animation.rs |
Retargeted idle animation on a morphed character |
morphs_and_templates.rs |
Template system and runtime morph targets |
stitched_parts.rs |
Multi-part meshes with continuous normals and per-part morphs |
assets.rs |
Loading body parts, clothing, hair, and accessories |
character_creator.rs |
Real-time mesh modification UI with sliders |
ragdoll_avian.rs |
Full-body ragdoll with avian3d physics |
ragdoll_avian_partial.rs |
Partial ragdoll (arms only) with kinematic colliders |
Run examples with:
cargo run --example lod
cargo run --example stress_test
cargo run --example animation --features avian| Feature | Description |
|---|---|
avian |
Enables ragdoll physics via avian3d |
debug |
Enables Bevy's debug rendering |
Custom meshes, morph targets, and rig data can be authored in Blender using MPFB and exported as .mhclo/.obj files with .target shape keys. The crate's native asset loaders handle these formats automatically.
The HumentityAnimationPostProcess system set runs in PostUpdate after Bevy's AnimationSystems and before TransformSystems::Propagate. The built-in rescale_root_bone_translation system runs in this set to correct root bone Y translation for different human proportions.
To add your own animation postprocessing, order your systems relative to this set:
app.add_systems(
PostUpdate,
my_system.after(HumentityAnimationPostProcess),
);