在方形内创建n个角的多边形的算法(HTML5 - Canvas)?

时间:2014-10-29 19:17:29

标签: javascript html5 algorithm canvas polygon

考虑以下设置:

CSS:

div {
    position :absolute;
    top: 50px;
    left: 50px;
    height: 200px;
    width: 200px;
    border: 2px solid black;
}

HTML:

<div id="container">
    <canvas id="my-canvas"/>
</div>

现在在#my-canvas里面我希望能够绘制一个多边形,其中任意数量的角始终位于其一侧(而不是其中一个角)。多边形将“占用”的高度和宽度应始终等于其父级的高度和宽度(在本例中为200px X 200px),我们将假设它始终为正方形。

是否有标准算法来实现(不使用任何JS库)?

#my-container内绘制一个包含4个角的多边形是最简单的,因为你基本上会创建一个相等大小的正方形:

var c = document.getElementById('my-canvas').getContext('2d');
var side = document.getElementById('container').clientHeight;
c.fillStyle = '#f00';
c.beginPath();
c.moveTo(0, 0);
c.lineTo(0, side);
c.lineTo(side, side);
c.lineTo(side, 0);
c.closePath();
c.fill();

但是有5个角或n个角的多边形呢?

var corners = 5;
var c = document.getElementById('my-canvas').getContext('2d');
var side = (document.getElementById('container').clientHeight * 4) / corners;
c.fillStyle = '#f00';
c.beginPath();
c.moveTo(0, 0);
// ok the following is totally wrong, but I'm sure there is a loop involved and that the x & y
// should increment/decrement each time around in some way, relevant to the value of *side*
for (var i=0; i<corners; i++) {
   c.lineTo((side*i), side);
} 
c.closePath();
c.fill();

提前感谢您的帮助!

注意:

我只对两边长度相等的多边形感兴趣(我想这就是你所说的“常规”多边形?)。

由于我们在HTML / CSS世界中,允许的最小边显然是1px的长度。所以这是唯一的限制。

我所指的多边形类型是this image左下角的多边形。显然他们是“常规凸”类型?道歉,但我不是数学家。

2 个答案:

答案 0 :(得分:3)

这实际上不是一个简单的问题。首先,让我们不那么含糊地说明问题:

  

给定边长 S 的正方形和整数 n ≥3,确定最大长度 L ,使得有规则的凸多边形 n 长度为 L 的边可以构造成:

     
      
  1. 多边形的一边位于正方形的一边;
  2.   
  3. 多边形包含在正方形内。
  4.   

请注意,要求多边形触摸正方形以外的任何位置,而不是位于正方形一侧的一侧。 (虽然对于 n &gt; 3,表明会有其他联系点并不太难。)

一旦边长 L ,那么构造多边形本身就相当容易了。让我们先做这个(因为它对第一部分有帮助)。从简单的trig开始,多边形的中心将是距离

  

h = L /(2⋅tan(π/ n ))

多边形底部中心上方的

(从对称性)也将是方形底部的中心。对于其余部分,我们假设多边形的中心是我们坐标系的原点,+ X轴向右,+ Y轴向上。 (请注意,我们的Y轴与通常的计算机图形坐标系相反。)多边形的半径(从中心到每个顶点的距离)将是

  

r = L /(2⋅sin(π/ n ))

我们打电话给上面的等式1 ;我们将在最后回顾它。顶点将由:

给出
  

