From 4fe71ea594105978485bc657b3e3b6c34d676552 Mon Sep 17 00:00:00 2001 From: Greeble <166992735+greeble-dev@users.noreply.github.com> Date: Fri, 7 Mar 2025 17:12:26 +0000 Subject: [PATCH 1/6] Added `transform_mesh` test. --- Cargo.toml | 10 +++ tests/3d/transform_mesh.rs | 124 +++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 tests/3d/transform_mesh.rs diff --git a/Cargo.toml b/Cargo.toml index dab13437df5bc..56f1d7cfe7255 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4127,6 +4127,16 @@ description = "Demonstrates specular tints and maps" category = "3D Rendering" wasm = true +[[example]] +name = "transform_mesh" +path = "tests/3d/transform_mesh.rs" +doc-scrape-examples = true +required-features = ["pbr_anisotropy_texture"] + +[package.metadata.example.transform_mesh] +hidden = true + + [profile.wasm-release] inherits = "release" opt-level = "z" diff --git a/tests/3d/transform_mesh.rs b/tests/3d/transform_mesh.rs new file mode 100644 index 0000000000000..c0a5dcdf7de11 --- /dev/null +++ b/tests/3d/transform_mesh.rs @@ -0,0 +1,124 @@ +//! Test that transforming a mesh correctly updates normals and tangents. + +use bevy::prelude::*; +use bevy::render::camera::ScalingMode; +use bevy_render::mesh::SphereKind; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, (setup_environment, setup_meshes)) + .add_systems(Update, animate_light) + .run(); +} + +fn setup_environment( + mut commands: Commands, + mut mesh_assets: ResMut>, + mut material_assets: ResMut>, +) { + let description = "(left to right)\n\ + 0: Original mesh.\n\ + 1: Rotated and scaled.\n\ + 2: Rotated and scaled, normals and tangents recalculated."; + + commands.spawn(( + Text::new(description), + Node { + position_type: PositionType::Absolute, + top: Val::Px(12.0), + left: Val::Px(12.0), + ..default() + }, + )); + + commands.spawn(( + Camera3d::default(), + Transform::from_xyz(0.0, 0.0, 1.0).looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y), + Projection::Orthographic(OrthographicProjection { + scaling_mode: ScalingMode::AutoMin { + min_width: 10.0, + min_height: 5.0, + }, + ..OrthographicProjection::default_3d() + }), + )); + + commands.spawn(( + Transform::from_xyz(1.0, 1.0, 0.5).looking_at(Vec3::ZERO, Vec3::Y), + DirectionalLight { + shadows_enabled: true, + ..default() + }, + )); + + commands.spawn(( + Transform::from_xyz(0.0, 0.0, -11.0), + Mesh3d(mesh_assets.add(Plane3d::default().mesh().size(100.0, 100.0).normal(Dir3::Z))), + MeshMaterial3d(material_assets.add(StandardMaterial { + base_color: Color::srgb(0.05, 0.05, 0.15), + reflectance: 0.2, + ..default() + })), + )); +} + +fn setup_meshes( + mut commands: Commands, + mut mesh_assets: ResMut>, + mut material_assets: ResMut>, +) { + let material = MeshMaterial3d(material_assets.add(StandardMaterial { + base_color: Color::srgb(0.2, 0.4, 0.2), + // Use anisotropy so that tangents affect lighting. + anisotropy_rotation: 0.5, + anisotropy_strength: 1.0, + ..Default::default() + })); + + let original_mesh = Mesh::from( + Sphere::new(1.0) + .mesh() + .kind(SphereKind::Ico { subdivisions: 8 }), + ) + .with_computed_normals() + .with_generated_tangents() + .unwrap(); + + let transform = Transform::from_scale(Vec3::new(1.5, 0.5, 1.0)).with_rotation( + Quat::from_axis_angle(Vec3::splat(1.0).normalize(), 135.0_f32.to_radians()), + ); + + let transformed_mesh = original_mesh.clone().transformed_by(transform); + + let recalculated_mesh = transformed_mesh + .clone() + .with_computed_normals() + .with_generated_tangents() + .unwrap(); + + commands.spawn(( + Transform::from_xyz(-3.0, 0.0, -10.0), + Mesh3d(mesh_assets.add(original_mesh)), + material.clone(), + )); + + commands.spawn(( + Transform::from_xyz(0.0, 0.0, -10.0), + Mesh3d(mesh_assets.add(transformed_mesh)), + material.clone(), + )); + + commands.spawn(( + Transform::from_xyz(3.0, 0.0, -10.0), + Mesh3d(mesh_assets.add(recalculated_mesh)), + material.clone(), + )); +} + +fn animate_light(mut lights: Query<&mut Transform, With>, time: Res