Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upchecking triangle orientation #17
Comments
|
Compare rgl, there's a few gotchas for getting into rgl form:
## vectorized index helpers for calculating triangle area
tri_ix <- function(x) {
offset <- rep(seq(0, nrow(x) - 1, by = 3), each = 3)
c(3L, 1L, 2L) + offset
}
tri_jx <- function(x) {
offset <- rep(seq(0, nrow(x) - 1, by = 3), each = 3)
c(1L, 2L, 3L) + offset
}
## signed triangle area (negative is anti-clockwise orientation)
### https://github.com/hypertidy/silicate/blob/ab7610290e45567c134dd7e48f4ba8199c5bcf59/R/triangles.R
tri_wind <- function(x) {
ix <- tri_ix(x)
jx <- tri_jx(x)
colSums(matrix((x[ix, 1] + x[jx, 1]) * (x[ix, 2] - x[jx, 2]), nrow = 3L))/2
}
## plot polygon as index labels at its vertices
plot_poly <- function(x) {
xr <- range(x[,1])
yr <- range(x[,2])
xr <- xr + c(-1, 1) * diff(xr)/10
yr <- yr + c(-1, 1) * diff(yr)/10
plot(x, type = "n", xlim = xr, ylim = yr)
text(x, lab = 1:nrow(x), pos = 4)
}
## plot triangles (input is triplets of xy coordinates)
## with one side an oriented arrow
## signed area of triangle is labelled at its centre
plot_tri <- function(x, add = TRUE) {
if (!add) plot(x, asp = 1, type = "n")
idx <- c(1:3, 1)
for (i in seq_len(nrow(x)/3)) {
#print(idx)
## plot only the first arrow
arrows(x[idx[1], 1], x[idx[1], 2],
x[idx[2], 1], x[idx[2], 2], length = 0.35, lty = 2, lwd = 2)
## segments the rest
segments(x[idx[2:3], 1], x[idx[2:3], 2],
x[idx[3:4], 1], x[idx[3:4], 2])
text(mean(x[idx[1:3], 1]),
mean(x[idx[1:3], 2]), lab = tri_wind(x[idx[1:3], ]),
col = "firebrick", cex = 0.7)
idx <- idx + 3
}
}
## here we need a object-to-polycoords-na thing
library(silicate)
#>
#> Attaching package: 'silicate'
#> The following object is masked from 'package:stats':
#>
#> filter
x <- minimal_mesh
p <- sc_path(x) %>% dplyr::filter(object_ == object_[1])
xy <- sc_coord(x)[1:sum(p$ncoords_), ]
library(dplyr)
#>
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#>
#> filter, lag
#> The following objects are masked from 'package:base':
#>
#> intersect, setdiff, setequal, union
rl <- p %>% dplyr::filter(object_ == object_[1]) %>%
pull(ncoords_)
rbind_na <- function(x) {
head(do.call(rbind, lapply(x, function(a) rbind(head(a, -1), NA))),
-1)
}
l <- split(xy, rep(seq_along(rl), rl))
## reverse winding of hole
#l[[2]] <- l[[2]][nrow(l[[2]]):1, ]
pcd <- rbind_na(l)
library(rgl)
## check rgls' take (make sure random = FALSE!)
tri <- rgl::triangulate(pcd$x_, pcd$y_, random = FALSE)
tri_wind(as.matrix(pcd)[tri, ])
#> [1] -0.0690 -0.1000 -0.0200 -0.0300 -0.0190 -0.0285 -0.0300 -0.0550 -0.0500
#> [10] -0.1125 -0.0625 -0.0955
plot_poly(pcd[!is.na(pcd$x_), ])
plot_tri(as.matrix(pcd)[tri, ])Created on 2020-04-29 by the reprex package (v0.3.0) |
|
You can see the effect of triangle winding in rgl by setting ## rgl polygon with a hole
xy <- structure(c(0, 0, 0.75, 1, 0.5, 0.8, 0.69, NA, 0.2, 0.5, 0.5,
0.3, 0.2, 0, 1, 1, 0.8, 0.7, 0.6, 0, NA, 0.2, 0.2, 0.4, 0.6,
0.4), .Dim = c(13L, 2L), .Dimnames = list(NULL, c("x_", "y_")))
tri <-
structure(c(7L, 9L, 1L, 9L, 2L, 1L, 9L, 13L, 2L, 7L, 10L, 9L,
7L, 11L, 10L, 7L, 5L, 11L, 5L, 12L, 11L, 5L, 2L, 12L, 2L, 13L,
12L, 5L, 3L, 2L, 5L, 4L, 3L, 7L, 6L, 5L), .Dim = c(3L, 12L), nextvert = c(7L,
1L, 2L, 3L, 4L, 5L, 6L, NA, 13L, 9L, 10L, 11L, 12L))
rgl::clear3d()
## the "back" is triangle-orientation
triangles3d(cbind(xy, 0)[tri, ], back = "lines")
## make some triangles clockwise
tri[, c(1, 3, 5)] <- tri[3:1, c(1, 3, 5)]
rgl::clear3d()
triangles3d(cbind(xy, 0)[tri, ], col = "firebrick", back = "lines")
The triangles are otherwise filled on the other side |
|
Nice. I'm going to assume going forward that they will always come out a-clockwise out of decido. Will you consider guaranteeing this in the docs (and perhaps with a check for when upgrading earcut.hpp)? Thanks again! |
|
Sure, that should be fine. Are you confident that your clockwise-winded triangle with the vertical side was a false-positive? I'd still like to have that R-polygon, that's a neat example - I couldn't find an easy version of the logo polygon anywhere. It's been very nice to clean out that wind-order, area, and plot code - that's going to be very handy in lots of places and this has flushed out some confusion for me! |
|
Yes, pretty sure it was a false positive caused by my assuming that Here is the code to get the R logo in polygons (and the hush hush bit is I'm working on getting it rayrendered for a post of mine, which is how I ended up down this particular rabbit hole).
|
|
haha nice manual svg parsing - glad you have done that I baulked! |
|
Whatever it takes =) |
|
I think I got the orientation thing exactly backwards. Signed area should be positive counter-clockwise. |
|
That's right. Doesn't really matter though, it's pretty obvious once you know about signed areas, so thanks a bunch for showing me that. Kinda like the earcut.hpp docs have the direction backwards. It's important, but really the truly important thing is that it's consistent. You can always spot check and figure out the sign/direction (though, it wouldn't be a bad thing for the docs to be correct). I don't know how long it would have taken me to run into signed areas on my own given it's not at all my area of expertise. |
|
Cool appreciate the feedback, I always need this extra validation to know I'm not off-base, I will check with some local math experts on the general idea and put this in a tiny package. |
|
Note to self
|
|
@brodieG I haven't gone any deeper, but it's curious that this example page uses clockwise = positive? https://rosettacode.org/wiki/Shoelace_formula_for_polygonal_area It's entirely arbitrary of course but ... |
|
From my reading, I'm seeing that the absolute value of that equation is positive for clockwise. Due to the use of |
|
Oh I see, they're being a bit casual and really calculating "area". Thanks for replying, that link is nice too. |
|
Thanks @brodieG I put some of this in the doc, thanks! Needs a much more thorough treatment as part of a bigger story that's developing in R ;) |



With motivation from @brodieG
This example still WIP, but has
earcut(poly)I'm keen to see any counter examples, does earcut.hpp ever return clockwise-oriented triangles? I don't think so, there are collinear (or visually-collinear at any rate) cases with zero-area, or very small epsilon-positive area - I see that triangulating
silicate::inlandwaters, sthng like 44 out of 33000 triangles there.Created on 2020-04-27 by the reprex package (v0.3.0)