@jscad/modeling 2.9.3 → 2.9.4
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 +13 -0
- package/dist/jscad-modeling.min.js +7 -7
- package/package.json +2 -2
- package/src/geometries/geom3/applyTransforms.js +1 -2
- package/src/maths/mat4/isMirroring.js +11 -11
- package/src/operations/booleans/intersectGeom2.test.js +69 -0
- package/src/operations/booleans/{intersect.test.js → intersectGeom3.test.js} +3 -71
- package/src/operations/booleans/subtractGeom2.test.js +72 -0
- package/src/operations/booleans/{subtract.test.js → subtractGeom3.test.js} +3 -74
- package/src/operations/booleans/unionGeom2.test.js +166 -0
- package/src/operations/booleans/{union.test.js → unionGeom3.test.js} +3 -168
- package/src/operations/extrusions/extrudeFromSlices.js +3 -2
- package/src/operations/extrusions/slice/repair.js +62 -0
- package/src/operations/modifiers/reTesselateCoplanarPolygons.js +32 -31
- package/src/operations/extrusions/slice/repairSlice.js +0 -47
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
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.4](https://github.com/jscad/OpenJSCAD.org/compare/@jscad/modeling@2.9.3...@jscad/modeling@2.9.4) (2022-05-15)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Performance Improvements
|
|
10
|
+
|
|
11
|
+
* **maths:** reworked mat4 isMirror to use pure math, eliminating vec3 creation ([#1088](https://github.com/jscad/OpenJSCAD.org/issues/1088)) ([db4f220](https://github.com/jscad/OpenJSCAD.org/commit/db4f220d000fd08c7e3adab16401a8ea3b06478a))
|
|
12
|
+
* **modeling:** improved performance of reTesselateCoplanarPolygons by using maps or sets ([#1085](https://github.com/jscad/OpenJSCAD.org/issues/1085)) ([fc57103](https://github.com/jscad/OpenJSCAD.org/commit/fc57103f3183781dd3cf97600e9a33386da9a514))
|
|
13
|
+
* **modeling:** use Map instead of {} for slice.repair ([#1083](https://github.com/jscad/OpenJSCAD.org/issues/1083)) ([05d2984](https://github.com/jscad/OpenJSCAD.org/commit/05d29849404193f2e4ed6e945a058aec2b124e3d))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
6
19
|
## [2.9.3](https://github.com/jscad/OpenJSCAD.org/compare/@jscad/modeling@2.9.2...@jscad/modeling@2.9.3) (2022-04-24)
|
|
7
20
|
|
|
8
21
|
|
|
@@ -425,9 +425,9 @@ const invert=(t,e)=>{const n=e[0],r=e[1],l=e[2],o=e[3],s=e[4],u=e[5],c=e[6],i=e[
|
|
|
425
425
|
const isIdentity=t=>1===t[0]&&0===t[1]&&0===t[2]&&0===t[3]&&0===t[4]&&1===t[5]&&0===t[6]&&0===t[7]&&0===t[8]&&0===t[9]&&1===t[10]&&0===t[11]&&0===t[12]&&0===t[13]&&0===t[14]&&1===t[15];module.exports=isIdentity;
|
|
426
426
|
|
|
427
427
|
},{}],143:[function(require,module,exports){
|
|
428
|
-
const
|
|
428
|
+
const isMirroring=r=>{const i=r[4]*r[9]-r[8]*r[5],o=r[8]*r[1]-r[0]*r[9],n=r[0]*r[5]-r[4]*r[1];return i*r[2]+o*r[6]+n*r[10]<0};module.exports=isMirroring;
|
|
429
429
|
|
|
430
|
-
},{
|
|
430
|
+
},{}],144:[function(require,module,exports){
|
|
431
431
|
const isOnlyTransformScale=s=>isZero(s[1])&&isZero(s[2])&&isZero(s[3])&&isZero(s[4])&&isZero(s[6])&&isZero(s[7])&&isZero(s[8])&&isZero(s[9])&&isZero(s[11])&&1===s[15],isZero=s=>Math.abs(s)<Number.EPSILON;module.exports=isOnlyTransformScale;
|
|
432
432
|
|
|
433
433
|
},{}],145:[function(require,module,exports){
|
|
@@ -905,9 +905,9 @@ const geom2=require("../../../geometries/geom2"),plane=require("../../../maths/p
|
|
|
905
905
|
const pointInTriangle=(n,a,e,r,i,o,t,x)=>(i-t)*(a-x)-(n-t)*(o-x)>=0&&(n-t)*(r-x)-(e-t)*(a-x)>=0&&(e-t)*(o-x)-(i-t)*(r-x)>=0,area=(n,a,e)=>(a.y-n.y)*(e.x-a.x)-(a.x-n.x)*(e.y-a.y);module.exports={area:area,pointInTriangle:pointInTriangle};
|
|
906
906
|
|
|
907
907
|
},{}],303:[function(require,module,exports){
|
|
908
|
-
const mat4=require("../../maths/mat4"),geom2=require("../../geometries/geom2"),geom3=require("../../geometries/geom3"),poly3=require("../../geometries/poly3"),slice=require("./slice"),repairSlice=require("./slice/
|
|
908
|
+
const mat4=require("../../maths/mat4"),geom2=require("../../geometries/geom2"),geom3=require("../../geometries/geom3"),poly3=require("../../geometries/poly3"),slice=require("./slice"),repairSlice=require("./slice/repair"),extrudeWalls=require("./extrudeWalls"),defaultCallback=(e,r,l)=>{let t=null;return geom2.isA(l)&&(t=slice.fromSides(geom2.toSides(l))),poly3.isA(l)&&(t=slice.fromPoints(poly3.toPoints(l))),0===e||1===e?slice.transform(mat4.fromTranslation(mat4.create(),[0,0,e]),t):null},extrudeFromSlices=(e,r)=>{const l={numberOfSlices:2,capStart:!0,capEnd:!0,close:!1,repair:!0,callback:defaultCallback},{numberOfSlices:t,capStart:o,capEnd:c,close:s,repair:i,callback:a}=Object.assign({},l,e);if(t<2)throw new Error("numberOfSlices must be 2 or more");i&&(r=repairSlice(r));const n=t-1;let u=null,m=null,f=null,g=[];for(let e=0;e<t;e++){const l=a(e/n,e,r);if(l){if(!slice.isA(l))throw new Error("the callback function must return slice objects");if(0===slice.toEdges(l).length)throw new Error("the callback function must return slices with one or more edges");f&&(g=g.concat(extrudeWalls(f,l))),0===e&&(u=l),e===t-1&&(m=l),f=l}}if(c){const e=slice.toPolygons(m);g=g.concat(e)}if(o){const e=slice.toPolygons(u).map(poly3.invert);g=g.concat(e)}return o||c||s&&!slice.equals(m,u)&&(g=g.concat(extrudeWalls(m,u))),geom3.create(g)};module.exports=extrudeFromSlices;
|
|
909
909
|
|
|
910
|
-
},{"../../geometries/geom2":22,"../../geometries/geom3":37,"../../geometries/poly3":75,"../../maths/mat4":140,"./extrudeWalls":311,"./slice":320,"./slice/
|
|
910
|
+
},{"../../geometries/geom2":22,"../../geometries/geom3":37,"../../geometries/poly3":75,"../../maths/mat4":140,"./extrudeWalls":311,"./slice":320,"./slice/repair":322}],304:[function(require,module,exports){
|
|
911
911
|
const flatten=require("../../utils/flatten"),geom2=require("../../geometries/geom2"),path2=require("../../geometries/path2"),extrudeLinearGeom2=require("./extrudeLinearGeom2"),extrudeLinearPath2=require("./extrudeLinearPath2"),extrudeLinear=(e,...t)=>{const{height:r,twistAngle:i,twistSteps:n,repair:a}=Object.assign({},{height:1,twistAngle:0,twistSteps:1,repair:!0},e);if(0===(t=flatten(t)).length)throw new Error("wrong number of arguments");e={offset:[0,0,r],twistAngle:i,twistSteps:n,repair:a};const s=t.map(t=>path2.isA(t)?extrudeLinearPath2(e,t):geom2.isA(t)?extrudeLinearGeom2(e,t):t);return 1===s.length?s[0]:s};module.exports=extrudeLinear;
|
|
912
912
|
|
|
913
913
|
},{"../../geometries/geom2":22,"../../geometries/path2":58,"../../utils/flatten":391,"./extrudeLinearGeom2":305,"./extrudeLinearPath2":306}],305:[function(require,module,exports){
|
|
@@ -962,9 +962,9 @@ module.exports={calculatePlane:require("./calculatePlane"),clone:require("./clon
|
|
|
962
962
|
const isA=e=>!!(e&&"object"==typeof e&&"edges"in e&&Array.isArray(e.edges));module.exports=isA;
|
|
963
963
|
|
|
964
964
|
},{}],322:[function(require,module,exports){
|
|
965
|
-
const vec3=require("../../../maths/vec3"),
|
|
965
|
+
const vec3=require("../../../maths/vec3"),create=require("./create"),repair=e=>{if(!e.edges)return e;let t=e.edges;const r=new Map,a=new Map;(t=t.filter(e=>!vec3.equals(e[0],e[1]))).forEach(e=>{const t=e[0].toString(),o=e[1].toString();r.set(t,e[0]),r.set(o,e[1]),a.set(t,(a.get(t)||0)+1),a.set(o,(a.get(o)||0)-1)});const o=[],s=[];return a.forEach((e,t)=>{e<0&&o.push(t),e>0&&s.push(t)}),o.forEach(e=>{const a=r.get(e);let o,c=1/0;s.forEach(e=>{const t=r.get(e),s=Math.hypot(a[0]-t[0],a[1]-t[1]);s<c&&(c=s,o=t)}),console.warn(`slice.repair: repairing vertex gap ${a} to ${o} distance ${c}`),t=t.map(t=>t[0].toString()===e?[o,t[1]]:t[1].toString()===e?[t[0],o]:t)}),create(t)};module.exports=repair;
|
|
966
966
|
|
|
967
|
-
},{"../../../maths/vec3":217}],323:[function(require,module,exports){
|
|
967
|
+
},{"../../../maths/vec3":217,"./create":316}],323:[function(require,module,exports){
|
|
968
968
|
const create=require("./create"),reverse=(...e)=>{let r,t;return 1===e.length?(r=create(),t=e[0]):(r=e[0],t=e[1]),r.edges=t.edges.map(e=>[e[1],e[0]]),r};module.exports=reverse;
|
|
969
969
|
|
|
970
970
|
},{"./create":316}],324:[function(require,module,exports){
|
|
@@ -1040,7 +1040,7 @@ const constants=require("../../maths/constants"),vec3=require("../../maths/vec3"
|
|
|
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;
|
|
1041
1041
|
|
|
1042
1042
|
},{"../../geometries/poly3":75,"../../maths/utils/aboutEqualNormals":163,"../../maths/vec3":217}],348:[function(require,module,exports){
|
|
1043
|
-
const{EPS:EPS}=require("../../maths/constants"),line2=require("../../maths/line2"),vec2=require("../../maths/vec2"),OrthoNormalBasis=require("../../maths/OrthoNormalBasis"),interpolateBetween2DPointsForY=require("../../maths/utils/interpolateBetween2DPointsForY"),{insertSorted:insertSorted,fnNumberSort:fnNumberSort}=require("../../utils"),poly3=require("../../geometries/poly3"),reTesselateCoplanarPolygons=t=>{if(t.length<2)return t;const e=[],o=t.length,n=poly3.plane(t[0]),l=new OrthoNormalBasis(n),i=[],r=[],s=
|
|
1043
|
+
const{EPS:EPS}=require("../../maths/constants"),line2=require("../../maths/line2"),vec2=require("../../maths/vec2"),OrthoNormalBasis=require("../../maths/OrthoNormalBasis"),interpolateBetween2DPointsForY=require("../../maths/utils/interpolateBetween2DPointsForY"),{insertSorted:insertSorted,fnNumberSort:fnNumberSort}=require("../../utils"),poly3=require("../../geometries/poly3"),reTesselateCoplanarPolygons=t=>{if(t.length<2)return t;const e=[],o=t.length,n=poly3.plane(t[0]),l=new OrthoNormalBasis(n),i=[],r=[],s=new Map,f=new Map,p=new Map,h=10/EPS;for(let e=0;e<o;e++){const o=t[e];let n=[],g=o.vertices.length,c=-1;if(g>0){let t,i;for(let r=0;r<g;r++){let s=l.to2D(o.vertices[r]);const g=Math.floor(s[1]*h);let a;p.has(g)?a=p.get(g):p.has(g+1)?a=p.get(g+1):p.has(g-1)?a=p.get(g-1):(a=s[1],p.set(g,s[1])),s=vec2.fromValues(s[0],a),n.push(s);const u=s[1];(0===r||u<t)&&(t=u,c=r),(0===r||u>i)&&(i=u);let m=f.get(u);m||(m={},f.set(u,m)),m[e]=!0}if(t>=i)n=[],g=0,c=-1;else{let o=s.get(t);o||(o=[],s.set(t,o)),o.push(e)}}n.reverse(),c=g-c-1,i.push(n),r.push(c)}const g=[];f.forEach((t,e)=>g.push(e)),g.sort(fnNumberSort);let c=[],a=[];for(let t=0;t<g.length;t++){const o=[],p=g[t],h=f.get(p);for(let t=0;t<c.length;++t){const e=c[t],o=e.polygonindex;if(h[o]){const n=i[o],l=n.length;let r=e.leftvertexindex,s=e.rightvertexindex;for(;;){let t=r+1;if(t>=l&&(t=0),n[t][1]!==p)break;r=t}let f=s-1;if(f<0&&(f=l-1),n[f][1]===p&&(s=f),r!==e.leftvertexindex&&r===s)c.splice(t,1),--t;else{e.leftvertexindex=r,e.rightvertexindex=s,e.topleft=n[r],e.topright=n[s];let t=r+1;t>=l&&(t=0),e.bottomleft=n[t];let o=s-1;o<0&&(o=l-1),e.bottomright=n[o]}}}let u;if(t>=g.length-1)c=[],u=null;else{const e=.5*(p+(u=Number(g[t+1]))),o=s.get(p);for(const t in o){const n=o[t],l=i[n],s=l.length,f=r[n];let h=f;for(;;){let t=h+1;if(t>=s&&(t=0),l[t][1]!==p)break;if(t===f)break;h=t}let g=f;for(;;){let t=g-1;if(t<0&&(t=s-1),l[t][1]!==p)break;if(t===h)break;g=t}let a=h+1;a>=s&&(a=0);let u=g-1;u<0&&(u=s-1);const m={polygonindex:n,leftvertexindex:h,rightvertexindex:g,topleft:l[h],topright:l[g],bottomleft:l[a],bottomright:l[u]};insertSorted(c,m,(t,o)=>{const n=interpolateBetween2DPointsForY(t.topleft,t.bottomleft,e),l=interpolateBetween2DPointsForY(o.topleft,o.bottomleft,e);return n>l?1:n<l?-1:0})}}for(const t in c){const e=c[t];let n=interpolateBetween2DPointsForY(e.topleft,e.bottomleft,p);const l=vec2.fromValues(n,p);n=interpolateBetween2DPointsForY(e.topright,e.bottomright,p);const i=vec2.fromValues(n,p);n=interpolateBetween2DPointsForY(e.topleft,e.bottomleft,u);const r=vec2.fromValues(n,u);n=interpolateBetween2DPointsForY(e.topright,e.bottomright,u);const s=vec2.fromValues(n,u),f={topleft:l,topright:i,bottomleft:r,bottomright:s,leftline:line2.fromPoints(line2.create(),l,r),rightline:line2.fromPoints(line2.create(),s,i)};if(o.length>0){const t=o[o.length-1],e=vec2.distance(f.topleft,t.topright),n=vec2.distance(f.bottomleft,t.bottomright);e<EPS&&n<EPS&&(f.topleft=t.topleft,f.leftline=t.leftline,f.bottomleft=t.bottomleft,o.splice(o.length-1,1))}o.push(f)}if(t>0){const t=new Set,i=new Set;for(let e=0;e<o.length;e++){const n=o[e];for(let e=0;e<a.length;e++)if(!i.has(e)){const o=a[e];if(vec2.distance(o.bottomleft,n.topleft)<EPS&&vec2.distance(o.bottomright,n.topright)<EPS){i.add(e);const l=line2.direction(n.leftline),r=line2.direction(o.leftline),s=l[0]-r[0],f=line2.direction(n.rightline),p=line2.direction(o.rightline),h=f[0]-p[0],g=Math.abs(s)<EPS,c=Math.abs(h)<EPS,a=c||h>=0;(g||s>=0)&&a&&(n.outpolygon=o.outpolygon,n.leftlinecontinues=g,n.rightlinecontinues=c,t.add(e));break}}}for(let o=0;o<a.length;o++)if(!t.has(o)){const t=a[o];t.outpolygon.rightpoints.push(t.bottomright),vec2.distance(t.bottomright,t.bottomleft)>EPS&&t.outpolygon.leftpoints.push(t.bottomleft),t.outpolygon.leftpoints.reverse();const i=t.outpolygon.rightpoints.concat(t.outpolygon.leftpoints).map(t=>l.to3D(t)),r=poly3.fromPointsAndPlane(i,n);r.vertices.length&&e.push(r)}}for(let t=0;t<o.length;t++){const e=o[t];e.outpolygon?(e.leftlinecontinues||e.outpolygon.leftpoints.push(e.topleft),e.rightlinecontinues||e.outpolygon.rightpoints.push(e.topright)):(e.outpolygon={leftpoints:[],rightpoints:[]},e.outpolygon.leftpoints.push(e.topleft),vec2.distance(e.topleft,e.topright)>EPS&&e.outpolygon.rightpoints.push(e.topright))}a=o}return e};module.exports=reTesselateCoplanarPolygons;
|
|
1044
1044
|
|
|
1045
1045
|
},{"../../geometries/poly3":75,"../../maths/OrthoNormalBasis":89,"../../maths/constants":90,"../../maths/line2":101,"../../maths/utils/interpolateBetween2DPointsForY":166,"../../maths/vec2":186,"../../utils":393}],349:[function(require,module,exports){
|
|
1046
1046
|
const geom3=require("../../geometries/geom3"),poly3=require("../../geometries/poly3"),aboutEqualNormals=require("../../maths/utils/aboutEqualNormals"),reTesselateCoplanarPolygons=require("./reTesselateCoplanarPolygons"),coplanar=(e,o)=>Math.abs(e[3]-o[3])<1.5e-7&&aboutEqualNormals(e,o),retessellate=e=>{if(e.isRetesselated)return e;const o=geom3.toPolygons(e),s=[];o.forEach(e=>{const o=s.find(o=>coplanar(o[0],poly3.plane(e)));if(o){o[1].push(e)}else s.push([poly3.plane(e),[e]])});let a=[];s.forEach(e=>{const o=e[1],s=reTesselateCoplanarPolygons(o);a=a.concat(s)});const l=geom3.create(a);return l.isRetesselated=!0,l};module.exports=retessellate;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jscad/modeling",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.4",
|
|
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": "
|
|
64
|
+
"gitHead": "662965f40a1ce628aa97f30b814586e72a3acb36"
|
|
65
65
|
}
|
|
@@ -14,9 +14,8 @@ const applyTransforms = (geometry) => {
|
|
|
14
14
|
if (mat4.isIdentity(geometry.transforms)) return geometry
|
|
15
15
|
|
|
16
16
|
// apply transforms to each polygon
|
|
17
|
-
// const isMirror = mat4.isMirroring(geometry.transforms)
|
|
18
|
-
// TBD if (isMirror) newvertices.reverse()
|
|
19
17
|
geometry.polygons = geometry.polygons.map((polygon) => poly3.transform(geometry.transforms, polygon))
|
|
18
|
+
// reset transforms
|
|
20
19
|
geometry.transforms = mat4.create()
|
|
21
20
|
return geometry
|
|
22
21
|
}
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
const cross = require('../vec3/cross')
|
|
2
|
-
const dot = require('../vec3/dot')
|
|
3
|
-
const fromValues = require('../vec3/fromValues')
|
|
4
|
-
|
|
5
1
|
/**
|
|
6
2
|
* Determine whether the given matrix is a mirroring transformation.
|
|
7
3
|
*
|
|
@@ -10,15 +6,19 @@ const fromValues = require('../vec3/fromValues')
|
|
|
10
6
|
* @alias module:modeling/maths/mat4.isMirroring
|
|
11
7
|
*/
|
|
12
8
|
const isMirroring = (matrix) => {
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
9
|
+
// const xVector = [matrix[0], matrix[4], matrix[8]]
|
|
10
|
+
// const yVector = [matrix[1], matrix[5], matrix[9]]
|
|
11
|
+
// const zVector = [matrix[2], matrix[6], matrix[10]]
|
|
16
12
|
|
|
17
|
-
// for a true orthogonal, non-mirrored base,
|
|
13
|
+
// for a true orthogonal, non-mirrored base, xVector.cross(yVector) == zVector
|
|
18
14
|
// If they have an opposite direction then we are mirroring
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
15
|
+
// calcuate xVector.cross(yVector)
|
|
16
|
+
const x = matrix[4] * matrix[9] - matrix[8] * matrix[5]
|
|
17
|
+
const y = matrix[8] * matrix[1] - matrix[0] * matrix[9]
|
|
18
|
+
const z = matrix[0] * matrix[5] - matrix[4] * matrix[1]
|
|
19
|
+
// calcualte dot(cross, zVector)
|
|
20
|
+
const d = x * matrix[2] + y * matrix[6] + z * matrix[10]
|
|
21
|
+
return (d < 0)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
module.exports = isMirroring
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const test = require('ava')
|
|
2
|
+
|
|
3
|
+
const { comparePoints } = require('../../../test/helpers')
|
|
4
|
+
|
|
5
|
+
const { geom2 } = require('../../geometries')
|
|
6
|
+
|
|
7
|
+
const { circle, rectangle } = require('../../primitives')
|
|
8
|
+
|
|
9
|
+
const { intersect } = require('./index')
|
|
10
|
+
|
|
11
|
+
const { center } = require('../transforms/center')
|
|
12
|
+
|
|
13
|
+
test('intersect: intersect of one or more geom2 objects produces expected geometry', (t) => {
|
|
14
|
+
const geometry1 = circle({ radius: 2, segments: 8 })
|
|
15
|
+
|
|
16
|
+
// intersect of one object
|
|
17
|
+
const result1 = intersect(geometry1)
|
|
18
|
+
let obs = geom2.toPoints(result1)
|
|
19
|
+
let exp = [
|
|
20
|
+
[2, 0],
|
|
21
|
+
[1.4142000000000001, 1.4142000000000001],
|
|
22
|
+
[0, 2],
|
|
23
|
+
[-1.4142000000000001, 1.4142000000000001],
|
|
24
|
+
[-2, 0],
|
|
25
|
+
[-1.4142000000000001, -1.4142000000000001],
|
|
26
|
+
[0, -2],
|
|
27
|
+
[1.4142000000000001, -1.4142000000000001]
|
|
28
|
+
]
|
|
29
|
+
t.notThrows(() => geom2.validate(result1))
|
|
30
|
+
t.is(obs.length, 8)
|
|
31
|
+
t.true(comparePoints(obs, exp))
|
|
32
|
+
|
|
33
|
+
// intersect of two non-overlapping objects
|
|
34
|
+
const geometry2 = center({ relativeTo: [10, 10, 0] }, rectangle({ size: [4, 4] }))
|
|
35
|
+
|
|
36
|
+
const result2 = intersect(geometry1, geometry2)
|
|
37
|
+
obs = geom2.toPoints(result2)
|
|
38
|
+
t.notThrows(() => geom2.validate(result2))
|
|
39
|
+
t.is(obs.length, 0)
|
|
40
|
+
|
|
41
|
+
// intersect of two partially overlapping objects
|
|
42
|
+
const geometry3 = rectangle({ size: [18, 18] })
|
|
43
|
+
|
|
44
|
+
const result3 = intersect(geometry2, geometry3)
|
|
45
|
+
obs = geom2.toPoints(result3)
|
|
46
|
+
exp = [
|
|
47
|
+
[9, 9], [8, 9], [8, 8], [9, 8]
|
|
48
|
+
]
|
|
49
|
+
t.notThrows(() => geom2.validate(result3))
|
|
50
|
+
t.is(obs.length, 4)
|
|
51
|
+
t.true(comparePoints(obs, exp))
|
|
52
|
+
|
|
53
|
+
// intersect of two completely overlapping objects
|
|
54
|
+
const result4 = intersect(geometry1, geometry3)
|
|
55
|
+
obs = geom2.toPoints(result4)
|
|
56
|
+
exp = [
|
|
57
|
+
[2, 0],
|
|
58
|
+
[1.4142000000000001, 1.4142000000000001],
|
|
59
|
+
[0, 2],
|
|
60
|
+
[-1.4142000000000001, 1.4142000000000001],
|
|
61
|
+
[-2, 0],
|
|
62
|
+
[-1.4142000000000001, -1.4142000000000001],
|
|
63
|
+
[0, -2],
|
|
64
|
+
[1.4142000000000001, -1.4142000000000001]
|
|
65
|
+
]
|
|
66
|
+
t.notThrows(() => geom2.validate(result4))
|
|
67
|
+
t.is(obs.length, 8)
|
|
68
|
+
t.true(comparePoints(obs, exp))
|
|
69
|
+
})
|
|
@@ -1,83 +1,15 @@
|
|
|
1
1
|
const test = require('ava')
|
|
2
2
|
|
|
3
|
-
const { comparePolygonsAsPoints
|
|
3
|
+
const { comparePolygonsAsPoints } = require('../../../test/helpers')
|
|
4
4
|
|
|
5
|
-
const {
|
|
5
|
+
const { geom3 } = require('../../geometries')
|
|
6
6
|
|
|
7
|
-
const {
|
|
7
|
+
const { sphere, cuboid } = require('../../primitives')
|
|
8
8
|
|
|
9
9
|
const { intersect } = require('./index')
|
|
10
10
|
|
|
11
11
|
const { center } = require('../transforms/center')
|
|
12
12
|
|
|
13
|
-
// test('intersect: intersect of a path produces expected changes to points', (t) => {
|
|
14
|
-
// let geometry = path.fromPoints({}, [[0, 1, 0], [1, 0, 0]])
|
|
15
|
-
//
|
|
16
|
-
// geometry = intersect({normal: [1, 0, 0]}, geometry)
|
|
17
|
-
// let obs = path.toPoints(geometry)
|
|
18
|
-
// let exp = []
|
|
19
|
-
//
|
|
20
|
-
// t.deepEqual(obs, exp)
|
|
21
|
-
// })
|
|
22
|
-
|
|
23
|
-
test('intersect: intersect of one or more geom2 objects produces expected geometry', (t) => {
|
|
24
|
-
const geometry1 = circle({ radius: 2, segments: 8 })
|
|
25
|
-
|
|
26
|
-
// intersect of one object
|
|
27
|
-
const result1 = intersect(geometry1)
|
|
28
|
-
let obs = geom2.toPoints(result1)
|
|
29
|
-
let exp = [
|
|
30
|
-
[2, 0],
|
|
31
|
-
[1.4142000000000001, 1.4142000000000001],
|
|
32
|
-
[0, 2],
|
|
33
|
-
[-1.4142000000000001, 1.4142000000000001],
|
|
34
|
-
[-2, 0],
|
|
35
|
-
[-1.4142000000000001, -1.4142000000000001],
|
|
36
|
-
[0, -2],
|
|
37
|
-
[1.4142000000000001, -1.4142000000000001]
|
|
38
|
-
]
|
|
39
|
-
t.notThrows(() => geom2.validate(result1))
|
|
40
|
-
t.is(obs.length, 8)
|
|
41
|
-
t.true(comparePoints(obs, exp))
|
|
42
|
-
|
|
43
|
-
// intersect of two non-overlapping objects
|
|
44
|
-
const geometry2 = center({ relativeTo: [10, 10, 0] }, rectangle({ size: [4, 4] }))
|
|
45
|
-
|
|
46
|
-
const result2 = intersect(geometry1, geometry2)
|
|
47
|
-
obs = geom2.toPoints(result2)
|
|
48
|
-
t.notThrows(() => geom2.validate(result2))
|
|
49
|
-
t.is(obs.length, 0)
|
|
50
|
-
|
|
51
|
-
// intersect of two partially overlapping objects
|
|
52
|
-
const geometry3 = rectangle({ size: [18, 18] })
|
|
53
|
-
|
|
54
|
-
const result3 = intersect(geometry2, geometry3)
|
|
55
|
-
obs = geom2.toPoints(result3)
|
|
56
|
-
exp = [
|
|
57
|
-
[9, 9], [8, 9], [8, 8], [9, 8]
|
|
58
|
-
]
|
|
59
|
-
t.notThrows(() => geom2.validate(result3))
|
|
60
|
-
t.is(obs.length, 4)
|
|
61
|
-
t.true(comparePoints(obs, exp))
|
|
62
|
-
|
|
63
|
-
// intersect of two completely overlapping objects
|
|
64
|
-
const result4 = intersect(geometry1, geometry3)
|
|
65
|
-
obs = geom2.toPoints(result4)
|
|
66
|
-
exp = [
|
|
67
|
-
[2, 0],
|
|
68
|
-
[1.4142000000000001, 1.4142000000000001],
|
|
69
|
-
[0, 2],
|
|
70
|
-
[-1.4142000000000001, 1.4142000000000001],
|
|
71
|
-
[-2, 0],
|
|
72
|
-
[-1.4142000000000001, -1.4142000000000001],
|
|
73
|
-
[0, -2],
|
|
74
|
-
[1.4142000000000001, -1.4142000000000001]
|
|
75
|
-
]
|
|
76
|
-
t.notThrows(() => geom2.validate(result4))
|
|
77
|
-
t.is(obs.length, 8)
|
|
78
|
-
t.true(comparePoints(obs, exp))
|
|
79
|
-
})
|
|
80
|
-
|
|
81
13
|
test('intersect: intersect of one or more geom3 objects produces expected geometry', (t) => {
|
|
82
14
|
const geometry1 = sphere({ radius: 2, segments: 8 })
|
|
83
15
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const test = require('ava')
|
|
2
|
+
|
|
3
|
+
const { comparePoints } = require('../../../test/helpers')
|
|
4
|
+
|
|
5
|
+
const { geom2 } = require('../../geometries')
|
|
6
|
+
|
|
7
|
+
const { circle, rectangle } = require('../../primitives')
|
|
8
|
+
|
|
9
|
+
const { subtract } = require('./index')
|
|
10
|
+
|
|
11
|
+
const { center } = require('../transforms/center')
|
|
12
|
+
|
|
13
|
+
test('subtract: subtract of one or more geom2 objects produces expected geometry', (t) => {
|
|
14
|
+
const geometry1 = circle({ radius: 2, segments: 8 })
|
|
15
|
+
|
|
16
|
+
// subtract of one object
|
|
17
|
+
const result1 = subtract(geometry1)
|
|
18
|
+
let obs = geom2.toPoints(result1)
|
|
19
|
+
let exp = [
|
|
20
|
+
[2, 0],
|
|
21
|
+
[1.4142000000000001, 1.4142000000000001],
|
|
22
|
+
[0, 2],
|
|
23
|
+
[-1.4142000000000001, 1.4142000000000001],
|
|
24
|
+
[-2, 0],
|
|
25
|
+
[-1.4142000000000001, -1.4142000000000001],
|
|
26
|
+
[0, -2],
|
|
27
|
+
[1.4142000000000001, -1.4142000000000001]
|
|
28
|
+
]
|
|
29
|
+
t.notThrows(() => geom2.validate(result1))
|
|
30
|
+
t.is(obs.length, 8)
|
|
31
|
+
t.true(comparePoints(obs, exp))
|
|
32
|
+
|
|
33
|
+
// subtract of two non-overlapping objects
|
|
34
|
+
const geometry2 = center({ relativeTo: [10, 10, 0] }, rectangle({ size: [4, 4] }))
|
|
35
|
+
|
|
36
|
+
const result2 = subtract(geometry1, geometry2)
|
|
37
|
+
obs = geom2.toPoints(result2)
|
|
38
|
+
exp = [
|
|
39
|
+
[2, 0],
|
|
40
|
+
[1.4142000000000001, 1.4142000000000001],
|
|
41
|
+
[0, 2],
|
|
42
|
+
[-1.4142000000000001, 1.4142000000000001],
|
|
43
|
+
[-2, 0],
|
|
44
|
+
[-1.4142000000000001, -1.4142000000000001],
|
|
45
|
+
[0, -2],
|
|
46
|
+
[1.4142000000000001, -1.4142000000000001]
|
|
47
|
+
]
|
|
48
|
+
t.notThrows(() => geom2.validate(result2))
|
|
49
|
+
t.is(obs.length, 8)
|
|
50
|
+
t.true(comparePoints(obs, exp))
|
|
51
|
+
|
|
52
|
+
// subtract of two partially overlapping objects
|
|
53
|
+
const geometry3 = rectangle({ size: [18, 18] })
|
|
54
|
+
|
|
55
|
+
const result3 = subtract(geometry2, geometry3)
|
|
56
|
+
obs = geom2.toPoints(result3)
|
|
57
|
+
exp = [
|
|
58
|
+
[12, 12], [9, 9], [8, 9], [8, 12], [9, 8], [12, 8]
|
|
59
|
+
]
|
|
60
|
+
t.notThrows(() => geom2.validate(result3))
|
|
61
|
+
t.is(obs.length, 6)
|
|
62
|
+
t.true(comparePoints(obs, exp))
|
|
63
|
+
|
|
64
|
+
// subtract of two completely overlapping objects
|
|
65
|
+
const result4 = subtract(geometry1, geometry3)
|
|
66
|
+
obs = geom2.toPoints(result4)
|
|
67
|
+
exp = [
|
|
68
|
+
]
|
|
69
|
+
t.notThrows(() => geom2.validate(result4))
|
|
70
|
+
t.is(obs.length, 0)
|
|
71
|
+
t.deepEqual(obs, exp)
|
|
72
|
+
})
|
|
@@ -1,86 +1,15 @@
|
|
|
1
1
|
const test = require('ava')
|
|
2
2
|
|
|
3
|
-
const { comparePolygonsAsPoints
|
|
3
|
+
const { comparePolygonsAsPoints } = require('../../../test/helpers')
|
|
4
4
|
|
|
5
|
-
const {
|
|
5
|
+
const { geom3 } = require('../../geometries')
|
|
6
6
|
|
|
7
|
-
const {
|
|
7
|
+
const { sphere, cuboid } = require('../../primitives')
|
|
8
8
|
|
|
9
9
|
const { subtract } = require('./index')
|
|
10
10
|
|
|
11
11
|
const { center } = require('../transforms/center')
|
|
12
12
|
|
|
13
|
-
// test('subtract: subtract of a path produces expected changes to points', (t) => {
|
|
14
|
-
// let geometry = path.fromPoints({}, [[0, 1, 0], [1, 0, 0]])
|
|
15
|
-
//
|
|
16
|
-
// geometry = subtract({normal: [1, 0, 0]}, geometry)
|
|
17
|
-
// let obs = path.toPoints(geometry)
|
|
18
|
-
// let exp = []
|
|
19
|
-
//
|
|
20
|
-
// t.deepEqual(obs, exp)
|
|
21
|
-
// })
|
|
22
|
-
|
|
23
|
-
test('subtract: subtract of one or more geom2 objects produces expected geometry', (t) => {
|
|
24
|
-
const geometry1 = circle({ radius: 2, segments: 8 })
|
|
25
|
-
|
|
26
|
-
// subtract of one object
|
|
27
|
-
const result1 = subtract(geometry1)
|
|
28
|
-
let obs = geom2.toPoints(result1)
|
|
29
|
-
let exp = [
|
|
30
|
-
[2, 0],
|
|
31
|
-
[1.4142000000000001, 1.4142000000000001],
|
|
32
|
-
[0, 2],
|
|
33
|
-
[-1.4142000000000001, 1.4142000000000001],
|
|
34
|
-
[-2, 0],
|
|
35
|
-
[-1.4142000000000001, -1.4142000000000001],
|
|
36
|
-
[0, -2],
|
|
37
|
-
[1.4142000000000001, -1.4142000000000001]
|
|
38
|
-
]
|
|
39
|
-
t.notThrows(() => geom2.validate(result1))
|
|
40
|
-
t.is(obs.length, 8)
|
|
41
|
-
t.true(comparePoints(obs, exp))
|
|
42
|
-
|
|
43
|
-
// subtract of two non-overlapping objects
|
|
44
|
-
const geometry2 = center({ relativeTo: [10, 10, 0] }, rectangle({ size: [4, 4] }))
|
|
45
|
-
|
|
46
|
-
const result2 = subtract(geometry1, geometry2)
|
|
47
|
-
obs = geom2.toPoints(result2)
|
|
48
|
-
exp = [
|
|
49
|
-
[2, 0],
|
|
50
|
-
[1.4142000000000001, 1.4142000000000001],
|
|
51
|
-
[0, 2],
|
|
52
|
-
[-1.4142000000000001, 1.4142000000000001],
|
|
53
|
-
[-2, 0],
|
|
54
|
-
[-1.4142000000000001, -1.4142000000000001],
|
|
55
|
-
[0, -2],
|
|
56
|
-
[1.4142000000000001, -1.4142000000000001]
|
|
57
|
-
]
|
|
58
|
-
t.notThrows(() => geom2.validate(result2))
|
|
59
|
-
t.is(obs.length, 8)
|
|
60
|
-
t.true(comparePoints(obs, exp))
|
|
61
|
-
|
|
62
|
-
// subtract of two partially overlapping objects
|
|
63
|
-
const geometry3 = rectangle({ size: [18, 18] })
|
|
64
|
-
|
|
65
|
-
const result3 = subtract(geometry2, geometry3)
|
|
66
|
-
obs = geom2.toPoints(result3)
|
|
67
|
-
exp = [
|
|
68
|
-
[12, 12], [9, 9], [8, 9], [8, 12], [9, 8], [12, 8]
|
|
69
|
-
]
|
|
70
|
-
t.notThrows(() => geom2.validate(result3))
|
|
71
|
-
t.is(obs.length, 6)
|
|
72
|
-
t.true(comparePoints(obs, exp))
|
|
73
|
-
|
|
74
|
-
// subtract of two completely overlapping objects
|
|
75
|
-
const result4 = subtract(geometry1, geometry3)
|
|
76
|
-
obs = geom2.toPoints(result4)
|
|
77
|
-
exp = [
|
|
78
|
-
]
|
|
79
|
-
t.notThrows(() => geom2.validate(result4))
|
|
80
|
-
t.is(obs.length, 0)
|
|
81
|
-
t.deepEqual(obs, exp)
|
|
82
|
-
})
|
|
83
|
-
|
|
84
13
|
test('subtract: subtract of one or more geom3 objects produces expected geometry', (t) => {
|
|
85
14
|
const geometry1 = sphere({ radius: 2, segments: 8 })
|
|
86
15
|
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
const test = require('ava')
|
|
2
|
+
|
|
3
|
+
const { comparePoints } = require('../../../test/helpers')
|
|
4
|
+
|
|
5
|
+
const { geom2 } = require('../../geometries')
|
|
6
|
+
|
|
7
|
+
const { circle, rectangle } = require('../../primitives')
|
|
8
|
+
|
|
9
|
+
const { union } = require('./index')
|
|
10
|
+
|
|
11
|
+
const { center } = require('../transforms/center')
|
|
12
|
+
const { translate } = require('../transforms/translate')
|
|
13
|
+
|
|
14
|
+
test('union of one or more geom2 objects produces expected geometry', (t) => {
|
|
15
|
+
const geometry1 = circle({ radius: 2, segments: 8 })
|
|
16
|
+
|
|
17
|
+
// union of one object
|
|
18
|
+
const result1 = union(geometry1)
|
|
19
|
+
let obs = geom2.toPoints(result1)
|
|
20
|
+
let exp = [
|
|
21
|
+
[2, 0],
|
|
22
|
+
[1.4142000000000001, 1.4142000000000001],
|
|
23
|
+
[0, 2],
|
|
24
|
+
[-1.4142000000000001, 1.4142000000000001],
|
|
25
|
+
[-2, 0],
|
|
26
|
+
[-1.4142000000000001, -1.4142000000000001],
|
|
27
|
+
[0, -2],
|
|
28
|
+
[1.4142000000000001, -1.4142000000000001]
|
|
29
|
+
]
|
|
30
|
+
t.notThrows(() => geom2.validate(result1))
|
|
31
|
+
t.true(comparePoints(obs, exp))
|
|
32
|
+
|
|
33
|
+
// union of two non-overlapping objects
|
|
34
|
+
const geometry2 = center({ relativeTo: [10, 10, 0] }, rectangle({ size: [4, 4] }))
|
|
35
|
+
|
|
36
|
+
const result2 = union(geometry1, geometry2)
|
|
37
|
+
obs = geom2.toPoints(result2)
|
|
38
|
+
exp = [
|
|
39
|
+
[2, 0],
|
|
40
|
+
[1.4142000000000001, 1.4142000000000001],
|
|
41
|
+
[0, 2],
|
|
42
|
+
[-1.4142000000000001, 1.4142000000000001],
|
|
43
|
+
[-2, 0],
|
|
44
|
+
[-1.4142000000000001, -1.4142000000000001],
|
|
45
|
+
[0, -2],
|
|
46
|
+
[8, 12],
|
|
47
|
+
[8, 8],
|
|
48
|
+
[12, 8],
|
|
49
|
+
[12, 12],
|
|
50
|
+
[1.4142000000000001, -1.4142000000000001]
|
|
51
|
+
]
|
|
52
|
+
t.notThrows(() => geom2.validate(result2))
|
|
53
|
+
t.true(comparePoints(obs, exp))
|
|
54
|
+
|
|
55
|
+
// union of two partially overlapping objects
|
|
56
|
+
const geometry3 = rectangle({ size: [18, 18] })
|
|
57
|
+
|
|
58
|
+
const result3 = union(geometry2, geometry3)
|
|
59
|
+
obs = geom2.toPoints(result3)
|
|
60
|
+
exp = [
|
|
61
|
+
[11.999973333333333, 11.999973333333333],
|
|
62
|
+
[7.999933333333333, 11.999973333333333],
|
|
63
|
+
[9.000053333333334, 7.999933333333333],
|
|
64
|
+
[-9.000053333333334, 9.000053333333334],
|
|
65
|
+
[-9.000053333333334, -9.000053333333334],
|
|
66
|
+
[9.000053333333334, -9.000053333333334],
|
|
67
|
+
[7.999933333333333, 9.000053333333334],
|
|
68
|
+
[11.999973333333333, 7.999933333333333]
|
|
69
|
+
]
|
|
70
|
+
t.notThrows(() => geom2.validate(result3))
|
|
71
|
+
t.true(comparePoints(obs, exp))
|
|
72
|
+
|
|
73
|
+
// union of two completely overlapping objects
|
|
74
|
+
const result4 = union(geometry1, geometry3)
|
|
75
|
+
obs = geom2.toPoints(result4)
|
|
76
|
+
exp = [
|
|
77
|
+
[-9.000046666666666, -9.000046666666666],
|
|
78
|
+
[9.000046666666666, -9.000046666666666],
|
|
79
|
+
[9.000046666666666, 9.000046666666666],
|
|
80
|
+
[-9.000046666666666, 9.000046666666666]
|
|
81
|
+
]
|
|
82
|
+
t.notThrows(() => geom2.validate(result4))
|
|
83
|
+
t.true(comparePoints(obs, exp))
|
|
84
|
+
|
|
85
|
+
// union of unions of non-overlapping objects (BSP gap from #907)
|
|
86
|
+
const circ = circle({ radius: 1, segments: 32 })
|
|
87
|
+
const result5 = union(
|
|
88
|
+
union(
|
|
89
|
+
translate([17, 21], circ),
|
|
90
|
+
translate([7, 0], circ)
|
|
91
|
+
),
|
|
92
|
+
union(
|
|
93
|
+
translate([3, 21], circ),
|
|
94
|
+
translate([17, 21], circ)
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
obs = geom2.toPoints(result5)
|
|
98
|
+
t.notThrows.skip(() => geom2.validate(result5))
|
|
99
|
+
t.is(obs.length, 112)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('union of geom2 with closing issues #15', (t) => {
|
|
103
|
+
const c = geom2.create([
|
|
104
|
+
[[-45.82118740347841168159, -16.85726810555620147625], [-49.30331715865012398581, -14.68093629710870118288]],
|
|
105
|
+
[[-49.10586702080816223770, -15.27604177352110781385], [-48.16645938811709015681, -15.86317173589183227023]],
|
|
106
|
+
[[-49.60419521731581937729, -14.89550781504266296906], [-49.42407001323204696064, -15.67605088949303393520]],
|
|
107
|
+
[[-49.05727291218684626983, -15.48661638542171203881], [-49.10586702080816223770, -15.27604177352110781385]],
|
|
108
|
+
[[-49.30706235399220815907, -15.81529674600091794900], [-46.00505780290426827150, -17.21108547999804727624]],
|
|
109
|
+
[[-46.00505780290426827150, -17.21108547999804727624], [-45.85939703723252591772, -17.21502856394236857795]],
|
|
110
|
+
[[-45.85939703723252591772, -17.21502856394236857795], [-45.74972032664388166268, -17.11909303495791334626]],
|
|
111
|
+
[[-45.74972032664388166268, -17.11909303495791334626], [-45.73424573227583067592, -16.97420292661295349035]],
|
|
112
|
+
[[-45.73424573227583067592, -16.97420292661295349035], [-45.82118740347841168159, -16.85726810555620147625]],
|
|
113
|
+
[[-49.30331715865012398581, -14.68093629710870118288], [-49.45428884427643367871, -14.65565769658912387285]],
|
|
114
|
+
[[-49.45428884427643367871, -14.65565769658912387285], [-49.57891661679624917269, -14.74453612941635327616]],
|
|
115
|
+
[[-49.57891661679624917269, -14.74453612941635327616], [-49.60419521731581937729, -14.89550781504266296906]],
|
|
116
|
+
[[-49.42407001323204696064, -15.67605088949303393520], [-49.30706235399220815907, -15.81529674600091794900]],
|
|
117
|
+
[[-48.16645938811709015681, -15.86317173589183227023], [-49.05727291218684626983, -15.48661638542171203881]]
|
|
118
|
+
])
|
|
119
|
+
const d = geom2.create([
|
|
120
|
+
[[-49.03431352173912216585, -15.58610714407888764299], [-49.21443872582289458251, -14.80556406962851667686]],
|
|
121
|
+
[[-68.31614651314507113966, -3.10790373951434872879], [-49.34036769611472550423, -15.79733157434056778357]],
|
|
122
|
+
[[-49.58572929483430868913, -14.97552686612213790340], [-49.53755741140093959984, -15.18427183431472826669]],
|
|
123
|
+
[[-49.53755741140093959984, -15.18427183431472826669], [-54.61235529924312714911, -11.79066769321313756791]],
|
|
124
|
+
[[-49.30227466841120076424, -14.68159232649114187552], [-68.09792828135776687759, -2.77270756611528668145]],
|
|
125
|
+
[[-49.21443872582289458251, -14.80556406962851667686], [-49.30227466841120076424, -14.68159232649114187552]],
|
|
126
|
+
[[-49.34036769611472550423, -15.79733157434056778357], [-49.18823337756091262918, -15.82684012194931710837]],
|
|
127
|
+
[[-49.18823337756091262918, -15.82684012194931710837], [-49.06069007212390431505, -15.73881563386780157998]],
|
|
128
|
+
[[-49.06069007212390431505, -15.73881563386780157998], [-49.03431352173912216585, -15.58610714407888764299]],
|
|
129
|
+
[[-68.09792828135776687759, -2.77270756611528668145], [-68.24753735887460948106, -2.74623350179570024920]],
|
|
130
|
+
[[-68.24753735887460948106, -2.74623350179570024920], [-68.37258141465594007968, -2.83253376987636329432]],
|
|
131
|
+
[[-68.37258141465594007968, -2.83253376987636329432], [-68.40089829889257089235, -2.98180502037078554167]],
|
|
132
|
+
[[-68.40089829889257089235, -2.98180502037078554167], [-68.31614651314507113966, -3.10790373951434872879]],
|
|
133
|
+
[[-54.61235529924312714911, -11.79066769321313756791], [-49.58572929483430868913, -14.97552686612213790340]]
|
|
134
|
+
])
|
|
135
|
+
// geom2.toOutlines(c)
|
|
136
|
+
// geom2.toOutlines(d)
|
|
137
|
+
|
|
138
|
+
const obs = union(c, d)
|
|
139
|
+
// const outlines = geom2.toOutlines(obs)
|
|
140
|
+
const pts = geom2.toPoints(obs)
|
|
141
|
+
const exp = [
|
|
142
|
+
[-49.10585516965137, -15.276000175919414],
|
|
143
|
+
[-49.0573272145917, -15.486679335654257],
|
|
144
|
+
[-49.307011370463215, -15.815286644243773],
|
|
145
|
+
[-46.00502320253235, -17.211117609669667],
|
|
146
|
+
[-45.85943933735334, -17.215031154432545],
|
|
147
|
+
[-45.74972963250071, -17.119149307742074],
|
|
148
|
+
[-45.734205904941305, -16.974217700023555],
|
|
149
|
+
[-48.166473975068946, -15.86316234184296],
|
|
150
|
+
[-49.318621553259746, -15.801589237573706],
|
|
151
|
+
[-49.585786209072104, -14.975570389622606],
|
|
152
|
+
[-68.31614189569036, -3.1078763476921982],
|
|
153
|
+
[-49.53751915699663, -15.184292776976012],
|
|
154
|
+
[-68.09789654941396, -2.7727464644978874],
|
|
155
|
+
[-68.24752441084793, -2.7462648116024244],
|
|
156
|
+
[-68.37262739176788, -2.8324932478777995],
|
|
157
|
+
[-68.40093536555268, -2.98186020632758],
|
|
158
|
+
[-54.61234310251047, -11.79072766159384],
|
|
159
|
+
[-49.30335872868453, -14.680880468978017],
|
|
160
|
+
[-49.34040695243976, -15.797284338334542],
|
|
161
|
+
[-45.82121705016925, -16.857333163105647]
|
|
162
|
+
]
|
|
163
|
+
t.notThrows(() => geom2.validate(obs))
|
|
164
|
+
t.is(pts.length, 20) // number of sides in union
|
|
165
|
+
t.true(comparePoints(pts, exp))
|
|
166
|
+
})
|
|
@@ -1,113 +1,14 @@
|
|
|
1
1
|
const test = require('ava')
|
|
2
2
|
|
|
3
|
-
const { comparePolygonsAsPoints
|
|
3
|
+
const { comparePolygonsAsPoints } = require('../../../test/helpers')
|
|
4
4
|
|
|
5
|
-
const {
|
|
5
|
+
const { geom3 } = require('../../geometries')
|
|
6
6
|
|
|
7
|
-
const {
|
|
7
|
+
const { sphere, cuboid } = require('../../primitives')
|
|
8
8
|
|
|
9
9
|
const { union } = require('./index')
|
|
10
10
|
|
|
11
11
|
const { center } = require('../transforms/center')
|
|
12
|
-
const { translate } = require('../transforms/translate')
|
|
13
|
-
|
|
14
|
-
// test('union: union of a path produces expected changes to points', (t) => {
|
|
15
|
-
// let geometry = path.fromPoints({}, [[0, 1, 0], [1, 0, 0]])
|
|
16
|
-
//
|
|
17
|
-
// geometry = union({normal: [1, 0, 0]}, geometry)
|
|
18
|
-
// let obs = path.toPoints(geometry)
|
|
19
|
-
// let exp = []
|
|
20
|
-
//
|
|
21
|
-
// t.deepEqual(obs, exp)
|
|
22
|
-
// })
|
|
23
|
-
|
|
24
|
-
test('union of one or more geom2 objects produces expected geometry', (t) => {
|
|
25
|
-
const geometry1 = circle({ radius: 2, segments: 8 })
|
|
26
|
-
|
|
27
|
-
// union of one object
|
|
28
|
-
const result1 = union(geometry1)
|
|
29
|
-
let obs = geom2.toPoints(result1)
|
|
30
|
-
let exp = [
|
|
31
|
-
[2, 0],
|
|
32
|
-
[1.4142000000000001, 1.4142000000000001],
|
|
33
|
-
[0, 2],
|
|
34
|
-
[-1.4142000000000001, 1.4142000000000001],
|
|
35
|
-
[-2, 0],
|
|
36
|
-
[-1.4142000000000001, -1.4142000000000001],
|
|
37
|
-
[0, -2],
|
|
38
|
-
[1.4142000000000001, -1.4142000000000001]
|
|
39
|
-
]
|
|
40
|
-
t.notThrows(() => geom2.validate(result1))
|
|
41
|
-
t.true(comparePoints(obs, exp))
|
|
42
|
-
|
|
43
|
-
// union of two non-overlapping objects
|
|
44
|
-
const geometry2 = center({ relativeTo: [10, 10, 0] }, rectangle({ size: [4, 4] }))
|
|
45
|
-
|
|
46
|
-
const result2 = union(geometry1, geometry2)
|
|
47
|
-
obs = geom2.toPoints(result2)
|
|
48
|
-
exp = [
|
|
49
|
-
[2, 0],
|
|
50
|
-
[1.4142000000000001, 1.4142000000000001],
|
|
51
|
-
[0, 2],
|
|
52
|
-
[-1.4142000000000001, 1.4142000000000001],
|
|
53
|
-
[-2, 0],
|
|
54
|
-
[-1.4142000000000001, -1.4142000000000001],
|
|
55
|
-
[0, -2],
|
|
56
|
-
[8, 12],
|
|
57
|
-
[8, 8],
|
|
58
|
-
[12, 8],
|
|
59
|
-
[12, 12],
|
|
60
|
-
[1.4142000000000001, -1.4142000000000001]
|
|
61
|
-
]
|
|
62
|
-
t.notThrows(() => geom2.validate(result2))
|
|
63
|
-
t.true(comparePoints(obs, exp))
|
|
64
|
-
|
|
65
|
-
// union of two partially overlapping objects
|
|
66
|
-
const geometry3 = rectangle({ size: [18, 18] })
|
|
67
|
-
|
|
68
|
-
const result3 = union(geometry2, geometry3)
|
|
69
|
-
obs = geom2.toPoints(result3)
|
|
70
|
-
exp = [
|
|
71
|
-
[11.999973333333333, 11.999973333333333],
|
|
72
|
-
[7.999933333333333, 11.999973333333333],
|
|
73
|
-
[9.000053333333334, 7.999933333333333],
|
|
74
|
-
[-9.000053333333334, 9.000053333333334],
|
|
75
|
-
[-9.000053333333334, -9.000053333333334],
|
|
76
|
-
[9.000053333333334, -9.000053333333334],
|
|
77
|
-
[7.999933333333333, 9.000053333333334],
|
|
78
|
-
[11.999973333333333, 7.999933333333333]
|
|
79
|
-
]
|
|
80
|
-
t.notThrows(() => geom2.validate(result3))
|
|
81
|
-
t.true(comparePoints(obs, exp))
|
|
82
|
-
|
|
83
|
-
// union of two completely overlapping objects
|
|
84
|
-
const result4 = union(geometry1, geometry3)
|
|
85
|
-
obs = geom2.toPoints(result4)
|
|
86
|
-
exp = [
|
|
87
|
-
[-9.000046666666666, -9.000046666666666],
|
|
88
|
-
[9.000046666666666, -9.000046666666666],
|
|
89
|
-
[9.000046666666666, 9.000046666666666],
|
|
90
|
-
[-9.000046666666666, 9.000046666666666]
|
|
91
|
-
]
|
|
92
|
-
t.notThrows(() => geom2.validate(result4))
|
|
93
|
-
t.true(comparePoints(obs, exp))
|
|
94
|
-
|
|
95
|
-
// union of unions of non-overlapping objects (BSP gap from #907)
|
|
96
|
-
const circ = circle({ radius: 1, segments: 32 })
|
|
97
|
-
const result5 = union(
|
|
98
|
-
union(
|
|
99
|
-
translate([17, 21], circ),
|
|
100
|
-
translate([7, 0], circ),
|
|
101
|
-
),
|
|
102
|
-
union(
|
|
103
|
-
translate([3, 21], circ),
|
|
104
|
-
translate([17, 21], circ),
|
|
105
|
-
)
|
|
106
|
-
)
|
|
107
|
-
obs = geom2.toPoints(result5)
|
|
108
|
-
t.notThrows.skip(() => geom2.validate(result5))
|
|
109
|
-
t.is(obs.length, 112)
|
|
110
|
-
})
|
|
111
12
|
|
|
112
13
|
test('union of one or more geom3 objects produces expected geometry', (t) => {
|
|
113
14
|
const geometry1 = sphere({ radius: 2, segments: 8 })
|
|
@@ -230,69 +131,3 @@ test('union of geom3 with rounding issues #137', (t) => {
|
|
|
230
131
|
t.notThrows(() => geom3.validate(obs))
|
|
231
132
|
t.is(pts.length, 6) // number of polygons in union
|
|
232
133
|
})
|
|
233
|
-
|
|
234
|
-
test('union of geom2 with closing issues #15', (t) => {
|
|
235
|
-
const c = geom2.create([
|
|
236
|
-
[[-45.82118740347841168159, -16.85726810555620147625], [-49.30331715865012398581, -14.68093629710870118288]],
|
|
237
|
-
[[-49.10586702080816223770, -15.27604177352110781385], [-48.16645938811709015681, -15.86317173589183227023]],
|
|
238
|
-
[[-49.60419521731581937729, -14.89550781504266296906], [-49.42407001323204696064, -15.67605088949303393520]],
|
|
239
|
-
[[-49.05727291218684626983, -15.48661638542171203881], [-49.10586702080816223770, -15.27604177352110781385]],
|
|
240
|
-
[[-49.30706235399220815907, -15.81529674600091794900], [-46.00505780290426827150, -17.21108547999804727624]],
|
|
241
|
-
[[-46.00505780290426827150, -17.21108547999804727624], [-45.85939703723252591772, -17.21502856394236857795]],
|
|
242
|
-
[[-45.85939703723252591772, -17.21502856394236857795], [-45.74972032664388166268, -17.11909303495791334626]],
|
|
243
|
-
[[-45.74972032664388166268, -17.11909303495791334626], [-45.73424573227583067592, -16.97420292661295349035]],
|
|
244
|
-
[[-45.73424573227583067592, -16.97420292661295349035], [-45.82118740347841168159, -16.85726810555620147625]],
|
|
245
|
-
[[-49.30331715865012398581, -14.68093629710870118288], [-49.45428884427643367871, -14.65565769658912387285]],
|
|
246
|
-
[[-49.45428884427643367871, -14.65565769658912387285], [-49.57891661679624917269, -14.74453612941635327616]],
|
|
247
|
-
[[-49.57891661679624917269, -14.74453612941635327616], [-49.60419521731581937729, -14.89550781504266296906]],
|
|
248
|
-
[[-49.42407001323204696064, -15.67605088949303393520], [-49.30706235399220815907, -15.81529674600091794900]],
|
|
249
|
-
[[-48.16645938811709015681, -15.86317173589183227023], [-49.05727291218684626983, -15.48661638542171203881]]
|
|
250
|
-
])
|
|
251
|
-
const d = geom2.create([
|
|
252
|
-
[[-49.03431352173912216585, -15.58610714407888764299], [-49.21443872582289458251, -14.80556406962851667686]],
|
|
253
|
-
[[-68.31614651314507113966, -3.10790373951434872879], [-49.34036769611472550423, -15.79733157434056778357]],
|
|
254
|
-
[[-49.58572929483430868913, -14.97552686612213790340], [-49.53755741140093959984, -15.18427183431472826669]],
|
|
255
|
-
[[-49.53755741140093959984, -15.18427183431472826669], [-54.61235529924312714911, -11.79066769321313756791]],
|
|
256
|
-
[[-49.30227466841120076424, -14.68159232649114187552], [-68.09792828135776687759, -2.77270756611528668145]],
|
|
257
|
-
[[-49.21443872582289458251, -14.80556406962851667686], [-49.30227466841120076424, -14.68159232649114187552]],
|
|
258
|
-
[[-49.34036769611472550423, -15.79733157434056778357], [-49.18823337756091262918, -15.82684012194931710837]],
|
|
259
|
-
[[-49.18823337756091262918, -15.82684012194931710837], [-49.06069007212390431505, -15.73881563386780157998]],
|
|
260
|
-
[[-49.06069007212390431505, -15.73881563386780157998], [-49.03431352173912216585, -15.58610714407888764299]],
|
|
261
|
-
[[-68.09792828135776687759, -2.77270756611528668145], [-68.24753735887460948106, -2.74623350179570024920]],
|
|
262
|
-
[[-68.24753735887460948106, -2.74623350179570024920], [-68.37258141465594007968, -2.83253376987636329432]],
|
|
263
|
-
[[-68.37258141465594007968, -2.83253376987636329432], [-68.40089829889257089235, -2.98180502037078554167]],
|
|
264
|
-
[[-68.40089829889257089235, -2.98180502037078554167], [-68.31614651314507113966, -3.10790373951434872879]],
|
|
265
|
-
[[-54.61235529924312714911, -11.79066769321313756791], [-49.58572929483430868913, -14.97552686612213790340]]
|
|
266
|
-
])
|
|
267
|
-
// geom2.toOutlines(c)
|
|
268
|
-
// geom2.toOutlines(d)
|
|
269
|
-
|
|
270
|
-
const obs = union(c, d)
|
|
271
|
-
// const outlines = geom2.toOutlines(obs)
|
|
272
|
-
const pts = geom2.toPoints(obs)
|
|
273
|
-
const exp = [
|
|
274
|
-
[-49.10585516965137, -15.276000175919414],
|
|
275
|
-
[-49.0573272145917, -15.486679335654257],
|
|
276
|
-
[-49.307011370463215, -15.815286644243773],
|
|
277
|
-
[-46.00502320253235, -17.211117609669667],
|
|
278
|
-
[-45.85943933735334, -17.215031154432545],
|
|
279
|
-
[-45.74972963250071, -17.119149307742074],
|
|
280
|
-
[-45.734205904941305, -16.974217700023555],
|
|
281
|
-
[-48.166473975068946, -15.86316234184296],
|
|
282
|
-
[-49.318621553259746, -15.801589237573706],
|
|
283
|
-
[-49.585786209072104, -14.975570389622606],
|
|
284
|
-
[-68.31614189569036, -3.1078763476921982],
|
|
285
|
-
[-49.53751915699663, -15.184292776976012],
|
|
286
|
-
[-68.09789654941396, -2.7727464644978874],
|
|
287
|
-
[-68.24752441084793, -2.7462648116024244],
|
|
288
|
-
[-68.37262739176788, -2.8324932478777995],
|
|
289
|
-
[-68.40093536555268, -2.98186020632758],
|
|
290
|
-
[-54.61234310251047, -11.79072766159384],
|
|
291
|
-
[-49.30335872868453, -14.680880468978017],
|
|
292
|
-
[-49.34040695243976, -15.797284338334542],
|
|
293
|
-
[-45.82121705016925, -16.857333163105647]
|
|
294
|
-
]
|
|
295
|
-
t.notThrows(() => geom2.validate(obs))
|
|
296
|
-
t.is(pts.length, 20) // number of sides in union
|
|
297
|
-
t.true(comparePoints(pts, exp))
|
|
298
|
-
})
|
|
@@ -5,7 +5,7 @@ const geom3 = require('../../geometries/geom3')
|
|
|
5
5
|
const poly3 = require('../../geometries/poly3')
|
|
6
6
|
|
|
7
7
|
const slice = require('./slice')
|
|
8
|
-
const repairSlice = require('./slice/
|
|
8
|
+
const repairSlice = require('./slice/repair')
|
|
9
9
|
|
|
10
10
|
const extrudeWalls = require('./extrudeWalls')
|
|
11
11
|
|
|
@@ -59,7 +59,8 @@ const extrudeFromSlices = (options, base) => {
|
|
|
59
59
|
|
|
60
60
|
// Repair gaps in the base slice
|
|
61
61
|
if (repair) {
|
|
62
|
-
|
|
62
|
+
// note: base must be a slice, if base is geom2 this doesn't repair
|
|
63
|
+
base = repairSlice(base)
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
const sMax = numberOfSlices - 1
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const vec3 = require('../../../maths/vec3')
|
|
2
|
+
const create = require('./create')
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Mend gaps in a 2D slice to make it a closed polygon
|
|
6
|
+
*/
|
|
7
|
+
const repair = (slice) => {
|
|
8
|
+
if (!slice.edges) return slice
|
|
9
|
+
let edges = slice.edges
|
|
10
|
+
const vertexMap = new Map() // string key to vertex map
|
|
11
|
+
const edgeCount = new Map() // count of (in - out) edges
|
|
12
|
+
|
|
13
|
+
// Remove self-edges
|
|
14
|
+
edges = edges.filter((e) => !vec3.equals(e[0], e[1]))
|
|
15
|
+
|
|
16
|
+
// build vertex and edge count maps
|
|
17
|
+
edges.forEach((edge) => {
|
|
18
|
+
const inKey = edge[0].toString()
|
|
19
|
+
const outKey = edge[1].toString()
|
|
20
|
+
vertexMap.set(inKey, edge[0])
|
|
21
|
+
vertexMap.set(outKey, edge[1])
|
|
22
|
+
edgeCount.set(inKey, (edgeCount.get(inKey) || 0) + 1) // in
|
|
23
|
+
edgeCount.set(outKey, (edgeCount.get(outKey) || 0) - 1) // out
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// find vertices which are missing in or out edges
|
|
27
|
+
const missingIn = []
|
|
28
|
+
const missingOut = []
|
|
29
|
+
edgeCount.forEach((count, vertex) => {
|
|
30
|
+
if (count < 0) missingIn.push(vertex)
|
|
31
|
+
if (count > 0) missingOut.push(vertex)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// pairwise distance of bad vertices
|
|
35
|
+
missingIn.forEach((key1) => {
|
|
36
|
+
const v1 = vertexMap.get(key1)
|
|
37
|
+
|
|
38
|
+
// find the closest vertex that is missing an out edge
|
|
39
|
+
let bestDistance = Infinity
|
|
40
|
+
let bestReplacement
|
|
41
|
+
missingOut.forEach((key2) => {
|
|
42
|
+
const v2 = vertexMap.get(key2)
|
|
43
|
+
const distance = Math.hypot(v1[0] - v2[0], v1[1] - v2[1])
|
|
44
|
+
if (distance < bestDistance) {
|
|
45
|
+
bestDistance = distance
|
|
46
|
+
bestReplacement = v2
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
console.warn(`slice.repair: repairing vertex gap ${v1} to ${bestReplacement} distance ${bestDistance}`)
|
|
50
|
+
|
|
51
|
+
// merge broken vertices
|
|
52
|
+
edges = edges.map((edge) => {
|
|
53
|
+
if (edge[0].toString() === key1) return [bestReplacement, edge[1]]
|
|
54
|
+
if (edge[1].toString() === key1) return [edge[0], bestReplacement]
|
|
55
|
+
return edge
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
return create(edges)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = repair
|
|
@@ -23,14 +23,13 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
23
23
|
const orthobasis = new OrthoNormalBasis(plane)
|
|
24
24
|
const polygonvertices2d = [] // array of array of Vector2D
|
|
25
25
|
const polygontopvertexindexes = [] // array of indexes of topmost vertex per polygon
|
|
26
|
-
const topy2polygonindexes =
|
|
27
|
-
const ycoordinatetopolygonindexes =
|
|
28
|
-
|
|
29
|
-
const ycoordinatebins = {}
|
|
26
|
+
const topy2polygonindexes = new Map()
|
|
27
|
+
const ycoordinatetopolygonindexes = new Map()
|
|
30
28
|
|
|
31
29
|
// convert all polygon vertices to 2D
|
|
32
30
|
// Make a list of all encountered y coordinates
|
|
33
31
|
// And build a map of all polygons that have a vertex at a certain y coordinate:
|
|
32
|
+
const ycoordinatebins = new Map()
|
|
34
33
|
const ycoordinateBinningFactor = 10 / EPS
|
|
35
34
|
for (let polygonindex = 0; polygonindex < numpolygons; polygonindex++) {
|
|
36
35
|
const poly3d = sourcepolygons[polygonindex]
|
|
@@ -46,15 +45,15 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
46
45
|
// close to each other, give them the same y coordinate:
|
|
47
46
|
const ycoordinatebin = Math.floor(pos2d[1] * ycoordinateBinningFactor)
|
|
48
47
|
let newy
|
|
49
|
-
if (ycoordinatebin
|
|
50
|
-
newy = ycoordinatebins
|
|
51
|
-
} else if (ycoordinatebin + 1
|
|
52
|
-
newy = ycoordinatebins
|
|
53
|
-
} else if (ycoordinatebin - 1
|
|
54
|
-
newy = ycoordinatebins
|
|
48
|
+
if (ycoordinatebins.has(ycoordinatebin)) {
|
|
49
|
+
newy = ycoordinatebins.get(ycoordinatebin)
|
|
50
|
+
} else if (ycoordinatebins.has(ycoordinatebin + 1)) {
|
|
51
|
+
newy = ycoordinatebins.get(ycoordinatebin + 1)
|
|
52
|
+
} else if (ycoordinatebins.has(ycoordinatebin - 1)) {
|
|
53
|
+
newy = ycoordinatebins.get(ycoordinatebin - 1)
|
|
55
54
|
} else {
|
|
56
55
|
newy = pos2d[1]
|
|
57
|
-
ycoordinatebins
|
|
56
|
+
ycoordinatebins.set(ycoordinatebin, pos2d[1])
|
|
58
57
|
}
|
|
59
58
|
pos2d = vec2.fromValues(pos2d[0], newy)
|
|
60
59
|
vertices2d.push(pos2d)
|
|
@@ -66,10 +65,12 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
66
65
|
if ((i === 0) || (y > maxy)) {
|
|
67
66
|
maxy = y
|
|
68
67
|
}
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
let polygonindexes = ycoordinatetopolygonindexes.get(y)
|
|
69
|
+
if (!polygonindexes) {
|
|
70
|
+
polygonindexes = {} // PERF
|
|
71
|
+
ycoordinatetopolygonindexes.set(y, polygonindexes)
|
|
71
72
|
}
|
|
72
|
-
|
|
73
|
+
polygonindexes[polygonindex] = true
|
|
73
74
|
}
|
|
74
75
|
if (miny >= maxy) {
|
|
75
76
|
// degenerate polygon, all vertices have same y coordinate. Just ignore it from now:
|
|
@@ -77,10 +78,12 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
77
78
|
numvertices = 0
|
|
78
79
|
minindex = -1
|
|
79
80
|
} else {
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
let polygonindexes = topy2polygonindexes.get(miny)
|
|
82
|
+
if (!polygonindexes) {
|
|
83
|
+
polygonindexes = []
|
|
84
|
+
topy2polygonindexes.set(miny, polygonindexes)
|
|
82
85
|
}
|
|
83
|
-
|
|
86
|
+
polygonindexes.push(polygonindex)
|
|
84
87
|
}
|
|
85
88
|
} // if(numvertices > 0)
|
|
86
89
|
// reverse the vertex order:
|
|
@@ -89,8 +92,9 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
89
92
|
polygonvertices2d.push(vertices2d)
|
|
90
93
|
polygontopvertexindexes.push(minindex)
|
|
91
94
|
}
|
|
95
|
+
|
|
92
96
|
const ycoordinates = []
|
|
93
|
-
|
|
97
|
+
ycoordinatetopolygonindexes.forEach((polylist, y) => ycoordinates.push(y))
|
|
94
98
|
ycoordinates.sort(fnNumberSort)
|
|
95
99
|
|
|
96
100
|
// Now we will iterate over all y coordinates, from lowest to highest y coordinate
|
|
@@ -108,15 +112,14 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
108
112
|
let prevoutpolygonrow = []
|
|
109
113
|
for (let yindex = 0; yindex < ycoordinates.length; yindex++) {
|
|
110
114
|
const newoutpolygonrow = []
|
|
111
|
-
const
|
|
112
|
-
const ycoordinate = Number(ycoordinateasstring)
|
|
115
|
+
const ycoordinate = ycoordinates[yindex]
|
|
113
116
|
|
|
114
117
|
// update activepolygons for this y coordinate:
|
|
115
118
|
// - Remove any polygons that end at this y coordinate
|
|
116
119
|
// - update leftvertexindex and rightvertexindex (which point to the current vertex index
|
|
117
120
|
// at the the left and right side of the polygon
|
|
118
121
|
// Iterate over all polygons that have a corner at this y coordinate:
|
|
119
|
-
const polygonindexeswithcorner = ycoordinatetopolygonindexes
|
|
122
|
+
const polygonindexeswithcorner = ycoordinatetopolygonindexes.get(ycoordinate)
|
|
120
123
|
for (let activepolygonindex = 0; activepolygonindex < activepolygons.length; ++activepolygonindex) {
|
|
121
124
|
const activepolygon = activepolygons[activepolygonindex]
|
|
122
125
|
const polygonindex = activepolygon.polygonindex
|
|
@@ -166,7 +169,7 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
166
169
|
nextycoordinate = Number(ycoordinates[yindex + 1])
|
|
167
170
|
const middleycoordinate = 0.5 * (ycoordinate + nextycoordinate)
|
|
168
171
|
// update activepolygons by adding any polygons that start here:
|
|
169
|
-
const startingpolygonindexes = topy2polygonindexes
|
|
172
|
+
const startingpolygonindexes = topy2polygonindexes.get(ycoordinate)
|
|
170
173
|
for (const polygonindexKey in startingpolygonindexes) {
|
|
171
174
|
const polygonindex = startingpolygonindexes[polygonindexKey]
|
|
172
175
|
const vertices2d = polygonvertices2d[polygonindex]
|
|
@@ -212,8 +215,6 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
212
215
|
})
|
|
213
216
|
} // for(let polygonindex in startingpolygonindexes)
|
|
214
217
|
} // yindex < ycoordinates.length-1
|
|
215
|
-
// if( (yindex === ycoordinates.length-1) || (nextycoordinate - ycoordinate > EPS) )
|
|
216
|
-
// FIXME : what ???
|
|
217
218
|
|
|
218
219
|
// Now activepolygons is up to date
|
|
219
220
|
// Build the output polygons for the next row in newoutpolygonrow:
|
|
@@ -252,19 +253,19 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
252
253
|
} // for(activepolygon in activepolygons)
|
|
253
254
|
if (yindex > 0) {
|
|
254
255
|
// try to match the new polygons against the previous row:
|
|
255
|
-
const prevcontinuedindexes =
|
|
256
|
-
const matchedindexes =
|
|
256
|
+
const prevcontinuedindexes = new Set()
|
|
257
|
+
const matchedindexes = new Set()
|
|
257
258
|
for (let i = 0; i < newoutpolygonrow.length; i++) {
|
|
258
259
|
const thispolygon = newoutpolygonrow[i]
|
|
259
260
|
for (let ii = 0; ii < prevoutpolygonrow.length; ii++) {
|
|
260
|
-
if (!matchedindexes
|
|
261
|
+
if (!matchedindexes.has(ii)) { // not already processed?
|
|
261
262
|
// We have a match if the sidelines are equal or if the top coordinates
|
|
262
263
|
// are on the sidelines of the previous polygon
|
|
263
264
|
const prevpolygon = prevoutpolygonrow[ii]
|
|
264
265
|
if (vec2.distance(prevpolygon.bottomleft, thispolygon.topleft) < EPS) {
|
|
265
266
|
if (vec2.distance(prevpolygon.bottomright, thispolygon.topright) < EPS) {
|
|
266
267
|
// Yes, the top of this polygon matches the bottom of the previous:
|
|
267
|
-
matchedindexes
|
|
268
|
+
matchedindexes.add(ii)
|
|
268
269
|
// Now check if the joined polygon would remain convex:
|
|
269
270
|
const v1 = line2.direction(thispolygon.leftline)
|
|
270
271
|
const v2 = line2.direction(prevpolygon.leftline)
|
|
@@ -284,16 +285,16 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
284
285
|
thispolygon.outpolygon = prevpolygon.outpolygon
|
|
285
286
|
thispolygon.leftlinecontinues = leftlinecontinues
|
|
286
287
|
thispolygon.rightlinecontinues = rightlinecontinues
|
|
287
|
-
prevcontinuedindexes
|
|
288
|
+
prevcontinuedindexes.add(ii)
|
|
288
289
|
}
|
|
289
290
|
break
|
|
290
291
|
}
|
|
291
292
|
}
|
|
292
|
-
} // if(!prevcontinuedindexes
|
|
293
|
+
} // if(!prevcontinuedindexes.has(ii))
|
|
293
294
|
} // for ii
|
|
294
295
|
} // for i
|
|
295
296
|
for (let ii = 0; ii < prevoutpolygonrow.length; ii++) {
|
|
296
|
-
if (!prevcontinuedindexes
|
|
297
|
+
if (!prevcontinuedindexes.has(ii)) {
|
|
297
298
|
// polygon ends here
|
|
298
299
|
// Finish the polygon with the last point(s):
|
|
299
300
|
const prevpolygon = prevoutpolygonrow[ii]
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
const vec3 = require('../../../maths/vec3')
|
|
2
|
-
|
|
3
|
-
/*
|
|
4
|
-
* Mend gaps in a 2D slice to make it a closed polygon
|
|
5
|
-
*/
|
|
6
|
-
const repairSlice = (slice) => {
|
|
7
|
-
if (!slice.edges) return slice
|
|
8
|
-
const vertexMap = {} // string key to vertex map
|
|
9
|
-
const edgeCount = {} // count of (in - out) edges
|
|
10
|
-
slice.edges.forEach((edge) => {
|
|
11
|
-
const inKey = edge[0].toString()
|
|
12
|
-
const outKey = edge[1].toString()
|
|
13
|
-
vertexMap[inKey] = edge[0]
|
|
14
|
-
vertexMap[outKey] = edge[1]
|
|
15
|
-
edgeCount[inKey] = (edgeCount[inKey] || 0) + 1 // in
|
|
16
|
-
edgeCount[outKey] = (edgeCount[outKey] || 0) - 1 // out
|
|
17
|
-
})
|
|
18
|
-
// find vertices which are missing in or out edges
|
|
19
|
-
const missingIn = Object.keys(edgeCount).filter((e) => edgeCount[e] < 0)
|
|
20
|
-
const missingOut = Object.keys(edgeCount).filter((e) => edgeCount[e] > 0)
|
|
21
|
-
// pairwise distance of bad vertices
|
|
22
|
-
missingIn.forEach((key1) => {
|
|
23
|
-
const v1 = vertexMap[key1]
|
|
24
|
-
// find the closest vertex that is missing an out edge
|
|
25
|
-
let bestDistance = Infinity
|
|
26
|
-
let bestReplacement
|
|
27
|
-
missingOut.forEach((key2) => {
|
|
28
|
-
const v2 = vertexMap[key2]
|
|
29
|
-
const distance = Math.hypot(v1[0] - v2[0], v1[1] - v2[1])
|
|
30
|
-
if (distance < bestDistance) {
|
|
31
|
-
bestDistance = distance
|
|
32
|
-
bestReplacement = v2
|
|
33
|
-
}
|
|
34
|
-
})
|
|
35
|
-
console.warn(`repairSlice: repairing vertex gap ${v1} to ${bestReplacement} distance ${bestDistance}`)
|
|
36
|
-
// merge broken vertices
|
|
37
|
-
slice.edges.forEach((edge) => {
|
|
38
|
-
if (edge[0].toString() === key1) edge[0] = bestReplacement
|
|
39
|
-
if (edge[1].toString() === key1) edge[1] = bestReplacement
|
|
40
|
-
})
|
|
41
|
-
})
|
|
42
|
-
// Remove self-edges
|
|
43
|
-
slice.edges = slice.edges.filter((e) => !vec3.equals(e[0], e[1]))
|
|
44
|
-
return slice
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
module.exports = repairSlice
|