2018년 9월 디자인과 코딩을 함께 다루는 유튜브 채널을 열었습니다. "타입과 미디어" 사이트는 당분간 유지할 계획이지만, 새로운 정보는 유튜브 비디오를 통해서 소개하겠습니다. 많은 관심 부탁드립니다! — 정대인

복잡한 도형 그리기

세부 목차

앞에서 프로세싱이 제공하는 기본적인 도형 그리기 함수들을 알아보았습니다. 그러나 rect()ellipse()로 표현이 불가능한 복잡한 도형을 그려야 할 때는 vertex(), curveVertex(), bezierVertex() 등의 함수를 사용할 수 있습니다.

아래에서 알아볼 새로운 함수들은 일러스트레이터의 펜 툴이 제공하는 기능과 흡사합니다. 프로세싱과 일러스트레이터 모두 벡터 방식에 기초하고 있기 때문입니다. 펜 툴을 사용할 때 포인트를 콕콕 찍어서 도형을 만들듯이, 프로세싱에서는 vertex를 사용할 수 있습니다.

곡선을 그리는 다양한 방법은 수학에 기초하고 있기 때문에 조금 어렵게 느껴질 수도 있습니다. 하지만, 차근차근 살펴보면 이해하는 것이 불가능하지 않습니다. 책상에 모눈종이를 준비하고, 연필로 점을 찍어가며 이해하는 방법은 큰 도움이 됩니다. 일단! 심호흡 한 번 하고 들어가보지요.

vertex

vertex()를 이용해서 글자를 그려봅시다. vertex()를 이용해서 그릴 때에는 beginShape()endShape()으로 코드를 감싸줘야합니다. 이는 도형의 시작과 끝을 명시하는 것입니다.

vertex(x, y);

vertex()의 사용법은 point()와 동일합니다. 점의 x, y 좌표를 인수로 넣어줍니다.

void setup() {
  size(600, 200);
  noStroke();
}

void draw() {
  background(255);
  // set color
  fill(0);
  beginShape();
  vertex(30, 20);
  vertex(10, 60);
  vertex(50, 60);
  endShape(CLOSE);

  // update color
  fill(80);
  beginShape();
  vertex(65, 10);
  vertex(65, 35);
  vertex(55, 35);
  vertex(55, 45);
  vertex(65, 45);
  vertex(65, 70);
  vertex(75, 70);
  vertex(75, 10);
  endShape(CLOSE);
  
  // update color
  fill(160);
  beginShape();
  vertex(40, 70);
  vertex(40, 100);
  vertex(80, 100);
  vertex(80, 70);
  endShape(CLOSE);
}
Your browser does not support canvas.

위의 예제에서 처음 보는 또다른 함수는 stroke()fill()입니다. 이를 통해서 도형의 색을 정할 수 있습니다. 일러스트레이터에서 스트로크와 필 컬러를 설정하는 것과 동일합니다. 색을 넣고 싶지 않을 때에는 각각 noStroke()noFill()을 사용합니다.

주의할 점은, 색은 도형을 그리기 이전에 설정해줘야 한다는 것입니다. 색을 한번 설정하면, 그 코드의 아랫부분에서 그려지는 모든 도형은 같은 색을 사용하게 됩니다.

도형이 그려지는 순서 또한 주의해야 합니다. 코드에서 위쪽에 그린 도형은 화면에서 아래에 위치하게 됩니다. 여러 도형을 겹쳐서 그리면 이를 분명하게 알 수 있습니다.

같은 예제를 가지고, 이번에는 색을 바꿔서 실행해봅시다.

// setup() 코드 생략...

void draw() {
  background(255);
  // set color
  stroke(0);
  beginShape();
  vertex(30, 20);
  vertex(10, 60);
  vertex(50, 60);
  endShape();

  // update color
  stroke(80);
  beginShape();
  vertex(65, 10);
  vertex(65, 35);
  vertex(55, 35);
  vertex(55, 45);
  vertex(65, 45);
  vertex(65, 70);
  vertex(75, 70);
  vertex(75, 10);
  endShape();
  
  // update color
  stroke(160);
  beginShape();
  vertex(40, 70);
  vertex(40, 100);
  vertex(80, 100);
  vertex(80, 70);
  endShape();
}
Your browser does not support canvas.

