본문 바로가기

42cursus

cub3d - mlx사용법 위주로 정리하기 (42seoul)

0. 프로젝트 목표

- 1인칭으로 3D 미로 표현하기 (그래픽 프로젝트)

- 새로운 라이브러리 minilibx에 대해서 학습하기

- raycasting에 대해서 알고리즘으로 작성하기

- users.atw.hu/wolf3d/ 처럼 만들어보기

- 결과물

 

 

 

1. Makefile

- 기본적으로 라이브러리를 2개 사용 (libft.a, libmlx.a)

- .c -> .o 로 컴파일 할때는 -I 옵션으로 헤더만 찾아주면 됨

- .o를 .exe 파일로 만들 때(링킹할 때)는 -L, -l 옵션으로 라이브러리를 묶어주면 됨

- MacOS에서는 -frameword OpenGL -framework AppKit -lz 가 필수..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
CC = gcc
CFLAGS = -Wall -Wextra -Werror
 
NAME = cub3d
 
SRCS_DIR = ./srcs
SRCS_NAME = #... files.c
SRCS = $(addprefix $(SRCS_DIR)/, $(SRCS_NAME))
OBJS = $(SRCS:.c=.o)
 
LIB_NAME = ft
LIB_DIR = ./lib
LIB = $(addprefix $(LIB_DIR)/, libft.a)
 
MLX_NAME = mlx
MLX_DIR = ./mlx
MLX = $(addprefix $(MLX_DIR)/, libmlx.a)
 
 
$(NAME) : $(OBJS)
    $(MAKE) -C $(LIB_DIR) bonus
    $(MAKE) -C $(MLX_DIR) all
    $(CC) $(CFLAGS) -L$(LIB_DIR) -l$(LIB_NAME) -L$(MLX_DIR) -l$(MLX_NAME) \
           -framework OpenGL -framework AppKit $^ -o $@
 
$(SRCS_DIR)/%.o : $(SRCS_DIR)/%.c
    $(CC) $(CFLAGS) -I$(MLX_DIR) -I$(LIB_DIR) -c $< -o $@
 
 
all : $(NAME)
 
clean :
    $(MAKE) -C $(LIB_DIR) clean
    $(MAKE) -C $(MLX_DIR) clean
    rm -rf $(OBJS)
 
fclean :
    $(MAKE) -C $(LIB_DIR) fclean
    $(MAKE) -C $(MLX_DIR) fclean
    rm -rf $(NAME) $(OBJS)
 
re : fclean all
 
bonus : all
 
.PHONY : all clean fclean re bonus
cs

 

 

 

 

 

2. MiniLibx man page 겉핥기

- man page부터 보기

- 완전한 사용 예시를 보는 것이 아님

- 함수가 뭐가 있는지, 반환값으로 무엇을 하는지에 중점적으로 보기

- 그냥 꼼꼼한 겉핥기? -> 이후에는 42Docs를 보고 따라해 볼 것임

- mms버전에는 man페이지가 존재

 

man mlx

1
void *mlx_init();
cs

- MiniLibX는 간단한 윈도우 인터페이스 라이브러리

- 전반적인 프레임워크 지식이 필요없이 MiniLibX는 그래픽 소프트웨어를 쉽게 만들 수 있음

- 윈도우 창 생성, 그리기, 이미지, 기본적인 이벤트 관리도 가능

- MacOS : 다른 소프트웨어, 이벤트 등을 동시 처리해주는 윈도우서버와 직접적으로 상호작용하고 GPU와 상호작용함

- 올바른 mlx API 사용을 위해 mlx.h가 include 되어야 함 (mlx.h 은 함수프로토타입을 가짐)

- 소프트웨어와 디스플레이간에 연결을 초기화 해야함

- 연결이 되면 MiniLibX의 다른 함수를 display로 부터 메시지를 받거나 보낼 수 있음

- 그 연결을 mlx_init 함수가 함

 

 

man mlx_new_window

1
2
3
void *mlx_new_window(void *mlx_ptr, int size_x, int size_y, char *title);
int  mlx_clear_window(void *mlx_ptr, void *win_ptr);
int  mlx_destroy_window(void *mlx_ptr, void *win_ptr);
cs

- window를 관리함

- mlx_new_window 함수는 size_x, size_y를 이용해서 사이즈를 결정하고, title로 window의 bar를 설정해서 새로운 윈도우를 만듦

- 매개변수로 mlx_ptr은 mlx_init으로 받은 void*를 넣어주면 됨

 -> 리턴값: 성공시, 새로운 윈도우식별자인 void*를 던져 줌

 -> 리턴값: 실패시, NULL 반환

 -> 리턴값은 다른 minilibx 함수에서 사용될 것

- mlx_clear_window 함수는 매개변수로 받은 win_ptr을 검은색으로 clear함 (clear in black)

- mlx_destroy_window 함수는 매개변수로 받은 win_ptr로 윈도우를 destroy함

 -> 둘다 mlx_ptr, win_ptr를 받음에 명심

 -> 리턴값 : 아무것도 아님

 

 

man mlx_new_image

1
2
3
4
5
6
7
8
void            *mlx_new_image(void *mlx_ptr, int width, int height);
char            *mlx_get_data_addr(void *img_ptr, int *bits_per_pixel, int *size_line, int *endian);
int             mlx_put_image_to_window(void *mlx_ptr, void *win_ptr, void *img_ptr, int x, int y);
unsigned int    mlx_get_color_value(void *mlx_ptr, int color);
void            *mlx_xpm_to_image(void *mlx_ptr, char **xpm_data, int *width, int *height);
void            *mlx_xpm_file_to_image(void *mlx_ptr, char *filename, int *width, int *height);
void            *mlx_png_file_to_image(void *mlx_ptr, char *filename, int *width, int *height);
int             mlx_destroy_image(void *mlx_ptr, void *img_ptr);
cs

- image를 Manipulating

- mlx_new_image 함수는 메모리에 이미지를 새로 만듦

 -> 리턴값 : 성공시, image를 조작할 수 있는 이미지식별자를 void*로 리턴

 -> 리턴값 : 실패시, NULL 반환

 -> 대충 읽어보니 이 함수로 이미지를 만들었지만 화면에 띄우지는 않음

- mlx_put_image_to_window 함수는 window에 img를 놓거나 이미지내부를 그림

- mlx_get_data_addr 함수는 만들어진 img의 정보를 리턴함

 -> img_ptr: 사용할 이미지

 -> bits_per_pixel : 픽셀 색을 표현하는데 필요한 비트 수로 채워짐

 -> size_line : 메모리 안에 이미지의 한줄을 저장하기 위해 사용되는 바이트 수

 -> endian : 이미지 안의 픽셀 색깔을 저장하는데 필요한 수치 (0 ~ 1)

 -> 리턴값 : 성공시, image가 저장된 메모리지역의 첫번째 주소를 char*로 리턴

 -> 리턴값 : 실패시, NULL 반환

 -> 읽어봐도 모름, 반환받은 char*로 부터 하나씩 접근해서 픽셀 색깔을 알아낼 수 있는듯

 -> 아마 이런식? : char *[2] -> 3번째 픽셀색깔을 char로 표현

- mlx_destroy_image 함수는 img_ptr로 주어진 이미지를 없앰

- mlx_get_color_value 함수는 디스플레이가 이해할 수 있는, bits_per_pixel 요구사항에 맞는 컬러를 unsigned int로 리턴함

 -> least significant 비트의 위치는 로컬컴퓨터의 엔디안에 의존한다는데 뭔소린지 모름

- mlx_xpm_to_image, mlx_xpm_file_to_image, xml_png_file_to_image 함수는 같은 방식으로 이미지를 만듦

 -> 리턴값은 mlx_new_image 함수와 같음

 -> minilibx가 xpm, png라이브러리 표준을 사용하지 않기 때문에 처리할 수 없을 수도 있음, 투명성은 처리함

 -> 무슨 소린지 모름

 

 

man mlx_pixel_put

1
2
int     mlx_pixel_put(void *mlx_ptr, void *win_ptr, int x, int y, int color);
int     mlx_string_put(void *mlx_ptr, void *win_ptr, int x, int y, int color, char *string);
cs

- 기본적으로 window에 그리기

- mlx_pixel_put 함수는 지정된 컬러로 window의 (x, y)좌표에 픽셀을 그림

- (0, 0)은 좌측상단임 (x는 right값, y는 down값)

- mlx_string_put 함수는 다 똑같으나 string을 (xm y)에 보여줌

 -> 두 함수 모두 window밖에 디스플레이를 지움 -> mlx_pixel_put을 느리게 함 -> image를 고려

 

 

man mlx_loop

1
2
3
4
5
int     mlx_loop(void *mlx_ptr);
int     mlx_key_hook(void *win_ptr, int (*f_ptr)(), void *param);
int     mlx_mouse_hook(void *win_ptr, int (*f_ptr)(), void *param);
int     mlx_expose_hook(void *win_ptr, int (*f_ptr)(), void *param);
int     mlx_loop_hook(void *win_ptr, int (*f_ptr)(), void *param);
cs

- 이벤트 처리함

- 그래픽 시스템은 두방향 (1. screen display 하기, 2. 키보드 마우스 정보 얻어오기)

- mlx_loop() 함수는 이벤트를 받기위해 끝나지 않는 함수임

- mlx_key_hook, mlx_mouse_hook, mlx_expose_hook 함수는 같은 방식으로 작동함

 -> f_ptr함수 포인터는 해당 이벤트가 발생했을 때 호출되는 함수 포인터임

 -> param은 함수가 호출될 때마다 함수에 전달되고 저장하기 위해 사용됨

 -> param의 주소는 수정되거나 사용되지 않음

