filling shapes with patterns in CImg - polygon

I want to be able to draw shapes and flood-fill them with various fill patterns (diagonal lines, stipple dots, etc).
The CImg library includes functions for drawing various shapes with an arbitrary line pattern for the outline. But I don't see anything about fill pattern.
I think this can probably be done using a bitwise or mathematical operator to mask the pattern onto a solid image, but I'd like to see the specific code for doing it.

Yes, polygon fill patterns can be achieved by first drawing the polygon in a solid color and then using the &= operator with a pre-loaded black-and-white pattern image.
// preload several pattern images (black and white versions of the desired fill patterns)
CImg<unsigned char> *fillPatternImages[ NumFillPatterns ] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
fillPatternImages[ 0 ] = new CImg<unsigned char>( "256x256_bw_dotted_fill.png" );
... etc. for all patterns you want to use
// create an empty image
CImg<unsigned char> image( 256, 256, 1, 4, 0 );
// draw the polygon (or in the case of my code, any number of polygons) on the image in a solid color
if ( nShapeType == SHPT_POLYGON && fillPattern != FILL_PATTERN_NONE )
{
for( int i = 0 ; i < nShapeCount ; i++ )
{
SHPObject *psShape;
psShape = SHPReadObject( hSHP, panHits[ i ] );
for ( int part = 0 ; part < psShape->nParts ; part++ )
{
int numPoints;
if ( part < ( psShape->nParts - 1 ) )
{
numPoints = psShape->panPartStart[ part + 1 ] - psShape->panPartStart[ part ];
}
else
{
numPoints = psShape->nVertices - psShape->panPartStart[ part ];
}
CImg<int> pointImage( numPoints, 2, 1, 1, 0 );
int s = psShape->panPartStart[ part ];
for ( int p = 0 ; p < numPoints ; p++ )
{
int screenX;
int screenY;
GetTileXYFromMercatorLonLat( (float)psShape->padfX[ s + p ], (float)psShape->padfY[ s + p ], x, y, z, &screenX, &screenY );
pointImage( p, 0 ) = screenX;
pointImage( p, 1 ) = screenY;
}
image.draw_polygon( pointImage, fillColor );
}
SHPDestroyObject( psShape );
}
}
// to achieve a non-solid pattern, & the image with a pre-loaded pattern image
if ( fillPattern > -1 )
{
image &= *fillPatternImages[ fillPattern ];
}

Related

QCustomPlot: Plotting 2 Colormaps - Axes and Ticks Dissappear

I want to plot two colormaps. I want to plot them into the same window but into different axisrects. On top of each other. What happens is that the first color map is plotted nicely, but the second one is overlapping most of the axes and ticks and looks just awful.
Here is a minimal working example:
QCustomPlot * pCstmPlt = new QCustomPlot;
pCstmPlt->show();
QCPAxisRect *axisRect = pCstmPlt->axisRect(0);
axisRect->setupFullAxesBox(true);
QCPColorMap *colorMap = new QCPColorMap(axisRect->axis(QCPAxis::atBottom),axisRect->axis(QCPAxis::atLeft));
int Clrny = 100;
int Clrnx = 100;
colorMap->data()->setSize(Clrnx, Clrny);
colorMap->data()->setRange( QCPRange( 1,100 ), QCPRange( 1,100 ) );
double x, y, dVal;
for (int xIndex=0; xIndex<100; ++xIndex)
{
for (int yIndex=0; yIndex<100; ++yIndex)
{
colorMap->data()->cellToCoord(xIndex, yIndex, &x, &y);
dVal = ( xIndex * yIndex );
colorMap->data()->setCell(xIndex, yIndex, dVal);
}
}
// add a color scale:
QCPColorScale *pmycolorScale = new QCPColorScale(pCstmPlt);
pCstmPlt->plotLayout()->addElement(0, 1, pmycolorScale);
pmycolorScale->setType(QCPAxis::atRight);
colorMap->setColorScale(pmycolorScale);
colorMap->setGradient( QCPColorGradient::gpPolar );
axisRect = new QCPAxisRect(pCstmPlt,true);
axisRect->setupFullAxesBox(true);
pCstmPlt->plotLayout()->addElement(1,0,axisRect);
QCPColorMap *colorMap2 = new QCPColorMap(axisRect->axis(QCPAxis::atBottom),axisRect->axis(QCPAxis::atLeft));
colorMap2->data()->setSize(Clrnx, Clrny);
colorMap2->data()->setRange( QCPRange( 1,100 ), QCPRange( 1,100 ) );
for (int xIndex=0; xIndex<100; ++xIndex)
{
for (int yIndex=0; yIndex<100; ++yIndex)
{
colorMap2->data()->cellToCoord(xIndex, yIndex, &x, &y);
dVal = ( xIndex * yIndex );
colorMap2->data()->setCell(xIndex, yIndex, dVal);
}
}
// add a color scale:
QCPColorScale *pmycolorScale2 = new QCPColorScale(pCstmPlt);
pCstmPlt->plotLayout()->addElement(1, 1, pmycolorScale2);
pmycolorScale2->setType(QCPAxis::atRight);
colorMap2->setColorScale(pmycolorScale2);
colorMap2->setGradient( QCPColorGradient::gpPolar );
colorMap->rescaleDataRange();
colorMap2->rescaleDataRange();
pCstmPlt->rescaleAxes();
pCstmPlt->setMinimumSize(QSize(500,500));
The result looks like this:
So, clearly, the lower plot misses ticks and axes.
In case any one else will be wondering about this: The question was answered somewhere else,
https://www.qcustomplot.com/index.php/support/forum/2269
The trick was to put all axes on the "axes" layer with
axis->setLayer("axes");

