깊이 버퍼 쉐도우 맵이라는거 정말 좋은 그림자 기법입니다.

첫째 구현하기 간단하고.. 둘째 간단함에도 불구 하고 자기그림자까지 표현하는 정교함까지..

그런데 장점이 좋은 만큼 단점도 눈에 확 들어오는데요.

 

1. Z Fighting 에 의한 Moire 발생.

2. 넓은 영역을 커버 할때 나타나는 엄청 거슬리는 깍뚜기(알리아싱)들.

 

그래서 여러방법이 나오는데

PSM (Perspective Shadow map), LisPSM(Light Space Perspective Shadow map), TSM(Trapezoidal Shadow Maps).. 등등등..

그래서 검색을 해보면 별 해괴 망측한 수식들과 그림들이 튀어나옵니다.

뭐 이런 그림들이지요. 저는 당최 모르겠습니다.

대학교에서 4년이나 물리학을 배웠는데도 도데체가 뭔소린지.. 뷰프러스텀 짜부려놓고 저게 무슨 논문이라고.. 지들은 알아보는건지..

남들은 잘정리된 논문이라고 하던데.. 난 머리가 나쁜것인지..

 

그래서 그냥 제가 만들기로 했더랬지요.

먼저 아주 전통적인 깊이버퍼 그림자를 구현 해보면..

이렇게 나옵니다.. 180m X 180m를 그림자 한장으로 덮었더니 이꼴이 났는데요.

그림자의 해상도는 자그마치 2048X2048 짜립니다.

예.. 멀리 있는건 볼만 하지만 앞에 있는 그림자는 정말 못봐주겠더랩니다.

2048이라는 해상도는 다 어따가 팽개친건지..

다음 그림이 어따가 팽개쳤는지 알려줍니다.

개략적으로 그린것이긴 하지만 대충 봐도 낭비가 엄청 심하다는것을 알수 있구요.

2048이면 뭐합니까 앞에서는 40~200 픽셀 정도 밖에 못쓰는데..
..

..

..
그럼 다음 그림을 보면..

오오.. 앞에 그림과 거의 같은 위치에서 보는 것이지만 틀려도 너무 틀립니다.

물론 해상도는 되려 작습니다. 1024X1024를 쓰고 있습니다.

어떻게 이런일이 가능해 버린걸까요?

깊이버퍼를 보면 다음과 같습니다.

역시 대충 그린 그림입니다. 하지만 뭔가 틀리지요?

네. 카메라와 가까운곳은 크게 그려주고 먼곳은 작게 그려주는것. 그게 포인트 입니다.

낭비되는 범위는 비슷하지만 그려지는 화질이 틀린것이지요.

PSM의 공통된 의도는 바로 그겁니다. 가까운곳에는 텍스처의 픽셀을 많이 할당하고 먼곳은 적게 할당한다.

간단하지요?

그런데 그 수식 구할라고 하면 아주 박터집니다. 도데체 뭔말인지도 모르겠고 수식은 뭔 기호가 그렇게 많은지..

물론 위에 제가 직접 사용한 방식은 PSM의 의도만을 가지고 순수하게 제가 그냥 코딩한 것이라서 내세우긴 좀 뭐합니다만....

 

저와 마찬가지로 그냥 PSM의 맛보기라도 하고싶다라는 분들이 있을꺼라 생각하고..

또 제가 알고 있는 이 방법이 의외로 다른 분들에게 도움이 될지도 모른다는 생각에 씁니다.

 

[시작.]

먼저 전통적인 깊이 버퍼 그림자의 순서입니다.

1. 깊이텍스처를 준비한다.

2. 렌더 타겟을 깊이텍스처로 전환

3. 광원의 위치로 View행렬을 세팅(단. Up Direction은 카메라가 보는 방향)

4. 광원의 FOV대로 Projection행렬 세팅

5. 깊이값을 렌더링

6. 렌더 타겟을 다시 주버퍼로 전환

7.  카메라의 위치로 View행렬 세팅

8. 카메라의 FOV대로 Projection 행렬 세팅

9. 3과4에서 쓰인 행렬식을 토대로 깊이 버퍼를 가져와 비교하여 그림자 채색.

- 2로 다시 반복.

...

이건데요.

제가 사용한 방법은 위의 전통적인 방법과 순서는 전혀 안틀립니다.

아니.. 아예 똑같습니다.