- mlx_loop_hook 함수는 이벤트가 발생하지 않을 때 발생함

- 함수가 이벤트를 캐치할 때, 임의의 함수를 부를 것

 

=======================================================================================

 >>>>>>  여기까지 봤으면 이제 42Docs보고 따라하기

 

 

 

 

 

3. MiniLibX 42Docs 실습해보기

- harm-smits.github.io/42docs/libs/minilibx

 

MiniLibX

Find code examples, optimization tricks, and much more.

harm-smits.github.io

- 위의 man page 정리한거랑 비슷한 내용이 있을 수 있으나 또 보면 좋으니 또 볼건 봄

 

Color

- 0xTTRRGGBB 로 표현함

 -> 4byte라서 int로 표현할 수 있음 (int가 4byte일 때)

- shift 연산자로 색을 표현하거나 연산이 가능함

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// encoding
int        create_trgb(int t, int r, int g, int b)
{
    return (t << 24 | r << 16 | g << 8 | b);
}
 
// decoding
int        get_t(int trgb)
{
    return (trgb & (0xFF << 24));
}
int        get_r(int trgb)
{
    return (trgb & (0xFF << 16));
}
int        get_g(int trgb)
{
    return (trgb & (0xFF << 8));
}
int        get_b(int trgb)
{
    return (trgb & 0xFF);
}
cs

cf) trgb & (0xFF << 24) 이런식 보다는 0xFF & (trgb >> 24)가 0~255로 리턴값 놀기 더 편할 수도

 

Initialization

- 다른 함수에 접근하기 위해 #include 필요 + mlx_init 함수를 실행

- #include <mlx.h>

- 그래픽 시스템과 연결을 하게 해주고 mlx의 인스턴스인 void *로 리턴함

1
2
3
4
5
6
7
8
9
#include <mlx.h>
 
int        main(void)
{
    void    *mlx;
 
    mlx = mlx_init();
    return (0);
}
cs

- 해당 코드만 실행한 경우 아무것도 보이지 않음 -> window를 만들지 않았기 때문

 

window open

1
2
3
4
5
6
7
8
9
10
11
12
#include <mlx.h>
 
int        main(void)
{
    void    *mlx;
    void    *mlx_win;
 
    mlx = mlx_init();
    mlx_win = mlx_new_window(mlx, 19201080"HelloWorld!");
    mlx_loop(mlx);
    return (0);
}
cs

- mlx_new_window 사용

- window instance를 리턴함

- height, width, title_name을 받음

- 끝나지 않기 위해 mlx_loop(mlx)를 사용함 (window rendering을 initiate)

300 x 200 로 만든 window창

 

pixels to image

- mlx_pixel_put 함수는 렌더될 프레임을 기다림없이 즉시 찍어내기 때문에 매우매우 느림,

 -> 이러한 이유로 우리는 픽셀 전부를 이미지로 버퍼링해야 함

 -> 복잡한 것 같지만 복잡하지 않다고 함

- 코드보면서 이해

- mlx_new_image()mlx_get_data_addr()에 대해서 꼼꼼히 봐야 함

- initialize the image

1
2
3
4
5
void *mlx;
void *img;
 
mlx = mlx_init();
img = mlx_new_image(mlx, 19201080);
cs

- 하지만 이런식으로는 write pixel을 하기 좀 그럼

- 그래서 아래와 같이 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef struct     s_data {
    void        *img;
    char        *addr;
    int            bits_per_pixel;
    int            line_length;
    int            endian;
}                t_data;
 
int        main(void)
{
    void    *mlx;
    t_data    img;
 
    mlx = mlx_init();
    img.img = mlx_new_image(mlx, 19201080);
 
    /* call mlx_get_data_addr 
    ** we pass (bits_per_pixel, line_length, endian), then
    ** then these will be set accordingly for the *current* data address
    */
    img.addr = mlx_get_data_addr(img.img, &img.bits_per_pixel, &img.line_length, &img.endian);
    return (0);
}
cs

- 바이트가 정렬되어 있지 않으므로 항상 메모리 offset을 계산해야 함 (line_length, bits_per_pixel을 이용해서)

- 공식이 존재함 -> offset = (y * line_length + x * (bits_per_pixel / 8));

1
2
3
4
5
6
7
8
9
10
/*
** like `mlx_pixel_put(mlx, mlx_win, x, y, color)`
*/
void    my_mlx_pixel_put(t_data *data, int x, int y, int color)
{
    char *dst;
    
    dst = data->addr + (y * data->line_length + x * (data->bits_per_pixel / 8));
    *(unsigned int*)dst = color;
}
cs

