Delete obsolete examples & fix the rest to incorporate the new application API

This commit is contained in:
Nikita Lisitsa 2023-07-21 01:31:30 +03:00
parent 7f5d50787d
commit d473ef3ea4
19 changed files with 671 additions and 2050 deletions

View file

@ -5,7 +5,7 @@ file(GLOB PSEMEK_EXAMPLES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURREN
foreach(example ${PSEMEK_EXAMPLES}) foreach(example ${PSEMEK_EXAMPLES})
get_filename_component(TARGET_NAME "${example}" NAME_WLE) get_filename_component(TARGET_NAME "${example}" NAME_WLE)
set(TARGET_NAME psemek-example-${TARGET_NAME}) set(TARGET_NAME psemek-example-${TARGET_NAME})
psemek_add_executable(${TARGET_NAME} ${example}) psemek_add_application(${TARGET_NAME} ${example})
if(TARGET ${TARGET_NAME}) if(TARGET ${TARGET_NAME})
target_link_libraries(${TARGET_NAME} PUBLIC psemek ZLIB::ZLIB) target_link_libraries(${TARGET_NAME} PUBLIC psemek ZLIB::ZLIB)
target_compile_definitions(${TARGET_NAME} PUBLIC -DPSEMEK_EXAMPLES_DIR="${CMAKE_CURRENT_SOURCE_DIR}") target_compile_definitions(${TARGET_NAME} PUBLIC -DPSEMEK_EXAMPLES_DIR="${CMAKE_CURRENT_SOURCE_DIR}")

View file

@ -1,5 +1,5 @@
#include <psemek/app/app.hpp> #include <psemek/app/application_base.hpp>
#include <psemek/app/main.hpp> #include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/painter.hpp> #include <psemek/gfx/painter.hpp>
@ -651,14 +651,14 @@ controller lerp(controller const & c1, controller const & c2, float t)
} }
struct animation_2d_app struct animation_2d_app
: app::app : app::application_base
{ {
animation_2d_app(); animation_2d_app(options const &, context const &);
void on_resize(int width, int height) override; void on_event(app::resize_event const & event) override;
void on_mouse_wheel(int delta) override; void on_event(app::mouse_wheel_event const & event) override;
void on_key_down(SDL_Keycode key) override; void on_event(app::key_event const & event) override;
void update() override; void update() override;
void present() override; void present() override;
@ -688,6 +688,8 @@ struct animation_2d_app
test, test,
} mode = mode::train; } mode = mode::train;
application::context context;
std::vector<controller> population; std::vector<controller> population;
std::size_t const population_size = 1024; std::size_t const population_size = 1024;
std::size_t const max_train_frames = 10.f / physics.dt; std::size_t const max_train_frames = 10.f / physics.dt;
@ -720,24 +722,24 @@ struct animation_2d_app
void do_test(); void do_test();
}; };
animation_2d_app::animation_2d_app() animation_2d_app::animation_2d_app(options const &, application::context const & context)
: app("Animation 2D") : context(context)
{ {
view_bbox[1] = {-1.f, 15.f}; view_bbox[1] = {-1.f, 15.f};
vsync(false); context.vsync(false);
} }
void animation_2d_app::on_resize(int width, int height) void animation_2d_app::on_event(app::resize_event const & event)
{ {
app::on_resize(width, height); app::application_base::on_event(event);
update_camera(); update_camera();
} }
void animation_2d_app::on_mouse_wheel(int delta) void animation_2d_app::on_event(app::mouse_wheel_event const & event)
{ {
float p = std::pow(0.8f, delta); float p = std::pow(0.8f, event.delta);
view_bbox[1].max *= p; view_bbox[1].max *= p;
update_camera(); update_camera();
@ -745,7 +747,7 @@ void animation_2d_app::on_mouse_wheel(int delta)
void animation_2d_app::update_camera() void animation_2d_app::update_camera()
{ {
float ratio = static_cast<float>(width()) / height(); float ratio = static_cast<float>(state().size[0]) / state().size[1];
float cx = 0.f; float cx = 0.f;
if (centered) if (centered)
@ -766,22 +768,22 @@ void animation_2d_app::update_camera()
view_bbox[0] = geom::expand(geom::interval<float>::singleton(cx), ratio * view_bbox[1].length() / 2.f); view_bbox[0] = geom::expand(geom::interval<float>::singleton(cx), ratio * view_bbox[1].length() / 2.f);
} }
void animation_2d_app::on_key_down(SDL_Keycode key) void animation_2d_app::on_event(app::key_event const & event)
{ {
if (key == SDLK_c) if (event.down && event.key == app::keycode::C)
{ {
centered = !centered; centered = !centered;
update_camera(); update_camera();
} }
if (key == SDLK_p) if (event.down && event.key == app::keycode::P)
{ {
testing_control = !testing_control; testing_control = !testing_control;
} }
if (mode == mode::train) if (mode == mode::train)
{ {
if (key == SDLK_s) if (event.down && event.key == app::keycode::S)
{ {
train_iterations = max_train_iterations; train_iterations = max_train_iterations;
} }
@ -789,12 +791,12 @@ void animation_2d_app::on_key_down(SDL_Keycode key)
if (mode == mode::test) if (mode == mode::test)
{ {
bool reset = false; bool reset = false;
if (key == SDLK_LEFT) if (event.down && event.key == app::keycode::LEFT)
{ {
reset = true; reset = true;
test_id = (test_id + population.size() - 1) % population.size(); test_id = (test_id + population.size() - 1) % population.size();
} }
if (key == SDLK_RIGHT) if (event.down && event.key == app::keycode::RIGHT)
{ {
reset = true; reset = true;
test_id = (test_id + 1) % population.size(); test_id = (test_id + 1) % population.size();
@ -844,7 +846,7 @@ void animation_2d_app::update()
population[0].reset(); population[0].reset();
vsync(true); context.vsync(true);
} }
} }
else if (mode == mode::test) else if (mode == mode::test)
@ -1143,14 +1145,6 @@ float animation_2d_app::eval_score(controller const & c, random::generator rng)
if (frame + 1 == max_train_frames) if (frame + 1 == max_train_frames)
{ {
float mean_pos_x = 0.f;
float mass = 0.f;
for (auto const & b : physics.bones)
{
mean_pos_x += b.position[0] * b.mass;
mass += b.mass;
}
mean_pos_x /= mass;
cur_score = reward / max_train_variations; cur_score = reward / max_train_variations;
} }
@ -1165,7 +1159,6 @@ float animation_2d_app::eval_score(controller const & c, random::generator rng)
return score; return score;
} }
// Old evolutionary training implementation // Old evolutionary training implementation
void animation_2d_app::do_train() void animation_2d_app::do_train()
{ {
@ -1367,17 +1360,12 @@ void animation_2d_app::do_train()
void animation_2d_app::do_test() void animation_2d_app::do_test()
{ {
std::optional<geom::point<float, 2>> m; geom::point<float, 2> mouse = view_bbox.corner(state().mouse[0] * 1.f / state().size[0], 1.f - state().mouse[1] * 1.f / state().size[1]);
if (mouse())
{
m = view_bbox.corner((*mouse())[0] * 1.f / width(), 1.f - (*mouse())[1] * 1.f / height());
}
if (!is_left_button_down()) if (state().mouse_button_down.contains(app::mouse_button::left))
{ {
selected = std::nullopt; selected = std::nullopt;
if (m)
{ {
float selected_distance = std::numeric_limits<float>::infinity(); float selected_distance = std::numeric_limits<float>::infinity();
@ -1389,7 +1377,7 @@ void animation_2d_app::do_test()
auto p1 = b.position + b.direction * b.length / 2.f; auto p1 = b.position + b.direction * b.length / 2.f;
auto r = p1 - p0; auto r = p1 - p0;
auto d = *m - p0; auto d = mouse - p0;
float t = geom::dot(d, r) / geom::dot(r, r); float t = geom::dot(d, r) / geom::dot(r, r);
@ -1401,8 +1389,8 @@ void animation_2d_app::do_test()
} }
else else
{ {
float d0 = geom::distance(p0, *m); float d0 = geom::distance(p0, mouse);
float d1 = geom::distance(p1, *m); float d1 = geom::distance(p1, mouse);
if (d0 < d1) if (d0 < d1)
{ {
@ -1427,10 +1415,10 @@ void animation_2d_app::do_test()
} }
std::optional<system::selection> sel; std::optional<system::selection> sel;
if (selected && is_left_button_down()) if (selected && state().mouse_button_down.contains(app::mouse_button::left))
{ {
auto const & b = physics.bones[*selected]; auto const & b = physics.bones[*selected];
auto delta = b.position + b.direction * selected_s * b.length / 2.f - *m; auto delta = b.position + b.direction * selected_s * b.length / 2.f - mouse;
sel = system::selection{*selected, selected_s, delta}; sel = system::selection{*selected, selected_s, delta};
} }
@ -1515,7 +1503,7 @@ void animation_2d_app::present()
int margin = 40; int margin = 40;
float const step = 1.f; float const step = 1.f;
int max_frames_shown = (width() - 2 * margin) / step; int max_frames_shown = (state().size[0] - 2 * margin) / step;
int start = std::max(0, static_cast<int>(test_speeds.size() - max_frames_shown)); int start = std::max(0, static_cast<int>(test_speeds.size() - max_frames_shown));
for (std::size_t i = start; i + 1 < test_speeds.size(); ++i) for (std::size_t i = start; i + 1 < test_speeds.size(); ++i)
@ -1542,12 +1530,17 @@ void animation_2d_app::present()
// if (mode == mode::test && !test_speeds.empty()) painter.text({40.f, 136.f}, util::to_string("Speed: ", test_speeds.back()), opts); // if (mode == mode::test && !test_speeds.empty()) painter.text({40.f, 136.f}, util::to_string("Speed: ", test_speeds.back()), opts);
} }
painter.render(geom::window_camera{width(), height()}.transform()); painter.render(geom::window_camera{state().size[0], state().size[1]}.transform());
} }
} }
int main() namespace psemek::app
{ {
return app::main<animation_2d_app>();
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<animation_2d_app>({.name = "Animation 2D example"});
}
} }

View file

@ -17,8 +17,8 @@
#include <psemek/audio/combine/stereo.hpp> #include <psemek/audio/combine/stereo.hpp>
#include <psemek/audio/combine/mixer.hpp> #include <psemek/audio/combine/mixer.hpp>
#include <psemek/audio/midi.hpp> #include <psemek/audio/midi.hpp>
#include <psemek/app/app.hpp> #include <psemek/app/application_base.hpp>
#include <psemek/app/main.hpp> #include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/painter.hpp> #include <psemek/gfx/painter.hpp>
#include <psemek/util/clock.hpp> #include <psemek/util/clock.hpp>
#include <psemek/util/to_string.hpp> #include <psemek/util/to_string.hpp>
@ -31,41 +31,41 @@
using namespace psemek; using namespace psemek;
static std::map<SDL_Keycode, int> const key_to_midi static std::map<app::keycode, int> const key_to_midi
{ {
{SDLK_z, 59}, {app::keycode::Z, 59},
{SDLK_x, 60}, {app::keycode::X, 60},
{SDLK_c, 61}, {app::keycode::C, 61},
{SDLK_v, 62}, {app::keycode::V, 62},
{SDLK_b, 63}, {app::keycode::B, 63},
{SDLK_n, 64}, {app::keycode::N, 64},
{SDLK_m, 65}, {app::keycode::M, 65},
{SDLK_COMMA, 66}, {app::keycode::COMMA, 66},
{SDLK_PERIOD, 67}, {app::keycode::PERIOD, 67},
{SDLK_SLASH, 68}, {app::keycode::SLASH, 68},
{SDLK_a, 69}, {app::keycode::A, 69},
{SDLK_s, 70}, {app::keycode::S, 70},
{SDLK_d, 71}, {app::keycode::D, 71},
{SDLK_f, 72}, {app::keycode::F, 72},
{SDLK_g, 73}, {app::keycode::G, 73},
{SDLK_h, 74}, {app::keycode::H, 74},
{SDLK_j, 75}, {app::keycode::J, 75},
{SDLK_k, 76}, {app::keycode::K, 76},
{SDLK_l, 77}, {app::keycode::L, 77},
{SDLK_SEMICOLON, 78}, {app::keycode::SEMICOLON, 78},
{SDLK_QUOTE, 79}, {app::keycode::APOSTROPHE, 79},
{SDLK_q, 80}, {app::keycode::Q, 80},
{SDLK_w, 81}, {app::keycode::W, 81},
{SDLK_e, 82}, {app::keycode::E, 82},
{SDLK_r, 83}, {app::keycode::R, 83},
{SDLK_t, 84}, {app::keycode::T, 84},
{SDLK_y, 85}, {app::keycode::Y, 85},
{SDLK_u, 86}, {app::keycode::U, 86},
{SDLK_i, 87}, {app::keycode::I, 87},
{SDLK_o, 88}, {app::keycode::O, 88},
{SDLK_p, 89}, {app::keycode::P, 89},
{SDLK_LEFTBRACKET, 90}, {app::keycode::LEFTBRACKET, 90},
{SDLK_RIGHTBRACKET, 91}, {app::keycode::RIGHTBRACKET, 91},
}; };
static geom::interval<int> const key_rows[3] = { static geom::interval<int> const key_rows[3] = {
@ -79,27 +79,27 @@ static std::string_view const midi_name[12] = {
}; };
struct audio_app struct audio_app
: app::app : app::application_base
{ {
audio_app() audio_app(options const &, context const &)
: app::app("Audio example")
{ {
engine_ = audio::make_engine();
mixer_ = audio::make_mixer(); mixer_ = audio::make_mixer();
volume_control_ = audio::volume_stereo(mixer_, 0.5f, 0.5f, 0.1f); volume_control_ = audio::volume_stereo(mixer_, 0.5f, 0.5f, 0.1f);
pitch_control_ = audio::pitch(volume_control_, 1.f, 0.025f); pitch_control_ = audio::pitch(volume_control_, 1.f, 0.025f);
auto compressor = audio::compressor(pitch_control_, audio::from_db(-2.f), 0.95f, 0.002f, 1.f, audio::from_db(1.f)); auto compressor = audio::compressor(pitch_control_, audio::from_db(-2.f), 0.95f, 0.002f, 1.f, audio::from_db(1.f));
pause_control_ = audio::pause(compressor, false, 0.01f); pause_control_ = audio::pause(compressor, false, 0.01f);
engine_.output()->stream(pause_control_); engine_->output()->stream(pause_control_);
} }
void on_key_down(SDL_Keycode key) override void on_event(app::key_event const & event) override
{ {
app::app::on_key_down(key); app::application_base::on_event(event);
if (key_to_midi.contains(key)) if (event.down && key_to_midi.contains(event.key))
{ {
int midi = key_to_midi.at(key); int midi = key_to_midi.at(event.key);
if (!channels_.contains(midi)) if (!channels_.contains(midi))
{ {
auto tone = audio::karplus_strong(audio::midi_frequency(midi)); auto tone = audio::karplus_strong(audio::midi_frequency(midi));
@ -108,35 +108,28 @@ struct audio_app
} }
} }
if (key == SDLK_SPACE) if (!event.down && key_to_midi.contains(event.key))
{ {
pause_control_->paused(!pause_control_->paused()); int midi = key_to_midi.at(event.key);
}
if (key == SDLK_KP_PLUS)
pitch_control_->pitch(std::pow(2.f, 1.f / 12.f));
if (key == SDLK_KP_MINUS)
pitch_control_->pitch(std::pow(2.f, - 1.f / 12.f));
}
void on_key_up(SDL_Keycode key) override
{
app::app::on_key_up(key);
if (key_to_midi.contains(key))
{
int midi = key_to_midi.at(key);
auto & ch = channels_[midi]; auto & ch = channels_[midi];
if (auto s = ch->stream()) if (auto s = ch->stream())
ch->stream(audio::fade_out(s, 0.1f)); ch->stream(audio::fade_out(s, 0.1f));
channels_.erase(midi); channels_.erase(midi);
} }
if (key == SDLK_KP_PLUS) if (event.down && event.key == app::keycode::SPACE)
pause_control_->paused(!pause_control_->paused());
if (event.down && event.key == app::keycode::KP_PLUS)
pitch_control_->pitch(std::pow(2.f, 1.f / 12.f));
if (event.down && event.key == app::keycode::KP_MINUS)
pitch_control_->pitch(std::pow(2.f, - 1.f / 12.f));
if (!event.down && event.key == app::keycode::KP_PLUS)
pitch_control_->pitch(1.f); pitch_control_->pitch(1.f);
if (key == SDLK_KP_MINUS) if (!event.down && event.key == app::keycode::KP_MINUS)
pitch_control_->pitch(1.f); pitch_control_->pitch(1.f);
} }
@ -161,8 +154,8 @@ struct audio_app
float margin = 4.f; float margin = 4.f;
float border = 8.f; float border = 8.f;
float center = width() / 2.f; float center = state().size[0] / 2.f;
float y = height() / 2.f + size * std::size(key_rows) / 2.f; float y = state().size[1] / 2.f + size * std::size(key_rows) / 2.f;
gfx::painter::text_options opts; gfx::painter::text_options opts;
opts.scale = 2.f; opts.scale = 2.f;
@ -194,11 +187,11 @@ struct audio_app
opts.scale = 4.f; opts.scale = 4.f;
opts.c = pause_control_->paused() ? gfx::color_rgba{255, 0, 0, 255} : gfx::color_rgba{0, 127, 0, 255}; opts.c = pause_control_->paused() ? gfx::color_rgba{255, 0, 0, 255} : gfx::color_rgba{0, 127, 0, 255};
painter_.text({width() / 2.f, height() - 200.f}, pause_control_->paused() ? "PAUSED" : "PLAYING", opts); painter_.text({state().size[0] / 2.f, state().size[1] - 200.f}, pause_control_->paused() ? "PAUSED" : "PLAYING", opts);
{ {
float x = width() - 200.f; float x = state().size[0] - 200.f;
float y = height() / 2.f; float y = state().size[1] / 2.f;
float w = 4.f; float w = 4.f;
float h = 64.f; float h = 64.f;
@ -211,16 +204,16 @@ struct audio_app
painter_.rect({{{x - s, x + s}, {r - w, r + w}}}, {0, 0, 255, 255}); painter_.rect({{{x - s, x + s}, {r - w, r + w}}}, {0, 0, 255, 255});
} }
painter_.render(geom::window_camera{width(), height()}.transform()); painter_.render(geom::window_camera{state().size[0], state().size[1]}.transform());
} }
void on_scene_exit() override ~audio_app() override
{ {
prof::dump(); prof::dump();
} }
private: private:
audio::engine engine_; std::unique_ptr<audio::engine> engine_;
audio::mixer_ptr mixer_; audio::mixer_ptr mixer_;
std::shared_ptr<audio::volume_control_stereo> volume_control_; std::shared_ptr<audio::volume_control_stereo> volume_control_;
std::shared_ptr<audio::pitch_control> pitch_control_; std::shared_ptr<audio::pitch_control> pitch_control_;
@ -235,7 +228,12 @@ private:
gfx::painter painter_; gfx::painter painter_;
}; };
int main() namespace psemek::app
{ {
return app::main<audio_app>();
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<audio_app>({.name = "Audio example"});
}
} }

View file

@ -1,5 +1,5 @@
#include <psemek/app/app.hpp> #include <psemek/app/application_base.hpp>
#include <psemek/app/main.hpp> #include <psemek/app/default_application_factory.hpp>
#include <psemek/geom/box.hpp> #include <psemek/geom/box.hpp>
#include <psemek/geom/mesh.hpp> #include <psemek/geom/mesh.hpp>
#include <psemek/geom/intersection.hpp> #include <psemek/geom/intersection.hpp>
@ -23,6 +23,7 @@
#include <psemek/util/clock.hpp> #include <psemek/util/clock.hpp>
#include <psemek/util/to_string.hpp> #include <psemek/util/to_string.hpp>
#include <psemek/async/threadpool.hpp> #include <psemek/async/threadpool.hpp>
#include <psemek/log/log.hpp>
#include <iostream> #include <iostream>
@ -161,7 +162,7 @@ void main()
)"; )";
struct cloud_app struct cloud_app
: app::app : app::application_base
{ {
geom::spherical_camera camera; geom::spherical_camera camera;
@ -191,7 +192,6 @@ struct cloud_app
gfx::texture_3d shadow_texture; gfx::texture_3d shadow_texture;
cloud_app(std::size_t size) cloud_app(std::size_t size)
: app::app("Cloud")
{ {
geom::vector<int, 3> cloud_size{2 * size, size, size}; geom::vector<int, 3> cloud_size{2 * size, size, size};
bbox = {{{-2.f, 2.f}, {-1.f, 1.f}, {-1.f, 1.f}}}; bbox = {{{-2.f, 2.f}, {-1.f, 1.f}, {-1.f, 1.f}}};
@ -466,28 +466,35 @@ struct cloud_app
} }
} }
void on_resize(int width, int height) override void on_event(app::resize_event const & event) override
{ {
app::on_resize(width, height); app::application_base::on_event(event);
float aspect_ratio = (1.f * width) / height; gl::Viewport(0, 0, event.size[0], event.size[1]);
float aspect_ratio = (1.f * event.size[0]) / event.size[1];
camera.set_fov(camera.fov_y, aspect_ratio); camera.set_fov(camera.fov_y, aspect_ratio);
} }
void on_mouse_move(int x, int y, int dx, int dy) override void on_event(app::mouse_move_event const & event) override
{ {
app::on_mouse_move(x, y, dx, dy); auto const old_mouse = state().mouse;
if (is_middle_button_down()) app::application_base::on_event(event);
if (state().mouse_button_down.contains(app::mouse_button::middle))
{ {
camera.azimuthal_angle -= dx * 0.01f; auto const delta = event.position - old_mouse;
camera.elevation_angle += dy * 0.01f; camera.azimuthal_angle -= delta[0] * 0.01f;
camera.elevation_angle += delta[1] * 0.01f;
} }
} }
void on_mouse_wheel(int delta) override void on_event(app::mouse_wheel_event const & event) override
{ {
camera.distance *= std::pow(0.8f, delta); app::application_base::on_event(event);
camera.distance *= std::pow(0.8f, event.delta);
} }
void update_slice_mesh() void update_slice_mesh()
@ -534,6 +541,9 @@ struct cloud_app
slice_mesh.load(builder.vertices, builder.indices); slice_mesh.load(builder.vertices, builder.indices);
} }
void update() override
{}
void present() override void present() override
{ {
update_slice_mesh(); update_slice_mesh();
@ -592,18 +602,24 @@ struct cloud_app
} }
}; };
int main(int argc, char ** argv) namespace psemek::app
{ {
if (argc != 1 && argc != 2)
std::unique_ptr<application::factory> make_application_factory()
{ {
std::cout << "Usage: " << argv[0] << " [ size ]\n"; return default_application_factory({.name = "Cloud example"}, [](application::options const &, application::context const & context)
return 0; -> std::unique_ptr<application> {
if (context.args.size() != 1 && context.args.size() != 2)
{
std::cout << "Usage: " << context.args[0] << " [ size ]\n";
return nullptr;
}
std::size_t size = 32;
if (context.args.size() == 2)
size = util::from_string<std::size_t>(context.args[1]);
return std::make_unique<cloud_app>(size);
});
} }
std::size_t size = 32;
if (argc == 2)
{
size = util::from_string<std::size_t>(argv[1]);
}
return app::main<cloud_app>(size);
} }

View file

@ -1,5 +1,5 @@
#include <psemek/app/app.hpp> #include <psemek/app/application_base.hpp>
#include <psemek/app/main.hpp> #include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/renderer/deferred.hpp> #include <psemek/gfx/renderer/deferred.hpp>
#include <psemek/gfx/effect/gamma.hpp> #include <psemek/gfx/effect/gamma.hpp>
@ -22,14 +22,14 @@
using namespace psemek; using namespace psemek;
struct deferred_app struct deferred_app
: app::app : app::application_base
{ {
deferred_app(); deferred_app(options const & options, context const & context);
void on_resize(int width, int height) override; void on_event(app::resize_event const & event) override;
void on_mouse_move(int x, int y, int dx, int dy) override; void on_event(app::mouse_move_event const & event) override;
void on_mouse_wheel(int delta) override; void on_event(app::mouse_wheel_event const & event) override;
void update() override; void update() override;
void present() override; void present() override;
@ -60,10 +60,9 @@ struct deferred_app
gfx::painter painter; gfx::painter painter;
}; };
deferred_app::deferred_app() deferred_app::deferred_app(options const &, context const & context)
: app("Deferred shading example", 0)
{ {
vsync(false); context.vsync(false);
camera.fov_y = geom::rad(45.f); camera.fov_y = geom::rad(45.f);
camera.near_clip = 0.1f; camera.near_clip = 0.1f;
@ -291,34 +290,39 @@ deferred_app::deferred_app()
} }
} }
void deferred_app::on_resize(int width, int height) void deferred_app::on_event(app::resize_event const & event)
{ {
app::on_resize(width, height); app::application_base::on_event(event);
camera.set_fov(camera.fov_y, static_cast<float>(width) / height); camera.set_fov(camera.fov_y, static_cast<float>(event.size[0]) / event.size[1]);
pre_gamma_texture.load<geom::vector<std::uint16_t, 3>>({width, height}); pre_gamma_texture.load<geom::vector<std::uint16_t, 3>>(geom::cast<std::size_t>(event.size));
pre_gamma_framebuffer.color(pre_gamma_texture); pre_gamma_framebuffer.color(pre_gamma_texture);
pre_gamma_framebuffer.assert_complete(); pre_gamma_framebuffer.assert_complete();
pre_fxaa_texture.load<gfx::color_rgb>({width, height}); pre_fxaa_texture.load<gfx::color_rgb>(geom::cast<std::size_t>(event.size));
pre_fxaa_framebuffer.color(pre_fxaa_texture); pre_fxaa_framebuffer.color(pre_fxaa_texture);
pre_fxaa_framebuffer.assert_complete(); pre_fxaa_framebuffer.assert_complete();
} }
void deferred_app::on_mouse_move(int x, int y, int dx, int dy) void deferred_app::on_event(app::mouse_move_event const & event)
{ {
app::on_mouse_move(x, y, dx, dy); auto const old_mouse = state().mouse;
if (is_middle_button_down()) app::application_base::on_event(event);
if (state().mouse_button_down.contains(app::mouse_button::middle))
{ {
camera.azimuthal_angle -= dx * 0.01f; auto delta = event.position - old_mouse;
camera.elevation_angle += dy * 0.01f; camera.azimuthal_angle -= delta[0] * 0.01f;
camera.elevation_angle += delta[1] * 0.01f;
} }
} }
void deferred_app::on_mouse_wheel(int delta) void deferred_app::on_event(app::mouse_wheel_event const & event)
{ {
camera.distance *= std::pow(0.8f, delta); app::application_base::on_event(event);
camera.distance *= std::pow(0.8f, event.delta);
} }
void deferred_app::update() void deferred_app::update()
@ -458,7 +462,7 @@ void deferred_app::present()
gfx::render_target target; gfx::render_target target;
target.framebuffer = &pre_gamma_framebuffer; target.framebuffer = &pre_gamma_framebuffer;
target.draw_buffer = gl::COLOR_ATTACHMENT0; target.draw_buffer = gl::COLOR_ATTACHMENT0;
target.viewport = {{{0, width()}, {0, height()}}}; target.viewport = {{{0, state().size[0]}, {0, state().size[1]}}};
renderer.render(objects, target, options); renderer.render(objects, target, options);
} }
@ -466,7 +470,7 @@ void deferred_app::present()
gfx::render_target target; gfx::render_target target;
target.framebuffer = &pre_fxaa_framebuffer; target.framebuffer = &pre_fxaa_framebuffer;
target.draw_buffer = gl::COLOR_ATTACHMENT0; target.draw_buffer = gl::COLOR_ATTACHMENT0;
target.viewport = {{{0, width()}, {0, height()}}}; target.viewport = {{{0, state().size[0]}, {0, state().size[1]}}};
gamma_correction.invoke(pre_gamma_texture, target, {1.f / gamma}); gamma_correction.invoke(pre_gamma_texture, target, {1.f / gamma});
} }
@ -474,7 +478,7 @@ void deferred_app::present()
gfx::render_target target; gfx::render_target target;
target.framebuffer = &gfx::framebuffer::null(); target.framebuffer = &gfx::framebuffer::null();
target.draw_buffer = gl::BACK_LEFT; target.draw_buffer = gl::BACK_LEFT;
target.viewport = {{{0, width()}, {0, height()}}}; target.viewport = {{{0, state().size[0]}, {0, state().size[1]}}};
fxaa.invoke(pre_fxaa_texture, target); fxaa.invoke(pre_fxaa_texture, target);
} }
@ -491,10 +495,15 @@ void deferred_app::present()
gl::Enable(gl::BLEND); gl::Enable(gl::BLEND);
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
painter.render(geom::window_camera{width(), height()}.transform()); painter.render(geom::window_camera{state().size[0], state().size[1]}.transform());
} }
int main() namespace psemek::app
{ {
return app::main<deferred_app>();
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<deferred_app>({.name = "Deferred shading example", .multisampling = 0});
}
} }

View file

@ -1,5 +1,5 @@
#include <psemek/app/app.hpp> #include <psemek/app/application_base.hpp>
#include <psemek/app/main.hpp> #include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/mesh.hpp> #include <psemek/gfx/mesh.hpp>
#include <psemek/gfx/program.hpp> #include <psemek/gfx/program.hpp>
#include <psemek/gfx/texture.hpp> #include <psemek/gfx/texture.hpp>
@ -254,7 +254,7 @@ void candle_renderer::update_instances()
} }
struct fire_app struct fire_app
: app::app : app::application_base
{ {
geom::spherical_camera camera; geom::spherical_camera camera;
@ -263,8 +263,7 @@ struct fire_app
util::clock<std::chrono::duration<float>> clock; util::clock<std::chrono::duration<float>> clock;
float time = 0.f; float time = 0.f;
fire_app() fire_app(options const &, context const &)
: app("Fire")
{ {
camera.fov_y = geom::rad(45.f); camera.fov_y = geom::rad(45.f);
camera.near_clip = 0.01f; camera.near_clip = 0.01f;
@ -277,32 +276,36 @@ struct fire_app
candles.add({0.f, 0.f, 0.f}, {0.f, 0.f, 1.f}, 1.f); candles.add({0.f, 0.f, 0.f}, {0.f, 0.f, 1.f}, 1.f);
} }
void on_resize(int width, int height) override void on_event(app::resize_event const & event) override
{ {
app::on_resize(width, height); app::application_base::on_event(event);
camera.set_fov(camera.fov_y, (1.f * width) / height); camera.set_fov(camera.fov_y, (1.f * event.size[0]) / event.size[1]);
} }
void on_mouse_move(int x, int y, int dx, int dy) override void on_event(app::mouse_move_event const & event) override
{ {
app::on_mouse_move(x, y, dx, dy); auto const old_mouse = state().mouse;
if (is_middle_button_down()) app::application_base::on_event(event);
if (state().mouse_button_down.contains(app::mouse_button::middle))
{ {
camera.azimuthal_angle -= dx * 0.01f; auto const delta = event.position - old_mouse;
camera.elevation_angle += dy * 0.01f;
camera.azimuthal_angle -= delta[0] * 0.01f;
camera.elevation_angle += delta[1] * 0.01f;
} }
} }
void on_mouse_wheel(int delta) override void on_event(app::mouse_wheel_event const & event) override
{ {
app::on_mouse_wheel(delta); app::application_base::on_event(event);
camera.distance *= std::pow(0.8f, delta); camera.distance *= std::pow(0.8f, event.delta);
} }
void update() override void update() override
{ {
if (!is_key_down(SDLK_SPACE)) if (!state().key_down.contains(app::keycode::SPACE))
time += clock.restart().count(); time += clock.restart().count();
} }
@ -319,7 +322,12 @@ struct fire_app
}; };
int main() namespace psemek::app
{ {
return app::main<fire_app>();
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<fire_app>({.name = "Fire example", .multisampling = 4});
}
} }

View file

