세 점(기준점, 점1, 점2)이 있을 때 atan()함수를 1번만 써서 사잇각을 구하는 방법이다.

원리는 간단하다.


아래와 같이 한 각과 기준각이 이루는 각도를 α, 나머지 각을 β라 한다.



그러면 θ = α - β 라 할 수 있다.


tanθ는 다음과 같이 나타낼 수 있으므로 sinθ와 cosθ를 구하여 atan2의 각 매개변수로 넣으면 각을 알 수 있을 것이다.


먼저 sinθ는 삼각함수의 덧셈정리에 의해 이렇게 전개할 수 있다.


위 식을 처음에 주어진 x분과 y성분으로 나타낼 수 있으므로,

최종적으로는 이렇게 쓸 수 있다.




코사인세타도 같은 방법으로 전개한다.


공통항인 1/l1l2를 소거하면 아래와 같은 표현식을 얻을 수 있다.



angle = atan2(y1x2-x1y2, x1x2+y1y2);




atan2 함수가 아닌 atan함수를 사용한다면 atan(sin값/cos값) 형태로 사용하면 될 것이다.

→ angle = atan(y1x2-x1y2 / x1x2+y1y2);



▼ 컴퓨터 프로그램으로 작성하여 구동한 화면.





펄린노이즈(perlin noise)를 연습하다가 나온 실패작(?).

아래에 코드(ActionScript3.0)가 있지만 펄린노이즈 코드는 아닙니다.

그래도 그냥 올려봅니다...







package
{
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.geom.Rectangle;
    import flash.geom.Vector3D;
    import flash.utils.setInterval;
   
   
    public class Main extends Sprite
    {
       
        public function Main():void
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
       
        private function init(e:Event = null):void
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            // entry point
           
   
            var bd:BitmapData = new BitmapData(500, 500, false);
            var bmp:Bitmap = new Bitmap(bd);   
            bmp.x = bmp.y = 50;
            addChild(bmp);
           
            var vertices:Vector.<Vector.<Vector3D>> = new Vector.<Vector.<Vector3D>>();
            for (var i:int = 0; i < 10; i++) {
                vertices[i] = new Vector.<Vector3D>();
                for (var j:int = 0; j < 10; j++) {
                    vertices[i][j] = new Vector3D(uRand(), uRand());
                    trace(i, j);
                    if(i>0 && j>0)
                        noise50(bd, i*50,j*50,vertices[i-1][j-1], vertices[i][j-1], vertices[i-1][j], vertices[i][j]);
                }
            }
               

           
           
       
        }
       
        private function uRand():Number
        {
            return Math.random()*2-1;
        }
       
        private function noise50(bd:BitmapData, destX:int, destY:int, gx0y0:Vector3D, gx1y0:Vector3D, gx0y1:Vector3D, gx1y1:Vector3D):void
        {
            var x0:Number = 0;
            var y0:Number = 0;
            var x1:Number = 1;
            var y1:Number = 1;   

           
            for (var yi:Number = 0.00; yi < 1; yi+=0.02) {
                for (var xi:Number = 0.00; xi < 1; xi+=0.02) {
                    var s:Number = gx0y0.dotProduct(new Vector3D(xi-x0, yi-y0));
                    var t:Number = gx1y0.dotProduct(new Vector3D(xi-x1, yi-y0));
                    var u:Number = gx0y1.dotProduct(new Vector3D(xi-x0, yi-y1));
                    var v:Number = gx1y1.dotProduct(new Vector3D(xi-x1, yi-y1));
                   
                    var sxi:Number = 3*(xi-x0)*(xi-x0) - 2*(xi-x0)*(xi-x0)*(xi-x0);
                    var syi:Number = 3*(yi-y0)*(yi-y0) - 2*(yi-y0)*(yi-y0)*(yi-y0);                   
                    var a:Number = s+sxi*(t-s);
                    var b:Number = u+sxi*(v-u);                   
                    var zi:Number = a+syi*(b-a);
                   
                    bd.fillRect(new Rectangle(destX+xi*50,destY+yi*50,1,1),0x010203*int((zi+0.5)*4095));
                }
            }
        }
       
    }
   
}



GetNext()는 pos값을 다음 값으로 만들기 때문에

먼저 임시변수를 만들어 현재 pos값을 저장해두고 그 임시변수 위치에 해당하는 요소를 지워야 한다.


POSITION pos = list.GetHeadPosition();

POSITION posTemp;