- 잘은 모르겠지만, 여기까지 보면

 -> mlx_new_image()로 img식별자(void*)를 받고

 -> mlx_get_data_addr()로 img데이터(char*)를 받아서

 -> 설정된 char* 에 수학공식으로 얻은 offset을 더하고 컬러를 메모리에 넣는 듯

 -> my_mlx_pixel_put을 한다고 해도 window에 표현한 것은 아님

- 또한, 문제도 존재

 -> 이미지가 실시간으로 window에 표시되는데 동일한 이미지를 변경 중이면 이미지가 깨질 수도 있음

 -> 그러므로 이미지를 2개 이상 만드는 것이 안정적임

 

pushing image to a window

- 만든 이미지를 가지고 mlx함수를 이용하면 됨 (mlx_put_image_to_window)

- 아래는 그 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <mlx.h>
 
typedef struct  s_data {
    void        *img;
    char        *addr;
    int         bits_per_pixel;
    int         line_length;
    int         endian;
}               t_data;
 
void            my_mlx_pixel_put(t_data *data, int x, int y, int color)
{
    char    *dst;
 
    dst = data->addr + (y * data->line_length + x * (data->bits_per_pixel / 8));
    *(unsigned int*)dst = color;
}
 
int             main(void)
{
    void    *mlx;
    void    *mlx_win;
    t_data  img;
 
    mlx = mlx_init();
    mlx_win = mlx_new_window(mlx, 600400"Hello world!");
    img.img = mlx_new_image(mlx, 600400);
    img.addr = mlx_get_data_addr(img.img, &img.bits_per_pixel, &img.line_length,
                                 &img.endian);
    
    my_mlx_pixel_put(&img, 550x00FF0000);
    mlx_put_image_to_window(mlx, mlx_win, img.img, 00);
    mlx_loop(mlx);
    return (0);
}
 
cs

 

- 이 과정으로 별찍기처럼 삼각형을 만들 수도 있음

my_mlx_pixel_put과 반복문을 활용하여 삼각형찍기

 

Hooks

- 소프트웨어 요소들 사이에 전달되는 함수 호출이나 메시지 혹은 이벤트등을 인터셉트해서 OS, APP에 행동을 변경하는데 사용하는 말

- 즉, 나에겐 키입력이나 마우스입력을 application에 도달하기전에 인터셉트하여 다른 역할을 하게끔 하는 것 Hooking이라고 말함

- MiniLibX에는 다양한 hook함수가 존재함

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <mlx.h>
#include <stdio.h>
 
void    key_hook(int keycode, int *p_cnt)
{
    printf("key_hook...%d\n", keycode);
    *p_cnt += 1;
}
 
int        main(void)
{
    int        cnt;
    void    *mlx;
    void    *win;
 
    cnt = 0;
    mlx = mlx_init();
    win = mlx_new_window(mlx, 640480"title");
    mlx_key_hook(win, &key_hook, &cnt);
    mlx_loop(mlx);
    return (0);    
}
cs

- keycode라는 변수를 mlx_key_hook함수가 key_hook을 호출할 때 넣어주는 듯

- mlx_key_hook 함수같이 특정"key" 말고 그냥 mlx_hook 함수를 사용할 수도 있음

- mlx_hook 함수는 매개변수가 더 필요한데 그것은 X11 event types과 Keymask가 필요함

 

events

- 이벤트는 상호작용하는 app을 쓰는 근본임

- 많은 X11이벤트가 존재 (참고. harm-smits.github.io/42docs/libs/minilibx/events.html)

 -> X11는 MiniLibX에서 사용되는 라이브러리

 -> 그 중에 02:KeyPress, 03:KeyRelease, 17:DestroyNotift정도는 알아 두기

- 예시에서는 KeyPress event(2), KeyPressMask(1)를 사용함

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include "../mlx/mlx.h"
#include <stdio.h>
#include <stdlib.h>
 
typedef struct     s_mlx
{
    void        *mlx;
    void        *win;
    int            cnt;
}                t_mlx;
 
int        keypress_event(int keycode, t_mlx *mlx)
{
    mlx->cnt++;
    printf("cnt : %d\n", mlx->cnt);
    printf("keycode : %d\n", keycode);
    if (keycode == 53)
        exit(0);
    return (0);
}
 
int        main(void)
{
    t_mlx    mlx;
 
    mlx.cnt = 0;
    mlx.mlx = mlx_init();
    mlx.win = mlx_new_window(mlx.mlx, 600400"mlx");
 
    mlx_hook(mlx.win, 2, 1L<<0&keypress_event, &mlx);
    mlx_loop(mlx.mlx);
    return (0);
}
cs

 

Loops

- 프레임당 그림 그리기에 수월함

- mlx_loop_hook을 할 것

 -> mlx_hook : 이벤트를 감지하기 (키보드 입력 이벤트)

 -> mlx_loop_hook : 화면 뿌리기 (레이케스팅의 출력값)

- 잘은 모르겠찌만 mlx_loop_hook은 아무런 이벤트가 발생하지 않으면(즉, hooking이 없다면) 실행하는 듯

