user2272048
user2272048

Reputation:

Algorithm creating polygon of n corners inside a square (HTML5 - Canvas)?

Consider the following setup :

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>

Now inside #my-canvas I want to be able to draw a polygon with any number of corners that always sits on one of its sides (and not one of its corners). The height and width that the polygon will "take up" should always be equal to that of its parent (in this case 200px X 200px) which we will assume that it's always going to be a square.

Is there a standard algorithm to achieve that (without using any JS libraries)?

Drawing a polygon with 4 corners inside #my-container is the easiest I guess as you will basically be creating a square of equal size :

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();

But what about a polygon with 5 corners or n corners ?

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();

Thank you in advance for any help!

NOTE :

I am only interested in polygons whose sides are of equal length (I guess this is what you'd call a "regular" polygon?).

Since we're in the HTML/CSS universe, the smallest side allowed will obviously be that of a length of 1px. So this is the only restriction.

The type of polygons I'm referring to are the ones found at the bottom left of this image. Apparently they are of type "regular convex" ? Apologies but I'm not a mathematician.

Upvotes: 2

Views: 2004

Answers (2)

CaldasGSM
CaldasGSM

Reputation: 3052

Simple..

  • divide a circle into x amount of sides, 360 / x = angle of side
  • calculate each point of shape
  • calculate the angle of a face
  • rotate shape so face is flat into the container
  • re-scale points to fit boundaries

any doubts.. just check the code below

//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"/>

Upvotes: 2

Ted Hopp
Ted Hopp

Reputation: 234867

This is actually a not a simple problem. First, let's state the problem less ambiguously:

Given a square of side length S and an integer n ≥ 3, determine the maximum length L such that a regular, convex polygon with n sides of length L can be constructed so that:

  1. one side of the polygon lies on a side of the square;
  2. the polygon is contained within the square.

Note that this does not require that the polygon touch the square anywhere other than on the side that lies on a side of the square. (Although for n > 3, it's not too hard to show that there will be other points of contact.)

Once one has the side length L, it's fairly easy to then construct the polygon itself. Let's do that first (because it will help with the first part). From simple trig, the center of the polygon will be a distance

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

above the center of the polygon's bottom side, which (from symmetry) will also be the center of the bottom side of the square. For the rest of this, let's assume that the center of the polygon is the origin of our coordinate system, with the +X axis to the right and the +Y axis up. (Note that our Y axis is reversed from the usual computer graphics coordinate system.) The radius of the polygon (the distance from the center to each vertex) will be

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

Let's call the above Equation 1; we will refer back to it at the end. The vertices will be given by:

xi = r ⋅ cos(θ0 + 2 ⋅ π ⋅ i / n)
yi = r ⋅ sin(θ0 + 2 ⋅ π ⋅ i / n)

where θ0 is a "phase angle" that determines the rotation of the polygon. We want the rotation to be such that the bottom side of the polygon is horizontal. If we want the first vertex (i = 0) to be the right vertex of the polygon's bottom, then we should use

θ0 = −π / 2

With that substitution, our coordinate equations simplify to:

xi = r ⋅ sin(2 ⋅ π ⋅ i / n)
yi = −r ⋅ cos(2 ⋅ π ⋅ i / n)

For future reference, the above will be Equation 2. Now we need to determine L. It's easier, I think, to invert the problem: for a given L, what is the smallest S such that a square of side length S can contain the polygon with one side containing a side of the polygon. We can then scale everything to match the given square of the original problem. But this is pretty easy. Let's use L = 1. For n even, the diameter d of the polygon (the largest distance between points on the polygon) is

d = 2 ⋅ r = 1 / sin(π / n)      (n even)

For n odd, it's not too hard to see that the diameter is the distance from one vertex on the base to the vertex at the top of the polygon. With a little construction (left to the reader) and the law of cosines, that turns out to be:

d = sqrt([1 + cos(π / n)] / [2 ⋅ sin2(π / n)])      (n odd)

In both cases, it's easy to establish that at least one diameter of the polygon is horizontal. Thus, the side of the smallest square that contains the polygon (with the polygon base resting on the bottom of the square) must be at least the diameter, and (by definition of the diameter of the polygon) the square never has to be larger.

Since we want to scale everything so that the side of the square measures the given quantity S, we finally have:

L = S / d

So the algorithm to construct the polygon is to first determine L as we've just described, then use Equation 1 to compute r, and finally use Equation 2 to compute the vertices, which should be connected sequentially to form the polygon.

Upvotes: 4

Related Questions