하지만 코드상에서 틀린부분은 바로 4번(광원의 FOV대로 Projection행렬 세팅) 인데요.

일단 똑깉이 구해놓고 행렬하나를 더 만들어서 곱한후에 세팅합니다.

그 행렬이 뭔고 하면 그림을 위에 보시는 것처럼 짜부러 뜨리는 행렬입니다. (아래는 넓게 위쪽은 좁게)

 

어떻게?

바로 우리가 알고 있는 카메라 프로젝션 행렬을 곱해주는 겁니다. 근데 그냥 곱하면 안되구요.

..

..

프로젝션 행렬을 통하고 나면 좌우 위아래는 -1~1 로 고정되구요 앞뒤는 0~1로 고정됩니다.

원래 프로젝션 행렬은 앞은 크게 뒤는 작게 그릴수 있게 해주는 것입니다.

-Z 축으로 왜곡

 

그런데 우리는 위는 좁게 아래는 크게 만들어줘야 합니다.

-Y 축으로 왜곡

감이 오시지요?

우리가 가장 많이 쓰는 프로젝션 행렬은 Dx에서 다음과 같이 정의 됩니다.

2*zn/w  0          0                   0
0           2*zn/h  0                   0
0           0          zf/(zf-zn)       1
0           0          zn*zf/(zn-zf)  0

w는 뷰 프러스텀의 폭

h는 뷰 프러스텀의 높이

zn은 근단면의 거리

zf는 원단면의 거리입니다.

앞에서 말한것과 같이 Z축 왜곡이니까 Y축 왜곡으로 바꿔볼잡시면

2*zn/w  0                     0            0
0           zf/(zf-zn)        0             1
0           0                    2*zn/h     0
0           zn*zf/(zn-zf)   0             0

이렇게 됩니다.

이걸 4번에서 구한 프로젝션 행렬에 곱해준다음 적용하는 겁니다.

간단하죠?

하다못해 셰이더 코드는 100% 똑같습니다.

 

w는 원래 같으면 다른 값을 쓰겠지만 1 이하가 됩니다. 이미 프로젝션 행렬을 통한 다음엔 최대 좌우 폭은 -1~1 2가 되기때문이지요.

w와 h는 근단면의 폭과 높이이기 때문에 2보다는 작아지니까요.

그리고 zn과 zf는 모두 0~1 사이값을 갖게 됩니다. zf는 1보다 더 커져도 큰 무리는 없지만 zn은 정말 세밀하게 조절 해야됩니다.

zn값 하나 때문에 퀄리티가 오르락 내리락 합니다. 너무 작으면 깍뚜기가 나오고 너무크면 가까운곳은 그림자가 사라집니다.

테스트를 거쳐야 나오는 값이라 뭐라 포스팅 할수가 없네요.

 

이 방법도 단점은 있습니다.

바로 라이트의 방향과 카메라의 방향의 각에 따라 퀄리티가 들쭉 날쭉 합니다.

이는 좀더 그림자맵을 그릴때 방향을 잘 잡아야 하는 문제니까 금방 잡힐꺼 같습니다.

 

최종적으로 모든게 적용된 스크린샷을 올려 볼께욤.










갑자기 만든거 치고는 괜찮아보여 혼자 만족중입니다. 하하핫!

/////////////////////////////////////////////////////////////////////////////////
float4x4 mLightSpaceWvp;
float4x4 mWvp;
float4x4 mObjWorld;


float4 LightAmbient = {0.1, 0.1, 0.1, 1};
float4 LightDiffuse = {1, 1, 1, 1};
float4 LightDirInv;
float4 LightPos;

/////////////////////////////////////////////////////////////////////////////////
#include "../common/afx_texture_macro.fxh"

TEXTURE_2D( txrDepthMap, sampDepthMap, Clamp, NONE, LINEAR, LINEAR)
TEXTURE_2D( txrTerrain, sampTerrain, Wrap, LINEAR, LINEAR, LINEAR)

/////////////////////////////////////////////////////////////////////////////////
// Render Depth from Light
struct vso_RenderDepthMap
{
float4 HPosition : POSITION;
float4 LightSpacePos : TEXCOORD0;
};

