Hello. I’d like to share my experience on how to make an outer stroke at a polygon. The problem is that you have a polygon that you want to trace with an outer stroke and the stroke may not touch the faces of the polygon. This article breaks down the stroke algorithm and provides js code to prove how the algorithm works in practice. As a result of the algorithm, you should get the following multi-colored stroke for the polygon:

## Theory

A polygon is a closed shape consisting of vertices and edges. To draw a polygon stroke, you need to find points that are equidistant from the vertices of the polygon and connect them with lines. Linear algebra will help us find points. To demonstrate the algorithm for finding stroke points near a polygon, let’s imagine an arbitrary polygon:

In the algorithm, we find the normalized distance vectors for each vertex. A distance vector is a vector that, when added to the vertex of a polygon, will produce a stroke point. To find a distance vector, you need to take two vertex edges and represent them as normalized vectors. The direction of these vectors must be coming from the vertex. When the normalized vectors are added, the distance vector is obtained. This vector will originate from the vertex at the angle of the bisector, since normalization produces two vectors of the same length, when added together, the resulting vector will exit the vertex and divide the angle in half. The resulting distance vector will need to be normalized to ensure that the path is traced evenly. To demonstrate the steps involved in finding a normalized distance vector, the steps are illustrated in the step-by-step figure below:

The resulting normalized distance vectors may have incorrect directions. Be directed inside or out of the contour:

When adding vectors to polygon vertices, the resulting stroke points can be inside or outside the path. And as a result, you get the wrong stroke. To avoid this, you first need to determine how the polygon points traverse the path clockwise or counterclockwise. This will allow you to stroke either inside or outside the polygon. To determine the direction of the polygon’s contour, find its leftmost vertex by the x-coordinate and represent the two edges of the vertex as vectors. Vectors are directed after the following points on the path. Perform vector multiplication on the vectors. The symbol of the resulting multiplication result will show how the path is set. If the number is positive, then counterclockwise, if negative, clockwise:

Now it remains to determine the direction of the normalized distance vectors. To do this, you need to take two edges directed in the direction of the contour bypass at each vertex and multiply them vectorically. From the result, we recognize the arithmetic sign. The arithmetic sign will show whether the distance vector changes direction or remains the same. Let’s say that the polygon points define the contour counterclockwise. Then, if the sign of the result of vector multiplication is positive, then the direction of the distance vector changes, if it is negative, then the direction remains the same. To change the direction of the vector, it is enough to multiply its coordinates by -1. If the polygon points define the contour clockwise, the algorithm for determining the direction of the distance vector changes. For a positive multiplication result, the vector does not change direction, but for a negative one, it does.

The resulting directional vectors should be added to the vertices of the polygon to get the stroke points:

## Practice

The performance of the algorithm will be given in js code. I won’t be unsubstantiated and will give the code with comments:

```
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<canvas id="polygon"></canvas>
</body>
<!-- входные данные -->
<script>
const points = [
{x: 0.36229, y: 0.64789},
{x: 0.51492, y: 0.58590},
{x: 0.47563, y: 0.76014},
{x: 0.44950, y: 0.73590},
{x: 0.46292, y: 0.83392},
{x: 0.52162, y:0.75190},
{x: 0.48531, y:0.76230},
{x: 0.57529, y: 0.53189},
{x: 0.41261, y: 0.59189},
{x: 0.53001, y: 0.32786},
{x: 0.47131, y: 0.32986},
{x: 0.36229, y: 0.64789}
];
const canvas = document.getElementById('polygon');
const ctx = canvas.getContext('2d')
const size = 300;
canvas.width = size;
canvas.height = size;
</script>
<!-- рисуем обводку -->
<script>
//функция нормализация вектора
const doNorm = (vec) => {
const len = Math.sqrt(vec.x * vec.x + vec.y * vec.y)
return {x: vec.x / len, y: vec.y / len}
}
//функция возращает соседние индексы вершин у выбранного индеска
const getNeighboringIndices = (selectIndex, maxIndex) => {
if (selectIndex === 0) {
selectIndex = maxIndex
}
const lastIndex = selectIndex + 1
return {
beginIndex: selectIndex - 1,
middleIndex: selectIndex,
lastIndex: lastIndex > maxIndex ? 1 : lastIndex
}
}
//узнаем направление обхода контура
const indexOnMinX = points.reduce((res, point, index, array) => { //Узнаем индекс c самым маленьким x
if(points[res].x > point.x) {
return index
}
return res
}, 0)
const inds = getNeighboringIndices(indexOnMinX, points.length - 1)
const v1 = {x: points[inds.middleIndex].x - points[inds.beginIndex].x, y: points[inds.middleIndex].y - points[inds.beginIndex].y}
const v2 = {x: points[inds.lastIndex].x - points[inds.middleIndex].x, y: points[inds.lastIndex].y - points[inds.middleIndex].y}
const multiVec = v1.x * v2.y - v2.x * v1.y //Векторное произведение
const isClockwise = multiVec < 0
//суммирование веторов, вычисление нормализованного вектора отстояния, получение точек отстояния от полигона
const borderPoints = []
for (let i = 0; i < points.length; ++i) {
//получение направлющий ветора с длинной
const inds = getNeighboringIndices(i, points.length - 1)
const v1_norm = doNorm( {x: points[inds.beginIndex].x - points[inds.middleIndex].x, y: points[inds.beginIndex].y - points[inds.middleIndex].y} )
const v2_norm = doNorm ( {x: points[inds.lastIndex].x - points[inds.middleIndex].x, y: points[inds.lastIndex].y - points[inds.middleIndex].y })
const resVec_norm = doNorm( {x: v1_norm.x + v2_norm.x, y: v1_norm.y + v2_norm.y} )
const k = 0.05 //коэффициент увеличивает длину нормализованного вектора отстояния
const resVec = { x: resVec_norm.x * k, y: resVec_norm.y * k }
//Расчет направления для направлюящего вектора
v1_norm.x = -v1_norm.x; v1_norm.y = -v1_norm.y //меняем знак у вектора v1, чтобы v1 и v2 были последовательно направлены по контуру
const multiVec = v1_norm.x * v2_norm.y - v2_norm.x * v1_norm.y //векторное произведение
if (isClockwise && multiVec < 0) {
resVec.x = -resVec.x; resVec.y = -resVec.y
}
if(!isClockwise && multiVec > 0) {
resVec.x = -resVec.x; resVec.y = -resVec.y
}
//добавляем в массив точки обводки
borderPoints.push( {x: points[i].x + resVec.x, y: points[i].y + resVec.y } )
}
ctx.beginPath();
ctx.moveTo( size * borderPoints[0].x, size * borderPoints[0].y)
for (let i = 1; i < borderPoints.length; ++i) {
ctx.lineTo(size * borderPoints[i].x, size * borderPoints[i].y)
}
ctx.fillStyle="yellow";
ctx.fill();
ctx.strokeStyle = "red";
ctx.lineWidth = 3;
ctx.stroke();
</script>
<!-- рисуем изначальную фигуру -->
<script>
ctx.beginPath();
ctx.moveTo( size * points[0].x, size * points[0].y)
for (let i = 1; i < points.length; ++i) {
ctx.lineTo(size * points[i].x, size * points[i].y)
}
ctx.fillStyle = "blue";
ctx.fill()
</script>
</html>
```

Thus, the algorithm is divided into several stages. In the first step, you need to find the distance vectors at each vertex of the polygon. In the second, define how the polygon points are defined, the path traversal is either clockwise or counterclockwise. And in the last step, if necessary, change the direction of the found distance vectors and add them to each vertex to which it belongs.

———-

### Acknowledgment and Usage Notice

The editorial team at TechBurst Magazine acknowledges the invaluable contribution of Александр the author of the original article that forms the foundation of our publication. We sincerely appreciate the author’s work. All images in this publication are sourced directly from the original article, where a reference to the author’s profile is provided as well. This publication respects the author’s rights and enhances the visibility of their original work. If there are any concerns or the author wishes to discuss this matter further, we welcome an open dialogue to address potential issues and find an amicable resolution. Feel free to contact us through the ‘Contact Us’ section; the link is available in the website footer.