Here is the implementation of a rasterizer in pure C. I want you to implement perspective based on the provided FOV, or ensure that perspective is implemented.
```c

/* Implementation of a simple rasterized 3D renderer in pure C without external dependencies.
   This application is single-threaded and draws only triangles to the terminal.
   Shades are determined by brightness based on the distance from the camera and drawn as ASCII characters.
   Colors are not used in distance-based shading.
   This program only uses standard ISO C99 code. */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

/* Screen dimensions */
#define WIDTH 80
#define HEIGHT 40

/* Depth range */
#define ZNEAR 0.1f
#define ZFAR 100.0f

/* Field of view */
#define FOV 90.0f

/* Aspect ratio correction, since characters are taller than they are wide */
#define ASPECT_RATIO_CORRECTION 1.2f

/* Types and basic structures */
typedef unsigned char u8_t;
typedef float f32_t;
typedef int i32_t;
typedef unsigned int u32_t;

typedef struct { u8_t r, g, b, a; } col_t;
typedef struct { f32_t x, y, z; } vec3_t;
typedef struct { f32_t m[3][3]; } mat3_t;
typedef struct { vec3_t p; col_t c; } vert_t;
typedef struct { vert_t a, b, c; } tri_t;
typedef struct { vec3_t p; u8_t a; } light_t;
typedef struct { i32_t x, y; f32_t z; } screen_vert_t;
typedef struct { screen_vert_t a, b, c; } screen_tri_t;

/* Shades are determined by brightness related to distance.
   Closer objects are brighter and use brighter ASCII characters */
const u8_t *shades[] = { " ", ".", ":", "-", "=", "+", "*", "%", "#", "@", "█" };
#define NSHADES (sizeof(shades) / sizeof(shades[0]))

/* Canvas to draw on screen */
col_t fb[HEIGHT][WIDTH]; // Frame buffer
f32_t zb_buffer[HEIGHT][WIDTH]; // Z-buffer
f32_t distance_buffer[HEIGHT][WIDTH]; // Distance buffer

/* Utility functions */
const u8_t *get_shade(f32_t a) {
    if (a < 0.0f) a = 0.0f;
    if (a > 1.0f) a = 1.0f;
    u8_t shade = (u8_t)(a * (NSHADES - 1));
    return shades[shade];
}

void set_color(col_t c) {
    static f32_t error;
    printf("\x1b[38;2;%d;%d;%dm", c.r, c.g, c.b);

    f32_t total_chars = ASPECT_RATIO_CORRECTION + error;
    int full_chars = (int)floorf(total_chars);
    error = total_chars - full_chars;

    for(int i = 0; i < full_chars; ++i)
        printf("%s", get_shade(c.a / 255.0f)); // Correctly map alpha [0,255] to [0,1]
}

/* Advances to the next line and resets color */
void next_line() {
    printf("\x1b[0m\n");
}

/* Clears the terminal screen and moves the cursor to the home position */
void clear_screen(){
    printf("\x1b[2J"); // Clear screen
    printf("\x1b[H");  // Move cursor to home position
}

/* Clears the frame buffer and initializes the Z-buffer and Distance buffer */
void clear_fb() {
    for(u32_t i = 0; i < HEIGHT * WIDTH; i++) {
        ((col_t*)fb)[i] = (col_t){0, 0, 0, 0}; // Black background with zero alpha
        ((f32_t*)zb_buffer)[i] = ZFAR; // Initialize Z-buffer to farthest depth
        ((f32_t*)distance_buffer)[i] = ZFAR; // Initialize Distance buffer to farthest distance
    }
}

/* Function to project 3D point to 2D screen space */
screen_vert_t project_vertex(vec3_t p) {
    screen_vert_t sv;
    // Simple orthographic projection
    sv.x = (int)((p.x + 1.0f) * 0.5f * WIDTH);
    sv.y = (int)((1.0f - (p.y + 1.0f) * 0.5f) * HEIGHT); // Invert Y for screen space
    sv.z = p.z;
    return sv;
}

/* Function to compute barycentric coordinates */
void compute_barycentric(int px, int py, screen_tri_t tri, f32_t *u, f32_t *v, f32_t *w) {
    f32_t denom = (f32_t)((tri.b.y - tri.c.y)*(tri.a.x - tri.c.x) + (tri.c.x - tri.b.x)*(tri.a.y - tri.c.y));
    if (fabsf(denom) < 1e-6f) { // Degenerate triangle
        *u = *v = *w = -1.0f;
        return;
    }
    *u = ((tri.b.y - tri.c.y)*(px - tri.c.x) + (tri.c.x - tri.b.x)*(py - tri.c.y)) / denom;
    *v = ((tri.c.y - tri.a.y)*(px - tri.c.x) + (tri.a.x - tri.c.x)*(py - tri.c.y)) / denom;
    *w = 1.0f - (*u) - (*v);
}

/* Function to rasterize a single triangle using screen_tri_t */
void rasterize_triangle(tri_t tri) {
    // Project vertices to screen space
    screen_tri_t st = {
        .a = project_vertex(tri.a.p),
        .b = project_vertex(tri.b.p),
        .c = project_vertex(tri.c.p)
    };

    // Compute bounding box
    i32_t minX = st.a.x < st.b.x ? (st.a.x < st.c.x ? st.a.x : st.c.x) : (st.b.x < st.c.x ? st.b.x : st.c.x);
    i32_t maxX = st.a.x > st.b.x ? (st.a.x > st.c.x ? st.a.x : st.c.x) : (st.b.x > st.c.x ? st.b.x : st.c.x);
    i32_t minY = st.a.y < st.b.y ? (st.a.y < st.c.y ? st.a.y : st.c.y) : (st.b.y < st.c.y ? st.b.y : st.c.y);
    i32_t maxY = st.a.y > st.b.y ? (st.a.y > st.c.y ? st.a.y : st.c.y) : (st.b.y > st.c.y ? st.b.y : st.c.y);

    // Clamp to screen dimensions
    if(minX < 0) minX = 0;
    if(maxX >= WIDTH) maxX = WIDTH -1;
    if(minY < 0) minY = 0;
    if(maxY >= HEIGHT) maxY = HEIGHT -1;

    // Iterate over the bounding box
    for(int y = minY; y <= maxY; y++) {
        for(int x = minX; x <= maxX; x++) {
            f32_t u, v, w;
            compute_barycentric(x + 0.5f, y + 0.5f, st, &u, &v, &w);

            // Check if inside the triangle
            if(u >= 0.0f && v >= 0.0f && w >= 0.0f) {
                // Interpolate depth (z)
                f32_t depth = u * st.a.z + v * st.b.z + w * st.c.z;

                // Z-buffer test
                if(depth < zb_buffer[y][x]) {
                    zb_buffer[y][x] = depth;

                    // Interpolate 3D position
                    vec3_t pos = {
                        .x = u * tri.a.p.x + v * tri.b.p.x + w * tri.c.p.x,
                        .y = u * tri.a.p.y + v * tri.b.p.y + w * tri.c.p.y,
                        .z = u * tri.a.p.z + v * tri.b.p.z + w * tri.c.p.z
                    };

                    // Compute distance from camera (origin)
                    f32_t distance = sqrtf(pos.x * pos.x + pos.y * pos.y + pos.z * pos.z);
                    distance_buffer[y][x] = distance;

                    // Optionally, store color if needed elsewhere
                    
                    col_t color = {
                        .r = (u * tri.a.c.r) + (v * tri.b.c.r) + (w * tri.c.c.r),
                        .g = (u * tri.a.c.g) + (v * tri.b.c.g) + (w * tri.c.c.g),
                        .b = (u * tri.a.c.b) + (v * tri.b.c.b) + (w * tri.c.c.b),
                        .a = 255 // Full alpha
                    };
                    fb[y][x] = color;
                    
                }
            }
        }
    }
}

/* Render the frame buffer with fixed color shading */
void render_fb_fixed_char() {
    for(int y = 0; y < HEIGHT; y++) {
        for(int x = 0; x < WIDTH; x++) {
            col_t color = fb[y][x];
            color.a = 255; // Full alpha
            set_color(color);
            printf("%s", get_shade(color.a / 255.0f));
        }
        next_line();
    }
    // Reset color at the end
    printf("\x1b[0m");
}

/* Render the frame buffer with distance-based shading */
void render_fb_distance() {
    for(int y = 0; y < HEIGHT; y++) {
        for(int x = 0; x < WIDTH; x++) {
            f32_t distance = distance_buffer[y][x];

            //printf("Distance: %f\n", distance);


            // Normalize distance between ZNEAR and ZFAR
            f32_t normalized = (distance - ZNEAR) / (ZFAR - ZNEAR);

            // Clamp the normalized value between 0 and 1
            if(normalized < 0.0f) normalized = 0.0f;
            if(normalized > 1.0f) normalized = 1.0f;

            // Invert the normalized distance for brightness (closer objects are brighter)
            f32_t brightness = 1.0f - normalized;

            // Get the corresponding shade based on brightness
            const u8_t *shade = get_shade(brightness);

            // Optionally, set color to white with adjusted brightness
            
            col_t color = fb[y][x];
            color.a = (u8_t)(brightness * 255); // Full alpha
            set_color(color);
            //printf("pixel at (%d, %d) is %s\n", x, y, shade);
        }
        next_line();
    }

    // Reset terminal color at the end
    printf("\x1b[0m");
}

int main() {
    /* Example triangle */
    tri_t triangle = {
        .a = { .p = { -1.0f, -1.0f, -120.0f }, .c = { 255, 0, 0, 255 } }, // Red
        .b = { .p = {  1.0f, -1.0f, -1.0f }, .c = { 0, 255, 0, 255 } }, // Green
        .c = { .p = {  0.0f,  1.0f, -1.0f }, .c = { 0, 0, 255, 255 } }  // Blue
    };

    /* Clear the screen */
    clear_screen();

    /* Clear frame buffer, Z-buffer, and Distance buffer */
    clear_fb();

    /* Rasterize the triangle */
    rasterize_triangle(triangle);

    /* Render the frame buffer to terminal */
    // Choose one of the rendering methods:
    // render_fb_fixed_char(); // Use this for fixed character rendering
    render_fb_distance(); // Use this for distance-based shading

    return 0;
}
```