HOME / BLOG / How I run my cub3d in the web
How I run my cub3d in the web
5 days
2025 09
This walk-through will show you how I managed to run my cub3d (in C with mlx) into the web.
This is how my cub3d structure looks like. This is more less how I delivered the project.


The cube executable must be provided a map which is not ideal. We will fix this later, we can simply add a small menu screen where you can choose a level. But for now I will hard code the map, so instead of passing argv[1] wherever in you code, just pass a hardcoded string. And prevent the program from displaying an error when no arguments are passed.

Here is how I did it:

Now I can launch the game simply with:
./cube3d
Fortunately I didn't include <mlx.h> in all my source files, instead I was using a centralized header "cub3d.h", so I can simlpy switch from mlx.h to MLX42.h.
The next step is to clone the MLX42 library and compile it:
git clone https://github.com/codam-coding-college/MLX42 cd MLX42 mkdir build cmake -B build
Then you can run the Makefile in the build directory to make the library
cd build make
Now we can try to compile our project with MLX42 to see how many errors we get:
I had to make sure my makefile included the following:
-LMLX42/build -lmlx42 -ldl -lglfw -lm -pthread # linking mlx42 lib -IMLX42/include # linking MLX42 headers
And I also made sure to replace "mlx.h" by "MLX42/MLX42.h"

We see: mlx_destroy_image & mlx_put_image_to_window which are supported by mlx42 but not by MLX42.
Instead MLX42 provides:
mlx_delete_image
mlx_image_to_window
But of course these functions must receive the
mlx_t* mlx
which is not present in our current setup, although mlx.h had something similar, which is a void * when you call mlx_init().
Another small detail, the origina mlx.h normally requires to track two(2) pointers. After calling mlx_init and also after calling mlx_new_window. With mlx42.h we don't need to care about generating a new window, since this is an abstraction in the library code, and the window is not needed explicitly, we only heve the mlx_t * pointer.
You can read more about it here:
https://github.com/codam-coding-college/MLX42/wiki/Basics#main
For this reason I will modify my structure, which I used to store the mlx_ptr...
Another small detail, the origina mlx.h normally requires to track two(2) pointers. After calling mlx_init and also after calling mlx_new_window. With mlx42.h we don't need to care about generating a new window, since this is an abstraction in the library code, and the window is not needed explicitly, we only heve the mlx_t * pointer.
You can read more about it here:
https://github.com/codam-coding-college/MLX42/wiki/Basics#main
For this reason I will modify my structure, which I used to store the mlx_ptr...
typedef struct s_window { void *mlx_ptr; void *win_ptr; int height; int width; } t_window;
Becomes:
typedef struct s_window { mlx_t *mlx; int height; int width; } t_window;
I had a function which created my t_window:
win = new_window(WIN_H, WIN_W, "cub3D");
Let's modify this function to make sure it complies with the new data structure.

Let's just continue with the main to clean the main and go back to the compilation errors.

We changed new_window, so it will properly setup our structure, but we are using the win->win_ptr & win->mlx_ptr which are not present anymore.
Instead we will implement the following functions:
Instead we will implement the following functions:
bool mlx_loop_hook(mlx_t* mlx, void (*f)(void*), void* param); void mlx_key_hook(mlx_t* mlx, mlx_keyfunc func, void* param); void mlx_close_hook(mlx_t* mlx, mlx_closefunc func, void* param);
*Note: (I know my code is not pretty, I was doing this project while learning as a noob, which is a great example to show any project can be easily converted to run on the web!)
Now our main looks compatible with MLX42, init_game which I call in the main is not related to anything with mlx, on the other hand init_game_window does, so we will have a look at that function.