@ -1,636 +0,0 @@
#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/gfx/mesh.hpp>
#include <psemek/gfx/program.hpp>
#include <psemek/gfx/texture.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/gfx/framebuffer.hpp>
#include <psemek/gfx/renderbuffer.hpp>
#include <psemek/geom/camera.hpp>
#include <psemek/geom/math.hpp>
#include <psemek/geom/rotation.hpp>
#include <psemek/geom/scale.hpp>
#include <psemek/geom/translation.hpp>
#include <psemek/geom/easing.hpp>
#include <psemek/geom/gradient.hpp>
#include <psemek/geom/permutation.hpp>
#include <psemek/random/device.hpp>
#include <psemek/random/generator.hpp>
#include <psemek/random/uniform_sphere.hpp>
#include <psemek/random/uniform_ball.hpp>
#include <psemek/random/uniform_box.hpp>
#include <psemek/pcg/perlin.hpp>
#include <psemek/pcg/sample.hpp>
#include <psemek/util/clock.hpp>
#include <psemek/util/assert.hpp>
#include <psemek/util/moving_average.hpp>
#include <psemek/util/to_string.hpp>
#include <psemek/util/pretty_print.hpp>
using namespace psemek;
static char const ground_vs[] =
R"(#version 330
uniform mat4 u_transform;
layout (location = 0) in vec4 in_position;
layout (location = 1) in vec4 in_color;
out vec4 color;
void main()
{
gl_Position = u_transform * in_position;
color = in_color;
}
)";
static char const ground_fs[] =
R"(#version 330
in vec4 color;
out vec4 out_color;
void main()
{
out_color = color;
}
)";
static char const grass_vs[] =
R"(#version 330
uniform mat4 u_transform;
uniform mat4 u_tile_transform;
uniform float u_height00;
uniform float u_height01;
uniform float u_height10;
uniform float u_height11;
uniform sampler1D u_texture;
layout (location = 0) in vec4 in_position;
layout (location = 1) in float in_t;
out vec4 color;
void main()
{
vec2 p = (u_tile_transform * vec4(in_position.xy * 2.0 - vec2(0.5), 0.0, 1.0)).xy;
vec2 o = (u_tile_transform * vec4(0.5, 0.5, 0.0, 1.0)).xy;
o = vec2(floor(o.x), floor(o.y));
vec2 d = p - o;
float h = mix(mix(u_height00, u_height01, d.x), mix(u_height10, u_height11, d.x), d.y);
gl_Position = u_transform * vec4(p, in_position.z * h, 1.0);
// color = mix(vec4(0.0, 0.0, 0.0, 1.0), texture(u_texture, in_t), in_position.z);
color = texture(u_texture, in_t);
}
)";
static char const grass_fs[] =
R"(#version 330
in vec4 color;
out vec4 out_color;
void main()
{
out_color = color;
}
)";
static char const grass_slice_vs[] =
R"(#version 330
uniform mat4 u_transform;
uniform mat4 u_tile_transform;
uniform float u_density00;
uniform float u_density01;
uniform float u_density10;
uniform float u_density11;
layout (location = 0) in vec4 in_position;
layout (location = 1) in vec2 in_texcoord;
out vec3 texcoord;
void main()
{
vec2 p = (u_tile_transform * vec4(in_position.xy, 0.0, 1.0)).xy;
vec2 o = (u_tile_transform * vec4(0.5, 0.5, 0.0, 1.0)).xy;
o = vec2(floor(o.x), floor(o.y));
vec2 d = p - o;
float level = mix(mix(u_density00, u_density01, d.x), mix(u_density10, u_density11, d.x), d.y);
gl_Position = u_transform * vec4(p, in_position.z, 1.0);
texcoord = vec3(in_texcoord, level);
}
)";
static char const grass_slice_fs[] =
R"(#version 330
uniform sampler2DArray u_texture;
in vec3 texcoord;
out vec4 out_color;
void main()
{
float l0 = floor(texcoord.z);
float l1 = l0 + 1;
float t = texcoord.z - l0;
vec4 c0 = texture(u_texture, vec3(texcoord.xy, l0));
vec4 c1 = texture(u_texture, vec3(texcoord.xy, l1));
vec4 c = mix(c0, c1, t);
// vec4 c = c0;
out_color = vec4(c.rgb / c.a, c.a);
}
)";
struct grass_app
: app::app
{
geom::free_camera camera;
int size = 64;
int const density_level_count = 8;
pcg::perlin<float, 2> density;
gfx::program ground_program{ground_vs, ground_fs};
gfx::mesh ground_mesh;
gfx::program grass_program{grass_vs, grass_fs};
gfx::mesh grass_mesh;
gfx::texture_1d grass_texture;
gfx::program grass_slice_program{grass_slice_vs, grass_slice_fs};
gfx::texture_2d_array grass_slice_z_texture;
gfx::mesh grass_slice_z_mesh;
gfx::framebuffer grass_slice_framebuffer;
gfx::renderbuffer grass_slice_renderbuffer;
geom::matrix<float, 4, 4> random_transform[8];
util::array<int, 2> random_transform_index;
util::clock<> frame_clock;
util::moving_average<double> frame_time{64};
util::clock<std::chrono::duration<float>> update_clock;
gfx::painter painter;
grass_app()
: app("Grass", 4)
{
vsync(false);
camera.fov_y = geom::rad(45.f);
camera.near_clip = 0.1f;
camera.far_clip = 1000.f;
camera.pos = {0.5f, 0.5f, 1.5f};
camera.rotateYZ(geom::rad(-90.f));
init_ground();
init_grass();
init_grass_slices();
random::generator rng;
random::uniform_sphere_vector_distribution<float, 2> d;
util::array<geom::vector<float, 2>, 2> grad({size / 8 + 1, size / 8 + 1});
for (auto & v : grad) v = d(rng);
density = pcg::perlin<float, 2>(std::move(grad));
for (int i = 0; i < 8; ++i)
{
if (i < 4)
random_transform[i] = geom::matrix<float, 4, 4>::identity();
else
random_transform[i] = geom::swap<float, 3>(0, 1).homogeneous_matrix();
random_transform[i] = random_transform[i]
* geom::translation<float, 3>(geom::vector{0.5f, 0.5f, 0.f}).homogeneous_matrix()
* geom::plane_rotation<float, 3>(0, 1, i * geom::pi / 2.f).homogeneous_matrix()
* geom::translation<float, 3>(geom::vector{-0.5f, -0.5f, 0.f}).homogeneous_matrix();
}
random_transform_index.resize({size, size});
for (auto & i : random_transform_index)
i = random::uniform_int_distribution<int>{0, 7}(rng);
}
void init_ground()
{
ground_mesh.setup<geom::point<float, 3>, gfx::normalized<gfx::color_rgba>>();
struct vertex
{
geom::point<float, 3> pos;
gfx::color_rgba color;
};
std::vector<vertex> vertices;
std::vector<geom::triangle<std::uint32_t>> triangles;
gfx::color_rgba ground_color{47, 23, 11, 255};
for (int x = 0; x < size; ++x)
{
for (int y = 0; y < size; ++y)
{
std::uint32_t base = vertices.size();
float d = 0.0f;
vertices.push_back({{x + d, y + d, 0.f}, ground_color});
vertices.push_back({{x + 1 - d, y + d, 0.f}, ground_color});
vertices.push_back({{x + d, y + 1 - d, 0.f}, ground_color});
vertices.push_back({{x + 1 - d, y + 1 - d, 0.f}, ground_color});
triangles.push_back({base + 0, base + 1, base + 2});
triangles.push_back({base + 2, base + 1, base + 3});
}
}
ground_mesh.load(vertices, triangles, gl::STATIC_DRAW);
}
void init_grass()
{
frame_clock.restart();
struct vertex
{
geom::point<std::uint16_t, 3> pos;
std::uint8_t t;
std::uint8_t density;
vertex(geom::point<float, 3> const & p, float t, float d)
{
for (std::size_t i = 0; i < 2; ++i)
pos[i] = geom::clamp(std::round((p[i] + 0.5f) / 2.f * 65535.f), {0.f, 65535.f});
pos[2] = geom::clamp(std::round(p[2] * 65535.f), {0.f, 65535.f});
this->t = geom::clamp(std::round(t * 255.f), {0.f, 255.f});
this->density = geom::clamp(std::round(d * 255.f), {0.f, 255.f});
}
};
{
geom::gradient<float, gfx::color_4f> g
{
std::make_pair(-1.f, gfx::color_4f{0.f, 0.2f, 0.f, 1.f}),
geom::easing_type::linear,
std::pair{0.f, gfx::color_4f{0.4f, 0.65f, 0.35f, 1.f}},
geom::easing_type::linear,
std::pair{1.f, gfx::color_4f{0.7f, 0.75f, 0.2f, 1.f}}
};
util::array<gfx::color_rgba, 1> pm({256});
for (std::size_t i = 0; i < pm.width(); ++i)
{
pm(i) = gfx::to_coloru8(g((i + 0.5f) / pm.width()));
}
grass_texture.load(pm);
grass_texture.linear_filter();
grass_texture.generate_mipmap();
}
random::generator rng;
std::size_t memory = 0;
grass_mesh.setup<gfx::normalized<geom::point<std::uint16_t, 3>>, gfx::normalized<std::uint8_t>, gfx::normalized<std::uint8_t>>();
std::vector<vertex> vertices;
std::vector<geom::triangle<std::uint32_t>> triangles;
random::uniform_box_point_distribution<float, 2> d_origin({{{0.f, 1.f}, {0.f, 1.f}}});
random::uniform_sphere_vector_distribution<float, 2> d_orientation;
random::uniform_real_distribution<float> d_width{1.f / 64.f, 1.f / 128.f};
random::uniform_real_distribution<float> d_height{0.25f, 1.f};
random::uniform_real_distribution<float> d_density{0.f, 1.f};
for (int blade = 0; blade < 4096; ++blade)
{
int const segments = 8;
float const max_lean = 0.2f;
int tx = (blade % 64) % 8;
int ty = (blade % 64) / 8;
auto o = d_origin(rng);
o[0] = o[0] / 8.f + tx / 8.f;
o[1] = o[1] / 8.f + ty / 8.f;
auto r = d_orientation(rng);
auto s = d_width(rng) * 0.5f;
auto h = d_height(rng);
auto n = geom::ort(r);
auto color = random::uniform_real_distribution<float>{0.f, 1.f}(rng);
float den = d_density(rng);
auto get = [&](float x, float z) -> vertex
{
assert(z >= 0.f);
assert(z <= 1.f);
float y = (1.f - std::sqrt(1.f - z * z)) * max_lean;
float w = 1.f - z / h;
return {geom::point{o[0] + r[0] * s * x * w + n[0] * y, o[1] + r[1] * s * x * w + n[1] * y, z}, color, den};
};
for (int s = 0; s <= segments; ++s)
{
float t = (s * 1.f) / segments;
t = geom::easing(geom::easing_type::quadratic_out, t);
float z = h * t;
std::uint32_t base = vertices.size();
if (s < segments)
{
vertices.push_back(get(-1.f, z));
vertices.push_back(get( 1.f, z));
if (s > 0)
{
triangles.push_back({base - 2, base - 1, base});
triangles.push_back({base, base - 1, base + 1});
}
}
else
{
vertices.push_back(get(0.f, z));
triangles.push_back({base - 2, base - 1, base});
}
}
}
grass_mesh.load(vertices, triangles, gl::STATIC_DRAW);
memory += (vertices.size() * sizeof(vertices[0]) + triangles.size() * sizeof(triangles[0]));
log::info() << memory << " bytes";
log::info() << vertices.size() << " vertices";
log::info() << frame_clock.count();
}
void init_grass_slices()
{
struct vertex
{
geom::point<float, 3> position;
geom::vector<float, 2> texcoord;
};
int const slice_count = 4;
float slice_width = 1.f / slice_count;
std::vector<vertex> vertices;
vertices.push_back({{0.f, 0.f, slice_width}, {0.f, 0.f}});
vertices.push_back({{1.f, 0.f, slice_width}, {1.f, 0.f}});
vertices.push_back({{0.f, 1.f, slice_width}, {0.f, 1.f}});
vertices.push_back({{0.f, 1.f, slice_width}, {0.f, 1.f}});
vertices.push_back({{1.f, 0.f, slice_width}, {1.f, 0.f}});
vertices.push_back({{1.f, 1.f, slice_width}, {1.f, 1.f}});
grass_slice_z_mesh.setup<geom::point<float, 3>, geom::vector<float, 2>>();
grass_slice_z_mesh.load(vertices, gl::TRIANGLES, gl::STATIC_DRAW);
std::size_t slice_resolution = 256;
grass_slice_z_texture.load<gfx::color_rgba>({slice_resolution, slice_resolution, density_level_count});
grass_slice_renderbuffer.storage(gl::DEPTH24_STENCIL8, {slice_resolution, slice_resolution});
for (int d = 0; d < density_level_count; ++d)
{
grass_slice_framebuffer.color(grass_slice_z_texture, d);
grass_slice_framebuffer.depth(grass_slice_renderbuffer);
grass_slice_framebuffer.assert_complete();
gl::Viewport(0, 0, slice_resolution, slice_resolution);
gl::ClearColor(0.f, 0.f, 0.f, 0.f);
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
gl::Enable(gl::DEPTH_TEST);
gl::DepthFunc(gl::LEQUAL);
grass_program.bind();
grass_program["u_tile_transform"] = geom::matrix<float, 4, 4>::identity();
grass_program["u_height00"] = 1.f;
grass_program["u_height01"] = 1.f;
grass_program["u_height10"] = 1.f;
grass_program["u_height11"] = 1.f;
grass_program["u_texture"] = 0;
grass_texture.bind();
for (int x = -1; x <= 1; ++x)
{
for (int y = -1; y <= 1; ++y)
{
grass_program["u_transform"] = geom::translation<float, 3>({-1.f + 2.f * x, -1.f + 2.f * y, 0.f}).homogeneous_matrix() * geom::scale<float, 3>({2.f, 2.f, 1.f}).homogeneous_matrix();
grass_mesh.draw(0, (grass_mesh.index_count() * (d + 1)) / density_level_count);
}
}
}
gfx::framebuffer::null().bind();
grass_slice_z_texture.linear_filter();
grass_slice_z_texture.anisotropy();
grass_slice_z_texture.generate_mipmap();
grass_slice_z_texture.clamp();
// grass_slice_z_texture.bind();
// gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST);
// gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR);
}
void on_resize(int width, int height) override
{
app::on_resize(width, height);
camera.set_fov(camera.fov_y, (1.f * width) / height);
}
void on_mouse_move(int x, int y, int dx, int dy) override
{
app::on_mouse_move(x, y, dx, dy);
if (is_middle_button_down())
{
camera.rotateZX(0.01f * dx);
camera.rotateYZ(0.01f * dy);
}
}
void on_mouse_wheel(int delta) override
{
app::on_mouse_wheel(delta);
}
void update() override
{
float dt = update_clock.restart().count();
auto d = camera.direction();
float s = 20.f;
if (is_key_down(SDLK_LSHIFT))
s = 2.5f;
if (is_key_down(SDLK_SPACE))
{
d[2] = 0.f;
d = geom::normalized(d);
}
auto n = geom::normalized(geom::cross(d, geom::vector{0.f, 0.f, 1.f}));
if (is_key_down(SDLK_w))
{
camera.pos += d * dt * s;
}
if (is_key_down(SDLK_s))
{
camera.pos -= d * dt * s;
}
if (is_key_down(SDLK_a))
{
camera.pos -= n * dt * s;
}
if (is_key_down(SDLK_d))
{
camera.pos += n * dt * s;
}
}
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::LEQUAL);
gl::Enable(gl::BLEND);
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
auto camera_transform = camera.transform();
ground_program.bind();
ground_program["u_transform"] = camera_transform;
ground_mesh.draw();
grass_program.bind();
grass_program["u_texture"] = 0;
grass_texture.bind();
grass_program["u_transform"] = camera_transform;
std::size_t triangles = 0;
for (int x = 0; x < size; ++x)
{
for (int y = 0; y < size; ++y)
{
if (x < size / 2) continue;
grass_program["u_tile_transform"] = geom::translation<float, 3>(geom::vector{x, y, 0.f}).homogeneous_matrix()
* random_transform[random_transform_index(x, y)];
float d = density({(x + 0.5f) / size, (y + 0.5f) / size});
int i = std::floor(d * density_level_count);
if (i > density_level_count - 1) i = density_level_count - 1;
{
float h00 = density({(x + 0.f) / size, (y + 0.f) / size});
float h01 = density({(x + 1.f) / size, (y + 0.f) / size});
float h10 = density({(x + 0.f) / size, (y + 1.f) / size});
float h11 = density({(x + 1.f) / size, (y + 1.f) / size});
grass_program["u_height00"] = h00;
grass_program["u_height01"] = h01;
grass_program["u_height10"] = h10;
grass_program["u_height11"] = h11;
grass_mesh.draw(0, ((i + 1) * grass_mesh.index_count()) / density_level_count);
triangles += (((i + 1) * grass_mesh.index_count()) / density_level_count) / 3;
}
}
}
grass_slice_program.bind();;
grass_slice_program["u_transform"] = camera_transform;
grass_slice_program["u_texture"] = 0;
grass_slice_z_texture.bind();
for (int x = 0; x < size; ++x)
{
for (int y = 0; y < size; ++y)
{
if (x >= size / 2) continue;
grass_slice_program["u_tile_transform"] = geom::translation<float, 3>(geom::vector{x, y, 0.f}).homogeneous_matrix()
* random_transform[random_transform_index(x, y)];
float d00 = density({(x + 0.f) / size, (y + 0.f) / size}) * density_level_count;
float d01 = density({(x + 1.f) / size, (y + 0.f) / size}) * density_level_count;
float d10 = density({(x + 0.f) / size, (y + 1.f) / size}) * density_level_count;
float d11 = density({(x + 1.f) / size, (y + 1.f) / size}) * density_level_count;
grass_slice_program["u_density00"] = d00;
grass_slice_program["u_density01"] = d01;
grass_slice_program["u_density10"] = d10;
grass_slice_program["u_density11"] = d11;
grass_slice_z_mesh.draw();
}
}
frame_time.push(frame_clock.restart().count());
{
gfx::painter::text_options opts;
opts.x = gfx::painter::x_align::left;
opts.y = gfx::painter::y_align::top;
opts.f = gfx::painter::font::font_9x12;
opts.scale = 2.f;
opts.c = {0, 0, 0, 255};
painter.text({0.f, 0.f}, util::to_string("FPS: ", std::round(1.0 / frame_time.average())), opts);
painter.text({0.f, 24.f}, util::to_string("Camera: ", camera.position()), opts);
painter.text({0.f, 48.f}, util::to_string("Triangles: ", triangles), opts);
}
gl::Disable(gl::DEPTH_TEST);
gl::Enable(gl::BLEND);
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
geom::window_camera camera;
camera.width = width();
camera.height = height();
painter.render(camera.transform());
}
};
int main()
{
return app::main<grass_app>();
}

View file

