삽질하는플머

코헨-서더랜드의 알고리즘 이야기

탐구생활/Delphi
어지간한 컴퓨터 그래픽 이론책자에 빠짐없이 등장하는 이 알고리즘은 1967년도에 만들어진 대표적인 클리핑 알고리즘이다.  
컴퓨터라는 물건 자체가 일반적이지 않던 시절, 직선을 뷰포트에 맞추어 잘라주기 위해 이런 고민을 했다는 사실이 그저 놀랍기만 하다. 

졸업연구 과제를 선택할 때, 밑천이 필요없는 과제를 고르다보니 컴퓨터 프로그램을 만들게 되었고 
배운게 도둑질이라고 설계->가공 과정에서 뭔가 건드려볼만한 주제라고 선택한 것이
오토캐드의 DXF 파일을 가공용 NC 코드로 바꿔보자는 시도였다. 

당시까지 짜본거라고는 오토캐드의 리습과 배치파일밖에 없던 무식한 시절,
일년 후배 여모씨를 족쳐가며 익숙치않은 터보C로 겉모습만 그럴듯한 DXF뷰어를 만들어가는데... 
아... 이거 이상하다. 줌인/줌아웃 기능을 넣고 선을 확대하다보니 엉뚱한 곳에 찌꺼기 선이 죽죽 그려진다. 
지금 생각하면 뷰포트를 한참 벗어나는 선을 그릴 때 사용한 변수값이 오버플로해서 생겨난 현상일텐데,
인터넷이 없던 시절이라 어디 물어볼 곳도 마땅치 않고 그저 냉가슴만 앓을 뿐...

그러다 눈에 띈 책이 지금도 보물처럼 간직하고 있는 이 책. 
꾸벅꾸벅 졸면서 들었던 컴퓨터 그래픽스 개론이라는 수업의 교재. 



수업시간에는 그저 지루해 죽겠더니만, 막상 상황이 닥치니 어쩌면 그렇게 머리에 쏙쏙 들어오는 옳은말만 적혀있는지...
뷰포트 클리핑을 적용한 뒤 마음껏 확대해도 더 이상 쓰레기 정보가 나타나지 않는다는 사실이 어찌나 신기하던지... 
그리하여 이 알고리즘은 책의 내용을 실제 코드로 만들어 본 최초의 경험이 되었다.


뭐 지금은 세월이 하도 좋아져서... 어지간한 확대기능 정도로는 당시 경험했던 오버플로는 구경할 수도 없다.
게다가 3D API를 쓰면 삼각형의 클리핑도 하드웨어에서 구현되버리니 이 알고리즘은 예전 추억과 함께 봉인!!
살면서 다시 써 볼 일이 있을까 싶었는데... 



(그런데 그 일이 실제로 일어났습니다!!!)



먹고 사는 이야기니 자세한 내용은 말할 수 없고,

좀 많이 넓은 높이맵과 이 위를 지나가는 물체와의 충돌점을 계산해야 하는 상황에 처했다. 
물체가 지나가는 경로상의 셀들을 추출하는 문제는 브랜슨햄, 또는 GPG의 네비메시 코드를 써먹으면 되는데
문제는 물체의 이동경로가 아주 길거나 높이맵과 전혀 상관없는 곳을 지나갈 때, 
검사범위를 높이맵 이내로 제한하거나 아예 검사를 생략하려면 어떻게 해햐 할까. 

자연스럽게 이 캐캐묵은 알고리즘이 떠올랐다.
선분과 뷰포트의 관계에 따라 아예 그리지 않거나 뷰포트 이내로 잘라 그리게 하는 절묘한 비법. 
이 비법을 쓰면 물체의 긴 이동경로 중 높이맵과 관계된 부분만 잘라내거나 
이동경로가 높이맵과 관계없다면 버릴 수도 있게 된다. 

위키백과를 열어보자. 
http://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland 

친절한 C코드도 제공된다. 아이조아~ 
이 코드는 우리가 산수시간에 배웠던 1사분면 좌표계를 기준으로 하므로 좌하단이 원점이다. 
내가 사용하는 좌표계는 무조건 좌상단이 원점이므로 이 부분만 주의해 델파이로 옮겨보자. 

function CohenSutherlandLineClip(var x1, y1, x2, y2: Single; const xmin, ymin, xmax, ymax: Single): Boolean;
const
  V_INSIDE = 0;
  V_LEFT   = 1;
  V_RIGHT  = 2;
  V_BOTTOM = 4;
  V_TOP    = 8;

  function ComputeOutcode(const x, y: Single): LongWord;
  begin
    Result := V_INSIDE;
    if x < xmin then Result := Result or V_LEFT else
    if x > xmax then Result := Result or V_RIGHT;

    if y < ymin then Result := Result or V_TOP else
    if y > ymax then Result := Result or V_BOTTOM;
  end;

