From c02be0fa2fc597f139d85d774b77e3df67817b7d Mon Sep 17 00:00:00 2001 From: Matthew Jakeman Date: Fri, 20 Nov 2020 01:51:58 +1300 Subject: [PATCH] Fix building with gcc --- tinyray.c | 898 +++++++++++++++++++++++++++--------------------------- 1 file changed, 449 insertions(+), 449 deletions(-) diff --git a/tinyray.c b/tinyray.c index 51adaf2..0bdcb3e 100644 --- a/tinyray.c +++ b/tinyray.c @@ -1,450 +1,450 @@ -// ENGGEN131 (2020) - Lab 9 (5th - 9th October, 2020) -// EXERCISE SIX - Da Vinci Code -// -// tinyray.c - A raytracer in exactly 400 lines of C -// Author: Matthew Jakeman -// -// Entirely original code, inspired by the following resources: -// - https://github.com/ssloy/tinyraytracer -// - https://www.gabrielgambetta.com/computer-graphics-from-scratch/basic-ray-tracing.html -// - https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-sphere-intersection - -#define STB_IMAGE_WRITE_IMPLEMENTATION -#include "stb_image_write.h" -#include - -typedef unsigned char pixel; - -/* Minimal Floating Point Vector Maths - Author: Matthew Jakeman */ -typedef struct { - float x; - float y; - float z; -} Vec3; - -Vec3 vec3_new(float x, float y, float z) { - Vec3 vec; - vec.x = x; - vec.y = y; - vec.z = z; - return vec; -} - -Vec3 vec3_add(Vec3 a, Vec3 b) { - return vec3_new(a.x + b.x, a.y + b.y, a.z + b.z); -} - -Vec3 vec3_subtract(Vec3 a, Vec3 b) { - return vec3_new(a.x - b.x, a.y - b.y, a.z - b.z); -} - -Vec3 vec3_subtract_scalar(Vec3 a, float b) { - return vec3_new(a.x - b, a.y - b, a.z - b); -} - -Vec3 vec3_divide_scalar(Vec3 a, float b) { - return vec3_new(a.x/b, a.y/b, a.z/b); -} - -Vec3 vec3_multiply(Vec3 a, Vec3 b) { - return vec3_new(a.x*b.x, a.y*b.y, a.z*b.z); -} - -Vec3 vec3_multiply_scalar(Vec3 a, float b) { - return vec3_new(a.x*b, a.y*b, a.z*b); -} - -float vec3_dot(Vec3 a, Vec3 b) { - return a.x*b.x + a.y*b.y + a.z*b.z; -} - -float vec3_len(Vec3 v) { - return (float)sqrtf((v.x * v.x) + (v.y * v.y) + (v.z * v.z)); -} - -Vec3 vec3_normalise(Vec3 v) { - return vec3_divide_scalar(v, vec3_len(v)); // Calculate unit vector -} - -Vec3 vec3_clamp(Vec3 v, float min_value, float max_value) { - return vec3_new( - min(max(v.x, min_value), max_value), - min(max(v.y, min_value), max_value), - min(max(v.z, min_value), max_value) - ); -} -// END - vectors - -/* Struct Definitions */ -typedef Vec3 RgbColour; - -typedef struct { - RgbColour diffuse; - float specular; -} Material; - -typedef struct { - Vec3 centre; - float radius; - Material material; -} Sphere; - -typedef struct { - Vec3 origin; - Vec3 direction; -} Ray; - -typedef enum { - PointLight, - DirectionalLight, - AmbientLight -} LightType; - -typedef struct { - LightType type; - Vec3 position; // point only - Vec3 direction; // directional only - float intensity; -} Light; - -/* Globals */ -const static Vec3 Zero = {0}; -const static Vec3 Invalid = {-1, -1, -1}; -const static int FOV = 1; //3.1415/2; -const static int MAX_DIST = 1000; -const static int PROJ_DIST = 1; -const static unsigned int WIDTH = 600; -const static unsigned int HEIGHT = 600; - -/* Constructors */ -Sphere sphere_new(Vec3 centre, float radius, Material material) { - Sphere sphere = {0}; - sphere.centre = centre; - sphere.radius = radius; - sphere.material = material; - return sphere; -} - -Light light_point_new(float intensity, Vec3 position) { - Light light = {0}; - light.type = PointLight; - light.position = position; - light.intensity = intensity; - return light; -} - -Light light_ambient_new(float intensity) { - Light light = {0}; - light.type = AmbientLight; - light.intensity = intensity; - return light; -} - -Light light_directional_new(float intensity, Vec3 direction) { - Light light = {0}; - light.type = DirectionalLight; - light.direction = direction; - light.intensity = intensity; - return light; -} - -Material material_new(Vec3 diffuse, float specular) { - Material material = {0}; - material.diffuse = diffuse; - material.specular = specular; - return material; -} - -Ray ray_new(Vec3 origin, Vec3 direction) { - Ray ray; - ray.origin = origin; - ray.direction = direction; - return ray; -} - -// Lighting Compute Algorithm -// Adapted from https://www.gabrielgambetta.com/ -// -// ARGS: -// - point = point of intersection -// - normal = normal vector at point -// RETURNS: -// - intensity of light -float lighting_compute(Vec3 point, Vec3 normal, - Vec3 view, float specular, - Light *lights, int num_lights) { - - // Intensity of light for the given pixel - float intensity = 0.0f; - - // Iterate over lights - for (int i = 0; i < num_lights; i++) - { - Light *light = &lights[i]; - if (light->type == AmbientLight) - { - // Simply add ambient light to total - intensity += light->intensity; - } - else - { - Vec3 light_ray; - if (light->type == PointLight) - // Point Light: Direction of ray from light to point - light_ray = vec3_subtract(light->position, point); - else - // Directional Light: Direction - light_ray = light->direction; - - // Diffuse - float reflect = vec3_dot(normal, light_ray); - intensity += (light->intensity * reflect)/(vec3_len(normal) * vec3_len(light_ray)); - - // Specular - if (specular != -1) - { - Vec3 r = vec3_subtract(vec3_multiply_scalar(normal, 2 * vec3_dot(normal, light_ray)), light_ray); - float reflect_view_proj = vec3_dot(r, view); - if (reflect_view_proj > 0) - { - float cosine = reflect_view_proj/(vec3_len(r) * vec3_len(view)); - intensity += light->intensity * powf(cosine, specular); - } - } - } - } - - return intensity; -} - -// Sphere-Ray Intersection -// Returns 1 if intersection found, otherwise 0 -// -// ARGS: -// - sphere = sphere to intersect -// - ray = description of ray properties (e.g. direction) -// RETURNS: -// - boolean of whether ray intersected a sphere -// - [out] dist0, dist 1 = perpendicular distances to points of intersection -int do_sphere_raycast(Sphere sphere, Ray ray, float *dist0, float *dist1) { - - // Please see sphere_ray_intersection.bmp - *dist0 = 0; - *dist1 = 0; - - // Find L and tca - Vec3 L = vec3_subtract(sphere.centre, ray.origin); - float tca = vec3_dot(L, ray.direction); - - // Discard if intersection is behind origin - if (tca < 0) - return 0; - - // Find d - float d = sqrtf(vec3_dot(L, L) - tca * tca); - if (d > sphere.radius) - return 0; - - // Calculate thc using pythagoras - float thc = sqrtf(sphere.radius * sphere.radius - d * d); - - // Calculate t0 and t1 (perpendicular distance to - // the 0th and 1st intersection) - float t0 = tca - thc; - float t1 = tca + thc; - - // Ensure at least one of t0 and t1 is greater than zero - if (t0 < 0 && t1 < 0) - return 0; - - *dist0 = t0; - *dist1 = t1; - - return 1; // Intersection found -} - -// Raytrace Scene at Point -// -// ARGS: -// - origin = location of camera -// - dir = direction of projectile -// - min_t, max_t = min and max clipping planes -// - spheres, num_spheres = array of spheres to test -// - lights, num_lights = array of lights in the scene -// RETURNS: -// - rgb colour of pixel being raytraced -RgbColour raytrace(Vec3 origin, Vec3 dir, float min_t, float max_t, - Sphere *spheres, int num_spheres, - Light *lights, int num_lights) { - - // Closest sphere to screen (for depth-testing) - Sphere *closest = 0; - - // We use t_comp to store the t-depth of the closest - // sphere and compare it with other spheres to perform - // primitive depth testing (where 't' is perpendicular - // distance to the point of intersection) - float t_comp = (float)MAX_DIST; - - // Ray to test - Ray ray = ray_new(origin, dir); - - // Cycle through all spheres and depth-test - for (int i = 0; i < num_spheres; i++) - { - float dist0, dist1; - if (do_sphere_raycast(spheres[i], ray, &dist0, &dist1)) - { - // Check dist0 - if ((min_t < dist0 && dist0 < max_t) && - dist0 < t_comp) - { - t_comp = dist0; - closest = &spheres[i]; - } - - // Now check dist1 - if ((min_t < dist1 && dist1 < max_t) && - dist1 < t_comp) - { - t_comp = dist1; - closest = &spheres[i]; - } - } - } - - if (!closest) - return Invalid; - - Vec3 point = vec3_add(origin, vec3_multiply_scalar(dir, t_comp)); - Vec3 normal = vec3_normalise(vec3_subtract(point, closest->centre)); - Material material = closest->material; - - return vec3_clamp( - vec3_multiply_scalar( - material.diffuse, - lighting_compute( - point, normal, - vec3_multiply_scalar(dir, -1), - material.specular, - lights, num_lights - ) - ), - 0.0f, 255.0f // Clamp between 0 and 255 - ); -} - -// Watermark: Says "MATT J" -#define MARK_COLS 33 -#define MARK_ROWS 7 -int mark[MARK_ROWS][MARK_COLS] = { - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0}, - {0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}, - {0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}, - {0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}, - {0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -}; - -// Draws a watermark on the screen using the above array -// NOTE: Make sure size and stride do not cause the function to exceed -// array bounds. This will cause a crash! -void DrawWatermark(pixel *data) { - int start_x = 20; - int start_y = 540; - int size = 3, stride = 1; - - for (int i = 0; i < MARK_COLS; i++) { - for (int j = 0; j < MARK_ROWS; j++) { - int y_corner = start_y + j*(size*2 + stride); - int x_corner = start_x + i*(size*2 + stride); - - // Draw Square - for (int x = x_corner; x < (x_corner + size); x++) { - for (int y = y_corner; y < (y_corner + size); y++) { - if (mark[j][i] == 0) { - data[(y*WIDTH + x) * 3 + 0] = (pixel)(i/(float)MARK_COLS * 255) % 180; - data[(y*WIDTH + x) * 3 + 1] = (pixel)(j/(float)MARK_ROWS * 255) % 180; - data[(y*WIDTH + x) * 3 + 2] = (pixel)240; - } - else { - data[(y*WIDTH + x) * 3 + 0] = (pixel)255; - data[(y*WIDTH + x) * 3 + 1] = (pixel)255; - data[(y*WIDTH + x) * 3 + 2] = (pixel)255; - } - } - } - } - } -} - -int main(void) -{ - pixel *data = malloc(sizeof(pixel) * 3 * WIDTH * HEIGHT); - - // Materials - Material blue = material_new(vec3_new(69, 161, 255), 500); - Material white = material_new(vec3_new(240, 240, 240), 180); - Material red = material_new(vec3_new(255, 0, 57), 10); - Material ground = material_new(vec3_new(0, 57, 89), 1000); - - // Scene - #define NUM_SPHERES 4 - Sphere spheres[NUM_SPHERES]; - spheres[0] = sphere_new(vec3_new(-0.75f, -0.2f, 6.5f), 1.5f, red); - spheres[1] = sphere_new(vec3_new(0, -1, 5), 1.0f, blue); - spheres[2] = sphere_new(vec3_new(2, -0.5, 8), 3.0f, white); - spheres[3] = sphere_new(vec3_new(0, -4001, 0), 4000, ground); - - // Lights - #define NUM_LIGHTS 3 - Light lights[NUM_LIGHTS]; - lights[0] = light_ambient_new(0.2f); - lights[1] = light_point_new(0.6f, vec3_new(-8, 1, 0)); - lights[2] = light_directional_new(0.2f, vec3_new(1, 4, -8)); - - // For non-square images (future-proofing?) - float aspect_ratio = (float)WIDTH/(float)HEIGHT; - float screen_dim = tanf(FOV / (float)2); - - Vec3 origin = Zero; - - // Render - for (int x = 0; x < WIDTH; x++) { - for (int y = 0; y < HEIGHT; y++) { - - // Background - data[(y*WIDTH + x) * 3 + 0] = (pixel)(y/(float)WIDTH * 255); - data[(y*WIDTH + x) * 3 + 1] = (pixel)(x/(float)HEIGHT * 255); - data[(y*WIDTH + x) * 3 + 2] = (pixel)160; - - // Get Pixel in World Coords - float x_world_coord = (2*(x + 0.5f)/(float)HEIGHT - 1) * screen_dim * aspect_ratio; - float y_world_coord = -(2*(y + 0.5f)/(float)WIDTH - 1) * screen_dim; - Vec3 dir = vec3_normalise(vec3_new(x_world_coord, y_world_coord, 1)); - - // Raytrace Pixel - RgbColour colour = raytrace(origin, dir, 1.0f, (float)MAX_DIST, - spheres, NUM_SPHERES, - lights, NUM_LIGHTS); - - // Draw Geometry - if (colour.x != -1) { - data[(y*WIDTH + x) * 3 + 0] = (pixel)colour.x; - data[(y*WIDTH + x) * 3 + 1] = (pixel)colour.y; - data[(y*WIDTH + x) * 3 + 2] = (pixel)colour.z; - } - } - } - - // Output - DrawWatermark(data); - - // Write to file - printf("tinyray: writing to file!"); - if (!stbi_write_bmp("output.bmp", WIDTH, HEIGHT, 3, data)) - printf("tinyray: failed to write image!"); - - return 0; +// ENGGEN131 (2020) - Lab 9 (5th - 9th October, 2020) +// EXERCISE SIX - Da Vinci Code +// +// tinyray.c - A raytracer in exactly 400 lines of C +// Author: Matthew Jakeman +// +// Entirely original code, inspired by the following resources: +// - https://github.com/ssloy/tinyraytracer +// - https://www.gabrielgambetta.com/computer-graphics-from-scratch/basic-ray-tracing.html +// - https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-sphere-intersection + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" +#include + +// A single byte-type representing one channel of a pixel +typedef unsigned char byte; + +/* Minimal Floating Point Vector Maths - Author: Matthew Jakeman */ +typedef struct { + float x; + float y; + float z; +} Vec3; + +Vec3 vec3_new(float x, float y, float z) { + Vec3 vec; + vec.x = x; + vec.y = y; + vec.z = z; + return vec; +} + +Vec3 vec3_add(Vec3 a, Vec3 b) { + return vec3_new(a.x + b.x, a.y + b.y, a.z + b.z); +} + +Vec3 vec3_subtract(Vec3 a, Vec3 b) { + return vec3_new(a.x - b.x, a.y - b.y, a.z - b.z); +} + +Vec3 vec3_subtract_scalar(Vec3 a, float b) { + return vec3_new(a.x - b, a.y - b, a.z - b); +} + +Vec3 vec3_divide_scalar(Vec3 a, float b) { + return vec3_new(a.x/b, a.y/b, a.z/b); +} + +Vec3 vec3_multiply(Vec3 a, Vec3 b) { + return vec3_new(a.x*b.x, a.y*b.y, a.z*b.z); +} + +Vec3 vec3_multiply_scalar(Vec3 a, float b) { + return vec3_new(a.x*b, a.y*b, a.z*b); +} + +float vec3_dot(Vec3 a, Vec3 b) { + return a.x*b.x + a.y*b.y + a.z*b.z; +} + +float vec3_len(Vec3 v) { + return (float)sqrtf((v.x * v.x) + (v.y * v.y) + (v.z * v.z)); +} + +Vec3 vec3_normalise(Vec3 v) { + return vec3_divide_scalar(v, vec3_len(v)); // Calculate unit vector +} + +Vec3 vec3_clamp(Vec3 v, float min_value, float max_value) { + return vec3_new( + fmin(fmax(v.x, min_value), max_value), + fmin(fmax(v.y, min_value), max_value), + fmin(fmax(v.z, min_value), max_value) + ); +} +// END - vectors + +/* Struct Definitions */ +typedef Vec3 RgbColour; + +typedef struct { + RgbColour diffuse; + float specular; +} Material; + +typedef struct { + Vec3 centre; + float radius; + Material material; +} Sphere; + +typedef struct { + Vec3 origin; + Vec3 direction; +} Ray; + +typedef enum { + PointLight, + DirectionalLight, + AmbientLight +} LightType; + +typedef struct { + LightType type; + Vec3 position; // point only + Vec3 direction; // directional only + float intensity; +} Light; + +/* Globals */ +const static Vec3 Zero = {0}; +const static Vec3 Invalid = {-1, -1, -1}; +const static int FOV = 1; //3.1415/2; +const static int MAX_DIST = 1000; +const static unsigned int WIDTH = 600; +const static unsigned int HEIGHT = 600; + +/* Constructors */ +Sphere sphere_new(Vec3 centre, float radius, Material material) { + Sphere sphere = {0}; + sphere.centre = centre; + sphere.radius = radius; + sphere.material = material; + return sphere; +} + +Light light_point_new(float intensity, Vec3 position) { + Light light = {0}; + light.type = PointLight; + light.position = position; + light.intensity = intensity; + return light; +} + +Light light_ambient_new(float intensity) { + Light light = {0}; + light.type = AmbientLight; + light.intensity = intensity; + return light; +} + +Light light_directional_new(float intensity, Vec3 direction) { + Light light = {0}; + light.type = DirectionalLight; + light.direction = direction; + light.intensity = intensity; + return light; +} + +Material material_new(Vec3 diffuse, float specular) { + Material material = {0}; + material.diffuse = diffuse; + material.specular = specular; + return material; +} + +Ray ray_new(Vec3 origin, Vec3 direction) { + Ray ray; + ray.origin = origin; + ray.direction = direction; + return ray; +} + +// Lighting Compute Algorithm +// Adapted from https://www.gabrielgambetta.com/ +// +// ARGS: +// - point = point of intersection +// - normal = normal vector at point +// RETURNS: +// - intensity of light +float lighting_compute(Vec3 point, Vec3 normal, + Vec3 view, float specular, + Light *lights, int num_lights) { + + // Intensity of light for the given pixel + float intensity = 0.0f; + + // Iterate over lights + for (int i = 0; i < num_lights; i++) + { + Light *light = &lights[i]; + if (light->type == AmbientLight) + { + // Simply add ambient light to total + intensity += light->intensity; + } + else + { + Vec3 light_ray; + if (light->type == PointLight) + // Point Light: Direction of ray from light to point + light_ray = vec3_subtract(light->position, point); + else + // Directional Light: Direction + light_ray = light->direction; + + // Diffuse + float reflect = vec3_dot(normal, light_ray); + intensity += (light->intensity * reflect)/(vec3_len(normal) * vec3_len(light_ray)); + + // Specular + if (specular != -1) + { + Vec3 r = vec3_subtract(vec3_multiply_scalar(normal, 2 * vec3_dot(normal, light_ray)), light_ray); + float reflect_view_proj = vec3_dot(r, view); + if (reflect_view_proj > 0) + { + float cosine = reflect_view_proj/(vec3_len(r) * vec3_len(view)); + intensity += light->intensity * powf(cosine, specular); + } + } + } + } + + return intensity; +} + +// Sphere-Ray Intersection +// Returns 1 if intersection found, otherwise 0 +// +// ARGS: +// - sphere = sphere to intersect +// - ray = description of ray properties (e.g. direction) +// RETURNS: +// - boolean of whether ray intersected a sphere +// - [out] dist0, dist 1 = perpendicular distances to points of intersection +int do_sphere_raycast(Sphere sphere, Ray ray, float *dist0, float *dist1) { + + // Please see sphere_ray_intersection.bmp + *dist0 = 0; + *dist1 = 0; + + // Find L and tca + Vec3 L = vec3_subtract(sphere.centre, ray.origin); + float tca = vec3_dot(L, ray.direction); + + // Discard if intersection is behind origin + if (tca < 0) + return 0; + + // Find d + float d = sqrtf(vec3_dot(L, L) - tca * tca); + if (d > sphere.radius) + return 0; + + // Calculate thc using pythagoras + float thc = sqrtf(sphere.radius * sphere.radius - d * d); + + // Calculate t0 and t1 (perpendicular distance to + // the 0th and 1st intersection) + float t0 = tca - thc; + float t1 = tca + thc; + + // Ensure at least one of t0 and t1 is greater than zero + if (t0 < 0 && t1 < 0) + return 0; + + *dist0 = t0; + *dist1 = t1; + + return 1; // Intersection found +} + +// Raytrace Scene at Point +// +// ARGS: +// - origin = location of camera +// - dir = direction of projectile +// - min_t, max_t = min and max clipping planes +// - spheres, num_spheres = array of spheres to test +// - lights, num_lights = array of lights in the scene +// RETURNS: +// - rgb colour of pixel being raytraced +RgbColour raytrace(Vec3 origin, Vec3 dir, float min_t, float max_t, + Sphere *spheres, int num_spheres, + Light *lights, int num_lights) { + + // Closest sphere to screen (for depth-testing) + Sphere *closest = 0; + + // We use t_comp to store the t-depth of the closest + // sphere and compare it with other spheres to perform + // primitive depth testing (where 't' is perpendicular + // distance to the point of intersection) + float t_comp = (float)MAX_DIST; + + // Ray to test + Ray ray = ray_new(origin, dir); + + // Cycle through all spheres and depth-test + for (int i = 0; i < num_spheres; i++) + { + float dist0, dist1; + if (do_sphere_raycast(spheres[i], ray, &dist0, &dist1)) + { + // Check dist0 + if ((min_t < dist0 && dist0 < max_t) && + dist0 < t_comp) + { + t_comp = dist0; + closest = &spheres[i]; + } + + // Now check dist1 + if ((min_t < dist1 && dist1 < max_t) && + dist1 < t_comp) + { + t_comp = dist1; + closest = &spheres[i]; + } + } + } + + if (!closest) + return Invalid; + + Vec3 point = vec3_add(origin, vec3_multiply_scalar(dir, t_comp)); + Vec3 normal = vec3_normalise(vec3_subtract(point, closest->centre)); + Material material = closest->material; + + return vec3_clamp( + vec3_multiply_scalar( + material.diffuse, + lighting_compute( + point, normal, + vec3_multiply_scalar(dir, -1), + material.specular, + lights, num_lights + ) + ), + 0.0f, 255.0f // Clamp between 0 and 255 + ); +} + +// Watermark: Says "MATT J" +#define MARK_COLS 33 +#define MARK_ROWS 7 +int mark[MARK_ROWS][MARK_COLS] = { + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0}, + {0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}, + {0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}, + {0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}, + {0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +}; + +// Draws a watermark on the screen using the above array +// NOTE: Make sure size and stride do not cause the function to exceed +// array bounds. This will cause a crash! +void DrawWatermark(byte *data) { + int start_x = 20; + int start_y = 540; + int size = 3, stride = 1; + + for (int i = 0; i < MARK_COLS; i++) { + for (int j = 0; j < MARK_ROWS; j++) { + int y_corner = start_y + j*(size*2 + stride); + int x_corner = start_x + i*(size*2 + stride); + + // Draw Square + for (int x = x_corner; x < (x_corner + size); x++) { + for (int y = y_corner; y < (y_corner + size); y++) { + if (mark[j][i] == 0) { + data[(y*WIDTH + x) * 3 + 0] = (byte)(i/(float)MARK_COLS * 255) % 180; + data[(y*WIDTH + x) * 3 + 1] = (byte)(j/(float)MARK_ROWS * 255) % 180; + data[(y*WIDTH + x) * 3 + 2] = (byte)240; + } + else { + data[(y*WIDTH + x) * 3 + 0] = (byte)255; + data[(y*WIDTH + x) * 3 + 1] = (byte)255; + data[(y*WIDTH + x) * 3 + 2] = (byte)255; + } + } + } + } + } +} + +int main(void) +{ + byte *data = malloc(sizeof(byte) * 3 * WIDTH * HEIGHT); + + // Materials + Material blue = material_new(vec3_new(69, 161, 255), 500); + Material white = material_new(vec3_new(240, 240, 240), 180); + Material red = material_new(vec3_new(255, 0, 57), 10); + Material ground = material_new(vec3_new(0, 57, 89), 1000); + + // Scene + #define NUM_SPHERES 4 + Sphere spheres[NUM_SPHERES]; + spheres[0] = sphere_new(vec3_new(-0.75f, -0.2f, 6.5f), 1.5f, red); + spheres[1] = sphere_new(vec3_new(0, -1, 5), 1.0f, blue); + spheres[2] = sphere_new(vec3_new(2, -0.5, 8), 3.0f, white); + spheres[3] = sphere_new(vec3_new(0, -4001, 0), 4000, ground); + + // Lights + #define NUM_LIGHTS 3 + Light lights[NUM_LIGHTS]; + lights[0] = light_ambient_new(0.2f); + lights[1] = light_point_new(0.6f, vec3_new(-8, 1, 0)); + lights[2] = light_directional_new(0.2f, vec3_new(1, 4, -8)); + + // For non-square images (future-proofing?) + float aspect_ratio = (float)WIDTH/(float)HEIGHT; + float screen_dim = tanf(FOV / (float)2); + + Vec3 origin = Zero; + + // Render + for (int x = 0; x < WIDTH; x++) { + for (int y = 0; y < HEIGHT; y++) { + + // Background + data[(y*WIDTH + x) * 3 + 0] = (byte)(y/(float)WIDTH * 255); + data[(y*WIDTH + x) * 3 + 1] = (byte)(x/(float)HEIGHT * 255); + data[(y*WIDTH + x) * 3 + 2] = (byte)160; + + // Get Pixel in World Coords + float x_world_coord = (2*(x + 0.5f)/(float)HEIGHT - 1) * screen_dim * aspect_ratio; + float y_world_coord = -(2*(y + 0.5f)/(float)WIDTH - 1) * screen_dim; + Vec3 dir = vec3_normalise(vec3_new(x_world_coord, y_world_coord, 1)); + + // Raytrace Pixel + RgbColour colour = raytrace(origin, dir, 1.0f, (float)MAX_DIST, + spheres, NUM_SPHERES, + lights, NUM_LIGHTS); + + // Draw Geometry + if (colour.x != -1) { + data[(y*WIDTH + x) * 3 + 0] = (byte)colour.x; + data[(y*WIDTH + x) * 3 + 1] = (byte)colour.y; + data[(y*WIDTH + x) * 3 + 2] = (byte)colour.z; + } + } + } + + // Output + DrawWatermark(data); + + // Write to file + printf("tinyray: writing to file!"); + if (!stbi_write_bmp("output.bmp", WIDTH, HEIGHT, 3, data)) + printf("tinyray: failed to write image!"); + + return 0; } \ No newline at end of file