[아두이노] processing 3D 도형 제어

IOT/아두이노|2019. 4. 25. 09:00

[아두이노] processing 3D 도형 제어 


예전에 잠깐 아두이노와 연동하는 processing을 소개 한 적이 있습니다. 오늘은 아두이노 부분을 제외하고 간단히 processing에서 키보드와 마우스로 도형을 제어하는 원리를 소개하려고 합니다. 이부분을 어느정도 이해하시면 나중에 아두이노에서 제어값을 입력된 값으로 해서 processing 이미지를 제어 할 수 있기 때문입니다. 오늘 다루게 될 키보드와 마우스가 나중에 아두이노로 대체하면 재밌는 표현들을 할 수 있기 때문에 관심을 가져줬으면 하는 마음으로 포스팅을 합니다.

1. processing 3D box() 도형 그리기


[복습]

void setup() {  
  한번만 수행;
}
void draw() { 
  무한 반복 수행;
}

아두이노랑 구조가 비슷하다고 했죠. setup()은 한번만 수행되는 초기화 작업을 코딩하고, draw()은 계속 그리는 무한 작업 코딩을 담당합니다.

void setup() {  
    
    size(600,600,P3D); //창사이즈
    noStroke();  //테두리없음

//  frameRate(30); //초당 프레임
} 
void draw() {  
     background(0); //배경색
}

추가적으로 초기 설정하는 함수들이 있는데 다 생략하고 딱 두개만 사용합니다. 윈도우 창이 하나 생성되는데 size() 함수로 윈도우 창 만듭니다. 여기서, "P3D" 3D Rendering 모드입니다. "OPENGL"로 선언해도 됩니다. 참고로 opengl은 import 선언이 필요하지만요. 그리고 테두리를 그리지 않는 noStroke()함수를 사용합니다. box 그렸는데 테두리가 보기 흉해서 테두리를 지웠네요.

배경색은 background(0)로 검정색으로 했네요. 아래 그림은 위 코딩의 결과입니다.


다음으로,

void draw() {  
    background(0); //배경색
    lights();  //조명

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    box(200,100,200); //상자
    popMatrix(); //End
}

옆에 주석을 다 달아 놓았습니다. 배경색은 검정색으로 하고 lights() 조명효과를 나타냅니다. 이미지 도형을 만들면 그 이미지에 light를 비추는 효과라고 생각하시면 됩니다.

fill()은 그리는 도형에 어떤 색을 채울것인지 지정하는 함수입니다. 즉, fill() 함수에 의해서 다음에 그려진 도형의 표면의 색이 결정됩니다. 사각형을 그리면 사각형 면의 색이 fill()함수의 색으로 채워진다고 생각하시면 됩니다.

pushMatrix(), popMatrix()함수가 있는데 스텍, 행렬의 의미를 이해하고 있어야 하지만 설명을 해도 쉽게 의미를 전달하기 어렵습니다. 다음으로 약식 꼼수 의미로 이해해 주셨으면 합니다. 저 함수가 나오면 push랑 pop은 한쌍이라고 생각하세요. 그리고 그리는 영역으로 push 시작위치에서 pop은 그리기 끝나는 위치라고 생각하세요. push~pop사이의 그리기 명령함수는 그 영역안에서만 독립적으로 적용된다고 생각하시면 될 듯요.


위 그림으로 다시 설명하면은 draw()에서 한장에 종이에 다가 그림을 그린다고 생각하시면 push~pop함수가 있으면 그 부분은 새로운 종이로 독립적인 영역의 그림을 그린다고 생각하시면 됩니다. 여기서, draw라는 메인 종이 위헤 새로 그린 종이를 붙이는 느낌으로 이해하시면 될 듯요. push~pop 안에 또다른 push~pop이 나오면 밖에 push~pop은 부모라고 생각하시고 부모 종이 위에 자식 종이가 두장 있다고 생각하시면 됩니다. 이때 자식 종이들은 각각 독립된 영역으로 A종이에 그리는 명령은 B종이에 영향을 줄 수 없고, B종이에 그리는 명령은 A종이에 영향을 줄 수 없는 독립된 영역으로 보시면 됩니다. 하지만 부모 종이에서는 자식 A, B가 둘 다 포함되어 있기 때문에 부모 종이에서 그리는 명령은 A, B 종이에 영향을 주게 됩니다.

왜! 이렇게 번거롭게 push~pop를 사용하느냐면 한두개의 도형을 그릴 때는 상관 없지만 다수의 도형을 그리고 배치하고 개별적인 도형들이 움직이거나 변화가 일어나게 코딩할려면 push~pop이 없이 그냥 한다면 어렵습니다. 그리는 명령을 매번 일일히 변화를 줘야 하기 때문이지요. 한장의 종이에 자동차 그림을 그린다면 한번에 전체를 다 그려야 합니다. 엔진, 바퀴, 핸들 등등의 각각의 부품들을 한번에 한장에 다 그려야 하기 때문에 그리고 나면 다시 엔진이 맘에 안든다고 엔진을 바꿀려고 하면 수정하기도 힘들고 하나를 변경하면 그 영향이 전체에 가기 때문에 무척 힘든 작업이 됩니다.

