psemek/examples/physics_3d.cpp

373 lines
11 KiB
C++

#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/phys/engine_3d.hpp>
#include <psemek/gfx/program.hpp>
#include <psemek/gfx/mesh.hpp>
#include <psemek/cg/body/icosahedron.hpp>
#include <psemek/cg/body/box.hpp>
#include <psemek/math/camera.hpp>
#include <psemek/math/mesh.hpp>
#include <psemek/math/rotation.hpp>
#include <psemek/math/translation.hpp>
#include <psemek/math/scale.hpp>
#include <psemek/random/device.hpp>
#include <psemek/random/generator.hpp>
#include <psemek/random/uniform.hpp>
#include <psemek/random/uniform_sphere.hpp>
#include <psemek/random/uniform_ball.hpp>
#include <psemek/util/clock.hpp>
#include <psemek/log/log.hpp>
using namespace psemek;
static char const program_vs[] =
R"(#version 330
uniform mat4 u_camera_transform;
uniform mat4 u_object_transform;
layout (location = 0) in vec4 in_position;
layout (location = 1) in vec3 in_normal;
out vec3 position;
out vec3 normal;
out vec3 test_position;
void main()
{
test_position = in_position.xyz;
vec4 p = u_object_transform * in_position;
position = p.xyz;
gl_Position = u_camera_transform * p;
normal = (u_object_transform * vec4(in_normal, 0.0)).xyz;
}
)";
static char const program_fs[] =
R"(#version 330
uniform vec3 u_light_direction;
uniform vec3 u_light_color;
uniform vec3 u_object_color;
uniform int u_grid;
layout (location = 0) out vec4 out_color;
in vec3 position;
in vec3 normal;
in vec3 test_position;
float mmin(vec3 v)
{
return min(v.x, min(v.y, v.z));
}
void main()
{
float lit = dot(normalize(normal), u_light_direction) * 0.5 + 0.5;
vec3 object_color = u_object_color;
if (u_grid == 1)
object_color = mix(vec3(1.0), u_object_color, smoothstep(0.0, 0.05, mmin(abs(test_position))));
vec3 color = u_light_color * object_color * lit;
color = pow(color, vec3(1.0 / 2.2));
out_color = vec4(color, 1.0);
}
)";
struct physics_3d_app
: app::application_base
{
physics_3d_app(options const &, context const &)
: program_(program_vs, program_fs)
, rng_{random::device{}}
{
camera_.near_clip = 0.1f;
camera_.far_clip = 1000.f;
camera_.fov_y = math::rad(90.f);
camera_.fov_x = camera_.fov_y;
camera_.target = {0.f, 0.f, 0.f};
camera_.distance = 10.f;
camera_.elevation_angle = math::rad(45.f);
camera_.azimuthal_angle = math::rad(30.f);
camera_distance_tgt_ = camera_.distance;
camera_azimuthal_angle_tgt_ = camera_.azimuthal_angle;
camera_elevation_angle_tgt_ = camera_.elevation_angle;
struct vertex
{
math::point<float, 3> position;
math::vector<float, 3> normal;
static void setup(gfx::mesh & m)
{
m.setup<math::point<float, 3>, math::vector<float, 3>>();
}
};
{
std::vector<vertex> vertices;
vertices.push_back({{-10.f, -10.f, 0.f}, {0.f, 0.f, 1.f}});
vertices.push_back({{ 10.f, -10.f, 0.f}, {0.f, 0.f, 1.f}});
vertices.push_back({{-10.f, 10.f, 0.f}, {0.f, 0.f, 1.f}});
vertices.push_back({{-10.f, 10.f, 0.f}, {0.f, 0.f, 1.f}});
vertices.push_back({{ 10.f, -10.f, 0.f}, {0.f, 0.f, 1.f}});
vertices.push_back({{ 10.f, 10.f, 0.f}, {0.f, 0.f, 1.f}});
vertex::setup(plane_mesh_);
plane_mesh_.load(vertices, gl::TRIANGLES, gl::STATIC_DRAW);
}
{
cg::icosahedron<float> body{{0.f, 0.f, 0.f}, 1.f};
auto const & body_vertices = cg::vertices(body);
auto const & body_triangles = cg::triangles(body);
std::vector<math::point<float, 3>> positions;
std::copy(body_vertices.begin(), body_vertices.end(), std::back_inserter(positions));
std::vector<math::triangle<std::uint32_t>> triangles;
for (auto const & t : body_triangles)
triangles.push_back({t[0], t[1], t[2]});
for (int iterations = 0; iterations < 2; ++iterations)
{
math::subdivide(positions, triangles);
for (auto & p : positions)
p = p.zero() + math::normalized(p - p.zero());
}
auto normals = math::smooth_normals(positions, triangles);
std::vector<vertex> vertices;
for (std::size_t i = 0; i < positions.size(); ++i)
vertices.push_back({positions[i], normals[i]});
vertex::setup(sphere_mesh_);
sphere_mesh_.load(vertices, triangles, gl::STATIC_DRAW);
}
{
cg::box<float, 3> body{{{{-1.f, 1.f}, {-1.f, 1.f}, {-1.f, 1.f}}}};
auto const & body_vertices = cg::vertices(body);
auto const & body_triangles = cg::triangles(body);
std::vector<math::point<float, 3>> positions;
std::copy(body_vertices.begin(), body_vertices.end(), std::back_inserter(positions));
std::vector<math::triangle<std::uint32_t>> triangles;
for (auto const & t : body_triangles)
triangles.push_back({t[0], t[1], t[2]});
auto flat_positions = math::deindex(positions, triangles);
std::vector<vertex> vertices;
for (auto const & t : flat_positions)
{
auto n = math::normal(t[0], t[1], t[2]);
vertices.push_back({t[0], n});
vertices.push_back({t[1], n});
vertices.push_back({t[2], n});
}
vertex::setup(box_mesh_);
box_mesh_.load(vertices, gl::TRIANGLES, gl::STATIC_DRAW);
}
engine_.set_gravity({0.f, 0.f, -10.f});
// auto h0 = engine_.add_object(engine_.add_shape(phys3d::ball{1.f}), engine_.add_material({1.f, 1.f, 10.f}), {{-4.f, 0.25f, 2.f}});
// auto h1 = engine_.add_object(engine_.add_shape(phys3d::ball{1.f}), engine_.add_material({1.f, 1.f, 10.f}), {{ 4.f, -0.25f, 2.f}});
// engine_.get_object_state(h0).velocity = { 3.f, 0.f, 0.f};
// engine_.get_object_state(h1).velocity = {-3.f, 0.f, 0.f};
// (void)h0;
// (void)h1;
engine_.add_object(engine_.add_shape(phys3d::half_space{{0.f, 0.f, 1.f}, 0.f}), engine_.add_material({1.f, 0.f, 10.f}), {{0.f, 0.f, 0.f}});
// engine_.add_object(engine_.add_shape(phys3d::half_space{{ 1.f, 0.f, 0.f}, -10.f}), engine_.add_material({1.f, 1.f, 50.f}), {{0.f, 0.f, 0.f}});
// engine_.add_object(engine_.add_shape(phys3d::half_space{{-1.f, 0.f, 0.f}, -10.f}), engine_.add_material({1.f, 1.f, 50.f}), {{0.f, 0.f, 0.f}});
// engine_.add_object(engine_.add_shape(phys3d::half_space{{ 0.f, 1.f, 0.f}, -10.f}), engine_.add_material({1.f, 1.f, 50.f}), {{0.f, 0.f, 0.f}});
// engine_.add_object(engine_.add_shape(phys3d::half_space{{ 0.f, -1.f, 0.f}, -10.f}), engine_.add_material({1.f, 1.f, 50.f}), {{0.f, 0.f, 0.f}});
}
void on_event(app::resize_event const & event) override
{
app::application_base::on_event(event);
camera_.set_fov(camera_.fov_y, (1.f * event.size[0]) / event.size[1]);
}
void on_event(app::mouse_move_event const & event) override
{
auto const old_mouse = state().mouse;
app::application_base::on_event(event);
if (state().mouse_button_down.contains(app::mouse_button::right))
{
auto const delta = event.position - old_mouse;
camera_elevation_angle_tgt_ += delta[1] * 0.003f;
camera_azimuthal_angle_tgt_ -= delta[0] * 0.003f;
}
}
void on_event(app::mouse_wheel_event const & event) override
{
app::application_base::on_event(event);
camera_distance_tgt_ *= std::pow(0.8f, event.delta);
}
void on_event(app::key_event const & event) override
{
if (event.down && event.key == app::keycode::SPACE)
{
// float r = 7.f;
// engine_.add_object(engine_.add_shape(phys3d::ball{3.f}), engine_.add_material({1.f, 0.125f, 50.f}), {{random::uniform(rng_, -r, r), random::uniform(rng_, -r, r), 20.f}});
// engine_.add_object(engine_.add_shape(phys3d::ball{1.f}), engine_.add_material({1.f, 0.5f, 50.f}), {{0.f, 0.f, 20.f}});
phys3d::object_state state;
state.rotation.coords = random::uniform_sphere_vector_distribution<float, 4>()(rng_);
if (!engine_.is_object(1))
state.position = {0.f, 0.f, 4.f};
else
state.position = {0.01f, 0.01f, 4.f};
// state.angular_velocity = random::uniform_ball_vector_distribution<float, 3>(25.f)(rng_);
engine_.add_object(engine_.add_shape(phys3d::box{{1.5f, 1.5f, 1.5f}}), engine_.add_material({1.f, 0.f, 10.f}), state);
// engine_.add_object(engine_.add_shape(phys3d::ball{1.f}), engine_.add_material({1.f, 0.5f, 100.f}), state);
}
else if (event.down && event.key == app::keycode::F)
{
float const f = 1.f;
for (std::uint32_t h = 1; h < engine_.object_count(); ++h)
if (engine_.is_object(h))
engine_.add_impulse(h, {random::uniform(rng_, -f, f), random::uniform(rng_, -f, f), random::uniform(rng_, 0.f, 0.f)});
}
}
void update() override
{
float const dt = clock_.restart().count();
physics_lag_ += dt;
float const ph_dt = 0.004f;
while (physics_lag_ >= ph_dt)
{
physics_lag_ -= ph_dt;
engine_.update(ph_dt);
float E = 0.f;
for (phys3d::engine::object_handle h = 1; h < engine_.object_count(); ++h)
{
if (!engine_.is_object(h)) continue;
auto m = engine_.get_object_mass(h);
auto I = engine_.get_object_inertia(h);
auto v = engine_.get_object_state(h).velocity;
auto w = engine_.get_object_state(h).angular_velocity;
auto H = engine_.get_object_state(h).position[2];
E += 0.5f * math::length_sqr(v) * m + 0.5f * math::dot(w, I * w) + m * 10.f * H;
}
log::info() << "Energy: " << E;
}
camera_.distance += (camera_distance_tgt_ - camera_.distance) * (1.f - std::exp(- 20.f * dt));
camera_.azimuthal_angle += (camera_azimuthal_angle_tgt_ - camera_.azimuthal_angle) * (1.f - std::exp(- 20.f * dt));
camera_.elevation_angle += (camera_elevation_angle_tgt_ - camera_.elevation_angle) * (1.f - std::exp(- 20.f * dt));
}
void present() override
{
gl::ClearColor(0.8f, 0.8f, 1.f, 1.f);
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
gl::Enable(gl::DEPTH_TEST);
gl::DepthFunc(gl::LESS);
program_.bind();
program_["u_camera_transform"] = camera_.transform();
program_["u_light_direction"] = math::normalized(math::vector{1.f, 1.f, 1.f});
program_["u_light_color"] = math::vector{1.f, 1.f, 1.f};
program_["u_object_transform"] = math::matrix<float, 4, 4>::identity();
program_["u_object_color"] = math::vector{0.5f, 0.5f, 0.5f};
program_["u_grid"] = 0;
plane_mesh_.draw();
program_["u_object_color"] = math::vector{0.f, 0.f, 1.f};
program_["u_grid"] = 1;
for (phys3d::engine::object_handle h = 0; h < engine_.object_count(); ++h)
{
if (!engine_.is_object(h)) continue;
auto sh = engine_.get_shape(engine_.get_object_shape(h));
if (auto s = std::get_if<phys3d::ball const *>(&sh))
{
program_["u_object_transform"] =
math::translation<float, 3>(engine_.get_object_state(h).position - math::point<float, 3>::zero()).homogeneous_matrix() *
math::quaternion_rotation<float>(engine_.get_object_state(h).rotation).homogeneous_matrix() *
math::scale<float, 3>((*s)->radius).homogeneous_matrix();
sphere_mesh_.draw();
}
else if (auto s = std::get_if<phys3d::box const *>(&sh))
{
program_["u_object_transform"] =
math::translation<float, 3>(engine_.get_object_state(h).position - math::point<float, 3>::zero()).homogeneous_matrix() *
math::quaternion_rotation<float>(engine_.get_object_state(h).rotation).homogeneous_matrix() *
math::scale<float, 3>((*s)->dimensions / 2.f).homogeneous_matrix();
box_mesh_.draw();
}
}
}
private:
gfx::program program_;
math::spherical_camera camera_;
float camera_distance_tgt_;
float camera_azimuthal_angle_tgt_;
float camera_elevation_angle_tgt_;
gfx::mesh plane_mesh_;
gfx::mesh sphere_mesh_;
gfx::mesh box_mesh_;
util::clock<std::chrono::duration<float>, std::chrono::high_resolution_clock> clock_;
phys3d::engine engine_;
float physics_lag_ = 0.f;
random::generator rng_;
};
namespace psemek::app
{
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<physics_3d_app>({.name = "Physics 3D example"});
}
}