add sdl, fix vec3, fix threading

This commit is contained in:
zongor 2025-07-24 09:59:47 -04:00
parent 8151edeebf
commit ffc8cdbdeb
8 changed files with 341 additions and 63 deletions

View File

@ -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

29
src/canvas.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef CANVAS_H
#define CANVAS_H
#include <cstdint>
#include <vector>
struct Color {
uint8_t r, g, b;
};
struct Canvas {
std::vector<Color> 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

View File

@ -1,19 +1,46 @@
#include "canvas.h"
#include "simulation.h"
#include <thread>
#include <SDL2/SDL.h>
#include <chrono>
#include <thread>
int main() {
auto state = std::make_shared<SimulationState>();
state->positions.push_back(Vec3(1.0, 2.0, 3.0));
Canvas canvas(800, 600);
RingBuffer<SimulationState> 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();
}

24
src/ray.h Normal file
View File

@ -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

View File

@ -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<std::mutex> lock(mtx);
return full;
}

View File

@ -1,34 +1,165 @@
#include "simulation.h"
#include "canvas.h"
#include "vec3.h"
#include <chrono>
#include <iostream>
#include <thread>
std::atomic<bool> running(true);
RingBuffer<Vec3> snapshot_buffer(128);
void simulation_loop(std::shared_ptr<SimulationState> 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<SimulationState> &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<double> 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<SimulationState> &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
}
}

View File

@ -6,16 +6,24 @@
#include <mutex>
#include <atomic>
#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<Vec3> positions;
std::vector<Body> bodies;
uint64_t tick; // global simulation tick
};
extern std::atomic<bool> running;
extern RingBuffer<Vec3> snapshot_buffer;
void simulation_loop(std::shared_ptr<SimulationState> state);
void render_loop();
void simulation_thread(RingBuffer<SimulationState> &buffer);
void render_thread(RingBuffer<SimulationState> &buffer, Canvas& canvas);
#endif

View File

@ -1,45 +1,101 @@
#ifndef VEC3_H
#define VEC3_H
#include <array>
#include <iostream>
#include <cmath>
#include <ostream>
class Vec3 {
std::array<double, 3> data;
std::array<double, 3> 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