한글 오토마타에 대하여...
PDA의 키보드를 만들어야 할 상황이 있어서 제작을 해보았다.



UI는 따로 처리된 DLL에서 호출하여 사용하는 방식이고 키 코드를 넘겨 받아서 한글 조합을 하고 있다.
이번에 제작한 오토마타는 유니코드(0xac00 ~ 0xd7a3 범위)내의 한글코드를 처리하고 있다.
때문에 표현 불가능한 글자가 몇개 있는 것 같다.
아니 정확하게 표현 불가능한 코드가 있다.

하지만 입력 방식이 키보드가 아닌 키 코드의 입력 방식이기 때문에 문제는 되지 않았다. 또한 PDA는 유니코드를 사용하니 일단 사용자 범주안에서는 OK 이다.!!
문제는 키보드 입력방식에 적용이 어렵다.
이부분은 보완하여 수정해봐야 겠다.
Define 한 키 코드의 입력이 아닌 char 값을 입력 받을 수 있도록 추가로 처리를 해야 할 것 같다.

소스를 올리기 전에 간단한 한글 오토마타 와 한글 형태소 분리에 대한 설명을 해야 할 것 같다.

※ 본문 내용의 오토마타 구현 과정은 핸드폰과 같이 키의 조합이 키보드와는 다른 모형을 기준으로 하고 있다. 기본 원리를 파악하면 키의 배열이나 키의 숫자가 달라진다고 하여 문제가 될 것은 없는 것 같다.


한글 형태소 분리
{
   간단하게 설명하고 끝내려 한다.자세하게 설명 되어있는 사이트는 찾아보니 생각보다 많이 있었다.
   그리고 내가 그 사이트 보다 자세하게 설명할 자신도 없다.
}

한글코드의 기본개념: http://paero3.myzip.co.kr/hangeul_code/index.html 찾아가서 보면 너무너무 설명을 잘 하고 있다.
원문의 내용을 필요한 부분만 간단 요약 하면 이렇다.

윈도우의 한글코드 : Microsoft의 통합형 한글(확장 완성형 코드:2350자)
확장 완성형 코드 = (기존 완성형 코드 영역 0xB0A1~0xC8FE) + (0x8141~0xC6FE 부분에 한글을 추가)

완성형 코드

 정의:
완성형 방식은, 완성된 글자를 일종의 그림처럼 다루는 방식이다. 따라서 그 발상의 밑바탕에서 바라봤을 때는, 한자 같은 상형문자를 다루는 방식과 근본적으로 아무 차이점이 없다. 그리고 그게 '가나다' 순서를 지키려면, 이미 글자 체계 또한 완성되었다고 본 후 코드를 배열해야 한다. 유니코드 2.0의한글 배열은, 현대 한글에서 조합 가능한 숫자 '11,172'자를 쭉 순서대로 배열해 놓았다. 즉 '2바이트 완성형' 방식이다.

 크기: 2바이트

 자주 쓰이는 한글 2350자를 골라 가나다 순으로 배치
 0xB0A1~0xC8FE: 차례대로 코드 부여
  상위 바이트 0xB0 ~ 0xC8
  하위 바이트 0xA1~ 0xFE

조합형 코드

 정의:
조합형 방식은, 한글 글자의 구성 원리인 초성-중성-종성의 세 갈래 음운을 코드 자료로 삼아 설계된다. 따라서 여기에서는 완성된 글자에 초점이 있는 게 아니라, 각 음운들에 초점이 놓인다.

 크기: 2바이트

상위 1비트: 아스키 코드와 구분하기 위해 사용
다음 5비트: 초성
다음 5비트: 중성
다음 5비트: 종성


핵심은?...

첫번째 논점은 한글글자를 초성 + 중성 + 종성 으로 분리해 내는 것이다. 그리고 반대로 한글자모(자음,모음)가 입력될 때 이미 입력되어 있던 한글자모와 조합하여 완성시키는 것이다.

조합 완성된 글자 와 한글자모 사이에는 다음과 같은 수식이 적용된다.

초성(19자)
– ㄱ(0) ㄲ(1) ㄴ(2) ㄷ(3) ㄸ ㄹ ㅁ ㅂ ㅃ ㅅ ㅆ ㅇ ㅈ ㅉ ㅊ ㅋ(15) ㅌ(16) ㅍ(17) ㅎ(18)

중성(21자)
– ㅏ(0) ㅐ(1) ㅑ(2) ㅒ(3) ㅓ ㅔ ㅕ ㅖ ㅗ ㅘ ㅙ ㅚ ㅛ ㅜ ㅝ ㅞ ㅟ ㅠ(17) ㅡ(18) ㅢ(19) ㅣ(20)

종성(28자)
– 없음(0) ㄱ(1) ㄲ(2) ㄳ ㄴ ㄵ ㄶ ㄷ ㄹ ㄺ ㄻ ㄼ ㄽ ㄾ ㄿ ㅀ ㅁ ㅂ ㅄ ㅅ ㅆ ㅇ ㅈ ㅊ ㅋ ㅌ ㅍ(26) ㅎ(27)

표현범위 : 가(0xAC00) ~ 힣(D7A3)

한글글자 = 초성 * 21 * 28 + (중성 - 19) * 28 + (종성 - 40) + [각주:1]BASE_CODE(한글코드 테이블의 첫번째 문자코드)

예) 강 = ㄱ + ㅏ + ㅇ 
         = 0 * 21 * 28 + (19 - 19) * 28 + (61 - 40) + 0xAC00 = 0xAC00(44032) + 21
         = 44053
         = 0xAC15

두번째 임의로 입력되어지는 한글자모를 받아서 이전에 입력되어진 한글자모와 한글조합이 이루어 지는지의 판단과 처리 및 이후 프로세스의 정립에 있다.

위에 보여지는 키패드의 그림상 아파트를 입력하려면 다음과 같은 순서에 의해 입력이 되어진다.

아파트 = ㅇ + ㅏ + ㅂ + ㅂ + ㅏ + ㄷ + ㄷ + ㅣ + ㅣ (ㅇ) <- 초성
          = 아 + ㅂ + ㅂ + ㅏ + ㄷ + ㄷ + ㅣ + ㅣ (ㅏ) <- 조합 초성 + 중성
          = 압 + ㅂ + ㅏ + ㄷ + ㄷ + ㅣ + ㅣ (ㅂ) <- 조합 초성 + 중성 + 종성
          = 앞 + ㅏ + ㄷ + ㄷ + ㅣ + ㅣ (ㅂ) <- 종성 합성
          = 아파 + ㄷ + ㄷ + ㅣ + ㅣ (ㅏ) <- 완성문자 분해,조합 초성 + 중성
          = 아팓 + ㄷ + ㅣ + ㅣ (ㄷ) <- 조합 초성 + 중성 + 종성
          = 아팥 + ㅣ + ㅣ (ㄷ) <- 종성 합성
          = 아파티 + ㅣ (ㅣ) <- 완성문자 분해, 초성 + 중성
          = 아파트 (ㅣ) <- 중성 합성

(아파트 세글자를 찍기 위해 그렇게도 강대리는 날을 깟다 -_ㅠ)

세번째 키의 배열과 조합의 순서도 중요하다. 위의 키패드 배열을 보면 ㅂ, ㅍ 이 한쌍을 이루고 ㄱ,ㅋ 이 한쌍을 이룬다. 이것은 우리가 기존에 사용해온 핸드폰의 영향도 있을 것이다. 그런데 왜 ㅡ, ㅣ는 순서가 바뀌어 있을까?...
간단한 예로 '애벌레'를 들어보겠다.
애벌레 = ㅇ + ㅏ + ㅣ + ㅂ + ㅓ + ㄹ + ㄹ + ㅓ + ㅣ
          = 아 + ㅣ + ㅂ + ㅓ + ㄹ + ㄹ + ㅓ + ㅣ
          = 애 + ㅂ + ㅓ + ㄹ + ㄹ + ㅓ + ㅣ
          = 앱 + ㅓ + ㄹ + ㄹ + ㅓ + ㅣ
          = 애버 + ㄹ + ㄹ + ㅓ + ㅣ
          = 애벌 + ㄹ + ㅓ + ㅣ
          = 애벌러 + ㅣ
          = 애벌레

입력된 순서와 키의 입력을 보면 알겠지만 'l' 라는 음소는 조합에 많이 사용되는 문자이다. 때문에 키입력의 최초 입력에서 나와야 입력이 편하다. 만약 ㅡ 를 두번 입력해야 ㅣ 로 변경이 가능하다면 문자 하나를 찍는 것이 보통 힘든일이 아닐 것이다...
(키패드 중에 에니콜에 들어있는 키패드 배열(천지인인가...)이 가장 좋은 것 같다. LG, 모토로라,에버,에니콜 모든 핸드폰을 사용해 보았지만 그중에서도 모토로라는 정말 최악이다. 키의 배열이 한손으로 문자를 만들기가 너무 힘들다. PDA에 모아키라는 것이 있다...난 정말... 왜만들었지??)


소스코드

초기모델


초기 제작한 모델은 아파트를 쓰기위해서 항상 띄어쓰기를 입력하여 문자입력을 종료시켜야 되었다.
때문에 이런 문제들을 확인 후 몇가지를 수정하여 소스를 다듬었다. 초성, 중성, 종성 합성 테이블을 두어 테이블 내에서 변환을 조절하였다.

다음에 오는 소스코드는 나름 정리를 한다고 한 모습임에도 정리가 않되고 있다.
포스팅을 하면서 이것저것 웹서핑을 통하여 찾아보았다. 다행이도 어느정도는 오토마타라는 표현에 구색을 맞추지 않았나 생각이 든다. 갱장이 복잡하게 처리하고 있는 어떤 소스를 웹서핑 중에 확인하였지만 중요한 골격이 되는 알고리즘(? 그냥 단순한 프로세스 정도인가....)은 크게 다르지 않은가 라고 생각이 된다. 누군가 고수님이 이 포스팅을 확인하고 지적해 준다면 정말 감사하겠다. 어찌되었든 잘 돌아가고 있는 모습을 볼때에는 참 뿌듯할 뿐이다. 이 프로세스 유도하기 위해 빽보드에 ㄱ,ㄴ,ㄷ을 무수히 써보기만 했다. ㅋㅋ

빽보드에 삽질한 흔적 이후 많은 변형이 있었지만 골격을 잡기 위한 과정에서 중요한 정보랄까

AutomataKR.h
#pragma once

#define	KEY_CODE_SPACE		-1	// 띄어쓰기
#define	KEY_CODE_ENTER		-2	// 내려쓰기
#define	KEY_CODE_BACKSPACE	-3	// 지우기

class CAutomataKR
{
public:
	CAutomataKR(void);
	~CAutomataKR(void);

public:
	void		Clear();					// 버퍼초기화
	wchar_t	SetKeyCode(int	nKeyCode);	// 키코드 받기 (정해진 코드값을 입력 받아 조합)

	wchar_t	ingWord;		// 작성중 글자
	CString	completeText;	// 완성 문자열

private:
	enum	HAN_STATUS	 // 단어조합상태
	{
		HS_FIRST = 0,		// 초성
		HS_FIRST_V,		// 자음 + 자음 
		HS_FIRST_C,		// 모음 + 모음
		HS_MIDDLE_STATE,	// 초성 + 모음 + 모음
		HS_END,			// 초성 + 중성 + 종성
		HS_END_STATE,		// 초성 + 중성 + 자음 + 자음
		HS_END_EXCEPTION	// 초성 + 중성 + 종성(곁자음)
	};

	int		m_nStatus;			// 단어조합상태
	int		m_nPhonemez[5]; 	// 음소[초,중,종,곁자음1,곁자음2]

	wchar_t	m_completeWord;	// 완성글자

	// 변환
	//
	int		ToInitial(int nKeyCode);	// 초성으로
	int		ToFinal(int nKeyCode);	// 종성으로

	// 분해
	//
	void		DecomposeConsonant();	// 자음분해

	// 합성
	//
	BOOL	MixInitial(int nKeyCode);	// 초성합성
	BOOL	MixFinal(int nKeyCode);	// 종성합성

	BOOL	MixVowel(int * nCurCode, int nInCode);	// 모음합성

	// 조합(한글완성)
	//
	wchar_t	CombineHangle(int cho, int jung, int jong);
	wchar_t	CombineHangle(int status);
	void		CombineIngWord(int status);

	int		DownGradeIngWordStatus(wchar_t word);	//조합상태 낮추기
};

AutomataKR.cpp
코드가 길어지면 집중력이 떨어진다는 것을 다시 한번 느낌니다.
keenam님께서 덧글로 지적해 주신 부분을 수정하였습니다.
정말 감사합니다.
최근 학습의 열의가 많이 떨어져 있습니다. 어쩌면 좋을까요 ^^;;
관심 가져주시는 분들께 감사를 드립니다. 꾸벅