man page 중에서...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include "../mlx/mlx.h"
#include <stdio.h>
 
typedef struct    s_data
{
    void        *mlx;
    void        *win;
    int            x;
    int            y;
}                t_data;
 
int     event_key(int keycode, t_data *data)
{
    printf("keycode : %d\n", keycode);
    data->x++;
    data->y++;
    return (0);
}
 
int     render_next_frame(t_data *data)
{
    data->x++;
    data->y++;
    mlx_pixel_put(data->mlx, data->win, data->x, data->y, 0xFF0000);
    return (0);
}
 
int     main(void)
{
    t_data data;
 
    data.x = 0; data.y = 0;
    data.mlx = mlx_init();
    data.win = mlx_new_window(data.mlx, 19201080"title");
    mlx_hook(data.win, 21, event_key, &data);
    mlx_loop_hook(data.mlx, render_next_frame, &data);
    mlx_loop(data.mlx);
}
cs

 

images

- 텍스처나 스프라이트를 사용하는데 유용할 것

- 이미지를 읽기 위해서는 XMP파일이거나 PNG 파일 형식이어야 함

- mlx_xpm_file_to_image 혹은 mlx_png_file_to_image 이 있는데 png함수는 현재 메모리 누수라고 함

 -> 그래서 xpm파일을 다들 읽는 거 였음

- 리턴값이 NULL이면 실패임

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "../mlx/mlx.h"
#include <stdio.h>
 
int        main(void)
{
    void    *mlx;
    void    *win;
    void    *img;
    int        img_width;
    int        img_height;
 
    mlx = mlx_init();
    win = mlx_new_window(mlx, 1000800"title");
    img = mlx_xpm_file_to_image(mlx, "./img/wall_n.xpm"&img_width, &img_height);
    if (img == NULL)
        printf("Failed\n");
    mlx_put_image_to_window(mlx, win, img, 00);
    mlx_loop(mlx);
    return (0);
}
cs

 

 

 

 

 

4. mlx 예제

- github.com/taelee42/mlx_example 에 포스팅 되어있는 taelee 카뎃분이 만든 예제를 실행해보려함

- 겹치는 내용이 많이 존재

 

example_02_key_handling

- KEY_PRESS는 입력할때와 입력하는 동안

- KEY_release는 뗄 때

- KEY_EXIT는 윈도우의 exit버튼을 누를 때

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <stdio.h>
#include <stdlib.h>
#include "../mlx/mlx.h"
 
# define X_EVENT_KEY_PRESS        2
# define X_EVENT_KEY_release    3
# define X_EVENT_KEY_EXIT        17
 
# define KEY_ESC    53
# define KEY_W        13
# define KEY_A        0
# define KEY_S        1
# define KEY_D        2
 
typedef struct    s_data
{
    int            x;
    int            y;
}                t_data;
 
int        key_press(int keycode, t_data *data)
{
    if (keycode == KEY_A)
        data->x--;
    if (keycode == KEY_D)
        data->x++;
    if (keycode == KEY_W)
        data->y++;
    if (keycode == KEY_S)
        data->y--;
    printf("x:y = %d:%d\n", data->x, data->y);
    if (keycode == KEY_ESC)
        exit(0);
    return (0);
}
 
int        key_release(int keycode, t_data *data)
{
    printf("key release: %d\n %d:%d\n", keycode, data->x, data->y);
    return (0);
}
 
int        key_exit(int keycode, t_data *data)
{
    printf("key_exit : %d\n %d:%d\n", keycode, data->x, data->y);
    exit(0);
}
 
int        main(void)
{
    void    *mlx;
    void    *win;
    t_data    data;
    data.x = 0;
    data.y = 0;
 
    mlx = mlx_init();
    win = mlx_new_window(mlx, 800600"title");
    mlx_hook(win, X_EVENT_KEY_PRESS, 0, key_press, &data);
    mlx_hook(win, X_EVENT_KEY_release, 0, key_release, &data);
    mlx_hook(win, X_EVENT_KEY_EXIT, 0, key_exit, &data);
    mlx_loop(mlx);
    return (0);
}
 
cs

 

 

example_03_img_loading

- mlx_new_image는 내가 사용할 이미지 만드는 것

- mlx_xpm_file_to_image는 파일로부터 이미지 만드는 것

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include "../mlx/mlx.h"
 
int        main(void)
{
    void    *mlx;
    void    *win;
    void    *img;
    int        image_width;
    int        image_height;
 
    mlx = mlx_init();
    win = mlx_new_window(mlx, 800600"title");
    img = mlx_xpm_file_to_image(mlx, "./img/wall_n.xpm"&image_width, &image_height);
    mlx_put_image_to_window(mlx, win, img, 00);
    printf("%d : %d\n", image_width, image_height);
    mlx_loop(mlx);
    return (0);
}
 
