gaming-pads/floodfill.c

300 lines
10 KiB
C

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "include/raylib.h"
#include "raymath.h"
#define SCREEN_WIDTH 1000
#define SCREEN_HEIGHT 800
#define IMG_W 500
#define IMG_H 400
#define IMG_SIZE IMG_W * IMG_H
#define TARGET_FPS 120
const int maxRadius = 25;
const int paintTankMax = 250;
const Color playerColor = { 0, 121, 241, 255 };
typedef enum Direction {
NORTH,
WEST,
EAST,
SOUTH,
} Direction;
typedef struct Pixel {
int x;
int y;
} Pixel;
typedef struct GameState {
float dt;
float time_elapsed;
int frame_count;
Texture displayTexture;
Image imageBuffer;
int paintTank;
bool joystickActive;
Vector2 joystickStartPos;
Vector2 playerPos;
Vector2 playerDir;
Color colorUnderPlayer;
int *bfsBuffer;
Pixel *queue;
int q_head;
int q_tail;
Pixel *fill;
int fillCount;
bool once;
bool nomore;
Texture debugBfsTx;
Image debugBfsImg;
} GameState;
int PixelToIndex(Pixel p) {return p.y * IMG_W + p.x;}
Pixel IndexToPixel(int idx) {return (Pixel) {idx % IMG_W, (int)(idx / IMG_W) }; }
// Pixel IsInBounds(int idx, Direction direction) {return (Pixel) {idx % IMG_W, (int)(idx / IMG_W) }; }
GameState *Init() {
SetTraceLogLevel(LOG_ERROR);
InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Flood Fill");
SetTargetFPS(TARGET_FPS);
Image imageBuffer = GenImageColor(IMG_W, IMG_H, WHITE);
Image debugBfsImg = GenImageColor(IMG_W, IMG_H, BLACK);
GameState state = {
.imageBuffer = imageBuffer,
.displayTexture = LoadTextureFromImage(imageBuffer),
.debugBfsImg = debugBfsImg,
.debugBfsTx = LoadTextureFromImage(debugBfsImg),
.playerPos = (Vector2){40, 40},
.paintTank = paintTankMax,
.colorUnderPlayer = BLUE,
.bfsBuffer = RL_CALLOC(IMG_SIZE, sizeof(int)),
.queue = RL_CALLOC(IMG_SIZE, sizeof(Pixel)),
.fill = RL_CALLOC(IMG_SIZE, sizeof(Pixel)),
};
int r = IMG_W * 0.2f;
ImageDrawCircle(&state.imageBuffer, 0, 0, r, playerColor);
ImageDrawCircle(&state.imageBuffer, IMG_W, 0, r, GREEN);
ImageDrawCircle(&state.imageBuffer, 0, IMG_H, r, VIOLET);
ImageDrawCircle(&state.imageBuffer, IMG_W, IMG_H, r, ORANGE);
UpdateTexture(state.displayTexture, state.imageBuffer.data);
GameState *state_ptr = RL_MALLOC(sizeof(GameState));
memcpy(state_ptr, &state, sizeof(GameState));
return state_ptr;
}
void Input(GameState *S) {
if (IsMouseButtonPressed(0)) {
S->joystickStartPos = GetMousePosition();
S->joystickActive = true;
}
if (IsMouseButtonReleased(0)) {
S->joystickActive = false;
}
}
void Update(GameState *S) {
bool dirty = false;
if (S->joystickActive) {
Vector2 dist = Vector2Subtract(GetMousePosition(), S->joystickStartPos);
Vector2 joystickDelta = Vector2Normalize(dist);
Vector2 dir = Vector2Normalize(joystickDelta);
S->playerDir = Vector2Lerp(S->playerDir, dir, S->dt * 5.0f);
S->playerPos = Vector2Add(S->playerPos, Vector2Scale(S->playerDir, 100.0f * S->dt));
Vector2 minBounds = (Vector2){25.0f, 25.0f};
Vector2 maxBounds = (Vector2){SCREEN_WIDTH - 25.0f, SCREEN_HEIGHT - 25.0f};
S->playerPos = Vector2Clamp(S->playerPos, minBounds, maxBounds);
float r = (float)S->paintTank / 10.0f;
dist = Vector2Scale(S->playerDir, r + 1);
Vector2 offset = Vector2Add(S->playerPos, dist);
S->colorUnderPlayer = GetImageColor(S->imageBuffer, offset.x, offset.y);
float mag = Vector2Length(joystickDelta);
if (mag > 0.0f) {
bool sameColor = ColorIsEqual(S->colorUnderPlayer, playerColor);
if (sameColor && S->paintTank < paintTankMax) {
S->paintTank += 4;
S->paintTank = S->paintTank > paintTankMax ? paintTankMax : S->paintTank;
} else {
S->paintTank -= 1;
S->paintTank = S->paintTank < 0 ? 0 : S->paintTank;
}
if (S->paintTank > 0 && !sameColor) {
ImageDrawCircleV(&S->imageBuffer, S->playerPos, r, BLUE);
UpdateTexture(S->displayTexture, S->imageBuffer.data);
dirty = true;
}
}
}
if (!dirty || S->once) {
// if (true) {
return;
}
for (int i = 0; i < IMG_SIZE; i++) {
Pixel pixel = IndexToPixel(i);
Color color = GetImageColor(S->imageBuffer, pixel.x, pixel.y);
bool sameColor = ColorIsEqual(color, playerColor);
if (S->bfsBuffer[i] != 0 || sameColor) {
continue;
}
// printf("X: %d Y: %d - C: %d IDX: %d \n", pixel.x, pixel.y, sameColor, S->bfsBuffer[i]);
S->queue[0] = pixel;
S->q_tail++;
bool edgeDetected = false;
while (S->q_head != S->q_tail) {
Pixel pixel = S->queue[S->q_head++];
int idx = PixelToIndex(pixel);
int left = abs(pixel.y * IMG_W - idx) - 1 >= 0 ? idx - 1 : -1;
int right = abs(pixel.y * IMG_W - idx) + 1 < IMG_W ? idx + 1 : IMG_W;
int top = idx - IMG_W;
int bottom = idx + IMG_W;
int directions[] = { left, right, top, bottom };
for (int i = 0; i < 4; i++) {
int j = directions[i];
// printf("IDX: %d ||| %d %d %d %d \n", idx, left, right, top, bottom);
if ((i < 2 && j >= 0 && j < IMG_W) || (i >= 2 && j >= 0 && j < IMG_SIZE)) {
Pixel pixel = IndexToPixel(j);
Color c = GetImageColor(S->imageBuffer, pixel.x, pixel.y);
if (S->bfsBuffer[j] == 0 && !ColorIsEqual(playerColor, c)) {
// printf("IDX: %d J: %d PX: %d PY: %d\n", idx, j, pixel.x, pixel.y);
S->queue[S->q_tail++] = pixel;
} else if (S->bfsBuffer[j] == 0) {
S->bfsBuffer[j] = -1;
}
} else {
edgeDetected = true;
}
}
S->bfsBuffer[idx] = 1;
if (!edgeDetected) {
S->fill[S->fillCount++] = pixel;
} else {
}
}
// This should only get called twice
// printf("PX: %d PY: %d\n", pixel.x, pixel.y);
// Fill that sucker
if (!edgeDetected) {
printf("no dice\n");
for (int i = 0; i < S->fillCount; i++) {
Pixel p = S->fill[i];
ImageDrawPixel(&S->imageBuffer, p.x, p.y, playerColor);
}
UpdateTexture(S->displayTexture, S->imageBuffer.data);
}
S->fillCount = 0;
S->q_head = S->q_tail = 0;
}
// memset(S->bfsBuffer, 0, IMG_SIZE * sizeof(int));
S->once = true;
// printf("===========================\n");
}
void Draw2D(const GameState *S) {
Rectangle source = {0, 0, IMG_W, IMG_H};
Rectangle dest = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT};
DrawTexturePro(S->displayTexture, source, dest, (Vector2){0,0}, 0, WHITE);
DrawCircleV(S->playerPos, 20, BLACK);
Vector2 lookPos = Vector2Scale(S->playerDir, 50.0f);
lookPos = Vector2Add(S->playerPos, lookPos);
DrawLineV(S->playerPos, lookPos, RED);
int barWidth = 50, barHeight = 12;
float perc = S->paintTank / (float)paintTankMax;
float barX = S->playerPos.x - barWidth / 2;
float barY = S->playerPos.y + 10;
Rectangle barBGRect = {barX, barY, barWidth, barHeight};
Rectangle barFGRect = {barX, barY, barWidth * perc, barHeight};
DrawRectangleRec(barBGRect, GRAY);
DrawRectangleRec(barFGRect, RED);
if (S->joystickActive) {
Color grayish = {200, 200, 200, 100};
DrawRing(S->joystickStartPos, 60, 80, 0.0f, 360.0f, 50, grayish);
Vector2 mousePos = GetMousePosition();
Vector2 padDir = Vector2Normalize(Vector2Subtract(mousePos, S->joystickStartPos));
Vector2 offsetPos = Vector2Add(S->joystickStartPos, Vector2Scale(padDir, 30.0f));
Vector2 clampedPos = Vector2Clamp(mousePos, S->joystickStartPos, offsetPos);
DrawCircleV(clampedPos, 25, grayish);
}
}
void Draw2DDebug(GameState *S) {
Rectangle rect = {SCREEN_WIDTH / 2 - 50, SCREEN_HEIGHT - 100, 110, 50};
DrawRectangleRec(rect, S->colorUnderPlayer);
DrawRectangleLinesEx(rect, 2, BLACK);
if (S->once) {
if (!S->nomore) {
// Color color = BLACK;
for (int i = 0; i < IMG_W; i++) {
for (int j = 0; j < IMG_H; j++) {
// int idx = S->bfsBuffer[i];
// int idx = 2;
// if (idx == 0) {
// color = PINK;
// } else if (idx == 1) {
// color = MAGENTA;
// } else if (idx == -1) {
// color = LIME;
// } else {
// // printf("Not even one right? %d\n", idx);
// }
// Pixel p = IndexToPixel(i);
// printf("X: %d Y: %d\n", p.x, p.y);
// ImageDrawPixel(&S->debugBfsImg, i, j, RED);
}
}
printf("What the hell is going on?\n");
ImageDrawPixel(&S->debugBfsImg, 0, 0, RED);
UpdateTexture(S->debugBfsTx, &S->debugBfsImg);
}
Rectangle source = {0, 0, IMG_W, IMG_H};
Vector2 pos = {SCREEN_WIDTH * 0.5f - IMG_W * 0.5f,SCREEN_HEIGHT * 0.5f - IMG_H * 0.5f};
Rectangle dest = {pos.x, pos.y, IMG_W, IMG_H};
// DrawTexturePro(S->debugBfsTx, source, dest, (Vector2){0,0}, 0, WHITE);
DrawTexture(S->debugBfsTx, 300, 200, WHITE);
S->nomore = true;
}
DrawFPS(rect.x + 17, SCREEN_HEIGHT - 33);
}
int main(void) {
GameState *state = Init();
while (!WindowShouldClose()) {
state->dt = GetFrameTime();
state->time_elapsed = GetTime();
Input(state);
Update(state);
BeginDrawing();
{
ClearBackground(RAYWHITE);
Draw2D(state);
Draw2DDebug(state);
}
EndDrawing();
state->frame_count++;
}
UnloadImage(state->imageBuffer);
UnloadTexture(state->displayTexture);
UnloadImage(state->debugBfsImg);
UnloadTexture(state->debugBfsTx);
CloseWindow();
free(state->fill);
free(state->queue);
free(state->bfsBuffer);
free(state);
}