keenam 님이 다시 지적해주신 부분이 있었습니다.  (;ㅁ;) 눈물이 앞을 가립니다. 아~
종성이 곁자음이 아닐경우 지우기 키를 입력하였을때 무조건 종성을 삭제하는 부분을 수정하여 반영하였습니다. 그리고 변수명 및 Function 명을 좀더 의미 있는 단어로 수정 처리하였습니다.

  
#include "StdAfx.h"
#include "AutomataKR.h"

const int BASE_CODE = 0xac00;	// 기초음성(가)
const int LIMIT_MIN = 0xac00;	// 음성범위 MIN(가)
const int LIMIT_MAX = 0xd7a3;	// 음성범위 MAX

// 음성 테이블
wchar_t SOUND_TABLE[68] =
{
	/* 초성 19자 0 ~ 18 */
	_T('ㄱ'), _T('ㄲ'), _T('ㄴ'), _T('ㄷ'), _T('ㄸ'),
	_T('ㄹ'), _T('ㅁ'), _T('ㅂ'), _T('ㅃ'), _T('ㅅ'),
	_T('ㅆ'), _T('ㅇ'), _T('ㅈ'), _T('ㅉ'), _T('ㅊ'),
	_T('ㅋ'), _T('ㅌ'), _T('ㅍ'), _T('ㅎ'),
	/* 중성 21자 19 ~ 39 */
	_T('ㅏ'), _T('ㅐ'), _T('ㅑ'), _T('ㅒ'), _T('ㅓ'),
	_T('ㅔ'), _T('ㅕ'), _T('ㅖ'), _T('ㅗ'), _T('ㅘ'),
	_T('ㅙ'), _T('ㅚ'), _T('ㅛ'), _T('ㅜ'), _T('ㅝ'),
	_T('ㅞ'), _T('ㅟ'), _T('ㅠ'), _T('ㅡ'), _T('ㅢ'),
	_T('ㅣ'),
	/* 종성 28자 40 ~ 67 */
	_T(' '), _T('ㄱ'), _T('ㄲ'), _T('ㄳ'), _T('ㄴ'),
	_T('ㄵ'), _T('ㄶ'), _T('ㄷ'), _T('ㄹ'), _T('ㄺ'), 
	_T('ㄻ'), _T('ㄼ'), _T('ㄽ'), _T('ㄾ'), _T('ㄿ'),
	_T('ㅀ'), _T('ㅁ'), _T('ㅂ'), _T('ㅄ'), _T('ㅅ'),
	_T('ㅆ'), _T('ㅇ'), _T('ㅈ'), _T('ㅊ'), _T('ㅋ'),
	_T('ㅌ'), _T('ㅍ'), _T('ㅎ')
};

// 초성 합성 테이블
int MIXED_CHO_CONSON[14][3] =
{
	{ 0, 0,15}, // ㄱ,ㄱ,ㅋ
	{15, 0, 1}, // ㅋ,ㄱ,ㄲ
	{ 1, 0, 0}, // ㄲ,ㄱ,ㄱ

	{ 3, 3,16}, // ㄷ,ㄷ,ㅌ
	{16, 3, 4}, // ㅌ,ㄷ,ㄸ
	{ 4, 3, 3}, // ㄸ,ㄷ,ㄷ

	{ 7, 7,17}, // ㅂ,ㅂ,ㅍ
	{17, 7, 8}, // ㅍ,ㅂ,ㅃ
	{ 8, 7, 7}, // ㅃ,ㅂ,ㅂ

	{ 9, 9,10}, // ㅅ,ㅅ,ㅆ
	{10, 9, 9}, // ㅆ,ㅅ,ㅅ

	{12,12,14}, // ㅈ,ㅈ,ㅊ
	{14,12,13}, // ㅊ,ㅈ,ㅉ
	{13,12,12}  // ㅉ,ㅈ,ㅈ
};

// 초성,중성 모음 합성 테이블
int MIXED_VOWEL[21][3] = 
{
	{19,19,21},	// ㅏ,ㅏ,ㅑ
	{21,19,19},	// ㅑ,ㅏ,ㅏ

	{19,39,20},	// ㅏ,ㅣ,ㅐ
	{21,39,22},	// ㅑ,ㅣ,ㅒ

	{23,23,25},	// ㅓ,ㅓ,ㅕ
	{25,23,23},	// ㅕ,ㅓ,ㅓ

	{23,39,24},	// ㅓ,ㅣ,ㅔ
	{25,39,26},	// ㅕ,ㅣ,ㅖ

	{27,27,31},	// ㅗ,ㅗ,ㅛ
	{31,27,27},	// ㅛ,ㅗ,ㅗ

	{27,19,28},	// ㅗ,ㅏ,ㅘ
	{28,39,29},	// ㅘ,ㅣ,ㅙ

	{27,39,30},	// ㅗ,ㅣ,ㅚ

	{32,32,36},	// ㅜ,ㅜ,ㅠ
	{36,32,32},	// ㅠ,ㅜ,ㅜ

	{32,23,33},	// ㅜ,ㅓ,ㅝ
	{33,39,34},	// ㅝ,ㅣ,ㅞ

	{32,39,35},	// ㅜ,ㅣ,ㅟ

	{39,39,37},	// ㅣ,ㅣ,ㅡ
	{37,39,38},	// ㅡ,ㅣ,ㅢ
	{38,39,39}	// ㅢ,ㅣ,ㅣ
};

// 종성 합성 테이블
int MIXED_JONG_CONSON[22][3] = 
{
	{41,41,64}, // ㄱ,ㄱ,ㅋ
	{64,41,42}, // ㅋ,ㄱ,ㄲ
	{42,41,41}, // ㄲ,ㄱ,ㄱ

	{41,59,43}, // ㄱ,ㅅ,ㄳ

	{44,62,45}, // ㄴ,ㅈ,ㄵ
	{44,67,46}, // ㄴ,ㅎ,ㄶ

	{47,47,65}, // ㄷ,ㄷ,ㅌ
	{65,47,47}, // ㅌ,ㄷ,ㄷ

	{48,41,49}, // ㄹ,ㄱ,ㄺ
	{48,56,50}, // ㄹ,ㅁ,ㄻ

	{48,57,51}, // ㄹ,ㅂ,ㄼ
	{51,57,54}, // ㄼ,ㅂ,ㄿ

	{48,59,52}, // ㄹ,ㅅ,ㄽ
	{48,47,53}, // ㄹ,ㄷ,ㄾ	
	{48,67,55}, // ㄹ,ㅎ,ㅀ

	{57,57,66}, // ㅂ,ㅂ,ㅍ
	{66,57,57}, // ㅍ,ㅂ,ㅂ

	{57,59,58}, // ㅂ,ㅅ,ㅄ

	{59,59,60}, // ㅅ,ㅅ,ㅆ
	{60,59,59}, // ㅆ,ㅅ,ㅅ

	{62,62,63}, // ㅈ,ㅈ,ㅊ
	{63,62,62}  // ㅊ,ㅈ,ㅈ
};

// 종성 분해 테이블
int DIVIDE_JONG_CONSON[13][3] = 
{
	{41,41,42}, // ㄱ,ㄱ,ㄲ
	{41,59,43}, // ㄱ,ㅅ,ㄳ
	{44,62,45}, // ㄴ,ㅈ,ㄵ
	{44,67,46}, // ㄴ,ㅎ,ㄶ
	{48,41,49}, // ㄹ,ㄱ,ㄺ
	{48,56,50}, // ㄹ,ㅁ,ㄻ
	{48,57,51}, // ㄹ,ㅂ,ㄼ
	{51,66,54}, // ㄹ,ㅍ,ㄿ
	{48,59,52}, // ㄹ,ㅅ,ㄽ
	{48,65,53}, // ㄹ,ㅌ,ㄾ	
	{48,67,55}, // ㄹ,ㅎ,ㅀ
	{57,59,58}, // ㅂ,ㅅ,ㅄ
	{59,59,60}  // ㅅ,ㅅ,ㅆ
};

CAutomataKR::CAutomataKR(void)
{
	Clear();
}

CAutomataKR::~CAutomataKR(void)
{
}

//
// 버퍼 초기화
void CAutomataKR::Clear()
{
	m_nStatus		= HS_FIRST;
	completeText	= _T("");
	ingWord			= NULL;
	m_completeWord	= NULL;
}

//
// 키코드 입력 및 조합 (정해진 int 코드값을 입력 받아 한글조합)
wchar_t CAutomataKR::SetKeyCode(int nKeyCode)
{
	m_completeWord = NULL;

	// 특수키 입력
	//
	if(nKeyCode < 0)
	{
		m_nStatus = HS_FIRST;

		if(nKeyCode == KEY_CODE_SPACE) // 띄어쓰기
		{
			if(ingWord != NULL)
				completeText += ingWord;
			else
				completeText += _T(' ');

			ingWord = NULL;
		}
		else if(nKeyCode == KEY_CODE_ENTER) // 내려쓰기
		{
			if(ingWord != NULL)
				completeText += ingWord;

			completeText += _T("\r\n");

			ingWord = NULL;
		}
		else if(nKeyCode == KEY_CODE_BACKSPACE) // 지우기
		{
			if(ingWord == NULL)
			{
				if(completeText.GetLength() > 0)
				{
					if(completeText.Right(1) == _T("\n"))
						completeText = completeText.Left(completeText.GetLength() -2);
					else
						completeText = completeText.Left(completeText.GetLength() -1);
				}
			}
			else
			{
				m_nStatus = DownGradeIngWordStatus(ingWord);
			}
		}

		return m_completeWord;
	}

	switch(m_nStatus)
	{
	case HS_FIRST:
		// 초성
		//
		m_nPhonemez[0]	= nKeyCode;
		ingWord			= SOUND_TABLE[m_nPhonemez[0]];
		m_nStatus		= nKeyCode > 18 ? HS_FIRST_C : HS_FIRST_V;
		break;

	case HS_FIRST_C:
		// 모음 + 모음
		//
		if(nKeyCode > 18)	// 모음
		{
			if(MixVowel(&m_nPhonemez[0], nKeyCode) == FALSE)
			{
				m_completeWord = SOUND_TABLE[m_nPhonemez[0]];
				m_nPhonemez[0] = nKeyCode;
			}
		}
		else				// 자음
		{
			m_completeWord	= SOUND_TABLE[m_nPhonemez[0]];
			m_nPhonemez[0]	= nKeyCode;
			m_nStatus		= HS_FIRST_V;
		}
		break;

	case HS_FIRST_V:
		// 자음 + 자음
		//
		if(nKeyCode > 18)	// 모음
		{
			m_nPhonemez[1]	= nKeyCode;
			m_nStatus		= HS_MIDDLE_STATE;
		}
		else				// 자음
		{
			if(!MixInitial(nKeyCode))
			{
				m_completeWord	= SOUND_TABLE[m_nPhonemez[0]];
				m_nPhonemez[0]	= nKeyCode;
				m_nStatus		= HS_FIRST_V;
			}
		}
		break;

	case HS_MIDDLE_STATE:
		// 초성 + 모음 + 모음
		//
		if(nKeyCode > 18)
		{
			if(MixVowel(&m_nPhonemez[1], nKeyCode) == FALSE)
			{
				m_completeWord	= CombineHangle(1);
				m_nPhonemez[0]	= nKeyCode;
				m_nStatus		= HS_FIRST_C;
			}
		}
		else
		{
			int jungCode = ToFinal(nKeyCode);

			if(jungCode > 0)
			{
				m_nPhonemez[2]	= jungCode;
				m_nStatus		= HS_END_STATE;
			}
			else
			{
				m_completeWord	= CombineHangle(1);
				m_nPhonemez[0]	= nKeyCode;
				m_nStatus		= HS_FIRST;
			}
		}
		break;

	case HS_END:
		// 초성 + 중성 + 종성
		//
		if(nKeyCode > 18)  
		{
			m_completeWord	= CombineHangle(1);
			m_nPhonemez[0]	= nKeyCode;
			m_nStatus		= HS_FIRST;
		}
		else
		{
			int jungCode = ToFinal(nKeyCode);
			if(jungCode > 0)
			{
				m_nPhonemez[2]	= jungCode;
				m_nStatus		= HS_END_STATE;
			}
			else
			{
				m_completeWord	= CombineHangle(1);
				m_nPhonemez[0]	= nKeyCode;
				m_nStatus		= HS_FIRST;
			}  
		}
		break;

	case HS_END_STATE:
		// 초성 + 중성 + 자음(종) + 자음(종)
		//
		if(nKeyCode > 18)
		{
			m_completeWord = CombineHangle(1);

			m_nPhonemez[0]	= ToInitial(m_nPhonemez[2]);
			m_nPhonemez[1]	= nKeyCode;
			m_nStatus		= HS_MIDDLE_STATE;
		}
		else
		{
			int jungCode = ToFinal(nKeyCode);
			if(jungCode > 0)
			{
				m_nStatus = HS_END_EXCEPTION;

				if(!MixFinal(jungCode))
				{
					m_completeWord	= CombineHangle(2);
					m_nPhonemez[0]	= nKeyCode;
					m_nStatus		= HS_FIRST_V;
				}
			}
			else
			{
				m_completeWord	= CombineHangle(2);
				m_nPhonemez[0]	= nKeyCode;
				m_nStatus		= HS_FIRST_V;
			}  
		}
		break;

	case HS_END_EXCEPTION:
		// 초성 + 중성 + 종성(곁자음)
		//
		if(nKeyCode > 18)  
		{
			DecomposeConsonant();
			m_nPhonemez[1]	= nKeyCode;
			m_nStatus		= HS_MIDDLE_STATE;
		}
		else
		{
			int jungCode = ToFinal(nKeyCode);
			if(jungCode > 0)
			{
				m_nStatus = HS_END_EXCEPTION;

				if(!MixFinal(jungCode))
				{
					m_completeWord	= CombineHangle(2);
					m_nPhonemez[0]	= nKeyCode;
					m_nStatus		= HS_FIRST_V;
				}
			}
			else
			{
				m_completeWord	= CombineHangle(2);
				m_nPhonemez[0]	= nKeyCode;
				m_nStatus		= HS_FIRST_V; 
			}
		}
		break;
	}

	// 현재 보이는 글자상태
	CombineIngWord(m_nStatus);

	// 완성 문자열 만들기
	if(m_completeWord != NULL) 
		completeText += TCHAR(m_completeWord);

	return m_completeWord;
}