vso_RenderDepthMap VS_RenderDepthMap(float3 pos : POSITION)
{
vso_RenderDepthMap o = (vso_RenderDepthMap)0;

float4 Pos = {pos, 1};
o.HPosition = mul(Pos, mLightSpaceWvp); // 라이트위치로부터의 깊이값을 저장하기 위해.
// 실제 필요한 건 (깊이맵을 그리기위한)z 값.
o.LightSpacePos = o.HPosition / o.HPosition.w; // 프로젝션을 거치면서 값이 w로 정규화되지 않는 경우를 위해 표준회
return o;
}
float4 PS_RenderDepthMap(float4 LightSpacePos : TEXCOORD0 ) : COLOR0
{
//LightSpacePos /= LightSpacePos.w; // 픽셀셰이더에서 정규화하면 좀더 세밀하겠지만 별 차이없음.
return (float4)LightSpacePos.z; // 깊이맵 그리기. 캐릭터가 이동할때마다 재계산되어 깊이맵도 변함.
// 모든 오브젝트에 그림자가 생김.
}


/////////////////////////////////////////////////////////////////////////////////
// Render Terrain
struct vso_RenderTerrain
{
float4 HPosition : POSITION;
float2 t : TEXCOORD0;
float4 LightSpacePos: TEXCOORD1;
};
vso_RenderTerrain VS_RenderTerrain(float3 pos:POSITION, float2 t:TEXCOORD0)
{
vso_RenderTerrain o = (vso_RenderTerrain)0;

float4 Pos = float4(pos,1);
o.HPosition = mul( Pos, mWvp);
o.t = t;

float4 PosLWvp = mul( float4(pos,1), mLightSpaceWvp);

PosLWvp /= PosLWvp.w; // for range ( -1 ~ 1 )
o.LightSpacePos.x = PosLWvp.x*0.5 + 0.5;
o.LightSpacePos.y = -PosLWvp.y*0.5 + 0.5;
o.LightSpacePos.z = PosLWvp.z;
o.LightSpacePos.w = PosLWvp.w;

return o;
}
float4 PS_RenderTerrain(float2 t:TEXCOORD0, float4 LightSpacePos:TEXCOORD1) : COLOR0
{
float4 addc = (float4)0.1;
float4 col1 = tex2D(sampTerrain, t)+addc;
float4 col2;
// 깊이값 비교.
// 0.03 는 오차보정용.
if( LightSpacePos.z-0.03 > tex2D(sampDepthMap, LightSpacePos.xy).z) col2 = (float4)0.3;
else col2 = (float4)1.0;

return col1*col2;
}
// ///////////////////////////////////////////////////////////////////////////////
struct vso_Object
{
float4 HPosition : POSITION;
float4 Normal : TEXCOORD0;
};
vso_Object VS_RenderObj( float3 pos : POSITION, float3 Normal:NORMAL )
{
vso_Object o = (vso_Object)0;
o.HPosition = mul(float4(pos, 1), mWvp );
o.Normal = float4( mul(Normal,(float3x3)mObjWorld), 0);
return o;
}

float4 PS_RenderObj(float4 Normal : TEXCOORD0) : COLOR0
{
float4 o = (float4)0;
float4 N = normalize(Normal);
float4 L = normalize(LightDirInv);
float ldn = max( dot(N, L), 0 );
o = LightAmbient + LightDiffuse * ldn ;
return o;
}
/////////////////////////////////////////////////////////////////////////////////
technique DepthShadow
{
// Render Shadow to Texture
pass P0
{
VertexShader = compile vs_2_0 VS_RenderDepthMap();
PixelShader = compile ps_2_0 PS_RenderDepthMap();
}
pass P1
{
VertexShader = compile vs_2_0 VS_RenderTerrain();
PixelShader = compile ps_2_0 PS_RenderTerrain();
}
pass P2
{
VertexShader = compile vs_2_0 VS_RenderObj();
PixelShader = compile ps_2_0 PS_RenderObj();
}
}

원근투영은 물체의 정점이 시점으로 수렴되지만, 직교투영은 그렇지 않죠. 

따라서 직교투영에서 피킹에 사용할 반직선은 노멀라이즈한 시선방향벡터를 투영평면상의 마우스 위치만큼 옮기면 간단하게 구할 수 있습니다. 

OpenGL의 경우 직교투영행렬의 [0,0], [1,1] 은 다음과 같이 생겨먹었습니다. {D3D도 마찬가지겠군요.} 

OrthoMatrix[0,0] = 2/(Right-Left) 
OrthoMatrix[1,1] = 2/(Top-Bottom) 

투영평면에서 뷰볼륨에 정규화된 마우스의 위치는 다음과 같이 구할 수 있죠. 

