If you're seeing this message, it means we're having trouble loading external resources on our website.

웹 필터가 올바르게 작동하지 않으면 도메인 *. kastatic.org*.kasandbox.org이 차단되어 있는지 확인하세요.

주요 내용

3D 도형 생성하기

이제 정육면체의 위치나 크기는 어떻게 바꿀 수 있을까요? 아니면 직육면체나 다른 입방체를 그리고 싶으면 어떻게 해야 할까요? 지금 코드로는 노드를 하나씩 변경해야 하므로 성가신 일입니다. 특정 위치와 크기를 갖는 직육면체를 만들 수 있는 단순한 방법이 더 좋을 것입니다. 다시 말해서 위치와 크기를 노드(node)와 엣지(edge)의 배열에 대응시키는 함수가 필요합니다.

직육면체 정의하기

직육면체에는 가로, 세로, 높이가 있습니다.
직육면체는 3D 공간에서 특정한 위치가 있고, 이는 여섯 개의 매개변수로 나타낼 수 있습니다. 정육면체의 위치를 정의하는 방법이 두 개 있습니다. 즉, 정육면체의 중심을 정의하거나 한 꼭짓점을 정의합니다. 전자의 경우가 더욱 일반적이지만 후자의 경우는 사용하기 더 쉽습니다.
함수는 노드와 엣지의 두 배열을 반환해야 합니다. 두 변수를 반환하는 방법은 변수를 nodes에 대한 키(key)와 edge에 대한 키를 갖는 객체로 묶는 것입니다. 변수로는 임의의 문자열을 사용할 수 있습니다. 동일한 단어를 사용하는 것이 보다 쉬워 보이네요.
// (x, y, z)에 꼭짓점을 갖는 직육면체를 생성합니다.
// 가로는 w, 세로는 d, 높이는 h입니다.
var createCuboid = function(x, y, z, w, h, d) {
   var nodes = [];
   var edges = [];
   var shape = { 'nodes': nodes, 'edges': edges };
   return shape;
};
직육면체를 만드는 데 해당 함수을 사용하면 다음과 같이 첫 번째 노드에 접근할 수 있습니다.
var object = createCuboid(0, 0, 0, 100, 160, 50);
var node0 = shape.nodes[0];
이것은 node0nodes 배열의 첫 번째 값으로 설정하는 것입니다. 그러나 이 때 노드나 엣지 배열에는 주어진 값이 없습니다.
위치와 그에 대응하는 크기의 모든 가능한 조합으로 노드를 정의합니다. 엣지는 전과 동일하게 정의합니다.(다만, 각각의 엣지를 개별적으로 정의하지 않고 한번에 다 정의하는 것이 다릅니다.) 이 함수를 이용하면 입방체의 크기를 음수로 정의할 수도 있습니다.
var createCuboid = function(x, y, z, w, h, d) {
   var nodes = [[x, y, z ], [x, y, z+d], [x, y+h, z ], [x, y+h, z+d], [x+w, y, z ], [x+w, y, z+d], [x+w, y+h, z ], [x+w, y+h, z+d]];

   var edges = [[0, 1], [1, 3], [3, 2], [2, 0], [4, 5], [5, 7], [7, 6], [6, 4], [0, 4], [1, 5], [2, 6], [3, 7]];

   return { 'nodes': nodes, 'edges': edges};
};
이제 다음과 같이 한 노드가 원점에 위치한 폭이 100이고 높이가 160이고 깊이가 50인 직육면체를 만들 수 있습니다.
var shape = createCuboid(0, 0, 0, 100, 160, 50);
이전 코드는 전역 변수인 nodesedges를 참조하기 때문에 객체의 프로퍼티를 이와 같은 전역 변수에 저장해야 합니다.
var nodes = shape.nodes; var edges = shape.edges;
아래에 전체 코드를 볼 수 있습니다.

여러 도형 만들기

서로 다른 크기를 갖는 도형을 만들 수 있습니다. 그런데 도형을 한 개 이상 만들고 싶으면 어떻게 해야 할까요? 사물의 개수를 변수로 고려하고 싶을 때는 배열이 유용합니다. 도형에 관한 배열를 생성합시다.
var shape1 = createCuboid(-120, -20, -20, 240, 40, 40);
var shape2 = createCuboid(-120, -50, -30, -20, 100, 60);
var shape3 = createCuboid( 120, -50, -30, 20, 100, 60);
var shapes = [shape1, shape2, shape3];
이제 객체의 배열에 적용하는 출력(display) 함수와 회전(rotate) 함수를 변경하면 됩니다. 우선 모든 모양에 대해 반복문을 적용하는 for문에 엣지를 출력하는 코드를 묶습니다.
// 엣지를 그립니다.
stroke(edgeColor);
for (var shapeNum = 0; shapeNum < shapes.length; shapeNum++) {
   var nodes = shapes[shapeNum].nodes;
   var edges = shapes[shapeNum].edges;
   for (var e = 0; e < edges.length; e++) {
      var n0 = edges[e][0];
      var n1 = edges[e][1];
      var node0 = nodes[n0];
      var node1 = nodes[n1];
      line(node0[0], node0[1], node1[0], node1[1]);
   }
}
그리고 노드를 출력하기 위해서도 비슷하게 for문을 적용합니다.
// 노드를 그립니다.
fill(nodeColor);
noStroke();
for (var shapeNum = 0; shapeNum < shapes.length; shapeNum++) {
   var nodes = shapes[shapeNum].nodes;
   for (var n = 0; n < nodes.length; n++) {
      var node = nodes[n]; ellipse(node[0], node[1], nodeSize, nodeSize);
   }
}
비슷한 for 순환문을 회전(rotation) 함수 각각에 추가할 수도 있지만 각 함수에 노드 배열을 입력하는 것이 훨씬 유연한 코딩이 될 것 같습니다. 그렇게 하면 모양이 서로 독립적으로 회전할 수 있습니다. 예를 들면 rotateZ3D() 함수는 다음과 같습니다.
var rotateZ3D = function(theta, nodes) { ... };
이제 마우스를 이용하여 회전을 할 때 도형에 대해 반복문을 적용하고 각각에 대한 함수를 호출해야 합니다.
mouseDragged = function() {
   var dx = mouseX - pmouseX;
   var dy = mouseY - pmouseY;
   for (var shapeNum = 0; shapeNum < shapes.length; shapeNum++) {
      var nodes = shapes[shapeNum].nodes;
      rotateY3D(dx, nodes);
      rotateX3D(dy, nodes);
   }
};
임의의 노드에 전달되지 못하는 화전(rotate) 함수의 다른 호출은 꼭 제거해야 합니다. 아래에 완성된 코드를 살펴보세요.