var
  OutCode1, OutCode2: LongWord;
  ChkCode: LongWord;
  x, y : Single;
begin
  Result := false;

  OutCode1 := ComputeOutcode(x1, y1);
  OutCode2 := ComputeOutcode(x2, y2);

  while true do
  begin
    // Bitwise OR is 0. Trivially accept and get out of loop
    if not Boolean(OutCode1 or OutCode2) then
    begin
      Result := true;
      Break;
    end else
    // Bitwise AND is not 0. Trivially reject and get out of loop
    if Boolean(OutCode1 and OutCode2) then
    begin
      Break;
    end else
    begin
      // At least one endpoint is outside the clip rectangle; pick it.
      if Boolean(OutCode1) then
      begin
        ChkCode := OutCode1;
        x := x1;
        y := y1;
      end else
      begin
        ChkCode := OutCode2;
        x := x2;
        y := y2;
      end;
      // Now find the intersection point;
      // use formulas y = y0 + slope * (x - x0), x = x0 + (1 / slope) * (y - y0)
      if Boolean(ChkCode and V_TOP) then
      begin
        x := x1 + (x2 - x1) * (ymin - y1) / (y2 - y1);
        y := ymin;
      end else
      if Boolean(ChkCode and V_BOTTOM) then
      begin
        x := x1 + (x2 - x1) * (ymax - y1) / (y2 - y1);
        y := ymax;
      end else
      if Boolean(ChkCode and V_RIGHT) then
      begin
        y := y1 + (y2 - y1) * (xmax - x1) / (x2 - x1);
        x := xmax;
      end else
      if Boolean(ChkCode and V_LEFT) then
      begin
        y := y1 + (y2 - y1) * (xmin - x1) / (x2 - x1);
        x := xmin;
      end;

      // Now we move outside point to intersection point to clip
      // and get ready for next pass.
      if ChkCode = OutCode1 then
      begin
        x1 := x;
        y1 := y;
        OutCode1 := ComputeOutCode(x1, y1);
      end else
      begin
        x2 := x;
        y2 := y;
        OutCode2 := ComputeOutCode(x2, y2);
      end;
    end;
  end;
end;



테스트는 대충 다음과 같이. 
뷰포트는 윈도 클라이언트 영역에서 80픽셀씩 안쪽으로 잡았다. 
마우스를 두 번 눌러 입력받은 선분을 그려주고 클리핑을 통과한 선은 빨간색으로 조금 굵게 그려주자. 
전역 변수 따로잡기 귀찮으니 J+ 지시자를 써서 상수를 static으로 처리. 

......

{$J+}

procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
const
  CLICK_COUNT : Integer = 0;
  LAST_POS: TPoint = (x:-100; y:-100);
var
  x1, x2, y1, y2: Single;
  xmin, ymin, xmax, ymax: Single;
begin
  case CLICK_COUNT of
    0: LAST_POS := Point(X, Y);
    1:
    begin
      Canvas.Pen.Width := 1;
      Canvas.Pen.Color := clBlack;
      Canvas.MoveTo(LAST_POS.X, LAST_POS.Y);
      Canvas.LineTo(X, Y);

      xmin := 80;
      ymin := 80;
      xmax := ClientWidth - 80;
      ymax := ClientHeight - 80;

      x1 := LAST_POS.X;
      y1 := LAST_POS.Y;
      x2 := X;
      y2 := Y;

      if CohenSutherlandLineClip(x1, y1, x2, y2, xmin, ymin, xmax, ymax) then
      begin
        Canvas.Pen.Width := 2;
        Canvas.Pen.Color := clRed;

        Canvas.MoveTo(Round(x1), Round(y1));
        Canvas.LineTo(Round(x2), Round(y2));
      end;
    end;
  end;

  Inc(CLICK_COUNT);
  CLICK_COUNT := CLICK_COUNT mod 2;
end;

procedure TForm1.FormPaint(Sender: TObject);
begin
  Canvas.Brush.Style := bsClear;
  Canvas.Pen.Color := clBlue;
  Canvas.Pen.Width := 1;
  Canvas.Rectangle(80, 80, ClientWidth-80, ClientHeight-80);
end;

...... 



결과는 다음과 같다. 파란 뷰포트 안에 포함되는 직선만 굵고 붉게!! 그려진다.  

 
 
아이~ 아름다워라~

방문자를 위한 마지막 조공짤은 위키백과에서 찾은 이반 서더랜드옹의 사진 

  


그래. 대머리와 흰머리도 그 속에 채워진게 많으면 저렇게 간지가 좔좔 넘치잖아??!!
마누라가 아무리 압박해도 염색따위는 하지 말자!!! (귀찮아서가 아님!!!)
 

