389 lines
9.6 KiB
C
389 lines
9.6 KiB
C
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <time.h>
|
|
|
|
#include <SDL2/SDL.h>
|
|
|
|
#define W 800
|
|
#define H 600
|
|
#define PI acos(-1)
|
|
|
|
typedef struct {
|
|
unsigned char r, g, b, a;
|
|
} rgb_t;
|
|
const rgb_t WHITE = {255, 255, 255, 255};
|
|
const rgb_t BLACK = {0, 0, 0, 255};
|
|
const rgb_t RED = {255, 0, 0, 255};
|
|
const rgb_t GREEN = {0, 255, 0, 255};
|
|
const rgb_t BLUE = {0, 0, 255, 255};
|
|
|
|
typedef struct {
|
|
SDL_Renderer *renderer;
|
|
SDL_Window *window;
|
|
rgb_t *canvas;
|
|
} sdl_state_t;
|
|
|
|
typedef struct {
|
|
int x, y;
|
|
} vec2_t;
|
|
|
|
typedef struct {
|
|
vec2_t a, b, c;
|
|
} triangle_t;
|
|
|
|
#define max(x, y) ((x) > (y) ? (x) : (y))
|
|
#define min(x, y) ((x) < (y) ? (x) : (y))
|
|
#define swap(x, y) do { \
|
|
(x) ^= (y); \
|
|
(y) ^= (x); \
|
|
(x) ^= (y); \
|
|
} while (0)
|
|
|
|
void clear_canvas(rgb_t canvas[], rgb_t colour);
|
|
sdl_state_t init_sdl(void);
|
|
void free_sdl(sdl_state_t state);
|
|
void render(sdl_state_t state);
|
|
void plot_rgb(rgb_t canvas[], uint16_t x, uint16_t y, rgb_t colour);
|
|
void draw_line(rgb_t canvas[], rgb_t c, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
|
|
|
|
sdl_state_t
|
|
init_sdl(void)
|
|
{
|
|
SDL_Init(SDL_INIT_VIDEO);
|
|
|
|
sdl_state_t ret;
|
|
|
|
ret.window = SDL_CreateWindow("Game",
|
|
SDL_WINDOWPOS_UNDEFINED,
|
|
SDL_WINDOWPOS_UNDEFINED,
|
|
W,
|
|
H,
|
|
// TODO: when we have a proper event loop, we can
|
|
// add resizing windows.
|
|
/* SDL_WINDOW_RESIZABLE | SDL_WINDOW_SHOWN); */
|
|
SDL_WINDOW_MOUSE_CAPTURE | SDL_WINDOW_SHOWN);
|
|
ret.renderer = SDL_CreateRenderer(ret.window, -1, SDL_RENDERER_SOFTWARE);
|
|
SDL_SetRenderDrawColor(ret.renderer, 0, 0, 0, 255);
|
|
SDL_RenderClear(ret.renderer);
|
|
SDL_RenderPresent(ret.renderer);
|
|
|
|
ret.canvas = malloc(sizeof(rgb_t) * W * H);
|
|
clear_canvas(ret.canvas, (rgb_t){0, 0, 0, 255});
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
free_sdl(sdl_state_t state)
|
|
{
|
|
SDL_DestroyRenderer(state.renderer);
|
|
SDL_DestroyWindow(state.window);
|
|
free(state.canvas);
|
|
SDL_Quit();
|
|
}
|
|
|
|
// TODO: Highly unefficient. Makes a *lot* of SDL calls.
|
|
void
|
|
render(sdl_state_t state)
|
|
{
|
|
for (size_t y = 0; y < H; y++) {
|
|
for (size_t x = 0; x < W; x++) {
|
|
rgb_t curr = state.canvas[x + y * W];
|
|
SDL_SetRenderDrawColor(state.renderer, curr.r, curr.g, curr.b, curr.a);
|
|
SDL_RenderDrawPoint(state.renderer, x, y);
|
|
}
|
|
}
|
|
|
|
SDL_RenderPresent(state.renderer);
|
|
}
|
|
|
|
void
|
|
plot_rgb(rgb_t canvas[], uint16_t x, uint16_t y, rgb_t colour)
|
|
{
|
|
if (x < W && y < H && x >= 0 && y >= 0)
|
|
canvas[x + y * W] = colour;
|
|
}
|
|
|
|
void
|
|
clear_canvas(rgb_t canvas[], rgb_t colour)
|
|
{
|
|
for (size_t y = 0; y < H; y++) {
|
|
for (size_t x = 0; x < W; x++) {
|
|
plot_rgb(canvas, x, y, colour);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
draw_line(rgb_t canvas[], rgb_t c, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
|
|
{
|
|
if (x0 > x1) {
|
|
swap(x0, x1);
|
|
swap(y0, y1);
|
|
}
|
|
|
|
int dx = abs(x1 - x0);
|
|
int dy = abs(y1 - y0);
|
|
int eps = 0;
|
|
|
|
if (y1 > y0) {
|
|
if (dy < dx) {
|
|
int y = y0;
|
|
for (int x = x0; x <= x1; x++) {
|
|
plot_rgb(canvas, x, y, c);
|
|
eps += dy;
|
|
if ((eps * 2) >= dx) {
|
|
y++;
|
|
eps -= dx;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
int x = x0;
|
|
for (int y = y0; y <= y1; y++) {
|
|
plot_rgb(canvas, x, y, c);
|
|
eps += dx;
|
|
if ((eps * 2) >= dy) {
|
|
x++;
|
|
eps -= dy;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (dy < dx) {
|
|
int y = y0;
|
|
for (int x = x0; x <= x1; x++) {
|
|
plot_rgb(canvas, x, y, c);
|
|
eps += dy;
|
|
if ((eps * 2) >= dx) {
|
|
y--;
|
|
eps -= dx;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
swap(x0, x1);
|
|
swap(y0, y1);
|
|
int x = x0;
|
|
for (int y = y0; y <= y1; y++) {
|
|
plot_rgb(canvas, x, y, c);
|
|
eps += dx;
|
|
if ((eps * 2) >= dy) {
|
|
x--;
|
|
eps -= dy;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
draw_horizontal_line(rgb_t canvas[], rgb_t c, uint16_t x0, uint16_t x1, uint16_t y)
|
|
{
|
|
for (uint16_t x = x0; x < x1; x++)
|
|
canvas[x + y * W] = c;
|
|
}
|
|
|
|
void
|
|
draw_triangle(rgb_t canvas[], rgb_t c, triangle_t tri)
|
|
{
|
|
draw_line(canvas, c, tri.a.x, tri.a.y, tri.b.x, tri.b.y);
|
|
draw_line(canvas, c, tri.b.x, tri.b.y, tri.c.x, tri.c.y);
|
|
draw_line(canvas, c, tri.c.x, tri.c.y, tri.a.x, tri.a.y);
|
|
}
|
|
|
|
void
|
|
fill_bottom_flat_triangle(rgb_t canvas[], rgb_t c, triangle_t tri)
|
|
{
|
|
assert(tri.b.y == tri.c.y);
|
|
|
|
if (tri.b.x > tri.c.x) {
|
|
swap(tri.b.y, tri.c.y);
|
|
swap(tri.b.x, tri.c.x);
|
|
}
|
|
|
|
float slope0 = (float)(tri.b.x - tri.a.x) / (float)(tri.b.y - tri.a.y);
|
|
float slope1 = (float)(tri.c.x - tri.a.x) / (float)(tri.c.y - tri.a.y);
|
|
|
|
float x0, x1;
|
|
x0 = x1 = tri.a.x;
|
|
|
|
for (int y = tri.a.y; y <= tri.b.y; y++) {
|
|
draw_horizontal_line(canvas, c, x0, x1, y);
|
|
x0 += slope0;
|
|
x1 += slope1;
|
|
}
|
|
}
|
|
|
|
void
|
|
fill_top_flat_triangle(rgb_t canvas[], rgb_t c, triangle_t tri)
|
|
{
|
|
assert(tri.a.y == tri.b.y);
|
|
|
|
if (tri.a.x > tri.b.x) {
|
|
swap(tri.a.y, tri.b.y);
|
|
swap(tri.a.x, tri.b.x);
|
|
}
|
|
|
|
float slope0 = (float)(tri.c.x - tri.a.x) / (float)(tri.c.y - tri.a.y);
|
|
float slope1 = (float)(tri.c.x - tri.b.x) / (float)(tri.c.y - tri.b.y);
|
|
|
|
float x0, x1;
|
|
x0 = x1 = tri.c.x;
|
|
|
|
for (int y = tri.c.y; y > tri.a.y; y--) {
|
|
draw_horizontal_line(canvas, c, x0, x1, y);
|
|
x0 -= slope0;
|
|
x1 -= slope1;
|
|
}
|
|
}
|
|
|
|
/* http://www.sunshine2k.de/coding/java/TriangleRasterization/TriangleRasterization.html */
|
|
void
|
|
fill_triangle(rgb_t canvas[], rgb_t c, triangle_t tri)
|
|
{
|
|
/* tri.a <= tri.b <= tri.c */
|
|
if (tri.b.y < tri.a.y) {
|
|
swap(tri.b.y, tri.a.y);
|
|
swap(tri.b.x, tri.a.x);
|
|
}
|
|
if (tri.c.y < tri.a.y) {
|
|
swap(tri.c.y, tri.a.y);
|
|
swap(tri.c.x, tri.a.x);
|
|
}
|
|
if (tri.c.y < tri.b.y) {
|
|
swap(tri.c.y, tri.b.y);
|
|
swap(tri.c.x, tri.b.x);
|
|
}
|
|
|
|
/* simple solutions */
|
|
if (tri.b.y == tri.c.y) {
|
|
fill_bottom_flat_triangle(canvas, c, tri);
|
|
}
|
|
else if (tri.a.y == tri.b.y) {
|
|
fill_top_flat_triangle(canvas, c, tri);
|
|
}
|
|
else {
|
|
vec2_t trid = {
|
|
(tri.a.x + ((float)(tri.b.y - tri.a.y) / (float)(tri.c.y - tri.a.y)) * (tri.c.x - tri.a.x)),
|
|
tri.b.y};
|
|
|
|
if (tri.a.y > tri.b.y) {
|
|
swap(tri.a.y, tri.b.y);
|
|
swap(tri.a.x, tri.b.x);
|
|
}
|
|
|
|
fill_bottom_flat_triangle(canvas, c, (triangle_t) {
|
|
tri.a, tri.b, trid
|
|
});
|
|
fill_top_flat_triangle(canvas, c, (triangle_t) {
|
|
tri.b, trid, tri.c
|
|
});
|
|
}
|
|
}
|
|
|
|
vec2_t
|
|
midpoint(vec2_t a, vec2_t b)
|
|
{
|
|
return (vec2_t) {
|
|
(a.x + b.x) / 2,
|
|
(a.y + b.y) / 2
|
|
};
|
|
}
|
|
|
|
/* saves canvas as a P6 image */
|
|
void
|
|
screenshot(rgb_t canvas[])
|
|
{
|
|
time_t t = time(NULL);
|
|
char output[64];
|
|
strftime(output, sizeof(output), "screenshot-%Y%m%d-%H%M%S.ppm", localtime(&t));
|
|
FILE *out = fopen(output, "w");
|
|
fprintf(out, "P6\n%d %d\n255\n", W, H);
|
|
for (int y = 0; y < H; y++) {
|
|
for (int x = 0; x < W; x++) {
|
|
rgb_t cur = canvas[x + y * W];
|
|
fprintf(out, "%c%c%c", cur.r, cur.g, cur.b);
|
|
}
|
|
}
|
|
fclose(out);
|
|
fprintf(stderr, "Saved %s\n", output);
|
|
}
|
|
|
|
void
|
|
cool_effect(rgb_t canvas[])
|
|
{
|
|
draw_line(canvas,
|
|
WHITE,
|
|
cos(SDL_GetTicks()*0.001) * 300 + W/2,
|
|
sin(SDL_GetTicks()*0.001) * 300 + H/2,
|
|
W/2,
|
|
H/2);
|
|
|
|
draw_line(canvas,
|
|
WHITE,
|
|
cos(SDL_GetTicks()*0.001+1.333*PI) * 300 + W/2,
|
|
sin(SDL_GetTicks()*0.001+1.333*PI) * 300 + H/2,
|
|
W/2,
|
|
H/2);
|
|
|
|
draw_line(canvas,
|
|
WHITE,
|
|
cos(SDL_GetTicks()*0.001+0.667*PI) * 300 + W/2,
|
|
sin(SDL_GetTicks()*0.001+0.667*PI) * 300 + H/2,
|
|
W/2,
|
|
H/2);
|
|
|
|
|
|
/* vec2_t a = {0, 100}, b = {200, 0}, c = {200, 200}; */
|
|
vec2_t a = {
|
|
cos(SDL_GetTicks()*0.001) * 300 + W/2,
|
|
sin(SDL_GetTicks()*0.001) * 300 + H/2
|
|
};
|
|
vec2_t b = {
|
|
cos(SDL_GetTicks()*0.001+0.667*PI) * 300 + W/2,
|
|
sin(SDL_GetTicks()*0.001+0.667*PI) * 300 + H/2
|
|
};
|
|
vec2_t c = {
|
|
cos(SDL_GetTicks()*0.001+1.333*PI) * 300 + W/2,
|
|
sin(SDL_GetTicks()*0.001+1.333*PI) * 300 + H/2,
|
|
};
|
|
for (int i = 0; i < 10; i++) {
|
|
fill_triangle(canvas, i % 2 ? BLACK : WHITE, (triangle_t){a, b, c});
|
|
vec2_t tmp_a = midpoint(a, b);
|
|
vec2_t tmp_b = midpoint(b, c);
|
|
vec2_t tmp_c = midpoint(c, a);
|
|
a = tmp_a;
|
|
b = tmp_b;
|
|
c = tmp_c;
|
|
}
|
|
}
|
|
|
|
int
|
|
main(void)
|
|
{
|
|
sdl_state_t state = init_sdl();
|
|
|
|
SDL_Event event;
|
|
for (;;) {
|
|
uint32_t buttons = SDL_GetMouseState(NULL, NULL);
|
|
if (buttons & SDL_BUTTON(3))
|
|
break;
|
|
else if (buttons & SDL_BUTTON(1))
|
|
screenshot(state.canvas);
|
|
|
|
clear_canvas(state.canvas, BLACK);
|
|
|
|
cool_effect(state.canvas);
|
|
|
|
render(state);
|
|
if (SDL_PollEvent(&event) && event.type == SDL_QUIT)
|
|
break;
|
|
}
|
|
|
|
free_sdl(state);
|
|
}
|