x i = r ⋅cos(θ 0 +2⋅ π⋅ i / n
   y i = r ⋅sin(θ 0 +2⋅π⋅< em> i / n

其中θ 0 是确定多边形旋转的“相位角”。我们希望旋转使得多边形的底边是水平的。如果我们想要第一个顶点( i = 0)是多边形底部的右顶点,那么我们应该使用

  

θ 0 =-π/ 2

通过这种替换,我们的坐标方程简化为:

  

x i = r ⋅sin(2⋅π⋅ i / n
   y i = - r ⋅cos(2⋅π⋅ i / n

为了将来参考,以上将是公式2 。现在我们需要确定 L 。我认为,反转问题更容易:对于给定的 L ,最小的 S 是什么,使得边长 S 的正方形可以包含多边形,其中一边包含多边形的一边。然后我们可以扩展所有内容以匹配原始问题的给定平方。但这很容易。让我们使用 L = 1.对于 n 偶数,多边形的直径 d (多边形上点之间的最大距离)是

  

d =2⋅ r = 1 / sin(π/ n )( n 偶数)< / p>

对于 n 奇数,不难看出直径是从基底上的一个顶点到多边形顶部顶点的距离。有一点结构(留给读者)和余弦定律,结果证明是:

  

d = sqrt([1 + cos(π/ n )] / [2·sin 2 (π/ n )])( n 奇数)

在这两种情况下,很容易确定多边形的至少一个直径是水平的。因此,包含多边形的最小正方形的一侧(多边形底座位于正方形的底部)必须至少为直径,并且(根据多边形直径的定义)正方形不必更大

由于我们想要扩展一切,以便方块的一侧测量给定数量 S ,我们最终得到:

  

L = S / d

因此,构造多边形的算法首先确定 L ,如我们刚刚描述的那样,然后使用公式1计算 r ,最后使用公式2计算顶点,这些顶点应按顺序连接以形成多边形。

答案 1 :(得分:3)

简单..

  • 将圆分成x个边,360 / x =边角
  • 计算每个形状点
  • 计算脸部的角度
  • 旋转形状,使脸部平放入容器
  • 重新缩放点以适应边界

任何疑惑..只需检查下面的代码

//load data
var c = document.getElementById('canvas').getContext('2d');
var width = document.getElementById('canvas').clientWidth;
var height = document.getElementById('canvas').clientHeight;
var corners = 5;
//initial calculation
var radius = 1;
var angle = (Math.PI * 2) / corners;

//build points 
var points = [];
for (var i=0; i<corners; i++)
{
    var a = angle * i;
    //sin and cos are swithced,point 0 is bottom one
    var x = (Math.sin(a)*radius);
    var y = (Math.cos(a)*radius);
    points.push({
        x:x
        ,y:y
    })
} 
//get the angle of a side
var sideangle = Math.atan2(points[1].y-points[0].y, points[1].x-points[0].x)
//rotate point around bottom one
var o = points[0];
for (var i=1; i<points.length; i++)
{
    points[i] = {
        x:Math.cos(sideangle) * (points[i].x - o.x) - Math.sin(sideangle) * (points[i].y-o.y) + o.x
        ,y:Math.sin(sideangle) * (points[i].x - o.x) + Math.cos(sideangle) * (points[i].y - o.y) + o.y
    };
}
//by this point the figure is "flat on the floor" lets measure its size
var rect = {top:2,left:2,right:-2,bottom:-2};
for (var i=0; i<points.length; i++)
{
    rect.top = Math.min(rect.top,points[i].y);
    rect.bottom = Math.max(rect.bottom,points[i].y);
    rect.left = Math.min(rect.left,points[i].x);
    rect.right = Math.max(rect.right,points[i].x);
}
rect.width = Math.abs(rect.right - rect.left);
rect.height = Math.abs(rect.bottom - rect.top);
//make points relative to top left of rect
for (var i=0; i<points.length; i++)
{
    points[i] = {
        x: points[i].x - rect.left
        ,y: points[i].y - rect.top
    };
}
//lets scale and position the poly based on its rect
var ratioX = width / rect.width
var ratioY = height / rect.height
var ratio = Math.min(ratioX, ratioY);

for (var i=0; i<points.length; i++)
{
    points[i] = {
        x: (points[i].x * ratio)
        ,y: (points[i].y * ratio)
    };
}

//draw path
c.beginPath();

c.moveTo(points[0].x, points[0].y);
for (var i=1; i<points.length; i++)
    c.lineTo(points[i].x, points[i].y);
c.closePath();
//draw
c.strokeStyle='#f00'
c.stroke();
c.fillStyle = '#f00';
//c.fill();
canvas{
    position :absolute;
    top: 50px;
    left: 50px;
    border: 2px solid black;
}
<!--size must be defined as attribute-->
<canvas width='300' height='200' id="canvas"/>