// 초성으로 변환
int CAutomataKR::ToInitial(int nKeyCode)
{
	switch(nKeyCode)
	{
	case 41: return 0;	// ㄱ
	case 42: return 1;	// ㄲ
	case 44: return 2;	// ㄴ
	case 47: return 3;	// ㄷ
	case 48: return 5;	// ㄹ
	case 56: return 6;	// ㅁ
	case 57: return 7;	// ㅂ
	case 59: return 9;	// ㅅ
	case 60: return 10;	// ㅆ
	case 61: return 11;	// ㅇ
	case 62: return 12;	// ㅈ
	case 63: return 14;	// ㅊ
	case 64: return 15;	// ㅋ
	case 65: return 16;	// ㅌ
	case 66: return 17;	// ㅍ
	case 67: return 18;	// ㅎ
	}
	return -1;
}

// 종성으로 변환
int CAutomataKR::ToFinal(int nKeyCode)
{
	switch(nKeyCode)
	{
	case 0: return 41;	// ㄱ
	case 1: return 42;	// ㄲ
	case 2: return 44;	// ㄴ
	case 3: return 47;	// ㄷ
	case 5: return 48;	// ㄹ
	case 6: return 56;	// ㅁ
	case 7: return 57;	// ㅂ
	case 9: return 59;	// ㅅ
	case 10: return 60;	// ㅆ
	case 11: return 61;	// ㅇ
	case 12: return 62;	// ㅈ
	case 14: return 63;	// ㅊ
	case 15: return 64;	// ㅋ
	case 16: return 65;	// ㅌ
	case 17: return 66;	// ㅍ
	case 18: return 67;	// ㅎ
	}
	return -1;
}

// 자음분해
void CAutomataKR::DecomposeConsonant()
{
	int i = 0;
	if(m_nPhonemez[3] > 40 || m_nPhonemez[4] > 40)
	{
		do
		{
			if(DIVIDE_JONG_CONSON[i][2] == m_nPhonemez[2])
			{
				m_nPhonemez[3] = DIVIDE_JONG_CONSON[i][0];
				m_nPhonemez[4] = DIVIDE_JONG_CONSON[i][1];

				m_completeWord = CombineHangle(3);
				m_nPhonemez[0]	 = ToInitial(m_nPhonemez[4]);
				return;
			}
		}
		while(++i< 13);
	}

	m_completeWord = CombineHangle(1);
	m_nPhonemez[0]	 = ToInitial(m_nPhonemez[2]);
}

// 초성합성
BOOL CAutomataKR::MixInitial(int nKeyCode)
{
	if(nKeyCode >= 19)
		return FALSE;

	int i = 0;
	do
	{
		if(MIXED_CHO_CONSON[i][0] == m_nPhonemez[0] && MIXED_CHO_CONSON[i][1] == nKeyCode)
		{
			m_nPhonemez[0] = MIXED_CHO_CONSON[i][2];
			return TRUE;
		}
	}
	while(++i < 14);

	return FALSE;
}

// 종성합성
BOOL CAutomataKR::MixFinal(int nKeyCode)
{
	if(nKeyCode <= 40) return FALSE;

	int i = 0;
	do
	{
		if(MIXED_JONG_CONSON[i][0] == m_nPhonemez[2] && MIXED_JONG_CONSON[i][1] == nKeyCode)
		{
			m_nPhonemez[3] = m_nPhonemez[2];
			m_nPhonemez[4] = nKeyCode;
			m_nPhonemez[2] = MIXED_JONG_CONSON[i][2];

			return TRUE;
		}
	}
	while(++i < 22);

	return FALSE;
}

// 모음합성
BOOL CAutomataKR::MixVowel(int * currentCode, int inputCode)
{
	int i = 0;
	do
	{
		if(MIXED_VOWEL[i][0] == * currentCode && MIXED_VOWEL[i][1] == inputCode)
		{
			* currentCode = MIXED_VOWEL[i][2];
			return TRUE;
		}
	}
	while(++i< 21);

	return FALSE;
}

// 한글조합
wchar_t CAutomataKR::CombineHangle(int cho, int jung, int jong)
{
	// 초성 * 21 * 28 + (중성 - 19) * 28 + (종성 - 40) + BASE_CODE;
	return BASE_CODE - 572 + jong + cho * 588 + jung * 28;
}