How do I sample isampler3d in webgl2?

two question. first off, how could I set a particular value in a 3d texture to 1, lets say the y coordinate of the element at index 1,1,1 in the following Int16Array so I could later read it. I think it'd go something like this:
var data = new Int16Array(size * size * size);
data.fill(0);
// ??? (somehow I'd set values of the data array at index 1,1,1 but I'm unsure how)
data ??? = 1;
gl.texImage3D(
gl.TEXTURE_3D,
0,
gl.R16I,
size,
size,
size,
0,
gl.RED_INTEGER,
gl.SHORT,
data);
secondly, later in my fragment shader, how could I grab that value using the GLSL texture function. I think it'd go something like this:
uniform isampler3d t_sampler;
...
ivec4 value = texture( t_sampler , vec3( 1.0 , 1.0 , 1.0 ) );
if( value.y == 1 ){
// do some special stuff
}
any help would be appreciated. again I'm just trying to create my texture using a data array I create and then read that value in the frag shader.
fyi this code is running but failing to get to the "do some special stuff" part.
thanks
// ??? (somehow I'd set values of the data array at index 1,1,1 but I'm unsure how)
data ??? = 1;
const width = ??
const height = ??
const depth = ??
const numChannels = 1; // 1 for RED, 2 for RG, 3 for RGB, 4 for RGBA
const sliceSize = width * height * numChannels;
const rowSize = width * numChannels;
const x = 1;
const y = 1;
const z = 1;
const offset = z * sliceSize + y * rowSize + x;
data[offset] = redValue;
If there are more channels, for example RGBA then
data[offset + 0] = redValue;
data[offset + 1] = greenValue;
data[offset + 2] = blueValue;
data[offset + 3] = alphaValue;
how could I grab that value using the GLSL texture function
To get a specific value from a texture you can use texelFetch with pixel/texel coordinates.
uniform isampler3d t_sampler;
...
int x = 1;
int y = 1;
int z = 1;
int mipLevel = 0;
ivec4 value = texelFetch(t_sampler, ivec3(x, y, z), mipLevel);
if( value.y == 1 ){
// do some special stuff
}
Be sure to check the JavaScript console for errors. In your case you probably need to set filtering to NEAREST since you're not providing mips and since integer textures can not be filtered.

Convert #hex to 0xFF-hex