cs

실행 결과값

 

 

example_04_img_making

- mlx_get_data_addr의 인자에 대해서는 크게 신경쓰지 말기

- int*로 캐스팅하여 컬러를 바로바로 입히게 만듬

 -> 메모리에 4byte를 입히기 위해서임

 -> 원래 리턴값이 char*인데 char*변수로 그대로 받으면 index[] 연산 할때마다 곱해주는 일을 해야 하므로

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdlib.h>
#include "../mlx/mlx.h"
 
typedef struct    s_mlx
{
    void        *mlx;
    void        *win;
}                t_mlx;
 
typedef struct    s_img
{
    void        *img;
    int            *data;
    int            bpp;
    int            line_size;
    int            endian;
}                t_img;
 
 
int        main(void)
{
    t_mlx    mlx;
    t_img    img;
 
    mlx.mlx = mlx_init();
    mlx.win = mlx_new_window(mlx.mlx, 800600"title");
    img.img = mlx_new_image(mlx.mlx, 400300);
    img.data = (int *)mlx_get_data_addr(img.img, &(img.bpp), &(img.line_size), &(img.endian));
 
    int y = 0;
    while (y < 300)
    {
        int x = 0;
        while (x < 400)
        {
            if (x % 2 == 0)
                img.data[400 * y + x] = 0x0000FF;
            else
                img.data[400 * y + x] = 0xFFFFFF;
            x++;
        }
        y++;
    }
    mlx_put_image_to_window(mlx.mlx, mlx.win, img.img, 00);
    mlx_loop(mlx.mlx);
    return (0);
}
cs

결과값

 

 

example_05_loading_and_modifying

- image를 xpm에서 대꼬와서 get_data로 컬러 수정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include "../mlx/mlx.h"
 
typedef struct    s_mlx
{
    void        *mlx;
    void        *win;
}                t_mlx;
 
typedef struct s_img
{
    void        *img;
    int            *data;
    int            width;
    int            height;
    int            bpp;
    int            line_size;
    int            endian;
}                t_img;
 
int        main(void)
{
    t_mlx    mlx;
    t_img    img;
 
    mlx.mlx = mlx_init();
    mlx.win = mlx_new_window(mlx.mlx, 300300"title");
    img.img = mlx_xpm_file_to_image(mlx.mlx, "./img/wall_n.xpm"&(img.width), &(img.height));
    img.data = (int *)mlx_get_data_addr(img.img, &(img.bpp), &(img.line_size), &(img.endian));
 
    int y = 0;
    while (y < img.height)
    {
        int x = 0;
        while (x < img.width)
        {
            if (x % 5 == 0)
                img.data[img.width * y + x] = 0x00FF00;
            x++;
        }
        y++;
    }
    mlx_put_image_to_window(mlx.mlx, mlx.win, img.img, 00);
    mlx_loop(mlx.mlx);
    return (0);
}
 
cs

실행 결과값

 

example_06_map_2d

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#include "../mlx/mlx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
 
# define ROW        11
# define COL        15
# define TILE_SIZE    32
# define WIDTH        COL * TILE_SIZE
# define HEIGHT        ROW * TILE_SIZE
 
# define KEY_ESC    53
# define KEY_EVENT_PRESS    2
# define KEY_EVENT_EXIT        17
 
typedef struct    s_img
{
    void        *img;
    int            *data;
    int            bpp;
    int            line_size;
    int            endian;
}                t_img;
 
typedef struct    s_game
{
    void        *mlx;
    void        *win;
    int            map[ROW][COL];
    t_img        img;
}                t_game;
 
int        key_press(int keycode)
{
    if (keycode == KEY_ESC)
        exit(0);
    return (0);
}
 
int        exit_button(void)
{
    exit(0);
}
 
void    draw_square(t_game *game, int x, int y, int color)
{
    int dx;
    int dy;
 
    dy = 0;
    while (dy < TILE_SIZE)
    {
        dx = 0;
        while (dx < TILE_SIZE)
        {
            game->img.data[WIDTH * (y + dy) + (x + dx)] = color;
            dx++;
        }
        dy++;
    }
}
 
void    draw_squares(t_game *game)
{
    int x;
    int y;
 
    y = 0;
    while (y < ROW)
    {
        x = 0;
        while (x < COL)
        {
            if (game->map[y][x] == 1)
                draw_square(game, TILE_SIZE * x, TILE_SIZE * y, 0x0000FF);
            else
                draw_square(game, TILE_SIZE * x, TILE_SIZE * y, 0xFFFFFF);
            x++;
        }
        y++;
    }
}
 