wchar_t CAutomataKR::CombineHangle(int status)
{
	switch(status)
	{
	//초성 + 중성
	case 1: return CombineHangle(m_nPhonemez[0], m_nPhonemez[1], 40);

	//초성 + 중성 + 종성
	case 2: return CombineHangle(m_nPhonemez[0], m_nPhonemez[1], m_nPhonemez[2]);
	
	//초성 + 중성 + 곁자음01
	case 3: return CombineHangle(m_nPhonemez[0], m_nPhonemez[1], m_nPhonemez[3]);
	}
	return ' ';
}

void CAutomataKR::CombineIngWord(int status)
{
	switch(m_nStatus)
	{
	case HS_FIRST:
	case HS_FIRST_V:
	case HS_FIRST_C:
		ingWord = SOUND_TABLE[m_nPhonemez[0]];
		break;

	case HS_MIDDLE_STATE:
	case HS_END:
		ingWord = CombineHangle(1);
		break;

	case HS_END_STATE:
	case HS_END_EXCEPTION:
		ingWord = CombineHangle(2);
		break;
	}
}

int CAutomataKR::DownGradeIngWordStatus(wchar_t word)
{
	int iWord = word;

	//초성만 있는 경우
	//
	if(iWord < LIMIT_MIN || iWord > LIMIT_MAX)
	{
		ingWord = NULL;

		return HS_FIRST;
	}

	//문자코드 체계
	//iWord = firstWord * (21*28)
	//		+ middleWord * 28
	//		+ lastWord * 27
	//		+ BASE_CODE;
	//
	int totalWord	= iWord - BASE_CODE;
	int iFirstWord	= totalWord / 28 / 21;	//초성
	int iMiddleWord = totalWord / 28 % 21;	//중성
	int iLastWord	= totalWord % 28;		//종성

	m_nPhonemez[0] = iFirstWord; //초성저장

	if(iLastWord == 0)	//종성이 없는 경우
	{
		ingWord = SOUND_TABLE[m_nPhonemez[0]];

		return HS_FIRST_V;
	}

	m_nPhonemez[1] = iMiddleWord + 19; //중성저장

	iLastWord += 40;

	for(int i = 0; i < 13; i++)
	{
		if(iLastWord == DIVIDE_JONG_CONSON[i][2])
		{
			ingWord = CombineHangle(3);
			m_nPhonemez[2] = DIVIDE_JONG_CONSON[i][0]; // 종성저장
			return HS_END_EXCEPTION;
		}
	}

	ingWord = CombineHangle(1);

	return HS_MIDDLE_STATE;
}
    





히스토리)
2009.01.
 - 초본 완성
2009.05.
 - 버그 수정 후 블로그 등록
2009.09.30
 - 모음합성시 발생하는 버그 < keenam님 제보
2009.11.11.
 - 종성이 곁자음이 아닐 경우 지우기 키를 입력시 종성이 삭제되는 문제 < keenam님 제보
 - 변수명 과 함수명 정리, 코딩 스타일(띄어쓰기, 내려쓰기, 주석) 정리
2009.12.28.
 - 광고좀 넣어봤습니다. (굽신굽신)
