给定正多边形的一侧,找到剩余的边

时间:2015-04-25 04:07:11

标签: javascript algorithm math geometry turtle-graphics

我们得到了:

  • 正多边形的边数
  • 由坐标(x1,y1)和(x2,y2)
  • 定义的一侧

我必须找出剩余的坐标。我怎么能这样做?

2 个答案:

答案 0 :(得分:3)

假设我们有turtle通过从n移动到x1, y1来绘制x2, y2边的正多边形的一段。为了绘制剩余的边,我们这样做n-1次:

  • 转过一定角度
  • 向前移动一段距离

我们必须转动的角度是360 / n度。例如,如果我们绘制一个三角形,乌龟必须在每个分段后转120度。如果我们顺时针绘制三角形,我们会从乌龟的当前方向减去120度。如果我们逆时针绘制它,我们会增加120度。

向前移动的距离是第一段的长度。我们可以使用Pythagorean theorem计算细分的长度。在JavaScript中,我们可以实现它:

var dx = x2-x1,
    dy = y2-y1,
    length = Math.sqrt(dx*dx + dy*dy);

乌龟的初始方向与第一个线段的角度相同,我们可以通过取dx/length的反余弦来计算:

var angle = Math.acos(dx/length);
if (dy < 0) {
  angle = 2*Math.PI - angle;
}

为了利用转弯角度和线段长度,我们必须实施以下海龟操作:

  • 设置海龟位置x, y
  • 设置海龟方向angle
  • 以目前的方向向前移动海龟distance单位
  • delta添加到乌龟方向
  • 查询海龟的当前位置x, y

在实现这些操作之后,我们可以编写一个迭代n-1次的循环。在每次迭代中,我们转动并向前移动规定的数量,查询乌龟位置,并打印坐标。

为了实施海龟行动,我们必须存储海龟的位置和方向。这是一个简单的方法:

var turtle = { x: 0, y: 0, angle: 0 };

要将乌龟distance单位向前移动,我们使用基本的三角函数:

turtle.x += Math.cos(turtle.angle) * distance;
turtle.y += Math.sin(turtle.angle) * distance;

请注意,JavaScript中的三角函数使用弧度而不是度数。圆中有2π弧度,因此π弧度为半圆形。如果我们有一个以弧度表示的角度r,则等效度为r / Math.PI * 180

当我们从乌龟方向添加或减去一个值时,可能会以小于零或大于2π的角度结束。这不会影响我们的三角计算,但它可能使程序难以调试。为了确保角度始终在[0,2π]范围内,我们可以在turtle.angle被修改时执行以下操作:

turtle.angle -= Math.floor(turtle.angle / (2*Math.PI)) * 2*Math.PI;

我编写了一个代码片段来演示乌龟方法。单击下面的蓝色按钮运行代码,然后单击并拖动以绘制多边形的第一个段。您可以通过单击加号和减号来更改边数。

var Polygon = {
  color: {
    axes: '#ccc',
    sides: {
       hover: { plain: '#dddfa4', special: '#9d9c64' },
       final: { plain: '#b0c598', special: '#4f7337' }
    }
  }
};

Polygon.turtle = { x: 0, y: 0, angle: 0 };
Polygon.turtle.setPosition = function (x, y) {
  var g = Polygon,
      turtle = g.turtle,
      context = g.context,
      origin = g.origin;
  turtle.x = x;
  turtle.y = y;
  context.moveTo(origin.left + turtle.x, origin.top - turtle.y);
};
Polygon.turtle.setAngle = function (angle) {
  var g = Polygon,
      turtle = g.turtle;
  turtle.angle = angle;
};
Polygon.turtle.left = function (delta) {
  var g = Polygon,
      turtle = g.turtle;
  turtle.angle = g.normalizeAngle(turtle.angle + delta);
};
Polygon.turtle.right = function (delta) {
  var g = Polygon,
      turtle = g.turtle;
  turtle.angle = g.normalizeAngle(turtle.angle - delta);
};     
Polygon.normalizeAngle = function (angle) {
  angle -= Math.floor(angle / (2*Math.PI)) * 2*Math.PI;
  return angle;
};
Polygon.turtle.forward = function (distance) {
  var g = Polygon, 
      turtle = g.turtle,
      canvas = g.canvas,
      context = g.context,
      origin = g.origin;
  turtle.x += Math.cos(turtle.angle) * distance;
  turtle.y += Math.sin(turtle.angle) * distance;
  context.lineTo(origin.left + turtle.x, origin.top - turtle.y);
};