하지만 push~pop은 각각의 엔진이면 엔진 바퀴면 바퀴를 독립적으로 그리고 한장에 종이에 독립적으로 그림 그림을 가져와서 해당 위치에 붙이기만 하면 조립을 쉽게 완성할 수 있습니다. 나중에 엔진을 바꾸고 싶다면 엔진만 다시 그려서 해당 위치에 붙이면 쉽게 변경할 수 있게 됩니다.

한마디로 말하면 각 그림을 캡슐화 한다고 생각하시면 됩니다.

다시 설명드리면,


위 그림을 그리기 위해서 push~pop이 없다면 한개의 좌표계에서 몸체와 2개의 바퀴를 3D공간 좌표계에서 위치 좌표를 일일히 이동시켜서 그 위치에서 그림을 그려야 합니다. 한개라도 변경되는 일이 발생하면 전체에 영향을 주게 됩니다. 그리고 바퀴만 회전시키고 싶을때 제어하기가 무자 까다롭습니다.

하지만 push~pop을 사용하면 독립된 3D좌표계에서 독립적으로 그리게 됩니다.


위 그림처럼 계별적 3D 좌표계에서 도형을 그린 다음에 메인 draw() 이 그림을 push~pop으로 배치만 하면 됩니다. 나중에 원의 색을 바꾸고 싶으면 push~pop안에 fill()값만 바꾸면 색을 쉽게 변경이 됩니다. 바퀴가 두개인데 복제도 가능해지죠. 바퀴를 회전 시키고 싶으면 push~pop에 rotate()함수만 삽입하면 됩니다. draw()함수 내에서 하나의 좌표에게서 push~pop의 개별좌표계로 독립된 표현이 된다고 생각하시면 되겠습니다.

3D 포스트가 아닌데 삼천포로 빠지고 말았네요. 나중에 아두이노와 연동해서 뭔가 화려한 3D 관련 표현을 하실 수 있으니깐 가장 중요한 push~pop을 이야기 하다보니 좀 설명이 길어 졌네요.

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    box(200,100,200); //상자
    popMatrix(); //End

fill(0,255,0)함수로 다음에 그리게 될 도형의 표면 색을 녹색으로 정했습니다. translate(x,y,z)로 그리게 될 도형이 윈도우창에서 어디쯤 위치할 것인지 이동 시킵니다. 즉, 도형의 색은 녹색이고 특정위치로 이동시킨 곳에 상자가 그려진다고 생각하시면 될 듯 싶네요.

[결과]


위 그림에서는 상자가 녹색인 것은 알 수 있지만 3D 상자인지는 알 수 없네요. 이제 회전을 시켜 볼까요.

2. Mouse 회전


마우스 회전은 간단합니다. 상자가 그려진 윈도우창에 마우스의 좌표를 가져 올 수 있다면 쉽게 해결 됩니다. processing에서는 마우스 좌표를 변수로 실시간 좌표를 가져올 수 있습니다. mouseX, mouseY가 그 변수명입니다. 이 변수를 사용하면 현재 마우스가 가리키는 좌표값을 얻을 수 있습니다. 회전을 시켜볼까요.

회전 rotate()함수는 그냥 회전인데 마우스 좌표로 X, Y 축으로 회전를 해봅시다. processing 에서는 각 축을 기준으로 회전 시키는 함수를 제공합니다.

해당 함수를 살펴보면, ratateX()은 X축 기준으로 회전함수이고, ratateY()은 Y축 기준으로 회전하는 함수입니다. mouseX, mouseY 변수는 size()함수로 만든 윈도우 창의 영역 안에 마우스의 좌표(x,y)값을 실시간으로 가져 올 수 있어 이 값을 회전각으로 하면 쉽게 회전 할 수 있습니다.

처음, 회전각은 mouseX로 했더니 너무 고속으로 회전해서 약간 회전을 느리게 하기 위해서 0.1을 곱해도 너무 빠르고 0.01정도 하니깐 제 컴퓨터에서 이상적으로 회전 하네요.

회전각(mouseX * 0.01)로 회전시켜 보았습니다.

void setup() {  
    
    size(600,600,P3D); //창사이즈
    noStroke();  //테두리없음
//  frameRate(30); //초당 프레임
} 

void draw() {  
    background(0); //배경색
    lights();  //조명

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100); //이동
    rotateX(mouseY*0.01); //x축 회전
    rotateY(mouseX*0.01); //y축 회전
    box(200,100,200); //상자
    popMatrix(); //End
}