2010.02.11.
 - 오타버그 수정(Line 137 의 {48,57,54}// ㄹ,ㅍ,ㄿ 의 코드 수정
2010.11.15.
 - 테스트 소스 링크 추가

블로그에 올리기 까지의 히스토리는 정확하게 기억이 나지 않습니다.
이렇게까지 관심이 높아질줄 몰랐습니다.
그리고 이렇게 보완이 될지 몰랐습니다.
앞으로는 히스토리 관리를 해줘야 할것 같습니다.

프로젝트 진행중에 버그를 하나 더 잡을수 있었네요 흑...



최근 짬이 나는 시간을 빌려 테스트 소스를 만들어보았습니다.
급하게 만들다 보니 큰 변화가 없어서 아쉽지만 그래도 테스트 소스를 만들었다는 것에 의미를 두고자 합니다.
테스트 주도개발 TDD 라는 것이 있습니다. 애자일이다 익스트림이다 하는 바람은 제 주변에서 거세게 불어본적이 없어 잘 모르겠지만 한번쯤 내 입에 맞는 애자일을 아니 , 나만을위한 프로그래밍 개발법을 세워봐야 할것 같네요. 어째든 포스트 링크 추가합니다.
2010/11/12 - 한글 오토마타 만들기 테스트 소스

Trackback : 0 Comment : 74
  • 이전 댓글 더보기
  • 2010.04.07 09:52 주소 수정/삭제 답글

    비밀댓글입니다

    • BlogIcon 맛있는 2010.04.08 09:56 신고 수정/삭제

      음 안들어갈 것 같습니다. ^^
      중반 이후로 입력상태 값을 정리 하면서 확장 수정되면서 기존에 있던 상태값 중에 퇴화? 라고 하면 맞을까요? 퇴화되어 버린것 같습니다.

  • 2010.04.20 00:46 주소 수정/삭제 답글

    비밀댓글입니다

  • 맥 2010.05.31 13:11 주소 수정/삭제 답글

    안녕하세요 ^^ 오토마타를 검색하다가 우연히 들렀습니다
    핸드폰이라 그런지 모르겠지만 단독종성 'ㄱ','ㅀ' 같은경우 키코드가 종성과 조금 달랐습니다 ^^;
    2글자 더 있더군요. 'ㄸ','ㅃ'

    • BlogIcon 맛있는 2010.05.31 17:24 신고 수정/삭제

      음.. 죄송합니다. 단독종성이란 단어와, 'ㄱ','ㅀ' 같은경우 키코드가 종성과 조금 다르다는 말이? 핸드폰에 입력했을때를 말씀하시는 건가요?

    • 맥 2010.05.31 18:49 수정/삭제

      아..제가 웹에서 오토마타를 구현했거든요, 그러면서 알아낸 사실이 ㄱ,ㄴ,ㄷ,ㄹ 같이 초성만 있는 단어들의 코드값이 조금 다르다는 거에요.

      기본폼 초성 + 중성 + 종성(없어도 됨) 일 경우 기본 코드값이 0XAC00 이지만

      종성만 있는 경우 ㅀ <- 이런글자라던가
      ㄱ,ㄴ,ㄷ,ㄸ, 등등 을 단독종성이라고 표기하였습니다 .
      저 경우는 0x3131 부터 시작하더군요. 그리고 종성의 키맵을 따라가는듯 하더니 ㄸ 과 ㅃ 이 받침에 허용되지 않지만 단독으로는 쓰여서 추가되있었습니다.

      그래서 총 30 자였습니다 ^^;; 그걸 알려드리려고한거에요.

      강대리님 글을 참조해서 저도 웹에서 오토마타를 구현했는데 저는 형태소 검색에 저 기술을 썼습니다.

      네이버에서 검색되는것처럼

      '가' 까지 치면 '강대리' 가 검색되도록 하는거요.

      말이 너무 길어졌는데 이해가 잘 되실런지 모르겠네요;;

    • BlogIcon 맛있는 2010.06.01 09:53 신고 수정/삭제

      초성만 있는 경우를 말씀하시는 것 같습니다.
      기본적으로 한글 자모 필드가 있어서 표현 키입력이 가능한데요. ㄸ, ㅃ, ㅀ 같은 자들을 모음없이 사용할때 초성자음만 온것으로 판단하여 처리함니다.

      음... 다시한번 설명하자면 한글에서는 종성위치에 ㄸ,ㅃ 같은자가 올수 없도록 되어있습니다. 즉 종성위치에 ㄸ, ㅃ 을 키입력 자체가 할수 없습니다. 때문에 이 글자를 초성의 범주에 포함하여 구분하는 것이 맞지 않을까 생각이 됩니다.

      음... 한가지더 제가 표현한 방식에서는 앞자의 종성과 다음 자의 초성자음 또는 중성모음의 결합을 일부 고려하지 않았습니다. (솔찍히 이야기하면 꾀 번거로운 작업이였기 때문에 이정도면 만족이란 자기암시에 마음을 노았다고 해야할까요 ^^;;;)

      결론적으로 판단기준에서 볼때 ㄱ,ㄴ,ㄷ,ㄹ,... 등의 자음만이 입력되는 순간은 일반적으로 초성으로 판단하는 것이 맞지 않을까 생각합니다. 종성이란 뜻자체가 의미하는 것이 마지막에 소리나는 말소리라는 건데요 앞자가 있어야 종성이란 의미가 생기는 것이니까 말입니다.

    • 맥 2010.06.07 16:07 수정/삭제

      네 따지면 초성이 맞겠지만 종성에서만 쓰일수 있는 ㅀ 등이 있어서 종성의 범주에 넣었었습니다 ^^;

      참 한글이 애매하네요~

    • BlogIcon 맛있는 2010.06.08 14:04 신고 수정/삭제

      네 맞습니다. ^^ 가장어려운 말이 한글일지도 모를 일입니다. ㅎㅎ

  • 완전초짜게임맨! 2010.08.05 19:57 주소 수정/삭제 답글

    오토마타 관련된 정보를 찾다가 여기를 봅니다... (좋은 정보가 +.+)
    지금 저희가 개발하는 게임이 있어서 SDL 이걸 적용 해두 될까요?

    • BlogIcon 맛있는 2010.08.09 10:04 신고 수정/삭제

      네 사용하셔도 됩니다. ^^
      감사합니다.
      앞으로 더 좋은 글을 올릴수 있도록 노력하겠습니다.
      꾸벅 (__)

  • 1퍼센트 2010.11.04 11:18 주소 수정/삭제 답글

    오토마타 관련 정보를 찾다가 알게 되었습니다. 감사드리구요~
    터치모니터에 사용할 한글 입력기를 만들고 있는데 이걸 사용해도 될까요?
    그리고 혹시 예제 프로그램 같은게 있나요? 시험삼아서 vs2005로 만들어보고 있는데 키코드를 어떤걸 넣어야 할지 감이 잘 안오네요..
    미리 감사드립니다~^^

    • BlogIcon 맛있는 2010.11.15 11:24 신고 수정/삭제

      예제 프로그램을 만들었습니다. 뭐 잘 만들지를 못해 도움이 될지 모르겠지만 글 하단에 링크 걸었습니다. 감사합니다. ^^

  • 사향제비나비 2010.12.11 21:48 주소 수정/삭제 답글

    KT나랏글(LG이지한글) 소스를 찾다가 찾아서 들어왔습니다. 오픈소스 프로젝트인 XBMC 미디어 센터에 한글 오토마타 기능을 넣어보려고 소스를 찾던중 여기까지 왔네요. 혹시, LG 이지한글은 구현해 놓으신 것이 없나요? 구현해 놓으신 소스를 이용해서 한번 해봐야 겠네요. 소스 오픈 감사합니다.

    • BlogIcon 맛있는 2010.12.13 09:24 신고 수정/삭제

      대단한 것도 아닙니다. C++이라는 언어를 배우지도 않은 상태에서 소스코딩을 하다보니, 외국인이 한국말을 하듯이 짜놓은 부분이 많이 있습니다. 때문에 짜임세가 많이 부족합니다. (-_-)a 잘못된 부분이 있다거나, 좀더 좋은 방법이 있다고 한다면 말씀 부탁드립니다.

  • 그노마 2010.12.21 10:49 주소 수정/삭제 답글

    멋진 오토마타이군요 QT쪽에 활용해보고 개선 부분이 있다면 알려드리도록 하겠습니다.
    감사합니다.

  • 미키드 2011.01.03 12:14 주소 수정/삭제 답글

    이런 멋진 정보를 공개하시다니 능력자 이십니다.
    지금 MFC기반으로 지화인식 프로그램 제작중인대 이부분에서 막혔는대 소중한 정보 감사합니다.

    • BlogIcon 맛있는 2011.01.06 12:31 신고 수정/삭제

      부끄럽습니다. -_-; 더 좋은 프로그램을 위해 항상 노력해야 하는데 말처럼은 되지 않는 것 같습니다.
      도움이 되었다니 다행입니다.

  • 2011.06.21 15:28 주소 수정/삭제 답글

    비밀댓글입니다

    • BlogIcon 맛있는 2011.08.09 10:53 신고 수정/삭제

      한자 오토마타를 만든다는 것은 좀 개념이 다를것 같습니다. 물론 더욱 힘들것 같다는 생각입니다. -_-;

      키보드 입력의 오토마타는 또다른 문제꺼리들이 존재하기 때문에 쉽지 않는 작업으로 판단됩니다.

      개발산정은 좀 까다롭지 않을까 합니다.

  • strongpen 2011.07.28 09:21 주소 수정/삭제 답글

    안녕하세요. ^^ 덧글들을 보니 소스를 사용해도 될거 같아서 인사 꾸벅 드립니다.^^ 감사합니다.

  • 정수현 2011.09.09 21:13 주소 수정/삭제 답글

    저두 이거 보구 한글 오토마타 공부하고 있습니다. 회사 그만두고 프로그램 공부하고 있어요...
    이 코드로 제가 생각하는 입력기 어플하나 만들어도 될까요? ㅋㅋ 담에 완성되면 인형오리님에게 살짝 보여 드릴께요^^ 인형오리님 "쵝오!!!~~~"

    • BlogIcon 맛있는 2011.10.26 12:37 신고 수정/삭제

      최고까지는 않되는데요 글쩍글쩍 ^^ 편하게 사용하세요.
      즐거운 코딩되세요. ㅎㅎ

  • 이화영 2011.11.10 17:16 주소 수정/삭제 답글

    안녕하세요. 회사에서 한글 입력기를 구현하다 구글링으로 강대리님의 소스를 찾게 되었네요.
    개발 환경과 입력방법은 다르지만 아무래도 강대리님의 완성도 높은 소스를 많이 사용할 것 같네요. ^^
    예제프로그램으로 공개해주신 코드를 사용해도 될까요?
    좋은 설명과 좋은 예제 코드에 감사드립니다. ^^ 즐거운 하루보내세요.

  • 2011.12.02 17:17 주소 수정/삭제 답글

    비밀댓글입니다

    • BlogIcon 맛있는 2011.12.13 12:34 신고 수정/삭제

      음... 이화영님의 의견에 고민을 좀 해봤습니다.
      일단 기본적으로 자유롭게 사용하는 것을 기준으로 작성하여 올린 posting 인데 확정을 하여 프로그램을 수정하였다는 것은 고민스럽기는 합니다.

      조금더 조언을 받아도 좋을것 같아 제 메일 주소를 올림니다.

      xangfi@daum.net

      이화영님의 의견을 들어보고 판단하여 행동하는 것이 좋을것 같아 문의를 드리겠습니다.
      좋은 의견 정말 감사합니다.

  • BlogIcon anydragon 2012.01.02 12:36 주소 수정/삭제 답글

    안녕하세요. 너무 좋은 소스를 공개로 사용할수 있게 해주셔서 감사합니다. 추후 종성의 분해 ㅢ 같은 결합에서 삭제 버튼 누를때 ㅡ만 남게 하는 것 추가 계획이 있으신지요??ㅎㅎ 얻어 쓰려는 주제에 감히 여쭈어 봅니다.

    • BlogIcon 맛있는 2012.01.03 11:09 신고 수정/삭제

      아마 추가 수정을 없을 것 같습니다.
      최초 포스팅을 한것도 한 3,4년쯤 된것 같습니다.
      오토마타 관련 작업을 계속하고 있는 것도 아니여서 아마도 작업은 더이상 진행될것 같지는 않습니다.

      죄송합니다. ^^

  • purityeyes 2012.05.15 13:18 주소 수정/삭제 답글

    좋은자료 게시해 주셔서 감사합니다. 근데 묻고싶은데 있습니다. 제가 C#으로 동일하게 구현하려고 하는데... 이해가 안가네요.. 번거로우시겠지만, 가상한글키보드 개발에 필요한 지식이나 자료좀 공유해 주실수 있으세요?? dwjun2k@gmail.com 부탁드립니다... 학생입니다.

  • BlogIcon 맛있는 2012.06.01 01:34 신고 주소 수정/삭제 답글

    이해가 않간다는 부분은 문법적인 부분인가요?
    문법적인 부분이라면 C를 조금 보시면 될것 같습니다.
    올려놓은 소스는 기본 C 문법 마스터만 되었다면 어느정보 분석이 가능하지 않을까 생각됩니다.

    그 외의 다른 부분이라면,
    조금더 정확하게 문의하고 싶으신 부분을 말씀해 주시면,
    확인후에 설명 또는 참고가능한 어떠한 자료등을 제공해드릴수도 있지 않을까 생각됩니다.

    뭐 솔찍한 마음은 이렇게 블로그를 관리하는 것도 요즘 버거운 나이가 되어버렸습니다. 머쓱..

  • 김영주 2014.02.13 16:07 주소 수정/삭제 답글

    소스를 활용해서 Openframeworks 기반의 한글 화상 키보드 만드는데 너무 잘 활용 하였습니다.
    감사합니다.
    혹 기존 소스를 활용 하여 Openframeworks용 이나 유니티용 addon을 만들어 오픈소스화 해볼려고 하는데 괜찮을지 문의 드립니다. 출처는 정확하게 밝히겠습니다~

    • BlogIcon 맛있는 2014.02.26 10:38 신고 수정/삭제

      아 네 ^^
      감사합니다.
      오픈소스에 도움이 된다니 기쁨니다.
      즐거운 코딩되세요.

  • misshy2 2014.04.03 00:58 주소 수정/삭제 답글

    아파트 예문에서요~~
    아파티 + ㅣ (ㅣ) <- 완성문자 분해, 초성 + 중성
    라고 하셨는데 위에
    아파 + ㄷ + ㄷ + ㅣ + ㅣ (ㅏ) <- 완성문자 분해,조합 초성 + 중성
    부분과 같은 것 아닌가요~_~
    초성이 아니라 조합초성인것 같아요~
    좋은 이론 & 자료 감사합니다 ^ㅡ^
    많은 도움이 되고 있어요 !!!!

    • BlogIcon 맛있는 2014.04.17 09:49 신고 수정/삭제

      이제보니 표현이 좀 어렵게 되었네요.
      아파티 + ㅣ (ㅣ) 의 뜻은
      앞의 '아팥'에 'ㅣ'입력이 들어온 상황을 이야기 합니다.
      때문에 '아팥'이 '아파' + 'ㅌ' 으로 완성문자의 분해가 일어납니다.
      그리고 입력된 'ㅣ'와 분리된 자음 'ㅌ'이 조합되는 상황으로
      초성 + 중성의 상황이 됩니다.

      표현을 좀더 매끄럽게 정리해보겠습니다.
      감사합니다.

  • wpwp1329 2014.10.14 23:47 주소 수정/삭제 답글

    한글입력에 관한 자료를 찾기가 굉장히 힘든데
    무척이나 도움이 되는 자료네요 ㅠㅠ
    감사히 보고갑니다.

  • BlogIcon icq4ever 2017.05.09 01:49 주소 수정/삭제 답글

    안녕하세요. 어렵게 작성한 코드를 공개해주셔서 감사합니다. 윗분도 오픈프레임웍스용으로 사용하셨다는데 저역시 오픈프레임웍스용 애드온으로 제작하여 오픈소스로 공개하려고 합니다. 출처 역시 명시해두었습니다.