Polygon.resizeCanvas = function() {
  var g = Polygon,
      canvas = g.canvas,
      context = g.context,
      width = canvas.width = window.innerWidth,
      height = canvas.height = window.innerHeight;
  g.origin = { left: Math.floor(width/2), top: Math.floor(height/2) };
  g.drawAxes();
};
Polygon.drawAxes = function() {
  var g = Polygon,
      canvas = g.canvas,
      context = g.context,
      origin = g.origin,
      color = g.color;
  context.lineWidth = 2; 
  context.strokeStyle = color.axes;
  context.beginPath();
  context.moveTo(origin.left, 0);
  context.lineTo(origin.left, canvas.height);
  context.moveTo(0, origin.top);
  context.lineTo(canvas.width, origin.top);
  context.stroke();
};
Polygon.drawPolygon = function (situation) {
  var g = Polygon,
      canvas = g.canvas,
      context = g.context,
      turtle = g.turtle,
      color = g.color,
      n = parseInt(document.getElementById('numSides').innerHTML, 10),
      turn = 2*Math.PI / n,
      x1 = g.x1, y1 = g.y1, x2 = g.x2, y2 = g.y2,
      dx = x2-x1,
      dy = y2-y1,
      length = Math.sqrt(dx*dx + dy*dy);
  var angle = Math.acos(dx/length);
  if (dy < 0) {
    angle = 2*Math.PI - angle;
  }
  context.clearRect(0, 0, canvas.width, canvas.height);
  g.drawAxes();
  context.lineWidth = 4;
  context.lineCap = 'round';
  context.beginPath();
  context.strokeStyle = color.sides[situation].plain;
  turtle.setPosition(x1, y1);
  turtle.setAngle(angle);
  for (var i = 0; i < n; ++i) {
    turtle.forward(length);
    turtle.left(turn);
  }
  context.closePath();
  context.stroke();
  context.strokeStyle = color.sides[situation].special;
  context.beginPath();
  turtle.setPosition(x1, y1);
  turtle.forward(length);
  context.stroke();
}
Polygon.load = function () {
  var g = Polygon,
      canvas = g.canvas = document.getElementById('surface'),
      context = g.context = canvas.getContext('2d'),
      display = { begin: document.getElementById('begin'),
                  end: document.getElementById('end') },
      color = g.color;
  g.resizeCanvas();
  window.onresize = g.resizeCanvas;
  function makeUnselectable(element) {
    element.className += ' unselectable';
    element.ondragstart = element.onselectstart = function (event) {
      event.preventDefault();
    };
  }
  makeUnselectable(canvas);
  var numSides = document.getElementById('numSides'),
      minus = document.getElementById('minus'),
      plus = document.getElementById('plus');
  minus.onmousedown = function () {
    var current = parseInt(numSides.innerHTML, 10);
    if (current == 3) {
      return;
    }
    numSides.innerHTML = current-1;
    g.drawPolygon('final');
  };
  plus.onmousedown = function () {
    var current = parseInt(numSides.innerHTML, 10);
    if (current == 20) {
      return;
    }
    numSides.innerHTML = current+1;
    g.drawPolygon('final');
  };
  var controls = [display.begin, display.end, numSides, minus, plus,
                  document.getElementById('options')];
  for (var i = 0; i < controls.length; ++i) {
    makeUnselectable(controls[i]);
  }
  var getPosition = function (event) {
    event = event || window.event;
    var rect = canvas.getBoundingClientRect(),
        left = event.clientX - rect.left,
        top = event.clientY - rect.top,
        origin = g.origin,
        x = left - origin.left,
        y = origin.top - top;
    return { x: x, y: y };
  };
  canvas.onmousedown = function (event) {
    document.body.style.cursor = 'default';
    var position = getPosition(event);
    g.x1 = g.x2 = position.x;
    g.y1 = g.y2 = position.y;
    display.begin.innerHTML =
        '<span class="label">x1, y1 =</span> '+g.x1+', '+g.y1;
    display.end.innerHTML = '';
    g.drawPolygon('hover');
    for (var i = 0; i < controls.length; ++i) {
      controls[i].style.zIndex = -10;
    }
    canvas.onmousemove = function (event) {
      var position = getPosition(event);
      g.x2 = position.x;
      g.y2 = position.y;
      display.end.innerHTML =
          '<span class="label">x2, y2 =</span> '+g.x2+', '+g.y2;
      g.drawPolygon('hover');
    };
  };
  function noop() {
  }
  canvas.onmousemove = noop;
  canvas.onmouseup = canvas.onmouseout = function (event) {
    if (canvas.onmousemove === noop) {
      return;
    }
    canvas.onmousemove = noop;
    g.drawPolygon('final');
    for (var i = 0; i < controls.length; ++i) {
      controls[i].style.zIndex = 0;
    }
  };
};
window.onload = Polygon.load;
body {
  margin: 0;
  padding: 0;
  overflow: hidden;
}
.unselectable {
  -webkit-user-select: none;
  -khtml-user-drag: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -moz-user-select: -moz-none;
  -ms-user-select: none;
  user-select: none;
}
canvas {
  width: 100%;
  height: 100%;
}
.display {
  color: #444;
  position: fixed;
  left: 40px;
  font-family: sans-serif;
  font-size: 20px;
}
.label {
  color: #aaa;
}
#begin {
  top: 20px;
}
#end {
  top: 60px;
}
#options {
  position: fixed;
  left: 40px;
  top: 100px;
  font-family: sans-serif;
  font-size: 28px;
}
#options div {
  display: inline;
}
#options .button {
  font-size: 32px;
  cursor: pointer;
}
#options .button:hover {
  color: #55838e;
}
#options .button, #numSides {
  padding: 0 5px;
}
#numSides {
  cursor: default;
}
<div class="display" id="begin"></div>
<div class="display" id="end"></div>

<div id="options">
  <div class="button" id="minus">&minus;</div><div
       id="numSides">6</div><div
       class="button" id="plus">&plus;</div>
</div>

<canvas id="surface"></canvas>

答案 1 :(得分:2)

如果边数是N而多边形边是由坐标(x1,y1)和(x2,y2)定义的,那么这边的中点是

(mx, my) = ((x1+x2)/2, (y1+y2)/2)

垂直向量(左侧为逆时针顺序)是

(px, py) = (-(y2-y1), x2-x1)

单位垂直向量

(nx, ny) = (px/Len, py/Len), where Len = Sqrt(px*px + py*py)

apothem length

  ApoLen = Len / (2*Tan(Pi/N))

apothem vector

(ax, ay) = ApoLen * (nx, ny) = (px / (2 * Tan(Pi/N)), py / (2 * Tan(Pi/N)))

多边形中心坐标是

(cx, cy) = (mx, my) + (ax, ay)

外接圆半径

  R = ApoLen / Cos(Pi/N)

顶点(x1,x2)的起始角度为

  BaseAngle = ArcTan2(y1 - cy, x1 - cy)

和ith vertice(从一开始编号)具有角度

 Ai = BaseAngle + (i - 1) * 2 * Pi / N

和坐标

  (xi, yi) = (cx + R * Cos(Ai), cy + R * Sin(Ai))