Files
RayTracer/raytracer_formatted.c
T
2025-02-17 18:08:58 +01:00

682 lines
18 KiB
C

/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* raytracer_formatted.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: yantoine <yantoine@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/02/13 19:56:17 by yantoine #+# #+# */
/* Updated: 2025/02/17 17:18:17 by yantoine ### ########.fr */
/* */
/* ************************************************************************** */
/*
*Raytracer interactif en un seul fichier C avec lecture d'un fichier de configuration (.rt)
*
*Compilation (exemple sous Linux) :
* gcc raytracer.c -lSDL2 -lm -O2 -o raytracer
*
*Utilisation :
* ./raytracer config.rt
*Si aucun fichier n'est passé en argument, une scène par défaut est utilisée.
*/
// Ambient et caméra (JE GARDE POUR FAIRE LA FONCTION UN EXEMPLE)
float ambient_ratio = 0.1f;
t_vec3 ambient_color = {0.1f, 0.1f, 0.1f};
t_vec3 camPos = {0.0f, 0.0f, 0.0f};
t_vec3 camDir = {0.0f, 0.0f, -1.0f};
float fov = 90.0f;
float yaw = 0.0f;// vue de gauche a droite
float pitch = 0.0f; // vue de haut en bas en radians
// ----- Parsing du fichier de configuration -----
void load_config(const char *filename)
{
//---- Jlaisse pour contextualiser -----
while (fgets(line, sizeof(line), fp))
{
// Ignore les lignes vides ou commençant par #
if (line[0] == '\n' || line[0] == '#')
continue ;
// Ambient lighting: A ratio R,G,B
if (line[0] == 'A')
{
int r, g, b;
if (sscanf(line, "A %f %d,%d,%d", &ratio, &r, &g, &b) == 4)
{
ambient_ratio = ratio;
ambient_color = (t_vec3){r / 255.0f, g / 255.0f, b / 255.0f};
printf("Ambiance : ratio=%.2f, color=(%d,%d,%d)\n", ratio, r, g,
b);
}
}
// Camera: C pos_x,pos_y,pos_z norm_x,norm_y,norm_z FOV
else if (line[0] == 'C')
{
float px, py, pz, nx, ny, nz, f;
if (sscanf(line, "C %f,%f,%f %f,%f,%f %f", &px, &py, &pz, &nx, &ny,
&nz, &f) == 7)
{
camPos = (t_vec3){px, py, pz};
camDir = vec3_normalize((t_vec3){nx, ny, nz});
fov = f;
yaw = atan2f(camDir.x, -camDir.z);
pitch = asinf(camDir.y);
printf("Camera : pos=(%.2f,%.2f,%.2f), dir=(%.2f,%.2f,%.2f),
fov=%.2f\n", px, py, pz, nx, ny, nz, f);
}
}
// Light: L pos_x,pos_y,pos_z brightness R,G,B
else if (line[0] == 'L')
{
float lx, ly, lz, bright;
int r, g, b;
if (sscanf(line, "L %f,%f,%f %f %d,%d,%d", &lx, &ly, &lz, &bright,
&r, &g, &b) == 7)
{
if (numLights < MAX_LIGHTS)
{
lights[numLights].pos = (t_vec3){lx, ly, lz};
lights[numLights].brightness = bright;
lights[numLights].color = (t_vec3){r / 255.0f, g / 255.0f, b
/ 255.0f};
numLights++;
printf("Light : pos=(%.2f,%.2f,%.2f), bright=%.2f,
color=(%d,%d,%d)\n", lx, ly, lz, bright, r, g, b);
}
}
}
// Sphere: sp pos_x,pos_y,pos_z diameter R,G,B
else if (strncmp(line, "sp", 2) == 0)
{
float cx, cy, cz, diam;
int r, g, b;
if (sscanf(line, "sp %f,%f,%f %f %d,%d,%d", &cx, &cy, &cz, &diam,
&r, &g, &b) == 7)
{
if (numSpheres < MAX_SPHERES)
{
spheres[numSpheres].center = (t_vec3){cx, cy, cz};
spheres[numSpheres].radius = diam / 2.0f;
spheres[numSpheres].color = (t_vec3){r / 255.0f, g / 255.0f,
b / 255.0f};
numSpheres++;
printf("Sphere : center=(%.2f,%.2f,%.2f), diam=%.2f,
color=(%d,%d,%d)\n", cx, cy, cz, diam, r, g, b);
}
}
}
// Plane: pl pos_x,pos_y,pos_z norm_x,norm_y,norm_z R,G,B
else if (strncmp(line, "pl", 2) == 0)
{
float px, py, pz, nx, ny, nz;
int r, g, b;
// Correction : nous attendons 9 éléments (3 + 3 + 3)
if (sscanf(line, "pl %f,%f,%f %f,%f,%f %d,%d,%d", &px, &py, &pz,
&nx, &ny, &nz, &r, &g, &b) == 9)
{
if (numPlanes < MAX_PLANES)
{
planes[numPlanes].point = (t_vec3){px, py, pz};
planes[numPlanes].normal = vec3_normalize((t_vec3){nx, ny,
nz});
planes[numPlanes].color = (t_vec3){r / 255.0f, g / 255.0f, b
/ 255.0f};
numPlanes++;
printf("Plane : point=(%.2f,%.2f,%.2f),
normal=(%.2f,%.2f,%.2f), color=(%d,%d,%d)\n", px, py,
pz, nx, ny, nz, r, g, b);
}
}
}
// Cylinder: cy pos_x,pos_y,pos_z axis_x,axis_y,axis_z diameter height R,G,B
else if (strncmp(line, "cy", 2) == 0)
{
float cx, cy, cz, ax, ay, az, diam, height;
int r, g, b;
// Correction : nous attendons 11 éléments (3 + 3 + 1 + 1 + 3)
if (sscanf(line, "cy %f,%f,%f %f,%f,%f %f %f %d,%d,%d", &cx, &cy,
&cz, &ax, &ay, &az, &diam, &height, &r, &g, &b) == 11)
{
if (numCylinders < MAX_CYLINDERS)
{
cylinders[numCylinders].center = (t_vec3){cx, cy, cz};
cylinders[numCylinders].axis = vec3_normalize((t_vec3){ax,
ay, az});
cylinders[numCylinders].radius = diam / 2.0f;
cylinders[numCylinders].height = height;
cylinders[numCylinders].color = (t_vec3){r / 255.0f, g
/ 255.0f, b / 255.0f};
numCylinders++;
printf("Cylinder : center=(%.2f,%.2f,%.2f),
axis=(%.2f,%.2f,%.2f), diam=%.2f, height=%.2f,
color=(%d,%d,%d)\n", cx, cy, cz, ax, ay, az, diam,
height, r, g, b);
}
}
}
}
fclose(fp);
}
// ----- Fonctions d'intersection -----
float intersectSphere(Ray ray, Sphere s, t_vec3 *hitNormal)
{
oc = vec3_sub(ray.origin, s.center);
a = vec3_dot(ray.dir, ray.dir);
b = 2.0f * vec3_dot(oc, ray.dir);
c = vec3_dot(oc, oc) - s.radius * s.radius;
disc = b * b - 4 * a * c;
if (disc < 0)
return (-1);
sqrtDisc = sqrtf(disc);
t0 = (-b - sqrtDisc) / (2 * a);
t1 = (-b + sqrtDisc) / (2 * a);
t = (t0 > 1e-3f) ? t0 : t1;
if (t < 1e-3f)
return (-1);
hitPoint = vec3_add(ray.origin, vec3_scale(ray.dir, t));
*hitNormal = vec3_normalize(vec3_sub(hitPoint, s.center));
return (t);
}
float intersectPlane(Ray ray, Plane p, t_vec3 *hitNormal)
{
float denom;
float t;
denom = vec3_dot(p.normal, ray.dir);
if (fabs(denom) < 1e-6f)
return (-1);
t = vec3_dot(vec3_sub(p.point, ray.origin), p.normal) / denom;
if (t < 1e-3f)
return (-1);
*hitNormal = p.normal;
return (t);
}
// Intersection d'un cylindre orienté arbitrairement (version simplifiée)
float intersectCylinder(Ray ray, Cylinder cy, t_vec3 *hitNormal)
{
t_vec3 d;
t_vec3 oc;
t_vec3 v;
float d_dot_v;
float oc_dot_v;
t_vec3 d_perp;
t_vec3 oc_perp;
float a;
float b;
float c;
float disc;
float sqrtDisc;
float t0;
float t1;
float t_side;
float y;
float y;
float t_cap;
float t_bot;
t_vec3 p;
t_vec3 cp;
float dist;
float t_top;
t_vec3 p;
t_vec3 cp;
float dist;
float t_final;
t_vec3 hitPoint;
t_vec3 cp;
float proj;
t_vec3 n;
d = ray.dir;
oc = vec3_sub(ray.origin, cy.center);
v = cy.axis;
d_dot_v = vec3_dot(d, v);
oc_dot_v = vec3_dot(oc, v);
d_perp = vec3_sub(d, vec3_scale(v, d_dot_v));
oc_perp = vec3_sub(oc, vec3_scale(v, oc_dot_v));
a = vec3_dot(d_perp, d_perp);
b = 2 * vec3_dot(d_perp, oc_perp);
c = vec3_dot(oc_perp, oc_perp) - cy.radius * cy.radius;
disc = b * b - 4 * a * c;
if (disc < 0)
return (-1);
sqrtDisc = sqrtf(disc);
t0 = (-b - sqrtDisc) / (2 * a);
t1 = (-b + sqrtDisc) / (2 * a);
t_side = -1;
if (t0 > 1e-3f)
{
y = oc_dot_v + t0 * d_dot_v;
if (fabs(y) <= cy.height / 2.0f)
t_side = t0;
}
if (t_side < 0 && t1 > 1e-3f)
{
y = oc_dot_v + t1 * d_dot_v;
if (fabs(y) <= cy.height / 2.0f)
t_side = t1;
}
t_cap = -1;
if (fabs(d_dot_v) > 1e-6f)
{
t_bot = ((-cy.height / 2.0f) - oc_dot_v) / d_dot_v;
if (t_bot > 1e-3f)
{
p = vec3_add(ray.origin, vec3_scale(d, t_bot));
cp = vec3_sub(p, cy.center);
dist = vec3_length(vec3_sub(cp, vec3_scale(v, vec3_dot(cp, v))));
if (dist <= cy.radius)
t_cap = t_bot;
}
t_top = ((cy.height / 2.0f) - oc_dot_v) / d_dot_v;
if (t_top > 1e-3f)
{
p = vec3_add(ray.origin, vec3_scale(d, t_top));
cp = vec3_sub(p, cy.center);
dist = vec3_length(vec3_sub(cp, vec3_scale(v, vec3_dot(cp, v))));
if (dist <= cy.radius && (t_cap < 0 || t_top < t_cap))
t_cap = t_top;
}
}
t_final = -1;
if (t_side > 1e-3f && t_cap > 1e-3f)
t_final = (t_side < t_cap) ? t_side : t_cap;
else if (t_side > 1e-3f)
t_final = t_side;
else
t_final = t_cap;
if (t_final < 1e-3f)
return (-1);
hitPoint = vec3_add(ray.origin, vec3_scale(d, t_final));
cp = vec3_sub(hitPoint, cy.center);
proj = vec3_dot(cp, v);
if (fabs(proj) < cy.height / 2.0f - 1e-3f)
{
n = vec3_sub(cp, vec3_scale(v, proj));
*hitNormal = vec3_normalize(n);
}
else
{
*hitNormal = (proj > 0) ? v : vec3_scale(v, -1);
}
return t_final;
}
// ----- Test d'ombre -----
bool isInShadow(t_vec3 hitPoint, t_vec3 lightPos)
{
t_vec3 toLight;
float maxT;
t_vec3 L;
const float epsilon = 1e-3f;
Ray shadowRay;
t_vec3 dummy;
float t;
toLight = vec3_sub(lightPos, hitPoint);
maxT = vec3_length(toLight);
L = vec3_normalize(toLight);
shadowRay = {vec3_add(hitPoint, vec3_scale(L, epsilon)), L};
for (int i = 0; i < numSpheres; i++)
{
t = intersectSphere(shadowRay, spheres[i], &dummy);
if (t > epsilon && t < maxT)
return true;
}
for (int i = 0; i < numPlanes; i++)
{
t = intersectPlane(shadowRay, planes[i], &dummy);
if (t > epsilon && t < maxT)
return true;
}
for (int i = 0; i < numCylinders; i++)
{
t = intersectCylinder(shadowRay, cylinders[i], &dummy);
if (t > epsilon && t < maxT)
return true;
}
return false;
}
// ----- Lancer de rayon (ray tracing) -----
t_vec3 trace(Ray ray)
{
float tMin;
t_vec3 hitNormal;
t_vec3 objColor;
bool hitSomething;
float t;
t_vec3 n;
t_vec3 n;
t_vec3 n;
t_vec3 hitPoint;
t_vec3 finalColor;
Light light;
t_vec3 L;
float diff;
t_vec3 viewDir;
t_vec3 halfDir;
float spec;
tMin = 1e9;
hitNormal = {0, 0, 0};
objColor = {0, 0, 0};
hitSomething = false;
for (int i = 0; i < numSpheres; i++)
{
t = intersectSphere(ray, spheres[i], &n);
if (t > 1e-3f && t < tMin)
{
tMin = t;
hitNormal = n;
objColor = spheres[i].color;
hitSomething = true;
}
}
for (int i = 0; i < numPlanes; i++)
{
t = intersectPlane(ray, planes[i], &n);
if (t > 1e-3f && t < tMin)
{
tMin = t;
hitNormal = n;
objColor = planes[i].color;
hitSomething = true;
}
}
for (int i = 0; i < numCylinders; i++)
{
t = intersectCylinder(ray, cylinders[i], &n);
if (t > 1e-3f && t < tMin)
{
tMin = t;
hitNormal = n;
objColor = cylinders[i].color;
hitSomething = true;
}
}
if (hitSomething)
{
hitPoint = vec3_add(ray.origin, vec3_scale(ray.dir, tMin));
finalColor = vec3_scale(objColor, ambient_ratio);
for (int i = 0; i < numLights; i++)
{
light = lights[i];
L = vec3_normalize(vec3_sub(light.pos, hitPoint));
if (isInShadow(hitPoint, light.pos))
continue ;
diff = fmaxf(0.0f, vec3_dot(hitNormal, L));
viewDir = vec3_normalize(vec3_sub(camPos, hitPoint));
halfDir = vec3_normalize(vec3_add(L, viewDir));
spec = powf(fmaxf(0.0f, vec3_dot(hitNormal, halfDir)), 32.0f);
finalColor = vec3_add(finalColor, vec3_scale(vec3_mul(objColor,
light.color), diff * light.brightness));
finalColor = vec3_add(finalColor, vec3_scale((t_vec3){1, 1, 1}, spec
* light.brightness));
}
if (finalColor.x > 1.0f)
finalColor.x = 1.0f;
if (finalColor.y > 1.0f)
finalColor.y = 1.0f;
if (finalColor.z > 1.0f)
finalColor.z = 1.0f;
return finalColor;
}
return (t_vec3){0.2f, 0.7f, 1.0f}; // Fond (ciel)
}
// ----- Main -----
int main(int argc, char *argv[])
{
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Texture *texture;
Uint32 *pixels;
bool running;
SDL_Event event;
const float moveSpeed = 0.5f;
const float rotSpeed = 0.03f;
const float mouseSensitivity = 0.002f;
bool key_w = false, key_s = false, key_a = false, key_d;
bool key_left = false, key_right = false, key_up = false,
key_down;
t_vec3 right;
t_vec3 camUp;
float ndc_x;
float ndc_y;
float aspect;
float scale;
float screen_x;
float screen_y;
t_vec3 rayDir;
Ray ray;
t_vec3 color;
Uint8 r;
Uint8 g;
Uint8 b;
// Chargement de la config si un fichier est passé en argument
if (argc > 1)
load_config(argv[1]);
// Scène par défaut si aucune config n'a été chargée
if (numSpheres == 0 && numPlanes == 0 && numCylinders == 0
&& numLights == 0)
{
spheres[0].center = (t_vec3){0, 0, 0};
spheres[0].radius = 1.0f;
spheres[0].color = (t_vec3){1, 0, 0};
numSpheres = 1;
planes[0].point = (t_vec3){0, -5, 0};
planes[0].normal = (t_vec3){0, 1, 0};
planes[0].color = (t_vec3){0, 1, 0};
numPlanes = 1;
cylinders[0].center = (t_vec3){10, 0, 0};
cylinders[0].axis = (t_vec3){0, 1, 0};
cylinders[0].radius = 4.0f;
cylinders[0].height = 20.0f;
cylinders[0].color = (t_vec3){0, 0, 1};
numCylinders = 1;
lights[0].pos = (t_vec3){0, 50, 20};
lights[0].brightness = 1.0f;
lights[0].color = (t_vec3){1, 1, 1};
numLights = 1;
ambient_ratio = 0.3f;
ambient_color = (t_vec3){1, 1, 1};
camPos = (t_vec3){0, 0, 20};
camDir = vec3_normalize((t_vec3){0, 0, -1});
fov = 70.0f;
yaw = atan2f(camDir.x, -camDir.z);
pitch = asinf(camDir.y);
}
if (SDL_Init(SDL_INIT_VIDEO) < 0)
{
fprintf(stderr, "Erreur SDL_Init: %s\n", SDL_GetError());
return 1;
}
window = SDL_CreateWindow("Raytracer interactif", SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED, WIDTH * 2, HEIGHT * 2, SDL_WINDOW_SHOWN);
if (!window)
{
fprintf(stderr, "Erreur SDL_CreateWindow: %s\n", SDL_GetError());
SDL_Quit();
return 1;
}
SDL_SetRelativeMouseMode(SDL_TRUE);
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!renderer)
{
fprintf(stderr, "Erreur SDL_CreateRenderer: %s\n", SDL_GetError());
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
if (!texture)
{
fprintf(stderr, "Erreur SDL_CreateTexture: %s\n", SDL_GetError());
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
pixels = malloc(WIDTH * HEIGHT * sizeof(Uint32));
if (!pixels)
{
fprintf(stderr, "Erreur d'allocation mémoire.\n");
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
running = true;
key_w = false, key_s = false, key_a = false, key_d = false;
key_left = false, key_right = false, key_up = false, key_down = false;
while (running)
{
while (SDL_PollEvent(&event))
{
if (event.type == SDL_QUIT)
running = false;
else if (event.type == SDL_KEYDOWN)
{
switch (event.key.keysym.sym)
{
case SDLK_ESCAPE:
running = false;
break ;
case SDLK_w:
key_w = true;
break ;
case SDLK_s:
key_s = true;
break ;
case SDLK_a:
key_a = true;
break ;
case SDLK_d:
key_d = true;
break ;
case SDLK_LEFT:
key_left = true;
break ;
case SDLK_RIGHT:
key_right = true;
break ;
case SDLK_UP:
key_up = true;
break ;
case SDLK_DOWN:
key_down = true;
break ;
default:
break ;
}
}
else if (event.type == SDL_KEYUP)
{
switch (event.key.keysym.sym)
{
case SDLK_w:
key_w = false;
break ;
case SDLK_s:
key_s = false;
break ;
case SDLK_a:
key_a = false;
break ;
case SDLK_d:
key_d = false;
break ;
case SDLK_LEFT:
key_left = false;
break ;
case SDLK_RIGHT:
key_right = false;
break ;
case SDLK_UP:
key_up = false;
break ;
case SDLK_DOWN:
key_down = false;
break ;
default:
break ;
}
}
else if (event.type == SDL_MOUSEMOTION)
{
yaw += event.motion.xrel * mouseSensitivity;
pitch -= event.motion.yrel * mouseSensitivity;
}
}
if (key_left)
yaw -= rotSpeed;
if (key_right)
yaw += rotSpeed;
if (key_up)
pitch += rotSpeed;
if (key_down)
pitch -= rotSpeed;
if (pitch > 1.57f - 0.01f)
pitch = 1.57f - 0.01f;
if (pitch < -1.57f + 0.01f)
pitch = -1.57f + 0.01f;
camDir.x = cosf(pitch) * sinf(yaw);
camDir.y = sinf(pitch);
camDir.z = -cosf(pitch) * cosf(yaw);
camDir = vec3_normalize(camDir);
right = {cosf(yaw), 0, sinf(yaw)};
right = vec3_normalize(right);
camUp = vec3_normalize(vec3_cross(right, camDir));
if (key_w)
camPos = vec3_add(camPos, vec3_scale(camDir, moveSpeed));
if (key_s)
camPos = vec3_sub(camPos, vec3_scale(camDir, moveSpeed));
if (key_a)
camPos = vec3_sub(camPos, vec3_scale(right, moveSpeed));
if (key_d)
camPos = vec3_add(camPos, vec3_scale(right, moveSpeed));
for (int y = 0; y < HEIGHT; y++)
{
for (int x = 0; x < WIDTH; x++)
{
ndc_x = (x + 0.5f) / WIDTH;
ndc_y = (y + 0.5f) / HEIGHT;
aspect = (float)WIDTH / HEIGHT;
scale = tanf((fov * 0.5f) * (M_PI / 180.0f));
screen_x = (2 * ndc_x - 1) * aspect * scale;
screen_y = (1 - 2 * ndc_y) * scale;
rayDir = vec3_add(camDir, vec3_add(vec3_scale(right, screen_x),
vec3_scale(camUp, screen_y)));
rayDir = vec3_normalize(rayDir);
ray = {camPos, rayDir};
color = trace(ray);
r = (Uint8)(fminf(color.x, 1.0f) * 255);
g = (Uint8)(fminf(color.y, 1.0f) * 255);
b = (Uint8)(fminf(color.z, 1.0f) * 255);
pixels[y * WIDTH + x] = (255 << 24) | (r << 16) | (g << 8) | b;
}
}
SDL_UpdateTexture(texture, NULL, pixels, WIDTH * sizeof(Uint32));
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
}
free(pixels);
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}