aX = (마우스 X * 2.0 / 화면 너비)-1.0 
aY = -(마우스 Y * 2.0 / 화면 높이)-1.0 

정규화되지 않은 마우스 위치는 OrthoMatrix[0,0], OrthoMatrix[1,1]을 aX,aY에 각각 곱해주시면 되겠죠?? 

bX = aX * OrthoMatrix[0,0] 
bY = aY * OrthoMatrix[1,1] 

이렇게 얻어진 bX와 bY를 이용해 반직선의 시점과 종점을 만들어 보죠. 

시점 = (bX, bY, 0) 
종점 = (bX, bY, -1) {D3D에서는 (bX, bY, 1)이 되겠죠??} 

이 정점을 뷰변환행렬의 역행렬로 변환시키면 세계공간에서 피킹에 사용할 반직선을 얻을 수 있습니다. 

뷰좌표계의 U, V, N 벡터를 알고 있다면... U벡터를 bX만큼 스케일, V벡터를 bY만큼 스케일 해서 얻어진 좌표에 카메라 시점벡터를 더한 좌표를 원점으로, 다시 여기에 N벡터를 뺀 좌표를 종점으로 하는 반직선을 만들어 쓰시면 되죠. {D3D에서는 N벡터를 더해야... ^^} 

더 간단하게 해결하시려면 피킹하려는 물체와 시점의 거리를 아주 길게 만드세요. 그러면 직교투영에서의 실제 반직선과 비슷하게 맞아들어가기 때문에 그럭저럭 사용할 수는 있습니다. 물론 현재 직교투영으로 보여지는 이미지와 같은 크기로 보여지도록 반직선을 계산할 원근투영의 Fov를 잘 설정하셔야 하지만요~ ^^; 

Fov = arctan( OrthoMatrix[0,0] / 카메라 거리 ) 

제가 만들어 쓰는 맵툴도 직교투영과 원근투영을 바꿀 수 있지만 어차피 내부에서만 쓸 거라... 맵작업 하시는 분들 께 직교투영할 때는 카메라를 멀리 빼서 사용하라고 압력을 넣고 있습죠~~ ^^;; (음... 이 글을 보면 안되는데~~ 흐흐~~) 

typeid, type_info 

 

필요헤더 : typeinfo.h 또는 typeinfo( std 이름공간 사용, 밑에서는 생략 )

 

두 타입이 같거나 같지 않음을 비교, 또는 타입을 문자열로 바꿔줄 때 typeid와 type_info를 사용할 수 있다.

 

이 두 녀석은 '실행시간' 에 행해진다.

 

먼저 두 타입을 비교하기 위해서는 typeid( 타입 ) 을 사용한다. typeid 는 지정한 타입에 특정지어지는 type_info 의

 

참조값을 반환한다. type_info 는 ==, != 연산자를 가지고 있어서 이 연산자를 통해 비교할 수 있다. 즉,

 

if( typeid( int ) != typeid( char ) )

    printf( "같지 않은데요.\n" );

 

위와 같이 사용할 수 있다.

 

만약 type_info 의 참조값을 어디엔가 유지하고 싶다면 다음을 주의하자. 타입에 대한 참조값은 항상 일정한 것은 아니다. 이것은

 

&typeid( int ) == &typeid( int ) 가 항상 성립한다고 볼 수 없다는 것이다. 참조값을 유지한다면 참조에 대한 포인터를 유지하고

 

비교시 포인터의 값을 비교해야 된다.

 

예 :

    const type_info *int_type = &typeid( int );

    const type_info *char_type = &typeid( char );

 

    if( *int_type != *char_type )

        printf( "같지 않은데요.\n" );

 

 

다음으로 타입에 대한 문자열을 얻는 방법인데, 이것은 type_info 객체를 이용하는 것이다.

 

type_info 클래스는 생성자와 복사대입연산자가 private 로 설정되어 있어서 객체를 직접 생성하는 것은 불가능하고

 

typeid 로부터 얻어내야 한다. type_info 멤버함수에는 name() 함수가 있는데 이것은 자료형에 대한 const char* 타입의 문자열을

 

반환해준다. 즉,

 

printf( "이 타입은 %s타입이네요.\n", typeid( int ).name() );

 

과 같이 작성할 수 있다. 하지만 타입에 대한 문자열 변환은 표준에 없는 내용이어서 그 결과를 예측하기는 어렵다.

 

