add hitable, add sphere, first working prototype!

This commit is contained in:
zongor 2025-07-24 12:35:07 -04:00
parent ffc8cdbdeb
commit a67c2d19d6
9 changed files with 308 additions and 118 deletions

View File

@ -1,29 +1,44 @@
#ifndef CANVAS_H
#define CANVAS_H
#include "vec3.h"
#include <cstdint>
#include <vector>
struct Color {
using Color = Vec3;
struct Rgb {
uint8_t r, g, b;
};
inline Rgb to_rgb(const Color &c) {
auto clamp = [](double x) { return x < 0.0 ? 0.0 : (x > 1.0 ? 1.0 : x); };
return {static_cast<uint8_t>(255.999 * clamp(c.x())),
static_cast<uint8_t>(255.999 * clamp(c.y())),
static_cast<uint8_t>(255.999 * clamp(c.z()))};
}
struct Canvas {
std::vector<Color> pixels;
std::vector<Rgb> 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) {
void set_pixel(int x, int y, Rgb 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];
void set_pixel(int x, int y, Color color) {
if (x >= 0 && x < width && y >= 0 && y < height) {
pixels[y * width + x] = to_rgb(color);
}
}
Rgb get_pixel(int x, int y) const { return pixels[y * width + x]; }
};
#endif

32
src/common.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef RTWEEKEND_H
#define RTWEEKEND_H
#include <cmath>
#include <iostream>
#include <limits>
#include <memory>
// C++ Std Usings
using std::make_shared;
using std::shared_ptr;
// Constants
const double infinity = std::numeric_limits<double>::infinity();
const double pi = 3.1415926535897932385;
// Utility Functions
inline double degrees_to_radians(double degrees) {
return degrees * pi / 180.0;
}
// Common Headers
#include "ray.h"
#include "vec3.h"
#include "canvas.h"
#endif

20
src/hittable.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef HITTABLE_H
#define HITTABLE_H
#include "ray.h"
class hit_record {
public:
Point3 p;
Vec3 normal;
double t;
};
class hittable {
public:
virtual ~hittable() = default;
virtual bool hit(const Ray& r, double ray_tmin, double ray_tmax, hit_record& rec) const = 0;
};
#endif

41
src/hittable_list.h Normal file
View File

@ -0,0 +1,41 @@
#ifndef HITTABLE_LIST_H
#define HITTABLE_LIST_H
#include "hittable.h"
#include <memory>
#include <vector>
using std::make_shared;
using std::shared_ptr;
class hittable_list : public hittable {
public:
std::vector<shared_ptr<hittable>> objects;
hittable_list() {}
hittable_list(shared_ptr<hittable> object) { add(object); }
void clear() { objects.clear(); }
void add(shared_ptr<hittable> object) { objects.push_back(object); }
bool hit(const Ray &r, double ray_tmin, double ray_tmax,
hit_record &rec) const override {
hit_record temp_rec;
bool hit_anything = false;
auto closest_so_far = ray_tmax;
for (const auto &object : objects) {
if (object->hit(r, ray_tmin, closest_so_far, temp_rec)) {
hit_anything = true;
closest_so_far = temp_rec.t;
rec = temp_rec;
}
}
return hit_anything;
}
};
#endif

View File

@ -5,7 +5,14 @@
#include <thread>
int main() {
Canvas canvas(800, 600);
auto aspect_ratio = 16.0 / 9.0;
int image_width = 400;
// Calculate the image height, and ensure that it's at least 1.
int image_height = int(image_width / aspect_ratio);
image_height = (image_height < 1) ? 1 : image_height;
Canvas canvas(image_width, image_height);
RingBuffer<SimulationState> buffer(64);
std::thread sim(simulation_thread, std::ref(buffer));

View File

@ -1,24 +1,23 @@
#ifndef RAY_H
#define RAY_H
#include "canvas.h"
#include "vec3.h"
class Ray {
public:
Ray() {}
public:
Ray() {}
Ray(const Point3& origin, const Vec3& direction) : orig(origin), dir(direction) {}
Ray(const Point3 &origin, const Vec3 &direction)
: orig(origin), dir(direction) {}
const Point3& origin() const { return orig; }
const Vec3& direction() const { return dir; }
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;
Point3 at(double t) const { return orig + t * dir; }
private:
Point3 orig;
Vec3 dir;
};
#endif

View File

@ -1,6 +1,8 @@
#include "simulation.h"
#include "canvas.h"
#include "vec3.h"
#include "common.h"
#include "hittable.h"
#include "hittable_list.h"
#include "sphere.h"
#include <chrono>
#include <iostream>
@ -14,98 +16,41 @@ std::atomic<bool> running(true);
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
1.989e30, 100.0}); // 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
Vec3(0.0, 47.36e3, 0.0), 0.33011e24,
10}); // 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
Vec3(0.0, 35.02e3, 0.0), 4.8675e24,
30}); // 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
Vec3(0.0, 29.78e3, 0.0), 5.9724e24,
30}); // 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
Vec3(0.0, 24.07e3, 0.0), 0.64171e24,
20}); // 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
Vec3(0.0, 13e3, 0.0), 1898.19e24,
50}); // 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
Vec3(0.0, 9.68e3, 0.0), 568.34e24,
40}); // 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
Vec3(0.0, 6.80e3, 0.0), 86.813e24,
30}); // 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
Vec3(0.0, 5.43e3, 0.0), 102.413e24,
30}); // a planet similar to neptune
return state;
}
/**
* 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();
}
}
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;
float dt = 0.01; // if we didnt do this it would run too fast for render
std::chrono::duration<float> elapsed;
auto last = std::chrono::high_resolution_clock::now();
@ -114,8 +59,57 @@ void simulation_thread(RingBuffer<SimulationState> &buffer) {
elapsed = now - last;
if (elapsed.count() >= dt) {
simulate_step(state);
float t_0 = 0;
float t = t_0;
float dt = 86400;
float 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();
float r_mag =
sqrt(r_vector.x() * r_vector.x() + r_vector.y() * r_vector.y() +
r_vector.z() * r_vector.z());
float 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();
}
}
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;
// Try to push it into the buffer
while (!buffer.push(state)) {
// Buffer full, drop or wait (e.g. yield or sleep)
@ -130,21 +124,59 @@ void simulation_thread(RingBuffer<SimulationState> &buffer) {
}
}
Color ray_color(const Ray &r, const hittable &world) {
hit_record rec;
if (world.hit(r, 0, infinity, rec)) {
return 0.5 * (rec.normal + Color(1, 1, 1));
}
Vec3 unit_direction = unit_vector(r.direction());
auto a = 0.5 * (unit_direction.y() + 1.0);
return (1.0 - a) * Color(1.0, 1.0, 1.0) + a * Color(0.5, 0.7, 1.0);
}
/**
* Render raytracing.
*/
void render(SimulationState state, Canvas &canvas) {
hittable_list world;
const double SCALE = 1e9;
for (auto b : state.bodies) {
auto bb = b.position / SCALE;
world.add(make_shared<sphere>(Vec3(bb.x(), bb.z(), bb.y()), b.radius));
}
// Camera
auto focal_length = 1.0;
auto viewport_height = 2.0;
auto viewport_width = viewport_height * (float(canvas.width) / canvas.height);
auto camera_center = Point3(0, 0, -100);
// Calculate the vectors across the horizontal and down the vertical viewport
// edges.
auto viewport_u = Vec3(viewport_width, 0, 0);
auto viewport_v = Vec3(0, -viewport_height, 0);
// Calculate the horizontal and vertical delta vectors from pixel to pixel.
auto pixel_delta_u = viewport_u / canvas.width;
auto pixel_delta_v = viewport_v / canvas.height;
// Calculate the location of the upper left pixel.
auto viewport_upper_left = camera_center - Vec3(0, 0, focal_length) -
viewport_u / 2 - viewport_v / 2;
auto pixel00_loc =
viewport_upper_left + 0.5 * (pixel_delta_u + pixel_delta_v);
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;
auto pixel_center =
pixel00_loc + (i * pixel_delta_u) + (j * pixel_delta_v);
auto ray_direction = pixel_center - camera_center;
Ray r(camera_center, ray_direction);
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));
canvas.set_pixel(i, j, ray_color(r, world));
}
}
}

