오늘은 지난 주에 살펴보았던 기본적인 이미지입력에 추가하여 인터액션이 있는 이미지를 구현하기 위해서 기본적인 컴퓨터의 입력장치에 대해 생각해보자. 컴퓨터에 기본적으로 제공되는 입력장치는 마우스와 키보드를 들 수 있다. 최근에는 모든 노트북에 카메라가 장착되어 마치 기본입력장치로서 이제는 카메라도 포함될 수 있으리라 보지만 카메라는 좀 복잡한 데이터전송규약을 갖고 있기 때문에 나중에 살펴보기로하고 오늘은 processing에서 제공하고 있는 마우스와 키보드의 클래스에 대해서 알아보도록한다.
처음으로 마우스의 Coordination을 이해하기 위해서 간단한 예를 살펴보자. 마우스의 Coordination은 기존에 설명했던 Display의 Coordination과 그렇게 다르지 않다. 그도 그럴것이 마우스의 위치는 상대적으로 모니터의 위치, 즉 "Pixel"의 정의와 해상도에 상응하기 때문이다. 물론 Processing에서는 마우스의 위치가 Display와 상응한다. 아래와 같이 "Mouse 1D"로 정의된 프로세싱 기본튜터리얼의 예제를 보면 이러한 마우스의 위치를 정확히 검사해 볼 수 있는데 특히 println을 통해서 마우스의 위치를 파악할 수 있을 것이다. 실행하여 Processing 에디터의 아래 출력창을 확인해보자.
아래의 샘플을 보면 두개의 rect(leftcornerwidth, leftcornerheight, sizewidth, sizeheight)을 마우스가 display가운데에 위치했을 때를 기준으로 왼쪽으로 옮겼을 때, 오른쪽으로 옮겼을 때의 사각형의 크기를 변화시키는 인터렉션에 관한 코드이다. gx와 gy는 변수로서 사각의 위치를 변화시키는 변수로서 작용한다.
1. Mouse 1D.
int gx = 15;
int gy = 35;
float leftColor = 0.0;
float rightColor = 0.0;
void setup() {
size(200, 200);
colorMode(RGB, 1.0);
noStroke();
}
void draw() {
background(0.0);
update(mouseX);
fill(0.0, leftColor + 0.4, leftColor + 0.6);
rect(width/4-gx, height/2-gx, gx*2, gx*2);
//여기서 width는 200이므로 만일 x의 코오디네이션이 100이라 할 때
//아래의 계산 방식 gx=x/2의 방식에서 보면 50-50이므로 0에 위치한다.
//그리고 height의 위치는 50이고 크기는 100의 사각형이 된다.
fill(0.0, rightColor + 0.2, rightColor + 0.4);
rect(width/1.33-gy, height/2-gy, gy*2, gy*2);
//오른쪽에 위치한 사각형의 위치는 대략 (100, 50, 100, 100)이 된다.
}
void update(int x) {
leftColor = -0.002 * x/2 + 0.06;
rightColor = 0.002 * x/2 + 0.06;
gx = x/2;
gy = 100-x/2;
//아래의 코드는 x마우스의 위치가 범위를 벗어날 때 미니멈,
//맥시멈 크기를 고정시키기 위한 장치라 할 수 있다.
//maximun and minimum
if (gx < 10) {
gx = 10;
} else if (gx > 90) {
gx = 90;
}
if (gy > 90) {
gy = 90;
} else if (gy < 10) {
gy = 10;
}
}
다음 예를 들어보자. 또다른 좌우의 사각형의 크기를 마우스의 움직임에 따라서 제어하는 것이다. 위의 예제보다는 아마도 쉽게 보일 것이다. 위의 예제도 마찬가지로 쉬운 예이지만.
2. Mouse 2D.
void setup()
{
size(200, 200);
noStroke();
rectMode(CENTER);
}
void draw()
{
background(51);
fill(255, 204);
rect(mouseX, height/2, mouseY/2+10, mouseY/2+10);
fill(255, 204);
int inverseX = width-mouseX;
int inverseY = height-mouseY;
rect(inverseX, height/2, (inverseY/2)+10, (inverseY/2)+10);
}
위의 basic processing 예제는 산술에서 보다 간단해 보이지만 사실 첫번째 예제와 그다지 상이한 점을 찾아보기 힘들다. 단지 "rectMode()"가 "CENTER"로 정의 되어서 이러한 산술의 문제가 좀 다르게 해결 되었을 뿐이다. "void draw()"이하를 보면 바탕과 "fill()"을 통해 색을 정의하고 "rect(mouseX, height/2, mouseY/2+10, mouseY/2+10);"을 통해서 사각형을 정의하고있다. 좀더 자세히 보면 "rect()"의 쓰임새가 좀 달라진 것을 볼 수 있는데, 네개의 변수는 각각 (사각형 가운데 x, 사각형 가운데 y, 사각형 너비, 사각형 높이)이다. 때문에 내용을 보면 "mouseY"의 위치에 따라서 사각형의 크기가 바뀌는 것을 볼 수 있고, 사각형의 위치 중 "y"는 고정되어있고, "x"의 위치가 마우스의 움직임에 따라서 변하는 것을 볼 수 있다.
3. Mouse Pressed
아래의 예제는 어떻게 마우스클릭에 대한 예제이다.
void setup() {
size(200, 200);
fill(126);
background(102);
}
void draw() {
if(mousePressed) {
stroke(255);
} else {
stroke(0);
}
line(mouseX-66, mouseY, mouseX+66, mouseY);
line(mouseX, mouseY-66, mouseX, mouseY+66);
}
아주 쉬운 예제인데 x, y를 기점으로 132픽셀 크기의 십자를 마우스의 움직임에 따라서
표현하되 마우스를 클릭할 경우 색을 하얀색으로 바꾸는 것이다. 여기서 "stroke()"
기능이 이용되었다. 말하자면 선의 색을 결정하는 것이 "stroke()"인 것이다.
Easing and Constrain
이제 약간 복잡한 마우스 움직임을 구현해보자. 디스플레이크기 200픽셀x200픽셀을 생성하고 마우스의 움직임에 따라 ellipse가 움직이도록 해보자. 마우스가 움직임에 따라서 원이 화면의 밖으로 나가지 않도록 프로그래밍해보자. 그리고 마우스가 움직이는 것에 따라 천천히 따라오도록 만들어보자. 아래의 예제는 이러한 목적을 충족시킨다.
Easing
float x;
float y;
float targetX, targetY;
float easing = 0.05;
void setup()
{
size(200, 200);
smooth();
noStroke();
}
void draw()
{
background( 51 );
targetX = mouseX;
float dx = targetX - x;
if(abs(dx) > 1) {
x += dx * easing;
}
targetY = mouseY;
float dy = targetY - y;
if(abs(dy) > 1) {
y += dy * easing;
}
ellipse(x, y, 33, 33);
}
여기서 변수 "easing"은 "0.05"로 선언되었다. 물론 여러분들이 이 변수를 바꾸어 적용해 볼 수도 있을 것이다. 숫자가 1에 가까울 수록 마우스에 "ellipse"가 따라 붙는 속도가 빨라질 것이다. 여기서 "targetX, Y"라는 변수가 선언되었는데 이는 아래의 산술 "dx", "dy"와 관련이 있다. "dx = targetX - x;"로 정의 되었는데 절대값"dx 혹dy"가 1보다 크다면 반복되는 동안 x = 이전 x + dx*easing을 실행하라 라는 뜻이다. 반복이 되는 동안 "x"나 "y"의 값은 "targetX"와 "targetY"에 가까와 질 것이다. 때문에 "ellipse()"가 마우스 포인터를 천천히 따라 붙는 형상을 보일 것이다.
Constrain
float mx;
float my;
float easing = 0.05;
int radius = 24;
int edge = 56;
int inner = edge + radius;
void setup() {
size(200, 200);
noStroke();
smooth();
ellipseMode(RADIUS);
rectMode(CORNERS);
}
void draw() {
background(51);
if (abs(mouseX - mx) > 0.1) {
mx = mx + (mouseX - mx) * easing;
}
if (abs(mouseY - my) > 0.1) {
my = my + (mouseY- my) * easing;
}
mx = constrain(mx, inner, width - inner);
my = constrain(my, inner, height - inner);
fill(76);
rect(edge, edge, width-edge, height-edge);
fill(255);
ellipse(mx, my, radius, radius);
}
이제까지 마우스의 코오디네이션과 간단한 디바이스와 컴퓨터간의 인터렉션에 대해서 공부해보았다. 이제 마우스의 상세한 인터렉션인 마우스 오버, 드래그 등을 살펴보자. 아래의 예제는 이러한 마우스의 기능을 간단하게 숙지할 수 있도록 고안된 프로세싱 기본예제이다. 물론 조건문을 익히지 않았다면 좀 이해하기가 쉽지 않을 것이다. 따라서 조건문을 숙지하지 않은 분들은 조건문 예제파일을 먼저 숙지하도록 한다. conditionals1, conditionals2
Mouse Functions
float bx;
float by;
int bs = 20;
boolean bover = false;
boolean locked = false;
float bdifx = 0.0;
float bdify = 0.0;
void setup()
{
size(200, 200);
bx = width/2.0;
by = height/2.0;
rectMode(RADIUS);
}
void draw()
{
background(0);
// Test if the cursor is over the box
if (mouseX > bx-bs && mouseX < bx+bs &&
mouseY > by-bs && mouseY < by+bs) {
bover = true;
if(!locked) {
stroke(255);
fill(153);
}
} else {
stroke(153);
fill(153);
bover = false;
}
// Draw the box
rect(bx, by, bs, bs);
}
void mousePressed() {
if(bover) {
locked = true;
fill(255, 255, 255);
} else {
locked = false;
}
bdifx = mouseX-bx;
bdify = mouseY-by;
}
void mouseDragged() {
if(locked) {
bx = mouseX-bdifx;
by = mouseY-bdify;
}
}
void mouseReleased() {
locked = false;
}
자 위의 코드를 상세히 살펴보도록하자. 이 예제에서는 유용하게도 여러 변수값의 형태에 대해서도 이해할 수 있을 것이다. 처음에 보이는 변수의 선언을 보면 다양한 형태를 볼 수 있는데, "float", "int", "boolean"등이다. "float"과 "int"는 숫자의 표현방식 쯤으로 이해하면 좋을 것이다. processing references에 간단히 링크를 시켜놓았다. 자세히 읽어보도록하자. 선언된 변수의 값을 대략 살펴보면 이해하기 편할 터인데 "float"은 소수, "int"는 정수 쯤으로 이해해보자.
자 변수를 살펴보면 처음에 선언된 것이 아래와 같다.
float bx;
float by;
int bs = 20;
boolean bover = false;
boolean locked = false;
float bdifx = 0.0;
float bdify = 0.0;
참 이해하기 힘든 코드일 수도 있는데 아주 간단한 코드라 생각할 수도 있을 것이다. 마우스를 클릭할 범위가 주어졌기 때문에 이해하기가 그렇게 힘들진 않다는 이야기다. 첫번째 조건문에서 선언되었던 "rectMode(RADIUS)"이므로 "bs = 20"는 반지름인, 너비의 1/2이 20인 사각형안에 마우스가 위치하면 "if (mouseX > bx-bs && mouseX < bx+bs && mouseY > by-bs && mouseY < by+bs)" 연산이 시작된다. 그리고 마우스가 반드시 사각형의 범위 안에 위치한다는 법이 없으므로 변수 중, "bdifx = mouseX-bx;"과 "bdify = mouseY-by;"로 사각형의 중심에서 마우스의 포인터가 얼마나 떨어져 있는지를 계산하도록 하였고(물론 프로그래밍의 특성상 이는 순차적으로 일어난다. 말하자면 이전의 포지션이 "bx, by = 20"이었다면 그로부터 마우스가 사각형 내부에 위치했을 때 마우스위치와 사각형 포지션의 차이를 계산한다. 순차적인 계산인 것이다.) 마우스가 클릭된 상태에서 드래그할 경우 사각형 중심의 위치는 다시 아래의 산술에 의해 계산된다. "bx = mouseX-bdifx;, by = mouseY-bdify;". 그리고 마우스를 릴리스 할 경우 사각형의 위치는 고정된고 연산은 더이상 진행되지 않는다. 영리한 코딩이었다.
Keyboard
자 이제 키보드에 이해해 볼 차례다.
키보드를 클릭하면 키보드의 인덱스인 아스키변수가 사각형의 형태와 색에 영향을 미치고 미니멀한 이미지를 생성할 수 있도록 고안된 코드이다. 키의 아스키코드변수를 어떻게 이용할 수 있는 지 코드에 잘 설명되어있다.
int rectWidth;
void setup() {
size(200, 200);
noStroke();
background(0);
rectWidth = width/4;
}
void draw() {
// keep draw() here to continue looping while waiting for keys
}
void keyPressed() {
int keyIndex = -1;
if (key >= 'A' && key <= 'Z') {
keyIndex = key - 'A';
} else if (key >= 'a' && key <= 'z') {
keyIndex = key - 'a';
}
if (keyIndex == -1) {
// If it's not a letter key, clear the screen
background(0);
} else {
// It's a letter key, fill a rectangle
fill(millis() % 255);
float x = map(keyIndex, 0, 25, 0, width - rectWidth);
rect(x, 0, rectWidth, height);
}
}
아주 간단한 코드이다. 아래의 부분이 사실 이 코드의 핵심이라 할 수 있겠다.
else {
// It's a letter key, fill a rectangle
fill(millis() % 255);
float x = map(keyIndex, 0, 25, 0, width - rectWidth);
rect(x, 0, rectWidth, height);
}
키 인덱스 값에 의해 문자인 지 아닌 지를 구분하며 문자인 경우 인덱스의 키기에 맞춰 이미지의 위치가 결정된다. "rect(x, 0, rectWidth, height)"에서 "x"는 "map()"에 의해 디스플레이 크기에 맞추어 리스케일된다. 리스케일된 변수는 화면상 사각형의 "x"좌표를 결정하게 되는 것이다.
Keyboard Function
int max_height = 20;
int min_height = 10;
int letter_height = max_height; // Height of the letters
int letter_width = 10; // Width of the letter
int x = -letter_width; // X position of the letters
int y = 0; // Y position of the letters
boolean newletter;
int numChars = 26; // There are 26 characters in the alphabet
color[] colors = new color[numChars];
void setup()
{
size(200, 200);
noStroke();
colorMode(RGB, numChars);
background(numChars/2);
// Set a gray value for each key
for(int i=0; i<numChars; i++) {
colors[i] = color(i, i, i);
}
}
void draw()
{
if(newletter == true) {
// Draw the "letter"
int y_pos;
if (letter_height == max_height) {
y_pos = y;
rect( x, y_pos, letter_width, letter_height );
} else {
y_pos = y + min_height;
rect( x, y_pos, letter_width, letter_height );
fill(numChars/2);
rect( x, y_pos-min_height, letter_width, letter_height );
}
newletter = false;
}
}
void keyPressed()
{
// if the key is between 'A'(65) and 'z'(122)
if( key >= 'A' && key <= 'z') {
int keyIndex;
if(key <= 'Z') {
keyIndex = key-'A';
letter_height = max_height;
fill(colors[key-'A']);
} else {
keyIndex = key-'a';
letter_height = min_height;
fill(colors[key-'a']);
}
} else {
fill(0);
letter_height = 10;
}
newletter = true;
// Update the "letter" position
x = ( x + letter_width );
// Wrap horizontally
if (x > width - letter_width) {
x = 0;
y+= max_height;
}
// Wrap vertically
if( y > height - letter_height) {
y = 0; // reset y to 0
}
}
위의 예제는 키보드 기능 중 키를 눌렀을 때 일어나는 두가지 사건의 형태를 사각형의 minimum height와 maximum height의 형태로 재현하였다. 물론 순차적인 이벤트는 "y"위치가 디스플레이의 맨 마지막에 위치할 때 다시 초기화되어 "0"으로 돌이가게끔 하였다.
Milliseconds and Clock
자 이제 타이밍에 대해 이해해 볼 차례다. 컴퓨터는 여러분들이 생각하는 것보다 꽤 속도가 빠른가보다. Millisecond란 1초를 1/1000값이다. 아래의 코드는 이러한 애플릿을 시작한 후 경과된 밀리세컨드의 값을 통한 모듈로연산을 형상화한 코드이다. modulo(%) reference
float scale;
void setup()
{
size(200, 200);
noStroke();
scale = width/10;
}
void draw()
{
for(int i=0; i<scale; i++) {
colorMode(RGB, (i+1) * scale * 10);
fill(millis()%((i+1) * scale * 10) );
rect(i*scale, 0, scale, height);
}
}
Clock
아래의 예제는 소위 어떻게 processing이 현재의 시각을 읽어낼 수 있는가하는 코드이다.
int cx, cy;
float secondsRadius;
float minutesRadius;
float hoursRadius;
float clockDiameter;
void setup() {
size(200, 200);
stroke(255);
smooth();
int radius = min(width, height) / 2;
secondsRadius = radius * 0.72;
minutesRadius = radius * 0.60;
hoursRadius = radius * 0.50;
clockDiameter = radius * 1.8;
cx = width / 2;
cy = height / 2;
}
void draw2() {
background(0);
// Draw the clock background
fill(80);
noStroke();
ellipse(cx, cy, clockDiameter, clockDiameter);
// Angles for sin() and cos() start at 3 o'clock;
// subtract HALF_PI to make them start at the top
float s = map(second(), 0, 60, 0, TWO_PI) - HALF_PI;
float m = map(minute() + norm(second(), 0, 60), 0, 60, 0, TWO_PI) - HALF_PI;
float h = map(hour() + norm(minute(), 0, 60), 0, 24, 0, TWO_PI * 2) - HALF_PI;
// Draw the hands of the clock
stroke(255);
strokeWeight(1);
line(cx, cy, cx + cos(s) * secondsRadius, cy + sin(s) * secondsRadius);
strokeWeight(2);
line(cx, cy, cx + cos(m) * minutesRadius, cy + sin(m) * minutesRadius);
strokeWeight(4);
line(cx, cy, cx + cos(h) * hoursRadius, cy + sin(h) * hoursRadius);
// Draw the minute ticks
strokeWeight(2);
beginShape(POINTS);
for (int a = 0; a < 360; a+=6) {
float x = cx + cos(radians(a)) * secondsRadius;
float y = cy + sin(radians(a)) * secondsRadius;
vertex(x, y);
}
endShape();
}
과제1
과제2
과제3
과제4
과제5