Post date: Dec 28, 2014 8:54:17 AM
이번 글에서는 매쓰매티카의 용도보다는 윈도우즈 프로그래밍의 매핑 모드에 대해서 글을 써보려고 한다.
예전에 필자가 프로그래밍을 처음 시작 했을 때 찰스페졸드 아저씨의 책을 읽으면서, 이해는 됬었는데, 막상 해보면, 프로그래밍이 잘 안 되어 여러 측면으로 실험을 해 가면서 공부한 기억이 있다.
원도우즈는 리눅스나 애플 OS보다 개발자가 좀더 정교한 좌표계를 가지고 프로그래밍을 할 수 있는데, 좌표계를 개발자 또는 사용자의 편의 맞게 만들 수가 있는것이다. 원도우에서는 매핑모드라고 하여 논리 단위의 좌표를 지정할 수가 있다.
예를 들면, 인치, 센티미터와 같은 길이의 기본 단위를 정하여, 화면에 그래프나 도형을 그릴 때, 일상에서 사용하는 측정 단위로 표시하거나 표현을 할 수가 있는 것이다.
그럼, 먼저 매카(매쓰매티카)로 극좌표계 꽃잎(Petal)을 그려보자
보통 꽃잎 곡선은 r = a cos nθ로 그릴 수 있는데, n의 수가 홀 수 이면 n개 만큼, 짝수이면 2n개의 꽃잎을 만들 수 있다. 먼저 실험적으로 2를 입력 하여 네 개의 꽃잎을 그려 보자.
PolarPlot[Cos[2 θ], {θ, 0, 2 Pi}]
PolarPlot 함수 하나로 간단하게 행운의 상징인 네 잎 클로버를 그려보았다. 그려 본 김에 이번엔 n의 값에 4를 입력 하여 꽃잎의 수를 좀 더 늘려 보자.
PolarPlot[Cos[4 θ], {θ, 0, 2 Pi}]
더할나위없이 너무 간단하다^^
매쓰매티카에는 프리젠테이션용 Demonstration 이라는 것을 만들 수가 있는데, 이 것을 이용하면 꽃잎이 그려지는 과정도 관찰 해 볼 수 있다.
Manipulate[ PolarPlot[Cos[2 θ], {θ, θmin, θmax}, PlotRange -> {{-1, 1}, {-1, 1}}],
{θmin, 0, 2 π}, {θmax, 0.001, 2π}]
Manipulate는 슬라이더와 연결된 수식에 각 항의 값을 조종하므로서 그래프의 변화되는 과정을 직접 바로 확인 할 수 있는 것으로, 위에 문법은 PolarPlot함수로 꽃잎을 그리되, Cos[2 θ] 식으로, θ 시작과 종료의 범위를 0, 2 Pi로 각각 설정하여 꽃잎을 그리시오 하는매카(매쓰매티카)의 함수문이다.
그려지는 과정을 확인하기 위한 것이므로 최소, 최대 값을 변수식으로 사용하였고, 각각의 범위를 2 Pi로 하였다. 이 것 또한 프로그래밍이기 때문에 간과하는 것이 항상 생기는데, 최대값의 범위를 최소값의 초기 값과 같은 값으로 설정하지 않기 위해 0.001로 설정하였다. 만약 같은 값을 입력하면 매카가 오류 메시지를 친절하게 보여 줄 것이다.
자, 이제 윈도우즈 프로그래밍을 하여, 위의 매카의 사용과 비교하였을 때 얼마나 손이 많이 가는지 그리고 알아야 될게 얼마나 더 많은지를 비교하여 보자^^
윈도우즈 시스템에서는 SetMapMode 함수를 사용하여 일반적 좌표계 또는 프로그래머(사용자를 포함한) 만의 좌표계를 만들 수 있다. 극좌표계를 표현하는데 있어, 필자가 자주 사용하는 것은 화면의 왼쪽 그리고 위쪽 상단에서 y축의 좌표 크기가 아래 쪽으로 내려올 수록 증가하는 좌표가 아닌, 그 반대인 좌표계 방식을 설명하려 한다.
먼저 몇 개를 살펴보면 아래와 같은 것들이 있는데, 이 것들은 x축이 왼쪽에서 오른쪽으로 y축은 아래쪽에서 위쪽 방향으로 증가하는 좌표계이다.
MM_HIENGLISH 0.001 inch
MM_HIMETRIC 0.01 mm
기타 등등...
MM_ANISOTROPIC
이 중 시간적 변화와 같은 빠른 신호를 실시간적으로 관측하는데 보통 사용하는 매핑모드는 MM_ANISOTROPIC이다. 이 모드는 오실로스코프와 같은 장치 화면에다가 수직축에는 신호의 크기를, 수평축에는 시간등을 나타내는데 사용한다.
MM_ISOTROPIC는 MM_ANISOTROPIC와 다르게 위 매카로 그린 정사각형과 같이 x, y축이 대칭되어 비례적인 좌표계를 그릴 때 사용한다. 하지만, MM_ANISOTROPIC만을 사용해하여 두 개의 방식을 모두 표현 할 수 있다.
위 두 개의 모드를 MM_ANISOTROPIC로 사용하여 프로그래밍 해보자.
다음은 필자가 직접 MFC 코드로 만들어 본 그래프로서, 한 개의 윈도우 창안에 왼쪽은 정사각형의 대칭 비례적인 모드의 네 잎 클로버를 그렸고 오른쪽에는 코사인 값의 실시간 그래프를 그렸다.
자, 이제 아래 그래프 두개를 프로그래밍을 통해 그려보자.
MM_ISOTROPIC MM_ANISOTROPIC
먼저 네 잎 클로버를 그려보자.
1. 화면 사각형의 크기를 구한다.
CPen penGreenGuide;
penGreenGuide.CreatePen(PS_DOT, 1, RGB(0, 0, 0));
13. 생성한 검은색 팬을 사용하기 위해 윈도우즈 시스템에 알린다.
CPen *pPenOld = (CPen *)dc.SelectObject(&penGreenGuide);
14. 정사각형을 그린다. 좌표계의 원점이 정중앙이므로 좌축상단은 x, y값은 (-100, 100)이어야 하고 우측 하단의 x, y의 값은 (100, 100)으로 한다.
dc.Rectangle(CRect(-(POLAR_WINDOW_WIDTH / 2),
-(POLAR_WINDOW_HEIGHT / 2),
POLAR_WINDOW_WIDTH / 2,
POLAR_WINDOW_HEIGHT / 2));
15. 윈도우즈 시스템에 사용을 완료한 검은색 팬을 내려 놓고 기존 펜을 돌려 준다. 이렇게 안하면 리소스 릭이 발생할 수 있다.
dc.SelectObject(pPenOld);
16. 수평선 x축을 그린다.
dc.MoveTo(0, 0);
dc.LineTo(POLAR_WINDOW_WIDTH / 2, 0);
dc.MoveTo(0, 0);
dc.LineTo(-(POLAR_WINDOW_WIDTH / 2), 0);
17. 수직선 y축을 그린다.
dc.MoveTo(0, 0);
dc.LineTo(0, POLAR_WINDOW_HEIGHT / 2);
dc.MoveTo(0, 0);
dc.LineTo(0, -(POLAR_WINDOW_HEIGHT / 2));
18. 자, 최종 클로버를 그리기 전에 꽃잎 곡선식 r = a cos nθ 각 항의 값을 준비 해 보자.
const double PI = 3.1415926535;
const double TWOPI = 2 * PI;
double dTheta;
double dNTheta = 0;
double dCosValue = 0;
double dX;
double dY;
double dTheta_increment = (PI/11000.0) * 8;
double dR;
double dA = POLAR_WINDOW_WIDTH / 2;
double dN = 2.0;
19. 루프를 통해 증분 세타 값이 2Pi값이 될 때까지 증가 시켜 가며 곡선식 값에 의한 x, y 좌표 값을 SetPixel함수를 통해 찍어 보자. 찍다라는 표현을 쓴 것은 SetPixel이 하나의 점만을 그리기 때문이다.
for(dTheta=0.0; dTheta<(TWOPI); dTheta+=dTheta_increment)
{
dNTheta = dN * dTheta;
dCosValue = cos(dNTheta);
dR = dA * dCosValue;
dX = dR * cos(dTheta);
dY = dR * sin(dTheta);
20. Pi값 전까진 파란색으로 그리고 이후로는 빨간색으로 그려 보았다.
if(dTheta < 3.14f)
dc.SetPixel((int)dX, (int)dY, RGB(0, 0, 255));
else
dc.SetPixel((int)dX, (int)dY, RGB(255, 0, 0));
}
음,,, 아직 반 밖에 안 했는데도, 여기까지의 프로그래밍의 설명이 꾀 길어진 것 같다. 프로그래밍하는 것과 매카를 이용하는 것을 비교 하여 보기 위해 글을 쓰기 시작했는데, 매카(매쓰매티카)의 간편함이 얼마나 대단한 것인지 나자신도, 독자도 명확히 인지하는 것은 더 이상 설명하지 않아도 자명한 일 같다.
자, 그럼 나머지 반 코사인 그래프를 그려 보자.
21. 코사인 그래프의 좌표계는 보통 맨 좌측 중앙이 원점이고 오른쪽으로 이동해 가며 그래프를 그린다. 원점의 x축 값을 클로버 정사각형의 오른쪽 끝에서 빈 공간 간격을 더한 위치로 한다.
int nCosXOrigin = dPolarViewWidth + GAP;
22. 코사인 그래프의 논리적 좌표 크기는 가로 너비 폭이 큰 것이 자연스운 것이므로 높이보다 크게 만든다.
int COS_WINDOW_WIDTH = 300;
int COS_WINDOW_HEIGHT = 200;
23. 매핑 모드는 클로버와 똑같이 MM_ANISOTROPIC로 설정한다.
dc.SetMapMode(MM_ANISOTROPIC);
24. 코사인 그래프의 원점은 21번에서 만든 것과 같이 좌측 중앙으로 한다.
dc.SetViewportOrg(nCosXOrigin, nPolarYOrigin);
25. 물리적인 좌표계 크기는 정사각형의 클로버 좌표계와 같은 값으로 설정한다.
dc.SetViewportExt(dPolarViewWidth, -dPolarViewHeigt);
26. 논릭적인 좌표계의 크기는 22번에서 설명한 것 같이 가로 폭이 더 큰 좌표계를 만든다.
dc.SetWindowExt(COS_WINDOW_WIDTH, COS_WINDOW_HEIGHT);
27. 2Pi까지 dTheta_increment 크기 횟수로 계속 회전하므로, x축의 이동 증가 비율값을 구하기 위한 최대 횟수 크기를 만들자
const double THETA_COUNT = TWOPI / dTheta_Added;
int nCosX = 0;
double dRateCosX = 0;
28. 이제, 코사인 그래프를 그려보자
for(dTheta=0.0; dTheta<(TWOPI); dTheta+=dTheta_Added)
{
dNTheta = dN * dTheta;
dCosValue = cos(dNTheta);
dR = dA * dCosValue;
dX = dR * cos(dTheta);
dY = dR * sin(dTheta);
dRateCosX = nCosX / THETA_COUNT * COS_WINDOW_WIDTH;
29. 증분 비율 값을 통해 구하여진 x축의 값과 코사인 값 y를 좌표로 화면에 그린다.
if(dTheta < 3.14f)
dc.SetPixel((int)dRateCosX, (int)dR, RGB(255, 0, 0));
else
dc.SetPixel((int)dRateCosX, (int)dR, RGB(0, 0, 255));
nCosX++;
}
위 두개의 그래프를 하나의 윈도우 창 안에 함께 그리려면 dc.SaveDC() 함수를 사용하면 된다. 네 잎 클로버와 코사인 그래프 코드 사이에 입력하여, 위에 필자가 만든 두 개의 그래프를 간결한 형태의 코드로 만들 수 있다. 이 것은 이 글을 보는 독자가 함 해보길 바라며, 글을 마친다.
이 글의 마지막으로, 새 해에는 모두들 행복하고 많은 복 많이 받으시길 바랍니다^^
CRect rcClient;
GetClientRect(&rcClient);
2. 하얀색 브러쉬를 만들어 윈도우 창 화면을 그 크기만큼 백색으로 채우자
CBrush brWhite;
brWhite.CreateSolidBrush(RGB(255, 255, 255));
dc.FillRect(rcClient, &brWhite);
3. 윈도우 창 화면의 가로, 세로 크기를 구한다.
double dWidth = rcClient.Width();
double dHeight = rcClient.Height();
4. 위 두개의 그래프 정 중앙 사이에는 빈 공간이 있다. 이 간격을 한 20정도로 하자
const int GAP = 20;
5. 왼쪽에 있는 클로버의 너비를 두 그래 사이의 간격 크기를 제외 한 크기로 지정하고, 높이는 윈도우 창 크기 만큼의 값을 만든다.
double dPolarViewWidth = dWidth / 2 - GAP;
double dPolarViewHeigt = dHeight;
6.정사각형 비율의 크기로서 너비와 높이의 크기를 같은 값으로 한다.
const int POLAR_WINDOW_WIDTH = 200;
const int POLAR_WINDOW_HEIGHT = 200;
7. 화면 좌표계의 원점값을 만든다. 즉, 화면 중앙에 할 것인지, 아니면 화면 좌측 상단 또는 좌측 하단등에 지정할 것인지를 결정한다. 우리는 클로버 화면 너비 및 높이의 정 중앙의 원점 값을 만든다.
int nPolarXOrigin = dPolarViewWidth / 2;
int nPolarYOrigin = dHeight / 2;
8. 매핑 모드를 설정한다.
dc.SetMapMode(MM_ANISOTROPIC);
9. 이제부터는 위에서 만든 값들을 통해, 윈도우즈 시스템에 그 값을 지정해 보자. 매핑 모드를 바로 위에서 설정하였으니, 5번에서 만든 화면 좌표계의 원점을 설정한다.
dc.SetViewportOrg(nPolarXOrigin, nPolarYOrigin);
10. 윈도우즈 시스템에서는 논리적인 좌표계와 물리적인 좌표계라는 것이 있는데, 논리좌표계의 그려진 결과물을 물리적인 좌표계의 비율로 매핑하는 것을 의미한다. 글자 그대로 물리적인 좌표는 디바이스 장치의 좌표계로 이해하면 편할 것다. 높이의 값에 - 마이너스 값을 붙인 것은 y의 수직 좌표 방향 값이 위쪽으로 올라 갈 수로록 증가 하기 위한 것이다.
dc.SetViewportExt(dPolarViewWidth, -dPolarViewHeigt);
11. 논리좌표계 수직, 수평 크기는 정사각형의 네 잎 클로버를 그리기로 하였으므로, 같은 비율의 값으로 6번에서 만든 값으로 지정한다.
dc.SetWindowExt(POLAR_WINDOW_WIDTH, POLAR_WINDOW_HEIGHT);
12. 앞에서 매카가 그린 좌표계의 수평수직, x, y 축 라인을 그려보자. 먼저, 검은색 펜을 하나 생성한다.