@ -1,5 +1,5 @@
#include <psemek/app/app.hpp> #include <psemek/app/application_base.hpp>
#include <psemek/app/main.hpp> #include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/painter.hpp> #include <psemek/gfx/painter.hpp>
#include <psemek/gfx/gl.hpp> #include <psemek/gfx/gl.hpp>
#include <psemek/geom/scale.hpp> #include <psemek/geom/scale.hpp>
@ -7,14 +7,12 @@
#include <psemek/geom/constants.hpp> #include <psemek/geom/constants.hpp>
#include <psemek/util/clock.hpp> #include <psemek/util/clock.hpp>
#include <psemek/util/to_string.hpp> #include <psemek/util/to_string.hpp>
#include <psemek/audio/engine.hpp>
#include <psemek/audio/constants.hpp>
#include <psemek/audio/detail/oscillator.hpp>
#include <psemek/audio/effect/compressor.hpp>
#include <psemek/prof/profiler.hpp> #include <psemek/prof/profiler.hpp>
#include <psemek/util/moving_average.hpp> #include <psemek/util/moving_average.hpp>
#include <psemek/log/log.hpp>
#include <random> #include <random>
#include <set>
/* /*
No optimizations, 125: No optimizations, 125:
@ -85,15 +83,15 @@ geom::point world_center{0.f, 0.f};
int const SOLVE_ITERATIONS = 16; int const SOLVE_ITERATIONS = 16;
float const BIAS = 1.f / 8.f; float const BIAS = 1.f / 8.f;
struct myapp : app::app struct myapp
: app::application_base
{ {
myapp() myapp(options const &, context const & context)
: app("Test app", 4) : rng_{std::random_device{}()}
, rng_{std::random_device{}()}
{ {
gl::ClearColor(1.f, 1.f, 1.f, 1.f); gl::ClearColor(1.f, 1.f, 1.f, 1.f);
vsync(true); context.vsync(true);
std::uniform_real_distribution<float> d{-50.f, 50.f}; std::uniform_real_distribution<float> d{-50.f, 50.f};
std::uniform_real_distribution<float> rr{0.5f, 2.f}; std::uniform_real_distribution<float> rr{0.5f, 2.f};
@ -181,32 +179,32 @@ struct myapp : app::app
p.old_pos = p.pos; p.old_pos = p.pos;
} }
void on_resize(int width, int height) override void on_event(app::resize_event const & event) override
{ {
app::on_resize(width, height); app::application_base::on_event(event);
gl::Viewport(0, 0, width, height); gl::Viewport(0, 0, event.size[0], event.size[1]);
window_size_ = {width, height}; camera_ratio_ = static_cast<float>(event.size[0]) / event.size[1];
camera_ratio_ = static_cast<float>(width) / height;
} }
void on_mouse_wheel(int delta) override void on_event(app::mouse_wheel_event const & event) override
{ {
camera_size_ *= std::pow(0.8f, delta); app::application_base::on_event(event);
camera_size_ *= std::pow(0.8f, event.delta);
} }
void on_left_button_down() override void on_event(app::mouse_button_event const & event) override
{ {
if (mouse_) if (event.button == app::mouse_button::left && event.down)
{ {
geom::scale<float, 2> const flip_y({1.f, -1.f}); geom::scale<float, 2> const flip_y({1.f, -1.f});
float const scale = camera_size_ / window_size_[1]; float const scale = camera_size_ / state().size[1];
geom::point<int, 2> const screen_center { window_size_[0] / 2, window_size_[1] / 2 }; geom::point<int, 2> const screen_center { state().size[0] / 2,state().size[1] / 2 };
auto target = camera_center_ + flip_y(geom::cast<float>(*mouse_ - screen_center) * scale); auto target = camera_center_ + flip_y(geom::cast<float>(state().mouse - screen_center) * scale);
force_target_ = target; force_target_ = target;
// geom::point<float, 2> pos{100.f, 0.f}; // geom::point<float, 2> pos{100.f, 0.f};
@ -221,57 +219,57 @@ struct myapp : app::app
// p.vel += 4000.f * r / geom::length_sqr(r) / p.mass; // p.vel += 4000.f * r / geom::length_sqr(r) / p.mass;
// } // }
} }
if (event.button == app::mouse_button::left && !event.down)
{
force_target_ = std::nullopt;
}
if (event.button == app::mouse_button::right && event.down)
{
camera_drag_ = state().mouse;
}
if (event.button == app::mouse_button::right && !event.down)
{
camera_drag_ = std::nullopt;
}
} }
void on_left_button_up() override void on_event(app::mouse_move_event const & event) override
{ {
force_target_ = std::nullopt; app::application_base::on_event(event);
}
void on_right_button_down() override
{
camera_drag_ = mouse_;
}
void on_right_button_up() override
{
camera_drag_ = std::nullopt;
}
void on_mouse_move(int x, int y, int, int) override
{
mouse_ = {x, y};
if (camera_drag_) if (camera_drag_)
{ {
geom::scale<float, 2> const flip_y({1.f, -1.f}); geom::scale<float, 2> const flip_y({1.f, -1.f});
float const scale = camera_size_ / window_size_[1]; float const scale = camera_size_ / state().size[1];
camera_center_ += flip_y(geom::cast<float>(*camera_drag_ - *mouse_) * scale); camera_center_ += flip_y(geom::cast<float>(*camera_drag_ - event.position) * scale);
camera_drag_ = mouse_; camera_drag_ = event.position;
} }
if (force_target_) if (force_target_)
{ {
geom::scale<float, 2> const flip_y({1.f, -1.f}); geom::scale<float, 2> const flip_y({1.f, -1.f});
float const scale = camera_size_ / window_size_[1]; float const scale = camera_size_ / state().size[1];
geom::point<int, 2> const screen_center { window_size_[0] / 2, window_size_[1] / 2 }; geom::point<int, 2> const screen_center { state().size[0] / 2, state().size[1] / 2 };
auto target = camera_center_ + flip_y(geom::cast<float>(*mouse_ - screen_center) * scale); auto target = camera_center_ + flip_y(geom::cast<float>(state().mouse - screen_center) * scale);
force_target_ = target; force_target_ = target;
} }
} }
void on_key_down(SDL_Keycode key) override void on_event(app::key_event const & event) override
{ {
app::app::on_key_down(key); app::application_base::on_event(event);
if (key == SDLK_SPACE) if (event.key == app::keycode::SPACE && event.down)
{ {
paused_ = !paused_; paused_ = !paused_;
} }
if (key == SDLK_c) if (event.key == app::keycode::C && event.down)
{ {
particles_.push_back({{200.f, 0.f}, {-5000.f, 0.f}, 0.f, 0.f, 1.f, 100.f, 1.f}); particles_.push_back({{200.f, 0.f}, {-5000.f, 0.f}, 0.f, 0.f, 1.f, 100.f, 1.f});
particles_.back().old_pos = particles_.back().pos; particles_.back().old_pos = particles_.back().pos;
@ -365,7 +363,7 @@ struct myapp : app::app
// log::info() << "Force: #" << i << " = " << std::setprecision(10) << particles_[i].acc; // log::info() << "Force: #" << i << " = " << std::setprecision(10) << particles_[i].acc;
// } // }
if (is_key_down(SDLK_m)) if (state().key_down.contains(app::keycode::M))
{ {
for (auto & p : particles_) for (auto & p : particles_)
p.vel *= std::exp(-100.f*dt); p.vel *= std::exp(-100.f*dt);
@ -1188,7 +1186,7 @@ struct myapp : app::app
float c = 1.f - p.density; float c = 1.f - p.density;
auto x = static_cast<std::uint8_t>(c * 255.f); auto x = static_cast<std::uint8_t>(c * 255.f);
float s = window_size_[1] / camera_size_; float s = state().size[1] / camera_size_;
float r = std::max(p.radius * s, 1.5f) / s; float r = std::max(p.radius * s, 1.5f) / s;
painter_.circle(p.pos, r, {x, x, x, 191}); painter_.circle(p.pos, r, {x, x, x, 191});
@ -1225,15 +1223,15 @@ struct myapp : app::app
painter_.text(pos + geom::vector{1.f, 0.f}, str, opts); painter_.text(pos + geom::vector{1.f, 0.f}, str, opts);
}; };
put({width() / 2.f, 30.f}, util::to_string("ITERATIONS: ", SOLVE_ITERATIONS)); put({state().size[0] / 2.f, 30.f}, util::to_string("ITERATIONS: ", SOLVE_ITERATIONS));
put({width() / 2.f, 60.f}, BIAS == 1.f ? "BIAS: 1" : util::to_string("BIAS: 1/", std::round(1.f / BIAS))); put({state().size[0] / 2.f, 60.f}, BIAS == 1.f ? "BIAS: 1" : util::to_string("BIAS: 1/", std::round(1.f / BIAS)));
// painter_.text({10.f, 10.f}, util::to_string("Angular velocity: ", rotation_.average()), opts); // painter_.text({10.f, 10.f}, util::to_string("Angular velocity: ", rotation_.average()), opts);
// painter_.text({10.f, 30.f}, util::to_string("Collisions: ", collisions_, " = ", (collisions_ * 1.f / particles_.size() / particles_.size()), " N^2"), opts); // painter_.text({10.f, 30.f}, util::to_string("Collisions: ", collisions_, " = ", (collisions_ * 1.f / particles_.size() / particles_.size()), " N^2"), opts);
// painter_.text({10.f, 50.f}, util::to_string("Energy: ", energy_), opts); // painter_.text({10.f, 50.f}, util::to_string("Energy: ", energy_), opts);
} }
painter_.render(geom::window_camera{width(), height()}.transform()); painter_.render(geom::window_camera{state().size[0], state().size[1]}.transform());
} }
~myapp() ~myapp()
@ -1247,15 +1245,12 @@ struct myapp : app::app
private: private:
std::default_random_engine rng_; std::default_random_engine rng_;
geom::vector<int, 2> window_size_;
std::optional<geom::point<float, 2>> force_target_; std::optional<geom::point<float, 2>> force_target_;
geom::point<float, 2> camera_center_ { 0.f, 0.f }; geom::point<float, 2> camera_center_ { 0.f, 0.f };
float camera_size_ = 150.f; float camera_size_ = 150.f;
float camera_ratio_ = 1.f; float camera_ratio_ = 1.f;
std::optional<geom::point<int, 2>> mouse_;
std::optional<geom::point<int, 2>> camera_drag_; std::optional<geom::point<int, 2>> camera_drag_;
gfx::painter painter_; gfx::painter painter_;
@ -1266,8 +1261,6 @@ private:
float total_forces_ = 0.f; float total_forces_ = 0.f;
float total_collisions_ = 0.f; float total_collisions_ = 0.f;
audio::engine audio_;
util::moving_average<float> rotation_{300}; util::moving_average<float> rotation_{300};
int collisions_ = 0; int collisions_ = 0;
float energy_ = 0.f; float energy_ = 0.f;
@ -1275,7 +1268,12 @@ private:
bool paused_ = true; bool paused_ = true;
}; };
int main() namespace psemek::app
{ {
return app::main<myapp>();
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<myapp>({.name = "Gravity example"});
}
} }

View file

@ -1,5 +1,7 @@
#include <psemek/parser/primitives.hpp> #include <psemek/parser/primitives.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <iterator> #include <iterator>
@ -74,14 +76,23 @@ Stream & operator << (Stream & s, std::variant<T, Ts...> const & v)
return s; return s;
} }
int main() namespace psemek::app
{ {
using namespace psemek::parser;
auto const p = map(concat(integer<int>, ws, one_of(ch('+'), ch('-')), ws, integer<int>), [](auto const & t){ std::unique_ptr<application::factory> make_application_factory()
auto id = [](auto x){ return x; }; {
return std::make_tuple(std::get<0>(t), std::visit(id, std::get<2>(t)), std::get<4>(t)); return default_application_factory({.name = "Parser example"}, [](auto const & ...){
}); using namespace psemek::parser;
auto const p = map(concat(integer<int>, ws, one_of(ch('+'), ch('-')), ws, integer<int>), [](auto const & t){
auto id = [](auto x){ return x; };
return std::make_tuple(std::get<0>(t), std::visit(id, std::get<2>(t)), std::get<4>(t));
});
std::cout << p.parse("45 + 67") << std::endl;
return nullptr;
});
}
std::cout << p.parse("45 + 67") << std::endl;
} }

View file

@ -1,5 +1,5 @@
#include <psemek/app/app.hpp> #include <psemek/app/application_base.hpp>
#include <psemek/app/main.hpp> #include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/gl.hpp> #include <psemek/gfx/gl.hpp>
#include <psemek/gfx/painter.hpp> #include <psemek/gfx/painter.hpp>
@ -17,6 +17,8 @@
#include <psemek/util/to_string.hpp> #include <psemek/util/to_string.hpp>
#include <psemek/util/statistics.hpp> #include <psemek/util/statistics.hpp>
#include <psemek/log/log.hpp>
#include <vector> #include <vector>
using namespace psemek; using namespace psemek;
@ -69,23 +71,20 @@ void stick_model::add_stick(std::size_t i, std::size_t j, bool solid)
static const float ball_radius = 0.1f; static const float ball_radius = 0.1f;
struct physics_demo_app struct physics_demo_app
: app::app : app::application_base
{ {
physics_demo_app(); physics_demo_app(options const &, context const &);
~physics_demo_app(); ~physics_demo_app();
void on_resize(int width, int height) override; void on_event(app::resize_event const & event) override;
void on_left_button_down() override; void on_event(app::mouse_button_event const & event) override;
void on_left_button_up() override;
void on_right_button_down() override; void on_event(app::mouse_move_event const & event) override;
void on_mouse_move(int x, int y, int, int) override; void on_event(app::key_event const & event) override;
void on_key_down(SDL_Keycode key) override; void on_event(app::mouse_wheel_event const & event) override;
void on_mouse_wheel(int delta) override;
void update() override; void update() override;
void present() override; void present() override;
@ -115,8 +114,7 @@ struct physics_demo_app
util::statistics<float> physics_update_stats; util::statistics<float> physics_update_stats;
}; };
physics_demo_app::physics_demo_app() physics_demo_app::physics_demo_app(options const &, context const &)
: app("Physics Demo", 4)
{ {
view_region[0] = {0.f, 0.f}; view_region[0] = {0.f, 0.f};
view_region[1] = {-2.f, 5.f}; view_region[1] = {-2.f, 5.f};
@ -204,11 +202,11 @@ physics_demo_app::~physics_demo_app()
log::info() << "Update: " << physics_update_stats; log::info() << "Update: " << physics_update_stats;
} }
void physics_demo_app::on_resize(int width, int height) void physics_demo_app::on_event(app::resize_event const & event)
{ {
app::on_resize(width, height); app::application_base::on_event(event);
float aspect_ratio = static_cast<float>(width) / height; float aspect_ratio = static_cast<float>(event.size[0]) / event.size[1];
float xc = view_region[0].center(); float xc = view_region[0].center();
float yl = view_region[1].length(); float yl = view_region[1].length();
@ -217,49 +215,46 @@ void physics_demo_app::on_resize(int width, int height)
view_region[0].max = xc + yl / 2.f * aspect_ratio; view_region[0].max = xc + yl / 2.f * aspect_ratio;
} }
void physics_demo_app::on_left_button_down() void physics_demo_app::on_event(app::mouse_button_event const & event)
{ {
app::on_left_button_down(); app::application_base::on_event(event);
if (mouse() && closest_point) if (event.button == app::mouse_button::left && event.down && closest_point)
{ {
drag_delta = world_to_screen(model.points[*closest_point]) - geom::cast<float>(*mouse()); drag_delta = world_to_screen(model.points[*closest_point]) - geom::cast<float>(state().mouse);
model.vels[*closest_point] = geom::vector<float, 2>::zero(); model.vels[*closest_point] = geom::vector<float, 2>::zero();
} }
}
void physics_demo_app::on_left_button_up() if (event.button == app::mouse_button::left && !event.down)
{
app::on_left_button_up();
drag_delta = std::nullopt;
}
void physics_demo_app::on_right_button_down()
{
if (new_spring_start)
{ {
new_spring_start = std::nullopt; drag_delta = std::nullopt;
} }
else if (closest_point)
if (event.button == app::mouse_button::right && event.down)
{ {
model.movable[*closest_point] = !model.movable[*closest_point]; if (new_spring_start)
model.vels[*closest_point] = geom::vector<float, 2>::zero(); {
new_spring_start = std::nullopt;
}
else if (closest_point)
{
model.movable[*closest_point] = !model.movable[*closest_point];
model.vels[*closest_point] = geom::vector<float, 2>::zero();
}
} }
} }
void physics_demo_app::on_mouse_move(int x, int y, int dx, int dy) void physics_demo_app::on_event(app::mouse_move_event const & event)
{ {
app::on_mouse_move(x, y, dx, dy); app::application_base::on_event(event);
if (!drag_delta) if (!drag_delta)
{ {
closest_point = std::nullopt; closest_point = std::nullopt;
closest_stick = std::nullopt; closest_stick = std::nullopt;
if (mouse())
{ {
auto const m = geom::cast<float>(*mouse()); auto const m = geom::cast<float>(state().mouse);
std::size_t closest = 0; std::size_t closest = 0;
float distance = std::numeric_limits<float>::infinity(); float distance = std::numeric_limits<float>::infinity();
@ -303,13 +298,15 @@ void physics_demo_app::on_mouse_move(int x, int y, int dx, int dy)
} }
} }
void physics_demo_app::on_key_down(SDL_Keycode key) void physics_demo_app::on_event(app::key_event const & event)
{ {
if (key == SDLK_SPACE) app::application_base::on_event(event);
if (event.down && event.key == app::keycode::SPACE)
{ {
paused = !paused; paused = !paused;
} }
else if (key == SDLK_x) else if (event.down && event.key == app::keycode::X)
{ {
if (closest_point) if (closest_point)
{ {
@ -350,7 +347,7 @@ void physics_demo_app::on_key_down(SDL_Keycode key)
closest_stick = std::nullopt; closest_stick = std::nullopt;
} }
} }
else if (key == SDLK_c) else if (event.down && event.key == app::keycode::C)
{ {
if (new_spring_start && closest_point) if (new_spring_start && closest_point)
{ {
@ -360,9 +357,9 @@ void physics_demo_app::on_key_down(SDL_Keycode key)
new_spring_start = std::nullopt; new_spring_start = std::nullopt;
} }
} }
else if (new_spring_start && mouse()) else if (new_spring_start)
{ {
model.points.push_back(screen_to_world(geom::cast<float>(*mouse()))); model.points.push_back(screen_to_world(geom::cast<float>(state().mouse)));
model.vels.push_back(geom::vector<float, 2>::zero()); model.vels.push_back(geom::vector<float, 2>::zero());
model.movable.push_back(false); model.movable.push_back(false);
model.add_stick(*new_spring_start, model.points.size() - 1); model.add_stick(*new_spring_start, model.points.size() - 1);
@ -373,65 +370,59 @@ void physics_demo_app::on_key_down(SDL_Keycode key)
new_spring_start = *closest_point; new_spring_start = *closest_point;
closest_point = std::nullopt; closest_point = std::nullopt;
} }
else if (mouse()) else
{ {
model.points.push_back(screen_to_world(geom::cast<float>(*mouse()))); model.points.push_back(screen_to_world(geom::cast<float>(state().mouse)));
model.vels.push_back(geom::vector<float, 2>::zero()); model.vels.push_back(geom::vector<float, 2>::zero());
model.movable.push_back(false); model.movable.push_back(false);
} }
} }
else if (key == SDLK_n) else if (event.down && event.key == app::keycode::N)
{ {
if (mouse()) model.points.push_back(screen_to_world(geom::cast<float>(state().mouse)));
{ model.vels.push_back(geom::vector<float, 2>::zero());
model.points.push_back(screen_to_world(geom::cast<float>(*mouse()))); model.movable.push_back(true);
model.vels.push_back(geom::vector<float, 2>::zero());
model.movable.push_back(true);
}
} }
else if (key == SDLK_f) else if (event.down && event.key == app::keycode::F)
{ {
if (closest_stick) if (closest_stick)
{ {
model.stick_solid[*closest_stick] = !model.stick_solid[*closest_stick]; model.stick_solid[*closest_stick] = !model.stick_solid[*closest_stick];
} }
} }
else if (key == SDLK_w) else if (event.down && event.key == app::keycode::W)
{
if (mouse())
{
std::uint32_t const base = model.points.size();
int N = 12;
auto o = screen_to_world(geom::cast<float>(*mouse()));
model.points.push_back(o);
model.vels.push_back(geom::vector<float, 2>::zero());
model.movable.push_back(true);
float r = 0.5f;
for (int i = 0; i < N; ++i)
{
float a = (2.f * geom::pi * i) / N;
model.points.push_back({o[0] + std::cos(a) * r, o[1] + std::sin(a) * r});
model.vels.push_back(geom::vector<float, 2>::zero());
model.movable.push_back(true);
}
for (int i = 0; i < N; ++i)
{
model.add_stick(base, base + i + 1, false);
model.add_stick(base + i + 1, base + 1 + ((i + 1) % N));
}
}
}
else if (key == SDLK_b)
{ {
std::uint32_t const base = model.points.size(); std::uint32_t const base = model.points.size();
auto o = screen_to_world(geom::cast<float>(*mouse())); int N = 12;
auto o = screen_to_world(geom::cast<float>(state().mouse));
model.points.push_back(o);
model.vels.push_back(geom::vector<float, 2>::zero());
model.movable.push_back(true);
float r = 0.5f;
for (int i = 0; i < N; ++i)
{
float a = (2.f * geom::pi * i) / N;
model.points.push_back({o[0] + std::cos(a) * r, o[1] + std::sin(a) * r});
model.vels.push_back(geom::vector<float, 2>::zero());
model.movable.push_back(true);
}
for (int i = 0; i < N; ++i)
{
model.add_stick(base, base + i + 1, false);
model.add_stick(base + i + 1, base + 1 + ((i + 1) % N));
}
}
else if (event.down && event.key == app::keycode::B)
{
std::uint32_t const base = model.points.size();
auto o = screen_to_world(geom::cast<float>(state().mouse));
float w = 0.25f; float w = 0.25f;
@ -458,13 +449,15 @@ void physics_demo_app::on_key_down(SDL_Keycode key)
} }
} }
void physics_demo_app::on_mouse_wheel(int delta) void physics_demo_app::on_event(app::mouse_wheel_event const & event)
{ {
app::application_base::on_event(event);
if (closest_stick) if (closest_stick)
{ {
auto & l = model.stick_length[*closest_stick]; auto & l = model.stick_length[*closest_stick];
l += delta * ball_radius / 2.f; l += event.delta * ball_radius / 2.f;
l = std::max(3.f * ball_radius, l); l = std::max(3.f * ball_radius, l);
} }
@ -584,9 +577,9 @@ void physics_demo_app::update()
std::optional<std::size_t> fixed; std::optional<std::size_t> fixed;
if (closest_point && drag_delta && mouse()) if (closest_point && drag_delta)
{ {
auto target = screen_to_world(geom::cast<float>(*mouse()) + *drag_delta); auto target = screen_to_world(geom::cast<float>(state().mouse) + *drag_delta);
auto & point = model.points[*closest_point]; auto & point = model.points[*closest_point];
auto & vel = model.vels[*closest_point]; auto & vel = model.vels[*closest_point];
@ -807,7 +800,7 @@ void physics_demo_app::update()
void physics_demo_app::present() void physics_demo_app::present()
{ {
gl::Viewport(0, 0, width(), height()); gl::Viewport(0, 0, state().size[0], state().size[1]);
gl::ClearColor(0.8f, 0.8f, 0.9f, 0.f); gl::ClearColor(0.8f, 0.8f, 0.9f, 0.f);
gl::Clear(gl::COLOR_BUFFER_BIT); gl::Clear(gl::COLOR_BUFFER_BIT);
@ -821,9 +814,9 @@ void physics_demo_app::present()
model.stick_solid[i] ? gfx::color_rgba{0, 0, 0, 255} : gfx::color_rgba{63, 63, 63, 255}); model.stick_solid[i] ? gfx::color_rgba{0, 0, 0, 255} : gfx::color_rgba{63, 63, 63, 255});
} }
if (new_spring_start && mouse()) if (new_spring_start)
{ {
painter.line(model.points[*new_spring_start], screen_to_world(geom::cast<float>(*mouse())), ball_radius / 2.f, gfx::red); painter.line(model.points[*new_spring_start], screen_to_world(geom::cast<float>(state().mouse)), ball_radius / 2.f, gfx::red);
} }
if (closest_stick) if (closest_stick)
@ -872,22 +865,28 @@ void physics_demo_app::present()
text.clear(); text.clear();
painter.render(geom::window_camera{width(), height()}.transform()); painter.render(geom::window_camera{state().size[0], state().size[1]}.transform());
} }
geom::point<float, 2> physics_demo_app::screen_to_world(geom::point<float, 2> const & p) const geom::point<float, 2> physics_demo_app::screen_to_world(geom::point<float, 2> const & p) const
{ {
return geom::orthographic<float, 2>(view_region).inverse()(geom::point{2.f * p[0] / width() - 1.f, 1.f - 2.f * p[1] / height()}); return geom::orthographic<float, 2>(view_region).inverse()(geom::point{2.f * p[0] / state().size[0] - 1.f, 1.f - 2.f * p[1] / state().size[1]});
} }
geom::point<float, 2> physics_demo_app::world_to_screen(geom::point<float, 2> const & p) const geom::point<float, 2> physics_demo_app::world_to_screen(geom::point<float, 2> const & p) const
{ {
auto q = geom::orthographic<float, 2>(view_region)(p); auto q = geom::orthographic<float, 2>(view_region)(p);
return {(q[0] + 1.f) / 2.f * width(), (1.f - q[1]) / 2.f * height()}; return {(q[0] + 1.f) / 2.f * state().size[0], (1.f - q[1]) / 2.f * state().size[1]};
} }
int main() namespace psemek::app
{ {
return app::main<physics_demo_app>();
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<physics_demo_app>({.name = "Physics example"});
}
} }

View file

@ -1,5 +1,5 @@
#include <psemek/app/app.hpp> #include <psemek/app/application_base.hpp>
#include <psemek/app/main.hpp> #include <psemek/app/default_application_factory.hpp>
#include <psemek/phys/engine_2d.hpp> #include <psemek/phys/engine_2d.hpp>
@ -18,13 +18,15 @@
#include <psemek/random/generator.hpp> #include <psemek/random/generator.hpp>
#include <psemek/random/uniform.hpp> #include <psemek/random/uniform.hpp>
#include <psemek/log/log.hpp>
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
using namespace psemek; using namespace psemek;
struct physics_2d_app struct physics_2d_app
: app::app : app::application_base
{ {
phys2d::engine physics; phys2d::engine physics;
@ -66,8 +68,7 @@ struct physics_2d_app
random::generator gen; random::generator gen;
physics_2d_app() physics_2d_app(options const &, context const &)
: app("Physics 2D example", 4)
{ {
simulation_box = {{{-world_width, world_width}, {-world_height, world_height}}}; simulation_box = {{{-world_width, world_width}, {-world_height, world_height}}};
@ -201,11 +202,11 @@ struct physics_2d_app
prof::dump(); prof::dump();
} }
void on_resize(int width, int height) override void on_event(app::resize_event const & event) override
{ {
app::on_resize(width, height); app::application_base::on_event(event);
float const ratio = static_cast<float>(width) / height; float const ratio = static_cast<float>(event.size[0]) / event.size[1];
float const c = view_box[0].center(); float const c = view_box[0].center();
float const l = view_box[1].length() * ratio; float const l = view_box[1].length() * ratio;
@ -213,52 +214,51 @@ struct physics_2d_app
view_box[0] = {c - l / 2.f, c + l / 2.f}; view_box[0] = {c - l / 2.f, c + l / 2.f};
} }
void on_left_button_down() override void on_event(app::mouse_button_event const & event) override
{ {
app::on_left_button_down(); app::application_base::on_event(event);
if (selected_ball && mouse) if (event.button == app::mouse_button::left && event.down)
{ {
auto const & s = physics.group_static_state(ball_group)[*selected_ball]; if (selected_ball && mouse)
auto r = s.position - *mouse; {
r = geom::plane_rotation<float, 2>(0, 1, -s.rotation)(r); auto const & s = physics.group_static_state(ball_group)[*selected_ball];
drag_delta = r; auto r = s.position - *mouse;
r = geom::plane_rotation<float, 2>(0, 1, -s.rotation)(r);
drag_delta = r;
}
else if (mouse)
{
// physics.add_object(ball_group, ball_shape, material, {*mouse, 0.f}, {{0.f, 0.f}, 0.f});
// physics.add_object(ball_group, large_ball_shape, material, {*mouse, 0.f}, {});
physics.add_object(box_group, box_shape, material, {*mouse, 0.f}, {});
// physics.add_object(box_group, wide_box_shape, material, {*mouse, 0.f}, {});
}
} }
else if (mouse)
if (event.button == app::mouse_button::left && !event.down)
{ {
// physics.add_object(ball_group, ball_shape, material, {*mouse, 0.f}, {{0.f, 0.f}, 0.f}); drag_delta = std::nullopt;
// physics.add_object(ball_group, large_ball_shape, material, {*mouse, 0.f}, {}); }
physics.add_object(box_group, box_shape, material, {*mouse, 0.f}, {});
// physics.add_object(box_group, wide_box_shape, material, {*mouse, 0.f}, {}); if (event.button == app::mouse_button::right && event.down)
{
if (mouse)
{
physics.add_object(ball_group, ball_shape, material, {*mouse, 0.f}, {{0.f, 0.f}, 0.f});
// physics.add_object(box_group, box_shape, material, {*mouse, 0.f}, {});
// physics.add_object(box_group, wide_box_shape, material, {*mouse, 0.f}, {});
// physics.explode(*mouse, 10.f, 100.f);
}
} }
} }
void on_left_button_up() override void on_event(app::mouse_move_event const & event) override
{ {
app::on_left_button_up(); app::application_base::on_event(event);
drag_delta = std::nullopt; float mx = event.position[0] * 1.f / state().size[0];
} float my = 1.f - event.position[1] * 1.f / state().size[1];
void on_right_button_down() override
{
app::on_right_button_down();
if (mouse)
{
physics.add_object(ball_group, ball_shape, material, {*mouse, 0.f}, {{0.f, 0.f}, 0.f});
// physics.add_object(box_group, box_shape, material, {*mouse, 0.f}, {});
// physics.add_object(box_group, wide_box_shape, material, {*mouse, 0.f}, {});
// physics.explode(*mouse, 10.f, 100.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); mouse = view_box.corner(mx, my);
} }
@ -270,9 +270,9 @@ struct physics_2d_app
float MOTOR = 15.f; float MOTOR = 15.f;
float m = 0.f; float m = 0.f;
if (is_key_down(SDLK_LEFT)) if (state().key_down.contains(app::keycode::LEFT))
m += MOTOR; m += MOTOR;
if (is_key_down(SDLK_RIGHT)) if (state().key_down.contains(app::keycode::RIGHT))
m += -MOTOR; m += -MOTOR;
m = 15.f; m = 15.f;
motor = m; motor = m;
@ -488,7 +488,12 @@ struct physics_2d_app
}; };
int main() namespace psemek::app
{ {
return app::main<physics_2d_app>();
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<physics_2d_app>({.name = "Physics example", .multisampling = 4});
}
} }

View file

@ -1,5 +1,5 @@
#include <psemek/app/app.hpp> #include <psemek/app/application_base.hpp>
#include <psemek/app/main.hpp> #include <psemek/app/default_application_factory.hpp>
#include <psemek/phys/engine_3d.hpp> #include <psemek/phys/engine_3d.hpp>
@ -23,6 +23,8 @@
#include <psemek/util/clock.hpp> #include <psemek/util/clock.hpp>
#include <psemek/log/log.hpp>
using namespace psemek; using namespace psemek;
static char const program_vs[] = static char const program_vs[] =
@ -85,11 +87,10 @@ void main()
)"; )";
struct physics_3d_app struct physics_3d_app
: app::app : app::application_base
{ {
physics_3d_app() physics_3d_app(options const &, context const &)
: app::app("Physics 3D", 4) : program_(program_vs, program_fs)
, program_(program_vs, program_fs)
, rng_{random::device{}} , rng_{random::device{}}
{ {
camera_.near_clip = 0.1f; camera_.near_clip = 0.1f;
@ -204,34 +205,37 @@ struct physics_3d_app
// 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_resize(int width, int height) override void on_event(app::resize_event const & event) override
{ {
app::app::on_resize(width, height); app::application_base::on_event(event);
camera_.set_fov(camera_.fov_y, (1.f * width) / height); camera_.set_fov(camera_.fov_y, (1.f * event.size[0]) / event.size[1]);
} }
void on_mouse_move(int x, int y, int dx, int dy) override void on_event(app::mouse_move_event const & event) override
{ {
app::app::on_mouse_move(x, y, dx, dy); auto const old_mouse = state().mouse;
if (is_right_button_down()) app::application_base::on_event(event);
if (state().mouse_button_down.contains(app::mouse_button::right))
{ {
camera_elevation_angle_tgt_ += dy * 0.003f; auto const delta = event.position - old_mouse;
camera_azimuthal_angle_tgt_ -= dx * 0.003f; camera_elevation_angle_tgt_ += delta[1] * 0.003f;
camera_azimuthal_angle_tgt_ -= delta[0] * 0.003f;
} }
} }
void on_mouse_wheel(int delta) override void on_event(app::mouse_wheel_event const & event) override
{ {
app::app::on_mouse_wheel(delta); app::application_base::on_event(event);
camera_distance_tgt_ *= std::pow(0.8f, delta); camera_distance_tgt_ *= std::pow(0.8f, event.delta);
} }
void on_key_down(SDL_Keycode key) override void on_event(app::key_event const & event) override
{ {
if (key == SDLK_SPACE) if (event.down && event.key == app::keycode::SPACE)
{ {
// float r = 7.f; // 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{3.f}), engine_.add_material({1.f, 0.125f, 50.f}), {{random::uniform(rng_, -r, r), random::uniform(rng_, -r, r), 20.f}});
@ -249,7 +253,7 @@ struct physics_3d_app
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::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); // engine_.add_object(engine_.add_shape(phys3d::ball{1.f}), engine_.add_material({1.f, 0.5f, 100.f}), state);
} }
else if (key == SDLK_f) else if (event.down && event.key == app::keycode::F)
{ {
float const f = 1.f; float const f = 1.f;
for (std::uint32_t h = 1; h < engine_.object_count(); ++h) for (std::uint32_t h = 1; h < engine_.object_count(); ++h)
@ -358,7 +362,12 @@ private:
random::generator rng_; random::generator rng_;
}; };
int main() namespace psemek::app
{ {
return app::main<physics_3d_app>();
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<physics_3d_app>({.name = "Physics 3D example"});
}
} }

View file

@ -1,5 +1,5 @@
#include <psemek/app/app.hpp> #include <psemek/app/application_base.hpp>
#include <psemek/app/main.hpp> #include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/painter.hpp> #include <psemek/gfx/painter.hpp>
#include <psemek/gfx/gl.hpp> #include <psemek/gfx/gl.hpp>
#include <psemek/geom/scale.hpp> #include <psemek/geom/scale.hpp>
@ -44,10 +44,10 @@ struct particle
gfx::color_rgba color; gfx::color_rgba color;
}; };
struct platformer_app : app::app struct platformer_app
: app::application_base
{ {
platformer_app() platformer_app(options const &, context const &)
: app("Platformer", 4)
{ {
map_.platforms.push_back({{{-10.f, 10.f}, {-5.f, -4.f}}}); map_.platforms.push_back({{{-10.f, 10.f}, {-5.f, -4.f}}});
@ -129,18 +129,18 @@ struct platformer_app : app::app
} }
} }
if (is_key_down(SDLK_a)) if (state().key_down.contains(app::keycode::A))
player_.velocity[0] -= acceleration * dt; player_.velocity[0] -= acceleration * dt;
if (is_key_down(SDLK_d)) if (state().key_down.contains(app::keycode::D))
player_.velocity[0] += acceleration * dt; player_.velocity[0] += acceleration * dt;
if (player_.grounded && (is_key_down(SDLK_a) ^ is_key_down(SDLK_d))) if (player_.grounded && (state().key_down.contains(app::keycode::A) ^ state().key_down.contains(app::keycode::D)))
{ {
move_particle_spawn_timer_ += dt; move_particle_spawn_timer_ += dt;
if (move_particle_spawn_timer_ > move_particle_spawn_period) if (move_particle_spawn_timer_ > move_particle_spawn_period)
{ {
move_particle_spawn_timer_ -= move_particle_spawn_period; move_particle_spawn_timer_ -= move_particle_spawn_period;
float s = is_key_down(SDLK_a) ? 1.f : -1.f; float s = state().key_down.contains(app::keycode::A) ? 1.f : -1.f;
auto position = player_.position + geom::vector{s * player_.size[0], -player_.size[1]}; auto position = player_.position + geom::vector{s * player_.size[0], -player_.size[1]};
auto velocity = geom::direction(random::uniform(rng_, geom::rad(60.f), geom::rad(120.f))); auto velocity = geom::direction(random::uniform(rng_, geom::rad(60.f), geom::rad(120.f)));
@ -153,7 +153,7 @@ struct platformer_app : app::app
} }
} }
if (player_.grounded && is_key_down(SDLK_w)) if (player_.grounded && state().key_down.contains(app::keycode::W))
{ {
player_.velocity[1] += jump_speed; player_.velocity[1] += jump_speed;
@ -228,7 +228,7 @@ struct platformer_app : app::app
painter_.circle(p.position, p.size, gfx::to_coloru8(colorf)); painter_.circle(p.position, p.size, gfx::to_coloru8(colorf));
} }
float const aspect_ratio = width() * 1.f / height(); float const aspect_ratio = state().size[0] * 1.f / state().size[1];
float const view_size = 5.f; float const view_size = 5.f;
geom::box<float, 2> view_box{{{-view_size * aspect_ratio, view_size * aspect_ratio}, {-view_size, view_size}}}; geom::box<float, 2> view_box{{{-view_size * aspect_ratio, view_size * aspect_ratio}, {-view_size, view_size}}};
@ -247,7 +247,12 @@ private:
gfx::painter painter_; gfx::painter painter_;
}; };
int main() namespace psemek::app
{ {
return app::main<platformer_app>();
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<platformer_app>({.name = "Platformer example"});
}
} }

View file

@ -1,5 +1,5 @@
#include <psemek/app/app.hpp> #include <psemek/app/application_base.hpp>
#include <psemek/app/main.hpp> #include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/program.hpp> #include <psemek/gfx/program.hpp>
#include <psemek/gfx/mesh.hpp> #include <psemek/gfx/mesh.hpp>
@ -484,7 +484,7 @@ struct vertex
}; };
struct shadow_app struct shadow_app
: app::app : app::application_base
{ {
geom::spherical_camera camera; geom::spherical_camera camera;
@ -509,23 +509,23 @@ struct shadow_app
float time = 0.f; float time = 0.f;
bool paused = false; bool paused = false;
shadow_app(); shadow_app(options const &, context const &);
void on_resize(int width, int height) override; void on_event(app::resize_event const & event) override;
void on_mouse_move(int x, int y, int dx, int dy) override; void on_event(app::mouse_move_event const & event) override;
void on_mouse_wheel(int delta) override; void on_event(app::mouse_wheel_event const & event) override;
void on_key_down(SDL_Keycode key) override; void on_event(app::key_event const & event) override;
void update() override {}
void present() override; void present() override;
}; };
shadow_app::shadow_app() shadow_app::shadow_app(options const &, context const & context)
: app("Shadow")
{ {
vsync(true); context.vsync(true);
camera.near_clip = 0.1f; camera.near_clip = 0.1f;
camera.far_clip = 1000.f; camera.far_clip = 1000.f;
@ -702,34 +702,39 @@ shadow_app::shadow_app()
} }
} }
void shadow_app::on_resize(int width, int height) void shadow_app::on_event(app::resize_event const & event)
{ {
app::on_resize(width, height); app::application_base::on_event(event);
camera.set_fov(camera.fov_y, (1.f * width) / height); camera.set_fov(camera.fov_y, (1.f * event.size[0]) / event.size[1]);
} }
void shadow_app::on_mouse_move(int x, int y, int dx, int dy) void shadow_app::on_event(app::mouse_move_event const & event)
{ {
app::on_mouse_move(x, y, dx, dy); auto const old_mouse = state().mouse;
if (is_middle_button_down()) app::application_base::on_event(event);
if (state().mouse_button_down.contains(app::mouse_button::middle))
{ {
camera.azimuthal_angle -= dx * 0.01f; auto const delta = event.position - old_mouse;
camera.elevation_angle += dy * 0.01f; camera.azimuthal_angle -= delta[0] * 0.01f;
camera.elevation_angle += delta[1] * 0.01f;
} }
} }
void shadow_app::on_mouse_wheel(int delta) void shadow_app::on_event(app::mouse_wheel_event const & event)
{ {
app::on_mouse_wheel(delta); app::application_base::on_event(event);
camera.distance *= std::pow(0.8f, delta); camera.distance *= std::pow(0.8f, event.delta);
} }
void shadow_app::on_key_down(SDL_Keycode key) void shadow_app::on_event(app::key_event const & event)
{ {
if (key == SDLK_SPACE) app::application_base::on_event(event);
if (event.down && event.key == app::keycode::SPACE)
paused = !paused; paused = !paused;
} }
@ -745,7 +750,7 @@ void shadow_app::present()
gfx::framebuffer::null().bind(); gfx::framebuffer::null().bind();
gl::DrawBuffer(gl::BACK); gl::DrawBuffer(gl::BACK);
gl::Viewport(0, 0, width(), height()); gl::Viewport(0, 0, state().size[0], state().size[1]);
gl::ClearColor(0.7f, 0.7f, 1.f, 0.f); gl::ClearColor(0.7f, 0.7f, 1.f, 0.f);
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT); gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
@ -760,7 +765,7 @@ void shadow_app::present()
shadow_renderer::render_options options; shadow_renderer::render_options options;
options.framebuffer = &gfx::framebuffer::null(); options.framebuffer = &gfx::framebuffer::null();
options.viewport = {{{0, width()}, {0, height()}}}; options.viewport = {{{0, state().size[0]}, {0, state().size[1]}}};
options.draw_buffer = gl::BACK; options.draw_buffer = gl::BACK;
options.transform = camera.transform(); options.transform = camera.transform();
options.light.position = geom::homogeneous(light_dir); options.light.position = geom::homogeneous(light_dir);
@ -771,7 +776,13 @@ void shadow_app::present()
gfx::check_error(); gfx::check_error();
} }
int main() namespace psemek::app
{ {
return app::main<shadow_app>();
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<shadow_app>({.name = "Shadow example"});
}
} }

View file

@ -1,5 +1,5 @@
#include <psemek/app/app.hpp> #include <psemek/app/application_base.hpp>
#include <psemek/app/main.hpp> #include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/gl.hpp> #include <psemek/gfx/gl.hpp>
#include <psemek/gfx/mesh.hpp> #include <psemek/gfx/mesh.hpp>
@ -601,13 +601,13 @@ void main()
})"; })";
struct srtm_app struct srtm_app
: app::app : app::application_base
{ {
srtm_app(); srtm_app(options const &, context const &);
void on_resize(int width, int height) override; void on_event(app::resize_event const & event) override;
void on_mouse_move(int x, int y, int dx, int dy) override; void on_event(app::mouse_move_event const & event) override;
void update() override; void update() override;
void present() override; void present() override;
@ -632,11 +632,10 @@ struct srtm_app
gfx::painter painter; gfx::painter painter;
}; };
srtm_app::srtm_app() srtm_app::srtm_app(options const &, context const & context)
: app("SRTM", 4)
{ {
vsync(true); context.vsync(true);
show_cursor(false); context.show_cursor(false);
camera.fov_y = geom::rad(45.f); camera.fov_y = geom::rad(45.f);
camera.near_clip = 0.0001f; camera.near_clip = 0.0001f;
@ -688,19 +687,23 @@ srtm_app::srtm_app()
} }
} }
void srtm_app::on_resize(int width, int height) void srtm_app::on_event(app::resize_event const & event)
{ {
app::on_resize(width, height); app::application_base::on_event(event);
camera.set_fov(camera.fov_y, (1.f * width) / height);
camera.set_fov(camera.fov_y, (1.f * event.size[0]) / event.size[1]);
camera_transform = camera.transform(); camera_transform = camera.transform();
} }
void srtm_app::on_mouse_move(int x, int y, int dx, int dy) void srtm_app::on_event(app::mouse_move_event const & event)
{ {
app::on_mouse_move(x, y, dx, dy); auto const old_mouse = state().mouse;
camera.rotateZX(0.01f * dx); app::application_base::on_event(event);
camera.rotateYZ(0.01f * dy);
auto const delta = event.position - old_mouse;
camera.rotateZX(0.01f * delta[0]);
camera.rotateYZ(0.01f * delta[1]);
} }
void srtm_app::update() void srtm_app::update()
@ -708,12 +711,12 @@ void srtm_app::update()
float dt = frame_clock.restart().count(); float dt = frame_clock.restart().count();
frame_dt_average.push(dt); frame_dt_average.push(dt);
if (is_key_down(SDLK_q)) if (state().key_down.contains(app::keycode::Q))
{ {
camera.rotateXY(- 4.f * dt); camera.rotateXY(- 4.f * dt);
} }
if (is_key_down(SDLK_e)) if (state().key_down.contains(app::keycode::E))
{ {
camera.rotateXY(4.f * dt); camera.rotateXY(4.f * dt);
} }
@ -724,32 +727,32 @@ void srtm_app::update()
auto const camera_up = camera.axis_y(); auto const camera_up = camera.axis_y();
auto const camera_right = camera.axis_x(); auto const camera_right = camera.axis_x();
if (is_key_down(SDLK_w)) if (state().key_down.contains(app::keycode::W))
{ {
camera.pos += camera_speed * dt * camera_forward; camera.pos += camera_speed * dt * camera_forward;
} }
if (is_key_down(SDLK_s)) if (state().key_down.contains(app::keycode::S))
{ {
camera.pos -= camera_speed * dt * camera_forward; camera.pos -= camera_speed * dt * camera_forward;
} }
if (is_key_down(SDLK_d)) if (state().key_down.contains(app::keycode::D))
{ {
camera.pos += camera_speed * dt * camera_right; camera.pos += camera_speed * dt * camera_right;
} }
if (is_key_down(SDLK_a)) if (state().key_down.contains(app::keycode::A))
{ {
camera.pos -= camera_speed * dt * camera_right; camera.pos -= camera_speed * dt * camera_right;
} }
if (is_key_down(SDLK_LSHIFT)) if (state().key_down.contains(app::keycode::LSHIFT))
{ {
camera.pos += camera_speed * dt * camera_up; camera.pos += camera_speed * dt * camera_up;
} }
if (is_key_down(SDLK_LCTRL)) if (state().key_down.contains(app::keycode::LCTRL))
{ {
camera.pos -= camera_speed * dt * camera_up; camera.pos -= camera_speed * dt * camera_up;
} }
@ -911,7 +914,7 @@ void srtm_app::present()
distance = std::min(distance, geom::length(c - v[1])); distance = std::min(distance, geom::length(c - v[1]));
distance = std::min(distance, geom::length(c - v[2])); distance = std::min(distance, geom::length(c - v[2]));
on_screen_unit = width() / distance / std::tan(camera.fov_x / 2.f); on_screen_unit = state().size[0] / distance / std::tan(camera.fov_x / 2.f);
} }
assert(on_screen_unit > 0.f); assert(on_screen_unit > 0.f);
@ -983,11 +986,14 @@ void srtm_app::present()
// simple_renderer.push(gfx::simple_renderer::render_state{&selected_mesh, gfx::white.as_color_rgba()}); // simple_renderer.push(gfx::simple_renderer::render_state{&selected_mesh, gfx::white.as_color_rgba()});
// simple_renderer.render(gfx::simple_renderer::render_options{camera_transform}); // simple_renderer.render(gfx::simple_renderer::render_options{camera_transform});
int const width = state().size[0];
int const height = state().size[1];
info.push_back(util::to_string("Tiles: ", rendered_tiles)); info.push_back(util::to_string("Tiles: ", rendered_tiles));
{ {
float s = 10.f; float s = 10.f;
painter.line({width() / 2.f - s, height() / 2.f}, {width() / 2.f + s, height() / 2.f}, 3.f, gfx::cyan, false); painter.line({width / 2.f - s, height / 2.f}, {width / 2.f + s, height / 2.f}, 3.f, gfx::cyan, false);
painter.line({width() / 2.f, height() / 2.f - s}, {width() / 2.f, height() / 2.f + s}, 3.f, gfx::cyan, false); painter.line({width / 2.f, height / 2.f - s}, {width / 2.f, height / 2.f + s}, 3.f, gfx::cyan, false);
} }
info.push_back(util::to_string("Nodes: ", nodes.node_count())); info.push_back(util::to_string("Nodes: ", nodes.node_count()));
info.push_back(util::to_string("Tasks: ", nodes.loader_queue_size())); info.push_back(util::to_string("Tasks: ", nodes.loader_queue_size()));
@ -1020,10 +1026,15 @@ void srtm_app::present()
gl::Enable(gl::BLEND); gl::Enable(gl::BLEND);
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
gl::Disable(gl::DEPTH_TEST); gl::Disable(gl::DEPTH_TEST);
painter.render(geom::window_camera{width(), height()}.transform()); painter.render(geom::window_camera{width, height}.transform());
} }
int main() namespace psemek::app
{ {
return app::main<srtm_app>();
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<srtm_app>({.name = "SRTM example", .multisampling = 4});
}
} }

View file

@ -1,532 +0,0 @@
#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/gfx/mesh.hpp>
#include <psemek/gfx/program.hpp>
#include <psemek/geom/camera.hpp>
#include <psemek/geom/rotation.hpp>
#include <psemek/geom/gram_schmidt.hpp>
#include <psemek/gfx/color.hpp>
#include <psemek/geom/math.hpp>
#include <psemek/geom/interval.hpp>
#include <psemek/random/uniform.hpp>
#include <psemek/random/generator.hpp>
#include <psemek/util/recursive.hpp>
#include <vector>
using namespace psemek;
// All spacial parameters in meters
// All angles in degrees
struct tree_species_description
{
struct curve_description
{
geom::interval<float> base_radius;
geom::interval<float> segment_length;
std::function<geom::interval<int>(float)> segment_count;
geom::interval<float> lean_angle;
geom::interval<float> rotation_angle;
float up_tendency;
float splitting_probability;
geom::interval<float> splitting_angle;
geom::interval<float> initial_branching_distance;
geom::interval<float> branching_distance;
geom::interval<int> branching_children_count;
geom::interval<float> branching_rotation;
geom::interval<float> branching_angle;
geom::interval<float> branching_size_multiplier;
};
curve_description trunk;
curve_description branch;
};
auto fir_species()
{
tree_species_description desc;
desc.trunk.base_radius = {0.1f, 0.15f};
desc.trunk.segment_length = {0.5f, 0.8f};
desc.trunk.segment_count = [](float){
return geom::interval{7, 10};
};
desc.trunk.lean_angle = {-5.f, 5.f};
desc.trunk.rotation_angle = {-5.f, 5.f};
desc.trunk.up_tendency = 1.f;
desc.trunk.initial_branching_distance = {0.5f, 0.5f};
desc.trunk.branching_distance = {0.4f, 0.5f};
desc.trunk.branching_children_count = {4, 4};
desc.trunk.branching_rotation = {30, 60.f};
desc.trunk.branching_angle = {55.f, 65.f};
desc.trunk.branching_size_multiplier = {1.f, 1.f};
desc.trunk.splitting_probability = 0.f;
desc.branch.base_radius = {0.01f, 0.05f};
desc.branch.segment_length = {0.05f, 0.20f};
desc.branch.segment_count = [](float h){
int min = std::max<int>(1, 18 * (1.f - h));
int max = std::min<int>(20, 20 * (1.f - h));
if (min > max)
std::swap(min, max);
return geom::interval<int>{min, max};
};
desc.branch.lean_angle = {-5.f, 5.f};
desc.branch.rotation_angle = {-1.f, 1.f};
desc.branch.up_tendency = 0.0f;
desc.branch.initial_branching_distance = {0.4f, 0.4f};
desc.branch.branching_distance = {0.1f, 0.2f};
desc.branch.branching_children_count = {1, 1};
desc.branch.branching_rotation = {175.f, 185.f};
desc.branch.branching_angle = {45.f, 45.f};
desc.branch.branching_size_multiplier = {0.8f, 0.8f};
desc.branch.splitting_probability = 0.f;
return desc;
}
auto oak_species()
{
tree_species_description desc;
desc.trunk.base_radius = {0.2f, 0.3f};
desc.trunk.segment_length = {0.5f, 0.8f};
desc.trunk.segment_count = [](float){
return geom::interval{4, 7};
};
desc.trunk.lean_angle = {-15.f, 15.f};
desc.trunk.rotation_angle = {-180.f, 180.f};
desc.trunk.up_tendency = 0.f;
desc.trunk.initial_branching_distance = {20.f, 30.f};
desc.trunk.branching_distance = {0.1f, 0.15f};
desc.trunk.branching_children_count = {1, 2};
desc.trunk.branching_rotation = {60, 180.f};
desc.trunk.branching_angle = {30.f, 75.f};
desc.trunk.branching_size_multiplier = {1.f, 1.f};
desc.trunk.splitting_probability = 0.1f;
desc.trunk.splitting_angle = {15.f, 60.f};
desc.branch.base_radius = {0.04f, 0.05f};
desc.branch.segment_length = {0.1f, 0.15f};
desc.branch.segment_count = [](float h){
h = std::max(h / 4.f, 0.f);
// h = std::min(h, 1.f);
// h = 2.f * h - 1.f;
// float t = std::sqrt(1.f - h * h);
return geom::interval<int>{6, 10};
};
desc.branch.lean_angle = {-15.f, 15.f};
desc.branch.rotation_angle = {-30.f, 30.f};
desc.branch.up_tendency = 0.f;
desc.branch.initial_branching_distance = {0.4f, 0.5f};
desc.branch.branching_distance = {0.4f, 0.5f};
desc.branch.branching_children_count = {1, 1};
desc.branch.branching_rotation = {175.f, 185.f};
desc.branch.branching_angle = {30.f, 60.f};
desc.branch.branching_size_multiplier = {0.8f, 0.8f};
desc.branch.splitting_probability = 0.1f;
desc.branch.splitting_angle = {15.f, 60.f};
return desc;
}
struct tree_description
{
struct node
{
geom::point<float, 3> position;
float radius;
};
struct branch
{
std::size_t parent;
std::vector<node> nodes;
};
std::vector<branch> branches;
};
template <typename RNG>
tree_description generate(tree_species_description const & species, RNG && rng)
{
// warm up!
for (int i = 0; i < 16; ++i)
rng();
tree_description result;
struct frame
{
geom::point<float, 3> pos;
geom::vector<float, 3> z;
geom::vector<float, 3> x;
};
auto generate_curve = util::recursive([&](auto & self, frame f, tree_species_description::curve_description const & curve_desc, tree_species_description::curve_description const & children_desc,
int level, float min_radius, int max_segments, float h, float branching_distance, float branching_rotation) -> void
{
std::size_t id = result.branches.size();
result.branches.emplace_back();
float base_radius = random::uniform_distribution<float>{curve_desc.base_radius}(rng);
base_radius = std::min(base_radius, min_radius);
int segment_count = random::uniform_distribution<int>{curve_desc.segment_count(h)}(rng);
segment_count = std::min(max_segments, segment_count);
float expected_height = segment_count * curve_desc.segment_length.center();
result.branches[id].parent = 0;
result.branches[id].nodes.push_back({f.pos, base_radius});
for (int i = 0; i < segment_count; ++i)
{
float t = (i + 1.f) / segment_count;
float length = random::uniform_real_distribution<float>{curve_desc.segment_length}(rng);
float lean = geom::rad(random::uniform_real_distribution<float>{curve_desc.lean_angle}(rng));
f.z = geom::axis_rotation<float>{f.x, lean}(f.z);
if (curve_desc.up_tendency > 0.f)
f.z = geom::slerp(f.z, geom::vector{0.f, 0.f, 1.f}, t * curve_desc.up_tendency);
else if (curve_desc.up_tendency < 0.f)
f.z = geom::slerp(f.z, geom::vector{0.f, 0.f, -1.f}, - t * curve_desc.up_tendency);
float rotation = geom::rad(random::uniform_real_distribution<float>{curve_desc.rotation_angle}(rng));
f.x = geom::axis_rotation<float>{f.z, rotation}(f.x);
auto new_pos = f.pos + f.z * length;
float new_radius = (1.f - t) * base_radius;
if (level < 3)
{
float available_branching_length = length;
while (branching_distance < available_branching_length)
{
available_branching_length -= branching_distance;
int children_count = random::uniform_distribution<int>{curve_desc.branching_children_count}(rng);
for (int c = 0; c < children_count; ++c)
{
float angle = random::uniform_distribution<float>{curve_desc.branching_angle}(rng);
float rotation = 0;
if (children_count > 1)
rotation = c * 2.f * geom::pi / children_count;
float branch_t = 1.f - available_branching_length / length;
geom::vector y{0.f, 0.f, 1.f};
frame child_f;
child_f.pos = geom::lerp(f.pos, new_pos, branch_t);
child_f.z = geom::slerp(f.z, f.x, angle / 90.f);
child_f.z = geom::axis_rotation<float>{f.z, branching_rotation + rotation}(child_f.z);
child_f.x = geom::normalized(geom::cross(y, child_f.z));
float multiplier = random::uniform_distribution<float>{curve_desc.branching_size_multiplier}(rng);
float branching_distance = random::uniform_distribution<float>{children_desc.initial_branching_distance}(rng);
self(child_f, children_desc, children_desc, level + 1,
geom::lerp(result.branches[id].nodes.back().radius, new_radius, branch_t) * multiplier,
(level == 0) ? 1024 : (segment_count - i) * multiplier,
(level == 0) ? child_f.pos[2] / expected_height : h,
branching_distance, 0.f);
}
branching_rotation += geom::rad(random::uniform_distribution<float>{curve_desc.branching_rotation}(rng));
branching_distance = random::uniform_distribution<float>{curve_desc.branching_distance}(rng);
}
branching_distance -= available_branching_length;
}
f.pos = new_pos;
result.branches[id].nodes.push_back({f.pos, new_radius});
if (i + 1 < segment_count && random::uniform_distribution<float>{}(rng) < curve_desc.splitting_probability)
{
frame f1 = f;
frame f2 = f;
float a1 = geom::rad(random::uniform_distribution<float>{curve_desc.splitting_angle}(rng));
float a2 = geom::rad(random::uniform_distribution<float>{curve_desc.splitting_angle}(rng));
f1.z = geom::axis_rotation<float>{f.x, a1}(f1.z);
f2.z = geom::axis_rotation<float>{f.x, -a2}(f2.z);
float h = f.pos[2] / expected_height;
self(f1, curve_desc, children_desc, level, new_radius, segment_count - i - 1, h, branching_distance, branching_rotation);
self(f2, curve_desc, children_desc, level, new_radius, segment_count - i - 1, h, branching_distance, branching_rotation);
break;
}
}
});
frame starting_frame;
starting_frame.pos = {0.f, 0.f, 0.f};
starting_frame.z = {0.f, 0.f, 1.f};
starting_frame.x = {1.f, 0.f, 0.f};
float initial_orientation = random::uniform_distribution<float>{0.f, 2.f * geom::pi}(rng);
starting_frame.x = geom::axis_rotation<float>{starting_frame.z, initial_orientation}(starting_frame.x);
generate_curve(starting_frame, species.trunk, species.branch, 0, std::numeric_limits<float>::infinity(), 1024, 0.f,
random::uniform_distribution<float>{species.trunk.initial_branching_distance}(rng), 0.f);
return result;
}
static char const vertex_source[] =
R"(#version 330
uniform mat4 u_transform;
layout (location = 0) in vec4 in_position;
layout (location = 1) in vec4 in_color;
out vec4 color;
out vec3 pos;
void main()
{
gl_Position = u_transform * in_position;
color = in_color;
pos = in_position.xyz;
}
)";
static char const fragment_source[] =
R"(#version 330
in vec4 color;
in vec3 pos;
out vec4 out_color;
void main()
{
vec3 n = normalize(cross(dFdx(pos), dFdy(pos)));
vec3 light = normalize(vec3(1.0, 1.0, 1.0));
float l = 0.5 + 0.5 * dot(n, light);
out_color = vec4(l * color.rgb, 1.0);
}
)";
struct tree_app
: app::app
{
tree_species_description species = oak_species();
tree_description tree_desc;
gfx::mesh tree_mesh;
gfx::program tree_program{vertex_source, fragment_source};
geom::spherical_camera camera;
std::uint64_t seed = 0;
tree_app()
: app("Tree")
{
tree_desc = generate(species, random::generator{seed, 0ull});
update_mesh();
setup_camera();
}
void update_mesh();
void setup_camera();
void on_mouse_move(int x, int y, int dx, int dy) override;
void on_mouse_wheel(int delta) override;
void on_resize(int width, int height) override;
void on_key_down(SDL_Keycode key) override;
void present() override;
};
struct vertex
{
geom::point<float, 3> position;
gfx::color_rgba color;
};
void tree_app::update_mesh()
{
tree_mesh.setup<geom::point<float, 3>, gfx::normalized<gfx::color_rgba>>();
std::vector<vertex> vertices;
std::vector<std::uint32_t> indices;
{
int const basement_size = 5;
float basement_offset = 0.05f;
gfx::color_rgba basement_color { 127, 127, 127, 255 };
for (int x = -basement_size; x < basement_size; ++x)
{
for (int y = -basement_size; y < basement_size; ++y)
{
auto base = vertices.size();
vertices.push_back({{x + basement_offset, y + basement_offset, 0.f}, basement_color});
vertices.push_back({{x + 1 - basement_offset, y + basement_offset, 0.f}, basement_color});
vertices.push_back({{x + basement_offset, y + 1 - basement_offset, 0.f}, basement_color});
vertices.push_back({{x + 1 - basement_offset, y + 1 - basement_offset, 0.f}, basement_color});
indices.push_back(base + 0);
indices.push_back(base + 1);
indices.push_back(base + 2);
indices.push_back(base + 2);
indices.push_back(base + 1);
indices.push_back(base + 3);
}
}
}
gfx::color_rgba trunk_color { 127, 63, 0, 255 };
auto build_branch = [&](std::vector<tree_description::node> const & nodes)
{
int const N = 6;
auto const base = vertices.size();
auto z = geom::vector{0.f, 0.f, 1.f};
auto x = geom::ort(z);
for (std::size_t i = 0; i < nodes.size(); ++i)
{
if (i + 1 < nodes.size())
z = geom::normalized(nodes[i + 1].position - nodes[i].position);
else if (nodes.size() >= 2)
z = geom::normalized(nodes[i].position - nodes[i - 1].position);
auto y = geom::cross(z, x);
geom::gram_schmidt(z, x, y);
for (int k = 0; k < N; ++k)
{
float a = (k * geom::pi * 2.f) / N;
vertices.push_back({nodes[i].position + (x * std::cos(a) + y * std::sin(a)) * nodes[i].radius, trunk_color});
}
}
for (std::size_t i = 0; i + 1 < nodes.size(); ++i)
{
for (int k = 0; k < N; ++k)
{
int kk = (k + 1) % N;
indices.push_back(base + i * N + k);
indices.push_back(base + i * N + kk);
indices.push_back(base + i * N + k + N);
indices.push_back(base + i * N + k + N);
indices.push_back(base + i * N + kk);
indices.push_back(base + i * N + kk + N);
}
}
};
for (auto const & b : tree_desc.branches)
build_branch(b.nodes);
tree_mesh.load(vertices, indices, gl::TRIANGLES, gl::STATIC_DRAW);
}
void tree_app::setup_camera()
{
camera.fov_y = geom::rad(45.f);
camera.near_clip = 0.1f;
camera.far_clip = 100.f;
camera.target = {0.f, 0.f, 0.f};
camera.elevation_angle = geom::rad(45.f);
camera.azimuthal_angle = 0.f;
camera.distance = 10.f;
}
void tree_app::on_mouse_move(int x, int y, int dx, int dy)
{
app::on_mouse_move(x, y, dx, dy);
if (is_middle_button_down())
{
camera.azimuthal_angle -= dx * 0.01f;
camera.elevation_angle += dy * 0.01f;
}
if (is_right_button_down())
{
camera.target[2] += dy * 0.001f * camera.distance;
}
}
void tree_app::on_mouse_wheel(int delta)
{
camera.distance *= std::pow(0.8f, delta);
}
void tree_app::on_resize(int width, int height)
{
app::on_resize(width, height);
camera.set_fov(camera.fov_y, (1.f * width) / height);
}
void tree_app::on_key_down(SDL_Keycode key)
{
if (key == SDLK_SPACE)
{
++seed;
tree_desc = generate(species, random::generator{std::uint64_t{seed}, 0ull});
update_mesh();
}
}
void tree_app::present()
{
gl::ClearColor(0.7f, 0.7f, 1.f, 1.f);
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
gl::Enable(gl::DEPTH_TEST);
gl::DepthFunc(gl::LEQUAL);
gl::Enable(gl::CULL_FACE);
tree_program.bind();
tree_program["u_transform"] = camera.transform();
tree_mesh.draw();
}
int main()
{
return app::main<tree_app>();
}

View file

@ -1,5 +1,5 @@
#include <psemek/app/app.hpp> #include <psemek/app/application_base.hpp>
#include <psemek/app/main.hpp> #include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/painter.hpp> #include <psemek/gfx/painter.hpp>
#include <psemek/gfx/gl.hpp> #include <psemek/gfx/gl.hpp>
#include <psemek/geom/scale.hpp> #include <psemek/geom/scale.hpp>
@ -23,10 +23,9 @@
using namespace psemek; using namespace psemek;
struct triangulation_app struct triangulation_app
: app::app : app::application_base
{ {
triangulation_app() triangulation_app(options const &, context const &)
: app("Triangulation example", 16)
{ {
std::ifstream in(PSEMEK_EXAMPLES_DIR "/turkey"); std::ifstream in(PSEMEK_EXAMPLES_DIR "/turkey");
@ -80,36 +79,32 @@ struct triangulation_app
std::iota(closest_points_.begin(), closest_points_.end(), std::uint16_t{0}); std::iota(closest_points_.begin(), closest_points_.end(), std::uint16_t{0});
} }
void on_left_button_down() override void on_event(app::mouse_button_event const & event) override
{ {
app::on_left_button_down(); app::application_base::on_event(event);
if (mouse()) if (event.down && event.button == app::mouse_button::left)
drag_start_ = mouse(); drag_start_ = state().mouse;
if (!event.down && event.button == app::mouse_button::left)
drag_start_ = std::nullopt;
} }
void on_left_button_up() override void on_event(app::mouse_wheel_event const & event) override
{ {
app::on_left_button_up(); camera_size_tgt_ *= std::pow(0.8f, event.delta);
drag_start_ = std::nullopt;
}
void on_mouse_wheel(int delta) override
{
camera_size_tgt_ *= std::pow(0.8f, delta);
} }
void update() override void update() override
{ {
float const dt = clock_.restart().count(); float const dt = clock_.restart().count();
if (drag_start_ && mouse()) if (drag_start_)
{ {
auto delta = *mouse() - *drag_start_; auto delta = state().mouse - *drag_start_;
delta[1] *= -1; delta[1] *= -1;
camera_center_ -= geom::cast<float>(delta) * camera_size_ / (1.f * height()); camera_center_ -= geom::cast<float>(delta) * camera_size_ / (1.f * state().size[1]);
drag_start_ = mouse(); drag_start_ = state().mouse;
} }
camera_size_ += (camera_size_tgt_ - camera_size_) * (1.f - std::exp(- 20.f * dt)); camera_size_ += (camera_size_tgt_ - camera_size_) * (1.f - std::exp(- 20.f * dt));
@ -120,10 +115,10 @@ struct triangulation_app
gl::ClearColor(1.f, 1.f, 1.f, 1.f); gl::ClearColor(1.f, 1.f, 1.f, 1.f);
gl::Clear(gl::COLOR_BUFFER_BIT); gl::Clear(gl::COLOR_BUFFER_BIT);
float aspect_ratio = (width() * 1.f) / height(); float aspect_ratio = (state().size[0] * 1.f) / state().size[1];
geom::box<float, 2> view_bbox = geom::expand(geom::box<float, 2>::singleton(camera_center_), geom::vector{camera_size_ * 0.5f * aspect_ratio, camera_size_ * 0.5f}); geom::box<float, 2> view_bbox = geom::expand(geom::box<float, 2>::singleton(camera_center_), geom::vector{camera_size_ * 0.5f * aspect_ratio, camera_size_ * 0.5f});
float line_width = 4.f * camera_size_ / height(); float line_width = 4.f * camera_size_ / state().size[1];
auto edge = [this, line_width](auto i0, auto i1, gfx::color_rgba const & color) auto edge = [this, line_width](auto i0, auto i1, gfx::color_rgba const & color)
{ {
@ -153,11 +148,10 @@ struct triangulation_app
painter_.render(camera_transform); painter_.render(camera_transform);
if (auto m = mouse(); m)
{ {
geom::point<float, 2> m_world; geom::point<float, 2> m_world;
m_world[0] = geom::lerp(view_bbox[0], (*m)[0] * 1.f / width()); m_world[0] = geom::lerp(view_bbox[0], state().mouse[0] * 1.f / state().size[0]);
m_world[1] = geom::lerp(view_bbox[1], 1.f - (*m)[1] * 1.f / height()); m_world[1] = geom::lerp(view_bbox[1], 1.f - state().mouse[1] * 1.f / state().size[1]);
auto compare = [&](auto i, auto j){ auto compare = [&](auto i, auto j){
return geom::distance(points_[i], m_world) < geom::distance(points_[j], m_world); return geom::distance(points_[i], m_world) < geom::distance(points_[j], m_world);
@ -182,13 +176,13 @@ struct triangulation_app
opts.y = gfx::painter::y_align::bottom; opts.y = gfx::painter::y_align::bottom;
opts.scale = 2.f; opts.scale = 2.f;
auto p = geom::swizzle<0, 1>(geom::as_point(camera_transform * geom::homogeneous(geom::swizzle<0, 1, -1>(points_[i])))); auto p = geom::swizzle<0, 1>(geom::as_point(camera_transform * geom::homogeneous(geom::swizzle<0, 1, -1>(points_[i]))));
p[0] = std::round((p[0] * 0.5f + 0.5f) * width()); p[0] = std::round((p[0] * 0.5f + 0.5f) * state().size[0]);
p[1] = std::round((0.5f - p[1] * 0.5f) * height()); p[1] = std::round((0.5f - p[1] * 0.5f) * state().size[1]);
painter_.text(p, util::to_string(i), opts); painter_.text(p, util::to_string(i), opts);
} }
} }
painter_.render(geom::window_camera{width(), height()}.transform()); painter_.render(geom::window_camera{state().size[0], state().size[1]}.transform());
} }
private: private:
@ -208,7 +202,25 @@ private:
util::clock<std::chrono::duration<float>, std::chrono::high_resolution_clock> clock_; util::clock<std::chrono::duration<float>, std::chrono::high_resolution_clock> clock_;
}; };
int main() namespace psemek::app
{ {
return app::main<triangulation_app>();
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<triangulation_app>({.name = "Triangulation example"
""
""
""
""
""
""
""
""
""
""
""
""
""});
}
} }

View file

@ -1,24 +1,25 @@
#include <psemek/app/app.hpp> #include <psemek/app/application_base.hpp>
#include <psemek/app/main.hpp> #include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/painter.hpp> #include <psemek/gfx/painter.hpp>
#include <psemek/geom/orthographic.hpp> #include <psemek/geom/orthographic.hpp>
#include <psemek/log/log.hpp>
using namespace psemek; using namespace psemek;
struct water_1d_app struct water_1d_app
: app::app : app::application_base
{ {
water_1d_app(); water_1d_app(options const &, context const &);
void update() override; void update() override;
void present() override; void present() override;
void on_resize(int width, int height) override; void on_event(app::resize_event const & event) override;
geom::box<float, 2> simulation_area; geom::box<float, 2> simulation_area;
float aspect_ratio; float aspect_ratio;
std::size_t const N = 50; std::size_t const N = 100;
std::vector<float> bed; std::vector<float> bed;
std::vector<float> height; std::vector<float> height;
std::vector<float> discharge; std::vector<float> discharge;
@ -28,8 +29,7 @@ struct water_1d_app
gfx::painter painter; gfx::painter painter;
}; };
water_1d_app::water_1d_app() water_1d_app::water_1d_app(options const &, context const &)
: app("Water 1D simulation", 4)
{ {
simulation_area[0] = {-1.f, 1.f}; simulation_area[0] = {-1.f, 1.f};
simulation_area[1] = {0.f, 1.f}; simulation_area[1] = {0.f, 1.f};
@ -42,21 +42,25 @@ water_1d_app::water_1d_app()
{ {
bed[i] += (0.5f * (N - i)) / N; bed[i] += (0.5f * (N - i)) / N;
bed[i] += (100.f / (N - i)) / N; bed[i] += (100.f / (N - i)) / N;
bed[i] += 0.125f * std::exp(- 1000.f * geom::sqr(i * 1.f / N - 0.5f));
// bed[i] += (0.25f * i) / N; // bed[i] += (0.25f * i) / N;
// height[i] += (0.3f * i) / N; // height[i] += (0.3f * i) / N;
// height[i] = 0.3f - bed[i]; // height[i] = 0.3f - bed[i];
// height[i] = 0.25f; // height[i] = 0.25f;
} }
// bed[N * 3 / 4] = 0.2f; // bed[N * 3 / 4] += 0.2f;
discharge[0] = 0.04f; discharge[0] = 0.04f;
discharge[N] = 0.f; discharge[N] = 0.f;
// height[0] = 20.f;
} }
void water_1d_app::update() void water_1d_app::update()
{ {
float const dt = 0.01f; float const dt = 0.005f;
float const dx = simulation_area[0].length() / N; float const dx = simulation_area[0].length() / N;
float const g = 10.f; float const g = 10.f;
float const max_speed = 1.f; float const max_speed = 1.f;
@ -152,12 +156,33 @@ void water_1d_app::present()
painter.rect(simulation_area, {255, 255, 255, 255}); painter.rect(simulation_area, {255, 255, 255, 255});
for (std::size_t i = 0; i < N; ++i) gfx::color_rgba const earth_color{127, 63, 0, 255};
gfx::color_rgba const water_color{0, 0, 255, 255};
for (std::size_t i = 0; i + 1 < N; ++i)
{ {
painter.rect({{{x(i), x(i + 1)}, {simulation_area[1].min, simulation_area[1].min + bed[i]}}}, gfx::dark(gfx::yellow).as_color_rgba()); geom::point p0{x(i + 0) * 0.5f + x(i + 1) * 0.5f, simulation_area[1].min};
painter.rect({{{x(i), x(i + 1)}, {simulation_area[1].min + bed[i], simulation_area[1].min + bed[i] + height[i]}}}, gfx::blue); geom::point p1{x(i + 1) * 0.5f + x(i + 2) * 0.5f, simulation_area[1].min};
geom::point q0{p0[0], simulation_area[1].min + bed[i]};
geom::point q1{p1[0], simulation_area[1].min + bed[i + 1]};
geom::point w0{p0[0], simulation_area[1].min + bed[i] + height[i]};
geom::point w1{p1[0], simulation_area[1].min + bed[i + 1] + height[i + 1]};
painter.triangle(p0, p1, q0, earth_color);
painter.triangle(q0, p1, q1, earth_color);
painter.triangle(q0, q1, w0, water_color);
painter.triangle(w0, q1, w1, water_color);
} }
// for (std::size_t i = 0; i < N; ++i)
// {
// painter.rect({{{x(i), x(i + 1)}, {simulation_area[1].min, simulation_area[1].min + bed[i]}}}, gfx::dark(gfx::yellow).as_color_rgba());
// painter.rect({{{x(i), x(i + 1)}, {simulation_area[1].min + bed[i], simulation_area[1].min + bed[i] + height[i]}}}, gfx::blue);
// }
for (std::size_t i = 1; i < N; ++i) for (std::size_t i = 1; i < N; ++i)
{ {
painter.line({x(i), 0.f}, {x(i), discharge[i]}, 0.005f, gfx::red); painter.line({x(i), 0.f}, {x(i), discharge[i]}, 0.005f, gfx::red);
@ -166,11 +191,11 @@ void water_1d_app::present()
painter.render(transform); painter.render(transform);
} }
void water_1d_app::on_resize(int width, int height) void water_1d_app::on_event(app::resize_event const & event)
{ {
app::on_resize(width, height); app::application_base::on_event(event);
aspect_ratio = (width * 1.f) / height; aspect_ratio = (event.size[0] * 1.f) / event.size[1];
} }
float water_1d_app::x(std::size_t i) const float water_1d_app::x(std::size_t i) const
@ -178,7 +203,12 @@ float water_1d_app::x(std::size_t i) const
return geom::lerp(simulation_area[0], (1.f * i) / N); return geom::lerp(simulation_area[0], (1.f * i) / N);
} }
int main() namespace psemek::app
{ {
return app::main<water_1d_app>();
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<water_1d_app>({.name = "Water example", .multisampling = 4});
}
} }

View file

@ -1,326 +0,0 @@
#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/geom/orthographic.hpp>
#include <psemek/geom/gauss.hpp>
using namespace psemek;
static std::pair<int, int> unpack_triangular_id(int id)
{
int i = int(std::floor(0.5f * (sqrt(1.f + 8.f * id) - 1.f)));
int j = id - (i * (i + 1)) / 2;
return {i, j};
}
static int pack_triangular_id(std::pair<int, int> const & p)
{
return (p.first * (p.first + 1)) / 2 + p.second;
}
struct water_2d_app
: app::app
{
water_2d_app();
void update() override;
void present() override;
void on_resize(int width, int height) override;
geom::box<float, 2> simulation_area;
float aspect_ratio;
int const N = 25;
int const first_type_count = (N * (N + 1)) / 2;
std::vector<float> water_height;
std::vector<geom::vector<float, 2>> flux[3];
geom::point<float, 2> p[3];
gfx::painter painter;
};
water_2d_app::water_2d_app()
: app("Water 2D simulation", 4)
{
p[0] = {- N / 2.f, 0.f};
p[1] = { N / 2.f, 0.f};
p[2] = {0.f, N * std::sqrt(0.75f)};
simulation_area |= p[0];
simulation_area |= p[1];
simulation_area |= p[2];
water_height.assign(N * N, 0.5f);
flux[0].assign(first_type_count, geom::vector{0.f, -5.f});
flux[1].assign(first_type_count, geom::vector{0.f, -5.f});
flux[2].assign(first_type_count, geom::vector{0.f, -5.f});
for (int i = 0; i < N; ++i)
{
flux[0][first_type_count - i - 1] = geom::vector<float, 2>::zero();
flux[1][first_type_count - i - 1] = geom::vector<float, 2>::zero();
flux[2][first_type_count - i - 1] = geom::vector<float, 2>::zero();
}
}
void water_2d_app::update()
{
float const dt = 0.01f;
float const max_speed = 10.f;
geom::vector<float, 2> const flux_normal[3] =
{
geom::vector{ std::sqrt(3.f) / 2.f, 1.f / 2.f },
geom::vector{ - std::sqrt(3.f) / 2.f, 1.f / 2.f },
geom::vector{ 0.f, -1.f },
};
auto const rot1 = [this](std::pair<int, int> const & p)
{
return std::pair{N - p.second, p.first - p.second};
};
auto const rot2 = [rot1](std::pair<int, int> const & p)
{
return rot1(rot1(p));
};
std::vector<float> dh(water_height.size(), 0.f);
std::vector<geom::vector<float, 2>> du[3];
for (int s = 0; s < 3; ++s)
du[s].assign(first_type_count, geom::vector{0.f, 0.f});
for (int id = 0; id < N * N; ++id)
{
bool const first_type = (id < first_type_count);
auto const [i, j] = first_type ? unpack_triangular_id(id) : unpack_triangular_id(id - first_type_count);
int e[3];
if (first_type)
{
e[0] = pack_triangular_id({i, j});
e[1] = pack_triangular_id(rot1({i + 1, j + 1}));
e[2] = pack_triangular_id(rot2({i + 1, j}));
}
else
{
e[0] = pack_triangular_id({i, j});
e[1] = pack_triangular_id(rot1({i + 2, j + 2}));
e[2] = pack_triangular_id(rot2({i + 2, j}));
}
float const flux_sign = first_type ? -1.f : 1.f;
for (int s = 0; s < 3; ++s)
dh[id] += flux_sign * geom::dot(flux_normal[s], flux[s][e[s]]) * dt;
}
for (int id = 0; id < N * N; ++id)
{
water_height[id] += dh[id];
water_height[id] = std::max(0.f, water_height[id]);
}
for (int s = 0; s < 3; ++s)
{
for (int id = 0; id < first_type_count - N; ++id)
{
auto const [i, j] = unpack_triangular_id(id);
std::pair<int, int> tin, tout;
if (s == 0)
{
tout = {i, j};
tin = {i, j};
}
else if (s == 1)
{
tout = rot2({i + 1, j});
tin = rot2({i + 2, j});
}
else // if (s == 2)
{
tout = rot1({i + 1, j + 1});
tin = rot1({i + 2, j + 2});
}
}
}
for (int s = 0; s < 3; ++s)
{
for (int id = 0; id < first_type_count - N; ++id)
{
flux[s][id] += du[s][id];
auto const [i, j] = unpack_triangular_id(id);
std::pair<int, int> tid;
bool first_type;
float const nflux = geom::dot(flux[s][id], flux_normal[s]);
bool const forward = nflux > 0.f;
if (forward)
{
first_type = true;
if (s == 0)
tid = {i, j};
else if (s == 1)
tid = rot2({i + 1, j});
else // if (s == 2)
tid = rot1({i + 1, j + 1});
}
else
{
first_type = false;
if (s == 0)
tid = {i, j};
else if (s == 1)
tid = rot2({i + 2, j});
else // if (s == 2)
tid = rot1({i + 2, j + 2});
}
auto const h = water_height[pack_triangular_id(tid) + (first_type ? 0 : first_type_count)];
float const max_flux = h * max_speed;
if (forward && nflux > max_flux)
{
flux[s][id] -= flux_normal[s] * (nflux - max_flux);
}
if (!forward && (-nflux > max_flux))
{
flux[s][id] += flux_normal[s] * ((-nflux) - max_flux);
}
// if (forward)
// flux[s][id] = std::min(flux[s][id], h * max_speed);
// else
// flux[s][id] = std::max(flux[s][id], - h * max_speed);
}
}
}
void water_2d_app::present()
{
gl::ClearColor(0.8f, 0.8f, 0.8f, 0.8f);
gl::Clear(gl::COLOR_BUFFER_BIT);
gl::Enable(gl::BLEND);
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
geom::box<float, 3> view_area;
{
view_area[0] = simulation_area[0];
view_area[1] = simulation_area[1];
view_area[2] = {-1.f, 1.f};
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 = 2.f / std::sqrt(width() * height() * geom::det(transform));
// Background
painter.triangle(p[0], p[1], p[2], gfx::white);
// Water tiles
for (int id = 0; id < N * N; ++id)
{
bool const first_type = (id < first_type_count);
auto const [i, j] = first_type ? unpack_triangular_id(id) : unpack_triangular_id(id - first_type_count);
(void)pack_triangular_id;
auto at = [this](int i, int j)
{
float const u = (i * 1.f) / N;
float const v = (j * 1.f) / N;
return geom::lerpn(p[0], 1.f - u, p[1], v, p[2], u - v);
};
auto f = [](float h)
{
float const C = 1.f;
float const e = std::exp(- C * h);
return (1.f - e) / (1.f + e);
};
auto color = gfx::to_coloru8(gfx::color_4f{0.f, 0.f, 1.f, f(water_height[id])});
if (water_height[id] < 0.f)
color = gfx::magenta;
if (first_type)
{
painter.triangle(at(i, j), at(i + 1, j + 1), at(i + 1, j), color);
}
else
{
painter.triangle(at(i + 1, j), at(i + 1, j + 1), at(i + 2, j + 1), color);
}
}
// Edges
// if(false)
for (int i = 1; i <= N; ++i)
{
float t = (i * 1.f) / N;
painter.line(geom::lerp(p[0], p[1], t), geom::lerp(p[0], p[2], t), 2.f * pixel, gfx::black, false);
painter.line(geom::lerp(p[1], p[0], t), geom::lerp(p[1], p[2], t), 2.f * pixel, gfx::black, false);
painter.line(geom::lerp(p[2], p[0], t), geom::lerp(p[2], p[1], t), 2.f * pixel, gfx::black, false);
}
// Flux
// if(false)
for (int side = 0; side < 3; ++side)
{
auto at = [this, side](int i, int j)
{
float const u = (i * 1.f) / N;
float const v = (j * 1.f) / N;
return geom::lerpn(p[side], 1.f - u, p[(side + 1) % 3], v, p[(side + 2) % 3], u - v);
};
for (int id = 0; id < first_type_count; ++id)
{
float const scale = 0.1f;
auto const [i, j] = unpack_triangular_id(id);
auto const p0 = at(i + 1, j);
auto const p1 = at(i + 1, j + 1);
auto const p = geom::lerp(p0, p1, 0.5f);
painter.line(p, p + flux[side][id] * scale, pixel * 2.f, gfx::red, false);
}
}
painter.render(transform);
}
void water_2d_app::on_resize(int width, int height)
{
app::on_resize(width, height);
aspect_ratio = (width * 1.f) / height;
}
int main()
{
return app::main<water_2d_app>();
}