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/three",
|
||||
"geo/base",
|
||||
// "geo/render",
|
||||
"geo/point",
|
||||
"geo/points",
|
||||
"geo/slope",
|
||||
"geo/line",
|
||||
"geo/bounds",
|
||||
"geo/polygon",
|
||||
"geo/polygons",
|
||||
// "geo/gyroid",
|
||||
"geo/polygon",
|
||||
"geo/mesh",
|
||||
"data/local",
|
||||
"data/index",
|
||||
|
|
@ -383,8 +381,8 @@ const script = {
|
|||
"geo/slope",
|
||||
"geo/line",
|
||||
"geo/bounds",
|
||||
"geo/polygon",
|
||||
"geo/polygons",
|
||||
"geo/polygon",
|
||||
"geo/gyroid",
|
||||
"geo/mesh",
|
||||
// "moto/broker",
|
||||
|
|
@ -432,8 +430,8 @@ const script = {
|
|||
"geo/slope",
|
||||
"geo/line",
|
||||
"geo/bounds",
|
||||
"geo/polygon",
|
||||
"geo/polygons",
|
||||
"geo/polygon",
|
||||
"geo/gyroid",
|
||||
"kiri-mode/fdm/driver",
|
||||
"kiri-mode/fdm/slice",
|
||||
|
|
@ -453,7 +451,6 @@ const script = {
|
|||
"add/array",
|
||||
"add/three",
|
||||
"geo/base",
|
||||
// "geo/render",
|
||||
"geo/point",
|
||||
"geo/points",
|
||||
"geo/slope",
|
||||
|
|
|
|||
|
|
@ -4,216 +4,217 @@
|
|||
|
||||
(function() {
|
||||
|
||||
if (self.base.gyroid) return;
|
||||
if (self.base.gyroid) return;
|
||||
|
||||
const base = self.base;
|
||||
const PI2 = Math.PI * 2;
|
||||
let cache = {};
|
||||
let lastVal;
|
||||
let lastRes = 0;
|
||||
let lastSlice = 0;
|
||||
const base = self.base;
|
||||
const PI2 = Math.PI * 2;
|
||||
|
||||
/**
|
||||
* @param off {number} z offset value from 0-1
|
||||
* @param res {number} resolution (pixels/slices per side)
|
||||
*/
|
||||
function slice(off, res, val) {
|
||||
// auto clear cach if it hasn't been hit in the last 20 seconds
|
||||
// or the requested resolution or tip values have changed
|
||||
let now = Date.now();
|
||||
if (res !== lastRes || val !== lastVal || now - lastSlice > 20000) {
|
||||
// console.log({clear_cache: now});
|
||||
cache = {};
|
||||
let cache = {};
|
||||
let lastVal;
|
||||
let lastRes = 0;
|
||||
let lastSlice = 0;
|
||||
|
||||
/**
|
||||
* @param off {number} z offset value from 0-1
|
||||
* @param res {number} resolution (pixels/slices per side)
|
||||
*/
|
||||
function slice(off, res, val) {
|
||||
// auto clear cach if it hasn't been hit in the last 20 seconds
|
||||
// or the requested resolution or tip values have changed
|
||||
let now = Date.now();
|
||||
if (res !== lastRes || val !== lastVal || now - lastSlice > 20000) {
|
||||
// console.log({clear_cache: now});
|
||||
cache = {};
|
||||
}
|
||||
lastVal = val;
|
||||
lastRes = res;
|
||||
lastSlice = now;
|
||||
let rez = parseInt(res || 200);
|
||||
let inc = PI2 / rez;
|
||||
let z = PI2 * off;
|
||||
let key = (z % PI2).round(3);
|
||||
let hit = cache[key];
|
||||
if (hit) {
|
||||
return hit;
|
||||
}
|
||||
let tip = val || 0;
|
||||
let edge = [];
|
||||
let vals = [];
|
||||
let points = 0;
|
||||
let points_lr = 0;
|
||||
let points_td = 0;
|
||||
for (let x=0; x<PI2; x += inc) {
|
||||
let vrow = []; // raw values row
|
||||
let erow = []; // edge values row
|
||||
edge.push(erow);
|
||||
vals.push(vrow);
|
||||
for (let y=0; y<PI2; y += inc) {
|
||||
erow.push(0);
|
||||
vrow.push(
|
||||
Math.sin(x) * Math.cos(y) +
|
||||
Math.sin(y) * Math.cos(z) +
|
||||
Math.sin(z) * Math.cos(x)
|
||||
);
|
||||
}
|
||||
lastVal = val;
|
||||
lastRes = res;
|
||||
lastSlice = now;
|
||||
let rez = parseInt(res || 200);
|
||||
let inc = PI2 / rez;
|
||||
let z = PI2 * off;
|
||||
let key = (z % PI2).round(3);
|
||||
let hit = cache[key];
|
||||
if (hit) {
|
||||
return hit;
|
||||
}
|
||||
|
||||
// left-right threshold search (red)
|
||||
vals.forEach((vrow, y) => {
|
||||
let erow = edge[y];
|
||||
let lval = vrow[vrow.length - 1];
|
||||
vrow.forEach((val, x) => {
|
||||
if (
|
||||
(lval <= tip && val >= tip) || (lval >= tip && val <= tip) ||
|
||||
(lval <= -tip && val >= -tip) || (lval >= -tip && val <= -tip)
|
||||
) {
|
||||
erow[x] = 1;
|
||||
points++;
|
||||
points_lr++;
|
||||
}
|
||||
lval = val;
|
||||
})
|
||||
});
|
||||
|
||||
// top-down threshold search (green)
|
||||
for (let x=0; x<rez; x++) {
|
||||
let lval = vals[vals.length-1][x];
|
||||
for (let y=0; y<rez; y++) {
|
||||
let val = vals[y][x];
|
||||
if (
|
||||
(lval <= tip && val >= tip) || (lval >= tip && val <= tip) ||
|
||||
(lval <= -tip && val >= -tip) || (lval >= -tip && val <= -tip)
|
||||
) {
|
||||
if (edge[y][x]) {
|
||||
edge[y][x] = 3;
|
||||
} else {
|
||||
edge[y][x] = 2;
|
||||
points++
|
||||
}
|
||||
points_td++;
|
||||
}
|
||||
lval = val;
|
||||
}
|
||||
let tip = val || 0;
|
||||
let edge = [];
|
||||
let vals = [];
|
||||
let points = 0;
|
||||
let points_lr = 0;
|
||||
let points_td = 0;
|
||||
for (let x=0; x<PI2; x += inc) {
|
||||
let vrow = []; // raw values row
|
||||
let erow = []; // edge values row
|
||||
edge.push(erow);
|
||||
vals.push(vrow);
|
||||
for (let y=0; y<PI2; y += inc) {
|
||||
erow.push(0);
|
||||
vrow.push(
|
||||
Math.sin(x) * Math.cos(y) +
|
||||
Math.sin(y) * Math.cos(z) +
|
||||
Math.sin(z) * Math.cos(x)
|
||||
);
|
||||
}
|
||||
|
||||
// deterime prevailing direction for chaining
|
||||
let dir = points_td > points_lr ? 'lr' : 'td';
|
||||
|
||||
// create sparse representation
|
||||
let sparse = [];
|
||||
let center = rez / 2;
|
||||
edge.forEach((row,y) => {
|
||||
row.forEach((val,x) => {
|
||||
if (val) {
|
||||
let dx = Math.abs(x - center);
|
||||
let dy = Math.abs(y - center);
|
||||
sparse.push({x: x/rez, y: y/rez, val, dist: Math.max(dx,dy)});
|
||||
}
|
||||
});
|
||||
});
|
||||
sparse.sort((a,b) => {
|
||||
return b.dist - a.dist;
|
||||
});
|
||||
|
||||
// join sparse points array by closest distance
|
||||
let polys = [];
|
||||
let chain;
|
||||
let added;
|
||||
let cleared = 0;
|
||||
let maxdist = 0.05;
|
||||
|
||||
do {
|
||||
chain = null;
|
||||
for (let i=0; i<sparse.length; i++) {
|
||||
if (sparse[i]) {
|
||||
chain = [ sparse[i] ];
|
||||
polys.push(chain);
|
||||
sparse[i] = null;
|
||||
cleared++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// left-right threshold search (red)
|
||||
vals.forEach((vrow, y) => {
|
||||
let erow = edge[y];
|
||||
let lval = vrow[vrow.length - 1];
|
||||
vrow.forEach((val, x) => {
|
||||
if (
|
||||
(lval <= tip && val >= tip) || (lval >= tip && val <= tip) ||
|
||||
(lval <= -tip && val >= -tip) || (lval >= -tip && val <= -tip)
|
||||
) {
|
||||
erow[x] = 1;
|
||||
points++;
|
||||
points_lr++;
|
||||
}
|
||||
lval = val;
|
||||
})
|
||||
});
|
||||
|
||||
// top-down threshold search (green)
|
||||
for (let x=0; x<rez; x++) {
|
||||
let lval = vals[vals.length-1][x];
|
||||
for (let y=0; y<rez; y++) {
|
||||
let val = vals[y][x];
|
||||
if (
|
||||
(lval <= tip && val >= tip) || (lval >= tip && val <= tip) ||
|
||||
(lval <= -tip && val >= -tip) || (lval >= -tip && val <= -tip)
|
||||
) {
|
||||
if (edge[y][x]) {
|
||||
edge[y][x] = 3;
|
||||
} else {
|
||||
edge[y][x] = 2;
|
||||
points++
|
||||
}
|
||||
points_td++;
|
||||
}
|
||||
lval = val;
|
||||
}
|
||||
}
|
||||
|
||||
// deterime prevailing direction for chaining
|
||||
let dir = points_td > points_lr ? 'lr' : 'td';
|
||||
|
||||
// create sparse representation
|
||||
let sparse = [];
|
||||
let center = rez / 2;
|
||||
edge.forEach((row,y) => {
|
||||
row.forEach((val,x) => {
|
||||
if (val) {
|
||||
let dx = Math.abs(x - center);
|
||||
let dy = Math.abs(y - center);
|
||||
sparse.push({x: x/rez, y: y/rez, val, dist: Math.max(dx,dy)});
|
||||
}
|
||||
});
|
||||
});
|
||||
sparse.sort((a,b) => {
|
||||
return b.dist - a.dist;
|
||||
});
|
||||
|
||||
// join sparse points array by closest distance
|
||||
let polys = [];
|
||||
let chain;
|
||||
let added;
|
||||
let cleared = 0;
|
||||
let maxdist = 0.05;
|
||||
|
||||
do {
|
||||
chain = null;
|
||||
added = false;
|
||||
let target = chain[chain.length - 1];
|
||||
let cl_elm = null;
|
||||
let cl_idx = null;
|
||||
let cl_dst = Infinity;
|
||||
for (let i=0; i<sparse.length; i++) {
|
||||
if (sparse[i]) {
|
||||
chain = [ sparse[i] ];
|
||||
polys.push(chain);
|
||||
sparse[i] = null;
|
||||
cleared++;
|
||||
let test_el = sparse[i];
|
||||
if (test_el) {
|
||||
let dst = distTo(target, test_el, dir);
|
||||
if (cl_idx === null || dst < cl_dst) {
|
||||
cl_idx = i;
|
||||
cl_elm = test_el;
|
||||
cl_dst = dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cl_elm) {
|
||||
if (cl_dst > maxdist) {
|
||||
break;
|
||||
}
|
||||
sparse[cl_idx] = null;
|
||||
cleared++;
|
||||
chain.push(cl_elm);
|
||||
added = true;
|
||||
}
|
||||
do {
|
||||
added = false;
|
||||
let target = chain[chain.length - 1];
|
||||
let cl_elm = null;
|
||||
let cl_idx = null;
|
||||
let cl_dst = Infinity;
|
||||
for (let i=0; i<sparse.length; i++) {
|
||||
let test_el = sparse[i];
|
||||
if (test_el) {
|
||||
let dst = distTo(target, test_el, dir);
|
||||
if (cl_idx === null || dst < cl_dst) {
|
||||
cl_idx = i;
|
||||
cl_elm = test_el;
|
||||
cl_dst = dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cl_elm) {
|
||||
if (cl_dst > maxdist) {
|
||||
break;
|
||||
}
|
||||
sparse[cl_idx] = null;
|
||||
cleared++;
|
||||
chain.push(cl_elm);
|
||||
added = true;
|
||||
}
|
||||
} while (added);
|
||||
} while (cleared < sparse.length);
|
||||
} while (added);
|
||||
} while (cleared < sparse.length);
|
||||
|
||||
let psimple = polys
|
||||
.map(poly => filter(poly, 0))
|
||||
.map(poly => filter(poly, inc));
|
||||
let psimple = polys
|
||||
.map(poly => filter(poly, 0))
|
||||
.map(poly => filter(poly, inc));
|
||||
|
||||
let slice = {edge, points, dir, polys: psimple};
|
||||
cache[key] = slice;
|
||||
return slice;
|
||||
let slice = {edge, points, dir, polys: psimple};
|
||||
cache[key] = slice;
|
||||
return slice;
|
||||
}
|
||||
|
||||
// merge co-linear and distance threshold
|
||||
function filter(poly, inc) {
|
||||
if (poly.length <= 2) {
|
||||
return poly;
|
||||
}
|
||||
|
||||
// merge co-linear and distance threshold
|
||||
function filter(poly, inc) {
|
||||
if (poly.length <= 2) {
|
||||
return poly;
|
||||
let nupoly = [ poly[0] ];
|
||||
let e1 = poly[1];
|
||||
let e2 = null;
|
||||
let last = poly.length - 2;
|
||||
for (let i=1; i<poly.length; i++) {
|
||||
let el = poly[i];
|
||||
let drop = inc ?
|
||||
(distTo(e1, el) <= inc) :
|
||||
(e1.x === el.x || e1.y === el.y);
|
||||
if (drop) {
|
||||
e2 = el;
|
||||
if (i < last) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let nupoly = [ poly[0] ];
|
||||
let e1 = poly[1];
|
||||
let e2 = null;
|
||||
let last = poly.length - 2;
|
||||
for (let i=1; i<poly.length; i++) {
|
||||
let el = poly[i];
|
||||
let drop = inc ?
|
||||
(distTo(e1, el) <= inc) :
|
||||
(e1.x === el.x || e1.y === el.y);
|
||||
if (drop) {
|
||||
e2 = el;
|
||||
if (i < last) {
|
||||
continue;
|
||||
}
|
||||
if (e2) {
|
||||
nupoly.push({x:(e1.x + e2.x)/2, y:(e1.y + e2.y)/2});
|
||||
e2 = null;
|
||||
} else {
|
||||
nupoly.push(e1);
|
||||
if (i === last) {
|
||||
nupoly.push(el);
|
||||
}
|
||||
if (e2) {
|
||||
nupoly.push({x:(e1.x + e2.x)/2, y:(e1.y + e2.y)/2});
|
||||
e2 = null;
|
||||
} else {
|
||||
nupoly.push(e1);
|
||||
if (i === last) {
|
||||
nupoly.push(el);
|
||||
}
|
||||
}
|
||||
e1 = el;
|
||||
}
|
||||
nupoly.push(poly[poly.length-1]);
|
||||
return nupoly;
|
||||
e1 = el;
|
||||
}
|
||||
nupoly.push(poly[poly.length-1]);
|
||||
return nupoly;
|
||||
}
|
||||
|
||||
function distTo(a, b, dir) {
|
||||
let dx = a.x - b.x;
|
||||
let dy = a.y - b.y;
|
||||
// bias distance by prevailing direction of discovery to join stragglers
|
||||
if (dir === 'lr') dx = dx / 2;
|
||||
if (dir === 'td') dy = dy / 2;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
function distTo(a, b, dir) {
|
||||
let dx = a.x - b.x;
|
||||
let dy = a.y - b.y;
|
||||
// bias distance by prevailing direction of discovery to join stragglers
|
||||
if (dir === 'lr') dx = dx / 2;
|
||||
if (dir === 'td') dy = dy / 2;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
base.gyroid = { slice };
|
||||
base.gyroid = { slice };
|
||||
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -4,79 +4,45 @@
|
|||
|
||||
(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;
|
||||
BASE.newLine = newLine;
|
||||
BASE.newOrderedLine = newOrderedLine;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
* @param {String} [key]
|
||||
* @constructor
|
||||
*/
|
||||
function Line(p1, p2, key) {
|
||||
class Line {
|
||||
constructor(p1, p2, key) {
|
||||
if (!key) key = [p1.key, p2.key].join(';');
|
||||
this.p1 = p1;
|
||||
this.p2 = p2;
|
||||
this.key = key;
|
||||
this.coplanar = false;
|
||||
this.edge = false;
|
||||
this.del = false;
|
||||
}
|
||||
|
||||
/** ******************************************************************
|
||||
* Line Prototype Functions
|
||||
******************************************************************* */
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
PRO.length = function() {
|
||||
length() {
|
||||
return Math.sqrt(this.length2());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number} square of length
|
||||
*/
|
||||
PRO.length2 = function() {
|
||||
length2() {
|
||||
return this.p1.distToSq2D(this.p2);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Slope}
|
||||
*/
|
||||
PRO.slope = function() {
|
||||
return BASE.newSlope(this.p1.slopeTo(this.p2));
|
||||
};
|
||||
slope() {
|
||||
return base.newSlope(this.p1.slopeTo(this.p2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Line}
|
||||
*/
|
||||
PRO.reverse = function() {
|
||||
reverse() {
|
||||
let t = this.p1;
|
||||
this.p1 = this.p2;
|
||||
this.p2 = t;
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Point}
|
||||
*/
|
||||
PRO.midpoint = function() {
|
||||
midpoint() {
|
||||
return this.p1.midPointTo(this.p2);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* faulty when line doubles back at 180?
|
||||
* @param {Line} line
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PRO.isCollinear = function(line) {
|
||||
isCollinear(line) {
|
||||
let p1 = this.p1,
|
||||
p2 = this.p2,
|
||||
p3 = line.p1,
|
||||
|
|
@ -85,27 +51,20 @@
|
|||
d1y = (p2.y - p1.y),
|
||||
d2x = (p4.x - p3.x),
|
||||
d2y = (p4.y - p3.y);
|
||||
|
||||
return Math.abs( (d2y * d1x) - (d2x * d1y) ) < 0.0001;
|
||||
};
|
||||
|
||||
/** ******************************************************************
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
function newOrderedLine(p1, p2, key) {
|
||||
return p1.key < p2.key ? newLine(p1,p2,key) : newLine(p2,p1,key);
|
||||
}
|
||||
function newLine(p1, p2, key) {
|
||||
return new Line(p1, p2, key);
|
||||
}
|
||||
|
||||
function newOrderedLine(p1, p2, key) {
|
||||
return p1.key < p2.key ? newLine(p1,p2,key) : newLine(p2,p1,key);
|
||||
}
|
||||
|
||||
base.Line = Line;
|
||||
base.newLine = newLine;
|
||||
base.newOrderedLine = newOrderedLine;
|
||||
|
||||
})();
|
||||
|
|
|
|||
569
src/geo/point.js
569
src/geo/point.js
|
|
@ -4,132 +4,97 @@
|
|||
|
||||
(function() {
|
||||
|
||||
if (self.base.Point) return;
|
||||
if (self.base.Point) return;
|
||||
|
||||
const BASE = self.base,
|
||||
UTIL = BASE.util,
|
||||
CONF = BASE.config,
|
||||
KEYS = BASE.key,
|
||||
ROUND = UTIL.round;
|
||||
const base = self.base;
|
||||
const { util, config, key } = base;
|
||||
const { round } = util;
|
||||
|
||||
class Point {
|
||||
constructor(x,y,z,key) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z || 0;
|
||||
if (key) {
|
||||
this._key = key;
|
||||
}
|
||||
}
|
||||
|
||||
get key() {
|
||||
if (this._key) {
|
||||
return this._key;
|
||||
}
|
||||
return this._key = [
|
||||
((this.x * 100000) | 0),
|
||||
((this.y * 100000) | 0),
|
||||
((this.z * 100000) | 0)
|
||||
].join('');
|
||||
class Point {
|
||||
constructor(x, y, z, key) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z || 0;
|
||||
if (key) {
|
||||
this._key = key;
|
||||
}
|
||||
}
|
||||
|
||||
const PRO = Point.prototype;
|
||||
get key() {
|
||||
if (this._key) {
|
||||
return this._key;
|
||||
}
|
||||
return this._key = [
|
||||
((this.x * 100000) | 0),
|
||||
((this.y * 100000) | 0),
|
||||
((this.z * 100000) | 0)
|
||||
].join('');
|
||||
}
|
||||
|
||||
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() {
|
||||
toClipper() {
|
||||
return {
|
||||
X: this.x * CONF.clipper,
|
||||
Y: this.y * CONF.clipper
|
||||
X: this.x * config.clipper,
|
||||
Y: this.y * config.clipper
|
||||
};
|
||||
}
|
||||
|
||||
PRO.setZ = function(z) {
|
||||
setZ(z) {
|
||||
this.z = z;
|
||||
return this;
|
||||
}
|
||||
|
||||
PRO.swapXZ = function() {
|
||||
swapXZ() {
|
||||
let p = this,
|
||||
t = p.x;
|
||||
p.x = p.z;
|
||||
p.z = t;
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
PRO.swapYZ = function() {
|
||||
swapYZ() {
|
||||
let p = this,
|
||||
t = p.y;
|
||||
p.y = p.z;
|
||||
p.z = t;
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
PRO.round = function(precision) {
|
||||
round(precision) {
|
||||
return newPoint(
|
||||
this.x.round(precision),
|
||||
this.y.round(precision),
|
||||
this.z.round(precision));
|
||||
};
|
||||
}
|
||||
|
||||
PRO.addFacet = function(facet) {
|
||||
addFacet(facet) {
|
||||
if (!this.group) this.group = [];
|
||||
this.group.push(facet);
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
PRO.rekey = function() {
|
||||
rekey() {
|
||||
this._key = undefined;
|
||||
};
|
||||
}
|
||||
|
||||
PRO.toString = function() {
|
||||
toString() {
|
||||
return this.key;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Point}
|
||||
*/
|
||||
PRO.clone = function() {
|
||||
clone() {
|
||||
return newPoint(this.x, this.y, this.z, this._key);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Point} p
|
||||
* @returns {Slope}
|
||||
*/
|
||||
PRO.slopeTo = function(p) {
|
||||
return BASE.newSlope(this, p);
|
||||
};
|
||||
slopeTo(p) {
|
||||
return base.newSlope(this, p);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Point} p
|
||||
* @param {String} [k]
|
||||
* @returns {Line}
|
||||
*/
|
||||
PRO.lineTo = function(p, k) {
|
||||
return BASE.newLine(this, p, k);
|
||||
};
|
||||
lineTo(p, k) {
|
||||
return base.newLine(this, p, k);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Point} p
|
||||
* @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);
|
||||
};
|
||||
isNear(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
|
||||
|
|
@ -139,10 +104,9 @@
|
|||
* @param {Point} p2
|
||||
* @returns {number}
|
||||
*/
|
||||
PRO.distToLine = function(p1, p2) {
|
||||
// return p2l(this, p1, p2);
|
||||
distToLine(p1, p2) {
|
||||
return Math.sqrt(this.distToLineSq(p1, p2));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* normal distance needs to be formulated to replace both functions.
|
||||
*/
|
||||
PRO.distToLineNew = function(p1, p2) {
|
||||
distToLineNew(p1, p2) {
|
||||
return p2l(this, p1, p2);
|
||||
// return Math.sqrt(this.distToLineSq(p1, p2));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* return square of distance to line connecting points p1, p2
|
||||
* 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,
|
||||
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;
|
||||
|
||||
if (t < 0) return UTIL.distSq(p, p1);
|
||||
if (t > 1) return UTIL.distSq(p, p2);
|
||||
if (t < 0) return util.distSq(p, p1);
|
||||
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));
|
||||
};
|
||||
|
||||
// ---( begin fix distToLine )---
|
||||
|
||||
function dot(u, v) {
|
||||
return u.x * v.x + u.y * v.y;
|
||||
return util.distSqv2(p.x, p.y, p1.x + t * (p2.x - p1.x), p1.y + t * (p2.y - p1.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);
|
||||
}
|
||||
|
||||
// ---( end fix distToLine )---
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
* @param {number} dist2
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PRO.withinDist2 = function(p1, p2, dist2) {
|
||||
withinDist2(p1, p2, dist2) {
|
||||
let ll2 = p1.distToSq2D(p2),
|
||||
dp1 = this.distToSq2D(p1),
|
||||
dp2 = this.distToSq2D(p2);
|
||||
|
|
@ -231,71 +153,53 @@
|
|||
// dist2 to the described line segment.
|
||||
if (dp1 > ll2 && dp2 > ll2) return false;
|
||||
return this.distToLineSq(p1, p2) < dist2;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Point} p2
|
||||
* @returns {Point}
|
||||
*/
|
||||
PRO.midPointTo = function(p2) {
|
||||
return newPoint((this.x + p2.x)/2, (this.y + p2.y)/2, this.z);
|
||||
};
|
||||
midPointTo(p2) {
|
||||
return newPoint((this.x + p2.x) / 2, (this.y + p2.y) / 2, this.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Point} p2
|
||||
* @returns {Point}
|
||||
*/
|
||||
PRO.midPointTo3D = function(p2) {
|
||||
midPointTo3D(p2) {
|
||||
return newPoint(
|
||||
(this.x + p2.x)/2,
|
||||
(this.y + p2.y)/2,
|
||||
(this.z + p2.z)/2
|
||||
(this.x + p2.x) / 2,
|
||||
(this.y + p2.y) / 2,
|
||||
(this.z + p2.z) / 2
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* non-scale corrected version of follow()
|
||||
*
|
||||
* @param slope
|
||||
* @param mult
|
||||
* @returns {Point}
|
||||
*/
|
||||
PRO.projectOnSlope = function(slope, mult) {
|
||||
projectOnSlope(slope, mult) {
|
||||
return newPoint(
|
||||
this.x + slope.dx * mult,
|
||||
this.y + slope.dy * mult,
|
||||
this.z);
|
||||
};
|
||||
}
|
||||
|
||||
PRO.followTo = function(point, mult) {
|
||||
followTo(point, mult) {
|
||||
return this.follow(this.slopeTo(point), mult);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* return a point along the line this from point to p2 offset
|
||||
* 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,
|
||||
dx = p2.x - p1.x,
|
||||
dy = p2.y - p1.y,
|
||||
ls = dist / Math.sqrt(dx * dx + dy * dy),
|
||||
ox = dx * 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
|
||||
* 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,
|
||||
dx = p2.x - p1.x,
|
||||
dy = p2.y - p1.y;
|
||||
|
|
@ -306,93 +210,82 @@
|
|||
ox = dx * 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Point} p2
|
||||
* @param {number} offset
|
||||
* @returns {Line}
|
||||
*/
|
||||
PRO.offsetLineTo = function(p2, offset) {
|
||||
offsetLineTo(p2, offset) {
|
||||
let p1 = this,
|
||||
dx = p2.x - p1.x,
|
||||
dy = p2.y - p1.y,
|
||||
ls = offset / Math.sqrt(dx * dx + dy * dy),
|
||||
ox = dx * ls,
|
||||
oy = dy * ls,
|
||||
np1 = newPoint(p1.x - oy, p1.y + ox, p1.z, KEYS.NONE),
|
||||
np2 = newPoint(p2.x - oy, p2.y + ox, p2.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, key.NONE);
|
||||
np1.op = p1;
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if a point is inside of a polygon
|
||||
* 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;
|
||||
|
||||
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];
|
||||
p2 = p[(i+1)%pl];
|
||||
p2 = p[(i + 1) % pl];
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return inside;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if the point is inside of a polygon but
|
||||
* not inside any of it's children
|
||||
*
|
||||
* @param {Polygon | Polygon[]} poly
|
||||
* @return {boolean} true if inside outer but not inner
|
||||
*/
|
||||
PRO.isInPolygon = function(poly) {
|
||||
let point = this, i;
|
||||
isInPolygon(poly) {
|
||||
let point = this,
|
||||
i;
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
let holes = poly.inner;
|
||||
if (point.inPolygon(poly) || point.nearPolygon(poly, CONF.precision_merge_sq)) {
|
||||
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(poly) || point.nearPolygon(poly, config.precision_merge_sq)) {
|
||||
for (i = 0; holes && i < holes.length; i++) {
|
||||
if (point.inPolygon(holes[i]) && !point.nearPolygon(holes[i], config.precision_merge_sq)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if the point is inside of a polygon but
|
||||
* not inside any of it's children
|
||||
*
|
||||
* @param {Polygon | Polygon[]} poly
|
||||
* @return {boolean} true if inside outer but not inner
|
||||
* returns true if the point is inside of a polygon but
|
||||
* not inside any of it's children
|
||||
*/
|
||||
PRO.isInPolygonOnly = function(poly) {
|
||||
let point = this, i;
|
||||
isInPolygonOnly(poly) {
|
||||
let point = this,
|
||||
i;
|
||||
if (Array.isArray(poly)) {
|
||||
for (i=0; i<poly.length; i++) {
|
||||
for (i = 0; i < poly.length; i++) {
|
||||
if (point.isInPolygonOnly(poly[i])) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -401,129 +294,96 @@
|
|||
}
|
||||
let holes = poly.inner;
|
||||
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;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if point is near polygon edge. distance is squared.
|
||||
*
|
||||
* @param {Polygon} poly
|
||||
* @param {number} dist2
|
||||
* @param {boolean} [inner] process inner polygons
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PRO.nearPolygon = function(poly, dist2, inner) {
|
||||
nearPolygon(poly, dist2, inner) {
|
||||
// throw new Error("nearPolygon");
|
||||
for (let i=0, p=poly.points, pl=p.length ; i<pl; i++) {
|
||||
if (this.withinDist2(p[i], p[(i+1)%pl], dist2)) {
|
||||
for (let i = 0, p = poly.points, pl = p.length; i < pl; i++) {
|
||||
if (this.withinDist2(p[i], p[(i + 1) % pl], dist2)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a new point following given slope for given distance
|
||||
* 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);
|
||||
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
|
||||
*
|
||||
* @param {Point} p
|
||||
* @param {number} z
|
||||
* @returns {Point}
|
||||
* for point, return intersecting point on z to next point if points
|
||||
* are on either size of z
|
||||
*/
|
||||
PRO.intersectZ = function(p, z) {
|
||||
intersectZ(p, z) {
|
||||
let dx = p.x - this.x,
|
||||
dy = p.y - this.y,
|
||||
dz = p.z - this.z,
|
||||
pct = 1 - ((p.z - z) / dz);
|
||||
return newPoint(this.x + dx * pct, this.y + dy * pct, this.z + dz * pct);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Point} p
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PRO.isEqual2D = function(p) {
|
||||
isEqual2D(p) {
|
||||
return this === p || (this.x === p.x && this.y === p.y);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if points are close enough to be considered equivalent
|
||||
*
|
||||
* @param {Point} p
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PRO.isMergable2D = function(p) {
|
||||
return this.isEqual2D(p) || (this.distToSq2D(p) < CONF.precision_merge_sq);
|
||||
};
|
||||
isMergable2D(p) {
|
||||
return this.isEqual2D(p) || (this.distToSq2D(p) < config.precision_merge_sq);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
}
|
||||
|
||||
PRO.isEqual2D = function(p) {
|
||||
isEqual2D(p) {
|
||||
return this === p || (this.x === p.x && this.y === p.y);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if points are close enough to be considered equivalent
|
||||
*
|
||||
* @param {Point} p
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PRO.isMergable3D = function(p) {
|
||||
return this.isEqual(p) || (this.distToSq3D(p) < CONF.precision_merge_sq);
|
||||
};
|
||||
isMergable3D(p) {
|
||||
return this.isEqual(p) || (this.distToSq3D(p) < config.precision_merge_sq);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 min distance from point to a polygon
|
||||
|
|
@ -532,7 +392,7 @@
|
|||
* @param {Polygon} poly
|
||||
* @param {number} [threshold] stop looking if under threshold
|
||||
*/
|
||||
PRO.distToPolySegments = function(poly, threshold) {
|
||||
distToPolySegments(poly, threshold) {
|
||||
let point = this,
|
||||
mindist = Infinity;
|
||||
poly.forEachSegment(function(p1, p2) {
|
||||
|
|
@ -542,32 +402,31 @@
|
|||
if (mindist <= threshold) return true;
|
||||
});
|
||||
return mindist;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Polygon} poly
|
||||
* @param {number} [threshold] stop looking if under threshold
|
||||
*/
|
||||
PRO.distToPolyPoints = function(poly, threshold) {
|
||||
let point = this, mindist = Infinity;
|
||||
distToPolyPoints(poly, threshold) {
|
||||
let point = this,
|
||||
mindist = Infinity;
|
||||
poly.forEachPoint(function(pp) {
|
||||
mindist = Math.min(mindist, point.distTo2D(pp));
|
||||
if (mindist < threshold) return true;
|
||||
});
|
||||
return mindist;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Point[]} points
|
||||
* @param {number} max
|
||||
* @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";
|
||||
let mind = Infinity,
|
||||
minp = null,
|
||||
i, p, d;
|
||||
for (i=0; i<points.length; i++) {
|
||||
for (i = 0; i < points.length; i++) {
|
||||
p = points[i];
|
||||
if (p === this || p.del) continue;
|
||||
d = this.distToSq2D(p);
|
||||
|
|
@ -577,14 +436,16 @@
|
|||
}
|
||||
}
|
||||
return minp;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Point[]} points
|
||||
* @return {number} average square dist to cloud of points
|
||||
*/
|
||||
PRO.averageDistTo = function(points) {
|
||||
let sum = 0.0, count = 0, i;
|
||||
averageDistTo(points) {
|
||||
let sum = 0.0,
|
||||
count = 0,
|
||||
i;
|
||||
for (i = 0; i < points.length; i++) {
|
||||
if (points[i] != this) {
|
||||
sum += this.distToSq2D(points[i]);
|
||||
|
|
@ -592,125 +453,125 @@
|
|||
}
|
||||
}
|
||||
return sum / count;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* dist to point in 2D
|
||||
*
|
||||
* @param {Point} p
|
||||
* @returns {number}
|
||||
*/
|
||||
PRO.distTo2D = function(p) {
|
||||
distTo2D(p) {
|
||||
let dx = this.x - p.x,
|
||||
dy = this.y - p.y;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* square of distance in 2D
|
||||
*
|
||||
* @param {Point} p
|
||||
* @returns {number}
|
||||
*/
|
||||
PRO.distToSq2D = function(p) {
|
||||
distToSq2D(p) {
|
||||
let dx = this.x - p.x,
|
||||
dy = this.y - p.y;
|
||||
return dx * dx + dy * dy;
|
||||
};
|
||||
}
|
||||
|
||||
PRO.distTo3D = function(p) {
|
||||
distTo3D(p) {
|
||||
let dx = this.x - p.x,
|
||||
dy = this.y - p.y,
|
||||
dz = this.z - p.z;
|
||||
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* square of distance in 3D
|
||||
*
|
||||
* @param {Point} p
|
||||
* @returns {number}
|
||||
*/
|
||||
PRO.distToSq3D = function(p) {
|
||||
distToSq3D(p) {
|
||||
let dx = this.x - p.x,
|
||||
dy = this.y - p.y,
|
||||
dz = this.z - p.z;
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
as_y = this.y - a.y,
|
||||
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 - b.x) * (this.y - b.y) - (c.y - b.y) * (this.x - b.x) > 0 != s_ab) return false;
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if point is on a line described by two points.
|
||||
* test sum of distances p1->this + this->p2 ~= p1->p2 whens
|
||||
* slopes from p1->this same as this->p2
|
||||
*
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PRO.onLine = function(p1, p2) {
|
||||
return this.distToLine(p1, p2) < CONF.precision_point_on_line;
|
||||
};
|
||||
onLine(p1, p2) {
|
||||
return this.distToLine(p1, p2) < config.precision_point_on_line;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {THREE.Vector3} delta
|
||||
* @return {Point} new offset point
|
||||
*/
|
||||
PRO.add = function(delta) {
|
||||
add(delta) {
|
||||
return newPoint(this.x + delta.x, this.y + delta.y, this.z + delta.z);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {THREE.Vector3} delta
|
||||
* @return {Point} new offset point
|
||||
*/
|
||||
PRO.sub = function(delta) {
|
||||
sub(delta) {
|
||||
return newPoint(this.x - delta.x, this.y - delta.y, this.z - delta.z);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {THREE.Vector3} delta
|
||||
*/
|
||||
PRO.move = function(delta) {
|
||||
move(delta) {
|
||||
this.x += delta.x;
|
||||
this.y += delta.y;
|
||||
this.z += delta.z;
|
||||
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,128 +4,125 @@
|
|||
|
||||
(function() {
|
||||
|
||||
const BASE = self.base;
|
||||
const CONF = BASE.config;
|
||||
const base = self.base;
|
||||
const { config } = base;
|
||||
|
||||
BASE.verticesToPoints = verticesToPoints;
|
||||
BASE.pointsToVertices = pointsToVertices;
|
||||
base.verticesToPoints = verticesToPoints;
|
||||
base.pointsToVertices = pointsToVertices;
|
||||
|
||||
/**
|
||||
* converts a geometry point array into a kiri point array
|
||||
* with auto-decimation
|
||||
*
|
||||
* @param {Float32Array} array
|
||||
* @returns {Array}
|
||||
*/
|
||||
function verticesToPoints(array, options) {
|
||||
let parr = new Array(array.length / 3),
|
||||
i = 0,
|
||||
j = 0,
|
||||
t = Date.now(),
|
||||
hash = {},
|
||||
unique = 0,
|
||||
passes = 0,
|
||||
points,
|
||||
oldpoints = parr.length,
|
||||
newpoints;
|
||||
/**
|
||||
* converts a geometry point array into a kiri point array
|
||||
* with auto-decimation
|
||||
*/
|
||||
function verticesToPoints(array, options) {
|
||||
let parr = new Array(array.length / 3),
|
||||
i = 0,
|
||||
j = 0,
|
||||
t = Date.now(),
|
||||
hash = {},
|
||||
unique = 0,
|
||||
passes = 0,
|
||||
points,
|
||||
oldpoints = parr.length,
|
||||
newpoints;
|
||||
|
||||
// replace point objects with their equivalents
|
||||
while (i < array.length) {
|
||||
let p = BASE.newPoint(array[i++], array[i++], array[i++]),
|
||||
k = p.key,
|
||||
m = hash[k];
|
||||
if (!m) {
|
||||
m = p;
|
||||
hash[k] = p;
|
||||
unique++;
|
||||
}
|
||||
parr[j++] = m;
|
||||
// replace point objects with their equivalents
|
||||
while (i < array.length) {
|
||||
let p = base.newPoint(array[i++], array[i++], array[i++]),
|
||||
k = p.key,
|
||||
m = hash[k];
|
||||
if (!m) {
|
||||
m = p;
|
||||
hash[k] = p;
|
||||
unique++;
|
||||
}
|
||||
parr[j++] = m;
|
||||
}
|
||||
|
||||
let {threshold, precision, maxpass} = options || {};
|
||||
// threshold = point count for triggering decimation
|
||||
// precision = under which points are merged
|
||||
// maxpass = max number of decimations
|
||||
threshold = threshold > 0 ? threshold : CONF.decimate_threshold;
|
||||
precision = precision >= 0 ? precision : CONF.precision_decimate;
|
||||
maxpass = maxpass >= 0 ? maxpass : 10;
|
||||
let {threshold, precision, maxpass} = options || {};
|
||||
// threshold = point count for triggering decimation
|
||||
// precision = under which points are merged
|
||||
// maxpass = max number of decimations
|
||||
threshold = threshold > 0 ? threshold : config.decimate_threshold;
|
||||
precision = precision >= 0 ? precision : config.precision_decimate;
|
||||
maxpass = maxpass >= 0 ? maxpass : 10;
|
||||
|
||||
// decimate until all point spacing > precision
|
||||
if (maxpass && precision > 0.0)
|
||||
while (parr.length > threshold) {
|
||||
let lines = [], line, dec = 0;
|
||||
for (i=0; i<oldpoints; ) {
|
||||
let p1 = parr[i++],
|
||||
p2 = parr[i++],
|
||||
p3 = parr[i++];
|
||||
lines.push( {p1:p1, p2:p2, d:Math.sqrt(p1.distToSq3D(p2))} );
|
||||
lines.push( {p1:p1, p2:p3, d:Math.sqrt(p1.distToSq3D(p3))} );
|
||||
lines.push( {p1:p2, p2:p3, d:Math.sqrt(p2.distToSq3D(p3))} );
|
||||
}
|
||||
// sort by ascending line length
|
||||
lines.sort(function(a,b) {
|
||||
return a.d - b.d
|
||||
});
|
||||
// create offset mid-points
|
||||
for (i=0; i<lines.length; i++) {
|
||||
line = lines[i];
|
||||
// skip lines longer than precision threshold
|
||||
if (line.d >= precision) break;
|
||||
// skip lines where one of the points is already offset
|
||||
if (line.p1.op || line.p2.op) continue;
|
||||
// todo skip dropping lines where either point is a "sharp" on 3 vectors
|
||||
// todo skip dropping lines where either point connects to a "long" line
|
||||
line.p1.op = line.p2.op = line.p1.midPointTo3D(line.p2);
|
||||
dec++;
|
||||
}
|
||||
// exit if nothing to decimate
|
||||
if (dec === 0) break;
|
||||
passes++;
|
||||
// create new facets
|
||||
points = new Array(oldpoints);
|
||||
newpoints = 0;
|
||||
for (i=0; i<oldpoints; ) {
|
||||
let p1 = parr[i++],
|
||||
p2 = parr[i++],
|
||||
p3 = parr[i++];
|
||||
// drop facets with two offset points
|
||||
if (p1.op && p1.op === p2.op) continue;
|
||||
if (p1.op && p1.op === p3.op) continue;
|
||||
if (p2.op && p2.op === p3.op) continue;
|
||||
// otherwise emit altered facet
|
||||
points[newpoints++] = p1.op || p1;
|
||||
points[newpoints++] = p2.op || p2;
|
||||
points[newpoints++] = p3.op || p3;
|
||||
}
|
||||
parr = points.slice(0,newpoints);
|
||||
oldpoints = newpoints;
|
||||
if (passes >= maxpass) {
|
||||
break;
|
||||
}
|
||||
// decimate until all point spacing > precision
|
||||
if (maxpass && precision > 0.0)
|
||||
while (parr.length > threshold) {
|
||||
let lines = [], line, dec = 0;
|
||||
for (i=0; i<oldpoints; ) {
|
||||
let p1 = parr[i++],
|
||||
p2 = parr[i++],
|
||||
p3 = parr[i++];
|
||||
lines.push( {p1:p1, p2:p2, d:Math.sqrt(p1.distToSq3D(p2))} );
|
||||
lines.push( {p1:p1, p2:p3, d:Math.sqrt(p1.distToSq3D(p3))} );
|
||||
lines.push( {p1:p2, p2:p3, d:Math.sqrt(p2.distToSq3D(p3))} );
|
||||
}
|
||||
|
||||
// if (passes) console.trace({passes, threshold, precision, maxpass});
|
||||
|
||||
if (passes) console.log({
|
||||
before: array.length / 3,
|
||||
after: parr.length,
|
||||
unique: unique,
|
||||
decimations: passes,
|
||||
time: (Date.now() - t)
|
||||
// sort by ascending line length
|
||||
lines.sort(function(a,b) {
|
||||
return a.d - b.d
|
||||
});
|
||||
|
||||
return parr;
|
||||
}
|
||||
|
||||
function pointsToVertices(points) {
|
||||
let vertices = new Float32Array(points.length * 3),
|
||||
i = 0, vi = 0;
|
||||
while (i < points.length) {
|
||||
vertices[vi++] = points[i].x;
|
||||
vertices[vi++] = points[i].y;
|
||||
vertices[vi++] = points[i++].z;
|
||||
// create offset mid-points
|
||||
for (i=0; i<lines.length; i++) {
|
||||
line = lines[i];
|
||||
// skip lines longer than precision threshold
|
||||
if (line.d >= precision) break;
|
||||
// skip lines where one of the points is already offset
|
||||
if (line.p1.op || line.p2.op) continue;
|
||||
// todo skip dropping lines where either point is a "sharp" on 3 vectors
|
||||
// todo skip dropping lines where either point connects to a "long" line
|
||||
line.p1.op = line.p2.op = line.p1.midPointTo3D(line.p2);
|
||||
dec++;
|
||||
}
|
||||
// exit if nothing to decimate
|
||||
if (dec === 0) break;
|
||||
passes++;
|
||||
// create new facets
|
||||
points = new Array(oldpoints);
|
||||
newpoints = 0;
|
||||
for (i=0; i<oldpoints; ) {
|
||||
let p1 = parr[i++],
|
||||
p2 = parr[i++],
|
||||
p3 = parr[i++];
|
||||
// drop facets with two offset points
|
||||
if (p1.op && p1.op === p2.op) continue;
|
||||
if (p1.op && p1.op === p3.op) continue;
|
||||
if (p2.op && p2.op === p3.op) continue;
|
||||
// otherwise emit altered facet
|
||||
points[newpoints++] = p1.op || p1;
|
||||
points[newpoints++] = p2.op || p2;
|
||||
points[newpoints++] = p3.op || p3;
|
||||
}
|
||||
parr = points.slice(0,newpoints);
|
||||
oldpoints = newpoints;
|
||||
if (passes >= maxpass) {
|
||||
break;
|
||||
}
|
||||
return vertices;
|
||||
}
|
||||
|
||||
// if (passes) console.trace({passes, threshold, precision, maxpass});
|
||||
|
||||
if (passes) console.log({
|
||||
before: array.length / 3,
|
||||
after: parr.length,
|
||||
unique: unique,
|
||||
decimations: passes,
|
||||
time: (Date.now() - t)
|
||||
});
|
||||
|
||||
return parr;
|
||||
}
|
||||
|
||||
function pointsToVertices(points) {
|
||||
let vertices = new Float32Array(points.length * 3),
|
||||
i = 0, vi = 0;
|
||||
while (i < points.length) {
|
||||
vertices[vi++] = points[i].x;
|
||||
vertices[vi++] = points[i].y;
|
||||
vertices[vi++] = points[i++].z;
|
||||
}
|
||||
return vertices;
|
||||
}
|
||||
|
||||
})();
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
1745
src/geo/polygons.js
1745
src/geo/polygons.js
File diff suppressed because it is too large
Load diff
|
|
@ -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);
|
||||
}
|
||||
|
||||
})();
|
||||
1448
src/geo/slicer.js
1448
src/geo/slicer.js
File diff suppressed because it is too large
Load diff
144
src/geo/slope.js
144
src/geo/slope.js
|
|
@ -4,147 +4,105 @@
|
|||
|
||||
(function() {
|
||||
|
||||
if (self.base.Slope) return;
|
||||
const base = self.base;
|
||||
if (base.Slope) return;
|
||||
|
||||
const BASE = self.base,
|
||||
CONF = BASE.config,
|
||||
ABS = Math.abs,
|
||||
PRO = Slope.prototype,
|
||||
DEG2RAD = Math.PI / 180,
|
||||
RAD2DEG = 180 / Math.PI;
|
||||
const { config } = base;
|
||||
const ABS = Math.abs,
|
||||
DEG2RAD = Math.PI / 180,
|
||||
RAD2DEG = 180 / Math.PI;
|
||||
|
||||
BASE.Slope = Slope;
|
||||
BASE.newSlope = newSlope;
|
||||
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) {
|
||||
class Slope {
|
||||
constructor(p1, p2, dx, dy) {
|
||||
this.dx = p1 && p2 ? p2.x - p1.x : dx;
|
||||
this.dy = p1 && p2 ? p2.y - p1.y : dy;
|
||||
this.angle = Math.atan2(this.dy, this.dx) * RAD2DEG;
|
||||
}
|
||||
|
||||
/** ******************************************************************
|
||||
* Slope Prototype Functions
|
||||
******************************************************************* */
|
||||
|
||||
PRO.toString = function() {
|
||||
toString() {
|
||||
return [this.dx, this.dy, this.angle].join(',');
|
||||
};
|
||||
}
|
||||
|
||||
PRO.clone = function() {
|
||||
clone() {
|
||||
return new Slope(null, null, this.dx, this.dy);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Slope} s
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PRO.isSame = function(s) {
|
||||
isSame(s) {
|
||||
// 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.dy) <= CONF.precision_merge && ABS(s.dy) <= CONF.precision_merge) return true;
|
||||
if (ABS(this.dx) <= config.precision_merge && ABS(s.dx) <= config.precision_merge) return true;
|
||||
if (ABS(this.dy) <= config.precision_merge && ABS(s.dy) <= config.precision_merge) return true;
|
||||
// check angle within a range
|
||||
let prec = Math.min(1/Math.sqrt(this.dx * this.dx + this.dy * this.dy), CONF.precision_slope_merge);
|
||||
return angleWithinDelta(this.angle, s.angle, prec || CONF.precision_slope);
|
||||
};
|
||||
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 || config.precision_slope);
|
||||
}
|
||||
|
||||
/**
|
||||
* turn slope 90 degrees
|
||||
*
|
||||
* @returns {Slope}
|
||||
*/
|
||||
PRO.normal = function() {
|
||||
normal() {
|
||||
let t = this.dx;
|
||||
this.dx = -this.dy;
|
||||
this.dy = t;
|
||||
this.angle = Math.atan2(this.dy, this.dx) * RAD2DEG;
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
PRO.invert = function() {
|
||||
invert() {
|
||||
this.dx = -this.dx;
|
||||
this.dy = -this.dy;
|
||||
this.angle = Math.atan2(this.dy, this.dx) * RAD2DEG;
|
||||
return this;
|
||||
}
|
||||
|
||||
PRO.toUnit = function() {
|
||||
toUnit() {
|
||||
let max = Math.max(ABS(this.dx), ABS(this.dy));
|
||||
this.dx = this.dx / max;
|
||||
this.dy = this.dy / max;
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
PRO.factor = function(f) {
|
||||
factor(f) {
|
||||
this.dx *= f;
|
||||
this.dy *= f;
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* reverse (180 degree) slope
|
||||
*
|
||||
* @returns {Slope}
|
||||
*/
|
||||
PRO.invert = function() {
|
||||
invert() {
|
||||
this.dx = -this.dx;
|
||||
this.dy = -this.dy;
|
||||
this.angle = 180 - this.angle;
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
PRO.angleDiff = function(s2,sign) {
|
||||
angleDiff(s2,sign) {
|
||||
const n1 = this.angle;
|
||||
const n2 = s2.angle;
|
||||
let diff = n2 - n1;
|
||||
while (diff < -180) diff += 360;
|
||||
while (diff > 180) diff -= 360;
|
||||
return sign ? diff : Math.abs(diff);
|
||||
};
|
||||
|
||||
/** ******************************************************************
|
||||
* Connect to base and Helpers
|
||||
******************************************************************* */
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return ABS(a-b) < v;
|
||||
}
|
||||
}
|
||||
|
||||
function angleWithinDelta(a1, a2, delta) {
|
||||
return (ABS(a1-a2) <= delta || 360-ABS(a1-a2) <= delta);
|
||||
}
|
||||
/**
|
||||
* returns true if the difference between a & b is less than v
|
||||
*/
|
||||
function minDeltaABS(a,b,v) {
|
||||
return ABS(a-b) < v;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param p1
|
||||
* @param p2
|
||||
* @param dx
|
||||
* @param dy
|
||||
* @returns {Slope}
|
||||
*/
|
||||
function newSlope(p1, p2, dx, dy) {
|
||||
return new Slope(p1, p2, dx, dy);
|
||||
}
|
||||
function angleWithinDelta(a1, a2, delta) {
|
||||
return (ABS(a1-a2) <= delta || 360-ABS(a1-a2) <= delta);
|
||||
}
|
||||
|
||||
function newSlope(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)
|
||||
);
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
|
|||
354
src/geo/wasm.js
354
src/geo/wasm.js
|
|
@ -5,202 +5,202 @@
|
|||
// only active in workers
|
||||
if (!self.window) (function() {
|
||||
|
||||
if (!self.geo) self.geo = {
|
||||
enable,
|
||||
disable,
|
||||
count: {
|
||||
offset: 0,
|
||||
union: 0,
|
||||
diff: 0
|
||||
}
|
||||
};
|
||||
|
||||
const factor = self.base.config.clipper;
|
||||
const geo = self.geo;
|
||||
|
||||
function log() {
|
||||
console.log(...arguments);
|
||||
if (!self.geo) self.geo = {
|
||||
enable,
|
||||
disable,
|
||||
count: {
|
||||
offset: 0,
|
||||
union: 0,
|
||||
diff: 0
|
||||
}
|
||||
};
|
||||
|
||||
function writePolys(view, polys) {
|
||||
let pcount = 0;
|
||||
for (let poly of polys) {
|
||||
pcount += writePoly(view, poly);
|
||||
}
|
||||
return pcount;
|
||||
const factor = self.base.config.clipper;
|
||||
const geo = self.geo;
|
||||
|
||||
function log() {
|
||||
console.log(...arguments);
|
||||
}
|
||||
|
||||
function writePolys(view, polys) {
|
||||
let pcount = 0;
|
||||
for (let poly of polys) {
|
||||
pcount += writePoly(view, poly);
|
||||
}
|
||||
return pcount;
|
||||
}
|
||||
|
||||
function writePoly(view, poly, inner) {
|
||||
if (inner) {
|
||||
poly.setCounterClockwise();
|
||||
function writePoly(view, poly, inner) {
|
||||
if (inner) {
|
||||
poly.setCounterClockwise();
|
||||
} else {
|
||||
poly.setClockwise();
|
||||
}
|
||||
let count = 1;
|
||||
let points = poly.points;
|
||||
let inners = poly.inner;
|
||||
view.writeU16(points.length, true);
|
||||
for (let i=0, il=points.length; i<il; i++) {
|
||||
let point = points[i];
|
||||
view.writeI32((point.x * factor)|0, true);
|
||||
view.writeI32((point.y * factor)|0, true);
|
||||
}
|
||||
if (inners) {
|
||||
for (let i=0, il=inners.length; i<il; i++) {
|
||||
count += writePoly(view, inners[i], true);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
function readPoly(view, z) {
|
||||
let points = view.readU16(true);
|
||||
if (points === 0) return;
|
||||
let poly = self.base.newPolygon();
|
||||
while (points-- > 0) {
|
||||
poly.add(view.readI32(true)/factor, view.readI32(true)/factor, z || 0);
|
||||
}
|
||||
return poly;
|
||||
}
|
||||
|
||||
function readPolys(view, z, out = []) {
|
||||
for (;;) {
|
||||
let poly = readPoly(view, z);
|
||||
if (poly) {
|
||||
out.push(poly);
|
||||
} else {
|
||||
poly.setClockwise();
|
||||
break;
|
||||
}
|
||||
let count = 1;
|
||||
let points = poly.points;
|
||||
let inners = poly.inner;
|
||||
view.writeU16(points.length, true);
|
||||
for (let i=0, il=points.length; i<il; i++) {
|
||||
let point = points[i];
|
||||
view.writeI32((point.x * factor)|0, true);
|
||||
view.writeI32((point.y * factor)|0, true);
|
||||
}
|
||||
if (inners) {
|
||||
for (let i=0, il=inners.length; i<il; i++) {
|
||||
count += writePoly(view, inners[i], true);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function readPoly(view, z) {
|
||||
let points = view.readU16(true);
|
||||
if (points === 0) return;
|
||||
let poly = self.base.newPolygon();
|
||||
while (points-- > 0) {
|
||||
poly.add(view.readI32(true)/factor, view.readI32(true)/factor, z || 0);
|
||||
}
|
||||
return poly;
|
||||
function polyOffset(polys, offset, z, clean, simple) {
|
||||
geo.count.offset++;
|
||||
let wasm = geo.wasm,
|
||||
buffer = geo.wasm.shared,
|
||||
pcount = writePolys(new DataWriter(wasm.heap, buffer), polys),
|
||||
resat = wasm.fn.offset(buffer, pcount, offset * factor, clean, simple),
|
||||
out = readPolys(new DataReader(wasm.heap, resat), z);
|
||||
return polyNest(out);
|
||||
}
|
||||
|
||||
function polyUnion(polys, z) {
|
||||
geo.count.union++;
|
||||
let wasm = geo.wasm,
|
||||
buffer = geo.wasm.shared,
|
||||
pcount = writePolys(new DataWriter(wasm.heap, buffer), polys),
|
||||
resat = wasm.fn.union(buffer, pcount),
|
||||
out = readPolys(new DataReader(wasm.heap, resat), z);
|
||||
return polyNest(out);
|
||||
}
|
||||
|
||||
function polyDiff(polysA, polysB, z, AB, BA) {
|
||||
geo.count.diff++;
|
||||
let wasm = geo.wasm,
|
||||
buffer = geo.wasm.shared,
|
||||
writer = new DataWriter(wasm.heap, buffer),
|
||||
pcountA = writePolys(writer, polysA),
|
||||
pcountB = writePolys(writer, polysB),
|
||||
resat = wasm.fn.diff(buffer, pcountA, pcountB, AB?1:0, BA?1:0, base.config.clipperClean),
|
||||
reader = new DataReader(wasm.heap, resat);
|
||||
if (AB) {
|
||||
AB.appendAll(polyNest(readPolys(reader, z)));
|
||||
}
|
||||
if (BA) {
|
||||
BA.appendAll(polyNest(readPolys(reader, z)));
|
||||
}
|
||||
}
|
||||
|
||||
function readPolys(view, z, out = []) {
|
||||
for (;;) {
|
||||
let poly = readPoly(view, z);
|
||||
if (poly) {
|
||||
out.push(poly);
|
||||
} else {
|
||||
// nest closed polygons without existing parent / child relationships
|
||||
function polyNest(polys) {
|
||||
polys.sort((a,b) => {
|
||||
return b.bounds.minx - a.bounds.minx;
|
||||
});
|
||||
// from smallest to largest, check for enclosing bounds and nest
|
||||
for (let i=0, il=polys.length; i<il; i++) {
|
||||
let smaller = polys[i];
|
||||
// prevent parent poly from being consumed
|
||||
if (smaller.inner) continue;
|
||||
for (let j=i+1; j<il; j++) {
|
||||
let larger = polys[j];
|
||||
if (larger.bounds.contains(smaller.bounds)) {
|
||||
larger.addInner(smaller);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function polyOffset(polys, offset, z, clean, simple) {
|
||||
geo.count.offset++;
|
||||
let wasm = geo.wasm,
|
||||
buffer = geo.wasm.shared,
|
||||
pcount = writePolys(new DataWriter(wasm.heap, buffer), polys),
|
||||
resat = wasm.fn.offset(buffer, pcount, offset * factor, clean, simple),
|
||||
out = readPolys(new DataReader(wasm.heap, resat), z);
|
||||
return polyNest(out);
|
||||
}
|
||||
|
||||
function polyUnion(polys, z) {
|
||||
geo.count.union++;
|
||||
let wasm = geo.wasm,
|
||||
buffer = geo.wasm.shared,
|
||||
pcount = writePolys(new DataWriter(wasm.heap, buffer), polys),
|
||||
resat = wasm.fn.union(buffer, pcount),
|
||||
out = readPolys(new DataReader(wasm.heap, resat), z);
|
||||
return polyNest(out);
|
||||
}
|
||||
|
||||
function polyDiff(polysA, polysB, z, AB, BA) {
|
||||
geo.count.diff++;
|
||||
let wasm = geo.wasm,
|
||||
buffer = geo.wasm.shared,
|
||||
writer = new DataWriter(wasm.heap, buffer),
|
||||
pcountA = writePolys(writer, polysA),
|
||||
pcountB = writePolys(writer, polysB),
|
||||
resat = wasm.fn.diff(buffer, pcountA, pcountB, AB?1:0, BA?1:0, base.config.clipperClean),
|
||||
reader = new DataReader(wasm.heap, resat);
|
||||
if (AB) {
|
||||
AB.appendAll(polyNest(readPolys(reader, z)));
|
||||
}
|
||||
if (BA) {
|
||||
BA.appendAll(polyNest(readPolys(reader, z)));
|
||||
let tops = [];
|
||||
for (let i=0, il=polys.length; i<il; i++) {
|
||||
let poly = polys[i];
|
||||
if (!poly.parent) {
|
||||
tops.push(poly);
|
||||
}
|
||||
}
|
||||
return tops;
|
||||
}
|
||||
|
||||
// nest closed polygons without existing parent / child relationships
|
||||
function polyNest(polys) {
|
||||
polys.sort((a,b) => {
|
||||
return b.bounds.minx - a.bounds.minx;
|
||||
function readString(pos, len) {
|
||||
let view = new DataReader(geo.wasm.heap, pos);
|
||||
let out = [];
|
||||
while (len-- > 0) {
|
||||
out.push(String.fromCharCode(view.readU8()));
|
||||
}
|
||||
return out.join('');
|
||||
}
|
||||
|
||||
function enable() {
|
||||
if (geo.wasm || geo._wasm) {
|
||||
return;
|
||||
}
|
||||
geo._wasm = 'loading';
|
||||
fetch('/wasm/kiri-geo.wasm')
|
||||
.then(response => response.arrayBuffer())
|
||||
.then(bytes => WebAssembly.instantiate(bytes, {
|
||||
env: {
|
||||
debug_string: (len, ptr) => { console.log('wasm', readString(ptr, len)) }
|
||||
},
|
||||
wasi_snapshot_preview1: {
|
||||
// args_get: (count,bufsize) => { return 0 },
|
||||
// args_sizes_get: (count,bufsize) => { },
|
||||
// environ_get: (count,bufsize) => { return 0 },
|
||||
// environ_sizes_get: (count,bufsize) => { },
|
||||
proc_exit: (code) => { return code }
|
||||
}
|
||||
}))
|
||||
.then(results => {
|
||||
// console.log({enabled: geo.wasm});
|
||||
delete geo._wasm;
|
||||
let { module, instance } = results;
|
||||
let { exports } = instance;
|
||||
let heap = new DataView(exports.memory.buffer);
|
||||
let wasm = geo.wasm = {
|
||||
heap,
|
||||
exports,
|
||||
memory: exports.memory,
|
||||
memmax: exports.memory.buffer.byteLength,
|
||||
malloc: exports.mem_get,
|
||||
free: exports.mem_clr
|
||||
};
|
||||
wasm.shared = wasm.malloc(1024 * 1024 * 30),
|
||||
wasm.fn = {
|
||||
diff: exports.poly_diff,
|
||||
union: exports.poly_union,
|
||||
offset: exports.poly_offset
|
||||
};
|
||||
wasm.js = {
|
||||
diff: polyDiff,
|
||||
union: polyUnion,
|
||||
offset: polyOffset
|
||||
};
|
||||
});
|
||||
// from smallest to largest, check for enclosing bounds and nest
|
||||
for (let i=0, il=polys.length; i<il; i++) {
|
||||
let smaller = polys[i];
|
||||
// prevent parent poly from being consumed
|
||||
if (smaller.inner) continue;
|
||||
for (let j=i+1; j<il; j++) {
|
||||
let larger = polys[j];
|
||||
if (larger.bounds.contains(smaller.bounds)) {
|
||||
larger.addInner(smaller);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let tops = [];
|
||||
for (let i=0, il=polys.length; i<il; i++) {
|
||||
let poly = polys[i];
|
||||
if (!poly.parent) {
|
||||
tops.push(poly);
|
||||
}
|
||||
}
|
||||
return tops;
|
||||
}
|
||||
}
|
||||
|
||||
function readString(pos, len) {
|
||||
let view = new DataReader(geo.wasm.heap, pos);
|
||||
let out = [];
|
||||
while (len-- > 0) {
|
||||
out.push(String.fromCharCode(view.readU8()));
|
||||
}
|
||||
return out.join('');
|
||||
}
|
||||
|
||||
function enable() {
|
||||
if (geo.wasm || geo._wasm) {
|
||||
return;
|
||||
}
|
||||
geo._wasm = 'loading';
|
||||
fetch('/wasm/kiri-geo.wasm')
|
||||
.then(response => response.arrayBuffer())
|
||||
.then(bytes => WebAssembly.instantiate(bytes, {
|
||||
env: {
|
||||
debug_string: (len, ptr) => { console.log('wasm', readString(ptr, len)) }
|
||||
},
|
||||
wasi_snapshot_preview1: {
|
||||
// args_get: (count,bufsize) => { return 0 },
|
||||
// args_sizes_get: (count,bufsize) => { },
|
||||
// environ_get: (count,bufsize) => { return 0 },
|
||||
// environ_sizes_get: (count,bufsize) => { },
|
||||
proc_exit: (code) => { return code }
|
||||
}
|
||||
}))
|
||||
.then(results => {
|
||||
// console.log({enabled: geo.wasm});
|
||||
delete geo._wasm;
|
||||
let { module, instance } = results;
|
||||
let { exports } = instance;
|
||||
let heap = new DataView(exports.memory.buffer);
|
||||
let wasm = geo.wasm = {
|
||||
heap,
|
||||
exports,
|
||||
memory: exports.memory,
|
||||
memmax: exports.memory.buffer.byteLength,
|
||||
malloc: exports.mem_get,
|
||||
free: exports.mem_clr
|
||||
};
|
||||
wasm.shared = wasm.malloc(1024 * 1024 * 30),
|
||||
wasm.fn = {
|
||||
diff: exports.poly_diff,
|
||||
union: exports.poly_union,
|
||||
offset: exports.poly_offset
|
||||
};
|
||||
wasm.js = {
|
||||
diff: polyDiff,
|
||||
union: polyUnion,
|
||||
offset: polyOffset
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function disable() {
|
||||
if (geo.wasm) {
|
||||
delete geo.wasm;
|
||||
// console.log({disabled: geo});
|
||||
}
|
||||
function disable() {
|
||||
if (geo.wasm) {
|
||||
delete geo.wasm;
|
||||
// console.log({disabled: geo});
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
|
|
|
|||
Loading…
Reference in a new issue