VC++6.0 에서는 다음과 같은 형태로 출력해준다.

 

class A

{

...

};

 

printf( "이 타입은 %s타입이네요.\n", typeid( A ).name() );

 

결과 : 이 타입은 class A타입이네요.

포인터? 생각만 하면 머리가 아파오는 녀석이죠..C 언어로 생계를 유지해온 저로서도 복잡한 포인터를 보면 피하고 싶습니다..이런 포인터 인데 함수포인터라니? 이건 뭐 대마왕급이군요...

그런데 이런 대마왕도 상황에 따라, 그리고 고급프로그래밍을 위해서 간간히 써주어야만 하는 돌발상황이 발생하곤 합니다..어쩔수 없는 운명이죠..TT

피할수 없다면 한번 부딪혀 보는 건 어떨까요? 기초 개념만 잡으면 너무 쉽다(?)고 느끼실 겁니다..자, 그럼 기본부터 한번 시작해 보겠습니다..

우선 다음과 같은 코드를 보죠..아주 단순한 코드입니다..

-----------------------------------------------------------------------------------

main()

{

int a = 10;

int* b = &a;

...

...

}

-----------------------------------------------------------------------------------

변수 a 에 10을 대입하고 변수 b 에는 a 의 주소값을 저장합니다. 그럼 여기서 질문하나 해볼까요? 변수 a 와 b 는 어디에 존재할까요? CPU ? 하드디스크 ? 아님 메모리?

모두 아시겠지만 변수는 메모리, 정확히 말하면 RAM 에 존재합니다. 아래 그림에 간략히 나타내보았습니다.

그럼 다음 코드를 한번 보겠습니다..좀전보다 조금 어렵습니다..(???)

-----------------------------------------------------------------------------------

void func(int x)

{

printf("x = %d\n",x);

}

main()

{

int a = 10;

int* b = &a;

func(a);

}

-----------------------------------------------------------------------------------

이번에는 함수 func 라는 놈이 등장했네요..여기서 다시 질문하나 할께요..변수는 RAM 에 저장되는데 그럼 함수 func 는 어디에 저장될까요? 또 main 도 특별한 함수죠..즉, 시작할때 제일 먼저 불리는 함수..이런 함수들은 도대체 어디에 존재하는걸까요????

함수는 변수와 마찬가지로 RAM 에 저장되어 있습니다...왜냐면 실행되는 모든 프로그램은 RAM 에 저장되어야 하는데 함수도 프로그램의 일부이니까요..그림으로 나타내 볼까요..

위 그림처럼 RAM 의 어딘가에 func 라는 함수부분이 저장되어 있습니다. RAM에는 언제나 주소가 있죠? 예를 들어 변수 a 는 &a 라는 주소에 있고 변수 b 는 &b 라는 곳에 있습니다. 그럼 func 는 어디에 있을까요?

주소를 나타내려면 & 를 붙인다고 했으니 &func? 아님 임의의 다른곳??

C 언어에서 함수가 저장되어 있는 주소값은 바로 함수 이름입니다. 즉, 함수이름이 함수가 저장되어 있는 첫번째 번지를 나타냅니다. (마치 배열과 비슷합니다. 배열 이름이 배열의 시작주소를 나타내는 것처럼요..) 그래서 func 라는 함수가 저장되어 있는 번지는 func 이고, main 함수가 저장되어 있는 번지는 main 입니다..(많이 헛갈리시죠..예전에 제가 그랬습니다...)

이제 함수가 저장되어 있는 주소값을 알았으니 그 값을 저장할수 있는 변수도 있어야겠죠..이렇듯 함수가 저장되어 있는 주소값을 저장할수 있는 변수를 '함수포인터' 라고 합니다. 즉, 함수포인터는 함수의 이름을 가지는 변수입니다..

그럼 함수이름을 저장할수 있는 변수는 어떻게 선언하고 초기화 할까요? 다음의 소스를 한번 보죠..

-----------------------------------------------------------------------------------

void func(int x)

{

printf("x = %d\n",x);

}

main()

{

int a = 10;

int* b = &a;

void (*test)(int);

test = func;

func(a);

}

-----------------------------------------------------------------------------------

우선 'void (*test)(int);' 부분입니다. 해석을 해보면 test 라는 변수는 인자로 한개의 정수(-int-) 를 가지고 반환형이 없는 (-void-) 함수를 가리키는 포인터이다 입니다. 이렇게 선언된 test 라는 변수에 func, 즉 func 가 저장되어 있는 주소값(-함수이름-)을 대입하는 곳이 소스에서 'test=func' 입니다. (func 함수가 인자로 정수한개를 받고 반환형은 없죠..확인해보세요)

