Compare commits

...
Sign in to create a new pull request.

822 commits

Author SHA1 Message Date
77d359fa52 Implement sdl2+wgpu backend for macos+metal 2026-06-30 23:47:25 +03:00
c5b8eb4f49 Fix wgpu chained_struct constructors 2026-06-30 23:46:51 +03:00
810770564e Implement util::executable_path() for macos 2026-06-30 23:46:26 +03:00
bb0c3bb178 Remove random::uniform_real_distribution copy constructor 2026-06-30 23:45:40 +03:00
056a855a32 Fix false-positive unused lambda capture in ecs::dispatcher 2026-06-30 23:45:24 +03:00
9c76feb150 Replace GNU-specific ##__VA_ARGS__ with __VA_OPT__ 2026-06-30 23:44:31 +03:00
379b1e53a0 Replace std::result_of with std::invoke_result 2026-06-30 23:43:52 +03:00
940c2bb07e Only use --whole-archive on android 2026-06-30 15:29:00 +03:00
cd87a325d0 Remove template operator<< for dummy stacktrace in util::exception 2026-06-30 15:28:47 +03:00
44d4deca22 Use system_clock in event journal time printing 2026-06-30 15:28:24 +03:00
e3030416ce Replace sprintf with snprintf in stb_image_write.h 2026-06-30 15:27:28 +03:00
f251b1b07f Fix util::mutexed::exchange 2026-06-30 15:27:15 +03:00
c0a60cbdc8 Remove unused boost/stacktrace include 2026-06-30 15:27:02 +03:00
a9094975be Use util::mutexed in audio::channel if atomic_ptr<shared_ptr> is not supported 2026-06-30 14:40:47 +03:00
99978c3241 Add util::mutexed<T> 2026-06-30 14:40:08 +03:00
5a6975b3f4 Fix subobject initialization in math/detail/array_2d.hpp 2026-06-30 14:39:12 +03:00
0cc148f653 Fix missing include in math/detail/array.hpp 2026-06-30 14:38:24 +03:00
e5d356e1fc Remove Boost.Math from COMPONENTS when finding boost in config mode 2026-06-30 13:52:38 +03:00
500646bfe9 Fix util::reversed() to support both begin()/end() methods and free functions 2026-06-30 13:13:54 +03:00
aeb0f4a799 Make sure math::vector,point are trivially default constructible 2026-06-30 13:13:05 +03:00
68aedbf2fa Add hash table formatters 2026-06-07 18:48:10 +03:00
03af0a7099 Fix character types in math primitive formatters 2026-06-07 18:48:01 +03:00
3a22c74ce5 Add std::format support for matrices 2026-06-05 23:00:28 +03:00
892662307f Fix some math primitive formatters 2026-06-05 23:00:19 +03:00
0731dd9216 Add wgpu::memory_usage for textures and texture formats 2026-05-11 13:47:41 +03:00
074cbb18ba Mark appropriate methods of wgpu wrappers as const 2026-05-11 13:47:21 +03:00
b3cb60371e Tweak vector & quaternion slerp 2026-04-14 21:04:56 +03:00
2d9355f829 More weather v2 tests 2026-04-12 17:13:23 +03:00
5b98171283 Weather sim v2: use a fixed initial wind field + a small noise for all 8 climate snapshots 2026-04-09 15:18:19 +03:00
c097420a50 Weather sim v2: river generation 2026-04-08 16:32:05 +03:00
1044443e9b Weather sim v2: make land & water heating speed differ 2026-04-08 12:27:08 +03:00
dd8781a1b2 Weather experiments v2 2026-04-08 01:38:00 +03:00
005008b720 Find Boost in config mode 2026-04-06 23:04:01 +03:00
2a486c6d8f More weather experiments 2026-04-06 23:04:01 +03:00
d1a3bf15d4 Fix util::ndarray::copy() in case of an empty array 2026-02-20 20:19:32 +03:00
c3f48fdbc0 Unify render pass & compute pass timestamp writes in wgpu 2026-02-11 17:55:48 +03:00
2f781525d7 Implement math::concat for arbitrary types, not just vectors 2026-02-03 23:45:00 +03:00
339de6b218 Handle negative values as zeroes in statistics log buckets 2026-02-03 23:44:46 +03:00
0e813c3307 Fix compilation on gcc-15: int template arguments can't be implicitly casted to size_t 2026-01-25 14:21:13 +03:00
23f153c38d Weather simulation experiments wip 2025-12-15 13:19:23 +03:00
f88d158b35 Weather simulation wip 2025-12-12 15:35:31 +03:00
02817be114 Weather simulation: biome distribution 2025-12-11 16:44:36 +03:00
69a2a04811 Weather simulation force field experiments 2025-12-10 17:32:29 +03:00
9e4a96f622 Weather simulation test wip 2025-12-07 13:31:15 +03:00
253eda65ce Weather simulation test: periodic X-boundary 2025-12-06 14:07:19 +03:00
e3cbbb5b47 Weather simulation test (wip) 2025-12-05 17:10:52 +03:00
2549d248a5 Improve matrix norms computation & add linf_norm 2025-12-04 18:11:32 +03:00
8832700e25 Optimize math::length(vector) to prevent allocations for runtime-sized vectors 2025-12-04 18:11:11 +03:00
a5acb9534b Big math::vector,point,matrix refactor: fully support dynamic (runtime) size in basic operations 2025-12-04 17:22:32 +03:00
a8e08bf6d3 Remove dynamic-sized storage from math::box 2025-12-04 17:21:57 +03:00
4b314d78c4 Make math::matrix::values() return util::span 2025-12-03 15:24:48 +03:00
62f2adcb82 Update to wgpu-native v27.0.2.0 2025-12-02 17:40:51 +03:00
48d756bdd5 Reimplement journal lib without sqlite & remove sqlite dependency 2025-12-02 13:43:30 +03:00
7c398e28ff Fix hash_table::insert (lmao) 2025-11-13 12:42:33 +03:00
774620c673 Change math::matrix storage type in preparation for dynamic-sized matrices 2025-10-25 16:12:38 +03:00
17d857ecf2 Use util::array as the storage for math types with dynamic size 2025-10-25 15:20:59 +03:00
e40467a8d1 Add util::array - a dynamic array without auto-expansion 2025-10-25 15:20:42 +03:00
b99a4da18f Rename util::array -> util::ndarray 2025-10-25 14:51:30 +03:00
e23455356d Support float32 output in SDL2 audio backend (with int16 fallback) 2025-10-23 18:40:30 +03:00
64ffe27c2a Fix abs(fixed_point), lmao 2025-10-19 02:40:35 +03:00
615ee51229 Support async::executor::clear() 2025-10-18 17:31:34 +03:00
78e9731d8d Fix math library std::formatters & make them use format rules derived from base scalar type 2025-10-18 15:27:39 +03:00
b1ea7bc763 Add gfx::luminance(color) 2025-10-11 00:39:57 +03:00
424bf5f533 Add ecs::container::detach_finally/destroy_finally helpers 2025-10-10 16:01:17 +03:00
4a4f680d0a Add debug assertions for when an entity's archetype is changed while it is being created/destroyed 2025-10-10 16:01:00 +03:00
8d25721d35 Don't call most SDL_GL_xxx functions when using WebGPU API 2025-09-16 20:45:12 +03:00
7c5062e081 Add hash_table::reserve 2025-09-16 20:44:42 +03:00
7300679f56 Properly initialize math::interval<util::fixed_point> 2025-09-14 17:22:10 +03:00
8a739bf637 Fixed-point improvements:
* Mark everything constexpr
* Use unsigned type for multiplication to prevent UB on overflow
* Add fp -> fp conversions
* Add floor & ceil functions
2025-09-14 00:03:07 +03:00
7beba986ae Add math::cast(ray) 2025-09-14 00:02:28 +03:00
a42d025ffb libs/math fixes in case scalar type isn't implicitly constructible from int 2025-09-14 00:02:19 +03:00
be372ee007 Switch to a simpler & faster uniform real generation algorithm 2025-09-07 14:01:18 +03:00
754b279b1a Optimize math::smootherstep somewhat 2025-09-07 14:00:45 +03:00
08f62bdaf7 Rewrite math::angle_difference without trigonometry 2025-09-07 14:00:25 +03:00
3b88908534 Log max execution time in profiler dump 2025-09-07 13:59:57 +03:00
3b89037c25 Pass string_view instead of string in profiler scopes 2025-09-07 13:59:43 +03:00
3bb8bd36f3 Add math::sqrt(dual) 2025-09-07 13:59:16 +03:00
1b548f1ba3 Profile tasks execution in async::threadpool 2025-09-07 13:59:06 +03:00
5c0d4d8d29 Make future return reference instead of value 2025-09-06 15:35:07 +03:00
0210b61540 Tweak util::fixed_point comments 2025-09-06 11:22:51 +03:00
56d23480b0 Add async::make_ready_future(T const &) 2025-09-04 14:18:18 +03:00
059b2b1539 Use null util::function instead of a stop_execution exception when stopping threadpool threads to prevent exceptions from triggering when debugging 2025-09-04 00:55:04 +03:00
5e7a9b7697 Fix saving thread names in logger 2025-09-04 00:54:26 +03:00
3e4d4f39ea Fix NaN's in profiling statistics 2025-09-04 00:54:04 +03:00
8784938e97 Make async::future shared & support multiple .then() calls 2025-09-03 20:20:16 +03:00
78ea54ac2d Add math::dual weak comparison operators & some transcendental functions 2025-08-31 14:46:20 +03:00
2cce0082cf Fix math::smooth(er)step in case the type isn't implicitly constructible from integers 2025-08-31 14:45:52 +03:00
905e98070c Fix includes in util/recursive.hpp 2025-08-29 16:39:28 +03:00
d2f35276bf Add util::is_pow2 2025-08-29 16:39:19 +03:00
fa214ed956 Support more generic index factories in ecs container 2025-08-24 12:09:00 +03:00
c7b1bc0b0a Fix throwing key_error in util::hash_table for heterogeneous keys 2025-08-17 21:09:31 +03:00
b83a3635fc Remove modification callbacks API from ecs::container - ain't gonna implement them anyway 2025-08-11 18:19:44 +03:00
b1255f7a14 Expose wgpu instance in app context 2025-08-09 13:31:23 +03:00
46f672599d Upgrade to wgpu-native version 25.0.2.1 2025-08-08 23:20:44 +03:00
d3366b56aa Support version argument in find-wgpu-native script 2025-08-08 23:19:48 +03:00
0e000b190d Nump minimum cmake version 2025-08-08 23:19:06 +03:00
7b1ed4bd95 Make util::make_uuid use md5 hash 2025-08-06 17:54:29 +03:00
e57b284ffc Add constexpr md5 hash implementation 2025-08-06 17:53:24 +03:00
80daea54fa Move old font classes to an inline fonts::v1 namespace 2025-08-06 17:53:00 +03:00
68ba3698f8 Fix transcendetal functions on util::fixed_point 2025-07-29 22:29:17 +03:00
ce829f7356 Add permutation groups & implement converting cyclic & dihedral groups into permutations 2025-07-12 01:28:40 +03:00
1f57c76036 Add std::array hash 2025-07-12 01:28:14 +03:00
c3c8446431 Make default-initialized math::trs the identity 2025-07-10 02:13:02 +03:00
a7cbe69712 Add math::trs class & use it in gltf animations 2025-07-09 21:02:22 +03:00
d826940b4c Make bt nodes execute subnodes immediately (and not across several updates) whenever possible 2025-07-08 20:30:46 +03:00
14b18197bf Support resetting vecr::renderer to an existing image 2025-06-09 14:24:37 +03:00
afd942af14 Support worley noise in noise-generator tool 2025-06-09 13:37:20 +03:00
08510e265b Support comparing wgpu objects by comparing underlying pointers 2025-06-05 18:47:38 +03:00
24f3df6f35 Add app::apply(event_state, text_input_event) 2025-05-20 17:23:23 +03:00
fde307e96f Update license 2025-05-15 12:57:28 +03:00
cc031470b2 Add particle life example 2025-05-14 23:05:35 +03:00
a8e32e2d98 Add cg::kdtree::closer_than 2025-05-14 22:56:26 +03:00
08ca6ab21b Fix bug in cg::kdtree::insert 2025-05-14 22:56:15 +03:00
2f75df3c91 Track the size of kd-tree nodes 2025-05-14 22:55:17 +03:00
132a521b6a Add gfx::painter::circle() with different center & border colors 2025-05-14 22:54:05 +03:00
1d8ba361fc Fix compiling journal lib without sqlite 2025-05-14 22:53:36 +03:00
d5abbb4762 Support random::uniform_from with initializer_list 2025-05-14 22:52:47 +03:00
8e89679219 Remove tests for util::blob copying since the copy operator was removed 2025-05-14 22:52:29 +03:00
93aa697347 Support RAII-wrapping the execution of an ECS system into some context (e.g. for profiling) 2025-04-13 17:36:43 +03:00
c75809aa75 Support move operations in prof::profiler 2025-04-13 17:36:05 +03:00
6a8563cce9 Support freetype font fallbacks by storing a sequence of fonts 2025-04-13 12:44:44 +03:00
e4222f35ff Replace util::blob copy constructor/assignment with .copy() method and mark all appropriate methods noexcept 2025-04-13 12:44:26 +03:00
73abc03ae2 Add explicit util::utf8_range(std::string) constructor 2025-04-13 12:43:56 +03:00
7e2c2a4c6d Implement utf8 encoding and replace std::codecvt with custom code in utf32 <-> utf8 conversions 2025-04-13 12:43:40 +03:00
6cf5eb008b Support operator--() for util::utf8_iterator 2025-04-13 12:42:51 +03:00
9f0f07885d Show unknown characters as '?' in freetype fonts 2025-04-13 00:43:42 +03:00
083e5841aa Add ecs::container::finally mechanism 2025-04-10 11:57:50 +03:00
0034e6a9f1 Disable text input at application start in SDL2 backend 2025-04-07 11:10:12 +03:00
944d1374a5 Fix handling text input events in app::scene_application 2025-04-07 11:09:01 +03:00
5fcd2a171f Support using a lambda in vecr colorizer 2025-04-06 21:55:40 +03:00
472d095e2d Support setting individual pixels in vecr::renderer 2025-04-06 21:55:29 +03:00
c8a58d1de4 Add sqlite-based event journaling library 2025-04-04 21:07:49 +03:00
8099e928dc Add std::format formatter for ecs::handle 2025-04-04 21:07:31 +03:00
57f9f7331c Add std::format formatters for basic math types 2025-04-04 21:07:19 +03:00
6a8a896aba Remove bt logging 2025-04-04 15:15:49 +03:00
e5811b61a0 Fix triggering constructor & destructor callbacks when attaching/detaching ecs components 2025-03-06 18:20:40 +03:00
42260d4d7d Replace template operator() of util::function with a fixed-arg version to put all implicit conversions & list initializations to the actual call site 2025-03-06 18:20:40 +03:00
4dc8b6a183 Use _WIN32 macro to detect Windows (instead of other macro variants) 2025-03-04 20:00:03 +03:00
2e28b3ffc6 Use FT_New_Memory_Face for freetype fonts to fix loading non-ascii paths on Windows 2025-03-04 19:59:38 +03:00
d41c53193d Use wide strings in util::executable_path() 2025-03-04 19:59:04 +03:00
ef85eec178 Use wide strings for io::file_stream on Windows 2025-03-04 18:12:21 +03:00
dab7b6327e Fix wgpu packaging for windows 2025-02-26 22:13:14 +03:00
195a31fa1c Add text input events in SDL2 backend 2025-02-24 13:02:48 +03:00
12eed4dda5 Add HSV <-> RGB conversions 2025-02-23 13:56:33 +03:00
4f4e86ce4f Add math::inverse_smoothstep 2025-02-23 13:56:13 +03:00
4c16594b3f Use pi casted to appropriate type instead of implicit conversion to double in math/math.hpp 2025-02-23 11:46:43 +03:00
85451a1d6f Add gfx::hue(colorf) 2025-02-23 00:31:13 +03:00
72694664de Add util::moving_average::max_count() 2025-02-21 22:26:24 +03:00
28a4d9ccb0 Make cg::kdtree::closest const & add cg::kdtree::clear & assign 2025-02-21 19:20:54 +03:00
97c9d79a5a Add default values to wgpu-related fields in application::options 2025-02-16 18:43:30 +03:00
3655fc9c6f Add (bad) support for multiline text in gfx::painter: 2025-02-03 00:43:39 +03:00
cd31187d3f Add missing include 2025-02-02 21:28:59 +03:00
a6268663da More 2d shallow water tests 2025-01-30 13:07:30 +03:00
fa9c5efaf0 Add 2D hex water example 2025-01-30 12:40:32 +03:00
f75c04d988 Make gfx::mesh only bind index buffer as GL_ELEMENT_ARRAY_BUFFER, in accordance with WebGL restrictions 2025-01-30 12:40:19 +03:00
4e2aa5f577 Update stb_image.h 2025-01-30 12:39:13 +03:00
64d85dd1c0 Don't require OpenMP in sort creatures 2d example 2025-01-30 12:39:04 +03:00
d4e3cc623a Replace deprecated std::atomic_load with std::atomic<shared_ptr> in audio::channel 2025-01-30 12:38:44 +03:00
cca966b33e Fix missing include 2025-01-30 12:38:10 +03:00
b01c594cba Add math::fmod that works for negative inputs 2025-01-30 12:37:48 +03:00
adcf761243 Support dynamic size in math::vector 2025-01-26 19:03:33 +03:00
6fc476f1f0 Support building without Boost.Stacktrace 2025-01-26 14:45:43 +03:00
4890761b0a More generous iteration count for 3-layer XOR nn test 2025-01-25 21:14:53 +03:00
5587c43611 ECS library 32-bit fixes 2025-01-25 21:14:32 +03:00
5155175d9b Flush test name to stdout when starting to run a test 2025-01-25 20:35:57 +03:00
2c3565df61 32-bit compilation fixes:
* Use uint64_t instead of size_t as hash return value

 * Expect alignof(uint64_t) <= 8 instead of == 8
2025-01-25 20:35:37 +03:00
7c15c1bb0d Add water 2d example 2025-01-25 16:01:49 +03:00
3940766f7d Compilation fixes related to ui_legacy lib 2025-01-21 14:10:35 +03:00
b967af7ddd Add back default fonts 2025-01-21 14:10:35 +03:00
a49be3b253 Fix util::hash_map with std::pair as keys 2025-01-21 14:10:30 +03:00
8a21e53ee1 Rename old ui library to ui_legacy 2025-01-21 12:14:38 +03:00
20f3bef6bd Add random::poisson distribution 2025-01-12 20:05:47 +03:00
073ac16223 Add a basic k-d tree implementation 2025-01-09 18:22:28 +03:00
c59b28e13f Rename 'geom' library to 'math' 2024-12-10 20:22:59 +03:00
89cbbaeeef Fix bt::conditional node: proper assert handling & child nodes starting 2024-12-07 20:05:16 +03:00
6d4abb7c03 Add bt::conditional node 2024-12-05 23:04:50 +03:00
e7482bb165 Fix max_anisotropy naming in wgpu 2024-12-03 22:29:06 +03:00
bfa0491f39 Support retrieving ECS table statistics 2024-11-29 01:15:35 +03:00
95b6651fc3 Support pushing > 1 measurement at a time in profiler 2024-11-29 00:44:45 +03:00
9b3f2df2a0 Rely on float->int conversion instead of std::floor in audio resampler hot loop 2024-11-29 00:07:02 +03:00
80b4cc938d Fix constructors & destructors order
Order of callbacks was affected by the order of caches, which are pretty
much arbitrary. For a fix, explicitly order the callbacks globally
per-world.
2024-11-28 18:55:17 +03:00
fad1580379 Fix GCC false positive -Warray-bounds in geom::swizzle when using ubsan 2024-11-19 19:09:01 +03:00
ad752f0ea1 Separate ordered & unordered component hashes in ecs 2024-11-18 16:55:39 +03:00
3359aaa62d Simplify ecs::query_cache_container: store caches directly instead of storing nodes with uuid duplicates 2024-11-18 16:42:23 +03:00
daa2b9c49a Add bt_log macro for logging AI actions 2024-11-18 16:38:21 +03:00
6971b8545a Add util::null_ostream 2024-11-18 16:36:02 +03:00
5138e00c35 Move heavy gltf animation code to a cpp file 2024-11-14 21:45:08 +03:00
fbb032fe63 Support native limits in wgpu device creation 2024-11-14 13:48:40 +03:00
f986f0d4b1 Add defaults to wgpu::limits 2024-11-14 13:47:56 +03:00
e6ae945b92 Fix creating chained structs in wgpu 2024-11-14 13:47:13 +03:00
ce8547c8d3 Preprocess glTF rotation animations to force shortest-arc interpolation 2024-10-15 21:42:00 +03:00
b5d556d06f Fix cubic spline interpolation in glTF animations 2024-10-15 21:40:18 +03:00
8b5c35f89a Add audio::mixer::stream_count() 2024-09-18 12:12:23 +03:00
44672ac1fd Add bt_assert macro for non-crashing assertions in behavior trees 2024-09-13 23:28:10 +03:00
95492efea7 Add copy assignment to ecs::accessor 2024-09-13 23:27:48 +03:00
d66092b86d Add fixed-point arithmetic implementation 2024-09-05 23:16:18 +03:00
8617fac987 Fix usage of math functions in geom::vector to be ADL-friendly 2024-09-05 23:15:37 +03:00
655dd2778f Implement string entity description in ecs 2024-09-01 23:59:31 +03:00
fbf78f1dc4 Fix east const 2024-08-31 14:40:08 +03:00
d032f93eb3 Support retrieving entity count from ecs::container 2024-08-29 15:52:21 +03:00
2bd4e5790b Support embedded textures in glTF 2024-08-26 23:42:29 +03:00
954068ba3a Fix merging util::statistics 2024-08-22 19:53:53 +03:00
ba8b33d49e Add scripts to build packaging images 2024-08-22 19:25:05 +03:00
7ada90e3d0 Fix webgpu packaging on windows 2024-08-22 19:22:16 +03:00
4e9a551452 Better touch events support 2024-08-22 18:36:37 +03:00
c3a859d358 Stop compilation for android if application name is not set 2024-08-22 18:36:05 +03:00
aca5048f6f Stop compilation if libbacktrace is not built 2024-08-22 18:35:43 +03:00
88b8c2354f Fix building libbacktrace for android 2024-08-22 18:35:25 +03:00
ffc77e5fa3 Fix resource stream impl for android 2024-08-21 19:18:44 +03:00
17a0aef630 Fix uninitialized value warning 2024-08-21 19:18:16 +03:00
02bd1dc2f1 Globally disable -Wdangling-reference for GCC-13+ 2024-08-21 19:17:59 +03:00
57aebfb42d Compile minimp3 as C++ 2024-08-21 19:17:33 +03:00
3dde4d6b55 Remove template dependance on float/double in util::statistics_log_bucket 2024-08-21 19:17:10 +03:00
5042fbbf7e Make freetype fully optional 2024-08-21 13:45:45 +03:00
013212e582 Fix packaging wgpu dll 2024-08-20 15:31:47 +03:00
c16194c95c Support drawing polygons in gfx::painter 2024-08-20 11:51:12 +03:00
a2c83633ae Support non-const width lines in gfx::painter 2024-08-18 01:12:17 +03:00
82f7d5d429 Rename util::begin/end -> xbegin/xend to prevent ambiguity via ADL 2024-08-17 20:19:07 +03:00
6368ca5e68 Support 2D scale in gfx::painter::text 2024-08-17 18:17:32 +03:00
0568521879 Add project-creation script 2024-08-16 18:27:22 +03:00
c350c8f911 Fix random int generation 2024-08-16 18:13:59 +03:00
241f3afde3 Conditionally add freetype and wgpu libs to packaging files 2024-08-16 18:12:47 +03:00
9273a6f538 Support freetype packaging for windows 2024-08-15 13:01:15 +03:00
3a24090607 Remove app -> ui dependency 2024-08-13 14:51:12 +03:00
dbd479d413 Farewell to reactive ui library 2024-08-12 23:16:34 +03:00
4fde43313c Reactive UI library wip: add floating, shape_reader and storage 2024-08-10 11:58:21 +03:00
ddddfb67ce Separate mouseover state vs mouseover event in ui::button 2024-08-09 15:17:11 +03:00
84905b6f05 Fix ui::button updating to new event sources 2024-08-09 13:33:28 +03:00
9ce8082e4d Add gfx::blend(color_rgba, color_rgba) 2024-08-08 17:40:27 +03:00
2fad7e0349 Fix reconcilliation with empty children 2024-08-08 16:47:01 +03:00
820a184fa7 Fix handling empty children in ui::box_layout 2024-08-08 13:29:42 +03:00
6089c02e90 Fix ui reconcilliation for empty children 2024-08-08 13:29:23 +03:00
5141643531 Fix freetype font build for windows 2024-08-08 00:27:39 +03:00
f177f29a60 Add psemek_add_dev_application & psemek_add_dev_tool for non-packaged executables 2024-08-08 00:27:19 +03:00
57027c837e Add freetype to linux packaging 2024-08-07 23:27:04 +03:00
7e96709dc1 Turn wgpu::buffer::usage into a enum class 2024-08-07 22:28:23 +03:00
47ec94641b Support filter in vecr renderer 2024-08-07 18:48:29 +03:00
f69657c826 Support animating ui::move 2024-08-07 18:47:57 +03:00
611dc959da Require glyph drawing from ui::renderer & implement ui::label component 2024-08-07 15:45:19 +03:00
ab50b4c323 Remove debug printing from packaging functions 2024-08-07 15:43:11 +03:00
61626b9179 Introduce fonts v2 & add freetype font implementation 2024-08-07 15:42:48 +03:00
a4d666096e Remove library-specific graphics api defines in favor of global PSEMEK_GRAPHICS_API_XXX 2024-08-02 20:39:52 +03:00
0718282dda Support booleans in glTF extras 2024-08-02 16:56:15 +03:00
c1c9e304da Add debug code to fix resolution to 1024x576 2024-08-02 16:55:47 +03:00
9843a0cf06 Fix reshaping on window resize in ui controller 2024-08-02 12:20:48 +03:00
0cc04cdbb6 Don't log showing/hiding cursor 2024-08-02 12:02:32 +03:00
d5b755bde0 Support ui component-specific cursors specified by a string id 2024-08-02 11:53:42 +03:00
61fd45d40c Fix event handing order in ui controller 2024-08-01 23:01:39 +03:00
3ac46bc8dc Make sure subscribed-to 'children' reactive values stay alive in ui::component_factory_base 2024-08-01 22:24:05 +03:00
9f063218bf Add debug markers to react::value internal nodes to track their lifetime 2024-08-01 22:23:18 +03:00
367f82a01f New UI library internals redesign: add global event_state, add delayed events queue, fix reshaping & subscribing to size_constraints 2024-07-31 15:55:23 +03:00
7151fef7a8 Redesign psemek::sdl2 cursor api 2024-07-30 23:51:49 +03:00
13862d8b47 Fix handling state in ui::impl::button_base 2024-07-30 23:51:28 +03:00
7346aff9f6 Fix ui::impl::box_layout_base handling added elements 2024-07-30 13:03:41 +03:00
9234a28344 Support animating UI components 2024-07-29 19:04:04 +03:00
ddaf2407a9 Fix math overflow crash in computing profiling statistics 2024-07-29 14:16:51 +03:00
3856fdd827 UI library wip: implement frame, extend & move 2024-07-29 13:24:14 +03:00
8cc3356eb1 Add ui::impl::renderer 2024-07-29 11:11:20 +03:00
d7b68e44a8 Fix updating size policies in ui::impl::box_layout_base 2024-07-29 11:11:10 +03:00
b32a891183 Bugfix in ui::impl::button_base 2024-07-29 11:09:33 +03:00
1ed8b28633 Bugfix in react::map_vector 2024-07-29 11:09:19 +03:00
ae815ec538 UI library wip: event handling, auto-layout, basic layouts & buttons 2024-07-28 19:28:38 +03:00
546c0f2a7b Support subscribing to react::value forever 2024-07-28 19:28:02 +03:00
018f3ae0b0 Replace ui::size_polygon with simpler 2d box 2024-07-21 14:32:57 +03:00
325dc01757 Separate show_mouse and relative_mouse_mode 2024-07-20 02:17:22 +03:00
d2ba791be1 Support subscribing to util::signal without the need to keep the subscription token 2024-07-19 21:14:38 +03:00
403e24bf4c Add null ecs handle for convenience 2024-07-19 00:35:37 +03:00
5df29246a5 Fix util::at(vector) 2024-07-17 19:43:54 +03:00
058505e9f0 Support explicitly creating & destroying profiler frames 2024-07-12 17:58:18 +03:00
54fb90214f Add missing non-template methods for hash table 2024-07-11 18:22:08 +03:00
99ca3ec2eb Bugfix in ecs query caches: store the component UUIDs in vectors instead of hash sets, because the order of components matters 2024-07-10 21:21:05 +03:00
e3750707bc Refactor geom::pointwise* and add geom::pointwise_pow 2024-07-04 22:58:18 +03:00
e55a98c2a0 Add ecs::accessor::contains(uuid) 2024-06-27 16:51:25 +03:00
b73a37deea Add vecr::closed(path) 2024-06-22 01:17:02 +03:00
bae1fb8c08 Support overall primitive alpha in vecr::renderer 2024-06-22 01:16:51 +03:00
b8ad66732a Add vecr::replace blend mode 2024-06-22 01:16:29 +03:00
17e23e41d4 Support specular highlight in vecr lighting colorizer 2024-06-22 01:16:15 +03:00
b290bfb063 Fix vecr::any unnecessarily moving objects 2024-06-22 01:15:48 +03:00
bced20ddc6 Add bt::by_index node 2024-06-19 16:39:33 +03:00
37a3d7f827 Fix ecs::dispatcher to work with ecs::without 2024-06-19 14:37:09 +03:00
bd202d08ae Add wgpu to windows packaging 2024-06-18 12:35:39 +03:00
7218730e7c Add wgpu to linux packaging 2024-06-18 12:35:22 +03:00
7537338bdb Create the packaging output directory before packaging 2024-06-18 12:35:12 +03:00
85031e0d59 Support globbing in psemek_package_files 2024-06-17 23:41:26 +03:00
faacf6dbc0 Tiny fix in ecs accessor is_empty constness 2024-06-16 20:41:39 +03:00
4b9591885a Fix audio::channel::is_stopped 2024-06-16 20:41:24 +03:00
aa32b09fca Support matrices in gltf_accessor_iterator 2024-06-08 17:33:14 +03:00
6d13bfa407 Load gltf skin names 2024-06-08 17:32:57 +03:00
b9a6fbf99f Add non-const accessors to gfx::gltf_animation channels 2024-06-08 17:07:41 +03:00
ea037acbc0 Use 5%-accurate log-bucketing statistics in profiler 2024-06-03 23:25:50 +03:00
30877401a3 Add log-bucketing statistics in util 2024-06-03 23:25:29 +03:00
6c5815ff76 Add util::spatial_array::empty 2024-06-03 23:25:14 +03:00
488290be4f Refactor util::statistics and use a more robust mean & variance computation algorithm 2024-06-03 21:00:19 +03:00
f8c52bcfe2 Use truncated normal distribution for percentile approximation in util::statistics_lite 2024-06-03 20:32:26 +03:00
47b772a432 Use statistics_lite in profiler by default 2024-06-03 18:15:52 +03:00
ad36acf238 Fix memory leak in wgpu lib 2024-06-03 18:15:33 +03:00
22a57f91db Huge refactor: use util::hash_table instead of std::unordered everywhere 2024-06-03 14:36:39 +03:00
e79266d7e2 Make util::hash_table::empty() const 2024-06-03 14:36:09 +03:00
cbd99d1c4d Make util::hash_table more heterogeneous-friendly 2024-06-03 14:35:58 +03:00
bd1393c505 Improve working with ecs caches, support retrieving ecs cache & table count, and fix ecs query container memory leak 2024-06-03 13:59:50 +03:00
68d4adcad2 Support reporting ECS memory usage 2024-06-03 11:35:37 +03:00
7279a5b6f5 Fix unused caches in ecs::dispatcher 2024-06-02 15:48:04 +03:00
91c2dbca67 Fixed in util::hash_table clearing & destructor 2024-06-02 15:47:41 +03:00
2dd9d1e22d Remove unused include 2024-06-02 15:47:16 +03:00
44d30a6f8d Add fake percentile computation in util::statistics_lite based on normal distribution 2024-06-02 15:46:56 +03:00
100e2d6af8 Add missing includes in util/ebo 2024-06-02 15:32:26 +03:00
7333bcd922 Support removal from util::hash_table and add more hash table tests 2024-06-01 14:56:53 +03:00
fc18c75557 Fix ecs bug when finalizing removal of detached entity in the last row of the old table 2024-05-31 19:07:51 +03:00
0bfedccc7b Fix typo in ecs 2024-05-31 19:06:00 +03:00
c68418e868 Add static assert on bt::action result type 2024-05-30 21:58:44 +03:00
283bc9efe5 Add bt::lazy 2024-05-30 17:25:46 +03:00
e1201e7913 Fill in iterator traits for gltf accessor iterator 2024-05-28 12:36:40 +03:00
b5117ca8b2 Support quaternions in gltf::accessor_iterator 2024-05-28 12:36:03 +03:00
b04456066b Fix ecs documentation 2024-05-27 18:51:35 +03:00
d4f08acf24 Fix return type of geom::lerp 2024-05-26 17:11:08 +03:00
443fa3d8ca ECS object lifetime issues fixes 2024-05-24 21:42:17 +03:00
01b8bd3d59 Add pointwise log & exp for geom::vectors 2024-05-23 16:51:40 +03:00
b9b18b800d Properly call constructors & destructors when attaching/detaching ECS components 2024-05-23 13:28:48 +03:00
cedc29f39a Allow creating ECS entities inside batch systems 2024-05-21 19:20:04 +03:00
eab13e2ae5 Track uuid->type mapping for ecs components and throw an exception when the same uuid is used for different components 2024-05-21 13:38:05 +03:00
6591dd1893 Remove unused includes in ecs/container.hpp 2024-05-21 13:37:25 +03:00
938aa688cc Fix registering const components 2024-05-21 13:23:14 +03:00
70b260ec50 Fix delete[] alignment in ecs table storage 2024-05-21 13:22:49 +03:00
933874eddf Add #pragmas in ecs::accessor to silence false-positive dangling reference warning in GCC 2024-05-21 13:22:27 +03:00
d9935c7476 Use std::list in ecs::query_cache to prevent reallocation problems 2024-05-21 13:21:49 +03:00
c6f9fbd244 Add util::hash_all for creating a hash out of a bunch of objects 2024-05-21 13:21:01 +03:00
713a479eed Fix registering ecs constructors & destructors 2024-05-20 16:43:25 +03:00
1da8e341c5 Trigger constructors when cloning an ecs entity 2024-05-20 16:43:11 +03:00
2423547973 Bugfix: allow ecs constructors & destructors to mutate entities 2024-05-20 16:42:43 +03:00
045f399245 Refactor ecs::container::clone in terms of try_clone 2024-05-20 16:41:27 +03:00
1538fa002f Support util::function constructing from & assigning to nullptr 2024-05-20 16:41:01 +03:00
cae25f4719 Use wgpu instance extras to select the backend 2024-05-18 21:41:09 +03:00
fffd2c70e6 Update to wgpu-native v0.19.4.1 2024-05-18 21:40:54 +03:00
b436e7b2d8 Log used wgpu-native version 2024-05-18 21:19:08 +03:00
6581143837 Setup WebGPU logging 2024-05-18 21:13:43 +03:00
cb4a8e836f Missing include compilation fix 2024-05-15 13:39:09 +03:00
c18a8ba2c5 Add implicit util::hstring(const char*) constructor 2024-04-26 13:13:53 +03:00
119a323b72 Update package images to use ubuntu:24.04 with gcc-13 and mingw-13 2024-03-28 15:27:54 +03:00
6770ce908f Refactor trivial serialization 2024-03-27 21:37:10 +03:00
b26849e689 Minor pcg::lazy_perlin fix 2024-03-20 20:57:17 +03:00
83f92ae9cb Add util::dsu 2024-03-18 15:06:07 +03:00
bede9c95f9 Fix computing contact point in edge-edge case in cg::separation 2024-03-17 11:32:59 +03:00
663f7bc5f4 Support retrieving extra properties & raw mesh from gltf mesh 2024-03-16 17:37:57 +03:00
ff1c144f25 Add geom::outer_product 2024-03-16 00:25:20 +03:00
a5812c02c8 Add generic cg::triangle_mesh convex body 2024-03-15 23:27:24 +03:00
52969c718a Compute contact point in cg::separation 2024-03-15 23:27:10 +03:00
49eafc4806 Add geom::cross_product_matrix 2024-03-15 23:26:54 +03:00
1cc2c23602 Support glb in gfx::gltf_mesh 2024-03-15 23:26:43 +03:00
d7c9b484fe Add cg::irregular_triangular_prism 2024-03-14 14:12:23 +03:00
718d0c7d04 Add geom::project_from 2024-03-14 12:54:12 +03:00
47d3156c57 Add crude point-convex body distance to cg 2024-03-14 08:42:58 +03:00
d179ef65a2 Add geom::cast(quaternion) 2024-03-14 08:42:09 +03:00
a26826fc03 Add ignored warning in stb_image_write 2024-03-14 08:41:47 +03:00
3187908435 Add missing include 2024-03-14 08:41:23 +03:00
448b6dc9fa Add relative mouse movement data in app event 2024-03-12 20:29:51 +03:00
153cc87986 Add non-template access methods to util::hash_table 2024-03-12 20:29:29 +03:00
645423ecba Compilation fix 2024-03-12 20:29:08 +03:00
24a74b1208 Add util::any_set::insert 2024-03-11 23:34:42 +03:00
49fe46bce2 Add util::array serialization 2024-03-11 21:38:10 +03:00
dbcffa8d4a Support gamma-correction in gfx::blur 2024-03-11 21:04:43 +03:00
646ef45acf Disable alpha blending in gfx::blur 2024-03-11 21:04:27 +03:00
05af18ba3f Implement lazy-loading audio tracks 2024-03-11 01:00:24 +03:00
bd3aa04922 Support loading raw audio track from blob 2024-03-11 00:59:17 +03:00
4a17529e39 Fix cross-compiling build tools 2024-03-07 18:34:42 +03:00
1ccf82c2c5 Add comment about gfx::texture_view units 2024-03-04 13:49:11 +03:00
d48f9c086f Make app::scene_application call scene->on_exit when stopped 2024-03-03 14:44:01 +03:00
9b9af9e3bc Don't require wgpu-native with OpenGL api 2024-03-03 02:17:16 +03:00
c7108a0e39 Fix building with OpenGL backend 2024-03-03 02:16:55 +03:00
57aceed172 Remove duplicate hash_table::operator[] (how did this even happen?) 2024-03-03 02:14:41 +03:00
ca2d106725 Add soft-body evolution examples 2024-02-28 17:06:49 +03:00
4ff36a61ba Add util::hash_map::operator[] 2024-02-28 17:06:48 +03:00
d1104f9aac Fix util::thread move assignment operator 2024-02-28 17:06:48 +03:00
84ef3faa68 Use faster quaternion rotation formula 2024-02-28 17:06:48 +03:00
540d63ce5b Add geom::compare_swap utility function 2024-02-28 17:06:48 +03:00
d09bd76a14 Add a few debug assertions to ecs::container 2024-02-10 23:47:59 +03:00
e28a78166e Support edge ids in util::pathfinder 2024-02-07 22:01:42 +03:00
740fab84be Add copying and constructing from initializer_list for util::hash_table 2024-02-07 22:01:19 +03:00
929e8091dc Fix perfect forwarding in constructor args in util::pathfinder 2024-02-07 14:14:13 +03:00
752593a589 Fix random::normal_distribution: actually use mean & stddev instead of always generating N(0,1) 2024-02-05 13:32:35 +03:00
04443b1e3a Support mutable lambdas in ecs::dispatcher 2024-02-05 13:04:53 +03:00
13a86a76c8 Add ecs::dispatcher - an event-based ECS system launcher 2024-02-03 15:22:33 +03:00
5533826711 Update ecs to-do list 2024-01-30 21:57:51 +03:00
92f02d8ac0 Fix computing ecs table query cache in the presence of without<> components 2024-01-30 21:57:21 +03:00
88677eb893 Make constructors & destructors operate on const components only in ecs 2024-01-30 21:56:55 +03:00
802bb1a74d Call constructors & destructors in ecs attach/detach 2024-01-30 21:56:30 +03:00
e6f5fc17a4 Remove useless code in pathfinder & support retrieving full path 2024-01-29 19:16:17 +03:00
099a09e4d9 Use util::hash_map instead of std::unordered_map for pathfinder 2024-01-29 17:44:27 +03:00
64a6713b61 Implement util::hash_table::operator[] and at() 2024-01-29 17:44:11 +03:00
368d1edd71 Remove duplicate util::key_error exception, don't require the key to be convertible to string 2024-01-29 17:43:52 +03:00
470b7a0757 Add TODO to reimplement weighted distribution using Vose's alias algorithm 2024-01-26 13:29:04 +03:00
4b1d513cd6 Add ecs::handle comparison operators 2024-01-25 22:27:24 +03:00
f5bd285336 Add geom::closed(interval) for iterating over closed interval 2024-01-17 11:49:14 +03:00
46a49ef42d Support creating ecs index without passing the container 2024-01-10 00:44:23 +03:00
8fcaef4ba1 ECS index API wip: support creating & storing indices 2024-01-10 00:12:55 +03:00
080893ee96 Fix copying entities 2024-01-06 22:21:07 +03:00
5e64a4c216 Bugfix in ecs 2024-01-06 20:12:26 +03:00
a36d25a34b Add geom::pointwise_divide 2024-01-06 19:57:26 +03:00
451701726c Bugfix in ecs 2024-01-03 21:22:49 +03:00
1ad6bf50a6 Add some defaults in wgpu sampler descriptor 2024-01-03 18:37:20 +03:00
880c61f789 Add wgpu indirect draw command structs 2024-01-03 12:31:30 +03:00
efbcf48d01 Add gltf accessor iterator 2024-01-03 03:25:28 +03:00
c3d964fafd Support loading GLB assets 2024-01-03 02:21:16 +03:00
00231ce3f6 Fix missing include 2024-01-02 18:24:13 +03:00
3e10b1e294 Add missing render pass encoder depth stencil attachment handling 2024-01-02 17:53:48 +03:00
78a131952b Add wgpu::buffer::usage operator | 2024-01-02 16:05:10 +03:00
8ff0adb710 Fix wgpu::device::create_render_pipeline 2024-01-02 16:04:40 +03:00
0998da6329 Add helper functions for constructing util::span 2024-01-02 16:04:26 +03:00
7c122c5664 WebGPU wrapper finished: add multi draw indirect count support 2024-01-01 17:39:42 +03:00
082b8e0493 WebGPU wrapper wip: add push constants support 2024-01-01 17:29:30 +03:00
0a088684d9 WebGPU wrapper wip: add native feature names from wgpu.h 2024-01-01 17:14:33 +03:00
d943f14185 WebGPU wrapper wip: add command encoder methods 2024-01-01 17:10:06 +03:00
97ee3c1134 WebGPU wrapper wip: add device methods 2024-01-01 16:43:46 +03:00
407df9a21d WebGPU wrapper wip: add render bundle encoder object 2024-01-01 16:19:50 +03:00
7071f3363d WebGPU wrapper wip: add render bundle object 2024-01-01 16:10:10 +03:00
2d8be7560b WebGPU wrapper wip: remove unnecessary object copying 2024-01-01 16:04:16 +03:00
e8b9d18d05 WebGPU wrapper wip: add render pass encoder object 2024-01-01 16:01:13 +03:00
818db07676 WebGPU wrapper wip: add compute pass encoder object 2024-01-01 15:03:44 +03:00
c0b485ae4b WebGPU wrapper wip: add pipeline layout object 2024-01-01 14:47:57 +03:00
20b584b5e0 WebGPU wrapper wip: add bind group layout object 2024-01-01 14:43:12 +03:00
4370bf608f WebGPU wrapper wip: move constant_entry to a separate header 2024-01-01 14:23:18 +03:00
e34b187235 WebGPU wrapper wip: add bind group object 2024-01-01 14:21:25 +03:00
e0281cd13f WebGPU wrapper wip: add compute pipeline object 2024-01-01 14:04:47 +03:00
ea71e1878e WebGPU wrapper wip: add render pipeline object 2023-12-31 15:21:20 +03:00
cde91b55a9 WebGPU wrapper wip: add device.create_* methods for existing objects 2023-12-31 14:35:51 +03:00
e52ab0d731 WebGPU wrapper wip: add sampler object 2023-12-31 14:08:04 +03:00
260d584df8 WebGPU wrapper wip: add shader module object 2023-12-31 13:51:15 +03:00
20a114eb41 WebGPU wrapper wip: clearing screen pipeline working 2023-12-31 02:13:28 +03:00
095b06d5f1 WebGPU wrapper wip: add queue methods 2023-12-30 23:47:02 +03:00
5add6df083 WebGPU wrapper wip: add texture::descriptor 2023-12-30 23:26:27 +03:00
66d6fa5c36 WebGPU wrapper wip: add command buffer object 2023-12-30 23:03:51 +03:00
2af6a1000a WebGPU wrapper wip: add buffer object 2023-12-30 23:03:34 +03:00
1144d4a46c WebGPU wrapper wip: add texture & texture view methods 2023-12-30 20:35:45 +03:00
0555243990 Log WebGPU backend when initializing 2023-12-30 20:08:53 +03:00
e2fdb777de WebGPU support wip 2023-12-30 16:50:44 +03:00
7611d375dc Add solving/inverting lower & upper triangular systems/matrices and tests 2023-12-28 19:01:14 +03:00
05b7f2d560 Add cholesky decomposition implementation & tests 2023-12-28 17:53:42 +03:00
2364c15120 Add QR eigenvalue algorithm and tests (non-symmetric tests need some more checking) 2023-12-28 17:16:12 +03:00
5eef1e13f5 Support pretty-printing matrices 2023-12-28 17:15:15 +03:00
5e61832a9b Add QR decomposition implementation & tests 2023-12-28 13:25:17 +03:00
94ea4cf932 Add noise-generator tool 2023-12-23 15:24:08 +03:00
a85e72a5b6 ECS destructors wip 2023-12-18 12:45:42 +03:00
028b4e1296 ECS constructors wip 2023-12-18 12:38:58 +03:00
0b1522722c Move ecs/entity_container.cpp -> ecs/container.cpp 2023-12-17 19:59:24 +03:00
bda4a156e7 Support registering components in ecs::container 2023-12-17 15:44:38 +03:00
3b5e649a31 Support cloning entities in ecs::container 2023-12-17 15:30:12 +03:00
9f86a8d71a Add some ecs todo's 2023-12-17 14:47:08 +03:00
5cb6421258 Clean up includes in libs/ecs 2023-12-17 12:53:28 +03:00
b3df337b8f Add ecs::system_set 2023-12-17 12:47:21 +03:00
340a5f4254 Support ecs::without 2023-12-16 23:01:06 +03:00
08b14ded93 Add static_assert to notify when a function is not invocable in ecs::container::apply & batch_apply 2023-12-16 21:55:59 +03:00
eb87f1ea20 Support const-qualified components in ecs::container::apply and add const-related docs 2023-12-16 21:51:45 +03:00
2a97a467aa Return query cache from ecs::container::apply and batch_apply 2023-12-16 21:33:13 +03:00
6a13a06187 Support const ecs::accessor and const-qualified component types in ecs::accessor::get 2023-12-16 20:06:40 +03:00
6833531edb More ecs::accessor docs 2023-12-16 19:58:17 +03:00
e1150a98fc Tidy up ecs library docs 2023-12-16 19:54:42 +03:00
c7cad03f0a Rename ecs::registration_token -> ecs::token 2023-12-16 18:53:50 +03:00
76e43590e9 Update ecs::container comments 2023-12-16 16:42:49 +03:00
fa87ab4425 Rename ecs::entity_container -> ecs::container 2023-12-16 16:35:04 +03:00
1a133f2d3e Rename ecs::entity_accessor -> ecs::accessor 2023-12-16 16:32:46 +03:00
95b26d890e Rename ecs::entity_handle -> ecs::handle 2023-12-16 16:29:54 +03:00
d48bbc91a6 Rename psemek_declare_uuid -> psemek_ecs_declare_uuid 2023-12-16 16:18:50 +03:00
bcae6dde4e Revert "Make util::assertion_handler return void"
This reverts commit ac0f45d9f0.
2023-12-04 18:33:00 +03:00
0b1bd8cf2f Remove libmpg & libpng from packaging 2023-12-04 18:15:02 +03:00
de9c695a7a Windows compilation fix 2023-12-04 18:14:46 +03:00
30ae586b98 Remove dependency on libpng 2023-12-04 18:01:37 +03:00
1b949c0b7f Remove old NetPBM & PNG code in favour of new stb_* implementations 2023-12-04 18:00:08 +03:00
1c54dd85a5 Support saving images via stb_image_write 2023-12-04 17:43:04 +03:00
6ff3351ab8 Compile stb_image as C++ and use proper assertion handler 2023-12-04 11:38:14 +03:00
ac0f45d9f0 Make util::assertion_handler return void 2023-12-04 11:37:44 +03:00
dca01b7cc4 Support reading arbitrary image formats via stb_image 2023-12-03 20:32:13 +03:00
a8fc4ea741 Add io::istream::finished() method 2023-12-03 20:31:48 +03:00
8038da6987 More window state logging in SDL2 backend 2023-12-03 18:28:54 +03:00
a8c89f84bd Support enabling windowed mode 2023-12-03 18:12:03 +03:00
323b264b41 Compilation fix 2023-11-03 12:59:37 +03:00
8d120dd407 Fix audio resampling in mp3 loading & in pitch effect: make the stream stop instead of infinitely looping the last resampled patch 2023-11-03 12:58:02 +03:00
e31132face Warn when the audio callback takes more than it should 2023-11-03 12:02:08 +03:00
352d03ed3f Support retrieving elapsed duration from a scoped profiler 2023-11-03 12:01:12 +03:00
de1a454079 Don't request alpha channel when loading monochrome PNG 2023-11-03 11:34:18 +03:00
f9dda53c61 Better idiv & imod implementation 2023-11-02 20:15:45 +03:00
1584301a21 Use std::ranges for cg::area 2023-11-02 20:15:08 +03:00
340a04d17c Fix loading paletted PNG without alpha channel 2023-11-02 13:03:22 +03:00
721c88fbee Change the way discrete GPU forcing works 2023-10-25 16:50:08 +03:00
eddda6d787 Log window resize events in sdl2 backend 2023-10-24 15:21:13 +03:00
f1c0959fba Improve audio duration api 2023-10-11 19:02:57 +03:00
5fa8e4a3a3 Mingw compilation fixes 2023-10-08 15:58:51 +03:00
f743201565 Remove useless copy when creating mp3 track from blob 2023-10-08 13:51:29 +03:00
03ae98d8a9 Audio::loop fixes 2023-10-06 18:57:34 +03:00
52b18c07a4 Fix echo effect 2023-10-05 17:12:20 +03:00
51a5bcd3bc Add all-pass echo filter 2023-10-04 14:34:25 +03:00
9e0babfd2c Add echo effect 2023-10-04 14:34:17 +03:00
76c91e50c1 New gcc compilation fixes 2023-10-03 21:32:13 +03:00
9a800b8f7a Implement first-order feedback audio filter 2023-10-03 14:32:31 +03:00
9cbb9127b9 Implement true audio::white_noise 2023-10-03 14:32:17 +03:00
831dd7b688 Don't use std::isspace in kerned font implementation 2023-10-02 23:26:04 +03:00
8be3544295 Fix creating audio track from util::blob 2023-10-02 17:25:51 +03:00
424ed06ecb Fix audio::concat 2023-10-02 17:25:35 +03:00
8c8ede7587 Add util::split(string, delim) 2023-10-01 01:19:31 +03:00
8a437086d6 Package with debug information 2023-09-29 17:25:48 +03:00
27a9c9ee7b Support access bits in gfx::buffer::map 2023-09-17 20:19:36 +03:00
6e366cea16 UI component factory base exception bugfix 2023-09-10 13:44:52 +03:00
85a6ade2cb More ECS API todo's 2023-08-26 22:53:00 +03:00
e16ebb8822 Add some ECS API todo's 2023-08-26 22:12:25 +03:00
85c0c56e03 Add static asserts in ecs::entity_component::apply/batch_apply to check that all component types are different 2023-08-26 22:11:55 +03:00
f46b3bdc40 Minor ecs::entity_container documentation fixes 2023-08-26 22:00:56 +03:00
7183a441e5 Fix ecs::entity_container::batch_apply for empty component types 2023-08-26 22:00:41 +03:00
bc63d98d51 Move ecs::entity_accessor to a separate header 2023-08-26 21:53:02 +03:00
8b1157c641 Implement ecs::entity_container::attach/detach 2023-08-26 18:24:10 +03:00
59c803d31c ECS library wip: rewrite tables using explicit columns 2023-08-26 12:33:14 +03:00
1d20bd5a17 Fix util::hash_table inserting non-const pair 2023-08-26 12:32:54 +03:00
24d1f1e5bf Add more hash_table tests 2023-08-24 18:50:48 +03:00
5a1db3097b Add util::hash_table::empty 2023-08-24 18:50:40 +03:00
d2604bc5ce Fix moving util::hash_table 2023-08-24 18:50:31 +03:00
31ffd4dc54 Remove component bitsets from ecs; use uuid hash tables instead 2023-08-24 17:33:19 +03:00
809a0ec212 Make util::hash_table search heterogeneous & add hash_table::contains 2023-08-24 17:32:57 +03:00
183644d46f Add proper util::hash_table move constructor & assignment 2023-08-24 17:32:33 +03:00
8adfe7320b Fix util::hash_table::find 2023-08-24 17:32:17 +03:00
1e74639ff4 Fix util::hash_table_iterator::advance 2023-08-24 17:31:50 +03:00
dbeee752b7 Speed up searching for ecs table columns using a hash_map<uuid, column_id> 2023-08-24 16:26:29 +03:00
a27378a3a7 Add util::hash_set/map with some tests 2023-08-24 16:24:47 +03:00
d3fa7fdfea Make ecs::entity_accessor::get throw the documented exception 2023-08-24 16:19:49 +03:00
6bab38a545 Better ecs::entity_container documentation 2023-08-24 10:48:57 +03:00
3701df15f4 Support creating new ecs entities while iterating over them 2023-08-23 17:46:50 +03:00
d7babe6a0f Fix destroying ecs tables 2023-08-23 17:00:42 +03:00
b99371dc29 Support removing ecs entities while iterating over them using apply 2023-08-23 16:10:39 +03:00
c0668e2de2 Bugfix in ecs entity creation in case a table with the same components but in a different order already exists 2023-08-23 14:47:26 +03:00
72508eb445 Support ecs::entity_container::batch_apply & store full entity handles (instead of entity IDs) in ECS tables 2023-08-23 12:58:00 +03:00
0b562a26c1 Add entity_container to ECS system arguments; make both entity_container and entity_handle optional arguments 2023-08-23 12:22:50 +03:00
0f621a9f3f Add ecs::entity_container::attach/detach declarations (no implementation yet) 2023-08-23 00:05:12 +03:00
fee1f647b2 Add declare_uuid macro to simplify creating ecs component classes 2023-08-23 00:04:41 +03:00
2d2362d85c More util::uuid functions: create from string, check for/convert to RFC 4122 2023-08-23 00:04:05 +03:00
8a7891d561 Make util::hash_combine and hash_sequence constexpr 2023-08-23 00:01:52 +03:00
9f36dfc0e4 Make compilation fail if ecs::entity_container::create is called with several equal component types 2023-08-22 23:24:49 +03:00
c59a28433a Better ecs::entity_accessor interface 2023-08-22 23:15:56 +03:00
82682c0317 Refactor ecs::entity_container & add documentation 2023-08-22 22:36:04 +03:00
c1991cbb57 ECS library wip: support explicit query cache 2023-08-22 21:18:53 +03:00
e0e0df8128 ECS library wip & tests 2023-08-22 20:30:04 +03:00
c6805dea21 Fix printing tests profiling data 2023-08-22 20:28:57 +03:00
535627f962 Make random::generator::min/max constexpr 2023-08-22 20:28:44 +03:00
d1975dd917 Add fibonacci music box example 2023-08-22 14:53:53 +03:00
63008d62ff ECS library wip 2023-08-22 14:53:03 +03:00
190fd5e51e Fix creating util::span<T const> from an array of non-const T 2023-08-22 14:52:51 +03:00
79e90fb03c Fix util::dynamic_bitset operations 2023-08-22 14:52:28 +03:00
c35478ac88 Fix util::dynamic_bitset hash 2023-08-22 14:52:16 +03:00
2667d1aadb New ECS library wip 2023-08-19 15:21:17 +03:00
981629cb74 Add util::dynamic_bitset 2023-08-19 15:21:08 +03:00
58215fedc1 Add util::unique_sequential_storage for storing an array of unique elements 2023-08-19 15:20:57 +03:00
f27a4fa26d Add util::uuid 2023-08-19 15:20:21 +03:00
16dcbe9603 Fix setting thread name on macos 2023-08-15 12:10:42 +03:00
1036ebae30 Fix sir::is_custom 2023-08-15 11:47:43 +03:00
67c7ce0878 Use concepts in geom::point and vector constructors 2023-08-15 11:47:20 +03:00
b467af74c9 Fix libbacktrace build on MacOS 2023-08-15 11:46:36 +03:00
8b01b6684c Fix building libbacktrace for windows 2023-08-07 00:23:16 +03:00
80c243ee2f Don't strip linux binary to preserve symbol names 2023-08-06 21:28:18 +03:00
3b00182f63 Fix weird stacktrace abi issue 2023-08-06 21:27:42 +03:00
5eb7cbff92 Better crash signal handling 2023-08-06 19:07:14 +03:00
f6377045c9 Add red color to stderr logging 2023-08-06 19:00:34 +03:00
04243db779 Synchronize stdout & stderr for logging 2023-08-06 18:44:35 +03:00
85d7a0ca33 Huge refactor: use util::exception everywhere instead of std exceptions 2023-08-06 18:33:07 +03:00
2bff6d6cf3 Fix building libbacktrace (still horrible though) 2023-08-06 18:32:18 +03:00
248bd049db Setup default logging sinks to output to stdout/stderr based on log level 2023-08-06 12:56:07 +03:00
128abc453e Add util::at helper 2023-08-06 12:55:31 +03:00
dd12ad9477 Add special handling for util::exception in various places to print stacktrace info 2023-08-06 12:53:03 +03:00
1c22892eec Add util::exception class that holds stacktrace information 2023-08-06 12:52:17 +03:00
56c14e6111 Add libbacktrace 3rdparty submodule 2023-08-06 12:51:42 +03:00
d13c097d61 Logging small signal handling refactor & concurrency fixes 2023-08-05 12:46:21 +03:00
95e99e4104 Support retrieving specific channels from gltf_animation 2023-08-02 19:18:35 +03:00
5630ae2da3 Fix gltf_animation for cubic interpolation when time is outside bounds 2023-08-02 19:18:20 +03:00
0019063c4e Fix flushing logs 2023-08-02 14:01:26 +03:00
64672d3f1f Parse metallic & roughness pbr parameters from glTF materials 2023-08-01 22:27:53 +03:00
564a0001a2 Support padding in instance attributes 2023-07-29 18:35:29 +03:00
644c9ca5b6 Add depth32f pixel 2023-07-22 20:04:06 +03:00
02ecca5ebc Add vecr::blur 2023-07-22 20:03:53 +03:00
4ce09fc6b0 Rename keycode::GRAVE to BACKQUOTE 2023-07-22 00:49:42 +03:00
de5c0a3371 Fix mapping gfx::buffer 2023-07-22 00:49:28 +03:00
0836cf00a6 Properly log graphics api name in gfx::init 2023-07-22 00:49:15 +03:00
d473ef3ea4 Delete obsolete examples & fix the rest to incorporate the new application API 2023-07-21 01:31:30 +03:00
7f5d50787d Make default_application_factory support a custom factory callback 2023-07-21 01:30:57 +03:00
6ee75f8bae Support command-line arguments in application context 2023-07-21 01:30:31 +03:00
bd826103d3 Exit if application is null in sdl2 backend 2023-07-21 01:30:12 +03:00
f5f094bdc6 Support keycode events in SDL2 backend 2023-07-21 01:29:36 +03:00
a4a07c0d29 Add keycode enum 2023-07-21 01:29:19 +03:00
810f64ad76 Refactor android backend JNI calls & fix application context 2023-07-20 11:11:02 +03:00
2e569b2cd3 Packaging fixes 2023-07-18 20:40:53 +03:00
b33ef168ea Add linux packaging 2023-07-18 20:23:34 +03:00
54b78e6c66 Android WIP: support OpenGL ES 3.2 and implement Android backend library 2023-07-18 15:43:14 +03:00
93d5c55c68 Add new resources system in app library 2023-07-18 15:34:46 +03:00
bf90a8edd9 Silense clang deprecated warning in unicode converters 2023-07-18 15:33:18 +03:00
e37bdf6bd2 Add touch events 2023-07-18 15:24:20 +03:00
a4e7b318ed Make application:🏭:options return a value instead of a reference 2023-07-18 15:24:02 +03:00
a1b00d4a70 Make linking psemek-backend optional 2023-07-14 22:28:52 +03:00
ceb69b9d62 Make SDL2 into an optional backend 2023-07-14 22:28:20 +03:00
b910d16261 Grand app refactor: move main to sdl2 lib, make the rest of the engine independent of SDL2 2023-07-14 22:25:45 +03:00
42f986ce4a Add util::executable_path() 2023-07-14 20:54:41 +03:00
ffe7afff1f Move broken ui_scene_legacy to ui_legacy library 2023-07-14 20:54:28 +03:00
9e65c02541 Make random::uniform_from work with raw arrays 2023-07-08 11:17:28 +03:00
2ea0427b0f Support converting util::blob to util::span explicitly 2023-07-06 22:26:11 +03:00
b9ed814cfe Support constructing span<T> from span<const T> 2023-07-06 22:25:55 +03:00
33488e67ca Fix gltf animation channel sampler 2023-07-06 19:25:46 +03:00
3570cb4fea Fix vecr::bbox in case of infinite bboxes to prevent NaN's 2023-06-21 13:17:33 +03:00
703ba37394 Add geom::isfinite for some primitive types 2023-06-21 13:17:09 +03:00
168cca63e3 Fix bbox(vecr::mirror) 2023-06-21 13:16:55 +03:00
698bd2a60b Fix handling primitive bbox in vecr::renderer 2023-06-21 13:06:05 +03:00
Jan Niklas Hasse
bc94fe256d Merged in Jan-Niklas-Hasse/fix-audiorecorders-storage-not-being-big-1686863243104 (pull request #1)
Fix audio::recorder's storage not being big enough in some cases causing invalid writes

Approved-by: Nikita Lisitsa
2023-06-21 09:41:57 +00:00
4befa1cad7 Optimize util::signal & support signal<Args...> 2023-06-20 02:11:49 +03:00
d7c8d8710c Fix vecr::renderer primitive bbox 2023-06-19 20:00:50 +03:00
1bd3f30be4 Support retrieving the raw canvas from vecr::renderer 2023-06-19 19:58:39 +03:00
f70cdf9d8e Optimize vecr::renderer by using primitive's bbox 2023-06-19 19:11:50 +03:00
Jan Niklas Hasse
ec18266d9e Fix audio::recorder's storage not being big enough in some cases causing invalid writes 2023-06-15 21:08:16 +00:00
2e2df09790 Add geom::concat 2023-06-12 14:35:39 +03:00
95dc09d7a3 Support retrieving the underlying pixmap from a texture atlas 2023-06-12 01:36:23 +03:00
18c49f7740 Add CPU vector graphics library 2023-06-06 13:22:00 +03:00
93e5691d8a Implement writing png pixmaps 2023-06-06 13:21:50 +03:00
769b5786ae Add util::array::subarray 2023-06-06 13:21:39 +03:00
e4fcf82f70 Support fmap over a tuple 2023-06-06 13:21:30 +03:00
48a85dfd66 Fix blending transparent colors 2023-06-06 13:21:04 +03:00
1770a83bdc Add any-type container to serialization streams 2023-05-29 16:38:28 +03:00
1d35ed2abb Add type-save any-type container 2023-05-29 16:38:16 +03:00
e80d831d10 Add util::animation_manager::clear 2023-05-29 13:56:11 +03:00
ab20daf5ce Add std::optional serialization 2023-05-29 13:05:19 +03:00
f824fd6612 Add random generator serialization 2023-05-29 13:04:58 +03:00
b5cea86b46 Add cyclic & dihedral group serialization 2023-05-29 13:04:43 +03:00
0791aa1096 Make sure util::atlas size is power-of-two 2023-05-25 01:55:33 +03:00
eb55ad5644 Texture atlas compilation fixes 2023-05-15 23:28:40 +03:00
30ed7a781e Add some juice to platformer example 2023-05-13 10:36:39 +03:00
1ef17e8918 Add platformer example 2023-05-13 00:04:41 +03:00
cc4b2c645b Replace float_t with float (like wtf) 2023-05-13 00:04:41 +03:00
933734bd2e New UI library wip 2023-05-06 12:55:06 +03:00
5895c01c4c Make util::not_implemented throw a specific exception type instead of runtime_error 2023-05-06 12:55:06 +03:00
6f6a263553 Add util::cyclic_iterator 2023-05-06 12:55:06 +03:00
aae4fa238f Add << operators for vectors & sets in tests 2023-05-06 12:55:06 +03:00
0675585a53 Audio stuff 2023-05-01 01:20:07 +03:00
4b223c5ed1 MacOS compilation fixes 2023-04-28 13:21:58 +03:00
dc6335a4ab Remove ui example 2023-04-27 01:17:03 +03:00
e8ea3e0fd4 New UI library automatic layout wip 2023-04-21 17:26:44 +03:00
2c13bcac15 Implement react::join 2023-04-21 17:26:30 +03:00
d2afa09adf Implement react::map(func, vector) 2023-04-21 17:26:19 +03:00
343cb394b5 Rename ui::grid_layout -> box_layout 2023-04-19 20:01:49 +03:00
cddc8a3235 New UI library wip: reconciliation fixes & tests 2023-04-19 19:01:24 +03:00
546db9fb98 Add util::type_name(std::type_index) 2023-04-19 19:01:06 +03:00
c3e8068668 Fix react::value initialization from react::source 2023-04-19 19:00:43 +03:00
a1f06a57ca Add more utilities to tests 2023-04-19 19:00:23 +03:00
327e4b9744 Remove unused ui/reconciliator.cpp 2023-04-18 16:01:38 +03:00
aeec694443 Fixes in test runner 2023-04-18 16:00:23 +03:00
1246985763 New UI library wip 2023-04-18 15:30:38 +03:00
e21692743c Support creating react::source with an immediate subscriber 2023-04-18 15:30:30 +03:00
9f0179a86a Fix typo in util::signal::subscription_token 2023-04-18 15:29:49 +03:00
b0814989e1 Add CMake option to use the legacy UI 2023-04-18 15:29:22 +03:00
53bfbb0cd0 Move fonts code to a separate library 2023-04-18 15:28:52 +03:00
b21332b9a4 Update app::ui_scene to use the legacy ui library 2023-04-18 15:28:10 +03:00
4b59aa619d Remove unused tasks library 2023-04-17 13:12:19 +03:00
3c4ccbbf54 Move ui library to ui_legacy 2023-04-17 13:12:04 +03:00
df5e188236 Improve util::dfs::cycle_iterator 2023-04-15 13:16:09 +03:00
3bfe867cc0 Allow random::weighted_distribution to release it's weights array 2023-04-02 11:56:02 +03:00
7ae3bbfe96 Support vertex colors in glTF parser 2023-03-30 20:46:31 +03:00
7ad2c92d31 Parse blender extras in glTF 2023-03-30 10:48:54 +03:00
21e35a988a Parse emission texture & KHR_materials_emissive_strength from glTF 2023-03-29 23:53:09 +03:00
fcd86fb359 Fix cubic glTF animation 2023-03-27 11:32:21 +03:00
063e8e43ba glTF animations 2023-03-26 23:51:51 +03:00
c26f626baf GCC 12 compilation fixes 2023-03-26 23:51:40 +03:00
1561162cf8 Parse glTF joints & weights attributes 2023-03-26 00:12:20 +03:00
0c2c950a8c Fix default scale when parsing glTF 2023-03-26 00:12:09 +03:00
d930ef6d93 Parse skins & animations in gltf parser 2023-03-25 19:50:32 +03:00
3bb0b53daf Support doubleSided property of gltf materials 2023-03-25 11:34:29 +03:00
0b0be3539f Include mipmaps in texture memory usage 2023-03-20 13:57:31 +03:00
d824159c2f Fix random::uniform() for integral types 2023-03-20 13:04:53 +03:00
d68242d59d Support material texture in gltf parser 2023-03-18 17:36:52 +03:00
98300b2f64 Clang compilation fix 2023-03-17 15:15:11 +03:00
0633aacc63 Add group value iterators operator- 2023-03-16 20:09:32 +03:00
ef6ef081d8 Support KHR_lights_punctual in gltf parser 2023-03-15 15:57:12 +03:00
b52039d47c React library wip 2023-03-14 12:48:25 +03:00
cac70befe5 Make geom::swizzle work for boxes 2023-03-09 11:37:54 +03:00
23f818078f Fix gfx::memory_usage(texture) 2023-03-08 22:24:49 +03:00
c639e533fe Add some util::range helper methods 2023-03-08 22:24:38 +03:00
d27d4cca17 Add util::make_span(initializer_list) 2023-03-06 08:58:33 +03:00
20574e23e3 Add gfx::draw_buffer 2023-03-04 10:58:26 +03:00
73711b8ffa Fix geom::inverse(affine_transform) 2023-03-03 15:34:38 +03:00
cc7135a0f4 Warn about unknown pixel format instead of throwing 2023-02-28 01:36:17 +03:00
ac436cb155 Add geom::simplex hash 2023-02-28 01:35:25 +03:00
8b456ce9e9 Bind gfx::buffer when attaching it to buffer texture (otherwise it doesn't work if the buffer wasn't bound at least once yet) 2023-02-27 12:42:41 +03:00
32da717a55 Add float -> normalized (un)signed 8/16-bit converters to gfx 2023-02-26 23:46:06 +03:00
696ed71090 Add gfx memory usage utilities 2023-02-26 20:44:29 +03:00
6d5a01921c Remember memory size in gfx::buffer 2023-02-26 20:08:59 +03:00
11af452b16 Make util enum values_range constexpr 2023-02-25 12:42:36 +03:00
84b804adf7 Add geom::gradient default constructor 2023-02-25 12:42:15 +03:00
d90317576c Add exponential random distribution 2023-02-24 22:57:03 +03:00
9b304ead00 Add reactive expressions library 2023-02-24 15:01:53 +03:00
15b08aeb47 Add a simple signal implementation to util 2023-02-24 15:01:42 +03:00
aa61c63609 Turn util::to_string into a callable object 2023-02-24 15:01:27 +03:00
10ddf4933b Fix bug in geom::cubic_hermite 2023-02-14 23:48:17 +03:00
43e95e89f3 Add cubic hermite spline to geom 2023-02-14 13:50:43 +03:00
4cb86d9314 Return finished animations from util::animation_manager 2023-02-14 13:50:32 +03:00
ab13864021 Add node & material index to gltf_asset 2023-02-13 21:46:59 +03:00
5f63fb76f5 Add stride information to gltf parser 2023-02-13 18:04:08 +03:00
b7252f8746 Add a simple animation manager to util 2023-02-13 18:03:56 +03:00
0979b55f72 Throw std::system_error if a file could not be opened in io::file_stream 2023-02-12 11:41:02 +03:00
1a06f5071d Remove obsolete gfx::mesh::count method 2023-02-12 11:40:38 +03:00
5e6cd060d7 Make io::read_full return util::blob 2023-02-10 21:15:19 +03:00
40c62d621a Add util::dfs 2023-02-09 15:23:05 +03:00
e304b09f9f Fix random::uniform_from to work with rvalue containers and return types 2023-02-08 23:56:23 +03:00
1f1612f7e6 Add cg::bbox(container) 2023-02-08 23:56:04 +03:00
519c487c4d Add ui::font::baseline method 2023-02-07 17:01:25 +03:00
417c6f0080 Support more PNG color types 2023-02-05 00:33:53 +03:00
ed1764ffea Don't rely on [] operator in random::uniform_from 2023-02-05 00:33:44 +03:00
7c20a91546 Make kerned_font shape glyphs along the Y=0 baseline 2023-02-04 18:59:45 +03:00
a2922708e8 Support floating-point glyph coordinates 2023-02-04 18:21:43 +03:00
1582b341d3 Support child nodes & node transforms in gltf parser 2023-02-01 20:35:32 +03:00
0e84751ac9 Make gfx::gltf_mesh return empty primitive span if mesh wasn't found 2023-01-25 13:01:03 +03:00
ce1809e50d Support material names & emission in gltf parser 2023-01-24 12:01:51 +03:00
5ce8647b3d Fix cg::separation for 2D bodies 2023-01-23 19:07:33 +03:00
fdcf0d5b88 MacOS fixes 2023-01-16 23:53:34 +03:00
8e286a2cc0 Load gltf textures as sRGB 2023-01-15 22:57:14 +03:00
f6d9b14f38 Add group -> matrix converter 2023-01-15 22:42:24 +03:00
4c5d8777d6 Add tiny groups library with tests 2023-01-15 22:42:14 +03:00
3ed7f142d7 Comment fix 2023-01-15 22:41:51 +03:00
e83df0d965 Fix util::make_pathfinder 2023-01-15 22:41:29 +03:00
63f576aee5 Add gfx::blend for colors 2023-01-15 14:23:06 +03:00
441db52e71 Add gfx::lerp for float colors 2023-01-15 14:22:55 +03:00
437714288b Refactor gltf_mesh 2023-01-14 03:35:12 +03:00
514f4006ef Parse textures in gltf_parser 2023-01-14 03:35:03 +03:00
f3700dc4b8 Update todo 2023-01-12 16:07:47 +03:00
e51fc4029d Add midi note to frequency converter 2023-01-12 15:18:16 +03:00
292ccadc7e Add audio::mix helper function 2023-01-11 15:23:34 +03:00
5e93208fcb Add audio::record helper function 2023-01-11 15:23:22 +03:00
a279fbfcb8 Make audio::fade_out return true length 2023-01-11 15:23:04 +03:00
104d4a92a0 Support frequency resampling for wav audio 2023-01-10 14:52:43 +03:00
56505ebe85 Fix obsolete include 2023-01-10 14:46:27 +03:00
3b5ad89ba0 Audio recorder & duplicator refactor 2023-01-10 14:41:51 +03:00
cbd5a15ce8 Implement wav audio::track using raw track 2023-01-10 14:41:31 +03:00
a67e00d601 Implement raw audio::track from scratch, without dependence on recoder & duplicator 2023-01-10 14:41:12 +03:00
633e366650 Fix audio::mixer 2023-01-10 14:40:43 +03:00
4db40a0276 Audio refactor: move some stuff to detail and combine subdirs 2023-01-10 03:46:09 +03:00
2209648999 Audio library refactor: use spans for stream->read 2023-01-10 03:35:34 +03:00
7376109e96 util::span interface improvements 2023-01-10 03:35:21 +03:00
148efb02cc Fix ecs tests 2023-01-09 20:49:56 +03:00
df9d40540b Fix tests logging 2023-01-09 20:49:50 +03:00
f9e8b76029 Fix gfx rapidjson dependency 2023-01-08 17:56:31 +03:00
c980e72e14 Make gfx::gltf_mesh::mesh::primitive::draw const 2023-01-07 02:46:40 +03:00
13590ff9e5 Add gltf asset parser & mesh generator 2023-01-07 02:26:26 +03:00
e19b515404 Make psemek_package_files respect relative directories 2023-01-07 02:26:16 +03:00
630ae28301 Add simple 1st order feedforward filters 2023-01-06 20:09:43 +03:00
ef95af60ae Support creating a loop directly from a track 2023-01-06 20:09:21 +03:00
0d23d8bcab Add audio white noise generator 2023-01-06 19:32:34 +03:00
3c22074f8d Implement audio::concat effect 2023-01-06 19:15:48 +03:00
098c3e84b1 Add common directories to file dialog 2023-01-05 19:30:00 +03:00
9a5206ebf9 Fix file dialog on windows 2023-01-05 19:24:24 +03:00
b9939ba10e Show full text in tagged_text parse error 2023-01-05 19:12:35 +03:00
2906e31634 Fix scroller position after reshape 2023-01-05 18:38:47 +03:00
3f5735e10f Don't send mouse events to hidden scroller children 2023-01-05 18:35:39 +03:00
0b358f6877 Support label on link mouseover callback 2023-01-05 18:18:11 +03:00
dd38061d1c Send initial scene resize after the update and before the rendering 2023-01-05 16:42:58 +03:00
1241fdff9c Make path canonical before using it in file dialog 2023-01-05 16:14:59 +03:00
bf3f6f61d1 Remove non-implemented file dialog common paths 2023-01-05 16:12:00 +03:00
ea726c7a93 Better file dialog confirm button behavior 2023-01-05 16:10:50 +03:00
1b8339ed5d Escape paths in file dialog 2023-01-05 16:08:47 +03:00
361ef9fd7a Add default scroller implementation 2023-01-05 04:54:01 +03:00
874d4bd5c8 Huge ui::scroller changes: support asymmetric shaping, remove sticking feature 2023-01-05 02:40:47 +03:00
c928b88062 Try to fix label cursor issue 2023-01-04 21:47:31 +03:00
cdbe162521 Add default toggle button implementation 2023-01-04 21:14:14 +03:00
42e1136123 Remove sdl2-mixer dependency 2023-01-04 18:11:38 +03:00
9e5b6997f6 Add a CMake function for adding extra files to packaging 2023-01-04 18:01:06 +03:00
594dcc9230 Hide file dialog confirm button when appropriate 2023-01-04 17:09:31 +03:00
d60eb6f659 Flush log on signal 2023-01-04 16:06:34 +03:00
267304ce6a Fix file dialog rewriting selected files (facepalm) 2023-01-04 03:10:23 +03:00
0a64bd0cbb Sort entries in file dialog 2023-01-04 01:44:37 +03:00
b1063f83d9 Add file icon to file dialog 2023-01-04 01:39:37 +03:00
f31e97edde Add a file dialog implementation 2023-01-03 23:31:45 +03:00
9481997034 Support retrieving ui:🪟:on_close callback & explicitly closing a window 2023-01-03 23:31:36 +03:00
9c70c8e19e Add util::range deduction guides 2023-01-02 23:07:20 +03:00
c43aa72334 Another selector spawning lifetime fix 2023-01-01 23:53:54 +03:00
c0c659ee61 Fix spawned selector lifetime 2023-01-01 21:54:49 +03:00
8425900fe0 Implement synchronous implementation of async::executor 2022-12-26 14:06:39 +03:00
9600bd96d5 Add async::threadpool::thread_count() 2022-12-26 14:06:23 +03:00
5a0469364f Add geom::contains(box, box) 2022-12-24 23:53:46 +03:00
ee9847a904 Fix cg::delaunay for some weird circular cases 2022-12-23 13:26:41 +03:00
eb33aa3b91 Fix handling degenerate states in cg::triangulation 2022-12-23 10:02:06 +03:00
e1d4608e3f Highlight the option that owns the active submenu in ui::selector 2022-12-23 10:01:24 +03:00
d6f53ed76f Make default ui::selector use fg_color instead of bg_color 2022-12-23 09:59:31 +03:00
64425e4cc9 Implement adding windows resource file for a target 2022-12-22 21:50:25 +03:00
2349882abc Fix updating apt-get in Dockerfiles 2022-12-22 21:49:53 +03:00
b247566b09 Fix used docker image for packaging 2022-12-22 21:48:51 +03:00
bd9e148d9b Fix extra semicolon 2022-12-22 21:48:32 +03:00
a63bc2eb01 Add submenu arrows to default ui::selector 2022-12-22 16:33:46 +03:00
72cbec959d Support ui::button::on_release callback 2022-12-22 16:06:02 +03:00
d692256580 Support alpha-channel in ui::color_picker 2022-12-20 18:19:14 +03:00
17bb412140 Draw checkers for transparent color in ui::color_view 2022-12-20 18:19:05 +03:00
8e0c27c8c0 Add ui::color_picker composite element 2022-12-20 17:12:18 +03:00
5af0c66599 Add simple ui::color_view element 2022-12-20 17:12:06 +03:00
932e64a991 Fix default ui::button child & size constraints handling 2022-12-20 17:11:54 +03:00
5bf544d032 Add generic ui::spawn 2022-12-20 17:11:23 +03:00
183e5bb482 Rename ui::spawn -> ui::spawn_selector 2022-12-20 17:11:14 +03:00
79eceaa616 Fix selectors lifetime issues 2022-12-19 21:17:22 +03:00
6fe5d72415 Fix ui::grid_layout::set_size element parenting 2022-12-19 20:35:12 +03:00
cf13025cc6 Support update callback in ui::event_interceptor 2022-12-19 12:29:22 +03:00
c1699fe882 Add default image provider 2022-12-19 09:52:02 +03:00
852a170c97 Add default ui::edit implementation 2022-12-18 21:55:30 +03:00
9ab72a7c1a Fix default ui::window impl event handling 2022-12-18 21:55:23 +03:00
1c9fb28ebf Implement shadows in default selector impl 2022-12-18 19:22:55 +03:00
f42b91a80e Another selector callback fix 2022-12-18 19:15:24 +03:00
e28b7a8e67 Fix rapidjson dependence 2022-12-18 18:35:13 +03:00
a1307b84b9 Support changing ui::default_element_factory close icon 2022-12-18 18:17:40 +03:00
3a93ddce65 Fix selector callback patching 2022-12-18 18:17:00 +03:00
d5fdb2a7c5 Support changing ui::label text_style without tagged text 2022-12-18 18:15:14 +03:00
7d55b374e4 Add util::open_url 2022-12-18 18:14:43 +03:00
3d2c03c1b4 Implement default ui::selector 2022-12-18 16:42:39 +03:00
83cb71d2c0 Implement width/height_constraints for default ui::frame implementation 2022-12-18 16:42:27 +03:00
83559aba51 Fix ui::label line height calculation 2022-12-18 16:41:59 +03:00
a7d331544c Implement ui::event_interceptor::width/height_constraints 2022-12-18 16:41:44 +03:00
b00e59ab08 Add rapidjson, msdf & bitmap fonts support and bmfont parser 2022-12-18 14:43:57 +03:00
87c612a0eb Add 'size' cursor type 2022-12-17 15:18:32 +03:00
5f7da5ddc5 Fix label tagged text error reporting 2022-12-16 16:28:24 +03:00
e959c0ec1d Add gfx::buffer::load_subdata 2022-12-16 15:48:17 +03:00
1198653891 Tweak polygon ear clipping to behave well with duplicate vertices 2022-12-16 01:55:21 +03:00
3f382651c4 Add geom::polygon_distance 2022-12-15 20:11:06 +03:00
d01a8186d8 Add geom::polygon_contains 2022-12-15 20:10:57 +03:00
9f045c18a8 Remove app::on_quit(), use app::stop() instead 2022-12-13 22:40:22 +03:00
30c35fd768 Move electron crystal example to separate project 2022-12-08 19:30:07 +03:00
592fb437e7 Electron crystal simulation computation time measurement 2022-12-08 18:00:01 +03:00
8aec37d1de Electron crystal simulation UI tweaks 2022-12-08 17:54:08 +03:00
11e46ba1c6 Support retrieving caption label from ui::window 2022-12-08 17:53:55 +03:00
0434a7d904 Implement Barnes-Hut algorithm for electron crystal simulation 2022-12-08 17:41:48 +03:00
f006ecb364 Add electron crystal simulation 2022-12-08 16:46:23 +03:00
b0bd606727 More gravity experiments 2022-12-08 14:46:54 +03:00
735 changed files with 60053 additions and 9001 deletions

6
.gitmodules vendored Normal file
View file

@ -0,0 +1,6 @@
[submodule "3rdparty/rapidjson"]
path = 3rdparty/rapidjson
url = https://github.com/Tencent/rapidjson.git
[submodule "3rdparty/libbacktrace"]
path = 3rdparty/libbacktrace
url = https://github.com/ianlancetaylor/libbacktrace.git

32
3rdparty/CMakeLists.txt vendored Normal file
View file

@ -0,0 +1,32 @@
file(GLOB_RECURSE RAPIDJSON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/rapidjson/include/*")
add_library(rapidjson INTERFACE EXCLUDE_FROM_ALL "${RAPIDJSON_SOURCES}")
target_include_directories(rapidjson INTERFACE "${CMAKE_CURRENT_LIST_DIR}/rapidjson/include")
if(PSEMEK_STACKTRACE)
set(LIBBACKTRACE_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/libbacktrace")
set(LIBBACKTRACE_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/libbacktrace")
set(LIBBACKTRACE_INCLUDE_DIR "${LIBBACKTRACE_BUILD_DIR}/include")
set(LIBBACKTRACE_LIBRARY "${LIBBACKTRACE_BUILD_DIR}/.libs/libbacktrace.a")
if(NOT EXISTS "${LIBBACKTRACE_BUILD_DIR}")
file(COPY "${LIBBACKTRACE_SOURCE_DIR}" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
make_directory("${LIBBACKTRACE_INCLUDE_DIR}")
file(COPY "${LIBBACKTRACE_SOURCE_DIR}/backtrace.h" DESTINATION "${LIBBACKTRACE_INCLUDE_DIR}")
if(PSEMEK_PACKAGE_TARGET AND WIN32)
execute_process(COMMAND "./configure" "--host=x86_64-w64-mingw32" "CC=${CMAKE_C_COMPILER}" WORKING_DIRECTORY "${LIBBACKTRACE_BUILD_DIR}" COMMAND_ECHO STDOUT)
elseif(PSEMEK_PACKAGE_TARGET AND ANDROID)
execute_process(COMMAND "./configure" "--host=aarch64-none-linux-android34" "CC=${CMAKE_C_COMPILER}" "CFLAGS=--target=aarch64-none-linux-android34" WORKING_DIRECTORY "${LIBBACKTRACE_BUILD_DIR}" COMMAND_ECHO STDOUT)
else()
execute_process(COMMAND "./configure" "CC=${CMAKE_C_COMPILER}" WORKING_DIRECTORY "${LIBBACKTRACE_BUILD_DIR}" COMMAND_ECHO STDOUT)
endif()
execute_process(COMMAND "make" WORKING_DIRECTORY "${LIBBACKTRACE_BUILD_DIR}" RESULT_VARIABLE LIBBACKTRACE_BUILD_RESULT COMMAND_ECHO STDOUT)
if(NOT (${LIBBACKTRACE_BUILD_RESULT} EQUAL 0))
message(FATAL_ERROR "libbacktrace build failed")
endif()
endif()
add_library(libbacktrace INTERFACE)
target_include_directories(libbacktrace INTERFACE "${LIBBACKTRACE_INCLUDE_DIR}")
target_link_libraries(libbacktrace INTERFACE "${LIBBACKTRACE_LIBRARY}")
endif()

1
3rdparty/libbacktrace vendored Submodule

@ -0,0 +1 @@
Subproject commit 9ae4f4ae4481b1e69d38ed810980d33103544613

1
3rdparty/rapidjson vendored Submodule

@ -0,0 +1 @@
Subproject commit 80b6d1c83402a5785c486603c5611923159d0894

View file

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.8)
cmake_minimum_required(VERSION 3.10)
project(psemek)
cmake_policy(SET CMP0077 NEW)
@ -18,6 +18,11 @@ if(PSEMEK_ROBUST_PREDICATES)
list(APPEND PSEMEK_DEFINITIONS "-DPSEMEK_ROBUST_PREDICATES=1")
endif()
option(PSEMEK_USE_FREETYPE "Include Freetype fonts support" OFF)
if(PSEMEK_USE_FREETYPE)
list(APPEND PSEMEK_DEFINITIONS "-DPSEMEK_USE_FREETYPE=1")
endif()
set(PSEMEK_CXX_FLAGS)
if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") OR (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang"))
list(APPEND PSEMEK_CXX_FLAGS -Wall -Werror -Wextra -pedantic -Wno-narrowing -Wno-sign-compare)
@ -25,27 +30,59 @@ endif()
if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU"))
# gcc gives way too many false positives on -Wmaybe-uninitialized
list(APPEND PSEMEK_CXX_FLAGS -Wno-maybe-uninitialized)
if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 13)
# gcc-13 gives false positive -Wdangling-reference in ecs::accessor::get()
list(APPEND PSEMEK_CXX_FLAGS -Wno-dangling-reference)
endif()
endif()
if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang"))
# -Wdtor-name gives false-positive on audio::engine::impl::~impl
list(APPEND PSEMEK_CXX_FLAGS -Wno-dtor-name)
endif()
option(PSEMEK_ASAN "Turn on Address Sanitizer" OFF)
if(PSEMEK_ASAN)
list(APPEND CMAKE_CXX_FLAGS -fsanitize=address)
set(PSEMEK_BACKEND "SDL2" CACHE STRING "Application backend (OFF/SDL2/ANDROID)")
option(PSEMEK_LEGACY_UI "Use legacy UI library" OFF)
message(STATUS "Using backend ${PSEMEK_BACKEND}")
if(PSEMEK_BACKEND STREQUAL "ANDROID")
set(PSEMEK_GL_API gles32)
set(PSEMEK_GL_LIBRARIES GLESv3 EGL)
set(PSEMEK_PACKAGE_AS_LIBRARY ON CACHE INTERNAL "Build applications as shared libraries")
list(APPEND PSEMEK_CXX_FLAGS -fPIC)
else()
find_package(Threads REQUIRED)
set(OpenGL_GL_PREFERENCE LEGACY)
find_package(OpenGL REQUIRED)
set(PSEMEK_GL_API gl33)
set(PSEMEK_GL_LIBRARIES OpenGL::GL)
set(PSEMEK_PACKAGE_AS_LIBRARY OFF CACHE INTERNAL "Build applications as shared libraries")
endif()
option(PSEMEK_UBSAN "Turn on UB Sanitizer" OFF)
if(PSEMEK_UBSAN)
list(APPEND CMAKE_CXX_FLAGS -fsanitize=undefined)
if(NOT DEFINED PSEMEK_GRAPHICS_API)
set(PSEMEK_GRAPHICS_API OPENGL)
endif()
if(PSEMEK_GRAPHICS_API STREQUAL WEBGPU)
find_package(wgpu-native 27.0.2.0 REQUIRED)
endif()
message(STATUS "Using graphics API ${PSEMEK_GRAPHICS_API}")
list(APPEND PSEMEK_DEFINITIONS "-DPSEMEK_GRAPHICS_API_${PSEMEK_GRAPHICS_API}=1")
option(PSEMEK_STACKTRACE "Use Boost.Stacktrace for stacktraces in exceptions" ON)
if(PSEMEK_STACKTRACE)
list(APPEND PSEMEK_DEFINITIONS "-DPSEMEK_STACKTRACE=1")
endif()
add_subdirectory(3rdparty)
get_directory_property(PSEMEK_PARENT_DIRECTORY PARENT_DIRECTORY)
add_subdirectory(package)
if(PSEMEK_PACKAGE_MODE)
if(PSEMEK_PACKAGE_MODE AND PSEMEK_PACKAGE_TARGET AND NOT PSEMEK_PACKAGE_AS_LIBRARY)
set(Boost_USE_STATIC_LIBS ON)
endif()

29
LICENSE.txt Normal file
View file

@ -0,0 +1,29 @@
MIT NON-AI NON-NFT License
Copyright (c) 2025, Nikita Lisitsa
Permission is hereby granted, free of charge, to any person obtaining a copy of the software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
In addition, the following restrictions apply:
1. The Software, any modifications made to it, any artifacts or media generated by it, and any parts thereof, including but not limited to images,
video, and code snippets, may not be used for the purpose of training or improving machine learning algorithms, or be included in any dataset used
for training or improving machine learning algorithms, including but not limited to artificial intelligence, natural language processing, large
language models, or data mining. This condition applies to any derivatives, modifications, or updates based on the Software code. Any usage of the
Software in an AI-training dataset is considered a breach of this License.
2. The Software, any modifications made to it, any artifacts or media generated by it, and any parts thereof, including but not limited to images,
video, and code snippets, may not be owned via a blockchain-backed ownership token, or as a means of generating blockchain-backed ownership tokens,
including but not limited to non-fungible tokens. Any usage of the Software to generate non-fungible tokens is considered a breach of this License.
3. Any person or organization found to be in violation of these restrictions will be subject to legal action and may be held liable for any damages
resulting from such use.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,29 @@
if(wgpu-native_FOUND)
set(wgpu-native_FIND_QUIETLY TRUE)
endif()
# Don't search for include files - these are bundled with psemek-wgpu lib
find_library(wgpu-native_LIBRARY NAMES libwgpu_native.a wgpu_native.dll wgpu_native PATHS "${WGPU_NATIVE_ROOT}" "${WGPU_NATIVE_ROOT}/lib")
find_file(wgpu-native_VERSION_FILE wgpu-native-git-tag PATHS "${WGPU_NATIVE_ROOT}/wgpu-native-meta")
if(EXISTS "${wgpu-native_VERSION_FILE}")
file(READ "${wgpu-native_VERSION_FILE}" wgpu-native_VERSION_NOT_STRIPPED)
string(STRIP "${wgpu-native_VERSION_NOT_STRIPPED}" wgpu-native_VERSION_STRIPPED)
string(SUBSTRING "${wgpu-native_VERSION_STRIPPED}" 1 -1 wgpu-native_VERSION)
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(wgpu-native
REQUIRED_VARS wgpu-native_LIBRARY wgpu-native_VERSION_FILE
VERSION_VAR wgpu-native_VERSION
)
if(wgpu-native_FOUND AND NOT TARGET wgpu-native)
set(wgpu-native_LIBRARIES ${wgpu-native_LIBRARY})
add_library(wgpu-native STATIC IMPORTED)
set_target_properties(wgpu-native PROPERTIES
IMPORTED_LOCATION "${wgpu-native_LIBRARIES}"
)
endif()
mark_as_advanced(wgpu-native_LIBRARY wgpu-native_VERSION_FILE)

View file

@ -2,12 +2,17 @@ find_package(ZLIB REQUIRED)
file(GLOB PSEMEK_EXAMPLES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
find_package(OpenMP)
foreach(example ${PSEMEK_EXAMPLES})
get_filename_component(TARGET_NAME "${example}" NAME_WLE)
set(TARGET_NAME psemek-example-${TARGET_NAME})
psemek_add_executable(${TARGET_NAME} ${example})
psemek_add_application(${TARGET_NAME} ${example})
if(TARGET ${TARGET_NAME})
target_link_libraries(${TARGET_NAME} PUBLIC psemek ZLIB::ZLIB)
if(OpenMP_CXX_FOUND)
target_link_libraries(${TARGET_NAME} PUBLIC OpenMP::OpenMP_CXX)
endif()
target_compile_definitions(${TARGET_NAME} PUBLIC -DPSEMEK_EXAMPLES_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
endif()
endforeach()

View file

@ -1,14 +1,14 @@
#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/geom/camera.hpp>
#include <psemek/geom/gauss.hpp>
#include <psemek/geom/distance.hpp>
#include <psemek/geom/interval.hpp>
#include <psemek/geom/math.hpp>
#include <psemek/geom/rotation.hpp>
#include <psemek/math/camera.hpp>
#include <psemek/math/gauss.hpp>
#include <psemek/math/distance.hpp>
#include <psemek/math/interval.hpp>
#include <psemek/math/math.hpp>
#include <psemek/math/rotation.hpp>
#include <psemek/random/device.hpp>
#include <psemek/random/generator.hpp>
@ -35,9 +35,9 @@ namespace
struct bone
{
geom::point<float, 2> position;
geom::vector<float, 2> direction;
geom::vector<float, 2> velocity;
math::point<float, 2> position;
math::vector<float, 2> direction;
math::vector<float, 2> velocity;
float angular_velocity;
float length;
float width;
@ -51,16 +51,16 @@ struct joint
float s0, s1;
// angle range max should be in [0, 2*pi]
// angle range min should be less or equal to angle range max
std::optional<geom::interval<float>> angle_range = std::nullopt;
std::optional<math::interval<float>> angle_range = std::nullopt;
};
template <std::size_t Bones, std::size_t Constraints>
struct constraint
{
std::size_t bone[Bones];
geom::matrix<float, Constraints, 4 * Bones> jacobian;
geom::vector<float, Constraints> bias;
geom::interval<float> range = geom::interval<float>::full();
math::matrix<float, Constraints, 4 * Bones> jacobian;
math::vector<float, Constraints> bias;
math::interval<float> range = math::interval<float>::full();
};
struct system
@ -74,7 +74,7 @@ struct system
{
std::size_t index;
float pos;
geom::vector<float, 2> delta;
math::vector<float, 2> delta;
};
void advance(float time, std::optional<selection> sel, util::function<std::vector<float>(system const &)> torque_provider);
@ -105,7 +105,7 @@ void system::step(std::optional<selection> sel, util::function<std::vector<float
if (old_torque_.size() != joints.size())
old_torque_.assign(joints.size(), 0.f);
geom::vector<float, 2> const gravity { 0.f, -10.f };
math::vector<float, 2> const gravity { 0.f, -10.f };
for (std::size_t i = 0; i < bones.size(); ++i)
{
@ -138,7 +138,7 @@ void system::step(std::optional<selection> sel, util::function<std::vector<float
auto & b = bones[i];
b.position += b.velocity * dt;
b.direction = geom::normalized(b.direction + geom::ort(b.direction) * b.angular_velocity * dt);
b.direction = math::normalized(b.direction + math::ort(b.direction) * b.angular_velocity * dt);
}
std::vector<constraint<1, 1>> constraints_1_1;
@ -175,7 +175,7 @@ void system::step(std::optional<selection> sel, util::function<std::vector<float
float const bounce = 0.05f;
float const friction = 0.05f;
auto push_1 = [&](float depth, float s, geom::vector<float, 2> const & v){
auto push_1 = [&](float depth, float s, math::vector<float, 2> const & v){
constraint<1, 1> & c = constraints_1_1.emplace_back();
c.bone[0] = i;
@ -209,8 +209,8 @@ void system::step(std::optional<selection> sel, util::function<std::vector<float
auto p0 = b.position - b.direction * b.length / 2.f;
auto p1 = b.position + b.direction * b.length / 2.f;
auto v0 = b.velocity - geom::ort(b.direction) * b.angular_velocity * b.length / 2.f;
auto v1 = b.velocity + geom::ort(b.direction) * b.angular_velocity * b.length / 2.f;
auto v0 = b.velocity - math::ort(b.direction) * b.angular_velocity * b.length / 2.f;
auto v1 = b.velocity + math::ort(b.direction) * b.angular_velocity * b.length / 2.f;
if (p0[1] < b.width / 2.f)
{
@ -303,8 +303,8 @@ void system::step(std::optional<selection> sel, util::function<std::vector<float
if (j.angle_range)
{
float x = geom::dot(b0.direction, b1.direction);
float y = geom::det(b0.direction, b1.direction);
float x = math::dot(b0.direction, b1.direction);
float y = math::det(b0.direction, b1.direction);
// a \in [-pi, pi]
float a = std::atan2(y, x);
@ -314,10 +314,10 @@ void system::step(std::optional<selection> sel, util::function<std::vector<float
// j.angle_range->center() \in [-pi, 2*pi]
// delta \in [-3*pi, 2*pi]
float delta = a - j.angle_range->center();
if (delta > geom::pi)
delta -= 2.f * geom::pi;
if (delta < -geom::pi)
delta += 2.f * geom::pi;
if (delta > math::pi)
delta -= 2.f * math::pi;
if (delta < -math::pi)
delta += 2.f * math::pi;
// delta in [-pi, pi]
float const half_length = j.angle_range->length() / 2.f;
@ -380,7 +380,7 @@ void system::step(std::optional<selection> sel, util::function<std::vector<float
template <std::size_t B, std::size_t C>
void system::solve_constraint(constraint<B, C> const & c)
{
geom::vector<float, 4 * B> v;
math::vector<float, 4 * B> v;
auto j_inv_mass = c.jacobian;
for (std::size_t i = 0; i < B; ++i)
@ -401,11 +401,11 @@ void system::solve_constraint(constraint<B, C> const & c)
}
}
geom::vector<float, C> l = *geom::solve(j_inv_mass * geom::transpose(c.jacobian), - c.jacobian * v - c.bias);
math::vector<float, C> l = *math::solve(j_inv_mass * math::transpose(c.jacobian), - c.jacobian * v - c.bias);
for (std::size_t j = 0; j < C; ++j)
l[j] = geom::clamp(l[j], c.range);
l[j] = math::clamp(l[j], c.range);
auto p = geom::transpose(j_inv_mass) * l;
auto p = math::transpose(j_inv_mass) * l;
for (std::size_t i = 0; i < B; ++i)
{
@ -413,7 +413,7 @@ void system::solve_constraint(constraint<B, C> const & c)
b.velocity[0] += p[4 * i + 0];
b.velocity[1] += p[4 * i + 1];
b.angular_velocity += geom::det(b.direction, geom::vector{p[4 * i + 2], p[4 * i + 3]});
b.angular_velocity += math::det(b.direction, math::vector{p[4 * i + 2], p[4 * i + 3]});
}
}
@ -433,15 +433,15 @@ struct controller
static constexpr float max_output = 20.f;
geom::matrix<float, outputs, inputs> m1;
geom::vector<float, outputs> t1;
math::matrix<float, outputs, inputs> m1;
math::vector<float, outputs> t1;
// geom::matrix<float, layer1, inputs> m1;
// geom::vector<float, layer1> t1;
// geom::matrix<float, layer2, layer1> m2;
// geom::vector<float, layer2> t2;
// geom::matrix<float, layer3, layer2> m3;
// geom::vector<float, layer3> t3;
// math::matrix<float, layer1, inputs> m1;
// math::vector<float, layer1> t1;
// math::matrix<float, layer2, layer1> m2;
// math::vector<float, layer2> t2;
// math::matrix<float, layer3, layer2> m3;
// math::vector<float, layer3> t3;
//Eigen::VectorXf to_eigen() const;
//void from_eigen(Eigen::VectorXf const & v);
@ -456,7 +456,7 @@ struct controller
void reset();
geom::vector<float, outputs> apply(system const & s) const;
math::vector<float, outputs> apply(system const & s) const;
void read(std::istream & is);
void write(std::ostream & os) const;
@ -464,7 +464,7 @@ struct controller
static float activation(float x);
template <std::size_t N>
static geom::vector<float, N> activation(geom::vector<float, N> v);
static math::vector<float, N> activation(math::vector<float, N> v);
template <typename T>
static void read(std::istream & is, T & x);
@ -562,14 +562,14 @@ void controller::mutate(RNG && rng, float amplitude)
void controller::reset()
{}
geom::vector<float, controller::outputs> controller::apply(system const & sys) const
math::vector<float, controller::outputs> controller::apply(system const & sys) const
{
if (sys.bones.size() * 7 != inputs)
throw std::runtime_error(util::to_string("Wrong number of inputs: should be ", sys.bones.size() * 7));
if (sys.joints.size() != outputs)
throw std::runtime_error(util::to_string("Wrong number of outputs: should be ", sys.joints.size()));
geom::vector<float, inputs> input;
math::vector<float, inputs> input;
for (std::size_t i = 0; i < inputs / 7; ++i)
{
input[7 * i + 0] = sys.bones[i].position[0] - sys.bones[0].position[0];
@ -630,7 +630,7 @@ float controller::activation(float x)
}
template <std::size_t N>
geom::vector<float, N> controller::activation(geom::vector<float, N> v)
math::vector<float, N> controller::activation(math::vector<float, N> v)
{
for (std::size_t i = 0; i < N; ++i)
v[i] = activation(v[i]);
@ -640,32 +640,32 @@ geom::vector<float, N> controller::activation(geom::vector<float, N> v)
controller lerp(controller const & c1, controller const & c2, float t)
{
controller c;
c.m1 = geom::lerp(c1.m1, c2.m1, t);
// c.m2 = geom::lerp(c1.m2, c2.m2, t);
// c.m3 = geom::lerp(c1.m3, c2.m3, t);
c.t1 = geom::lerp(c1.t1, c2.t1, t);
// c.t2 = geom::lerp(c1.t2, c2.t2, t);
// c.t3 = geom::lerp(c1.t3, c2.t3, t);
c.m1 = math::lerp(c1.m1, c2.m1, t);
// c.m2 = math::lerp(c1.m2, c2.m2, t);
// c.m3 = math::lerp(c1.m3, c2.m3, t);
c.t1 = math::lerp(c1.t1, c2.t1, t);
// c.t2 = math::lerp(c1.t2, c2.t2, t);
// c.t3 = math::lerp(c1.t3, c2.t3, t);
c.generation = std::max(c1.generation, c2.generation) + 1;
return c;
}
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_mouse_wheel(int delta) override;
void on_event(app::resize_event const & event) 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 update_camera();
geom::box<float, 2> view_bbox;
math::box<float, 2> view_bbox;
bool centered = false;
gfx::painter painter;
@ -688,6 +688,8 @@ struct animation_2d_app
test,
} mode = mode::train;
application::context context;
std::vector<controller> population;
std::size_t const population_size = 1024;
std::size_t const max_train_frames = 10.f / physics.dt;
@ -698,7 +700,7 @@ struct animation_2d_app
std::size_t train_iterations = 0;
std::size_t const max_train_iterations = 1024*8;
float const initial_variance = 10.f;
static constexpr auto mutation_amplitude = [](float t){ return std::pow(10.f, 1.f + geom::lerp(0.f, -2.f, t)); };
static constexpr auto mutation_amplitude = [](float t){ return std::pow(10.f, 1.f + math::lerp(0.f, -2.f, t)); };
//Eigen::VectorXf mean;
//Eigen::MatrixXf covariance;
@ -720,24 +722,24 @@ struct animation_2d_app
void do_test();
};
animation_2d_app::animation_2d_app()
: app("Animation 2D")
animation_2d_app::animation_2d_app(options const &, application::context const & context)
: context(context)
{
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();
}
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;
update_camera();
@ -745,7 +747,7 @@ void animation_2d_app::on_mouse_wheel(int delta)
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;
if (centered)
@ -763,25 +765,25 @@ void animation_2d_app::update_camera()
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);
view_bbox[0] = math::expand(math::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;
update_camera();
}
if (key == SDLK_p)
if (event.down && event.key == app::keycode::P)
{
testing_control = !testing_control;
}
if (mode == mode::train)
{
if (key == SDLK_s)
if (event.down && event.key == app::keycode::S)
{
train_iterations = max_train_iterations;
}
@ -789,12 +791,12 @@ void animation_2d_app::on_key_down(SDL_Keycode key)
if (mode == mode::test)
{
bool reset = false;
if (key == SDLK_LEFT)
if (event.down && event.key == app::keycode::LEFT)
{
reset = true;
test_id = (test_id + population.size() - 1) % population.size();
}
if (key == SDLK_RIGHT)
if (event.down && event.key == app::keycode::RIGHT)
{
reset = true;
test_id = (test_id + 1) % population.size();
@ -807,14 +809,14 @@ void animation_2d_app::on_key_down(SDL_Keycode key)
float shiftx = random::uniform_distribution<float>{-1.f, 1.f}(rng) * position_variation_amplitude;
float shifty = random::uniform_distribution<float>{0.f, 1.f}(rng) * position_variation_amplitude;
float angle = random::uniform_distribution<float>{-1.f, 1.f}(rng) * geom::rad(angle_variation_amplitude);
float angle = random::uniform_distribution<float>{-1.f, 1.f}(rng) * math::rad(angle_variation_amplitude);
geom::plane_rotation<float, 2> rot{0, 1, angle};
math::plane_rotation<float, 2> rot{0, 1, angle};
float miny = 0.f;
for (auto & b : physics.bones)
{
b.position = rot(b.position) + geom::vector{shiftx, shifty};
b.position = rot(b.position) + math::vector{shiftx, shifty};
b.direction = rot(b.direction);
miny = std::min(miny, b.position[1] - std::abs(b.direction[1]) * b.length / 2.f - b.width / 2.f);
}
@ -844,7 +846,7 @@ void animation_2d_app::update()
population[0].reset();
vsync(true);
context.vsync(true);
}
}
else if (mode == mode::test)
@ -869,8 +871,8 @@ void animation_2d_app::reset_state(system & sys) const
sys.bones.push_back(bone{{0.5f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
sys.bones.push_back(bone{{0.25f, 0.5f}, {0.5f, 1.f}, {0.f, 0.f}, 0.f, std::sqrt(1.25f), 0.25f});
sys.bones.push_back(bone{{0.75f, 1.5f}, {0.5f, 1.f}, {0.f, 0.f}, 0.f, std::sqrt(1.25f), 0.25f});
sys.joints.push_back(joint{0, 1, -1.f, -1.f, geom::interval<float>{geom::rad(60.f), geom::rad(120.f)}});
sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval<float>{geom::rad(-30.f), geom::rad(30.f)}});
sys.joints.push_back(joint{0, 1, -1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval<float>{math::rad(-30.f), math::rad(30.f)}});
//*/
/*
@ -882,10 +884,10 @@ void animation_2d_app::reset_state(system & sys) const
sys.bones.push_back(bone{{ 0.f, h*2.f }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f});
sys.bones.push_back(bone{{ s, h*1.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, h, 0.25f});
sys.bones.push_back(bone{{ s, h*0.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, h, 0.25f});
sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval{geom::rad( 0.f), geom::rad(45.f)}});
sys.joints.push_back(joint{1, 2, 1.f, -s, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
sys.joints.push_back(joint{2, 3, s, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
sys.joints.push_back(joint{3, 4, 1.f, -1.f, geom::interval{geom::rad( 0.f), geom::rad(45.f)}});
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad( 0.f), math::rad(45.f)}});
sys.joints.push_back(joint{1, 2, 1.f, -s, math::interval{math::rad(240.f), math::rad(300.f)}});
sys.joints.push_back(joint{2, 3, s, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
sys.joints.push_back(joint{3, 4, 1.f, -1.f, math::interval{math::rad( 0.f), math::rad(45.f)}});
//*/
/*
@ -894,10 +896,10 @@ void animation_2d_app::reset_state(system & sys) const
sys.bones.push_back(bone{{1.f, 0.5f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
sys.bones.push_back(bone{{0.5f, 1.f}, {-1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
sys.bones.push_back(bone{{0.f, 0.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval<float>{geom::rad(60.f), geom::rad(120.f)}});
sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval<float>{geom::rad(60.f), geom::rad(120.f)}});
sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval<float>{geom::rad(60.f), geom::rad(120.f)}});
sys.joints.push_back(joint{3, 0, 1.f, -1.f, geom::interval<float>{geom::rad(60.f), geom::rad(120.f)}});
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
sys.joints.push_back(joint{3, 0, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
//*/
/*
@ -906,10 +908,10 @@ void animation_2d_app::reset_state(system & sys) const
sys.bones.push_back(bone{{1.f, 0.5f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
sys.bones.push_back(bone{{0.5f, 1.f}, {-1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
sys.bones.push_back(bone{{0.f, 0.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval<float>{geom::rad(60.f), geom::rad(120.f)}});
sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval<float>{geom::rad(60.f), geom::rad(120.f)}});
sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval<float>{geom::rad(60.f), geom::rad(120.f)}});
sys.joints.push_back(joint{3, 0, 1.f, -1.f, geom::interval<float>{geom::rad(60.f), geom::rad(120.f)}});
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
sys.joints.push_back(joint{3, 0, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
//*/
/*
@ -925,7 +927,7 @@ void animation_2d_app::reset_state(system & sys) const
sys.bones.push_back(bone{{-0.25f, std::sqrt(3.f) * 0.25f}, {0.5f, -std::sqrt(3.f) * 0.5}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
for (int i = 0; i < 2 * n + 4; ++i)
sys.joints.push_back(joint{i, (i + 1) % (2 * n + 4), 1.f, -1.f, geom::interval<float>{geom::rad(0.f), geom::rad(60.f)}});
sys.joints.push_back(joint{i, (i + 1) % (2 * n + 4), 1.f, -1.f, math::interval<float>{math::rad(0.f), math::rad(60.f)}});
//*/
/*
@ -934,7 +936,7 @@ void animation_2d_app::reset_state(system & sys) const
for (int i = 0; i < n; ++i)
sys.bones.push_back(bone{{i + 0.5f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
for (int i = 0; i + 1 < n; ++i)
sys.joints.push_back(joint{i, i + 1, 1.f, -1.f, geom::interval<float>{geom::rad(-30.f), geom::rad(30.f)}});
sys.joints.push_back(joint{i, i + 1, 1.f, -1.f, math::interval<float>{math::rad(-30.f), math::rad(30.f)}});
//*/
@ -949,11 +951,11 @@ void animation_2d_app::reset_state(system & sys) const
}
for (int i = 0; i + 1 < n; ++i)
sys.joints.push_back(joint{i, i + 1, 1.f, -1.f, geom::interval<float>{geom::rad(-30.f), geom::rad(30.f)}});
sys.joints.push_back(joint{i, i + 1, 1.f, -1.f, math::interval<float>{math::rad(-30.f), math::rad(30.f)}});
for (int i = 0; i < n; ++i)
{
sys.joints.push_back(joint{i, n + 2 * i + 0, -0.5f, -1.f, geom::interval<float>{geom::rad(240.f), geom::rad(300.f)}});
sys.joints.push_back(joint{i, n + 2 * i + 1, 0.5f, -1.f, geom::interval<float>{geom::rad(240.f), geom::rad(300.f)}});
sys.joints.push_back(joint{i, n + 2 * i + 0, -0.5f, -1.f, math::interval<float>{math::rad(240.f), math::rad(300.f)}});
sys.joints.push_back(joint{i, n + 2 * i + 1, 0.5f, -1.f, math::interval<float>{math::rad(240.f), math::rad(300.f)}});
}
//*/
@ -963,9 +965,9 @@ void animation_2d_app::reset_state(system & sys) const
sys.bones.push_back(bone{{0.5f, 1.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
sys.bones.push_back(bone{{1.5f, 1.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
sys.bones.push_back(bone{{2.f, 0.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval{geom::rad(-15.f), geom::rad( 15.f)}});
sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval{math::rad(-15.f), math::rad( 15.f)}});
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
//*/
/*
@ -975,10 +977,10 @@ void animation_2d_app::reset_state(system & sys) const
sys.bones.push_back(bone{{ 0.f, 1.f }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f});
sys.bones.push_back(bone{{ 1.f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
sys.bones.push_back(bone{{ 1.f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval{geom::rad( 0.f), geom::rad(45.f)}});
sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
sys.joints.push_back(joint{3, 4, 1.f, -1.f, geom::interval{geom::rad( 0.f), geom::rad(45.f)}});
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad( 0.f), math::rad(45.f)}});
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
sys.joints.push_back(joint{3, 4, 1.f, -1.f, math::interval{math::rad( 0.f), math::rad(45.f)}});
//*/
/*
@ -988,10 +990,10 @@ void animation_2d_app::reset_state(system & sys) const
sys.bones.push_back(bone{{ 0.f, 1.f }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f});
sys.bones.push_back(bone{{ 1.f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
sys.bones.push_back(bone{{ 1.f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval{geom::rad(-45.f), geom::rad( 0.f)}});
sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
sys.joints.push_back(joint{3, 4, 1.f, -1.f, geom::interval{geom::rad(-45.f), geom::rad( 0.f)}});
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad(-45.f), math::rad( 0.f)}});
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
sys.joints.push_back(joint{3, 4, 1.f, -1.f, math::interval{math::rad(-45.f), math::rad( 0.f)}});
//*/
/*
@ -1001,10 +1003,10 @@ void animation_2d_app::reset_state(system & sys) const
sys.bones.push_back(bone{{ 0.f, 1.f }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f});
sys.bones.push_back(bone{{ 1.f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
sys.bones.push_back(bone{{ 1.f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval{geom::rad(-45.f), geom::rad( 0.f)}});
sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
sys.joints.push_back(joint{3, 4, 1.f, -1.f, geom::interval{geom::rad( 0.f), geom::rad( 45.f)}});
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad(-45.f), math::rad( 0.f)}});
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
sys.joints.push_back(joint{3, 4, 1.f, -1.f, math::interval{math::rad( 0.f), math::rad( 45.f)}});
//*/
/*
@ -1014,17 +1016,17 @@ void animation_2d_app::reset_state(system & sys) const
sys.bones.push_back(bone{{ 0.f, 1.f }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f});
sys.bones.push_back(bone{{ 1.f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
sys.bones.push_back(bone{{ 1.f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval{geom::rad( 0.f), geom::rad( 45.f)}});
sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
sys.joints.push_back(joint{3, 4, 1.f, -1.f, geom::interval{geom::rad(-45.f), geom::rad( 0.f)}});
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad( 0.f), math::rad( 45.f)}});
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
sys.joints.push_back(joint{3, 4, 1.f, -1.f, math::interval{math::rad(-45.f), math::rad( 0.f)}});
//*/
float const bone_density = 1.f;
for (auto & b : sys.bones)
{
b.position[1] += 0.125f;
b.direction = geom::normalized(b.direction);
b.direction = math::normalized(b.direction);
b.mass = b.length * b.width * bone_density;
b.inertia = b.mass * (b.length * b.length + b.width * b.width) / 12.f;
}
@ -1043,14 +1045,14 @@ float animation_2d_app::eval_score(controller const & c, random::generator rng)
float shiftx = random::uniform_distribution<float>{-1.f, 1.f}(rng) * position_variation_amplitude;
float shifty = random::uniform_distribution<float>{0.f, 1.f}(rng) * position_variation_amplitude;
float angle = random::uniform_distribution<float>{-1.f, 1.f}(rng) * geom::rad(angle_variation_amplitude);
float angle = random::uniform_distribution<float>{-1.f, 1.f}(rng) * math::rad(angle_variation_amplitude);
geom::plane_rotation<float, 2> rot{0, 1, angle};
math::plane_rotation<float, 2> rot{0, 1, angle};
float miny = 0.f;
for (auto & b : physics.bones)
{
b.position = rot(b.position) + geom::vector{shiftx, shifty};
b.position = rot(b.position) + math::vector{shiftx, shifty};
b.direction = rot(b.direction);
miny = std::min(miny, b.position[1] - std::abs(b.direction[1]) * b.length / 2.f - b.width / 2.f);
}
@ -1098,7 +1100,7 @@ float animation_2d_app::eval_score(controller const & c, random::generator rng)
auto ctrl = c.apply(physics);
for (std::size_t i = 0; i < ctrl.dimension(); ++i)
torque[i] = ctrl[i];
energy += geom::length(ctrl) * dt;
energy += math::length(ctrl) * dt;
return torque;
});
@ -1117,12 +1119,12 @@ float animation_2d_app::eval_score(controller const & c, random::generator rng)
}
}
// penalty += geom::sqr((1.f - physics.bones[2].direction[0]) * dt);
// penalty += math::sqr((1.f - physics.bones[2].direction[0]) * dt);
// float const target_speed = 2.f;
// penalty += geom::sqr((physics.bones[2].velocity[0] - target_speed) / target_speed) * dt;
// penalty += math::sqr((physics.bones[2].velocity[0] - target_speed) / target_speed) * dt;
// penalty -= physics.bones[2].velocity[0] * dt / physics.bones;
auto cm_vel = geom::vector<float, 2>::zero();
auto cm_vel = math::vector<float, 2>::zero();
float mass = 0.f;
for (auto const & b : physics.bones)
{
@ -1143,14 +1145,6 @@ float animation_2d_app::eval_score(controller const & c, random::generator rng)
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;
}
@ -1165,7 +1159,6 @@ float animation_2d_app::eval_score(controller const & c, random::generator rng)
return score;
}
// Old evolutionary training implementation
void animation_2d_app::do_train()
{
@ -1367,17 +1360,12 @@ void animation_2d_app::do_train()
void animation_2d_app::do_test()
{
std::optional<geom::point<float, 2>> m;
if (mouse())
{
m = view_bbox.corner((*mouse())[0] * 1.f / width(), 1.f - (*mouse())[1] * 1.f / height());
}
math::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 (!is_left_button_down())
if (state().mouse_button_down.contains(app::mouse_button::left))
{
selected = std::nullopt;
if (m)
{
float selected_distance = std::numeric_limits<float>::infinity();
@ -1389,20 +1377,20 @@ void animation_2d_app::do_test()
auto p1 = b.position + b.direction * b.length / 2.f;
auto r = p1 - p0;
auto d = *m - p0;
auto d = mouse - p0;
float t = geom::dot(d, r) / geom::dot(r, r);
float t = math::dot(d, r) / math::dot(r, r);
float distance;
if (0.f <= t && t <= 1.f)
{
distance = geom::length(d - r * t);
distance = math::length(d - r * t);
}
else
{
float d0 = geom::distance(p0, *m);
float d1 = geom::distance(p1, *m);
float d0 = math::distance(p0, mouse);
float d1 = math::distance(p1, mouse);
if (d0 < d1)
{
@ -1427,10 +1415,10 @@ void animation_2d_app::do_test()
}
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 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};
}
@ -1452,7 +1440,7 @@ void animation_2d_app::do_test()
}
{
auto cm_vel = geom::vector<float, 2>::zero();
auto cm_vel = math::vector<float, 2>::zero();
float mass = 0.f;
for (auto const & b : physics.bones)
{
@ -1503,7 +1491,7 @@ void animation_2d_app::present()
}
}
painter.render(geom::orthographic_camera{view_bbox}.transform());
painter.render(math::orthographic_camera{view_bbox}.transform());
float avg_speed = 0.f;
@ -1515,14 +1503,14 @@ void animation_2d_app::present()
int margin = 40;
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));
for (std::size_t i = start; i + 1 < test_speeds.size(); ++i)
{
float const scale = 2.f;
geom::point p0{40.f + (i - start ) * step, 180.f - test_speeds[i ] * scale};
geom::point p1{40.f + (i - start + 1) * step, 180.f - test_speeds[i + 1] * scale};
math::point p0{40.f + (i - start ) * step, 180.f - test_speeds[i ] * scale};
math::point p1{40.f + (i - start + 1) * step, 180.f - test_speeds[i + 1] * scale};
painter.line(p0, p1, 2.f, gfx::red, false);
}
}
@ -1532,7 +1520,7 @@ void animation_2d_app::present()
opts.c = gfx::black;
opts.x = gfx::painter::x_align::left;
opts.y = gfx::painter::y_align::top;
opts.scale = 2.f;
opts.scale = {2.f, 2.f};
painter.text({40.f, 40.f}, util::to_string(train_iterations, "/", max_train_iterations), opts);
painter.text({40.f, 64.f}, util::to_string("Best score: ", std::setprecision(10), best_score), opts);
painter.text({40.f, 88.f}, util::to_string("Model: ", test_id, "/", population.size(), ", gen ", population[test_id].generation), opts);
@ -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);
}
painter.render(geom::window_camera{width(), height()}.transform());
painter.render(math::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

@ -10,19 +10,20 @@
#include <psemek/audio/effect/fade_out.hpp>
#include <psemek/audio/effect/compressor.hpp>
#include <psemek/audio/effect/pause.hpp>
#include <psemek/audio/effect/loop.hpp>
#include <psemek/audio/combine/loop.hpp>
#include <psemek/audio/effect/pitch.hpp>
#include <psemek/audio/effect/distortion.hpp>
#include <psemek/audio/duplicate.hpp>
#include <psemek/audio/stereo.hpp>
#include <psemek/audio/mixer.hpp>
#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/audio/combine/duplicate.hpp>
#include <psemek/audio/combine/stereo.hpp>
#include <psemek/audio/combine/mixer.hpp>
#include <psemek/audio/midi.hpp>
#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/util/clock.hpp>
#include <psemek/util/to_string.hpp>
#include <psemek/geom/constants.hpp>
#include <psemek/geom/camera.hpp>
#include <psemek/math/constants.hpp>
#include <psemek/math/camera.hpp>
#include <psemek/prof/profiler.hpp>
#include <psemek/io/file_stream.hpp>
@ -30,44 +31,44 @@
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},
{SDLK_x, 60},
{SDLK_c, 61},
{SDLK_v, 62},
{SDLK_b, 63},
{SDLK_n, 64},
{SDLK_m, 65},
{SDLK_COMMA, 66},
{SDLK_PERIOD, 67},
{SDLK_SLASH, 68},
{SDLK_a, 69},
{SDLK_s, 70},
{SDLK_d, 71},
{SDLK_f, 72},
{SDLK_g, 73},
{SDLK_h, 74},
{SDLK_j, 75},
{SDLK_k, 76},
{SDLK_l, 77},
{SDLK_SEMICOLON, 78},
{SDLK_QUOTE, 79},
{SDLK_q, 80},
{SDLK_w, 81},
{SDLK_e, 82},
{SDLK_r, 83},
{SDLK_t, 84},
{SDLK_y, 85},
{SDLK_u, 86},
{SDLK_i, 87},
{SDLK_o, 88},
{SDLK_p, 89},
{SDLK_LEFTBRACKET, 90},
{SDLK_RIGHTBRACKET, 91},
{app::keycode::Z, 59},
{app::keycode::X, 60},
{app::keycode::C, 61},
{app::keycode::V, 62},
{app::keycode::B, 63},
{app::keycode::N, 64},
{app::keycode::M, 65},
{app::keycode::COMMA, 66},
{app::keycode::PERIOD, 67},
{app::keycode::SLASH, 68},
{app::keycode::A, 69},
{app::keycode::S, 70},
{app::keycode::D, 71},
{app::keycode::F, 72},
{app::keycode::G, 73},
{app::keycode::H, 74},
{app::keycode::J, 75},
{app::keycode::K, 76},
{app::keycode::L, 77},
{app::keycode::SEMICOLON, 78},
{app::keycode::APOSTROPHE, 79},
{app::keycode::Q, 80},
{app::keycode::W, 81},
{app::keycode::E, 82},
{app::keycode::R, 83},
{app::keycode::T, 84},
{app::keycode::Y, 85},
{app::keycode::U, 86},
{app::keycode::I, 87},
{app::keycode::O, 88},
{app::keycode::P, 89},
{app::keycode::LEFTBRACKET, 90},
{app::keycode::RIGHTBRACKET, 91},
};
static geom::interval<int> const key_rows[3] = {
static math::interval<int> const key_rows[3] = {
{59, 68},
{69, 79},
{80, 91},
@ -78,64 +79,57 @@ static std::string_view const midi_name[12] = {
};
struct audio_app
: app::app
: app::application_base
{
audio_app()
: app::app("Audio example")
audio_app(options const &, context const &)
{
engine_ = audio::make_engine();
mixer_ = audio::make_mixer();
volume_control_ = audio::volume_stereo(mixer_, 0.5f, 0.5f, 0.1f);
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));
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))
{
auto tone = audio::karplus_strong(440.f * std::pow(2.f, (midi - 69) / 12.f));
auto tone = audio::karplus_strong(audio::midi_frequency(midi));
tone = audio::distortion(std::move(tone), 4.f);
channels_[midi] = mixer_->add(audio::fade_in(tone, 0.005f));
}
}
if (key == SDLK_SPACE)
if (!event.down && key_to_midi.contains(event.key))
{
pause_control_->paused(!pause_control_->paused());
}
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);
int midi = key_to_midi.at(event.key);
auto & ch = channels_[midi];
if (auto s = ch->stream())
ch->stream(audio::fade_out(s, 0.1f));
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);
if (key == SDLK_KP_MINUS)
if (!event.down && event.key == app::keycode::KP_MINUS)
pitch_control_->pitch(1.f);
}
@ -160,11 +154,11 @@ struct audio_app
float margin = 4.f;
float border = 8.f;
float center = width() / 2.f;
float y = height() / 2.f + size * std::size(key_rows) / 2.f;
float center = state().size[0] / 2.f;
float y = state().size[1] / 2.f + size * std::size(key_rows) / 2.f;
gfx::painter::text_options opts;
opts.scale = 2.f;
opts.scale = {2.f, 2.f};
opts.c = {0, 0, 0, 255};
opts.x = gfx::painter::x_align::center;
opts.y = gfx::painter::y_align::center;
@ -191,13 +185,13 @@ struct audio_app
}
}
opts.scale = 4.f;
opts.scale = {4.f, 4.f};
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 y = height() / 2.f;
float x = state().size[0] - 200.f;
float y = state().size[1] / 2.f;
float w = 4.f;
float h = 64.f;
@ -210,16 +204,16 @@ struct audio_app
painter_.rect({{{x - s, x + s}, {r - w, r + w}}}, {0, 0, 255, 255});
}
painter_.render(geom::window_camera{width(), height()}.transform());
painter_.render(math::window_camera{state().size[0], state().size[1]}.transform());
}
void on_scene_exit() override
~audio_app() override
{
prof::dump();
}
private:
audio::engine engine_;
std::unique_ptr<audio::engine> engine_;
audio::mixer_ptr mixer_;
std::shared_ptr<audio::volume_control_stereo> volume_control_;
std::shared_ptr<audio::pitch_control> pitch_control_;
@ -234,7 +228,12 @@ private:
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"});
}
}

BIN
examples/biomes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

View file

@ -1,13 +1,13 @@
#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/geom/box.hpp>
#include <psemek/geom/mesh.hpp>
#include <psemek/geom/intersection.hpp>
#include <psemek/geom/camera.hpp>
#include <psemek/geom/homogeneous.hpp>
#include <psemek/geom/constants.hpp>
#include <psemek/geom/math.hpp>
#include <psemek/geom/gradient.hpp>
#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/math/box.hpp>
#include <psemek/math/mesh.hpp>
#include <psemek/math/intersection.hpp>
#include <psemek/math/camera.hpp>
#include <psemek/math/homogeneous.hpp>
#include <psemek/math/constants.hpp>
#include <psemek/math/math.hpp>
#include <psemek/math/gradient.hpp>
#include <psemek/gfx/gl.hpp>
#include <psemek/gfx/mesh.hpp>
#include <psemek/gfx/renderer/simple.hpp>
@ -23,6 +23,7 @@
#include <psemek/util/clock.hpp>
#include <psemek/util/to_string.hpp>
#include <psemek/async/threadpool.hpp>
#include <psemek/log/log.hpp>
#include <iostream>
@ -50,19 +51,19 @@ auto barycenter(Iterator begin, Iterator end)
template <typename Container>
auto barycenter(Container const & c)
{
return barycenter(util::begin(c), util::end(c));
return barycenter(util::xbegin(c), util::xend(c));
}
std::vector<geom::point<float, 3>> intersection(geom::vector<float, 4> const & f, std::vector<geom::point<float, 3>> const & vertices, std::vector<geom::segment<std::uint32_t>> const & edges)
std::vector<math::point<float, 3>> intersection(math::vector<float, 4> const & f, std::vector<math::point<float, 3>> const & vertices, std::vector<math::segment<std::uint32_t>> const & edges)
{
std::vector<geom::point<float, 3>> points;
std::vector<math::point<float, 3>> points;
for (auto e : edges)
{
auto p0 = vertices[e[0]];
auto p1 = vertices[e[1]];
auto f0 = dot(f, geom::homogeneous(p0));
auto f1 = dot(f, geom::homogeneous(p1));
auto f0 = dot(f, math::homogeneous(p0));
auto f1 = dot(f, math::homogeneous(p1));
if ((f0 >= 0.f) ^ (f1 >= 0.f))
{
@ -70,14 +71,14 @@ std::vector<geom::point<float, 3>> intersection(geom::vector<float, 4> const & f
}
}
geom::vector<float, 3> const n { f[0], f[1], f[2] };
math::vector<float, 3> const n { f[0], f[1], f[2] };
if (!points.empty())
{
auto const & p0 = points[0];
std::sort(points.begin() + 1, points.end(), [&](geom::point<float, 3> const & p1, geom::point<float, 3> const & p2){
return geom::dot(geom::cross(p1 - p0, p2 - p0), n) > 0.f;
std::sort(points.begin() + 1, points.end(), [&](math::point<float, 3> const & p1, math::point<float, 3> const & p2){
return math::dot(math::cross(p1 - p0, p2 - p0), n) > 0.f;
});
}
@ -88,9 +89,9 @@ template <typename Vertex, std::size_t N, typename Index = std::uint32_t>
struct mesh_builder
{
std::vector<Vertex> vertices;
std::vector<geom::simplex<Index, N>> indices;
std::vector<math::simplex<Index, N>> indices;
void add(std::vector<Vertex> const & v, std::vector<geom::simplex<Index, N>> const & i)
void add(std::vector<Vertex> const & v, std::vector<math::simplex<Index, N>> const & i)
{
Index const base = static_cast<Index>(vertices.size());
std::size_t begin = indices.size();
@ -161,27 +162,27 @@ void main()
)";
struct cloud_app
: app::app
: app::application_base
{
geom::spherical_camera camera;
math::spherical_camera camera;
float step;
float max_density = 2.f;
geom::interval<float> harmonic_range;
math::interval<float> harmonic_range;
util::clock<std::chrono::duration<float>> clock;
geom::box<float, 3> bbox;
std::vector<geom::point<float, 3>> bbox_vertices;
std::vector<geom::segment<std::uint32_t>> bbox_edges;
math::box<float, 3> bbox;
std::vector<math::point<float, 3>> bbox_vertices;
std::vector<math::segment<std::uint32_t>> bbox_edges;
gfx::mesh bbox_mesh;
gfx::mesh slice_mesh;
gfx::mesh light_mesh;
std::vector<geom::vector<float, 3>> dirs;
std::vector<math::vector<float, 3>> dirs;
gfx::simple_renderer renderer;
@ -191,14 +192,13 @@ struct cloud_app
gfx::texture_3d shadow_texture;
cloud_app(std::size_t size)
: app::app("Cloud")
{
geom::vector<int, 3> cloud_size{2 * size, size, size};
math::vector<int, 3> cloud_size{2 * size, size, size};
bbox = {{{-2.f, 2.f}, {-1.f, 1.f}, {-1.f, 1.f}}};
step = bbox[0].length() / cloud_size[0];
camera.fov_y = geom::rad(45.f);
camera.fov_y = math::rad(45.f);
camera.near_clip = 0.01f;
camera.far_clip = 100.f;
@ -207,15 +207,15 @@ struct cloud_app
camera.elevation_angle = 0.f;
camera.distance = 10.f;
bbox_vertices = geom::vertices(bbox);
bbox_edges = geom::edges(bbox);
bbox_vertices = math::vertices(bbox);
bbox_edges = math::edges(bbox);
bbox_mesh.setup<geom::point<float, 3>>();
bbox_mesh.setup<math::point<float, 3>>();
bbox_mesh.load(bbox_vertices, bbox_edges);
slice_mesh.setup<geom::point<float, 3>, geom::vector<float, 3>>();
slice_mesh.setup<math::point<float, 3>, math::vector<float, 3>>();
light_mesh.setup<geom::point<float, 3>>();
light_mesh.setup<math::point<float, 3>>();
{
async::threadpool bg("bg");
@ -223,7 +223,7 @@ struct cloud_app
random::generator rng(random::device{});
random::uniform_sphere_vector_distribution<float, 3> d;
std::vector<util::array<geom::vector<float, 3>, 3>> grad(4);
std::vector<util::ndarray<math::vector<float, 3>, 3>> grad(4);
std::vector<float> weights(grad.size());
float weight_sum = 0.f;
@ -245,9 +245,9 @@ struct cloud_app
// can't use template argument deduction for first argument due to gcc bug
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89062
geom::gradient<float> g(std::make_pair(0.2f, 0.f), geom::easing_type::quadratic_out, std::pair{0.3f, max_density});
math::gradient<float> g(std::make_pair(0.2f, 0.f), math::easing_type::quadratic_out, std::pair{0.3f, max_density});
util::array<std::uint8_t, 3> cloud_data({cloud_size[0], cloud_size[1], cloud_size[2]});
util::ndarray<std::uint8_t, 3> cloud_data({cloud_size[0], cloud_size[1], cloud_size[2]});
for (std::size_t z = 0; z < cloud_data.depth(); ++z)
{
@ -256,17 +256,17 @@ struct cloud_app
bg.post([&, z, y]{
for (std::size_t x = 0; x < cloud_data.width(); ++x)
{
geom::vector<float, 3> p;
math::vector<float, 3> p;
p[0] = (x * 1.f) / cloud_data.width();
p[1] = (y * 1.f) / cloud_data.height();
p[2] = (z * 1.f) / cloud_data.depth();
float v = perlin(p);
auto d = p - geom::vector{0.5f, 0.5f, 0.5f};
v *= std::max(0.f, 1.f - geom::length(d) / 0.5f);
auto d = p - math::vector{0.5f, 0.5f, 0.5f};
v *= std::max(0.f, 1.f - math::length(d) / 0.5f);
cloud_data(x, y, z) = geom::clamp(g(v) / max_density * 255.f, {0.f, 255.f});
cloud_data(x, y, z) = math::clamp(g(v) / max_density * 255.f, {0.f, 255.f});
}
});
}
@ -280,9 +280,9 @@ struct cloud_app
cloud_texture.linear_filter();
cloud_texture.generate_mipmap();
auto value_at = [this, &cloud_data](geom::point<float, 3> const & p)
auto value_at = [this, &cloud_data](math::point<float, 3> const & p)
{
geom::vector<float, 3> d;
math::vector<float, 3> d;
d[0] = (p[0] - bbox[0].min) / bbox[0].length() * cloud_data.width();
d[1] = (p[1] - bbox[1].min) / bbox[1].length() * cloud_data.height();
d[2] = (p[2] - bbox[2].min) / bbox[2].length() * cloud_data.depth();
@ -298,12 +298,12 @@ struct cloud_app
if (d[2] < 0.0f) return 0.f;
if (d[2] >= cloud_data.depth() - 1) return 0.f;
geom::vector<int, 3> i;
math::vector<int, 3> i;
i[0] = std::floor(d[0]);
i[1] = std::floor(d[1]);
i[2] = std::floor(d[2]);
geom::vector<float, 3> t;
math::vector<float, 3> t;
t[0] = d[0] - i[0];
t[1] = d[1] - i[1];
t[2] = d[2] - i[2];
@ -317,18 +317,18 @@ struct cloud_app
float v011 = cloud_data(i[0], i[1] + 1, i[2] + 1);
float v111 = cloud_data(i[0] + 1, i[1] + 1, i[2] + 1);
float v00 = geom::lerp(v000, v100, t[0]);
float v10 = geom::lerp(v010, v110, t[0]);
float v01 = geom::lerp(v001, v101, t[0]);
float v11 = geom::lerp(v011, v111, t[0]);
float v00 = math::lerp(v000, v100, t[0]);
float v10 = math::lerp(v010, v110, t[0]);
float v01 = math::lerp(v001, v101, t[0]);
float v11 = math::lerp(v011, v111, t[0]);
float v0 = geom::lerp(v00, v10, t[1]);
float v1 = geom::lerp(v01, v11, t[1]);
float v0 = math::lerp(v00, v10, t[1]);
float v1 = math::lerp(v01, v11, t[1]);
return geom::lerp(v0, v1, t[2]) / 255.f * max_density;
return math::lerp(v0, v1, t[2]) / 255.f * max_density;
};
util::array<geom::vector<float, 4>, 3> cloud_shadow_f(cloud_data.dims());
util::ndarray<math::vector<float, 4>, 3> cloud_shadow_f(cloud_data.dims());
dirs.resize(32);
@ -336,7 +336,7 @@ struct cloud_app
float const phi = (1.f + std::sqrt(5.f)) / 2.f;
for (int i = 0; i < dirs.size(); ++i)
{
float a = 2.f * geom::pi * (i / phi);
float a = 2.f * math::pi * (i / phi);
float b = std::acos(1.f - (2.f * i) / dirs.size());
dirs[i][0] = std::cos(a) * std::sin(b);
@ -344,14 +344,14 @@ struct cloud_app
dirs[i][2] = std::cos(b);
}
float diag = std::sqrt(geom::sqr(bbox[0].length()) + geom::sqr(bbox[1].length()) + geom::sqr(bbox[2].length()));
float diag = std::sqrt(math::sqr(bbox[0].length()) + math::sqr(bbox[1].length()) + math::sqr(bbox[2].length()));
int steps = diag / step;
auto process = [&, this](auto const & idx)
{
float const step = bbox[0].length() / cloud_size[0];
geom::point<float, 3> o;
math::point<float, 3> o;
for (std::size_t i = 0; i < 3; ++i)
o[i] = bbox[i].min + bbox[i].length() * (idx[i] + 0.5f) / cloud_shadow_f.dim(i);
@ -364,7 +364,7 @@ struct cloud_app
{
float density = 0.f;
geom::point<float, 3> p = o + dir * (steps * step);
math::point<float, 3> p = o + dir * (steps * step);
for (int s = steps; s > 0; --s)
{
@ -374,10 +374,10 @@ struct cloud_app
p -= dir * step;
}
sum_0 += density * 2.f * std::sqrt(geom::pi);
sum_x += density * dir[0] * std::sqrt(12.f * geom::pi);
sum_y += density * dir[1] * std::sqrt(12.f * geom::pi);
sum_z += density * dir[2] * std::sqrt(12.f * geom::pi);
sum_0 += density * 2.f * std::sqrt(math::pi);
sum_x += density * dir[0] * std::sqrt(12.f * math::pi);
sum_y += density * dir[1] * std::sqrt(12.f * math::pi);
sum_z += density * dir[2] * std::sqrt(12.f * math::pi);
++count;
}
@ -414,8 +414,8 @@ struct cloud_app
log::info() << "Finished!";
geom::interval<float> range_0;
geom::interval<float> range_d;
math::interval<float> range_0;
math::interval<float> range_d;
for (auto const & v : cloud_shadow_f)
{
@ -448,14 +448,14 @@ struct cloud_app
}
}
util::array<geom::vector<std::uint8_t, 4>, 3> cloud_shadow(cloud_data.dims());
util::ndarray<math::vector<std::uint8_t, 4>, 3> cloud_shadow(cloud_data.dims());
for (auto const & idx : cloud_shadow.indices())
{
for (std::size_t i = 0; i < 4; ++i)
{
float v = (cloud_shadow_f(idx)[i] - harmonic_range.min) / harmonic_range.length();
cloud_shadow(idx)[i] = geom::clamp(255.f * v, {0.f, 255.f});
cloud_shadow(idx)[i] = math::clamp(255.f * v, {0.f, 255.f});
}
}
@ -466,48 +466,55 @@ 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);
}
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;
camera.elevation_angle += dy * 0.01f;
auto const delta = event.position - old_mouse;
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()
{
auto n = -camera.direction();
geom::vector<float, 4> f {n[0], n[1], n[2], 0.f};
math::vector<float, 4> f {n[0], n[1], n[2], 0.f};
geom::interval<float> range;
math::interval<float> range;
for (auto p : bbox_vertices)
{
range |= geom::dot(f, geom::homogeneous(p));
range |= math::dot(f, math::homogeneous(p));
}
int count = std::ceil(range.length() / step);
struct vertex
{
geom::point<float, 3> pos;
geom::vector<float, 3> tc;
math::point<float, 3> pos;
math::vector<float, 3> tc;
};
mesh_builder<vertex, 2> builder;
@ -517,7 +524,7 @@ struct cloud_app
f[3] = - (range.max - (range.length() * s) / count);
auto slice_vertices = intersection(f, bbox_vertices, bbox_edges);
auto slice_indices = geom::triangulate_convex(slice_vertices);
auto slice_indices = math::triangulate_convex(slice_vertices);
std::vector<vertex> vertices;
for (auto p : slice_vertices)
@ -534,17 +541,20 @@ struct cloud_app
slice_mesh.load(builder.vertices, builder.indices);
}
void update() override
{}
void present() override
{
update_slice_mesh();
float t = clock.count();
geom::vector<float, 3> light_dir = geom::normalized(geom::vector{std::cos(t), std::sin(t), 0.5f});
math::vector<float, 3> light_dir = math::normalized(math::vector{std::cos(t), std::sin(t), 0.5f});
{
std::vector<geom::point<float, 3>> light_vertices;
auto o = geom::point<float, 3>::zero() + light_dir * 4.f;
std::vector<math::point<float, 3>> light_vertices;
auto o = math::point<float, 3>::zero() + light_dir * 4.f;
float s = 0.2f;
for (auto d : dirs)
{
@ -581,7 +591,7 @@ struct cloud_app
cloud_program["u_max_density"] = max_density;
cloud_program["u_min_harmonic"] = harmonic_range.min;
cloud_program["u_max_harmonic"] = harmonic_range.max;
cloud_program["u_light"] = geom::normalized(light_dir);
cloud_program["u_light"] = math::normalized(light_dir);
gl::ActiveTexture(gl::TEXTURE0);
cloud_texture.bind();
gl::ActiveTexture(gl::TEXTURE1);
@ -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 0;
return default_application_factory({.name = "Cloud example"}, [](application::options const &, application::context const & context)
-> 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,17 +1,17 @@
#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/renderer/deferred.hpp>
#include <psemek/gfx/effect/gamma.hpp>
#include <psemek/gfx/effect/fxaa.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/geom/mesh.hpp>
#include <psemek/geom/camera.hpp>
#include <psemek/geom/math.hpp>
#include <psemek/geom/rotation.hpp>
#include <psemek/geom/translation.hpp>
#include <psemek/geom/scale.hpp>
#include <psemek/math/mesh.hpp>
#include <psemek/math/camera.hpp>
#include <psemek/math/math.hpp>
#include <psemek/math/rotation.hpp>
#include <psemek/math/translation.hpp>
#include <psemek/math/scale.hpp>
#include <psemek/util/clock.hpp>
#include <psemek/util/moving_average.hpp>
@ -22,14 +22,14 @@
using namespace psemek;
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_mouse_wheel(int delta) override;
void on_event(app::mouse_move_event const & event) override;
void on_event(app::mouse_wheel_event const & event) override;
void update() override;
void present() override;
@ -44,7 +44,7 @@ struct deferred_app
gfx::texture_2d pre_fxaa_texture;
gfx::fxaa fxaa;
geom::spherical_camera camera;
math::spherical_camera camera;
gfx::mesh plane;
gfx::mesh cube;
@ -60,18 +60,17 @@ struct deferred_app
gfx::painter painter;
};
deferred_app::deferred_app()
: app("Deferred shading example", 0)
deferred_app::deferred_app(options const &, context const & context)
{
vsync(false);
context.vsync(false);
camera.fov_y = geom::rad(45.f);
camera.fov_y = math::rad(45.f);
camera.near_clip = 0.1f;
camera.far_clip = 1000.f;
camera.target = {0.f, 0.f, 0.f};
camera.distance = 20.f;
camera.azimuthal_angle = 0.f;
camera.elevation_angle = geom::rad(30.f);
camera.elevation_angle = math::rad(30.f);
pre_gamma_texture.linear_filter();
pre_fxaa_texture.linear_filter();
@ -86,10 +85,10 @@ deferred_app::deferred_app()
struct vertex
{
geom::point<float, 3> position;
math::point<float, 3> position;
gfx::color_rgba color;
geom::vector<float, 2> texcoord;
geom::vector<float, 3> normal;
math::vector<float, 2> texcoord;
math::vector<float, 3> normal;
static auto attribs()
{
@ -99,7 +98,7 @@ deferred_app::deferred_app()
struct instance
{
geom::matrix<float, 3, 4> transform;
math::matrix<float, 3, 4> transform;
static auto attribs()
{
@ -116,7 +115,7 @@ deferred_app::deferred_app()
vertices.push_back({{-1.f, 1.f, 0.f}, color, {0.f, 0.f}, {0.f, 0.f, 1.f}});
vertices.push_back({{ 1.f, 1.f, 0.f}, color, {0.f, 0.f}, {0.f, 0.f, 1.f}});
std::vector<geom::triangle<std::uint32_t>> indices;
std::vector<math::triangle<std::uint32_t>> indices;
indices.push_back({0, 1, 2});
indices.push_back({2, 1, 3});
@ -125,17 +124,17 @@ deferred_app::deferred_app()
}
{
auto box = geom::box<float, 3>{{{-1.f, 1.f}, {-1.f, 1.f}, {-1.f, 1.f}}};
auto box = math::box<float, 3>{{{-1.f, 1.f}, {-1.f, 1.f}, {-1.f, 1.f}}};
auto triangles = geom::deindex(geom::vertices(box), geom::faces(box));
auto triangles = math::deindex(math::vertices(box), math::faces(box));
std::vector<geom::triangle<vertex>> vertices;
std::vector<math::triangle<vertex>> vertices;
for (auto const & t : triangles)
{
auto & r = vertices.emplace_back();
auto n = geom::normal(t[0], t[1], t[2]);
auto n = math::normal(t[0], t[1], t[2]);
std::size_t tcm = 0;
if (std::abs(n[1]) > std::abs(n[tcm])) tcm = 1;
@ -163,7 +162,7 @@ deferred_app::deferred_app()
{
std::vector<vertex> vertices;
geom::point<float, 3> const origin {0.f, 0.f, 0.f};
math::point<float, 3> const origin {0.f, 0.f, 0.f};
float const radius = 1.f;
@ -175,19 +174,19 @@ deferred_app::deferred_app()
{
for (int i = 0; i < 4 * N; ++i)
{
float a = (geom::pi * i) / (2 * N);
float b = (geom::pi * j) / (2 * N);
float a = (math::pi * i) / (2 * N);
float b = (math::pi * j) / (2 * N);
geom::vector n{std::cos(a) * std::cos(b), std::sin(a) * std::cos(b), std::sin(b)};
math::vector n{std::cos(a) * std::cos(b), std::sin(a) * std::cos(b), std::sin(b)};
vertices.push_back({origin + radius * n, c, {0.f, 0.f}, n});
}
}
vertices.push_back({origin + geom::vector{0.f, 0.f, -radius}, c, {0.f, 0.f}, {0.f, 0.f, -1.f}});
vertices.push_back({origin + geom::vector{0.f, 0.f, radius}, c, {0.f, 0.f}, {0.f, 0.f, 1.f}});
vertices.push_back({origin + math::vector{0.f, 0.f, -radius}, c, {0.f, 0.f}, {0.f, 0.f, -1.f}});
vertices.push_back({origin + math::vector{0.f, 0.f, radius}, c, {0.f, 0.f}, {0.f, 0.f, 1.f}});
std::vector<geom::triangle<std::uint32_t>> indices;
std::vector<math::triangle<std::uint32_t>> indices;
auto idx = [](int i, int j) -> std::uint32_t { return (i % (4 * N)) + 4 * N * (j + N - 1); };
@ -217,7 +216,7 @@ deferred_app::deferred_app()
{
std::vector<vertex> vertices;
geom::point<float, 3> const position = {0.f, 0.f, 0.f};
math::point<float, 3> const position = {0.f, 0.f, 0.f};
float const radius1 = 0.8f;
float const radius2 = 0.2f;
@ -231,18 +230,18 @@ deferred_app::deferred_app()
{
for (int i = 0; i < N; ++i)
{
float a = (2.f * geom::pi * i) / N;
float b = (2.f * geom::pi * j) / M;
float a = (2.f * math::pi * i) / N;
float b = (2.f * math::pi * j) / M;
geom::vector r{std::cos(a), std::sin(a), 0.f};
math::vector r{std::cos(a), std::sin(a), 0.f};
geom::vector n{std::cos(a) * std::cos(b), std::sin(a) * std::cos(b), std::sin(b)};
math::vector n{std::cos(a) * std::cos(b), std::sin(a) * std::cos(b), std::sin(b)};
vertices.push_back({position + radius1 * r + radius2 * n, color, {0.f, 0.f}, n});
}
}
std::vector<geom::triangle<std::uint32_t>> indices;
std::vector<math::triangle<std::uint32_t>> indices;
auto idx = [](int i, int j) -> std::uint32_t { return (i % N) + N * (j % M); };
@ -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);
camera.set_fov(camera.fov_y, static_cast<float>(width) / height);
app::application_base::on_event(event);
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<math::vector<std::uint16_t, 3>>(math::cast<std::size_t>(event.size));
pre_gamma_framebuffer.color(pre_gamma_texture);
pre_gamma_framebuffer.assert_complete();
pre_fxaa_texture.load<gfx::color_rgb>({width, height});
pre_fxaa_texture.load<gfx::color_rgb>(math::cast<std::size_t>(event.size));
pre_fxaa_framebuffer.color(pre_fxaa_texture);
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;
camera.elevation_angle += dy * 0.01f;
auto delta = event.position - old_mouse;
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()
@ -339,7 +343,7 @@ void deferred_app::present()
{
gfx::deferred_renderer::object obj;
obj.mesh = &plane;
obj.pre_transform = geom::scale<float, 3>(10.f).affine_matrix();
obj.pre_transform = math::scale<float, 3>(10.f).affine_matrix();
obj.bbox = {{{-10.f, 10.f}, {-10.f, 10.f}, {0.f, 0.f}}};
obj.mat = &plane_material;
objects.push_back(obj);
@ -354,7 +358,7 @@ void deferred_app::present()
{
gfx::deferred_renderer::object obj;
obj.mesh = &cube;
obj.pre_transform = geom::translation<float, 3>{geom::vector{x, y, 3.f}}.affine_matrix();
obj.pre_transform = math::translation<float, 3>{math::vector{x, y, 3.f}}.affine_matrix();
obj.bbox = {{{x - 1.f, x + 1.f}, {y - 1.f, y + 1.f}, {2.f, 4.f}}};
obj.mat = &cube_material;
objects.push_back(obj);
@ -370,7 +374,7 @@ void deferred_app::present()
{
gfx::deferred_renderer::object obj;
obj.mesh = &sphere;
obj.pre_transform = geom::translation<float, 3>{geom::vector{0.f, 0.f, z}}.affine_matrix();
obj.pre_transform = math::translation<float, 3>{math::vector{0.f, 0.f, z}}.affine_matrix();
obj.bbox = {{{-1.f, 1.f}, {-1.f, 1.f}, {z - 1.f, z + 1.f}}};
obj.mat = &sphere_material;
objects.push_back(obj);
@ -379,7 +383,7 @@ void deferred_app::present()
{
gfx::deferred_renderer::object obj;
obj.mesh = &torus;
obj.pre_transform = geom::translation<float, 3>{geom::vector{0.f, 0.f, 4.f}}.affine_matrix();
obj.pre_transform = math::translation<float, 3>{math::vector{0.f, 0.f, 4.f}}.affine_matrix();
obj.bbox = {{{-1.f, 1.f}, {-1.f, 1.f}, {4 - 0.2f, 4 + 0.2f}}};
obj.mat = &sphere_material;
objects.push_back(obj);
@ -388,7 +392,7 @@ void deferred_app::present()
gfx::deferred_renderer::options options;
options.camera = &camera;
options.clear_color = geom::vector{0.f, 0.f, 0.1f};
options.clear_color = math::vector{0.f, 0.f, 0.1f};
options.ambient = {1.f, 1.f, 1.f};
options.directional_lights.emplace_back();
@ -415,7 +419,7 @@ void deferred_app::present()
for (int i = 0; i < 24; ++i)
{
float a = (i * geom::pi) / 12.f;
float a = (i * math::pi) / 12.f;
auto & l = options.point_lights.emplace_back();
l.color = {15.f, 15.f, 15.f};
@ -433,9 +437,9 @@ void deferred_app::present()
float const s = 0.1f;
gfx::deferred_renderer::object obj;
obj.mesh = &sphere;
obj.pre_transform = (geom::translation<float, 3>{l.position - geom::point<float, 3>::zero()}.transform() * geom::scale<float, 3>(s).transform()).affine_matrix();
obj.pre_transform = (math::translation<float, 3>{l.position - math::point<float, 3>::zero()}.transform() * math::scale<float, 3>(s).transform()).affine_matrix();
obj.bbox = {{{l.position[0] - s, l.position[0] + s}, {l.position[1] - s, l.position[1] + s}, {l.position[2] - s, l.position[2] + s}}};
light_materials[i].color = geom::vector{l.color[0], l.color[1], l.color[2], 1.f};
light_materials[i].color = math::vector{l.color[0], l.color[1], l.color[2], 1.f};
light_materials[i].lit = false;
light_materials[i].casts_shadow = false;
obj.mat = &light_materials[i];
@ -458,7 +462,7 @@ void deferred_app::present()
gfx::render_target target;
target.framebuffer = &pre_gamma_framebuffer;
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);
}
@ -466,7 +470,7 @@ void deferred_app::present()
gfx::render_target target;
target.framebuffer = &pre_fxaa_framebuffer;
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});
}
@ -474,13 +478,13 @@ void deferred_app::present()
gfx::render_target target;
target.framebuffer = &gfx::framebuffer::null();
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);
}
{
gfx::painter::text_options opts;
opts.scale = 2.f;
opts.scale = {2.f, 2.f};
opts.f = gfx::painter::font::font_9x12;
opts.x = gfx::painter::x_align::left;
opts.y = gfx::painter::y_align::top;
@ -491,10 +495,15 @@ void deferred_app::present()
gl::Enable(gl::BLEND);
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
painter.render(geom::window_camera{width(), height()}.transform());
painter.render(math::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

@ -0,0 +1,167 @@
#include <psemek/audio/engine.hpp>
#include <psemek/audio/wave/silence.hpp>
#include <psemek/audio/wave/sine.hpp>
#include <psemek/audio/wave/sawtooth.hpp>
#include <psemek/audio/wave/square.hpp>
#include <psemek/audio/wave/triangle.hpp>
#include <psemek/audio/wave/karplus_strong.hpp>
#include <psemek/audio/effect/volume.hpp>
#include <psemek/audio/effect/fade_in.hpp>
#include <psemek/audio/effect/fade_out.hpp>
#include <psemek/audio/effect/compressor.hpp>
#include <psemek/audio/effect/pause.hpp>
#include <psemek/audio/effect/truncate.hpp>
#include <psemek/audio/combine/concat.hpp>
#include <psemek/audio/combine/loop.hpp>
#include <psemek/audio/effect/pitch.hpp>
#include <psemek/audio/effect/distortion.hpp>
#include <psemek/audio/combine/duplicate.hpp>
#include <psemek/audio/combine/stereo.hpp>
#include <psemek/audio/combine/mixer.hpp>
#include <psemek/audio/midi.hpp>
#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/log/log.hpp>
#include <psemek/util/ndarray.hpp>
#include <unordered_map>
// Inspired by https://www.youtube.com/watch?v=_aIf4WUCNZU
using namespace psemek;
std::vector<std::vector<math::point<int, 2>>> fibonacci_cycles(int n)
{
util::ndarray<int, 2> cycle({n, n}, -1);
std::vector<std::vector<math::point<int, 2>>> result;
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
if (cycle(i, j) != -1)
continue;
int cycle_id = result.size();
auto & current = result.emplace_back();
math::point p{i, j};
while (true)
{
current.push_back(p);
cycle(p[0], p[1]) = cycle_id;
p = {p[1], (p[0] + p[1]) % n};
if (p == math::point{i, j})
break;
}
}
}
return result;
}
std::vector<int> major_scale()
{
return {0, 2, 4, 5, 7, 9, 11};
}
std::vector<int> minor_scale()
{
return {0, 2, 3, 5, 7, 8, 10};
}
std::shared_ptr<audio::stream> note(int i, float duration)
{
return audio::fade_in(audio::fade_out(audio::karplus_strong(audio::midi_frequency(69 + i)), duration), duration / 16.f);
}
std::shared_ptr<audio::track> generate(std::vector<int> const & scale, std::vector<math::point<int, 2>> const & cycle, float speed)
{
auto mixer = audio::make_mixer();
for (int i = 0; i < cycle.size(); ++i)
mixer->add(audio::concat({audio::truncate(audio::silence(), speed * i), note(scale[cycle[i][0] % scale.size()] + 12 * (cycle[i][0] / scale.size()), speed)}));
return audio::record(mixer, speed * cycle.size());
}
struct fibonacci_music_box_app
: app::application_base
{
fibonacci_music_box_app(options const &, context const &)
{
auto cycles = fibonacci_cycles(16);
auto scale = minor_scale();
float speed = 0.25f;
log::info() << "Found cycles of length:";
for (int i = 0; i < cycles.size(); ++i)
{
log::info() << " #" << i << ": " << cycles[i].size();
cycles_.push_back(audio::loop(generate(scale, cycles[i], speed)));
}
channels_.resize(cycles_.size());
engine_ = audio::make_engine();
mixer_ = audio::make_mixer();
auto compressor = audio::compressor(mixer_, audio::from_db(-2.f), 0.95f, 0.002f, 1.f, audio::from_db(1.f));
engine_->output()->stream(compressor);
}
void on_event(app::key_event const & event) override
{
app::application_base::on_event(event);
if (event.down && event.key >= app::keycode::NUM_1 && event.key <= app::keycode::NUM_0)
{
int i;
if (event.key == app::keycode::NUM_0)
i = 0;
else
i = 1 + (int(event.key) - int(app::keycode::NUM_1));
log::info() << "Pressed " << i;
if (i < channels_.size())
{
if (channels_[i])
{
channels_[i]->stop();
channels_[i] = nullptr;
}
else
{
channels_[i] = mixer_->add(cycles_[i]);
}
}
}
}
void update() override
{}
void present() override
{}
private:
std::unique_ptr<audio::engine> engine_;
audio::mixer_ptr mixer_;
std::vector<audio::stream_ptr> cycles_;
std::vector<audio::channel_ptr> channels_;
};
namespace psemek::app
{
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<fibonacci_music_box_app>({.name = "Fibonacci music box"});
}
}

View file

@ -1,15 +1,15 @@
#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/mesh.hpp>
#include <psemek/gfx/program.hpp>
#include <psemek/gfx/texture.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/math/camera.hpp>
#include <psemek/math/math.hpp>
#include <psemek/math/rotation.hpp>
#include <psemek/math/scale.hpp>
#include <psemek/math/translation.hpp>
#include <psemek/math/easing.hpp>
#include <psemek/math/gradient.hpp>
#include <psemek/random/device.hpp>
#include <psemek/random/generator.hpp>
#include <psemek/random/uniform_sphere.hpp>
@ -81,17 +81,17 @@ struct candle_renderer
{
candle_renderer();
void add(geom::point<float, 3> const & pos, geom::vector<float, 3> const & dir, float size);
void add(math::point<float, 3> const & pos, math::vector<float, 3> const & dir, float size);
void render(geom::camera const & camera, float time);
void render(math::camera const & camera, float time);
private:
struct candle
{
geom::point<float, 3> pos;
geom::vector<float, 3> dir;
math::point<float, 3> pos;
math::vector<float, 3> dir;
float size;
geom::vector<float, 2> noise_offset;
math::vector<float, 2> noise_offset;
};
std::vector<candle> candles_;
@ -109,7 +109,7 @@ private:
candle_renderer::candle_renderer()
{
std::vector<geom::point<float, 3>> vertices;
std::vector<math::point<float, 3>> vertices;
vertices.push_back({-1.f, 0.f, -1.f});
vertices.push_back({ 1.f, 0.f, -1.f});
vertices.push_back({ 1.f, 0.f, 1.f});
@ -117,25 +117,25 @@ candle_renderer::candle_renderer()
vertices.push_back({ 1.f, 0.f, 1.f});
vertices.push_back({-1.f, 0.f, 1.f});
mesh_.setup<geom::point<float, 3>, gfx::instanced<geom::point<float, 3>>, gfx::instanced<geom::vector<float, 3>>, gfx::instanced<float>, gfx::instanced<geom::vector<float, 2>>>();
mesh_.setup<math::point<float, 3>, gfx::instanced<math::point<float, 3>>, gfx::instanced<math::vector<float, 3>>, gfx::instanced<float>, gfx::instanced<math::vector<float, 2>>>();
mesh_.load(vertices, gl::TRIANGLES);
geom::vector<float, 4> c0 {1.f, 0.99f, 0.98f, 1.f};
geom::vector<float, 4> c1 {1.f, 0.4f, 0.f, 0.75f};
geom::vector<float, 4> c2 {c1[0], c1[1], c1[2], 0.f};
math::vector<float, 4> c0 {1.f, 0.99f, 0.98f, 1.f};
math::vector<float, 4> c1 {1.f, 0.4f, 0.f, 0.75f};
math::vector<float, 4> c2 {c1[0], c1[1], c1[2], 0.f};
geom::gradient<float> gy{
math::gradient<float> gy{
std::make_pair(0.f, 0.f),
geom::easing_type::quadratic_in,
math::easing_type::quadratic_in,
std::make_pair(1.f, 2.f),
};
geom::gradient<float, geom::vector<float, 4>> gc
math::gradient<float, math::vector<float, 4>> gc
{
std::make_pair(0.1f, c0),
geom::easing_type::linear,
math::easing_type::linear,
std::pair{0.8f, c1},
geom::easing_type::cubic,
math::easing_type::cubic,
std::pair{1.2f, c2}
};
@ -172,7 +172,7 @@ candle_renderer::candle_renderer()
random::generator rng;
random::uniform_sphere_vector_distribution<float, 2> d;
util::array<geom::vector<float, 2>, 2> grad({16, 16});
util::ndarray<math::vector<float, 2>, 2> grad({16, 16});
for (auto & v : grad) v = d(rng);
pcg::perlin<float, 2> perlinx(grad, pcg::seamless);
@ -180,13 +180,13 @@ candle_renderer::candle_renderer()
for (auto & v : grad) v = d(rng);
pcg::perlin<float, 2> perliny(grad, pcg::seamless);
gfx::basic_pixmap<geom::vector<std::uint8_t, 2>> pm({512, 512});
gfx::basic_pixmap<math::vector<std::uint8_t, 2>> pm({512, 512});
for (auto idx : pm.indices())
{
float x = (0.5f + idx[0]) / pm.width();
float y = (0.5f + idx[1]) / pm.height();
pm(idx) = gfx::to_coloru8(geom::vector{perlinx({x, y}), perliny({x, y})});
pm(idx) = gfx::to_coloru8(math::vector{perlinx({x, y}), perliny({x, y})});
}
noise_texture_.load(pm);
noise_texture_.linear_filter();
@ -196,11 +196,11 @@ candle_renderer::candle_renderer()
}
}
void candle_renderer::add(geom::point<float, 3> const & pos, geom::vector<float, 3> const & dir, float size)
void candle_renderer::add(math::point<float, 3> const & pos, math::vector<float, 3> const & dir, float size)
{
random::generator rng{random::device{}};
random::uniform_sphere_vector_distribution<float, 2> d;
geom::vector<float, 2> noise_offset;
math::vector<float, 2> noise_offset;
while (true)
{
noise_offset = d(rng);
@ -215,7 +215,7 @@ void candle_renderer::add(geom::point<float, 3> const & pos, geom::vector<float,
instances_need_update_ = true;
}
void candle_renderer::render(geom::camera const & camera, float time)
void candle_renderer::render(math::camera const & camera, float time)
{
if (instances_need_update_) update_instances();
@ -236,10 +236,10 @@ void candle_renderer::update_instances()
{
struct instance
{
geom::point<float, 3> pos;
geom::vector<float, 3> dir;
math::point<float, 3> pos;
math::vector<float, 3> dir;
float size;
geom::vector<float, 2> noise_offset;
math::vector<float, 2> noise_offset;
};
std::vector<instance> instances;
@ -254,19 +254,18 @@ void candle_renderer::update_instances()
}
struct fire_app
: app::app
: app::application_base
{
geom::spherical_camera camera;
math::spherical_camera camera;
candle_renderer candles;
util::clock<std::chrono::duration<float>> clock;
float time = 0.f;
fire_app()
: app("Fire")
fire_app(options const &, context const &)
{
camera.fov_y = geom::rad(45.f);
camera.fov_y = math::rad(45.f);
camera.near_clip = 0.01f;
camera.far_clip = 1000.f;
camera.target = {0.f, 0.f, 0.f};
@ -277,32 +276,36 @@ struct fire_app
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);
camera.set_fov(camera.fov_y, (1.f * width) / height);
app::application_base::on_event(event);
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;
camera.elevation_angle += dy * 0.01f;
auto const delta = event.position - old_mouse;
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);
camera.distance *= std::pow(0.8f, delta);
app::application_base::on_event(event);
camera.distance *= std::pow(0.8f, event.delta);
}
void update() override
{
if (!is_key_down(SDLK_SPACE))
if (!state().key_down.contains(app::keycode::SPACE))
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>();
}

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View file

@ -1,5 +1,7 @@
#include <psemek/parser/primitives.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <iostream>
#include <fstream>
#include <iterator>
@ -74,14 +76,23 @@ Stream & operator << (Stream & s, std::variant<T, Ts...> const & v)
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){
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::unique_ptr<application::factory> make_application_factory()
{
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

@ -0,0 +1,441 @@
#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/gfx/gl.hpp>
#include <psemek/math/camera.hpp>
#include <psemek/math/contains.hpp>
#include <psemek/cg/kdtree.hpp>
#include <psemek/random/device.hpp>
#include <psemek/random/generator.hpp>
#include <psemek/random/uniform.hpp>
#include <psemek/random/uniform_box.hpp>
#include <psemek/util/clock.hpp>
#include <psemek/util/ndarray.hpp>
#include <psemek/log/log.hpp>
#include <format>
using namespace psemek;
struct particle
{
math::point<float, 2> position;
math::vector<float, 2> velocity;
float mass;
float radius;
int type;
};
struct particle_life_2d_app
: app::application_base
{
particle_life_2d_app(options const &, context const &)
{
float S = 5.f;
float W = 160.f * S;
float H = 90.f * S;
area_ = {{{-W, W}, {-H, H}}};
types_ = 10;
auto seed = random::device{}();
// Explosions
// types_ = 6;
// seed = 3940378904;
// Cells
// types_ = 8;
// seed = 3071915692;
// Cells v2
// types_ = 6;
// seed = 3337663839;
// Cool cells!
types_ = 10;
seed = 388834085;
// One cell, use central gravity
// types_ = 10;
// seed = 175098945;
// Mitochondria cell
// types_ = 10;
// seed = 1763331406;
// Just fun
// types_ = 10;
// seed = 1676103531;
// Another cell
// types_ = 10;
// seed = 2396049785;
log::info() << "Seed: " << seed;
random::generator rng{seed, 0};
random::uniform_distribution<int> dcolor(63, 255);
random::uniform_distribution<int> dtype(0, types_ - 1);
random::uniform_box_point_distribution<float, 2> darea(area_);
for (int i = 0; i < types_; ++i)
colors_.push_back({dcolor(rng), dcolor(rng), dcolor(rng), 255});
for (int i = 0; i < 4 * 1024; ++i)
{
particles_.push_back({
.position = darea(rng),
.velocity = {0.f, 0.f},
.mass = 1.f,
.radius = 1.f,
.type = dtype(rng),
// .type = random::uniform_from(rng, {4, 6}),
});
}
auto dforce = [](auto & rng)
{
return random::uniform(rng, 20.f, 100.f) * (random::uniform<bool>(rng) ? 1.f : -1.f);
};
random::uniform_distribution<float> ddistance(3.f, 20.f);
force_constants_.resize({types_, types_});
force_distance_.resize({types_, types_});
collision_distance_.resize({types_, types_});
for (int i = 0; i < types_; ++i)
for (int j = 0; j < types_; ++j)
force_constants_(i, j) = dforce(rng);
for (int i = 0; i < types_; ++i)
for (int j = 0; j < types_; ++j)
force_distance_(i, j) = ddistance(rng);
for (int i = 0; i < types_; ++i)
for (int j = 0; j < types_; ++j)
collision_distance_(i, j) = random::uniform(rng, 0.125f, 0.75f) * force_distance_(i, j);
// for (int i = 0; i < types_; ++i)
// for (int j = 0; j < types_; ++j)
// force_constants_(i, j) = -100.f * ((i == j) ? 1.f : 2.f);
max_force_distance_.resize(types_, 2.f);
for (int i = 0; i < types_; ++i)
for (int j = 0; j < types_; ++j)
math::make_max(max_force_distance_[i], force_distance_(i, j));
}
void on_event(app::mouse_wheel_event const & event) override
{
app::application_base::on_event(event);
scale_target_ *= std::pow(1.25f, -event.delta);
}
void on_event(app::key_event const & event) override
{
app::application_base::on_event(event);
if (event.down && event.key == app::keycode::SPACE)
paused_ ^= true;
}
void update() override
{
float const dt = 0.02f;
scale_ += (scale_target_ - scale_) * (- std::expm1(- 20.f * dt));
auto visible_area = area_;
visible_area = math::expand(visible_area, 0.5f * (scale_ - 1.f) * visible_area.dimensions());
float window_aspect_ratio = state().size[0] * 1.f / state().size[1];
float area_aspect_ratio = visible_area[0].length() / visible_area[1].length();
if (area_aspect_ratio < window_aspect_ratio)
{
view_area_[1] = visible_area[1];
view_area_[0] = math::expand(math::interval<float>::singleton(visible_area[0].center()), visible_area[1].length() * window_aspect_ratio * 0.5f);
}
else
{
view_area_[0] = visible_area[0];
view_area_[1] = math::expand(math::interval<float>::singleton(visible_area[1].center()), visible_area[0].length() / window_aspect_ratio * 0.5f);
}
if (!paused_)
for (int step = 0; step < 5; ++step)
{
cg::kdtree<float, 2, int> kdtree;
for (int i = 0; i < particles_.size(); ++i)
kdtree.insert({particles_[i].position, i});
float const collision_strength = 200.f;
mouseover_particle_ = std::nullopt;
if (state().mouse_button_down.contains(app::mouse_button::left))
{
math::point<float, 2> m;
m[0] = math::lerp(view_area_[0], state().mouse[0] * 1.f / state().size[0]);
m[1] = math::lerp(view_area_[1], 1.f - state().mouse[1] * 1.f / state().size[1]);
mouseover_particle_ = kdtree.closest(m).data;
}
#pragma omp parallel for
for (int i = 0; i < particles_.size(); ++i)
{
// ======== kd-tree based algorithm ========
auto & pi = particles_[i];
auto f = math::vector{0.f, 0.f};
kdtree.closer_than_map(pi.position, max_force_distance_[pi.type], [&](auto const & value){
auto j = value.data;
if (i == j)
return;
auto const & pj = particles_[j];
auto r = pj.position - pi.position;
float l = math::length(r);
auto n = r / l;
// ==== 1/R forces ====
// float lg = std::max(0.5f, l);
// auto g = n / (lg * lg);
// if (l < force_distance_(pi.type, pj.type))
// f += force_constants_(pi.type, pj.type) * g;
// float collision_distance = pi.radius + pj.radius;
// if (l < collision_distance)
// {
// float d = std::max(0.25f, l / collision_distance);
// f -= collision_strength * n * (1.f / (d * d) - 1.f);
// }
// ==== Linear forces ====
if (l < force_distance_(pi.type, pj.type))
f += force_constants_(pi.type, pj.type) * n * (1.f - l / force_distance_(pi.type, pj.type));
if (l < collision_distance_(pi.type, pj.type))
f -= 10.f * std::abs(force_constants_(pi.type, pj.type)) * n * (1.f - l / collision_distance_(pi.type, pj.type));
(void)collision_strength;
});
pi.velocity += f * dt / pi.mass;
// ======== Naive algorithm v2 ========
// auto & pi = particles_[i];
// auto f = math::vector{0.f, 0.f};
// for (int j = 0; j < particles_.size(); ++j)
// {
// if (i == j)
// continue;
// auto const & pj = particles_[j];
// auto r = pj.position - pi.position;
// float l = math::length(r);
// auto n = r / l;
// auto g = n / (l * l);
// if (l < force_distance_(pi.type, pj.type))
// f += force_constants_(pi.type, pj.type) * g;
// if (l < pi.radius + pj.radius)
// {
// float d = l / (pi.radius + pj.radius);
// f -= collision_strength * n * (1.f / (d * d) - 1.f);
// }
// }
// pi.velocity += f * dt / pi.mass;
// ======== Naive algorithm ========
// for (int j = i + 1; j < particles_.size(); ++j)
// {
// auto & pi = particles_[i];
// auto & pj = particles_[j];
// auto fi = math::vector{0.f, 0.f};
// auto fj = math::vector{0.f, 0.f};
// auto r = pj.position - pi.position;
// float l = math::length(r);
// auto n = r / l;
// auto g = n / (l * l);
// if (l < force_distance_(pi.type, pj.type))
// fi += force_constants_(pi.type, pj.type) * g;
// if (l < force_distance_(pj.type, pi.type))
// fj -= force_constants_(pj.type, pi.type) * g;
// if (l < pi.radius + pj.radius)
// {
// float d = l / (pi.radius + pj.radius);
// auto f = collision_strength * n * (1.f / (d * d) - 1.f);
// fi -= f;
// fj += f;
// }
// pi.velocity += fi * dt / pi.mass;
// pj.velocity += fj * dt / pj.mass;
// }
}
float const friction = 10.f;
float const friction_factor = std::exp(- friction * dt);
float const wall_distance = 0.f;
float const wall_force = 10.f;
float const gravity = 0.f;
float const center_gravity = 0.f;
float const line_gravity = 0.f;
float const circle_gravity = 0.f;
float const circle_radius = 100.f;
bool const circle_wall = false;
float const circle_wall_radius = 100.f;
for (auto & p : particles_)
{
auto r = p.position - p.position.zero();
p.velocity[1] -= gravity * dt;
p.velocity[1] -= line_gravity * p.position[1] * dt;
p.velocity -= center_gravity * r * dt;
p.velocity += circle_gravity * r / math::length(r) * (circle_radius - math::length(r)) * dt;
p.velocity *= friction_factor;
p.position += p.velocity * dt;
for (int d : {0, 1})
{
// if (p.position[d] < area_[d].min + p.radius)
// {
// p.position[d] = area_[d].min + p.radius;
// p.velocity[d] *= -1.f;
// }
// if (p.position[d] > area_[d].max - p.radius)
// {
// p.position[d] = area_[d].max - p.radius;
// p.velocity[d] *= -1.f;
// }
if (circle_wall)
{
auto r = p.position - p.position.zero();
auto l = math::length(r);
if (l > circle_wall_radius)
{
p.velocity -= wall_force * r / l * (l - circle_wall_radius) * dt;
}
}
else
{
if (p.position[d] < area_[d].min + wall_distance)
{
p.velocity[d] += wall_force * (area_[d].min + wall_distance - p.position[d]) * dt;
}
if (p.position[d] > area_[d].max - wall_distance)
{
p.velocity[d] -= wall_force * (p.position[d] - (area_[d].max - wall_distance)) * dt;
}
}
}
}
}
}
void present() override
{
gl::Viewport(0, 0, state().size[0], state().size[1]);
gl::ClearColor(0.02f, 0.02f, 0.02f, 1.f);
gl::Clear(gl::COLOR_BUFFER_BIT);
for (auto const & p : particles_)
{
auto c0 = colors_[p.type];
c0[3] = 15;
auto c1 = colors_[p.type];
c1[3] = 0;
painter_.circle(p.position, p.radius * 8.f, c0, c1);
}
for (auto const & p : particles_)
painter_.circle(p.position, p.radius, colors_[p.type]);
painter_.render(math::orthographic_camera{view_area_}.transform());
if (mouseover_particle_)
{
int type = particles_[*mouseover_particle_].type;
painter_.text({state().size[0] / 2.f, state().size[1] - 20.f}, std::format("Species #{}", type), {
.scale = {2.f, 2.f},
.y = gfx::painter::y_align::bottom,
.c = colors_[type],
});
}
painter_.render(math::window_camera{state().size[0], state().size[1]}.transform());
}
private:
gfx::painter painter_;
util::clock<std::chrono::duration<float>, std::chrono::high_resolution_clock> clock_;
math::box<float, 2> area_;
math::box<float, 2> view_area_;
float scale_ = 1.f;
float scale_target_ = 1.f;
int types_;
std::vector<gfx::color_rgba> colors_;
util::ndarray<float, 2> force_constants_;
util::ndarray<float, 2> force_distance_;
util::ndarray<float, 2> collision_distance_;
std::vector<float> max_force_distance_;
std::vector<particle> particles_;
std::optional<int> mouseover_particle_;
bool paused_ = true;
};
namespace psemek::app
{
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<particle_life_2d_app>({.name = "Particle Life 2D"});
}
}

View file

@ -1,22 +1,24 @@
#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/gl.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/geom/point.hpp>
#include <psemek/geom/simplex.hpp>
#include <psemek/geom/orthographic.hpp>
#include <psemek/geom/rotation.hpp>
#include <psemek/geom/camera.hpp>
#include <psemek/geom/math.hpp>
#include <psemek/geom/distance.hpp>
#include <psemek/math/point.hpp>
#include <psemek/math/simplex.hpp>
#include <psemek/math/orthographic.hpp>
#include <psemek/math/rotation.hpp>
#include <psemek/math/camera.hpp>
#include <psemek/math/math.hpp>
#include <psemek/math/distance.hpp>
#include <psemek/util/clock.hpp>
#include <psemek/util/unused.hpp>
#include <psemek/util/to_string.hpp>
#include <psemek/util/statistics.hpp>
#include <psemek/log/log.hpp>
#include <vector>
using namespace psemek;
@ -48,11 +50,11 @@ namespace psemek::util
struct stick_model
{
std::vector<geom::point<float, 2>> points;
std::vector<geom::vector<float, 2>> vels;
std::vector<math::point<float, 2>> points;
std::vector<math::vector<float, 2>> vels;
std::vector<bool> movable;
std::vector<geom::segment<std::uint32_t>> sticks;
std::vector<math::segment<std::uint32_t>> sticks;
std::vector<float> stick_length;
std::vector<bool> stick_solid;
@ -62,36 +64,33 @@ struct stick_model
void stick_model::add_stick(std::size_t i, std::size_t j, bool solid)
{
sticks.push_back({i, j});
stick_length.push_back(geom::distance(points[i], points[j]));
stick_length.push_back(math::distance(points[i], points[j]));
stick_solid.push_back(solid);
}
static const float ball_radius = 0.1f;
struct physics_demo_app
: app::app
: app::application_base
{
physics_demo_app();
physics_demo_app(options const &, context const &);
~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_left_button_up() override;
void on_event(app::mouse_button_event const & event) 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_mouse_wheel(int delta) override;
void on_event(app::mouse_wheel_event const & event) override;
void update() override;
void present() override;
geom::point<float, 2> screen_to_world(geom::point<float, 2> const & p) const;
geom::point<float, 2> world_to_screen(geom::point<float, 2> const & p) const;
math::point<float, 2> screen_to_world(math::point<float, 2> const & p) const;
math::point<float, 2> world_to_screen(math::point<float, 2> const & p) const;
util::clock<std::chrono::duration<float>> frame_clock;
float update_time_left = 0.f;
@ -102,11 +101,11 @@ struct physics_demo_app
gfx::painter painter;
geom::box<float, 2> view_region;
math::box<float, 2> view_region;
std::optional<std::size_t> closest_point;
std::optional<std::size_t> closest_stick;
std::optional<geom::vector<float, 2>> drag_delta;
std::optional<math::vector<float, 2>> drag_delta;
std::optional<std::size_t> new_spring_start;
@ -115,8 +114,7 @@ struct physics_demo_app
util::statistics<float> physics_update_stats;
};
physics_demo_app::physics_demo_app()
: app("Physics Demo", 4)
physics_demo_app::physics_demo_app(options const &, context const &)
{
view_region[0] = {0.f, 0.f};
view_region[1] = {-2.f, 5.f};
@ -167,7 +165,7 @@ physics_demo_app::physics_demo_app()
for (int i = 0; i < N; ++i)
{
float a = (2.f * geom::pi * i) / N;
float a = (2.f * math::pi * i) / N;
model.points.push_back({std::cos(a) * 0.5f, y + std::sin(a) * 0.5f});
}
@ -195,7 +193,7 @@ physics_demo_app::physics_demo_app()
model.add_stick(3, 4);
}
model.vels.assign(model.points.size(), geom::vector<float, 2>::zero());
model.vels.assign(model.points.size(), math::vector<float, 2>::zero());
model.movable.assign(model.points.size(), true);
}
@ -204,11 +202,11 @@ physics_demo_app::~physics_demo_app()
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 yl = view_region[1].length();
@ -217,56 +215,53 @@ void physics_demo_app::on_resize(int width, int height)
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());
model.vels[*closest_point] = geom::vector<float, 2>::zero();
drag_delta = world_to_screen(model.points[*closest_point]) - math::cast<float>(state().mouse);
model.vels[*closest_point] = math::vector<float, 2>::zero();
}
if (event.button == app::mouse_button::left && !event.down)
{
drag_delta = std::nullopt;
}
if (event.button == app::mouse_button::right && event.down)
{
if (new_spring_start)
{
new_spring_start = std::nullopt;
}
else if (closest_point)
{
model.movable[*closest_point] = !model.movable[*closest_point];
model.vels[*closest_point] = math::vector<float, 2>::zero();
}
}
}
void physics_demo_app::on_left_button_up()
void physics_demo_app::on_event(app::mouse_move_event const & event)
{
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;
}
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)
{
app::on_mouse_move(x, y, dx, dy);
app::application_base::on_event(event);
if (!drag_delta)
{
closest_point = std::nullopt;
closest_stick = std::nullopt;
if (mouse())
{
auto const m = geom::cast<float>(*mouse());
auto const m = math::cast<float>(state().mouse);
std::size_t closest = 0;
float distance = std::numeric_limits<float>::infinity();
for (std::size_t i = 0; i < model.points.size(); ++i)
{
auto const d = geom::distance(m, world_to_screen(model.points[i]));
auto const d = math::distance(m, world_to_screen(model.points[i]));
if (d < distance)
{
distance = d;
@ -286,7 +281,7 @@ void physics_demo_app::on_mouse_move(int x, int y, int dx, int dy)
for (std::size_t i = 0; i < model.sticks.size(); ++i)
{
auto const d = geom::distance(m, geom::simplex{world_to_screen(model.points[model.sticks[i][0]]), world_to_screen(model.points[model.sticks[i][1]])});
auto const d = math::distance(m, math::simplex{world_to_screen(model.points[model.sticks[i][0]]), world_to_screen(model.points[model.sticks[i][1]])});
if (d < distance)
{
distance = d;
@ -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;
}
else if (key == SDLK_x)
else if (event.down && event.key == app::keycode::X)
{
if (closest_point)
{
@ -317,7 +314,7 @@ void physics_demo_app::on_key_down(SDL_Keycode key)
model.vels.erase(model.vels.begin() + *closest_point);
model.movable.erase(model.movable.begin() + *closest_point);
std::vector<geom::segment<std::uint32_t>> new_sticks;
std::vector<math::segment<std::uint32_t>> new_sticks;
std::vector<float> new_stick_length;
std::vector<bool> new_stick_solid;
@ -350,7 +347,7 @@ void physics_demo_app::on_key_down(SDL_Keycode key)
closest_stick = std::nullopt;
}
}
else if (key == SDLK_c)
else if (event.down && event.key == app::keycode::C)
{
if (new_spring_start && closest_point)
{
@ -360,10 +357,10 @@ void physics_demo_app::on_key_down(SDL_Keycode key)
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.vels.push_back(geom::vector<float, 2>::zero());
model.points.push_back(screen_to_world(math::cast<float>(state().mouse)));
model.vels.push_back(math::vector<float, 2>::zero());
model.movable.push_back(false);
model.add_stick(*new_spring_start, model.points.size() - 1);
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;
closest_point = std::nullopt;
}
else if (mouse())
else
{
model.points.push_back(screen_to_world(geom::cast<float>(*mouse())));
model.vels.push_back(geom::vector<float, 2>::zero());
model.points.push_back(screen_to_world(math::cast<float>(state().mouse)));
model.vels.push_back(math::vector<float, 2>::zero());
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>(*mouse())));
model.vels.push_back(geom::vector<float, 2>::zero());
model.movable.push_back(true);
}
model.points.push_back(screen_to_world(math::cast<float>(state().mouse)));
model.vels.push_back(math::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)
{
model.stick_solid[*closest_stick] = !model.stick_solid[*closest_stick];
}
}
else if (key == SDLK_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)
else if (event.down && event.key == app::keycode::W)
{
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(math::cast<float>(state().mouse));
model.points.push_back(o);
model.vels.push_back(math::vector<float, 2>::zero());
model.movable.push_back(true);
float r = 0.5f;
for (int i = 0; i < N; ++i)
{
float a = (2.f * math::pi * i) / N;
model.points.push_back({o[0] + std::cos(a) * r, o[1] + std::sin(a) * r});
model.vels.push_back(math::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(math::cast<float>(state().mouse));
float w = 0.25f;
@ -440,10 +431,10 @@ void physics_demo_app::on_key_down(SDL_Keycode key)
model.points.push_back({o[0] - w, o[1] + w});
model.points.push_back({o[0] + w, o[1] + w});
model.vels.push_back(geom::vector<float, 2>::zero());
model.vels.push_back(geom::vector<float, 2>::zero());
model.vels.push_back(geom::vector<float, 2>::zero());
model.vels.push_back(geom::vector<float, 2>::zero());
model.vels.push_back(math::vector<float, 2>::zero());
model.vels.push_back(math::vector<float, 2>::zero());
model.vels.push_back(math::vector<float, 2>::zero());
model.vels.push_back(math::vector<float, 2>::zero());
model.movable.push_back(true);
model.movable.push_back(true);
model.movable.push_back(true);
@ -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)
{
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);
}
@ -490,7 +483,7 @@ void physics_demo_app::update()
float const stick_friction = 500.f;
float const stick_bounce = 0.9f;
geom::vector<float, 2> const gravity {0.f, -10.f};
math::vector<float, 2> const gravity {0.f, -10.f};
update_time_left += frame_clock.restart().count();
@ -514,7 +507,7 @@ void physics_demo_app::update()
std::vector<util::fixed_vector<std::array<int, 2>, 8>> ball_cells(model.points.size());
util::array<util::fixed_vector<std::uint32_t, 16>, 2> cells({std::ceil(view_region[0].length() / cell_size), std::ceil(view_region[1].length() / cell_size)});
util::ndarray<util::fixed_vector<std::uint32_t, 16>, 2> cells({std::ceil(view_region[0].length() / cell_size), std::ceil(view_region[1].length() / cell_size)});
float dx = view_region[0].length() / cells.width();
float dy = view_region[1].length() / cells.height();
@ -522,10 +515,10 @@ void physics_demo_app::update()
for (std::size_t i = 0; i < model.points.size(); ++i)
{
int x = std::floor(cells.width() * (model.points[i][0] - view_region[0].min) / view_region[0].length());
x = geom::clamp<int>(x, {0, cells.width() - 1});
x = math::clamp<int>(x, {0, cells.width() - 1});
int y = std::floor(cells.height() * (model.points[i][1] - view_region[1].min) / view_region[1].length());
y = geom::clamp<int>(y, {0, cells.height() - 1});
y = math::clamp<int>(y, {0, cells.height() - 1});
float cx = view_region[0].min + dx * x;
float cy = view_region[1].min + dy * y;
@ -584,9 +577,9 @@ void physics_demo_app::update()
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(math::cast<float>(state().mouse) + *drag_delta);
auto & point = model.points[*closest_point];
auto & vel = model.vels[*closest_point];
@ -598,7 +591,7 @@ void physics_demo_app::update()
fixed = closest_point;
}
std::vector<geom::vector<float, 2>> force(model.points.size(), geom::vector<float, 2>::zero());
std::vector<math::vector<float, 2>> force(model.points.size(), math::vector<float, 2>::zero());
for (std::size_t i = 0; i < model.points.size(); ++i)
{
@ -609,7 +602,7 @@ void physics_demo_app::update()
{
auto const & s = model.sticks[i];
auto d = model.points[s[1]] - model.points[s[0]];
auto l = geom::length(d);
auto l = math::length(d);
auto n = d / l;
auto f = - spring_constant * (l - model.stick_length[i]) * n / 2.f;
@ -627,17 +620,17 @@ void physics_demo_app::update()
model.vels[i] += dt * force[i];
}
std::vector<geom::vector<float, 2>> impulse(model.points.size(), geom::vector<float, 2>::zero());
std::vector<geom::vector<float, 2>> offset(model.points.size(), geom::vector<float, 2>::zero());
std::vector<math::vector<float, 2>> impulse(model.points.size(), math::vector<float, 2>::zero());
std::vector<math::vector<float, 2>> offset(model.points.size(), math::vector<float, 2>::zero());
for (std::size_t i = 0; i < model.sticks.size(); ++i)
{
auto const & s = model.sticks[i];
auto rel = model.vels[s[1]] - model.vels[s[0]];
auto n = geom::normalized(model.points[s[1]] - model.points[s[0]]);
auto n = math::normalized(model.points[s[1]] - model.points[s[0]]);
rel = n * geom::dot(rel, n);
rel = n * math::dot(rel, n);
impulse[s[0]] += rel / 2.f * spring_damping_constant * dt;
impulse[s[1]] -= rel / 2.f * spring_damping_constant * dt;
@ -646,7 +639,7 @@ void physics_demo_app::update()
for (std::size_t i = 0; i < model.points.size(); ++i)
{
bool collision = false;
geom::vector<float, 2> n;
math::vector<float, 2> n;
float d;
if (model.points[i][0] < view_region[0].min + ball_radius && model.vels[i][0] < 0.f)
@ -682,7 +675,7 @@ void physics_demo_app::update()
offset[i] += n * d;
(void)d;
auto nv = n * geom::dot(n, model.vels[i]);
auto nv = n * math::dot(n, model.vels[i]);
auto tv = model.vels[i] - nv;
auto jn = - (1.f + ground_bounce) * nv;
@ -691,9 +684,9 @@ void physics_demo_app::update()
unused(ground_mu);
// if (geom::length(jt) > ground_mu * geom::length(jn))
// if (math::length(jt) > ground_mu * math::length(jn))
// {
// jt = geom::normalized(jt) * ground_mu * geom::length(jn);
// jt = math::normalized(jt) * ground_mu * math::length(jn);
// }
impulse[i] += jn + jt;
@ -715,14 +708,14 @@ void physics_demo_app::update()
checked.push_back(j);
auto r = geom::distance(model.points[i], model.points[j]);
auto r = math::distance(model.points[i], model.points[j]);
if (r < ball_radius * 2.f)
{
auto n = (model.points[j] - model.points[i]) / r;
auto rel = model.vels[j] - model.vels[i];
auto d = geom::dot(rel, n);
auto d = math::dot(rel, n);
if (d < 0.f)
{
@ -752,23 +745,23 @@ void physics_demo_app::update()
if (i == s[0] || i == s[1]) continue;
auto d = geom::normalized(model.points[s[1]] - model.points[s[0]]);
auto d = math::normalized(model.points[s[1]] - model.points[s[0]]);
auto t = geom::dot(d, model.points[i] - model.points[s[0]]) / geom::distance(model.points[s[1]], model.points[s[0]]);
auto t = math::dot(d, model.points[i] - model.points[s[0]]) / math::distance(model.points[s[1]], model.points[s[0]]);
if (t < 0.f || t > 1.f) continue;
auto v = geom::lerp(model.vels[s[0]], model.vels[s[1]], t);
auto v = math::lerp(model.vels[s[0]], model.vels[s[1]], t);
auto rel = model.vels[i] - v;
auto n = geom::ort(d);
auto n = math::ort(d);
float r = geom::dot(n, model.points[i] - model.points[s[0]]);
float r = math::dot(n, model.points[i] - model.points[s[0]]);
if (geom::dot(rel, n) * r >= 0.f) continue;
if (math::dot(rel, n) * r >= 0.f) continue;
auto nv = n * geom::dot(rel, n);
auto nv = n * math::dot(rel, n);
auto tv = rel - nv;
unused(stick_friction);
@ -776,10 +769,10 @@ void physics_demo_app::update()
if (std::abs(r) >= ball_radius) continue;
geom::vector<float, 2> off = (r < 0.f ? -1.f : 1.f) * n * (ball_radius - std::abs(r));
math::vector<float, 2> off = (r < 0.f ? -1.f : 1.f) * n * (ball_radius - std::abs(r));
unused(off);
geom::vector<float, 2> imp = - nv * (1.f + stick_bounce) - tv * stick_friction * dt;
math::vector<float, 2> imp = - nv * (1.f + stick_bounce) - tv * stick_friction * dt;
offset[i] += off * 2.f / 3.f;
offset[s[0]] -= off / 3.f * (1.f - t);
@ -807,7 +800,7 @@ void physics_demo_app::update()
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::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});
}
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(math::cast<float>(state().mouse)), ball_radius / 2.f, gfx::red);
}
if (closest_stick)
@ -841,7 +834,7 @@ void physics_demo_app::present()
painter.circle(model.points[*closest_point], ball_radius, gfx::red);
}
painter.render(geom::orthographic<float, 3>(geom::box<float, 3>{{view_region[0], view_region[1], {-1.f, 1.f}}}).homogeneous_matrix());
painter.render(math::orthographic<float, 3>(math::box<float, 3>{{view_region[0], view_region[1], {-1.f, 1.f}}}).homogeneous_matrix());
text = util::to_string("Balls: ", model.points.size(), "\n", text);
@ -851,7 +844,7 @@ void physics_demo_app::present()
opts.y = gfx::painter::y_align::top;
opts.c = gfx::black;
opts.f = gfx::painter::font::font_9x12;
opts.scale = 2.f;
opts.scale = {2.f, 2.f};
float y = 10.f;
for (std::size_t i = 0;;)
@ -872,22 +865,28 @@ void physics_demo_app::present()
text.clear();
painter.render(geom::window_camera{width(), height()}.transform());
painter.render(math::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
math::point<float, 2> physics_demo_app::screen_to_world(math::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 math::orthographic<float, 2>(view_region).inverse()(math::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
math::point<float, 2> physics_demo_app::world_to_screen(math::point<float, 2> const & p) const
{
auto q = geom::orthographic<float, 2>(view_region)(p);
auto q = math::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,13 +1,13 @@
#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/phys/engine_2d.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/geom/box.hpp>
#include <psemek/geom/camera.hpp>
#include <psemek/geom/rotation.hpp>
#include <psemek/math/box.hpp>
#include <psemek/math/camera.hpp>
#include <psemek/math/rotation.hpp>
#include <psemek/util/clock.hpp>
#include <psemek/util/recursive.hpp>
@ -18,18 +18,20 @@
#include <psemek/random/generator.hpp>
#include <psemek/random/uniform.hpp>
#include <psemek/log/log.hpp>
#include <cstdint>
#include <vector>
using namespace psemek;
struct physics_2d_app
: app::app
: app::application_base
{
phys2d::engine physics;
geom::box<float, 2> view_box;
geom::box<float, 2> simulation_box;
math::box<float, 2> view_box;
math::box<float, 2> simulation_box;
gfx::painter painter;
@ -52,11 +54,11 @@ struct physics_2d_app
std::vector<float> radiuses;
std::optional<geom::point<float, 2>> mouse;
std::optional<math::point<float, 2>> mouse;
std::optional<std::size_t> selected_ball;
std::optional<geom::vector<float, 2>> drag_delta;
std::optional<math::vector<float, 2>> drag_delta;
std::size_t motor_id;
@ -66,8 +68,7 @@ struct physics_2d_app
random::generator gen;
physics_2d_app()
: app("Physics 2D example", 4)
physics_2d_app(options const &, context const &)
{
simulation_box = {{{-world_width, world_width}, {-world_height, world_height}}};
@ -103,7 +104,7 @@ struct physics_2d_app
{
for (int x = nx / 2 - ky / 2; x < nx / 2 - ky / 2 + (ky - y); ++x)
{
geom::point<float, 2> pos{simulation_box.corner((x + 0.5f * y + 0.5f) / nx, (y + 0.5f) / ny)};
math::point<float, 2> pos{simulation_box.corner((x + 0.5f * y + 0.5f) / nx, (y + 0.5f) / ny)};
physics.add_object(box_group, box_shape, material, {pos, 0.f}, {{0.f, 0.f}, 0.01f});
}
}
@ -117,19 +118,19 @@ struct physics_2d_app
{
if (chess && (y % 2))
{
geom::point<float, 2> pos{simulation_box.corner((0.25f) / nx, (y + 0.5f) / ny)};
math::point<float, 2> pos{simulation_box.corner((0.25f) / nx, (y + 0.5f) / ny)};
physics.add_object(box_group, small_box_shape, material, {pos, 0.f}, {{0.f, 0.f}, 0.01f});
}
for (int x = 0; x < nx - chess * (y % 2); ++x)
{
geom::point<float, 2> pos{simulation_box.corner((x + 0.5f + chess * 0.5f * (y % 2)) / nx, (y + 0.5f) / ny)};
math::point<float, 2> pos{simulation_box.corner((x + 0.5f + chess * 0.5f * (y % 2)) / nx, (y + 0.5f) / ny)};
physics.add_object(box_group, box_shape, material, {pos, 0.f}, {{0.f, 0.f}, 0.01f});
}
if (chess && (y % 2))
{
geom::point<float, 2> pos{simulation_box.corner((nx - 0.25f) / nx, (y + 0.5f) / ny)};
math::point<float, 2> pos{simulation_box.corner((nx - 0.25f) / nx, (y + 0.5f) / ny)};
physics.add_object(box_group, small_box_shape, material, {pos, 0.f}, {{0.f, 0.f}, 0.01f});
}
}
@ -145,9 +146,9 @@ struct physics_2d_app
if(false)
for (int i = 0; i < 15; ++i)
{
float a = geom::rad(360 * i / 15.f);
float r = ball_radius * 15.f / geom::pi;
geom::point<float, 2> pos{r * std::cos(a), r * std::sin(a)};
float a = math::rad(360 * i / 15.f);
float r = ball_radius * 15.f / math::pi;
math::point<float, 2> pos{r * std::cos(a), r * std::sin(a)};
physics.add_object(ball_group, ball_shape, material, {pos, 0.f}, {});
}
}
@ -168,12 +169,12 @@ struct physics_2d_app
physics.add_object(wall_group, wall_3_shape, wall_material, {}, {});
auto task = util::recursive([this, dx = box_width * 0.4f](auto && self) mutable -> void {
physics.add_object(box_group, box_shape, material, {simulation_box.corner(0.5f, 0.9f) + geom::vector{dx, 0.f}, 0.f}, {});
// physics.add_object(ball_group, ball_shape, material, {simulation_box.corner(0.5f, 0.9f) + geom::vector{dx, 0.f}, 0.f}, {});
physics.add_object(box_group, box_shape, material, {simulation_box.corner(0.5f, 0.9f) + math::vector{dx, 0.f}, 0.f}, {});
// physics.add_object(ball_group, ball_shape, material, {simulation_box.corner(0.5f, 0.9f) + math::vector{dx, 0.f}, 0.f}, {});
// float r = random::uniform_distribution<float>{0.05f, 0.5f}(gen);
// auto new_shape = physics.add_shape(phys2d::ball{r});
// physics.add_object(ball_group, new_shape, material, {simulation_box.corner(0.5f, 0.9f) + geom::vector{dx, 0.f}, 0.f}, {});
// physics.add_object(ball_group, new_shape, material, {simulation_box.corner(0.5f, 0.9f) + math::vector{dx, 0.f}, 0.f}, {});
// radiuses.push_back(r);
dx *= -1.f;
@ -201,11 +202,11 @@ struct physics_2d_app
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 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};
}
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];
auto r = s.position - *mouse;
r = geom::plane_rotation<float, 2>(0, 1, -s.rotation)(r);
drag_delta = r;
if (selected_ball && mouse)
{
auto const & s = physics.group_static_state(ball_group)[*selected_ball];
auto r = s.position - *mouse;
r = math::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});
// 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}, {});
drag_delta = std::nullopt;
}
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;
}
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();
float mx = event.position[0] * 1.f / state().size[0];
float my = 1.f - event.position[1] * 1.f / state().size[1];
mouse = view_box.corner(mx, my);
}
@ -270,9 +270,9 @@ struct physics_2d_app
float MOTOR = 15.f;
float m = 0.f;
if (is_key_down(SDLK_LEFT))
if (state().key_down.contains(app::keycode::LEFT))
m += MOTOR;
if (is_key_down(SDLK_RIGHT))
if (state().key_down.contains(app::keycode::RIGHT))
m += -MOTOR;
m = 15.f;
motor = m;
@ -286,13 +286,13 @@ struct physics_2d_app
{
auto r = -*drag_delta;
auto R = geom::plane_rotation<float, 2>(0, 1, s.rotation);
auto R = math::plane_rotation<float, 2>(0, 1, s.rotation);
geom::vector<float, 2> f;
math::vector<float, 2> f;
f[0] = s.position[0] + R(r)[0] - (*mouse)[0];
f[1] = s.position[1] + R(r)[1] - (*mouse)[1];
geom::matrix<float, 2, 3> J;
math::matrix<float, 2, 3> J;
J[0][0] = 1.f;
J[0][1] = 0.f;
J[0][2] = - std::sin(s.rotation) * r[0] + std::cos(s.rotation) * r[1];
@ -300,12 +300,12 @@ struct physics_2d_app
J[1][1] = 1.f;
J[1][2] = std::cos(s.rotation) * r[0] - std::sin(s.rotation) * r[1];
geom::vector<float, 3> v;
math::vector<float, 3> v;
v[0] = d.velocity[0];
v[1] = d.velocity[1];
v[2] = d.angular_velocity;
auto dv = geom::least_squares(J, -(1.f / 8.f) * f / dt - (J * v));
auto dv = math::least_squares(J, -(1.f / 8.f) * f / dt - (J * v));
if (dv)
{
d.velocity[0] += (*dv)[0] * lambda;
@ -323,24 +323,24 @@ struct physics_2d_app
auto & d1 = physics.group_dynamic_state(group)[j];
auto r = s1.position - s0.position;
float l = geom::length(r);
float l = math::length(r);
geom::vector<float, 1> f;
math::vector<float, 1> f;
f[0] = l - target;
geom::matrix<float, 1, 4> J;
math::matrix<float, 1, 4> J;
J[0][0] = -r[0] / l;
J[0][1] = -r[1] / l;
J[0][2] = r[0] / l;
J[0][3] = r[1] / l;
geom::vector<float, 4> v;
math::vector<float, 4> v;
v[0] = d0.velocity[0];
v[1] = d0.velocity[1];
v[2] = d1.velocity[0];
v[3] = d1.velocity[1];
auto dv = geom::least_squares(J, -(1.f / 8.f) * f / dt - (J * v));
auto dv = math::least_squares(J, -(1.f / 8.f) * f / dt - (J * v));
if (dv)
{
d0.velocity[0] += (*dv)[0] * lambda;
@ -413,7 +413,7 @@ struct physics_2d_app
for (std::size_t i = 0; i < physics.group_size(ball_group); ++i)
{
auto p = physics.group_static_state(ball_group)[i].position;
float d = geom::distance(p, *mouse);
float d = math::distance(p, *mouse);
if (d <= std::min(closest, ball_radius))
{
closest = d;
@ -441,8 +441,8 @@ struct physics_2d_app
painter.circle(p, r, gfx::black);
painter.circle(p, r - line_width, gfx::light(gfx::blue, selected ? 0.8f : 1.f).as_color_rgba());
geom::vector const n{std::cos(a), std::sin(a)};
geom::vector const m = geom::ort(n);
math::vector const n{std::cos(a), std::sin(a)};
math::vector const m = math::ort(n);
painter.line(p - n * ball_radius / 2.f, p + n * ball_radius / 2.f, line_width, gfx::black, false);
painter.line(p - m * ball_radius / 2.f, p + m * ball_radius / 2.f, line_width, gfx::black, false);
@ -458,13 +458,13 @@ struct physics_2d_app
float w = std::get<phys2d::box const *>(physics.get_shape(physics.get_object(box_group, i).shape))->width / 2.f;
float h = std::get<phys2d::box const *>(physics.get_shape(physics.get_object(box_group, i).shape))->height / 2.f;
geom::vector<float, 2> q[4];
math::vector<float, 2> q[4];
q[0] = {-w, -h};
q[1] = { w, -h};
q[2] = { w, h};
q[3] = {-w, h};
auto m = geom::plane_rotation<float, 2>(0, 1, a).linear_matrix();
auto m = math::plane_rotation<float, 2>(0, 1, a).linear_matrix();
for (std::size_t j = 0; j < 4; ++j)
q[j] = m * q[j];
@ -483,12 +483,17 @@ struct physics_2d_app
painter.line(simulation_box.corner(1, 1), simulation_box.corner(0, 1), line_width, gfx::black, false);
painter.line(simulation_box.corner(0, 1), simulation_box.corner(0, 0), line_width, gfx::black, false);
painter.render(geom::orthographic_camera{view_box}.transform());
painter.render(math::orthographic_camera{view_box}.transform());
}
};
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/main.hpp>
#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/phys/engine_3d.hpp>
@ -9,11 +9,11 @@
#include <psemek/cg/body/icosahedron.hpp>
#include <psemek/cg/body/box.hpp>
#include <psemek/geom/camera.hpp>
#include <psemek/geom/mesh.hpp>
#include <psemek/geom/rotation.hpp>
#include <psemek/geom/translation.hpp>
#include <psemek/geom/scale.hpp>
#include <psemek/math/camera.hpp>
#include <psemek/math/mesh.hpp>
#include <psemek/math/rotation.hpp>
#include <psemek/math/translation.hpp>
#include <psemek/math/scale.hpp>
#include <psemek/random/device.hpp>
#include <psemek/random/generator.hpp>
@ -23,6 +23,8 @@
#include <psemek/util/clock.hpp>
#include <psemek/log/log.hpp>
using namespace psemek;
static char const program_vs[] =
@ -85,21 +87,20 @@ void main()
)";
struct physics_3d_app
: app::app
: app::application_base
{
physics_3d_app()
: app::app("Physics 3D", 4)
, program_(program_vs, program_fs)
physics_3d_app(options const &, context const &)
: program_(program_vs, program_fs)
, rng_{random::device{}}
{
camera_.near_clip = 0.1f;
camera_.far_clip = 1000.f;
camera_.fov_y = geom::rad(90.f);
camera_.fov_y = math::rad(90.f);
camera_.fov_x = camera_.fov_y;
camera_.target = {0.f, 0.f, 0.f};
camera_.distance = 10.f;
camera_.elevation_angle = geom::rad(45.f);
camera_.azimuthal_angle = geom::rad(30.f);
camera_.elevation_angle = math::rad(45.f);
camera_.azimuthal_angle = math::rad(30.f);
camera_distance_tgt_ = camera_.distance;
camera_azimuthal_angle_tgt_ = camera_.azimuthal_angle;
@ -107,12 +108,12 @@ struct physics_3d_app
struct vertex
{
geom::point<float, 3> position;
geom::vector<float, 3> normal;
math::point<float, 3> position;
math::vector<float, 3> normal;
static void setup(gfx::mesh & m)
{
m.setup<geom::point<float, 3>, geom::vector<float, 3>>();
m.setup<math::point<float, 3>, math::vector<float, 3>>();
}
};
@ -135,21 +136,21 @@ struct physics_3d_app
auto const & body_vertices = cg::vertices(body);
auto const & body_triangles = cg::triangles(body);
std::vector<geom::point<float, 3>> positions;
std::vector<math::point<float, 3>> positions;
std::copy(body_vertices.begin(), body_vertices.end(), std::back_inserter(positions));
std::vector<geom::triangle<std::uint32_t>> triangles;
std::vector<math::triangle<std::uint32_t>> triangles;
for (auto const & t : body_triangles)
triangles.push_back({t[0], t[1], t[2]});
for (int iterations = 0; iterations < 2; ++iterations)
{
geom::subdivide(positions, triangles);
math::subdivide(positions, triangles);
for (auto & p : positions)
p = p.zero() + geom::normalized(p - p.zero());
p = p.zero() + math::normalized(p - p.zero());
}
auto normals = geom::smooth_normals(positions, triangles);
auto normals = math::smooth_normals(positions, triangles);
std::vector<vertex> vertices;
@ -166,19 +167,19 @@ struct physics_3d_app
auto const & body_vertices = cg::vertices(body);
auto const & body_triangles = cg::triangles(body);
std::vector<geom::point<float, 3>> positions;
std::vector<math::point<float, 3>> positions;
std::copy(body_vertices.begin(), body_vertices.end(), std::back_inserter(positions));
std::vector<geom::triangle<std::uint32_t>> triangles;
std::vector<math::triangle<std::uint32_t>> triangles;
for (auto const & t : body_triangles)
triangles.push_back({t[0], t[1], t[2]});
auto flat_positions = geom::deindex(positions, triangles);
auto flat_positions = math::deindex(positions, triangles);
std::vector<vertex> vertices;
for (auto const & t : flat_positions)
{
auto n = geom::normal(t[0], t[1], t[2]);
auto n = math::normal(t[0], t[1], t[2]);
vertices.push_back({t[0], n});
vertices.push_back({t[1], n});
vertices.push_back({t[2], n});
@ -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}});
}
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;
camera_azimuthal_angle_tgt_ -= dx * 0.003f;
auto const delta = event.position - old_mouse;
camera_elevation_angle_tgt_ += delta[1] * 0.003f;
camera_azimuthal_angle_tgt_ -= delta[0] * 0.003f;
}
}
void on_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;
// 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::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;
for (std::uint32_t h = 1; h < engine_.object_count(); ++h)
@ -282,7 +286,7 @@ struct physics_3d_app
auto w = engine_.get_object_state(h).angular_velocity;
auto H = engine_.get_object_state(h).position[2];
E += 0.5f * geom::length_sqr(v) * m + 0.5f * geom::dot(w, I * w) + m * 10.f * H;
E += 0.5f * math::length_sqr(v) * m + 0.5f * math::dot(w, I * w) + m * 10.f * H;
}
log::info() << "Energy: " << E;
}
@ -303,15 +307,15 @@ struct physics_3d_app
program_.bind();
program_["u_camera_transform"] = camera_.transform();
program_["u_light_direction"] = geom::normalized(geom::vector{1.f, 1.f, 1.f});
program_["u_light_color"] = geom::vector{1.f, 1.f, 1.f};
program_["u_light_direction"] = math::normalized(math::vector{1.f, 1.f, 1.f});
program_["u_light_color"] = math::vector{1.f, 1.f, 1.f};
program_["u_object_transform"] = geom::matrix<float, 4, 4>::identity();
program_["u_object_color"] = geom::vector{0.5f, 0.5f, 0.5f};
program_["u_object_transform"] = math::matrix<float, 4, 4>::identity();
program_["u_object_color"] = math::vector{0.5f, 0.5f, 0.5f};
program_["u_grid"] = 0;
plane_mesh_.draw();
program_["u_object_color"] = geom::vector{0.f, 0.f, 1.f};
program_["u_object_color"] = math::vector{0.f, 0.f, 1.f};
program_["u_grid"] = 1;
for (phys3d::engine::object_handle h = 0; h < engine_.object_count(); ++h)
{
@ -322,17 +326,17 @@ struct physics_3d_app
if (auto s = std::get_if<phys3d::ball const *>(&sh))
{
program_["u_object_transform"] =
geom::translation<float, 3>(engine_.get_object_state(h).position - geom::point<float, 3>::zero()).homogeneous_matrix() *
geom::quaternion_rotation<float>(engine_.get_object_state(h).rotation).homogeneous_matrix() *
geom::scale<float, 3>((*s)->radius).homogeneous_matrix();
math::translation<float, 3>(engine_.get_object_state(h).position - math::point<float, 3>::zero()).homogeneous_matrix() *
math::quaternion_rotation<float>(engine_.get_object_state(h).rotation).homogeneous_matrix() *
math::scale<float, 3>((*s)->radius).homogeneous_matrix();
sphere_mesh_.draw();
}
else if (auto s = std::get_if<phys3d::box const *>(&sh))
{
program_["u_object_transform"] =
geom::translation<float, 3>(engine_.get_object_state(h).position - geom::point<float, 3>::zero()).homogeneous_matrix() *
geom::quaternion_rotation<float>(engine_.get_object_state(h).rotation).homogeneous_matrix() *
geom::scale<float, 3>((*s)->dimensions / 2.f).homogeneous_matrix();
math::translation<float, 3>(engine_.get_object_state(h).position - math::point<float, 3>::zero()).homogeneous_matrix() *
math::quaternion_rotation<float>(engine_.get_object_state(h).rotation).homogeneous_matrix() *
math::scale<float, 3>((*s)->dimensions / 2.f).homogeneous_matrix();
box_mesh_.draw();
}
}
@ -341,7 +345,7 @@ struct physics_3d_app
private:
gfx::program program_;
geom::spherical_camera camera_;
math::spherical_camera camera_;
float camera_distance_tgt_;
float camera_azimuthal_angle_tgt_;
float camera_elevation_angle_tgt_;
@ -358,7 +362,12 @@ private:
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"});
}
}

258
examples/platformer.cpp Normal file
View file

@ -0,0 +1,258 @@
#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/gfx/gl.hpp>
#include <psemek/math/scale.hpp>
#include <psemek/math/camera.hpp>
#include <psemek/math/constants.hpp>
#include <psemek/math/intersection.hpp>
#include <psemek/random/generator.hpp>
#include <psemek/random/uniform.hpp>
#include <psemek/util/clock.hpp>
#include <psemek/util/to_string.hpp>
using namespace psemek;
struct map
{
std::vector<math::box<float, 2>> platforms;
};
struct player
{
math::vector<float, 2> size;
math::point<float, 2> position;
math::vector<float, 2> velocity;
math::point<float, 2> ghost_position;
math::vector<float, 2> ghost_velocity;
bool grounded = false;
math::box<float, 2> bbox() const
{
return math::expand(math::box<float, 2>::singleton(position), size);
}
};
struct particle
{
math::point<float, 2> position;
math::vector<float, 2> velocity;
float size;
float lifetime;
gfx::color_rgba color;
};
struct platformer_app
: app::application_base
{
platformer_app(options const &, context const &)
{
map_.platforms.push_back({{{-10.f, 10.f}, {-5.f, -4.f}}});
map_.platforms.push_back({{{-5.f, -3.f}, {-2.5f, -2.f}}});
map_.platforms.push_back({{{-1.f, 1.f}, {-2.f, -1.5f}}});
map_.platforms.push_back({{{ 3.f, 5.f}, {-1.5f, -1.f}}});
map_.platforms.push_back({{{-7.f, -6.f}, {-3.5f, -3.f}}});
player_.size = {0.25f, 0.5f};
player_.position = {0.f, 5.f};
player_.velocity = {0.f, 0.f};
player_.ghost_position = player_.position;
player_.ghost_velocity = {0.f, 0.f};
}
void update() override
{
float const dt = frame_clock_.restart().count();
float const gravity = -50.f;
float const acceleration = 200.f;
float const friction = 25.f;
float const jump_speed = 15.f;
math::vector const ghost_spring_force{200.f, 500.f};
math::vector const ghost_spring_damping{10.f, 20.f};
float const particle_grow_speed = 0.1f;
float const move_particle_spawn_period = 1.f / 32.f;
int const jump_particle_count = 16;
int const land_particle_count = 32;
player_.velocity[0] *= std::exp(- friction * dt);
player_.velocity[1] += dt * gravity;
player_.position += player_.velocity * dt;
{
auto v = (player_.position - player_.ghost_position);
player_.ghost_velocity += math::pointwise_mult(v, ghost_spring_force) * dt;
player_.ghost_velocity[0] *= std::exp(- ghost_spring_damping[0] * dt);
player_.ghost_velocity[1] *= std::exp(- ghost_spring_damping[1] * dt);
}
player_.ghost_position += player_.ghost_velocity * dt;
bool was_grounded = player_.grounded;
auto velocity_before_collision = player_.velocity;
player_.grounded = false;
for (auto const & platform : map_.platforms)
{
auto const player_box = player_.bbox();
auto const intersection = platform & player_box;
if (intersection.empty()) continue;
if (intersection[0].length() < intersection[1].length())
{
if (intersection[0].center() < player_.position[0])
player_.position[0] += intersection[0].length();
else
player_.position[0] -= intersection[0].length();
player_.velocity[0] = 0.f;
}
else
{
if (intersection[1].center() < player_.position[1])
{
player_.position[1] += intersection[1].length();
player_.grounded = true;
}
else
player_.position[1] -= intersection[1].length();
player_.velocity[1] = 0.f;
}
}
if (state().key_down.contains(app::keycode::A))
player_.velocity[0] -= acceleration * dt;
if (state().key_down.contains(app::keycode::D))
player_.velocity[0] += acceleration * dt;
if (player_.grounded && (state().key_down.contains(app::keycode::A) ^ state().key_down.contains(app::keycode::D)))
{
move_particle_spawn_timer_ += dt;
if (move_particle_spawn_timer_ > move_particle_spawn_period)
{
move_particle_spawn_timer_ -= move_particle_spawn_period;
float s = state().key_down.contains(app::keycode::A) ? 1.f : -1.f;
auto position = player_.position + math::vector{s * player_.size[0], -player_.size[1]};
auto velocity = math::direction(random::uniform(rng_, math::rad(60.f), math::rad(120.f)));
auto size = random::uniform(rng_, 0.05f, 0.15f);
auto lifetime = random::uniform(rng_, 0.25f, 0.5f);
int c = random::uniform(rng_, 63, 191);
gfx::color_rgba color{c, 0, c, 255};
particles_.push_back({position, velocity, size, lifetime, color});
}
}
if (player_.grounded && state().key_down.contains(app::keycode::W))
{
player_.velocity[1] += jump_speed;
for (int i = 0; i < jump_particle_count; ++i)
{
float s = random::uniform(rng_, -1.f, 1.f);
auto position = player_.position + math::vector{s * player_.size[0], -player_.size[1]};
auto velocity = (random::uniform<bool>(rng_) ? 1.f : -1.f) * math::direction(random::uniform(rng_, math::rad(-30.f), math::rad(30.f))) * 3.f;
auto size = random::uniform(rng_, 0.05f, 0.15f);
auto lifetime = random::uniform(rng_, 0.25f, 0.5f);
int c = random::uniform(rng_, 63, 191);
gfx::color_rgba color{c, 0, c, 255};
particles_.push_back({position, velocity, size, lifetime, color});
}
}
if (!was_grounded && player_.grounded)
{
for (int i = 0; i < land_particle_count; ++i)
{
float s = random::uniform(rng_, -1.f, 1.f);
auto position = player_.position + math::vector{s * player_.size[0], -player_.size[1]};
auto velocity = math::direction(random::uniform(rng_, math::rad(0.f), math::rad(180.f))) * (-velocity_before_collision[1]) * 0.2f;
auto size = random::uniform(rng_, 0.05f, 0.15f);
auto lifetime = random::uniform(rng_, 0.25f, 0.5f);
int c = random::uniform(rng_, 63, 191);
gfx::color_rgba color{c, 0, c, 255};
particles_.push_back({position, velocity, size, lifetime, color});
}
}
std::vector<particle> alive_particles;
for (auto & p : particles_)
{
p.lifetime -= dt;
if (p.lifetime <= 0.f) continue;
p.position += p.velocity * dt;
p.size += particle_grow_speed * dt;
alive_particles.push_back(p);
}
particles_ = std::move(alive_particles);
}
void present() override
{
gl::ClearColor(1.f, 1.f, 1.f, 0.f);
gl::Clear(gl::COLOR_BUFFER_BIT);
for (auto const & box : map_.platforms)
painter_.rect(box, {0, 0, 0, 255});
{
math::point bottom = player_.position - math::vector{0.f, player_.size[1]};
math::point top = player_.ghost_position + math::vector{0.f, player_.size[1]};
auto d = math::vector{player_.size[0], 0.f};
gfx::color_rgba color{255, 0, 0, 255};
painter_.triangle(bottom - d, bottom + d, top - d, color);
painter_.triangle(bottom + d, top - d, top + d, color);
}
for (auto const & p : particles_)
{
auto colorf = gfx::to_colorf(p.color);
colorf[3] *= 1.f - std::exp(- 2.f * p.lifetime);
painter_.circle(p.position, p.size, gfx::to_coloru8(colorf));
}
float const aspect_ratio = state().size[0] * 1.f / state().size[1];
float const view_size = 5.f;
math::box<float, 2> view_box{{{-view_size * aspect_ratio, view_size * aspect_ratio}, {-view_size, view_size}}};
painter_.render(math::orthographic_camera{view_box}.transform());
}
private:
map map_;
player player_;
std::vector<particle> particles_;
float move_particle_spawn_timer_ = 0.f;
random::generator rng_;
util::clock<> frame_clock_;
gfx::painter painter_;
};
namespace psemek::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/main.hpp>
#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/program.hpp>
#include <psemek/gfx/mesh.hpp>
@ -9,11 +9,11 @@
#include <psemek/gfx/framebuffer.hpp>
#include <psemek/gfx/error.hpp>
#include <psemek/geom/camera.hpp>
#include <psemek/geom/math.hpp>
#include <psemek/geom/mesh.hpp>
#include <psemek/geom/homogeneous.hpp>
#include <psemek/geom/gram_schmidt.hpp>
#include <psemek/math/camera.hpp>
#include <psemek/math/math.hpp>
#include <psemek/math/mesh.hpp>
#include <psemek/math/homogeneous.hpp>
#include <psemek/math/gram_schmidt.hpp>
#include <psemek/util/clock.hpp>
@ -96,16 +96,16 @@ struct phong_renderer
struct light
{
geom::vector<float, 4> position;
math::vector<float, 4> position;
};
struct render_options
{
gfx::framebuffer const * framebuffer;
GLenum draw_buffer;
geom::box<int, 2> viewport;
geom::matrix<float, 4, 4> transform;
geom::point<float, 3> camera_position;
math::box<int, 2> viewport;
math::matrix<float, 4, 4> transform;
math::point<float, 3> camera_position;
struct light light;
};
@ -134,13 +134,13 @@ void phong_renderer::render(render_options const & options)
program_.bind();
program_["u_transform"] = options.transform;
program_["u_light_position"] = options.light.position;
program_["u_camera_position"] = geom::homogeneous(options.camera_position);
program_["u_camera_position"] = math::homogeneous(options.camera_position);
for (auto const & state : render_states_)
{
if (!state.mesh) continue;
program_["u_material"] = geom::vector{state.material.ambient, state.material.diffuse, state.material.specular, state.material.shininess};
program_["u_material"] = math::vector{state.material.ambient, state.material.diffuse, state.material.specular, state.material.shininess};
state.mesh->draw();
}
@ -177,12 +177,12 @@ struct shadow_map_builder
struct render_state
{
gfx::mesh const * mesh;
geom::box<float, 3> bbox;
math::box<float, 3> bbox;
};
struct render_options
{
geom::vector<float, 3> light;
math::vector<float, 3> light;
};
shadow_map_builder(std::size_t width, std::size_t height);
@ -191,7 +191,7 @@ struct shadow_map_builder
void build(render_options const & options);
geom::matrix<float, 4, 4> const & transform() const;
math::matrix<float, 4, 4> const & transform() const;
gfx::texture_2d const & texture() const;
@ -201,7 +201,7 @@ private:
gfx::framebuffer framebuffer_;
gfx::texture_2d depth_texture_;
geom::matrix<float, 4, 4> transform_;
math::matrix<float, 4, 4> transform_;
};
shadow_map_builder::shadow_map_builder(std::size_t width, std::size_t height)
@ -224,7 +224,7 @@ void shadow_map_builder::push(render_state const & state)
void shadow_map_builder::build(render_options const & options)
{
geom::vector<float, 3> light_axes[3];
math::vector<float, 3> light_axes[3];
light_axes[2] = -options.light;
light_axes[1] = {0.f, 0.f, 1.f};
@ -232,24 +232,24 @@ void shadow_map_builder::build(render_options const & options)
if (light_axes[2][2] > 0.5f)
light_axes[1] = {0.f, 1.f, 0.f};
geom::gram_schmidt(light_axes[2], light_axes[1]);
math::gram_schmidt(light_axes[2], light_axes[1]);
light_axes[0] = geom::cross(light_axes[2], light_axes[1]);
light_axes[0] = math::cross(light_axes[2], light_axes[1]);
geom::box<float, 3> light_bbox;
math::box<float, 3> light_bbox;
geom::point<float, 3> origin = geom::point<float, 3>::zero();
math::point<float, 3> origin = math::point<float, 3>::zero();
for (auto const & state : render_states_)
{
for (auto const & v : geom::vertices(state.bbox))
for (auto const & v : math::vertices(state.bbox))
{
for (std::size_t i = 0; i < 3; ++i)
light_bbox[i] |= geom::dot(light_axes[i], v - origin);
light_bbox[i] |= math::dot(light_axes[i], v - origin);
}
}
transform_ = geom::orthographic_camera{light_bbox}.projection() * geom::homogeneous(geom::by_rows(light_axes[0], light_axes[1], light_axes[2]));
transform_ = math::orthographic_camera{light_bbox}.projection() * math::homogeneous(math::by_rows(light_axes[0], light_axes[1], light_axes[2]));
framebuffer_.bind();
gl::DrawBuffer(gl::NONE);
@ -289,7 +289,7 @@ void shadow_map_builder::build(render_options const & options)
*/
}
geom::matrix<float, 4, 4> const & shadow_map_builder::transform() const
math::matrix<float, 4, 4> const & shadow_map_builder::transform() const
{
return transform_;
}
@ -399,21 +399,21 @@ struct shadow_renderer
struct material material;
gfx::mesh const * mesh;
bool casts_shadow;
std::optional<geom::box<float, 3>> bbox;
std::optional<math::box<float, 3>> bbox;
};
struct light
{
geom::vector<float, 4> position;
math::vector<float, 4> position;
};
struct render_options
{
gfx::framebuffer const * framebuffer;
GLenum draw_buffer;
geom::box<int, 2> viewport;
geom::matrix<float, 4, 4> transform;
geom::point<float, 3> camera_position;
math::box<int, 2> viewport;
math::matrix<float, 4, 4> transform;
math::point<float, 3> camera_position;
struct light light;
};
@ -460,7 +460,7 @@ void shadow_renderer::render(render_options const & options)
program_.bind();
program_["u_transform"] = options.transform;
program_["u_light_position"] = options.light.position;
program_["u_camera_position"] = geom::homogeneous(options.camera_position);
program_["u_camera_position"] = math::homogeneous(options.camera_position);
program_["u_shadow_transform"] = builder_.transform();
program_["u_shadow_map"] = 0;
@ -469,7 +469,7 @@ void shadow_renderer::render(render_options const & options)
for (auto const & state : render_states_)
{
program_["u_material"] = geom::vector{state.material.ambient, state.material.diffuse, state.material.specular, state.material.shininess};
program_["u_material"] = math::vector{state.material.ambient, state.material.diffuse, state.material.specular, state.material.shininess};
state.mesh->draw();
}
@ -478,15 +478,15 @@ void shadow_renderer::render(render_options const & options)
struct vertex
{
geom::point<float, 3> position;
geom::vector<float, 3> normal;
math::point<float, 3> position;
math::vector<float, 3> normal;
gfx::color_rgba color;
};
struct shadow_app
: app::app
: app::application_base
{
geom::spherical_camera camera;
math::spherical_camera camera;
shadow_renderer renderer;
@ -495,44 +495,44 @@ struct shadow_app
gfx::mesh cube_mesh;
shadow_renderer::material cube_material;
geom::box<float, 3> cube_bbox;
math::box<float, 3> cube_bbox;
gfx::mesh sphere_mesh;
shadow_renderer::material sphere_material;
geom::box<float, 3> sphere_bbox;
math::box<float, 3> sphere_bbox;
gfx::mesh torus_mesh;
shadow_renderer::material torus_material;
geom::box<float, 3> torus_bbox;
math::box<float, 3> torus_bbox;
util::clock<std::chrono::duration<float>> clock;
float time = 0.f;
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;
};
shadow_app::shadow_app()
: app("Shadow")
shadow_app::shadow_app(options const &, context const & context)
{
vsync(true);
context.vsync(true);
camera.near_clip = 0.1f;
camera.far_clip = 1000.f;
camera.fov_y = geom::rad(45.f);
camera.fov_y = math::rad(45.f);
camera.azimuthal_angle = 0.f;
camera.elevation_angle = geom::rad(30.f);
camera.elevation_angle = math::rad(30.f);
camera.target = {0.f, 0.f, 0.f};
camera.distance = 10.f;
@ -547,7 +547,7 @@ shadow_app::shadow_app()
vertices.push_back({{ 10.f, -10.f, 0.f}, {0.f, 0.f, 1.f}, {127, 127, 127, 255}});
vertices.push_back({{ 10.f, 10.f, 0.f}, {0.f, 0.f, 1.f}, {127, 127, 127, 255}});
plane_mesh.setup<geom::point<float, 3>, geom::vector<float, 3>, gfx::normalized<gfx::color_rgba>>();
plane_mesh.setup<math::point<float, 3>, math::vector<float, 3>, gfx::normalized<gfx::color_rgba>>();
plane_mesh.load(vertices, gl::TRIANGLES, gl::STATIC_DRAW);
plane_material.ambient = 0.2f;
@ -557,12 +557,12 @@ shadow_app::shadow_app()
}
{
auto cube = geom::box<float, 3>{{{-1.f, 1.f}, {-1.f, 1.f}, {0.f, 5.f}}};
auto cube = math::box<float, 3>{{{-1.f, 1.f}, {-1.f, 1.f}, {0.f, 5.f}}};
auto vertices = geom::vertices(cube);
auto faces = geom::faces(cube);
auto normals = geom::flat_normals(vertices, faces);
auto flat_vertices = geom::deindex(vertices, faces);
auto vertices = math::vertices(cube);
auto faces = math::faces(cube);
auto normals = math::flat_normals(vertices, faces);
auto flat_vertices = math::deindex(vertices, faces);
std::vector<vertex> mesh_vertices;
@ -573,7 +573,7 @@ shadow_app::shadow_app()
mesh_vertices.push_back({flat_vertices[i][2], normals[i], {255, 127, 127, 255}});
}
cube_mesh.setup<geom::point<float, 3>, geom::vector<float, 3>, gfx::normalized<gfx::color_rgba>>();
cube_mesh.setup<math::point<float, 3>, math::vector<float, 3>, gfx::normalized<gfx::color_rgba>>();
cube_mesh.load(mesh_vertices, gl::TRIANGLES, gl::STATIC_DRAW);
cube_material.ambient = 0.2f;
@ -587,7 +587,7 @@ shadow_app::shadow_app()
{
std::vector<vertex> vertices;
geom::point<float, 3> const position = {3.f, 2.f, 3.f};
math::point<float, 3> const position = {3.f, 2.f, 3.f};
float const radius = 1.f;
@ -599,19 +599,19 @@ shadow_app::shadow_app()
{
for (int i = 0; i < 4 * N; ++i)
{
float a = (geom::pi * i) / (2 * N);
float b = (geom::pi * j) / (2 * N);
float a = (math::pi * i) / (2 * N);
float b = (math::pi * j) / (2 * N);
geom::vector n{std::cos(a) * std::cos(b), std::sin(a) * std::cos(b), std::sin(b)};
math::vector n{std::cos(a) * std::cos(b), std::sin(a) * std::cos(b), std::sin(b)};
vertices.push_back({position + radius * n, n, color});
}
}
vertices.push_back({position + geom::vector{0.f, 0.f, -radius}, {0.f, 0.f, -1.f}, color});
vertices.push_back({position + geom::vector{0.f, 0.f, radius}, {0.f, 0.f, 1.f}, color});
vertices.push_back({position + math::vector{0.f, 0.f, -radius}, {0.f, 0.f, -1.f}, color});
vertices.push_back({position + math::vector{0.f, 0.f, radius}, {0.f, 0.f, 1.f}, color});
std::vector<geom::triangle<std::uint32_t>> indices;
std::vector<math::triangle<std::uint32_t>> indices;
auto idx = [](int i, int j) -> std::uint32_t { return (i % (4 * N)) + 4 * N * (j + N - 1); };
@ -634,7 +634,7 @@ shadow_app::shadow_app()
indices.push_back({idx(i, N - 1), idx(i + 1, N - 1), (2 * N - 1) * (4 * N) + 1});
}
sphere_mesh.setup<geom::point<float, 3>, geom::vector<float, 3>, gfx::normalized<gfx::color_rgba>>();
sphere_mesh.setup<math::point<float, 3>, math::vector<float, 3>, gfx::normalized<gfx::color_rgba>>();
sphere_mesh.load(vertices, indices, gl::STATIC_DRAW);
sphere_material.ambient = 0.2f;
@ -650,7 +650,7 @@ shadow_app::shadow_app()
{
std::vector<vertex> vertices;
geom::point<float, 3> const position = {-3.f, 2.f, 3.f};
math::point<float, 3> const position = {-3.f, 2.f, 3.f};
float const radius1 = 1.f;
float const radius2 = 0.2f;
@ -664,18 +664,18 @@ shadow_app::shadow_app()
{
for (int i = 0; i < N; ++i)
{
float a = (2.f * geom::pi * i) / N;
float b = (2.f * geom::pi * j) / M;
float a = (2.f * math::pi * i) / N;
float b = (2.f * math::pi * j) / M;
geom::vector r{std::cos(a), std::sin(a), 0.f};
math::vector r{std::cos(a), std::sin(a), 0.f};
geom::vector n{std::cos(a) * std::cos(b), std::sin(a) * std::cos(b), std::sin(b)};
math::vector n{std::cos(a) * std::cos(b), std::sin(a) * std::cos(b), std::sin(b)};
vertices.push_back({position + radius1 * r + radius2 * n, n, color});
}
}
std::vector<geom::triangle<std::uint32_t>> indices;
std::vector<math::triangle<std::uint32_t>> indices;
auto idx = [](int i, int j) -> std::uint32_t { return (i % N) + N * (j % M); };
@ -688,7 +688,7 @@ shadow_app::shadow_app()
}
}
torus_mesh.setup<geom::point<float, 3>, geom::vector<float, 3>, gfx::normalized<gfx::color_rgba>>();
torus_mesh.setup<math::point<float, 3>, math::vector<float, 3>, gfx::normalized<gfx::color_rgba>>();
torus_mesh.load(vertices, indices, gl::STATIC_DRAW);
torus_material.ambient = 0.2f;
@ -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;
camera.elevation_angle += dy * 0.01f;
auto const delta = event.position - old_mouse;
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;
}
@ -740,12 +745,12 @@ void shadow_app::present()
else
clock.restart();
geom::vector<float, 3> light_dir = {std::cos(time), std::sin(time), 0.5f};
math::vector<float, 3> light_dir = {std::cos(time), std::sin(time), 0.5f};
gfx::framebuffer::null().bind();
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::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
@ -760,10 +765,10 @@ void shadow_app::present()
shadow_renderer::render_options options;
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.transform = camera.transform();
options.light.position = geom::homogeneous(light_dir);
options.light.position = math::homogeneous(light_dir);
options.camera_position = camera.position();
renderer.render(options);
@ -771,7 +776,13 @@ void shadow_app::present()
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

@ -0,0 +1,957 @@
#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/phys/engine_2d.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/math/simplex.hpp>
#include <psemek/math/math.hpp>
#include <psemek/math/camera.hpp>
#include <psemek/math/scale.hpp>
#include <psemek/util/hash_table.hpp>
#include <psemek/util/clock.hpp>
#include <psemek/random/generator.hpp>
#include <psemek/random/uniform.hpp>
#include <psemek/random/normal.hpp>
#include <psemek/random/weighted.hpp>
#include <psemek/log/log.hpp>
#include <psemek/util/thread.hpp>
#include <psemek/util/to_string.hpp>
#include <deque>
using namespace psemek;
static float const sim_dt = 0.005f;
static float const gravity = 25.f;
static float const air_friction = 0.1f;
static float const ground_friction = 100.f;
static float const hills_friction = 100.f;
static float const spring_damping = 10.f;
static float const max_spring_force = 25000.f;
static math::interval<float> const cell_size_range{1.f, 1.f};
static math::interval<float> const cell_period_range{0.25f, 4.f};
static float const muscle_expand_extent = 0.5f;
static int const population_size = 4 * 1024;
static float const simulation_time = 30.f;
static float const display_simulation_time = 30.f;
enum class cell_type
{
hard_tissue,
soft_tissue,
muscle,
dead,
};
struct cell_info
{
cell_type type;
float size = 1.f;
float phase = 0.f;
float period = 1.f;
};
float spring_force(cell_type type)
{
switch (type)
{
case cell_type::soft_tissue:
return 2000.f;
case cell_type::hard_tissue:
return 5000.f;
case cell_type::muscle:
return 2000.f;
case cell_type::dead:
return 100.f;
}
return 0.f;
}
static math::vector<int, 2> const neighbours[4] =
{
{-1, 0},
{ 1, 0},
{ 0, -1},
{ 0, 1},
};
struct map
{
math::vector<float, 2> ground_normal{0.f, 1.f};
std::vector<math::point<float, 2>> ground_points;
};
using creature_blueprint = std::unordered_map<math::point<int, 2>, cell_info>;
struct creature
{
struct cell
{
std::uint32_t indices[4];
cell_info info;
};
creature_blueprint blueprint;
int generation = 0;
std::optional<float> score = std::nullopt;
std::vector<math::point<float, 2>> positions;
std::vector<math::vector<float, 2>> velocities;
std::vector<cell> cells;
std::vector<math::segment<std::uint32_t>> outer_edges;
math::point<float, 2> center() const;
math::vector<float, 2> center_velocity() const;
void translate(math::vector<float, 2> const & delta);
void push(math::vector<float, 2> const & delta_velocity);
bool dead() const;
void update(float dt);
void collide(map const & map, float dt);
void compute_outer_edges();
};
math::point<float, 2> creature::center() const
{
math::vector<float, 2> sum{0.f, 0.f};
for (int i = 1; i < positions.size(); ++i)
{
sum += positions[i] - positions[0];
}
return positions[0] + sum / (1.f * positions.size());
}
math::vector<float, 2> creature::center_velocity() const
{
math::vector<float, 2> sum{0.f, 0.f};
for (int i = 1; i < velocities.size(); ++i)
{
sum += velocities[i];
}
return sum / (1.f * velocities.size());
}
void creature::translate(math::vector<float, 2> const & delta)
{
for (auto & pos : positions)
pos += delta;
}
void creature::push(math::vector<float, 2> const & delta_velocity)
{
for (auto & vel : velocities)
vel += delta_velocity;
}
bool creature::dead() const
{
for (auto const & cell : cells)
if (cell.info.type == cell_type::dead)
return true;
return false;
}
void creature::update(float dt)
{
for (auto & vel : velocities)
vel[1] -= gravity * dt;
for (auto & vel : velocities)
{
auto v = math::length(vel);
vel -= air_friction * v * vel * dt;
}
for (int i = 0; i < positions.size(); ++i)
positions[i] += velocities[i] * dt;
static math::vector<float, 2> const deltas[4]
{
{-0.5f, -0.5f},
{ 0.5f, -0.5f},
{ 0.5f, 0.5f},
{-0.5f, 0.5f},
};
for (auto & cell : cells)
{
float size = cell.info.size;
if (cell.info.type == cell_type::muscle)
{
cell.info.phase += dt / cell.info.period;
while (cell.info.phase >= 1.f)
cell.info.phase -= 1.f;
size *= 1.f + muscle_expand_extent * std::sin(cell.info.phase * 2.f * math::pi);
}
math::point<float, 2> center{0.f, 0.f};
for (auto idx : cell.indices)
{
center += (positions[idx] - math::point{0.f, 0.f}) * 0.25f;
}
float A = 0.f;
float B = 0.f;
for (int i = 0; i < 4; ++i)
{
auto const p = positions[cell.indices[i]] - center;
auto const q = deltas[i] * size;
A += math::dot(p, q);
B += math::det(p, q);
}
float const angle = - std::atan2(B, A);
for (int i = 0; i < 4; ++i)
{
auto const target = center + math::rotate(deltas[i] * size, angle);
auto force = spring_force(cell.info.type) * (target - positions[cell.indices[i]]);
if (auto f = math::length(force); f > max_spring_force)
cell.info.type = cell_type::dead;
velocities[cell.indices[i]] += force * dt;
}
auto cmvel = math::vector{0.f, 0.f};
auto rotation = 0.f;
for (auto idx : cell.indices)
{
cmvel += velocities[idx] * 0.25f;
rotation += math::det(positions[idx] - center, velocities[idx]) * (0.25f / math::length_sqr(positions[idx] - center));
}
for (auto idx : cell.indices)
{
auto target_vel = cmvel + math::ort(positions[idx] - center) * rotation;
velocities[idx] += (target_vel - velocities[idx]) * (1.f - std::exp(- spring_damping * dt));
}
}
}
void creature::collide(map const & map, float dt)
{
for (int i = 0; i < positions.size(); ++i)
{
auto delta = positions[i] - math::point<float, 2>{0.f, 0.f};
auto dist = math::dot(delta, map.ground_normal);
if (dist < 0.f)
{
positions[i] -= dist * map.ground_normal;
auto tangent = math::ort(map.ground_normal);
auto vn = math::dot(velocities[i], map.ground_normal);
auto vt = math::dot(velocities[i], tangent);
vn = std::max(0.f, vn);
vt *= std::exp(- dt * ground_friction);
velocities[i] = map.ground_normal * vn + tangent * vt;
}
auto it = std::upper_bound(map.ground_points.begin(), map.ground_points.end(), positions[i][0], [](float v, auto const & p){ return v < p[0]; });
if (it != map.ground_points.begin() && it != map.ground_points.end())
{
auto j = (it - map.ground_points.begin()) - 1;
auto n = math::normalized(math::ort(map.ground_points[j + 1] - map.ground_points[j]));
float dist = math::dot(positions[i] - map.ground_points[j], n);
if (dist < 0.f)
{
positions[i] -= dist * n;
auto tangent = math::ort(n);
auto vn = math::dot(velocities[i], n);
auto vt = math::dot(velocities[i], tangent);
vn = std::max(0.f, vn);
vt *= std::exp(- dt * hills_friction);
velocities[i] = n * vn + tangent * vt;
}
}
}
}
void creature::compute_outer_edges()
{
util::hash_set<math::segment<std::uint32_t>> edge_set;
for (auto const & cell : cells)
{
for (int i = 0; i < 4; ++i)
{
math::segment<std::uint32_t> segment;
segment[0] = cell.indices[i];
segment[1] = cell.indices[(i + 1) % 4];
edge_set.insert(segment);
}
}
outer_edges.clear();
for (auto const & edge : edge_set)
{
auto dual = edge;
std::swap(dual[0], dual[1]);
if (!edge_set.contains(dual))
outer_edges.push_back(edge);
}
}
gfx::color_rgba cell_color(cell_type type)
{
switch (type)
{
case cell_type::soft_tissue:
return {255, 255, 255, 255};
case cell_type::hard_tissue:
return {127, 127, 127, 255};
case cell_type::muscle:
return {255, 127, 127, 255};
case cell_type::dead:
return {0, 0, 0, 0};
}
return {255, 0, 255, 255};
}
void draw(gfx::painter & painter, creature const & creature)
{
for (auto const & cell : creature.cells)
{
math::point<float, 2> center{0.f, 0.f};
for (auto idx : cell.indices)
center += 0.25f * (creature.positions[idx] - math::point{0.f, 0.f});
gfx::color_rgba const color = cell_color(cell.info.type);
gfx::color_rgba const bgcolor = gfx::dark(color, 0.25f);
math::point<float, 2> ps[4];
for (int i = 0; i < 4; ++i)
ps[i] = creature.positions[cell.indices[i]];
painter.triangle(ps[0], ps[1], ps[2], bgcolor);
painter.triangle(ps[2], ps[0], ps[3], bgcolor);
for (int i = 0; i < 4; ++i)
ps[i] = math::lerp(ps[i], center, 0.25f);
painter.triangle(ps[0], ps[1], ps[2], color);
painter.triangle(ps[2], ps[0], ps[3], color);
}
for (auto const & edge : creature.outer_edges)
{
gfx::color_rgba const color{0, 0, 0, 255};
float const width = 0.125f;
painter.line(creature.positions[edge.points[0]], creature.positions[edge.points[1]], width, color, false);
}
}
struct creature_builder
{
creature_builder() = default;
creature_builder(creature_blueprint blueprint)
: blueprint_(std::move(blueprint))
{}
void add(math::point<int, 2> const position, cell_info const & info)
{
blueprint_[position] = info;
}
bool contains(math::point<int, 2> const & position) const
{
return blueprint_.contains(position);
}
creature build(int generation)
{
static math::vector<int, 2> const vertex_delta[4]
{
{0, 0},
{1, 0},
{1, 1},
{0, 1},
};
creature result;
result.generation = generation;
util::hash_map<math::point<int, 2>, std::uint32_t> vertex_id;
for (auto const & cell : blueprint_)
{
auto & cell_out = result.cells.emplace_back();
cell_out.info = cell.second;
for (int i = 0; i < 4; ++i)
{
math::point<int, 2> vertex = cell.first + vertex_delta[i];
if (auto it = vertex_id.find(vertex); it != vertex_id.end())
{
cell_out.indices[i] = it->second;
}
else
{
cell_out.indices[i] = (vertex_id[vertex] = result.positions.size());
result.positions.push_back(math::cast<float>(vertex));
result.velocities.push_back(math::vector<float, 2>::zero());
}
}
}
result.blueprint = std::move(blueprint_);
result.compute_outer_edges();
return result;
}
bool connected() const
{
if (blueprint_.empty())
return false;
util::hash_set<math::point<int, 2>> visited;
std::deque<math::point<int, 2>> queue;
queue.push_back(blueprint_.begin()->first);
visited.insert(queue.back());
while (!queue.empty())
{
auto cur = queue.front();
queue.pop_front();
for (auto n : neighbours)
{
auto nn = cur + n;
if (blueprint_.contains(nn) && !visited.contains(nn))
{
visited.insert(nn);
queue.push_back(nn);
}
}
}
return visited.size() == blueprint_.size();
}
private:
creature_blueprint blueprint_;
};
int const display_creature_count = 4;
cell_info random_cell(random::generator & rng)
{
cell_info result;
result.size = random::uniform(rng, cell_size_range);
if (auto t = random::uniform(rng, 0, 2); t == 0)
result.type = cell_type::hard_tissue;
else if (t == 1)
result.type = cell_type::soft_tissue;
else
{
result.type = cell_type::muscle;
result.phase = random::uniform<float>(rng);
result.period = random::uniform<float>(rng, cell_period_range);
}
return result;
}
struct soft_creatures_2d_app
: app::application_base
{
soft_creatures_2d_app(options const &, context const &)
{
random::generator rng{random::device{}};
// map_.ground_points.push_back({5.f, 0.f});
// for (int x = 10; x <= 200; x += 1)
// map_.ground_points.push_back({x / 2.f, random::uniform(rng, 0.f, std::min(x, 200) / 200.f * 4.f)});
// map_.ground_points.push_back({x, std::min(2.f, math::sqr(x - 10) * 0.25f)});
// map_.ground_points.push_back({x, random::uniform(rng, 0.f, 4.f)});
for (int species = 0; species < population_size; ++species)
{
while (true)
{
int x_size = random::uniform(rng, 2, 7);
int y_size = random::uniform(rng, 2, 5);
x_size = 1;
y_size = 1;
creature_builder builder;
for (int x = 0; x < x_size; ++x)
{
for (int y = 0; y < y_size; ++y)
{
if (random::uniform<float>(rng) > 0.75f)
continue;
builder.add({x, y}, random_cell(rng));
}
}
if (builder.connected())
{
population_.push_back(builder.build(0));
break;
}
}
if (species < display_creature_count)
display_creatures_.push_back(population_.back());
}
simulator_thread_ = util::thread([this]{ run_simulation(); });
}
void on_event(app::key_event const & event) override
{
app::application_base::on_event(event);
if (event.down && event.key == app::keycode::SPACE)
paused_ ^= true;
}
void stop() override
{
app::application_base::stop();
stopping_.store(true);
simulator_thread_.join();
}
void update() override
{
if (state().key_down.contains(app::keycode::ESCAPE))
stop();
if (state().key_down.contains(app::keycode::RIGHT) || simulation_time_ >= display_simulation_time)
get_next_display_creatures();
float const frame_dt = frame_clock_.restart().count();
if (!paused_) physics_lag_ += frame_dt;
while (physics_lag_ >= sim_dt)
{
physics_lag_ -= sim_dt;
simulation_time_ += sim_dt;
for (auto & creature : display_creatures_)
{
creature.update(sim_dt);
creature.collide(map_, sim_dt);
}
}
float max_view_x = 0.f;
for (int c = 0; c < display_creatures_.size(); ++c)
for (auto const & p : display_creatures_[c].positions)
math::make_max(max_view_x, p[0]);
max_view_x = std::max(max_view_x + 10.f, max_view_x_);
max_view_x_ += (max_view_x - max_view_x_) * (1.f - std::exp(- 4.f * frame_dt));
}
void present() override
{
gl::ClearColor(0.5f, 0.6f, 0.8f, 0.f);
gl::Clear(gl::COLOR_BUFFER_BIT);
for (int c = 0; c < display_creatures_.size(); ++c)
{
float aspect_ratio = state().size[0] * 1.f / state().size[1] * display_creatures_.size();
math::box<float, 2> view_box = {{{0.f, 0.f}, {0.f, 0.f}}};
view_box[0] = {max_view_x_ - display_width, max_view_x_};
view_box[1] = {0.f, view_box[0].length() / aspect_ratio};
view_box[1] -= 2.f;
math::box<float, 2> ground_box;
ground_box[0] = math::interval<float>::full();
ground_box[1] = {math::limits<float>::min(), 0.f};
ground_box = ground_box & view_box;
gfx::color_rgba ground_color{127, 91, 65, 255};
painter_.rect(ground_box, ground_color);
gfx::color_rgba hills_color{91, 127, 65, 255};
for (int i = 0; i + 1 < map_.ground_points.size(); ++i)
{
float x0 = map_.ground_points[i ][0];
float x1 = map_.ground_points[i + 1][0];
float y0 = map_.ground_points[i ][1];
float y1 = map_.ground_points[i + 1][1];
painter_.triangle({x0, 0.f}, {x1, 0.f}, {x1, y1}, hills_color);
painter_.triangle({x0, 0.f}, {x1, y1}, {x0, y0}, hills_color);
}
int ruler_min = std::ceil(view_box[0].min / 5.f);
int ruler_max = std::floor(view_box[0].max / 5.f);
for (int r = ruler_min; r <= ruler_max; ++r)
{
float x = r * 5.f;
float y = ((r % 2) == 0) ? -1.f : -0.5f;
y = std::max(y, view_box[1].min);
float scale = 2.f * view_box[0].length() / state().size[0];
painter_.line({x, 0.f}, {x, y}, 0.125f, {255, 255, 255, 255}, false);
painter_.text3d({x + 0.1f, -0.5f, 0.f}, util::to_string(r * 5), {.scale = {scale, scale}, .x = gfx::painter::x_align::left, .c = {255, 255, 255, 127}}, math::scale<float, 3>({1.f, -1.f, 1.f}).linear_matrix());
}
draw(painter_, display_creatures_[c]);
float height = view_box[1].length();
view_box[1].max += c * height;
view_box[1].min = view_box[1].max - display_creatures_.size() * height;
painter_.render(math::orthographic_camera(view_box).transform());
}
int text_row = 0;
auto put_line = [&](std::string const & line)
{
painter_.text({13.f, 10.f + text_row * 24.f}, line, {.scale = {2.f, 2.f}, .x = gfx::painter::x_align::left, .y = gfx::painter::y_align::top, .c = {0, 0, 0, 255}});
painter_.text({12.f, 9.f + text_row * 24.f}, line, {.scale = {2.f, 2.f}, .x = gfx::painter::x_align::left, .y = gfx::painter::y_align::top, .c = {255, 255, 255, 255}});
++text_row;
};
int simulated_generation = 0;
float best_score = 0.f;
int best_generation = 0;
float avg_score = 0.f;
{
std::lock_guard lock{next_display_mutex_};
simulated_generation = next_display_generation_ + 1;
best_score = generation_best_score_;
best_generation = generation_best_generation_;
avg_score = generation_avg_score_;
}
put_line(util::to_string("Simulating generation: ", simulated_generation));
put_line(util::to_string("Best score: ", best_score, " (gen ", best_generation, ")"));
put_line(util::to_string("Average score: ", avg_score));
put_line(util::to_string("Display generation: ", display_generation_));
put_line(util::to_string("Time: ", simulation_time_));
for (int c = 0; c < display_creatures_.size(); ++c)
{
float x = state().size[0] - 12.f;
float y = (c * state().size[1] * 1.f) / display_creatures_.size() + 9.f;
auto text = util::to_string("Gen ", display_creatures_[c].generation);
painter_.text({x + 1.f, y + 1.f}, text, {.scale = {2.f, 2.f}, .x = gfx::painter::x_align::right, .y = gfx::painter::y_align::top, .c = {0, 0, 0, 255}});
painter_.text({x, y}, text, {.scale = {2.f, 2.f}, .x = gfx::painter::x_align::right, .y = gfx::painter::y_align::top, .c = {255, 255, 255, 255}});
if (display_creatures_[c].score)
{
y += 24.f;
auto text = util::to_string("Score ", *display_creatures_[c].score);
painter_.text({x + 1.f, y + 1.f}, text, {.scale = {2.f, 2.f}, .x = gfx::painter::x_align::right, .y = gfx::painter::y_align::top, .c = {0, 0, 0, 255}});
painter_.text({x, y}, text, {.scale = {2.f, 2.f}, .x = gfx::painter::x_align::right, .y = gfx::painter::y_align::top, .c = {255, 255, 255, 255}});
}
}
for (int i = 1; i < display_creature_count; ++i)
{
float y = (state().size[1] * i * 1.f) / display_creature_count;
painter_.line({0.f, y}, {state().size[0], y}, 4.f, {0, 0, 0, 255}, false);
}
painter_.render(math::window_camera(state().size[0], state().size[1]).transform());
}
private:
gfx::painter painter_;
util::clock<> frame_clock_;
map map_;
util::thread simulator_thread_;
std::vector<creature> population_;
std::mutex next_display_mutex_;
std::vector<creature> next_display_creatures_;
bool has_next_creatures_ = false;
int next_display_generation_ = 0;
float generation_best_score_ = 0.f;
float generation_avg_score_ = 0.f;
int generation_best_generation_ = 0;
float simulation_time_ = 0.f;
float physics_lag_ = 0.f;
bool paused_ = false;
std::vector<creature> display_creatures_;
int display_generation_ = 0;
float const display_width = 70.f;
float max_view_x_ = display_width - 5.f;
std::atomic<bool> stopping_{false};
void get_next_display_creatures()
{
bool new_creatures = false;
std::lock_guard lock{next_display_mutex_};
if (has_next_creatures_)
{
display_creatures_ = std::move(next_display_creatures_);
physics_lag_ = 0.f;
simulation_time_ = 0.f;
has_next_creatures_ = false;
display_generation_ = next_display_generation_;
max_view_x_ = display_width - 5.f;
new_creatures = true;
}
if (new_creatures)
{
for (auto & creature : display_creatures_)
{
auto center = creature.center();
float radius = -std::numeric_limits<float>::infinity();
for (auto const & pos : creature.positions)
math::make_max(radius, math::distance(pos, center));
float angle = 0.f;
for (auto & pos : creature.positions)
pos = math::vector{0.f, radius} + center + math::rotate(pos - center, angle);
}
}
}
void run_simulation()
{
static thread_local random::generator rng{random::device{}};
random::normal_distribution<float> randn;
int generation = 0;
while (!stopping_)
{
int const test_iterations = std::round(simulation_time / sim_dt);
++generation;
std::vector<creature> evaluated_creatures(population_.size());
#ifdef _OPENMP
#pragma omp parallel for
#endif
for (int i = 0; i < population_.size(); ++i)
{
auto & creature = population_[i];
if (!creature.score)
{
auto sim_creature = creature;
float muscle_power = 0.f;
for (auto const & cell : sim_creature.cells)
if (cell.info.type == cell_type::muscle)
muscle_power += std::pow(cell.info.period, -1.f);
auto center = sim_creature.center();
float radius = -std::numeric_limits<float>::infinity();
for (auto const & pos : sim_creature.positions)
math::make_max(radius, math::distance(pos, center));
auto angle = random::uniform<float>(rng, -1.f, 1.f) * math::rad(30.f);
angle = 0.f;
for (auto & pos : sim_creature.positions)
pos = math::vector{0.f, radius} + center + math::rotate(pos - center, angle);
auto start_pos = sim_creature.center();
float max_y = start_pos[1];
float sum_speed_x = 0.f;
float sum_speed_x2 = 0.f;
for (int iteration = 0; iteration < test_iterations; ++iteration)
{
sim_creature.update(sim_dt);
sim_creature.collide(map_, sim_dt);
math::make_max(max_y, sim_creature.center()[1]);
auto speed = sim_creature.center_velocity();
sum_speed_x += speed[0];
sum_speed_x2 += speed[0] * speed[0];
}
auto end_pos = sim_creature.center();
float avg_speed = sum_speed_x / test_iterations;
float var_speed = sum_speed_x2 / test_iterations - avg_speed * avg_speed;
float total_speed = (end_pos[0] - start_pos[0]) / simulation_time;
(void)total_speed;
(void)avg_speed;
(void)var_speed;
(void)muscle_power;
float score = muscle_power > 0.f ? (total_speed) : 0.f;
// float score = muscle_power > 0.f ? (total_speed / std::sqrt(muscle_power)) : 0.f;
// float score = muscle_power > 0.f ? (total_speed * (sim_creature.cells.size())) : 0.f;
// float score = muscle_power > 0.f ? (total_speed / std::sqrt(muscle_power) * std::sqrt(sim_creature.cells.size())) : 0.f;
// float score = muscle_power > 0.f ? (total_speed / muscle_power / std::sqrt(sim_creature.cells.size())) : 0.f;
if (sim_creature.dead())
score = 0.f;
creature.score = score;
}
evaluated_creatures[i] = std::move(creature);
}
if (stopping_)
break;
int const best = population_.size() / 4;
int const children_count = population_.size() - best;
population_.clear();
std::partial_sort(evaluated_creatures.begin(), evaluated_creatures.begin() + best, evaluated_creatures.end(), [](auto const & a, auto const & b){ return *a.score > *b.score; });
evaluated_creatures.resize(best);
population_ = std::move(evaluated_creatures);
float best_score = *population_.front().score;
int best_generation = population_.front().generation;
float avg_score = 0.f;
for (auto const & p : population_)
avg_score += *p.score;
avg_score /= population_.size();
float const mutation_boost = 1.f; //std::exp2(std::min(5.f, (generation - best_generation) / 10.f));
float const change_type_probability = 1.f / 8.f * mutation_boost;
float const erase_probability = 1.f / 8.f * mutation_boost;
float const grow_probability = 1.f / 8.f * mutation_boost;
float const sex_probability = 0.9f * mutation_boost;
std::vector<creature> next_display;
for (int i = 0; i < display_creature_count; ++i)
// next_display.push_back(creatures_with_score[i * creatures_with_score.size() / display_creature_count].first);
next_display.push_back(population_[i]);
std::vector<float> scores(population_.size());
for (int i = 0; i < population_.size(); ++i)
scores[i] = *population_[i].score;
random::weighted_distribution<float> random_parent(std::move(scores));
for (int i = 0; i < children_count; ++i)
{
auto const & parent1 = population_[random_parent(rng)];
auto const & parent2 = population_[random_parent(rng)];
creature_blueprint blueprint = parent1.blueprint;
if (random::uniform<float>(rng) < sex_probability)
{
for (auto const & cell : parent2.blueprint)
if (random::uniform<int>(rng, 0, 1) == 0)
blueprint[cell.first] = cell.second;
}
std::vector<math::point<int, 2>> erase_cells;
for (auto const & cell : blueprint)
{
if (random::uniform<float>(rng) < erase_probability)
erase_cells.push_back(cell.first);
}
for (auto cell : erase_cells)
blueprint.erase(cell);
std::vector<std::pair<math::point<int, 2>, cell_info>> grow_cells;
for (auto const & cell : blueprint)
{
for (auto n : neighbours)
{
auto ncell = cell.first + n;
if (blueprint.contains(ncell))
continue;
if (random::uniform<float>(rng) < grow_probability)
grow_cells.push_back({ncell, random_cell(rng)});
}
}
for (auto cell : grow_cells)
blueprint.insert(cell);
creature new_creature;
{
creature_builder builder(std::move(blueprint));
if (builder.connected())
new_creature = builder.build(generation);
else
new_creature = parent1;
}
for (auto & cell : new_creature.cells)
{
if (random::uniform<float>(rng) < change_type_probability)
{
cell.info = random_cell(rng);
new_creature.score = std::nullopt;
new_creature.generation = generation;
}
if (cell.info.type == cell_type::muscle)
{
cell.info.size += randn(rng) * 0.1f * mutation_boost;
cell.info.phase += randn(rng) * 0.1f * mutation_boost;
cell.info.period += randn(rng) * 0.1f * mutation_boost;
cell.info.size = math::clamp(cell.info.size, cell_size_range);
cell.info.phase = std::fmod(cell.info.phase, 1.f);
cell.info.period = math::clamp(cell.info.period, cell_period_range);
new_creature.score = std::nullopt;
new_creature.generation = generation;
}
}
population_.push_back(std::move(new_creature));
}
std::lock_guard lock{next_display_mutex_};
next_display_creatures_ = std::move(next_display);
next_display_generation_ = generation;
has_next_creatures_ = true;
generation_best_score_ = best_score;
generation_avg_score_ = avg_score;
generation_best_generation_ = best_generation;
}
}
};
namespace psemek::app
{
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<soft_creatures_2d_app>({.name = "Soft-body creatures", .multisampling = 4});
}
}

1071
examples/soft_plants_2d.cpp Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/gl.hpp>
#include <psemek/gfx/mesh.hpp>
@ -8,13 +8,13 @@
#include <psemek/gfx/renderer/simple.hpp>
#include <psemek/gfx/error.hpp>
#include <psemek/geom/camera.hpp>
#include <psemek/geom/math.hpp>
#include <psemek/geom/homogeneous.hpp>
#include <psemek/geom/gauss.hpp>
#include <psemek/geom/orientation.hpp>
#include <psemek/geom/intersection.hpp>
#include <psemek/geom/distance.hpp>
#include <psemek/math/camera.hpp>
#include <psemek/math/math.hpp>
#include <psemek/math/homogeneous.hpp>
#include <psemek/math/gauss.hpp>
#include <psemek/math/orientation.hpp>
#include <psemek/math/intersection.hpp>
#include <psemek/math/distance.hpp>
#include <psemek/cg/body/icosahedron.hpp>
#include <psemek/cg/body/box.hpp>
@ -83,7 +83,7 @@ static const float exaggeration = 1.f;
struct height_provider
{
float height_at(geom::vector<float, 3> const & v);
float height_at(math::vector<float, 3> const & v);
struct datum_id_hash
{
@ -100,13 +100,13 @@ struct height_provider
std::unordered_set<std::pair<int, int>, datum_id_hash> no_datums;
};
float height_provider::height_at(geom::vector<float, 3> const & v)
float height_provider::height_at(math::vector<float, 3> const & v)
{
static std::filesystem::path const data_path = "/home/lisyarus/data/srtm/dem";
float const lat = geom::deg(std::asin(v[2]));
float const lat = math::deg(std::asin(v[2]));
float const lon = geom::deg(std::atan2(v[0], -v[1]));
float const lon = math::deg(std::atan2(v[0], -v[1]));
int ilat = std::floor(lat);
int ilon = std::floor(lon);
@ -199,8 +199,8 @@ float height_provider::height_at(geom::vector<float, 3> const & v)
return (int)values[(3600 - lat) * 3601 + lon] - 32768;
};
int const tlat = geom::clamp<int>(std::floor((lat - ilat) * 3600), {0, 3600 - 1});
int const tlon = geom::clamp<int>(std::floor((lon - ilon) * 3600), {0, 3600 - 1});
int const tlat = math::clamp<int>(std::floor((lat - ilat) * 3600), {0, 3600 - 1});
int const tlon = math::clamp<int>(std::floor((lon - ilon) * 3600), {0, 3600 - 1});
float const mlat = (lat - ilat) * 3600.f - tlat;
float const mlon = (lon - ilon) * 3600.f - tlon;
@ -210,7 +210,7 @@ float height_provider::height_at(geom::vector<float, 3> const & v)
float h10 = at(tlat, tlon + 1);
float h11 = at(tlat + 1, tlon + 1);
return geom::lerp(geom::lerp(h00, h01, mlat), geom::lerp(h10, h11, mlat), mlon);
return math::lerp(math::lerp(h00, h01, mlat), math::lerp(h10, h11, mlat), mlon);
}
static constexpr int node_size_log2 = 8;
@ -256,8 +256,8 @@ void node_cache::store(std::size_t uid, std::vector<std::int16_t> const & data)
struct node
{
geom::vector<float, 3> v[3];
geom::interval<float> height_range;
math::vector<float, 3> v[3];
math::interval<float> height_range;
virtual bool draw(int level) = 0;
virtual node * child(int id) = 0;
@ -312,7 +312,7 @@ private:
};
node_controller::node_controller()
: icosahedron_{geom::point<float, 3>::zero(), 1.f}
: icosahedron_{math::point<float, 3>::zero(), 1.f}
{
std::vector<std::uint16_t> indices;
index_counts_[0] = 0;
@ -356,9 +356,9 @@ node * node_controller::root(int f)
n->uid = (1 << 5) | f;
auto face = cg::faces(icosahedron_)[f];
n->v[0] = icosahedron_.vertices[face[0]] - geom::point<float, 3>::zero();
n->v[1] = icosahedron_.vertices[face[1]] - geom::point<float, 3>::zero();
n->v[2] = icosahedron_.vertices[face[2]] - geom::point<float, 3>::zero();
n->v[0] = icosahedron_.vertices[face[0]] - math::point<float, 3>::zero();
n->v[1] = icosahedron_.vertices[face[1]] - math::point<float, 3>::zero();
n->v[2] = icosahedron_.vertices[face[2]] - math::point<float, 3>::zero();
roots_[f] = std::move(n);
}
@ -450,7 +450,7 @@ node * node_controller::node_impl::child(int id)
float t1 = (1.f * j) / (1.f * node_child_size);
float t2 = 1.f - t0 - t1;
return geom::normalized(v[0] * t0 + v[1] * t1 + v[2] * t2);
return math::normalized(v[0] * t0 + v[1] * t1 + v[2] * t2);
};
n->v[0] = at(i0, j0);
@ -487,7 +487,7 @@ void node_controller::node_impl::load_heights()
float t1 = (1.f * j) / (1.f * node_size);
float t2 = 1.f - t0 - t1;
return geom::normalized(v[0] * t0 + v[1] * t1 + v[2] * t2);
return math::normalized(v[0] * t0 + v[1] * t1 + v[2] * t2);
};
for (int i = 0; i <= node_size; ++i)
@ -601,19 +601,19 @@ void main()
})";
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 present() override;
geom::free_camera camera;
geom::matrix<float, 4, 4> camera_transform;
math::free_camera camera;
math::matrix<float, 4, 4> camera_transform;
bool camera_forward = false;
node_controller nodes;
@ -632,24 +632,23 @@ struct srtm_app
gfx::painter painter;
};
srtm_app::srtm_app()
: app("SRTM", 4)
srtm_app::srtm_app(options const &, context const & context)
{
vsync(true);
show_cursor(false);
context.vsync(true);
context.show_cursor(false);
camera.fov_y = geom::rad(45.f);
camera.fov_y = math::rad(45.f);
camera.near_clip = 0.0001f;
camera.far_clip = 10.f;
camera.pos = {0.f, -10.f, 0.f};
camera.rotateYZ(geom::rad(-90.f));
camera.rotateYZ(math::rad(-90.f));
camera_transform = camera.transform();
selected_mesh.setup<geom::point<float, 3>>();
selected_mesh.setup<math::point<float, 3>>();
{
util::array<gfx::color_rgb, 1> colors({16});
util::ndarray<gfx::color_rgb, 1> colors({16});
auto * c = colors.data();
@ -675,7 +674,7 @@ srtm_app::srtm_app()
}
{
util::array<gfx::color_rgb, 1> colors({5});
util::ndarray<gfx::color_rgb, 1> colors({5});
colors(0) = {0, 63, 127};
colors(1) = {0, 0, 127};
@ -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);
camera.set_fov(camera.fov_y, (1.f * width) / height);
app::application_base::on_event(event);
camera.set_fov(camera.fov_y, (1.f * event.size[0]) / event.size[1]);
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);
camera.rotateYZ(0.01f * dy);
app::application_base::on_event(event);
auto const delta = event.position - old_mouse;
camera.rotateZX(0.01f * delta[0]);
camera.rotateYZ(0.01f * delta[1]);
}
void srtm_app::update()
@ -708,48 +711,48 @@ void srtm_app::update()
float dt = frame_clock.restart().count();
frame_dt_average.push(dt);
if (is_key_down(SDLK_q))
if (state().key_down.contains(app::keycode::Q))
{
camera.rotateXY(- 4.f * dt);
}
if (is_key_down(SDLK_e))
if (state().key_down.contains(app::keycode::E))
{
camera.rotateXY(4.f * dt);
}
float const camera_speed = std::min(5.f, geom::distance(camera.pos, geom::point<float, 3>::zero()) - 1.f);
float const camera_speed = std::min(5.f, math::distance(camera.pos, math::point<float, 3>::zero()) - 1.f);
auto const camera_forward = camera.direction();
auto const camera_up = camera.axis_y();
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;
}
if (is_key_down(SDLK_s))
if (state().key_down.contains(app::keycode::S))
{
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;
}
if (is_key_down(SDLK_a))
if (state().key_down.contains(app::keycode::A))
{
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;
}
if (is_key_down(SDLK_LCTRL))
if (state().key_down.contains(app::keycode::LCTRL))
{
camera.pos -= camera_speed * dt * camera_up;
}
@ -780,11 +783,11 @@ std::ostream & operator << (std::ostream & os, std::vector<T> const & v)
void srtm_app::present()
{
cg::icosahedron<float> icosahedron{geom::point<float, 3>::zero(), 1.f};
cg::icosahedron<float> icosahedron{math::point<float, 3>::zero(), 1.f};
auto const & icosa_vertices = cg::vertices(icosahedron);
auto const & icosa_faces = cg::faces(icosahedron);
auto const icosa_side = geom::distance(icosa_vertices[icosa_faces[0][0]], icosa_vertices[icosa_faces[0][1]]);
auto const icosa_side = math::distance(icosa_vertices[icosa_faces[0][0]], icosa_vertices[icosa_faces[0][1]]);
std::vector<std::string> info;
@ -803,7 +806,7 @@ void srtm_app::present()
gl::PrimitiveRestartIndex(0xffffu);
{
auto d = geom::distance(camera.pos, geom::point<float, 3>::zero());
auto d = math::distance(camera.pos, math::point<float, 3>::zero());
camera.far_clip = std::sqrt(d * d + 1.f);
camera.near_clip = (d > 2.f) ? d - 2.f : 0.0001f;
}
@ -812,12 +815,12 @@ void srtm_app::present()
auto const camera_pos = camera.position();
auto const camera_direction = camera.direction();
info.push_back(util::to_string("Camera height: ", (geom::distance(camera_pos, geom::point<float, 3>::zero()) - 1.f) * 6400000.f, " m"));
info.push_back(util::to_string("Camera height: ", (math::distance(camera_pos, math::point<float, 3>::zero()) - 1.f) * 6400000.f, " m"));
auto const frustum = cg::frustum(camera_transform);
(void)frustum;
auto light = geom::normalized(geom::vector{camera_pos[0]-camera_pos[1], camera_pos[1]+camera_pos[0], 0.f});
auto light = math::normalized(math::vector{camera_pos[0]-camera_pos[1], camera_pos[1]+camera_pos[0], 0.f});
tile_close_program.bind();
tile_close_program["u_transform"] = camera_transform;
@ -843,20 +846,20 @@ void srtm_app::present()
info.push_back(util::to_string("Camera pos: ", camera_pos));
std::vector<geom::point<float, 3>> selected_vertices;
std::vector<math::point<float, 3>> selected_vertices;
std::size_t rendered_tiles = 0;
std::vector<std::size_t> id;
auto visit = util::recursive([&](auto & self, node * n, int level = 0) -> bool
{
auto const & v = n->v;
auto const o = geom::point<float, 3>::zero();
auto const o = math::point<float, 3>::zero();
{
bool culled = true;
for (std::size_t i = 0; i < 3; ++i)
{
if (geom::dot(v[i], geom::normalized(camera_pos - o)) >= 0.f)
if (math::dot(v[i], math::normalized(camera_pos - o)) >= 0.f)
{
culled = false;
break;
@ -870,34 +873,34 @@ void srtm_app::present()
bool const flat = n->height_range.max == n->height_range.min;
auto const m3 = (v[0] + v[1] + v[2]) / 3.f;
auto const m = geom::normalized(m3);
auto const m = math::normalized(m3);
auto const m0 = m * (n->height_range.empty() ? 0.f : n->height_range.min) / 6400000.f;
auto const m1 = m * (n->height_range.empty() ? 1.f : flat ? n->height_range.min + 1.f : n->height_range.max) / 6400000.f + (m - m3);
geom::triangle<geom::point<float, 3>> t{o + v[0] + m0, o + v[1] + m0, o + v[2] + m0};
math::triangle<math::point<float, 3>> t{o + v[0] + m0, o + v[1] + m0, o + v[2] + m0};
cg::triangular_prism<float> body{t, m1 - m0};
// if (cg::separation(body, frustum).second > 0.f)
// return true;
bool const selected = geom::intersect(geom::ray{camera_pos, camera_direction}, t);
bool const selected = math::intersect(math::ray{camera_pos, camera_direction}, t);
float on_screen_unit;
{
auto edge = [](auto const & v0, auto const & v1, auto const & u) -> std::optional<float>
{
auto const n = geom::normalized(geom::cross(v0, v1));
auto v = geom::normalized(u - n * dot(u, n));
if (geom::dot(geom::cross(v0, v), geom::cross(v, v1)) >= 0.f)
return geom::length(v - u);
auto const n = math::normalized(math::cross(v0, v1));
auto v = math::normalized(u - n * dot(u, n));
if (math::dot(math::cross(v0, v), math::cross(v, v1)) >= 0.f)
return math::length(v - u);
return std::nullopt;
};
float distance = std::numeric_limits<float>::infinity();
auto c = camera_pos - o;
if (geom::det(v[0], v[1], c) >= 0.f && geom::det(v[1], v[2], c) >= 0.f && geom::det(v[2], v[0], c) >= 0.f)
distance = std::min(distance, geom::length(c) - 1.f);
if (math::det(v[0], v[1], c) >= 0.f && math::det(v[1], v[2], c) >= 0.f && math::det(v[2], v[0], c) >= 0.f)
distance = std::min(distance, math::length(c) - 1.f);
else
{
if (auto d = edge(v[0], v[1], c); d)
@ -907,11 +910,11 @@ void srtm_app::present()
if (auto d = edge(v[2], v[0], c); d)
distance = std::min(distance, *d);
}
distance = std::min(distance, geom::length(c - v[0]));
distance = std::min(distance, geom::length(c - v[1]));
distance = std::min(distance, geom::length(c - v[2]));
distance = std::min(distance, math::length(c - v[0]));
distance = std::min(distance, math::length(c - v[1]));
distance = std::min(distance, math::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);
@ -945,7 +948,7 @@ void srtm_app::present()
if (!should_draw_children || !all_children_drawn)
{
tile_n = geom::clamp(tile_n, {0, node_size_log2});
tile_n = math::clamp(tile_n, {0, node_size_log2});
++rendered_tiles;
@ -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.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));
{
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, height() / 2.f - s}, {width() / 2.f, height() / 2.f + s}, 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);
}
info.push_back(util::to_string("Nodes: ", nodes.node_count()));
info.push_back(util::to_string("Tasks: ", nodes.loader_queue_size()));
@ -1001,7 +1007,7 @@ void srtm_app::present()
opts.y = gfx::painter::y_align::top;
opts.f = gfx::painter::font::font_9x12;
opts.c = gfx::gray;
opts.scale = 2.f;
opts.scale = {2.f, 2.f};
for (int l = 0; l < info.size(); ++l)
{
@ -1020,10 +1026,15 @@ void srtm_app::present()
gl::Enable(gl::BLEND);
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
gl::Disable(gl::DEPTH_TEST);
painter.render(geom::window_camera{width(), height()}.transform());
painter.render(math::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,10 +1,10 @@
#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/gfx/gl.hpp>
#include <psemek/geom/scale.hpp>
#include <psemek/geom/camera.hpp>
#include <psemek/geom/constants.hpp>
#include <psemek/math/scale.hpp>
#include <psemek/math/camera.hpp>
#include <psemek/math/constants.hpp>
#include <psemek/cg/bbox.hpp>
#include <psemek/cg/triangulation/ear_clipping.hpp>
#include <psemek/cg/triangulation/monotone.hpp>
@ -13,8 +13,8 @@
#include <psemek/prof/profiler.hpp>
#include <psemek/util/clock.hpp>
#include <psemek/util/to_string.hpp>
#include <psemek/geom/homogeneous.hpp>
#include <psemek/geom/swizzle.hpp>
#include <psemek/math/homogeneous.hpp>
#include <psemek/math/swizzle.hpp>
#include <psemek/random/generator.hpp>
#include <psemek/random/uniform.hpp>
@ -23,16 +23,15 @@
using namespace psemek;
struct triangulation_app
: app::app
: app::application_base
{
triangulation_app()
: app("Triangulation example", 16)
triangulation_app(options const &, context const &)
{
std::ifstream in(PSEMEK_EXAMPLES_DIR "/turkey");
while (true)
{
geom::point<float, 2> p;
math::point<float, 2> p;
in >> p[0] >> p[1];
if (!in)
break;
@ -64,8 +63,8 @@ struct triangulation_app
{
prof::profiler prof("triangulate");
// auto dcel = cg::ear_clipping<std::uint16_t>(geom::fast, points_.begin(), points_.end());
auto dcel = cg::monotone_triangulation<std::uint16_t>(geom::fast, points_.begin(), points_.end());
// auto dcel = cg::ear_clipping<std::uint16_t>(math::fast, points_.begin(), points_.end());
auto dcel = cg::monotone_triangulation<std::uint16_t>(math::fast, points_.begin(), points_.end());
edges_ = cg::edge_mesh(dcel);
triangles_ = cg::triangle_mesh(dcel);
}
@ -80,36 +79,32 @@ struct triangulation_app
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())
drag_start_ = mouse();
if (event.down && event.button == app::mouse_button::left)
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();
drag_start_ = std::nullopt;
}
void on_mouse_wheel(int delta) override
{
camera_size_tgt_ *= std::pow(0.8f, delta);
camera_size_tgt_ *= std::pow(0.8f, event.delta);
}
void update() override
{
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;
camera_center_ -= geom::cast<float>(delta) * camera_size_ / (1.f * height());
drag_start_ = mouse();
camera_center_ -= math::cast<float>(delta) * camera_size_ / (1.f * state().size[1]);
drag_start_ = state().mouse;
}
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::Clear(gl::COLOR_BUFFER_BIT);
float aspect_ratio = (width() * 1.f) / height();
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 aspect_ratio = (state().size[0] * 1.f) / state().size[1];
math::box<float, 2> view_bbox = math::expand(math::box<float, 2>::singleton(camera_center_), math::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)
{
@ -149,18 +144,17 @@ struct triangulation_app
for (std::size_t i = 0; i < points_.size(); ++i)
edge(i, (i + 1) % points_.size(), gfx::black);
auto camera_transform = geom::orthographic_camera{view_bbox}.transform();
auto camera_transform = math::orthographic_camera{view_bbox}.transform();
painter_.render(camera_transform);
if (auto m = mouse(); m)
{
geom::point<float, 2> m_world;
m_world[0] = geom::lerp(view_bbox[0], (*m)[0] * 1.f / width());
m_world[1] = geom::lerp(view_bbox[1], 1.f - (*m)[1] * 1.f / height());
math::point<float, 2> m_world;
m_world[0] = math::lerp(view_bbox[0], state().mouse[0] * 1.f / state().size[0]);
m_world[1] = math::lerp(view_bbox[1], 1.f - state().mouse[1] * 1.f / state().size[1]);
auto compare = [&](auto i, auto j){
return geom::distance(points_[i], m_world) < geom::distance(points_[j], m_world);
return math::distance(points_[i], m_world) < math::distance(points_[j], m_world);
};
std::size_t const n_closest = 8;
@ -173,34 +167,34 @@ struct triangulation_app
{
auto i = closest_points_[j];
if (geom::distance(points_[i], m_world) > max_distance)
if (math::distance(points_[i], m_world) > max_distance)
break;
gfx::painter::text_options opts;
opts.c = {0, 0, 0, 255};
opts.x = gfx::painter::x_align::left;
opts.y = gfx::painter::y_align::bottom;
opts.scale = 2.f;
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[1] = std::round((0.5f - p[1] * 0.5f) * height());
opts.scale = {2.f, 2.f};
auto p = math::swizzle<0, 1>(math::as_point(camera_transform * math::homogeneous(math::swizzle<0, 1, -1>(points_[i]))));
p[0] = std::round((p[0] * 0.5f + 0.5f) * state().size[0]);
p[1] = std::round((0.5f - p[1] * 0.5f) * state().size[1]);
painter_.text(p, util::to_string(i), opts);
}
}
painter_.render(geom::window_camera{width(), height()}.transform());
painter_.render(math::window_camera{state().size[0], state().size[1]}.transform());
}
private:
std::vector<geom::point<float, 2>> points_;
std::vector<geom::segment<std::uint16_t>> edges_;
std::vector<geom::triangle<std::uint16_t>> triangles_;
geom::box<float, 2> bbox_;
geom::point<float, 2> camera_center_;
std::vector<math::point<float, 2>> points_;
std::vector<math::segment<std::uint16_t>> edges_;
std::vector<math::triangle<std::uint16_t>> triangles_;
math::box<float, 2> bbox_;
math::point<float, 2> camera_center_;
float camera_size_;
float camera_size_tgt_;
std::optional<geom::point<int, 2>> drag_start_;
std::optional<math::point<int, 2>> drag_start_;
std::vector<std::uint16_t> closest_points_;
gfx::painter painter_;
@ -208,7 +202,25 @@ private:
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,122 +0,0 @@
#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/gfx/gl.hpp>
#include <psemek/ui/controller.hpp>
#include <psemek/ui/default_element_factory.hpp>
#include <psemek/ui/font.hpp>
#include <psemek/ui/frame.hpp>
#include <psemek/ui/grid_layout.hpp>
#include <psemek/ui/button.hpp>
#include <psemek/ui/screen.hpp>
#include <psemek/ui/window.hpp>
#include <psemek/async/event_loop.hpp>
#include <psemek/util/recursive.hpp>
#include <psemek/util/to_string.hpp>
#include <psemek/geom/camera.hpp>
using namespace psemek;
static std::string const lorem_ipsum =
R"(Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.)";
static std::string const to_be =
R"(To be, or not to be, that is the question:
Whether 'tis nobler in the mind to suffer
The slings and arrows of outrageous fortune,
Or to take Arms against a Sea of troubles,
And by opposing end them: to die, to sleep;
No more; and by a sleep, to say we end
The heart-ache, and the thousand natural shocks
That Flesh is heir to? 'Tis a consummation)";
struct ui_example
: app::app
{
ui_example()
: app("UI example", 1)
, ui_controller(&loop)
{
auto style = std::make_shared<ui::style>();
style->font = ui::make_default_9x12_font();
style->text_scale = 2;
auto screen = element_factory.make_screen();
screen->set_style(style);
auto window = element_factory.make_window("Settings");
auto frame = element_factory.make_frame();
{
auto style = std::make_shared<ui::style>();
style->border_width = 0;
style->shadow_offset = {0, 0};
frame->set_style(style);
}
auto label = element_factory.make_label(to_be);
label->set_overflow(ui::label::overflow_mode::ellipsis);
frame->set_child(label);
window->set_child(frame);
screen->add_child(window, ui::screen::x_policy::center, ui::screen::y_policy::center);
ui_controller.set_root(std::move(screen));
}
void on_resize(int width, int height) override
{
app::on_resize(width, height);
ui_controller.reshape({{{0, width}, {0, height}}});
}
void on_mouse_move(int x, int y, int dx, int dy) override
{
app::on_mouse_move(x, y, dx, dy);
ui_controller.event(ui::mouse_move{{x, y}});
}
void on_left_button_down() override
{
app::on_left_button_down();
ui_controller.event(ui::mouse_click{ui::mouse_button::left, true});
}
void on_left_button_up() override
{
app::on_left_button_up();
ui_controller.event(ui::mouse_click{ui::mouse_button::left, false});
}
void update() override;
void present() override;
async::event_loop loop;
ui::controller ui_controller;
ui::default_element_factory element_factory;
};
void ui_example::update()
{
loop.pump();
}
void ui_example::present()
{
gl::ClearColor(0.8f, 0.8f, 0.8f, 1.f);
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
gfx::render_target rt;
rt.framebuffer = &gfx::framebuffer::null();
rt.draw_buffer = gl::BACK_LEFT;
rt.viewport = {{{0, width()}, {0, height()}}};
ui_controller.render(rt);
}
int main()
{
return app::main<ui_example>();
}

View file

@ -1,24 +1,25 @@
#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/geom/orthographic.hpp>
#include <psemek/math/orthographic.hpp>
#include <psemek/log/log.hpp>
using namespace psemek;
struct water_1d_app
: app::app
: app::application_base
{
water_1d_app();
water_1d_app(options const &, context const &);
void update() 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;
math::box<float, 2> simulation_area;
float aspect_ratio;
std::size_t const N = 50;
std::size_t const N = 100;
std::vector<float> bed;
std::vector<float> height;
std::vector<float> discharge;
@ -28,8 +29,7 @@ struct water_1d_app
gfx::painter painter;
};
water_1d_app::water_1d_app()
: app("Water 1D simulation", 4)
water_1d_app::water_1d_app(options const &, context const &)
{
simulation_area[0] = {-1.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] += (100.f / (N - i)) / N;
bed[i] += 0.125f * std::exp(- 1000.f * math::sqr(i * 1.f / N - 0.5f));
// bed[i] += (0.25f * i) / N;
// height[i] += (0.3f * i) / N;
// height[i] = 0.3f - bed[i];
// height[i] = 0.25f;
}
// bed[N * 3 / 4] = 0.2f;
// bed[N * 3 / 4] += 0.2f;
discharge[0] = 0.04f;
discharge[N] = 0.f;
// height[0] = 20.f;
}
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 g = 10.f;
float const max_speed = 1.f;
@ -134,7 +138,7 @@ void water_1d_app::present()
gl::ClearColor(0.8f, 0.8f, 0.8f, 0.8f);
gl::Clear(gl::COLOR_BUFFER_BIT);
geom::box<float, 3> view_area;
math::box<float, 3> view_area;
{
view_area[0] = simulation_area[0];
view_area[1] = simulation_area[1];
@ -148,16 +152,37 @@ void water_1d_app::present()
view_area[0].max += extra_x / 2.f;
}
geom::matrix<float, 4, 4> const transform = geom::orthographic{view_area}.homogeneous_matrix();
math::matrix<float, 4, 4> const transform = math::orthographic{view_area}.homogeneous_matrix();
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());
painter.rect({{{x(i), x(i + 1)}, {simulation_area[1].min + bed[i], simulation_area[1].min + bed[i] + height[i]}}}, gfx::blue);
math::point p0{x(i + 0) * 0.5f + x(i + 1) * 0.5f, simulation_area[1].min};
math::point p1{x(i + 1) * 0.5f + x(i + 2) * 0.5f, simulation_area[1].min};
math::point q0{p0[0], simulation_area[1].min + bed[i]};
math::point q1{p1[0], simulation_area[1].min + bed[i + 1]};
math::point w0{p0[0], simulation_area[1].min + bed[i] + height[i]};
math::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)
{
painter.line({x(i), 0.f}, {x(i), discharge[i]}, 0.005f, gfx::red);
@ -166,19 +191,24 @@ void water_1d_app::present()
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
{
return geom::lerp(simulation_area[0], (1.f * i) / N);
return math::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,212 +1,409 @@
#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/geom/orthographic.hpp>
#include <psemek/geom/gauss.hpp>
#include <psemek/math/orthographic.hpp>
#include <psemek/math/camera.hpp>
#include <psemek/log/log.hpp>
#include <psemek/util/ndarray.hpp>
#include <psemek/random/device.hpp>
#include <psemek/random/generator.hpp>
#include <psemek/random/uniform.hpp>
#include <psemek/random/uniform_sphere.hpp>
#include <psemek/pcg/perlin.hpp>
#include <format>
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
: app::application_base
{
water_2d_app();
water_2d_app(options const &, context const &);
void update() override;
void present() override;
void on_resize(int width, int height) override;
void on_event(app::resize_event const & event) override;
void on_event(app::key_event const & event) override;
geom::box<float, 2> simulation_area;
math::box<float, 2> simulation_area;
math::box<float, 2> inner_area;
float aspect_ratio;
int const N = 25;
int const first_type_count = (N * (N + 1)) / 2;
random::generator rng{random::device{}};
std::vector<float> water_height;
std::vector<geom::vector<float, 2>> flux[3];
int const N = 256;
geom::point<float, 2> p[3];
float time = 0.f;
util::ndarray<float, 2> bed;
util::ndarray<float, 2> water;
util::ndarray<float, 2> flowx;
util::ndarray<float, 2> flowy;
util::ndarray<math::vector<float, 2>, 2> velocity;
util::ndarray<float, 2> sediment;
util::ndarray<float, 2> new_sediment;
gfx::painter painter;
bool paused = true;
bool show_water = true;
bool show_velocity = false;
bool show_particles = false;
bool erosion_on = false;
bool rain_on = false;
struct particle
{
math::point<float, 2> position;
int lifetime;
};
std::vector<particle> particles;
};
water_2d_app::water_2d_app()
: app("Water 2D simulation", 4)
water_2d_app::water_2d_app(options const &, context const & ctx)
{
p[0] = {- N / 2.f, 0.f};
p[1] = { N / 2.f, 0.f};
p[2] = {0.f, N * std::sqrt(0.75f)};
(void)ctx;
// ctx.vsync(true);
simulation_area |= p[0];
simulation_area |= p[1];
simulation_area |= p[2];
simulation_area[0] = {-1.f, 1.f};
simulation_area[1] = {-1.f, 1.f};
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});
inner_area = math::shrink(simulation_area, simulation_area.dimensions() / (2.f * N));
for (int i = 0; i < N; ++i)
bed.assign({N, N}, 0.f);
water.assign({N, N}, 0.f);
flowx.assign({N + 1, N}, 0.f);
flowy.assign({N, N + 1}, 0.f);
velocity.assign({N, N}, math::vector<float, 2>::zero());
sediment.assign({N, N}, 0.f);
new_sediment.assign({N, N}, 0.f);
// water(N / 2, N / 2) = 100.f;
random::uniform_sphere_vector_distribution<float, 2> d;
util::ndarray<math::vector<float, 2>, 2> grads({9, 9});
for (auto & v : grads)
v = d(rng);
pcg::perlin<float, 2> noise(std::move(grads), pcg::seamless);
for (int y = 0; y < N; ++y)
{
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();
for (int x = 0; x < N; ++x)
{
// if (x == N / 2 && (y != N / 4 && y != 3 * N / 4))
// bed(x, y) = 100.f;
[[maybe_unused]] float tx = (x + 0.5f) / N;
[[maybe_unused]] float ty = (y + 0.5f) / N;
float n = 0.f;
// Archipelago
n = 0.5f * noise(tx, ty) + 0.5f * noise(math::frac(2.f * tx), math::frac(2.f * ty));
n -= 0.5f * math::distance(math::point{tx, ty}, math::point{0.5f, 0.5f});
n = 5.f * math::smoothstep(math::clamp(math::unlerp({0.45f, 0.55f}, n), {0.f, 1.f}));
// Island
// n = 4.f * std::max(0.f, 1.f - 3.f * math::distance(math::point{tx, ty}, math::point{0.5f, 0.5f}) + (2.f * noise(tx, ty) - 1.f) * 0.25f);
// Delta
// n = 4.f * std::abs(2.f * noise(tx, ty) - 1.f) * (1.f - tx);
// Double delta
// n = 4.f * std::abs(2.f * noise(tx, ty) - 1.f) * std::pow(4.f * tx * (1.f - tx), 2.f);
// River
// n = std::min(4.f, math::lerp(10.f, 20.f, noise(tx, ty)) * std::abs(ty - math::lerp({0.4f, 0.6f}, noise(tx, 0.f))));
// n = 0.f;
// Canyon
// n = 10.f * math::clamp(10.f * math::lerp(-1.f, 1.f, noise(tx, ty)), {0.f, 1.f});
// Canyon v2
// n = math::clamp(20.f * std::abs(2.f * noise(tx, ty) - 1.f) - 2.f, {0.f, 5.f});
// Shore
// n = math::clamp(20.f * (ty - math::lerp(0.4f, 0.6f, noise(0.f, tx))), {-4.f, 4.f}) + 4.f;
// Circles
// n = 20.f * std::max(0.f, 1.f - 25.f * math::length(math::pointwise_mult({1.f, 0.1f}, math::point{tx, std::fmod(10.f * ty, 1.f)} - math::point{0.5f, 0.5f})));
bed(x, y) = n;
// water(x, y) = std::max(0.f, 4.0f - bed(x, y));
// water(x, y) = 10.f - bed(x, y);
// if (x == 0)
// water(x, y) = 100.f - bed(x, y);
water(x, y) = std::max(0.f, 1.f - bed(x, y)) * (0.5f + 0.5f * noise(tx, ty));
flowx(x, y) = (2.f * ty - 1.f) * water(x, y) * 10.f / N;
}
}
// water(N / 2, N / 2) = 10000.f;
}
float const dt = 0.001f;
void water_2d_app::update()
{
float const dt = 0.01f;
float const max_speed = 10.f;
if (paused)
return;
geom::vector<float, 2> const flux_normal[3] =
float const dx = simulation_area[0].length() / N;
float const dy = simulation_area[1].length() / N;
float const g = 10.f;
float const friction = std::pow(1.f, dt);
float const viscosity = 0.f;
float const sediment_capacity = 0.1f;
float const erosion = 1.f;
float const deposition = 10.f;
float const coriolis = 0.01f;
int const max_particles = 16 * 1024;
int const particles_per_frame = 16;
int const max_particle_lifetime = max_particles / particles_per_frame;
// Init boundary flows
for (int x = 0; x < N; ++x)
{
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 },
};
// flowy(x, 0) = -1.f / N;
// flowy(x, N) = 1.f / N;
auto const rot1 = [this](std::pair<int, int> const & p)
// if (x >= 3 * N / 4) flowy(x, 0) = 3.f / N;
// if (x < N / 4) flowy(x, N) = -3.f / N;
// flowy(x, 0) = 5.f * std::sin(10.f * time) / N;
flowy(x, 0) = 0.f;
flowy(x, N) = 0.f;
}
for (int y = 0; y < N; ++y)
{
return std::pair{N - p.second, p.first - p.second};
};
// flowx(0, y) = 5.f * std::sin(10.f * time) / N;
auto const rot2 = [rot1](std::pair<int, int> const & p)
// flowx(0, y) = 10.f / N;
// flowx(N, y) = 1.f / N;
// flowx(0, y) = 10.f * ((y + 0.5f) / N) / N;
// flowx(N, y) = - (10.f / N - flowx(0, y));
// if (y < N / 4) flowx(0, y) = 3.f / N;
// if (y >= 3 * N / 4) flowx(N, y) = -3.f / N;
// if (bed(0, y) < 0.75f)
// flowx(0, y) = 4.f / N;
// flowx(0, y) = 5.f * math::sqr(std::max(0.f, std::sin(15.f * time))) / N;
// if (bed(N - 1, y) < 0.75f)
// flowx(N, y) = 5.f / N;
}
// Rain :)
if (rain_on)
for (int y = 0; y < N; ++y)
for (int x = 0; x < N; ++x)
// if (random::uniform<float>(rng) < 0.1f)
water(x, y) += random::uniform<float>(rng) * dt;
// water(x, y) += dt;
// water(N/2, N/2) += 1000.f * dt;
// Update X flows
for (int y = 0; y < N; ++y)
{
return rot1(rot1(p));
};
for (int x = 1; x < N; ++x)
flowx(x, y) = friction * flowx(x, y) + g * dt * (water(x - 1, y) + bed(x - 1, y) - water(x, y) - bed(x, y));
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});
flowx(0, y) = friction * flowx(0, y) + g * dt * (water(N - 1, y) + bed(N - 1, y) - water(0, y) - bed(0, y));
}
for (int id = 0; id < N * N; ++id)
// Update Y flows
for (int y = 1; y < N; ++y)
for (int x = 0; x < N; ++x)
flowy(x, y) = friction * flowy(x, y) + g * dt * (water(x, y - 1) + bed(x, y - 1) - water(x, y) - bed(x, y));
// Apply viscosity to X flows
for (int y = 0; y < N; ++y)
{
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)
for (int x = 1; x < N; ++x)
{
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}));
float h = (flowx(x, y) > 0.f) ? water(x - 1, y) : water(x, y);
h *= h;
if (h > 0.f)
flowx(x, y) *= h / (h + 3.f * dt * viscosity);
}
}
// Apply viscosity to Y flows
for (int y = 1; y < N; ++y)
{
for (int x = 0; x < N; ++x)
{
float h = (flowy(x, y) > 0.f) ? water(x, y - 1) : water(x, y);
h *= h;
if (h > 0.f)
flowy(x, y) *= h / (h + 3.f * dt * viscosity);
}
}
// Scale flows
for (int y = 0; y < N; ++y)
{
for (int x = 0; x < N; ++x)
{
float outflow = 0.f;
outflow += std::max(0.f, - flowx(x , y ));
outflow += std::max(0.f, flowx((x + 1) % N, y ));
outflow += std::max(0.f, - flowy(x , y ));
outflow += std::max(0.f, flowy(x , y + 1));
float max_outflow = water(x, y) * dx * dy / dt;
if (outflow > 0.f)
{
float scale = std::min(1.f, max_outflow / outflow);
if (flowx(x, y) < 0.f) flowx(x, y) *= scale;
if (flowx((x + 1) % N, y) > 0.f) flowx((x + 1) % N, y) *= scale;
if (flowy(x, y) < 0.f) flowy(x, y) *= scale;
if (flowy(x, y + 1) > 0.f) flowy(x, y + 1) *= scale;
}
}
}
// Update water and compute velocity
for (int y = 0; y < N; ++y)
{
for (int x = 0; x < N; ++x)
{
float w_old = water(x, y);
water(x, y) += dt / dx / dy * (flowx(x, y) + flowy(x, y) - flowx((x + 1) % N, y) - flowy(x, y + 1));
float w_avg = (w_old + water(x, y)) / 2.f;
math::vector v{0.f, 0.f};
if (w_avg > 0.f)
{
v[0] = (flowx(x, y) + flowx((x + 1) % N, y)) / 2.f / dx / w_avg;
v[1] = (flowy(x, y) + flowy(x, y + 1)) / 2.f / dy / w_avg;
}
velocity(x, y) = v;
}
}
// Add coriolis force
for (int y = 0; y < N; ++y)
{
for (int x = 0; x < N; ++x)
{
auto n = math::ort(velocity(x, y)) * coriolis * dt / 2.f * std::sin(float(math::pi) / 2.f * ((y + 0.5f) * 2.f / N - 1.f));
flowx(x, y) += n[0];
flowx((x + 1) % N, y) += n[0];
flowy(x, y) += n[1];
flowy(x, y + 1) += n[1];
}
}
if (erosion_on)
{
// Erosion-deposition
for (int y = 0; y < N; ++y)
{
for (int x = 0; x < N; ++x)
{
float c = sediment_capacity * water(x, y) * math::length(velocity(x, y));
if (c >= sediment(x, y))
{
float delta = std::min(bed(x, y), dt * erosion * (c - sediment(x, y)));
bed(x, y) -= delta;
sediment(x, y) += delta;
}
else
{
float delta = std::min(sediment(x, y), dt * deposition * (sediment(x, y) - c));
bed(x, y) += delta;
sediment(x, y) -= delta;
}
}
}
// Sediment transport
for (int y = 0; y < N; ++y)
{
for (int x = 0; x < N; ++x)
{
math::point p = math::lerp(simulation_area, {(x + 0.5f) / N, (y + 0.5f) / N});
p -= velocity(x, y) * dt;
p = math::clamp(p, inner_area);
auto q = math::unlerp(inner_area, p) * (N - 1.f);
int ix = std::min<int>(N - 2, std::floor(q[0]));
int iy = std::min<int>(N - 2, std::floor(q[1]));
float tx = q[0] - ix;
float ty = q[1] - iy;
new_sediment(x, y) = 0.f
+ sediment(ix, iy) * (1.f - tx) * (1.f - ty)
+ sediment(ix + 1, iy) * tx * (1.f - ty)
+ sediment(ix, iy + 1) * (1.f - tx) * ty
+ sediment(ix + 1, iy + 1) * tx * ty
;
}
}
std::swap(sediment, new_sediment);
}
// Init particles
for (int i = 0; i < particles_per_frame; ++i)
if (particles.size() < max_particles)
{
math::point pos{random::uniform<float>(rng, 0.f, N), random::uniform<float>(rng, 0.f, N)};
if (water(std::floor(pos[0]), std::floor(pos[1])) > 1e-3f)
particles.push_back({pos, 0});
}
// Update particles
for (auto & p : particles)
{
p.position += velocity(std::min<int>(N - 1, std::floor(p.position[0])), std::min<int>(N - 1, std::floor(p.position[1]))) * (N * dt);
p.position[0] = math::fmod(p.position[0], float(N));
p.position[1] = math::clamp(p.position[1], {0.f, N});
p.lifetime += 1;
}
// Destroy particles
for (int i = 0; i < particles.size();)
{
if (particles[i].lifetime >= max_particle_lifetime)
{
std::swap(particles[i], particles.back());
particles.pop_back();
}
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;
++i;
}
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);
}
}
time += dt;
}
void water_2d_app::present()
@ -214,16 +411,13 @@ 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;
math::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;
float extra_y = view_area[1].length() * 0.f;
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();
@ -231,96 +425,153 @@ void water_2d_app::present()
view_area[0].max += extra_x / 2.f;
}
geom::matrix<float, 4, 4> const transform = geom::orthographic{view_area}.homogeneous_matrix();
math::matrix<float, 4, 4> const transform = math::orthographic{view_area}.homogeneous_matrix();
float const pixel = 2.f / std::sqrt(width() * height() * geom::det(transform));
painter.rect(simulation_area, {0, 0, 0, 255});
// Background
painter.triangle(p[0], p[1], p[2], gfx::white);
float invN = 1.f / N;
// Water tiles
for (int id = 0; id < N * N; ++id)
for (int y = 0; y < N; ++y)
{
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)
for (int x = 0; x < N; ++x)
{
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 min = simulation_area.corner(x * invN, y * invN);
auto max = simulation_area.corner((x + 1) * invN, (y + 1) * invN);
auto f = [](float h)
{
float const C = 1.f;
float const e = std::exp(- C * h);
return (1.f - e) / (1.f + e);
};
float b = 1.f - std::exp(- 1.f * bed(x, y));
float w = 1.f - std::exp(- 1.f * water(x, y));
float s = 1.f - std::exp(- 1.f * sediment(x, y));
auto color = gfx::to_coloru8(gfx::color_4f{0.f, 0.f, 1.f, f(water_height[id])});
// if (water(x, y) > 1e-3f)
// w = 0.5f + 0.5f * w;
if (water_height[id] < 0.f)
color = gfx::magenta;
auto bed_color = gfx::to_coloru8(gfx::color_4f{0.96f, 0.72f, 0.53f, b});
// auto bed_color = gfx::to_coloru8(gfx::color_4f{0.3f, 0.5f, 0.1f, b});
auto color = gfx::to_coloru8(gfx::color_4f{0.f, 0.25f, 1.f, w});
auto sediment_color = gfx::to_coloru8(gfx::color_4f{1.f, 0.25f, 0.f, s * w});
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);
auto box = math::box<float, 2>::singleton(min) | math::box<float, 2>::singleton(max);
painter.rect(box, bed_color);
if (show_water)
{
painter.rect(box, color);
painter.rect(box, sediment_color);
}
}
}
// Edges
// if(false)
for (int i = 1; i <= N; ++i)
if (show_velocity)
{
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);
for (int y = 0; y < N; ++y)
{
for (int x = 0; x < N; ++x)
{
auto center = simulation_area.corner((x + 0.5f) * invN, (y + 0.5f) * invN);
auto v = water(x, y) * velocity(x, y) / 40.f;
float max_length = 0.05f;
auto l = math::length(v);
// auto color = gfx::to_coloru8(gfx::color_4f{1.f, std::exp(- 10.f * l), 0.f, 1.f});
auto color = gfx::color_rgba{255, 255, 255, 255};
float s = std::min(1.f, l / max_length);
// s = 1.f;
auto d = math::normalized(v) * max_length * s / 2.f;
painter.line(center - d, center + d, s * 0.001f, 0.f, color, color, false);
}
}
}
// Flux
// if(false)
for (int side = 0; side < 3; ++side)
if (show_particles)
{
auto at = [this, side](int i, int j)
for (auto const & p : particles)
{
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);
auto pos = math::lerp(simulation_area, math::vector{p.position[0] / N, p.position[1] / N});
painter.circle(pos, 0.005f, {255, 255, 255, 127}, 6);
}
}
auto mouse = math::cast<float>(state().mouse);
mouse[0] = math::lerp(view_area[0], mouse[0] / state().size[0]);
mouse[1] = math::lerp(view_area[1], 1.f - mouse[1] / state().size[1]);
int mx = std::floor(N * math::unlerp(simulation_area[0], mouse[0]));
int my = std::floor(N * math::unlerp(simulation_area[1], mouse[1]));
if (mx >= 0 && mx < N && my >= 0 && my < N)
{
gfx::painter::text_options opts
{
.scale = {0.004f, -0.004f},
.c = {255, 0, 255, 255},
};
painter.text({view_area[0].center(), math::lerp(view_area[1], 0.03f)}, std::format("b {:.3f} + w {:.3f} = h {:.3f}", bed(mx, my), water(mx, my), bed(mx, my) + water(mx, my)), opts);
painter.text({view_area[0].center(), math::lerp(view_area[1], 0.05f)}, std::format("s {:.3f}", sediment(mx, my)), opts);
for (int id = 0; id < first_type_count; ++id)
if (state().mouse_button_down.contains(app::mouse_button::left))
{
float const scale = 0.1f;
for (int dy = -2; dy <= 2; ++dy)
{
for (int dx = -2; dx <= 2; ++dx)
{
int x = mx + dx;
int y = my + dy;
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);
if (x >= 0 && x < N && y >= 0 && y < N)
water(x, y) += 1000.f * dt;
}
}
}
}
painter.render(transform);
{
painter.text({20.f, 20.f}, std::format("{} particles", particles.size()), {.scale = {2.f, 2.f}, .x = gfx::painter::x_align::left, .y = gfx::painter::y_align::top, .c = {0, 0, 0, 255}});
painter.render(math::window_camera{state().size[0], state().size[1]}.transform());
}
}
void water_2d_app::on_resize(int width, int height)
void water_2d_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];
}
int main()
void water_2d_app::on_event(app::key_event const & event)
{
return app::main<water_2d_app>();
if (event.down && event.key == app::keycode::SPACE)
paused ^= true;
if (event.down && event.key == app::keycode::W)
show_water ^= true;
if (event.down && event.key == app::keycode::V)
show_velocity ^= true;
if (event.down && event.key == app::keycode::P)
show_particles ^= true;
if (event.down && event.key == app::keycode::E)
erosion_on ^= true;
if (event.down && event.key == app::keycode::R)
rain_on ^= true;
}
namespace psemek::app
{
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<water_2d_app>({.name = "Water 2D example", .multisampling = 4});
}
}

349
examples/water_2d_hex.cpp Normal file
View file

@ -0,0 +1,349 @@
#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/gl.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/math/box.hpp>
#include <psemek/math/camera.hpp>
#include <psemek/math/gauss.hpp>
#include <psemek/util/ndarray.hpp>
#include <psemek/random/generator.hpp>
#include <psemek/random/device.hpp>
#include <psemek/random/uniform_sphere.hpp>
#include <psemek/pcg/perlin.hpp>
#include <psemek/prof/profiler.hpp>
using namespace psemek;
static const math::vector x_axis {1.f, 0.f};
static const math::vector y_axis {0.5f, std::sqrt(0.75f)};
static const math::vector z_axis = y_axis - x_axis;
static const int N = 64;
static const float dt = 0.1f;
static const float dx = 1.f;
static float const g = 10.f;
static float const friction = std::pow(0.875f, dt);
math::point<float, 2> to_world(int x, int y)
{
return math::point{0.f, 0.f} + x_axis * (x - N / 2.f) + y_axis * (y - N / 2.f);
}
math::point<float, 2> to_grid(math::point<float, 2> const & p)
{
static auto matrix = *math::inverse(math::by_columns(x_axis, y_axis));
static auto zero = math::point{0.f, 0.f};
return zero + matrix * (p - zero) + math::vector{N / 2.f, N / 2.f};
}
struct water_2d_hex_app
: app::application_base
{
water_2d_hex_app(options const &, context const &);
void update() override;
void present() override;
void on_event(app::resize_event const & event) override;
void on_event(app::key_event const & event) override;
void stop() override;
private:
bool vsync_on_ = true;
std::function<void(bool)> set_vsync_;
random::generator rng_{random::device{}};
float aspect_ratio_ = 1.f;
math::vector<int, 2> screen_size_;
math::box<float, 2> view_area_;
gfx::painter painter_;
bool paused_ = false;
float time_ = 0.f;
bool show_velocity_ = false;
util::ndarray<float, 2> bed_;
util::ndarray<float, 2> water_;
util::ndarray<float, 2> flow_x_; // flow_x(x, y) is (x-1, y) => (x, y)
util::ndarray<float, 2> flow_y_; // flow_y(x, y) is (x, y-1) => (x, y)
util::ndarray<float, 2> flow_z_; // flow_z(x, y) is (x, y-1) => (x-1, y)
};
water_2d_hex_app::water_2d_hex_app(options const &, context const & ctx)
: set_vsync_(ctx.vsync)
{
set_vsync_(vsync_on_);
bed_.resize({N + 1, N + 1}, 0.f);
water_.resize({N + 1, N + 1}, 0.f);
flow_x_.resize({N + 2, N + 1}, 0.f);
flow_y_.resize({N + 1, N + 2}, 0.f);
flow_z_.resize({N + 2, N + 2}, 0.f);
random::uniform_sphere_vector_distribution<float, 2> d_grad;
util::ndarray<math::vector<float, 2>, 2> perlin_grad({17, 17});
for (auto & v : perlin_grad)
v = d_grad(rng_);
pcg::perlin<float, 2> noise(std::move(perlin_grad));
for (int y = 0; y <= N; ++y)
{
for (int x = 0; x <= N; ++x)
{
auto q = (to_world(x, y) - math::point{0.f, 0.f}) / (1.f * N);
auto p = q + math::vector{0.5f, 0.5f};
(void)p;
// Canyon
bed_(x, y) = std::max(0.f, 10.f * std::abs(2.f * noise(p) - 1.f) - 1.5f);
// Islands
// bed_(x, y) = 5.f * math::smoothstep(math::clamp(math::unlerp({0.45f, 0.55f}, noise(p) - 1.f * math::length(q)), {0.f, 1.f}));
water_(x, y) = 0.f;
}
}
}
void water_2d_hex_app::update()
{
{
float y_extent = (N / 2) * y_axis[1];
view_area_[1] = {- y_extent, y_extent};
view_area_[0] = {- y_extent * aspect_ratio_, y_extent * aspect_ratio_};
}
if (state().mouse_button_down.contains(app::mouse_button::left))
{
auto m = math::lerp(view_area_, math::vector{state().mouse[0] * 1.f / screen_size_[0], 1.f - state().mouse[1] * 1.f / screen_size_[1]});
auto p = to_grid(m);
int const R = 4;
for (int dy = -R; dy <= R; ++dy)
{
for (int dx = -R; dx <= R; ++dx)
{
if (dx + dy < -R || dx + dy > R) continue;
int mx = std::round(p[0]) + dx;
int my = std::round(p[1]) + dy;
if (mx >= 0 && mx <= N && my >= 0 && my <= N)
{
auto c = to_world(mx, my);
water_(mx, my) += 10.f * std::exp(- 0.25f * math::distance_sqr(c, m)) * dt;
}
}
}
}
if (paused_)
return;
prof::profiler prof("update");
time_ += dt;
// Init boundary flows
for (int i = 0; i <= N / 2; ++i)
if (bed_(N / 2 - i, i) < 0.5f)
flow_x_(N / 2 - i, i) = 300.f / N;
// flow_x_(N / 2 - i, i) = 0*std::pow(std::sin(0.5f * time_) * std::sin(math::pi * (i * 4.f / N)), 5.f) * 300.f / N;
for (int i = 0; i <= N / 2; ++i)
if (bed_(N - i, N / 2 + i) < 0.5f)
flow_x_(N - i + 1, N / 2 + i) = 300.f / N;
// Update X flows
for (int y = 0; y <= N; ++y)
// for (int x = 1; x <= N; ++x)
for (int x = std::max(1, N / 2 + 1 - y); x <= std::min(N, 3 * N / 2 - y); ++x)
// if (x + y - 1 >= N / 2 && x + y <= 3 * N / 2)
flow_x_(x, y) = friction * flow_x_(x, y) + g * dt * (water_(x - 1, y) + bed_(x - 1, y) - water_(x, y) - bed_(x, y));
// Update Y flows
for (int y = 1; y <= N; ++y)
// for (int x = 0; x <= N; ++x)
for (int x = std::max(0, N / 2 + 1 - y); x <= std::min(N, 3 * N / 2 - y); ++x)
// if (x + y - 1 >= N / 2 && x + y <= 3 * N / 2)
flow_y_(x, y) = friction * flow_y_(x, y) + g * dt * (water_(x, y - 1) + bed_(x, y - 1) - water_(x, y) - bed_(x, y));
// Update Z flows
for (int y = 1; y <= N; ++y)
// for (int x = 1; x <= N; ++x)
for (int x = std::max(1, N / 2 + 1 - y); x <= std::min(N, 3 * N / 2 + 1 - y); ++x)
// if (x + y - 1 >= N / 2 && x + y - 1 <= 3 * N / 2)
flow_z_(x, y) = friction * flow_z_(x, y) + g * dt * (water_(x, y - 1) + bed_(x, y - 1) - water_(x - 1, y) - bed_(x - 1, y));
// Scale flows
for (int y = 0; y <= N; ++y)
{
// for (int x = 0; x <= N; ++x)
for (int x = std::max(0, N / 2 - y); x <= std::min(N, 3 * N / 2 - y); ++x)
{
// if (x + y >= N / 2 && x + y <= 3 * N / 2)
{
float outflow = 0.f;
float & fin1 = flow_x_(x , y );
float & fin2 = flow_y_(x , y );
float & fin3 = flow_z_(x + 1, y );
float & fout1 = flow_x_(x + 1, y );
float & fout2 = flow_y_(x , y + 1);
float & fout3 = flow_z_(x , y + 1);
outflow += std::max(0.f, -fin1);
outflow += std::max(0.f, -fin2);
outflow += std::max(0.f, -fin3);
outflow += std::max(0.f, fout1);
outflow += std::max(0.f, fout2);
outflow += std::max(0.f, fout3);
if (outflow > 0.f)
{
float max_outflow = water_(x, y) * dx * dx / dt;
float scale = std::min(1.f, max_outflow / outflow);
fin1 *= (fin1 < 0.f ? scale : 1.f);
fin2 *= (fin2 < 0.f ? scale : 1.f);
fin3 *= (fin3 < 0.f ? scale : 1.f);
fout1 *= (fout1 > 0.f ? scale : 1.f);
fout2 *= (fout2 > 0.f ? scale : 1.f);
fout3 *= (fout3 > 0.f ? scale : 1.f);
}
}
}
}
// Update water
for (int y = 0; y <= N; ++y)
// for (int x = 0; x <= N; ++x)
for (int x = std::max(0, N / 2 - y); x <= std::min(N, 3 * N / 2 - y); ++x)
// if (x + y >= N / 2 && x + y <= 3 * N / 2)
water_(x, y) += dt / dx / dx * (flow_x_(x, y) + flow_y_(x, y) + flow_z_(x + 1,y) - flow_x_(x + 1, y) - flow_y_(x, y + 1) - flow_z_(x, y + 1));
}
void water_2d_hex_app::present()
{
gl::ClearColor(0.f, 0.f, 0.f, 0.f);
gl::Clear(gl::COLOR_BUFFER_BIT);
for (int y = 0; y < N; ++y)
{
for (int x = 0; x < N; ++x)
{
auto color = [this](int x, int y)
{
auto bed = gfx::color_4f{0.9f, 0.7f, 0.5f, - std::expm1(- bed_(x, y))};
auto water = gfx::color_4f{0.0f, 0.25f, 1.f, - std::expm1(- water_(x, y))};
auto color = gfx::color_4f{0.f, 0.f, 0.f, 0.f};
color = gfx::blend(color, bed);
color = gfx::blend(color, water);
return gfx::to_coloru8(color);
};
auto p00 = to_world(x, y);
auto p01 = to_world(x + 1, y);
auto p10 = to_world(x, y + 1);
auto p11 = to_world(x + 1, y + 1);
auto b00 = color(x, y);
auto b01 = color(x + 1, y);
auto b10 = color(x, y + 1);
auto b11 = color(x + 1, y + 1);
if (x + y >= N / 2 && x + y < (3 * N) / 2)
painter_.triangle(p00, p01, p10, b00, b01, b10);
if (x + y + 1 >= N / 2 && x + y + 1 < (3 * N) / 2)
painter_.triangle(p10, p01, p11, b10, b01, b11);
}
}
if (show_velocity_)
for (int y = 0; y <= N; ++y)
{
for (int x = 0; x <= N; ++x)
{
if (x + y >= N / 2 && x + y <= (3 * N) / 2)
{
auto p = to_world(x, y);
auto vx = (flow_x_(x, y) + flow_x_(x + 1, y)) * 0.5f * x_axis;
auto vy = (flow_y_(x, y) + flow_y_(x, y + 1)) * 0.5f * y_axis;
auto vz = (flow_z_(x + 1, y) + flow_z_(x, y + 1)) * 0.5f * z_axis;
auto v = (vx + vy + vz);
auto M = 1.f;
auto l = math::length(v);
float s = std::min(1.f, l / M);
float c = -std::expm1(- 0.1f * l);
auto color = gfx::to_coloru8(gfx::color_4f{1.f, 1.f - c, 1.f - c, 1.f});
painter_.line(p, p + v * (s / l), 0.25f * s, 0.f, color, color, true);
}
}
}
painter_.render(math::orthographic_camera{view_area_}.transform());
}
void water_2d_hex_app::on_event(app::resize_event const & event)
{
gl::Viewport(0, 0, event.size[0], event.size[1]);
screen_size_ = event.size;
aspect_ratio_ = (event.size[0] * 1.f) / event.size[1];
}
void water_2d_hex_app::on_event(app::key_event const & event)
{
if (event.down)
{
switch (event.key)
{
case app::keycode::V:
vsync_on_ ^= true;
set_vsync_(vsync_on_);
break;
case app::keycode::C:
show_velocity_ ^= true;
break;
case app::keycode::SPACE:
paused_ ^= true;
break;
default:
break;
}
}
}
void water_2d_hex_app::stop()
{
app::application_base::stop();
prof::dump();
}
namespace psemek::app
{
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<water_2d_hex_app>({.name = "Water 2D hex example", .multisampling = 4});
}
}

881
examples/weather.cpp Normal file
View file

@ -0,0 +1,881 @@
#include <psemek/app/application_base.hpp>
#include <psemek/app/default_application_factory.hpp>
#include <psemek/gfx/painter.hpp>
#include <psemek/gfx/gl.hpp>
#include <psemek/math/camera.hpp>
#include <psemek/math/gradient.hpp>
#include <psemek/random/generator.hpp>
#include <psemek/random/device.hpp>
#include <psemek/random/uniform_ball.hpp>
#include <psemek/pcg/perlin.hpp>
#include <psemek/pcg/fractal.hpp>
#include <psemek/util/ndarray.hpp>
#include <psemek/log/log.hpp>
#include <psemek/io/file_stream.hpp>
using namespace psemek;
auto make_perlin(random::generator & rng, int min_octave, int max_octave, float power)
{
std::vector<pcg::perlin<float, 2>> octaves;
std::vector<float> weights;
random::uniform_sphere_vector_distribution<float, 2> random_vector{};
for (int octave = min_octave; octave < max_octave; ++octave)
{
int size = 1 << octave;
util::ndarray<math::vector<float, 2>, 2> gradients({size + 1, size + 1});
for (auto & g : gradients)
g = random_vector(rng);
octaves.emplace_back(std::move(gradients));
weights.push_back(std::pow(power, - octave));
}
float weight_sum = 0.f;
for (auto w : weights)
weight_sum += w;
for (auto & w : weights)
w /= weight_sum;
return pcg::fractal<pcg::perlin<float, 2>>(std::move(octaves), std::move(weights));
}
void make_force_field(random::generator & rng, util::ndarray<math::vector<float, 2>, 2> & result, float scale)
{
auto noise_1 = make_perlin(rng, 4, 6, 2.f);
auto noise_2 = make_perlin(rng, 4, 6, 2.f);
for (int y = 0; y < result.height(); ++y)
{
for (int x = 0; x < result.width(); ++x)
{
math::point p{(x + 0.5f) / result.height(), (y + 0.5f) / result.width()};
result(x, y) = scale * (math::vector{noise_1(p), noise_2(p)} * 2.f - math::vector{1.f, 1.f});
}
}
}
struct weather_app
: app::application_base
{
static constexpr int N = 128;
const bool static_mode = false;
const float dt = 20.f;
const float viscosity = static_mode ? 0.01f : 0.f;
const bool temperature_advection = true;
const float advection_magnification = 1.f;
const float temperature_diffusion = 0.0004f;
const float cooling = 0.01f / 300.f;
const float cooling_factor = std::exp(- cooling * dt);
const float heating = 323.f * (std::exp(cooling * dt) - 1.f) / dt;
const float water_heating_factor = 0.9f;
const float coriolis = 0.001f;
const float coriolis_bands = 6.f;
const float band_force = 0.00001f;
const float friction = 0.f;
const float slope_friction = 1.f;
const float slope_force = 0.001f;
const float land_force = 0.05f;
const float buoyancy_factor = 0.0002f * 0;
const float vorticity_confinement = 0.f;
const float elevation_temperature_drop = 30.f;
const float evaporation = 1.0f;
const float max_humidity_factor = 1.f;
const float precipitation_factor = 0.0001f;
const float force_field_amplitude = 0.00005f;
const float random_forces = 0.25f * (static_mode ? 0.f : 1.f);
const float force_field_switch_duration = 720.f * 7.5f; // 7.5 days
const int force_field_switch_frames = std::round(force_field_switch_duration / dt);
// const float friction_factor = 1.f - std::exp(- friction * dt);
const bool periodic_x = true;
// random::generator rng{random::device{}};
random::generator rng{0, 0};
gfx::pixmap_rgba biomes_map;
float expected_temperature_at(int y, bool water) const
{
// float latitude = (y - N * 0.5f) * 2.f / N;
// return std::cos(latitude * float(math::pi));
return temperature_income_at(y) * (water ? water_heating_factor : 1.f) * dt / (std::exp(cooling * dt) - 1.f);
}
float temperature_income_at(int y) const
{
// float latitude = (y - N * 0.5f) * 2.f / N;
float latitude = y * 1.f / N;
return heating * math::lerp(0.75f, 1.f, std::cos(latitude * float(math::pi) / 2.f));
// return heating * math::lerp(0.8f, 1.f, 1.f - std::abs(latitude));
}
int wrap(int i) const
{
return (i + N) % N;
}
weather_app(options const &, context const &)
{
simulation_box_ = {{{0.f, N}, {0.f, N}}};
terrain_.resize({N, N}, 0.f);
velocity_.resize({N, N});
new_velocity_.resize({N, N});
pressure_.resize({N, N}, 0.f);
temperature_.resize({N, N}, -1.f);
new_temperature_.resize({N, N});
average_temperature_.resize({N, N}, 0.f);
force_field_main_.resize({N, N});
force_field_current_.resize({N, N});
force_field_next_.resize({N, N});
vorticity_.resize({N, N});
humidity_.resize({N, N});
new_humidity_.resize({N, N});
precipitation_.resize({N, N});
average_precipitation_.resize({N, N});
auto terrain_noise = make_perlin(rng, 2, 10, 1.6f);
for (int y = 0; y < N; ++y)
{
for (int x = 0; x < N; ++x)
{
auto d = math::length(math::vector{x - N / 2.f, y - N / 2.f}) / (N / 2.f);
(void)d;
float value = terrain_noise((x + 0.5f) / N, (y + 0.5f) / N);
value = pow(value, 4.f) - d / 4.f;
value = math::lerp(1.f, 16.f, value);
terrain_(x, y) = value;
}
}
auto heightmap = gfx::read_image<std::uint8_t>(io::file_istream{std::filesystem::path{PSEMEK_EXAMPLES_DIR} / "heightmap_seed_1.png"});
for (int y = 0; y < N; ++y)
{
for (int x = 0; x < N; ++x)
{
terrain_(x, y) = ((heightmap(x, y) / 255.f) * 2048.f - 512.f) / 1024.f;
}
}
random::uniform_ball_vector_distribution<float, 2> random_velocity{};
// for (auto & v : velocity_)
// {
// v = random_velocity(rng) * 0.01f;
// v += std::cos(0.5f * float(math::pi) * latitude * coriolis_bands
// }
for (int y = 0; y < N; ++y)
{
for (int x = 0; x < N; ++x)
{
float latitude = (N * 0.5f - y) * 2.f / N;
velocity_(x, y) = random_velocity(rng) * 0.f + 0.f * math::vector{-std::cos(0.5f * float(math::pi) * latitude * coriolis_bands), 0.f};
temperature_(x, y) = expected_temperature_at(y, terrain_(x, y) <= 0.f);
}
}
for (int y = 0; y < N; ++y)
{
for (int x = 0; x < N; ++x)
{
if (terrain_(x, y) > 0.f)
continue;
float max_humidity = std::max(0.f, temperature_(x, y) - 223.f) * max_humidity_factor;
humidity_(x, y) = max_humidity + dt * evaporation * std::max(0.f, temperature_(x, y) - 273.f) * (1.f - precipitation_factor * dt) / precipitation_factor / dt;
humidity_(x, y) = 0.f;
}
}
make_force_field(rng, force_field_main_, 0.5f);
make_force_field(rng, force_field_next_, 0.5f);
biomes_map = gfx::read_image<gfx::color_rgba>(io::file_istream{std::filesystem::path{PSEMEK_EXAMPLES_DIR} / "biomes.png"});
}
void on_event(app::key_event const & event) override
{
if (event.down && event.key == app::keycode::SPACE)
paused_ ^= true;
if (event.down && event.key == app::keycode::V)
show_velocity_ ^= true;
if (event.down && event.key == app::keycode::T)
show_temperature_ ^= true;
if (event.down && event.key == app::keycode::D)
show_temperature_delta_ ^= true;
if (event.down && event.key == app::keycode::A)
show_average_temperature_delta_ ^= true;
if (event.down && event.key == app::keycode::P)
show_pressure_ ^= true;
if (event.down && event.key == app::keycode::H)
show_land_ ^= true;
if (event.down && event.key == app::keycode::W)
show_water_vapor_ ^= true;
if (event.down && event.key == app::keycode::R)
show_precipitation_ ^= true;
if (event.down && event.key == app::keycode::Q)
show_average_precipitation_ ^= true;
if (event.down && event.key == app::keycode::B)
show_biomes_ ^= true;
}
void update() override
{
if (paused_)
return;
// Update force field
if ((frame_ % force_field_switch_frames) == 0)
{
std::swap(force_field_current_, force_field_next_);
make_force_field(rng, force_field_next_, 0.5f);
}
[[maybe_unused]] float const force_field_t = ((frame_ % force_field_switch_frames) + 0.5f) / force_field_switch_frames;
int xmin = periodic_x ? 0 : 1;
int xmax = periodic_x ? N : N - 1; // exclusive
// Temperature source
for (int y = 0; y < N; ++y)
{
for (int x = 0; x < N; ++x)
{
bool const is_water = terrain_(x, y) <= 0.f;
// temperature_(x, y) = math::lerp(temperature_(x, y), expected_temperature_at(y), 1.f - std::exp(- heating * dt));
temperature_(x, y) += dt * temperature_income_at(y) * (is_water ? water_heating_factor : 1.f);
temperature_(x, y) *= cooling_factor;
}
}
// Evaporation
for (int y = 0; y < N; ++y)
{
for (int x = 0; x < N; ++x)
{
if (terrain_(x, y) <= 0.f)
humidity_(x, y) += dt * evaporation * std::max(0.f, temperature_(x, y) - 273.f);
// float discharge = std::min(humidity_(x, y), precipitation_factor * dt);
// float discharge = humidity_(x, y) * precipitation_factor * dt;
// float max_humidity = temperature_(x, y) * max_humidity_factor;
float max_humidity = temperature_(x, y) * temperature_(x, y) * max_humidity_factor;
float discharge = std::max(0.f, humidity_(x, y) - max_humidity) * precipitation_factor * dt;
humidity_(x, y) -= discharge;
precipitation_(x, y) = discharge / dt;
}
}
// Velocity & temperature advection
for (int i = 0; i < N; ++i)
{
new_temperature_(i, 0) = temperature_(i, 0);
new_temperature_(i, N - 1) = temperature_(i, N - 1);
new_humidity_(i, 0) = humidity_(i, 0);
new_humidity_(i, N - 1) = humidity_(i, N - 1);
if (!periodic_x)
{
new_temperature_(0, i) = temperature_(0, i);
new_temperature_(N - 1, i) = temperature_(N - 1, i);
new_humidity_(0, i) = humidity_(0, i);
new_humidity_(N - 1, i) = humidity_(N - 1, i);
}
}
for (int y = 1; y < N - 1; ++y)
{
for (int x = xmin; x < xmax; ++x)
{
auto v = velocity_(x, y);
auto p = math::point{x + 0.5f, y + 0.5f} - (advection_magnification * dt) * v;
p[0] = p[0] - 0.5f;
p[1] = math::clamp(p[1] - 0.5f, {0.f, N - 1.f});
if (!periodic_x)
p[0] = math::clamp(p[0], {0.f, N - 1.f});
int ix = std::floor(p[0]);
int iy = std::min<int>(N - 1, std::floor(p[1]));
if (!periodic_x)
ix = std::min(N - 1, ix);
float tx = p[0] - ix;
float ty = p[1] - iy;
new_velocity_(x, y) = math::lerp(
math::lerp(velocity_(wrap(ix + 0), iy + 0), velocity_(wrap(ix + 1), iy + 0), tx),
math::lerp(velocity_(wrap(ix + 0), iy + 1), velocity_(wrap(ix + 1), iy + 1), tx),
ty
);
new_temperature_(x, y) = math::lerp(
math::lerp(temperature_(wrap(ix + 0), iy + 0), temperature_(wrap(ix + 1), iy + 0), tx),
math::lerp(temperature_(wrap(ix + 0), iy + 1), temperature_(wrap(ix + 1), iy + 1), tx),
ty
);
new_humidity_(x, y) = math::lerp(
math::lerp(humidity_(wrap(ix + 0), iy + 0), humidity_(wrap(ix + 1), iy + 0), tx),
math::lerp(humidity_(wrap(ix + 0), iy + 1), humidity_(wrap(ix + 1), iy + 1), tx),
ty
);
}
}
std::swap(velocity_, new_velocity_);
if (temperature_advection) std::swap(temperature_, new_temperature_);
std::swap(humidity_, new_humidity_);
// Apply velocity diffusion
for (int y = 0; y < N; ++y)
for (int x = 0; x < N; ++x)
new_velocity_(x, y) = velocity_(x, y);
for (int y = 1; y < N - 1; ++y)
{
for (int x = xmin; x < xmax; ++x)
{
// Velocity Laplacian
auto laplacian = velocity_(wrap(x + 1), y) + velocity_(wrap(x - 1), y) + velocity_(x, y + 1) + velocity_(x, y - 1) - 4.f * velocity_(x, y);
new_velocity_(x, y) = velocity_(x, y) + viscosity * dt * laplacian;
}
}
std::swap(velocity_, new_velocity_);
// Apply temperature diffusion
for (int y = 0; y < N; ++y)
for (int x = 0; x < N; ++x)
new_temperature_(x, y) = temperature_(x, y);
for (int y = 1; y < N - 1; ++y)
{
for (int x = xmin; x < xmax; ++x)
{
// Temperature Laplacian
auto laplacian = temperature_(wrap(x + 1), y) + temperature_(wrap(x - 1), y) + temperature_(x, y + 1) + temperature_(x, y - 1) - 4.f * temperature_(x, y);
new_temperature_(x, y) = temperature_(x, y) + temperature_diffusion * dt * laplacian;
}
}
std::swap(temperature_, new_temperature_);
// Compute vorticity
for (int y = 1; y < N - 1; ++y)
for (int x = xmin; x < xmax; ++x)
vorticity_(x, y) = (velocity_(x, y + 1)[0] - velocity_(x, y - 1)[0]) / 2.f - (velocity_(wrap(x + 1), y)[1] - velocity_(wrap(x - 1), y)[1]) / 2;
// Apply forces & friction
for (int y = 1; y < N - 1; ++y)
{
for (int x = xmin; x < xmax; ++x)
{
// float latitude = (N * 0.5f - y) * 2.f / N;
[[maybe_unused]] float latitude = (N - y) * 1.f / N;
// velocity_(x, y) += math::ort(velocity_(x, y)) * (coriolis * dt * std::sin(0.5f * float(math::pi) * latitude * coriolis_bands));
velocity_(x, y) = math::rotate(velocity_(x, y), coriolis * dt * std::sin(0.5f * float(math::pi) * latitude * coriolis_bands));
// auto force = force_field_main_(x, y) + random_forces * math::lerp(force_field_current_(x, y), force_field_next_(x, y), force_field_t);
// velocity_(x, y) += (dt * force_field_amplitude) * force;
// velocity_(x, y)[0] += dt * 0.000001f * std::cos(0.5f * float(math::pi) * latitude * 4.f);
// velocity_(x, y)[0] += dt * 0.000001f;
velocity_(x, y)[0] += dt * band_force * std::sin(0.5f * float(math::pi) * latitude * coriolis_bands);
[[maybe_unused]] math::vector terrain_gradient
{
(std::max(0.f, terrain_(x + 1, y)) - std::max(0.f, terrain_(x - 1, y))) / 2.f,
(std::max(0.f, terrain_(x, y + 1)) - std::max(0.f, terrain_(x, y - 1))) / 2.f,
};
[[maybe_unused]] math::vector temperature_gradient
{
(temperature_(x + 1, y) - temperature_(x - 1, y)) / 2.f,
(temperature_(x, y + 1) - temperature_(x, y - 1)) / 2.f,
};
velocity_(x, y) += temperature_gradient * buoyancy_factor * dt;
[[maybe_unused]] float slope_factor = std::exp(- dt * slope_friction * math::dot(math::normalized(velocity_(x, y)), terrain_gradient));
velocity_(x, y) *= std::min(1.f, slope_factor);
// velocity_(x, y) -= terrain_gradient * slope_force * dt;
// [[maybe_unused]] float slope_factor = std::exp(- dt * slope_force * std::pow(math::length(terrain_gradient), 4.f));
[[maybe_unused]] float land_factor = std::exp(- dt * land_force * std::pow(std::max(0.f, terrain_(x, y)), 1.f));
velocity_(x, y) *= land_factor;
// Directional external force
// velocity_(x, y)[1] += 0.001f * dt * std::sin(0.5f * float(math::pi) * latitude * coriolis_bands);
// velocity_(x, y) += math::direction(frame_ * dt * 2.f * float(math::pi) / 10080.f) * 0.001f * dt;
math::vector vorticity_gradient
{
(vorticity_(wrap(x + 1), y) - vorticity_(wrap(x - 1), y)) / 2.f,
(vorticity_(x, y + 1) - vorticity_(x, y - 1)) / 2.f,
};
if (auto l = math::length(vorticity_gradient); l > 0.f)
vorticity_gradient /= l;
velocity_(x, y) += vorticity_confinement * dt * vorticity_(x, y) * math::ort(vorticity_gradient);
float local_friction = friction * terrain_(x, y);
// velocity_(x, y) -= local_friction * velocity_(x, y) * math::length(velocity_(x, y));
[[maybe_unused]] float local_friction_factor = std::exp(- local_friction * dt);
// velocity_(x, y) *= local_friction_factor;
}
}
// Solve Poisson equation for pressure
for (int iteration = 0; iteration < 16; ++iteration)
{
int ymin = ((iteration % 2) == 0) ? 1 : N - 2;
int ymax = ((iteration % 2) == 0) ? N - 1 : 0;
int ystep = ((iteration % 2) == 0) ? 1 : -1;
for (int y = ymin; y != ymax; y += ystep)
{
for (int x = xmin; x < xmax; ++x)
{
// Velocity divergence
float divergence = (velocity_(wrap(x + 1), y)[0] - velocity_(wrap(x - 1), y)[0] + velocity_(x, y + 1)[1] - velocity_(x, y - 1)[1]) / 2.f;
// Gauss-Seidel iteration step
pressure_(x, y) = (pressure_(wrap(x - 1), y) + pressure_(wrap(x + 1), y) + pressure_(x, y - 1) + pressure_(x, y + 1) - divergence) / 4.f;
}
}
}
// Apply boundary conditions for pressure
for (int i = 0; i < N; ++i)
{
if (!periodic_x)
{
pressure_(0, i) = pressure_(1, i);
pressure_(N - 1, i) = pressure_(N - 2, i);
}
pressure_(i, 0) = pressure_(i, 1);
pressure_(i, N - 1) = pressure_(i, N - 2);
}
if (!periodic_x)
{
pressure_(0, 0) = (pressure_(0, 1) + pressure_(1, 0)) / 2.f;
pressure_(N-1, 0) = (pressure_(N-1, 1) + pressure_(N-2, 0)) / 2.f;
pressure_(0, N-1) = (pressure_(0, N-2) + pressure_(1, N-2)) / 2.f;
pressure_(N-1, N-1) = (pressure_(N-1, N-2) + pressure_(N-2, N-1)) / 2.f;
}
// Normalize pressure
float average_pressure = 0.f;
for (auto const & value : pressure_)
average_pressure += value;
average_pressure /= (1.f * N * N);
for (auto & value : pressure_)
value -= average_pressure;
// Project velocity into divergence-free space
// by subtracting pressure gradient
for (int y = 1; y < N - 1; ++y)
{
for (int x = xmin; x < xmax; ++x)
{
// Pressure gradient
math::vector gradient{
(pressure_(wrap(x + 1), y) - pressure_(wrap(x - 1), y)) / 2.f,
(pressure_(x, y + 1) - pressure_(x, y - 1)) / 2.f
};
velocity_(x, y) -= gradient;
}
}
// Apply boundary conditions for velocity
for (int i = 0; i < N; ++i)
{
if (!periodic_x)
{
float left_boundary_flow = 0.f;//0.01f * std::sin((i * 1.f / N) * float(math::pi) * 4.f);
float right_boundary_flow = -left_boundary_flow;
velocity_(1, i)[0] = left_boundary_flow;
velocity_(N-2, i)[0] = right_boundary_flow;
velocity_(0, i)[0] = - velocity_(1, i)[0];
velocity_(N-1, i)[0] = - velocity_(N-2, i)[0];
}
velocity_(i, 0)[1] = -velocity_(i, 1)[1];
velocity_(i, N-2)[1] = -velocity_(i, N-2)[1];
// velocity_(i, 1)[0] = 0.01f;
// velocity_(i, N-2)[0] = 0.01f;
}
// Uncomment to visualize the force field
// for (int y = 0; y < N; ++y)
// for (int x = 0; x < N; ++x)
// velocity_(x, y) = 100000.f * force_field_(x, y);
// Uncomment to visualize the terrain gradient field
// for (int y = 1; y < N - 1; ++y)
// {
// for (int x = 1; x < N - 1; ++x)
// {
// math::vector terrain_gradient
// {
// (terrain_(x + 1, y) - terrain_(x - 1, y)) / 2.f,
// (terrain_(x, y + 1) - terrain_(x, y - 1)) / 2.f,
// };
// velocity_(x, y) = terrain_gradient;
// }
// }
// Apply boundary conditions for humidity
for (int i = 0; i < N; ++i)
{
if (!periodic_x)
{
humidity_(0, i) = 0.f;
humidity_(N - 1, i) = 0.f;
}
humidity_(i, 0) = 0.f;
humidity_(i, N - 1) = 0.f;
}
++frame_;
// Update all-time average temperature & precipitation
for (int y = 0; y < N; ++y)
{
for (int x = 0; x < N; ++x)
{
// float t = 1.f / frame_;
// float t = 1.f / std::min(8192, frame_);
float t = 1.f / std::min<float>(86400.f / dt, frame_); // year average
if (static_mode)
t = 1.f;
average_temperature_(x, y) = math::lerp(average_temperature_(x, y), temperature_(x, y), t);
average_precipitation_(x, y) = math::lerp(average_precipitation_(x, y), precipitation_(x, y), t);
}
}
}
void present() override
{
gl::ClearColor(0.f, 0.f, 0.f, 0.f);
gl::Clear(gl::COLOR_BUFFER_BIT);
float const aspect_ratio = state().size[0] * 1.f / state().size[1];
math::box<float, 2> view_box = math::expand(simulation_box_, 1.f);
if (view_box[0].length() / view_box[1].length() > aspect_ratio)
view_box[1] = math::expand(view_box[1], (view_box[0].length() / aspect_ratio - view_box[1].length()) / 2.f);
else
view_box[0] = math::expand(view_box[0], (view_box[1].length() * aspect_ratio - view_box[0].length()) / 2.f);
std::optional<math::vector<int, 2>> mouseover_cell;
{
auto mouse = math::lerp(view_box, math::vector{state().mouse[0] * 1.f / state().size[0], 1.f - state().mouse[1] * 1.f / state().size[1]});
int x = std::floor(mouse[0]);
int y = std::floor(mouse[1]);
if (x >= 0 && x < N && y >= 0 && y < N)
mouseover_cell = {x, y};
}
if (mouseover_cell && (state().mouse_button_down.contains(app::mouse_button::left) || state().mouse_button_down.contains(app::mouse_button::right)))
{
float delta = state().mouse_button_down.contains(app::mouse_button::left) ? 1.f : -1.f;
int R = 4;
for (int iy = -R; iy <= R; ++iy)
{
for (int ix = -R; ix <= R; ++ix)
{
int x = (*mouseover_cell)[0] + ix;
int y = (*mouseover_cell)[1] + iy;
if (x >= 0 && x < N && y >= 0 && y < N)
{
auto d = math::vector<float, 2>{ix, iy} / 2.f;
terrain_(x, y) += delta * 0.05f * std::exp(- math::dot(d, d));
}
}
}
}
if (mouseover_cell && state().mouse_button_down.contains(app::mouse_button::middle))
{
int R = 4;
float average = 0.f;
int count = 0;
for (int iy = -R; iy <= R; ++iy)
{
for (int ix = -R; ix <= R; ++ix)
{
int x = (*mouseover_cell)[0] + ix;
int y = (*mouseover_cell)[1] + iy;
if (x >= 0 && x < N && y >= 0 && y < N)
{
average += terrain_(x, y);
count += 1;
}
}
}
average /= count;
for (int iy = -R; iy <= R; ++iy)
{
for (int ix = -R; ix <= R; ++ix)
{
int x = (*mouseover_cell)[0] + ix;
int y = (*mouseover_cell)[1] + iy;
if (x >= 0 && x < N && y >= 0 && y < N)
{
auto d = math::vector<float, 2>{ix, iy} / 2.f;
terrain_(x, y) += (average - terrain_(x, y)) * 0.05f * std::exp(- math::dot(d, d));
}
}
}
}
[[maybe_unused]] float const pixel_size = view_box[0].length() / state().size[0];
auto map_color = [](float value, gfx::color_4f const & negative, gfx::color_4f const & positive){
return math::lerp(negative, positive, 1.f/ (1.f + std::exp(- value)));
};
auto map_temperature = [&](float value) {
return map_color(2.f * std::round(value / 20.f), {0.125f, 0.5f, 1.f, 0.75f}, {1.f, 0.5f, 0.125f, 0.75f});
};
auto map_biome = [this](float temperature, float precipitation)
{
auto x = math::clamp<int>(math::unlerp({ -3.f, 5.f}, precipitation) * biomes_map.width() , {0, biomes_map.width() - 1});
auto y = math::clamp<int>(math::unlerp({-10.f, 30.f}, temperature ) * biomes_map.height(), {0, biomes_map.height() - 1});
return gfx::to_colorf(biomes_map(x, y));
};
for (int y = 0; y < N; ++y)
{
for (int x = 0; x < N; ++x)
{
gfx::color_4f color = gfx::color_4f::zero();
if (show_land_ || show_biomes_)
{
if (!show_biomes_)
{
if (terrain_(x, y) <= 0.f)
color = {0.5f, 0.5f, 1.f, 1.f};
else
color = {1.f, 1.f, 1.f, 1.f};
}
else
{
if (terrain_(x, y) <= 0.f)
color = map_color(8.f * terrain_(x, y), {0.f, 0.f, 0.125f, 1.f}, {0.f, 1.f, 1.5f, 1.f});
else
{
float temperature = average_temperature_(x, y) - 273.f - std::max(0.f, terrain_(x, y)) * elevation_temperature_drop;
// float precipitation = (std::log10(std::max(1e-9f, average_precipitation_(x, y))) + 3.f) * 2.f;
// float precipitation = std::pow(average_precipitation_(x, y) * 125.f, 2.f) * 8.f;
float precipitation = std::log2(std::max(1e-9f, average_precipitation_(x, y)));
color = map_biome(temperature, precipitation);
}
}
if (show_land_ && x > 0 && x + 1 < N && y > 0 && y + 1 < N)
{
math::vector terrain_gradient
{
(terrain_(x + 1, y) - terrain_(x - 1, y)) / 2.f,
(terrain_(x, y + 1) - terrain_(x, y - 1)) / 2.f,
};
auto terrain_normal = math::normalized(math::vector{-terrain_gradient[0], -terrain_gradient[1], 0.125f});
float lightness = 0.5f + 0.5f * math::dot(terrain_normal, math::normalized(math::vector{1.f, 2.f, 3.f}));
color = gfx::dark(color, 1.f - lightness);
}
}
bool const is_water = terrain_(x, y) <= 0.f;
if (show_temperature_)
color = map_temperature(temperature_(x, y) - 273.f);
if (show_temperature_delta_)
color = gfx::blend(color, map_color((temperature_(x, y) - expected_temperature_at(y, is_water)), {0.125f, 0.5f, 1.f, 0.75f}, {1.f, 0.5f, 0.125f, 0.75f}));
if (show_average_temperature_delta_)
color = gfx::blend(color, map_color((average_temperature_(x, y) - expected_temperature_at(y, is_water)), {0.125f, 0.5f, 1.f, 0.75f}, {1.f, 0.5f, 0.125f, 0.75f}));
if (show_pressure_)
color = gfx::blend(color, map_color(10000.f * pressure_(x, y), {0.f, 0.f, 1.f, 0.75f}, {1.f, 0.f, 0.f, 0.75f}));
if (show_water_vapor_)
color = gfx::blend(color, map_color(humidity_(x, y) * 0.00001f, {0.f, 1.f, 1.f, -0.75f}, {0.f, 1.f, 1.f, 0.75f}));
if (show_precipitation_)
{
// color = gfx::blend(color, map_color(precipitation_(x, y), {1.f, 1.f, 1.f, -1.f}, {1.f, 1.f, 1.f, 1.f}));
float alpha = 2.f / (1.f + std::exp(- 0.1f * precipitation_(x, y))) - 1.f;
gfx::color_4f cloud_color{1.f, 1.f, 1.f, alpha * 0.875f};
if (y > 0 && y + 1 < N)
{
if (periodic_x || (x > 0 && x + 1 < N))
{
math::vector gradient
{
(precipitation_(wrap(x + 1), y) - precipitation_(wrap(x - 1), y)) / 2.f,
(precipitation_(x, y + 1) - precipitation_(x, y - 1)) / 2.f,
};
auto normal = math::normalized(math::vector{-gradient[0], -gradient[1], 2.f});
auto lightness = 0.5f + 0.5f * math::dot(normal, math::normalized(math::vector{1.f, 2.f, 3.f}));
cloud_color = gfx::dark(cloud_color, 1.f - lightness);
}
}
color = gfx::blend(color, cloud_color);
}
if (show_average_precipitation_)
color = gfx::blend(color, map_color(average_precipitation_(x, y), {0.f, 1.f, 1.f, -0.75f}, {0.f, 1.f, 1.f, 0.75f}));
painter_.rect({{{x, x + 1.f}, {y, y + 1.f}}}, gfx::to_coloru8(color));
}
}
if (show_velocity_)
{
for (int y = 0; y < N; ++y)
{
for (int x = 0; x < N; ++x)
{
math::point center{x + 0.5f, y + 0.5f};
auto v = velocity_(x, y);
auto color = gfx::color_4f::zero();
if (auto l = math::length(v); l > 0.f)
{
float const magnification = 200.f;
float const max_length = 1.5f;
v *= 0.5f * max_length * (1.f - std::exp(- magnification * l)) / l;
// color = gfx::lerp(gfx::color_4f{0.5f, 1.f, 0.f, 1.f}, gfx::color_4f{1.f, 0.f, 0.f, 1.f}, 1.f - std::exp(- 0.25f * magnification * l));
color = map_color(0.1f * (temperature_(x, y) - 273.f), {0.125f, 0.5f, 1.f, 1.f}, {1.f, 0.5f, 0.125f, 1.f});
}
auto n = math::ort(v) * 0.3f;
painter_.triangle(center - v - n, center - v + n, center + v, gfx::to_coloru8(color));
}
}
}
auto push_text = [&, row = 0](std::string const & text) mutable
{
painter_.text(view_box.corner(0.f, 1.f) - math::vector{0.f, row * pixel_size * 2.f * 12.f}, text, {.scale = {2.f * pixel_size, - 2.f * pixel_size}, .x = gfx::painter::x_align::left, .y = gfx::painter::y_align::top, .c = {255, 255, 255, 255}});
++row;
};
push_text(std::format("Frame {}", frame_));
push_text(std::format("Day {:.2f}", frame_ / (720.f / dt)));
if (mouseover_cell)
{
int x = (*mouseover_cell)[0];
int y = (*mouseover_cell)[1];
painter_.rect({{{x, x + 1.f}, {y, y + 1.f}}}, {255, 255, 255, 127});
push_text(std::format("{} {}", x, y));
push_text(std::format("V = {:.3f} {:.3f}", velocity_(x, y)[0] * 1000.f, velocity_(x, y)[1] * 1000.f));
push_text(std::format("P = {:.3f}", pressure_(x, y) * 1000.f));
push_text(std::format("T = {:.3f}", temperature_(x, y) - 273.f));
push_text(std::format("A = {:.3f}", average_temperature_(x, y) - 273.f));
push_text(std::format("E = {:.3f}", expected_temperature_at(y, terrain_(x, y) <= 0.f) - 273.f));
push_text(std::format("H = {:.3f}", terrain_(x, y)));
push_text(std::format("W = {:.3f}", humidity_(x, y)));
push_text(std::format("R = {:.3f}", precipitation_(x, y)));
push_text(std::format("AR= {:.3f}", average_precipitation_(x, y)));
}
painter_.render(math::orthographic_camera{view_box}.transform());
}
private:
gfx::painter painter_;
math::box<float, 2> simulation_box_;
bool paused_ = true;
bool show_velocity_ = true;
bool show_temperature_ = false;
bool show_temperature_delta_ = false;
bool show_average_temperature_delta_ = false;
bool show_pressure_ = false;
bool show_land_ = true;
bool show_biomes_ = true;
bool show_water_vapor_ = false;
bool show_precipitation_ = false;
bool show_average_precipitation_ = false;
util::ndarray<float, 2> terrain_;
util::ndarray<math::vector<float, 2>, 2> velocity_;
util::ndarray<math::vector<float, 2>, 2> new_velocity_;
util::ndarray<float, 2> pressure_;
util::ndarray<float, 2> vorticity_;
util::ndarray<float, 2> temperature_;
util::ndarray<float, 2> new_temperature_;
util::ndarray<float, 2> average_temperature_;
util::ndarray<float, 2> humidity_;
util::ndarray<float, 2> new_humidity_;
util::ndarray<float, 2> precipitation_;
util::ndarray<float, 2> average_precipitation_;
util::ndarray<math::vector<float, 2>, 2> force_field_main_;
util::ndarray<math::vector<float, 2>, 2> force_field_current_;
util::ndarray<math::vector<float, 2>, 2> force_field_next_;
int frame_ = 0;
};
namespace psemek::app
{
std::unique_ptr<application::factory> make_application_factory()
{
return default_application_factory<weather_app>({.name = "Weather simulation test"});
}
}

1265
examples/weather_v2.cpp Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,38 @@
file(GLOB PSEMEK_LIB_DIRS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/*")
list(REMOVE_ITEM PSEMEK_LIB_DIRS "CMakeLists.txt")
set(PSEMEK_LIBRARIES)
foreach(lib ${PSEMEK_LIB_DIRS})
if(PSEMEK_LEGACY_UI)
list(REMOVE_ITEM PSEMEK_LIB_DIRS "ui")
else()
list(REMOVE_ITEM PSEMEK_LIB_DIRS "ui_legacy")
endif()
if(NOT (PSEMEK_GRAPHICS_API STREQUAL WEBGPU))
list(REMOVE_ITEM PSEMEK_LIB_DIRS "wgpu")
endif()
list(REMOVE_ITEM PSEMEK_LIB_DIRS sdl2 android)
set(PSEMEK_BACKEND_LIB_DIR)
if(PSEMEK_BACKEND STREQUAL "SDL2")
set(PSEMEK_BACKEND_LIB_DIR "sdl2")
elseif(PSEMEK_BACKEND STREQUAL "ANDROID")
set(PSEMEK_BACKEND_LIB_DIR "android")
endif()
set(PSEMEK_LIBRARIES)
set(PSEMEK_BACKEND_LIBRARY)
foreach(lib ${PSEMEK_LIB_DIRS} ${PSEMEK_BACKEND_LIB_DIR})
add_subdirectory(${lib})
target_compile_definitions(psemek-${lib} PUBLIC ${PSEMEK_DEFINITIONS})
target_compile_options(psemek-${lib} PUBLIC ${PSEMEK_CXX_FLAGS})
set_target_properties(psemek-${lib} PROPERTIES EXCLUDE_FROM_ALL TRUE)
list(APPEND PSEMEK_LIBRARIES psemek-${lib})
if(lib STREQUAL PSEMEK_BACKEND_LIB_DIR)
set(PSEMEK_BACKEND_LIBRARY psemek-${lib})
else()
list(APPEND PSEMEK_LIBRARIES psemek-${lib})
endif()
endforeach()
set(PSEMEK_LIBRARIES ${PSEMEK_LIBRARIES} PARENT_SCOPE)
set(PSEMEK_BACKEND_LIBRARY ${PSEMEK_BACKEND_LIBRARY} CACHE INTERNAL "Application backend library target")

View file

@ -0,0 +1,6 @@
file(GLOB_RECURSE PSEMEK_ANDROID_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "include/*.hpp")
file(GLOB_RECURSE PSEMEK_ANDROID_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "source/*.cpp")
psemek_add_library(psemek-android ${PSEMEK_ANDROID_HEADERS} ${PSEMEK_ANDROID_SOURCES})
target_include_directories(psemek-android PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(psemek-android PUBLIC psemek-log psemek-util psemek-gfx psemek-audio psemek-app android log)

View file

@ -0,0 +1,21 @@
<?xml version='1.0'?>
<manifest xmlns:a='http://schemas.android.com/apk/res/android' package='psemek.app' a:versionCode='0' a:versionName='0'>
<uses-sdk
a:minSdkVersion="26"
a:targetSdkVersion="34"/>
<uses-feature
a:glEsVersion="0x00030002"
a:required="true"/>
<application
a:label='APPLICATION_NAME'>
<activity
a:name='psemek.app.MainActivity'
a:exported="true"
a:screenOrientation="landscape">
<intent-filter>
<category a:name='android.intent.category.LAUNCHER'/>
<action a:name='android.intent.action.MAIN'/>
</intent-filter>
</activity>
</application>
</manifest>

Binary file not shown.

View file

@ -0,0 +1,104 @@
package psemek.app;
import android.app.Activity;
import android.app.ActionBar;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
import android.view.WindowInsetsController;
import android.view.WindowInsets.Type;
import android.view.MotionEvent;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLES30;
import android.os.Bundle;
import java.lang.System;
public class MainActivity extends Activity {
class RendererImpl implements Renderer {
@Override
public void onSurfaceCreated(GL10 gl10, EGLConfig config) {
MainActivity.this.nativeApp.init();
}
@Override
public void onSurfaceChanged(GL10 gl10, int width, int height) {
MainActivity.this.nativeApp.resize(width, height);
}
@Override
public void onDrawFrame(GL10 gl10) {
MainActivity.this.nativeApp.drawFrame();
}
}
class ViewImpl extends GLSurfaceView {
private final RendererImpl renderer;
public ViewImpl(Context context) {
super(context);
setEGLContextClientVersion(3);
setEGLConfigChooser(8, 8, 8, 8, 24, 8);
renderer = new RendererImpl();
setRenderer(renderer);
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
@Override
public boolean onTouchEvent(MotionEvent e) {
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
MainActivity.this.nativeApp.touchDown((int)e.getX(), (int)e.getY());
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
MainActivity.this.nativeApp.touchUp((int)e.getX(), (int)e.getY());
return true;
case MotionEvent.ACTION_MOVE:
MainActivity.this.nativeApp.touchMove((int)e.getX(), (int)e.getY());
return true;
}
return false;
}
}
private PsemekApplication nativeApp;
private ViewImpl view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
System.loadLibrary("boost_random");
System.loadLibrary("TARGET_NAME");
ActionBar actionBar = getActionBar();
if (actionBar != null) {
actionBar.hide();
}
nativeApp = new PsemekApplication(this.getAssets());
view = new ViewImpl(this);
setContentView(view);
WindowInsetsController windowInsetsController = view.getWindowInsetsController();
if (windowInsetsController != null) {
windowInsetsController.hide(Type.systemBars());
windowInsetsController.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
}
}
@Override
protected void onDestroy() {
nativeApp.destroy();
super.onDestroy();
}
}

View file

@ -0,0 +1,150 @@
package psemek.app;
import android.util.Log;
import android.content.res.AssetManager;
import android.media.AudioTrack;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import java.io.InputStream;
import java.io.IOException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.Arrays;
import java.lang.Thread;
public class PsemekApplication {
private AudioTrack audioTrack;
private Thread audioThread;
private long nativeApp;
private static native void setupLogging();
private static native void setAssetManager(AssetManager assetManager);
private static native long createNativeApp();
private static native void destroyNativeApp(long ptr);
private static native void resizeNative(long ptr, int width, int height);
private static native void touchDownNative(long ptr, int x, int y);
private static native void touchUpNative(long ptr, int x, int y);
private static native void touchMoveNative(long ptr, int x, int y);
private static native void drawFrameNative(long ptr);
private static native int audioFrequencyNative();
private static native int audioGetSamples(float buffer[], int sampleOffset, int sampleCount);
private class StreamEventCallbackImpl extends AudioTrack.StreamEventCallback {
private float buffer[];
public StreamEventCallbackImpl() {
super();
buffer = new float[1024];
}
@Override
public void onDataRequest(AudioTrack track, int sizeInFrames) {
Log.e("psemek", "Requested audio " + sizeInFrames);
int sizeInSamples = sizeInFrames * 2;
if (buffer.length < sizeInSamples) {
buffer = Arrays.copyOf(buffer, sizeInSamples);
}
int samples = PsemekApplication.audioGetSamples(buffer, 0, buffer.length);
track.write(buffer, 0, samples, AudioTrack.WRITE_BLOCKING);
}
}
private class AudioThreadImpl extends Thread {
private float buffer[];
public AudioThreadImpl(int bufferSizeInFrames) {
super("audio");
buffer = new float[bufferSizeInFrames * 2];
}
@Override
public void run() {
while (!interrupted()) {
int samples = PsemekApplication.audioGetSamples(buffer, 0, buffer.length);
PsemekApplication.this.audioTrack.write(buffer, 0, samples, AudioTrack.WRITE_BLOCKING);
try {
Thread.sleep(1);
}
catch (InterruptedException e) {
break;
}
}
}
}
public PsemekApplication(AssetManager assetManager) {
setupLogging();
setAssetManager(assetManager);
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_GAME)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
AudioFormat audioFormat = new AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
.setSampleRate(audioFrequencyNative())
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
.build();
int bufferSize = AudioTrack.getMinBufferSize(audioFrequencyNative(), AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_FLOAT);
audioTrack = new AudioTrack.Builder()
.setAudioAttributes(audioAttributes)
.setAudioFormat(audioFormat)
.setBufferSizeInBytes(bufferSize)
.setTransferMode(AudioTrack.MODE_STREAM)
.setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY)
.build();
audioThread = new AudioThreadImpl(audioTrack.getBufferCapacityInFrames());
audioThread.start();
audioTrack.play();
}
public void init() {
nativeApp = createNativeApp();
}
public void resize(int width, int height) {
resizeNative(nativeApp, width, height);
}
public void touchDown(int x, int y)
{
touchDownNative(nativeApp, x, y);
}
public void touchUp(int x, int y)
{
touchUpNative(nativeApp, x, y);
}
public void touchMove(int x, int y)
{
touchMoveNative(nativeApp, x, y);
}
public void drawFrame() {
if (nativeApp != 0) {
drawFrameNative(nativeApp);
}
}
public void destroy() {
audioThread.interrupt();
if (nativeApp != 0)
destroyNativeApp(nativeApp);
nativeApp = 0;
}
}

View file

@ -0,0 +1,57 @@
#include <psemek/audio/engine.hpp>
#include <psemek/audio/constants.hpp>
#include <psemek/log/log.hpp>
#include <psemek/prof/profiler.hpp>
#include <psemek/util/at_scope_exit.hpp>
#include <psemek/util/to_string.hpp>
#include <jni.h>
namespace psemek::android
{
namespace
{
audio::channel_ptr output_channel = std::make_shared<audio::channel>();
struct audio_engine_impl
: audio::engine
{
audio::channel_ptr output() override
{
return output_channel;
}
};
}
}
namespace psemek::audio
{
std::unique_ptr<engine> make_engine()
{
return std::make_unique<android::audio_engine_impl>();
}
}
extern "C" int Java_psemek_app_PsemekApplication_audioFrequencyNative(JNIEnv *, jclass)
{
return psemek::audio::frequency;
}
extern "C" int Java_psemek_app_PsemekApplication_audioGetSamples(JNIEnv * env, jclass, jfloatArray buffer, jint sampleOffset, jint sampleCount)
{
jfloat * data = env->GetFloatArrayElements(buffer, nullptr);
int result = 0;
if (auto stream = psemek::android::output_channel->stream())
result = stream->read({data + sampleOffset, sampleCount});
std::fill(data + sampleOffset + result, data + sampleOffset + sampleCount, 0.f);
return sampleCount;
}

View file

@ -0,0 +1,127 @@
#include <jni.h>
#include <android/log.h>
#include <psemek/app/application.hpp>
#include <psemek/gfx/init.hpp>
#include <psemek/log/log.hpp>
namespace
{
using namespace psemek;
android_LogPriority log_priority(log::level level)
{
switch (level)
{
case log::level::debug:
return ANDROID_LOG_DEBUG;
case log::level::info:
return ANDROID_LOG_INFO;
case log::level::warning:
return ANDROID_LOG_WARN;
case log::level::error:
return ANDROID_LOG_ERROR;
default:
return ANDROID_LOG_UNKNOWN;
}
}
struct sink_impl
: log::sink
{
void put_message(log::message const & msg) override
{
__android_log_write(log_priority(msg.level), "psemek", msg.message.data());
}
void flush() override
{}
};
template <typename F>
auto try_native(JNIEnv * env, F && f) try
{
return f();
}
catch (std::exception const & e)
{
env->ThrowNew(env->FindClass("java/lang/Exception"), e.what());
__builtin_unreachable();
}
catch (...)
{
env->ThrowNew(env->FindClass("java/lang/Exception"), "Unknown exception");
__builtin_unreachable();
}
}
extern "C" void Java_psemek_app_PsemekApplication_setupLogging(JNIEnv *, jclass)
{
psemek::log::add_sink(std::make_unique<sink_impl>());
}
extern "C" jlong Java_psemek_app_PsemekApplication_createNativeApp(JNIEnv * env, jclass)
{
return try_native(env, [&]{
auto factory = psemek::app::make_application_factory();
psemek::gfx::init();
psemek::app::application::context context;
context.show_cursor = [](bool){};
context.vsync = [](bool){};
context.relative_mouse_mode = [](bool){};
context.windowed = [](bool){};
auto app = factory->create(factory->options(), context).release();
return reinterpret_cast<jlong>(app);
});
}
extern "C" void Java_psemek_app_PsemekApplication_destroyNativeApp(JNIEnv *, jclass, jlong ptr)
{
delete reinterpret_cast<psemek::app::application *>(ptr);
}
extern "C" void Java_psemek_app_PsemekApplication_resizeNative(JNIEnv * env, jclass, jlong ptr, jint width, jint height)
{
try_native(env, [&]{
auto app = reinterpret_cast<psemek::app::application *>(ptr);
app->on_event(psemek::app::resize_event{{width, height}});
});
}
extern "C" void Java_psemek_app_PsemekApplication_touchDownNative(JNIEnv * env, jclass, jlong ptr, jint x, jint y)
{
try_native(env, [&]{
auto app = reinterpret_cast<psemek::app::application *>(ptr);
app->on_event(psemek::app::touch_down_event{{x, y}});
});
}
extern "C" void Java_psemek_app_PsemekApplication_touchUpNative(JNIEnv * env, jclass, jlong ptr, jint x, jint y)
{
try_native(env, [&]{
auto app = reinterpret_cast<psemek::app::application *>(ptr);
app->on_event(psemek::app::touch_up_event{{x, y}});
});
}
extern "C" void Java_psemek_app_PsemekApplication_touchMoveNative(JNIEnv * env, jclass, jlong ptr, jint x, jint y)
{
try_native(env, [&]{
auto app = reinterpret_cast<psemek::app::application *>(ptr);
app->on_event(psemek::app::touch_move_event{{x, y}});
});
}
extern "C" void Java_psemek_app_PsemekApplication_drawFrameNative(JNIEnv * env, jclass, jlong ptr)
{
try_native(env, [&]{
auto app = reinterpret_cast<psemek::app::application *>(ptr);
app->update();
app->present();
});
}

View file

@ -0,0 +1,64 @@
#include <psemek/app/resource.hpp>
#include <psemek/log/log.hpp>
#include <psemek/util/to_string.hpp>
#include <android/asset_manager_jni.h>
namespace
{
jobject assetManagerRef;
AAssetManager * assetManager;
struct stream_impl
: psemek::io::istream
{
stream_impl(AAsset * asset)
: asset_(asset)
{}
~stream_impl()
{
AAsset_close(asset_);
}
std::size_t read(char * p, std::size_t size) override
{
int result = AAsset_read(asset_, p, size);
if (result < 0)
throw std::runtime_error("Failed to read from resource");
return result;
}
bool finished() const override
{
return AAsset_getRemainingLength64(asset_) == 0;
}
private:
AAsset * asset_;
};
}
namespace psemek::app
{
std::unique_ptr<io::istream> open_resource(std::filesystem::path const & relative_path)
{
log::info() << "Opening resource " << relative_path;
auto asset = AAssetManager_open(assetManager, relative_path.c_str(), AASSET_MODE_STREAMING);
if (!asset)
throw std::runtime_error(util::to_string("Failed to open resource ", relative_path));
return std::make_unique<stream_impl>(asset);
}
}
extern "C" void Java_psemek_app_PsemekApplication_setAssetManager(JNIEnv * env, jclass, jobject manager)
{
assetManagerRef = env->NewLocalRef(manager);
assetManager = AAssetManager_fromJava(env, manager);
}

View file

@ -3,4 +3,7 @@ file(GLOB_RECURSE PSEMEK_APP_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "sou
psemek_add_library(psemek-app ${PSEMEK_APP_HEADERS} ${PSEMEK_APP_SOURCES})
target_include_directories(psemek-app PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(psemek-app PUBLIC psemek-log psemek-util psemek-gfx psemek-ui psemek-sdl2)
target_link_libraries(psemek-app PUBLIC psemek-log psemek-util psemek-gfx)
if(PSEMEK_GRAPHICS_API STREQUAL WEBGPU)
target_link_libraries(psemek-app PUBLIC psemek-wgpu)
endif()

View file

@ -1,68 +0,0 @@
#pragma once
#include <psemek/app/scene.hpp>
#include <psemek/app/scene_manager.hpp>
#include <psemek/geom/vector.hpp>
#include <psemek/util/pimpl.hpp>
#include <SDL2/SDL_keycode.h>
#include <memory>
namespace psemek::app
{
static const geom::vector<int, 2> common_resolutions[] =
{
{1024, 768},
{1280, 720},
{1280, 1024},
{1366, 768},
{1440, 900},
{1536, 864},
{1600, 900},
{1920, 1080},
};
struct app
: scene_base, scene_manager
{
struct options
{
int multisampling = 0;
std::optional<geom::vector<int, 2>> fixed_resolution = std::nullopt;
bool highdpi = false;
};
app(std::string const & name);
app(std::string const & name, int multisampling);
app(std::string const & name, options const & opts);
~app() override;
virtual bool running() const;
virtual void stop();
virtual void on_quit();
void on_resize(int width, int height) override;
void present() override;
void poll_events();
void run();
void push_scene(std::shared_ptr<scene> s) override;
std::shared_ptr<scene> pop_scene() override;
void show_cursor(bool show);
bool vsync() const;
void vsync(bool on);
float time() const;
private:
psemek_declare_pimpl
};
}

View file

@ -0,0 +1,71 @@
#pragma once
#include <psemek/app/event_handler.hpp>
#if defined(PSEMEK_GRAPHICS_API_WEBGPU)
#include <psemek/wgpu/instance.hpp>
#include <psemek/wgpu/adapter.hpp>
#include <psemek/wgpu/surface.hpp>
#include <psemek/wgpu/device.hpp>
#endif
#include <memory>
#include <functional>
namespace psemek::app
{
struct application
: event_handler
{
// Data sent to platform backend for initialization
struct options
{
std::string name;
int multisampling = 4;
bool highdpi = false;
#if defined(PSEMEK_GRAPHICS_API_WEBGPU)
std::vector<wgpu::feature> required_features = {};
std::optional<wgpu::limits> required_limits = {};
std::optional<wgpu::native_limits> required_native_limits = {};
#endif
};
// Data received from platform backend after initialization
struct context
{
std::vector<std::string> args;
std::function<void(bool)> show_cursor;
std::function<void(bool)> relative_mouse_mode;
std::function<void(bool)> windowed;
std::function<void(bool)> text_input;
#if defined(PSEMEK_GRAPHICS_API_OPENGL)
std::function<void(bool)> vsync;
#endif
#if defined(PSEMEK_GRAPHICS_API_WEBGPU)
wgpu::instance instance;
wgpu::adapter adapter;
wgpu::surface surface;
wgpu::device device;
#endif
};
struct factory
{
virtual application::options options() = 0;
virtual std::unique_ptr<application> create(struct application::options const & options, context const & context) = 0;
virtual ~factory() {}
};
virtual bool running() const = 0;
virtual void stop() = 0;
virtual void update() = 0;
virtual void present() = 0;
};
// Implemented by the user, called by platform backends
std::unique_ptr<application::factory> make_application_factory();
}

View file

@ -0,0 +1,33 @@
#pragma once
#include <psemek/app/application.hpp>
#include <psemek/app/event_state.hpp>
namespace psemek::app
{
struct application_base
: application
{
void on_event(resize_event const &) override;
void on_event(focus_event const &) override;
void on_event(mouse_move_event const &) override;
void on_event(mouse_wheel_event const &) override;
void on_event(mouse_button_event const &) override;
void on_event(touch_down_event const &) override;
void on_event(touch_up_event const &) override;
void on_event(touch_move_event const &) override;
void on_event(key_event const &) override;
void on_event(text_input_event const &) override;
void stop() override;
bool running() const override;
event_state const & state() const { return state_; }
private:
bool running_ = true;
event_state state_;
};
}

View file

@ -0,0 +1,47 @@
#pragma once
#include <psemek/app/application.hpp>
#include <memory>
namespace psemek::app
{
template <typename Factory>
std::unique_ptr<application::factory> default_application_factory(application::options const & options, Factory && factory)
{
struct factory_impl
: application::factory
{
application::options opts;
Factory factory;
factory_impl(application::options const & options, Factory && factory)
: opts(options)
, factory(std::move(factory))
{}
application::options options() override
{
return opts;
}
std::unique_ptr<application> create(application::options const & options, application::context const & context) override
{
return factory(options, context);
}
};
return std::make_unique<factory_impl>(options, std::move(factory));
}
template <typename Application>
std::unique_ptr<application::factory> default_application_factory(application::options const & options)
{
return default_application_factory(options, [](auto const & options, auto const & context){
return std::make_unique<Application>(options, context);
});
}
}

View file

@ -0,0 +1,24 @@
#pragma once
#include <psemek/app/events.hpp>
namespace psemek::app
{
struct event_handler
{
virtual void on_event(resize_event const &) {}
virtual void on_event(focus_event const &) {}
virtual void on_event(mouse_move_event const &) {}
virtual void on_event(mouse_wheel_event const &) {}
virtual void on_event(mouse_button_event const &) {}
virtual void on_event(touch_down_event const &) {}
virtual void on_event(touch_up_event const &) {}
virtual void on_event(touch_move_event const &) {}
virtual void on_event(key_event const &) {}
virtual void on_event(text_input_event const &) {}
virtual ~event_handler() {}
};
}

View file

@ -0,0 +1,67 @@
#pragma once
#include <psemek/app/events.hpp>
#include <psemek/util/hash_table.hpp>
namespace psemek::app
{
struct event_state
{
math::vector<int, 2> size = {0, 0};
bool focus = true;
math::point<int, 2> mouse = {0, 0};
int wheel = 0;
util::hash_set<mouse_button> mouse_button_down;
util::hash_set<keycode> key_down;
};
inline void apply(event_state & state, resize_event const & event)
{
state.size = event.size;
}
inline void apply(event_state & state, focus_event const & event)
{
state.focus = event.gained;
}
inline void apply(event_state & state, mouse_move_event const & event)
{
state.mouse = event.position;
}
inline void apply(event_state & state, mouse_wheel_event const & event)
{
state.wheel += event.delta;
}
inline void apply(event_state & state, mouse_button_event const & event)
{
if (event.down)
state.mouse_button_down.insert(event.button);
else
state.mouse_button_down.erase(event.button);
}
inline void apply(event_state &, touch_down_event const &)
{}
inline void apply(event_state &, touch_up_event const &)
{}
inline void apply(event_state &, touch_move_event const &)
{}
inline void apply(event_state & state, key_event const & event)
{
if (event.down)
state.key_down.insert(event.key);
else
state.key_down.erase(event.key);
}
inline void apply(event_state &, text_input_event const &)
{}
}

View file

@ -0,0 +1,182 @@
#pragma once
#include <psemek/math/point.hpp>
#include <string>
namespace psemek::app
{
struct resize_event
{
math::vector<int, 2> size;
};
struct focus_event
{
bool gained;
};
struct mouse_move_event
{
math::point<int, 2> position;
math::vector<int, 2> relative;
};
struct mouse_wheel_event
{
int delta;
};
enum class mouse_button
{
left,
middle,
right,
};
struct mouse_button_event
{
mouse_button button;
bool down;
};
struct touch_down_event
{
math::point<int, 2> position;
};
struct touch_up_event
{
math::point<int, 2> position;
};
struct touch_move_event
{
math::point<int, 2> position;
};
enum class keycode
{
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
NUM_1,
NUM_2,
NUM_3,
NUM_4,
NUM_5,
NUM_6,
NUM_7,
NUM_8,
NUM_9,
NUM_0,
RETURN,
ESCAPE,
BACKSPACE,
TAB,
SPACE,
MINUS,
EQUALS,
LEFTBRACKET,
RIGHTBRACKET,
BACKSLASH,
NONUSHASH,
SEMICOLON,
APOSTROPHE,
BACKQUOTE,
COMMA,
PERIOD,
SLASH,
CAPSLOCK,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
PRINTSCREEN,
SCROLLLOCK,
PAUSE,
INSERT,
HOME,
PAGEUP,
DELETE,
END,
PAGEDOWN,
RIGHT,
LEFT,
DOWN,
UP,
NUMLOCKCLEAR,
KP_DIVIDE,
KP_MULTIPLY,
KP_MINUS,
KP_PLUS,
KP_ENTER,
KP_1,
KP_2,
KP_3,
KP_4,
KP_5,
KP_6,
KP_7,
KP_8,
KP_9,
KP_0,
KP_PERIOD,
APPLICATION,
MUTE,
VOLUMEUP,
VOLUMEDOWN,
LCTRL,
LSHIFT,
LALT,
LGUI,
RCTRL,
RSHIFT,
RALT,
RGUI,
};
struct key_event
{
keycode key;
bool down;
};
struct text_input_event
{
std::string input;
};
}

View file

@ -1,47 +0,0 @@
#pragma once
#include <psemek/log/log.hpp>
#include <psemek/util/pretty_print.hpp>
#include <psemek/util/clock.hpp>
#include <utility>
namespace psemek::app
{
template <typename App, typename ... Args>
int main(Args && ... args) try
{
util::clock<std::chrono::milliseconds, std::chrono::high_resolution_clock> clock;
#ifdef PSEMEK_PACKAGE_MODE
log::level stdio_log_level = log::level::info;
#else
log::level stdio_log_level = log::level::debug;
#endif
log::add_sink(log::default_sink(io::std_out(), stdio_log_level));
log::register_thread("main");
App app(std::forward<Args>(args)...);
log::info() << "Started in " << util::pretty(clock.duration(), std::chrono::milliseconds{1});
log::info() << "Running";
app.run();
log::info() << "Quitting";
return EXIT_SUCCESS;
}
catch (std::exception const & e)
{
log::error() << e.what();
return EXIT_FAILURE;
}
catch (...)
{
log::error() << "Unknown exception";
return EXIT_FAILURE;
}
}

View file

@ -0,0 +1,17 @@
#pragma once
#include <psemek/io/stream.hpp>
#include <filesystem>
#include <memory>
namespace psemek::app
{
std::filesystem::path resource_root();
void set_resource_root(std::filesystem::path const & root);
// Implemented by a backend library
std::unique_ptr<io::istream> open_resource(std::filesystem::path const & relative_path);
}

View file

@ -1,93 +1,19 @@
#pragma once
#include <psemek/geom/point.hpp>
#include <SDL2/SDL_keycode.h>
#include <optional>
#include <set>
#include <psemek/app/event_handler.hpp>
#include <psemek/app/event_state.hpp>
namespace psemek::app
{
struct app;
struct scene
: event_handler
{
virtual ~scene() = 0;
virtual void on_scene_enter(app * /* parent */) {}
virtual void on_scene_exit() {}
virtual void on_resize(int /* width */, int /* height */) {}
virtual void on_focus_gained() {}
virtual void on_focus_lost() {}
virtual void on_mouse_move(int /* x */, int /* y */, int /* dx */, int /* dy */) {}
virtual void on_mouse_wheel(int /* delta */) {}
virtual void on_left_button_down() {}
virtual void on_left_button_up() {}
virtual void on_middle_button_down() {}
virtual void on_middle_button_up() {}
virtual void on_right_button_down() {}
virtual void on_right_button_up() {}
virtual void on_key_down(SDL_Keycode /* key */) {}
virtual void on_key_up(SDL_Keycode /* key */) {}
virtual void on_text_input(std::string_view /* text */) {}
virtual void on_enter(event_state const &) {}
virtual void on_exit() {}
virtual void update() {}
virtual void present() {}
};
inline scene::~scene() = default;
struct scene_base
: scene
{
void on_scene_enter(app * parent) override { parent_ = parent; }
void on_scene_exit() override { parent_ = nullptr; }
void on_resize(int width, int height) override { width_ = width; height_ = height; }
void on_mouse_move(int x, int y, int, int) override { mouse_ = geom::point{x, y}; }
void on_left_button_down() override { left_button_down_ = true; }
void on_left_button_up() override { left_button_down_ = false; }
void on_middle_button_down() override { middle_button_down_ = true; }
void on_middle_button_up() override { middle_button_down_ = false; }
void on_right_button_down() override { right_button_down_ = true; }
void on_right_button_up() override { right_button_down_ = false; }
void on_key_down(SDL_Keycode key) override { keys_.insert(key); }
void on_key_up(SDL_Keycode key) override { keys_.erase(key); }
bool active() const { return parent_ != nullptr; }
app * parent() const { return parent_; }
bool is_left_button_down() const { return left_button_down_; }
bool is_middle_button_down() const { return middle_button_down_; }
bool is_right_button_down() const { return right_button_down_; }
std::optional<geom::point<int, 2>> mouse() const { return mouse_; }
bool is_key_down(SDL_Keycode key) const { return keys_.count(key) > 0; }
int width() const { return width_; }
int height() const { return height_; }
private:
app * parent_ = nullptr;
int width_ = 0;
int height_ = 0;
bool left_button_down_ = false;
bool middle_button_down_ = false;
bool right_button_down_ = false;
std::optional<geom::point<int, 2>> mouse_;
std::set<SDL_Keycode> keys_;
};
}

View file

@ -0,0 +1,35 @@
#pragma once
#include <psemek/app/application_base.hpp>
#include <psemek/app/scene.hpp>
namespace psemek::app
{
struct scene_application
: application_base
{
void on_event(resize_event const &) override;
void on_event(focus_event const &) override;
void on_event(mouse_move_event const &) override;
void on_event(mouse_wheel_event const &) override;
void on_event(mouse_button_event const &) override;
void on_event(touch_down_event const &) override;
void on_event(touch_up_event const &) override;
void on_event(touch_move_event const &) override;
void on_event(key_event const &) override;
void on_event(text_input_event const &) override;
void stop() override;
void update() override;
void present() override;
virtual std::shared_ptr<scene> current_scene() = 0;
private:
template <typename Event>
void on_event_impl(Event const & event);
};
}

View file

@ -1,19 +0,0 @@
#pragma once
#include <psemek/app/scene.hpp>
#include <memory>
namespace psemek::app
{
struct scene_manager
{
virtual void push_scene(std::shared_ptr<scene>) = 0;
virtual std::shared_ptr<scene> pop_scene() = 0;
virtual ~scene_manager() {}
};
}

View file

@ -1,55 +0,0 @@
#pragma once
#include <psemek/app/scene.hpp>
#include <psemek/ui/controller.hpp>
#include <psemek/util/clock.hpp>
namespace psemek::app
{
struct ui_scene
: scene_base
{
ui_scene(ui::controller & controller);
void on_scene_enter(app * parent) override;
void on_scene_exit() override;
void on_resize(int width, int height) override;
void on_mouse_move(int x, int y, int dx, int dy) override;
void on_mouse_wheel(int delta) override;
void on_left_button_down() override;
void on_left_button_up() override;
void on_middle_button_down() override;
void on_middle_button_up() override;
void on_right_button_down() override;
void on_right_button_up() override;
void on_key_down(SDL_Keycode key) override;
void on_key_up(SDL_Keycode key) override;
void on_text_input(std::string_view text) override;
void update() override;
void present() override;
ui::controller & controller() const { return controller_; }
protected:
std::optional<std::size_t> max_events_per_frame_ = 64;
std::shared_ptr<ui::element> get_ui() { return ui_; }
void set_ui(std::shared_ptr<ui::element> ui);
private:
ui::controller & controller_;
std::shared_ptr<ui::element> ui_;
util::clock<std::chrono::duration<float>, std::chrono::high_resolution_clock> update_clock_;
};
}

View file

@ -1,325 +0,0 @@
#include <psemek/app/app.hpp>
#include <psemek/gfx/gl.hpp>
#include <psemek/log/log.hpp>
#include <psemek/sdl2/init.hpp>
#include <SDL2/SDL.h>
#include <vector>
namespace psemek::app
{
using clock = std::chrono::high_resolution_clock;
struct app::impl
{
app * parent;
std::shared_ptr<void> sdl_init;
SDL_Window * window = nullptr;
SDL_GLContext gl_context = nullptr;
std::vector<std::shared_ptr<scene>> scene_stack;
bool running = false;
bool had_initial_resize = false;
bool had_scene_exit = false;
clock::time_point start_time;
impl(app * parent)
: parent(parent)
, sdl_init(sdl2::init(SDL_INIT_VIDEO))
{}
~impl()
{
if (gl_context) SDL_GL_DeleteContext(gl_context);
if (window) SDL_DestroyWindow(window);
}
std::shared_ptr<scene> get_scene()
{
if (!scene_stack.empty())
return scene_stack.back();
return std::shared_ptr<scene>(parent, [](scene *){});
}
};
app::app(std::string const & name)
: app(name, 0)
{}
app::app(std::string const & name, int multisampling)
: app(name, options{multisampling})
{}
app::app(std::string const & name, options const & opts)
: pimpl_{make_impl(this)}
{
impl().start_time = clock::now();
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, gl::sys::major_version());
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, gl::sys::minor_version());
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
if (opts.multisampling == 0)
{
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
}
else
{
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, opts.multisampling);
}
std::uint32_t flags = SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN;
if (!opts.fixed_resolution)
flags |= SDL_WINDOW_RESIZABLE | SDL_WINDOW_MAXIMIZED;
if (opts.highdpi) flags |= SDL_WINDOW_ALLOW_HIGHDPI;
int width = opts.fixed_resolution ? (*opts.fixed_resolution)[0] : 1024;
int height = opts.fixed_resolution ? (*opts.fixed_resolution)[1] : 768;
impl().window = SDL_CreateWindow(name.data(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, flags);
if (!impl().window)
sdl2::fail("Failed to create window: ");
impl().gl_context = SDL_GL_CreateContext(impl().window);
if (!impl().gl_context)
sdl2::fail("Failed to create OpenGL context: ");
SDL_GL_MakeCurrent(impl().window, impl().gl_context);
if (!gl::sys::initialize())
throw std::runtime_error("Failed to load OpenGL functions");
auto vendor = gl::GetString(gl::VENDOR);
auto renderer = gl::GetString(gl::RENDERER);
int major, minor;
gl::GetIntegerv(gl::MAJOR_VERSION, &major);
gl::GetIntegerv(gl::MINOR_VERSION, &minor);
log::info() << "Initialized OpenGL " << major << '.' << minor << ", " << vendor << ", " << renderer;
SDL_GL_GetDrawableSize(impl().window, &width, &height);
scene_base::on_resize(width, height);
log::info() << "Initial window size: " << width << 'x' << height;
SDL_StopTextInput();
}
app::~app()
{}
bool app::running() const
{
return impl().running;
}
void app::stop()
{
impl().running = false;
}
void app::on_resize(int width, int height)
{
scene_base::on_resize(width, height);
gl::Viewport(0, 0, width, height);
}
void app::on_quit()
{
impl().running = false;
}
void app::poll_events()
{
auto handler = [this]{ return impl().get_scene(); };
for (SDL_Event e; SDL_PollEvent(&e);) switch (e.type)
{
case SDL_QUIT:
on_quit();
break;
case SDL_WINDOWEVENT: switch (e.window.event)
{
case SDL_WINDOWEVENT_RESIZED:
impl().had_initial_resize = true;
{
int width, height;
SDL_GL_GetDrawableSize(impl().window, &width, &height);
handler()->on_resize(width, height);
log::info() << "Window resized to " << width << 'x' << height;
}
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
handler()->on_focus_gained();
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
handler()->on_focus_lost();
break;
}
break;
case SDL_MOUSEMOTION:
handler()->on_mouse_move(e.motion.x, e.motion.y, e.motion.xrel, e.motion.yrel);
break;
case SDL_MOUSEWHEEL:
handler()->on_mouse_wheel(e.wheel.y);
break;
case SDL_MOUSEBUTTONDOWN:
switch (e.button.button)
{
case SDL_BUTTON_LEFT:
handler()->on_left_button_down();
break;
case SDL_BUTTON_MIDDLE:
handler()->on_middle_button_down();
break;
case SDL_BUTTON_RIGHT:
handler()->on_right_button_down();
break;
}
break;
case SDL_MOUSEBUTTONUP:
switch (e.button.button)
{
case SDL_BUTTON_LEFT:
handler()->on_left_button_up();
break;
case SDL_BUTTON_MIDDLE:
handler()->on_middle_button_up();
break;
case SDL_BUTTON_RIGHT:
handler()->on_right_button_up();
break;
}
break;
case SDL_KEYDOWN:
handler()->on_key_down(e.key.keysym.sym);
break;
case SDL_KEYUP:
handler()->on_key_up(e.key.keysym.sym);
break;
case SDL_TEXTINPUT:
handler()->on_text_input(e.text.text);
break;
}
}
void app::present()
{
gl::ClearColor(0.7f, 0.7f, 1.f, 1.f);
gl::Clear(gl::COLOR_BUFFER_BIT);
}
void app::run()
{
SDL_ShowWindow(impl().window);
impl().running = true;
if (impl().get_scene().get() == this)
on_scene_enter(this);
while (running())
{
poll_events();
auto handler = [this]{ return impl().get_scene(); };
if (!impl().had_initial_resize)
{
int w, h;
SDL_GetWindowSize(impl().window, &w, &h);
impl().had_initial_resize = true;
handler()->on_resize(w, h);
}
if (!running()) break;
handler()->update();
handler()->present();
SDL_GL_SwapWindow(impl().window);
}
if (!impl().had_scene_exit)
{
impl().had_scene_exit = true;
impl().get_scene()->on_scene_exit();
}
}
void app::push_scene(std::shared_ptr<scene> s)
{
if (!impl().had_scene_exit)
{
impl().had_scene_exit = true;
impl().get_scene()->on_scene_exit();
}
impl().had_initial_resize = false;
impl().had_scene_exit = false;
impl().scene_stack.push_back(std::move(s));
impl().get_scene()->on_scene_enter(this);
}
std::shared_ptr<scene> app::pop_scene()
{
if (impl().scene_stack.empty())
return nullptr;
if (!impl().had_scene_exit)
{
impl().had_scene_exit = true;
impl().get_scene()->on_scene_exit();
}
impl().had_initial_resize = false;
auto s = std::move(impl().scene_stack.back());
impl().scene_stack.pop_back();
impl().get_scene()->on_scene_enter(this);
return s;
}
void app::show_cursor(bool show)
{
SDL_ShowCursor(show ? SDL_TRUE : SDL_FALSE);
SDL_SetRelativeMouseMode(show ? SDL_FALSE : SDL_TRUE);
}
float app::time() const
{
return std::chrono::duration_cast<std::chrono::duration<float>>(clock::now() - impl().start_time).count();
}
bool app::vsync() const
{
return SDL_GL_GetSwapInterval() != 0;
}
void app::vsync(bool on)
{
if (on)
{
// try adaptive vsync
if (SDL_GL_SetSwapInterval(-1) != 0)
{
// failed, try usual vsync then
SDL_GL_SetSwapInterval(1);
}
}
else
{
SDL_GL_SetSwapInterval(0);
}
}
}

View file

@ -0,0 +1,64 @@
#include <psemek/app/application_base.hpp>
namespace psemek::app
{
void application_base::on_event(resize_event const & event)
{
apply(state_, event);
}
void application_base::on_event(focus_event const & event)
{
apply(state_, event);
}
void application_base::on_event(mouse_move_event const & event)
{
apply(state_, event);
}
void application_base::on_event(mouse_wheel_event const & event)
{
apply(state_, event);
}
void application_base::on_event(mouse_button_event const & event)
{
apply(state_, event);
}
void application_base::on_event(touch_down_event const & event)
{
apply(state_, event);
}
void application_base::on_event(touch_up_event const & event)
{
apply(state_, event);
}
void application_base::on_event(touch_move_event const & event)
{
apply(state_, event);
}
void application_base::on_event(key_event const & event)
{
apply(state_, event);
}
void application_base::on_event(text_input_event const &)
{}
void application_base::stop()
{
running_ = false;
}
bool application_base::running() const
{
return running_;
}
}

View file

@ -0,0 +1,24 @@
#include <psemek/app/resource.hpp>
#include <psemek/util/executable_path.hpp>
namespace psemek::app
{
namespace
{
static std::filesystem::path global_resource_root = util::executable_path().parent_path();
}
std::filesystem::path resource_root()
{
return global_resource_root;
}
void set_resource_root(std::filesystem::path const & root)
{
global_resource_root = root;
}
}

View file

@ -0,0 +1,83 @@
#include <psemek/app/scene_application.hpp>
namespace psemek::app
{
void scene_application::on_event(resize_event const & event)
{
on_event_impl(event);
}
void scene_application::on_event(focus_event const & event)
{
on_event_impl(event);
}
void scene_application::on_event(mouse_move_event const & event)
{
on_event_impl(event);
}
void scene_application::on_event(mouse_wheel_event const & event)
{
on_event_impl(event);
}
void scene_application::on_event(mouse_button_event const & event)
{
on_event_impl(event);
}
void scene_application::on_event(touch_down_event const & event)
{
on_event_impl(event);
}
void scene_application::on_event(touch_up_event const & event)
{
on_event_impl(event);
}
void scene_application::on_event(touch_move_event const & event)
{
on_event_impl(event);
}
void scene_application::on_event(key_event const & event)
{
on_event_impl(event);
}
void scene_application::on_event(text_input_event const & event)
{
on_event_impl(event);
}
void scene_application::stop()
{
if (auto scene = current_scene())
scene->on_exit();
application_base::stop();
}
void scene_application::update()
{
if (auto scene = current_scene())
scene->update();
}
void scene_application::present()
{
if (auto scene = current_scene())
scene->present();
}
template <typename Event>
void scene_application::on_event_impl(Event const & event)
{
application_base::on_event(event);
if (auto scene = current_scene())
scene->on_event(event);
}
}

View file

@ -1,121 +0,0 @@
#include <psemek/app/ui_scene.hpp>
#include <psemek/log/log.hpp>
namespace psemek::app
{
ui_scene::ui_scene(ui::controller & controller)
: controller_(controller)
{}
void ui_scene::on_scene_enter(app * parent)
{
scene_base::on_scene_enter(parent);
controller_.set_root(ui_);
update_clock_.restart();
}
void ui_scene::on_scene_exit()
{
scene_base::on_scene_exit();
controller_.set_root(nullptr);
}
void ui_scene::on_resize(int width, int height)
{
scene_base::on_resize(width, height);
controller_.reshape({{{0.f, width}, {0.f, height}}});
}
void ui_scene::on_mouse_move(int x, int y, int dx, int dy)
{
scene_base::on_mouse_move(x, y, dx, dy);
controller_.event(ui::mouse_move{{x, y}});
}
void ui_scene::on_mouse_wheel(int delta)
{
scene_base::on_mouse_wheel(delta);
controller_.event(ui::mouse_wheel{delta});
}
void ui_scene::on_left_button_down()
{
scene_base::on_left_button_down();
controller_.event(ui::mouse_click{ui::mouse_button::left, true});
}
void ui_scene::on_left_button_up()
{
scene_base::on_left_button_up();
controller_.event(ui::mouse_click{ui::mouse_button::left, false});
}
void ui_scene::on_middle_button_down()
{
scene_base::on_left_button_down();
controller_.event(ui::mouse_click{ui::mouse_button::middle, true});
}
void ui_scene::on_middle_button_up()
{
scene_base::on_left_button_down();
controller_.event(ui::mouse_click{ui::mouse_button::middle, false});
}
void ui_scene::on_right_button_down()
{
scene_base::on_right_button_down();
controller_.event(ui::mouse_click{ui::mouse_button::right, true});
}
void ui_scene::on_right_button_up()
{
scene_base::on_right_button_down();
controller_.event(ui::mouse_click{ui::mouse_button::right, false});
}
void ui_scene::on_key_down(SDL_Keycode key)
{
scene_base::on_key_down(key);
controller_.event(ui::key_press{key, true});
}
void ui_scene::on_key_up(SDL_Keycode key)
{
scene_base::on_key_up(key);
controller_.event(ui::key_press{key, false});
}
void ui_scene::on_text_input(std::string_view text)
{
scene_base::on_text_input(text);
controller_.event(ui::text_input{text});
}
void ui_scene::update()
{
controller_.update(update_clock_.restart().count());
std::size_t events = controller_.loop()->pump(max_events_per_frame_);
if (max_events_per_frame_ && events == *max_events_per_frame_)
log::warning() << "UI event loop had more than " << *max_events_per_frame_ << " events, delaying others";
}
void ui_scene::present()
{
gfx::render_target rt;
rt.viewport = {{{0, width()}, {0, height()}}};
rt.framebuffer = &gfx::framebuffer::null();
rt.draw_buffer = gl::BACK_LEFT;
controller_.render(rt);
}
void ui_scene::set_ui(std::shared_ptr<ui::element> ui)
{
ui_ = std::move(ui);
if (active())
controller_.set_root(ui_);
}
}

View file

@ -3,4 +3,8 @@ file(GLOB_RECURSE PSEMEK_ASYNC_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "s
psemek_add_library(psemek-async ${PSEMEK_ASYNC_HEADERS} ${PSEMEK_ASYNC_SOURCES})
target_include_directories(psemek-async PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(psemek-async PUBLIC psemek-log psemek-util)
target_link_libraries(psemek-async PUBLIC
psemek-log
psemek-util
psemek-prof
)

View file

@ -17,6 +17,8 @@ namespace psemek::async
void stop() override;
void clear() override;
void wait() override;
void wait_for(clock::duration period) override;

View file

@ -4,7 +4,6 @@
#include <psemek/util/function.hpp>
#include <chrono>
#include <memory>
namespace psemek::async
{
@ -30,6 +29,9 @@ namespace psemek::async
// NB: the executor must call stop() from destructor.
virtual void stop() = 0;
// Clear the pending tasks queue.
virtual void clear() = 0;
// Wait for all the tasks to be executed.
// May take forever.
virtual void wait() = 0;

View file

@ -1,6 +1,7 @@
#pragma once
#include <psemek/util/function.hpp>
#include <psemek/util/exception.hpp>
#include <atomic>
#include <mutex>
@ -39,6 +40,18 @@ namespace psemek::async
using type = util::function<void()>;
};
template <typename T>
struct future_result_type
{
using type = T &;
};
template <>
struct future_result_type<void>
{
using type = void;
};
struct cancel_token
{};
@ -54,21 +67,26 @@ namespace psemek::async
std::weak_ptr<cancel_token> weak_cancel;
std::mutex then_mutex;
typename then_function<T>::type then_func;
std::vector<typename then_function<T>::type> then_funcs;
std::vector<util::function<void(std::exception_ptr)>> then_exception_funcs;
};
}
struct empty_future_error
: std::exception
: util::exception
{
char const * what() const noexcept { return "future is empty"; }
empty_future_error(util::stacktrace stacktrace = {})
: util::exception("Future is empty", std::move(stacktrace))
{}
};
struct canceled_task_error
: std::exception
: util::exception
{
char const * what() const noexcept { return "task canceled"; }
canceled_task_error(util::stacktrace stacktrace = {})
: util::exception("Task canceled", std::move(stacktrace))
{}
};
struct auto_cancel_tag{};
@ -80,14 +98,16 @@ namespace psemek::async
using result_type = T;
future() = default;
future(future&&) = default;
future(future const &) = default;
future(future &&) = default;
future(std::shared_ptr<detail::task_state<T>> state, std::shared_ptr<detail::cancel_token> cancel)
: state_(std::move(state))
, cancel_(std::move(cancel))
{}
future & operator = (future&&) = default;
future & operator = (future const &) = default;
future & operator = (future &&) = default;
~future()
{
@ -131,7 +151,7 @@ namespace psemek::async
return has_value_unsafe();
}
T get()
typename detail::future_result_type<T>::type get()
{
if (!state_)
throw empty_future_error{};
@ -143,7 +163,7 @@ namespace psemek::async
if constexpr (std::is_same_v<T, void>)
return;
else
return std::move(*(state_->value));
return *(state_->value);
}
else
std::rethrow_exception(state_->exception);
@ -177,15 +197,19 @@ namespace psemek::async
};
struct empty_promise_error
: std::exception
: util::exception
{
char const * what() const noexcept { return "promise is empty"; }
empty_promise_error(util::stacktrace stacktrace = {})
: util::exception("Promise is empty", std::move(stacktrace))
{}
};
struct satisfied_promise_error
: std::exception
: util::exception
{
char const * what() const noexcept { return "promise already contains a value or exception"; }
satisfied_promise_error(util::stacktrace stacktrace = {})
: util::exception("Promise already contains a value or exception", std::move(stacktrace))
{}
};
template <typename T>
@ -230,8 +254,10 @@ namespace psemek::async
state_->value = value;
}
state_->value_cv.notify_all();
if (state_->then_func)
state_->then_func(*state_->value);
std::lock_guard lock{state_->then_mutex};
for (auto & func : state_->then_funcs)
func(*state_->value);
}
void set_value(T && value)
@ -243,8 +269,10 @@ namespace psemek::async
state_->value = std::move(value);
}
state_->value_cv.notify_all();
if (state_->then_func)
state_->then_func(*state_->value);
std::lock_guard lock{state_->then_mutex};
for (auto & func : state_->then_funcs)
func(*state_->value);
}
void set_exception(std::exception_ptr e)
@ -254,6 +282,10 @@ namespace psemek::async
std::lock_guard lock{state_->value_mutex};
if (state_->value || state_->exception) throw satisfied_promise_error{};
state_->exception = std::move(e);
std::lock_guard lock_then{state_->then_mutex};
for (auto & func : state_->then_exception_funcs)
func(state_->exception);
}
state_->value_cv.notify_all();
}
@ -315,8 +347,10 @@ namespace psemek::async
state_->value = true;
}
state_->value_cv.notify_all();
if (state_->then_func)
state_->then_func();
std::lock_guard lock{state_->then_mutex};
for (auto & func : state_->then_funcs)
func();
}
void set_exception(std::exception_ptr e)
@ -326,6 +360,10 @@ namespace psemek::async
std::lock_guard lock{state_->value_mutex};
if (state_->value || state_->exception) throw satisfied_promise_error{};
state_->exception = std::move(e);
std::lock_guard lock_then{state_->then_mutex};
for (auto & func : state_->then_exception_funcs)
func(state_->exception);
}
state_->value_cv.notify_all();
}
@ -353,6 +391,14 @@ namespace psemek::async
return p.get_future();
}
template <typename T>
future<T> make_ready_future(T const & x)
{
promise<T> p;
p.set_value(x);
return p.get_future();
}
template <typename Signature>
struct packaged_task;
@ -479,7 +525,7 @@ namespace psemek::async
auto fut = t.get_future();
std::lock_guard lock{state_->then_mutex};
state_->then_func = std::move(t);
state_->then_funcs.push_back(std::move(t));
return fut;
}
else
@ -489,7 +535,7 @@ namespace psemek::async
auto fut = t.get_future();
std::lock_guard lock{state_->then_mutex};
state_->then_func = std::move(t);
state_->then_funcs.push_back(std::move(t));
return fut;
}
}

View file

@ -0,0 +1,28 @@
#pragma once
#include <psemek/async/executor.hpp>
namespace psemek::async
{
struct synchronous_executor
: executor
{
void post(task t) override;
void post_at(clock::time_point time, task t) override;
void stop() override;
void clear() override;
void wait() override;
void wait_for(clock::duration period) override;
void wait_until(clock::time_point time) override;
std::size_t task_count() const override;
};
}

View file

@ -4,7 +4,6 @@
#include <psemek/util/thread.hpp>
#include <psemek/util/synchronyzed_queue.hpp>
#include <future>
#include <vector>
#include <string>
#include <mutex>
@ -24,12 +23,16 @@ namespace psemek::async
~threadpool() override { stop(); }
std::size_t thread_count() const { return threads_.size(); }
void post(task t) override;
void post_at(clock::time_point time, task t) override;
void stop() override;
void clear() override;
void wait() override;
void wait_for(clock::duration period) override;

View file

@ -1,7 +1,5 @@
#include <psemek/async/event_loop.hpp>
#include <algorithm>
namespace psemek::async
{
@ -20,6 +18,11 @@ namespace psemek::async
queue_.clear();
}
void event_loop::clear()
{
queue_.clear();
}
void event_loop::wait()
{
while (!queue_.empty())

View file

@ -0,0 +1,37 @@
#include <psemek/async/synchronous_executor.hpp>
#include <psemek/util/exception.hpp>
namespace psemek::async
{
void synchronous_executor::post(task t)
{
t();
}
void synchronous_executor::post_at(clock::time_point, task)
{
throw util::exception("Synchronous_executor doesn't support executor::post_at");
}
void synchronous_executor::stop()
{}
void synchronous_executor::clear()
{}
void synchronous_executor::wait()
{}
void synchronous_executor::wait_for(clock::duration)
{}
void synchronous_executor::wait_until(clock::time_point)
{}
std::size_t synchronous_executor::task_count() const
{
return 0;
}
}

View file

@ -2,19 +2,14 @@
#include <psemek/util/unused.hpp>
#include <psemek/util/to_string.hpp>
#include <psemek/util/exception.hpp>
#include <psemek/prof/profiler.hpp>
#include <psemek/log/log.hpp>
namespace psemek::async
{
namespace
{
struct stop_execution{};
}
threadpool::threadpool(std::string const & name, std::size_t thread_count)
: working_count_{0}
{
@ -24,10 +19,13 @@ namespace psemek::async
threads_.emplace_back([this, tname = std::move(tname)]() mutable
{
log::thread_registrator reg(std::move(tname));
for (bool running = true; running;)
while (true)
{
auto task = task_queue_.pop();
if (!task)
break;
{
std::lock_guard lock{working_count_mutex_};
++working_count_;
@ -35,11 +33,12 @@ namespace psemek::async
try
{
prof::profiler prof("task");
task();
}
catch (stop_execution const &)
catch (util::exception const & e)
{
running = false;
log::error() << "Unhandled exception in threadpool executor: " << e;
}
catch (std::exception const & e)
{
@ -81,11 +80,16 @@ namespace psemek::async
for (auto const & thread: threads_)
{
unused(thread);
task_queue_.push([]{ throw stop_execution{}; });
task_queue_.push(nullptr);
}
threads_.clear();
}
void threadpool::clear()
{
task_queue_.clear();
}
void threadpool::wait()
{
std::unique_lock lock{working_count_mutex_};

View file

@ -1,8 +1,6 @@
find_package(SDL2_mixer REQUIRED)
file(GLOB_RECURSE PSEMEK_AUDIO_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "include/*.hpp" "include/*.h")
file(GLOB_RECURSE PSEMEK_AUDIO_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "source/*.cpp" "source/*.c")
file(GLOB_RECURSE PSEMEK_AUDIO_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "source/*.cpp")
psemek_add_library(psemek-audio ${PSEMEK_AUDIO_HEADERS} ${PSEMEK_AUDIO_SOURCES})
target_include_directories(psemek-audio PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(psemek-audio PUBLIC psemek-sdl2 psemek-geom psemek-util psemek-log psemek-prof SDL2_mixer)
target_link_libraries(psemek-audio PUBLIC psemek-random psemek-math psemek-util psemek-log psemek-prof)

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,14 @@
#pragma once
#include <psemek/audio/stream.hpp>
#include <version>
#include <psemek/audio/stream.hpp>
#ifdef __cpp_lib_atomic_shared_ptr
#include <atomic>
#else
#include <psemek/util/mutexed.hpp>
#endif
#include <atomic>
#include <memory>
namespace psemek::audio
@ -18,12 +24,12 @@ namespace psemek::audio
stream_ptr stream() const
{
return std::atomic_load(&stream_);
return stream_.load();
}
stream_ptr stream(stream_ptr new_stream)
{
return std::atomic_exchange(&stream_, std::move(new_stream));
return stream_.exchange(std::move(new_stream));
}
stream_ptr stop()
@ -33,11 +39,15 @@ namespace psemek::audio
bool is_stopped() const
{
return stream() != nullptr;
return stream() == nullptr;
}
private:
stream_ptr stream_;
#ifdef __cpp_lib_atomic_shared_ptr
std::atomic<stream_ptr> stream_;
#else
util::mutexed<stream_ptr> stream_;
#endif
};
using channel_ptr = std::shared_ptr<channel>;

View file

@ -0,0 +1,12 @@
#pragma once
#include <psemek/audio/stream.hpp>
#include <vector>
namespace psemek::audio
{
stream_ptr concat(std::vector<stream_ptr> streams);
}

View file

@ -0,0 +1,16 @@
#pragma once
#include <psemek/audio/stream.hpp>
#include <psemek/audio/track.hpp>
#include <psemek/audio/recorder.hpp>
#include <utility>
namespace psemek::audio
{
track_ptr make_duplicator(std::shared_ptr<recorder> recorder);
track_ptr make_duplicator(stream_ptr stream);
std::pair<stream_ptr, stream_ptr> duplicate(stream_ptr stream);
}

View file

@ -1,10 +1,12 @@
#pragma once
#include <psemek/audio/stream.hpp>
#include <psemek/audio/track.hpp>
namespace psemek::audio
{
stream_ptr loop(track_ptr track, std::optional<std::size_t> count = {});
stream_ptr loop(stream_ptr stream, std::optional<std::size_t> count = {});
}

View file

@ -4,6 +4,7 @@
#include <psemek/audio/channel.hpp>
#include <memory>
#include <vector>
namespace psemek::audio
{
@ -12,10 +13,14 @@ namespace psemek::audio
: stream
{
virtual channel_ptr add(stream_ptr stream) = 0;
virtual std::size_t stream_count() const = 0;
};
using mixer_ptr = std::shared_ptr<mixer>;
mixer_ptr make_mixer();
mixer_ptr mix(std::vector<stream_ptr> const & streams);
}

View file

@ -9,14 +9,24 @@ namespace psemek::audio
constexpr int frequency = 44100;
constexpr float inv_frequency = 1.f / frequency;
inline std::int64_t seconds_to_frames(float seconds)
{
return static_cast<std::int64_t>(std::round(seconds * frequency));
}
inline std::int64_t seconds_to_samples(float seconds)
{
return static_cast<std::int64_t>(2 * std::round(seconds * frequency));
return 2 * seconds_to_frames(seconds);
}
inline float frames_to_seconds(std::int64_t frames)
{
return static_cast<float>(frames) * inv_frequency;
}
inline float samples_to_seconds(std::int64_t samples)
{
return static_cast<float>(samples) * 0.5f * inv_frequency;
return frames_to_seconds(samples / 2);
}
inline float to_db(float amplitude)

View file

@ -1,7 +1,7 @@
#pragma once
#include <psemek/audio/constants.hpp>
#include <psemek/geom/constants.hpp>
#include <psemek/math/constants.hpp>
#include <complex>
@ -17,7 +17,7 @@ namespace psemek::audio
void frequency(float f)
{
m_ = std::exp(std::complex<float>{0.f, 2.f * geom::pi * f * inv_frequency});
m_ = std::exp(std::complex<float>{0.f, 2.f * math::pi * f * inv_frequency});
}
std::complex<float> phase() const

View file

@ -26,6 +26,8 @@ namespace psemek::audio
return result_;
}
std::vector<float> grab_result();
private:
std::vector<float> result_;
int position_{0};

View file

@ -0,0 +1,10 @@
#pragma once
#include <vector>
namespace psemek::audio::detail
{
std::vector<float> white_noise(std::size_t sample_count);
}

View file

@ -1,25 +0,0 @@
#pragma once
#include <psemek/audio/stream.hpp>
#include <psemek/audio/track.hpp>
#include <psemek/audio/recorder.hpp>
#include <utility>
namespace psemek::audio
{
track_ptr make_duplicator(std::shared_ptr<recorder> recorder);
inline track_ptr make_duplicator(stream_ptr stream)
{
return make_duplicator(make_recorder(std::move(stream)));
}
inline std::pair<stream_ptr, stream_ptr> duplicate(stream_ptr stream)
{
auto track = make_duplicator(std::move(stream));
return {track->stream(), track->stream()};
}
}

View file

@ -10,58 +10,87 @@ namespace psemek::audio
struct duration
{
private:
struct samples_tag{};
struct frames_tag{};
public:
duration()
: samples_{0}
: frames_{0}
{}
duration(std::int64_t samples)
: samples_{samples}
{}
static duration from_samples(std::int64_t samples)
{
return duration(samples_tag{}, samples);
}
duration(std::size_t samples)
: samples_{samples}
{}
static duration from_frames(std::int64_t samples)
{
return duration(frames_tag{}, samples);
}
duration(float seconds)
: samples_{seconds_to_samples(seconds)}
: frames_{seconds_to_frames(seconds)}
{}
duration(duration const & other) = default;
duration(duration const & other)
: frames_{other.frames_}
{}
float seconds() const
{
return samples_to_seconds(samples_);
return frames_to_seconds(frames_);
}
std::int64_t frames() const
{
return frames_;
}
std::int64_t samples() const
{
return samples_;
return frames_ * 2;
}
duration & operator = (duration const & other)
{
frames_ = other.frames_;
return *this;
}
duration & operator += (duration const & other)
{
samples_ += other.samples_;
frames_ += other.frames_;
return *this;
}
duration & operator -= (duration const & other)
{
samples_ -= other.samples_;
frames_ -= other.frames_;
return *this;
}
friend duration operator + (duration const & d1, duration const & d2)
{
return duration{d1.samples() + d2.samples()};
return duration{d1.frames() + d2.frames()};
}
friend duration operator - (duration const & d1, duration const & d2)
{
return duration{d1.samples() - d2.samples()};
return duration{d1.frames() - d2.frames()};
}
private:
std::int64_t samples_;
std::int64_t frames_;
duration(samples_tag, std::int64_t samples)
: frames_(samples / 2)
{}
duration(frames_tag, std::int64_t frames)
: frames_(frames)
{}
};
}

View file

@ -0,0 +1,13 @@
#pragma once
#include <psemek/audio/stream.hpp>
#include <psemek/audio/duration.hpp>
namespace psemek::audio
{
// All-pass filter
// Turns x[n] into y[n] = - gain * x[n] + x[n - delay] + gain * y[n - delay]
stream_ptr all_pass(stream_ptr stream, duration delay, float gain);
}

View file

@ -0,0 +1,13 @@
#pragma once
#include <psemek/audio/stream.hpp>
#include <psemek/audio/duration.hpp>
namespace psemek::audio
{
// Comb filter
// Turns x[n] into y[n] = x[n - delay] + gain * y[n - delay]
stream_ptr echo(stream_ptr stream, duration delay, float gain);
}

View file

@ -0,0 +1,17 @@
#pragma once
#include <psemek/audio/stream.hpp>
namespace psemek::audio
{
// Turns x[n] into y[n] = a0*x[n] + a1*x[n-1]
stream_ptr feedforward_filter(stream_ptr stream, float a0, float a1);
// Turns x[n] into y[n] = a0*x[n] + b1*y[n-1]
stream_ptr feedback_filter(stream_ptr stream, float a0, float b1);
stream_ptr low_pass_filter(stream_ptr stream);
stream_ptr high_pass_filter(stream_ptr stream);
}

View file

@ -20,7 +20,7 @@ namespace psemek::audio
float smoothness() const;
float smoothness(float value);
void apply(float * data, std::size_t sample_count);
void apply(util::span<float> samples);
private:
std::atomic<float> gain_[2];

View file

@ -1,30 +1,20 @@
#pragma once
#include <psemek/audio/stream.hpp>
#include <psemek/audio/track.hpp>
#include <psemek/audio/channel.hpp>
#include <psemek/util/pimpl.hpp>
#include <psemek/util/span.hpp>
#include <psemek/io/stream.hpp>
#include <memory>
#include <string_view>
#include <vector>
namespace psemek::audio
{
struct engine
{
engine();
~engine();
virtual channel_ptr output() = 0;
channel_ptr output();
private:
psemek_declare_pimpl
virtual ~engine() {}
};
// Implemented by platform backend
std::unique_ptr<engine> make_engine();
}

View file

@ -0,0 +1,13 @@
#pragma once
#include <cmath>
namespace psemek::audio
{
inline float midi_frequency(float note)
{
return 440.f * std::pow(2.f, (note - 69.f) / 12.f);
}
}

View file

@ -1,6 +1,9 @@
#pragma once
#include <psemek/audio/stream.hpp>
#include <psemek/audio/track.hpp>
#include <psemek/audio/channel.hpp>
#include <psemek/audio/duration.hpp>
#include <psemek/util/span.hpp>
#include <vector>
@ -10,17 +13,21 @@ namespace psemek::audio
struct recorder
{
virtual std::optional<std::size_t> length() const = 0;
virtual channel_ptr channel() = 0;
virtual std::size_t request(std::size_t samples) = 0;
virtual util::span<float const> buffer() const = 0;
virtual std::vector<float> grab_buffer() = 0;
virtual ~recorder() {}
};
std::shared_ptr<recorder> make_recorder();
std::shared_ptr<recorder> make_recorder(stream_ptr stream);
std::shared_ptr<recorder> make_recorder(std::vector<float> samples);
std::shared_ptr<recorder> make_recorder(util::span<float const> samples);
std::shared_ptr<track> record(stream_ptr stream);
std::shared_ptr<track> record(stream_ptr stream, duration duration);
}

View file

@ -1,5 +1,7 @@
#pragma once
#include <psemek/util/span.hpp>
#include <cstddef>
#include <memory>
#include <optional>
@ -14,7 +16,7 @@ namespace psemek::audio
// Return value less than sample count means end of stream
// Must be called from mixing thread
virtual std::size_t read(float * data, std::size_t sample_count) = 0;
virtual std::size_t read(util::span<float> samples) = 0;
// The number of samples already played from this stream
virtual std::size_t played() const = 0;

View file

@ -2,9 +2,11 @@
#include <psemek/audio/stream.hpp>
#include <psemek/util/span.hpp>
#include <psemek/util/blob.hpp>
#include <optional>
#include <vector>
#include <filesystem>
namespace psemek::audio
{
@ -20,12 +22,16 @@ namespace psemek::audio
using track_ptr = std::shared_ptr<track>;
track_ptr load_raw(util::span<float const> data);
track_ptr load_raw(util::blob data);
track_ptr load_raw(std::vector<float> data);
track_ptr load_wav(util::span<char const> data);
track_ptr load_wav(std::vector<char> const & data);
track_ptr load_mp3(util::span<char const> data);
track_ptr load_mp3(util::blob data);
track_ptr load_mp3(std::vector<char> data);
track_ptr load_lazy(std::filesystem::path const & path);
}

Some files were not shown because too many files have changed in this diff Show more