From ffc8cdbdeb6644e6867edda6c5bc01f932eee9a4 Mon Sep 17 00:00:00 2001 From: zongor Date: Thu, 24 Jul 2025 09:59:47 -0400 Subject: [PATCH] add sdl, fix vec3, fix threading --- Makefile | 3 +- src/canvas.h | 29 ++++++++ src/main.cpp | 47 ++++++++++--- src/ray.h | 24 +++++++ src/ring_buffer.h | 4 +- src/simulation.cpp | 163 ++++++++++++++++++++++++++++++++++++++++----- src/simulation.h | 16 +++-- src/vec3.h | 118 +++++++++++++++++++++++--------- 8 files changed, 341 insertions(+), 63 deletions(-) create mode 100644 src/canvas.h create mode 100644 src/ray.h diff --git a/Makefile b/Makefile index 6afbe66..6a791c5 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ STD ?= c++23 CXXFLAGS := -std=$(STD) -Wall -Wextra -O2 -Iinclude LDFLAGS := +LDLIBS := -lSDL2 # Source files and object files SRCS := $(shell find $(SRC_DIR) -name '*.cpp') @@ -17,7 +18,7 @@ all: $(TARGET) # Link final executable $(TARGET): $(OBJS) @mkdir -p $(dir $@) - $(CXX) $(OBJS) -o $@ $(LDFLAGS) + $(CXX) $(OBJS) -o $@ $(LDFLAGS) $(LDLIBS) # Compile source files $(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp diff --git a/src/canvas.h b/src/canvas.h new file mode 100644 index 0000000..433f572 --- /dev/null +++ b/src/canvas.h @@ -0,0 +1,29 @@ +#ifndef CANVAS_H +#define CANVAS_H + +#include +#include + +struct Color { + uint8_t r, g, b; +}; + +struct Canvas { + std::vector pixels; + int width; + int height; + + Canvas(int w, int h) : pixels(w * h), width(w), height(h) {} + + void set_pixel(int x, int y, Color color) { + if (x >= 0 && x < width && y >= 0 && y < height) { + pixels[y * width + x] = color; + } + } + + Color get_pixel(int x, int y) const { + return pixels[y * width + x]; + } +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp index fc8a8a8..a05aa27 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,19 +1,46 @@ +#include "canvas.h" #include "simulation.h" -#include +#include #include +#include int main() { - auto state = std::make_shared(); - state->positions.push_back(Vec3(1.0, 2.0, 3.0)); + Canvas canvas(800, 600); + RingBuffer buffer(64); - std::thread sim(simulation_loop, state); - std::thread render(render_loop); + std::thread sim(simulation_thread, std::ref(buffer)); + std::thread render(render_thread, std::ref(buffer), std::ref(canvas)); - std::this_thread::sleep_for(std::chrono::seconds(2)); - running = false; + SDL_Init(SDL_INIT_VIDEO); + SDL_Window *window = + SDL_CreateWindow("Solar System", SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, canvas.width, canvas.height, 0); + SDL_Renderer *renderer = + SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); + SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB24, + SDL_TEXTUREACCESS_STREAMING, + canvas.width, canvas.height); - sim.join(); - render.join(); + while (running) { + void *pixels; + int pitch; + SDL_LockTexture(texture, nullptr, &pixels, &pitch); + memcpy(pixels, canvas.pixels.data(), canvas.pixels.size() * sizeof(Color)); + SDL_UnlockTexture(texture); - return 0; + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, nullptr, nullptr); + SDL_RenderPresent(renderer); + + SDL_Event e; + while (SDL_PollEvent(&e)) { + if (e.type == SDL_QUIT) + running = false; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(16)); // ~60fps + } + + sim.join(); + render.join(); } diff --git a/src/ray.h b/src/ray.h new file mode 100644 index 0000000..9f4de14 --- /dev/null +++ b/src/ray.h @@ -0,0 +1,24 @@ +#ifndef RAY_H +#define RAY_H + +#include "vec3.h" + +class Ray { + public: + Ray() {} + + Ray(const Point3& origin, const Vec3& direction) : orig(origin), dir(direction) {} + + const Point3& origin() const { return orig; } + const Vec3& direction() const { return dir; } + + Point3 at(double t) const { + return orig + t*dir; + } + + private: + Point3 orig; + Vec3 dir; +}; + +#endif diff --git a/src/ring_buffer.h b/src/ring_buffer.h index c48c0bb..275ff69 100644 --- a/src/ring_buffer.h +++ b/src/ring_buffer.h @@ -41,6 +41,8 @@ public: T item = buffer[head]; full = false; head = (head + 1) % capacity; + + return item; } bool empty() const { @@ -48,7 +50,7 @@ public: return empty_unlocked(); } - bool full_() const { + bool _full() const { std::lock_guard lock(mtx); return full; } diff --git a/src/simulation.cpp b/src/simulation.cpp index 97d861a..597a63e 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -1,34 +1,165 @@ #include "simulation.h" +#include "canvas.h" +#include "vec3.h" + #include #include #include std::atomic running(true); -RingBuffer snapshot_buffer(128); -void simulation_loop(std::shared_ptr state) { - Vec3 velocity(0.01, 0.02, 0.03); +/** + * Initialize solar system. + */ +SimulationState initialize_solar_system() { + SimulationState state; + state.bodies.push_back(Body{"Sol", Vec3(0.0, 0.0, 0.0), Vec3(0.0, 0.0, 0.0), + 1.989e30}); // a star similar to the sun + state.bodies.push_back(Body{"Mercury", Vec3(57.909e9, 0.0, 0.0), + Vec3(0.0, 47.36e3, 0.0), + 0.33011e24}); // a planet similar to mercury + state.bodies.push_back(Body{"Venus", Vec3(108.209e9, 0.0, 0.0), + Vec3(0.0, 35.02e3, 0.0), + 4.8675e24}); // a planet similar to venus + state.bodies.push_back(Body{"Earth", Vec3(149.596e9, 0.0, 0.0), + Vec3(0.0, 29.78e3, 0.0), + 5.9724e24}); // a planet similar to earth + state.bodies.push_back(Body{"Mars", Vec3(227.923e9, 0.0, 0.0), + Vec3(0.0, 24.07e3, 0.0), + 0.64171e24}); // a planet similar to mars + state.bodies.push_back(Body{"Jupiter", Vec3(778.570e9, 0.0, 0.0), + Vec3(0.0, 13e3, 0.0), + 1898.19e24}); // a planet similar to jupiter + state.bodies.push_back(Body{"Saturn", Vec3(1433.529e9, 0.0, 0.0), + Vec3(0.0, 9.68e3, 0.0), + 568.34e24}); // a planet similar to saturn + state.bodies.push_back(Body{"Uranus", Vec3(2872.463e9, 0.0, 0.0), + Vec3(0.0, 6.80e3, 0.0), + 86.813e24}); // a planet similar to uranus + state.bodies.push_back(Body{"Neptune", Vec3(4495.060e9, 0.0, 0.0), + Vec3(0.0, 5.43e3, 0.0), + 102.413e24}); // a planet similar to neptune + return state; +} - while (running) { - for (auto &p : state->positions) { - p += velocity; +/** + * Step . + */ +void simulate_step(SimulationState state) { + double t_0 = 0; + double t = t_0; + double dt = 86400; + double BIG_G = 6.67e-11; // gravitational constant + + size_t n_bodies = state.bodies.size(); + for (size_t m1_idx = 0; m1_idx < n_bodies; m1_idx++) { + Vec3 a_g = {0, 0, 0}; + + for (size_t m2_idx = 0; m2_idx < n_bodies; m2_idx++) { + if (m2_idx != m1_idx) { + Vec3 r_vector; + + r_vector.x() = state.bodies[m1_idx].position.x() - + state.bodies[m2_idx].position.x(); + r_vector.y() = state.bodies[m1_idx].position.y() - + state.bodies[m2_idx].position.y(); + r_vector.z() = state.bodies[m1_idx].position.z() - + state.bodies[m2_idx].position.z(); + + double r_mag = + sqrt(r_vector.x() * r_vector.x() + r_vector.y() * r_vector.y() + + r_vector.z() * r_vector.z()); + + double acceleration = + -1.0 * BIG_G * (state.bodies[m2_idx].mass) / pow(r_mag, 2.0); + + Vec3 r_unit_vector = {r_vector.x() / r_mag, r_vector.y() / r_mag, + r_vector.z() / r_mag}; + + a_g.x() += acceleration * r_unit_vector.x(); + a_g.y() += acceleration * r_unit_vector.y(); + a_g.z() += acceleration * r_unit_vector.z(); + } } - while (snapshot_buffer.push(state->positions[0])) { - std::this_thread::yield(); + state.bodies[m1_idx].velocity.x() += a_g.x() * dt; + state.bodies[m1_idx].velocity.y() += a_g.y() * dt; + state.bodies[m1_idx].velocity.z() += a_g.z() * dt; + } + + for (size_t entity_idx = 0; entity_idx < n_bodies; entity_idx++) { + state.bodies[entity_idx].position.x() += + state.bodies[entity_idx].velocity.x() * dt; + state.bodies[entity_idx].position.y() += + state.bodies[entity_idx].velocity.y() * dt; + state.bodies[entity_idx].position.z() += + state.bodies[entity_idx].velocity.z() * dt; + } + + t += dt; +} + +/** + * Simulation thread. + */ +void simulation_thread(RingBuffer &buffer) { + SimulationState state = initialize_solar_system(); + double dt = 0.01; // if we didnt do this it would run too fast for render + std::chrono::duration elapsed; + + auto last = std::chrono::high_resolution_clock::now(); + + while (running) { + auto now = std::chrono::high_resolution_clock::now(); + elapsed = now - last; + + if (elapsed.count() >= dt) { + simulate_step(state); + + // Try to push it into the buffer + while (!buffer.push(state)) { + // Buffer full, drop or wait (e.g. yield or sleep) + std::this_thread::yield(); + } + + state.tick++; + last = now; + } else { + std::this_thread::sleep_for(std::chrono::milliseconds(2)); } } } -void render_loop() { - while (running) { +/** + * Render raytracing. + */ +void render(SimulationState state, Canvas &canvas) { + for (int j = 0; j < canvas.height; j++) { + for (int i = 0; i < canvas.width; i++) { + auto r = double(i) / (canvas.width - 1); + auto g = double(j) / (canvas.height - 1); + auto b = 0.0; - Vec3 latest; - while (latest = snapshot_buffer.pop()) { - std::cout << "[Render] "; - latest.print(); + uint8_t ir = uint8_t(255.999 * r); + uint8_t ig = uint8_t(255.999 * g); + uint8_t ib = uint8_t(255.999 * b); + + canvas.set_pixel(i, j, Color(ir, ig, ib)); + } + } +} + +/** + * Render thread. + */ +void render_thread(RingBuffer &buffer, Canvas &canvas) { + while (running) { + auto maybe_state = buffer.pop(); + if (maybe_state.has_value()) { + const SimulationState &state = maybe_state.value(); + render(state, canvas); // Pure function: write pixel colors into canvas + } else { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); } - // Optional: add very short pause to avoid spinning too fast - std::this_thread::yield(); // cooperative multitasking } } diff --git a/src/simulation.h b/src/simulation.h index b3444ec..0ededa5 100644 --- a/src/simulation.h +++ b/src/simulation.h @@ -6,16 +6,24 @@ #include #include #include "vec3.h" +#include "canvas.h" #include "ring_buffer.h" +struct Body { + std::string name; + Vec3 position; + Vec3 velocity; + double mass; +}; + struct SimulationState { - std::vector positions; + std::vector bodies; + uint64_t tick; // global simulation tick }; extern std::atomic running; -extern RingBuffer snapshot_buffer; -void simulation_loop(std::shared_ptr state); -void render_loop(); +void simulation_thread(RingBuffer &buffer); +void render_thread(RingBuffer &buffer, Canvas& canvas); #endif diff --git a/src/vec3.h b/src/vec3.h index 50caba2..c5ef70e 100644 --- a/src/vec3.h +++ b/src/vec3.h @@ -1,45 +1,101 @@ #ifndef VEC3_H #define VEC3_H -#include -#include +#include +#include class Vec3 { - std::array data; + std::array e{}; public: - // Constructors - Vec3() = default; - Vec3(double x, double y, double z) : data{x, y, z} {} + constexpr Vec3() = default; + constexpr Vec3(double x, double y, double z) noexcept : e{x, y, z} {} - // Named accessors (mutable) - double& x() { return data[0]; } - double& y() { return data[1]; } - double& z() { return data[2]; } + constexpr double x() const noexcept { return e[0]; } + constexpr double y() const noexcept { return e[1]; } + constexpr double z() const noexcept { return e[2]; } - // Named accessors (const) - double x() const { return data[0]; } - double y() const { return data[1]; } - double z() const { return data[2]; } + constexpr double &x() { return e[0]; } + constexpr double &y() { return e[1]; } + constexpr double &z() { return e[2]; } - // Arithmetic operators - friend Vec3 operator+(const Vec3& a, const Vec3& b) { - return Vec3(a.x() + b.x(), - a.y() + b.y(), - a.z() + b.z()); - } + constexpr double operator[](int i) const noexcept { return e[i]; } + constexpr double &operator[](int i) noexcept { return e[i]; } - Vec3& operator+=(const Vec3& other) { - x() += other.x(); - y() += other.y(); - z() += other.z(); - return *this; - } + constexpr Vec3 operator-() const noexcept { + return Vec3(-e[0], -e[1], -e[2]); + } - // Debug print - void print() const { - std::cout << "(" << x() << ", " << y() << ", " << z() << ")\n"; - } + constexpr Vec3 &operator+=(const Vec3 &v) noexcept { + e[0] += v.e[0]; + e[1] += v.e[1]; + e[2] += v.e[2]; + return *this; + } + + constexpr Vec3 &operator*=(double t) noexcept { + e[0] *= t; + e[1] *= t; + e[2] *= t; + return *this; + } + + constexpr Vec3 &operator/=(double t) noexcept { return *this *= 1.0 / t; } + + [[nodiscard]] + constexpr double length_squared() const noexcept { + return e[0] * e[0] + e[1] * e[1] + e[2] * e[2]; + } + + [[nodiscard]] + double length() const noexcept { + return std::sqrt(length_squared()); + } + + friend constexpr Vec3 operator+(Vec3 u, const Vec3 &v) noexcept { + return u += v; + } + + friend constexpr Vec3 operator-(Vec3 u, const Vec3 &v) noexcept { + u.e[0] -= v.e[0]; + u.e[1] -= v.e[1]; + u.e[2] -= v.e[2]; + return u; + } + + friend constexpr Vec3 operator*(Vec3 u, const Vec3 &v) noexcept { + u.e[0] *= v.e[0]; + u.e[1] *= v.e[1]; + u.e[2] *= v.e[2]; + return u; + } + + friend constexpr Vec3 operator*(double t, Vec3 v) noexcept { return v *= t; } + + friend constexpr Vec3 operator*(Vec3 v, double t) noexcept { return v *= t; } + + friend constexpr Vec3 operator/(Vec3 v, double t) noexcept { return v /= t; } + + friend constexpr double dot(const Vec3 &u, const Vec3 &v) noexcept { + return u.e[0] * v.e[0] + u.e[1] * v.e[1] + u.e[2] * v.e[2]; + } + + friend constexpr Vec3 cross(const Vec3 &u, const Vec3 &v) noexcept { + return Vec3(u.e[1] * v.e[2] - u.e[2] * v.e[1], + u.e[2] * v.e[0] - u.e[0] * v.e[2], + u.e[0] * v.e[1] - u.e[1] * v.e[0]); + } + + [[nodiscard]] + friend Vec3 unit_vector(Vec3 v) noexcept { + return v / v.length(); + } + + friend std::ostream &operator<<(std::ostream &out, const Vec3 &v) { + return out << v.e[0] << ' ' << v.e[1] << ' ' << v.e[2]; + } }; -#endif // VEC3_H +using Point3 = Vec3; + +#endif