안녕하세요! 2시간 쯤 전에 개발을 마치고 소드 아트 온라인 프로그레시브 2권을 읽고오니 공돌이가 싫어하는 태양은 사라지고 기분 좋은 밤 공기가 맞이해주는군요.
아 정말 좋은 밤입니다. 맥주 마시고 싶네요 ㅇㅅ;;

이 글은 이걸로 마지막입니다.

아직 개발해야 할게 몇 개 남았지만 제 실력에는 좀 무리일거 같고 여러분 실력이면 충분히 개발 가능하지 않을까 싶습니다.
이미 개발하시고 'ㅋㅋㅋ 이거 글 언제 올라오냐 ㅋㅋ 포기한건가 ㅋㅋㅋ' 라는 생각을 하고 계신 분들도 계시겠죠 ㅜ_ㅜ 제가 머리가 나빠서 그렇습니다 ㅜㅜ

뭐 기분 좋게 스크린샷으로 시작해보죠. 다운그레이드 됐다면 다운그레이드 됐지만 설치된 글꼴만 사용 가능합니다.



오오옹. 잘 되는 거십니다.

이 글은 http://youtil.wo.tc/113 <- 에서 설명하는 글을 마친 상태에서 진행됩니다.

먼저 EditBox를 띄우는 방법을 알아야겠죠.
이미 알고 계시겠지만 3D에서 흔히 사용하는 투영 행렬은 마지막 계산 결과가 -1, -1, 0 ~ 1, 1, 1 사이에 있게됩니다. 다시 말해서 프로젝션 행렬을 굳이 연산하지 않아도 괜찮다는 말이죠.

그래서 버텍스가 (-1, -1, 0.1), (1, 1, 0.1)인 사각형을 렌더링 하려면 그냥 identity 행렬을 곱해주고 그대로 렌더링하면 되고 그 결과는 Client Window의 0,0 부터 Width, Height까지 완벽하게 꽉 찬 사각형이 렌더링 되게 됩니다. 오호, 마침 저희에게 딱 필요하지 않나요? 이렇게 된거 아예 저 버텍스에다가 Scaling하고 Translation을 먹여서 크기를 바꿔서 클라이언트 좌표에 맵핑시켜서 렌더링 하는것도 괜찮은 방법인거 같습니다. 오오. 속도는 모르겠지만요.....

먼저 클라이언트 좌표계를 NDC공간으로 바꿔보죠. 으음. 일단 Client Window의 Width와 Height가 필요할것 같군요.

struct TransformNDC
{
	float scaleX, scaleY, scaleZ;
	float movX, movY, movZ;
};

TransformNDC ClientToNDC(const RECT& target, int windowWidth, int windowHeight)
{
	float width = target.right - target.left;
	float height = target.bottom - target.top;

	float scaleX = width / windowWidth;
	float scaleY = height / windowHeight;
	float scaleZ = 1;

	float ratioX = 2.0f / windowWidth;
	float ratioY = 2.0f / windowHeight;

	float movX = -(1 - scaleX) + (ratioX * target.left);
	float movY = (1 - scaleY) - (ratioY * target.top);
	float movZ = 0;

	return TransformNDC{ scaleX, scaleY, scaleZ, movX, movY, movZ };
}

굳이 클래스에 넣어야 하나 싶어서 그냥 만들어봤습니다.
대충 이렇게 씁니다.

auto translate = ClientToNDC(RECT{ 0, 0, 800, 600 }, mClientWidth, mClientHeight);

XMMATRIX scale = XMMatrixScaling(translate.scaleX, translate.scaleY, translate.scaleZ);
XMMATRIX world = XMMatrixTranslation(translate.movX, translate.movY, 0);

XMMATRIX를 블로그에서 쓰려니 갑자기 생각난건데 개발 할 때 DX초기화 하는걸 직접 하기가 좀 뭐시기 해서 제가 보는 책에 있는 프레임워크를 가져다 썼더니 쓰게됐네요. 전에 넥슨 다니시는(부럽) 학교 선배분께 'XNA라이브러리 쓰나요?' 라고 물었다가 '그게 망한지가 언젠데 아직도 쓰고있어?'라는 소리를 들어서(...) 저도 슬슬 버릴까 합니다만 책을 다 볼 때 까지는 쓰려고 합니다 ㅇㅅ.

뭐 이제
http://youtil.wo.tc/114 <- 이 글을 참고하셔서 IMM 컨텍스트를 적당한 메시지에 생성해 주시고...
http://youtil.wo.tc/115 <- 를 참고해서 적당히 insert와 erase '만' 쓰도록 하죠.
사실 115번 글에서 사용한걸 사용 가능할거라고 믿었는데 산산히 부숴졌습니다. 뭐 그래도 DirectWrite의 DrawText도 잘 만들어져 있어서요. 위 글에서 사용한 방법보단 좋을것같네요. 다소 귀찮지만.

