bevy_xpbd
bevy_xpbd copied to clipboard
documentation incorrect: apply_force_at_point mentions local points, but must be world space
i'm trying to implement a rigidbody that is operated by individual smaller jet engines. currently i do this by applying forces at points.
i reproduced the problem in a minimal project, a heron steam engine. this mechanism should be possible in the current system, correct?
i tried to implement it, but it looks like something is off. maybe my code is wrong? or maybe the torque is actually incorrectly handled in avian?
schematic:
from popularmechanics.com
however, instead this happens: the body finds an equilibrium at some arbitrary rotation. (the arrow symbolizes how the forces are applied. they are the heron jet exhausts. it should result in continuus rotation.)
https://github.com/user-attachments/assets/37628fa6-330d-4db7-b2de-6c261864c2f8
full code
full code example
main.rs:
use std::f32::consts::PI;
use avian3d::math::Quaternion;
use avian3d::PhysicsPlugins;
use avian3d::prelude::*;
use bevy::prelude::*;
use bevy::prelude::*;
use bevy::color::palettes::tailwind::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(PhysicsPlugins::default())
.add_systems(Startup, setup)
.add_systems(Update, (
(
update_herons_steam_engine.before(PhysicsStepSet::First),
).chain(),
))
.run();
}
/// applies two forces to the body, one on each side in opposite directions.
/// from https://www.popularmechanics.com/science/energy/a34554479/heron-aeolipile/
fn update_herons_steam_engine(
mut gizmos: Gizmos,
mut bodies: Query<(&Mass, &mut ExternalForce, &CenterOfMass, &GlobalTransform), With<TheBody>>,
) {
let Ok(
(
mass, mut force, local_body_center_of_mass,
body_w_transform
)
) = bodies.get_single_mut() else {
return
};
// note: all calculations are in world space unless otherwise marked.
// all angles in "rotations" (0 to 1).
let body_to_world = body_w_transform.affine();
let body_right = body_to_world.transform_vector3(Vec3::X).normalize();
let body_forward = body_to_world.transform_vector3(Vec3::Z).normalize();
let body_center_of_mass = body_to_world.transform_point(local_body_center_of_mass.0);
let local_sub_force_position = Vec3::Z;
let sub_force_direction = body_right * mass.0 * 5.0;
force.apply_force_at_point(sub_force_direction, local_sub_force_position, local_body_center_of_mass.0);
gizmos.arrow(body_center_of_mass + body_forward, body_center_of_mass + body_forward + sub_force_direction*0.1, GRAY_700);
force.apply_force_at_point(-sub_force_direction, -local_sub_force_position, local_body_center_of_mass.0);
gizmos.arrow(body_center_of_mass - body_forward, body_center_of_mass - body_forward - sub_force_direction*0.1, GRAY_700);
// visualize center of mass
gizmos.sphere(body_center_of_mass, Quaternion::IDENTITY, 0.1, RED_900, );
}
#[derive(Component)]
pub struct TheBody {}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut gizmo_config_store: ResMut<GizmoConfigStore>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let cam = commands.spawn((
Camera3dBundle {
transform: Transform::from_xyz(6.0, 8.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
}
)).id();
commands.spawn(DirectionalLightBundle {
directional_light: DirectionalLight {
shadows_enabled: true,
..default()
},
transform: Transform::from_rotation(Quat::from_rotation_x(-PI / 3.0)),
..default()
});
commands.spawn((
Collider::cuboid(12.0, 2.0, 12.0),
RigidBody::Static,
PbrBundle {
mesh: meshes.add(Cuboid::new(12.0, 2.0, 12.0)),
material: materials.add(Color::WHITE),
transform: Transform::from_translation(Vec3::Y * -1.0),
..default()
}
));
let body = commands.spawn((
TheBody {},
RigidBody::Dynamic,
ExternalForce::default().with_persistence(false),
ExternalTorque::default().with_persistence(false),
MassPropertiesBundle::new_computed(&Collider::cuboid(2.0, 2.0, 2.0,), 1.0),
Collider::cuboid(2.0, 2.0, 2.0,),
PbrBundle {
mesh: meshes.add(Cuboid::new(2.0, 2.0, 2.0)),
material: materials.add(Color::BLACK.mix(&Color::WHITE, 0.5)),
transform: Transform::from_translation(Vec3::Y * 2.0),
..default()
}
)).id();
// draw gizmos in front of everything else
for (_, config, _) in gizmo_config_store.iter_mut() {
config.depth_bias = -1.0;
}
}
cargo.toml
[package]
name = "bevy-hovertruck"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# todo: features=["dynamic_linking"]
bevy = { version = "0.14.0", features = [] }
avian3d = "0.1"
interpolation = "0.3.0"
[target.x86_64-pc-windows-msvc]
linker = "rust-lld.exe"
# Enable a small amount of optimization in debug mode.
[profile.dev]
opt-level = 1
# Enable a large amount of optimization in debug mode for dependencies.
[profile.dev.package."*"]
opt-level = 3
# Enable more optimization in release mode at the cost of compile time.
[profile.release]
# Compile the entire crate as one unit.
# Significantly slows compile times, marginal improvements.
codegen-units = 1
# Do a second optimization pass over the entire program, including dependencies.
# Slightly slows compile times, marginal improvements.
lto = "thin"
# Optimize for size in wasm-release mode to reduce load times and bandwidth usage on web.
[profile.wasm-release]
# Use release profile as default values.
inherits = "release"
# Optimize with size in mind (also try "s", sometimes it is better).
# This doesn't increase compilation times compared to -O3, great improvements.
opt-level = "z"
# Strip all debugging information from the binary to reduce file size.
strip = "debuginfo"