Why 0xFF0000ff is red, and #0000ff is blue? And how do I convert #0000ff into 0x, so it would work properly? I tried to add 0xFF in the beginning, but it results in unexpected (by me) behaviour
I am trying to implement this algorithm http://jsfiddle.net/greggman/wpfd8he1/
function getPixel(pixelData, x, y) {
if (x < 0 || y < 0 || x >= pixelData.width || y >= pixelData.height) {
return -1; // impossible color
} else {
return pixelData.data[y * pixelData.width + x];
}
}
function floodFill(ctx, x, y, fillColor) {
// read the pixels in the canvas
const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
// make a Uint32Array view on the pixels so we can manipulate pixels
// one 32bit value at a time instead of as 4 bytes per pixel
const pixelData = {
width: imageData.width,
height: imageData.height,
data: new Uint32Array(imageData.data.buffer),
};
// get the color we're filling
const targetColor = getPixel(pixelData, x, y);
// check we are actually filling a different color
if (targetColor !== fillColor) {
const pixelsToCheck = [x, y];
while (pixelsToCheck.length > 0) {
const y = pixelsToCheck.pop();
const x = pixelsToCheck.pop();
const currentColor = getPixel(pixelData, x, y);
if (currentColor === targetColor) {
pixelData.data[y * pixelData.width + x] = fillColor;
pixelsToCheck.push(x + 1, y);
pixelsToCheck.push(x - 1, y);
pixelsToCheck.push(x, y + 1);
pixelsToCheck.push(x, y - 1);
}
}
// put the data back
ctx.putImageData(imageData, 0, 0);
}
}
Hexadecimal colours follow the format [ 0x ] [ red] [ green] [ blue] [ transparency].
Conversely, hex 'codes' follow the format [ # ] [ red] [ green] [ blue] (with no transparency value listed).
Each colour is allocated 2 units between 0 and F. It does not matter whether the unit is lowercase or uppercase.
0xFF0000ff equates to the hex code #FF0000 with ff (solid) transparency. Breaking this down we have [ FF (red) ] [ 00 (green) ] [ 00 (blue) ] -- solid red.
In order to covert any hex code to hexadecimal notation, you simply need to prepend 0x and append the transparency value. Assuming the colour you want to convert is opaque, you simply need to append ff.
For example, to convert #0000ff (blue) to hex, you prepend 0x and append ff, giving you 0x0000ffff.
0x hex colors are reversed from web hex codes. #RRGGBBAA for hex code would be 0xAABBGGRR, where AA is alpha, BB is blue, GG is green, and RR is red.

qwt plot - how to make zoom according to mouse cursor

I used QWT for my project. I used Qwtplotmagnifier for zoom.
I want to zoom in relative to the mouse cursor. Can you Help me?
I had the same problem and I could not find any answer, so here is mine.
Based on this post : Calculating view offset for zooming in at the position of the mouse cursor
In order to implement a GoogleMap-style zoom, you have to inherit from QwtPlotMagnifier and reimplement widgetWheelEvent in order to store the cursor position when a scroll happens, and rescale function, to change the behavior of the zoom.
//widgetWheelEvent method
void CenterMouseMagnifier::widgetWheelEvent(QWheelEvent *wheelEvent)
{
this->cursorPos = wheelEvent->pos();
QwtPlotMagnifier::widgetWheelEvent(wheelEvent);
}
For the rescale method, I used the source code and modified it. You need to use the QwtScaleMap object of the canvas to transform the mouse cursor coordinates into axis coordinates of your plot. And finally, you just need to apply the formula given in the other post.
//rescale method
void CenterMouseMagnifier::rescale(double factor)
{
QwtPlot* plt = plot();
if ( plt == nullptr )
return;
factor = qAbs( factor );
if ( factor == 1.0 || factor == 0.0 )
return;
bool doReplot = false;
const bool autoReplot = plt->autoReplot();
plt->setAutoReplot( false );
for ( int axisId = 0; axisId < QwtPlot::axisCnt; axisId++ )
{
if ( isAxisEnabled( axisId ) )
{
const QwtScaleMap scaleMap = plt->canvasMap( axisId );
double v1 = scaleMap.s1(); //v1 is the bottom value of the axis scale
double v2 = scaleMap.s2(); //v2 is the top value of the axis scale
if ( scaleMap.transformation() )
{
// the coordinate system of the paint device is always linear
v1 = scaleMap.transform( v1 ); // scaleMap.p1()
v2 = scaleMap.transform( v2 ); // scaleMap.p2()
}
double c=0; //represent the position of the cursor in the axis coordinates
if (axisId == QwtPlot::xBottom) //we only work with these two axis
c = scaleMap.invTransform(cursorPos.x());
if (axisId == QwtPlot::yLeft)
c = scaleMap.invTransform(cursorPos.y());
const double center = 0.5 * ( v1 + v2 );
const double width_2 = 0.5 * ( v2 - v1 ) * factor;
const double newCenter = c - factor * (c - center);
v1 = newCenter - width_2;
v2 = newCenter + width_2;
if ( scaleMap.transformation() )
{
v1 = scaleMap.invTransform( v1 );
v2 = scaleMap.invTransform( v2 );
}
plt->setAxisScale( axisId, v1, v2 );
doReplot = true;
}
}
plt->setAutoReplot( autoReplot );
if ( doReplot )
plt->replot();
}
This works fine for me.
Based on this forum post:
bool ParentWidget::eventFilter(QObject *o, QEvent *e)
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(e);
if (mouseEvent->type()==QMouseEvent::MouseButtonPress && ((mouseEvent->buttons() & Qt::LeftButton)==Qt::LeftButton)) //do zoom on a mouse click
{
QRectF widgetRect(mouseEvent->pos().x() - 50, mouseEvent->pos().y() - 50, 100, 100); //build a rectangle around mouse cursor position
const QwtScaleMap xMap = plot->canvasMap(zoom->xAxis());
const QwtScaleMap yMap = plot->canvasMap(zoom->yAxis());
QRectF scaleRect = QRectF(
QPointF(xMap.invTransform(widgetRect.x()), yMap.invTransform(widgetRect.y())),
QPointF(xMap.invTransform(widgetRect.right()), yMap.invTransform(widgetRect.bottom())) ); //translate mouse rectangle to zoom rectangle
zoom->zoom(scaleRect);
}
}

