@flatten-js/polygon-offset
Version:
Polygon equidistant offset
275 lines (213 loc) • 9.4 kB
HTML
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>index.js - Documentation</title>
<script src="scripts/prettify/prettify.js"></script>
<script src="scripts/prettify/lang-css.js"></script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<nav>
<li class="nav-link nav-home-link"><a href="index.html">Home</a></li><li class="nav-heading">Classes</li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="Offset.html">Offset</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Offset.html#.offset">offset</a></span></li>
</nav>
<div id="main">
<h1 class="page-title">index.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>
/**
* Created by Alex Bol on 12/02/2018.
*/
"use strict";
let Flatten = require("flatten-js");
let BooleanOp = require("flatten-boolean-op");
let {Point, Segment, Vector, Line, Box, Arc, Polygon, Face} = Flatten;
let {point, segment, arc, vector} = Flatten;
/**
* Class Offset implements offset of polygons
*/
class Offset {
/**
* Offset polygon by given value
* @param {Polygon} polygon - input polygon
* @param {number} value - offset value, may be positive or negative
*/
static offset(polygon, value) {
let w = value;
let edges = [...polygon.edges];
let offsetPolygon = polygon.clone();
let offsetEdge;
if (w != 0) {
for (let edge of edges) {
if (edge.shape instanceof Flatten.Segment) {
offsetEdge = Offset.offsetSegment(edge.shape, w);
}
else {
offsetEdge = Offset.offsetArc(edge.shape, w);
}
if (w > 0) {
offsetPolygon = BooleanOp.unify(offsetPolygon, offsetEdge);
}
else {
offsetPolygon = BooleanOp.subtract(offsetPolygon, offsetEdge);
}
}
}
return offsetPolygon;
}
static offsetSegment(seg, value) {
let w = Math.abs(value);
let polygon = new Polygon();
let v_seg = vector(seg.start, seg.end);
let v_seg_unit = v_seg.normalize();
let v_left = v_seg_unit.rotate90CCW().multiply(w);
let v_right = v_seg_unit.rotate90CW().multiply(w);
let seg_left = seg.translate(v_left);
let seg_right = seg.translate(v_right).reverse();
let cap1 = Offset.arcSE(seg.end, seg_left.end, seg_right.start, Flatten.CW);
let cap2 = Offset.arcSE(seg.start, seg_right.end, seg_left.start, Flatten.CW);
polygon.addFace([seg_left, cap1, seg_right, cap2]);
return polygon;
}
static offsetArc(arc, value) {
let edges = [];
let w = Math.abs(value);
// Define outline polygon
let polygon = new Polygon();
let arc_cap1,arc_cap2;
let arc_outer = arc.clone();
arc_outer.r = arc.r + w;
arc_cap1 = Offset.arcStartSweep(arc.end, arc_outer.end, Math.PI, arc.counterClockwise);
arc_cap2 = Offset.arcEndSweep(arc.start, arc_outer.start, Math.PI, arc.counterClockwise);
let arc_inner = undefined;
if (arc.r > w) {
arc_inner = new Arc(arc.pc, arc.r - w, arc.endAngle, arc.startAngle,
arc.counterClockwise === Flatten.CW ? Flatten.CCW : Flatten.CW);
}
else {
// arc_inner = new Arc(arc.pc, w - arc.r, arc.startAngle, arc.endAngle, arc.counterClockwise);
arc_inner = new Segment(arc_cap1.end, arc_cap2.start);
}
polygon.addFace([arc_outer, arc_cap1, arc_inner, arc_cap2]);
[...polygon.faces][0].setArcLength();
// Create intersection points
let ips = Face.getSelfIntersections([...polygon.faces][0], polygon.edges, false);
// TODO: getSelfIntersections returns points with correspondent edges - avoid duplication
ips = ips.slice(0,ips.length/2); // for now slice array to avoid duplication in points
let int_points = [];
let edge_cap1;
let edge_cap2;
edge_cap1 = [...polygon.edges][1];
edge_cap2 = [...polygon.edges][3];
for (let pt of ips) {
BooleanOp.addToIntPoints(edge_cap1, pt, int_points);
BooleanOp.addToIntPoints(edge_cap2, pt, int_points);
}
// Sort intersection points and insert them as new vertices
let int_points_sorted = BooleanOp.getSortedArray(int_points);
BooleanOp.splitByIntersections(polygon, int_points_sorted);
// Set BV flags
let bv = Flatten.OUTSIDE;
for (let int_point of int_points_sorted) {
int_point.edge_before.bv = bv;
int_point.edge_after.bv = (bv == Flatten.OUTSIDE ? Flatten.INSIDE : Flatten.OUTSIDE);
bv = int_point.edge_after.bv; // invert flag on each iteration
}
// Remove inner "chains"
let op = Flatten.BOOLEAN_UNION;
BooleanOp.removeNotRelevantChains(polygon, op, int_points_sorted, true);
// return int_points_sorted;
// Swap links
let num = int_points.length;
if (num > 0) {
let edge_before;
let edge_after;
// 0 => 3
edge_before = int_points_sorted[0].edge_before;
edge_after = int_points_sorted[num-1].edge_after;
edge_before.next = edge_after;
edge_after.prev = edge_before;
// Fill in missed links in intersection points
int_points_sorted[0].edge_after = int_points_sorted[num-1].edge_after;
int_points_sorted[num-1].edge_before = int_points_sorted[0].edge_before;
if (num == 4) {
// 2 => 1
edge_before = int_points_sorted[2].edge_before;
edge_after = int_points_sorted[1].edge_after;
edge_before.next = edge_after;
edge_after.prev = edge_before;
// Fill in missed links in intersection points
int_points_sorted[2].edge_after = int_points_sorted[1].edge_after;
int_points_sorted[1].edge_before = int_points_sorted[2].edge_before;
}
// remove old faces
BooleanOp.removeOldFaces(polygon, int_points);
// restore faces
BooleanOp.restoreFaces(polygon, int_points, int_points);
}
let face0 = [...polygon.faces][0];
if (face0.orientation() === Flatten.ORIENTATION.CCW) {
polygon.reverse();
}
return polygon;
}
static arcSE(center, start, end, counterClockwise) {
let startAngle = vector(center,start).slope;
let endAngle = vector(center, end).slope;
if (Flatten.Utils.EQ(startAngle, endAngle)) {
endAngle += 2*Math.PI;
counterClockwise = true;
}
let r = vector(center, start).length;
return new Arc(center, r, startAngle, endAngle, counterClockwise);
}
static arcStartSweep(center, start, sweep, counterClockwise) {
let startAngle = vector(center,start).slope;
let endAngle = startAngle + sweep;
if (Flatten.Utils.EQ(startAngle, endAngle)) {
endAngle += 2*Math.PI;
counterClockwise = true;
}
let r = vector(center, start).length;
return new Arc(center, r, startAngle, endAngle, counterClockwise);
}
static arcEndSweep(center, end, sweep, counterClockwise) {
let {vector, Arc} = Flatten;
let endAngle = vector(center,end).slope;
let startAngle = endAngle - sweep;
if (Flatten.Utils.EQ(startAngle, endAngle)) {
endAngle += 2*Math.PI;
counterClockwise = true;
}
let r = vector(center, end).length;
return new Arc(center, r, startAngle, endAngle, counterClockwise);
}
}
Flatten.Polygon.prototype.offset = function(value) {
return Offset.offset(this, value);
};
module.exports = Offset;
</code></pre>
</article>
</section>
</div>
<br class="clear">
<footer>
Generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Jun 11 2018 20:36:13 GMT+0300 (Jerusalem Daylight Time) using the Minami theme.
</footer>
<script>prettyPrint();</script>
<script src="scripts/linenumber.js"></script>
</body>
</html>