그림을 통해 이해해보죠..

이제 마지막으로 함수포인터를 통해 함수가 어떻게 호출되는지 알아보겠습니다..소스코드에서 변수 포인터 b 를 통해 a 를 접근하고자 할때 '*b' 라고 기술했습니다. 그럼 함수포인터는 ? 함수포인터도 변수 이므로 *b 처럼 * 연산자를 이용해 함수를 접근할수 있습니다.다음의 코드를 보면서 결과값이 같다는 것을 확인해보시기 바랍니다...코드에서 (*test)(a) 의 해석은 다음과 같다고 할수 있습니다. 'test 라는 변수가 가리키는 곳에다 a 값을 넘겨준다'

-----------------------------------------------------------------------------------

void func(int x)

{

printf("x = %d\n",x);

}

main()

{

int a = 10;

int* b = &a;

void (*test)(int);

test = func;

func(a);

(*test)(a);

}

-----------------------------------------------------------------------------------

어떠신가요? 함수포인터라고 무시무시한 이름을 가지고 있지만 막상 별것 없죠..이런 함수포인터는 속도가 중요시되거나 그래픽관련 프로그래밍을 구현하고자 할때 가끔 쓰입니다. 또한 커널 프로그래밍을 할때도요..그러나 기본에 충실하시면 어렵지 않게 이해 할수 있을거라 믿습니다..

(이제 함수 포인터는 대마왕의 자리를 내주고 쓸쓸히 은퇴해야겠네요..)

다음의 예제를 한번 풀어보시고 이해해 보시기 바랍니다...(다음주제는 네트웍프로그래밍을 진행해 볼까 합니다..많은 응원부탁 ^^..)

언제나 즐거운 시간만 되세요...

[1] int (*fPtr1)(int); [이해난이도 : 하]

[2] int (*fPtr2)(int, int); [이해난이도 : 하]

[3] void (*signal(int, void(*handler)(int)) ) (int); [이해난이도 : 최상]

VS 6.0등에서 만든 프로젝트를 VS 2005 에서 빌드시 나타나는 링크 에러

원인  VS 2005 에서는 싱글 쓰레드용 라이브러리(libc, libcd)가 더이상 지원되지 않음.

런타임 라이브러리 종류별 스레드 타입의 예

LIBCI.lib : Single-thread, Static Link /ML
LIBCIMT.lib : Multithreaded, Static Lik /MT
MSVCIRT.lib : Multithreaded, dynamic link /MD

해결책

프로젝트 속성 -> 구성 속성 -> 링커 -> 입력 -> 특정 라이브러리 무시에 libcd.lib를 포함시키면 해결된다. 

프렌드 개념 잡기!!!

 

1. 프렌드 지정은 단방향이다.

 

내가 소영이한테 친구를 지정했어!!..
소영이는 나를 친구로 지정안했어!!..
난 소영이를 친구로 생각하니까 소영이가 요청하면 다 받아줘!!..
소영이는 나를 친구로 생각안하니까 내가 요청하는걸 안받아줘!..
이렇게 비참하게 생각하면 딱 와닿음.

 

2. 프렌드 지정은 전이되지 않는다. (EX 친구의 친구따위는 없다는거다)

3. 복수의 대상에 대해 동시에 프렌드 지정이 가능하다.

 EX ) A는 B랑 C랑 친구하고 싶어요!! +_+ 그럼

         B,C야 친구하자 는 안되고

         B야 친구하자;

         C야 친구하자;

라고 해야된다.

 

4. 프렌드관계는 상속되지 않는다. (엄마의 친구가 내 친구가 될 순 없자나? -_-..)

하지만, 여기서 예외가 있다.

나에겐 유니크한 부분과 엄마에게 유전받은 똑같은 부분이 있다.

그런 똑같은 부분에선 엄마의 친구와 공감대를 찾을 수 있는것처럼, 사용할수 있지만

나의 유니크한 부분은 안된다는거지.

 

 

 

아래의 예제를 보자.

 

#include <Turboc.h>

 

class Time;

class Date

{

private:

     int year,month,day;

public:

     Date(int y,int m,int d) { year=y;month=m;day=d; }

     void OutToday(Time &t);

};

 