while(pos != NULL) {

    posTemp = pos;

    CMyType myType = list.GetNext(pos);

    if(....CMyType이 요소삭제조건일 때) {

       list.RemoveAt(posTemp);

    }

}



POSITOIN타입이

typedef __POSITION* POSITION;

와 같이 정의되어있기 때문에 getNext()에서 pos값을 변경할 수 있는것이다.

우선 windows.h 헤더를 포함해야 한다.

BOOL gotoXY(int x,int y)
{
    COORD pos;   //short 타입의 X, Y 속성이 들어 있는 구조체이다.
    pos.X=x;
    pos.Y=y;
    return SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),pos);
}




보통 set메서드가 있으면 get메서드가 있기 마련이지만
커서위치를 얻으려면 GetConsoleCursorPosition같은게 없어서 좀 다른방법을 써야 한다.
여기서는 GetConsoleScreenBufferInfo()함수를 쓰는데

buf.dwCursorPosition역시 COORD 타입이다.

COORD getXY()
{
    COORD pos;
    CONSOLE_SCREEN_BUFFER_INFO buf;
    GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),&buf);
    pos.X = buf.dwCursorPosition.X;
    pos.Y = buf.dwCursorPosition.Y;
    return pos;
}



'ㄱ-힣'과 한글만 입력받게 하기


한글만 허용하도록(혹은 한글만 제한하는) 문자열을 검사할 때

ㄱ-힣 이라고 범위를 지정하는 경우를 볼 수 있는데

ㄱ는 유니코드 코드포인트가 12593,  '가'는 44032로 상당히 멀리 떨어져 있기 때문에

그 사이에 있는 문자들도 포함하게 되어버린다.


만약 다음과 같은 코드로 한글만 허용토록 했다면(Java),

if(str.matches("^[ㄱ-힣]*$"))

유니코드에서 코드포인트가 저 범위 안에 들어 있는 '誤'같은 문자까지 입력이 가능하게 된다.





따라서 자음/모음만 낱으로 있는 부분과 가-힣 부분을 따로 써야 한다.

 ^[ㄱ-ㅎㅏ-ㅣ가-힣]*$

혹은 완전한 글자만 허용하려면

 ^[가-힣]*$

으로 정규식을 쓰면 된다.



UTF-8과의 인코딩 문제 등으로 안 될 경우에는,

^[\u3131-\u318E\uAC00-\uD7A3]*$

(ㄱ-ㆎ 가-힣)

(ㆎ는 아래애)



\u1100-\u11f9 범위의 조합형 자모도 있지만 이 포스팅에서는 생략.






※사용예시 (프로그래밍 언어에 따라 정규식을 /와 /로 묶어서 표현하는 경우가 있음)


*Java


*AS3.0





결론은 ㄱ-힣 범위를 쓰면 의도와 다른 결과가 나올 수 있다는 것입니다..




int *A;

A는 int형 포인터이다.


int B[5];

B는 크기가 5인 int형 배열이다.


int *C[5];

연산자우선순위에서 배열첨자인[]가 먼저이므로 C는 배열이다. 크기가 5인 배열.

즉 int형 포인터(int *)를 5개 담을 수 있는 배열이다.


int (*D)[5];

괄호 안을 먼저 처리하므로 D는 포인터이다.

크기가 5인 int형 배열(int [5])의 포인터를 할당할 수 있는 포인터인 것이다.


int *(*E)[5];

D와 같이 괄호 안을 먼저 처리하므로 E는 포인터이다.

이 것 역시 남은 부분을 보면 E가 무슨 포인터인지 알 수 있는데,

남은부분이 int *[5]이므로 E는 int *[5]의 포인터를 담을 수 있는 포인터가 된다.

이 int *[5]꼴은 C와 같은 형태이다. 이것을 참조해서 정리해 보면

E는'int형 포인터(int *)를 5개 담을 수 있는 배열'의 포인터을 할당할 수 있는 포인터이다.


int *F[5][4];

[]가 먼저 처리되므로 F는 이차원배열이다.

int * 형태의 데이터를 5x4로 담을 수 있는 크기가 20인 이차원배열. 


int (*G[5])[4];

역시 우선순위를 따져보면 G는 크기 5의 배열이다.

남은부분은 int (*)[4]로, 이는 D와 같은 꼴이다.

즉 G는, D와 같은 형태의 데이터를 5개 담을 수 있는 배열.


int *(*H[5])[4];

H 역시 배열이며, 남은부분은 int *(*)[4]이고,

