psemek/examples/physics_2d.cpp

270 lines
7.6 KiB
C++

#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/phys/engine_2d.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/geom/box.hpp>
#include <psemek/geom/camera.hpp>
#include <psemek/geom/rotation.hpp>
#include <psemek/util/clock.hpp>
#include <psemek/util/recursive.hpp>
#include <psemek/util/profiler.hpp>
#include <psemek/async/event_loop.hpp>
#include <psemek/pcg/random/generator.hpp>
#include <psemek/pcg/random/uniform.hpp>
#include <cstdint>
#include <vector>
using namespace psemek;
struct physics_2d_app
: app::app
{
phys2d::engine physics;
geom::box<float, 2> view_box;
geom::box<float, 2> simulation_box;
gfx::painter painter;
util::clock<std::chrono::duration<float>> frame_clock;
float physics_lag = 0.f;
phys2d::engine::material_handle material;
phys2d::engine::shape_handle ball_shape, box_shape;
phys2d::engine::group_handle ball_group, box_group;
float const world_size = 5.f;
float const line_width = 0.05f;
float const ball_radius = 0.25f;
float const box_width = 2.f * ball_radius;
float const box_height = 2.f * ball_radius;
std::vector<float> radiuses;
std::optional<geom::point<float, 2>> mouse;
std::optional<std::size_t> selected_ball;
async::event_loop loop;
pcg::generator gen;
physics_2d_app()
: app("Physics 2D example", 4)
{
simulation_box = {{{-world_size, world_size}, {-world_size, world_size}}};
view_box = expand(simulation_box, 1.f);
physics.set_gravity({0.f, -9.8f});
float const inf = std::numeric_limits<float>::infinity();
material = physics.add_material({1.f, 0.5f, 0.9f});
ball_shape = physics.add_shape(phys2d::ball{ball_radius});
ball_group = physics.create_group();
box_shape = physics.add_shape(phys2d::box{box_width, box_height});
box_group = physics.create_group();
if(false)
for (int x = -4; x <= 4; ++x)
{
for (int y = 0; y < 3; ++y)
{
geom::point<float, 2> pos{simulation_box[0].center() + x * box_width, simulation_box[1].min + (y + 0.5f) * box_height};
physics.add_object(box_group, box_shape, material, {pos, 0.f}, {});
}
}
auto wall_group = physics.create_group();
auto wall_material = physics.add_material({inf, 0.5f, 0.5f});
auto wall_0_shape = physics.add_shape(phys2d::half_space{{1.f, 0.f}, simulation_box[0].min});
physics.add_object(wall_group, wall_0_shape, wall_material, {}, {});
auto wall_1_shape = physics.add_shape(phys2d::half_space{{-1.f, 0.f}, -simulation_box[0].max});
physics.add_object(wall_group, wall_1_shape, wall_material, {}, {});
auto wall_2_shape = physics.add_shape(phys2d::half_space{{0.f, 1.f}, simulation_box[1].min});
physics.add_object(wall_group, wall_2_shape, wall_material, {}, {});
auto wall_3_shape = physics.add_shape(phys2d::half_space{{0.f, -1.f}, -simulation_box[1].max});
physics.add_object(wall_group, wall_3_shape, wall_material, {}, {});
auto task = util::recursive([this, dx = 0.1f](auto && self) mutable -> void {
// physics.add_object(box_group, box_shape, material, {simulation_box.corner(0.5f, 0.9f) + geom::vector{dx, 0.f}, 0.f}, {});
physics.add_object(ball_group, ball_shape, material, {simulation_box.corner(0.5f, 0.9f) + geom::vector{dx, 0.f}, 0.f}, {});
// float r = pcg::uniform_distribution<float>{0.05f, 0.5f}(gen);
// auto new_shape = physics.add_shape(phys2d::ball{r});
// physics.add_object(ball_group, new_shape, material, {simulation_box.corner(0.5f, 0.9f) + geom::vector{dx, 0.f}, 0.f}, {});
// radiuses.push_back(r);
dx *= -1.f;
if (physics.group_size(box_group) < 200)
loop.dispatch_at(std::chrono::system_clock::now() + std::chrono::milliseconds{250}, self);
});
(void)task;
// task();
}
void on_resize(int width, int height) override
{
app::on_resize(width, height);
float const ratio = static_cast<float>(width) / height;
float const c = view_box[0].center();
float const l = view_box[1].length() * ratio;
view_box[0] = {c - l / 2.f, c + l / 2.f};
}
void on_left_button_down() override
{
app::on_left_button_down();
if (mouse)
{
physics.add_object(ball_group, ball_shape, material, {*mouse, 0.f}, {});
}
}
void on_left_button_up() override
{
app::on_left_button_up();
}
void on_right_button_down() override
{
app::on_right_button_down();
if (mouse)
{
physics.add_object(box_group, box_shape, material, {*mouse, 0.f}, {});
}
}
void on_mouse_move(int x, int y, int dx, int dy) override
{
app::on_mouse_move(x, y, dx, dy);
float mx = x * 1.f / width();
float my = 1.f - y * 1.f / height();
mouse = view_box.corner(mx, my);
}
void update() override
{
util::profiler prof("update");
loop.pump();
float const frame_dt = frame_clock.restart().count();
physics_lag += frame_dt;
float const physics_dt = 0.001f;
while (physics_lag > physics_dt)
// if (physics_lag > physics_dt)
{
physics_lag -= physics_dt;
physics.update(physics_dt);
}
selected_ball = std::nullopt;
if (mouse)
{
float closest = std::numeric_limits<float>::infinity();
for (std::size_t i = 0; i < physics.group_size(ball_group); ++i)
{
auto p = physics.group_static_state(ball_group)[i].position;
float d = geom::distance(p, *mouse);
if (d <= std::min(closest, ball_radius))
{
closest = d;
selected_ball = i;
}
}
}
}
void present() override
{
util::profiler prof("present");
gl::ClearColor(0.8f, 0.8f, 0.8f, 0.f);
gl::Clear(gl::COLOR_BUFFER_BIT);
for (std::size_t i = 0; i < physics.group_size(ball_group); ++i)
{
bool selected = selected_ball ? (*selected_ball == i) : false;
auto p = physics.group_static_state(ball_group)[i].position;
auto a = physics.group_static_state(ball_group)[i].rotation;
painter.circle(p, ball_radius, gfx::black);
painter.circle(p, ball_radius - line_width, gfx::light(gfx::blue, selected ? 0.8f : 1.f).as_color_rgba());
geom::vector const n{std::cos(a), std::sin(a)};
geom::vector const m = geom::ort(n);
painter.line(p - n * ball_radius / 2.f, p + n * ball_radius / 2.f, line_width, gfx::black, false);
painter.line(p - m * ball_radius / 2.f, p + m * ball_radius / 2.f, line_width, gfx::black, false);
}
for (std::size_t i = 0; i < physics.group_size(box_group); ++i)
{
// bool selected = false;
auto p = physics.group_static_state(box_group)[i].position;
auto a = physics.group_static_state(box_group)[i].rotation;
geom::vector<float, 2> q[4];
q[0] = {-box_width / 2.f, -box_height / 2.f};
q[1] = { box_width / 2.f, -box_height / 2.f};
q[2] = { box_width / 2.f, box_height / 2.f};
q[3] = {-box_width / 2.f, box_height / 2.f};
auto m = geom::plane_rotation<float, 2>(0, 1, a).linear_matrix();
for (std::size_t j = 0; j < 4; ++j)
q[j] = m * q[j];
painter.triangle(p + q[0], p + q[1], p + q[3], gfx::white);
painter.triangle(p + q[3], p + q[1], p + q[2], gfx::white);
painter.line(p + q[0], p + q[1], line_width, gfx::black, false);
painter.line(p + q[1], p + q[2], line_width, gfx::black, false);
painter.line(p + q[2], p + q[3], line_width, gfx::black, false);
painter.line(p + q[3], p + q[0], line_width, gfx::black, false);
}
painter.line(simulation_box.corner(0, 0), simulation_box.corner(1, 0), line_width, gfx::black, false);
painter.line(simulation_box.corner(1, 0), simulation_box.corner(1, 1), line_width, gfx::black, false);
painter.line(simulation_box.corner(1, 1), simulation_box.corner(0, 1), line_width, gfx::black, false);
painter.line(simulation_box.corner(0, 1), simulation_box.corner(0, 0), line_width, gfx::black, false);
painter.render(geom::orthographic_camera{view_box}.transform());
}
};
int main()
{
return app::main<physics_2d_app>();
}