void    draw_line(t_game *game, double x1, double y1, double x2, double y2)
{
    double    deltaX;
    double    deltaY;
    double    step;
 
    deltaX = x2 - x1;
    deltaY = y2 - y1;
    step = (fabs(deltaX) > fabs(deltaY)) ? fabs(deltaX) : fabs(deltaY);
    deltaX /= step;
    deltaY /= step;
    while (fabs(x2 - x1) > 0.01 || fabs(y2 - y1) > 0.01)
    {
        game->img.data[(int)floor(y1) * WIDTH + (int)floor(x1)] = 0xC0C0C0;
        x1 += deltaX;
        y1 += deltaY;
    }
}
 
void    draw_lines(t_game *game)
{
    int i;
 
    i = 0;
    while (i <= ROW)
    {
        draw_line(game, 0, TILE_SIZE * i, WIDTH, TILE_SIZE * i);
        i++;
    }
    i = 0;
    while (i <= COL)
    {
        draw_line(game, TILE_SIZE * i, 0, TILE_SIZE * i, HEIGHT);
        i++;
    }
}
 
int        draw_loop(t_game *game)
{
    draw_squares(game);
    draw_lines(game);
    mlx_put_image_to_window(game->mlx, game->win, game->img.img, 00);
    return (0);
}
 
int        main(void)
{
    int map[ROW][COL] = {
    {111111111111111},
    {100000000000101},
    {100001000000101},
    {111100000010101},
    {100000000010101},
    {100000001111101},
    {100000000000001},
    {100000000000001},
    {111111000111101},
    {100000000000001},
    {111111111111111}
    };
    t_game    game;
 
    game.mlx = mlx_init();
    game.win = mlx_new_window(game.mlx, WIDTH, HEIGHT, "2D_MAP");
    memcpy(game.map, map, sizeof(int* ROW * COL);
    game.img.img = mlx_new_image(game.mlx, WIDTH, HEIGHT);
    game.img.data = (int *)mlx_get_data_addr(game.img.img,
                        &game.img.bpp, &game.img.line_size, &game.img.endian);
 
    mlx_hook(game.win, KEY_EVENT_PRESS, 0, key_press, &game);
    mlx_hook(game.win, KEY_EVENT_EXIT, 0, exit_button, &game);
    mlx_loop_hook(game.mlx, draw_loop, &game);
 
    mlx_loop(game.mlx);
    return (0);
}
 
cs

결과값

 

 

 

 

 

5. 회전행렬의 곱을 이용한 광선로테이션

참고 사이트 : ghebook.blogspot.com/2020/08/blog-post.html

 

회전 행렬(Rotation Matrix)

물리학, 수학, 전자파, RF, 초고주파, 안테나, 통신 이론, 정보 이론

ghebook.blogspot.com

1) 방향벡터를 가지고 있어야 함

2) 방향벡터를 향해 광선을 쏨  (해당 dir_vec가 기울기, 이 방향으로 DDA알고리즘 사용)

3) 키보드 입력이 오면 방향벡터를 회전시켜야함

4) 시야각을 위해 방향벡터의 회전(약 30도)를 해서 양쪽에서 그림

 

- 코드의 일부

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# define PI    3.14159265359
 
void    draw_dir_ray(t_game *game, double angle)
{
    double    ray_x;
    double    ray_y;
    double    dx;
    double    dy;
    double    max_value;
 
    ray_x = game->pos.x;
    ray_y = game->pos.y;
 
    dx = cos(angle) * game->dir_vec.x - sin(angle) * game->dir_vec.y;
    dy = sin(angle) * game->dir_vec.x + cos(angle) * game->dir_vec.y;
 
    max_value = fmax(fabs(dx), fabs(dy));
    dx /= max_value;
    dy /= max_value;
    while (1)
    {
        if (game->map_img.data[WIDTH * (int)floor(ray_y) + (int)floor(ray_x)] != 0x0000FF)
            game->map_img.data[WIDTH * (int)floor(ray_y) + (int)floor(ray_x)] = 0xFF0000;
        else
            break;
        ray_x += dx;
        ray_y += dy;
    }
}
 
void    draw_ray(t_game *game)
{
    double angle;
 
    angle = 0;
    while (angle < PI/6)
    {
        draw_dir_ray(game, angle);
        draw_dir_ray(game, -angle);
        angle += PI/72;
    }
    mlx_put_image_to_window(game->mlx, game->win, game->map_img.img, 00);
}
 
void    rotate_matrix(t_vec2 *vec, double angle)
{
    double    tmp_x;
 
    tmp_x = cos(angle) * vec->- sin(angle) * vec->y;
    vec->= sin(angle) * vec->+ cos(angle) * vec->y;
    vec->= tmp_x;
}
 
int        key_press(int keycode, t_game *game)
{
    if (keycode == KEY_ESC)
        exit(0);
    if (keycode == KEY_LEFT)
        rotate_matrix(&game->dir_vec, PI/36);
    if (keycode == KEY_RIGHT)
        rotate_matrix(&game->dir_vec, -PI/36);
    return (0);
}
cs

결과 값

 

 

 

 