View File

@ -1,29 +1,30 @@
#ifndef SIMULATION_H
#define SIMULATION_H
#include <vector>
#include <memory>
#include <mutex>
#include <atomic>
#include "vec3.h"
#include "canvas.h"
#include "ring_buffer.h"
#include "vec3.h"
#include <atomic>
#include <memory>
#include <mutex>
#include <vector>
struct Body {
std::string name;
Vec3 position;
Vec3 velocity;
double mass;
std::string name;
Vec3 position;
Vec3 velocity;
double mass;
double radius;
};
struct SimulationState {
std::vector<Body> bodies;
uint64_t tick; // global simulation tick
std::vector<Body> bodies;
uint64_t tick; // global simulation tick
};
extern std::atomic<bool> running;
void simulation_thread(RingBuffer<SimulationState> &buffer);
void render_thread(RingBuffer<SimulationState> &buffer, Canvas& canvas);
void render_thread(RingBuffer<SimulationState> &buffer, Canvas &canvas);
#endif

43
src/sphere.h Normal file
View File

@ -0,0 +1,43 @@
#ifndef SPHERE_H
#define SPHERE_H
#include "hittable.h"
#include "vec3.h"
class sphere : public hittable {
public:
sphere(const Point3& center, double radius) : center(center), radius(std::fmax(0,radius)) {}
bool hit(const Ray& r, double ray_tmin, double ray_tmax, hit_record& rec) const override {
Vec3 oc = center - r.origin();
auto a = r.direction().length_squared();
auto h = dot(r.direction(), oc);
auto c = oc.length_squared() - radius*radius;
auto discriminant = h*h - a*c;
if (discriminant < 0)
return false;
auto sqrtd = std::sqrt(discriminant);
// Find the nearest root that lies in the acceptable range.
auto root = (h - sqrtd) / a;
if (root <= ray_tmin || ray_tmax <= root) {
root = (h + sqrtd) / a;
if (root <= ray_tmin || ray_tmax <= root)
return false;
}
rec.t = root;
rec.p = r.at(rec.t);
rec.normal = (rec.p - center) / radius;
return true;
}
private:
Point3 center;
double radius;
};
#endif