+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Preet Shihn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
# hachure-fill
|
||||
|
||||
[Demo](https://hachure-fill.pages.dev/)
|
||||
|
||||
This package calculates the hachure lined to fill a polygon. The **angle** of the lines and the **gap** between lines can be configured.
|
||||
|
||||
The algorithm works on convex, concave, simple, complex polygons, and polygons with holes.
|
||||
|
||||

|
||||
|
||||
|
||||
## Install
|
||||
|
||||
From npm
|
||||
|
||||
```
|
||||
npm install --save hachure-fill
|
||||
```
|
||||
|
||||
The package is distributed as an ES6 module.
|
||||
|
||||
## Usage
|
||||
|
||||
### hachureFill(points: Point[], angle: number, gap: number): Line[];
|
||||
|
||||
The function takes in a polygon, which is represented as an array of points (each point being a an array of 2 numbers `[x, y]`).
|
||||
|
||||
The **angle** sets the angle of the hachure lines in degrees.
|
||||
|
||||
The **gap** arguments sets the distance between each hachure line.
|
||||
|
||||
The function returns an array of lines. Each line is an array of two points.
|
||||
|
||||
```javascript
|
||||
import { hachureFill } from 'hachure-fill';
|
||||
|
||||
// Polygon vertices
|
||||
const vertices = [
|
||||
[10, 10],
|
||||
[200, 10],
|
||||
[100, 100],
|
||||
[300, 100],
|
||||
[60, 200]
|
||||
];
|
||||
|
||||
// Lines filling the polygon
|
||||
// at an angle of 45 degrees.
|
||||
// Lines are 10px apart.
|
||||
const lines = hachureFill(vertices, 45, 10);
|
||||
|
||||
// Draw lines...
|
||||
```
|
||||
|
||||
### hachureFill(points: Point[][], angle: number, gap: number): Line[];
|
||||
|
||||
When dealing with Polygon with holes, you can provide an array of polygons. The outer polygon, and polygones represented each of the holes.
|
||||
The polygons need not be nested, but if they are, even-odd rules apply to fill the shape.
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export type Point = [number, number];
|
||||
export type Line = [Point, Point];
|
||||
export type Polygon = Point[];
|
||||
export declare function hachureLines(polygons: Polygon | Polygon[], hachureGap: number, hachureAngle: number, hachureStepOffset?: number): Line[];
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
function rotatePoints(points, center, degrees) {
|
||||
if (points && points.length) {
|
||||
const [cx, cy] = center;
|
||||
const angle = (Math.PI / 180) * degrees;
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
for (const p of points) {
|
||||
const [x, y] = p;
|
||||
p[0] = ((x - cx) * cos) - ((y - cy) * sin) + cx;
|
||||
p[1] = ((x - cx) * sin) + ((y - cy) * cos) + cy;
|
||||
}
|
||||
}
|
||||
}
|
||||
function rotateLines(lines, center, degrees) {
|
||||
const points = [];
|
||||
lines.forEach((line) => points.push(...line));
|
||||
rotatePoints(points, center, degrees);
|
||||
}
|
||||
function areSamePoints(p1, p2) {
|
||||
return p1[0] === p2[0] && p1[1] === p2[1];
|
||||
}
|
||||
export function hachureLines(polygons, hachureGap, hachureAngle, hachureStepOffset = 1) {
|
||||
const angle = hachureAngle;
|
||||
const gap = Math.max(hachureGap, 0.1);
|
||||
const polygonList = (polygons[0] && polygons[0][0] && (typeof polygons[0][0] === 'number')) ? [polygons] : polygons;
|
||||
const rotationCenter = [0, 0];
|
||||
if (angle) {
|
||||
for (const polygon of polygonList) {
|
||||
rotatePoints(polygon, rotationCenter, angle);
|
||||
}
|
||||
}
|
||||
const lines = straightHachureLines(polygonList, gap, hachureStepOffset);
|
||||
if (angle) {
|
||||
for (const polygon of polygonList) {
|
||||
rotatePoints(polygon, rotationCenter, -angle);
|
||||
}
|
||||
rotateLines(lines, rotationCenter, -angle);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
function straightHachureLines(polygons, gap, hachureStepOffset) {
|
||||
const vertexArray = [];
|
||||
for (const polygon of polygons) {
|
||||
const vertices = [...polygon];
|
||||
if (!areSamePoints(vertices[0], vertices[vertices.length - 1])) {
|
||||
vertices.push([vertices[0][0], vertices[0][1]]);
|
||||
}
|
||||
if (vertices.length > 2) {
|
||||
vertexArray.push(vertices);
|
||||
}
|
||||
}
|
||||
const lines = [];
|
||||
gap = Math.max(gap, 0.1);
|
||||
// Create sorted edges table
|
||||
const edges = [];
|
||||
for (const vertices of vertexArray) {
|
||||
for (let i = 0; i < vertices.length - 1; i++) {
|
||||
const p1 = vertices[i];
|
||||
const p2 = vertices[i + 1];
|
||||
if (p1[1] !== p2[1]) {
|
||||
const ymin = Math.min(p1[1], p2[1]);
|
||||
edges.push({
|
||||
ymin,
|
||||
ymax: Math.max(p1[1], p2[1]),
|
||||
x: ymin === p1[1] ? p1[0] : p2[0],
|
||||
islope: (p2[0] - p1[0]) / (p2[1] - p1[1]),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
edges.sort((e1, e2) => {
|
||||
if (e1.ymin < e2.ymin) {
|
||||
return -1;
|
||||
}
|
||||
if (e1.ymin > e2.ymin) {
|
||||
return 1;
|
||||
}
|
||||
if (e1.x < e2.x) {
|
||||
return -1;
|
||||
}
|
||||
if (e1.x > e2.x) {
|
||||
return 1;
|
||||
}
|
||||
if (e1.ymax === e2.ymax) {
|
||||
return 0;
|
||||
}
|
||||
return (e1.ymax - e2.ymax) / Math.abs((e1.ymax - e2.ymax));
|
||||
});
|
||||
if (!edges.length) {
|
||||
return lines;
|
||||
}
|
||||
// Start scanning
|
||||
let activeEdges = [];
|
||||
let y = edges[0].ymin;
|
||||
let iteration = 0;
|
||||
while (activeEdges.length || edges.length) {
|
||||
if (edges.length) {
|
||||
let ix = -1;
|
||||
for (let i = 0; i < edges.length; i++) {
|
||||
if (edges[i].ymin > y) {
|
||||
break;
|
||||
}
|
||||
ix = i;
|
||||
}
|
||||
const removed = edges.splice(0, ix + 1);
|
||||
removed.forEach((edge) => {
|
||||
activeEdges.push({ s: y, edge });
|
||||
});
|
||||
}
|
||||
activeEdges = activeEdges.filter((ae) => {
|
||||
if (ae.edge.ymax <= y) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
activeEdges.sort((ae1, ae2) => {
|
||||
if (ae1.edge.x === ae2.edge.x) {
|
||||
return 0;
|
||||
}
|
||||
return (ae1.edge.x - ae2.edge.x) / Math.abs((ae1.edge.x - ae2.edge.x));
|
||||
});
|
||||
// fill between the edges
|
||||
if ((hachureStepOffset !== 1) || (iteration % gap === 0)) {
|
||||
if (activeEdges.length > 1) {
|
||||
for (let i = 0; i < activeEdges.length; i = i + 2) {
|
||||
const nexti = i + 1;
|
||||
if (nexti >= activeEdges.length) {
|
||||
break;
|
||||
}
|
||||
const ce = activeEdges[i].edge;
|
||||
const ne = activeEdges[nexti].edge;
|
||||
lines.push([
|
||||
[Math.round(ce.x), y],
|
||||
[Math.round(ne.x), y],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
y += hachureStepOffset;
|
||||
activeEdges.forEach((ae) => {
|
||||
ae.edge.x = ae.edge.x + (hachureStepOffset * ae.edge.islope);
|
||||
});
|
||||
iteration++;
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export declare function hachureFill(points: [number, number][], angle?: number, gap?: number): [[number, number], [number, number]][];
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "hachure-fill",
|
||||
"version": "0.5.2",
|
||||
"description": "Fill a polygon with lines ",
|
||||
"main": "bin/hachure.js",
|
||||
"types": "bin/hachure.d.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "npm run lint && npm run build-ts",
|
||||
"build-ts": "rm -rf bin && tsc",
|
||||
"lint": "eslint --ext ts src"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/pshihn/hachure-fill.git"
|
||||
},
|
||||
"keywords": [
|
||||
"hachure",
|
||||
"lines",
|
||||
"polygon",
|
||||
"plotter"
|
||||
],
|
||||
"author": "Preet Shihn",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/pshihn/hachure-fill/issues"
|
||||
},
|
||||
"homepage": "https://github.com/pshihn/hachure-fill#readme",
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||
"@typescript-eslint/parser": "^5.59.0",
|
||||
"esbuild": "^0.17.18",
|
||||
"eslint": "^8.39.0",
|
||||
"typescript": "^5.0.4"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user