@@ -6,22 +6,42 @@ const toSides = require('./toSides')
6
6
* Create a list of edges which SHARE vertices.
7
7
* This allows the edges to be traversed in order.
8
8
*/
9
- const toEdges = ( sides ) => {
10
- const vertices = { }
9
+ const toSharedVertices = ( sides ) => {
10
+ const unique = new Map ( ) // {key: vertex }
11
11
const getUniqueVertex = ( vertex ) => {
12
12
const key = vertex . toString ( )
13
- if ( ! vertices [ key ] ) {
14
- vertices [ key ] = vertex
13
+ if ( unique . has ( key ) ) {
14
+ return unique . get ( key )
15
+ } else {
16
+ unique . set ( key , vertex )
17
+ return vertex
15
18
}
16
- return vertices [ key ]
17
19
}
18
20
19
21
return sides . map ( ( side ) => side . map ( getUniqueVertex ) )
20
22
}
21
23
24
+ /*
25
+ * Convert a list of sides into a map from vertex to edges.
26
+ */
27
+ const toVertexMap = ( sides ) => {
28
+ const vertexMap = new Map ( )
29
+ // first map to edges with shared vertices
30
+ const edges = toSharedVertices ( sides )
31
+ // construct adjacent edges map
32
+ edges . forEach ( ( edge ) => {
33
+ if ( vertexMap . has ( edge [ 0 ] ) ) {
34
+ vertexMap . get ( edge [ 0 ] ) . push ( edge )
35
+ } else {
36
+ vertexMap . set ( edge [ 0 ] , [ edge ] )
37
+ }
38
+ } )
39
+ return vertexMap
40
+ }
41
+
22
42
/**
23
43
* Create the outline(s) of the given geometry.
24
- * @param {geom2 } geometry
44
+ * @param {geom2 } geometry - geometry to create outlines from
25
45
* @returns {Array } an array of outlines, where each outline is an array of ordered points
26
46
* @alias module:modeling/geometries/geom2.toOutlines
27
47
*
@@ -30,65 +50,35 @@ const toEdges = (sides) => {
30
50
* let outlines = toOutlines(geometry) // returns two outlines
31
51
*/
32
52
const toOutlines = ( geometry ) => {
33
- const vertexMap = new Map ( )
34
- const edges = toEdges ( toSides ( geometry ) )
35
- edges . forEach ( ( edge ) => {
36
- if ( ! ( vertexMap . has ( edge [ 0 ] ) ) ) {
37
- vertexMap . set ( edge [ 0 ] , [ ] )
38
- }
39
- const sideslist = vertexMap . get ( edge [ 0 ] )
40
- sideslist . push ( edge )
41
- } )
42
-
53
+ const vertexMap = toVertexMap ( toSides ( geometry ) ) // {vertex: [edges]}
43
54
const outlines = [ ]
44
55
while ( true ) {
45
- let startside
56
+ let startSide
46
57
for ( const [ vertex , edges ] of vertexMap ) {
47
- startside = edges . shift ( )
48
- if ( ! startside ) {
58
+ startSide = edges . shift ( )
59
+ if ( ! startSide ) {
49
60
vertexMap . delete ( vertex )
50
61
continue
51
62
}
52
63
break
53
64
}
54
- if ( startside === undefined ) break // all starting sides have been visited
65
+ if ( startSide === undefined ) break // all starting sides have been visited
55
66
56
67
const connectedVertexPoints = [ ]
57
- const startvertex = startside [ 0 ]
58
- const v0 = vec2 . create ( )
68
+ const startVertex = startSide [ 0 ]
59
69
while ( true ) {
60
- connectedVertexPoints . push ( startside [ 0 ] )
61
- const nextvertex = startside [ 1 ]
62
- if ( nextvertex === startvertex ) break // the outline has been closed
63
- const nextpossiblesides = vertexMap . get ( nextvertex )
64
- if ( ! nextpossiblesides ) {
65
- throw new Error ( 'the given geometry is not closed. verify proper construction' )
70
+ connectedVertexPoints . push ( startSide [ 0 ] )
71
+ const nextVertex = startSide [ 1 ]
72
+ if ( nextVertex === startVertex ) break // the outline has been closed
73
+ const nextPossibleSides = vertexMap . get ( nextVertex )
74
+ if ( ! nextPossibleSides ) {
75
+ throw new Error ( ` geometry is not closed at vertex ${ nextVertex } ` )
66
76
}
67
- let nextsideindex = - 1
68
- if ( nextpossiblesides . length === 1 ) {
69
- nextsideindex = 0
70
- } else {
71
- // more than one side starting at the same vertex
72
- let bestangle
73
- const startangle = vec2 . angleDegrees ( vec2 . subtract ( v0 , startside [ 1 ] , startside [ 0 ] ) )
74
- for ( let sideindex = 0 ; sideindex < nextpossiblesides . length ; sideindex ++ ) {
75
- const nextpossibleside = nextpossiblesides [ sideindex ]
76
- const nextangle = vec2 . angleDegrees ( vec2 . subtract ( v0 , nextpossibleside [ 1 ] , nextpossibleside [ 0 ] ) )
77
- let angledif = nextangle - startangle
78
- if ( angledif < - 180 ) angledif += 360
79
- if ( angledif >= 180 ) angledif -= 360
80
- if ( ( nextsideindex < 0 ) || ( angledif > bestangle ) ) {
81
- nextsideindex = sideindex
82
- bestangle = angledif
83
- }
84
- }
77
+ const nextSide = popNextSide ( startSide , nextPossibleSides )
78
+ if ( nextPossibleSides . length === 0 ) {
79
+ vertexMap . delete ( nextVertex )
85
80
}
86
- const nextside = nextpossiblesides [ nextsideindex ]
87
- nextpossiblesides . splice ( nextsideindex , 1 ) // remove side from list
88
- if ( nextpossiblesides . length === 0 ) {
89
- vertexMap . delete ( nextvertex )
90
- }
91
- startside = nextside
81
+ startSide = nextSide
92
82
} // inner loop
93
83
94
84
// due to the logic of fromPoints()
@@ -102,4 +92,28 @@ const toOutlines = (geometry) => {
102
92
return outlines
103
93
}
104
94
95
+ // find the first counter-clockwise edge from startSide and pop from nextSides
96
+ const popNextSide = ( startSide , nextSides ) => {
97
+ if ( nextSides . length === 1 ) {
98
+ return nextSides . pop ( )
99
+ }
100
+ const v0 = vec2 . create ( )
101
+ const startAngle = vec2 . angleDegrees ( vec2 . subtract ( v0 , startSide [ 1 ] , startSide [ 0 ] ) )
102
+ let bestAngle
103
+ let bestIndex
104
+ nextSides . forEach ( ( nextSide , index ) => {
105
+ const nextAngle = vec2 . angleDegrees ( vec2 . subtract ( v0 , nextSide [ 1 ] , nextSide [ 0 ] ) )
106
+ let angle = nextAngle - startAngle
107
+ if ( angle < - 180 ) angle += 360
108
+ if ( angle >= 180 ) angle -= 360
109
+ if ( bestIndex === undefined || angle > bestAngle ) {
110
+ bestIndex = index
111
+ bestAngle = angle
112
+ }
113
+ } )
114
+ const nextSide = nextSides [ bestIndex ]
115
+ nextSides . splice ( bestIndex , 1 ) // remove side from list
116
+ return nextSide
117
+ }
118
+
105
119
module . exports = toOutlines
0 commit comments