From 4cc3f85a745139f8f2fb0249a216781612c3265c Mon Sep 17 00:00:00 2001 From: Gregory Jefferis Date: Tue, 22 Sep 2026 17:11:19 +0100 Subject: [PATCH 1/2] pointsinside() uses natcpp winding-number test when available When natcpp (>= 0.3.2) is installed, pointsinside() logical results now use its generalised (solid-angle) winding-number test (c_pointsinside, method= "auto") instead of the normal-based Rvcg signed-distance test. The winding number does not depend on face normals, so it is robust to inconsistently oriented meshes that could previously give spurious inside/outside results. The choice is conditional at runtime via use_natcpp(version=): Rvcg remains the fallback when natcpp is absent and still serves rval="distance" and rval="mesh3d". Set options(nat.use_natcpp=FALSE) to force the Rvcg path. Bump version to 1.11.1 and update NEWS. Co-Authored-By: Claude Opus 4.8 --- DESCRIPTION | 2 +- NEWS.md | 10 ++++++++++ R/hxsurf.R | 32 +++++++++++++++++++++++++------- R/utils.R | 9 +++++++-- man/pointsinside.Rd | 15 ++++++++++++--- 5 files changed, 55 insertions(+), 13 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index fa709df5..03397319 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: nat Type: Package Title: NeuroAnatomy Toolbox for Analysis of 3D Image Data -Version: 1.11.0 +Version: 1.11.1 Authors@R: c( person("Gregory","Jefferis", email="jefferis@gmail.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-0587-9355")), diff --git a/NEWS.md b/NEWS.md index d35a0571..a2eab7ad 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,13 @@ +# nat 1.11.1 + +* `pointsinside()` now uses natcpp's (>= 0.3.2) generalised winding-number test + for logical results when the natcpp package is installed. This is independent + of face normals, so it is robust to inconsistently oriented meshes that could + previously give spurious inside/outside classifications with the normal-based + `Rvcg` test. The `Rvcg` path remains the fallback and still handles + `rval="distance"` and `rval="mesh3d"`; set `options(nat.use_natcpp=FALSE)` to + force it. + # nat 1.11.0 This release bundles together a large number of changes and is targeting a CRAN diff --git a/R/hxsurf.R b/R/hxsurf.R index 4e812bd6..5506516d 100644 --- a/R/hxsurf.R +++ b/R/hxsurf.R @@ -613,9 +613,18 @@ NULL #' testing repeatedly against the same surface, it may make sense to #' pre-convert. #' -#' \code{pointsinside} depends on the face normals for each face pointing out -#' of the object (see example). The face normals are defined by the order of -#' the three vertices making up a triangular face. You can flip the face +#' When the \href{https://natverse.org/natcpp/}{natcpp} package (>= 0.3.2) is +#' installed, logical results (\code{rval="logical"} or +#' \code{"consistent_logical"}) use its generalised (solid-angle) +#' winding-number test. This does not depend on face normals and is therefore +#' robust to inconsistently oriented faces, so the face-normal caveats below +#' apply only to the \code{Rvcg} fallback (used when natcpp is unavailable and +#' for \code{rval="distance"} or \code{"mesh3d"}). Set +#' \code{options(nat.use_natcpp=FALSE)} to force the \code{Rvcg} path. +#' +#' The \code{Rvcg} fallback depends on the face normals for each face pointing +#' out of the object (see example). The face normals are defined by the order +#' of the three vertices making up a triangular face. You can flip the face #' normal for a face by permuting the vertices (i.e. 1,2,3 -> 1,3,2). If you #' find for a given surface that points are outside when you expect them to be #' inside then the face normals are probably all the wrong way round. You can @@ -719,14 +728,23 @@ pointsinside.default<-function(x, surf, ..., rval=c('logical','distance', stop("Only logical return values are currently possible ", "with boundingbox objects!") } - - if(!requireNamespace('Rvcg', quietly = TRUE)) - stop("Please install suggested library Rvcg to use pointsinside") - + if(!inherits(surf,'mesh3d')) { surf=as.mesh3d(surf, ...) } + # Fast, normal-independent path for logical results: natcpp's generalised + # winding-number test (>= 0.3.2, method="auto"). Unlike the Rvcg signed + # distance it does not depend on face normals, so it avoids spurious results + # near thin/sharp features. distance and mesh3d still require Rvcg below. + if(rval=='consistent_logical' && !is.null(surf$it) && + use_natcpp(version='0.3.2')) { + return(natcpp::c_pointsinside(xyzmatrix(x), xyzmatrix(surf), t(surf$it))) + } + + if(!requireNamespace('Rvcg', quietly = TRUE)) + stop("Please install suggested library Rvcg to use pointsinside") + pts=xyzmatrix(x) rmesh=Rvcg::vcgClostKD(pts, surf, sign = TRUE) switch(rval, diff --git a/R/utils.R b/R/utils.R index 246642fb..bc6b65a5 100644 --- a/R/utils.R +++ b/R/utils.R @@ -19,7 +19,12 @@ nat_progress <- function (x, max = 100, message = NULL) { # to check if we should use natcpp # always=TRUE => use if installed even if option says otherwise -use_natcpp <- function(always=FALSE) { +# version => optional minimum natcpp version required (character or numeric_version) +use_natcpp <- function(always=FALSE, version=NULL) { opcheck <- isTRUE(always) || !isFALSE(getOption('nat.use_natcpp')) - opcheck && requireNamespace('natcpp', quietly = TRUE) + if(!opcheck || !requireNamespace('natcpp', quietly = TRUE)) + return(FALSE) + if(!is.null(version) && utils::packageVersion('natcpp') < version) + return(FALSE) + TRUE } diff --git a/man/pointsinside.Rd b/man/pointsinside.Rd index 49f9d48e..8d1304b0 100644 --- a/man/pointsinside.Rd +++ b/man/pointsinside.Rd @@ -40,9 +40,18 @@ Note that \code{hxsurf} surface objects will be converted to testing repeatedly against the same surface, it may make sense to pre-convert. - \code{pointsinside} depends on the face normals for each face pointing out - of the object (see example). The face normals are defined by the order of - the three vertices making up a triangular face. You can flip the face + When the \href{https://natverse.org/natcpp/}{natcpp} package (>= 0.3.2) is + installed, logical results (\code{rval="logical"} or + \code{"consistent_logical"}) use its generalised (solid-angle) + winding-number test. This does not depend on face normals and is therefore + robust to inconsistently oriented faces, so the face-normal caveats below + apply only to the \code{Rvcg} fallback (used when natcpp is unavailable and + for \code{rval="distance"} or \code{"mesh3d"}). Set + \code{options(nat.use_natcpp=FALSE)} to force the \code{Rvcg} path. + + The \code{Rvcg} fallback depends on the face normals for each face pointing + out of the object (see example). The face normals are defined by the order + of the three vertices making up a triangular face. You can flip the face normal for a face by permuting the vertices (i.e. 1,2,3 -> 1,3,2). If you find for a given surface that points are outside when you expect them to be inside then the face normals are probably all the wrong way round. You can From af9d2d1fb6cd7b3a356f18e459742b1aca0b6387 Mon Sep 17 00:00:00 2001 From: Gregory Jefferis Date: Tue, 22 Sep 2026 17:11:26 +0100 Subject: [PATCH 2/2] CI: use CRAN binaries on macOS/Windows, P3M only on Linux P3M's macOS arm64 Rvcg 0.25 binary currently fails to load with a missing libomp symbol (___kmpc_dispatch_init_4u), breaking vignette rebuilding on the macOS check leg. Restrict use-public-rspm to Linux (where the binary speed-up matters) so macOS and Windows fall back to CRAN's cleanly-linked binaries. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/R-CMD-check.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 214a7600..23cc65b1 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -40,7 +40,11 @@ jobs: with: r-version: ${{ matrix.config.r }} http-user-agent: ${{ matrix.config.http-user-agent }} - use-public-rspm: true + # Public RSPM (Posit Package Manager) binaries only on Linux, where + # they are the big win. macOS/Windows fall back to CRAN binaries: + # P3M's macOS arm64 Rvcg build currently fails to load with a missing + # libomp symbol (___kmpc_dispatch_init_4u), and CRAN's build is clean. + use-public-rspm: ${{ startsWith(matrix.config.os, 'ubuntu') }} - uses: r-lib/actions/setup-r-dependencies@v2 with: