├── .gitignore ├── boid.png ├── boid2.png ├── mouse.png ├── package.json ├── main.cpp ├── alpp ├── alpp.hpp ├── defaults.hpp ├── types.hpp ├── mixin.hpp ├── optional.hpp ├── image.hpp └── display.hpp ├── README.md ├── simulation ├── types.hpp ├── utils.hpp └── behaviours.hpp ├── LICENSE ├── simulation.cpp └── simulation.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | -------------------------------------------------------------------------------- /boid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoopPerfect/boids/HEAD/boid.png -------------------------------------------------------------------------------- /boid2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoopPerfect/boids/HEAD/boid2.png -------------------------------------------------------------------------------- /mouse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LoopPerfect/boids/HEAD/mouse.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "includePaths": [ 3 | ".", 4 | "/usr/lib/x86_64-linux-gnu/", 5 | "/usr/local/include/"], 6 | "libs": { 7 | "linux": ["liballegro.so", "liballegro_image.so"], 8 | "osx": ["liballegro.dylib"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "simulation.hpp" 2 | 3 | #ifdef JYTTER 4 | #include 5 | 6 | void simulation() { 7 | jyt.async(run); 8 | } 9 | 10 | #else 11 | 12 | int main(int argc, char** argv) { 13 | run(); 14 | return 0; 15 | } 16 | 17 | #endif -------------------------------------------------------------------------------- /alpp/alpp.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ALPP_ALPP_HPP 2 | #define ALPP_ALPP_HPP 3 | 4 | #include "types.hpp" 5 | #include "display.hpp" 6 | #include "image.hpp" 7 | 8 | namespace alpp { 9 | 10 | auto allegroInit() { 11 | al_init(); 12 | al_init_image_addon(); 13 | al_install_mouse(); 14 | return 1; 15 | } 16 | 17 | } 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /alpp/defaults.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ALPP_DEFAULTS_HPP 2 | #define ALPP_DEFAULTS_HPP 3 | 4 | #include "mixin.hpp" 5 | #include "types.hpp" 6 | 7 | namespace alpp { 8 | 9 | static const auto bitmap_defaults = mix ( 10 | Position{0,0}, 11 | Region{0,0,1,1}, 12 | Scale{1.0,1.0}, 13 | Center{0,0}, 14 | Rad{0}, 15 | Color{0xFF, 0xFF, 0xFF} 16 | ); 17 | 18 | using BitmapData = decltype(bitmap_defaults); 19 | 20 | 21 | 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C++ Boids simulation - Jyt - hot-module-reload playground 2 | 3 | This is a flocking demo based on Allegro. 4 | 5 | # Run 6 | 7 | with Clang: 8 | ``` 9 | clang++ -std=c++14 -I. -lallegro -lallegro_image -lallegro_main main.cpp -o main && ./main 10 | ``` 11 | or with Jyt: 12 | ``` 13 | jyt -x main.cpp --hot -i 14 | ``` 15 | 16 | # Requirements 17 | 18 | - C++-14 compliant compiler 19 | - [liballegro](http://allegro.cc) 20 | - [Jyt (optional)](http://jyt.io) 21 | -------------------------------------------------------------------------------- /simulation/types.hpp: -------------------------------------------------------------------------------- 1 | #ifndef TYPES_HPP 2 | #define TYPES_HPP 3 | 4 | 5 | using alpp::mix; 6 | using alpp::merge; 7 | 8 | struct BoidPos { 9 | double x; 10 | double y; 11 | }; 12 | 13 | struct BoidSpeed { 14 | double vx; 15 | double vy; 16 | }; 17 | 18 | struct BoidAcceleration { 19 | double ax; 20 | double ay; 21 | }; 22 | 23 | 24 | struct BoidID { 25 | size_t id; 26 | }; 27 | 28 | using BoidState = decltype(mix( BoidPos{0,0}, BoidSpeed{0,0}, BoidAcceleration{0,0}, BoidID{0} ) ); 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /alpp/types.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ALPP_TYPES_HPP 2 | #define ALPP_TYPES_HPP 3 | 4 | namespace alpp { 5 | 6 | struct Color { 7 | int r; 8 | int g; 9 | int b; 10 | }; 11 | 12 | struct Position { 13 | int px; 14 | int py; 15 | }; 16 | 17 | struct Region { 18 | int rx; 19 | int ry; 20 | int rw; 21 | int rh; 22 | }; 23 | 24 | struct Scale { 25 | float sx; 26 | float sy; 27 | }; 28 | 29 | 30 | struct Center { 31 | float cx; 32 | float cy; 33 | }; 34 | 35 | struct Rad { 36 | float rad; 37 | }; 38 | 39 | } 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /alpp/mixin.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ALPP_MIXIN_HPP 2 | #define ALPP_MIXIN_HPP 3 | 4 | namespace alpp { 5 | 6 | template 7 | void evalAll(X...) {}; 8 | 9 | template 10 | struct Mixin : Base... { 11 | Mixin(Base...B) 12 | :Base{B}... 13 | {} 14 | 15 | Mixin()=default; 16 | Mixin(Mixin const&)=default; 17 | 18 | template 19 | auto const& merge(B const&...b) { 20 | evalAll( set(b)... ); 21 | return *this; 22 | } 23 | 24 | private: 25 | template 26 | auto set(S const& s) -> decltype( (S*)this , 1 ) { 27 | ((S&)*this) = s; 28 | return true; 29 | } 30 | }; 31 | 32 | template 33 | auto merge(L l, R const&...r) { 34 | return l.merge(r...); 35 | } 36 | 37 | template 38 | auto mix(X const&...x) { 39 | return Mixin{x...}; 40 | } 41 | 42 | } 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /alpp/optional.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ALPP_OPTIONAL_HPP 2 | #define ALPP_OPTIONAL_HPP 3 | 4 | namespace alpp { 5 | 6 | template 7 | struct Optional { 8 | T data; 9 | bool empty; 10 | 11 | Optional() 12 | :empty{true} 13 | {} 14 | 15 | Optional(T data, bool hasValue=true) 16 | : data{data} 17 | , empty{!hasValue} 18 | {} 19 | 20 | 21 | T const& get() const { 22 | return data; 23 | } 24 | 25 | template 26 | auto then(F f) const { 27 | if(empty) { 28 | return; 29 | } 30 | f(data); 31 | } 32 | 33 | operator bool()const { return !empty; } 34 | 35 | }; 36 | 37 | template<> 38 | struct Optional { 39 | template 40 | auto then(F) const 41 | {} 42 | 43 | template 44 | operator Optional () const { 45 | return {}; 46 | } 47 | }; 48 | 49 | template 50 | auto optional(T data) -> Optional { 51 | return {data}; 52 | } 53 | 54 | auto optional() -> Optional { 55 | return {}; 56 | } 57 | 58 | 59 | } 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 LoopPerfect 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /alpp/image.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ALPP_IMAGE_HPP 2 | #define ALPP_IMAGE_HPP 3 | 4 | #include "defaults.hpp" 5 | #include 6 | #include 7 | 8 | namespace alpp { 9 | 10 | template 11 | void al_draw(I& i, D defaults, O...options) { 12 | auto o = merge(defaults, options...); 13 | al_draw_tinted_scaled_rotated_bitmap_region( 14 | i.img.get(), 15 | o.rx * i.width, o.ry * i.height, o.rw * i.width , o.rh * i.height, 16 | al_map_rgb(o.r, o.g, o.b), 17 | o.cx * i.width, o.cy * i.height, 18 | o.px, o.py, 19 | o.sx, o.sy, 20 | o.rad, 21 | 0 22 | ); 23 | } 24 | 25 | struct Image { 26 | Image()=default; 27 | ~Image()=default; 28 | 29 | Image(const char* path) { 30 | auto image = al_load_bitmap(path); 31 | img.reset( image , al_destroy_bitmap ); 32 | width = al_get_bitmap_width(img.get()); 33 | height = al_get_bitmap_height(img.get()); 34 | } 35 | 36 | template 37 | void draw(X const&...x) { 38 | if(img.get()) 39 | al_draw(*this, bitmap_defaults, x...); 40 | } 41 | 42 | ALLEGRO_BITMAP* getImage() { 43 | return img.get(); 44 | } 45 | 46 | std::shared_ptr img; 47 | size_t width; 48 | size_t height; 49 | }; 50 | 51 | 52 | 53 | } 54 | 55 | #endif 56 | 57 | -------------------------------------------------------------------------------- /simulation.cpp: -------------------------------------------------------------------------------- 1 | #include "simulation.hpp" 2 | 3 | drawBoid = []( 4 | auto const& boid, 5 | auto const& dispaly 6 | ) { 7 | auto image = (boid.id%2) ? boidImg : boid2Img; 8 | 9 | image.draw( 10 | alpp::Position{ 11 | (int)((boid.x+1)/2 * display.width ), 12 | (int)((boid.y+1)/2 * display.height ) 13 | }, 14 | alpp::Center{0.5, 0.5}, 15 | alpp::Scale{ 16 | 0.05f * display.width/(float)image.width, 17 | 0.05f * display.height/(float)image.height 18 | }, 19 | alpp::Color{ 255, 255, 255}, 20 | alpp::Rad{ (float)std::atan2( boid.vy, boid.vx ) } 21 | ); 22 | }; 23 | 24 | process = [](auto const& oldWorld, auto const& display, auto dt){ 25 | World newWorld = oldWorld; 26 | auto interactWithMouse = makeAttractor(mouseX, mouseY, display, -3000, 1); 27 | auto attractToCenter = makeAttractor(400, 400, display, 10, 2); 28 | auto follow = seekSibling(world, 20); 29 | auto repulse = makeRepulser(world, 0.1); 30 | drawMouse(display, mouseX, mouseY); 31 | for(auto& boid: newWorld) { 32 | boid= limitSpeed(boid, 0.5); 33 | boid= clipToWorld(boid); 34 | boid= follow(boid, dt); 35 | boid= interactWithMouse(boid, dt); 36 | boid= attractToCenter(boid, dt); 37 | boid= limitForces(boid, 1); 38 | boid= speedUpBoid(boid, dt); 39 | boid= moveBoid(boid, dt); 40 | } 41 | 42 | for(auto& boid: newWorld) { 43 | drawBoid(boid, display); 44 | } 45 | 46 | return newWorld; 47 | }; 48 | 49 | -------------------------------------------------------------------------------- /alpp/display.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ALPP_DISPLAY_HPP 2 | #define ALPP_DISPLAY_HPP 3 | 4 | #include "optional.hpp" 5 | #include 6 | #include 7 | namespace alpp { 8 | 9 | struct Display { 10 | Display() 11 | : width{0} 12 | , height{0} 13 | {} 14 | 15 | ~Display()=default; 16 | 17 | Display(const char* title, size_t width, size_t height) 18 | : width{width} 19 | , height{height} { 20 | display.reset( al_create_display(width, height), al_destroy_display ); 21 | queue.reset( al_create_event_queue(), al_destroy_event_queue ); 22 | al_set_window_title(display.get(), title); 23 | 24 | al_register_event_source( 25 | queue.get(), 26 | al_get_display_event_source(display.get()) 27 | ); 28 | 29 | al_register_event_source( 30 | queue.get(), 31 | al_get_mouse_event_source() 32 | ); 33 | } 34 | 35 | Optional getEvent() { 36 | ALLEGRO_EVENT e; 37 | bool hasValue = al_get_next_event(queue.get(), &e); 38 | return {e, hasValue}; 39 | } 40 | 41 | void clear( Color c = Color{0, 0, 0} ) { 42 | al_clear_to_color(al_map_rgb(c.r, c.g, c.b)); 43 | } 44 | 45 | void draw() { 46 | al_flip_display(); 47 | } 48 | 49 | size_t getWidth()const { 50 | return width; 51 | } 52 | 53 | size_t getHeight()const { 54 | return height; 55 | } 56 | 57 | bool setCursorVisible(const bool b) { 58 | if (b) { 59 | return al_show_mouse_cursor(display.get()); 60 | } else { 61 | return al_hide_mouse_cursor(display.get()); 62 | } 63 | } 64 | 65 | std::shared_ptr display; 66 | std::shared_ptr queue; 67 | size_t width; 68 | size_t height; 69 | }; 70 | 71 | 72 | } 73 | 74 | 75 | #endif 76 | -------------------------------------------------------------------------------- /simulation.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SIMULATION_HPP 2 | #define SIMULATION_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | using World = std::vector; 19 | 20 | static World world; 21 | 22 | static bool s = alpp::allegroInit(); 23 | static size_t mouseX = -1000; 24 | static size_t mouseY = -1000; 25 | static bool stop = false; 26 | static alpp::Display display; 27 | static double speed = 5; 28 | static auto boidImg = alpp::Image("boid.png"); 29 | static auto boid2Img = alpp::Image("boid2.png"); 30 | static auto mouseImg = alpp::Image("mouse.png"); 31 | 32 | /// \brief draws the mouse 33 | static void drawMouse(alpp::Display const& display, int mouseX, int mouseY) { 34 | auto image = mouseImg; 35 | 36 | image.draw( 37 | alpp::Position{ 38 | mouseX, 39 | mouseY 40 | }, 41 | alpp::Center{0.5, 0.5}, 42 | alpp::Color{ 255, 255, 255} 43 | ); 44 | } 45 | 46 | std::function< void(BoidState, alpp::Display const&) > drawBoid; 47 | std::function< World ( World const&, alpp::Display const&, double ) > process; 48 | std::function< void(ALLEGRO_EVENT) > eventHandler; 49 | 50 | void spawnBoid() { 51 | world.push_back(makeRandomBoid()); 52 | } 53 | 54 | void run() { 55 | #include "simulation.cpp" 56 | 57 | eventHandler = makeSimpleEventHandler(mouseX, mouseY, stop); 58 | 59 | for (int i = 0; i < 10; ++i) 60 | spawnBoid(); 61 | 62 | display = alpp::Display("LoopPerfect.com - Hot Reload Demo", 800, 800); 63 | display.setCursorVisible(false); 64 | 65 | std::srand(std::time(0)); 66 | 67 | stop=false; 68 | double dt=0.016; 69 | while(!stop) { 70 | using namespace std::chrono; 71 | 72 | auto start = std::chrono::system_clock::now(); 73 | size_t initialSize = world.size(); 74 | while(1) { 75 | auto e = display.getEvent(); 76 | e.then(eventHandler); 77 | if(!e) break; 78 | } 79 | 80 | display.clear(alpp::Color{100, 149, 237}); 81 | auto newWorld = process(world, display, dt*speed); 82 | if(initialSize == world.size() ){ 83 | world = newWorld; 84 | } 85 | display.draw(); 86 | 87 | auto end = system_clock::now(); 88 | 89 | long deltaT = duration_cast(end-start).count()/1000; 90 | dt = 0.0016*speed; 91 | 92 | std::this_thread::sleep_for( 93 | milliseconds( clip(16-deltaT,0l,16l) ) 94 | ); 95 | } 96 | display = alpp::Display(); 97 | } 98 | 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /simulation/utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_HPP 2 | #define UTILS_HPP 3 | 4 | 5 | #include 6 | #include 7 | #include 8 | #include "types.hpp" 9 | 10 | auto makeRandomPoint(float length = 1, BoidPos O = {0.0,0.0} ) { 11 | return [=](...){ 12 | auto r = length * std::rand() / (float)RAND_MAX; 13 | auto phi = (std::rand() / (float)RAND_MAX) * M_PI * 2 ; 14 | return BoidPos{ 15 | sin(phi) * r + O.x, 16 | cos(phi) * r + O.y 17 | }; 18 | }; 19 | } 20 | 21 | static size_t boidId = 0; 22 | auto makeRandomBoid(double length = 1, double velocity = 1, BoidPos O = {0.0,0.0}) { 23 | auto p = makeRandomPoint(length,O)(); 24 | auto v = makeRandomPoint(velocity)(); 25 | return mix( 26 | BoidPos{ 27 | p.x, 28 | p.y, 29 | }, 30 | 31 | BoidSpeed{ 32 | v.x, 33 | v.y, 34 | }, 35 | 36 | BoidAcceleration { 37 | 0.0, 38 | 0.0, 39 | }, 40 | 41 | BoidID{boidId++} 42 | ); 43 | } 44 | 45 | auto clip= [](auto x, auto min, auto max) { 46 | return std::min( std::max(x,min), max); 47 | }; 48 | 49 | auto compose() { 50 | return [=](auto x) { 51 | return x; 52 | }; 53 | } 54 | 55 | template 56 | auto compose(F f, Fs...fs) { 57 | return [=](auto x) { 58 | return compose(fs...)( f(x) ); 59 | }; 60 | } 61 | 62 | template 63 | auto map(F&&f) { 64 | return [f](auto const& v) { 65 | std::vector next(v.size()); 66 | std::transform(v.begin(), v.end(), next.begin(), f); 67 | return next; 68 | }; 69 | } 70 | 71 | template 72 | struct Enumerated { 73 | size_t index; 74 | T value; 75 | }; 76 | 77 | 78 | struct Range { 79 | size_t b; 80 | size_t e; 81 | 82 | auto begin() const { 83 | return Range{0,e}; 84 | } 85 | 86 | size_t size() const { return e-b; } 87 | 88 | auto operator[](size_t i) const { 89 | return b+i; 90 | } 91 | 92 | auto operator++() { 93 | ++b; 94 | return *this; 95 | } 96 | 97 | auto operator*() const { 98 | return b; 99 | } 100 | 101 | auto operator!=(Range end) const { 102 | return (b != end.e); 103 | } 104 | 105 | auto end() const { 106 | return Range{e,e}; 107 | } 108 | }; 109 | 110 | auto enumerate = [](size_t i=0) { 111 | return map([i](auto const& x) mutable { 112 | return Enumerated{ 113 | i++, x 114 | }; 115 | }); 116 | }; 117 | 118 | template 119 | auto lin(T x, T a, T b) { 120 | return a*(1-x) + b*x; 121 | } 122 | 123 | auto distance = [](BoidPos a , BoidPos b) { 124 | auto dx = a.x-b.x; 125 | auto dy = a.y-b.y; 126 | return sqrt(dx*dx+dy*dy); 127 | }; 128 | 129 | auto length = [](auto x, auto y) { 130 | return sqrt(x*x+y*y); 131 | }; 132 | 133 | 134 | template 135 | size_t bucketHash( 136 | P const p, 137 | size_t buckets, 138 | double min=-1.0, 139 | double max=1.0 ) { 140 | size_t x = (size_t) (buckets-1) * ((p.x-min) / max); 141 | size_t y = (size_t) (buckets-1) * ((p.x-min) / max); 142 | return x + y*buckets; 143 | } 144 | 145 | 146 | #endif 147 | -------------------------------------------------------------------------------- /simulation/behaviours.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BEHAVIOURS_HPP 2 | #define BEHAVIOURS_HPP 3 | 4 | #include "types.hpp" 5 | #include "utils.hpp" 6 | 7 | auto moveBoid = [](auto p, double dt) { 8 | return merge(p, BoidPos{ 9 | p.x + p.vx * dt, 10 | p.y + p.vy * dt 11 | }); 12 | }; 13 | 14 | auto speedUpBoid = [](auto p, double dt) { 15 | return merge(p, BoidSpeed{ 16 | p.vx + p.ax * dt, 17 | p.vy + p.ay * dt 18 | }); 19 | }; 20 | 21 | auto deaccelerateBoid = [](auto p, double dt) { 22 | return merge(p, BoidAcceleration{ 23 | p.ax / dt, 24 | p.ay / dt 25 | }); 26 | }; 27 | 28 | auto limitForces = [](auto p, double max) { 29 | double l = sqrt(p.ax*p.ax+p.ay*p.ay); 30 | if(l0)?+1:-1); 67 | repulsionY += 1/(dy*dy) * ((dy>0)?+1:-1); 68 | } 69 | 70 | return merge(boid, BoidAcceleration{ 71 | boid.ax + clip( repulsionX * factor, -2.0, 2.0), 72 | boid.ay + clip( repulsionY * factor, -2.0, 2.0) 73 | }); 74 | }; 75 | }; 76 | 77 | 78 | auto cancelForces = [](auto p) { 79 | return clipForces(p,0); 80 | }; 81 | 82 | auto clipToWorld = [](auto p) { 83 | return merge(p, BoidPos{ 84 | clip(p.x, -0.9, +0.9), 85 | clip(p.y, -0.9, +0.9) 86 | }); 87 | }; 88 | 89 | auto cyclicWorld = [](auto p) { 90 | return merge(p, BoidPos{ 91 | fmod(p.x+1.0,2.0)-1.0, 92 | fmod(p.y+1.0,2.0)-1.0, 93 | }); 94 | }; 95 | 96 | auto makeSimpleBoidDrawer = [](auto path, auto const& display) { 97 | return [image = alpp::Image(path), &display](auto p) mutable { 98 | image.draw( 99 | alpp::Position{ 100 | (int)((p.x+1)/2 * display.width ), 101 | (int)((p.y+1)/2 * display.height ) 102 | }, 103 | alpp::Center{0.5, 0.5}, 104 | alpp::Scale{ 105 | 0.05f * display.width/(float)image.width, 106 | 0.05f * display.height/(float)image.height 107 | }, 108 | alpp::Color{255, 255, 255}, 109 | alpp::Rad{ (float)std::atan2( p.vy, p.vx ) } 110 | ); 111 | return p; 112 | }; 113 | }; 114 | 115 | 116 | auto seek = [](auto const& b, double factor=0.03) { 117 | return [&b, factor](auto const& p) { 118 | auto dx = b.x-p.x; 119 | auto dy = b.y-p.y; 120 | auto l = sqrt(dx*dx + dy*dy); 121 | if(l<0.01) return p; 122 | return merge(p, BoidAcceleration{ 123 | p.ax + dx/l * factor, 124 | p.ay + dy/l * factor 125 | }); 126 | }; 127 | }; 128 | 129 | auto freeze = [](auto p) { 130 | return p; 131 | }; 132 | 133 | auto seekSibling = [](auto const& world, auto const& factor) { 134 | return [&world, factor](auto p, double dt) { 135 | return seek( world[ (p.id+1) % world.size() ], dt * factor)(p); 136 | }; 137 | }; 138 | 139 | auto makeSimpleEventHandler = [](auto& mouseX, auto& mouseY, auto& stop){ 140 | return [&](auto ev) { 141 | if(ev.type == ALLEGRO_EVENT_MOUSE_AXES ){ 142 | mouseX = ev.mouse.x; 143 | mouseY = ev.mouse.y; 144 | } else if (ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) { 145 | stop=true; 146 | } 147 | }; 148 | }; 149 | 150 | 151 | auto makeAttractor = [](size_t const& mouseX, size_t const& mouseY, auto const& display, double const factor, double const radius) { 152 | return [&, factor, radius]( auto p, double dt ) { 153 | auto x = (mouseX/(float)display.width - 0.5) * 2.0; 154 | auto y = (mouseY/(float)display.height - 0.5) * 2.0; 155 | auto dx = x - p.x; 156 | auto dy = y - p.y ; 157 | auto l = length(dx,dy); 158 | if(radius