update code style for geo classes
This commit is contained in:
parent
a51af1d4c6
commit
7b8a5be1c5
11 changed files with 2777 additions and 3147 deletions
9
app.js
9
app.js
|
|
@ -309,15 +309,13 @@ const script = {
|
||||||
"add/array",
|
"add/array",
|
||||||
"add/three",
|
"add/three",
|
||||||
"geo/base",
|
"geo/base",
|
||||||
// "geo/render",
|
|
||||||
"geo/point",
|
"geo/point",
|
||||||
"geo/points",
|
"geo/points",
|
||||||
"geo/slope",
|
"geo/slope",
|
||||||
"geo/line",
|
"geo/line",
|
||||||
"geo/bounds",
|
"geo/bounds",
|
||||||
"geo/polygon",
|
|
||||||
"geo/polygons",
|
"geo/polygons",
|
||||||
// "geo/gyroid",
|
"geo/polygon",
|
||||||
"geo/mesh",
|
"geo/mesh",
|
||||||
"data/local",
|
"data/local",
|
||||||
"data/index",
|
"data/index",
|
||||||
|
|
@ -383,8 +381,8 @@ const script = {
|
||||||
"geo/slope",
|
"geo/slope",
|
||||||
"geo/line",
|
"geo/line",
|
||||||
"geo/bounds",
|
"geo/bounds",
|
||||||
"geo/polygon",
|
|
||||||
"geo/polygons",
|
"geo/polygons",
|
||||||
|
"geo/polygon",
|
||||||
"geo/gyroid",
|
"geo/gyroid",
|
||||||
"geo/mesh",
|
"geo/mesh",
|
||||||
// "moto/broker",
|
// "moto/broker",
|
||||||
|
|
@ -432,8 +430,8 @@ const script = {
|
||||||
"geo/slope",
|
"geo/slope",
|
||||||
"geo/line",
|
"geo/line",
|
||||||
"geo/bounds",
|
"geo/bounds",
|
||||||
"geo/polygon",
|
|
||||||
"geo/polygons",
|
"geo/polygons",
|
||||||
|
"geo/polygon",
|
||||||
"geo/gyroid",
|
"geo/gyroid",
|
||||||
"kiri-mode/fdm/driver",
|
"kiri-mode/fdm/driver",
|
||||||
"kiri-mode/fdm/slice",
|
"kiri-mode/fdm/slice",
|
||||||
|
|
@ -453,7 +451,6 @@ const script = {
|
||||||
"add/array",
|
"add/array",
|
||||||
"add/three",
|
"add/three",
|
||||||
"geo/base",
|
"geo/base",
|
||||||
// "geo/render",
|
|
||||||
"geo/point",
|
"geo/point",
|
||||||
"geo/points",
|
"geo/points",
|
||||||
"geo/slope",
|
"geo/slope",
|
||||||
|
|
|
||||||
|
|
@ -4,20 +4,21 @@
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
if (self.base.gyroid) return;
|
if (self.base.gyroid) return;
|
||||||
|
|
||||||
const base = self.base;
|
const base = self.base;
|
||||||
const PI2 = Math.PI * 2;
|
const PI2 = Math.PI * 2;
|
||||||
let cache = {};
|
|
||||||
let lastVal;
|
|
||||||
let lastRes = 0;
|
|
||||||
let lastSlice = 0;
|
|
||||||
|
|
||||||
/**
|
let cache = {};
|
||||||
|
let lastVal;
|
||||||
|
let lastRes = 0;
|
||||||
|
let lastSlice = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
* @param off {number} z offset value from 0-1
|
* @param off {number} z offset value from 0-1
|
||||||
* @param res {number} resolution (pixels/slices per side)
|
* @param res {number} resolution (pixels/slices per side)
|
||||||
*/
|
*/
|
||||||
function slice(off, res, val) {
|
function slice(off, res, val) {
|
||||||
// auto clear cach if it hasn't been hit in the last 20 seconds
|
// auto clear cach if it hasn't been hit in the last 20 seconds
|
||||||
// or the requested resolution or tip values have changed
|
// or the requested resolution or tip values have changed
|
||||||
let now = Date.now();
|
let now = Date.now();
|
||||||
|
|
@ -168,10 +169,10 @@
|
||||||
let slice = {edge, points, dir, polys: psimple};
|
let slice = {edge, points, dir, polys: psimple};
|
||||||
cache[key] = slice;
|
cache[key] = slice;
|
||||||
return slice;
|
return slice;
|
||||||
}
|
}
|
||||||
|
|
||||||
// merge co-linear and distance threshold
|
// merge co-linear and distance threshold
|
||||||
function filter(poly, inc) {
|
function filter(poly, inc) {
|
||||||
if (poly.length <= 2) {
|
if (poly.length <= 2) {
|
||||||
return poly;
|
return poly;
|
||||||
}
|
}
|
||||||
|
|
@ -203,17 +204,17 @@
|
||||||
}
|
}
|
||||||
nupoly.push(poly[poly.length-1]);
|
nupoly.push(poly[poly.length-1]);
|
||||||
return nupoly;
|
return nupoly;
|
||||||
}
|
}
|
||||||
|
|
||||||
function distTo(a, b, dir) {
|
function distTo(a, b, dir) {
|
||||||
let dx = a.x - b.x;
|
let dx = a.x - b.x;
|
||||||
let dy = a.y - b.y;
|
let dy = a.y - b.y;
|
||||||
// bias distance by prevailing direction of discovery to join stragglers
|
// bias distance by prevailing direction of discovery to join stragglers
|
||||||
if (dir === 'lr') dx = dx / 2;
|
if (dir === 'lr') dx = dx / 2;
|
||||||
if (dir === 'td') dy = dy / 2;
|
if (dir === 'td') dy = dy / 2;
|
||||||
return Math.sqrt(dx * dx + dy * dy);
|
return Math.sqrt(dx * dx + dy * dy);
|
||||||
}
|
}
|
||||||
|
|
||||||
base.gyroid = { slice };
|
base.gyroid = { slice };
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -4,79 +4,45 @@
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
if (self.base.Line) return;
|
if (self.base.Line) return;
|
||||||
|
|
||||||
const BASE = self.base, PRO = Line.prototype;
|
const base = self.base;
|
||||||
|
|
||||||
BASE.Line = Line;
|
class Line {
|
||||||
BASE.newLine = newLine;
|
constructor(p1, p2, key) {
|
||||||
BASE.newOrderedLine = newOrderedLine;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Point} p1
|
|
||||||
* @param {Point} p2
|
|
||||||
* @param {String} [key]
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function Line(p1, p2, key) {
|
|
||||||
if (!key) key = [p1.key, p2.key].join(';');
|
if (!key) key = [p1.key, p2.key].join(';');
|
||||||
this.p1 = p1;
|
this.p1 = p1;
|
||||||
this.p2 = p2;
|
this.p2 = p2;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.coplanar = false;
|
|
||||||
this.edge = false;
|
|
||||||
this.del = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ******************************************************************
|
length() {
|
||||||
* Line Prototype Functions
|
|
||||||
******************************************************************* */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
PRO.length = function() {
|
|
||||||
return Math.sqrt(this.length2());
|
return Math.sqrt(this.length2());
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {number} square of length
|
* @returns {number} square of length
|
||||||
*/
|
*/
|
||||||
PRO.length2 = function() {
|
length2() {
|
||||||
return this.p1.distToSq2D(this.p2);
|
return this.p1.distToSq2D(this.p2);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
slope() {
|
||||||
* @returns {Slope}
|
return base.newSlope(this.p1.slopeTo(this.p2));
|
||||||
*/
|
}
|
||||||
PRO.slope = function() {
|
|
||||||
return BASE.newSlope(this.p1.slopeTo(this.p2));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
reverse() {
|
||||||
* @returns {Line}
|
|
||||||
*/
|
|
||||||
PRO.reverse = function() {
|
|
||||||
let t = this.p1;
|
let t = this.p1;
|
||||||
this.p1 = this.p2;
|
this.p1 = this.p2;
|
||||||
this.p2 = t;
|
this.p2 = t;
|
||||||
return this;
|
return this;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
midpoint() {
|
||||||
* @returns {Point}
|
|
||||||
*/
|
|
||||||
PRO.midpoint = function() {
|
|
||||||
return this.p1.midPointTo(this.p2);
|
return this.p1.midPointTo(this.p2);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
isCollinear(line) {
|
||||||
* faulty when line doubles back at 180?
|
|
||||||
* @param {Line} line
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
PRO.isCollinear = function(line) {
|
|
||||||
let p1 = this.p1,
|
let p1 = this.p1,
|
||||||
p2 = this.p2,
|
p2 = this.p2,
|
||||||
p3 = line.p1,
|
p3 = line.p1,
|
||||||
|
|
@ -85,27 +51,20 @@
|
||||||
d1y = (p2.y - p1.y),
|
d1y = (p2.y - p1.y),
|
||||||
d2x = (p4.x - p3.x),
|
d2x = (p4.x - p3.x),
|
||||||
d2y = (p4.y - p3.y);
|
d2y = (p4.y - p3.y);
|
||||||
|
|
||||||
return Math.abs( (d2y * d1x) - (d2x * d1y) ) < 0.0001;
|
return Math.abs( (d2y * d1x) - (d2x * d1y) ) < 0.0001;
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** ******************************************************************
|
function newLine(p1, p2, key) {
|
||||||
* Connect to base and Helpers
|
|
||||||
******************************************************************* */
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Point} p1
|
|
||||||
* @param {Point} p2
|
|
||||||
* @param {String} [key]
|
|
||||||
* @returns {Line}
|
|
||||||
*/
|
|
||||||
function newLine(p1, p2, key) {
|
|
||||||
return new Line(p1, p2, key);
|
return new Line(p1, p2, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
function newOrderedLine(p1, p2, key) {
|
function newOrderedLine(p1, p2, key) {
|
||||||
return p1.key < p2.key ? newLine(p1,p2,key) : newLine(p2,p1,key);
|
return p1.key < p2.key ? newLine(p1,p2,key) : newLine(p2,p1,key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
base.Line = Line;
|
||||||
|
base.newLine = newLine;
|
||||||
|
base.newOrderedLine = newOrderedLine;
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
535
src/geo/point.js
535
src/geo/point.js
|
|
@ -4,16 +4,14 @@
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
if (self.base.Point) return;
|
if (self.base.Point) return;
|
||||||
|
|
||||||
const BASE = self.base,
|
const base = self.base;
|
||||||
UTIL = BASE.util,
|
const { util, config, key } = base;
|
||||||
CONF = BASE.config,
|
const { round } = util;
|
||||||
KEYS = BASE.key,
|
|
||||||
ROUND = UTIL.round;
|
|
||||||
|
|
||||||
class Point {
|
class Point {
|
||||||
constructor(x,y,z,key) {
|
constructor(x, y, z, key) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
this.z = z || 0;
|
this.z = z || 0;
|
||||||
|
|
@ -32,104 +30,71 @@
|
||||||
((this.z * 100000) | 0)
|
((this.z * 100000) | 0)
|
||||||
].join('');
|
].join('');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const PRO = Point.prototype;
|
toClipper() {
|
||||||
|
|
||||||
BASE.Point = Point;
|
|
||||||
|
|
||||||
BASE.newPoint = newPoint;
|
|
||||||
|
|
||||||
BASE.pointFromClipper = function(cp, z) {
|
|
||||||
return newPoint(cp.X / CONF.clipper, cp.Y / CONF.clipper, z);
|
|
||||||
};
|
|
||||||
|
|
||||||
/** ******************************************************************
|
|
||||||
* Point Prototype Functions
|
|
||||||
******************************************************************* */
|
|
||||||
|
|
||||||
PRO.toClipper = function() {
|
|
||||||
return {
|
return {
|
||||||
X: this.x * CONF.clipper,
|
X: this.x * config.clipper,
|
||||||
Y: this.y * CONF.clipper
|
Y: this.y * config.clipper
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
PRO.setZ = function(z) {
|
setZ(z) {
|
||||||
this.z = z;
|
this.z = z;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
PRO.swapXZ = function() {
|
swapXZ() {
|
||||||
let p = this,
|
let p = this,
|
||||||
t = p.x;
|
t = p.x;
|
||||||
p.x = p.z;
|
p.x = p.z;
|
||||||
p.z = t;
|
p.z = t;
|
||||||
return this;
|
return this;
|
||||||
};
|
}
|
||||||
|
|
||||||
PRO.swapYZ = function() {
|
swapYZ() {
|
||||||
let p = this,
|
let p = this,
|
||||||
t = p.y;
|
t = p.y;
|
||||||
p.y = p.z;
|
p.y = p.z;
|
||||||
p.z = t;
|
p.z = t;
|
||||||
return this;
|
return this;
|
||||||
};
|
}
|
||||||
|
|
||||||
PRO.round = function(precision) {
|
round(precision) {
|
||||||
return newPoint(
|
return newPoint(
|
||||||
this.x.round(precision),
|
this.x.round(precision),
|
||||||
this.y.round(precision),
|
this.y.round(precision),
|
||||||
this.z.round(precision));
|
this.z.round(precision));
|
||||||
};
|
}
|
||||||
|
|
||||||
PRO.addFacet = function(facet) {
|
addFacet(facet) {
|
||||||
if (!this.group) this.group = [];
|
if (!this.group) this.group = [];
|
||||||
this.group.push(facet);
|
this.group.push(facet);
|
||||||
return this;
|
return this;
|
||||||
};
|
}
|
||||||
|
|
||||||
PRO.rekey = function() {
|
rekey() {
|
||||||
this._key = undefined;
|
this._key = undefined;
|
||||||
};
|
}
|
||||||
|
|
||||||
PRO.toString = function() {
|
toString() {
|
||||||
return this.key;
|
return this.key;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
clone() {
|
||||||
* @returns {Point}
|
|
||||||
*/
|
|
||||||
PRO.clone = function() {
|
|
||||||
return newPoint(this.x, this.y, this.z, this._key);
|
return newPoint(this.x, this.y, this.z, this._key);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
slopeTo(p) {
|
||||||
* @param {Point} p
|
return base.newSlope(this, p);
|
||||||
* @returns {Slope}
|
}
|
||||||
*/
|
|
||||||
PRO.slopeTo = function(p) {
|
|
||||||
return BASE.newSlope(this, p);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
lineTo(p, k) {
|
||||||
*
|
return base.newLine(this, p, k);
|
||||||
* @param {Point} p
|
}
|
||||||
* @param {String} [k]
|
|
||||||
* @returns {Line}
|
|
||||||
*/
|
|
||||||
PRO.lineTo = function(p, k) {
|
|
||||||
return BASE.newLine(this, p, k);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
isNear(p, dist) {
|
||||||
* @param {Point} p
|
return util.isCloseTo(this.x, p.x, dist) && util.isCloseTo(this.y, p.y, dist);
|
||||||
* @param {number} [dist]
|
}
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
PRO.isNear = function(p, dist) {
|
|
||||||
return UTIL.isCloseTo(this.x, p.x, dist) && UTIL.isCloseTo(this.y, p.y, dist);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return distance to line connecting points p1, p2
|
* return distance to line connecting points p1, p2
|
||||||
|
|
@ -139,10 +104,9 @@
|
||||||
* @param {Point} p2
|
* @param {Point} p2
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
PRO.distToLine = function(p1, p2) {
|
distToLine(p1, p2) {
|
||||||
// return p2l(this, p1, p2);
|
|
||||||
return Math.sqrt(this.distToLineSq(p1, p2));
|
return Math.sqrt(this.distToLineSq(p1, p2));
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* used exclusively in new fill code. output does not agree with
|
* used exclusively in new fill code. output does not agree with
|
||||||
|
|
@ -151,69 +115,27 @@
|
||||||
* offset clipping. both need to be investigated and a single line
|
* offset clipping. both need to be investigated and a single line
|
||||||
* normal distance needs to be formulated to replace both functions.
|
* normal distance needs to be formulated to replace both functions.
|
||||||
*/
|
*/
|
||||||
PRO.distToLineNew = function(p1, p2) {
|
distToLineNew(p1, p2) {
|
||||||
return p2l(this, p1, p2);
|
return p2l(this, p1, p2);
|
||||||
// return Math.sqrt(this.distToLineSq(p1, p2));
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return square of distance to line connecting points p1, p2
|
* return square of distance to line connecting points p1, p2
|
||||||
* distance is calculated on the perpendicular (normal) to line
|
* distance is calculated on the perpendicular (normal) to line
|
||||||
*
|
|
||||||
* @param {Point} p1
|
|
||||||
* @param {Point} p2
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
*/
|
||||||
PRO.distToLineSq = function(p1, p2) {
|
distToLineSq(p1, p2) {
|
||||||
let p = this,
|
let p = this,
|
||||||
d = UTIL.distSq(p1, p2);
|
d = util.distSq(p1, p2);
|
||||||
|
|
||||||
let t = ((p.x - p1.x) * (p2.x - p1.x) + (p.y - p1.y) * (p2.y - p1.y)) / d;
|
let t = ((p.x - p1.x) * (p2.x - p1.x) + (p.y - p1.y) * (p2.y - p1.y)) / d;
|
||||||
|
|
||||||
if (t < 0) return UTIL.distSq(p, p1);
|
if (t < 0) return util.distSq(p, p1);
|
||||||
if (t > 1) return UTIL.distSq(p, p2);
|
if (t > 1) return util.distSq(p, p2);
|
||||||
|
|
||||||
return UTIL.distSqv2(p.x, p.y, p1.x + t * (p2.x - p1.x), p1.y + t * (p2.y - p1.y));
|
return util.distSqv2(p.x, p.y, p1.x + t * (p2.x - p1.x), p1.y + t * (p2.y - p1.y));
|
||||||
};
|
|
||||||
|
|
||||||
// ---( begin fix distToLine )---
|
|
||||||
|
|
||||||
function dot(u, v) {
|
|
||||||
return u.x * v.x + u.y * v.y;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function norm(v) {
|
withinDist2(p1, p2, dist2) {
|
||||||
return Math.sqrt(dot(v,v));
|
|
||||||
}
|
|
||||||
|
|
||||||
function d(u, v) {
|
|
||||||
return norm({x: u.x - v.x, y: u.y - v.y});
|
|
||||||
}
|
|
||||||
|
|
||||||
function p2l(p, l1, l2) {
|
|
||||||
let v = {x: l2.x - l1.x, y: l2.y - l1.y};
|
|
||||||
let w = {x: p.x - l1.x, y: p.y - l1.y};
|
|
||||||
let c1 = dot(w, v);
|
|
||||||
let c2 = dot(v, v);
|
|
||||||
let b = c1 / c2;
|
|
||||||
if (isNaN(b)) {
|
|
||||||
// console.log('nan', {p, l1, l2, v, w, c1, c2});
|
|
||||||
b = 0;
|
|
||||||
}
|
|
||||||
let pb = {x: l1.x + b * v.x, y: l1.y + b * v.y};
|
|
||||||
return d(p, pb);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---( end fix distToLine )---
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Point} p1
|
|
||||||
* @param {Point} p2
|
|
||||||
* @param {number} dist2
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
PRO.withinDist2 = function(p1, p2, dist2) {
|
|
||||||
let ll2 = p1.distToSq2D(p2),
|
let ll2 = p1.distToSq2D(p2),
|
||||||
dp1 = this.distToSq2D(p1),
|
dp1 = this.distToSq2D(p1),
|
||||||
dp2 = this.distToSq2D(p2);
|
dp2 = this.distToSq2D(p2);
|
||||||
|
|
@ -231,71 +153,53 @@
|
||||||
// dist2 to the described line segment.
|
// dist2 to the described line segment.
|
||||||
if (dp1 > ll2 && dp2 > ll2) return false;
|
if (dp1 > ll2 && dp2 > ll2) return false;
|
||||||
return this.distToLineSq(p1, p2) < dist2;
|
return this.distToLineSq(p1, p2) < dist2;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
midPointTo(p2) {
|
||||||
* @param {Point} p2
|
return newPoint((this.x + p2.x) / 2, (this.y + p2.y) / 2, this.z);
|
||||||
* @returns {Point}
|
}
|
||||||
*/
|
|
||||||
PRO.midPointTo = function(p2) {
|
|
||||||
return newPoint((this.x + p2.x)/2, (this.y + p2.y)/2, this.z);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
midPointTo3D(p2) {
|
||||||
* @param {Point} p2
|
|
||||||
* @returns {Point}
|
|
||||||
*/
|
|
||||||
PRO.midPointTo3D = function(p2) {
|
|
||||||
return newPoint(
|
return newPoint(
|
||||||
(this.x + p2.x)/2,
|
(this.x + p2.x) / 2,
|
||||||
(this.y + p2.y)/2,
|
(this.y + p2.y) / 2,
|
||||||
(this.z + p2.z)/2
|
(this.z + p2.z) / 2
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* non-scale corrected version of follow()
|
* non-scale corrected version of follow()
|
||||||
*
|
|
||||||
* @param slope
|
|
||||||
* @param mult
|
|
||||||
* @returns {Point}
|
|
||||||
*/
|
*/
|
||||||
PRO.projectOnSlope = function(slope, mult) {
|
projectOnSlope(slope, mult) {
|
||||||
return newPoint(
|
return newPoint(
|
||||||
this.x + slope.dx * mult,
|
this.x + slope.dx * mult,
|
||||||
this.y + slope.dy * mult,
|
this.y + slope.dy * mult,
|
||||||
this.z);
|
this.z);
|
||||||
};
|
}
|
||||||
|
|
||||||
PRO.followTo = function(point, mult) {
|
followTo(point, mult) {
|
||||||
return this.follow(this.slopeTo(point), mult);
|
return this.follow(this.slopeTo(point), mult);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return a point along the line this from point to p2 offset
|
* return a point along the line this from point to p2 offset
|
||||||
* by a distance. positive distances are closer to this point.
|
* by a distance. positive distances are closer to this point.
|
||||||
*
|
|
||||||
* @param p2
|
|
||||||
* @param dist
|
|
||||||
*/
|
*/
|
||||||
PRO.offsetPointFrom = function(p2, dist) {
|
offsetPointFrom(p2, dist) {
|
||||||
let p1 = this,
|
let p1 = this,
|
||||||
dx = p2.x - p1.x,
|
dx = p2.x - p1.x,
|
||||||
dy = p2.y - p1.y,
|
dy = p2.y - p1.y,
|
||||||
ls = dist / Math.sqrt(dx * dx + dy * dy),
|
ls = dist / Math.sqrt(dx * dx + dy * dy),
|
||||||
ox = dx * ls,
|
ox = dx * ls,
|
||||||
oy = dy * ls;
|
oy = dy * ls;
|
||||||
return newPoint(p2.x - ox, p2.y - oy, p2.z, KEYS.NONE);
|
return newPoint(p2.x - ox, p2.y - oy, p2.z, key.NONE);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return a point along the line this from point to p2 offset
|
* return a point along the line this from point to p2 offset
|
||||||
* by a distance. positive distances are farther from this point.
|
* by a distance. positive distances are farther from this point.
|
||||||
*
|
|
||||||
* @param p2
|
|
||||||
* @param dist
|
|
||||||
*/
|
*/
|
||||||
PRO.offsetPointTo = function(p2, dist) {
|
offsetPointTo(p2, dist) {
|
||||||
let p1 = this,
|
let p1 = this,
|
||||||
dx = p2.x - p1.x,
|
dx = p2.x - p1.x,
|
||||||
dy = p2.y - p1.y;
|
dy = p2.y - p1.y;
|
||||||
|
|
@ -306,93 +210,82 @@
|
||||||
ox = dx * ls,
|
ox = dx * ls,
|
||||||
oy = dy * ls;
|
oy = dy * ls;
|
||||||
|
|
||||||
return newPoint(p1.x + ox, p1.y + oy, p2.z, KEYS.NONE);
|
return newPoint(p1.x + ox, p1.y + oy, p2.z, key.NONE);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
offsetLineTo(p2, offset) {
|
||||||
* @param {Point} p2
|
|
||||||
* @param {number} offset
|
|
||||||
* @returns {Line}
|
|
||||||
*/
|
|
||||||
PRO.offsetLineTo = function(p2, offset) {
|
|
||||||
let p1 = this,
|
let p1 = this,
|
||||||
dx = p2.x - p1.x,
|
dx = p2.x - p1.x,
|
||||||
dy = p2.y - p1.y,
|
dy = p2.y - p1.y,
|
||||||
ls = offset / Math.sqrt(dx * dx + dy * dy),
|
ls = offset / Math.sqrt(dx * dx + dy * dy),
|
||||||
ox = dx * ls,
|
ox = dx * ls,
|
||||||
oy = dy * ls,
|
oy = dy * ls,
|
||||||
np1 = newPoint(p1.x - oy, p1.y + ox, p1.z, KEYS.NONE),
|
np1 = newPoint(p1.x - oy, p1.y + ox, p1.z, key.NONE),
|
||||||
np2 = newPoint(p2.x - oy, p2.y + ox, p2.z, KEYS.NONE);
|
np2 = newPoint(p2.x - oy, p2.y + ox, p2.z, key.NONE);
|
||||||
np1.op = p1;
|
np1.op = p1;
|
||||||
np2.op = p2;
|
np2.op = p2;
|
||||||
return BASE.newLine(np1, np2, KEYS.NONE);
|
return base.newLine(np1, np2, key.NONE);
|
||||||
};
|
}
|
||||||
|
|
||||||
PRO.offset = function(x, y, z) {
|
offset(x, y, z) {
|
||||||
return newPoint(this.x + x, this.y + y, this.z + z);
|
return newPoint(this.x + x, this.y + y, this.z + z);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* checks if a point is inside of a polygon
|
* checks if a point is inside of a polygon
|
||||||
* does not check children/holes
|
* does not check children/holes
|
||||||
*
|
|
||||||
* @param {Polygon} poly
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
*/
|
||||||
PRO.inPolygon = function(poly) {
|
inPolygon(poly) {
|
||||||
if (!poly.bounds.containsXY(this.x, this.y)) return false;
|
if (!poly.bounds.containsXY(this.x, this.y)) return false;
|
||||||
|
|
||||||
let p = poly.points, pl = p.length, p1, p2, i, inside = false;
|
let p = poly.points,
|
||||||
|
pl = p.length,
|
||||||
|
p1, p2, i, inside = false;
|
||||||
|
|
||||||
for (i=0; i<pl; i++) {
|
for (i = 0; i < pl; i++) {
|
||||||
p1 = p[i];
|
p1 = p[i];
|
||||||
p2 = p[(i+1)%pl];
|
p2 = p[(i + 1) % pl];
|
||||||
if ((p1.y >= this.y) != (p2.y >= this.y) &&
|
if ((p1.y >= this.y) != (p2.y >= this.y) &&
|
||||||
(this.x <= (p2.x - p1.x) * (this.y - p1.y) / (p2.y - p1.y) + p1.x))
|
(this.x <= (p2.x - p1.x) * (this.y - p1.y) / (p2.y - p1.y) + p1.x)) {
|
||||||
{
|
|
||||||
inside = !inside;
|
inside = !inside;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return inside;
|
return inside;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns true if the point is inside of a polygon but
|
* returns true if the point is inside of a polygon but
|
||||||
* not inside any of it's children
|
* not inside any of it's children
|
||||||
*
|
|
||||||
* @param {Polygon | Polygon[]} poly
|
|
||||||
* @return {boolean} true if inside outer but not inner
|
|
||||||
*/
|
*/
|
||||||
PRO.isInPolygon = function(poly) {
|
isInPolygon(poly) {
|
||||||
let point = this, i;
|
let point = this,
|
||||||
|
i;
|
||||||
if (Array.isArray(poly)) {
|
if (Array.isArray(poly)) {
|
||||||
for (i=0; i<poly.length; i++) {
|
for (i = 0; i < poly.length; i++) {
|
||||||
if (point.isInPolygon(poly[i])) return true;
|
if (point.isInPolygon(poly[i])) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let holes = poly.inner;
|
let holes = poly.inner;
|
||||||
if (point.inPolygon(poly) || point.nearPolygon(poly, CONF.precision_merge_sq)) {
|
if (point.inPolygon(poly) || point.nearPolygon(poly, config.precision_merge_sq)) {
|
||||||
for (i=0; holes && i < holes.length; i++) {
|
for (i = 0; holes && i < holes.length; i++) {
|
||||||
if (point.inPolygon(holes[i]) && !point.nearPolygon(holes[i], CONF.precision_merge_sq)) return false;
|
if (point.inPolygon(holes[i]) && !point.nearPolygon(holes[i], config.precision_merge_sq)) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns true if the point is inside of a polygon but
|
* returns true if the point is inside of a polygon but
|
||||||
* not inside any of it's children
|
* not inside any of it's children
|
||||||
*
|
|
||||||
* @param {Polygon | Polygon[]} poly
|
|
||||||
* @return {boolean} true if inside outer but not inner
|
|
||||||
*/
|
*/
|
||||||
PRO.isInPolygonOnly = function(poly) {
|
isInPolygonOnly(poly) {
|
||||||
let point = this, i;
|
let point = this,
|
||||||
|
i;
|
||||||
if (Array.isArray(poly)) {
|
if (Array.isArray(poly)) {
|
||||||
for (i=0; i<poly.length; i++) {
|
for (i = 0; i < poly.length; i++) {
|
||||||
if (point.isInPolygonOnly(poly[i])) {
|
if (point.isInPolygonOnly(poly[i])) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -401,129 +294,96 @@
|
||||||
}
|
}
|
||||||
let holes = poly.inner;
|
let holes = poly.inner;
|
||||||
if (point.inPolygon(poly)) {
|
if (point.inPolygon(poly)) {
|
||||||
for (i=0; holes && i < holes.length; i++) {
|
for (i = 0; holes && i < holes.length; i++) {
|
||||||
if (point.inPolygon(holes[i])) return false;
|
if (point.inPolygon(holes[i])) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* checks if point is near polygon edge. distance is squared.
|
* checks if point is near polygon edge. distance is squared.
|
||||||
*
|
|
||||||
* @param {Polygon} poly
|
|
||||||
* @param {number} dist2
|
|
||||||
* @param {boolean} [inner] process inner polygons
|
* @param {boolean} [inner] process inner polygons
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
*/
|
||||||
PRO.nearPolygon = function(poly, dist2, inner) {
|
nearPolygon(poly, dist2, inner) {
|
||||||
// throw new Error("nearPolygon");
|
// throw new Error("nearPolygon");
|
||||||
for (let i=0, p=poly.points, pl=p.length ; i<pl; i++) {
|
for (let i = 0, p = poly.points, pl = p.length; i < pl; i++) {
|
||||||
if (this.withinDist2(p[i], p[(i+1)%pl], dist2)) {
|
if (this.withinDist2(p[i], p[(i + 1) % pl], dist2)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (inner && poly.inner) {
|
if (inner && poly.inner) {
|
||||||
for (let i=0; i<poly.inner.length; i++) {
|
for (let i = 0; i < poly.inner.length; i++) {
|
||||||
if (this.nearPolygon(poly.inner[i], dist2)) return true;
|
if (this.nearPolygon(poly.inner[i], dist2)) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns true if point will not be trimmed later
|
* returns true if point will not be trimmed later
|
||||||
*
|
|
||||||
* @param {Polygon} poly
|
|
||||||
* @param {number} offset
|
|
||||||
* @param {number} mindist2
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
*/
|
||||||
PRO.insideOffset = function(poly, offset, mindist2) {
|
insideOffset(poly, offset, mindist2) {
|
||||||
return this.inPolygon(poly) === (offset > 0) && !this.nearPolygon(poly, mindist2);
|
return this.inPolygon(poly) === (offset > 0) && !this.nearPolygon(poly, mindist2);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns a new point following given slope for given distance
|
* returns a new point following given slope for given distance
|
||||||
* same as projectOnSlope() but scaled
|
* same as projectOnSlope() but scaled
|
||||||
*
|
|
||||||
* @param {Slope} slope
|
|
||||||
* @param {number} distance
|
|
||||||
* @returns {Point}
|
|
||||||
*/
|
*/
|
||||||
PRO.follow = function(slope, distance) {
|
follow(slope, distance) {
|
||||||
let ls = distance / Math.sqrt(slope.dx * slope.dx + slope.dy * slope.dy);
|
let ls = distance / Math.sqrt(slope.dx * slope.dx + slope.dy * slope.dy);
|
||||||
return newPoint(this.x + slope.dx * ls, this.y + slope.dy * ls, this.z);
|
return newPoint(this.x + slope.dx * ls, this.y + slope.dy * ls, this.z);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* for point, return z-plane intersecting point on line to next point
|
* for point, return intersecting point on z to next point if points
|
||||||
*
|
* are on either size of z
|
||||||
* @param {Point} p
|
|
||||||
* @param {number} z
|
|
||||||
* @returns {Point}
|
|
||||||
*/
|
*/
|
||||||
PRO.intersectZ = function(p, z) {
|
intersectZ(p, z) {
|
||||||
let dx = p.x - this.x,
|
let dx = p.x - this.x,
|
||||||
dy = p.y - this.y,
|
dy = p.y - this.y,
|
||||||
dz = p.z - this.z,
|
dz = p.z - this.z,
|
||||||
pct = 1 - ((p.z - z) / dz);
|
pct = 1 - ((p.z - z) / dz);
|
||||||
return newPoint(this.x + dx * pct, this.y + dy * pct, this.z + dz * pct);
|
return newPoint(this.x + dx * pct, this.y + dy * pct, this.z + dz * pct);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
isEqual2D(p) {
|
||||||
* @param {Point} p
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
PRO.isEqual2D = function(p) {
|
|
||||||
return this === p || (this.x === p.x && this.y === p.y);
|
return this === p || (this.x === p.x && this.y === p.y);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns true if points are close enough to be considered equivalent
|
* returns true if points are close enough to be considered equivalent
|
||||||
*
|
|
||||||
* @param {Point} p
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
*/
|
||||||
PRO.isMergable2D = function(p) {
|
isMergable2D(p) {
|
||||||
return this.isEqual2D(p) || (this.distToSq2D(p) < CONF.precision_merge_sq);
|
return this.isEqual2D(p) || (this.distToSq2D(p) < config.precision_merge_sq);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* compares 3D point
|
* compares 3D point
|
||||||
*
|
|
||||||
* @param {Point} p
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
*/
|
||||||
PRO.isEqual = function(p) {
|
isEqual(p) {
|
||||||
return this === p || (this.x === p.x && this.y === p.y && this.z === p.z);
|
return this === p || (this.x === p.x && this.y === p.y && this.z === p.z);
|
||||||
};
|
}
|
||||||
|
|
||||||
PRO.isEqual2D = function(p) {
|
isEqual2D(p) {
|
||||||
return this === p || (this.x === p.x && this.y === p.y);
|
return this === p || (this.x === p.x && this.y === p.y);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns true if points are close enough to be considered equivalent
|
* returns true if points are close enough to be considered equivalent
|
||||||
*
|
|
||||||
* @param {Point} p
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
*/
|
||||||
PRO.isMergable3D = function(p) {
|
isMergable3D(p) {
|
||||||
return this.isEqual(p) || (this.distToSq3D(p) < CONF.precision_merge_sq);
|
return this.isEqual(p) || (this.distToSq3D(p) < config.precision_merge_sq);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return true if point is inside 2D square size dist*2 around p
|
* return true if point is inside 2D square size dist*2 around p
|
||||||
*
|
|
||||||
* @param {Point} p
|
|
||||||
* @param {number} dist
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
*/
|
||||||
PRO.isInBox = function(p, dist) {
|
isInBox(p, dist) {
|
||||||
return Math.abs(this.x - p.x) < dist && Math.abs(this.y - p.y) < dist;
|
return Math.abs(this.x - p.x) < dist && Math.abs(this.y - p.y) < dist;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return min distance from point to a polygon
|
* return min distance from point to a polygon
|
||||||
|
|
@ -532,7 +392,7 @@
|
||||||
* @param {Polygon} poly
|
* @param {Polygon} poly
|
||||||
* @param {number} [threshold] stop looking if under threshold
|
* @param {number} [threshold] stop looking if under threshold
|
||||||
*/
|
*/
|
||||||
PRO.distToPolySegments = function(poly, threshold) {
|
distToPolySegments(poly, threshold) {
|
||||||
let point = this,
|
let point = this,
|
||||||
mindist = Infinity;
|
mindist = Infinity;
|
||||||
poly.forEachSegment(function(p1, p2) {
|
poly.forEachSegment(function(p1, p2) {
|
||||||
|
|
@ -542,32 +402,31 @@
|
||||||
if (mindist <= threshold) return true;
|
if (mindist <= threshold) return true;
|
||||||
});
|
});
|
||||||
return mindist;
|
return mindist;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Polygon} poly
|
* @param {Polygon} poly
|
||||||
* @param {number} [threshold] stop looking if under threshold
|
* @param {number} [threshold] stop looking if under threshold
|
||||||
*/
|
*/
|
||||||
PRO.distToPolyPoints = function(poly, threshold) {
|
distToPolyPoints(poly, threshold) {
|
||||||
let point = this, mindist = Infinity;
|
let point = this,
|
||||||
|
mindist = Infinity;
|
||||||
poly.forEachPoint(function(pp) {
|
poly.forEachPoint(function(pp) {
|
||||||
mindist = Math.min(mindist, point.distTo2D(pp));
|
mindist = Math.min(mindist, point.distTo2D(pp));
|
||||||
if (mindist < threshold) return true;
|
if (mindist < threshold) return true;
|
||||||
});
|
});
|
||||||
return mindist;
|
return mindist;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Point[]} points
|
|
||||||
* @param {number} max
|
|
||||||
* @returns {Point} nearest point (less than max) from array to this point
|
* @returns {Point} nearest point (less than max) from array to this point
|
||||||
*/
|
*/
|
||||||
PRO.nearestTo = function(points, max) {
|
nearestTo(points, max) {
|
||||||
if (!max) throw "missing max";
|
if (!max) throw "missing max";
|
||||||
let mind = Infinity,
|
let mind = Infinity,
|
||||||
minp = null,
|
minp = null,
|
||||||
i, p, d;
|
i, p, d;
|
||||||
for (i=0; i<points.length; i++) {
|
for (i = 0; i < points.length; i++) {
|
||||||
p = points[i];
|
p = points[i];
|
||||||
if (p === this || p.del) continue;
|
if (p === this || p.del) continue;
|
||||||
d = this.distToSq2D(p);
|
d = this.distToSq2D(p);
|
||||||
|
|
@ -577,14 +436,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return minp;
|
return minp;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Point[]} points
|
* @param {Point[]} points
|
||||||
* @return {number} average square dist to cloud of points
|
* @return {number} average square dist to cloud of points
|
||||||
*/
|
*/
|
||||||
PRO.averageDistTo = function(points) {
|
averageDistTo(points) {
|
||||||
let sum = 0.0, count = 0, i;
|
let sum = 0.0,
|
||||||
|
count = 0,
|
||||||
|
i;
|
||||||
for (i = 0; i < points.length; i++) {
|
for (i = 0; i < points.length; i++) {
|
||||||
if (points[i] != this) {
|
if (points[i] != this) {
|
||||||
sum += this.distToSq2D(points[i]);
|
sum += this.distToSq2D(points[i]);
|
||||||
|
|
@ -592,125 +453,125 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sum / count;
|
return sum / count;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* dist to point in 2D
|
* dist to point in 2D
|
||||||
*
|
|
||||||
* @param {Point} p
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
*/
|
||||||
PRO.distTo2D = function(p) {
|
distTo2D(p) {
|
||||||
let dx = this.x - p.x,
|
let dx = this.x - p.x,
|
||||||
dy = this.y - p.y;
|
dy = this.y - p.y;
|
||||||
return Math.sqrt(dx * dx + dy * dy);
|
return Math.sqrt(dx * dx + dy * dy);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* square of distance in 2D
|
* square of distance in 2D
|
||||||
*
|
|
||||||
* @param {Point} p
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
*/
|
||||||
PRO.distToSq2D = function(p) {
|
distToSq2D(p) {
|
||||||
let dx = this.x - p.x,
|
let dx = this.x - p.x,
|
||||||
dy = this.y - p.y;
|
dy = this.y - p.y;
|
||||||
return dx * dx + dy * dy;
|
return dx * dx + dy * dy;
|
||||||
};
|
}
|
||||||
|
|
||||||
PRO.distTo3D = function(p) {
|
distTo3D(p) {
|
||||||
let dx = this.x - p.x,
|
let dx = this.x - p.x,
|
||||||
dy = this.y - p.y,
|
dy = this.y - p.y,
|
||||||
dz = this.z - p.z;
|
dz = this.z - p.z;
|
||||||
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* square of distance in 3D
|
* square of distance in 3D
|
||||||
*
|
|
||||||
* @param {Point} p
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
*/
|
||||||
PRO.distToSq3D = function(p) {
|
distToSq3D(p) {
|
||||||
let dx = this.x - p.x,
|
let dx = this.x - p.x,
|
||||||
dy = this.y - p.y,
|
dy = this.y - p.y,
|
||||||
dz = this.z - p.z;
|
dz = this.z - p.z;
|
||||||
return dx * dx + dy * dy + dz * dz;
|
return dx * dx + dy * dy + dz * dz;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns true if point is inside triangle described by three points
|
* returns true if point is inside triangle described by three points
|
||||||
*
|
|
||||||
* @param {Point} a
|
|
||||||
* @param {Point} b
|
|
||||||
* @param {Point} c
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
*/
|
||||||
PRO.inTriangle = function(a, b, c) {
|
inTriangle(a, b, c) {
|
||||||
let as_x = this.x - a.x,
|
let as_x = this.x - a.x,
|
||||||
as_y = this.y - a.y,
|
as_y = this.y - a.y,
|
||||||
s_ab = (b.x - a.x) * as_y - (b.y - a.y) * as_x > 0;
|
s_ab = (b.x - a.x) * as_y - (b.y - a.y) * as_x > 0;
|
||||||
if ((c.x - a.x) * as_y - (c.y - a.y) * as_x > 0 == s_ab) return false;
|
if ((c.x - a.x) * as_y - (c.y - a.y) * as_x > 0 == s_ab) return false;
|
||||||
if ((c.x - b.x) * (this.y - b.y) - (c.y - b.y) * (this.x - b.x) > 0 != s_ab) return false;
|
if ((c.x - b.x) * (this.y - b.y) - (c.y - b.y) * (this.x - b.x) > 0 != s_ab) return false;
|
||||||
return true;
|
return true;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns true if point is on a line described by two points.
|
* returns true if point is on a line described by two points.
|
||||||
* test sum of distances p1->this + this->p2 ~= p1->p2 whens
|
* test sum of distances p1->this + this->p2 ~= p1->p2 whens
|
||||||
* slopes from p1->this same as this->p2
|
* slopes from p1->this same as this->p2
|
||||||
*
|
|
||||||
* @param {Point} p1
|
|
||||||
* @param {Point} p2
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
*/
|
||||||
PRO.onLine = function(p1, p2) {
|
onLine(p1, p2) {
|
||||||
return this.distToLine(p1, p2) < CONF.precision_point_on_line;
|
return this.distToLine(p1, p2) < config.precision_point_on_line;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
add(delta) {
|
||||||
*
|
|
||||||
* @param {THREE.Vector3} delta
|
|
||||||
* @return {Point} new offset point
|
|
||||||
*/
|
|
||||||
PRO.add = function(delta) {
|
|
||||||
return newPoint(this.x + delta.x, this.y + delta.y, this.z + delta.z);
|
return newPoint(this.x + delta.x, this.y + delta.y, this.z + delta.z);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
sub(delta) {
|
||||||
*
|
|
||||||
* @param {THREE.Vector3} delta
|
|
||||||
* @return {Point} new offset point
|
|
||||||
*/
|
|
||||||
PRO.sub = function(delta) {
|
|
||||||
return newPoint(this.x - delta.x, this.y - delta.y, this.z - delta.z);
|
return newPoint(this.x - delta.x, this.y - delta.y, this.z - delta.z);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
move(delta) {
|
||||||
*
|
|
||||||
* @param {THREE.Vector3} delta
|
|
||||||
*/
|
|
||||||
PRO.move = function(delta) {
|
|
||||||
this.x += delta.x;
|
this.x += delta.x;
|
||||||
this.y += delta.y;
|
this.y += delta.y;
|
||||||
this.z += delta.z;
|
this.z += delta.z;
|
||||||
return this;
|
return this;
|
||||||
};
|
|
||||||
|
|
||||||
/** ******************************************************************
|
|
||||||
* Connect to base and Helpers
|
|
||||||
******************************************************************* */
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {number} x
|
|
||||||
* @param {number} y
|
|
||||||
* @param {number} z
|
|
||||||
* @param {String} [key]
|
|
||||||
* @returns {Point}
|
|
||||||
*/
|
|
||||||
function newPoint(x, y, z, key) {
|
|
||||||
return new Point(x, y, z, key);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dot(u, v) {
|
||||||
|
return u.x * v.x + u.y * v.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
function norm(v) {
|
||||||
|
return Math.sqrt(dot(v, v));
|
||||||
|
}
|
||||||
|
|
||||||
|
function d(u, v) {
|
||||||
|
return norm({
|
||||||
|
x: u.x - v.x,
|
||||||
|
y: u.y - v.y
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function p2l(p, l1, l2) {
|
||||||
|
let v = {
|
||||||
|
x: l2.x - l1.x,
|
||||||
|
y: l2.y - l1.y
|
||||||
|
};
|
||||||
|
let w = {
|
||||||
|
x: p.x - l1.x,
|
||||||
|
y: p.y - l1.y
|
||||||
|
};
|
||||||
|
let c1 = dot(w, v);
|
||||||
|
let c2 = dot(v, v);
|
||||||
|
let b = c1 / c2;
|
||||||
|
if (isNaN(b)) {
|
||||||
|
// console.log('nan', {p, l1, l2, v, w, c1, c2});
|
||||||
|
b = 0;
|
||||||
|
}
|
||||||
|
let pb = {
|
||||||
|
x: l1.x + b * v.x,
|
||||||
|
y: l1.y + b * v.y
|
||||||
|
};
|
||||||
|
return d(p, pb);
|
||||||
|
}
|
||||||
|
function newPoint(x, y, z, key) {
|
||||||
|
return new Point(x, y, z, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Point = Point;
|
||||||
|
base.newPoint = newPoint;
|
||||||
|
base.pointFromClipper = function(cp, z) {
|
||||||
|
return newPoint(cp.X / config.clipper, cp.Y / config.clipper, z);
|
||||||
|
};
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -4,20 +4,17 @@
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
const BASE = self.base;
|
const base = self.base;
|
||||||
const CONF = BASE.config;
|
const { config } = base;
|
||||||
|
|
||||||
BASE.verticesToPoints = verticesToPoints;
|
base.verticesToPoints = verticesToPoints;
|
||||||
BASE.pointsToVertices = pointsToVertices;
|
base.pointsToVertices = pointsToVertices;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* converts a geometry point array into a kiri point array
|
* converts a geometry point array into a kiri point array
|
||||||
* with auto-decimation
|
* with auto-decimation
|
||||||
*
|
|
||||||
* @param {Float32Array} array
|
|
||||||
* @returns {Array}
|
|
||||||
*/
|
*/
|
||||||
function verticesToPoints(array, options) {
|
function verticesToPoints(array, options) {
|
||||||
let parr = new Array(array.length / 3),
|
let parr = new Array(array.length / 3),
|
||||||
i = 0,
|
i = 0,
|
||||||
j = 0,
|
j = 0,
|
||||||
|
|
@ -31,7 +28,7 @@
|
||||||
|
|
||||||
// replace point objects with their equivalents
|
// replace point objects with their equivalents
|
||||||
while (i < array.length) {
|
while (i < array.length) {
|
||||||
let p = BASE.newPoint(array[i++], array[i++], array[i++]),
|
let p = base.newPoint(array[i++], array[i++], array[i++]),
|
||||||
k = p.key,
|
k = p.key,
|
||||||
m = hash[k];
|
m = hash[k];
|
||||||
if (!m) {
|
if (!m) {
|
||||||
|
|
@ -46,8 +43,8 @@
|
||||||
// threshold = point count for triggering decimation
|
// threshold = point count for triggering decimation
|
||||||
// precision = under which points are merged
|
// precision = under which points are merged
|
||||||
// maxpass = max number of decimations
|
// maxpass = max number of decimations
|
||||||
threshold = threshold > 0 ? threshold : CONF.decimate_threshold;
|
threshold = threshold > 0 ? threshold : config.decimate_threshold;
|
||||||
precision = precision >= 0 ? precision : CONF.precision_decimate;
|
precision = precision >= 0 ? precision : config.precision_decimate;
|
||||||
maxpass = maxpass >= 0 ? maxpass : 10;
|
maxpass = maxpass >= 0 ? maxpass : 10;
|
||||||
|
|
||||||
// decimate until all point spacing > precision
|
// decimate until all point spacing > precision
|
||||||
|
|
@ -115,9 +112,9 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
return parr;
|
return parr;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pointsToVertices(points) {
|
function pointsToVertices(points) {
|
||||||
let vertices = new Float32Array(points.length * 3),
|
let vertices = new Float32Array(points.length * 3),
|
||||||
i = 0, vi = 0;
|
i = 0, vi = 0;
|
||||||
while (i < points.length) {
|
while (i < points.length) {
|
||||||
|
|
@ -126,6 +123,6 @@
|
||||||
vertices[vi++] = points[i++].z;
|
vertices[vi++] = points[i++].z;
|
||||||
}
|
}
|
||||||
return vertices;
|
return vertices;
|
||||||
}
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -4,21 +4,24 @@
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
if (!self.base) self.base = {};
|
const base = self.base;
|
||||||
if (self.base.polygons) return;
|
if (base.polygons) return;
|
||||||
|
|
||||||
const BASE = self.base,
|
const { util, config, newPoint } = base;
|
||||||
UTIL = BASE.util,
|
const { sqr, numOrDefault } = util;
|
||||||
CONF = BASE.config,
|
|
||||||
DEG2RAD = Math.PI / 180,
|
const DEG2RAD = Math.PI / 180,
|
||||||
ABS = Math.abs,
|
|
||||||
SQRT = Math.sqrt,
|
SQRT = Math.sqrt,
|
||||||
SQR = UTIL.sqr,
|
SQR = util.sqr,
|
||||||
NOKEY = BASE.key.NONE,
|
ABS = Math.abs;
|
||||||
newPoint = BASE.newPoint,
|
|
||||||
numOrDefault = UTIL.numOrDefault;
|
|
||||||
|
|
||||||
const POLYS = BASE.polygons = {
|
const clib = self.ClipperLib,
|
||||||
|
clip = clib.Clipper,
|
||||||
|
ctyp = clib.ClipType,
|
||||||
|
ptyp = clib.PolyType,
|
||||||
|
cfil = clib.PolyFillType;
|
||||||
|
|
||||||
|
const POLYS = base.polygons = {
|
||||||
rayIntersect,
|
rayIntersect,
|
||||||
alignWindings,
|
alignWindings,
|
||||||
setWinding,
|
setWinding,
|
||||||
|
|
@ -44,37 +47,33 @@
|
||||||
cleanClipperTree,
|
cleanClipperTree,
|
||||||
fingerprintCompare,
|
fingerprintCompare,
|
||||||
fingerprint
|
fingerprint
|
||||||
};
|
};
|
||||||
|
|
||||||
/** ******************************************************************
|
function setZ(polys, z) {
|
||||||
* Polygon array utility functions
|
|
||||||
******************************************************************* */
|
|
||||||
|
|
||||||
function setZ(polys, z) {
|
|
||||||
for (let poly of polys) {
|
for (let poly of polys) {
|
||||||
poly.setZ(z);
|
poly.setZ(z);
|
||||||
}
|
}
|
||||||
return polys;
|
return polys;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toClipper(polys = []) {
|
function toClipper(polys = []) {
|
||||||
let out = [];
|
let out = [];
|
||||||
for (let poly of polys) {
|
for (let poly of polys) {
|
||||||
poly.toClipper(out);
|
poly.toClipper(out);
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fromClipperNode(tnode, z) {
|
function fromClipperNode(tnode, z) {
|
||||||
let poly = BASE.newPolygon();
|
let poly = base.newPolygon();
|
||||||
for (let point of tnode.m_polygon) {
|
for (let point of tnode.m_polygon) {
|
||||||
poly.push(BASE.pointFromClipper(point, z));
|
poly.push(base.pointFromClipper(point, z));
|
||||||
}
|
}
|
||||||
poly.open = tnode.IsOpen;
|
poly.open = tnode.IsOpen;
|
||||||
return poly;
|
return poly;
|
||||||
};
|
};
|
||||||
|
|
||||||
function fromClipperTree(tnode, z, tops, parent, minarea) {
|
function fromClipperTree(tnode, z, tops, parent, minarea) {
|
||||||
let poly,
|
let poly,
|
||||||
polys = tops || [],
|
polys = tops || [],
|
||||||
min = numOrDefault(minarea, 0.1);
|
min = numOrDefault(minarea, 0.1);
|
||||||
|
|
@ -96,9 +95,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
return polys;
|
return polys;
|
||||||
};
|
};
|
||||||
|
|
||||||
function fromClipperTreeUnion(tnode, z, minarea, tops, parent) {
|
function fromClipperTreeUnion(tnode, z, minarea, tops, parent) {
|
||||||
let polys = tops || [], poly;
|
let polys = tops || [], poly;
|
||||||
|
|
||||||
for (let child of tnode.m_Childs) {
|
for (let child of tnode.m_Childs) {
|
||||||
|
|
@ -117,22 +116,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
return polys;
|
return polys;
|
||||||
};
|
};
|
||||||
|
|
||||||
function cleanClipperTree(tree) {
|
|
||||||
let clib = self.ClipperLib,
|
|
||||||
clip = clib.Clipper;
|
|
||||||
|
|
||||||
|
function cleanClipperTree(tree) {
|
||||||
if (tree.m_Childs)
|
if (tree.m_Childs)
|
||||||
for (let child of tree.m_Childs) {
|
for (let child of tree.m_Childs) {
|
||||||
child.m_polygon = clip.CleanPolygon(child.m_polygon, CONF.clipperClean);
|
child.m_polygon = clip.CleanPolygon(child.m_polygon, config.clipperClean);
|
||||||
cleanClipperTree(child.m_Childs);
|
cleanClipperTree(child.m_Childs);
|
||||||
}
|
}
|
||||||
|
|
||||||
return tree;
|
return tree;
|
||||||
};
|
};
|
||||||
|
|
||||||
function filter(array, output, fn) {
|
function filter(array, output, fn) {
|
||||||
for (let poly of array) {
|
for (let poly of array) {
|
||||||
poly = fn(poly);
|
poly = fn(poly);
|
||||||
if (poly) {
|
if (poly) {
|
||||||
|
|
@ -144,13 +140,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
function points(polys) {
|
function points(polys) {
|
||||||
return polys.length ? polys.map(p => p.deepLength).reduce((a,v) => a+v) : 0;
|
return polys.length ? polys.map(p => p.deepLength).reduce((a,v) => a+v) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* todo use clipper polytree?
|
* todo use clipper polytree?
|
||||||
*
|
*
|
||||||
* use bounding boxes and sliceIntersection
|
* use bounding boxes and sliceIntersection
|
||||||
|
|
@ -162,7 +158,7 @@
|
||||||
* @param {boolean} opentop prevent open polygons from having inners
|
* @param {boolean} opentop prevent open polygons from having inners
|
||||||
* @returns {Polygon[]} top level parent polygons
|
* @returns {Polygon[]} top level parent polygons
|
||||||
*/
|
*/
|
||||||
function nest(polygons, deep, opentop) {
|
function nest(polygons, deep, opentop) {
|
||||||
if (!polygons) {
|
if (!polygons) {
|
||||||
return polygons;
|
return polygons;
|
||||||
}
|
}
|
||||||
|
|
@ -216,9 +212,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tops;
|
return tops;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sets windings for parents one way
|
* sets windings for parents one way
|
||||||
* and children in opposition
|
* and children in opposition
|
||||||
*
|
*
|
||||||
|
|
@ -226,7 +222,7 @@
|
||||||
* @param {boolean} CW
|
* @param {boolean} CW
|
||||||
* @param {boolean} [recurse]
|
* @param {boolean} [recurse]
|
||||||
*/
|
*/
|
||||||
function setWinding(array, CW, recurse) {
|
function setWinding(array, CW, recurse) {
|
||||||
if (!array) return;
|
if (!array) return;
|
||||||
let poly, i = 0;
|
let poly, i = 0;
|
||||||
while (i < array.length) {
|
while (i < array.length) {
|
||||||
|
|
@ -234,16 +230,16 @@
|
||||||
if (poly.isClockwise() !== CW) poly.reverse();
|
if (poly.isClockwise() !== CW) poly.reverse();
|
||||||
if (recurse && poly.inner) setWinding(poly.inner, !CW, false);
|
if (recurse && poly.inner) setWinding(poly.inner, !CW, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ensure all polygons have the same winding direction.
|
* ensure all polygons have the same winding direction.
|
||||||
* try to use reversals that touch the fewest nodes.
|
* try to use reversals that touch the fewest nodes.
|
||||||
*
|
*
|
||||||
* @param {Polygon[]} polys
|
* @param {Polygon[]} polys
|
||||||
* @return {boolean} true if aligned clockwise
|
* @return {boolean} true if aligned clockwise
|
||||||
*/
|
*/
|
||||||
function alignWindings(polys) {
|
function alignWindings(polys) {
|
||||||
let len = polys.length,
|
let len = polys.length,
|
||||||
fwd = 0,
|
fwd = 0,
|
||||||
pts = 0,
|
pts = 0,
|
||||||
|
|
@ -262,25 +258,25 @@
|
||||||
if (poly.isClockwise() != setCW) poly.reverse();
|
if (poly.isClockwise() != setCW) poly.reverse();
|
||||||
}
|
}
|
||||||
return setCW;
|
return setCW;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setContains(setA, poly) {
|
function setContains(setA, poly) {
|
||||||
for (let i=0; i<setA.length; i++) {
|
for (let i=0; i<setA.length; i++) {
|
||||||
if (setA[i].contains(poly)) return true;
|
if (setA[i].contains(poly)) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function flatten(polys, to, crush) {
|
function flatten(polys, to, crush) {
|
||||||
to = to || [];
|
to = to || [];
|
||||||
polys.forEach(function(poly) {
|
polys.forEach(function(poly) {
|
||||||
poly.flattenTo(to);
|
poly.flattenTo(to);
|
||||||
if (crush) poly.inner = null;
|
if (crush) poly.inner = null;
|
||||||
});
|
});
|
||||||
return to;
|
return to;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Diff two sets of polygons and return A-B, B-A.
|
* Diff two sets of polygons and return A-B, B-A.
|
||||||
* no polygons in a given set can overlap ... only between sets
|
* no polygons in a given set can overlap ... only between sets
|
||||||
*
|
*
|
||||||
|
|
@ -292,7 +288,7 @@
|
||||||
* @param {number} [minArea]
|
* @param {number} [minArea]
|
||||||
* @returns {Polygon[]} out
|
* @returns {Polygon[]} out
|
||||||
*/
|
*/
|
||||||
function subtract(setA, setB, outA, outB, z, minArea, opt = {}) {
|
function subtract(setA, setB, outA, outB, z, minArea, opt = {}) {
|
||||||
let min = minArea || 0.1,
|
let min = minArea || 0.1,
|
||||||
out = [];
|
out = [];
|
||||||
|
|
||||||
|
|
@ -326,11 +322,7 @@
|
||||||
outB.appendAll(filter(oB));
|
outB.appendAll(filter(oB));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let clib = self.ClipperLib,
|
let clip = new clib.Clipper(),
|
||||||
ctyp = clib.ClipType,
|
|
||||||
ptyp = clib.PolyType,
|
|
||||||
cfil = clib.PolyFillType,
|
|
||||||
clip = new clib.Clipper(),
|
|
||||||
ctre = new clib.PolyTree(),
|
ctre = new clib.PolyTree(),
|
||||||
sp1 = toClipper(setA),
|
sp1 = toClipper(setA),
|
||||||
sp2 = toClipper(setB);
|
sp2 = toClipper(setB);
|
||||||
|
|
@ -364,9 +356,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Slice.doProjectedFills()
|
* Slice.doProjectedFills()
|
||||||
* Print.init w/ brims
|
* Print.init w/ brims
|
||||||
*
|
*
|
||||||
|
|
@ -419,16 +411,12 @@
|
||||||
return uset;
|
return uset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Polygon} poly clipping mask
|
* @param {Polygon} poly clipping mask
|
||||||
* @returns {?Polygon[]}
|
* @returns {?Polygon[]}
|
||||||
*/
|
*/
|
||||||
function diff(setA, setB, z) {
|
function diff(setA, setB, z) {
|
||||||
let clib = self.ClipperLib,
|
let clip = new clib.Clipper(),
|
||||||
ctyp = clib.ClipType,
|
|
||||||
ptyp = clib.PolyType,
|
|
||||||
cfil = clib.PolyFillType,
|
|
||||||
clip = new clib.Clipper(),
|
|
||||||
ctre = new clib.PolyTree(),
|
ctre = new clib.PolyTree(),
|
||||||
sp1 = toClipper(setA),
|
sp1 = toClipper(setA),
|
||||||
sp2 = toClipper(setB);
|
sp2 = toClipper(setB);
|
||||||
|
|
@ -441,45 +429,44 @@
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Slice.doProjectedFills()
|
* Slice.doProjectedFills()
|
||||||
*
|
*
|
||||||
* @param {Polygon[]} setA source set
|
* @param {Polygon[]} setA source set
|
||||||
* @param {Polygon[]} setB mask set
|
* @param {Polygon[]} setB mask set
|
||||||
* @returns {Polygon[]}
|
* @returns {Polygon[]}
|
||||||
*/
|
*/
|
||||||
function trimTo(setA, setB) {
|
function trimTo(setA, setB) {
|
||||||
// handle null/empty slices
|
// handle null/empty slices
|
||||||
if (setA === setB || setA === null || setB === null) return null;
|
if (setA === setB || setA === null || setB === null) return null;
|
||||||
|
|
||||||
let out = [], tmp;
|
let out = [], tmp;
|
||||||
UTIL.doCombinations(setA, setB, {}, function(a, b) {
|
util.doCombinations(setA, setB, {}, function(a, b) {
|
||||||
if (tmp = a.mask(b)) {
|
if (tmp = a.mask(b)) {
|
||||||
out.appendAll(tmp);
|
out.appendAll(tmp);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sumCirc(polys) {
|
function sumCirc(polys) {
|
||||||
let sum = 0.0;
|
let sum = 0.0;
|
||||||
polys.forEach(function(poly) {
|
polys.forEach(function(poly) {
|
||||||
sum += poly.circularityDeep();
|
sum += poly.circularityDeep();
|
||||||
});
|
});
|
||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Polygon[]} polys
|
* @param {Polygon[]} polys
|
||||||
* @param {number} distance offset
|
* @param {number} distance offset
|
||||||
* @param {number} [z] defaults to 0
|
* @param {number} [z] defaults to 0
|
||||||
*/
|
*/
|
||||||
function expand_lines(poly, distance, z) {
|
function expand_lines(poly, distance, z) {
|
||||||
let fact = CONF.clipper,
|
let fact = config.clipper,
|
||||||
clib = self.ClipperLib,
|
|
||||||
cjnt = clib.JoinType,
|
cjnt = clib.JoinType,
|
||||||
cety = clib.EndType,
|
cety = clib.EndType,
|
||||||
coff = new clib.ClipperOffset(),
|
coff = new clib.ClipperOffset(),
|
||||||
|
|
@ -489,9 +476,9 @@
|
||||||
coff.Execute(ctre, distance * fact);
|
coff.Execute(ctre, distance * fact);
|
||||||
|
|
||||||
return fromClipperTree(ctre, z, null, null, 0);
|
return fromClipperTree(ctre, z, null, null, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Polygon[]} polys
|
* @param {Polygon[]} polys
|
||||||
* @param {number} distance offset
|
* @param {number} distance offset
|
||||||
* @param {number} [z] defaults to 0
|
* @param {number} [z] defaults to 0
|
||||||
|
|
@ -501,18 +488,18 @@
|
||||||
* @param {Function} [collector] receives output of each pass
|
* @param {Function} [collector] receives output of each pass
|
||||||
* @returns {Polygon[]} last offset
|
* @returns {Polygon[]} last offset
|
||||||
*/
|
*/
|
||||||
function expand(polys, distance, z, out, count, distance2, collector, min) {
|
function expand(polys, distance, z, out, count, distance2, collector, min) {
|
||||||
return offset(polys, [distance, distance2 || distance], {
|
return offset(polys, [distance, distance2 || distance], {
|
||||||
z, outs: out, call: collector, minArea: min, count, flat: true
|
z, outs: out, call: collector, minArea: min, count, flat: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* offset an array of polygons by distance with options to recurse
|
* offset an array of polygons by distance with options to recurse
|
||||||
* and return resulting gaps from offsets for thin wall detection in
|
* and return resulting gaps from offsets for thin wall detection in
|
||||||
* in FDM mode and uncleared areas in CAM mode.
|
* in FDM mode and uncleared areas in CAM mode.
|
||||||
*/
|
*/
|
||||||
function offset(polys, dist, opts = {}) {
|
function offset(polys, dist, opts = {}) {
|
||||||
// do not offset open lines
|
// do not offset open lines
|
||||||
polys = polys.filter(p => !p.open);
|
polys = polys.filter(p => !p.open);
|
||||||
|
|
||||||
|
|
@ -529,9 +516,9 @@
|
||||||
depth = numOrDefault(opts.depth, 0),
|
depth = numOrDefault(opts.depth, 0),
|
||||||
clean = opts.clean !== false,
|
clean = opts.clean !== false,
|
||||||
simple = opts.simple !== false,
|
simple = opts.simple !== false,
|
||||||
fill = numOrDefault(opts.fill, ClipperLib.PolyFillType.pftNonZero),
|
fill = numOrDefault(opts.fill, clib.PolyFillType.pftNonZero),
|
||||||
join = numOrDefault(opts.join, ClipperLib.JoinType.jtMiter),
|
join = numOrDefault(opts.join, clib.JoinType.jtMiter),
|
||||||
type = numOrDefault(opts.type, ClipperLib.EndType.etClosedPolygon),
|
type = numOrDefault(opts.type, clib.EndType.etClosedPolygon),
|
||||||
// if dist is array with values, shift out next offset
|
// if dist is array with values, shift out next offset
|
||||||
offs = Array.isArray(dist) ? (dist.length > 1 ? dist.shift() : dist[0]) : dist,
|
offs = Array.isArray(dist) ? (dist.length > 1 ? dist.shift() : dist[0]) : dist,
|
||||||
mina = numOrDefault(opts.minArea, 0.1),
|
mina = numOrDefault(opts.minArea, 0.1),
|
||||||
|
|
@ -539,26 +526,26 @@
|
||||||
|
|
||||||
if (opts.wasm && geo.wasm) {
|
if (opts.wasm && geo.wasm) {
|
||||||
try {
|
try {
|
||||||
polys = geo.wasm.js.offset(polys, offs, zed, clean ? CONF.clipperClean : 0, simple ? 1 : 0);
|
polys = geo.wasm.js.offset(polys, offs, zed, clean ? config.clipperClean : 0, simple ? 1 : 0);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('wasm error', e.message || e);
|
console.log('wasm error', e.message || e);
|
||||||
opts.wasm = false;
|
opts.wasm = false;
|
||||||
return offset(polys, dist, opts);
|
return offset(polys, dist, opts);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let coff = new ClipperLib.ClipperOffset(opts.miter, opts.arc),
|
let coff = new clib.ClipperOffset(opts.miter, opts.arc),
|
||||||
ctre = new ClipperLib.PolyTree();
|
ctre = new clib.PolyTree();
|
||||||
|
|
||||||
// setup offset
|
// setup offset
|
||||||
for (let poly of polys) {
|
for (let poly of polys) {
|
||||||
// convert to clipper format
|
// convert to clipper format
|
||||||
poly = poly.toClipper();
|
poly = poly.toClipper();
|
||||||
if (clean) poly = ClipperLib.Clipper.CleanPolygons(poly, CONF.clipperClean);
|
if (clean) poly = clib.Clipper.CleanPolygons(poly, config.clipperClean);
|
||||||
if (simple) poly = ClipperLib.Clipper.SimplifyPolygons(poly, fill);
|
if (simple) poly = clib.Clipper.SimplifyPolygons(poly, fill);
|
||||||
coff.AddPaths(poly, join, type);
|
coff.AddPaths(poly, join, type);
|
||||||
}
|
}
|
||||||
// perform offset
|
// perform offset
|
||||||
coff.Execute(ctre, offs * CONF.clipper);
|
coff.Execute(ctre, offs * config.clipper);
|
||||||
// convert back from clipper output format
|
// convert back from clipper output format
|
||||||
polys = fromClipperTree(ctre, zed, null, null, mina);
|
polys = fromClipperTree(ctre, zed, null, null, mina);
|
||||||
}
|
}
|
||||||
|
|
@ -600,14 +587,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
return opts.flat ? opts.outs : polys;
|
return opts.flat ? opts.outs : polys;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* progressive insetting that does inset + outset to debur as well
|
* progressive insetting that does inset + outset to debur as well
|
||||||
* as performing subtractive analysis between initial layer shell (ref)
|
* as performing subtractive analysis between initial layer shell (ref)
|
||||||
* and last offset (cmp) to produce gap candidates (for thinfill)
|
* and last offset (cmp) to produce gap candidates (for thinfill)
|
||||||
*/
|
*/
|
||||||
function inset(polys, dist, count, z, wasm) {
|
function inset(polys, dist, count, z, wasm) {
|
||||||
let total = count;
|
let total = count;
|
||||||
let layers = [];
|
let layers = [];
|
||||||
let ref = polys;
|
let ref = polys;
|
||||||
|
|
@ -637,9 +624,9 @@
|
||||||
ref = off;
|
ref = off;
|
||||||
}
|
}
|
||||||
return layers;
|
return layers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* todo use clipper opne poly clipping?
|
* todo use clipper opne poly clipping?
|
||||||
*
|
*
|
||||||
* @param {Polygon[]} polys
|
* @param {Polygon[]} polys
|
||||||
|
|
@ -650,7 +637,7 @@
|
||||||
* @param {number} [maxLen]
|
* @param {number} [maxLen]
|
||||||
* @returns {Point[]} supplied output or new array
|
* @returns {Point[]} supplied output or new array
|
||||||
*/
|
*/
|
||||||
function fillArea(polys, angle, spacing, output, minLen, maxLen) {
|
function fillArea(polys, angle, spacing, output, minLen, maxLen) {
|
||||||
if (polys.length === 0) return;
|
if (polys.length === 0) return;
|
||||||
|
|
||||||
let i = 1,
|
let i = 1,
|
||||||
|
|
@ -665,7 +652,7 @@
|
||||||
while (angle > 90) angle -= 180;
|
while (angle > 90) angle -= 180;
|
||||||
|
|
||||||
// X,Y ray slope derived from angle
|
// X,Y ray slope derived from angle
|
||||||
raySlope = BASE.newSlope(0,0,
|
raySlope = base.newSlope(0,0,
|
||||||
Math.cos(angle * DEG2RAD) * spacing,
|
Math.cos(angle * DEG2RAD) * spacing,
|
||||||
Math.sin(angle * DEG2RAD) * spacing
|
Math.sin(angle * DEG2RAD) * spacing
|
||||||
);
|
);
|
||||||
|
|
@ -685,14 +672,10 @@
|
||||||
step = SQRT(SQR(stepX) + SQR(stepY)),
|
step = SQRT(SQR(stepX) + SQR(stepY)),
|
||||||
steps = dist / step,
|
steps = dist / step,
|
||||||
start = angle < 0 ? { x:bounds.minx, y:bounds.miny, z:zpos } : { x:bounds.maxx, y:bounds.miny, z:zpos },
|
start = angle < 0 ? { x:bounds.minx, y:bounds.miny, z:zpos } : { x:bounds.maxx, y:bounds.miny, z:zpos },
|
||||||
clib = self.ClipperLib,
|
|
||||||
ctyp = clib.ClipType,
|
|
||||||
ptyp = clib.PolyType,
|
|
||||||
cfil = clib.PolyFillType,
|
|
||||||
clip = new clib.Clipper(),
|
clip = new clib.Clipper(),
|
||||||
ctre = new clib.PolyTree(),
|
ctre = new clib.PolyTree(),
|
||||||
minlen = BASE.config.clipper * (minLen || 0),
|
minlen = base.config.clipper * (minLen || 0),
|
||||||
maxlen = BASE.config.clipper * (maxLen || 0),
|
maxlen = base.config.clipper * (maxLen || 0),
|
||||||
lines = [];
|
lines = [];
|
||||||
|
|
||||||
// store origin as start/affinity point for fill
|
// store origin as start/affinity point for fill
|
||||||
|
|
@ -701,11 +684,11 @@
|
||||||
for (i = 0; i < steps; i++) {
|
for (i = 0; i < steps; i++) {
|
||||||
lines.push([
|
lines.push([
|
||||||
{
|
{
|
||||||
X: (start.x - raySlope.dx * 1000) * CONF.clipper,
|
X: (start.x - raySlope.dx * 1000) * config.clipper,
|
||||||
Y: (start.y - raySlope.dy * 1000) * CONF.clipper
|
Y: (start.y - raySlope.dy * 1000) * config.clipper
|
||||||
},{
|
},{
|
||||||
X: (start.x + raySlope.dx * 1000) * CONF.clipper,
|
X: (start.x + raySlope.dx * 1000) * config.clipper,
|
||||||
Y: (start.y + raySlope.dy * 1000) * CONF.clipper
|
Y: (start.y + raySlope.dy * 1000) * config.clipper
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
start.x += stepX;
|
start.x += stepX;
|
||||||
|
|
@ -724,8 +707,8 @@
|
||||||
if (minlen && plen < minlen) continue;
|
if (minlen && plen < minlen) continue;
|
||||||
if (maxlen && plen > maxlen) continue;
|
if (maxlen && plen > maxlen) continue;
|
||||||
}
|
}
|
||||||
let p1 = BASE.pointFromClipper(poly.m_polygon[0], zpos);
|
let p1 = base.pointFromClipper(poly.m_polygon[0], zpos);
|
||||||
let p2 = BASE.pointFromClipper(poly.m_polygon[1], zpos);
|
let p2 = base.pointFromClipper(poly.m_polygon[1], zpos);
|
||||||
let od = rayint.origin.distToLineNew(p1,p2) / spacing;
|
let od = rayint.origin.distToLineNew(p1,p2) / spacing;
|
||||||
lines.push([p1, p2, od]);
|
lines.push([p1, p2, od]);
|
||||||
}
|
}
|
||||||
|
|
@ -744,9 +727,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
return rayint;
|
return rayint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tracing a ray through a slice's polygons, find and return
|
* tracing a ray through a slice's polygons, find and return
|
||||||
* a sorted list of all intersecting points.
|
* a sorted list of all intersecting points.
|
||||||
*
|
*
|
||||||
|
|
@ -756,11 +739,11 @@
|
||||||
* @param {boolean} [for_fill]
|
* @param {boolean} [for_fill]
|
||||||
* @returns {Point[]}
|
* @returns {Point[]}
|
||||||
*/
|
*/
|
||||||
function rayIntersect(start, slope, polygons, for_fill) {
|
function rayIntersect(start, slope, polygons, for_fill) {
|
||||||
let i = 0,
|
let i = 0,
|
||||||
flat = [],
|
flat = [],
|
||||||
points = [],
|
points = [],
|
||||||
conf = BASE.config,
|
conf = base.config,
|
||||||
merge_dist = for_fill ? conf.precision_fill_merge : conf.precision_merge;
|
merge_dist = for_fill ? conf.precision_fill_merge : conf.precision_merge;
|
||||||
// todo use new flatten() function above
|
// todo use new flatten() function above
|
||||||
polygons.forEach(function(p) {
|
polygons.forEach(function(p) {
|
||||||
|
|
@ -773,7 +756,7 @@
|
||||||
pl = pp.length;;
|
pl = pp.length;;
|
||||||
for (let j = 0; j < pl; j++) {
|
for (let j = 0; j < pl; j++) {
|
||||||
let j2 = (j + 1) % pl,
|
let j2 = (j + 1) % pl,
|
||||||
ip = UTIL.intersectRayLine(start, slope, pp[j], pp[j2]);
|
ip = util.intersectRayLine(start, slope, pp[j], pp[j2]);
|
||||||
if (ip) {
|
if (ip) {
|
||||||
// add group object to point for cull detection
|
// add group object to point for cull detection
|
||||||
ip.group = polygon;
|
ip.group = polygon;
|
||||||
|
|
@ -819,7 +802,7 @@
|
||||||
* and test intersect the ray with that line. if it intersects, the points are
|
* and test intersect the ray with that line. if it intersects, the points are
|
||||||
* inside and we keep one of them. otherwise, they are outside and we drop both.
|
* inside and we keep one of them. otherwise, they are outside and we drop both.
|
||||||
*/
|
*/
|
||||||
if (!UTIL.intersectRayLine(start, slope, line[0], line[1])) {
|
if (!util.intersectRayLine(start, slope, line[0], line[1])) {
|
||||||
del = true;
|
del = true;
|
||||||
p1.del = true;
|
p1.del = true;
|
||||||
p2.del = true;
|
p2.del = true;
|
||||||
|
|
@ -883,9 +866,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fingerprint(polys) {
|
function fingerprint(polys) {
|
||||||
let finger = [];
|
let finger = [];
|
||||||
flatten(polys).sort((a,b) => {
|
flatten(polys).sort((a,b) => {
|
||||||
return a.area() > b.area();
|
return a.area() > b.area();
|
||||||
|
|
@ -897,10 +880,10 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return finger;
|
return finger;
|
||||||
}
|
}
|
||||||
|
|
||||||
// compare fingerprint arrays
|
// compare fingerprint arrays
|
||||||
function fingerprintCompare(a, b) {
|
function fingerprintCompare(a, b) {
|
||||||
// true if array is the same object
|
// true if array is the same object
|
||||||
if (a === b) {
|
if (a === b) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -930,11 +913,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// plan a route through an array of polygon center points
|
// plan a route through an array of polygon center points
|
||||||
// starting with the polygon center closest to "start"
|
// starting with the polygon center closest to "start"
|
||||||
function route(polys, start) {
|
function route(polys, start) {
|
||||||
let centers = [];
|
let centers = [];
|
||||||
let first, minDist = Infinity;
|
let first, minDist = Infinity;
|
||||||
for (let poly of polys) {
|
for (let poly of polys) {
|
||||||
|
|
@ -969,6 +952,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return routed.map(r => r.poly);
|
return routed.map(r => r.poly);
|
||||||
}
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
|
|
||||||
if (!self.base) self.base = {};
|
|
||||||
if (self.base.render) return;
|
|
||||||
|
|
||||||
const BASE = self.base,
|
|
||||||
materialCache = {},
|
|
||||||
line_width = 1;
|
|
||||||
|
|
||||||
BASE.render = {
|
|
||||||
wireframe : wireframe
|
|
||||||
};
|
|
||||||
|
|
||||||
/** ******************************************************************
|
|
||||||
* Render Functions
|
|
||||||
******************************************************************* */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* render triangle array as line segments. use of {@link newOrderedLine}
|
|
||||||
* allows lines to be cached by normalizing key order.
|
|
||||||
*
|
|
||||||
* @param {THREE.Group} group
|
|
||||||
* @param {Point[]} points
|
|
||||||
* @param {number} color
|
|
||||||
* @returns {THREE.Line}
|
|
||||||
*/
|
|
||||||
function wireframe(group, points, color) {
|
|
||||||
if (points.length % 3 != 0) throw "invalid line : "+points.length;
|
|
||||||
let lines = new THREE.BufferGeometry(),
|
|
||||||
hash = {},
|
|
||||||
added = 0,
|
|
||||||
vertices = [];
|
|
||||||
for (let i = 0; i < points.length; i += 3) {
|
|
||||||
let p1 = points[i],
|
|
||||||
p2 = points[i + 1],
|
|
||||||
p3 = points[i + 2],
|
|
||||||
l1 = newOrderedLine(p1, p2),
|
|
||||||
l2 = newOrderedLine(p2, p3),
|
|
||||||
l3 = newOrderedLine(p3, p1);
|
|
||||||
if (!hash[l1.key]) {
|
|
||||||
vertices.appendAll([
|
|
||||||
p1.x, p1.y, p1.z,
|
|
||||||
p2.x, p2.y, p2.z
|
|
||||||
]);
|
|
||||||
hash[l1.key] = ++added;
|
|
||||||
}
|
|
||||||
if (!hash[l2.key]) {
|
|
||||||
vertices.appendAll([
|
|
||||||
p2.x, p2.y, p2.z,
|
|
||||||
p3.x, p3.y, p3.z
|
|
||||||
]);
|
|
||||||
hash[l2.key] = ++added;
|
|
||||||
}
|
|
||||||
if (!hash[l3.key]) {
|
|
||||||
vertices.appendAll([
|
|
||||||
p3.x, p3.y, p3.z,
|
|
||||||
p1.x, p1.y, p1.z
|
|
||||||
]);
|
|
||||||
hash[l3.key] = ++added;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lines.setAttribute('position', new THREE.BufferAttribute( vertices.toFloat32(), 3 ) );
|
|
||||||
let mesh = new THREE.LineSegments(lines, getMaterial(color));
|
|
||||||
group.add(mesh);
|
|
||||||
return mesh;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** ******************************************************************
|
|
||||||
* Connect to base and Helpers
|
|
||||||
******************************************************************* */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} color
|
|
||||||
* @returns {THREE.LineBasicMaterial}
|
|
||||||
*/
|
|
||||||
function getMaterial(color) {
|
|
||||||
let material = materialCache[color];
|
|
||||||
if (!material) {
|
|
||||||
material = new THREE.LineBasicMaterial({
|
|
||||||
fog:false,
|
|
||||||
color: color,
|
|
||||||
linewidth: line_width
|
|
||||||
});
|
|
||||||
materialCache[color] = material;
|
|
||||||
}
|
|
||||||
return material;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* required for line caching in {@link base.render.wireframe}
|
|
||||||
*
|
|
||||||
* @param {Point} p1
|
|
||||||
* @param {Point} p2
|
|
||||||
* @returns {Line}
|
|
||||||
*/
|
|
||||||
function newOrderedLine(p1, p2) {
|
|
||||||
return p1.key < p2.key ? BASE.newLine(p1, p2) : BASE.newLine(p2, p1);
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
@ -8,17 +8,17 @@
|
||||||
*/
|
*/
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
let base = self.base;
|
const base = self.base;
|
||||||
if (base.slice) return;
|
if (base.slice) return;
|
||||||
|
|
||||||
let { config, util, polygons } = base
|
const { config, util, polygons } = base
|
||||||
let { newOrderedLine, newPolygon, newPoint } = base;
|
const { newOrderedLine, newPolygon, newPoint } = base;
|
||||||
|
|
||||||
function dval(v, dv) {
|
function dval(v, dv) {
|
||||||
return v !== undefined ? v : dv;
|
return v !== undefined ? v : dv;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given an array of points as triples, a bounding box and a set of
|
* Given an array of points as triples, a bounding box and a set of
|
||||||
* slicing controls, emit an array of Slice objects to the ondone()
|
* slicing controls, emit an array of Slice objects to the ondone()
|
||||||
* function. onupdate() will be called with two parameters (% completion
|
* function. onupdate() will be called with two parameters (% completion
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
* @param {Point[]} points vertex array
|
* @param {Point[]} points vertex array
|
||||||
* @param {Object} options slicing parameters
|
* @param {Object} options slicing parameters
|
||||||
*/
|
*/
|
||||||
async function slice(points, options = {}) {
|
async function slice(points, options = {}) {
|
||||||
let zMin = options.zmin || 0,
|
let zMin = options.zmin || 0,
|
||||||
zMax = options.zmax || 0,
|
zMax = options.zmax || 0,
|
||||||
zInc = options.zinc || 0,
|
zInc = options.zinc || 0,
|
||||||
|
|
@ -205,9 +205,9 @@
|
||||||
slices = slices.sort((a,b) => a.z - b.z);
|
slices = slices.sort((a,b) => a.z - b.z);
|
||||||
|
|
||||||
return { slices, points, zMin, zMax, zIndexes, zFlat };
|
return { slices, points, zMin, zMax, zIndexes, zFlat };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* given a point, append to the correct
|
* given a point, append to the correct
|
||||||
* 'where' objec tarray (on, over or under)
|
* 'where' objec tarray (on, over or under)
|
||||||
*
|
*
|
||||||
|
|
@ -215,7 +215,7 @@
|
||||||
* @param {number} z offset
|
* @param {number} z offset
|
||||||
* @param {Obejct} where
|
* @param {Obejct} where
|
||||||
*/
|
*/
|
||||||
function checkUnderOverOn(p, z, where) {
|
function checkUnderOverOn(p, z, where) {
|
||||||
let delta = p.z - z;
|
let delta = p.z - z;
|
||||||
if (Math.abs(delta) < config.precision_slice_z) { // on
|
if (Math.abs(delta) < config.precision_slice_z) { // on
|
||||||
where.on.push(p);
|
where.on.push(p);
|
||||||
|
|
@ -224,9 +224,9 @@
|
||||||
} else { // over
|
} else { // over
|
||||||
where.over.push(p);
|
where.over.push(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a point over and under a z offset, calculate
|
* Given a point over and under a z offset, calculate
|
||||||
* and return the intersection point on that z plane
|
* and return the intersection point on that z plane
|
||||||
*
|
*
|
||||||
|
|
@ -235,7 +235,7 @@
|
||||||
* @param {number} z offset
|
* @param {number} z offset
|
||||||
* @returns {Point} intersection point
|
* @returns {Point} intersection point
|
||||||
*/
|
*/
|
||||||
function intersectPoints(over, under, z) {
|
function intersectPoints(over, under, z) {
|
||||||
let ip = [];
|
let ip = [];
|
||||||
for (let i = 0; i < over.length; i++) {
|
for (let i = 0; i < over.length; i++) {
|
||||||
for (let j = 0; j < under.length; j++) {
|
for (let j = 0; j < under.length; j++) {
|
||||||
|
|
@ -243,21 +243,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ip;
|
return ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure points are unique with a cache/key algorithm
|
* Ensure points are unique with a cache/key algorithm
|
||||||
*/
|
*/
|
||||||
function getCachedPoint(phash, p) {
|
function getCachedPoint(phash, p) {
|
||||||
let cached = phash[p.key];
|
let cached = phash[p.key];
|
||||||
if (!cached) {
|
if (!cached) {
|
||||||
phash[p.key] = p;
|
phash[p.key] = p;
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given two points and hints about their edges,
|
* Given two points and hints about their edges,
|
||||||
* return a new Line object with points sorted
|
* return a new Line object with points sorted
|
||||||
* lexicographically by key. This allows for future
|
* lexicographically by key. This allows for future
|
||||||
|
|
@ -270,22 +270,22 @@
|
||||||
* @param {boolean} [edge]
|
* @param {boolean} [edge]
|
||||||
* @returns {Line}
|
* @returns {Line}
|
||||||
*/
|
*/
|
||||||
function makeZLine(phash, p1, p2, coplanar, edge) {
|
function makeZLine(phash, p1, p2, coplanar, edge) {
|
||||||
p1 = getCachedPoint(phash, p1);
|
p1 = getCachedPoint(phash, p1);
|
||||||
p2 = getCachedPoint(phash, p2);
|
p2 = getCachedPoint(phash, p2);
|
||||||
let line = newOrderedLine(p1,p2);
|
let line = newOrderedLine(p1,p2);
|
||||||
line.coplanar = coplanar || false;
|
line.coplanar = coplanar || false;
|
||||||
line.edge = edge || false;
|
line.edge = edge || false;
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* process a single z-slice on a single mesh and
|
* process a single z-slice on a single mesh and
|
||||||
* add to slices array
|
* add to slices array
|
||||||
*
|
*
|
||||||
* @param {number} z
|
* @param {number} z
|
||||||
*/
|
*/
|
||||||
async function sliceZ(z, points, options = {}) {
|
async function sliceZ(z, points, options = {}) {
|
||||||
let { zMin, zMax, under, over, both } = options,
|
let { zMin, zMax, under, over, both } = options,
|
||||||
groupFn = dval(options.groupr, both ? null : sliceConnect),
|
groupFn = dval(options.groupr, both ? null : sliceConnect),
|
||||||
phash = {},
|
phash = {},
|
||||||
|
|
@ -347,9 +347,9 @@
|
||||||
if (groupFn) rval.groups = groupFn(lines, z, options);
|
if (groupFn) rval.groups = groupFn(lines, z, options);
|
||||||
|
|
||||||
return rval;
|
return rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given an array of input lines (line soup), find the path through
|
* Given an array of input lines (line soup), find the path through
|
||||||
* joining line ends that encompasses the greatest area without self
|
* joining line ends that encompasses the greatest area without self
|
||||||
* interesection. Eliminate used points and repeat. Unjoined lines
|
* interesection. Eliminate used points and repeat. Unjoined lines
|
||||||
|
|
@ -359,7 +359,7 @@
|
||||||
* @param {number} [index]
|
* @param {number} [index]
|
||||||
* @returns {Array}
|
* @returns {Array}
|
||||||
*/
|
*/
|
||||||
function sliceConnect(input, z, opt = {}) {
|
function sliceConnect(input, z, opt = {}) {
|
||||||
let { debug, both } = opt;
|
let { debug, both } = opt;
|
||||||
|
|
||||||
if (both) {
|
if (both) {
|
||||||
|
|
@ -669,9 +669,9 @@
|
||||||
if (debug && emitted < points.length) console.log({ leftovers:points.length - emitted });
|
if (debug && emitted < points.length) console.log({ leftovers:points.length - emitted });
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* eliminate duplicate lines and interior-only lines (coplanar)
|
* eliminate duplicate lines and interior-only lines (coplanar)
|
||||||
*
|
*
|
||||||
* lines are sorted using lexicographic point keys such that
|
* lines are sorted using lexicographic point keys such that
|
||||||
|
|
@ -682,7 +682,7 @@
|
||||||
* @param {Line[]} lines
|
* @param {Line[]} lines
|
||||||
* @returns {Line[]}
|
* @returns {Line[]}
|
||||||
*/
|
*/
|
||||||
function removeDuplicateLines(lines) {
|
function removeDuplicateLines(lines) {
|
||||||
let output = [],
|
let output = [],
|
||||||
tmplines = [],
|
tmplines = [],
|
||||||
points = [],
|
points = [],
|
||||||
|
|
@ -771,9 +771,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
base.slice = slice;
|
base.slice = slice;
|
||||||
base.sliceZ = sliceZ;
|
base.sliceZ = sliceZ;
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
130
src/geo/slope.js
130
src/geo/slope.js
|
|
@ -4,147 +4,105 @@
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
if (self.base.Slope) return;
|
const base = self.base;
|
||||||
|
if (base.Slope) return;
|
||||||
|
|
||||||
const BASE = self.base,
|
const { config } = base;
|
||||||
CONF = BASE.config,
|
const ABS = Math.abs,
|
||||||
ABS = Math.abs,
|
|
||||||
PRO = Slope.prototype,
|
|
||||||
DEG2RAD = Math.PI / 180,
|
DEG2RAD = Math.PI / 180,
|
||||||
RAD2DEG = 180 / Math.PI;
|
RAD2DEG = 180 / Math.PI;
|
||||||
|
|
||||||
BASE.Slope = Slope;
|
class Slope {
|
||||||
BASE.newSlope = newSlope;
|
constructor(p1, p2, dx, dy) {
|
||||||
BASE.newSlopeFromAngle = function(angle) {
|
|
||||||
return newSlope(0,0,
|
|
||||||
Math.cos(angle * DEG2RAD),
|
|
||||||
Math.sin(angle * DEG2RAD)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param p1
|
|
||||||
* @param p2
|
|
||||||
* @param dx
|
|
||||||
* @param dy
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function Slope(p1, p2, dx, dy) {
|
|
||||||
this.dx = p1 && p2 ? p2.x - p1.x : dx;
|
this.dx = p1 && p2 ? p2.x - p1.x : dx;
|
||||||
this.dy = p1 && p2 ? p2.y - p1.y : dy;
|
this.dy = p1 && p2 ? p2.y - p1.y : dy;
|
||||||
this.angle = Math.atan2(this.dy, this.dx) * RAD2DEG;
|
this.angle = Math.atan2(this.dy, this.dx) * RAD2DEG;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ******************************************************************
|
toString() {
|
||||||
* Slope Prototype Functions
|
|
||||||
******************************************************************* */
|
|
||||||
|
|
||||||
PRO.toString = function() {
|
|
||||||
return [this.dx, this.dy, this.angle].join(',');
|
return [this.dx, this.dy, this.angle].join(',');
|
||||||
};
|
}
|
||||||
|
|
||||||
PRO.clone = function() {
|
clone() {
|
||||||
return new Slope(null, null, this.dx, this.dy);
|
return new Slope(null, null, this.dx, this.dy);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
isSame(s) {
|
||||||
* @param {Slope} s
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
PRO.isSame = function(s) {
|
|
||||||
// if very close to vertical or horizontal, they're the same
|
// if very close to vertical or horizontal, they're the same
|
||||||
if (ABS(this.dx) <= CONF.precision_merge && ABS(s.dx) <= CONF.precision_merge) return true;
|
if (ABS(this.dx) <= config.precision_merge && ABS(s.dx) <= config.precision_merge) return true;
|
||||||
if (ABS(this.dy) <= CONF.precision_merge && ABS(s.dy) <= CONF.precision_merge) return true;
|
if (ABS(this.dy) <= config.precision_merge && ABS(s.dy) <= config.precision_merge) return true;
|
||||||
// check angle within a range
|
// check angle within a range
|
||||||
let prec = Math.min(1/Math.sqrt(this.dx * this.dx + this.dy * this.dy), CONF.precision_slope_merge);
|
let prec = Math.min(1/Math.sqrt(this.dx * this.dx + this.dy * this.dy), config.precision_slope_merge);
|
||||||
return angleWithinDelta(this.angle, s.angle, prec || CONF.precision_slope);
|
return angleWithinDelta(this.angle, s.angle, prec || config.precision_slope);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
normal() {
|
||||||
* turn slope 90 degrees
|
|
||||||
*
|
|
||||||
* @returns {Slope}
|
|
||||||
*/
|
|
||||||
PRO.normal = function() {
|
|
||||||
let t = this.dx;
|
let t = this.dx;
|
||||||
this.dx = -this.dy;
|
this.dx = -this.dy;
|
||||||
this.dy = t;
|
this.dy = t;
|
||||||
this.angle = Math.atan2(this.dy, this.dx) * RAD2DEG;
|
this.angle = Math.atan2(this.dy, this.dx) * RAD2DEG;
|
||||||
return this;
|
return this;
|
||||||
};
|
}
|
||||||
|
|
||||||
PRO.invert = function() {
|
invert() {
|
||||||
this.dx = -this.dx;
|
this.dx = -this.dx;
|
||||||
this.dy = -this.dy;
|
this.dy = -this.dy;
|
||||||
this.angle = Math.atan2(this.dy, this.dx) * RAD2DEG;
|
this.angle = Math.atan2(this.dy, this.dx) * RAD2DEG;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
PRO.toUnit = function() {
|
toUnit() {
|
||||||
let max = Math.max(ABS(this.dx), ABS(this.dy));
|
let max = Math.max(ABS(this.dx), ABS(this.dy));
|
||||||
this.dx = this.dx / max;
|
this.dx = this.dx / max;
|
||||||
this.dy = this.dy / max;
|
this.dy = this.dy / max;
|
||||||
return this;
|
return this;
|
||||||
};
|
}
|
||||||
|
|
||||||
PRO.factor = function(f) {
|
factor(f) {
|
||||||
this.dx *= f;
|
this.dx *= f;
|
||||||
this.dy *= f;
|
this.dy *= f;
|
||||||
return this;
|
return this;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
invert() {
|
||||||
* reverse (180 degree) slope
|
|
||||||
*
|
|
||||||
* @returns {Slope}
|
|
||||||
*/
|
|
||||||
PRO.invert = function() {
|
|
||||||
this.dx = -this.dx;
|
this.dx = -this.dx;
|
||||||
this.dy = -this.dy;
|
this.dy = -this.dy;
|
||||||
this.angle = 180 - this.angle;
|
this.angle = 180 - this.angle;
|
||||||
return this;
|
return this;
|
||||||
};
|
}
|
||||||
|
|
||||||
PRO.angleDiff = function(s2,sign) {
|
angleDiff(s2,sign) {
|
||||||
const n1 = this.angle;
|
const n1 = this.angle;
|
||||||
const n2 = s2.angle;
|
const n2 = s2.angle;
|
||||||
let diff = n2 - n1;
|
let diff = n2 - n1;
|
||||||
while (diff < -180) diff += 360;
|
while (diff < -180) diff += 360;
|
||||||
while (diff > 180) diff -= 360;
|
while (diff > 180) diff -= 360;
|
||||||
return sign ? diff : Math.abs(diff);
|
return sign ? diff : Math.abs(diff);
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** ******************************************************************
|
/**
|
||||||
* Connect to base and Helpers
|
|
||||||
******************************************************************* */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns true if the difference between a & b is less than v
|
* returns true if the difference between a & b is less than v
|
||||||
*
|
|
||||||
* @param {number} a
|
|
||||||
* @param {number} b
|
|
||||||
* @param {number} v
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
*/
|
||||||
function minDeltaABS(a,b,v) {
|
function minDeltaABS(a,b,v) {
|
||||||
return ABS(a-b) < v;
|
return ABS(a-b) < v;
|
||||||
}
|
}
|
||||||
|
|
||||||
function angleWithinDelta(a1, a2, delta) {
|
function angleWithinDelta(a1, a2, delta) {
|
||||||
return (ABS(a1-a2) <= delta || 360-ABS(a1-a2) <= delta);
|
return (ABS(a1-a2) <= delta || 360-ABS(a1-a2) <= delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function newSlope(p1, p2, dx, dy) {
|
||||||
*
|
|
||||||
* @param p1
|
|
||||||
* @param p2
|
|
||||||
* @param dx
|
|
||||||
* @param dy
|
|
||||||
* @returns {Slope}
|
|
||||||
*/
|
|
||||||
function newSlope(p1, p2, dx, dy) {
|
|
||||||
return new Slope(p1, p2, dx, dy);
|
return new Slope(p1, p2, dx, dy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
base.Slope = Slope;
|
||||||
|
base.newSlope = newSlope;
|
||||||
|
base.newSlopeFromAngle = function(angle) {
|
||||||
|
return newSlope(0,0,
|
||||||
|
Math.cos(angle * DEG2RAD),
|
||||||
|
Math.sin(angle * DEG2RAD)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
// only active in workers
|
// only active in workers
|
||||||
if (!self.window) (function() {
|
if (!self.window) (function() {
|
||||||
|
|
||||||
if (!self.geo) self.geo = {
|
if (!self.geo) self.geo = {
|
||||||
enable,
|
enable,
|
||||||
disable,
|
disable,
|
||||||
count: {
|
count: {
|
||||||
|
|
@ -13,24 +13,24 @@ if (!self.window) (function() {
|
||||||
union: 0,
|
union: 0,
|
||||||
diff: 0
|
diff: 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const factor = self.base.config.clipper;
|
const factor = self.base.config.clipper;
|
||||||
const geo = self.geo;
|
const geo = self.geo;
|
||||||
|
|
||||||
function log() {
|
function log() {
|
||||||
console.log(...arguments);
|
console.log(...arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
function writePolys(view, polys) {
|
function writePolys(view, polys) {
|
||||||
let pcount = 0;
|
let pcount = 0;
|
||||||
for (let poly of polys) {
|
for (let poly of polys) {
|
||||||
pcount += writePoly(view, poly);
|
pcount += writePoly(view, poly);
|
||||||
}
|
}
|
||||||
return pcount;
|
return pcount;
|
||||||
}
|
}
|
||||||
|
|
||||||
function writePoly(view, poly, inner) {
|
function writePoly(view, poly, inner) {
|
||||||
if (inner) {
|
if (inner) {
|
||||||
poly.setCounterClockwise();
|
poly.setCounterClockwise();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -51,9 +51,9 @@ if (!self.window) (function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
function readPoly(view, z) {
|
function readPoly(view, z) {
|
||||||
let points = view.readU16(true);
|
let points = view.readU16(true);
|
||||||
if (points === 0) return;
|
if (points === 0) return;
|
||||||
let poly = self.base.newPolygon();
|
let poly = self.base.newPolygon();
|
||||||
|
|
@ -61,9 +61,9 @@ if (!self.window) (function() {
|
||||||
poly.add(view.readI32(true)/factor, view.readI32(true)/factor, z || 0);
|
poly.add(view.readI32(true)/factor, view.readI32(true)/factor, z || 0);
|
||||||
}
|
}
|
||||||
return poly;
|
return poly;
|
||||||
}
|
}
|
||||||
|
|
||||||
function readPolys(view, z, out = []) {
|
function readPolys(view, z, out = []) {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
let poly = readPoly(view, z);
|
let poly = readPoly(view, z);
|
||||||
if (poly) {
|
if (poly) {
|
||||||
|
|
@ -73,9 +73,9 @@ if (!self.window) (function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
function polyOffset(polys, offset, z, clean, simple) {
|
function polyOffset(polys, offset, z, clean, simple) {
|
||||||
geo.count.offset++;
|
geo.count.offset++;
|
||||||
let wasm = geo.wasm,
|
let wasm = geo.wasm,
|
||||||
buffer = geo.wasm.shared,
|
buffer = geo.wasm.shared,
|
||||||
|
|
@ -83,9 +83,9 @@ if (!self.window) (function() {
|
||||||
resat = wasm.fn.offset(buffer, pcount, offset * factor, clean, simple),
|
resat = wasm.fn.offset(buffer, pcount, offset * factor, clean, simple),
|
||||||
out = readPolys(new DataReader(wasm.heap, resat), z);
|
out = readPolys(new DataReader(wasm.heap, resat), z);
|
||||||
return polyNest(out);
|
return polyNest(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
function polyUnion(polys, z) {
|
function polyUnion(polys, z) {
|
||||||
geo.count.union++;
|
geo.count.union++;
|
||||||
let wasm = geo.wasm,
|
let wasm = geo.wasm,
|
||||||
buffer = geo.wasm.shared,
|
buffer = geo.wasm.shared,
|
||||||
|
|
@ -93,9 +93,9 @@ if (!self.window) (function() {
|
||||||
resat = wasm.fn.union(buffer, pcount),
|
resat = wasm.fn.union(buffer, pcount),
|
||||||
out = readPolys(new DataReader(wasm.heap, resat), z);
|
out = readPolys(new DataReader(wasm.heap, resat), z);
|
||||||
return polyNest(out);
|
return polyNest(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
function polyDiff(polysA, polysB, z, AB, BA) {
|
function polyDiff(polysA, polysB, z, AB, BA) {
|
||||||
geo.count.diff++;
|
geo.count.diff++;
|
||||||
let wasm = geo.wasm,
|
let wasm = geo.wasm,
|
||||||
buffer = geo.wasm.shared,
|
buffer = geo.wasm.shared,
|
||||||
|
|
@ -110,10 +110,10 @@ if (!self.window) (function() {
|
||||||
if (BA) {
|
if (BA) {
|
||||||
BA.appendAll(polyNest(readPolys(reader, z)));
|
BA.appendAll(polyNest(readPolys(reader, z)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// nest closed polygons without existing parent / child relationships
|
// nest closed polygons without existing parent / child relationships
|
||||||
function polyNest(polys) {
|
function polyNest(polys) {
|
||||||
polys.sort((a,b) => {
|
polys.sort((a,b) => {
|
||||||
return b.bounds.minx - a.bounds.minx;
|
return b.bounds.minx - a.bounds.minx;
|
||||||
});
|
});
|
||||||
|
|
@ -138,18 +138,18 @@ if (!self.window) (function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tops;
|
return tops;
|
||||||
}
|
}
|
||||||
|
|
||||||
function readString(pos, len) {
|
function readString(pos, len) {
|
||||||
let view = new DataReader(geo.wasm.heap, pos);
|
let view = new DataReader(geo.wasm.heap, pos);
|
||||||
let out = [];
|
let out = [];
|
||||||
while (len-- > 0) {
|
while (len-- > 0) {
|
||||||
out.push(String.fromCharCode(view.readU8()));
|
out.push(String.fromCharCode(view.readU8()));
|
||||||
}
|
}
|
||||||
return out.join('');
|
return out.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function enable() {
|
function enable() {
|
||||||
if (geo.wasm || geo._wasm) {
|
if (geo.wasm || geo._wasm) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -194,13 +194,13 @@ if (!self.window) (function() {
|
||||||
offset: polyOffset
|
offset: polyOffset
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function disable() {
|
function disable() {
|
||||||
if (geo.wasm) {
|
if (geo.wasm) {
|
||||||
delete geo.wasm;
|
delete geo.wasm;
|
||||||
// console.log({disabled: geo});
|
// console.log({disabled: geo});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue