@jscad/modeling 2.9.5 → 2.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,22 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [2.9.6](https://github.com/jscad/OpenJSCAD.org/compare/@jscad/modeling@2.9.5...@jscad/modeling@2.9.6) (2022-07-17)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **modeling:** corrected concat to prevent modifying original paths ([#1118](https://github.com/jscad/OpenJSCAD.org/issues/1118)) ([ebeaa26](https://github.com/jscad/OpenJSCAD.org/commit/ebeaa26d087646cc6b0f9b927a9230db471a0469))
12
+
13
+
14
+ ### Performance Improvements
15
+
16
+ * **modeling:** convert {} objects to Map in insertTjunctions ([#1109](https://github.com/jscad/OpenJSCAD.org/issues/1109)) ([03c7fd1](https://github.com/jscad/OpenJSCAD.org/commit/03c7fd12af53ca025e32d481989563717b8d2137))
17
+
18
+
19
+
20
+
21
+
6
22
  ## [2.9.5](https://github.com/jscad/OpenJSCAD.org/compare/@jscad/modeling@2.9.4...@jscad/modeling@2.9.5) (2022-06-12)
7
23
 
8
24
 
@@ -143,9 +143,9 @@ const vec2=require("../../maths/vec2"),fromPoints=require("./fromPoints"),toPoin
143
143
  const vec2=require("../../maths/vec2"),vec3=require("../../maths/vec2"),appendPoints=require("./appendPoints"),toPoints=require("./toPoints"),appendBezier=(e,t)=>{let{controlPoints:o,segments:r}=Object.assign({},{segments:16},e);if(!Array.isArray(o))throw new Error("controlPoints must be an array of one or more points");if(o.length<1)throw new Error("controlPoints must be an array of one or more points");if(r<4)throw new Error("segments must be four or more");if(t.isClosed)throw new Error("the given geometry cannot be closed");const n=toPoints(t);if(n.length<1)throw new Error("the given path must contain one or more points (as the starting point for the bezier curve)");if(null===(o=o.slice())[0]){if(o.length<2)throw new Error("a null control point must be passed with one more control points");let e=n[n.length-2];if("lastBezierControlPoint"in t&&(e=t.lastBezierControlPoint),!Array.isArray(e))throw new Error("the given path must contain TWO or more points if given a null control point");const r=vec2.scale(vec2.create(),n[n.length-1],2);vec2.subtract(r,r,e),o[0]=r}o.unshift(n[n.length-1]);const s=o.length-1,i=[];let c=1;for(let e=0;e<=s;++e)e>0&&(c*=e),i.push(c);const a=[];for(let e=0;e<=s;++e){const t=i[s]/(i[e]*i[s-e]);a.push(t)}const l=vec2.create(),h=vec2.create(),p=vec3.create(),u=e=>{let t=1,r=Math.pow(1-e,s);const n=1!==e?1/(1-e):1,i=vec2.create();for(let c=0;c<=s;++c){c===s&&(r=1);const h=a[c]*t*r,p=vec2.scale(l,o[c],h);vec2.add(i,i,p),t*=e,r*=n}return i},v=[],f=[],m=s+1;for(let e=0;e<m;++e){const t=e/(m-1),o=u(t);v.push(o),f.push(t)}let g=1;const w=2*Math.PI/r,P=Math.sin(w);for(;g<v.length-1;){const e=vec2.subtract(l,v[g],v[g-1]);vec2.normalize(e,e);const t=vec2.subtract(h,v[g+1],v[g]);vec2.normalize(t,t);const o=vec2.cross(p,e,t);if(Math.abs(o[2])>P){const e=f[g-1],t=f[g+1],o=e+1*(t-e)/3,r=e+2*(t-e)/3,n=u(o),s=u(r);v.splice(g,1,n,s),f.splice(g,1,o,r),--g<1&&(g=1)}else++g}v.shift();const b=appendPoints(v,t);return b.lastBezierControlPoint=o[o.length-2],b};module.exports=appendBezier;
144
144
 
145
145
  },{"../../maths/vec2":186,"./appendPoints":49,"./toPoints":62}],49:[function(require,module,exports){
146
- const fromPoints=require("./fromPoints"),toPoints=require("./toPoints"),appendPoints=(o,t)=>{if(t.isClosed)throw new Error("cannot append points to a closed path");let n=toPoints(t);return n=n.concat(o),fromPoints({},n)};module.exports=appendPoints;
146
+ const concat=require("./concat"),create=require("./create"),appendPoints=(e,c)=>concat(c,create(e));module.exports=appendPoints;
147
147
 
148
- },{"./fromPoints":57,"./toPoints":62}],50:[function(require,module,exports){
148
+ },{"./concat":53,"./create":54}],50:[function(require,module,exports){
149
149
  const mat4=require("../../maths/mat4"),vec2=require("../../maths/vec2"),applyTransforms=r=>mat4.isIdentity(r.transforms)?r:(r.points=r.points.map(s=>vec2.transform(vec2.create(),s,r.transforms)),r.transforms=mat4.create(),r);module.exports=applyTransforms;
150
150
 
151
151
  },{"../../maths/mat4":139,"../../maths/vec2":186}],51:[function(require,module,exports){
@@ -155,7 +155,7 @@ const clone=e=>Object.assign({},e);module.exports=clone;
155
155
  const{EPS:EPS}=require("../../maths/constants"),vec2=require("../../maths/vec2"),clone=require("./clone"),close=e=>{if(e.isClosed)return e;const t=clone(e);if(t.isClosed=!0,t.points.length>1){const e=t.points,n=e[0];let o=e[e.length-1];for(;vec2.distance(n,o)<EPS*EPS&&(e.pop(),1!==e.length);)o=e[e.length-1]}return t};module.exports=close;
156
156
 
157
157
  },{"../../maths/constants":90,"../../maths/vec2":186,"./clone":51}],53:[function(require,module,exports){
158
- const fromPoints=require("./fromPoints"),toPoints=require("./toPoints"),{equals:equals}=require("../../maths/vec2"),concat=(...o)=>{let t=!1;for(const e of o){if(t)throw new Error("Cannot concatenate to a closed path");t=e.isClosed}let e=[];return o.forEach(o=>{const t=toPoints(o);e.length>0&&t.length>0&&equals(t[0],e[e.length-1])&&t.shift(),e=e.concat(t)}),fromPoints({closed:t},e)};module.exports=concat;
158
+ const fromPoints=require("./fromPoints"),toPoints=require("./toPoints"),{equals:equals}=require("../../maths/vec2"),concat=(...t)=>{let o=!1,e=[];return t.forEach((t,n)=>{const s=toPoints(t).slice();if(e.length>0&&s.length>0&&equals(s[0],e[e.length-1])&&s.shift(),s.length>0&&o)throw new Error(`Cannot concatenate to a closed path; check the ${n}th path`);o=t.isClosed,e=e.concat(s)}),fromPoints({closed:o},e)};module.exports=concat;
159
159
 
160
160
  },{"../../maths/vec2":186,"./fromPoints":57,"./toPoints":62}],54:[function(require,module,exports){
161
161
  const mat4=require("../../maths/mat4"),create=e=>(void 0===e&&(e=[]),{points:e,isClosed:!1,transforms:mat4.create()});module.exports=create;
@@ -1034,7 +1034,7 @@ const flatten=require("../../utils/flatten"),measureEpsilon=require("../../measu
1034
1034
  module.exports={generalize:require("./generalize"),snap:require("./snap")};
1035
1035
 
1036
1036
  },{"./generalize":344,"./snap":350}],346:[function(require,module,exports){
1037
- const constants=require("../../maths/constants"),vec3=require("../../maths/vec3"),poly3=require("../../geometries/poly3"),assert=!1,getTag=e=>`${e}`,addSide=(e,t,n,s,c,r)=>{const l=getTag(s),o=getTag(c);const i=`${l}/${o}`,g=`${o}/${l}`;if(e.has(g))return deleteSide(e,t,n,c,s,null),null;const a={vertex0:s,vertex1:c,polygonindex:r};return e.has(i)?e.get(i).push(a):e.set(i,[a]),l in t?t[l].push(i):t[l]=[i],o in n?n[o].push(i):n[o]=[i],i},deleteSide=(e,t,n,s,c,r)=>{const l=getTag(s),o=getTag(c),i=`${l}/${o}`;let g=-1;const a=e.get(i);for(let e=0;e<a.length;e++){const t=a[e];let n=getTag(t.vertex0);if(n===l&&((n=getTag(t.vertex1))===o&&(null===r||t.polygonindex===r))){g=e;break}}a.splice(g,1),0===a.length&&e.delete(i),g=t[l].indexOf(i),t[l].splice(g,1),0===t[l].length&&delete t[l],g=n[o].indexOf(i),n[o].splice(g,1),0===n[o].length&&delete n[o]},insertTjunctions=e=>{const t=new Map;for(let n=0;n<e.length;n++){const s=e[n],c=s.vertices.length;if(c>=3){let e=s.vertices[0],r=getTag(e);for(let l=0;l<c;l++){let o=l+1;o===c&&(o=0);const i=s.vertices[o],g=getTag(i),a=`${r}/${g}`,v=`${g}/${r}`;if(t.has(v)){const e=t.get(v);e.splice(-1,1),0===e.length&&t.delete(v)}else{const s={vertex0:e,vertex1:i,polygonindex:n};t.has(a)?t.get(a).push(s):t.set(a,[s])}e=i,r=g}}else console.warn("warning: invalid polygon found during insertTjunctions")}if(t.size>0){const n={},s={},c={};for(const[e,r]of t)c[e]=!0,r.forEach(t=>{const c=getTag(t.vertex0),r=getTag(t.vertex1);c in n?n[c].push(e):n[c]=[e],r in s?s[r].push(e):s[r]=[e]});const r=e.slice(0);for(;0!==t.size;){for(const e of t.keys())c[e]=!0;let e=!1;for(;;){const l=Object.keys(c);if(0===l.length)break;const o=l[0];let i=!0;if(t.has(o)){const l=t.get(o);0;const g=l[0];for(let l=0;l<2;l++){const o=0===l?g.vertex0:g.vertex1,a=0===l?g.vertex1:g.vertex0,v=getTag(o),d=getTag(a);let f=[];0===l?v in s&&(f=s[v]):v in n&&(f=n[v]);for(let g=0;g<f.length;g++){const v=f[g],u=t.get(v)[0],x=0===l?u.vertex0:u.vertex1,h=0===l?u.vertex1:u.vertex0,p=getTag(x);getTag(h);if(p===d){deleteSide(t,n,s,o,a,null),deleteSide(t,n,s,a,o,null),i=!1,l=2,e=!0;break}{const g=o,v=a,d=x,f=vec3.subtract(vec3.create(),d,g),h=vec3.dot(vec3.subtract(vec3.create(),v,g),f)/vec3.dot(f,f);if(h>0&&h<1){const o=vec3.scale(vec3.create(),f,h);if(vec3.add(o,o,g),vec3.squaredDistance(o,v)<constants.EPS*constants.EPS){const o=u.polygonindex,g=r[o],v=getTag(u.vertex1);let d=-1;for(let e=0;e<g.vertices.length;e++)if(getTag(g.vertices[e])===v){d=e;break}0;const f=g.vertices.slice(0);f.splice(d,0,a);const x=poly3.create(f);r[o]=x,deleteSide(t,n,s,u.vertex0,u.vertex1,o);const h=addSide(t,n,s,u.vertex0,a,o),p=addSide(t,n,s,a,u.vertex1,o);null!==h&&(c[h]=!0),null!==p&&(c[p]=!0),i=!1,l=2,e=!0;break}}}}}}i&&delete c[o]}if(!e)break}e=r}return t.clear(),e};module.exports=insertTjunctions;
1037
+ const constants=require("../../maths/constants"),vec3=require("../../maths/vec3"),poly3=require("../../geometries/poly3"),assert=!1,getTag=e=>`${e}`,addSide=(e,t,s,n,r,c)=>{const o=getTag(n),g=getTag(r);const l=`${o}/${g}`,a=`${g}/${o}`;if(e.has(a))return deleteSide(e,t,s,r,n,null),null;const i={vertex0:n,vertex1:r,polygonindex:c};return e.has(l)?e.get(l).push(i):e.set(l,[i]),t.has(o)?t.get(o).push(l):t.set(o,[l]),s.has(g)?s.get(g).push(l):s.set(g,[l]),l},deleteSide=(e,t,s,n,r,c)=>{const o=getTag(n),g=getTag(r),l=`${o}/${g}`;let a=-1;const i=e.get(l);for(let e=0;e<i.length;e++){const t=i[e];let s=getTag(t.vertex0);if(s===o&&((s=getTag(t.vertex1))===g&&(null===c||t.polygonindex===c))){a=e;break}}i.splice(a,1),0===i.length&&e.delete(l),a=t.get(o).indexOf(l),t.get(o).splice(a,1),0===t.get(o).length&&t.delete(o),a=s.get(g).indexOf(l),s.get(g).splice(a,1),0===s.get(g).length&&s.delete(g)},insertTjunctions=e=>{const t=new Map;for(let s=0;s<e.length;s++){const n=e[s],r=n.vertices.length;if(r>=3){let e=n.vertices[0],c=getTag(e);for(let o=0;o<r;o++){let g=o+1;g===r&&(g=0);const l=n.vertices[g],a=getTag(l),i=`${c}/${a}`,v=`${a}/${c}`;if(t.has(v)){const e=t.get(v);e.splice(-1,1),0===e.length&&t.delete(v)}else{const n={vertex0:e,vertex1:l,polygonindex:s};t.has(i)?t.get(i).push(n):t.set(i,[n])}e=l,c=a}}else console.warn("warning: invalid polygon found during insertTjunctions")}if(t.size>0){const s=new Map,n=new Map,r=new Map;for(const[e,c]of t)r.set(e,!0),c.forEach(t=>{const r=getTag(t.vertex0),c=getTag(t.vertex1);s.has(r)?s.get(r).push(e):s.set(r,[e]),n.has(c)?n.get(c).push(e):n.set(c,[e])});const c=e.slice(0);for(;0!==t.size;){for(const e of t.keys())r.set(e,!0);let e=!1;for(;;){const o=Array.from(r.keys());if(0===o.length)break;const g=o[0];let l=!0;if(t.has(g)){const o=t.get(g);0;const a=o[0];for(let o=0;o<2;o++){const g=0===o?a.vertex0:a.vertex1,i=0===o?a.vertex1:a.vertex0,v=getTag(g),d=getTag(i);let h=[];0===o?n.has(v)&&(h=n.get(v)):s.has(v)&&(h=s.get(v));for(let a=0;a<h.length;a++){const v=h[a],f=t.get(v)[0],u=0===o?f.vertex0:f.vertex1,x=0===o?f.vertex1:f.vertex0,p=getTag(u);getTag(x);if(p===d){deleteSide(t,s,n,g,i,null),deleteSide(t,s,n,i,g,null),l=!1,o=2,e=!0;break}{const a=g,v=i,d=u,h=vec3.subtract(vec3.create(),d,a),x=vec3.dot(vec3.subtract(vec3.create(),v,a),h)/vec3.dot(h,h);if(x>0&&x<1){const g=vec3.scale(vec3.create(),h,x);if(vec3.add(g,g,a),vec3.squaredDistance(g,v)<constants.EPS*constants.EPS){const g=f.polygonindex,a=c[g],v=getTag(f.vertex1);let d=-1;for(let e=0;e<a.vertices.length;e++)if(getTag(a.vertices[e])===v){d=e;break}0;const h=a.vertices.slice(0);h.splice(d,0,i);const u=poly3.create(h);c[g]=u,deleteSide(t,s,n,f.vertex0,f.vertex1,g);const x=addSide(t,s,n,f.vertex0,i,g),p=addSide(t,s,n,i,f.vertex1,g);null!==x&&r.set(x,!0),null!==p&&r.set(p,!0),l=!1,o=2,e=!0;break}}}}}}l&&r.delete(g)}if(!e)break}e=c}return t.clear(),e};module.exports=insertTjunctions;
1038
1038
 
1039
1039
  },{"../../geometries/poly3":75,"../../maths/constants":90,"../../maths/vec3":217}],347:[function(require,module,exports){
1040
1040
  const aboutEqualNormals=require("../../maths/utils/aboutEqualNormals"),vec3=require("../../maths/vec3"),poly3=require("../../geometries/poly3"),createEdges=e=>{const n=poly3.toPoints(e),t=[];for(let e=0;e<n.length;e++){const l=(e+1)%n.length,r={v1:n[e],v2:n[l]};t.push(r)}for(let e=0;e<t.length;e++){const l=(e+1)%n.length;t[e].next=t[l],t[l].prev=t[e]}return t},insertEdge=(e,n)=>{const t=`${n.v1}:${n.v2}`;e.set(t,n)},deleteEdge=(e,n)=>{const t=`${n.v1}:${n.v2}`;e.delete(t)},findOppositeEdge=(e,n)=>{const t=`${n.v2}:${n.v1}`;return e.get(t)},calculateAnglesBetween=(e,n,t)=>{let l=e.prev.v1,r=e.prev.v2,o=n.next.v2;const v=calculateAngle(l,r,o,t);return l=n.prev.v1,r=n.prev.v2,o=e.next.v2,[v,calculateAngle(l,r,o,t)]},v1=vec3.create(),v2=vec3.create(),calculateAngle=(e,n,t,l)=>{const r=vec3.subtract(v1,n,e),o=vec3.subtract(v2,t,n);return vec3.cross(r,r,o),vec3.dot(r,l)},createPolygonAnd=e=>{let n;const t=[];for(;e.next;){const n=e.next;t.push(e.v1),e.v1=null,e.v2=null,e.next=null,e.prev=null,e=n}return t.length>0&&(n=poly3.create(t)),n},mergeCoplanarPolygons=e=>{if(e.length<2)return e;const n=e[0].plane,t=e.slice(),l=new Map;for(;t.length>0;){const e=t.shift(),r=createEdges(e);for(let e=0;e<r.length;e++){const t=r[e],o=findOppositeEdge(l,t);if(o){const e=calculateAnglesBetween(t,o,n);if(e[0]>=0&&e[1]>=0){const n=o.next,r=t.next;t.prev.next=o.next,t.next.prev=o.prev,o.prev.next=t.next,o.next.prev=t.prev,t.v1=null,t.v2=null,t.next=null,t.prev=null,deleteEdge(l,o),o.v1=null,o.v2=null,o.next=null,o.prev=null;const v=(e,n,t)=>{const l={v1:t.v1,v2:n.v2,next:n.next,prev:t.prev};t.prev.next=l,n.next.prev=l,deleteEdge(e,n),n.v1=null,n.v2=null,n.next=null,n.prev=null,deleteEdge(e,t),t.v1=null,t.v2=null,t.next=null,t.prev=null};0===e[0]&&v(l,n,n.prev),0===e[1]&&v(l,r,r.prev)}}else t.next&&insertEdge(l,t)}}const r=[];return l.forEach(e=>{const n=createPolygonAnd(e);n&&r.push(n)}),l.clear(),r},coplanar=(e,n)=>Math.abs(e[3]-n[3])<1.5e-7&&aboutEqualNormals(e,n),mergePolygons=(e,n)=>{const t=[];n.forEach(e=>{const n=t.find(n=>coplanar(n[0],poly3.plane(e)));if(n){n[1].push(e)}else t.push([poly3.plane(e),[e]])});let l=[];return t.forEach(e=>{const n=e[1],t=mergeCoplanarPolygons(n);l=l.concat(t)}),l};module.exports=mergePolygons;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jscad/modeling",
3
- "version": "2.9.5",
3
+ "version": "2.9.6",
4
4
  "description": "Constructive Solid Geometry (CSG) Library for JSCAD",
5
5
  "homepage": "https://openjscad.xyz/",
6
6
  "repository": "https://github.com/jscad/OpenJSCAD.org",
@@ -61,5 +61,5 @@
61
61
  "nyc": "15.1.0",
62
62
  "uglifyify": "5.0.2"
63
63
  },
64
- "gitHead": "225b034db0d94f748992da72b269833954a2e212"
64
+ "gitHead": "9768af96e5da00cd113c00ddeb0f6046707819b1"
65
65
  }
@@ -1,5 +1,5 @@
1
- const fromPoints = require('./fromPoints')
2
- const toPoints = require('./toPoints')
1
+ const concat = require('./concat')
2
+ const create = require('./create')
3
3
 
4
4
  /**
5
5
  * Append the given list of points to the end of the given geometry.
@@ -10,15 +10,6 @@ const toPoints = require('./toPoints')
10
10
  * @example
11
11
  * let newpath = appendPoints([[3, 4], [4, 5]], oldpath)
12
12
  */
13
- const appendPoints = (points, geometry) => {
14
- if (geometry.isClosed) {
15
- throw new Error('cannot append points to a closed path')
16
- }
17
-
18
- let newpoints = toPoints(geometry)
19
- newpoints = newpoints.concat(points)
20
-
21
- return fromPoints({}, newpoints)
22
- }
13
+ const appendPoints = (points, geometry) => concat(geometry, create(points))
23
14
 
24
15
  module.exports = appendPoints
@@ -17,3 +17,19 @@ test('appendPoints: appending to a path produces a new path with expected points
17
17
  t.not(p1, obs)
18
18
  t.is(pts.length, 4)
19
19
  })
20
+
21
+ test('appendPoints: appending empty points to a path produces a new path with expected points', (t) => {
22
+ const p1 = fromPoints({}, [[1, 1], [2, 2]])
23
+ const obs = appendPoints([], p1)
24
+ const pts = toPoints(obs)
25
+ t.not(p1, obs)
26
+ t.is(pts.length, 2)
27
+ })
28
+
29
+ test('appendPoints: appending same points to a path produces a new path with expected points', (t) => {
30
+ const p1 = fromPoints({}, [[1, 1], [2, 2]])
31
+ const obs = appendPoints([[2, 2], [3, 3]], p1)
32
+ const pts = toPoints(obs)
33
+ t.not(p1, obs)
34
+ t.is(pts.length, 3)
35
+ })
@@ -1,8 +1,11 @@
1
1
  const fromPoints = require('./fromPoints')
2
2
  const toPoints = require('./toPoints')
3
+
3
4
  const { equals } = require('../../maths/vec2')
5
+
4
6
  /**
5
7
  * Concatenate the given paths.
8
+ *
6
9
  * If both contain the same point at the junction, merge it into one.
7
10
  * A concatenation of zero paths is an empty, open path.
8
11
  * A concatenation of one closed path to a series of open paths produces a closed path.
@@ -17,16 +20,14 @@ const { equals } = require('../../maths/vec2')
17
20
  const concat = (...paths) => {
18
21
  // Only the last path can be closed, producing a closed path.
19
22
  let isClosed = false
20
- for (const path of paths) {
21
- if (isClosed) {
22
- throw new Error('Cannot concatenate to a closed path')
23
- }
24
- isClosed = path.isClosed
25
- }
26
23
  let newpoints = []
27
- paths.forEach((path) => {
28
- const tmp = toPoints(path)
24
+ paths.forEach((path, i) => {
25
+ const tmp = toPoints(path).slice()
29
26
  if (newpoints.length > 0 && tmp.length > 0 && equals(tmp[0], newpoints[newpoints.length - 1])) tmp.shift()
27
+ if (tmp.length > 0 && isClosed) {
28
+ throw new Error(`Cannot concatenate to a closed path; check the ${i}th path`)
29
+ }
30
+ isClosed = path.isClosed
30
31
  newpoints = newpoints.concat(tmp)
31
32
  })
32
33
  return fromPoints({ closed: isClosed }, newpoints)
@@ -10,10 +10,16 @@ test('concat: empty paths produces an empty open path', (t) => {
10
10
  t.true(equals(concat(fromPoints({}, []), fromPoints({}, [])), fromPoints({ closed: false }, [])))
11
11
  })
12
12
 
13
- test('concat: Two open paths produces a open path', (t) => {
14
- t.true(equals(concat(fromPoints({ closed: false }, [[0, 0]]),
15
- fromPoints({ closed: false }, [[1, 1]])),
16
- fromPoints({ closed: false }, [[0, 0], [1, 1]])))
13
+ test('concat: many open paths produces a open path', (t) => {
14
+ const p1 = fromPoints({ closed: false }, [[0, 0]])
15
+ const p2 = fromPoints({ closed: false }, [[1, 1]])
16
+ const p3 = fromPoints({ closed: false }, [[1, 1], [3, 3]])
17
+
18
+ const result = concat(p1, p2, p3)
19
+ t.true(equals(result, fromPoints({}, [[0, 0], [1, 1], [3, 3]])))
20
+ t.is(p1.points.length, 1)
21
+ t.is(p2.points.length, 1)
22
+ t.is(p3.points.length, 2)
17
23
  })
18
24
 
19
25
  test('concat: An open path and a closed path produces a closed path', (t) => {
@@ -23,7 +29,7 @@ test('concat: An open path and a closed path produces a closed path', (t) => {
23
29
  })
24
30
 
25
31
  test('concat: A closed path and an open path throws an error', (t) => {
26
- t.throws(() => concat(fromPoints({ closed: true }, [[0, 0]]),
27
- fromPoints({ closed: false }, [[1, 1]])),
28
- { message: 'Cannot concatenate to a closed path' })
32
+ const p1 = fromPoints({ closed: true }, [[0, 0]])
33
+ const p2 = fromPoints({ closed: false }, [[1, 1]])
34
+ t.throws(() => concat(p1, p2), { message: 'Cannot concatenate to a closed path; check the 1th path' })
29
35
  })
@@ -28,15 +28,15 @@ const addSide = (sidemap, vertextag2sidestart, vertextag2sideend, vertex0, verte
28
28
  } else {
29
29
  sidemap.get(newsidetag).push(newsideobj)
30
30
  }
31
- if (starttag in vertextag2sidestart) {
32
- vertextag2sidestart[starttag].push(newsidetag)
31
+ if (vertextag2sidestart.has(starttag)) {
32
+ vertextag2sidestart.get(starttag).push(newsidetag)
33
33
  } else {
34
- vertextag2sidestart[starttag] = [newsidetag]
34
+ vertextag2sidestart.set(starttag, [newsidetag])
35
35
  }
36
- if (endtag in vertextag2sideend) {
37
- vertextag2sideend[endtag].push(newsidetag)
36
+ if (vertextag2sideend.has(endtag)) {
37
+ vertextag2sideend.get(endtag).push(newsidetag)
38
38
  } else {
39
- vertextag2sideend[endtag] = [newsidetag]
39
+ vertextag2sideend.set(endtag, [newsidetag])
40
40
  }
41
41
  return newsidetag
42
42
  }
@@ -67,18 +67,18 @@ const deleteSide = (sidemap, vertextag2sidestart, vertextag2sideend, vertex0, ve
67
67
  }
68
68
 
69
69
  // adjust start and end lists
70
- idx = vertextag2sidestart[starttag].indexOf(sidetag)
70
+ idx = vertextag2sidestart.get(starttag).indexOf(sidetag)
71
71
  if (assert && idx < 0) throw new Error('assert failed')
72
- vertextag2sidestart[starttag].splice(idx, 1)
73
- if (vertextag2sidestart[starttag].length === 0) {
74
- delete vertextag2sidestart[starttag]
72
+ vertextag2sidestart.get(starttag).splice(idx, 1)
73
+ if (vertextag2sidestart.get(starttag).length === 0) {
74
+ vertextag2sidestart.delete(starttag)
75
75
  }
76
76
 
77
- idx = vertextag2sideend[endtag].indexOf(sidetag)
77
+ idx = vertextag2sideend.get(endtag).indexOf(sidetag)
78
78
  if (assert && idx < 0) throw new Error('assert failed')
79
- vertextag2sideend[endtag].splice(idx, 1)
80
- if (vertextag2sideend[endtag].length === 0) {
81
- delete vertextag2sideend[endtag]
79
+ vertextag2sideend.get(endtag).splice(idx, 1)
80
+ if (vertextag2sideend.get(endtag).length === 0) {
81
+ vertextag2sideend.delete(endtag)
82
82
  }
83
83
  }
84
84
 
@@ -158,25 +158,24 @@ const insertTjunctions = (polygons) => {
158
158
  }
159
159
 
160
160
  if (sidemap.size > 0) {
161
- // console.log('insertTjunctions',sidemap.size)
162
161
  // STEP 2 : create a list of starting sides and ending sides
163
- const vertextag2sidestart = {}
164
- const vertextag2sideend = {}
165
- const sidestocheck = {}
162
+ const vertextag2sidestart = new Map()
163
+ const vertextag2sideend = new Map()
164
+ const sidesToCheck = new Map()
166
165
  for (const [sidetag, sideobjs] of sidemap) {
167
- sidestocheck[sidetag] = true
166
+ sidesToCheck.set(sidetag, true)
168
167
  sideobjs.forEach((sideobj) => {
169
168
  const starttag = getTag(sideobj.vertex0)
170
169
  const endtag = getTag(sideobj.vertex1)
171
- if (starttag in vertextag2sidestart) {
172
- vertextag2sidestart[starttag].push(sidetag)
170
+ if (vertextag2sidestart.has(starttag)) {
171
+ vertextag2sidestart.get(starttag).push(sidetag)
173
172
  } else {
174
- vertextag2sidestart[starttag] = [sidetag]
173
+ vertextag2sidestart.set(starttag, [sidetag])
175
174
  }
176
- if (endtag in vertextag2sideend) {
177
- vertextag2sideend[endtag].push(sidetag)
175
+ if (vertextag2sideend.has(endtag)) {
176
+ vertextag2sideend.get(endtag).push(sidetag)
178
177
  } else {
179
- vertextag2sideend[endtag] = [sidetag]
178
+ vertextag2sideend.set(endtag, [sidetag])
180
179
  }
181
180
  })
182
181
  }
@@ -187,13 +186,13 @@ const insertTjunctions = (polygons) => {
187
186
  if (sidemap.size === 0) break
188
187
 
189
188
  for (const sidetag of sidemap.keys()) {
190
- sidestocheck[sidetag] = true
189
+ sidesToCheck.set(sidetag, true)
191
190
  }
192
191
 
193
192
  let donesomething = false
194
193
  while (true) {
195
- const sidetags = Object.keys(sidestocheck)
196
- if (sidetags.length === 0) break // sidestocheck is empty, we're done!
194
+ const sidetags = Array.from(sidesToCheck.keys())
195
+ if (sidetags.length === 0) break // sidesToCheck is empty, we're done!
197
196
  const sidetagtocheck = sidetags[0]
198
197
  let donewithside = true
199
198
  if (sidemap.has(sidetagtocheck)) {
@@ -207,12 +206,12 @@ const insertTjunctions = (polygons) => {
207
206
  const endvertextag = getTag(endvertex)
208
207
  let matchingsides = []
209
208
  if (directionindex === 0) {
210
- if (startvertextag in vertextag2sideend) {
211
- matchingsides = vertextag2sideend[startvertextag]
209
+ if (vertextag2sideend.has(startvertextag)) {
210
+ matchingsides = vertextag2sideend.get(startvertextag)
212
211
  }
213
212
  } else {
214
- if (startvertextag in vertextag2sidestart) {
215
- matchingsides = vertextag2sidestart[startvertextag]
213
+ if (vertextag2sidestart.has(startvertextag)) {
214
+ matchingsides = vertextag2sidestart.get(startvertextag)
216
215
  }
217
216
  }
218
217
  for (let matchingsideindex = 0; matchingsideindex < matchingsides.length; matchingsideindex++) {
@@ -267,8 +266,8 @@ const insertTjunctions = (polygons) => {
267
266
  deleteSide(sidemap, vertextag2sidestart, vertextag2sideend, matchingside.vertex0, matchingside.vertex1, polygonindex)
268
267
  const newsidetag1 = addSide(sidemap, vertextag2sidestart, vertextag2sideend, matchingside.vertex0, endvertex, polygonindex)
269
268
  const newsidetag2 = addSide(sidemap, vertextag2sidestart, vertextag2sideend, endvertex, matchingside.vertex1, polygonindex)
270
- if (newsidetag1 !== null) sidestocheck[newsidetag1] = true
271
- if (newsidetag2 !== null) sidestocheck[newsidetag2] = true
269
+ if (newsidetag1 !== null) sidesToCheck.set(newsidetag1, true)
270
+ if (newsidetag2 !== null) sidesToCheck.set(newsidetag2, true)
272
271
  donewithside = false
273
272
  directionindex = 2 // skip reverse direction check
274
273
  donesomething = true
@@ -280,7 +279,7 @@ const insertTjunctions = (polygons) => {
280
279
  } // for directionindex
281
280
  } // if(sidetagtocheck in sidemap)
282
281
  if (donewithside) {
283
- delete sidestocheck[sidetagtocheck]
282
+ sidesToCheck.delete(sidetagtocheck)
284
283
  }
285
284
  }
286
285
  if (!donesomething) break