dimforge / rapier

2D and 3D physics engines focused on performance.
https://rapier.rs
Apache License 2.0
3.96k stars 248 forks source link

no_std support #227

Open jimmyff opened 3 years ago

jimmyff commented 3 years ago

I'd love to use rapier to make some playdate games, but it's standard library dependency means I can't.

Is it all feasible that rapier could support no_std and any standard library dependencies could be brought in as an additional crate or something (eg: rapier-std)?

I'm sure there are other no_std platforms/devices that would love to use rapier too?

sebcrozet commented 3 years ago

That’s an interesting use-case. Though adding no_std support isn’t a priority right now. But it would be interesting for someone from the community to try to do it in the mean time. The first step would be to make parry work on no_std.

jimmyff commented 3 years ago

Thanks for the reply @sebcrozet. If i was more experienced with Rust, I'd try to port parry myself.

When I've got a few months experience under my belt I'll have a crack at it if someone doesn't get to it sooner.

HBehrens commented 3 years ago

Am in the exact same position (evaluating Rust engines that could work on embedded systems, including Playdate) and would love for Rapier to support no_std!

StratusFearMe21 commented 1 year ago

@jimmyff, I put together a version of rapier that compiles to the Playdate in no_std. I don't think you'll want to use it for any games though. The simulation I put together is just a ball, and 4 walls, controlled by the Playdate's accelerometer, and it runs at ~28 FPS. And that's just for a ball and some walls. It's also worth noting that the Playdate doesn't support NEON, which means no SIMD acceleration. I don't think this lib is viable for the Playdate.

StratusFearMe21 commented 1 year ago

https://github.com/dimforge/rapier/assets/57533634/e17e7e4c-c3b8-49fa-a78a-eeeaf1c0c755

StratusFearMe21 commented 1 year ago

For context, this is how it's meant to run, at ~31 FPS. The simulation runs faster on the simulator because my event loop just runs PhysicsPipeline.step() 33 times every update (for 30 FPS). Since it's slower than 30 FPS on the actual hardware, it runs slower.

https://github.com/dimforge/rapier/assets/57533634/4cbdf378-7cd5-4ad8-bc8b-b82223a5f1d6

StratusFearMe21 commented 1 year ago

Update, updated the simulation to use cuboids instead of balls and added the ability to add more cuboids (also compiled with LTO). In total, simulating 7 cuboids simultaneously ran at 17 FPS (max).

StratusFearMe21 commented 1 year ago

This is the simulation

#![no_std]
extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;

use core::ffi::c_void;

use alloc::boxed::Box;
use crankstart::crankstart_game;
use crankstart::graphics::{Bitmap, Graphics, LCDColor};
use crankstart::system::{PDButtons, System};
use crankstart::Game;
use crankstart::Playdate;
use euclid::{point2, size2, vec2};
use rapier2d::na::{self as nalgebra, ArrayStorage, ComplexField, Const, Matrix};
use rapier2d::prelude::*;

pub struct HelloWorld {
    ball: Bitmap,
    gravity: Matrix<f32, Const<2>, Const<1>, ArrayStorage<f32, 2, 1>>,
    integration_parameters: IntegrationParameters,
    physics_pipeline: PhysicsPipeline,
    island_manager: IslandManager,
    broad_phase: BroadPhase,
    narrow_phase: NarrowPhase,
    rigid_body_set: RigidBodySet,
    collider_set: ColliderSet,
    impulse_joint_set: ImpulseJointSet,
    multibody_joint_set: MultibodyJointSet,
    ccd_solver: CCDSolver,
    ball_handles: Vec<RigidBodyHandle>,
    // current_ms: usize, // sound: SoundSource,
}

unsafe extern "C" fn audio_callback(
    context: *mut c_void,
    left: *mut i16,
    right: *mut i16,
    len: i32,
) -> i32 {
    let left_slice = unsafe { core::slice::from_raw_parts_mut(left, len as _) };
    let right_slice = unsafe { core::slice::from_raw_parts_mut(right, len as _) };

    1
}

