Start implementing drawing order
This commit is contained in:
parent
77edf714bb
commit
f6848b5f4f
1
compile
1
compile
@ -12,6 +12,7 @@ RAYTRACER_SRC="src/window/*.c \
|
|||||||
src/raytracer/*.c \
|
src/raytracer/*.c \
|
||||||
src/math/*.c \
|
src/math/*.c \
|
||||||
src/camera/*.c \
|
src/camera/*.c \
|
||||||
|
$WAPP_SRC \
|
||||||
"
|
"
|
||||||
|
|
||||||
RASTERISER_SRC="src/window/*.c \
|
RASTERISER_SRC="src/window/*.c \
|
||||||
|
@ -31,7 +31,8 @@ MAKE_LIST_TYPE(scene_triangle_t);
|
|||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
RASTERISER_RENDER_WIREFRAME,
|
RASTERISER_RENDER_WIREFRAME,
|
||||||
RASTERISER_RENDER_SOLID,
|
RASTERISER_RENDER_FILLED,
|
||||||
|
RASTERISER_RENDER_SHADED,
|
||||||
} render_type_t;
|
} render_type_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@ -46,6 +47,9 @@ typedef struct {
|
|||||||
f32 h0;
|
f32 h0;
|
||||||
f32 h1;
|
f32 h1;
|
||||||
f32 h2;
|
f32 h2;
|
||||||
|
f32 z0;
|
||||||
|
f32 z1;
|
||||||
|
f32 z2;
|
||||||
colour_t colour;
|
colour_t colour;
|
||||||
} triangle_t;
|
} triangle_t;
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#define WINDOW_H
|
#define WINDOW_H
|
||||||
|
|
||||||
#include "aliases.h"
|
#include "aliases.h"
|
||||||
|
#include "mem_arena.h"
|
||||||
#include "vector/vec.h"
|
#include "vector/vec.h"
|
||||||
#include <SDL2/SDL_video.h>
|
#include <SDL2/SDL_video.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
@ -29,13 +30,17 @@ typedef struct {
|
|||||||
SDL_Window *window;
|
SDL_Window *window;
|
||||||
SDL_Surface *front_buffer;
|
SDL_Surface *front_buffer;
|
||||||
SDL_Surface *back_buffer;
|
SDL_Surface *back_buffer;
|
||||||
|
f32 *z_buffer;
|
||||||
} window_t;
|
} window_t;
|
||||||
|
|
||||||
bool init_window(window_t *wnd, u32 width, u32 height, const char *title);
|
bool init_window(Arena *arena, window_t *wnd, u32 width, u32 height,
|
||||||
|
const char *title);
|
||||||
void close_window(window_t *wnd);
|
void close_window(window_t *wnd);
|
||||||
|
|
||||||
void clear_window(window_t *wnd, colour_t colour);
|
void clear_window(window_t *wnd, colour_t colour);
|
||||||
void set_pixel(window_t *wnd, i32 x, i32 y, colour_t colour);
|
void set_pixel(window_t *wnd, i32 x, i32 y, colour_t colour);
|
||||||
|
void set_z_pixel(window_t *wnd, i32 x, i32 y, f32 value);
|
||||||
|
f32 get_z_pixel(const window_t *wnd, i32 x, i32 y);
|
||||||
void swap_buffers(window_t *wnd);
|
void swap_buffers(window_t *wnd);
|
||||||
|
|
||||||
vec3f_t window_to_viewport(const window_t *wnd, i32 x, i32 y, vec3f_t viewport);
|
vec3f_t window_to_viewport(const window_t *wnd, i32 x, i32 y, vec3f_t viewport);
|
||||||
|
@ -20,12 +20,6 @@ int main(void) {
|
|||||||
};
|
};
|
||||||
vec3f_t viewport = {.x = 1.0f, .y = 1.0f, .z = 1.0f};
|
vec3f_t viewport = {.x = 1.0f, .y = 1.0f, .z = 1.0f};
|
||||||
|
|
||||||
window_t window = {0};
|
|
||||||
|
|
||||||
if (!init_window(&window, 800, 800, "CG From Scratch Rasteriser")) {
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
Arena *arena = NULL;
|
Arena *arena = NULL;
|
||||||
u64 main_arena_capacity = 64ull * 1024ull * 1024ull * 1024ull;
|
u64 main_arena_capacity = 64ull * 1024ull * 1024ull * 1024ull;
|
||||||
if (!wapp_mem_arena_init(&arena, main_arena_capacity, WAPP_MEM_ALLOC_RESERVE,
|
if (!wapp_mem_arena_init(&arena, main_arena_capacity, WAPP_MEM_ALLOC_RESERVE,
|
||||||
@ -33,6 +27,12 @@ int main(void) {
|
|||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window_t window = {0};
|
||||||
|
|
||||||
|
if (!init_window(arena, &window, 800, 800, "CG From Scratch Rasteriser")) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
list_vertex_t *vertices = list_create(vertex_t, arena);
|
list_vertex_t *vertices = list_create(vertex_t, arena);
|
||||||
if (!vertices) {
|
if (!vertices) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
@ -181,7 +181,7 @@ int main(void) {
|
|||||||
|
|
||||||
clear_window(&window, bg);
|
clear_window(&window, bg);
|
||||||
|
|
||||||
render_scene(&window, frame_arena, &scene, RASTERISER_RENDER_SOLID);
|
render_scene(&window, frame_arena, &scene, RASTERISER_RENDER_FILLED);
|
||||||
|
|
||||||
swap_buffers(&window);
|
swap_buffers(&window);
|
||||||
|
|
||||||
|
@ -121,16 +121,23 @@ internal void render_instance(window_t *wnd, const rasteriser_scene_t *scene,
|
|||||||
list_append(vec2i_t, arena, projected, point);
|
list_append(vec2i_t, arena, projected, point);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vertex_t v0, v1, v2;
|
||||||
for (u64 i = 0; i < triangles->count; ++i) {
|
for (u64 i = 0; i < triangles->count; ++i) {
|
||||||
scene_triangle_t triangle = list_get(triangles, i);
|
scene_triangle_t triangle = list_get(triangles, i);
|
||||||
|
v0 = list_get(transformed, triangle.idx0);
|
||||||
|
v1 = list_get(transformed, triangle.idx1);
|
||||||
|
v2 = list_get(transformed, triangle.idx2);
|
||||||
|
|
||||||
triangle_t tri = {
|
triangle_t tri = {
|
||||||
.p0 = list_get(projected, triangle.idx0),
|
.p0 = list_get(projected, triangle.idx0),
|
||||||
.p1 = list_get(projected, triangle.idx1),
|
.p1 = list_get(projected, triangle.idx1),
|
||||||
.p2 = list_get(projected, triangle.idx2),
|
.p2 = list_get(projected, triangle.idx2),
|
||||||
.h0 = list_get(transformed, triangle.idx0).h,
|
.h0 = v0.h,
|
||||||
.h1 = list_get(transformed, triangle.idx1).h,
|
.h1 = v1.h,
|
||||||
.h2 = list_get(transformed, triangle.idx2).h,
|
.h2 = v2.h,
|
||||||
|
.z0 = v0.position.z,
|
||||||
|
.z1 = v1.position.z,
|
||||||
|
.z2 = v2.position.z,
|
||||||
.colour = triangle.colour,
|
.colour = triangle.colour,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -143,9 +150,12 @@ internal void render_instance(window_t *wnd, const rasteriser_scene_t *scene,
|
|||||||
case RASTERISER_RENDER_WIREFRAME:
|
case RASTERISER_RENDER_WIREFRAME:
|
||||||
draw_wireframe_triangle(wnd, arena, tri);
|
draw_wireframe_triangle(wnd, arena, tri);
|
||||||
break;
|
break;
|
||||||
case RASTERISER_RENDER_SOLID:
|
case RASTERISER_RENDER_FILLED:
|
||||||
draw_filled_triangle(wnd, arena, tri);
|
draw_filled_triangle(wnd, arena, tri);
|
||||||
break;
|
break;
|
||||||
|
case RASTERISER_RENDER_SHADED:
|
||||||
|
draw_shaded_triangle(wnd, arena, tri);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -176,22 +186,57 @@ void draw_filled_triangle(window_t *wnd, Arena *arena, triangle_t triangle) {
|
|||||||
list_pop(x01); // Last element of x01 is a duplicate of first element in x12
|
list_pop(x01); // Last element of x01 is a duplicate of first element in x12
|
||||||
list_merge(f32, arena, x012, x01, x12);
|
list_merge(f32, arena, x012, x01, x12);
|
||||||
|
|
||||||
|
f32 z0 = triangle.z0;
|
||||||
|
f32 z1 = triangle.z1;
|
||||||
|
f32 z2 = triangle.z2;
|
||||||
|
list_float *z01 = interpolate(arena, y0, z0, y1, z1);
|
||||||
|
list_float *z12 = interpolate(arena, y1, z1, y2, z2);
|
||||||
|
list_float *z02 = interpolate(arena, y0, z0, y2, z2);
|
||||||
|
list_float *z012 = NULL;
|
||||||
|
|
||||||
|
list_pop(z01); // Last element of z01 is a duplicate of first element in z12
|
||||||
|
list_merge(f32, arena, z012, z01, z12);
|
||||||
|
|
||||||
list_float *x_left;
|
list_float *x_left;
|
||||||
list_float *x_right;
|
list_float *x_right;
|
||||||
|
list_float *z_left;
|
||||||
|
list_float *z_right;
|
||||||
u64 middle = (u64)(floorf((f32)(x02->count) / 2.0f));
|
u64 middle = (u64)(floorf((f32)(x02->count) / 2.0f));
|
||||||
if (list_get(x02, middle) < list_get(x012, middle)) {
|
if (list_get(x02, middle) < list_get(x012, middle)) {
|
||||||
x_left = x02;
|
x_left = x02;
|
||||||
|
z_left = z02;
|
||||||
x_right = x012;
|
x_right = x012;
|
||||||
|
z_right = z012;
|
||||||
} else {
|
} else {
|
||||||
x_left = x012;
|
x_left = x012;
|
||||||
|
z_left = z012;
|
||||||
x_right = x02;
|
x_right = x02;
|
||||||
|
z_right = z02;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
list_float *z_segment = NULL;
|
||||||
|
i32 index = -1;
|
||||||
|
i64 xl = -1;
|
||||||
|
i64 xr = -1;
|
||||||
|
f32 current_z = INFINITY;
|
||||||
|
f32 new_z = INFINITY;
|
||||||
|
|
||||||
for (i64 y = y0; y <= y2; ++y) {
|
for (i64 y = y0; y <= y2; ++y) {
|
||||||
i32 index = y - y0;
|
index = y - y0;
|
||||||
for (i64 x = (i64)list_get(x_left, index); x < list_get(x_right, index);
|
xl = (i64)list_get(x_left, index);
|
||||||
++x) {
|
xr = (i64)list_get(x_right, index);
|
||||||
set_pixel(wnd, x, y, triangle.colour);
|
|
||||||
|
for (i64 x = xl; x < xr; ++x) {
|
||||||
|
z_segment = interpolate(arena, xl, list_get(z_left, index), xr,
|
||||||
|
list_get(z_right, index));
|
||||||
|
|
||||||
|
current_z = get_z_pixel(wnd, x, y);
|
||||||
|
new_z = list_get(z_segment, x - xl);
|
||||||
|
|
||||||
|
if (new_z <= current_z) {
|
||||||
|
set_z_pixel(wnd, x, y, new_z);
|
||||||
|
set_pixel(wnd, x, y, triangle.colour);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,27 +270,47 @@ void draw_shaded_triangle(window_t *wnd, Arena *arena, triangle_t triangle) {
|
|||||||
list_pop(h01); // Last element of h01 is a duplicate of first element in h12
|
list_pop(h01); // Last element of h01 is a duplicate of first element in h12
|
||||||
list_merge(f32, arena, h012, h01, h12);
|
list_merge(f32, arena, h012, h01, h12);
|
||||||
|
|
||||||
|
f32 z0 = triangle.z0;
|
||||||
|
f32 z1 = triangle.z1;
|
||||||
|
f32 z2 = triangle.z2;
|
||||||
|
list_float *z01 = interpolate(arena, y0, z0, y1, z1);
|
||||||
|
list_float *z12 = interpolate(arena, y1, z1, y2, z2);
|
||||||
|
list_float *z02 = interpolate(arena, y0, z0, y2, z2);
|
||||||
|
list_float *z012 = NULL;
|
||||||
|
|
||||||
|
list_pop(z01); // Last element of z01 is a duplicate of first element in z12
|
||||||
|
list_merge(f32, arena, z012, z01, z12);
|
||||||
|
|
||||||
list_float *x_left;
|
list_float *x_left;
|
||||||
list_float *x_right;
|
list_float *x_right;
|
||||||
list_float *h_left;
|
list_float *h_left;
|
||||||
list_float *h_right;
|
list_float *h_right;
|
||||||
|
list_float *z_left;
|
||||||
|
list_float *z_right;
|
||||||
u64 middle = (u64)(floorf((f32)(x02->count) / 2.0f));
|
u64 middle = (u64)(floorf((f32)(x02->count) / 2.0f));
|
||||||
if (list_get(x02, middle) < list_get(x012, middle)) {
|
if (list_get(x02, middle) < list_get(x012, middle)) {
|
||||||
x_left = x02;
|
x_left = x02;
|
||||||
h_left = h02;
|
h_left = h02;
|
||||||
|
z_left = z02;
|
||||||
x_right = x012;
|
x_right = x012;
|
||||||
h_right = h012;
|
h_right = h012;
|
||||||
|
z_right = z012;
|
||||||
} else {
|
} else {
|
||||||
x_left = x012;
|
x_left = x012;
|
||||||
h_left = h012;
|
h_left = h012;
|
||||||
|
z_left = z012;
|
||||||
x_right = x02;
|
x_right = x02;
|
||||||
h_right = h02;
|
h_right = h02;
|
||||||
|
z_right = z02;
|
||||||
}
|
}
|
||||||
|
|
||||||
list_float *h_segment = NULL;
|
list_float *h_segment = NULL;
|
||||||
|
list_float *z_segment = NULL;
|
||||||
i32 index = -1;
|
i32 index = -1;
|
||||||
i64 xl = -1;
|
i64 xl = -1;
|
||||||
i64 xr = -1;
|
i64 xr = -1;
|
||||||
|
f32 current_z = INFINITY;
|
||||||
|
f32 new_z = INFINITY;
|
||||||
colour_t shaded_colour = (colour_t){0};
|
colour_t shaded_colour = (colour_t){0};
|
||||||
|
|
||||||
for (i64 y = y0; y <= y2; ++y) {
|
for (i64 y = y0; y <= y2; ++y) {
|
||||||
@ -255,8 +320,17 @@ void draw_shaded_triangle(window_t *wnd, Arena *arena, triangle_t triangle) {
|
|||||||
for (i64 x = xl; x < xr; ++x) {
|
for (i64 x = xl; x < xr; ++x) {
|
||||||
h_segment = interpolate(arena, xl, list_get(h_left, index), xr,
|
h_segment = interpolate(arena, xl, list_get(h_left, index), xr,
|
||||||
list_get(h_right, index));
|
list_get(h_right, index));
|
||||||
|
z_segment = interpolate(arena, xl, list_get(z_left, index), xr,
|
||||||
|
list_get(z_right, index));
|
||||||
shaded_colour = colour_mul(triangle.colour, list_get(h_segment, x - xl));
|
shaded_colour = colour_mul(triangle.colour, list_get(h_segment, x - xl));
|
||||||
set_pixel(wnd, x, y, shaded_colour);
|
|
||||||
|
current_z = get_z_pixel(wnd, x, y);
|
||||||
|
new_z = list_get(z_segment, x - xl);
|
||||||
|
|
||||||
|
if (new_z <= current_z) {
|
||||||
|
set_z_pixel(wnd, x, y, new_z);
|
||||||
|
set_pixel(wnd, x, y, shaded_colour);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ i32 main(i32 argc, char *argv[]) {
|
|||||||
|
|
||||||
window_t window = {0};
|
window_t window = {0};
|
||||||
|
|
||||||
if (!init_window(&window, 800, 800, "CG From Scratch Raytracer")) {
|
if (!init_window(NULL, &window, 800, 800, "CG From Scratch Raytracer")) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
#include "window/window.h"
|
#include "window/window.h"
|
||||||
#include "aliases.h"
|
#include "aliases.h"
|
||||||
#include "math/math_utils.h"
|
#include "math/math_utils.h"
|
||||||
|
#include "mem_arena.h"
|
||||||
#include "vector/vec.h"
|
#include "vector/vec.h"
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <SDL2/SDL_error.h>
|
#include <SDL2/SDL_error.h>
|
||||||
#include <SDL2/SDL_pixels.h>
|
#include <SDL2/SDL_pixels.h>
|
||||||
#include <SDL2/SDL_surface.h>
|
#include <SDL2/SDL_surface.h>
|
||||||
|
#include <math.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
u32 index_from_coordinates(window_t *wnd, u32 x, u32 y);
|
u32 index_from_coordinates(const window_t *wnd, u32 x, u32 y);
|
||||||
i32 denormalise(i32 value, u32 max, u32 abs_half);
|
i32 denormalise(i32 value, u32 max, u32 abs_half);
|
||||||
vec2i_t denormalised_coords(const window_t *wnd, i32 x, i32 y);
|
vec2i_t denormalised_coords(const window_t *wnd, i32 x, i32 y);
|
||||||
void set_screen_pixel(window_t *wnd, u32 x, u32 y, colour_t colour);
|
void set_screen_pixel(window_t *wnd, u32 x, u32 y, colour_t colour);
|
||||||
|
void set_z_buffer_pixel(window_t *wnd, u32 x, u32 y, f32 value);
|
||||||
|
|
||||||
bool init_window(window_t *wnd, u32 width, u32 height, const char *title) {
|
bool init_window(Arena *arena, window_t *wnd, u32 width, u32 height,
|
||||||
|
const char *title) {
|
||||||
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
|
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
|
||||||
printf("Failed to initialise SDL: %s\n", SDL_GetError());
|
printf("Failed to initialise SDL: %s\n", SDL_GetError());
|
||||||
|
|
||||||
@ -64,6 +68,9 @@ bool init_window(window_t *wnd, u32 width, u32 height, const char *title) {
|
|||||||
wnd->half_width = width / 2;
|
wnd->half_width = width / 2;
|
||||||
wnd->half_height = height / 2;
|
wnd->half_height = height / 2;
|
||||||
wnd->title = title;
|
wnd->title = title;
|
||||||
|
if (arena) {
|
||||||
|
wnd->z_buffer = wapp_mem_arena_alloc(arena, width * height * sizeof(f32));
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -85,6 +92,8 @@ void close_window(window_t *wnd) {
|
|||||||
|
|
||||||
wnd->back_buffer = NULL;
|
wnd->back_buffer = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wnd->z_buffer = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
@ -98,6 +107,7 @@ void clear_window(window_t *wnd, colour_t colour) {
|
|||||||
|
|
||||||
for (u32 i = 0; i < count; ++i) {
|
for (u32 i = 0; i < count; ++i) {
|
||||||
pixels[i] = colour.colour;
|
pixels[i] = colour.colour;
|
||||||
|
wnd->z_buffer[i] = INFINITY;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_UnlockSurface(wnd->back_buffer);
|
SDL_UnlockSurface(wnd->back_buffer);
|
||||||
@ -117,13 +127,34 @@ void set_pixel(window_t *wnd, i32 x, i32 y, colour_t colour) {
|
|||||||
SDL_UnlockSurface(wnd->back_buffer);
|
SDL_UnlockSurface(wnd->back_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_z_pixel(window_t *wnd, i32 x, i32 y, f32 value) {
|
||||||
|
vec2i_t coords = denormalised_coords(wnd, x, y);
|
||||||
|
|
||||||
|
if (coords.x < 0 || coords.y < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_z_buffer_pixel(wnd, (u32)(coords.x), (u32)(coords.y), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
f32 get_z_pixel(const window_t *wnd, i32 x, i32 y) {
|
||||||
|
vec2i_t coords = denormalised_coords(wnd, x, y);
|
||||||
|
|
||||||
|
if (coords.x < 0 || coords.y < 0) {
|
||||||
|
return INFINITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 index = index_from_coordinates(wnd, (u32)(coords.x), (u32)(coords.y));
|
||||||
|
return wnd->z_buffer[index];
|
||||||
|
}
|
||||||
|
|
||||||
void swap_buffers(window_t *wnd) {
|
void swap_buffers(window_t *wnd) {
|
||||||
SDL_BlitSurface(wnd->back_buffer, NULL, wnd->front_buffer, NULL);
|
SDL_BlitSurface(wnd->back_buffer, NULL, wnd->front_buffer, NULL);
|
||||||
|
|
||||||
SDL_UpdateWindowSurface(wnd->window);
|
SDL_UpdateWindowSurface(wnd->window);
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 index_from_coordinates(window_t *wnd, u32 x, u32 y) {
|
u32 index_from_coordinates(const window_t *wnd, u32 x, u32 y) {
|
||||||
return y * wnd->width + x;
|
return y * wnd->width + x;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,6 +190,11 @@ void set_screen_pixel(window_t *wnd, u32 x, u32 y, colour_t colour) {
|
|||||||
pixels[index] = colour.colour;
|
pixels[index] = colour.colour;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_z_buffer_pixel(window_t *wnd, u32 x, u32 y, f32 value) {
|
||||||
|
u32 index = index_from_coordinates(wnd, x, y);
|
||||||
|
wnd->z_buffer[index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
vec3f_t window_to_viewport(const window_t *wnd, i32 x, i32 y,
|
vec3f_t window_to_viewport(const window_t *wnd, i32 x, i32 y,
|
||||||
vec3f_t viewport) {
|
vec3f_t viewport) {
|
||||||
return (vec3f_t){
|
return (vec3f_t){
|
||||||
|
Loading…
Reference in New Issue
Block a user