위의 예제는 noFill()을 사용해서 면의 색을 비활성화하고, stroke()로 선을 활성화하였습니다. 또 하나 변화를 준 것은 endShape()에서 인수였던 CLOSE를 삭제한 것입니다. CLOSE의 역할은 처음과 끝의 버텍스를 이어주는 것이었습니다.

curve

curve(cpx1, cpy1, x1, y1, x2, y2, cpx2, cpy2);

curve() 함수는 무려 8개의 인수를 요구합니다. 각각은 네 쌍의 점의 좌표로 표현되는데 첫번째와 마지막 쌍은 제어점(control points)이며, 둘째와 셋째 쌍의 점들이 각각 시작과 끝점을 나타냅니다.

void setup() {
  size(600, 200);
  noFill();
}

void draw() {
  background(255);
  // curve
  stroke(0);
  curve(0, 180, 15, 60, 45, 60, 60, 180);
  // control points
  stroke(0, 255, 0);
  ellipse(0, 180, 6, 6);
  ellipse(60, 180, 6, 6);
  // connecting lines
  line(0, 180, 45, 60);
  line(60, 180, 15, 60);

  stroke(0);
  beginShape();
  vertex(65, 10);
  vertex(65, 35);
  vertex(55, 35);
  vertex(55, 45);
  vertex(65, 45);
  vertex(65, 70);
  vertex(75, 70);
  vertex(75, 10);
  endShape();

  beginShape();
  vertex(40, 70);
  vertex(40, 100);
  vertex(80, 100);
  vertex(80, 70);
  endShape();
}
Your browser does not support canvas.

위의 예제에서 옅은 녹색의 선과 원은 제어점을 시각화한 것입니다.

curveVertex

curveVertex(x, y);

curveVertex()curve()와 비슷하지만, 단 하나의 커브를 그리는 대신에 여러개의 커브를 이어서 그릴 수 있습니다. curve()와 비슷하게 시작점과 끝점의 제어점을 설정해줘야 합니다. 일반적으로 첫 제어점은 커브의 시작점과, 마지막 제어점은 커브의 끝점과 동일한 좌표로 설정합니다. 그리고 중간의 버텍스들은 원하는 곳에 배치시키면, 자유로운 곡선을 그릴 수 있습니다. 이렇게 설정하면, 커브를 구성하는 각각의 점들은 주변 점들의 제어점 역할을 하기도 합니다. 여러점을 이어서 커브를 만드는만큼, beginShape()endShape()을 사용해야 합니다.

void setup() {
  size(600, 200);
  noFill();
  stroke(0);
}

void draw() {
  background(255);
  // curveVertex
  beginShape();
  curveVertex(10, 60); // the first control point
  curveVertex(10, 60); // the first vertex
  curveVertex(20, 20);
  curveVertex(30, 60);
  curveVertex(40, 20);
  curveVertex(60, 60); // the last vertex
  curveVertex(60, 60); // the last control point
  endShape();
  
  line(70, 10, 70, 70);
  line(70, 40, 80, 40);
  
  // curveVertex
  beginShape();
  curveVertex(30, 70);
  curveVertex(30, 70);
  curveVertex(70, 70);
  curveVertex(70, 80);
  curveVertex(30, 90);
  curveVertex(30, 100);
  curveVertex(70, 100);
  curveVertex(70, 100);
  endShape();
}
Your browser does not support canvas.

아래의 캔버스에서 마우스를 클릭하면, 커브버텍스들이 만들어지고 그들을 잇는 부드러운 곡선이 그려집니다. 스플라인 커브가 어떤 식으로 작동하는지 살펴볼 수 있습니다.

ArrayList<Vertex> vts;

void setup() {
  size(600, 200);
  vts = new ArrayList<Vertex>();
}

void draw() {
  background(255);

  noFill();
  stroke(0);
  beginShape();
  for (int i = 0; i < vts.size (); i++) {
    Vertex v = vts.get(i);

    // first control point
    if (i == 0) {
      curveVertex(v.pos.x, v.pos.y);
    }

    // all the points inbetween
    curveVertex(v.pos.x, v.pos.y);

    // last control point
    if (i == vts.size() - 1) {
      curveVertex(v.pos.x, v.pos.y);
    }
  }
  endShape();

  for (int i = 0; i < vts.size (); i++) {
    Vertex v = vts.get(i);
    v.display();
  }
}

void mousePressed() {
  Vertex v = new Vertex(mouseX, mouseY);
  vts.add( v );
}

void keyPressed() {
  if (key == ' ') {
    vts = new ArrayList<Vertex>();
  }
}

//------------ simple Vertex class
class Vertex {
  PVector pos;

  Vertex(float x, float y) {
    pos = new PVector(x, y);
  }

  void display() {
    noStroke();
    fill(255, 0, 0);
    ellipse(pos.x, pos.y, 6, 6);
  }
}
Your browser does not support canvas.

bezier

bezier(x1, y1, cpx1, cpy1, cpx2, cpy2, x2, y2);

bezier()curve()와 마찬가지로 8개의 인수를 요구합니다. 그러나 순서는 다릅니다. 일러스트레이터에서 펜 툴로 그리는 베지어 커브를 생각하면 이해가 쉽습니다. 두 쌍의 제어점(control points)은 베지어 커브의 핸들 역할을 합니다. curve()는 제어점이 커브가 그려지는 반대 방향으로 향해서 조금 혼란스럽지만, bezier()의 제어점은 커브가 움직이고자 방향으로 설정해줍니다. 커브의 시작점(또는 끝점)과 제어점의 거리가 커브가 휘어지는 정도를 결정해줍니다.

void setup() {
  size(600, 200);
  noFill();
  stroke(0);
}

void draw() {
  background(255);

  bezier(10, 20, 30, 10, 30, 30, 50, 20);
  bezier(10, 30, 30, 20, 30, 40, 50, 30);
  bezier(10, 40, 30, 30, 30, 50, 50, 40);
  bezier(10, 50, 30, 40, 30, 60, 50, 50);
  bezier(10, 60, 30, 50, 30, 70, 50, 60);
  line(70, 10, 70, 80);
  line(70, 40, 80, 40);

  bezier(50, 20, 70, 10, 80, 30, 100, 20);
  bezier(50, 60, 70, 50, 80, 70, 100, 60);

  bezier(100, 20, 120, 10, 120, 30, 140, 20);
  line(140, 20, 140, 40);
  bezier(100, 40, 120, 30, 120, 50, 140, 40);
  bezier(100, 60, 120, 50, 120, 70, 140, 60);
  line(160, 10, 160, 70);
  line(160, 40, 170, 40);

  beginShape();
  vertex(130, 70);
  vertex(130, 100);
  vertex(170, 100);
  vertex(170, 70);
  endShape(CLOSE);
}
Your browser does not support canvas.

위의 예제에서 베지에 커브는 연속된 것처럼 보이지만, 이는 단지 bezier()의 좌표를 맞추어서 여러 번 호출했기 때문입니다. 실제로 연속적인 베지어 커브를 그리려면 바로 다음에 다룰 bezierVertex()를 사용하세요.

bezierVertex

bezierVertex(cpx1, cpy1, cpx2, cpy2, x, y);

사용방법이 bezier()와 비슷한데 시작점이 빠져있지요? 시작점은 vertex()를 사용해서 먼저 설정해주면 됩니다. 여러점을 이어서 커브를 만들기 때문에, beginShape()endShape()을 사용해야 합니다.

void setup() {
  size(600, 200);
  noFill();
  stroke(0);
}

void draw() {
  background(255);

  ellipse(30, 40, 40, 40);
  
  beginShape();
  vertex(10, 70);
  vertex(40, 70);
  bezierVertex(80, 70, 70, 10, 60, 10);
  bezierVertex(40, 10, 70, 80, 70, 80);
  
  endShape();
}
Your browser does not support canvas.

curve? bezier?

커브와 베지에, 이렇게 곡선을 그리는 두가지 다른 방식을 살펴보았습니다. curvecurveVertex는 곡선의 기울기가 커질수록 제어점의 위치가 시작점/끝점과 매우 멀어지게 되고, 제어점의 사용방식이 직관적이지 못하다는 특징을 가지고 있습니다. 하지만, 여러 개의 점을 이어서 부드러운 곡선을 그릴 때에는 유용하다는 사실을 위의 예제를 통해서 알 수 있습니다. bezierbezierVertex의 경우는, 우리에게 이미 익숙한 일러스트레이터의 펜 툴과 작동방식이 동일하고, curve와 비교해서 사용방식이 더 직관적입니다. 제어점의 세밀한 조정을 통해서, 원하는 모양의 곡선을 정확하게 그려낼 수 있습니다. 점들을 이어서 자유로운 곡선을 그리는 경우가 아니라면, bezierbezierVertex의 사용을 추천합니다.

quadraticVertex

업데이트 예정

beginShape()의 다양한 모드

업데이트 예정

arc

arc(x, y, width, height, startAngle, stopAngle);

ellipse()를 이용하여 원을 그릴 수 있고, arc()를 이용하여 원의 일부, 즉, 호를 그릴 수 있습니다. arc()는 6개의 인수를 요구합니다. xy는 호를 포함하는 원의 중심점 좌표이며 widthheight는 그 원의 너비와 높이입니다. startAnglestopAngle은 호가 시작하고 끝나는 각입니다.

주의할 것은, arc()가 받는 각의 단위는 우리에게 익숙한 degree가 아니라 radian이라는 것입니다. 원을 한바퀴 돌리면, 0에서 360도(degree)이지만, 이를 radian으로 표현하면 0에서 TWO_PI가 됩니다. 반원은 PI입니다. 또 하나, 0의 기준은, 원을 시계로 보았을 때 12시가 아니라 3시에 해당합니다. 3시부터 시계방향으로 회전하면서, 각도값이 증가합니다.

중학교 때 배운 것처럼 PI는 약 3.14로 표현할 수 있습니다. radian의 값이 조금만 증가해도 각의 차이는 커진다는 사실, 주의하세요. 예를 들어 radian으로 표현해야하는 곳에 각도 90을 사용한다면, 직각을 얻는 것이 아니라, 원을 28바퀴 이상 회전하게 됩니다.

void setup() {
  size(600, 200);
  fill(0);
  stroke(0);
}

void draw() {
  background(255);

  arc(20, 40, 40, 40, PI + PI/3, PI + PI/3*2);
  arc(40, 40, 40, 40, PI + PI/3, PI + PI/3*2);
  arc(20, 40, 40, 40, PI/3, PI/3*2);
  arc(40, 40, 40, 40, PI/3, PI/3*2);
  line(70, 10, 70, 70);

  arc(120, 40, 60, 40, PI + PI/3, PI + PI/3*2);
  arc(120, 20, 90, 90, PI/3, PI/3*2);
  line(150, 10, 150, 70);
  line(150, 40, 160, 40);
}
Your browser does not support canvas.

프로세싱에서 각은 기본적으로 radian으로 표현되기 때문에, 이 새로운 단위에 익숙해지는 것이 좋습니다. 그러나 프로세싱은 radians() 함수도 제공합니다.

radians(degrees);

radians()는 각도(degrees)를 래디언으로 변환하고 그 값을 돌려줍니다.

float degree = radians(180);
println( degree );

위의 예제를 실행하면, 180도에 해당하는 값, 3.1415927이 콘솔에 출력되는 것을 볼 수 있습니다. 반대로 degrees() 함수를 이용하면, 래디언을 각도로 변화한 뒤 그 값을 돌려줍니다.

float radian = degrees(PI);
println( radian );

피터 조 Peter Cho 의 Type me, Type me not 프로젝트는 두 개의 호를 이용한다는 간단한 규칙만으로 알파벳의 모든 글자를 표현하고 있습니다.

Drawscript 사용방법

더 배우기