Find the outline of a union of grid-aligned squares

How to get the co-ordinates of the outline shape formed using smaller grid blocks.
For example, If I used 32x32 unit blocks for construct a shape (any shape).
Then how can I get overall co-ordinates of the shape, including the negative spaces.
For example:
One could arrange the blocks like this:
(each block is 32x32 and coordinates refer to bottom left corner of the block)
Block 1 - (0,0)
BLock 2 - (32,0)
Block 3 - 64,0)
Block 4 - (64,32)
Block 5 - (64, 64)
BLock 6 - (32, 64)
BLock 6 - (0 64)
Block 7 - (0, 32)
Now you can see this will create an empty space in the middle.
So what I would like to know is, how to get the coordinates of the above shape such that I get:
Main Block = (0,0), (96,0), (0,96)
Empty space = (32,32), (64,32), (64,64), (32,64)
Is there any mathematical solution to this?
Eventually I will be doing complex shapes.
thanks
******** edit ****
Hi,
How to deal with this condition?
<------------------^<----^
| || |
V------------------>| |
<------^ /^| |
| |<------^ / || |
| || |/ || |
V------>V------>V-->V---->
i would like the result to be like this
<-------------------<----^
| |
V ^-----------> |
| | / |
| <-------^ / |
| |/ |
V------>------->--->----->
Think of each square as an outline comprised of four vectors going in a counter-clockwise chain.
<-----^
| |
| |
V----->
So for all the squares in your shape, take the union of their outline vectors. If two outline vectors in the union are identical but go in opposite directions, they cancel each other out and are removed from the union.
For example, for two squares that are side by side the union is 8 vectors
<-----^<-----^
| || |
| || |
V----->V----->
which reduces to 6 vectors because the two vertical vectors in the middle cancel:
<-----<-----^
| |
| |
V----->----->
For the example you gave, the result of this will be (after cancellations):
<-----<-----<-----^
| |
| |
V ^-----> ^
| | | |
| | | |
V <-----V ^
| |
| |
V----->----->----->
You just have to connect up the vectors in the final reduced union to read off the outline paths. Note that the inner outline ("the hole") runs clockwise.
You would probably need to focus on Boolean Polygon Operations, unions in your case. There are plenty of papers covering the 2D boolean polygon operations and constructive planar geometry, see the Wikipedia: http://en.wikipedia.org/wiki/Boolean_operations_on_polygons.
I know this is very late to the discussion, but I recently had to deal with the same problem, and came up with the following algorithm, described in a somewhat high level here.
First, some terminology. As seen in the picture, we label the top left cell "r0c0" (i.e. row 0 column 0), and the one to the right "r0c1" and so on. We say that the edge to the left of rxcy starts at x,y and goes to x,(y+1) and so on.
The idea is to first find the points at which the outline should change direction.
That is the corners of the shape. I did this by first making a 2d array of numbers where the number was 1 if there was a cell in that place, and 0 otherwise.
Then I looped over the 2d array, and found that the top left point of a cell should be included if either the cell above and the cell to the right were both not there, or if they were both there and the cell above and to the left was not there. Similarly for the three other corners of the cell. We also remember to label the points according to what intercardinal direction they are in their cell (north west, north east and so on)
After this we have a list of points. We then start by finding the top left point of those and traversing. We know to start going right after the top left point. Using the image, we go right from a north west point. We then find the point that has the same y coordinate, but an x-coordinate larger, but the least largest of the points to the right. We also know that the next corner we hit should have an intercardinal direction of north west or north east, as we can't go from north to west by going right.
In the picture we would hit the north east point at r3c6. We then know to go down after that, because going to a north east point from the right means going down afterwards. We continue like this until we can find no more points.
There might still be points left after all this. This means we have disjoint cells, or that there is a hole. So just do the whole thing again.
I get that this wall of text is quite difficult to follow along with, so here is some typescript code, that will hopefully make it a bit simpler (sorry, don't know php). If you have any questions, please reach out. Also, this code can probably be optimized.
The main function is the createOutlines function
type Position = {row: number; column: number};
type Dimensions = { rows: number; columns: number };
type Point = {
x: number;
y: number;
};
type Direction = 'up' | 'left' | 'right' | 'down';
type InterCardinal = 'nw' | 'ne' | 'sw' | 'se';
type OutlinePoint = {
point: Point;
interCardinal: InterCardinal;
};
function findOutlinePoints(
positions: Position[],
dimensions: Dimensions
): OutlinePoint[] {
// Essentially a grid of 1 and undefined where it is 1 if there is a cell in that position
// The JSON.parse(JSON.stringify()) part is just a deep copy, as javascript is quite weird
const matrixOfPoints: (number | undefined)[][] = JSON.parse(JSON.stringify(Array(dimensions.rows).fill([])));
positions.forEach(({ row, column }) => {
matrixOfPoints[row][column] = 1;
});
const points: OutlinePoint[] = [];
for (let rowIndex = 0; rowIndex < dimensions.rows; rowIndex++) {
const row = matrixOfPoints[rowIndex];
if (row.length === 0) {
continue;
}
for (let columnIndex = 0; columnIndex < dimensions.columns; columnIndex++) {
const cell = row[columnIndex];
if (!cell) {
continue;
}
// Find the values of cells around the center cell
const nw = matrixOfPoints[rowIndex - 1]?.[columnIndex - 1];
const n = matrixOfPoints[rowIndex - 1]?.[columnIndex];
const ne = matrixOfPoints[rowIndex - 1]?.[columnIndex + 1];
const w = matrixOfPoints[rowIndex]?.[columnIndex - 1];
const e = matrixOfPoints[rowIndex]?.[columnIndex + 1];
const sw = matrixOfPoints[rowIndex + 1]?.[columnIndex - 1];
const s = matrixOfPoints[rowIndex + 1]?.[columnIndex];
const se = matrixOfPoints[rowIndex + 1]?.[columnIndex + 1];
// Add the points
// Top left point
if ((n == null && w == null) || (n != null && nw == null && w != null)) {
// The north west point of this cell is a corner point, so add this point and specify that it is a north west (nw) point
points.push({
point: { x: columnIndex, y: rowIndex },
interCardinal: 'nw'
});
}
// Top right point
if ((n == null && e == null) || (n != null && ne == null && e != null)) {
points.push({
point: { x: columnIndex + 1, y: rowIndex },
interCardinal: 'ne'
});
}
// Bottom left
if ((w == null && s == null) || (w != null && sw == null && s != null)) {
points.push({
point: { x: columnIndex, y: rowIndex + 1 },
interCardinal: 'sw'
});
}
// Bottom right
if ((e == null && s == null) || (e != null && se == null && s != null)) {
points.push({
point: { x: columnIndex + 1, y: rowIndex + 1 },
interCardinal: 'se'
});
}
}
}
return points;
}
// Finds the point that is left most, and of the left most points, the one that is highest. Also finds the index of that point in the list
function findTopLeftOutlinePoint(
outlinePoints: OutlinePoint[]
): [OutlinePoint | undefined, number] {
let topLeftPoint: OutlinePoint | undefined = undefined;
let index = -1;
outlinePoints.forEach((p, i) => {
if (topLeftPoint == null) {
topLeftPoint = p;
index = i;
return;
}
if (
p.point.x < topLeftPoint.point.x ||
(p.point.x <= topLeftPoint.point.x &&
p.point.y < topLeftPoint.point.y)
) {
index = i;
topLeftPoint = p;
}
});
return [topLeftPoint, index];
}
/** E.g. going, "up", coming to "nw", one has to go "right" */
const NextDirection: Record<Direction, Record<InterCardinal, Direction>> = {
up: {
nw: 'right',
ne: 'left',
sw: 'left',
se: 'right'
},
down: {
nw: 'left',
ne: 'right',
sw: 'right',
se: 'left'
},
right: {
nw: 'up',
ne: 'down',
sw: 'down',
se: 'up'
},
left: {
nw: 'down',
ne: 'up',
sw: 'up',
se: 'down'
}
};
// Given the previous point, and the direction, find the next point from the list of points
function findNextPoint(
previousPointInPath: OutlinePoint,
points: OutlinePoint[],
direction: Direction
): [OutlinePoint, number] | undefined {
// e.g. if coming from nw going right, we should find a point that has the same y coordinates, and has an interCardinal of ne or se
let nextPointIndex: number | undefined;
let nextPoint: OutlinePoint | undefined;
switch (direction) {
case 'right':
// We are going "right"
points.forEach((p, i) => {
if (
// The next point should have the same y coordinate
p.point.y === previousPointInPath.point.y &&
// The next point should have a larger x coordinate
p.point.x > previousPointInPath.point.x &&
// If the previous point is north, then the next point should be north as well. Similar for south
p.interCardinal[0] === previousPointInPath.interCardinal[0]
) {
if (nextPoint == null) {
nextPoint = p;
nextPointIndex = i;
return;
} else if (p.point.x < nextPoint.point.x) {
// This is closer to the previous point than the one we already found
nextPoint = p;
nextPointIndex = i;
return;
}
}
});
break;
case 'left':
points.forEach((p, i) => {
if (
p.point.y === previousPointInPath.point.y &&
p.point.x < previousPointInPath.point.x &&
p.interCardinal[0] === previousPointInPath.interCardinal[0]
) {
if (nextPoint == null) {
nextPoint = p;
nextPointIndex = i;
return;
} else if (p.point.x > nextPoint.point.x) {
nextPoint = p;
nextPointIndex = i;
return;
}
}
});
break;
case 'up':
points.forEach((p, i) => {
if (
p.point.x === previousPointInPath.point.x &&
p.point.y < previousPointInPath.point.y &&
p.interCardinal[1] === previousPointInPath.interCardinal[1]
) {
if (nextPoint == null) {
nextPoint = p;
nextPointIndex = i;
return;
} else if (p.point.y > nextPoint.point.y) {
nextPoint = p;
nextPointIndex = i;
return;
}
}
});
break;
case 'down':
points.forEach((p, i) => {
if (
p.point.x === previousPointInPath.point.x &&
p.point.y > previousPointInPath.point.y &&
p.interCardinal[1] === previousPointInPath.interCardinal[1]
) {
if (nextPoint == null) {
nextPoint = p;
nextPointIndex = i;
return;
} else if (p.point.y < nextPoint.point.y) {
nextPoint = p;
nextPointIndex = i;
return;
}
}
});
break;
}
// If we didn't find anything, we should close the loop
if (nextPoint == null || nextPointIndex == null) return undefined;
return [nextPoint, nextPointIndex];
}
// Find the oultine of cells in a grid.
function createOutlines(
positions: Position[],
dimensions: Dimensions
): OutlinePoint[][] {
const points = findOutlinePoints(positions, dimensions);
const paths: OutlinePoint[][] = [];
while (points.length > 0) {
// This loop creates new outlines until there are no points left
const pathPoints: OutlinePoint[] = [];
const [topLeftPoint, index] = findTopLeftOutlinePoint(points);
if (topLeftPoint == null) return [];
// Remove the top left point
points.splice(index, 1);
// And add it to the path
pathPoints.push(topLeftPoint);
let direction: Direction = 'up';
while (true) {
const previousPointInPath = pathPoints[pathPoints.length - 1];
direction = NextDirection[direction][previousPointInPath.interCardinal];
const nextPointInfo = findNextPoint(previousPointInPath, points, direction);
if (nextPointInfo == null) {
// We have reached the end
pathPoints.push(topLeftPoint); // Add the first point to the end to make a loop
paths.push(pathPoints);
break;
}
const [nextPoint, nextPointIndex] = nextPointInfo;
points.splice(nextPointIndex, 1);
pathPoints.push(nextPoint);
}
}
return paths;
}

Resources