Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
johannesvollmer opened this issue Jul 15, 2024 · 6 comments · Fixed by #430
Labels
A-Dynamics Relates to rigid body dynamics: motion, mass, constraint solving, joints, CCD, and so on C-Docs Improvements or additions to documentation

Comments

@johannesvollmer
Copy link
Contributor

johannesvollmer commented Jul 15, 2024

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.)

herons.steam.engine.unexpected.result.mp4

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"
@kmotyka-zx
Copy link

Disclaimer: just passing by and saw this issue.

From what I see in the video it looks like the forces applied aren't rotated with the cube like gizmos would show. Then I've look inside apply_force_at_point implementation and it looks that it takes simply a cross product between "center of mass to given point" vector in local coordinates and force taken in world coordinates. This alone should be suspicious, as the former value will be constant whereas latter constantly change even though in such scenario we would expect constant torque.

So to my inexperienced eye it should either be all world coordinates or all local coordinates.

@johannesvollmer

This comment was marked as resolved.

@kmotyka-zx
Copy link

Actually I would try first with all world-space values as the other methods like apply_force all mention world-space force. Maybe the docs are misworded for the "local point" part.

@johannesvollmer
Copy link
Contributor Author

I thought about that and came to the same on conclusion. Using all world space sounds like it could work.

@johannesvollmer
Copy link
Contributor Author

johannesvollmer commented Jul 16, 2024

I implemented this simple fix and it seems to work perfectly. It also makes sense in theory. So, the issue seems to be a mistake in the documentation.

@johannesvollmer
Copy link
Contributor Author

Thanks for your help, @kmotyka-zx! Appreciate it.

@johannesvollmer johannesvollmer changed the title bug? heron's steam engine apparently not working documentation incorrect: apply force at position mentions local points, but must be world space Jul 16, 2024
@johannesvollmer johannesvollmer changed the title documentation incorrect: apply force at position mentions local points, but must be world space documentation incorrect: apply_force_at_point mentions local points, but must be world space Jul 16, 2024
@Jondolf Jondolf added C-Docs Improvements or additions to documentation A-Dynamics Relates to rigid body dynamics: motion, mass, constraint solving, joints, CCD, and so on labels Jul 19, 2024
Jondolf added a commit that referenced this issue Dec 8, 2024
fix documentation for apply_force_at_point. 
fixes #428 

no code-changes at all, so no breaking changes.

---------

Co-authored-by: Joona Aalto <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Dynamics Relates to rigid body dynamics: motion, mass, constraint solving, joints, CCD, and so on C-Docs Improvements or additions to documentation
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants