@@ -15,6 +15,7 @@ const CAG = require('./CAG') // FIXME: circular dependency !
15
15
16
16
const Properties = require ( './Properties' )
17
17
const { Connector} = require ( './connectors' )
18
+ const fixTJunctions = require ( './utils/fixTJunctions' )
18
19
// let {fromPolygons} = require('./CSGMakers') // FIXME: circular dependency !
19
20
20
21
/** Class CSG
@@ -318,7 +319,7 @@ CSG.prototype = {
318
319
// the result is a true expansion of the solid
319
320
// If false, returns only the shell
320
321
expandedShell : function ( radius , resolution , unionWithThis ) {
321
- //const {sphere} = require('./primitives3d') // FIXME: circular dependency !
322
+ // const {sphere} = require('./primitives3d') // FIXME: circular dependency !
322
323
let csg = this . reTesselated ( )
323
324
let result
324
325
if ( unionWithThis ) {
@@ -732,13 +733,13 @@ CSG.prototype = {
732
733
shareds . push ( p . shared )
733
734
}
734
735
} )
735
- let numVerticesPerPolygon = new Uint32Array ( numpolygons ) ,
736
- polygonSharedIndexes = new Uint32Array ( numpolygons ) ,
737
- polygonVertices = new Uint32Array ( numpolygonvertices ) ,
738
- polygonPlaneIndexes = new Uint32Array ( numpolygons ) ,
739
- vertexData = new Float64Array ( numvertices * 3 ) ,
740
- planeData = new Float64Array ( numplanes * 4 ) ,
741
- polygonVerticesIndex = 0
736
+ let numVerticesPerPolygon = new Uint32Array ( numpolygons )
737
+ let polygonSharedIndexes = new Uint32Array ( numpolygons )
738
+ let polygonVertices = new Uint32Array ( numpolygonvertices )
739
+ let polygonPlaneIndexes = new Uint32Array ( numpolygons )
740
+ let vertexData = new Float64Array ( numvertices * 3 )
741
+ let planeData = new Float64Array ( numplanes * 4 )
742
+ let polygonVerticesIndex = 0
742
743
for ( let polygonindex = 0 ; polygonindex < numpolygons ; ++ polygonindex ) {
743
744
let p = csg . polygons [ polygonindex ]
744
745
numVerticesPerPolygon [ polygonindex ] = p . vertices . length
@@ -897,316 +898,8 @@ CSG.prototype = {
897
898
return cut3d . projectToOrthoNormalBasis ( orthobasis )
898
899
} ,
899
900
900
- /*
901
- fixTJunctions:
902
-
903
- Suppose we have two polygons ACDB and EDGF:
904
-
905
- A-----B
906
- | |
907
- | E--F
908
- | | |
909
- C-----D--G
910
-
911
- Note that vertex E forms a T-junction on the side BD. In this case some STL slicers will complain
912
- that the solid is not watertight. This is because the watertightness check is done by checking if
913
- each side DE is matched by another side ED.
914
-
915
- This function will return a new solid with ACDB replaced by ACDEB
916
-
917
- Note that this can create polygons that are slightly non-convex (due to rounding errors). Therefore the result should
918
- not be used for further CSG operations!
919
- */
920
901
fixTJunctions : function ( ) {
921
- let csg = this . canonicalized ( )
922
- let sidemap = { }
923
- for ( let polygonindex = 0 ; polygonindex < csg . polygons . length ; polygonindex ++ ) {
924
- let polygon = csg . polygons [ polygonindex ]
925
- let numvertices = polygon . vertices . length
926
- if ( numvertices >= 3 ) // should be true
927
- {
928
- let vertex = polygon . vertices [ 0 ]
929
- let vertextag = vertex . getTag ( )
930
- for ( let vertexindex = 0 ; vertexindex < numvertices ; vertexindex ++ ) {
931
- let nextvertexindex = vertexindex + 1
932
- if ( nextvertexindex === numvertices ) nextvertexindex = 0
933
- let nextvertex = polygon . vertices [ nextvertexindex ]
934
- let nextvertextag = nextvertex . getTag ( )
935
- let sidetag = vertextag + '/' + nextvertextag
936
- let reversesidetag = nextvertextag + '/' + vertextag
937
- if ( reversesidetag in sidemap ) {
938
- // this side matches the same side in another polygon. Remove from sidemap:
939
- let ar = sidemap [ reversesidetag ]
940
- ar . splice ( - 1 , 1 )
941
- if ( ar . length === 0 ) {
942
- delete sidemap [ reversesidetag ]
943
- }
944
- } else {
945
- let sideobj = {
946
- vertex0 : vertex ,
947
- vertex1 : nextvertex ,
948
- polygonindex : polygonindex
949
- }
950
- if ( ! ( sidetag in sidemap ) ) {
951
- sidemap [ sidetag ] = [ sideobj ]
952
- } else {
953
- sidemap [ sidetag ] . push ( sideobj )
954
- }
955
- }
956
- vertex = nextvertex
957
- vertextag = nextvertextag
958
- }
959
- }
960
- }
961
- // now sidemap contains 'unmatched' sides
962
- // i.e. side AB in one polygon does not have a matching side BA in another polygon
963
- let vertextag2sidestart = { }
964
- let vertextag2sideend = { }
965
- let sidestocheck = { }
966
- let sidemapisempty = true
967
- for ( let sidetag in sidemap ) {
968
- sidemapisempty = false
969
- sidestocheck [ sidetag ] = true
970
- sidemap [ sidetag ] . map ( function ( sideobj ) {
971
- let starttag = sideobj . vertex0 . getTag ( )
972
- let endtag = sideobj . vertex1 . getTag ( )
973
- if ( starttag in vertextag2sidestart ) {
974
- vertextag2sidestart [ starttag ] . push ( sidetag )
975
- } else {
976
- vertextag2sidestart [ starttag ] = [ sidetag ]
977
- }
978
- if ( endtag in vertextag2sideend ) {
979
- vertextag2sideend [ endtag ] . push ( sidetag )
980
- } else {
981
- vertextag2sideend [ endtag ] = [ sidetag ]
982
- }
983
- } )
984
- }
985
-
986
- if ( ! sidemapisempty ) {
987
- // make a copy of the polygons array, since we are going to modify it:
988
- let polygons = csg . polygons . slice ( 0 )
989
-
990
- function addSide ( vertex0 , vertex1 , polygonindex ) {
991
- let starttag = vertex0 . getTag ( )
992
- let endtag = vertex1 . getTag ( )
993
- if ( starttag === endtag ) throw new Error ( 'Assertion failed' )
994
- let newsidetag = starttag + '/' + endtag
995
- let reversesidetag = endtag + '/' + starttag
996
- if ( reversesidetag in sidemap ) {
997
- // we have a matching reverse oriented side.
998
- // Instead of adding the new side, cancel out the reverse side:
999
- // console.log("addSide("+newsidetag+") has reverse side:");
1000
- deleteSide ( vertex1 , vertex0 , null )
1001
- return null
1002
- }
1003
- // console.log("addSide("+newsidetag+")");
1004
- let newsideobj = {
1005
- vertex0 : vertex0 ,
1006
- vertex1 : vertex1 ,
1007
- polygonindex : polygonindex
1008
- }
1009
- if ( ! ( newsidetag in sidemap ) ) {
1010
- sidemap [ newsidetag ] = [ newsideobj ]
1011
- } else {
1012
- sidemap [ newsidetag ] . push ( newsideobj )
1013
- }
1014
- if ( starttag in vertextag2sidestart ) {
1015
- vertextag2sidestart [ starttag ] . push ( newsidetag )
1016
- } else {
1017
- vertextag2sidestart [ starttag ] = [ newsidetag ]
1018
- }
1019
- if ( endtag in vertextag2sideend ) {
1020
- vertextag2sideend [ endtag ] . push ( newsidetag )
1021
- } else {
1022
- vertextag2sideend [ endtag ] = [ newsidetag ]
1023
- }
1024
- return newsidetag
1025
- }
1026
-
1027
- function deleteSide ( vertex0 , vertex1 , polygonindex ) {
1028
- let starttag = vertex0 . getTag ( )
1029
- let endtag = vertex1 . getTag ( )
1030
- let sidetag = starttag + '/' + endtag
1031
- // console.log("deleteSide("+sidetag+")");
1032
- if ( ! ( sidetag in sidemap ) ) throw new Error ( 'Assertion failed' )
1033
- let idx = - 1
1034
- let sideobjs = sidemap [ sidetag ]
1035
- for ( let i = 0 ; i < sideobjs . length ; i ++ ) {
1036
- let sideobj = sideobjs [ i ]
1037
- if ( sideobj . vertex0 !== vertex0 ) continue
1038
- if ( sideobj . vertex1 !== vertex1 ) continue
1039
- if ( polygonindex !== null ) {
1040
- if ( sideobj . polygonindex !== polygonindex ) continue
1041
- }
1042
- idx = i
1043
- break
1044
- }
1045
- if ( idx < 0 ) throw new Error ( 'Assertion failed' )
1046
- sideobjs . splice ( idx , 1 )
1047
- if ( sideobjs . length === 0 ) {
1048
- delete sidemap [ sidetag ]
1049
- }
1050
- idx = vertextag2sidestart [ starttag ] . indexOf ( sidetag )
1051
- if ( idx < 0 ) throw new Error ( 'Assertion failed' )
1052
- vertextag2sidestart [ starttag ] . splice ( idx , 1 )
1053
- if ( vertextag2sidestart [ starttag ] . length === 0 ) {
1054
- delete vertextag2sidestart [ starttag ]
1055
- }
1056
-
1057
- idx = vertextag2sideend [ endtag ] . indexOf ( sidetag )
1058
- if ( idx < 0 ) throw new Error ( 'Assertion failed' )
1059
- vertextag2sideend [ endtag ] . splice ( idx , 1 )
1060
- if ( vertextag2sideend [ endtag ] . length === 0 ) {
1061
- delete vertextag2sideend [ endtag ]
1062
- }
1063
- }
1064
-
1065
- while ( true ) {
1066
- let sidemapisempty = true
1067
- for ( let sidetag in sidemap ) {
1068
- sidemapisempty = false
1069
- sidestocheck [ sidetag ] = true
1070
- }
1071
- if ( sidemapisempty ) break
1072
- let donesomething = false
1073
- while ( true ) {
1074
- let sidetagtocheck = null
1075
- for ( let sidetag in sidestocheck ) {
1076
- sidetagtocheck = sidetag
1077
- break
1078
- }
1079
- if ( sidetagtocheck === null ) break // sidestocheck is empty, we're done!
1080
- let donewithside = true
1081
- if ( sidetagtocheck in sidemap ) {
1082
- let sideobjs = sidemap [ sidetagtocheck ]
1083
- if ( sideobjs . length === 0 ) throw new Error ( 'Assertion failed' )
1084
- let sideobj = sideobjs [ 0 ]
1085
- for ( let directionindex = 0 ; directionindex < 2 ; directionindex ++ ) {
1086
- let startvertex = ( directionindex === 0 ) ? sideobj . vertex0 : sideobj . vertex1
1087
- let endvertex = ( directionindex === 0 ) ? sideobj . vertex1 : sideobj . vertex0
1088
- let startvertextag = startvertex . getTag ( )
1089
- let endvertextag = endvertex . getTag ( )
1090
- let matchingsides = [ ]
1091
- if ( directionindex === 0 ) {
1092
- if ( startvertextag in vertextag2sideend ) {
1093
- matchingsides = vertextag2sideend [ startvertextag ]
1094
- }
1095
- } else {
1096
- if ( startvertextag in vertextag2sidestart ) {
1097
- matchingsides = vertextag2sidestart [ startvertextag ]
1098
- }
1099
- }
1100
- for ( let matchingsideindex = 0 ; matchingsideindex < matchingsides . length ; matchingsideindex ++ ) {
1101
- let matchingsidetag = matchingsides [ matchingsideindex ]
1102
- let matchingside = sidemap [ matchingsidetag ] [ 0 ]
1103
- let matchingsidestartvertex = ( directionindex === 0 ) ? matchingside . vertex0 : matchingside . vertex1
1104
- let matchingsideendvertex = ( directionindex === 0 ) ? matchingside . vertex1 : matchingside . vertex0
1105
- let matchingsidestartvertextag = matchingsidestartvertex . getTag ( )
1106
- let matchingsideendvertextag = matchingsideendvertex . getTag ( )
1107
- if ( matchingsideendvertextag !== startvertextag ) throw new Error ( 'Assertion failed' )
1108
- if ( matchingsidestartvertextag === endvertextag ) {
1109
- // matchingside cancels sidetagtocheck
1110
- deleteSide ( startvertex , endvertex , null )
1111
- deleteSide ( endvertex , startvertex , null )
1112
- donewithside = false
1113
- directionindex = 2 // skip reverse direction check
1114
- donesomething = true
1115
- break
1116
- } else {
1117
- let startpos = startvertex . pos
1118
- let endpos = endvertex . pos
1119
- let checkpos = matchingsidestartvertex . pos
1120
- let direction = checkpos . minus ( startpos )
1121
- // Now we need to check if endpos is on the line startpos-checkpos:
1122
- let t = endpos . minus ( startpos ) . dot ( direction ) / direction . dot ( direction )
1123
- if ( ( t > 0 ) && ( t < 1 ) ) {
1124
- let closestpoint = startpos . plus ( direction . times ( t ) )
1125
- let distancesquared = closestpoint . distanceToSquared ( endpos )
1126
- if ( distancesquared < ( EPS * EPS ) ) {
1127
- // Yes it's a t-junction! We need to split matchingside in two:
1128
- let polygonindex = matchingside . polygonindex
1129
- let polygon = polygons [ polygonindex ]
1130
- // find the index of startvertextag in polygon:
1131
- let insertionvertextag = matchingside . vertex1 . getTag ( )
1132
- let insertionvertextagindex = - 1
1133
- for ( let i = 0 ; i < polygon . vertices . length ; i ++ ) {
1134
- if ( polygon . vertices [ i ] . getTag ( ) === insertionvertextag ) {
1135
- insertionvertextagindex = i
1136
- break
1137
- }
1138
- }
1139
- if ( insertionvertextagindex < 0 ) throw new Error ( 'Assertion failed' )
1140
- // split the side by inserting the vertex:
1141
- let newvertices = polygon . vertices . slice ( 0 )
1142
- newvertices . splice ( insertionvertextagindex , 0 , endvertex )
1143
- let newpolygon = new Polygon ( newvertices , polygon . shared /* polygon.plane */ )
1144
-
1145
- // FIX
1146
- // calculate plane with differents point
1147
- if ( isNaN ( newpolygon . plane . w ) ) {
1148
- let found = false ,
1149
- loop = function ( callback ) {
1150
- newpolygon . vertices . forEach ( function ( item ) {
1151
- if ( found ) return
1152
- callback ( item )
1153
- } )
1154
- }
1155
-
1156
- loop ( function ( a ) {
1157
- loop ( function ( b ) {
1158
- loop ( function ( c ) {
1159
- newpolygon . plane = Plane . fromPoints ( a . pos , b . pos , c . pos )
1160
- if ( ! isNaN ( newpolygon . plane . w ) ) {
1161
- found = true
1162
- }
1163
- } )
1164
- } )
1165
- } )
1166
- }
1167
- // FIX
1168
-
1169
- polygons [ polygonindex ] = newpolygon
1170
-
1171
- // remove the original sides from our maps:
1172
- // deleteSide(sideobj.vertex0, sideobj.vertex1, null);
1173
- deleteSide ( matchingside . vertex0 , matchingside . vertex1 , polygonindex )
1174
- let newsidetag1 = addSide ( matchingside . vertex0 , endvertex , polygonindex )
1175
- let newsidetag2 = addSide ( endvertex , matchingside . vertex1 , polygonindex )
1176
- if ( newsidetag1 !== null ) sidestocheck [ newsidetag1 ] = true
1177
- if ( newsidetag2 !== null ) sidestocheck [ newsidetag2 ] = true
1178
- donewithside = false
1179
- directionindex = 2 // skip reverse direction check
1180
- donesomething = true
1181
- break
1182
- } // if(distancesquared < 1e-10)
1183
- } // if( (t > 0) && (t < 1) )
1184
- } // if(endingstidestartvertextag === endvertextag)
1185
- } // for matchingsideindex
1186
- } // for directionindex
1187
- } // if(sidetagtocheck in sidemap)
1188
- if ( donewithside ) {
1189
- delete sidestocheck [ sidetag ]
1190
- }
1191
- }
1192
- if ( ! donesomething ) break
1193
- }
1194
- let newcsg = CSG . fromPolygons ( polygons )
1195
- newcsg . properties = csg . properties
1196
- newcsg . isCanonicalized = true
1197
- newcsg . isRetesselated = true
1198
- csg = newcsg
1199
- } // if(!sidemapisempty)
1200
- sidemapisempty = true
1201
- for ( let sidetag in sidemap ) {
1202
- sidemapisempty = false
1203
- break
1204
- }
1205
- if ( ! sidemapisempty ) {
1206
- // throw new Error("!sidemapisempty");
1207
- console . log ( '!sidemapisempty' )
1208
- }
1209
- return csg
902
+ return fixTJunctions ( CSG . fromPolygons , this )
1210
903
} ,
1211
904
1212
905
toTriangles : function ( ) {
@@ -1248,7 +941,6 @@ CSG.prototype = {
1248
941
}
1249
942
}
1250
943
1251
-
1252
944
/** Construct a CSG solid from a list of `Polygon` instances.
1253
945
* @param {Polygon[] } polygons - list of polygons
1254
946
* @returns {CSG } new CSG object
0 commit comments