impl HelloWorld {
    fn new(_playdate: &Playdate) -> anyhow::Result<Box<Self>> {
        System::get().set_peripherals_enabled(crankstart_sys::PDPeripherals::kAccelerometer)?;
        let ball = Graphics::get().load_bitmap("rust")?;
        let mut rigid_body_set = RigidBodySet::new();
        let mut collider_set = ColliderSet::new();
        let bottom_floor = RigidBodyBuilder::fixed()
            .translation(vector![0.0, 240.0])
            .build();
        let bottom_floor_handle = rigid_body_set.insert(bottom_floor);
        let collider = ColliderBuilder::cuboid(400.0, 0.1).build();
        collider_set.insert_with_parent(collider, bottom_floor_handle, &mut rigid_body_set);

        let top_floor = RigidBodyBuilder::fixed()
            .translation(vector![0.0, 0.0])
            .build();
        let top_floor_handle = rigid_body_set.insert(top_floor);
        let collider = ColliderBuilder::cuboid(400.0, 0.1).build();
        collider_set.insert_with_parent(collider, top_floor_handle, &mut rigid_body_set);

        let left_wall = RigidBodyBuilder::fixed()
            .translation(vector![0.0, 0.0])
            .build();
        let left_wall_handle = rigid_body_set.insert(left_wall);
        let collider = ColliderBuilder::cuboid(0.1, 240.0).build();
        collider_set.insert_with_parent(collider, left_wall_handle, &mut rigid_body_set);

        let right_wall = RigidBodyBuilder::fixed()
            .translation(vector![400.0, 0.0])
            .build();
        let right_wall_handle = rigid_body_set.insert(right_wall);
        let collider = ColliderBuilder::cuboid(0.1, 240.0).build();
        collider_set.insert_with_parent(collider, right_wall_handle, &mut rigid_body_set);

        let rigid_body = RigidBodyBuilder::dynamic()
            .can_sleep(false)
            .translation(vector![130.0, 200.0])
            .build();
        let collider = ColliderBuilder::cuboid(64.0, 64.0).restitution(0.7).build();
        let ball_body_handle = rigid_body_set.insert(rigid_body);
        collider_set.insert_with_parent(collider, ball_body_handle, &mut rigid_body_set);

        let gravity = vector![0.0f32, -9.81];
        let integration_parameters = IntegrationParameters::default();
        let physics_pipeline = PhysicsPipeline::new();
        let island_manager = IslandManager::new();
        let broad_phase = BroadPhase::new();
        let narrow_phase = NarrowPhase::new();
        let impulse_joint_set = ImpulseJointSet::new();
        let multibody_joint_set = MultibodyJointSet::new();
        let ccd_solver = CCDSolver::new();
        /*
        let sound =
            PLAYDATE
                .sound
                .add_source(Some(audio_callback), Box::into_raw(sound_ctx).cast(), false);
        */
        Ok(Box::new(Self {
            ball,
            gravity,
            integration_parameters,
            physics_pipeline,
            island_manager,
            broad_phase,
            narrow_phase,
            rigid_body_set,
            collider_set,
            impulse_joint_set,
            multibody_joint_set,
            ccd_solver,
            ball_handles: vec![ball_body_handle],
            // current_ms: 0,
            /* sound */
        }))
    }
}

impl Game for HelloWorld {
    fn update(&mut self, _: &mut Playdate) -> anyhow::Result<()> {
        // let ms = System::get().get_current_time_milliseconds()?;
        // let delta = ms - self.current_ms;
        // self.current_ms = ms;
        let system = System::get();
        let acceleromitor = system.get_accelerometer()?;
        let buttons = system.get_button_state()?;
        self.gravity.x = acceleromitor.0 * 9.81;
        self.gravity.y = acceleromitor.1 * 9.81;
        for _ in 0..33 as i32 {
            self.physics_pipeline.step(
                &self.gravity,
                &self.integration_parameters,
                &mut self.island_manager,
                &mut self.broad_phase,
                &mut self.narrow_phase,
                &mut self.rigid_body_set,
                &mut self.collider_set,
                &mut self.impulse_joint_set,
                &mut self.multibody_joint_set,
                &mut self.ccd_solver,
                None,
                &(),
            );
        }

        if (buttons.1 & PDButtons::kButtonA) == PDButtons::kButtonA {
            let rigid_body = RigidBodyBuilder::dynamic()
                .can_sleep(false)
                .translation(vector![130.0, 200.0])
                .build();
            let collider = ColliderBuilder::cuboid(64.0, 64.0).restitution(0.7).build();
            let ball_body_handle = self.rigid_body_set.insert(rigid_body);
            self.collider_set.insert_with_parent(
                collider,
                ball_body_handle,
                &mut self.rigid_body_set,
            );
            self.ball_handles.push(ball_body_handle);
        } else if (buttons.1 & PDButtons::kButtonB) == PDButtons::kButtonB {
            for handle in self.ball_handles.drain(1..) {
                self.rigid_body_set.remove(
                    handle,
                    &mut self.island_manager,
                    &mut self.collider_set,
                    &mut self.impulse_joint_set,
                    &mut self.multibody_joint_set,
                    true,
                );
            }
        }
        let graphics = Graphics::get();
        // Clear screen
        graphics.clear(LCDColor::Solid(
            crankstart::graphics::LCDSolidColor::kColorWhite,
        ))?;
        for handle in self.ball_handles.iter().copied() {
            let ball_body = &mut self.rigid_body_set[handle];
            let translation = ball_body.translation();
            self.ball.draw(
                point2(translation.x as i32 - 64, translation.y as i32 - 64),
                crankstart::graphics::LCDBitmapFlip::kBitmapUnflipped,
            )?;
        }
        // Draw text
        graphics.draw_text("Hello, World!", (230, 112).into())?;
        system.draw_fps(0, 0)?;
        /*
        // Draw FPS
        PLAYDATE.system.draw_fps(vec2![0, 0]);
        */
        Ok(())
    }
}

crankstart_game!(HelloWorld);
sebcrozet commented 6 months ago

@StratusFearMe21 This looks great! Would you mind sharing the changes you made on Rapier for no-std compatibility? I am curious to see what could be upstreamed.

Regarding performances, I supposed you are already compiling in release mode?