이는 E와 같은 형태이다.

즉 H는, E와 같은 형태의 데이터를 5개 담을 수 있는 배열.

 


function a() { b(); } function b() { c(); } function c() { d(); } function d() { trace(""); //중단점 1 setTimeout(e, 0); } function e() { trace(""); //중단점 2 } a();




● 중단점 1에서의 호출스택



d

c

b

a

init

Main





● 중단점 2에서의 호출스택 (비동기적으로 새로운 스택으로 실행된다.)



e

builtin::apply

onTimer

_timerDispatch

tick



flash.utils.setTimeout()함수를 실행하면

flash.utils.Timer객체의 함수들이 실행되고있다.

그래서 AS3.0레퍼런스에도 setTimeout()을 쓰기보다

repeat이 1인 Timer객체를 쓰라고 추천하고있다.










private function onSelected($evt:Event):void {

var list:Array = _pathFile.getDirectoryListing();

for (var i:int = 0; i < _pathFile.length; i++) {

list[i].addEventListener(Event.COMPLETE, onFileLoaded);

            list[i].load();

     }

    }


private function onFileLoaded($evt:Event):void

{

trace(1);

 }


위와 같은 코드를 작성하고 실행했는데 1이 출력되지 않는 것이었다.

그런데 이렇게 짠 다른 프로그램은 잘 동작하는 현상..


이유는, 그 프로그램에서는 onFileLoaded함수에서 $evt인자를 받아 어떤 처리를 하는데,

여기선 list의 요소를 참조하지 않아서 객체가 못 쓰는 상태(객체의 모든 참조 제거)가 되어버린 것이었다..


결국 list배열을 멤버변수로 둬서 해결..




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



▼이곳에 플래시가 삽입되어 있습니다. 




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package kr.tibyte
{
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Shape;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.geom.Point;
    import flash.geom.Vector3D;
     
    /**
     * ...
     * @author
     */
    public class Main extends Sprite
    {
         
        [Embed(source="../../sample1.png")]
        private var SampleImage1:Class;
        [Embed(source="../../sample2.png")]
        private var SampleImage2:Class;
         
        private var _bitmaps:Vector.<Bitmap> = new Vector.<Bitmap>(IMG_NUM);
        private var _imgArr:Vector.<Sprite> = new Vector.<Sprite>(IMG_NUM);
        private var _imgWidth:int = 120;
        private var _imgHeight:int = 80;
        private var _r:Number; //회전반경
        private var _yaw:Number = 0;
        private var _container:Sprite = new Sprite();
         
        private const IMG_NUM:int = 6;
        private const THETA:Number = 0.003*3/180*Math.PI; //회전각속도
        private const MARGIN:int = 20; //이미지 간격
         
        public function Main():void
        {
             
            for (var i:int = 0; i<IMG_NUM; i++)
            {
                if (Math.random()>0.6)
                    _bitmaps[i] = new SampleImage1();
                else
                    _bitmaps[i] = new SampleImage2();
                 
                _bitmaps[i].x = -_bitmaps[i].width/2-MARGIN;
                _bitmaps[i].y = -_bitmaps[i].height/2;
                _imgArr[i] = new Sprite();
                _imgArr[i].addChild(_bitmaps[i]);
                _imgArr[i].width = _imgWidth;
                _imgArr[i].height = _imgHeight;
                _container.addChild(_imgArr[i]);
            }
            //회전반경을 이미지크기+마진값에 딱 맞게 설정
            _r = (_imgWidth+2*MARGIN)/(2*Math.tan(Math.PI/IMG_NUM));
             
            _container.x = 200;
            _container.y = 150;
            this.transform.perspectiveProjection.projectionCenter = new Point(_container.x, _container.y);
            addChild(_container);
             
            stage.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
            stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
        }
         
        private function mouseMoveHandler($evt:MouseEvent):void
        {
             
            var old:Point = this.transform.perspectiveProjection.projectionCenter;
            old.y = stage.mouseY;
            root.transform.perspectiveProjection.projectionCenter = old;
         
        }
         
        private function enterFrameHandler($evt:Event):void
        {
             
            //마우스로 회전제어
            _yaw += THETA*(stage.mouseX-stage.stageWidth/2);
             
            display();
         
        }
         
 
         
        private function display():void
        {
            var angle:Number;
            for (var i:int = 0; i<IMG_NUM; i++)
            {
                angle = _yaw+i*2*Math.PI/IMG_NUM;
                _imgArr[i].x = _r*Math.cos(angle);
                _imgArr[i].z = _r*Math.sin(angle);
                 
                //angle과 수직이 되도록 rotationY 를 설정
                _imgArr[i].rotationY = (180/Math.PI)*-1*(Math.PI/2+angle);
                 
                 
                /* //rotationY 대신 transform속성으로 회전하기
                   _imgArr[i].transform.matrix3D.pointAt(new Vector3D(),Vector3D.Z_AXIS, new Vector3D(0, -1, 0));
                 */
            }
             
            //다시 그리기
            var seq:Vector.<int> = genSequence(IMG_NUM);
            seq.sort(compare);
             
            _container.removeChildren();
            for (i = 0; i<IMG_NUM; i++)
            {
                _container.addChild(_imgArr[seq[i]]);
            }
         
             
            //z소팅을 위한 비교함수
            function compare($a:int, $b:int):Number
            {
                if (_imgArr[$a].z>_imgArr[$b].z)
                    return -1;
                else if (_imgArr[$a].z==_imgArr[$b].z)
                    return 0;
                else
                    return 1;
            }
        }
 
         
        private function genSequence($n:int):Vector.<int>
        {
            var arr:Vector.<int> = new Vector.<int>($n);
            for (var i:int = 0; i<$n; i++) {
                arr[i] = i;
            }
            return arr.concat();
        }
    }
}



1페이지부터 5페이지까지 5개의 장면이 있고

다음 버튼과 이전 버튼이 있어서 페이지를 넘길 수 있는 프로그램을 만드는데

if문을 쓰지 않고 모듈러연산으로 처리하는 것이 생각나서 식을 구해보기로 했다.


아래 그림은 1부터 n까지의 자연수가 있을때

아무것도 더하지 않고 mod n연산을 하여 +1을 한 결과와,

처음에 1을 더한 경우, 2를 더한 경우에 대한 내용이다.


아래 그림은 처음에 n-2을 더한 경우와, n-1을 더한 경우이다. 

예를 들어 자연수의 집합 {1, 2, 3, 4, 5} 가 있을 때

집합의 각 원소에 n-2인 3을 더하면  집합은{4, 5, 6, 7, 8}가 되고 

이 집합의 mod 5를 구하면 {4, 0, 1, 2, 3} 이 되어

다시 이 집합에 +1을 하면 {5, 1, 2, 3, 4}가 되어서

최종적으로는 원래 집합이 우측으로 1칸씩 순환 이동하였다고 볼 수 있다.

또한 2는 1이 되고, 3은 2가 되고, 4는 3이 되고, 5는 4가 되고, 1은 다시 5가 되었으므로

처음에 만들고자 하였던 프로그램의 이전 버튼에 사용할 수 있을 것이다.


다시 위의 두 그림을 자세히 살펴보면,

집합의 원소 a가 있고 집합의 원소가 n개인 경우에, 이 집합의 원소를 p씩 증가시키고 싶을 때

(예를 들어 각각의 1,2,3,4,5 페이지에서 다음버튼을 눌러서 각각 2,3,4,5,1 페이지를 만들고자 하는 경우 등) 

다음과 같은 함수를 얻을 수 있다.

f(a,p,n) = (a+(p-1))% n +1

여기서 p는 좌측으로 p칸 순환시프트시킬 값도 된다.


처음의 프로그램 문제로 되돌아가서

5페이지일때의 다음버튼을 만든다고 하면 아래와 같이 프로그램을 작성할 수 있을 것이다.

page = (currentPage+(1-1))%5 + 1;

필요없는 연산을 제거하면,

page = currentPage%5 + 1;


이번에는 2페이지씩 넘기는 버튼을 만든다고 하면 코드는,

page = (currentPage+(2-1))%5 + 1; 에서 역시 필요없는 부분을 제거하여
page = (currentPage+1)%5 + 1;
이 된다.

그러면 반대방향으로 1페이지를 넘기는 이전버튼에 들어갈 코드는 어떻게 될까?
순환 시프트이므로 p값을 넣을 자리에 n-p값을 놓으면 된다.
page = (currentPage+(5-1-1))%5 + 1; 
page = (currentPage+3)%5 + 1; 


정리 :
(a+(p-1))% n +1
→ p씩 순환증가
→ p씩 좌측 순환시프트 

(a+(n-p-1))% n +1
→ p씩 순환감소
→ p씩 우측 순환시프트 



================================================================================



+ Recent posts