MinGW-MSYS 에 GNUStep base 얹기

탐구생활/Objective-C
왜 이 짓을 하냐고??
MinGW를 깔 때 Objective-C 컴파일러도 함께 설치해서 libobjc 가 깔려있는 상태지만, 
심심풀이삼아 Objective-C 코딩을 해보려면 NSObject 없이는 솔직히 아무것도 안되거덩.
하지만 그거 하자고 GNUStep 을 또 설치하기는 그렇잖아. 태국 홍수로 하드값이 비싸져 용량 늘리기도 힘든데...
게다가 GNUStep의 우중충한 GUI는 별로 관심없으니 base만 깔아보자는 이야기.

C:\MinGW 에 MinGW가 설치된 상태에서 C:\MinGW\GNUStep 에 필요한 환경을 구성하는 것이 목표임. 
다운로드 페이지는 여기. 
http://wwwmain.gnustep.org/resources/downloads.php?site=ftp%3A%2F%2Fftp.gnustep.org%2Fpub%2Fgnustep%2F 
이 중에서 GNUstep Base 가 오늘의 장난감이다. 

맨 밑에 Pre-requisites 항목에는 libffi, libxml, libtiff 가 필요하다고 나와있다. 
libtiff야 GUI에 쓰일테니 무시. libxml은 gnustep-base 구성시 --disable-xml 옵션으로 빼버릴 수 있으니 libffi만 설치하면 된다. 

$ mkdir build-gnustep
$  cd build-gnustep

$ wget ftp://sourceware.org/pub/libffi/libffi-3.0.9.tar.gz
$ tar -xzf libffi-3.0.9.tar.gz
$  cd libffi-3.0.9
$ ./configure --prefix=/mingw/GNUStep
$ make
$  make install



C:\MinGW\GNUStep 가 새로 생성되고 여기에 지금 생성된 녀석들이 들어감. 

/etc/profile 을 편집, /mingw/GNUStep/bin 을 PATH에 추가한다.

export PATH="$PATH:/mingw/GNUStep/bin"



셸 재구동 후 계속 진행하자. 재구동이 귀찮으면 wonderman님이 가르쳐주신 다음 방법을 써도 된다. 

$ source /etc/profile




동일한 방식으로 gnustep-make 다운로드 후 빌드. 

$ wget ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-make-2.6.2.tar.gz
$ tar -xzf  gnustep-make-2.6.2.tar.gz
$  cd gnustep-make-2.6.2 
$ ./configure --prefix=/mingw/GNUStep
$ make 
$ make install



 
이제 gnustep-base 다운로드 후 빌드. 구성옵션에 libffi 경로를 지정해준다.
libxml을 설치하지 않았으니 --disable-xml도 잊지 말 것. 

$ wget ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-base-1.24.0.tar.gz 
$  tar -xzf gnustep-base-1.24.0.tar.gz
$ cd gnustep-base-1.24.0
$ ./configure --prefix=/mingw/GNUStep \
--with-ffi-include=/mingw/GNUStep/lib/libffi-3.0.9/include \
--with-ffi-library=/mingw/GNUStep/lib \
--disable-xml
$ make
$  make install


 
끗! 

이제 테스트~ 테스트~~
 Dev C++을 열고 Objective-C용 컴파일 설정을 추가하자.


컴파일러 추가명령 : -fconstant-string-class=NSConstantString
링커 추가명령 : -lobjc -lgnustep-base
실행파일 디렉토리에 : C:\MinGW\GNUStep\bin 추가. 
라이브러리는 : C:\MinGW\GNUStep\lib
헤더는 : C:\MinGW\GNUStep\include



HelloWorld 예제를 만들어 돌려보면



뭐 잘 동작하는군. 




두 줄 요약.
- 굳이 GNUStep 을 따로 설치하지 않아도 단순히 Objective-C 학습용이라면 기존 MinGW 환경에 추가할 수 있다. 
- 하드 용량이 남아돈다면 그냥 GNUStep을 깔자.
 

덤. Dev-CPP에서 Objective-C 공부하기는 예전에 끄적여둔 http://oranke.tistory.com/141 를 참고.
 

MinGW에서 윈32용 SQL CIPHER 빌드 (작성중)

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

sqlite 암호화. SQL CIPHER

탐구생활/Others
sqlite에 암호화 기능이 추가된 SQL CIPHER 라는 물건이 있다. 
http://sqlcipher.net/

암호화에 OpenSSL을 사용해 DB내용을 완전히 인코딩하는 멋진 녀석인데... 
이게 윈도에서 빌드하기가 까다롭다고 악명이 높다. 
http://groups.google.com/group/sqlcipher/browse_thread/thread/55c6296b56bf4533

