@@ -85,6 +85,7 @@ destep_conv_surface <- function(dest, ep) {
8585 )
8686 assert_unique_name(surface $ NAME [surface $ POINT_NO == 0L ], " surface" )
8787 data.table :: setDT(surface )
88+ data.table :: setorderv(surface , c(" ID" , " POINT_NO" ))
8889
8990 # find the adjacent surfaces
9091 surface [BOUNDARY == " Surface" , by = " PLANE" , BOUNDARY_OBJECT : = rev(NAME )]
@@ -122,6 +123,11 @@ destep_conv_surface <- function(dest, ep) {
122123 )]
123124 }
124125
126+ # Use one minimal vertex sequence for each polygon. EnergyPlus simplifies
127+ # adjacent polygons internally; leaving redundant collinear points can make
128+ # the two otherwise identical sides end up with different vertex counts.
129+ surface <- surface [, destep_simplify_surface_polygon(.SD ), by = " ID" ]
130+
125131 # TODO: how does DeST handle the case when the surface is both a floor and a ceiling?
126132 # TODO: how does EnergyPlus handle "empty floor slab"?
127133
@@ -180,3 +186,43 @@ destep_conv_surface <- function(dest, ep) {
180186
181187 out
182188}
189+
190+ # Remove redundant vertices from one ordered DeST surface polygon while
191+ # retaining turns and at least the three vertices needed for a valid face.
192+ destep_simplify_surface_polygon <- function (surface , tolerance = 1e-8 ) {
193+ surface <- data.table :: copy(surface )
194+
195+ repeat {
196+ n_vertex <- nrow(surface )
197+ if (n_vertex < = 3L ) break
198+
199+ previous <- c(n_vertex , seq_len(n_vertex - 1L ))
200+ following <- c(seq.int(2L , n_vertex ), 1L )
201+ coordinates <- as.matrix(surface [, .(POINT_X , POINT_Y , POINT_Z )])
202+ incoming <- coordinates - coordinates [previous , , drop = FALSE ]
203+ outgoing <- coordinates [following , , drop = FALSE ] - coordinates
204+
205+ incoming_length <- sqrt(rowSums(incoming ^ 2 ))
206+ outgoing_length <- sqrt(rowSums(outgoing ^ 2 ))
207+ cross_product <- cbind(
208+ incoming [, 2L ] * outgoing [, 3L ] - incoming [, 3L ] * outgoing [, 2L ],
209+ incoming [, 3L ] * outgoing [, 1L ] - incoming [, 1L ] * outgoing [, 3L ],
210+ incoming [, 1L ] * outgoing [, 2L ] - incoming [, 2L ] * outgoing [, 1L ]
211+ )
212+ # A vertex is redundant only when the path continues straight through
213+ # it. A collinear reversal is retained because it changes the polygon.
214+ redundant <- incoming_length < = tolerance |
215+ outgoing_length < = tolerance |
216+ (
217+ sqrt(rowSums(cross_product ^ 2 )) < =
218+ tolerance * pmax(1 , incoming_length * outgoing_length ) &
219+ rowSums(incoming * outgoing ) > 0
220+ )
221+
222+ if (! any(redundant ) || n_vertex - sum(redundant ) < 3L ) break
223+ surface <- surface [! redundant ]
224+ }
225+
226+ data.table :: set(surface , NULL , " POINT_NO" , seq_len(nrow(surface )) - 1L )
227+ surface
228+ }
0 commit comments