6. Raycasting

lodev.org/cgtutor/raycasting.html

 

Raycasting

#define screenWidth 640 #define screenHeight 480 #define texWidth 64 #define texHeight 64 #define mapWidth 24 #define mapHeight 24 int worldMap[mapWidth][mapHeight]= { {4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,7,7,7,7,7,7,7,7}, {4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,

lodev.org

- 위의 링크에서 서브젝트에 대한 모든 것을 할 수 있음

- lodev아저씨의 Raycasting임

- 텍스쳐까지는 설명이 친절하나, part3가서 스프라이트에 대한 설명이 부족함

 -> 수학을 도구로서 사용하고 넘어가는 게 현재는 좋은 듯

- 기본적인 구조는 카메라 방향 부근으로 광선을 쏘고 광선이 어느 벽에 닿으면 그 충돌한 벽과 카메라평면까지의 거리를 구하여 그릴 line_height를 얻고 line_height만큼 세로로 그림

- 이것을 0 < x < width까지 모두 진행하여 실행함

- 텍스쳐 가로 좌표는 >>>  텍스쳐 가로길이 : x = 1 : 충돌한 지점 에 대한 비례식으로 얻음

- 텍스쳐 세로 좌표는 >>> 텍스쳐 세로길이 : y = line_height : 그리고있는 현재 y좌표

- 이런식으로 접근하여 이미지의 color값을 가져와 현재 (x, y)에 해당 컬러를 넣어주면 됨

- 하여튼 위의 lodev아저씨가 모두 알려줌

- 나는 텍스쳐는 내가 생각해서 코딩을 했고, 기본적인 raycasting에 대한 이해를 마치고 공식을 유도하면서 작성함

 -> 물론 스프라이트의 x,y 를 만들 때 역행렬을 곱하는 이유와 screen_x를 얻는 공식에 대해서는 스스로 유도하지 못하는 단계

 

 

 

7. map parsing

- 레이캐스팅보다 까다로울 수 있음

- 정확히는 레이캐스팅이 모르는 것이라면 파싱은 귀찮은 것

- 많은 경우의 수가 있고 모두를 막아야하고 프로그램이 종료되면 free해줘야 함

- 서브젝트의 요건을 모두 만족하고 테스트를 해봐야 함

github.com/humblEgo/cub3D_map_tester

 

humblEgo/cub3D_map_tester

- 42seoul cub3D 프로젝트의 맵 유효성 체크기능 tester. Contribute to humblEgo/cub3D_map_tester development by creating an account on GitHub.

github.com

- iwoo님께서 작성한 맵테스터를 사용해서 기본적인 파싱이 잘되었나 파악해야 함

 -> 물론 모든 경우가 들어있는 것은 아닐 것

 -> 특히 해상도 최댓값과 color가 띄어쓰기가 들어갈 수 있다는 것에 알아둘 것

기본적인 맵 구조

 

 

 

8. bmp 포맷으로 저장하기

dojang.io/mod/page/view.php?id=702

 

C 언어 코딩 도장: 81.1 비트맵 파일 포맷 알아보기

비트맵 파일은 바이너리 형식이므로 메모장 등 텍스트 편집기로 열어도 내용을 알아볼 수 없습니다. 따라서 비트맵 파일에서 픽셀 정보를 읽으려면 먼저 비트맵 파일의 구조를 알아야 합니다.

dojang.io

stackoverflow.com/questions/2654480/writing-bmp-image-in-pure-c-c-without-other-libraries

 

Writing BMP image in pure c/c++ without other libraries

In my algorithm, I need to create an information output. I need to write a boolean matrix into a bmp file. It must be a monocromic image, where pixels are white if the matrix on such element is true.

stackoverflow.com

- 코딩도장으로 기본적인 내용을 학습하고

- 스택오버플로에 올라온 글을 분석하면서 만들면 금방만듦

- 생각보다 쉬웠음

- 대부분 값이 정상이어야 bmp로 저장하고 open할 수 있으므로 실패할 가능성이 현저히 적음

 

 

 

9. 마지막

- 전체적으로는 mlx 연습 -> 레이캐스팅 -> 맵파싱 -> bmp 가 전부임

- 하지만 이것이 모두 완성이 되어도 빈틈은 있을 것

 -> 그 빈틈을 꼭 잘 메워야 함

- taelee, iwoo, mihykim, yohlee님 정말 감사함

 -> 예제, 테스터기, 번역에 큰 도움을 받음

 

- 부족한 수학적인 이해도 있었고, mlx의 void *에 대한 메모리 이슈도 있었고, 해상도 문제도 있었지만

 결과물이 눈으로 확연히 보이는 과제여서 그나마 할 때는 재밌게 한 듯

 

- 또한 안정성 검사가 힘들어서 완벽하다라고는 말할 수 없지만 대부분 꼼꼼히 만들어서 제출을 하시는 듯