[결과]


나중에 조이스틱 같은걸로 조정한 값을 이 부분을 대신하면 회전이 되겠죠.

2. 키보드 회전



이제는 키보드로 회전 시키는 방식을 살펴보도록 하겠습니다.

위 링크 소스에서 키보드 관련 부분 살펴보면,

void keyPressed() {
    if (key == CODED) {
            if (keyCode == UP) {
                 회전각;
            }   
    }
    else if (key == 'z'){
       명령문;
    }   
}

대충 구조는 이렇습니다. keyPressed()함수는 키보드의 키를 누르면 호출 되는 함수입니다. key는 눌렀을때의 키 값을 가지고 있으면 그 키 값이 CODED인지 채크하게 됩니다. CODED면 keyCode 변수의 키 값이 들어 있고 그 키가 UP, DOWN, LEFT, RIGHT 인지를 체크하게 됩니다. 참고로 화살표 키 값이라고 생각하시면 될 듯요.
일반 키값은 key에 들어 있으면 'a'~'z', 'A'~'Z' 등 등으로 키값을 가질 수 있고 key에 저장 되어 있어서 key로 비교하면 됩니다.

키보드로 회전한다면 방향키로 X, Y축 회전을 시키고 키값 Z, X키로 Z축으로 이동 제어 키로 쭘인/줌아웃을 시킬 예정입니다.

int x=0;
int y=0;
int z=0;

void setup() {  
    
    size(600,600,P3D); //창사이즈
    noStroke();  //테두리없음
        
//  frameRate(30); //초당 프레임
} 

void draw() {  
    background(0); //배경색
    lights();  //조명

    pushMatrix();  //Start
    fill(0,255,0); //채우기
    translate(width/2,height/2,-100+z); //이동
    rotateX(radians(y)); //x축 회전
    rotateY(radians(x)); //y축 회전
    
    box(200,100,200); //상자
    popMatrix(); //End
}

void keyPressed() {
  if (key == CODED) {
    if (keyCode == UP) {
      y+=1;
    } 
    else if (keyCode == DOWN) {
      y-=1;
    } 
    else if (keyCode == LEFT) {
      x-=1;
    } 
    else if (keyCode == RIGHT) {
      x+=1;
    }
  }
  else if (key == 'z' || key == 'Z') {
      z+=10;
  } 
  else if (key == 'x' || key == 'X') {
      z-=10;
  } 
}

위에서 if문이 조건식이 두개인데 '||'은 둘중 하나만 참이면 참으로 인정하는 기호입니다. 둘개 다 참이여야 참으로 인정하는 기호는 '&&'인데 참고로 알아 두세요. '||'로 'z' or 'Z' 중 Z키가 대소문자 중 아무거나 입력해도 Z로 인정한다는 의미입니다. 나머지는 구지 성명 드릴 필요는 없겠죠.

    rotateX(radians(y)); //x축 회전
    rotateY(radians(x)); //y축 회전

여기서 radians(y)은 라디안 단위로 각도로 변환시키는 함수입니다. 그냥 각도 구하기라고 생각하시면 될 듯 싶네요.

[결과]



화살표 방향키하고 춤인/줌아웃 키인 Z, X 키가 잘 작동하네요.

마무리


processing에 대해서 한번 관심을 가져보시라고 간단히 3D Rendering를 해보았습니다. 참고로 이미 만들어진 box()함수의 도형이지만 좌표계 기준으로 각 좌표로 모형을 그리고 싶다면 공식홈페이지에 도형 그리는 함수들을 찾아보시고 따라서 그려보세요, 충분히 그릴 수 있을 거라 생각됩니다.

참고로 processing 함수들이 다양한 기능들을 제공해주기 기 때문에 위 포스트한 코딩에서 추가 할 코딩 부분이 있지만 다 생략하고 기본만 되는 부분만 코딩한거라 좀 단순한 표현이 되었네요. 그래도 핵심적인 표현은 다 했습니다. 이제는 여러분들이 도형을 그리고 그 도형에 대해서 움직임을 push~pop를 이용해서 제어하시면 됩니다.

갑자기 processing를 포스팅을 한 이유는 최근에 스위치 버턴과 조이스틱으로 조정하는 것을 배웠습니다. 한번 여러분들이 이 코딩을 기반으로 아두이노와 접목하면 좋을 것 같아서 소개하게 되었습니다.

한번 스위치 버턴 or 조이스틱으로 오늘 배운 도형을 컨트롤 해보고 싶지 않으신지요. 제가 답을 바로 드리는 것보다 여러분들이 한번 지난 시간에 processing에서 아두이노와 어떻게 연동을 했는지 상단에 참고 링크 포스트를 읽어보시고 연구를 해보셨으면 합니다.

댓글()