Path: blob/main/examples/animation/custom_skinned_mesh.rs
9353 views
//! Skinned mesh example with mesh and joints data defined in code.1//! Example taken from <https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_019_SimpleSkin.md>23use std::f32::consts::*;45use bevy::{6asset::RenderAssetUsages,7camera::visibility::DynamicSkinnedMeshBounds,8math::ops,9mesh::{10skinning::{SkinnedMesh, SkinnedMeshInverseBindposes},11Indices, PrimitiveTopology, VertexAttributeValues,12},13prelude::*,14};15use rand::{Rng, SeedableRng};16use rand_chacha::ChaCha8Rng;1718fn main() {19App::new()20.add_plugins(DefaultPlugins)21.insert_resource(GlobalAmbientLight {22brightness: 3000.0,23..default()24})25.add_systems(Startup, setup)26.add_systems(Update, joint_animation)27.run();28}2930/// Used to mark a joint to be animated in the [`joint_animation`] system.31#[derive(Component)]32struct AnimatedJoint(isize);3334/// Construct a mesh and a skeleton with 2 joints for that mesh,35/// and mark the second joint to be animated.36/// It is similar to the scene defined in `models/SimpleSkin/SimpleSkin.gltf`37fn setup(38mut commands: Commands,39asset_server: Res<AssetServer>,40mut meshes: ResMut<Assets<Mesh>>,41mut materials: ResMut<Assets<StandardMaterial>>,42mut skinned_mesh_inverse_bindposes_assets: ResMut<Assets<SkinnedMeshInverseBindposes>>,43) {44// Create a camera45commands.spawn((46Camera3d::default(),47Transform::from_xyz(2.5, 2.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),48));4950// Create inverse bindpose matrices for a skeleton consists of 2 joints51let inverse_bindposes = skinned_mesh_inverse_bindposes_assets.add(vec![52Mat4::from_translation(Vec3::new(-0.5, -1.0, 0.0)),53Mat4::from_translation(Vec3::new(-0.5, -1.0, 0.0)),54]);5556// Create a mesh57let mesh = Mesh::new(58PrimitiveTopology::TriangleList,59RenderAssetUsages::RENDER_WORLD,60)61// Set mesh vertex positions62.with_inserted_attribute(63Mesh::ATTRIBUTE_POSITION,64vec![65[0.0, 0.0, 0.0],66[1.0, 0.0, 0.0],67[0.0, 0.5, 0.0],68[1.0, 0.5, 0.0],69[0.0, 1.0, 0.0],70[1.0, 1.0, 0.0],71[0.0, 1.5, 0.0],72[1.0, 1.5, 0.0],73[0.0, 2.0, 0.0],74[1.0, 2.0, 0.0],75],76)77// Add UV coordinates that map the left half of the texture since its a 1 x78// 2 rectangle.79.with_inserted_attribute(80Mesh::ATTRIBUTE_UV_0,81vec![82[0.0, 0.00],83[0.5, 0.00],84[0.0, 0.25],85[0.5, 0.25],86[0.0, 0.50],87[0.5, 0.50],88[0.0, 0.75],89[0.5, 0.75],90[0.0, 1.00],91[0.5, 1.00],92],93)94// Set mesh vertex normals95.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0.0, 0.0, 1.0]; 10])96// Set mesh vertex joint indices for mesh skinning.97// Each vertex gets 4 indices used to address the `JointTransforms` array in the vertex shader98// as well as `SkinnedMeshJoint` array in the `SkinnedMesh` component.99// This means that a maximum of 4 joints can affect a single vertex.100.with_inserted_attribute(101Mesh::ATTRIBUTE_JOINT_INDEX,102// Need to be explicit here as [u16; 4] could be either Uint16x4 or Unorm16x4.103VertexAttributeValues::Uint16x4(vec![104[0, 0, 0, 0],105[0, 0, 0, 0],106[0, 1, 0, 0],107[0, 1, 0, 0],108[0, 1, 0, 0],109[0, 1, 0, 0],110[0, 1, 0, 0],111[0, 1, 0, 0],112[0, 1, 0, 0],113[0, 1, 0, 0],114]),115)116// Set mesh vertex joint weights for mesh skinning.117// Each vertex gets 4 joint weights corresponding to the 4 joint indices assigned to it.118// The sum of these weights should equal to 1.119.with_inserted_attribute(120Mesh::ATTRIBUTE_JOINT_WEIGHT,121vec![122[1.00, 0.00, 0.0, 0.0],123[1.00, 0.00, 0.0, 0.0],124[0.75, 0.25, 0.0, 0.0],125[0.75, 0.25, 0.0, 0.0],126[0.50, 0.50, 0.0, 0.0],127[0.50, 0.50, 0.0, 0.0],128[0.25, 0.75, 0.0, 0.0],129[0.25, 0.75, 0.0, 0.0],130[0.00, 1.00, 0.0, 0.0],131[0.00, 1.00, 0.0, 0.0],132],133)134// Tell bevy to construct triangles from a list of vertex indices,135// where each 3 vertex indices form a triangle.136.with_inserted_indices(Indices::U16(vec![1370, 1, 3, 0, 3, 2, 2, 3, 5, 2, 5, 4, 4, 5, 7, 4, 7, 6, 6, 7, 9, 6, 9, 8,138]))139// Create skinned mesh bounds. Together with the `DynamicSkinnedMeshBounds`140// component, this will ensure the mesh is correctly frustum culled.141.with_generated_skinned_mesh_bounds()142.unwrap();143144let mesh = meshes.add(mesh);145146// We're seeding the PRNG here to make this example deterministic for testing purposes.147// This isn't strictly required in practical use unless you need your app to be deterministic.148let mut rng = ChaCha8Rng::seed_from_u64(42);149150for i in -5..5 {151// Create joint entities152let joint_0 = commands153.spawn(Transform::from_xyz(154i as f32 * 1.5,1550.0,156// Move quads back a small amount to avoid Z-fighting and not157// obscure the transform gizmos.158-(i as f32 * 0.01).abs(),159))160.id();161let joint_1 = commands.spawn((AnimatedJoint(i), Transform::IDENTITY)).id();162163// Set joint_1 as a child of joint_0.164commands.entity(joint_0).add_children(&[joint_1]);165166// Each joint in this vector corresponds to each inverse bindpose matrix in `SkinnedMeshInverseBindposes`.167let joint_entities = vec![joint_0, joint_1];168169// Create skinned mesh renderer. Note that its transform doesn't affect the position of the mesh.170commands.spawn((171Mesh3d(mesh.clone()),172MeshMaterial3d(materials.add(StandardMaterial {173base_color: Color::srgb(174rng.random_range(0.0..1.0),175rng.random_range(0.0..1.0),176rng.random_range(0.0..1.0),177),178base_color_texture: Some(asset_server.load("textures/uv_checker_bw.png")),179..default()180})),181SkinnedMesh {182inverse_bindposes: inverse_bindposes.clone(),183joints: joint_entities,184},185DynamicSkinnedMeshBounds,186));187}188}189190/// Animate the joint marked with [`AnimatedJoint`] component.191fn joint_animation(192time: Res<Time>,193mut query: Query<(&mut Transform, &AnimatedJoint)>,194mut gizmos: Gizmos,195) {196for (mut transform, animated_joint) in &mut query {197match animated_joint.0 {198-5 => {199transform.rotation =200Quat::from_rotation_x(FRAC_PI_2 * ops::sin(time.elapsed_secs()));201}202-4 => {203transform.rotation =204Quat::from_rotation_y(FRAC_PI_2 * ops::sin(time.elapsed_secs()));205}206-3 => {207transform.rotation =208Quat::from_rotation_z(FRAC_PI_2 * ops::sin(time.elapsed_secs()));209}210-2 => {211transform.scale.x = ops::sin(time.elapsed_secs()) + 1.0;212}213-1 => {214transform.scale.y = ops::sin(time.elapsed_secs()) + 1.0;215}2160 => {217transform.translation.x = 0.5 * ops::sin(time.elapsed_secs());218transform.translation.y = ops::cos(time.elapsed_secs());219}2201 => {221transform.translation.y = ops::sin(time.elapsed_secs());222transform.translation.z = ops::cos(time.elapsed_secs());223}2242 => {225transform.translation.x = ops::sin(time.elapsed_secs());226}2273 => {228transform.translation.y = ops::sin(time.elapsed_secs());229transform.scale.x = ops::sin(time.elapsed_secs()) + 1.0;230}231_ => (),232}233// Show transform234let mut axis = *transform;235axis.translation.x += animated_joint.0 as f32 * 1.5;236gizmos.axes(axis, 1.0);237}238}239240241