MLX42 provides already an out of the box mlx_image_t structure:
See more here: https://github.com/codam-coding-college/MLX42/wiki/Images#images-how-do-they-work
So we need to modify however we were doing the image management. We NO longer will need the addr and to be honest we don't need actually anything besides the mlx_image_t, but the idea here is just to make the mlx code work, so we can just remove the addr and img_ptr, even if the other members become redundant.
And later we will modify the pieces of code where we were using the addr, for example to get a pixel and it's color.
The mlx_new_image function is the same on mlx.h and MLX42.h so no need to change anything there except for the pointer, which now is mlx.
See more here: https://github.com/codam-coding-college/MLX42/wiki/Images#images-how-do-they-work
So we need to modify however we were doing the image management. We NO longer will need the addr and to be honest we don't need actually anything besides the mlx_image_t, but the idea here is just to make the mlx code work, so we can just remove the addr and img_ptr, even if the other members become redundant.
And later we will modify the pieces of code where we were using the addr, for example to get a pixel and it's color.
The mlx_new_image function is the same on mlx.h and MLX42.h so no need to change anything there except for the pointer, which now is mlx.

Finally mlx_put_image_to_window doesn't exist in MLX42 but a very similar one does:
int32_t mlx_image_to_window(mlx_t* mlx, mlx_image_t* img, int32_t x, int32_t y);
This function is actually way more simpler than mlx.h one, we just need to remove all pointers we were passing and only provide the mlx_image_t pointer.

Finally we can return to the compilation errors we saw earlier...
mlx_destroy_image(conf->img_game->win->mlx_ptr, conf->img_game->img_ptr); mlx_put_image_to_window(conf->img_game->win->mlx_ptr, conf->img_game->win->win_ptr, img_game->img_ptr, 0, 0);
We have similar functions in MLX42.h
void mlx_delete_image(mlx* mlx, mlx_image_t* image) int32_t mlx_image_to_window(mlx_t* mlx, mlx_image_t* img, int32_t x, int32_t y);
After applying all fixes we can recompile to continue fixing more errors:

Now it complains about the return type of the callback functions, let's have a look:
int handle_key_up(int key_code, void *params); int handle_key_down(int key_code, void *params); int exit_game(t_config *conf); int update(t_config *conf);
As you might already know, MLX42.h callbacks should return void. Read more about it here: https://github.com/codam-coding-college/MLX42/wiki/Hooks
Let's change then the return type annotations to void.
Also the key hook receives a custom type instead of just an int, we must also change this.
mlx_key_data_t keydata
Also MLX42.h doesn't allow void * type shadowing, and you must declare your callbacks data pointer as void *, but this is OK you can just simply convert the void* back to the type you need.
For example if my update function was this:
For example if my update function was this:
void update(t_config *conf);
It becomes:
void update(void *ptr); // Inside the function you can retrieve the pointer with a casting { t_config *conf = (t_config*)ptr; if (!ptr) return; ... }
Now to get an image color I had to customise the following functions, instead of using addr, bpp and lin length to get the pixel color, we do the following:
static inline bool in_bounds(const mlx_image_t* img, uint32_t x, uint32_t y) { return x < img->width && y < img->height; } unsigned int get_texture_color(t_image *texture, int x, int y) { if (!in_bounds(texture->img_ptr, x, y)) return 0; // or handle error const uint8_t* p = &texture->img_ptr->pixels[(y * texture->img_ptr->width + x) * 4]; const uint32_t b = p[0]; const uint32_t g = p[1]; const uint32_t r = p[2]; const uint32_t a = p[3]; return (a << 24) | (r << 16) | (g << 8) | b; }
Also what used to be my put_pixel_img:
void put_pixel_img(t_image *data, int x, int y, int color) { char *dst; dst = data->addr + (y * data->line_len + x * (data->bpp / 8)); *(unsigned int *) dst = color; }
became:
void put_pixel_img(mlx_image_t* img, int x, int y, uint32_t color) { if (x < 0 || y < 0 || x >= (int)img->width || y >= (int)img->height) return; uint8_t* p = &img->pixels[(y * img->width + x) * 4]; // unpack TRGB (0xAARRGGBB) into BGRA little-endian bytes p[0] = (uint8_t)(color & 0xFF); // B p[1] = (uint8_t)((color >> 8) & 0xFF); // G p[2] = (uint8_t)((color >> 16) & 0xFF);// R p[3] = (uint8_t)((color >> 24) & 0xFF);// A }
Finally just fix small compilation errors and you will be able to run the program! :)
I prepared a raycasting interactive website for you to try out!
Reach out if you have issues setting up your cub3d! :D GL and hf!
I prepared a raycasting interactive website for you to try out!
Reach out if you have issues setting up your cub3d! :D GL and hf!
