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
- 위의 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, 1920, 1080, "HelloWorld!");
mlx_loop(mlx);
return (0);
}
|
cs |
- mlx_new_window 사용
- window instance를 리턴함
- height, width, title_name을 받음
- 끝나지 않기 위해 mlx_loop(mlx)를 사용함 (window rendering을 initiate)
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, 1920, 1080);
|
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, 1920, 1080);
/* 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, 600, 400, "Hello world!");
img.img = mlx_new_image(mlx, 600, 400);
img.addr = mlx_get_data_addr(img.img, &img.bits_per_pixel, &img.line_length,
&img.endian);
my_mlx_pixel_put(&img, 5, 5, 0x00FF0000);
mlx_put_image_to_window(mlx, mlx_win, img.img, 0, 0);
mlx_loop(mlx);
return (0);
}
|
cs |
- 이 과정으로 별찍기처럼 삼각형을 만들 수도 있음
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, 640, 480, "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, 600, 400, "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이 없다면) 실행하는 듯
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, 1920, 1080, "title");
mlx_hook(data.win, 2, 1, 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, 1000, 800, "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, 0, 0);
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, 800, 600, "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, 800, 600, "title");
img = mlx_xpm_file_to_image(mlx, "./img/wall_n.xpm", &image_width, &image_height);
mlx_put_image_to_window(mlx, win, img, 0, 0);
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, 800, 600, "title");
img.img = mlx_new_image(mlx.mlx, 400, 300);
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, 0, 0);
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, 300, 300, "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, 0, 0);
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, 0, 0);
return (0);
}
int main(void)
{
int map[ROW][COL] = {
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1},
{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1},
{1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
};
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
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, 0, 0);
}
void rotate_matrix(t_vec2 *vec, double angle)
{
double tmp_x;
tmp_x = cos(angle) * vec->x - sin(angle) * vec->y;
vec->y = sin(angle) * vec->x + cos(angle) * vec->y;
vec->x = 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
- 위의 링크에서 서브젝트에 대한 모든 것을 할 수 있음
- 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
- iwoo님께서 작성한 맵테스터를 사용해서 기본적인 파싱이 잘되었나 파악해야 함
-> 물론 모든 경우가 들어있는 것은 아닐 것
-> 특히 해상도 최댓값과 color가 띄어쓰기가 들어갈 수 있다는 것에 알아둘 것
8. bmp 포맷으로 저장하기
dojang.io/mod/page/view.php?id=702
stackoverflow.com/questions/2654480/writing-bmp-image-in-pure-c-c-without-other-libraries
- 코딩도장으로 기본적인 내용을 학습하고
- 스택오버플로에 올라온 글을 분석하면서 만들면 금방만듦
- 생각보다 쉬웠음
- 대부분 값이 정상이어야 bmp로 저장하고 open할 수 있으므로 실패할 가능성이 현저히 적음
9. 마지막
- 전체적으로는 mlx 연습 -> 레이캐스팅 -> 맵파싱 -> bmp 가 전부임
- 하지만 이것이 모두 완성이 되어도 빈틈은 있을 것
-> 그 빈틈을 꼭 잘 메워야 함
- taelee, iwoo, mihykim, yohlee님 정말 감사함
-> 예제, 테스터기, 번역에 큰 도움을 받음
- 부족한 수학적인 이해도 있었고, mlx의 void *에 대한 메모리 이슈도 있었고, 해상도 문제도 있었지만
결과물이 눈으로 확연히 보이는 과제여서 그나마 할 때는 재밌게 한 듯
- 또한 안정성 검사가 힘들어서 완벽하다라고는 말할 수 없지만 대부분 꼼꼼히 만들어서 제출을 하시는 듯
'42cursus' 카테고리의 다른 글
Kubernetes(쿠버네티스) 개념 정리하기 (0) | 2021.01.07 |
---|---|
libasm - 참고 자료 위주로 정리하기 (42seoul) (1) | 2020.11.28 |
ft_server - 가이드 및 참고자료 정리하기 (42seoul) (2) | 2020.10.19 |
ft_printf - 초간단 정리 (42seoul) (3) | 2020.10.11 |
get_next_line - 초간단 정리 (42seoul) (0) | 2020.10.09 |