class Time

{

     friend void Date::OutToday(Time &t);

        (time t) < 값 변경 불가능, 복사만.

        (tima &t) 만약 size가 200byte면 그 200byte와 값을 그대로 전달받음

                         받은 값을 수정 가능

        (time *t) 하면 주소값만 가지고 오는것이므로 4byte가 되서 size가 적절해짐

                        const time *t를 한다면? 참조만 가능해진다.

         그러니까 값을 변경해야된다거나 그대로 전달받아야 할때는 &가 제일 좋다.

private:

     int hour,min,sec;

public:

     Time(int h,int m,int s) { hour=h;min=m;sec=s; }

};

 

void Date::OutToday(Time &t)

{

     printf("오늘은 %d년 %d월 %d일이며 지금 시간은 %d:%d:%d입니다.\n",

          year,month,day,t.hour,t.min,t.sec);

}

 

void main()

{

     Date D(2005,01,02);

     Time T(12,34,56);

     D.OutToday(T);

}

 
 
 
 
 
프렌드 멤버 함수를 알아보자!!
 

class Some

{

     ....

     friend void Any::func(Some &S);

};

 

이렇게 하면 Any 클레스의 func멤버만 Some에 엑세스가 가능해지지.
 

프렌드 멤버 함수를 알아보자!!endMem

#include <Turboc.h>

 

class Time;

class Date

{

private:

     int year,month,day;

public:

     Date(int y,int m,int d) { year=y;month=m;day=d; }

     void OutToday(Time &t);

};

 

class Time

{

     friend void Date::OutToday(Time &t); // Date의 OutToday만 Time &t와 친구먹는다.

private:

     int hour,min,sec;

public:

     Time(int h,int m,int s) { hour=h;min=m;sec=s; }

};

 

void Date::OutToday(Time &t)  // Date의 outToday에서 Time &t를 참조한다.

{

     printf("오늘은 %d년 %d월 %d일이며 지금 시간은 %d:%d:%d입니다.\n",

          year,month,day,t.hour,t.min,t.sec);

}

 

void main()

{

     Date D(2005,01,02);

     Time T(12,34,56);

     D.OutToday(T);   // Data에 OutToady에서만 Time클레스를 참조할수있다

}

 

기본적인 파일 관리함수

 

int _access( const char *path, int mode );

 

파일을 엑세스 하기 전에 파일이 실제로 존재하는지 확인하는 함수입니다.

 

path : 조사할 파일의 경로

 

mode: 조사할 상태 지정 ( 0 은 존재, 2 는 쓰기, 4 는 읽기 )

 

return : 요청한 허가 상태를 가지면 0을 리턴, 그렇지 않으면 -1을 리턴

 

 

 

int remove( const char *path );

int _unlink( const char *filename );

 

위의 두 함수는 파일을 삭제 합니다.

 

path : 삭제하고자 하는 파일의 경로만 지정

 

 

 

int rename( const char *oldname, const char *newname );

 

이 함수는 파일의 이름을 변경합니다.

 

oldname : 변경하고자 하는 파일의 이름

 

newname : 새로 설정할 파일의 이름

 

 

 

int _chmod( const char *filename, int pmode );

 

파일의 속성을 변경 합니다.

 

pmode : _S_IWRITE  ,  _S_IREAD  둘중 하나를 주거나 둘다 줄수도 있음

 

 

파일검색 함수

 

long _findfirst( char *filespec, struct _finddata_t *fileinfo );

 

위의 함수로 첫번째 파일 검색 합니다.

 

int _findnext( long handle, struct _finddata_t *fileinfo );

 

위의 함수로 조건이 일치하는 다음 파일을 찾을수 있습니다.

 

int _findclose( long handle );

 

위의 함수로 모든 검색이 완료된 후 검색 핸들을 닫아 줍니다.

 

 

 

디렉토리 관리 함수

 

int _chdir( const char *dirname );

 

현제 작업중인 디렉토리의 경로 변경

 

char *_getcwd( char *buffer, int maxlen );

 

현재 작업 디렉토리를 조사

 

int _mkdir( const char *dirname );

 

디렉토리 생성

 

int _rmdir( const char *dirname );

 

디렉토리 제거

 

void _splitpath( const char *path, char *drive, char *dir, char *fname, char *ext );

 

경로 관리 함수로서  4개의 구분자로 분리한다.

 

void _makepath( char *path, const char *drive, const char *dir, const char *fname

                              , const char *ext );

 

