From b5868dfdec344ef686ef0ad5ddcb8bda0ab075bd Mon Sep 17 00:00:00 2001 From: dvdrw Date: Thu, 21 Nov 2024 22:22:14 +0100 Subject: [PATCH] init: initial commit --- .gitignore | 3 + Makefile | 30 +++++++++ include/screen.hpp | 51 +++++++++++++++ include/swarm.hpp | 111 ++++++++++++++++++++++++++++++++ include/vec.hpp | 153 +++++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 84 +++++++++++++++++++++++++ src/screen.cpp | 34 ++++++++++ src/swarm.cpp | 1 + 8 files changed, 467 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 include/screen.hpp create mode 100644 include/swarm.hpp create mode 100644 include/vec.hpp create mode 100644 src/main.cpp create mode 100644 src/screen.cpp create mode 100644 src/swarm.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1af29d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.clangd +*.o +bin/swarm diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3b5ea7b --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +SHELL = /bin/bash + +SRC_DIR = ./src +INC_DIR = ./include +OBJ_DIR = ./obj + +HEADERS = $(wildcard $(INC_DIR)/*.hpp) +SRC_FILES = $(wildcard $(SRC_DIR)/*.cpp) +OBJ_FILES = $(addprefix $(OBJ_DIR)/,$(notdir $(SRC_FILES:.cpp=.o))) + +CXXFLAGS += -Wall -std=c++20 -O2 + +all: bin/swarm + +$(OBJ_DIR)/%.o: src/%.cpp + $(CXX) -I $(INC_DIR) $(CXXFLAGS) -c -o $@ $^ + +bin/swarm: $(OBJ_FILES) + @mkdir -p $(OBJ_DIR) + @mkdir -p bin + $(CXX) -I $(INC_DIR) $(CXXFLAGS) -o $@ $^ + +.PHONY: debug +debug: CXXFLAGS += -O0 -g +debug: clean bin/swarm + +.PHONY: clean +clean: + $(RM) obj/*.o + $(RM) bin/swarm diff --git a/include/screen.hpp b/include/screen.hpp new file mode 100644 index 0000000..cc6456e --- /dev/null +++ b/include/screen.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include + +#include +#include + +struct Screen { + char *buf; + std::size_t w, h; + + float dx = 0, dy = 0; + float sx = 1, sy = 1; + + void draw(); + void clear(); + + char &at(std::size_t n); + char &at(std::size_t x, std::size_t y); + + void resize(std::size_t n); + void resize(std::size_t x, std::size_t y); + + Screen(std::size_t n) : buf(nullptr) { resize(n); } + Screen(std::size_t x, std::size_t y) : buf(nullptr) { resize(x, y); } + ~Screen() { delete[] buf; } + +private: + char _dummy; + + static inline void gotoxy(int x, int y) + { + std::printf("\033[%d;%dH", y, x); + } +}; + +static termios old, current; +static inline void enter_noncanonical_mode(void) +{ + tcgetattr(STDIN_FILENO, &old); + current = old; + current.c_lflag &= ~ICANON; /* disable buffered i/o */ + current.c_lflag &= ~ECHO; /* set no echo mode */ + current.c_cc[VMIN] = 0;/* no wait */ + current.c_cc[VTIME] = 0;/* no wait */ + + tcsetattr(STDIN_FILENO, TCSANOW, ¤t); +} +static inline void enter_canonical_mode(void){ tcsetattr(0, TCSANOW, &old); } diff --git a/include/swarm.hpp b/include/swarm.hpp new file mode 100644 index 0000000..5e2ba00 --- /dev/null +++ b/include/swarm.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include + +#include +#include +#include +#include + +template +struct Agent { + using F = std::function; + + virtual void move(float dt) = 0; + virtual void step(float dt) = 0; + + virtual std::pair best() const = 0; + + Agent(F f) : f(f) {} +protected: + F f; +}; + +template +struct Particle : public Agent> { + std::pair> best() const override { return {pb, pb_pos}; }; + + void move(float dt=1) override { position = position + velocity * dt; } + + void step(float dt=1) override { + velocity = kviscosity * velocity + + knostalgia * (pb_pos - position) + + kpeer_pressure * (peer.best().second - position); + + float y = f(position); + if(y < pb) { + pb = y; + pb_pos = position; + } + }; + + float kviscosity = 0.9f; + float knostalgia = 2.5f; + float kpeer_pressure = 0.5f; + + Particle(Agent>::F f, const Agent>& peer, + vec position, vec velocity) + : Agent>(f), position(position), velocity(velocity), + pb_pos(position), peer(peer) { + pb = f(position); + } + + const vec &get_position() const { return position; }; + +private: + vec position; + vec velocity; + + vec pb_pos; + float pb; + + const Agent>& peer; +}; + +template +struct Swarm : public Agent> { + std::pair> best() const override { return {best_val, best_pos}; } + + void move(float dt=1) override { + for(auto &p : particles) + p.move(dt); + } + + void step(float dt=1) override { + if(particles.empty()) return; + + // find best before step() on each particle + // otherwise, if this were in best(), each p.step() would update it + const Particle *b = &particles[0]; + for(const auto &p : particles) { + if(b->best().first > p.best().first) b = &p; + } + + best_val = b->best().first; + best_pos = b->best().second; + + for(auto &p : particles) + p.step(dt); + } + + void add_particle(std::size_t n=1) { + for(std::size_t i=0; i <= n; ++i) + particles.push_back( + Particle(this->_f, *this, vec(0), vec(10)) + ); + } + + void add_particle(const vec &pos, const vec &vel) { + particles.push_back( + Particle(this->f, *this, pos, vel) + ); + } + + const std::vector>& get_particles() { return particles; }; + + Swarm(Agent>::F f) : Agent>(f) {} +private: + vec best_pos; + float best_val; + std::vector> particles; +}; diff --git a/include/vec.hpp b/include/vec.hpp new file mode 100644 index 0000000..4dbdc00 --- /dev/null +++ b/include/vec.hpp @@ -0,0 +1,153 @@ +#pragma once + +#include +#include + +#define do_op(o, r, i) \ + inline void operator o##= (const r & rhs) { for(unsigned n=0; n<(i); n++) c[n] o##= rhs.c[n]; } \ + inline void operator o##= (T d) { for(unsigned n=0; n<(i); n++) c[n] o##= d; } \ + inline r operator o (const r & rhs) const { r tmp(*this); tmp o##= rhs; return tmp; } \ + inline r operator o (T d) const { r tmp(*this); tmp o##= d; return tmp; } + +#define do_ops(c, i) \ + do_op(*, c, i); \ + do_op(/, c, i); \ + do_op(+, c, i); \ + do_op(-, c, i); + +template +struct vec { + T c[n]; +}; + +template +vec operator*(const T lhs, const vec& rhs) +{ return rhs * lhs; } + +template +struct vec<2, T> { + union { + T c[2]; + struct { + T x, y; + }; + }; + + vec(T x, T y) : c{x,y} {} + vec(T f) : vec(f,f) {} + vec() : vec(0,0) {} + + do_ops(vec, 2); + + inline vec<2,T> operator-(void) const { return {-x, -y}; } + + inline T operator^(const vec<2,T>& rhs) const + { return x * rhs.x + y * rhs.y; } + + /* Return normalised (unit) vector. */ + inline vec<2,T> norm() const + { return *this / len(); } + + inline T len() const + { return sqrtf(x*x + y*y); } + + inline T lensquared() const + { return x*x + y*y; } + + /* Return vector reflected around `n'. Expects `this' and `n' to be unit. */ + inline vec<2,T> ref(const vec<2,T>& n) const + { return n * ((*this ^ n) * 2) - *this;} + + inline vec<2,T> refract(const vec<2,T>& n, T ior_ratio) const + { + return n * sqrtf(1 - powf(ior_ratio, 2) * (1 - powf(*this ^ n, 2))) + (*this - n * (*this ^ n)) * ior_ratio; + } + + inline vec<2,T> refract(const vec<2,T>& n, T ior_incident, T ior_refract) const + { return refract(n, ior_incident / ior_refract); } + + inline vec<2,T> clamp(const vec<2,T>& from, const vec<2,T>& to) const + { return { fmin(fmax(x, from.x), to.x), + fmin(fmax(y, from.y), to.y) }; } + + inline vec<2,T> exp() const + { return { ::exp(x), ::exp(y) }; } + + inline vec<2,T> htan(T exposure = 0) const + { + return { powf((tanh(x + exposure) + 1) / 2, 2.2), powf((tanh(y + exposure) + 1) / 2, 2.2) }; + return { tanh(x + exposure), tanh(y + exposure) }; + } +}; + +template +struct vec<3, T> { + union { + T c[3]; + struct { + T x, y, z; + }; + }; + + vec(T x, T y, T z) : c{x,y,z} {} + vec(T f) : vec(f,f,f) {} + vec() : vec(0,0,0) {} + + do_ops(vec, 3); + + inline vec<3,T> operator-(void) const { return {-x, -y, -z}; } + + inline T operator^(const vec& rhs) const + { return x * rhs.x + y * rhs.y + z * rhs.z; } + + /* Return normalised (unit) vector. */ + inline vec<3,T> norm() const + { return *this / len(); } + + inline T len() const + { return sqrtf(x*x + y*y + z*z); } + + inline T lensquared() const + { return x*x + y*y + z*z; } + + /* Return vec<3,T>tor reflected around `n'. Expects `this' and `n' to be unit. */ + inline vec<3,T> ref(const vec<3,T>& n) const + { return n * ((*this ^ n) * 2) - *this;} + + /* Cross product. */ + inline vec<3,T> cross(const vec<3,T>& b) const + { return { y*b.z - z*b.y, + x*b.z - z*b.x, + x*b.y - y*b.x }; } + + inline vec<3,T> refract(const vec<3,T>& n, T ior_ratio) const + { + return n * sqrtf(1 - powf(ior_ratio, 2) * (1 - powf(*this ^ n, 2))) + (*this - n * (*this ^ n)) * ior_ratio; + } + + inline vec<3,T> refract(const vec<3,T>& n, T ior_incident, T ior_refract) const + { return refract(n, ior_incident / ior_refract); } + + inline vec<3,T> clamp(const vec<3,T>& from, const vec<3,T>& to) const + { return { fmin(fmax(x, from.x), to.x), + fmin(fmax(y, from.y), to.y), + fmin(fmax(z, from.z), to.z) }; } + + inline vec<3,T> exp() const + { return { ::exp(x), ::exp(y), ::exp(z) }; } + + inline vec<3,T> htan(T exposure = 0) const + { + return { powf((tanh(x + exposure) + 1) / 2, 2.2), powf((tanh(y + exposure) + 1) / 2, 2.2), powf((tanh(z + exposure) + 1) / 2, 2.2) }; + return { tanh(x + exposure), tanh(y + exposure), tanh(z + exposure) }; + } +}; + +#undef do_ops +#undef do_op + +#define V3_FMT "v3(%.2f, %.2f, %.2f)" +#define V3_ARG(v) (v).x, (v).y, (v).z + +#define V2_FMT "v2(%.2f, %.2f)" +#define V2_ARG(v) (v).x, (v).y diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..f01eb45 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,84 @@ +#include "screen.hpp" +#include "vec.hpp" +#include +#include + +#define FPS 60 +#define DT (1.0 / FPS) + +float f(vec<2> x) { + return (x.x - 49)*(x.x - 49) + (x.y - 13)*(x.y - 13); +} + +int +main(int argc, char **argv) { + int iter_count; + if(argc < 2) iter_count = 100; + else sscanf(argv[1], "%d", &iter_count); + + Swarm<2> swarm(f); + Screen scr(80, 24); + + swarm.add_particle({0, 0}, {7, 3}); + swarm.add_particle({4, 1}, {1, 5}); + swarm.add_particle({16, 10}, {9, 10}); + swarm.add_particle({7, 4}, {4, -3}); + swarm.add_particle({14, 1}, {1, 5}); + swarm.add_particle({1, 10}, {9, 10}); + swarm.add_particle({5, 4}, {5, -3}); + swarm.add_particle({4, 6}, {-1, 5}); + swarm.add_particle({2, 6}, {9, 0}); + swarm.add_particle({7, 7}, {4, -3}); + + enter_noncanonical_mode(); + printf("\e[?1049h"); + + bool pause = false; + bool frame_step = false; + + for(int i = 0; i < iter_count * FPS/4; ++i) { + if(!pause) { + scr.clear(); + for(const auto &p : swarm.get_particles()) { + const auto & pos = p.get_position(); + scr.at(pos.x, pos.y) = '#'; + } + scr.draw(); + + swarm.move(DT * 4); + if(i % (FPS/4) == (FPS/4)-1) + swarm.step(); + } + + if(frame_step) { + pause = true; + frame_step = false; + } + + struct {char buf[8]; int size;} inbuf = {{}, 0}; + inbuf.size = read(STDIN_FILENO, inbuf.buf, 7); + switch (*inbuf.buf) { + case 'q': + goto cleanup; + case ' ': + pause = !pause; + break; + case '.': + pause = false; + frame_step = true; + break; + } + + usleep(1000 * 1000 / FPS); + } + +cleanup: + printf("\e[?1049l"); + enter_canonical_mode(); + + auto [y, x] = swarm.best(); + + printf("Best: f(" V2_FMT ") = %.3f", V2_ARG(x), y); + + return 0; +} diff --git a/src/screen.cpp b/src/screen.cpp new file mode 100644 index 0000000..68ddca9 --- /dev/null +++ b/src/screen.cpp @@ -0,0 +1,34 @@ +#include +#include +#include + +void Screen::resize(std::size_t n) { + w = n; h = 1; + buf = (char *)realloc(buf, n * sizeof(char)); +} + +void Screen::resize(std::size_t x, std::size_t y) { + w = x; h = y; + buf = (char *)realloc(buf, x * y * sizeof(char)); +} + +char &Screen::at(std::size_t n) { return buf[n]; } +char &Screen::at(std::size_t x, std::size_t y) { + auto idx = (int)(((y-dy)/sy) * w) + (int)((x-dx)/sx); + if(idx >= w *h) return _dummy; + return buf[idx]; +} + +void Screen::clear() { + memset(buf, ' ', w*h); +} + +void Screen::draw() { + gotoxy(0, 0); + for(int i = 0; i < h; ++i) { + for(int j = 0; j < w; ++j) { + std::fputc(at(j, i), stdout); + } + std::fputc('\n', stdout); + } +} diff --git a/src/swarm.cpp b/src/swarm.cpp new file mode 100644 index 0000000..f1e1a8b --- /dev/null +++ b/src/swarm.cpp @@ -0,0 +1 @@ +#include