Add electron crystal simulation

This commit is contained in:
Nikita Lisitsa 2022-12-08 16:46:23 +03:00
parent b0bd606727
commit f006ecb364

View file

@ -0,0 +1,283 @@
#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/geom/orthographic.hpp>
#include <psemek/random/device.hpp>
#include <psemek/random/generator.hpp>
#include <psemek/random/uniform_ball.hpp>
#include <psemek/cg/triangulation/delaunay.hpp>
#include <psemek/util/clock.hpp>
#include <psemek/prof/profiler.hpp>
#include <psemek/app/ui_scene.hpp>
#include <psemek/ui/button.hpp>
#include <psemek/ui/slider.hpp>
#include <psemek/ui/default_element_factory.hpp>
#include <psemek/ui/frame.hpp>
#include <psemek/ui/screen.hpp>
#include <psemek/ui/grid_layout.hpp>
#include <psemek/ui/event_interceptor.hpp>
#include <psemek/util/to_string.hpp>
#include <vector>
using namespace psemek;
struct main_scene
: app::ui_scene
{
main_scene(ui::controller & ui_controller);
void update() override;
void present() override;
float simulation_radius = 1.f;
float simulation_radius_tgt = 1.f;
float potential = 1000.f;
float step = 1e-4f;
random::generator rng{random::device{}};
std::vector<geom::point<float, 2>> points;
std::vector<geom::segment<std::uint32_t>> edges;
std::vector<std::uint32_t> degree;
gfx::painter painter;
util::clock<> clock;
};
main_scene::main_scene(ui::controller & ui_controller)
: app::ui_scene(ui_controller)
{
ui::default_element_factory element_factory;
auto root = element_factory.make_screen();
auto wheel_event = std::make_shared<ui::event_interceptor>();
wheel_event->on_mouse_wheel([this](ui::mouse_wheel const & event){
simulation_radius_tgt *= std::pow(0.8f, event.delta);
return true;
});
wheel_event->set_child(element_factory.make_screen());
root->add_child(wheel_event, ui::screen::x_policy::fill, ui::screen::y_policy::fill);
auto panel = element_factory.make_frame();
panel->set_min_size(geom::vector{200.f, 100.f});
root->add_child(panel, ui::screen::x_policy::left, ui::screen::y_policy::top);
auto layout = element_factory.make_grid_layout();
panel->set_child(layout);
auto count_name_label = element_factory.make_label("Count:");
count_name_label->set_valign(ui::label::valignment::center);
count_name_label->set_halign(ui::label::halignment::left);
auto count_value_label = element_factory.make_label("");
count_value_label->set_valign(ui::label::valignment::center);
count_value_label->set_halign(ui::label::halignment::center);
auto count_slider = element_factory.make_slider();
count_slider->set_value_range({1, 2000}, false);
count_slider->on_value_changed([this, count_value_label](int value){
count_value_label->set_text(util::to_string(value));
if (value < points.size())
points.resize(value);
else
{
float radius = std::max(0.5f, std::sqrt(points.size() / 1000.f));
random::uniform_sphere_point_distribution<float, 2> d({0.f, 0.f}, radius * 1.25f);
while (points.size() < value)
points.push_back(d(rng));
}
});
count_slider->set_value(100);
auto potential_name_label = element_factory.make_label("Potential:");
potential_name_label->set_valign(ui::label::valignment::center);
potential_name_label->set_halign(ui::label::halignment::left);
auto potential_value_label = element_factory.make_label("");
potential_value_label->set_valign(ui::label::valignment::center);
potential_value_label->set_halign(ui::label::halignment::center);
auto potential_slider = element_factory.make_slider();
potential_slider->set_value_range({0, 100}, false);
potential_slider->on_value_changed([this, potential_value_label](int value){
potential = value * 10.f;
potential_value_label->set_text(util::to_string(potential));
});
potential_slider->set_value(100);
auto step_name_label = element_factory.make_label("Step:");
step_name_label->set_valign(ui::label::valignment::center);
step_name_label->set_halign(ui::label::halignment::left);
auto step_value_label = element_factory.make_label("");
step_value_label->set_valign(ui::label::valignment::center);
step_value_label->set_halign(ui::label::halignment::center);
auto step_slider = element_factory.make_slider();
step_slider->set_value_range({0, 12}, false);
step_slider->on_value_changed([this, step_value_label](int value){
step = std::pow(2.f, value - 20);
step_value_label->set_text(util::to_string(std::setprecision(1), std::scientific, step));
});
step_slider->set_value(11);
layout->set_size(3, 3);
layout->set_column_weight(0, 0.5f);
layout->set_column_weight(1, 0.5f);
layout->set(0, 0, count_name_label);
layout->set(0, 1, count_value_label);
layout->set(0, 2, count_slider);
layout->set(1, 0, potential_name_label);
layout->set(1, 1, potential_value_label);
layout->set(1, 2, potential_slider);
layout->set(2, 0, step_name_label);
layout->set(2, 1, step_value_label);
layout->set(2, 2, step_slider);
ui::style style;
style.font = ui::make_default_9x12_font();
style.text_scale = 2;
style.bg_color = gfx::color_rgba{0, 0, 127, 255};
style.fg_color = gfx::color_rgba{255, 255, 255, 255};
style.action_color = gfx::color_rgba{0, 0, 255, 255};
style.highlight_color = gfx::color_rgba{0, 255, 255, 255};
style.border_width = 0;
style.bevel_width = 0;
root->set_style(std::make_shared<ui::style>(std::move(style)));
set_ui(root);
}
void main_scene::update()
{
ui_scene::update();
float const dt = clock.restart().count();
const int iterations = 1;
auto const origin = geom::point<float, 2>::zero();
std::vector<geom::vector<float, 2>> delta;
for (int iteration = 0; iteration < iterations; ++iteration)
{
prof::profiler prof("descent");
delta.assign(points.size(), geom::vector<float, 2>::zero());
for (std::size_t i = 0; i < points.size(); ++i)
delta[i] += 2.f * potential * (origin - points[i]);
for (std::size_t i = 0; i < points.size(); ++i)
{
for (std::size_t j = i + 1; j < points.size(); ++j)
{
auto d = points[i] - points[j];
d /= std::pow(geom::length(d), 3.f);
delta[i] += d;
delta[j] -= d;
}
}
for (std::size_t i = 0; i < points.size(); ++i)
points[i] += delta[i] * step * dt;
}
simulation_radius += (simulation_radius_tgt - simulation_radius) * (1.f - std::exp(-10.f * dt));
prof::profiler prof("delaunay");
auto const dcel = cg::delaunay<std::uint32_t>(geom::robust, points.begin(), points.end());
edges.clear();
degree.assign(points.size(), 0);
for (std::size_t i = 0; i < dcel.edges.size(); ++i)
{
auto const e = dcel.edge(i);
auto const v0 = e.origin().index();
auto const v1 = e.twin().origin().index();
if (v0 < v1)
edges.push_back({v0, v1});
degree[v0]++;
}
}
void main_scene::present()
{
gl::ClearColor(1.f, 1.f, 1.f, 0.f);
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
geom::box<float, 3> view_area;
{
view_area[0] = {-simulation_radius, simulation_radius};
view_area[1] = {-simulation_radius, simulation_radius};
view_area[2] = {-1.f, 1.f};
float const aspect_ratio = width() * 1.f / height();
float extra_y = view_area[1].length() * 0.1f;
view_area[1].min -= extra_y;
view_area[1].max += extra_y;
float extra_x = view_area[1].length() * aspect_ratio - view_area[0].length();
view_area[0].min -= extra_x / 2.f;
view_area[0].max += extra_x / 2.f;
}
geom::matrix<float, 4, 4> const transform = geom::orthographic{view_area}.homogeneous_matrix();
float const pixel_size = view_area[0].length() / width();
for (auto const & e : edges)
painter.line(points[e[0]], points[e[1]], 2.f * pixel_size, {127, 127, 127, 255}, false);
for (std::size_t i = 0; i < points.size(); ++i)
{
gfx::color_rgba color{0, 0, 0, 255};
int quality = 6;
if (degree[i] == 5)
{
quality = 3;
color = {255, 0, 0, 255};
}
else if (degree[i] == 7)
{
quality = 4;
color = {0, 0, 255, 255};
}
painter.circle(points[i], 6.f * pixel_size, color, quality);
}
painter.render(transform);
ui_scene::present();
}
struct electron_crystal_app
: app::app
{
electron_crystal_app();
~electron_crystal_app();
async::event_loop event_loop;
ui::controller ui_controller;
};
electron_crystal_app::electron_crystal_app()
: app("Electron crystal simulation", 4)
, ui_controller(&event_loop)
{
push_scene(std::make_shared<main_scene>(ui_controller));
}
electron_crystal_app::~electron_crystal_app()
{
prof::dump();
}
int main()
{
return app::main<electron_crystal_app>();
}