캔버스로 볼록포 그리기

캔버스로 볼록한 포를 그립니다.

볼록포란, 위키 에도 있지만 간단하게 말하면 고무를 걸은 것 같은 상태.



할 일


  • 점을 흩어 낸다
  • 최소 볼록 집합을 찾는다
  • 선 그리기

  • 점을 뿌리다



    우선은 랜덤으로 100개의 좌표를 만들고 좌표의 위치에 점을 그립니다.

    좌표를 만들어 그리기
    // canvasを取得
    var canvas = document.getElementById('canvas');
    // canvasを画面サイズにする
    canvas.width = document.documentElement.clientWidth;
    canvas.height = document.documentElement.clientHeight;
    
    // contextを取得
    var context = canvas.getContext('2d');
    
    // 点を入れる配列
    var points = [];
    
    // あまり端に行き過ぎないように乱数を作成
    var margin = 100;
    for (var i = 0; i < 100; i++) {
      points.push({
        x: (Math.random() * (canvas.width - margin * 2) + margin) | 0,
        y: (Math.random() * (canvas.height - margin * 2) + margin) | 0
      });
    }
    
    // 点を描画
    context.fillStyle = '#00f';
    points.forEach(function (point) {
      context.beginPath();
      context.arc(point.x, point.y, 1, 0, Math.PI*2);
      context.fill();
    });
    

    이런 느낌.


    최소 볼록 세트를 찾는다.



    이 부분의 처리는 여러가지 방법이 있을 것 같지만, 이번은 다음과 같이 하여 볼록 집합을 요구했다.
  • 100 개의 좌표 중에서 Y 좌표가 가장 작은 좌표를 추출합니다
  • Y 좌표가 같은 것이있는 경우, 그 중에서 X 좌표가 최소인 것을 선택해, 기점으로 한다
  • 원점을 중심으로 다른 99 개의 좌표의 각도를 계산하여 얻은 값이 최소화 된 좌표를 다음 원점으로 설정합니다
  • 3을 첫 번째 시작점에 도달 할 때까지 반복합니다.

    theta를 계산하는 함수는 이런 느낌.

    getTheta
    // 座標同士の角度を計算
    var getTheta = function (point1, point2) {
      var width = point2.x - point1.x,
          height = point2.y - point1.y,
          radian = Math.atan(height / width),
          theta = radian * (180 / Math.PI);
    
      // arctanの返り値は -90 <= 角度 <= 90 なので、
      // 座標の象限によって値の調整を行う
      if (width >= 0 && height >= 0) {
        theta += 0;
      } else if ((width < 0 && height >= 0) || (width < 0 && height < 0)) {
        theta += 180;
      } else {
        theta += 360;
      }
    
      return theta;
    };
    

    100개의 좌표로부터 볼록 집합을 구하는 함수는 이런 느낌.

    getConvexPoints
    var getConvexPoints = function (points) {
    
      var convexPoints = [];
    
      // 一番小さいYを取得
      var minY = points.reduce(function (min, point) {
        return Math.min(min, point.y);
      }, Infinity);
    
      // 一番小さいYを持つ中で、一番小さいXを持つ点を取得
      var C1 = points.filter(function (point) {
        return point.y === minY;
      }).sort(function (a, b) {
        return a.x - b.x;
      })[0];
    
      convexPoints.push(C1);
    
      var Cn = C1;
      // 起点から時計回りに次の点を起点を走査していく
      while (1) {
        var minTheta = Infinity,
            cLength = convexPoints.length,
            borderTheta = cLength === 1 ? 0 : (function () {
              var current = convexPoints[cLength-1],
                  prev = convexPoints[cLength-2];
    
              return getTheta(prev, current)
            })(),
            index = -1;
    
        points.forEach(function (point, i) {
          if (point === Cn || point == null) { return; }
    
          var theta = getTheta(Cn, point);
          if (minTheta >= theta && theta >= borderTheta) {
            minTheta = theta;
            index = i;
          }
        });
    
        Cn = points[index];
    
        if (Cn === C1) { break; }
    
        convexPoints.push(Cn);
    
      };
    
      return convexPoints;
    };
    

    선을 그리다



    마지막으로, 구한 볼록 세트의 좌표를 따라 선을 그립니다.

    선을 그리다
    var convexPoints = getConvexPoints(points);
    context.strokeStyle = '#000';
    context.beginPath();
    convexPoints.forEach(function (point) {
      context.lineTo(point.x, point.y);
    });
    context.closePath();
    context.stroke();
    

    이런 느낌.


    하지만

  • 좋은 웹페이지 즐겨찾기