경로 관리 함수로서 분리되어 있는 구분자를 합친다.

 

 

 

 

디스크 관리 함수

 

int _getdrive( void );

 

int _chdrive( int drive );

 

위의 두 함수는 작업 드라이브를 조사하거나 변경한다.

ShellExecute(NULL, "open", http://naver.com, "", "", SW_SHOW );

내장 되어 있는 기본 웹 브라우저를 뜨우는 방법

ShellExecute(NULL, "open",

"C:\\Program Files\\InternetExplorer\\IExplorer.exe", 

"http://naver.com",
"",
SW_SHOW
);

인터넷 익스플로러를 지정하여 띄우는 방법


플라이웨이트 패턴 (Fly Weight)

동일한 종류의 객체 끼리는 중복된 정보를 가지게 될 가능성이 높습니다. 그래서 같은 종류의 객체끼리 중복되는 정보를 공유하는 것이 메모리 관리 측면이나, 중복을 제거하는 측면이나 이롭습니다.

중복되는 정보를 공유 하는 방법을 플라이 웨이트 패턴이라고 합니다.



class CImageData

{

        int m_nIdx;

        int m_nWidthm_nHeight;

        char *m_pData;

public:

        char *GetData(){return m_pData;}

        int GetWidth(){return m_nWidth;}

        int GetHeight(){return m_nHeight;}

};

 

class CImagePool //CImageData의 객체 소유권은 CImagePool에 있.

{

        std::map<intCImageData *>m_mapImageData//여러이미지데이터를담고있는map이다.

public:

        CImageData *GetImageData(int a_nIdx); //m_mapImageData에서해당idx를가진CImageData*를리턴한다.

};

 

class CMenu

{

        CImageData *m_pcImageData;

public:

        CMenu();

        ~CMenu(){} //여기서m_pcImageDatadelete 하면큰일난다.

 

        void SetImageData(CImageData *a_pcImageData){m_pcImageData = a_pcImageData;}

        void Draw()

        {

               //m_pcImageData의픽셀데이터를통해서화면에메뉴이미지데이터를그린다.

        }

};

 
팝업 메뉴마다 공유 될 수 있는 이미지 데이터를 메뉴 객체마다 따로 들고 있는 것은 메모리상의 중복이 생깁니다. 같은 이미지 데이터를 사용하는 메뉴 객체가 CImageData의 정보를 링크해 사용 한다면CImageData의 관리도 한 곳에서 이뤄지는 
잇점과, 위에서 말한 메모리 상의 중복도 제거할 수 있게 되는 것입니다.

 

 

class CQuestInfo

{

        int m_nQuestNo//퀘스트식별번호

        char m_szTItle[32]; //퀘스트이름

        char m_szDesc[512]; //퀘스트설명

        int m_nQuestMissionNo//퀘스트목표에할당된번호

        int m_nCompensateNo//퀘스트보상번호

        int m_nRequireProgressQuantity//목표 달성을 위해 필요한 진행량
        //
기타등등..

};

 

class CQuestInfoPool //CQuestInfo의객체소유권은CQuestInfoPool에게있다

{

        std::map<intCQuestInfo *>m_mapQuestInfo//CQuestInfo 데이터를담고있는map이다.

public:

        CQuestInfo *GetQuestInfo(int a_nQuestNo); //m_mapQuestInfo에서해당QuestNo를가진CQuestInfo*를리턴한다.

};

 

class CQuestData

{

        int m_nIdx//퀘스트고유번호

        int m_nProgressQuantity//진행량

        CQuestInfo *m_pcQuestInfo;

public:

        CQuestData();

        ~CQuestData(){} //여기서m_pcQuestInfodelete 하면큰일난다.

 

        void SetQuestInfo(CQuestInfo *a_pcQuestInfo){m_pcQuestInfo =a_pcQuestInfo;}

};

 
퀘스트의 경우에도 퀘스트의 종류에 따라 퀘스트 이름, 설명, 보상번호, 목표 번호, 목표 달성량 같은 것은 고정된 정보이고, 퀘스트의 고유 번호, 진행량과 같은 정보는 현재 진행중인 퀘스트마다 다르다. 

이렇게 고정된 정보와 변할 수 있는 정보를 나누고, 고정된 정보를 묶고 링크해서 사용함으로써 플라이 웨이트 패턴을 구현할 수 있습니다.

+ Recent posts