Created
January 18, 2025 22:34
Diamond-square Algorithm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// gui.h | |
// | |
// Linux: | |
// Dependencies (Ubuntu package names): libx11-dev, libxext-dev | |
// Compile: cc main.c -lX11 -Xext | |
// | |
// Windows: | |
// Dependencies: user32.lib, gdi32.lib | |
// Compile: cc main.c -lgdi32 -luser32 | |
#ifndef GUI_H | |
#define GUI_H | |
#ifdef __cplusplus | |
extern "C" { | |
#endif | |
#ifdef GUI_STATIC_IMPLEMENTATION | |
#define GUI_API static | |
#else | |
#define GUI_API | |
#endif | |
#ifndef GUI_PRINT_ERROR | |
#define GUI_PRINT_ERROR(format, ...) | |
#endif | |
#ifndef GUI_U8_T | |
#define GUI_U8_T unsigned char | |
#define GUI_I8_T char | |
#define GUI_U16_T unsigned short | |
#define GUI_I16_T short | |
#define GUI_U32_T unsigned int | |
#define GUI_I32_T int | |
#define GUI_U64_T unsigned long long | |
#define GUI_I64_T long long | |
#endif | |
#ifndef GUI_USIZE_T | |
#define GUI_USIZE_T unsigned long | |
#define GUI_ISIZE_T long | |
#endif | |
#if !defined(__cplusplus) && \ | |
!defined(bool) && \ | |
(!defined(__STDC_VERSION__) || __STDC_VERSION__ < 202300L) | |
#define true 1 | |
#define false 0 | |
#define bool _Bool | |
#endif | |
#if defined(__clang__) | |
#define GUI_COMPILER_CLANG | |
#elif defined(__GNUC__) | |
#define GUI_COMPILER_GCC | |
#elif defined(_MSC_VER) | |
#define GUI_COMPILER_MSVC | |
#else | |
#error "Compiler is not supported." | |
#endif | |
#if defined(_WIN32) || defined(_WIN64) | |
#define GUI_PLATFORM_WINDOWS | |
#elif defined(__linux__) || defined(__gnu_linux__) | |
#define GUI_PLATFORM_LINUX | |
#else | |
#error "Platform is not supported." | |
#endif | |
#ifndef GUI_ALLOCATOR_T | |
#define GUI_USE_DEFAULT_ALLOCATOR | |
#if defined(GUI_PLATFORM_WINDOWS) | |
#define GUI_ALLOCATOR_T void * | |
#elif defined(GUI_PLATFORM_LINUX) | |
#define GUI_ALLOCATOR_T void * | |
#else | |
#error "No default allocator is provided for the current platform." | |
#endif | |
#endif | |
typedef GUI_ALLOCATOR_T GuiAllocator; | |
typedef GUI_U8_T u8; | |
typedef GUI_I8_T i8; | |
typedef GUI_U16_T u16; | |
typedef GUI_I16_T i16; | |
typedef GUI_U32_T u32; | |
typedef GUI_I32_T i32; | |
typedef GUI_U64_T u64; | |
typedef GUI_I64_T i64; | |
typedef GUI_USIZE_T usize; | |
typedef GUI_ISIZE_T isize; | |
typedef float f32; | |
typedef double f64; | |
// ================================================================================================= | |
// Library interface | |
// ================================================================================================= | |
#define GUI_WINDOW_MAX_WIDTH ((isize) 16384) | |
#define GUI_WINDOW_MAX_HEIGHT ((isize) 16384) | |
typedef struct GuiWindow GuiWindow; | |
GUI_API GuiWindow *gui_window_create( | |
GuiAllocator allocator, | |
isize width, | |
isize height, | |
char const *title | |
); | |
GUI_API bool gui_window_should_close(GuiWindow *window); | |
GUI_API bool gui_window_resized(GuiWindow *window); | |
GUI_API void gui_window_size(GuiWindow *window, isize *width, isize *height); | |
// VK_OEM_CLEAR | |
#define GUI_MAX_KEY 0xFE | |
// Same values as in Windows virtual key codes: | |
// https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes | |
typedef enum { | |
GUI_KEY_NONE = 0x00, | |
GUI_KEY_ENTER = 0x0d, | |
GUI_KEY_ESCAPE = 0x1b, | |
GUI_KEY_SPACE = 0x20, | |
GUI_KEY_LEFT = 0x25, | |
GUI_KEY_RIGHT = 0x26, | |
GUI_KEY_UP = 0x27, | |
GUI_KEY_DOWN = 0x28, | |
GUI_KEY_A = 0x41, | |
GUI_KEY_B = 0x42, | |
GUI_KEY_C = 0x43, | |
GUI_KEY_D = 0x44, | |
GUI_KEY_E = 0x45, | |
GUI_KEY_F = 0x46, | |
GUI_KEY_G = 0x47, | |
GUI_KEY_H = 0x48, | |
GUI_KEY_I = 0x49, | |
GUI_KEY_J = 0x4a, | |
GUI_KEY_K = 0x4b, | |
GUI_KEY_L = 0x4c, | |
GUI_KEY_M = 0x4d, | |
GUI_KEY_N = 0x4e, | |
GUI_KEY_O = 0x4f, | |
GUI_KEY_P = 0x50, | |
GUI_KEY_Q = 0x51, | |
GUI_KEY_R = 0x52, | |
GUI_KEY_S = 0x53, | |
GUI_KEY_T = 0x54, | |
GUI_KEY_U = 0x55, | |
GUI_KEY_V = 0x56, | |
GUI_KEY_W = 0x57, | |
GUI_KEY_X = 0x58, | |
GUI_KEY_Y = 0x59, | |
GUI_KEY_Z = 0x5a, | |
} GuiKeyboardKey; | |
GUI_API bool gui_key_down(GuiWindow *window, GuiKeyboardKey key); | |
GUI_API bool gui_key_up(GuiWindow *window, GuiKeyboardKey key); | |
typedef struct GuiBitmap GuiBitmap; | |
GUI_API GuiBitmap *gui_window_bitmap(GuiWindow *window); | |
GUI_API void gui_bitmap_size(GuiBitmap *bitmap, isize *width, isize *height); | |
GUI_API u32 *gui_bitmap_data(GuiBitmap *bitmap); | |
GUI_API void gui_bitmap_render(GuiBitmap *bitmap); | |
GUI_API f64 gui_window_time(GuiWindow *window); | |
GUI_API f64 gui_window_frame_time(GuiWindow *window); | |
GUI_API void gui_window_destroy(GuiWindow *window); | |
#ifdef __cplusplus | |
} | |
#endif | |
#endif // GUI_H | |
// ================================================================================================= | |
// Implementation section | |
// ================================================================================================= | |
#if defined(GUI_IMPLEMENTATION) && !defined(GUI_IMPLEMENTED) | |
#define GUI_IMPLEMENTED | |
#ifndef NULL | |
#define NULL ((void *) 0) | |
#endif | |
#ifndef ARRAY_SIZE | |
#define ARRAY_SIZE(array) ((isize) sizeof(array) / (isize) sizeof((array)[0])) | |
#endif | |
static bool gui__window_size_out_of_bounds(isize width, isize height) { | |
return | |
width < 0 || width > GUI_WINDOW_MAX_WIDTH || | |
height < 0 || height > GUI_WINDOW_MAX_HEIGHT; | |
} | |
// ================================================================================================= | |
// Linux (libX11) platform code | |
// ================================================================================================= | |
#ifdef GUI_PLATFORM_LINUX | |
#include <X11/Xlib.h> | |
#include <X11/Xutil.h> | |
#include <X11/extensions/XShm.h> | |
#include <sys/shm.h> | |
#include <sys/ipc.h> | |
#include <time.h> | |
#ifdef GUI_USE_DEFAULT_ALLOCATOR | |
#include <stdlib.h> | |
#define GUI_ALLOC(allocator, size) ((void) (allocator), malloc(size)) | |
#define GUI_DEALLOC(allocator, ptr) ((void) (allocator), free(ptr)) | |
#endif | |
struct GuiBitmap { | |
GuiWindow *window; | |
XImage *image; | |
XShmSegmentInfo shared_segment_info; | |
bool available; | |
isize shared_buffer_size; | |
isize width; | |
isize height; | |
}; | |
struct GuiWindow { | |
Display *x11_display; | |
Window x11_window; | |
XVisualInfo x11_visual_info; | |
Atom delete_window_atom; | |
GuiKeyboardKey keycode_to_key[256]; | |
struct { | |
struct timespec created_time; | |
struct timespec last_update_time; | |
f64 last_frame_delta; | |
} timer; | |
GuiAllocator allocator; | |
bool should_close; | |
bool keys[GUI_MAX_KEY + 1]; | |
isize width; | |
isize height; | |
bool resized; | |
GuiBitmap bitmaps[2]; | |
isize active_bitmap_index; | |
}; | |
static bool gui__bitmap_create(GuiWindow *window, GuiBitmap *bitmap) { | |
isize bitmap_width = window->width; | |
isize bitmap_height = window->height; | |
XImage *image = XShmCreateImage( | |
window->x11_display, | |
window->x11_visual_info.visual, | |
(unsigned int) window->x11_visual_info.depth, | |
ZPixmap, | |
NULL, | |
&bitmap->shared_segment_info, | |
(unsigned int) bitmap_width, | |
(unsigned int) bitmap_height | |
); | |
if (image == NULL) { | |
GUI_PRINT_ERROR("Failed to create an X11 image for the bitmap.\n"); | |
return false; | |
} | |
isize shared_buffer_size = image->bytes_per_line * image->height; | |
int shared_memory_id = shmget( | |
IPC_PRIVATE, | |
(size_t) shared_buffer_size, | |
IPC_CREAT | 0600 | |
); | |
if (shared_memory_id == -1) { | |
GUI_PRINT_ERROR("Failed to allocate shared memory for the bitmap image.\n"); | |
return false; | |
} | |
char *image_memory = shmat(shared_memory_id, 0, 0); | |
image->data = image_memory; | |
bitmap->shared_segment_info.shmid = shared_memory_id; | |
bitmap->shared_segment_info.shmaddr = image_memory; | |
bitmap->shared_segment_info.readOnly = false; | |
XShmAttach(window->x11_display, &bitmap->shared_segment_info); | |
bitmap->window = window; | |
bitmap->image = image; | |
bitmap->available = true; | |
bitmap->width = bitmap_width; | |
bitmap->height = bitmap_height; | |
bitmap->shared_buffer_size = shared_buffer_size; | |
return true; | |
} | |
static void gui__bitmap_destroy(GuiBitmap *bitmap) { | |
if (bitmap->image != NULL) { | |
XShmDetach(bitmap->window->x11_display, &bitmap->shared_segment_info); | |
XDestroyImage(bitmap->image); | |
shmdt(bitmap->shared_segment_info.shmaddr); | |
shmctl(bitmap->shared_segment_info.shmid, IPC_RMID, 0); | |
for (isize i = 0; i < sizeof(GuiBitmap); i += 1) { | |
((u8 *) bitmap)[i] = 0; | |
} | |
} | |
} | |
static bool gui__bitmap_fit_to_window(GuiWindow *window, GuiBitmap *bitmap) { | |
isize bitmap_required_size = window->width * window->height * (isize) sizeof(u32); | |
if (bitmap->shared_buffer_size >= bitmap_required_size) { | |
int shared_memory_id = bitmap->shared_segment_info.shmid; | |
XShmDetach(window->x11_display, &bitmap->shared_segment_info); | |
XDestroyImage(bitmap->image); | |
XImage *image = XShmCreateImage( | |
window->x11_display, | |
window->x11_visual_info.visual, | |
(unsigned int) window->x11_visual_info.depth, | |
ZPixmap, | |
NULL, | |
&bitmap->shared_segment_info, | |
(unsigned int) window->width, | |
(unsigned int) window->height | |
); | |
if (image == NULL) { | |
GUI_PRINT_ERROR("Failed to create an X11 image for the bitmap.\n"); | |
return false; | |
} | |
char *image_memory = shmat(shared_memory_id, 0, 0); | |
image->data = image_memory; | |
bitmap->shared_segment_info.shmid = shared_memory_id; | |
bitmap->shared_segment_info.shmaddr = image_memory; | |
bitmap->shared_segment_info.readOnly = false; | |
XShmAttach(window->x11_display, &bitmap->shared_segment_info); | |
bitmap->image = image; | |
bitmap->width = window->width; | |
bitmap->height = window->height; | |
bitmap->shared_buffer_size = (isize) image->bytes_per_line * (isize) image->height; | |
} else { | |
gui__bitmap_destroy(bitmap); | |
if (!gui__bitmap_create(window, bitmap)) { | |
return false; | |
} | |
} | |
return true; | |
} | |
GUI_API GuiWindow *gui_window_create( | |
GuiAllocator allocator, | |
isize width, | |
isize height, | |
char const *title | |
) { | |
if (gui__window_size_out_of_bounds(width, height)) { | |
GUI_PRINT_ERROR("Requested window size is out of bounds.\n"); | |
return NULL; | |
} | |
GuiWindow *window = GUI_ALLOC(allocator, sizeof(GuiWindow)); | |
if (window == NULL) { | |
GUI_PRINT_ERROR("Failed to allocate memory for the window data.\n"); | |
return NULL; | |
} | |
Display *x11_display = XOpenDisplay(NULL); | |
if (x11_display == NULL) { | |
GUI_PRINT_ERROR("Failed to open X11 display.\n"); | |
return NULL; | |
} | |
Window x11_root_window = DefaultRootWindow(x11_display); | |
int x11_screen = DefaultScreen(x11_display); | |
XVisualInfo x11_visual_info; | |
if ( | |
!XMatchVisualInfo(x11_display, x11_screen, 24, TrueColor, &x11_visual_info) || | |
x11_visual_info.red_mask != 0x00ff0000 || | |
x11_visual_info.green_mask != 0x0000ff00 || | |
x11_visual_info.blue_mask != 0x000000ff | |
) { | |
GUI_PRINT_ERROR("TrueColor RGB-8-8-8 is not supported.\n"); | |
XCloseDisplay(x11_display); | |
return NULL; | |
} | |
XSetWindowAttributes x11_window_attributes = { | |
.background_pixel = BlackPixel(x11_display, x11_screen), | |
.colormap = XCreateColormap( | |
x11_display, | |
x11_root_window, | |
x11_visual_info.visual, | |
AllocNone | |
), | |
.bit_gravity = StaticGravity, | |
.event_mask = StructureNotifyMask | KeyPress | KeyRelease, | |
}; | |
Window x11_window = XCreateWindow( | |
x11_display, | |
x11_root_window, | |
0, | |
0, | |
(unsigned int) width, | |
(unsigned int) height, | |
0, | |
x11_visual_info.depth, | |
InputOutput, | |
x11_visual_info.visual, | |
CWBackPixel | CWColormap | CWBitGravity | CWEventMask, | |
&x11_window_attributes | |
); | |
if (x11_window == None) { | |
GUI_PRINT_ERROR("Failed to create an X11 window.\n"); | |
XCloseDisplay(x11_display); | |
return NULL; | |
} | |
XStoreName(x11_display, x11_window, title); | |
XMapWindow(x11_display, x11_window); | |
XFlush(x11_display); | |
// https://handmade.network/forums/articles/t/2834-tutorial_a_tour_through_xlib_and_related_technologies#part_4_-_even_better_shutdown | |
// | |
// Ask for a ClientMessage event to get sent when the window is getting closed, so that we could | |
// handle destroying the window ourselves. | |
Atom delete_window_atom = XInternAtom(x11_display, "WM_DELETE_WINDOW", False); | |
XSetWMProtocols(x11_display, x11_window, &delete_window_atom, 1); | |
window->x11_display = x11_display; | |
window->x11_window = x11_window; | |
window->x11_visual_info = x11_visual_info; | |
window->delete_window_atom = delete_window_atom; | |
window->allocator = allocator; | |
window->should_close = false; | |
window->width = width; | |
window->height = height; | |
window->resized = false; | |
for (isize i = 0; i < ARRAY_SIZE(window->keys); i += 1) { | |
window->keys[i] = false; | |
} | |
for (isize i = 0; i < 256; i += 1) { | |
window->keycode_to_key[i] = GUI_KEY_NONE; | |
} | |
struct { | |
KeySym keysym; | |
GuiKeyboardKey key; | |
} keysym_to_key[] = { | |
{XK_space, GUI_KEY_SPACE }, | |
{XK_a, GUI_KEY_A }, | |
{XK_b, GUI_KEY_B }, | |
{XK_c, GUI_KEY_C }, | |
{XK_d, GUI_KEY_D }, | |
{XK_e, GUI_KEY_E }, | |
{XK_f, GUI_KEY_F }, | |
{XK_g, GUI_KEY_G }, | |
{XK_h, GUI_KEY_H }, | |
{XK_i, GUI_KEY_I }, | |
{XK_j, GUI_KEY_J }, | |
{XK_k, GUI_KEY_K }, | |
{XK_l, GUI_KEY_L }, | |
{XK_m, GUI_KEY_M }, | |
{XK_n, GUI_KEY_N }, | |
{XK_o, GUI_KEY_O }, | |
{XK_p, GUI_KEY_P }, | |
{XK_q, GUI_KEY_Q }, | |
{XK_r, GUI_KEY_R }, | |
{XK_s, GUI_KEY_S }, | |
{XK_t, GUI_KEY_T }, | |
{XK_u, GUI_KEY_U }, | |
{XK_v, GUI_KEY_V }, | |
{XK_w, GUI_KEY_W }, | |
{XK_x, GUI_KEY_X }, | |
{XK_y, GUI_KEY_Y }, | |
{XK_z, GUI_KEY_Z }, | |
{XK_Escape, GUI_KEY_ESCAPE }, | |
{XK_Return, GUI_KEY_ENTER }, | |
{XK_Right, GUI_KEY_RIGHT }, | |
{XK_Left, GUI_KEY_LEFT }, | |
{XK_Down, GUI_KEY_DOWN }, | |
{XK_Up, GUI_KEY_UP }, | |
}; | |
for (isize i = 0; i < ARRAY_SIZE(keysym_to_key); i += 1) { | |
KeySym keysym = keysym_to_key[i].keysym; | |
GuiKeyboardKey key = keysym_to_key[i].key; | |
KeyCode keycode = XKeysymToKeycode(x11_display, keysym); | |
window->keycode_to_key[keycode] = key; | |
} | |
for (isize i = 0; i < ARRAY_SIZE(window->bitmaps); i += 1) { | |
window->bitmaps[i].window = window; | |
window->bitmaps[i].image = NULL; | |
window->bitmaps[i].available = true; | |
window->bitmaps[i].shared_segment_info.readOnly = false; | |
window->bitmaps[i].shared_segment_info.shmaddr = NULL; | |
window->bitmaps[i].shared_segment_info.shmid = 0; | |
window->bitmaps[i].shared_segment_info.shmseg = 0; | |
window->bitmaps[i].shared_buffer_size = 0; | |
window->bitmaps[i].width = 0; | |
window->bitmaps[i].height = 0; | |
} | |
window->active_bitmap_index = 0; | |
struct timespec created_time; | |
clock_gettime(CLOCK_MONOTONIC, &created_time); | |
window->timer.created_time = created_time; | |
window->timer.last_update_time = created_time; | |
window->timer.last_frame_delta = 0.0; | |
return window; | |
} | |
static void gui__window_process_events(GuiWindow *window) { | |
while (XPending(window->x11_display) > 0) { | |
XEvent event; | |
XNextEvent(window->x11_display, &event); | |
if (event.type == ConfigureNotify) { | |
window->width = event.xconfigure.width; | |
window->height = event.xconfigure.height; | |
window->resized = true; | |
} else if (event.type == KeyPress) { | |
window->keys[window->keycode_to_key[event.xkey.keycode]] = true; | |
} else if (event.type == KeyRelease) { | |
window->keys[window->keycode_to_key[event.xkey.keycode]] = false; | |
} else if (event.type == DestroyNotify) { | |
if (event.xdestroywindow.window == window->x11_window) { | |
window->should_close = true; | |
} | |
} else if (event.type == ClientMessage) { | |
if (event.xclient.data.l[0] == window->delete_window_atom) { | |
XDestroyWindow(window->x11_display, window->x11_window); | |
window->should_close = true; | |
} | |
} else if (event.type == XShmGetEventBase(window->x11_display) + ShmCompletion) { | |
XShmCompletionEvent *completion_event = (XShmCompletionEvent *) &event; | |
for (isize i = 0; i < ARRAY_SIZE(window->bitmaps); i += 1) { | |
if (window->bitmaps[i].shared_segment_info.shmseg == completion_event->shmseg) { | |
window->bitmaps[i].available = true; | |
break; | |
} | |
} | |
} | |
} | |
} | |
GUI_API bool gui_window_should_close(GuiWindow *window) { | |
if (window == NULL) { | |
return true; | |
} | |
struct timespec current_time; | |
clock_gettime(CLOCK_MONOTONIC, ¤t_time); | |
window->timer.last_frame_delta = | |
(f64) (current_time.tv_sec - window->timer.created_time.tv_sec) + | |
(f64) (current_time.tv_nsec - window->timer.created_time.tv_nsec) / 1e9; | |
window->timer.last_update_time = current_time; | |
window->resized = false; | |
gui__window_process_events(window); | |
return window->should_close; | |
} | |
GUI_API bool gui_window_resized(GuiWindow *window) { | |
return window->resized; | |
} | |
GUI_API void gui_window_size(GuiWindow *window, isize *width, isize *height) { | |
*width = window->width; | |
*height = window->height; | |
} | |
GUI_API bool gui_key_down(GuiWindow *window, GuiKeyboardKey key) { | |
return window->keys[key]; | |
} | |
GUI_API bool gui_key_up(GuiWindow *window, GuiKeyboardKey key) { | |
return !window->keys[key]; | |
} | |
GUI_API f64 gui_window_time(GuiWindow *window) { | |
struct timespec current_time; | |
clock_gettime(CLOCK_MONOTONIC, ¤t_time); | |
return | |
(f64) (current_time.tv_sec - window->timer.created_time.tv_sec) + | |
(f64) (current_time.tv_nsec - window->timer.created_time.tv_nsec) / 1e9; | |
} | |
GUI_API f64 gui_window_frame_time(GuiWindow *window) { | |
return window->timer.last_frame_delta; | |
} | |
GUI_API void gui_window_destroy(GuiWindow *window) { | |
if (window != NULL) { | |
for (isize i = 0; i < ARRAY_SIZE(window->bitmaps); i += 1) { | |
gui__bitmap_destroy(&window->bitmaps[i]); | |
} | |
XCloseDisplay(window->x11_display); | |
GuiAllocator allocator = window->allocator; | |
GUI_DEALLOC(allocator, window); | |
} | |
} | |
GUI_API GuiBitmap *gui_window_bitmap(GuiWindow *window) { | |
while (!window->bitmaps[window->active_bitmap_index].available) { | |
gui__window_process_events(window); | |
} | |
GuiBitmap *bitmap = &window->bitmaps[window->active_bitmap_index]; | |
window->active_bitmap_index = (window->active_bitmap_index + 1) % ARRAY_SIZE(window->bitmaps); | |
if (bitmap->width != window->width || bitmap->height != window->height) { | |
if (!gui__bitmap_fit_to_window(window, bitmap)) { | |
return NULL; | |
} | |
} | |
return bitmap; | |
} | |
GUI_API void gui_bitmap_size(GuiBitmap *bitmap, isize *width, isize *height) { | |
*width = bitmap->width; | |
*height = bitmap->height; | |
} | |
GUI_API void gui_bitmap_render(GuiBitmap *bitmap) { | |
bitmap->available = false; | |
XShmPutImage( | |
bitmap->window->x11_display, | |
bitmap->window->x11_window, | |
DefaultGC(bitmap->window->x11_display, bitmap->window->x11_visual_info.screen), | |
bitmap->image, | |
0, | |
0, | |
0, | |
0, | |
(unsigned int) bitmap->width, | |
(unsigned int) bitmap->height, | |
true | |
); | |
XFlush(bitmap->window->x11_display); | |
} | |
GUI_API u32 *gui_bitmap_data(GuiBitmap *bitmap) { | |
return (u32 *) bitmap->shared_segment_info.shmaddr; | |
} | |
#endif // GUI_PLATFORM_LINUX | |
#ifdef GUI_PLATFORM_WINDOWS | |
#define WIN32_LEAN_AND_MEAN | |
#define NOMINMAX | |
#include <Windows.h> | |
// ================================================================================================= | |
// Atomic operations implementation | |
// ================================================================================================= | |
// I took these from here: | |
// https://github.com/f1nalspace/final_game_tech/blob/master/final_platform_layer.h | |
#if defined(GUI_COMPILER_GCC) || defined(GUI_COMPILER_CLANG) | |
static u32 gui__u32_atomic_load(volatile u32 *source) { | |
u32 result = __sync_add_and_fetch(source, 0); | |
return result; | |
} | |
static void gui__u32_atomic_store(volatile u32 *dest, u32 value) { | |
__sync_synchronize(); | |
__sync_lock_test_and_set(dest, value); | |
} | |
static u32 gui__u32_atomic_exchange(volatile u32 *target, u32 value) { | |
__sync_synchronize(); | |
u32 result = __sync_lock_test_and_set(target, value); | |
return result; | |
} | |
#elif defined(GUI_COMPILER_MSVC) | |
static u32 gui__u32_atomic_load(volatile u32 *source) { | |
u32 result = (u32) InterlockedCompareExchange((volatile LONG *) source, 0, 0); | |
return result; | |
} | |
static void gui__u32_atomic_store(volatile u32 *dest, u32 value) { | |
InterlockedExchange((volatile LONG *) dest, value); | |
} | |
static u32 gui__u32_atomic_exchange(volatile u32 *target, u32 value) { | |
u32 result = (u32) InterlockedExchange((volatile LONG *) target, value); | |
return result; | |
} | |
#endif | |
// ================================================================================================= | |
// Windows platform code | |
// ================================================================================================= | |
#ifdef GUI_USE_DEFAULT_ALLOCATOR | |
#define GUI_ALLOC(allocator, size) \ | |
((void) (allocator), HeapAlloc(GetProcessHeap(), 0, (SIZE_T) (size))) | |
#define GUI_DEALLOC(allocator, ptr) \ | |
((void) (allocator), HeapFree(GetProcessHeap(), 0, ptr)) | |
#endif | |
#define GUI_WINDOW_CREATE_MESSAGE WM_USER | |
#define GUI_WINDOW_CLASS_NAME L"GUI_WINDOW_CLASS_NAME" | |
struct GuiBitmap { | |
GuiWindow *window; | |
HBITMAP handle; | |
HDC device_context; | |
u32 *data; | |
isize width; | |
isize height; | |
}; | |
struct GuiWindow { | |
HWND window_handle; | |
HDC window_device_context; | |
struct { | |
LARGE_INTEGER frequency; | |
LARGE_INTEGER created_time; | |
LARGE_INTEGER last_update_time; | |
f64 last_frame_delta; | |
} timer; | |
GuiAllocator allocator; | |
volatile u32 should_close; | |
volatile u32 keys[GUI_MAX_KEY + 1]; | |
volatile u32 width; | |
volatile u32 height; | |
volatile u32 resized; | |
GuiBitmap bitmap; | |
}; | |
static LRESULT CALLBACK gui__window_procedure( | |
HWND window_handle, | |
UINT message_type, | |
WPARAM w_param, | |
LPARAM l_param | |
) { | |
GuiWindow *window = NULL; | |
if (window_handle != NULL) { | |
window = (GuiWindow *) GetWindowLongPtrW(window_handle, GWLP_USERDATA); | |
} | |
if (window != NULL) { | |
switch (message_type) { | |
case WM_DESTROY: { | |
gui__u32_atomic_store(&window->should_close, true); | |
} break; | |
case WM_SIZE: { | |
u32 new_width = LOWORD(l_param); | |
gui__u32_atomic_store(&window->width, new_width); | |
u32 new_height = HIWORD(l_param); | |
gui__u32_atomic_store(&window->height, new_height); | |
gui__u32_atomic_store(&window->resized, true); | |
} break; | |
case WM_KEYDOWN: { | |
gui__u32_atomic_store(&window->keys[w_param], true); | |
} break; | |
case WM_KEYUP: { | |
gui__u32_atomic_store(&window->keys[w_param], false); | |
} break; | |
} | |
} | |
return DefWindowProcW(window_handle, message_type, w_param, l_param); | |
} | |
typedef struct { | |
GuiWindow *window; | |
WCHAR *title; | |
isize width; | |
isize height; | |
HANDLE created_event; | |
} GuiWindowCreateMessageData; | |
static DWORD gui__event_loop_procedure(LPVOID param) { | |
// Call a function from user32 (PeekMessageW in this case) to set up a message queue. | |
// https://stackoverflow.com/questions/22428066/postthreadmessage-doesnt-work#comment34127566_22433275 | |
{ | |
MSG message; | |
PeekMessageW(&message, NULL, 0, 0, PM_NOREMOVE); | |
HANDLE *event_loop_created_event = (HANDLE *) param; | |
SetEvent(*event_loop_created_event); | |
} | |
MSG message; | |
while (GetMessageW(&message, NULL, 0, 0) > 0) { | |
switch (message.message) { | |
case GUI_WINDOW_CREATE_MESSAGE: { | |
GuiWindowCreateMessageData *message_data = | |
(GuiWindowCreateMessageData *) message.lParam; | |
HWND window_handle = CreateWindowExW( | |
0, | |
GUI_WINDOW_CLASS_NAME, | |
message_data->title, | |
WS_OVERLAPPEDWINDOW, | |
CW_USEDEFAULT, | |
CW_USEDEFAULT, | |
message_data->width, | |
message_data->height, | |
NULL, | |
NULL, | |
GetModuleHandleW(NULL), | |
NULL | |
); | |
if (window_handle != NULL) { | |
SetWindowLongPtrW(window_handle, GWLP_USERDATA, (LONG_PTR) message_data->window); | |
ShowWindow(window_handle, SW_SHOWNORMAL); | |
} | |
message_data->window->window_handle = window_handle; | |
SetEvent(message_data->created_event); | |
} break; | |
default: { | |
TranslateMessage(&message); | |
DispatchMessageW(&message); | |
} break; | |
} | |
} | |
return 0; | |
} | |
GUI_API GuiWindow *gui_window_create( | |
GuiAllocator allocator, | |
isize width, | |
isize height, | |
char const *title | |
) { | |
if (gui__window_size_out_of_bounds(width, height)) { | |
GUI_PRINT_ERROR("Requested window size is out of bounds.\n"); | |
return NULL; | |
} | |
WNDCLASSW window_class_data = { | |
.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC, | |
.lpfnWndProc = gui__window_procedure, | |
.hInstance = GetModuleHandleW(NULL), | |
.hCursor = LoadCursor(NULL, IDC_ARROW), | |
.lpszClassName = GUI_WINDOW_CLASS_NAME, | |
}; | |
ATOM window_class = RegisterClassW(&window_class_data); | |
if (window_class == 0 && GetLastError() != ERROR_CLASS_ALREADY_EXISTS) { | |
GUI_PRINT_ERROR("Failed to create a window class.\n"); | |
return NULL; | |
} | |
// Create a separate thread which would be responsible for creating windows and handling their | |
// events. If we were to handle messages on the same thread where we render frames, message | |
// processing during resizing would interrupt rendering new frames until resizing is finished. | |
// | |
// https://github.com/cmuratori/dtc | |
static HANDLE event_loop_thread_handle = NULL; | |
static DWORD event_loop_thread_id = 0; | |
if (event_loop_thread_handle == NULL) { | |
HANDLE event_loop_created_event = CreateEventW(NULL, true, false, NULL); | |
event_loop_thread_handle = CreateThread( | |
NULL, | |
0, | |
gui__event_loop_procedure, | |
&event_loop_created_event, | |
0, | |
&event_loop_thread_id | |
); | |
if (event_loop_thread_handle == NULL) { | |
GUI_PRINT_ERROR("Failed to create a thread for the window event loop.\n"); | |
return NULL; | |
} | |
if (WaitForSingleObject(event_loop_created_event, 5000) != WAIT_OBJECT_0) { | |
GUI_PRINT_ERROR("Failed to create en event loop for the window.\n"); | |
return NULL; | |
} | |
} | |
GuiWindow *window = (GuiWindow *) GUI_ALLOC(allocator, sizeof(GuiWindow)); | |
if (window == NULL) { | |
GUI_PRINT_ERROR("Failed to allocate memory for the window data.\n"); | |
return NULL; | |
} | |
window->allocator = allocator; | |
window->should_close = false; | |
window->width = (u32) width; | |
window->height = (u32) height; | |
window->resized = false; | |
for (isize i = 0; i < ARRAY_SIZE(window->keys); i += 1) { | |
window->keys[i] = false; | |
} | |
WCHAR wide_title_stack_buffer[256]; | |
int wide_title_size = MultiByteToWideChar(CP_UTF8, 0, title, -1, NULL, 0); | |
WCHAR *wide_title = NULL; | |
if (wide_title_size <= ARRAY_SIZE(wide_title_stack_buffer)) { | |
wide_title = wide_title_stack_buffer; | |
} else { | |
wide_title = (WCHAR *) GUI_ALLOC(allocator, wide_title_size * (isize) sizeof(WCHAR)); | |
} | |
if (wide_title == NULL) { | |
wide_title = wide_title_stack_buffer; | |
wide_title_size = ARRAY_SIZE(wide_title_stack_buffer); | |
} | |
if (MultiByteToWideChar(CP_UTF8, 0, title, -1, wide_title, wide_title_size) == 0) { | |
*wide_title = 0; | |
} | |
GuiWindowCreateMessageData window_create_message_data = { | |
.window = window, | |
.title = wide_title, | |
.width = width, | |
.height = height, | |
.created_event = CreateEventW(NULL, true, false, NULL), | |
}; | |
if (!PostThreadMessageW( | |
event_loop_thread_id, | |
GUI_WINDOW_CREATE_MESSAGE, | |
0, | |
(LPARAM) &window_create_message_data | |
)) { | |
GUI_PRINT_ERROR("Failed to send a message to the event loop thread.\n"); | |
return NULL; | |
} | |
if (WaitForSingleObject(window_create_message_data.created_event, 5000) != WAIT_OBJECT_0) { | |
GUI_PRINT_ERROR("Failed to create a window.\n"); | |
return NULL; | |
} | |
if (wide_title != wide_title_stack_buffer) { | |
GUI_DEALLOC(allocator, wide_title); | |
} | |
if (window->window_handle == NULL) { | |
GUI_PRINT_ERROR("Failed to create a window.\n"); | |
return NULL; | |
} | |
window->window_device_context = GetDC(window->window_handle); | |
BITMAPINFO bitmap_info = { | |
.bmiHeader = { | |
.biSize = sizeof(BITMAPINFOHEADER), | |
.biWidth = width, | |
.biHeight = -height, | |
.biPlanes = 1, | |
.biBitCount = 32, | |
.biCompression = BI_RGB, | |
}, | |
}; | |
window->bitmap.handle = CreateDIBSection( | |
window->window_device_context, | |
&bitmap_info, | |
DIB_RGB_COLORS, | |
(void **) &window->bitmap.data, | |
NULL, | |
0 | |
); | |
window->bitmap.device_context = CreateCompatibleDC(window->window_device_context); | |
HBITMAP default_bitmap = (HBITMAP) SelectObject( | |
window->bitmap.device_context, | |
window->bitmap.handle | |
); | |
DeleteObject(default_bitmap); | |
window->bitmap.width = width; | |
window->bitmap.height = height; | |
window->bitmap.window = window; | |
QueryPerformanceFrequency(&window->timer.frequency); | |
LARGE_INTEGER created_time; | |
QueryPerformanceCounter(&created_time); | |
window->timer.created_time = created_time; | |
window->timer.last_update_time = created_time; | |
window->timer.last_frame_delta = 0.0; | |
return window; | |
} | |
GUI_API bool gui_window_should_close(GuiWindow *window) { | |
if (window == NULL) { | |
return true; | |
} | |
LARGE_INTEGER current_time; | |
QueryPerformanceCounter(¤t_time); | |
LONGLONG elapsed_ticks = current_time.QuadPart - window->timer.last_update_time.QuadPart; | |
window->timer.last_frame_delta = | |
(f64) (elapsed_ticks * 1000000 / window->timer.frequency.QuadPart) / 1e6; | |
window->timer.last_update_time = current_time; | |
return gui__u32_atomic_load(&window->should_close) != false; | |
} | |
GUI_API bool gui_window_resized(GuiWindow *window) { | |
return gui__u32_atomic_exchange(&window->resized, false) != false; | |
} | |
GUI_API void gui_window_size(GuiWindow *window, isize *width, isize *height) { | |
*width = (isize) gui__u32_atomic_load(&window->width); | |
*height = (isize) gui__u32_atomic_load(&window->height); | |
} | |
GUI_API bool gui_key_down(GuiWindow *window, GuiKeyboardKey key) { | |
return gui__u32_atomic_load(&window->keys[key]) != false; | |
} | |
GUI_API bool gui_key_up(GuiWindow *window, GuiKeyboardKey key) { | |
return gui__u32_atomic_load(&window->keys[key]) == false; | |
} | |
GUI_API f64 gui_window_time(GuiWindow *window) { | |
LARGE_INTEGER current_time; | |
QueryPerformanceCounter(¤t_time); | |
LONGLONG elapsed_ticks = current_time.QuadPart - window->timer.created_time.QuadPart; | |
return (f64) (elapsed_ticks * 1000000 / window->timer.frequency.QuadPart) / 1e6; | |
} | |
GUI_API f64 gui_window_frame_time(GuiWindow *window) { | |
return window->timer.last_frame_delta; | |
} | |
GUI_API void gui_window_destroy(GuiWindow *window) { | |
if (window != NULL) { | |
DeleteDC(window->bitmap.device_context); | |
DeleteObject(window->bitmap.data); | |
DeleteObject(window->bitmap.handle); | |
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-destroywindow | |
// > A thread cannot use DestroyWindow to destroy a window created by a different thread. | |
PostMessageW(window->window_handle, WM_CLOSE, 0, 0); | |
GuiAllocator allocator = window->allocator; | |
GUI_DEALLOC(allocator, window); | |
} | |
} | |
GUI_API GuiBitmap *gui_window_bitmap(GuiWindow *window) { | |
GuiBitmap *bitmap = &window->bitmap; | |
isize window_width, window_height; | |
gui_window_size(window, &window_width, &window_height); | |
if (bitmap->width != window_width || bitmap->height != window_height) { | |
BITMAPINFO bitmap_info = { | |
.bmiHeader = { | |
.biSize = sizeof(BITMAPINFOHEADER), | |
.biWidth = window_width, | |
.biHeight = -window_height, | |
.biPlanes = 1, | |
.biBitCount = 32, | |
.biCompression = BI_RGB, | |
}, | |
}; | |
HBITMAP new_bitmap_handle = CreateDIBSection( | |
window->window_device_context, | |
&bitmap_info, | |
DIB_RGB_COLORS, | |
(void **) &bitmap->data, | |
NULL, | |
0 | |
); | |
HDC new_bitmap_device_context = CreateCompatibleDC(window->window_device_context); | |
HBITMAP default_bitmap = (HBITMAP) SelectObject(new_bitmap_device_context, new_bitmap_handle); | |
DeleteObject(default_bitmap); | |
DeleteDC(bitmap->device_context); | |
DeleteObject(bitmap->data); | |
bitmap->device_context = new_bitmap_device_context; | |
bitmap->handle = new_bitmap_handle; | |
bitmap->width = window_width; | |
bitmap->height = window_height; | |
} | |
return bitmap; | |
} | |
GUI_API void gui_bitmap_size(GuiBitmap *bitmap, isize *width, isize *height) { | |
*width = bitmap->width; | |
*height = bitmap->height; | |
} | |
GUI_API void gui_bitmap_render(GuiBitmap *bitmap) { | |
BitBlt( | |
bitmap->window->window_device_context, | |
0, | |
0, | |
bitmap->width, | |
bitmap->height, | |
bitmap->device_context, | |
0, | |
0, | |
SRCCOPY | |
); | |
} | |
GUI_API u32 *gui_bitmap_data(GuiBitmap *bitmap) { | |
return bitmap->data; | |
} | |
#endif // GUI_PLATFORM_WINDOWS | |
#endif // GUI_IMPLEMENTATION |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ================================================================================================= | |
// PCG source code | |
// https://www.pcg-random.org/download.html#minimal-c-implementation | |
// https://github.com/imneme/pcg-c-basic/blob/master/pcg_basic.c | |
// ================================================================================================= | |
#include <stdint.h> | |
// *Really* minimal PCG32 code / (c) 2014 M.E. O'Neill / pcg-random.org | |
// Licensed under Apache License 2.0 (NO WARRANTY, etc. see website) | |
typedef struct { uint64_t state; uint64_t inc; } pcg32_random_t; | |
uint32_t pcg32_random_r(pcg32_random_t* rng) | |
{ | |
uint64_t oldstate = rng->state; | |
// Advance internal state | |
rng->state = oldstate * 6364136223846793005ULL + (rng->inc|1); | |
// Calculate output function (XSH RR), uses old state for max ILP | |
uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u; | |
uint32_t rot = oldstate >> 59u; | |
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); | |
} | |
void pcg32_srandom_r(pcg32_random_t* rng, uint64_t initstate, uint64_t initseq) | |
{ | |
rng->state = 0U; | |
rng->inc = (initseq << 1u) | 1u; | |
pcg32_random_r(rng); | |
rng->state += initstate; | |
pcg32_random_r(rng); | |
} | |
// ================================================================================================= | |
// Diamond-square algorithm plasma visualization | |
// | |
// https://en.wikipedia.org/wiki/Diamond-square_algorithm | |
// https://learn.64bitdragon.com/articles/computer-science/procedural-generation/the-diamond-square-algorithm | |
// https://lodev.org/cgtutor/plasma.html | |
// ================================================================================================= | |
#define GUI_IMPLEMENTATION | |
#include "gui.h" | |
#include <math.h> | |
#include <stdlib.h> | |
f64 f64_random(pcg32_random_t *rng, f64 from, f64 to) { | |
// https://www.pcg-random.org/using-pcg-c-basic.html#generating-doubles | |
// > If you are happy to have a floating point value in the range [0,1) that has been rounded | |
// > down to the nearest multiple of 1/2^32 | |
f64 random_float = ldexp(pcg32_random_r(rng), -32); | |
return from + (to - from) * random_float; | |
} | |
f64 f64_min(f64 self, f64 other) { | |
return self < other ? self : other; | |
} | |
f64 f64_max(f64 self, f64 other) { | |
return self > other ? self : other; | |
} | |
// Based on this: | |
// https://www.rapidtables.com/convert/color/hsv-to-rgb.html | |
u32 hsv_to_rgb(u8 hue, u8 saturation, u8 value) { | |
i32 hue_degrees = (i32) (360.0F * ((f32) hue / 255.0F)) % 360; | |
f32 saturation_norm = (f32) saturation / 255.0F; | |
f32 value_norm = (f32) value / 255.0F; | |
f32 c = value_norm * saturation_norm; | |
f32 x = c * (1.0F - fabsf(fmodf((f32) hue_degrees / 60.0F, 2.0F) - 1.0F)); | |
f32 red, green, blue; | |
switch (hue_degrees / 60) { | |
case 0: red = c; green = x; blue = 0.0F; break; | |
case 1: red = x; green = c; blue = 0.0F; break; | |
case 2: red = 0.0F; green = c; blue = x; break; | |
case 3: red = 0.0F; green = x; blue = c; break; | |
case 4: red = x; green = 0.0F; blue = c; break; | |
case 5: red = c; green = 0.0F; blue = x; break; | |
} | |
f32 m = value_norm - c; | |
return | |
((u32) (u8) ((red + m) * 255.0F) << 16) + | |
((u32) (u8) ((green + m) * 255.0F) << 8 ) + | |
((u32) (u8) ((blue + m) * 255.0F) << 0 ); | |
} | |
int main(void) { | |
pcg32_random_t rng; | |
pcg32_srandom_r(&rng, 0xcafe, 0xbabe); | |
// Outline of the diamond-square algorithm: | |
// 1. Set the corner pixels of the bitmap (which is a square). | |
// 2. Set the pixel in the middle of the square ("diamond" step). | |
// 3. Set the pixels in the middle of each side of the square ("square" step). | |
// 4. Subdivide the square into 4 more squares, repeat the steps 2 and 3 for each of them. | |
// Bitmap size is set to a "power of two minus one", so that there is a pixel right in the | |
// middle. | |
isize noise_size = 1025; | |
f64 *noise = (f64 *) malloc((size_t) (noise_size * noise_size * (isize) sizeof(f64))); | |
for (isize i = 0; i < noise_size * noise_size; i += 1) { | |
noise[i] = 0.0; | |
} | |
f64 random_value_scale = 256.0; | |
f64 random_factor = 2.0; | |
f64 corner_value = f64_random(&rng, 0.0, random_value_scale * random_factor); | |
noise[0 * noise_size + 0] = corner_value; | |
noise[0 * noise_size + (noise_size - 1)] = corner_value; | |
noise[(noise_size - 1) * noise_size + 0] = corner_value; | |
noise[(noise_size - 1) * noise_size + (noise_size - 1)] = corner_value; | |
isize tile = noise_size - 1; | |
while (tile > 1) { | |
isize half_tile = tile / 2; | |
// The diamond step. | |
// | |
// x and y iterate over coordinates of the "squares" (their top-left corners): | |
// - [0,0] | |
// - [0,2] | |
// - [2,0] | |
// - [2,2] | |
// | |
// Unkown pixels are in the middle of each square and we calculate their values based on the | |
// known pixels in the corners of the square. | |
// | |
// For example: | |
// bitmap[1,1] = (bitmap[0,0] + bitmap[0,2] + bitmap[2,0] + bitmap[2,2]) / 4 + random | |
// | |
// 0 1 2 3 4 | |
// +-----------+ | |
// 0 | X X X X X | | |
// | \ / \ / | | |
// 1 | X ? X ? X | | |
// | / \ / \ | | |
// 2 | X X X X X | | |
// | \ / \ / | | |
// 3 | X ? X ? X | | |
// | / \ / \ | | |
// 4 | X X X X X | | |
// +-----------+ | |
for (isize x = 0; x < noise_size - 1; x += tile) { | |
for (isize y = 0; y < noise_size - 1; y += tile) { | |
f64 average = | |
noise[y * noise_size + x] + | |
noise[y * noise_size + (x + tile)] + | |
noise[(y + tile) * noise_size + x] + | |
noise[(y + tile) * noise_size + (x + tile)]; | |
average /= 4.0; | |
f64 noise_value = | |
average + | |
f64_random(&rng, -random_value_scale, random_value_scale); | |
noise[(y + half_tile) * noise_size + (x + half_tile)] = noise_value; | |
} | |
} | |
// The square step. | |
// | |
// Here x and y iterate over the unkown pixels themselves: | |
// - [0,1] and [4,1] is set to the same value | |
// - [0,3] and [4,3] is set to the same value | |
// - [1,0] and [1,4] is set to the same value | |
// - [1,2] | |
// - [2,1] | |
// - [2,3] | |
// - [3,0] and [3,4] is set to the same value | |
// - [3,2] | |
// | |
// We calculate the unknown pixels based on the adjacent pixels to the left/right and at the | |
// top/bottom relative to the unknown pixel. | |
// | |
// For example: | |
// bitmap[2,1] = (bitmap[1,1] + bitmap[3,1] + bitmap[2,0] + bitmap[2,2]) / 4 + random | |
// | |
// Another example (boundary pixel): | |
// bitmap[0,1] = (bitmap[3,1] + bitmap[1,1] + bitmap[0,0] + bitmap[0,2]) / 4 + random | |
// | |
// 0 1 2 3 4 | |
// +-------------------+ | |
// 0 | X - ? - X - ? - X | | |
// | | | | | | | | |
// 1 | ? - X - ? - X - ? | | |
// | | | | | | | | |
// 2 | X - ? - X - ? - X | | |
// | | | | | | | | |
// 3 | ? - X - ? - X - ? | | |
// | | | | | | | | |
// 4 | X - ? - X - ? - X | | |
// +-------------------+ | |
for (isize x = 0; x < noise_size - 1; x += half_tile) { | |
for (isize y = (x + half_tile) % tile; y < noise_size - 1; y += tile) { | |
f64 average = | |
noise[y * noise_size + (x - half_tile + noise_size - 1) % (noise_size - 1)] + | |
noise[y * noise_size + (x + half_tile) % (noise_size - 1)] + | |
noise[((y + half_tile) % (noise_size - 1)) * noise_size + x] + | |
noise[((y - half_tile + noise_size - 1) % (noise_size - 1)) * noise_size + x]; | |
average /= 4.0; | |
f64 noise_value = | |
average + | |
f64_random(&rng, -random_value_scale, random_value_scale); | |
noise[y * noise_size + x] = noise_value; | |
if (x == 0) { | |
noise[y * noise_size + (noise_size - 1)] = noise_value; | |
} | |
if (y == 0) { | |
noise[(noise_size - 1) * noise_size + x] = noise_value; | |
} | |
} | |
} | |
random_value_scale = f64_max(random_value_scale / random_factor, 1.0 / random_factor); | |
tile /= 2; | |
} | |
f64 noise_min = INFINITY; | |
f64 noise_max = -INFINITY; | |
for (isize i = 0; i < noise_size * noise_size; i += 1) { | |
noise_min = f64_min(noise[i], noise_min); | |
noise_max = f64_max(noise[i], noise_max); | |
} | |
for (isize i = 0; i < noise_size * noise_size; i += 1) { | |
noise[i] = (noise[i] - noise_min) / (noise_max - noise_min); | |
} | |
u32 palette[256]; | |
for (isize hue = 0; hue < 256; hue += 1) { | |
palette[hue] = hsv_to_rgb((u8) hue, 175, 240); | |
} | |
GuiWindow *window = gui_window_create(NULL, 640, 480, "Diamond-square Algorithm"); | |
while (!gui_window_should_close(window)) { | |
if (gui_key_down(window, GUI_KEY_Q)) { | |
break; | |
} | |
double time = gui_window_time(window); | |
GuiBitmap *bitmap = gui_window_bitmap(window); | |
isize bitmap_width, bitmap_height; | |
gui_bitmap_size(bitmap, &bitmap_width, &bitmap_height); | |
u32 *bitmap_iter = gui_bitmap_data(bitmap);; | |
for (isize y = 0; y < bitmap_height; y += 1) { | |
for (isize x = 0; x < bitmap_width; x += 1) { | |
f64 noise_value = | |
noise[(y % (noise_size - 1)) * noise_size + (x % (noise_size - 1))]; | |
*bitmap_iter = palette[(isize) (255.0 * noise_value + 64.0 * time) % 256]; | |
bitmap_iter += 1; | |
} | |
} | |
gui_bitmap_render(bitmap); | |
} | |
gui_window_destroy(window); | |
free(noise); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment