[42Seoul]MiniLibx
<mlx 함수 설명 페이지>
"mlx.h"
<mlx 유래 및 상세 설명 페이지>
[42Seoul]FDF
<fdf 알고리즘 및 계산식 설명 페이지>
(1) mlx 활용과 color 표현 하는 방법!
<mlx 설명 팔만코딩경>
MiniLibX 파헤치기
<MiniLibX 파헤치기>
💡 You must use the miniLibX. Either in the version that is available on the system, or from its sources. If you choose to work with the sources, you will need to apply the same rules for your libft as those written above.
2021년 버전이 슬랙에 있으니 활용해보자.
링킹
할 때 mlx가 필요하다.이 때, 직접만든 Makefile 내에서 mlx의 Makefile까지 실행시켜야 하며,
libmlx.dylib 파일은 런타임 중 참조하는 동적 라이브러리이기 때문에 프로젝트 루트 폴더 (실행파일이 있는 곳)으로 옮겨야 한다.
💡 컴파일러는 컴파일 시 실행파일 안에 라이브러리 코드를 포함하지 않는다.
대신 실행파일에 라이브러리를 연결하기 위한 코드만 포함시키고, 실행 시 라이브러리에 연결한다.
-L
옵션과 -l
옵션을 사용하여 라이브러리를 같이 링킹한다."mlx.h"
헤더를 추가한다.void *
로 mlx_init()
의 리턴값을 받는다. (이하 mlx값
이라 설명함)void *
로 mlx_new_window
함수의 리턴값을 받는다.실패시 NULL을 리턴한다.
ex)
```c
void *mlx = mlx_init();
void *win = mlx_new_window (mlx, 1000, 1000, "테스트");
```
mlx loop
를 사용해야 한다.ex)
```c
void *mlx = mlx_init();
void *win = mlx_new_window (mlx, 1000, 1000, "테스트");
mlx_loop(mlx);
```
점을 찍기 위해선 mlx_pixel_put
명령어를 사용하는 방법과, mlx_put_image_to_window
를 사용하는 방법이 있다.
mlx_loop(mlx)를 실행하기 전, 미리 이미지에 어느 위치에 어느 색의 픽셀을 찍을 것인지 알려주어야 한다.
int color = create_trgb(0,255,0,0);
mlx_pixel_put (mlx, win, 200, 300, color)
💡 color는 아래에서 설명한다.
매개변수는 순서대로 mlx_init의 리턴값
, window
, 점을 찍을 x좌표
, y좌표
, 색
이다.
💡 만약 x, y좌표가 window의 범위를 벗어나면,
segmentation fault
가 뜬다.
서브젝트를 보면 image
를 사용하는 것을 강력히 권장하고 있다.
우선 void *
형의 mlx_new_image
() 리턴값을 저장한다.
void *image = mlx_new_image(mlx, x길이, y길이);
char *
형의 mlx_get_data_addr
의 리턴값을 받는다.
리턴값은, 위 1번의 이미지의 pixel 색상 정보를 저장하는 1차원 배열의 첫 주소값이다.
매개변수로 넣는 숫자들은 각각 다음과 같다.
bits_per_pixel
(pixel_color를 표현하기 위해 필요한 bit 수, 사실 상 32비트(ARGB))size_line
(가로 한 줄의 최대 길이, 200*200 사이즈라고 배열의 길이가 40000이 아니다.)endian
(이미지의 pixel_color가 little endian(0)인지, big endian(1)인지 알려준다, 사실 상 0)위 매개변수들은 int 변수의 주소값을 넘겨야 하며, size_line을 제외한 나머지는 사실 상 쓸 일이 없다.
int bit_per_pixel1 = 0;
int size_line1 = 0;
int endian1 = 0;
char *ptr = mlx_get_data_addr (image, &bit_per_pixel1,&size_line1, &endian1);
ptr로 받은 주소는, 해당 이미지의 첫번째 픽셀의 주소이다.
1차원 배열에 ARGB씩 32비트 크기로 나열되어있다.
픽셀 하나하나의 ARGB값을 조절할 수 있고, 이를 활용하여 선, 면 등을 나타낼 수 있다.
```c
int create_argb(int a, int r, int g, int b)
{
return (a << 24 | r << 16 | g << 8 | b);
}
for (int i =0 ; i< 200 ; i++)
{
for(int k = 0; k<200; k++){
char *dst2;
// 8 = sizeof(char)
dst2 = ptr2 + (k * size_line1 + i * bit_per_pixel1 / 8 );
*(unsigned int*)dst2 = create_argb(0,0,255,0);
}
}
```
mlx_put_image_to_window
함수를 사용하여 띄울 윈도우에 image를 넣는다.
mlx_put_image_to_window(mlx, win, image, 0/*이미지시작x좌표*/, 0/*"y좌표*/);
mlx_loop함수를 사용하여 창을 띄운다.
mlx_loop(mlx);
[FdF] Graphics
<투영방법들>
등축투영 pdf
<등축투영pdf>
매개변수의 예외처리는 간단하다.
우선 기본적으로 고려해야 할 사항은
정도이며, 추가로 해결해야 할 것이
가 있을 것이다.
메인함수의 매개변수 중 하나인 argc를 통해 정확히 1개의 인자를 받았을 때만 프로그램을 실행시킨다.
매개변수로 보낸 파일명의 파일 type을 확인해야한다.
서브젝트 기준 상 .fdf
파일만 허용하기에, 이를 확인하여야 한다.
구현방법은 split
함수를 통해 .을 기준으로 문자열을 분리시킨 후,
맨 뒤 파일명이 fdf가 맞는지 검사한다.
이때 strncmp
로 검사했는데 fdf의 길이가 3이라고 3만 검사할 경우,
.fdf와 .fdfd 등 그 뒤 문자를 확인하지 않기 때문에, null문자까지 합친 4개의 길이
를 비교해야 한다.
int is_valid_file_type(char *file_path)
{
char **path_divided;
int divided_length;
int answer;
answer = 0;
divided_length = 0;
path_divided = ft_split(file_path, '.');
if (!path_divided)
ft_error("split error");
while (path_divided[divided_length] != 0)
divided_length++;
if (ft_strncmp(path_divided[divided_length - 1], "fdf", 4) == 0)
answer = 1;
divided_length = 0;
free_2d_array(path_divided);
return (answer);
}
mlx_new_image의 사이즈 등 파싱 후에 만들어야 할 변수들이 몇 가지 있다.
이를 제외한 나머지 변수들을 초기화해준다.
이번 과제에서는 모든 파일의 변수를 구조체로 만들어 관리하였으며, 함수들에게는 주소값을 넘겨 관리하였다.
t_var *init(char **argv)
{
t_var *var;
var = (t_var *)malloc(sizeof(t_var));
if (!var)
ft_error("var malloc error");
init_struct(var); // 변수 처리
init_file(var, argv[1]); //parsing
calculate_gap(var);
init_offset(var); // 좌표처리
return (var);
}
제대로 파싱을 하기 위해선, 파일 내 정보가 적절한지부터 확인을 해야한다.
서브젝트와 예시파일들을 분석해보았을 때,
어느 한 줄에만 좌표가 더 많다거나 하는 예외는 있을 수 없고,
직사각형의 좌표값이 구해져야 정확한 파일인 것으로 판단하였다.
따라서 처음 파일을 열어 파일의 width
, height
값을 찾았고,
모든 행에서 같은 크기의 열을 가지는지 확인하였다.
그 후 close한 후 다시 파일을 열어 파싱을 진행하였다.
void init_file(t_var *var, char *path)
{
int fd;
int i;
fd = open(path, O_RDONLY);
if (fd < 0)
ft_error("file open error");
get_map_size(var, fd);
close(fd);
if (var->map_height <= 0 || var->map_width <= 0)
ft_error("file values error: no values");
i = 0;
var->map = (int **)malloc(sizeof(int *) * var->map_height);
if (!(var->map))
ft_error("map malloc error");
while (i < var->map_height)
{
var->map[i] = malloc(sizeof(int) * var->map_width);
if (!(var->map[i++]))
ft_error("map malloc error");
}
fd = open(path, O_RDONLY);
if (fd < 0)
ft_error("file open error");
save_map_data(var, fd);
close(fd);
}
또한, 예시파일을 보면 파일 숫자 옆에 ,0xff
와 같은 색상정보가 있는 파일들이 있는데,
이는 예전 과제의 잔재로 현 서브젝트(21.04.13)에서는 필요가 없기에 예외처리하였다.
서브젝트에서도 Each Number
라는 문구를 확인할 수 있다.
즉 예외처리는 아래와 같이
를 확인하였다.
파싱한 파일 정보들을 좌표로 변환하기 전, 점과 점 사이의 길이를 정해야 한다.
파일의 첫번째와 두번째 숫자가 각각 0
, 4
였다고 가정하였을 때,
좌표는 (0,0,0), (0,1,4)가 아니기 때문이다.
처음 점과 점 사이의 거리를 35라고 가정하였고,
Window의 최대 사이즈를 2000, 1500이라고 정한 후,
(점의 가로 개수(파싱한 파일 열의 개수)) * gap > Window_Max_X
||
(점의 세로 개수(파싱한 파일 행의 개수)) * gap > Window_Max_Y
일 경우 gap을 줄여나가 위 조건을 만족하는 형식으로 계산하였다.
파일 내 숫자들의 위치가 x, y 값들이며 숫자 value가 z값을 의미하므로,
while (y < var->map_height)
{
x = 0;
while (x < var->map_width)
{
(var->offset[y][x]).x = x * var->gap;
(var->offset[y][x]).y = y * var->gap;
(var->offset[y][x]).z = var->map[y][x];
x++;
}
y++;
}
로 계산하였다.
처음 구해진 좌표는 위에서 아래를 바라보는 것 마냥 z축의 정보를 볼 수 없다.
좌표를 과제에서 요구한 isometric projection을 통해 변환해주어야 한다.
링크로 대체
void rotate_x(double *x, double *y, double *z, double rot)
{
double prev_x;
double prev_y;
double prev_z;
double theta;
prev_x = *x;
prev_y = *y;
prev_z = *z;
theta = M_PI / 6 * rot;
*x = prev_x;
*y = prev_y * cos(theta) - prev_z * sin(theta);
*z = prev_y * sin(theta) + prev_z * cos(theta);
}
...
void ft_isometric(t_var *var, double *x, double *y, double *z)
{
*z *= var->gap * 0.125 * var->z_height;
rotate_x(x, y, z, var->theta_x_divider);
rotate_y(x, y, z, var->theta_y_divider);
rotate_z(x, y, z, var->theta_z_divider);
}
...
while (++i < var->map_height)
{
k = -1;
while (++k < var->map_width)
{
ft_isometric(var, &(((var->offset)[i][k]).x),
&(((var->offset)[i][k]).y), &(((var->offset)[i][k]).z));
}
}
이미 윈도우 최대 크기를 설정해두었지만, (ex, WINDOW_MAX_X
) 이는 대략적인 점과 점 사이를 구하기 위함이고,
좌표 변환으로 인해 변경된 좌표를 살펴보며 정확한 창 크기를 구할 필요가 있다
좌표변환하면서 미리 저장한 X, Y의 최대값과 최소값을 이용해 와이어프레임의 전체크기를 구하고,
창과 와이어프레임 사이의 여백을 조금 주어 창 크기를 계산한다.
void set_window_size(t_var *var)
{
var->window_x = (var->max_x - var->min_x) + WINDOW_MARGIN;
var->window_y = (var->max_y - var->min_y) + WINDOW_MARGIN;
if (WINDOW_MAX_X + WINDOW_MARGIN < (var->max_x - var->min_x))
var->window_x = (var->max_x - var->min_x) + WINDOW_MARGIN;
if (WINDOW_MAX_Y + WINDOW_MARGIN < (var->max_y - var->min_y))
var->window_y = (var->max_y - var->min_y) + WINDOW_MARGIN;
}
mlx_image를 생성하여 창에 대입한다.
이때, 이미지의 픽셀 값을 변경할 때 (0, 0)부터 채워넣게 된다면,
여백으로 인해 와이어프레임이 치우쳐저 나올 것이다.
image 좌표 출력시, 좌표 전체를 위/왼쪽 여백많큼 이동시킬 필요가 있다.
DDA나 bresenham 알고리즘을 사용하여 점과 점 사이를 구할 수 도 있지만,
우리가 중학교 때 배운 점과 점을 알 때 구할 수 있는 일차방정식
을 이용하여 해결하였다.
두 점이 다른 x값을 가지고 있다면, 점과 점 사이의 방정식을 이용하고 x를 0.0n
씩 증가시키며 해당하는 y값에 픽셀값을 수정하면 된다.
같은 x값을 가지고 있다면, y의 차이만큼만 y의 값을 증가시켜 픽셀값을 수정한다.
static void draw_edge(t_var *var, t_offset s, t_offset e)
{
double x_flag;
double x;
double y;
unsigned int *dst1;
x = s.x;
x_flag = 1;
if (s.x > e.x)
x_flag = -1;
if (s와 e의 두 점이 같으면)
same_x(var, s, e);
else
{
while ((int)x != (int)e.x)
{
y = round((e.y - s.y) / (e.x - s.x) * (x - s.x) + s.y); //직선의방정식
if (창 크기 안에 들어오는 픽셀위치면) // 확대했을 경우 창 밖에 점이 나갈 수 있음
{
dst1 = var->img_data->addr + ((int)y * var->img_data->size_line
+ (int)x * var->img_data->bit_per_pixel / 8);
*dst1 = create_argb(0, 255, 0, 0);
}
x += (0.05 * x_flag);
}
}
}
void draw(t_var *var)
{
int x;
int y;
y = 0;
while (y < var->map_height)
{
x = 0;
while (x < var->map_width)
{
if (x + 1 < var->map_width)
draw_edge(var, var->offset[y][x], var->offset[y][x + 1]);
if (y + 1 < var->map_height)
draw_edge(var, var->offset[y][x], var->offset[y + 1][x]);
x++;
}
y++;
}
}
회전 및 이동 등의 좌표변화가 있을 경우, 변환된 좌표로 다시 그림을 그려야 한다.
mlx_loop_hook함수를 이용하여 좌표변환 ~ draw 함수까지의 절차를 loop해야한다.
int main_loop(t_var *var)
{
int i;
int k;
i = -1;
k = -1;
free_offset(var->offset, var->map_height);
init_offset(var);
init_max_min_data(var);
projection(var);
mlx_destroy_image(var->mlx, var->img_data->img);
init_mlx_image(var);
set_image_align(var);
translate(var);
draw(var);
mlx_put_image_to_window(var->mlx, var->win, var->img_data->img, 0, 0);
return (0);
}
💡 메모리 릭을 잘 검사해야 한다.
키보드, 마우스 등의 입력값을 대기하고 있다가, 어느 입력이 들어오면 설정한 함수를 실행시킬 수 있다.
mlx_hook(var->win, 02, 1L << 0, hooks, var);
mlx_hook(var->win, 17, 1L << 5, red_cross_hook, var);
//02 -> button 클릭
//17 -> window distroy
int hooks(int keycode, t_var *var)
{
if (keycode == KEY_ESC)
esc_hook(var);
if (keycode == KEY_PLUS)
zoom_in_hook(var);
if (keycode == KEY_MINUS)
zoom_out_hook(var);
if (keycode == KEY_UP || keycode == KEY_DOWN
|| keycode == KEY_LEFT || keycode == KEY_RIGHT)
trans_hook(var, keycode);
if (keycode == KEY_ROTATE_Y_CLOCK || keycode == KEY_ROTATE_Y_CLOCK_R
|| keycode == KEY_ROTATE_X_CLOCK || keycode == KEY_ROTATE_X_CLOCK_R
|| keycode == KEY_ROTATE_Z_CLOCK || keycode == KEY_ROTATE_Z_CLOCK_R)
rotate_hook(var, keycode);
if (keycode == KEY_Z_HEIGHT_UP || keycode == KEY_Z_HEIGHT_DOWN)
z_height_hook(var, keycode);
return (1);
}