logo of the comfy engine Comfy

physics

2023-12-08

use blobs::*;
use comfy::*;

// This example shows an integration between comfy and blobs, a simple 2d physics engine. It's not
// the most beautiful example, and maybe a bit verbose for what it does, but it tries to showcase
// some more extensible ways of using comfy.
comfy_game!("Physics Example", PhysicsGame);

pub enum BallSpawningSpeed {
    Comfy,
    Uncomfy,
}

pub struct PhysicsGame {
    pub spawn_timer: f32,
    pub physics: Physics,
    pub ball_spawning_speed: BallSpawningSpeed,
}

impl GameLoop for PhysicsGame {
    fn new(_c: &mut EngineState) -> Self {
        let mut state = Self {
            spawn_timer: 0.0,
            physics: Physics::new(vec2(0.0, -20.0), false),
            ball_spawning_speed: BallSpawningSpeed::Comfy,
        };

        let rbd_handle =
            state.physics.insert_rbd(RigidBodyBuilder::new().build());

        state.physics.insert_collider_with_parent(
            ColliderBuilder::new().build(),
            rbd_handle,
        );

        // We can add a circle constraint which prevents any body from leaving the constrained area.
        // Note that currently blobs constraints work based on position and ignore collider radius.
        state
            .physics
            .constraints
            .push(Constraint { position: Vec2::ZERO, radius: 5.0 });

        // We'll need SFX for this
        load_sound_from_bytes(
            // Every sound gets a string name later used to reference it.
            "comfy-bell",
            include_bytes!(concat!(
                env!("CARGO_MANIFEST_DIR"),
                "/../assets/bell-sfx.ogg"
            )),
            StaticSoundSettings::default(),
        );

        state
    }

    fn update(&mut self, _c: &mut EngineContext) {
        self.spawn_timer -= delta();

        let limit = match self.ball_spawning_speed {
            BallSpawningSpeed::Comfy => 100,
            BallSpawningSpeed::Uncomfy => 1000,
        };

        if self.spawn_timer <= 0.0 && self.physics.rbd_count() < limit {
            let max_time = match self.ball_spawning_speed {
                BallSpawningSpeed::Comfy => 0.4,
                BallSpawningSpeed::Uncomfy => 0.01,
            };

            // Every time the timer expires we reset it to a random value.
            self.spawn_timer = random_range(0.01, max_time);

            let rbd_handle = self.physics.insert_rbd(
                RigidBodyBuilder::new().position(random_circle(2.0)).build(),
            );

            self.physics.insert_collider_with_parent(
                ColliderBuilder::new().radius(random_range(0.05, 0.4)).build(),
                rbd_handle,
            );

            play_sound("comfy-bell");
        }

        self.physics.step(delta() as f64);

        // We could iterate rigid bodies individually, but blobs also has a nice way of collecting
        // debug data in a simple format for rendering/logginc that we'll use to draw all bodies and
        // colliders.
        let debug = self.physics.debug_data();

        for body in debug.bodies.iter() {
            draw_circle(body.transform.translation, 0.1, RED.alpha(0.8), 5);
        }

        for constraint in self.physics.constraints.iter() {
            draw_circle(
                constraint.position,
                constraint.radius,
                WHITE.alpha(0.1),
                3,
            );
        }

        // Let's use the colliders arena to draw the colliders for a change. We could
        // also just use `debug.colliders`.
        for (_, collider) in self.physics.col_set.iter() {
            // We'll draw collider circles behind the rigid body circles using a lower z_index.
            draw_circle(
                collider.absolute_transform.translation,
                collider.radius,
                BLUE.alpha(0.5),
                4,
            );
        }

        draw_text(
            "Be warned, blobs isn't very stable with many balls :)",
            Position::screen_percent(0.5, 0.85).to_world(),
            WHITE,
            TextAlign::Center,
        );

        draw_text(
            &format!("There are now {} balls", self.physics.rbd_count()),
            Position::screen_percent(0.5, 0.15).to_world(),
            WHITE,
            TextAlign::Center,
        );

        egui::Window::new("More balls")
            .anchor(egui::Align2::LEFT_TOP, egui::vec2(10.0, 10.0))
            .show(egui(), |ui| {
                match self.ball_spawning_speed {
                    BallSpawningSpeed::Comfy => {
                        if ui.button("Make it go faster").clicked() {
                            self.ball_spawning_speed =
                                BallSpawningSpeed::Uncomfy;
                        }
                    }
                    BallSpawningSpeed::Uncomfy => {
                        if ui
                            .add(egui::Button::new(
                                egui::RichText::new("NOOOO, PLS STOP!!!").size(
                                    rescale(
                                        self.physics.rbd_count() as f32,
                                        0.0..limit as f32,
                                        10.0..100.0,
                                    ),
                                ),
                            ))
                            .clicked()
                        {
                            self.ball_spawning_speed = BallSpawningSpeed::Comfy;
                            self.physics.reset();
                        }
                    }
                }
            });
    }
}