적당히 첫번째 글을 마쳤다면 DWriteFactory(IDWriteFactory)객체가 하나 있을거라고 생각됩니다.(사실 저는 여기저기 짜집기한거라 DWriteFactroy가 첫번째 글에 있는 그 링크에 있는지 없는지 기억이 안나네요; 없으면 만들어주세요 ㅜ_ㅜ)

그래서 DWriteFactory를 잘 살펴보면 CreateTextLayout라는 맴버 함수가 있습니다. 이 녀석을 이용해 IDWriteTextLayout이라는 녀석을 만들어보죠.

IDWriteTextLayout* layout;
// DWriteFactory가 없다면 만들어주세요.
DWriteFactory->CreateTextLayout(printText.c_str(), cursorPos, TextFormat, mClientWidth, mClientHeight, &layout);

그러고 나서 layout 인터페이스에 짝대기를 하나 그어주니... GetMetrics 라는 맴버 함수가 하나 나오는군요!!! 우오오! 그리고 그 밑에 좀 더 찾아보니 무려 GetLineMetrics라는 녀석도 있습니다 우와아아! 이걸로 멀티 라인을 만들 수 있어요! 안에 무슨 변수가 들어가는지는 MSDN을 참고하시고! 여기 서중요한건 GetMetrics로 현재 몇 라인을 가지고 있냐와 폰트의 Height를 구할 수 있습니다. 그리고 GetLineMetrics라는 녀석을 사용하면 각 라인에 몇개의 글자가 들어가있는지 알 수 있죠.
자 그럼 아래 발로 짠 코드를 잠시 발로 볼까요?

IDWriteTextLayout* layout;
// 출력할 때는 모든 문자열이 필요하지만 길이를 계산할 때에는 현재 커서 위치만 알면 됩니다.
// 아래 두번째 인자를 봐주세요. 현재 커서 위치를 담고있는 변수입니다.
// mClient* 가 들어가는 곳은 에디트 박스의 크기를 넣으시면 됩니다. (만들어질 에디트 박스의 크기가 아닙니다. 보여지는 에디트 박스의 크기입니다!)
// 아래에서 사용되는 mClient*도 같은 의미입니다.
DWriteFactory->CreateTextLayout(printText.c_str(), cursorPos, TextFormat, mClientWidth, mClientHeight, &layout);

DWRITE_TEXT_METRICS metrics;
layout->GetMetrics(&metrics);

std::vector<DWRITE_LINE_METRICS> lineMetrics(metrics.lineCount);
UINT32 lineCount;

layout->GetLineMetrics(&lineMetrics[0], lineMetrics.capacity(), &lineCount);
ReleaseCOM(layout);

int length = lineMetrics[lineCount - 1].length;
// 전체 string에서 현재 라인의 string만 구하는 코드입니다.
// 현재 라인에서 커서를 움직이기 위해서는 현재 라인의 글자가 렌더링될 width를 알아야 하거든요.
const wchar_t* currentLineText = printText.c_str();
// 아래 배열이 음수가 될 일은 없냐구요? 없습니다. length의 최대 길이는 cursorPos와 같거든요.
currentLineText = ¤tLineText[cursorPos - length];
DWriteFactory->CreateTextLayout(currentLineText, length, TextFormat, mClientWidth, mClientHeight, &layout);

layout->GetMetrics(&metrics);
ReleaseCOM(layout);

자 이제RECT를 계산해서 위에서 만든 ClientToNDC 함수를 사용해서 사각형을 잘 변환 한 뒤 렌더링 하면 커서가 되겠죠?

// 블로그로 코드를 옮기다 보니 함수의 일부분만 올리게 되서
// 구조가 바뀌다보니 RECT변수를 급하게 하나 추가했습니다. 그래서 초기화 안했습니다.
// 실제 제가 사용하는 코드에서는 사용자 정의 구조체나 클래스는 제대로 참조자로 받아서 수정하지 return 따위도 하지 않습니다.
RECT rect;

rect.top = (lineCount - 1) * ceil(lineMetrics[0].height);
rect.bottom = rect->top + ceil(lineMetrics[0].height);
rect.left = ceil(metrics.widthIncludingTrailingWhitespace);
rect.right = rect->left + 1;

return rect;

드디어 제가 블로그를 시작한 이래로 가장 가장 길게 쓴 글이 막을 내렸군요.
너무 대충대충 설명하는 느낌이라 정말 죄송합니다. 이번 글은 1편 빼면나름 성실히 작성한다고 작성한건데 생각처럼 잘 되진 않았네요.

부디 제 글이 조금이나마 도움이 되길 바랍니다.
저는 이제 월광 1권을 읽어보러~~~ (저를 덕후로 만든게 제이노블인지라 제이노블을 좋아합니다.)

+ Recent posts