실제로 제공되는 소스에는 iOS, 리눅스, 심지어는 윈도 CE용 Make파일도 있는데
윈도용은 쏙 빠져있네. 

오래간만에 들러봤더니 아예 윈도용 바이너리를 빌드해서 판매하고 있군. 
개발자 한명당 $150이라 편의성을 생각하면 비싼편은 아니지만
치매 예방겸 한번쯤 도전해 볼 개연성은 충분할 듯~~


shared dll 빌드로 이미 산을 하나 퍼낸 하모씨의 삽을 빌려다 뚝딱뚝딱 static dll 빌드 성공.
기념 스샷 한 방~


CrossKylix 갖고놀기

탐구생활/Delphi
윈도+델파이 환경에서 리눅스용 실행파일을 만들 수 있다는 CrossKylix
http://crosskylix.untergrund.net 

페북의 자칭 컴맹님(타칭 컴퓨터맹주님)이 "70년대 미녀 배우와 지금 데이트하는 느낌" 라고 하셔서 호기심 급발동. 
당연한 이야기지만 Kylix가 필요하다. "Kylix 3 Open Edition" 을 사용해도 괜찮다는군. 
예전 볼랜드의 다운로드 링크는 거의 사라졌는데, 왠일인지 이 링크는 살아있다.
http://download.borlandforum.com/kylix/Kylix3Open/kylix3_open.tar.gz 
더 이상 라이센스 발급은 하지 않지만 CrossKylix와 사용은 가능하다. 
다운받아 적절한 곳에 압축을 풀자.  

crosskylix-110.zip 도 내려받아 압축을 풀고 setup.exe 를 실행시키자. 
설치는 편의상 C:\CrossKylix 에 하자. 




CrossKylix Installer 가 실행되면 아까  kylix3_open.tar.gz 의 압축을 풀어둔 폴더를 선택해준다. 



지원하는 델파이 버전은 6, 7, 8, 2005, 2006이다.
다만 2005 버전 이후로는 콘솔/웹 어플만 만들 수 있고, CossKylix 1.1.0 버전은 델 7에서만 테스트 되었다고 한다. 
델파이 7을 띄우고 CrossKylix가 설치된 폴더 아래 ideplugin 폴더에서 CrossKylix.dpk 패키지를 열어 인스톨 한다.



CrossKylix Options 창이 나타나면 CrossKylix 가 설치된 폴더를 다시 지정하고 OK를 눌러준다. 


 
이것으로 설치는 완료. "Hello World!" 를 출력하는 간단한 예제를 만들어보자.
File->New->Others 에서 Console Application을 선택. 

program Project2;

{$APPTYPE CONSOLE}

uses
  SysUtils;

begin
  WriteLn('Hello World!');
  ReadLn;
end.



델파이에서 F9를 눌러 실행하면 뭐 잘 돌아간다. 



이제 Project -> Build with CrossKylix 메뉴를 눌러보자. 



빌드 출력창에 리눅스 바이너리가 생성되었다는 메시지가 나타나면 성공!



이 바이너리를 리눅스(우분투 10.04 서버 LTS. 커널은 2.6.32)로 옮기고 실행시키면 짜잔~~



"Kylix 3 open edition" 이 GPL 2 로 배포되기 때문에 실행시 GPL 관련 메시지가 출력되기는 하지만, 아무튼 잘 돌아간다.


Kylix Open Edition 에서 콘솔 어플리케이션을 만들 때 나타나는 이 GPL 메시지에 대한 꼼수 하나를 덧붙이면, 이 메시지는 프로젝트 소스 내의 {$APPTYPE CONSOLE} 지시자에 반응한다. 따라서 이 지시자를 다음과 같이 수정하면 더 이상 GPL 메시지가 표시되지 않는다.

program Project2;

{$IFNDEF LINUX}
  {$APPTYPE CONSOLE}
{$ENDIF}

uses
  SysUtils;
                 
begin
  WriteLn('Hello World!');
  ReadLn;
end.



당연한 이야기지만, 이렇게 해도 리눅스에서 콘솔 입출력에는 아무런 문제가 없다. 



CLX 를 이용한 GUI 어플리케이션도 만들어 볼까 했는데... 윈도매니저가 깔린 리눅스 시스템이 주위에 하나도 없네. 
다음 기회에 갖고 놀아보기로 하고 오늘은 이만 취침~~


 
주의:
1. 프로젝트 경로명에 공백문자가 있으면 빌드에 애로사항이 꽃핀다.  
2. 64비트 윈도에서 CrossKylix를 C:\Program Files(x86)... 밑에 깔면 마찬가지로 애로사항이 꽃핀다.