bevy_xpbd icon indicating copy to clipboard operation
bevy_xpbd copied to clipboard

documentation incorrect: apply_force_at_point mentions local points, but must be world space

Open johannesvollmer opened this issue 1 year ago • 6 comments
trafficstars

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: pop-heron-aeoliple-1606150478 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"

johannesvollmer avatar Jul 15 '24 20:07 johannesvollmer